mirror of
https://github.com/torvalds/linux.git
synced 2024-11-24 13:11:40 +00:00
Merge branch 'pm-sysoff'
Merge system power off handling rework from Dmitry Osipenko for 5.19-rc1. This introduces a mechanism allowing power sequences to be used for powering off the system and makes related changes in platform-specific code for multiple platforms. * pm-sysoff: (29 commits) kernel/reboot: Change registration order of legacy power-off handler m68k: virt: Switch to new sys-off handler API kernel/reboot: Add devm_register_restart_handler() kernel/reboot: Add devm_register_power_off_handler() soc/tegra: pmc: Use sys-off handler API to power off Nexus 7 properly reboot: Remove pm_power_off_prepare() regulator: pfuze100: Use devm_register_sys_off_handler() ACPI: power: Switch to sys-off handler API memory: emif: Use kernel_can_power_off() mips: Use do_kernel_power_off() ia64: Use do_kernel_power_off() x86: Use do_kernel_power_off() sh: Use do_kernel_power_off() m68k: Switch to new sys-off handler API powerpc: Use do_kernel_power_off() xen/x86: Use do_kernel_power_off() parisc: Use do_kernel_power_off() arm64: Use do_kernel_power_off() riscv: Use do_kernel_power_off() csky: Use do_kernel_power_off() ...
This commit is contained in:
commit
9f9c1f6844
@ -116,9 +116,7 @@ void machine_power_off(void)
|
||||
{
|
||||
local_irq_disable();
|
||||
smp_send_stop();
|
||||
|
||||
if (pm_power_off)
|
||||
pm_power_off();
|
||||
do_kernel_power_off();
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -111,8 +111,7 @@ void machine_power_off(void)
|
||||
{
|
||||
local_irq_disable();
|
||||
smp_send_stop();
|
||||
if (pm_power_off)
|
||||
pm_power_off();
|
||||
do_kernel_power_off();
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -9,16 +9,14 @@ EXPORT_SYMBOL(pm_power_off);
|
||||
void machine_power_off(void)
|
||||
{
|
||||
local_irq_disable();
|
||||
if (pm_power_off)
|
||||
pm_power_off();
|
||||
do_kernel_power_off();
|
||||
asm volatile ("bkpt");
|
||||
}
|
||||
|
||||
void machine_halt(void)
|
||||
{
|
||||
local_irq_disable();
|
||||
if (pm_power_off)
|
||||
pm_power_off();
|
||||
do_kernel_power_off();
|
||||
asm volatile ("bkpt");
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/personality.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/sched/debug.h>
|
||||
#include <linux/sched/hotplug.h>
|
||||
@ -599,8 +600,7 @@ machine_halt (void)
|
||||
void
|
||||
machine_power_off (void)
|
||||
{
|
||||
if (pm_power_off)
|
||||
pm_power_off();
|
||||
do_kernel_power_off();
|
||||
machine_halt();
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include <linux/string.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/io.h>
|
||||
#include <asm/machdep.h>
|
||||
#include <asm/natfeat.h>
|
||||
@ -90,5 +91,5 @@ void __init nf_init(void)
|
||||
pr_info("NatFeats found (%s, %lu.%lu)\n", buf, version >> 16,
|
||||
version & 0xffff);
|
||||
|
||||
mach_power_off = nf_poweroff;
|
||||
register_platform_power_off(nf_poweroff);
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ extern int (*mach_get_rtc_pll)(struct rtc_pll_info *);
|
||||
extern int (*mach_set_rtc_pll)(struct rtc_pll_info *);
|
||||
extern void (*mach_reset)( void );
|
||||
extern void (*mach_halt)( void );
|
||||
extern void (*mach_power_off)( void );
|
||||
extern unsigned long (*mach_hd_init) (unsigned long, unsigned long);
|
||||
extern void (*mach_hd_setup)(char *, int *);
|
||||
extern void (*mach_heartbeat) (int);
|
||||
|
@ -67,12 +67,11 @@ void machine_halt(void)
|
||||
|
||||
void machine_power_off(void)
|
||||
{
|
||||
if (mach_power_off)
|
||||
mach_power_off();
|
||||
do_kernel_power_off();
|
||||
for (;;);
|
||||
}
|
||||
|
||||
void (*pm_power_off)(void) = machine_power_off;
|
||||
void (*pm_power_off)(void);
|
||||
EXPORT_SYMBOL(pm_power_off);
|
||||
|
||||
void show_regs(struct pt_regs * regs)
|
||||
|
@ -98,7 +98,6 @@ EXPORT_SYMBOL(mach_get_rtc_pll);
|
||||
EXPORT_SYMBOL(mach_set_rtc_pll);
|
||||
void (*mach_reset)( void );
|
||||
void (*mach_halt)( void );
|
||||
void (*mach_power_off)( void );
|
||||
#ifdef CONFIG_HEARTBEAT
|
||||
void (*mach_heartbeat) (int);
|
||||
EXPORT_SYMBOL(mach_heartbeat);
|
||||
|
@ -55,7 +55,6 @@ int (*mach_hwclk) (int, struct rtc_time*);
|
||||
/* machine dependent reboot functions */
|
||||
void (*mach_reset)(void);
|
||||
void (*mach_halt)(void);
|
||||
void (*mach_power_off)(void);
|
||||
|
||||
#ifdef CONFIG_M68000
|
||||
#if defined(CONFIG_M68328)
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/tty.h>
|
||||
@ -140,7 +141,6 @@ void __init config_mac(void)
|
||||
mach_hwclk = mac_hwclk;
|
||||
mach_reset = mac_reset;
|
||||
mach_halt = mac_poweroff;
|
||||
mach_power_off = mac_poweroff;
|
||||
#if IS_ENABLED(CONFIG_INPUT_M68K_BEEP)
|
||||
mach_beep = mac_mksound;
|
||||
#endif
|
||||
@ -160,6 +160,8 @@ void __init config_mac(void)
|
||||
|
||||
if (macintosh_config->ident == MAC_MODEL_IICI)
|
||||
mach_l2_flush = via_l2_flush;
|
||||
|
||||
register_platform_power_off(mac_poweroff);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/serial_core.h>
|
||||
#include <clocksource/timer-goldfish.h>
|
||||
|
||||
@ -126,5 +127,6 @@ void __init config_virt(void)
|
||||
mach_get_model = virt_get_model;
|
||||
mach_reset = virt_reset;
|
||||
mach_halt = virt_halt;
|
||||
mach_power_off = virt_halt;
|
||||
|
||||
register_platform_power_off(virt_halt);
|
||||
}
|
||||
|
@ -114,8 +114,7 @@ void machine_halt(void)
|
||||
|
||||
void machine_power_off(void)
|
||||
{
|
||||
if (pm_power_off)
|
||||
pm_power_off();
|
||||
do_kernel_power_off();
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
preempt_disable();
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/personality.h>
|
||||
#include <linux/ptrace.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/sched/debug.h>
|
||||
#include <linux/sched/task.h>
|
||||
@ -116,8 +117,7 @@ void machine_power_off(void)
|
||||
pdc_chassis_send_status(PDC_CHASSIS_DIRECT_SHUTDOWN);
|
||||
|
||||
/* ipmi_poweroff may have been installed. */
|
||||
if (pm_power_off)
|
||||
pm_power_off();
|
||||
do_kernel_power_off();
|
||||
|
||||
/* It seems we have no way to power the system off via
|
||||
* software. The user has to press the button himself. */
|
||||
|
@ -161,9 +161,7 @@ void machine_restart(char *cmd)
|
||||
void machine_power_off(void)
|
||||
{
|
||||
machine_shutdown();
|
||||
if (pm_power_off)
|
||||
pm_power_off();
|
||||
|
||||
do_kernel_power_off();
|
||||
smp_send_stop();
|
||||
machine_hang();
|
||||
}
|
||||
|
@ -1243,8 +1243,7 @@ static void bootcmds(void)
|
||||
} else if (cmd == 'h') {
|
||||
ppc_md.halt();
|
||||
} else if (cmd == 'p') {
|
||||
if (pm_power_off)
|
||||
pm_power_off();
|
||||
do_kernel_power_off();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,16 +23,12 @@ void machine_restart(char *cmd)
|
||||
|
||||
void machine_halt(void)
|
||||
{
|
||||
if (pm_power_off != NULL)
|
||||
pm_power_off();
|
||||
else
|
||||
default_power_off();
|
||||
do_kernel_power_off();
|
||||
default_power_off();
|
||||
}
|
||||
|
||||
void machine_power_off(void)
|
||||
{
|
||||
if (pm_power_off != NULL)
|
||||
pm_power_off();
|
||||
else
|
||||
default_power_off();
|
||||
do_kernel_power_off();
|
||||
default_power_off();
|
||||
}
|
||||
|
@ -46,8 +46,7 @@ static void native_machine_shutdown(void)
|
||||
|
||||
static void native_machine_power_off(void)
|
||||
{
|
||||
if (pm_power_off)
|
||||
pm_power_off();
|
||||
do_kernel_power_off();
|
||||
}
|
||||
|
||||
static void native_machine_halt(void)
|
||||
|
@ -739,10 +739,10 @@ static void native_machine_halt(void)
|
||||
|
||||
static void native_machine_power_off(void)
|
||||
{
|
||||
if (pm_power_off) {
|
||||
if (kernel_can_power_off()) {
|
||||
if (!reboot_force)
|
||||
machine_shutdown();
|
||||
pm_power_off();
|
||||
do_kernel_power_off();
|
||||
}
|
||||
/* A fallback in case there is no PM info available */
|
||||
tboot_shutdown(TB_SHUTDOWN_HALT);
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include <linux/pci.h>
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/edd.h>
|
||||
#include <linux/reboot.h>
|
||||
|
||||
#include <xen/xen.h>
|
||||
#include <xen/events.h>
|
||||
@ -1069,8 +1070,7 @@ static void xen_machine_halt(void)
|
||||
|
||||
static void xen_machine_power_off(void)
|
||||
{
|
||||
if (pm_power_off)
|
||||
pm_power_off();
|
||||
do_kernel_power_off();
|
||||
xen_reboot(SHUTDOWN_poweroff);
|
||||
}
|
||||
|
||||
|
@ -1035,20 +1035,22 @@ static void acpi_sleep_hibernate_setup(void)
|
||||
static inline void acpi_sleep_hibernate_setup(void) {}
|
||||
#endif /* !CONFIG_HIBERNATION */
|
||||
|
||||
static void acpi_power_off_prepare(void)
|
||||
static int acpi_power_off_prepare(struct sys_off_data *data)
|
||||
{
|
||||
/* Prepare to power off the system */
|
||||
acpi_sleep_prepare(ACPI_STATE_S5);
|
||||
acpi_disable_all_gpes();
|
||||
acpi_os_wait_events_complete();
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static void acpi_power_off(void)
|
||||
static int acpi_power_off(struct sys_off_data *data)
|
||||
{
|
||||
/* acpi_sleep_prepare(ACPI_STATE_S5) should have already been called */
|
||||
pr_debug("%s called\n", __func__);
|
||||
local_irq_disable();
|
||||
acpi_enter_sleep_state(ACPI_STATE_S5);
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
int __init acpi_sleep_init(void)
|
||||
@ -1067,8 +1069,14 @@ int __init acpi_sleep_init(void)
|
||||
|
||||
if (acpi_sleep_state_supported(ACPI_STATE_S5)) {
|
||||
sleep_states[ACPI_STATE_S5] = 1;
|
||||
pm_power_off_prepare = acpi_power_off_prepare;
|
||||
pm_power_off = acpi_power_off;
|
||||
|
||||
register_sys_off_handler(SYS_OFF_MODE_POWER_OFF_PREPARE,
|
||||
SYS_OFF_PRIO_FIRMWARE,
|
||||
acpi_power_off_prepare, NULL);
|
||||
|
||||
register_sys_off_handler(SYS_OFF_MODE_POWER_OFF,
|
||||
SYS_OFF_PRIO_FIRMWARE,
|
||||
acpi_power_off, NULL);
|
||||
} else {
|
||||
acpi_no_s5 = true;
|
||||
}
|
||||
|
@ -630,7 +630,7 @@ static irqreturn_t emif_threaded_isr(int irq, void *dev_id)
|
||||
dev_emerg(emif->dev, "SDRAM temperature exceeds operating limit.. Needs shut down!!!\n");
|
||||
|
||||
/* If we have Power OFF ability, use it, else try restarting */
|
||||
if (pm_power_off) {
|
||||
if (kernel_can_power_off()) {
|
||||
kernel_power_off();
|
||||
} else {
|
||||
WARN(1, "FIXME: NO pm_power_off!!! trying restart\n");
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/regulator/of_regulator.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/regulator/driver.h>
|
||||
#include <linux/regulator/machine.h>
|
||||
#include <linux/regulator/pfuze100.h>
|
||||
@ -571,10 +572,10 @@ static inline struct device_node *match_of_node(int index)
|
||||
return pfuze_matches[index].of_node;
|
||||
}
|
||||
|
||||
static struct pfuze_chip *syspm_pfuze_chip;
|
||||
|
||||
static void pfuze_power_off_prepare(void)
|
||||
static int pfuze_power_off_prepare(struct sys_off_data *data)
|
||||
{
|
||||
struct pfuze_chip *syspm_pfuze_chip = data->cb_data;
|
||||
|
||||
dev_info(syspm_pfuze_chip->dev, "Configure standby mode for power off");
|
||||
|
||||
/* Switch from default mode: APS/APS to APS/Off */
|
||||
@ -609,28 +610,30 @@ static void pfuze_power_off_prepare(void)
|
||||
regmap_update_bits(syspm_pfuze_chip->regmap, PFUZE100_VGEN6VOL,
|
||||
PFUZE100_VGENxLPWR | PFUZE100_VGENxSTBY,
|
||||
PFUZE100_VGENxSTBY);
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static int pfuze_power_off_prepare_init(struct pfuze_chip *pfuze_chip)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (pfuze_chip->chip_id != PFUZE100) {
|
||||
dev_warn(pfuze_chip->dev, "Requested pm_power_off_prepare handler for not supported chip\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (pm_power_off_prepare) {
|
||||
dev_warn(pfuze_chip->dev, "pm_power_off_prepare is already registered.\n");
|
||||
return -EBUSY;
|
||||
err = devm_register_sys_off_handler(pfuze_chip->dev,
|
||||
SYS_OFF_MODE_POWER_OFF_PREPARE,
|
||||
SYS_OFF_PRIO_DEFAULT,
|
||||
pfuze_power_off_prepare,
|
||||
pfuze_chip);
|
||||
if (err) {
|
||||
dev_err(pfuze_chip->dev, "failed to register sys-off handler: %d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (syspm_pfuze_chip) {
|
||||
dev_warn(pfuze_chip->dev, "syspm_pfuze_chip is already set.\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
syspm_pfuze_chip = pfuze_chip;
|
||||
pm_power_off_prepare = pfuze_power_off_prepare;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -839,23 +842,12 @@ static int pfuze100_regulator_probe(struct i2c_client *client,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pfuze100_regulator_remove(struct i2c_client *client)
|
||||
{
|
||||
if (syspm_pfuze_chip) {
|
||||
syspm_pfuze_chip = NULL;
|
||||
pm_power_off_prepare = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct i2c_driver pfuze_driver = {
|
||||
.driver = {
|
||||
.name = "pfuze100-regulator",
|
||||
.of_match_table = pfuze_dt_ids,
|
||||
},
|
||||
.probe = pfuze100_regulator_probe,
|
||||
.remove = pfuze100_regulator_remove,
|
||||
};
|
||||
module_i2c_driver(pfuze_driver);
|
||||
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_domain.h>
|
||||
#include <linux/pm_opp.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/reset.h>
|
||||
@ -108,6 +109,7 @@
|
||||
#define PMC_USB_DEBOUNCE_DEL 0xec
|
||||
#define PMC_USB_AO 0xf0
|
||||
|
||||
#define PMC_SCRATCH37 0x130
|
||||
#define PMC_SCRATCH41 0x140
|
||||
|
||||
#define PMC_WAKE2_MASK 0x160
|
||||
@ -1099,8 +1101,7 @@ static struct notifier_block tegra_pmc_reboot_notifier = {
|
||||
.notifier_call = tegra_pmc_reboot_notify,
|
||||
};
|
||||
|
||||
static int tegra_pmc_restart_notify(struct notifier_block *this,
|
||||
unsigned long action, void *data)
|
||||
static void tegra_pmc_restart(void)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
@ -1108,14 +1109,31 @@ static int tegra_pmc_restart_notify(struct notifier_block *this,
|
||||
value = tegra_pmc_readl(pmc, PMC_CNTRL);
|
||||
value |= PMC_CNTRL_MAIN_RST;
|
||||
tegra_pmc_writel(pmc, value, PMC_CNTRL);
|
||||
}
|
||||
|
||||
static int tegra_pmc_restart_handler(struct sys_off_data *data)
|
||||
{
|
||||
tegra_pmc_restart();
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static struct notifier_block tegra_pmc_restart_handler = {
|
||||
.notifier_call = tegra_pmc_restart_notify,
|
||||
.priority = 128,
|
||||
};
|
||||
static int tegra_pmc_power_off_handler(struct sys_off_data *data)
|
||||
{
|
||||
/*
|
||||
* Reboot Nexus 7 into special bootloader mode if USB cable is
|
||||
* connected in order to display battery status and power off.
|
||||
*/
|
||||
if (of_machine_is_compatible("asus,grouper") &&
|
||||
power_supply_is_system_supplied()) {
|
||||
const u32 go_to_charger_mode = 0xa5a55a5a;
|
||||
|
||||
tegra_pmc_writel(pmc, go_to_charger_mode, PMC_SCRATCH37);
|
||||
tegra_pmc_restart();
|
||||
}
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static int powergate_show(struct seq_file *s, void *data)
|
||||
{
|
||||
@ -2877,6 +2895,42 @@ static int tegra_pmc_probe(struct platform_device *pdev)
|
||||
pmc->clk = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* PMC should be last resort for restarting since it soft-resets
|
||||
* CPU without resetting everything else.
|
||||
*/
|
||||
err = devm_register_reboot_notifier(&pdev->dev,
|
||||
&tegra_pmc_reboot_notifier);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "unable to register reboot notifier, %d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = devm_register_sys_off_handler(&pdev->dev,
|
||||
SYS_OFF_MODE_RESTART,
|
||||
SYS_OFF_PRIO_LOW,
|
||||
tegra_pmc_restart_handler, NULL);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "failed to register sys-off handler: %d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* PMC should be primary power-off method if it soft-resets CPU,
|
||||
* asking bootloader to shutdown hardware.
|
||||
*/
|
||||
err = devm_register_sys_off_handler(&pdev->dev,
|
||||
SYS_OFF_MODE_POWER_OFF,
|
||||
SYS_OFF_PRIO_FIRMWARE,
|
||||
tegra_pmc_power_off_handler, NULL);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "failed to register sys-off handler: %d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* PCLK clock rate can't be retrieved using CLK API because it
|
||||
* causes lockup if CPU enters LP2 idle state from some other
|
||||
@ -2908,28 +2962,13 @@ static int tegra_pmc_probe(struct platform_device *pdev)
|
||||
goto cleanup_sysfs;
|
||||
}
|
||||
|
||||
err = devm_register_reboot_notifier(&pdev->dev,
|
||||
&tegra_pmc_reboot_notifier);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "unable to register reboot notifier, %d\n",
|
||||
err);
|
||||
goto cleanup_debugfs;
|
||||
}
|
||||
|
||||
err = register_restart_handler(&tegra_pmc_restart_handler);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "unable to register restart handler, %d\n",
|
||||
err);
|
||||
goto cleanup_debugfs;
|
||||
}
|
||||
|
||||
err = tegra_pmc_pinctrl_init(pmc);
|
||||
if (err)
|
||||
goto cleanup_restart_handler;
|
||||
goto cleanup_debugfs;
|
||||
|
||||
err = tegra_pmc_regmap_init(pmc);
|
||||
if (err < 0)
|
||||
goto cleanup_restart_handler;
|
||||
goto cleanup_debugfs;
|
||||
|
||||
err = tegra_powergate_init(pmc, pdev->dev.of_node);
|
||||
if (err < 0)
|
||||
@ -2952,8 +2991,6 @@ static int tegra_pmc_probe(struct platform_device *pdev)
|
||||
|
||||
cleanup_powergates:
|
||||
tegra_powergate_remove_all(pdev->dev.of_node);
|
||||
cleanup_restart_handler:
|
||||
unregister_restart_handler(&tegra_pmc_restart_handler);
|
||||
cleanup_debugfs:
|
||||
debugfs_remove(pmc->debugfs);
|
||||
cleanup_sysfs:
|
||||
|
@ -150,6 +150,11 @@ extern int raw_notifier_chain_register(struct raw_notifier_head *nh,
|
||||
extern int srcu_notifier_chain_register(struct srcu_notifier_head *nh,
|
||||
struct notifier_block *nb);
|
||||
|
||||
extern int atomic_notifier_chain_register_unique_prio(
|
||||
struct atomic_notifier_head *nh, struct notifier_block *nb);
|
||||
extern int blocking_notifier_chain_register_unique_prio(
|
||||
struct blocking_notifier_head *nh, struct notifier_block *nb);
|
||||
|
||||
extern int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh,
|
||||
struct notifier_block *nb);
|
||||
extern int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh,
|
||||
@ -173,6 +178,8 @@ extern int blocking_notifier_call_chain_robust(struct blocking_notifier_head *nh
|
||||
extern int raw_notifier_call_chain_robust(struct raw_notifier_head *nh,
|
||||
unsigned long val_up, unsigned long val_down, void *v);
|
||||
|
||||
extern bool atomic_notifier_call_chain_is_empty(struct atomic_notifier_head *nh);
|
||||
|
||||
#define NOTIFY_DONE 0x0000 /* Don't care */
|
||||
#define NOTIFY_OK 0x0001 /* Suits me */
|
||||
#define NOTIFY_STOP_MASK 0x8000 /* Don't call further */
|
||||
|
@ -21,7 +21,6 @@
|
||||
* Callbacks for platform drivers to implement.
|
||||
*/
|
||||
extern void (*pm_power_off)(void);
|
||||
extern void (*pm_power_off_prepare)(void);
|
||||
|
||||
struct device; /* we have a circular dep with device.h */
|
||||
#ifdef CONFIG_VT_CONSOLE_SLEEP
|
||||
|
@ -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,95 @@ extern void machine_shutdown(void);
|
||||
struct pt_regs;
|
||||
extern void machine_crash_shutdown(struct pt_regs *);
|
||||
|
||||
void do_kernel_power_off(void);
|
||||
|
||||
/*
|
||||
* 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);
|
||||
|
||||
int devm_register_power_off_handler(struct device *dev,
|
||||
int (*callback)(struct sys_off_data *data),
|
||||
void *cb_data);
|
||||
|
||||
int devm_register_restart_handler(struct device *dev,
|
||||
int (*callback)(struct sys_off_data *data),
|
||||
void *cb_data);
|
||||
|
||||
int register_platform_power_off(void (*power_off)(void));
|
||||
void unregister_platform_power_off(void (*power_off)(void));
|
||||
|
||||
/*
|
||||
* Architecture independent implemenations of sys_reboot commands.
|
||||
*/
|
||||
@ -70,6 +160,7 @@ extern void kernel_restart_prepare(char *cmd);
|
||||
extern void kernel_restart(char *cmd);
|
||||
extern void kernel_halt(void);
|
||||
extern void kernel_power_off(void);
|
||||
extern bool kernel_can_power_off(void);
|
||||
|
||||
extern int C_A_D; /* for sysctl */
|
||||
void ctrl_alt_del(void);
|
||||
|
@ -20,7 +20,8 @@ BLOCKING_NOTIFIER_HEAD(reboot_notifier_list);
|
||||
*/
|
||||
|
||||
static int notifier_chain_register(struct notifier_block **nl,
|
||||
struct notifier_block *n)
|
||||
struct notifier_block *n,
|
||||
bool unique_priority)
|
||||
{
|
||||
while ((*nl) != NULL) {
|
||||
if (unlikely((*nl) == n)) {
|
||||
@ -30,6 +31,8 @@ static int notifier_chain_register(struct notifier_block **nl,
|
||||
}
|
||||
if (n->priority > (*nl)->priority)
|
||||
break;
|
||||
if (n->priority == (*nl)->priority && unique_priority)
|
||||
return -EBUSY;
|
||||
nl = &((*nl)->next);
|
||||
}
|
||||
n->next = *nl;
|
||||
@ -144,12 +147,35 @@ int atomic_notifier_chain_register(struct atomic_notifier_head *nh,
|
||||
int ret;
|
||||
|
||||
spin_lock_irqsave(&nh->lock, flags);
|
||||
ret = notifier_chain_register(&nh->head, n);
|
||||
ret = notifier_chain_register(&nh->head, n, false);
|
||||
spin_unlock_irqrestore(&nh->lock, flags);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(atomic_notifier_chain_register);
|
||||
|
||||
/**
|
||||
* atomic_notifier_chain_register_unique_prio - Add notifier to an atomic notifier chain
|
||||
* @nh: Pointer to head of the atomic notifier chain
|
||||
* @n: New entry in notifier chain
|
||||
*
|
||||
* Adds a notifier to an atomic notifier chain if there is no other
|
||||
* notifier registered using the same priority.
|
||||
*
|
||||
* Returns 0 on success, %-EEXIST or %-EBUSY on error.
|
||||
*/
|
||||
int atomic_notifier_chain_register_unique_prio(struct atomic_notifier_head *nh,
|
||||
struct notifier_block *n)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
spin_lock_irqsave(&nh->lock, flags);
|
||||
ret = notifier_chain_register(&nh->head, n, true);
|
||||
spin_unlock_irqrestore(&nh->lock, flags);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(atomic_notifier_chain_register_unique_prio);
|
||||
|
||||
/**
|
||||
* atomic_notifier_chain_unregister - Remove notifier from an atomic notifier chain
|
||||
* @nh: Pointer to head of the atomic notifier chain
|
||||
@ -204,11 +230,44 @@ int atomic_notifier_call_chain(struct atomic_notifier_head *nh,
|
||||
EXPORT_SYMBOL_GPL(atomic_notifier_call_chain);
|
||||
NOKPROBE_SYMBOL(atomic_notifier_call_chain);
|
||||
|
||||
/**
|
||||
* atomic_notifier_call_chain_is_empty - Check whether notifier chain is empty
|
||||
* @nh: Pointer to head of the atomic notifier chain
|
||||
*
|
||||
* Checks whether notifier chain is empty.
|
||||
*
|
||||
* Returns true is notifier chain is empty, false otherwise.
|
||||
*/
|
||||
bool atomic_notifier_call_chain_is_empty(struct atomic_notifier_head *nh)
|
||||
{
|
||||
return !rcu_access_pointer(nh->head);
|
||||
}
|
||||
|
||||
/*
|
||||
* Blocking notifier chain routines. All access to the chain is
|
||||
* synchronized by an rwsem.
|
||||
*/
|
||||
|
||||
static int __blocking_notifier_chain_register(struct blocking_notifier_head *nh,
|
||||
struct notifier_block *n,
|
||||
bool unique_priority)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* This code gets used during boot-up, when task switching is
|
||||
* not yet working and interrupts must remain disabled. At
|
||||
* such times we must not call down_write().
|
||||
*/
|
||||
if (unlikely(system_state == SYSTEM_BOOTING))
|
||||
return notifier_chain_register(&nh->head, n, unique_priority);
|
||||
|
||||
down_write(&nh->rwsem);
|
||||
ret = notifier_chain_register(&nh->head, n, unique_priority);
|
||||
up_write(&nh->rwsem);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* blocking_notifier_chain_register - Add notifier to a blocking notifier chain
|
||||
* @nh: Pointer to head of the blocking notifier chain
|
||||
@ -222,23 +281,27 @@ NOKPROBE_SYMBOL(atomic_notifier_call_chain);
|
||||
int blocking_notifier_chain_register(struct blocking_notifier_head *nh,
|
||||
struct notifier_block *n)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* This code gets used during boot-up, when task switching is
|
||||
* not yet working and interrupts must remain disabled. At
|
||||
* such times we must not call down_write().
|
||||
*/
|
||||
if (unlikely(system_state == SYSTEM_BOOTING))
|
||||
return notifier_chain_register(&nh->head, n);
|
||||
|
||||
down_write(&nh->rwsem);
|
||||
ret = notifier_chain_register(&nh->head, n);
|
||||
up_write(&nh->rwsem);
|
||||
return ret;
|
||||
return __blocking_notifier_chain_register(nh, n, false);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(blocking_notifier_chain_register);
|
||||
|
||||
/**
|
||||
* blocking_notifier_chain_register_unique_prio - Add notifier to a blocking notifier chain
|
||||
* @nh: Pointer to head of the blocking notifier chain
|
||||
* @n: New entry in notifier chain
|
||||
*
|
||||
* Adds a notifier to an blocking notifier chain if there is no other
|
||||
* notifier registered using the same priority.
|
||||
*
|
||||
* Returns 0 on success, %-EEXIST or %-EBUSY on error.
|
||||
*/
|
||||
int blocking_notifier_chain_register_unique_prio(struct blocking_notifier_head *nh,
|
||||
struct notifier_block *n)
|
||||
{
|
||||
return __blocking_notifier_chain_register(nh, n, true);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(blocking_notifier_chain_register_unique_prio);
|
||||
|
||||
/**
|
||||
* blocking_notifier_chain_unregister - Remove notifier from a blocking notifier chain
|
||||
* @nh: Pointer to head of the blocking notifier chain
|
||||
@ -341,7 +404,7 @@ EXPORT_SYMBOL_GPL(blocking_notifier_call_chain);
|
||||
int raw_notifier_chain_register(struct raw_notifier_head *nh,
|
||||
struct notifier_block *n)
|
||||
{
|
||||
return notifier_chain_register(&nh->head, n);
|
||||
return notifier_chain_register(&nh->head, n, false);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(raw_notifier_chain_register);
|
||||
|
||||
@ -420,10 +483,10 @@ int srcu_notifier_chain_register(struct srcu_notifier_head *nh,
|
||||
* such times we must not call mutex_lock().
|
||||
*/
|
||||
if (unlikely(system_state == SYSTEM_BOOTING))
|
||||
return notifier_chain_register(&nh->head, n);
|
||||
return notifier_chain_register(&nh->head, n, false);
|
||||
|
||||
mutex_lock(&nh->mutex);
|
||||
ret = notifier_chain_register(&nh->head, n);
|
||||
ret = notifier_chain_register(&nh->head, n, false);
|
||||
mutex_unlock(&nh->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
350
kernel/reboot.c
350
kernel/reboot.c
@ -48,12 +48,20 @@ int reboot_cpu;
|
||||
enum reboot_type reboot_type = BOOT_ACPI;
|
||||
int reboot_force;
|
||||
|
||||
/*
|
||||
* If set, this is used for preparing the system to power off.
|
||||
*/
|
||||
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;
|
||||
};
|
||||
|
||||
void (*pm_power_off_prepare)(void);
|
||||
EXPORT_SYMBOL_GPL(pm_power_off_prepare);
|
||||
/*
|
||||
* Temporary stub that prevents linkage failure while we're in process
|
||||
* of removing all uses of legacy pm_power_off() around the kernel.
|
||||
*/
|
||||
void __weak (*pm_power_off)(void);
|
||||
|
||||
/**
|
||||
* emergency_restart - reboot the system
|
||||
@ -281,6 +289,316 @@ 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);
|
||||
|
||||
/**
|
||||
* devm_register_power_off_handler - Register power-off handler
|
||||
* @dev: Device that registers callback
|
||||
* @callback: Callback function
|
||||
* @cb_data: Callback's argument
|
||||
*
|
||||
* Registers resource-managed sys-off handler with a default priority
|
||||
* and using power-off mode.
|
||||
*
|
||||
* Returns zero on success, or error code on failure.
|
||||
*/
|
||||
int devm_register_power_off_handler(struct device *dev,
|
||||
int (*callback)(struct sys_off_data *data),
|
||||
void *cb_data)
|
||||
{
|
||||
return devm_register_sys_off_handler(dev,
|
||||
SYS_OFF_MODE_POWER_OFF,
|
||||
SYS_OFF_PRIO_DEFAULT,
|
||||
callback, cb_data);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_register_power_off_handler);
|
||||
|
||||
/**
|
||||
* devm_register_restart_handler - Register restart handler
|
||||
* @dev: Device that registers callback
|
||||
* @callback: Callback function
|
||||
* @cb_data: Callback's argument
|
||||
*
|
||||
* Registers resource-managed sys-off handler with a default priority
|
||||
* and using restart mode.
|
||||
*
|
||||
* Returns zero on success, or error code on failure.
|
||||
*/
|
||||
int devm_register_restart_handler(struct device *dev,
|
||||
int (*callback)(struct sys_off_data *data),
|
||||
void *cb_data)
|
||||
{
|
||||
return devm_register_sys_off_handler(dev,
|
||||
SYS_OFF_MODE_RESTART,
|
||||
SYS_OFF_PRIO_DEFAULT,
|
||||
callback, cb_data);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_register_restart_handler);
|
||||
|
||||
static struct sys_off_handler *platform_power_off_handler;
|
||||
|
||||
static int platform_power_off_notify(struct sys_off_data *data)
|
||||
{
|
||||
void (*platform_power_power_off_cb)(void) = data->cb_data;
|
||||
|
||||
platform_power_power_off_cb();
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* register_platform_power_off - Register platform-level power-off callback
|
||||
* @power_off: Power-off callback
|
||||
*
|
||||
* Registers power-off callback that will be called as last step
|
||||
* of the power-off sequence. This callback is expected to be invoked
|
||||
* for the last resort. Only one platform power-off callback is allowed
|
||||
* to be registered at a time.
|
||||
*
|
||||
* Returns zero on success, or error code on failure.
|
||||
*/
|
||||
int register_platform_power_off(void (*power_off)(void))
|
||||
{
|
||||
struct sys_off_handler *handler;
|
||||
|
||||
handler = register_sys_off_handler(SYS_OFF_MODE_POWER_OFF,
|
||||
SYS_OFF_PRIO_PLATFORM,
|
||||
platform_power_off_notify,
|
||||
power_off);
|
||||
if (IS_ERR(handler))
|
||||
return PTR_ERR(handler);
|
||||
|
||||
platform_power_off_handler = handler;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(register_platform_power_off);
|
||||
|
||||
/**
|
||||
* unregister_platform_power_off - Unregister platform-level power-off callback
|
||||
* @power_off: Power-off callback
|
||||
*
|
||||
* Unregisters previously registered platform power-off callback.
|
||||
*/
|
||||
void unregister_platform_power_off(void (*power_off)(void))
|
||||
{
|
||||
if (platform_power_off_handler &&
|
||||
platform_power_off_handler->cb_data == power_off) {
|
||||
unregister_sys_off_handler(platform_power_off_handler);
|
||||
platform_power_off_handler = NULL;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(unregister_platform_power_off);
|
||||
|
||||
static int legacy_pm_power_off(struct sys_off_data *data)
|
||||
{
|
||||
if (pm_power_off)
|
||||
pm_power_off();
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static void do_kernel_power_off_prepare(void)
|
||||
{
|
||||
blocking_notifier_call_chain(&power_off_prep_handler_list, 0, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* do_kernel_power_off - Execute kernel power-off handler call chain
|
||||
*
|
||||
* Expected to be called as last step of the power-off sequence.
|
||||
*
|
||||
* Powers off the system immediately if a power-off handler function has
|
||||
* been registered. Otherwise does nothing.
|
||||
*/
|
||||
void do_kernel_power_off(void)
|
||||
{
|
||||
atomic_notifier_call_chain(&power_off_handler_list, 0, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* kernel_can_power_off - check whether system can be powered off
|
||||
*
|
||||
* Returns true if power-off handler is registered and system can be
|
||||
* powered off, false otherwise.
|
||||
*/
|
||||
bool kernel_can_power_off(void)
|
||||
{
|
||||
return !atomic_notifier_call_chain_is_empty(&power_off_handler_list);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(kernel_can_power_off);
|
||||
|
||||
/**
|
||||
* kernel_power_off - power_off the system
|
||||
*
|
||||
@ -289,8 +607,7 @@ EXPORT_SYMBOL_GPL(kernel_halt);
|
||||
void kernel_power_off(void)
|
||||
{
|
||||
kernel_shutdown_prepare(SYSTEM_POWER_OFF);
|
||||
if (pm_power_off_prepare)
|
||||
pm_power_off_prepare();
|
||||
do_kernel_power_off_prepare();
|
||||
migrate_to_reboot_cpu();
|
||||
syscore_shutdown();
|
||||
pr_emerg("Power down\n");
|
||||
@ -313,6 +630,7 @@ SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
|
||||
void __user *, arg)
|
||||
{
|
||||
struct pid_namespace *pid_ns = task_active_pid_ns(current);
|
||||
struct sys_off_handler *sys_off = NULL;
|
||||
char buffer[256];
|
||||
int ret = 0;
|
||||
|
||||
@ -337,10 +655,25 @@ SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Register sys-off handlers for legacy PM callback. This allows
|
||||
* legacy PM callbacks temporary co-exist with the new sys-off API.
|
||||
*
|
||||
* TODO: Remove legacy handlers once all legacy PM users will be
|
||||
* switched to the sys-off based APIs.
|
||||
*/
|
||||
if (pm_power_off) {
|
||||
sys_off = register_sys_off_handler(SYS_OFF_MODE_POWER_OFF,
|
||||
SYS_OFF_PRIO_DEFAULT,
|
||||
legacy_pm_power_off, NULL);
|
||||
if (IS_ERR(sys_off))
|
||||
return PTR_ERR(sys_off);
|
||||
}
|
||||
|
||||
/* 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) && !kernel_can_power_off())
|
||||
cmd = LINUX_REBOOT_CMD_HALT;
|
||||
|
||||
mutex_lock(&system_transition_mutex);
|
||||
@ -394,6 +727,7 @@ SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
|
||||
break;
|
||||
}
|
||||
mutex_unlock(&system_transition_mutex);
|
||||
unregister_sys_off_handler(sys_off);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user