Path: blob/master/drivers/firmware/arm_scmi/scmi_power_control.c
26428 views
// SPDX-License-Identifier: GPL-2.01/*2* SCMI Generic SystemPower Control driver.3*4* Copyright (C) 2020-2022 ARM Ltd.5*/6/*7* In order to handle platform originated SCMI SystemPower requests (like8* shutdowns or cold/warm resets) we register an SCMI Notification notifier9* block to react when such SCMI SystemPower events are emitted by platform.10*11* Once such a notification is received we act accordingly to perform the12* required system transition depending on the kind of request.13*14* Graceful requests are routed to userspace through the same API methods15* (orderly_poweroff/reboot()) used by ACPI when handling ACPI Shutdown bus16* events.17*18* Direct forceful requests are not supported since are not meant to be sent19* by the SCMI platform to an OSPM like Linux.20*21* Additionally, graceful request notifications can carry an optional timeout22* field stating the maximum amount of time allowed by the platform for23* completion after which they are converted to forceful ones: the assumption24* here is that even graceful requests can be upper-bound by a maximum final25* timeout strictly enforced by the platform itself which can ultimately cut26* the power off at will anytime; in order to avoid such extreme scenario, we27* track progress of graceful requests through the means of a reboot notifier28* converting timed-out graceful requests to forceful ones, so at least we29* try to perform a clean sync and shutdown/restart before the power is cut.30*31* Given the peculiar nature of SCMI SystemPower protocol, that is being in32* charge of triggering system wide shutdown/reboot events, there should be33* only one SCMI platform actively emitting SystemPower events.34* For this reason the SCMI core takes care to enforce the creation of one35* single unique device associated to the SCMI System Power protocol; no matter36* how many SCMI platforms are defined on the system, only one can be designated37* to support System Power: as a consequence this driver will never be probed38* more than once.39*40* For similar reasons as soon as the first valid SystemPower is received by41* this driver and the shutdown/reboot is started, any further notification42* possibly emitted by the platform will be ignored.43*/4445#include <linux/math.h>46#include <linux/module.h>47#include <linux/mutex.h>48#include <linux/pm.h>49#include <linux/printk.h>50#include <linux/reboot.h>51#include <linux/scmi_protocol.h>52#include <linux/slab.h>53#include <linux/suspend.h>54#include <linux/time64.h>55#include <linux/timer.h>56#include <linux/types.h>57#include <linux/workqueue.h>5859#ifndef MODULE60#include <linux/fs.h>61#endif6263enum scmi_syspower_state {64SCMI_SYSPOWER_IDLE,65SCMI_SYSPOWER_IN_PROGRESS,66SCMI_SYSPOWER_REBOOTING67};6869/**70* struct scmi_syspower_conf - Common configuration71*72* @dev: A reference device73* @state: Current SystemPower state74* @state_mtx: @state related mutex75* @required_transition: The requested transition as decribed in the received76* SCMI SystemPower notification77* @userspace_nb: The notifier_block registered against the SCMI SystemPower78* notification to start the needed userspace interactions.79* @reboot_nb: A notifier_block optionally used to track reboot progress80* @forceful_work: A worker used to trigger a forceful transition once a81* graceful has timed out.82* @suspend_work: A worker used to trigger system suspend83*/84struct scmi_syspower_conf {85struct device *dev;86enum scmi_syspower_state state;87/* Protect access to state */88struct mutex state_mtx;89enum scmi_system_events required_transition;9091struct notifier_block userspace_nb;92struct notifier_block reboot_nb;9394struct delayed_work forceful_work;95struct work_struct suspend_work;96};9798#define userspace_nb_to_sconf(x) \99container_of(x, struct scmi_syspower_conf, userspace_nb)100101#define reboot_nb_to_sconf(x) \102container_of(x, struct scmi_syspower_conf, reboot_nb)103104#define dwork_to_sconf(x) \105container_of(x, struct scmi_syspower_conf, forceful_work)106107/**108* scmi_reboot_notifier - A reboot notifier to catch an ongoing successful109* system transition110* @nb: Reference to the related notifier block111* @reason: The reason for the ongoing reboot112* @__unused: The cmd being executed on a restart request (unused)113*114* When an ongoing system transition is detected, compatible with the one115* requested by SCMI, cancel the delayed work.116*117* Return: NOTIFY_OK in any case118*/119static int scmi_reboot_notifier(struct notifier_block *nb,120unsigned long reason, void *__unused)121{122struct scmi_syspower_conf *sc = reboot_nb_to_sconf(nb);123124mutex_lock(&sc->state_mtx);125switch (reason) {126case SYS_HALT:127case SYS_POWER_OFF:128if (sc->required_transition == SCMI_SYSTEM_SHUTDOWN)129sc->state = SCMI_SYSPOWER_REBOOTING;130break;131case SYS_RESTART:132if (sc->required_transition == SCMI_SYSTEM_COLDRESET ||133sc->required_transition == SCMI_SYSTEM_WARMRESET)134sc->state = SCMI_SYSPOWER_REBOOTING;135break;136default:137break;138}139140if (sc->state == SCMI_SYSPOWER_REBOOTING) {141dev_dbg(sc->dev, "Reboot in progress...cancel delayed work.\n");142cancel_delayed_work_sync(&sc->forceful_work);143}144mutex_unlock(&sc->state_mtx);145146return NOTIFY_OK;147}148149/**150* scmi_request_forceful_transition - Request forceful SystemPower transition151* @sc: A reference to the configuration data152*153* Initiates the required SystemPower transition without involving userspace:154* just trigger the action at the kernel level after issuing an emergency155* sync. (if possible at all)156*/157static inline void158scmi_request_forceful_transition(struct scmi_syspower_conf *sc)159{160dev_dbg(sc->dev, "Serving forceful request:%d\n",161sc->required_transition);162163#ifndef MODULE164emergency_sync();165#endif166switch (sc->required_transition) {167case SCMI_SYSTEM_SHUTDOWN:168kernel_power_off();169break;170case SCMI_SYSTEM_COLDRESET:171case SCMI_SYSTEM_WARMRESET:172kernel_restart(NULL);173break;174default:175break;176}177}178179static void scmi_forceful_work_func(struct work_struct *work)180{181struct scmi_syspower_conf *sc;182struct delayed_work *dwork;183184if (system_state > SYSTEM_RUNNING)185return;186187dwork = to_delayed_work(work);188sc = dwork_to_sconf(dwork);189190dev_dbg(sc->dev, "Graceful request timed out...forcing !\n");191mutex_lock(&sc->state_mtx);192/* avoid deadlock by unregistering reboot notifier first */193unregister_reboot_notifier(&sc->reboot_nb);194if (sc->state == SCMI_SYSPOWER_IN_PROGRESS)195scmi_request_forceful_transition(sc);196mutex_unlock(&sc->state_mtx);197}198199/**200* scmi_request_graceful_transition - Request graceful SystemPower transition201* @sc: A reference to the configuration data202* @timeout_ms: The desired timeout to wait for the shutdown to complete before203* system is forcibly shutdown.204*205* Initiates the required SystemPower transition, requesting userspace206* co-operation: it uses the same orderly_ methods used by ACPI Shutdown event207* processing.208*209* Takes care also to register a reboot notifier and to schedule a delayed work210* in order to detect if userspace actions are taking too long and in such a211* case to trigger a forceful transition.212*/213static void scmi_request_graceful_transition(struct scmi_syspower_conf *sc,214unsigned int timeout_ms)215{216unsigned int adj_timeout_ms = 0;217218if (timeout_ms) {219int ret;220221sc->reboot_nb.notifier_call = &scmi_reboot_notifier;222ret = register_reboot_notifier(&sc->reboot_nb);223if (!ret) {224/* Wait only up to 75% of the advertised timeout */225adj_timeout_ms = mult_frac(timeout_ms, 3, 4);226INIT_DELAYED_WORK(&sc->forceful_work,227scmi_forceful_work_func);228schedule_delayed_work(&sc->forceful_work,229msecs_to_jiffies(adj_timeout_ms));230} else {231/* Carry on best effort even without a reboot notifier */232dev_warn(sc->dev,233"Cannot register reboot notifier !\n");234}235}236237dev_dbg(sc->dev,238"Serving graceful req:%d (timeout_ms:%u adj_timeout_ms:%u)\n",239sc->required_transition, timeout_ms, adj_timeout_ms);240241switch (sc->required_transition) {242case SCMI_SYSTEM_SHUTDOWN:243/*244* When triggered early at boot-time the 'orderly' call will245* partially fail due to the lack of userspace itself, but246* the force=true argument will start anyway a successful247* forced shutdown.248*/249orderly_poweroff(true);250break;251case SCMI_SYSTEM_COLDRESET:252case SCMI_SYSTEM_WARMRESET:253orderly_reboot();254break;255case SCMI_SYSTEM_SUSPEND:256schedule_work(&sc->suspend_work);257break;258default:259break;260}261}262263/**264* scmi_userspace_notifier - Notifier callback to act on SystemPower265* Notifications266* @nb: Reference to the related notifier block267* @event: The SystemPower notification event id268* @data: The SystemPower event report269*270* This callback is in charge of decoding the received SystemPower report271* and act accordingly triggering a graceful or forceful system transition.272*273* Note that once a valid SCMI SystemPower event starts being served, any274* other following SystemPower notification received from the same SCMI275* instance (handle) will be ignored.276*277* Return: NOTIFY_OK once a valid SystemPower event has been successfully278* processed.279*/280static int scmi_userspace_notifier(struct notifier_block *nb,281unsigned long event, void *data)282{283struct scmi_system_power_state_notifier_report *er = data;284struct scmi_syspower_conf *sc = userspace_nb_to_sconf(nb);285286if (er->system_state >= SCMI_SYSTEM_MAX ||287er->system_state == SCMI_SYSTEM_POWERUP) {288dev_err(sc->dev, "Ignoring unsupported system_state: 0x%X\n",289er->system_state);290return NOTIFY_DONE;291}292293if (!SCMI_SYSPOWER_IS_REQUEST_GRACEFUL(er->flags)) {294dev_err(sc->dev, "Ignoring forceful notification.\n");295return NOTIFY_DONE;296}297298/*299* Bail out if system is already shutting down or an SCMI SystemPower300* requested is already being served.301*/302if (system_state > SYSTEM_RUNNING)303return NOTIFY_DONE;304mutex_lock(&sc->state_mtx);305if (sc->state != SCMI_SYSPOWER_IDLE) {306dev_dbg(sc->dev,307"Transition already in progress...ignore.\n");308mutex_unlock(&sc->state_mtx);309return NOTIFY_DONE;310}311sc->state = SCMI_SYSPOWER_IN_PROGRESS;312mutex_unlock(&sc->state_mtx);313314sc->required_transition = er->system_state;315316/* Leaving a trace in logs of who triggered the shutdown/reboot. */317dev_info(sc->dev, "Serving shutdown/reboot request: %d\n",318sc->required_transition);319320scmi_request_graceful_transition(sc, er->timeout);321322return NOTIFY_OK;323}324325static void scmi_suspend_work_func(struct work_struct *work)326{327pm_suspend(PM_SUSPEND_MEM);328}329330static int scmi_syspower_probe(struct scmi_device *sdev)331{332int ret;333struct scmi_syspower_conf *sc;334struct scmi_handle *handle = sdev->handle;335336if (!handle)337return -ENODEV;338339ret = handle->devm_protocol_acquire(sdev, SCMI_PROTOCOL_SYSTEM);340if (ret)341return ret;342343sc = devm_kzalloc(&sdev->dev, sizeof(*sc), GFP_KERNEL);344if (!sc)345return -ENOMEM;346347sc->state = SCMI_SYSPOWER_IDLE;348mutex_init(&sc->state_mtx);349sc->required_transition = SCMI_SYSTEM_MAX;350sc->userspace_nb.notifier_call = &scmi_userspace_notifier;351sc->dev = &sdev->dev;352dev_set_drvdata(&sdev->dev, sc);353354INIT_WORK(&sc->suspend_work, scmi_suspend_work_func);355356return handle->notify_ops->devm_event_notifier_register(sdev,357SCMI_PROTOCOL_SYSTEM,358SCMI_EVENT_SYSTEM_POWER_STATE_NOTIFIER,359NULL, &sc->userspace_nb);360}361362static int scmi_system_power_resume(struct device *dev)363{364struct scmi_syspower_conf *sc = dev_get_drvdata(dev);365366sc->state = SCMI_SYSPOWER_IDLE;367return 0;368}369370static const struct dev_pm_ops scmi_system_power_pmops = {371SYSTEM_SLEEP_PM_OPS(NULL, scmi_system_power_resume)372};373374static const struct scmi_device_id scmi_id_table[] = {375{ SCMI_PROTOCOL_SYSTEM, "syspower" },376{ },377};378MODULE_DEVICE_TABLE(scmi, scmi_id_table);379380static struct scmi_driver scmi_system_power_driver = {381.driver = {382.pm = pm_sleep_ptr(&scmi_system_power_pmops),383},384.name = "scmi-system-power",385.probe = scmi_syspower_probe,386.id_table = scmi_id_table,387};388module_scmi_driver(scmi_system_power_driver);389390MODULE_AUTHOR("Cristian Marussi <[email protected]>");391MODULE_DESCRIPTION("ARM SCMI SystemPower Control driver");392MODULE_LICENSE("GPL");393394395