Quelle scmi_power_control.c
Sprache: C
// SPDX-License-Identifier: GPL-2.0
/*
* SCMI Generic SystemPower Control driver .
*
* Copyright ( C ) 2020 - 2022 ARM Ltd .
*/
/*
* In order to handle platform originated SCMI SystemPower requests ( like
* shutdowns or cold / warm resets ) we register an SCMI Notification notifier
* block to react when such SCMI SystemPower events are emitted by platform .
*
* Once such a notification is received we act accordingly to perform the
* required system transition depending on the kind of request .
*
* Graceful requests are routed to userspace through the same API methods
* ( orderly_poweroff / reboot ( ) ) used by ACPI when handling ACPI Shutdown bus
* events .
*
* Direct forceful requests are not supported since are not meant to be sent
* by the SCMI platform to an OSPM like Linux .
*
* Additionally , graceful request notifications can carry an optional timeout
* field stating the maximum amount of time allowed by the platform for
* completion after which they are converted to forceful ones : the assumption
* here is that even graceful requests can be upper - bound by a maximum final
* timeout strictly enforced by the platform itself which can ultimately cut
* the power off at will anytime ; in order to avoid such extreme scenario , we
* track progress of graceful requests through the means of a reboot notifier
* converting timed - out graceful requests to forceful ones , so at least we
* try to perform a clean sync and shutdown / restart before the power is cut .
*
* Given the peculiar nature of SCMI SystemPower protocol , that is being in
* charge of triggering system wide shutdown / reboot events , there should be
* only one SCMI platform actively emitting SystemPower events .
* For this reason the SCMI core takes care to enforce the creation of one
* single unique device associated to the SCMI System Power protocol ; no matter
* how many SCMI platforms are defined on the system , only one can be designated
* to support System Power : as a consequence this driver will never be probed
* more than once .
*
* For similar reasons as soon as the first valid SystemPower is received by
* this driver and the shutdown / reboot is started , any further notification
* possibly emitted by the platform will be ignored .
*/
#include <linux/math.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pm.h>
#include <linux/printk.h>
#include <linux/reboot.h>
#include <linux/scmi_protocol.h>
#include <linux/slab.h>
#include <linux/suspend.h>
#include <linux/time64.h>
#include <linux/timer.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#ifndef MODULE
#include <linux/fs.h>
#endif
enum scmi_syspower_state {
SCMI_SYSPOWER_IDLE,
SCMI_SYSPOWER_IN_PROGRESS,
SCMI_SYSPOWER_REBOOTING
};
/**
* struct scmi_syspower_conf - Common configuration
*
* @ dev : A reference device
* @ state : Current SystemPower state
* @ state_mtx : @ state related mutex
* @ required_transition : The requested transition as decribed in the received
* SCMI SystemPower notification
* @ userspace_nb : The notifier_block registered against the SCMI SystemPower
* notification to start the needed userspace interactions .
* @ reboot_nb : A notifier_block optionally used to track reboot progress
* @ forceful_work : A worker used to trigger a forceful transition once a
* graceful has timed out .
* @ suspend_work : A worker used to trigger system suspend
*/
struct scmi_syspower_conf {
struct device *dev;
enum scmi_syspower_state state;
/* Protect access to state */
struct mutex state_mtx;
enum scmi_system_events required_transition;
struct notifier_block userspace_nb;
struct notifier_block reboot_nb;
struct delayed_work forceful_work;
struct work_struct suspend_work;
};
#define userspace_nb_to_sconf(x) \
container_of(x, struct scmi_syspower_conf, userspace_nb)
#define reboot_nb_to_sconf(x) \
container_of(x, struct scmi_syspower_conf, reboot_nb)
#define dwork_to_sconf(x) \
container_of(x, struct scmi_syspower_conf, forceful_work)
/**
* scmi_reboot_notifier - A reboot notifier to catch an ongoing successful
* system transition
* @ nb : Reference to the related notifier block
* @ reason : The reason for the ongoing reboot
* @ _ _ unused : The cmd being executed on a restart request ( unused )
*
* When an ongoing system transition is detected , compatible with the one
* requested by SCMI , cancel the delayed work .
*
* Return : NOTIFY_OK in any case
*/
static int scmi_reboot_notifier(struct notifier_block *nb,
unsigned long reason, void *__unused)
{
struct scmi_syspower_conf *sc = reboot_nb_to_sconf(nb);
mutex_lock(&sc->state_mtx);
switch (reason) {
case SYS_HALT:
case SYS_POWER_OFF:
if (sc->required_transition == SCMI_SYSTEM_SHUTDOWN)
sc->state = SCMI_SYSPOWER_REBOOTING;
break ;
case SYS_RESTART:
if (sc->required_transition == SCMI_SYSTEM_COLDRESET ||
sc->required_transition == SCMI_SYSTEM_WARMRESET)
sc->state = SCMI_SYSPOWER_REBOOTING;
break ;
default :
break ;
}
if (sc->state == SCMI_SYSPOWER_REBOOTING) {
dev_dbg(sc->dev, "Reboot in progress...cancel delayed work.\n" );
cancel_delayed_work_sync(&sc->forceful_work);
}
mutex_unlock(&sc->state_mtx);
return NOTIFY_OK;
}
/**
* scmi_request_forceful_transition - Request forceful SystemPower transition
* @ sc : A reference to the configuration data
*
* Initiates the required SystemPower transition without involving userspace :
* just trigger the action at the kernel level after issuing an emergency
* sync . ( if possible at all )
*/
static inline void
scmi_request_forceful_transition(struct scmi_syspower_conf *sc)
{
dev_dbg(sc->dev, "Serving forceful request:%d\n" ,
sc->required_transition);
#ifndef MODULE
emergency_sync();
#endif
switch (sc->required_transition) {
case SCMI_SYSTEM_SHUTDOWN:
kernel_power_off();
break ;
case SCMI_SYSTEM_COLDRESET:
case SCMI_SYSTEM_WARMRESET:
kernel_restart(NULL);
break ;
default :
break ;
}
}
static void scmi_forceful_work_func(struct work_struct *work)
{
struct scmi_syspower_conf *sc;
struct delayed_work *dwork;
if (system_state > SYSTEM_RUNNING)
return ;
dwork = to_delayed_work(work);
sc = dwork_to_sconf(dwork);
dev_dbg(sc->dev, "Graceful request timed out...forcing !\n" );
mutex_lock(&sc->state_mtx);
/* avoid deadlock by unregistering reboot notifier first */
unregister_reboot_notifier(&sc->reboot_nb);
if (sc->state == SCMI_SYSPOWER_IN_PROGRESS)
scmi_request_forceful_transition(sc);
mutex_unlock(&sc->state_mtx);
}
/**
* scmi_request_graceful_transition - Request graceful SystemPower transition
* @ sc : A reference to the configuration data
* @ timeout_ms : The desired timeout to wait for the shutdown to complete before
* system is forcibly shutdown .
*
* Initiates the required SystemPower transition , requesting userspace
* co - operation : it uses the same orderly_ methods used by ACPI Shutdown event
* processing .
*
* Takes care also to register a reboot notifier and to schedule a delayed work
* in order to detect if userspace actions are taking too long and in such a
* case to trigger a forceful transition .
*/
static void scmi_request_graceful_transition(struct scmi_syspower_conf *sc,
unsigned int timeout_ms)
{
unsigned int adj_timeout_ms = 0 ;
if (timeout_ms) {
int ret;
sc->reboot_nb.notifier_call = &scmi_reboot_notifier;
ret = register_reboot_notifier(&sc->reboot_nb);
if (!ret) {
/* Wait only up to 75% of the advertised timeout */
adj_timeout_ms = mult_frac(timeout_ms, 3 , 4 );
INIT_DELAYED_WORK(&sc->forceful_work,
scmi_forceful_work_func);
schedule_delayed_work(&sc->forceful_work,
msecs_to_jiffies(adj_timeout_ms));
} else {
/* Carry on best effort even without a reboot notifier */
dev_warn(sc->dev,
"Cannot register reboot notifier !\n" );
}
}
dev_dbg(sc->dev,
"Serving graceful req:%d (timeout_ms:%u adj_timeout_ms:%u)\n" ,
sc->required_transition, timeout_ms, adj_timeout_ms);
switch (sc->required_transition) {
case SCMI_SYSTEM_SHUTDOWN:
/*
* When triggered early at boot - time the ' orderly ' call will
* partially fail due to the lack of userspace itself , but
* the force = true argument will start anyway a successful
* forced shutdown .
*/
orderly_poweroff(true );
break ;
case SCMI_SYSTEM_COLDRESET:
case SCMI_SYSTEM_WARMRESET:
orderly_reboot();
break ;
case SCMI_SYSTEM_SUSPEND:
schedule_work(&sc->suspend_work);
break ;
default :
break ;
}
}
/**
* scmi_userspace_notifier - Notifier callback to act on SystemPower
* Notifications
* @ nb : Reference to the related notifier block
* @ event : The SystemPower notification event id
* @ data : The SystemPower event report
*
* This callback is in charge of decoding the received SystemPower report
* and act accordingly triggering a graceful or forceful system transition .
*
* Note that once a valid SCMI SystemPower event starts being served , any
* other following SystemPower notification received from the same SCMI
* instance ( handle ) will be ignored .
*
* Return : NOTIFY_OK once a valid SystemPower event has been successfully
* processed .
*/
static int scmi_userspace_notifier(struct notifier_block *nb,
unsigned long event, void *data)
{
struct scmi_system_power_state_notifier_report *er = data;
struct scmi_syspower_conf *sc = userspace_nb_to_sconf(nb);
if (er->system_state >= SCMI_SYSTEM_MAX ||
er->system_state == SCMI_SYSTEM_POWERUP) {
dev_err(sc->dev, "Ignoring unsupported system_state: 0x%X\n" ,
er->system_state);
return NOTIFY_DONE;
}
if (!SCMI_SYSPOWER_IS_REQUEST_GRACEFUL(er->flags)) {
dev_err(sc->dev, "Ignoring forceful notification.\n" );
return NOTIFY_DONE;
}
/*
* Bail out if system is already shutting down or an SCMI SystemPower
* requested is already being served .
*/
if (system_state > SYSTEM_RUNNING)
return NOTIFY_DONE;
mutex_lock(&sc->state_mtx);
if (sc->state != SCMI_SYSPOWER_IDLE) {
dev_dbg(sc->dev,
"Transition already in progress...ignore.\n" );
mutex_unlock(&sc->state_mtx);
return NOTIFY_DONE;
}
sc->state = SCMI_SYSPOWER_IN_PROGRESS;
mutex_unlock(&sc->state_mtx);
sc->required_transition = er->system_state;
/* Leaving a trace in logs of who triggered the shutdown/reboot. */
dev_info(sc->dev, "Serving shutdown/reboot request: %d\n" ,
sc->required_transition);
scmi_request_graceful_transition(sc, er->timeout);
return NOTIFY_OK;
}
static void scmi_suspend_work_func(struct work_struct *work)
{
pm_suspend(PM_SUSPEND_MEM);
}
static int scmi_syspower_probe(struct scmi_device *sdev)
{
int ret;
struct scmi_syspower_conf *sc;
struct scmi_handle *handle = sdev->handle;
if (!handle)
return -ENODEV;
ret = handle->devm_protocol_acquire(sdev, SCMI_PROTOCOL_SYSTEM);
if (ret)
return ret;
sc = devm_kzalloc(&sdev->dev, sizeof (*sc), GFP_KERNEL);
if (!sc)
return -ENOMEM;
sc->state = SCMI_SYSPOWER_IDLE;
mutex_init(&sc->state_mtx);
sc->required_transition = SCMI_SYSTEM_MAX;
sc->userspace_nb.notifier_call = &scmi_userspace_notifier;
sc->dev = &sdev->dev;
dev_set_drvdata(&sdev->dev, sc);
INIT_WORK(&sc->suspend_work, scmi_suspend_work_func);
return handle->notify_ops->devm_event_notifier_register(sdev,
SCMI_PROTOCOL_SYSTEM,
SCMI_EVENT_SYSTEM_POWER_STATE_NOTIFIER,
NULL, &sc->userspace_nb);
}
static int scmi_system_power_resume(struct device *dev)
{
struct scmi_syspower_conf *sc = dev_get_drvdata(dev);
sc->state = SCMI_SYSPOWER_IDLE;
return 0 ;
}
static const struct dev_pm_ops scmi_system_power_pmops = {
SYSTEM_SLEEP_PM_OPS(NULL, scmi_system_power_resume)
};
static const struct scmi_device_id scmi_id_table[] = {
{ SCMI_PROTOCOL_SYSTEM, "syspower" },
{ },
};
MODULE_DEVICE_TABLE(scmi, scmi_id_table);
static struct scmi_driver scmi_system_power_driver = {
.driver = {
.pm = pm_sleep_ptr(&scmi_system_power_pmops),
},
.name = "scmi-system-power" ,
.probe = scmi_syspower_probe,
.id_table = scmi_id_table,
};
module_scmi_driver(scmi_system_power_driver);
MODULE_AUTHOR("Cristian Marussi <cristian.marussi@arm.com>" );
MODULE_DESCRIPTION("ARM SCMI SystemPower Control driver" );
MODULE_LICENSE("GPL" );
Messung V0.5 in Prozent C=96 H=97 G=96
¤ Dauer der Verarbeitung: 0.17 Sekunden
(vorverarbeitet am 2026-09-28)
¤
*© Formatika GbR, Deutschland
2026-10-10
Neuigkeiten
Aktuelles
Motto des Tages
Open Source Software
Jenseits des Üblichen ....
Besucherstatistik
Statistik
#Sources=1126864
#Domains=1897691