SoC platforms often have multiple options for performing system's power off. Meanwhile kernel is limited to a single option. Add power off handler call chain API, which is inspired by the restart API. The old pm_power_off method will be kept around till all drivers are converted to the new API. Signed-off-by: Dmitry Osipenko <digetx@xxxxxxxxx> --- include/linux/reboot.h | 5 ++ kernel/reboot.c | 111 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 115 insertions(+), 1 deletion(-) diff --git a/include/linux/reboot.h b/include/linux/reboot.h index af907a3d68d1..f72fb8729608 100644 --- a/include/linux/reboot.h +++ b/include/linux/reboot.h @@ -49,6 +49,11 @@ extern int register_restart_handler(struct notifier_block *); extern int unregister_restart_handler(struct notifier_block *); extern void do_kernel_restart(char *cmd); +extern int register_poweroff_handler(struct notifier_block *); +extern int unregister_poweroff_handler(struct notifier_block *); + +extern int devm_register_poweroff_handler(struct device *, struct notifier_block *); + /* * Architecture-specific implementations of sys_reboot commands. */ diff --git a/kernel/reboot.c b/kernel/reboot.c index f7440c0c7e43..6e90afec2bb8 100644 --- a/kernel/reboot.c +++ b/kernel/reboot.c @@ -280,6 +280,113 @@ void kernel_halt(void) } EXPORT_SYMBOL_GPL(kernel_halt); +/* + * Notifier list for kernel code which wants to be called + * to power off the system. + */ +static BLOCKING_NOTIFIER_HEAD(poweroff_handler_list); + +/** + * register_poweroff_handler - Register function to be called to power off + * the system + * @nb: Info about handler function to be called + * @nb->priority: Handler priority. Handlers should follow the + * following guidelines for setting priorities. + * 0: Poweroff handler of last resort, + * with limited poweroff capabilities + * 128: Default poweroff handler; use if no other + * poweroff handler is expected to be available, + * and/or if poweroff functionality is + * sufficient to poweroff the entire system + * 255: Highest priority poweroff handler, will + * preempt all other poweroff handlers + * + * Registers a function with code to be called to power off the + * system. + * + * Registered functions will be called as last step of the poweroff + * sequence. + * + * Registered functions are expected to power off the system immediately. + * If more than one function is registered, the poweroff handler priority + * selects which function will be called first. + * + * Poweroff handlers are expected to be registered from non-architecture + * code, typically from drivers. A typical use case would be a system + * where poweroff functionality is provided through a PMIC. Multiple + * poweroff handlers may exist; for example, one poweroff handler might + * turn off the entire system, while another only turns off part of + * system. In such cases, the poweroff handler which only disables part + * of the hardware is expected to register with low priority to ensure + * that it only runs if no other means to power off the system is + * available. + * + * Currently always returns zero, as blocking_notifier_chain_register() + * always returns zero. + */ +int register_poweroff_handler(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&poweroff_handler_list, nb); +} +EXPORT_SYMBOL(register_poweroff_handler); + +/** + * unregister_poweroff_handler - Unregister previously registered + * poweroff handler + * @nb: Hook to be unregistered + * + * Unregisters a previously registered poweroff handler function. + * + * Returns zero on success, or %-ENOENT on failure. + */ +int unregister_poweroff_handler(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&poweroff_handler_list, nb); +} +EXPORT_SYMBOL(unregister_poweroff_handler); + +static void devm_unregister_poweroff_handler(struct device *dev, void *res) +{ + WARN_ON(unregister_poweroff_handler(*(struct notifier_block **)res)); +} + +int devm_register_poweroff_handler(struct device *dev, struct notifier_block *nb) +{ + struct notifier_block **rcnb; + int ret; + + rcnb = devres_alloc(devm_unregister_poweroff_handler, + sizeof(*rcnb), GFP_KERNEL); + if (!rcnb) + return -ENOMEM; + + ret = register_poweroff_handler(nb); + if (!ret) { + *rcnb = nb; + devres_add(dev, rcnb); + } else { + devres_free(rcnb); + } + + return ret; +} +EXPORT_SYMBOL(devm_register_poweroff_handler); + +/** + * do_kernel_power_off - Execute kernel poweroff handler call chain + * + * Calls functions registered with register_poweroff_handler. + * + * Expected to be called as last step of the poweroff sequence. + * + * Powers off the system immediately if a poweroff handler function has + * been registered. Otherwise does nothing. + */ +static void do_kernel_power_off(void) +{ + blocking_notifier_call_chain(&poweroff_handler_list, 0, NULL); +} + /** * kernel_power_off - power_off the system * @@ -295,6 +402,7 @@ void kernel_power_off(void) pr_emerg("Power down\n"); kmsg_dump(KMSG_DUMP_SHUTDOWN); machine_power_off(); + do_kernel_power_off(); } EXPORT_SYMBOL_GPL(kernel_power_off); @@ -339,7 +447,8 @@ SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd, /* Instead of trying to make the power_off code look like * halt when pm_power_off is not set do it the easy way. */ - if ((cmd == LINUX_REBOOT_CMD_POWER_OFF) && !pm_power_off) + if ((cmd == LINUX_REBOOT_CMD_POWER_OFF) && !pm_power_off && + blocking_notifier_call_chain_empty(&poweroff_handler_list)) cmd = LINUX_REBOOT_CMD_HALT; mutex_lock(&system_transition_mutex); -- 2.32.0