mirror of
https://github.com/torvalds/linux.git
synced 2024-11-10 14:11:52 +00:00
kernel/reboot: Introduce sys-off handler API
In order to support power-off chaining we need to get rid of the global pm_* variables, replacing them with the new kernel API functions that support chaining. Introduce new generic sys-off handler API that brings the following features: 1. Power-off and restart handlers are registered using same API function that supports chaining, hence all power-off and restart modes will support chaining using this unified function. 2. Prevents notifier priority collisions by disallowing registration of multiple handlers at the non-default priority level. 3. Supports passing opaque user argument to callback, which allows us to remove global variables from drivers. This patch adds support of the following sys-off modes: - SYS_OFF_MODE_POWER_OFF_PREPARE that replaces global pm_power_off_prepare variable and provides chaining support for power-off-prepare handlers. - SYS_OFF_MODE_POWER_OFF that replaces global pm_power_off variable and provides chaining support for power-off handlers. - SYS_OFF_MODE_RESTART that provides a better restart API, removing a need from drivers to have a global scratch variable by utilizing the opaque callback argument. Signed-off-by: Dmitry Osipenko <dmitry.osipenko@collabora.com> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
This commit is contained in:
parent
c82f898d87
commit
232edc2f72
@ -7,6 +7,7 @@
|
||||
#include <uapi/linux/reboot.h>
|
||||
|
||||
struct device;
|
||||
struct sys_off_handler;
|
||||
|
||||
#define SYS_DOWN 0x0001 /* Notify of system down */
|
||||
#define SYS_RESTART SYS_DOWN
|
||||
@ -62,6 +63,82 @@ extern void machine_shutdown(void);
|
||||
struct pt_regs;
|
||||
extern void machine_crash_shutdown(struct pt_regs *);
|
||||
|
||||
/*
|
||||
* sys-off handler API.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Standard sys-off priority levels. Users are expected to set priorities
|
||||
* relative to the standard levels.
|
||||
*
|
||||
* SYS_OFF_PRIO_PLATFORM: Use this for platform-level handlers.
|
||||
*
|
||||
* SYS_OFF_PRIO_LOW: Use this for handler of last resort.
|
||||
*
|
||||
* SYS_OFF_PRIO_DEFAULT: Use this for normal handlers.
|
||||
*
|
||||
* SYS_OFF_PRIO_HIGH: Use this for higher priority handlers.
|
||||
*
|
||||
* SYS_OFF_PRIO_FIRMWARE: Use this if handler uses firmware call.
|
||||
*/
|
||||
#define SYS_OFF_PRIO_PLATFORM -256
|
||||
#define SYS_OFF_PRIO_LOW -128
|
||||
#define SYS_OFF_PRIO_DEFAULT 0
|
||||
#define SYS_OFF_PRIO_HIGH 192
|
||||
#define SYS_OFF_PRIO_FIRMWARE 224
|
||||
|
||||
enum sys_off_mode {
|
||||
/**
|
||||
* @SYS_OFF_MODE_POWER_OFF_PREPARE:
|
||||
*
|
||||
* Handlers prepare system to be powered off. Handlers are
|
||||
* allowed to sleep.
|
||||
*/
|
||||
SYS_OFF_MODE_POWER_OFF_PREPARE,
|
||||
|
||||
/**
|
||||
* @SYS_OFF_MODE_POWER_OFF:
|
||||
*
|
||||
* Handlers power-off system. Handlers are disallowed to sleep.
|
||||
*/
|
||||
SYS_OFF_MODE_POWER_OFF,
|
||||
|
||||
/**
|
||||
* @SYS_OFF_MODE_RESTART:
|
||||
*
|
||||
* Handlers restart system. Handlers are disallowed to sleep.
|
||||
*/
|
||||
SYS_OFF_MODE_RESTART,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct sys_off_data - sys-off callback argument
|
||||
*
|
||||
* @mode: Mode ID. Currently used only by the sys-off restart mode,
|
||||
* see enum reboot_mode for the available modes.
|
||||
* @cb_data: User's callback data.
|
||||
* @cmd: Command string. Currently used only by the sys-off restart mode,
|
||||
* NULL otherwise.
|
||||
*/
|
||||
struct sys_off_data {
|
||||
int mode;
|
||||
void *cb_data;
|
||||
const char *cmd;
|
||||
};
|
||||
|
||||
struct sys_off_handler *
|
||||
register_sys_off_handler(enum sys_off_mode mode,
|
||||
int priority,
|
||||
int (*callback)(struct sys_off_data *data),
|
||||
void *cb_data);
|
||||
void unregister_sys_off_handler(struct sys_off_handler *handler);
|
||||
|
||||
int devm_register_sys_off_handler(struct device *dev,
|
||||
enum sys_off_mode mode,
|
||||
int priority,
|
||||
int (*callback)(struct sys_off_data *data),
|
||||
void *cb_data);
|
||||
|
||||
/*
|
||||
* Architecture independent implemenations of sys_reboot commands.
|
||||
*/
|
||||
|
182
kernel/reboot.c
182
kernel/reboot.c
@ -48,6 +48,15 @@ int reboot_cpu;
|
||||
enum reboot_type reboot_type = BOOT_ACPI;
|
||||
int reboot_force;
|
||||
|
||||
struct sys_off_handler {
|
||||
struct notifier_block nb;
|
||||
int (*sys_off_cb)(struct sys_off_data *data);
|
||||
void *cb_data;
|
||||
enum sys_off_mode mode;
|
||||
bool blocking;
|
||||
void *list;
|
||||
};
|
||||
|
||||
/*
|
||||
* If set, this is used for preparing the system to power off.
|
||||
*/
|
||||
@ -281,6 +290,179 @@ void kernel_halt(void)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(kernel_halt);
|
||||
|
||||
/*
|
||||
* Notifier list for kernel code which wants to be called
|
||||
* to prepare system for power off.
|
||||
*/
|
||||
static BLOCKING_NOTIFIER_HEAD(power_off_prep_handler_list);
|
||||
|
||||
/*
|
||||
* Notifier list for kernel code which wants to be called
|
||||
* to power off system.
|
||||
*/
|
||||
static ATOMIC_NOTIFIER_HEAD(power_off_handler_list);
|
||||
|
||||
static int sys_off_notify(struct notifier_block *nb,
|
||||
unsigned long mode, void *cmd)
|
||||
{
|
||||
struct sys_off_handler *handler;
|
||||
struct sys_off_data data = {};
|
||||
|
||||
handler = container_of(nb, struct sys_off_handler, nb);
|
||||
data.cb_data = handler->cb_data;
|
||||
data.mode = mode;
|
||||
data.cmd = cmd;
|
||||
|
||||
return handler->sys_off_cb(&data);
|
||||
}
|
||||
|
||||
/**
|
||||
* register_sys_off_handler - Register sys-off handler
|
||||
* @mode: Sys-off mode
|
||||
* @priority: Handler priority
|
||||
* @callback: Callback function
|
||||
* @cb_data: Callback argument
|
||||
*
|
||||
* Registers system power-off or restart handler that will be invoked
|
||||
* at the step corresponding to the given sys-off mode. Handler's callback
|
||||
* should return NOTIFY_DONE to permit execution of the next handler in
|
||||
* the call chain or NOTIFY_STOP to break the chain (in error case for
|
||||
* example).
|
||||
*
|
||||
* Multiple handlers can be registered at the default priority level.
|
||||
*
|
||||
* Only one handler can be registered at the non-default priority level,
|
||||
* otherwise ERR_PTR(-EBUSY) is returned.
|
||||
*
|
||||
* Returns a new instance of struct sys_off_handler on success, or
|
||||
* an ERR_PTR()-encoded error code otherwise.
|
||||
*/
|
||||
struct sys_off_handler *
|
||||
register_sys_off_handler(enum sys_off_mode mode,
|
||||
int priority,
|
||||
int (*callback)(struct sys_off_data *data),
|
||||
void *cb_data)
|
||||
{
|
||||
struct sys_off_handler *handler;
|
||||
int err;
|
||||
|
||||
handler = kzalloc(sizeof(*handler), GFP_KERNEL);
|
||||
if (!handler)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
switch (mode) {
|
||||
case SYS_OFF_MODE_POWER_OFF_PREPARE:
|
||||
handler->list = &power_off_prep_handler_list;
|
||||
handler->blocking = true;
|
||||
break;
|
||||
|
||||
case SYS_OFF_MODE_POWER_OFF:
|
||||
handler->list = &power_off_handler_list;
|
||||
break;
|
||||
|
||||
case SYS_OFF_MODE_RESTART:
|
||||
handler->list = &restart_handler_list;
|
||||
break;
|
||||
|
||||
default:
|
||||
kfree(handler);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
handler->nb.notifier_call = sys_off_notify;
|
||||
handler->nb.priority = priority;
|
||||
handler->sys_off_cb = callback;
|
||||
handler->cb_data = cb_data;
|
||||
handler->mode = mode;
|
||||
|
||||
if (handler->blocking) {
|
||||
if (priority == SYS_OFF_PRIO_DEFAULT)
|
||||
err = blocking_notifier_chain_register(handler->list,
|
||||
&handler->nb);
|
||||
else
|
||||
err = blocking_notifier_chain_register_unique_prio(handler->list,
|
||||
&handler->nb);
|
||||
} else {
|
||||
if (priority == SYS_OFF_PRIO_DEFAULT)
|
||||
err = atomic_notifier_chain_register(handler->list,
|
||||
&handler->nb);
|
||||
else
|
||||
err = atomic_notifier_chain_register_unique_prio(handler->list,
|
||||
&handler->nb);
|
||||
}
|
||||
|
||||
if (err) {
|
||||
kfree(handler);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
return handler;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(register_sys_off_handler);
|
||||
|
||||
/**
|
||||
* unregister_sys_off_handler - Unregister sys-off handler
|
||||
* @handler: Sys-off handler
|
||||
*
|
||||
* Unregisters given sys-off handler.
|
||||
*/
|
||||
void unregister_sys_off_handler(struct sys_off_handler *handler)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (!handler)
|
||||
return;
|
||||
|
||||
if (handler->blocking)
|
||||
err = blocking_notifier_chain_unregister(handler->list,
|
||||
&handler->nb);
|
||||
else
|
||||
err = atomic_notifier_chain_unregister(handler->list,
|
||||
&handler->nb);
|
||||
|
||||
/* sanity check, shall never happen */
|
||||
WARN_ON(err);
|
||||
|
||||
kfree(handler);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(unregister_sys_off_handler);
|
||||
|
||||
static void devm_unregister_sys_off_handler(void *data)
|
||||
{
|
||||
struct sys_off_handler *handler = data;
|
||||
|
||||
unregister_sys_off_handler(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* devm_register_sys_off_handler - Register sys-off handler
|
||||
* @dev: Device that registers handler
|
||||
* @mode: Sys-off mode
|
||||
* @priority: Handler priority
|
||||
* @callback: Callback function
|
||||
* @cb_data: Callback argument
|
||||
*
|
||||
* Registers resource-managed sys-off handler.
|
||||
*
|
||||
* Returns zero on success, or error code on failure.
|
||||
*/
|
||||
int devm_register_sys_off_handler(struct device *dev,
|
||||
enum sys_off_mode mode,
|
||||
int priority,
|
||||
int (*callback)(struct sys_off_data *data),
|
||||
void *cb_data)
|
||||
{
|
||||
struct sys_off_handler *handler;
|
||||
|
||||
handler = register_sys_off_handler(mode, priority, callback, cb_data);
|
||||
if (IS_ERR(handler))
|
||||
return PTR_ERR(handler);
|
||||
|
||||
return devm_add_action_or_reset(dev, devm_unregister_sys_off_handler,
|
||||
handler);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_register_sys_off_handler);
|
||||
|
||||
/**
|
||||
* kernel_power_off - power_off the system
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user