mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-26 10:49:33 +00:00
![Brandt, Todd E](/assets/img/avatar_default.png)
Change where ftrace is disabled and re-enabled during system
suspend/resume to allow tracing of device driver pm callbacks.
Ftrace will now be turned off when suspend reaches
disable_nonboot_cpus() instead of at the very beginning of system
suspend.
Ftrace was disabled during suspend/resume back in 2008 by
Steven Rostedt as he discovered there was a conflict in the
enable_nonboot_cpus() call (see commit f42ac38
"ftrace: disable
tracing for suspend to ram"). This change preserves his fix by
disabling ftrace, but only at the function where it is known
to cause problems.
The new change allows tracing of the device level code for better
debug.
[rjw: Changelog]
Signed-off-by: Todd Brandt <todd.e.brandt@intel.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
384 lines
8.9 KiB
C
384 lines
8.9 KiB
C
/*
|
|
* kernel/power/suspend.c - Suspend to RAM and standby functionality.
|
|
*
|
|
* Copyright (c) 2003 Patrick Mochel
|
|
* Copyright (c) 2003 Open Source Development Lab
|
|
* Copyright (c) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
|
|
*
|
|
* This file is released under the GPLv2.
|
|
*/
|
|
|
|
#include <linux/string.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/init.h>
|
|
#include <linux/console.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/syscalls.h>
|
|
#include <linux/gfp.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/list.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/export.h>
|
|
#include <linux/suspend.h>
|
|
#include <linux/syscore_ops.h>
|
|
#include <linux/ftrace.h>
|
|
#include <trace/events/power.h>
|
|
|
|
#include "power.h"
|
|
|
|
const char *const pm_states[PM_SUSPEND_MAX] = {
|
|
[PM_SUSPEND_FREEZE] = "freeze",
|
|
[PM_SUSPEND_STANDBY] = "standby",
|
|
[PM_SUSPEND_MEM] = "mem",
|
|
};
|
|
|
|
static const struct platform_suspend_ops *suspend_ops;
|
|
|
|
static bool need_suspend_ops(suspend_state_t state)
|
|
{
|
|
return !!(state > PM_SUSPEND_FREEZE);
|
|
}
|
|
|
|
static DECLARE_WAIT_QUEUE_HEAD(suspend_freeze_wait_head);
|
|
static bool suspend_freeze_wake;
|
|
|
|
static void freeze_begin(void)
|
|
{
|
|
suspend_freeze_wake = false;
|
|
}
|
|
|
|
static void freeze_enter(void)
|
|
{
|
|
wait_event(suspend_freeze_wait_head, suspend_freeze_wake);
|
|
}
|
|
|
|
void freeze_wake(void)
|
|
{
|
|
suspend_freeze_wake = true;
|
|
wake_up(&suspend_freeze_wait_head);
|
|
}
|
|
EXPORT_SYMBOL_GPL(freeze_wake);
|
|
|
|
/**
|
|
* suspend_set_ops - Set the global suspend method table.
|
|
* @ops: Suspend operations to use.
|
|
*/
|
|
void suspend_set_ops(const struct platform_suspend_ops *ops)
|
|
{
|
|
lock_system_sleep();
|
|
suspend_ops = ops;
|
|
unlock_system_sleep();
|
|
}
|
|
EXPORT_SYMBOL_GPL(suspend_set_ops);
|
|
|
|
bool valid_state(suspend_state_t state)
|
|
{
|
|
if (state == PM_SUSPEND_FREEZE) {
|
|
#ifdef CONFIG_PM_DEBUG
|
|
if (pm_test_level != TEST_NONE &&
|
|
pm_test_level != TEST_FREEZER &&
|
|
pm_test_level != TEST_DEVICES &&
|
|
pm_test_level != TEST_PLATFORM) {
|
|
printk(KERN_WARNING "Unsupported pm_test mode for "
|
|
"freeze state, please choose "
|
|
"none/freezer/devices/platform.\n");
|
|
return false;
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
/*
|
|
* PM_SUSPEND_STANDBY and PM_SUSPEND_MEMORY states need lowlevel
|
|
* support and need to be valid to the lowlevel
|
|
* implementation, no valid callback implies that none are valid.
|
|
*/
|
|
return suspend_ops && suspend_ops->valid && suspend_ops->valid(state);
|
|
}
|
|
|
|
/**
|
|
* suspend_valid_only_mem - Generic memory-only valid callback.
|
|
*
|
|
* Platform drivers that implement mem suspend only and only need to check for
|
|
* that in their .valid() callback can use this instead of rolling their own
|
|
* .valid() callback.
|
|
*/
|
|
int suspend_valid_only_mem(suspend_state_t state)
|
|
{
|
|
return state == PM_SUSPEND_MEM;
|
|
}
|
|
EXPORT_SYMBOL_GPL(suspend_valid_only_mem);
|
|
|
|
static int suspend_test(int level)
|
|
{
|
|
#ifdef CONFIG_PM_DEBUG
|
|
if (pm_test_level == level) {
|
|
printk(KERN_INFO "suspend debug: Waiting for 5 seconds.\n");
|
|
mdelay(5000);
|
|
return 1;
|
|
}
|
|
#endif /* !CONFIG_PM_DEBUG */
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* suspend_prepare - Prepare for entering system sleep state.
|
|
*
|
|
* Common code run for every system sleep state that can be entered (except for
|
|
* hibernation). Run suspend notifiers, allocate the "suspend" console and
|
|
* freeze processes.
|
|
*/
|
|
static int suspend_prepare(suspend_state_t state)
|
|
{
|
|
int error;
|
|
|
|
if (need_suspend_ops(state) && (!suspend_ops || !suspend_ops->enter))
|
|
return -EPERM;
|
|
|
|
pm_prepare_console();
|
|
|
|
error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);
|
|
if (error)
|
|
goto Finish;
|
|
|
|
error = suspend_freeze_processes();
|
|
if (!error)
|
|
return 0;
|
|
|
|
suspend_stats.failed_freeze++;
|
|
dpm_save_failed_step(SUSPEND_FREEZE);
|
|
Finish:
|
|
pm_notifier_call_chain(PM_POST_SUSPEND);
|
|
pm_restore_console();
|
|
return error;
|
|
}
|
|
|
|
/* default implementation */
|
|
void __attribute__ ((weak)) arch_suspend_disable_irqs(void)
|
|
{
|
|
local_irq_disable();
|
|
}
|
|
|
|
/* default implementation */
|
|
void __attribute__ ((weak)) arch_suspend_enable_irqs(void)
|
|
{
|
|
local_irq_enable();
|
|
}
|
|
|
|
/**
|
|
* suspend_enter - Make the system enter the given sleep state.
|
|
* @state: System sleep state to enter.
|
|
* @wakeup: Returns information that the sleep state should not be re-entered.
|
|
*
|
|
* This function should be called after devices have been suspended.
|
|
*/
|
|
static int suspend_enter(suspend_state_t state, bool *wakeup)
|
|
{
|
|
int error;
|
|
|
|
if (need_suspend_ops(state) && suspend_ops->prepare) {
|
|
error = suspend_ops->prepare();
|
|
if (error)
|
|
goto Platform_finish;
|
|
}
|
|
|
|
error = dpm_suspend_end(PMSG_SUSPEND);
|
|
if (error) {
|
|
printk(KERN_ERR "PM: Some devices failed to power down\n");
|
|
goto Platform_finish;
|
|
}
|
|
|
|
if (need_suspend_ops(state) && suspend_ops->prepare_late) {
|
|
error = suspend_ops->prepare_late();
|
|
if (error)
|
|
goto Platform_wake;
|
|
}
|
|
|
|
if (suspend_test(TEST_PLATFORM))
|
|
goto Platform_wake;
|
|
|
|
/*
|
|
* PM_SUSPEND_FREEZE equals
|
|
* frozen processes + suspended devices + idle processors.
|
|
* Thus we should invoke freeze_enter() soon after
|
|
* all the devices are suspended.
|
|
*/
|
|
if (state == PM_SUSPEND_FREEZE) {
|
|
freeze_enter();
|
|
goto Platform_wake;
|
|
}
|
|
|
|
ftrace_stop();
|
|
error = disable_nonboot_cpus();
|
|
if (error || suspend_test(TEST_CPUS))
|
|
goto Enable_cpus;
|
|
|
|
arch_suspend_disable_irqs();
|
|
BUG_ON(!irqs_disabled());
|
|
|
|
error = syscore_suspend();
|
|
if (!error) {
|
|
*wakeup = pm_wakeup_pending();
|
|
if (!(suspend_test(TEST_CORE) || *wakeup)) {
|
|
error = suspend_ops->enter(state);
|
|
events_check_enabled = false;
|
|
}
|
|
syscore_resume();
|
|
}
|
|
|
|
arch_suspend_enable_irqs();
|
|
BUG_ON(irqs_disabled());
|
|
|
|
Enable_cpus:
|
|
enable_nonboot_cpus();
|
|
ftrace_start();
|
|
|
|
Platform_wake:
|
|
if (need_suspend_ops(state) && suspend_ops->wake)
|
|
suspend_ops->wake();
|
|
|
|
dpm_resume_start(PMSG_RESUME);
|
|
|
|
Platform_finish:
|
|
if (need_suspend_ops(state) && suspend_ops->finish)
|
|
suspend_ops->finish();
|
|
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
* suspend_devices_and_enter - Suspend devices and enter system sleep state.
|
|
* @state: System sleep state to enter.
|
|
*/
|
|
int suspend_devices_and_enter(suspend_state_t state)
|
|
{
|
|
int error;
|
|
bool wakeup = false;
|
|
|
|
if (need_suspend_ops(state) && !suspend_ops)
|
|
return -ENOSYS;
|
|
|
|
trace_machine_suspend(state);
|
|
if (need_suspend_ops(state) && suspend_ops->begin) {
|
|
error = suspend_ops->begin(state);
|
|
if (error)
|
|
goto Close;
|
|
}
|
|
suspend_console();
|
|
suspend_test_start();
|
|
error = dpm_suspend_start(PMSG_SUSPEND);
|
|
if (error) {
|
|
pr_err("PM: Some devices failed to suspend, or early wake event detected\n");
|
|
goto Recover_platform;
|
|
}
|
|
suspend_test_finish("suspend devices");
|
|
if (suspend_test(TEST_DEVICES))
|
|
goto Recover_platform;
|
|
|
|
do {
|
|
error = suspend_enter(state, &wakeup);
|
|
} while (!error && !wakeup && need_suspend_ops(state)
|
|
&& suspend_ops->suspend_again && suspend_ops->suspend_again());
|
|
|
|
Resume_devices:
|
|
suspend_test_start();
|
|
dpm_resume_end(PMSG_RESUME);
|
|
suspend_test_finish("resume devices");
|
|
resume_console();
|
|
Close:
|
|
if (need_suspend_ops(state) && suspend_ops->end)
|
|
suspend_ops->end();
|
|
trace_machine_suspend(PWR_EVENT_EXIT);
|
|
return error;
|
|
|
|
Recover_platform:
|
|
if (need_suspend_ops(state) && suspend_ops->recover)
|
|
suspend_ops->recover();
|
|
goto Resume_devices;
|
|
}
|
|
|
|
/**
|
|
* suspend_finish - Clean up before finishing the suspend sequence.
|
|
*
|
|
* Call platform code to clean up, restart processes, and free the console that
|
|
* we've allocated. This routine is not called for hibernation.
|
|
*/
|
|
static void suspend_finish(void)
|
|
{
|
|
suspend_thaw_processes();
|
|
pm_notifier_call_chain(PM_POST_SUSPEND);
|
|
pm_restore_console();
|
|
}
|
|
|
|
/**
|
|
* enter_state - Do common work needed to enter system sleep state.
|
|
* @state: System sleep state to enter.
|
|
*
|
|
* Make sure that no one else is trying to put the system into a sleep state.
|
|
* Fail if that's not the case. Otherwise, prepare for system suspend, make the
|
|
* system enter the given sleep state and clean up after wakeup.
|
|
*/
|
|
static int enter_state(suspend_state_t state)
|
|
{
|
|
int error;
|
|
|
|
if (!valid_state(state))
|
|
return -ENODEV;
|
|
|
|
if (!mutex_trylock(&pm_mutex))
|
|
return -EBUSY;
|
|
|
|
if (state == PM_SUSPEND_FREEZE)
|
|
freeze_begin();
|
|
|
|
printk(KERN_INFO "PM: Syncing filesystems ... ");
|
|
sys_sync();
|
|
printk("done.\n");
|
|
|
|
pr_debug("PM: Preparing system for %s sleep\n", pm_states[state]);
|
|
error = suspend_prepare(state);
|
|
if (error)
|
|
goto Unlock;
|
|
|
|
if (suspend_test(TEST_FREEZER))
|
|
goto Finish;
|
|
|
|
pr_debug("PM: Entering %s sleep\n", pm_states[state]);
|
|
pm_restrict_gfp_mask();
|
|
error = suspend_devices_and_enter(state);
|
|
pm_restore_gfp_mask();
|
|
|
|
Finish:
|
|
pr_debug("PM: Finishing wakeup.\n");
|
|
suspend_finish();
|
|
Unlock:
|
|
mutex_unlock(&pm_mutex);
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
* pm_suspend - Externally visible function for suspending the system.
|
|
* @state: System sleep state to enter.
|
|
*
|
|
* Check if the value of @state represents one of the supported states,
|
|
* execute enter_state() and update system suspend statistics.
|
|
*/
|
|
int pm_suspend(suspend_state_t state)
|
|
{
|
|
int error;
|
|
|
|
if (state <= PM_SUSPEND_ON || state >= PM_SUSPEND_MAX)
|
|
return -EINVAL;
|
|
|
|
error = enter_state(state);
|
|
if (error) {
|
|
suspend_stats.fail++;
|
|
dpm_save_failed_errno(error);
|
|
} else {
|
|
suspend_stats.success++;
|
|
}
|
|
return error;
|
|
}
|
|
EXPORT_SYMBOL(pm_suspend);
|