kernel/reboot: Fix powering off using a non-syscall code paths

There are other methods of powering off machine than the reboot syscall.
Previously we missed to cover those methods and it created power-off
regression for some machines, like the PowerPC e500.

Fix this problem by moving the legacy sys-off handler registration to
the latest phase of power-off process and making the kernel_can_power_off()
check the legacy pm_power_off presence.

Tested-by: Michael Ellerman <mpe@ellerman.id.au> # ppce500
Reported-by: Michael Ellerman <mpe@ellerman.id.au> # ppce500
Fixes: da007f171fc9 ("kernel/reboot: Change registration order of legacy power-off handler")
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:
Dmitry Osipenko 2022-06-06 19:56:40 +03:00 committed by Rafael J. Wysocki
parent 587b9bfe06
commit 2b8c612c61

View File

@ -320,6 +320,7 @@ static struct sys_off_handler platform_sys_off_handler;
static struct sys_off_handler *alloc_sys_off_handler(int priority) static struct sys_off_handler *alloc_sys_off_handler(int priority)
{ {
struct sys_off_handler *handler; struct sys_off_handler *handler;
gfp_t flags;
/* /*
* Platforms like m68k can't allocate sys_off handler dynamically * Platforms like m68k can't allocate sys_off handler dynamically
@ -330,7 +331,12 @@ static struct sys_off_handler *alloc_sys_off_handler(int priority)
if (handler->cb_data) if (handler->cb_data)
return ERR_PTR(-EBUSY); return ERR_PTR(-EBUSY);
} else { } else {
handler = kzalloc(sizeof(*handler), GFP_KERNEL); if (system_state > SYSTEM_RUNNING)
flags = GFP_ATOMIC;
else
flags = GFP_KERNEL;
handler = kzalloc(sizeof(*handler), flags);
if (!handler) if (!handler)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
} }
@ -440,7 +446,7 @@ void unregister_sys_off_handler(struct sys_off_handler *handler)
{ {
int err; int err;
if (!handler) if (IS_ERR_OR_NULL(handler))
return; return;
if (handler->blocking) if (handler->blocking)
@ -615,7 +621,23 @@ static void do_kernel_power_off_prepare(void)
*/ */
void do_kernel_power_off(void) void do_kernel_power_off(void)
{ {
struct sys_off_handler *sys_off = NULL;
/*
* 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);
atomic_notifier_call_chain(&power_off_handler_list, 0, NULL); atomic_notifier_call_chain(&power_off_handler_list, 0, NULL);
unregister_sys_off_handler(sys_off);
} }
/** /**
@ -626,7 +648,8 @@ void do_kernel_power_off(void)
*/ */
bool kernel_can_power_off(void) bool kernel_can_power_off(void)
{ {
return !atomic_notifier_call_chain_is_empty(&power_off_handler_list); return !atomic_notifier_call_chain_is_empty(&power_off_handler_list) ||
pm_power_off;
} }
EXPORT_SYMBOL_GPL(kernel_can_power_off); EXPORT_SYMBOL_GPL(kernel_can_power_off);
@ -661,7 +684,6 @@ SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
void __user *, arg) void __user *, arg)
{ {
struct pid_namespace *pid_ns = task_active_pid_ns(current); struct pid_namespace *pid_ns = task_active_pid_ns(current);
struct sys_off_handler *sys_off = NULL;
char buffer[256]; char buffer[256];
int ret = 0; int ret = 0;
@ -686,21 +708,6 @@ SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
if (ret) if (ret)
return 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 /* Instead of trying to make the power_off code look like
* halt when pm_power_off is not set do it the easy way. * halt when pm_power_off is not set do it the easy way.
*/ */
@ -758,7 +765,6 @@ SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
break; break;
} }
mutex_unlock(&system_transition_mutex); mutex_unlock(&system_transition_mutex);
unregister_sys_off_handler(sys_off);
return ret; return ret;
} }