Skip to content

Commit

Permalink
power: Adds functionality to log the last suspend abort reason.
Browse files Browse the repository at this point in the history
Extends the last_resume_reason to log suspend abort reason. The abort
reasons will have 'Abort:' appended at the start to distinguish itself
from the resume reason.

Signed-off-by: Ruchi Kandoi <kandoiruchi@google.com>
  • Loading branch information
Ruchi Kandoi authored and javilonas committed Jun 9, 2015
1 parent a22b039 commit a74a6d4
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 32 deletions.
13 changes: 13 additions & 0 deletions drivers/base/power/main.c
Expand Up @@ -30,6 +30,7 @@
#include <linux/suspend.h>
#include <linux/cpuidle.h>
#include <linux/timer.h>
#include <linux/wakeup_reason.h>

#include "../base.h"
#include "power.h"
Expand Down Expand Up @@ -938,6 +939,7 @@ static int device_suspend_noirq(struct device *dev, pm_message_t state)
static int dpm_suspend_noirq(pm_message_t state)
{
ktime_t starttime = ktime_get();
char suspend_abort[MAX_SUSPEND_ABORT_LEN];
int error = 0;

cpuidle_pause();
Expand Down Expand Up @@ -965,6 +967,9 @@ static int dpm_suspend_noirq(pm_message_t state)
put_device(dev);

if (pm_wakeup_pending()) {
pm_get_active_wakeup_sources(suspend_abort,
MAX_SUSPEND_ABORT_LEN);
log_suspend_abort_reason(suspend_abort);
error = -EBUSY;
break;
}
Expand Down Expand Up @@ -1028,6 +1033,7 @@ static int device_suspend_late(struct device *dev, pm_message_t state)
static int dpm_suspend_late(pm_message_t state)
{
ktime_t starttime = ktime_get();
char suspend_abort[MAX_SUSPEND_ABORT_LEN];
int error = 0;

mutex_lock(&dpm_list_mtx);
Expand All @@ -1053,6 +1059,9 @@ static int dpm_suspend_late(pm_message_t state)
put_device(dev);

if (pm_wakeup_pending()) {
pm_get_active_wakeup_sources(suspend_abort,
MAX_SUSPEND_ABORT_LEN);
log_suspend_abort_reason(suspend_abort);
error = -EBUSY;
break;
}
Expand Down Expand Up @@ -1120,6 +1129,7 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
char *info = NULL;
int error = 0;
struct dpm_watchdog wd;
char suspend_abort[MAX_SUSPEND_ABORT_LEN];

dpm_wait_for_children(dev, async);

Expand All @@ -1136,6 +1146,9 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
pm_wakeup_event(dev, 0);

if (pm_wakeup_pending()) {
pm_get_active_wakeup_sources(suspend_abort,
MAX_SUSPEND_ABORT_LEN);
log_suspend_abort_reason(suspend_abort);
async_error = -EBUSY;
goto Complete;
}
Expand Down
16 changes: 16 additions & 0 deletions drivers/base/power/wakeup.c
Expand Up @@ -659,6 +659,22 @@ void pm_wakeup_event(struct device *dev, unsigned int msec)
}
EXPORT_SYMBOL_GPL(pm_wakeup_event);

void pm_get_active_wakeup_sources(char *pending_wakeup_source, size_t max)
{
struct wakeup_source *ws;
int len = 0;
rcu_read_lock();
len += snprintf(pending_wakeup_source, max, "Pending Wakeup Sources: ");
list_for_each_entry_rcu(ws, &wakeup_sources, entry) {
if (ws->active) {
len += snprintf(pending_wakeup_source + len, max,
"%s ", ws->name);
}
}
rcu_read_unlock();
}
EXPORT_SYMBOL_GPL(pm_get_active_wakeup_sources);

static void print_active_wakeup_sources(void)
{
struct wakeup_source *ws;
Expand Down
3 changes: 3 additions & 0 deletions drivers/base/syscore.c
Expand Up @@ -10,6 +10,7 @@
#include <linux/mutex.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/wakeup_reason.h>

static LIST_HEAD(syscore_ops_list);
static DEFINE_MUTEX(syscore_ops_lock);
Expand Down Expand Up @@ -73,6 +74,8 @@ int syscore_suspend(void)
return 0;

err_out:
log_suspend_abort_reason("System core suspend callback %pF failed",
ops->suspend);
pr_err("PM: System core suspend callback %pF failed.\n", ops->suspend);

list_for_each_entry_continue(ops, &syscore_ops_list, node)
Expand Down
2 changes: 1 addition & 1 deletion include/linux/suspend.h
Expand Up @@ -363,7 +363,7 @@ extern bool pm_wakeup_pending(void);
extern bool pm_get_wakeup_count(unsigned int *count, bool block);
extern bool pm_save_wakeup_count(unsigned int count);
extern void pm_wakep_autosleep_enabled(bool set);

extern void pm_get_active_wakeup_sources(char *pending_sources, size_t max);
static inline void lock_system_sleep(void)
{
current->flags |= PF_FREEZER_SKIP;
Expand Down
4 changes: 3 additions & 1 deletion include/linux/wakeup_reason.h
Expand Up @@ -18,6 +18,8 @@
#ifndef _LINUX_WAKEUP_REASON_H
#define _LINUX_WAKEUP_REASON_H

void log_wakeup_reason(int irq);
#define MAX_SUSPEND_ABORT_LEN 256

void log_wakeup_reason(int irq);
void log_suspend_abort_reason(const char *fmt, ...);
#endif /* _LINUX_WAKEUP_REASON_H */
7 changes: 6 additions & 1 deletion kernel/irq/pm.c
Expand Up @@ -10,7 +10,7 @@
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/syscore_ops.h>

#include <linux/wakeup_reason.h>
#include "internals.h"

/**
Expand Down Expand Up @@ -100,11 +100,16 @@ EXPORT_SYMBOL_GPL(resume_device_irqs);
int check_wakeup_irqs(void)
{
struct irq_desc *desc;
char suspend_abort[MAX_SUSPEND_ABORT_LEN];
int irq;

for_each_irq_desc(irq, desc) {
if (irqd_is_wakeup_set(&desc->irq_data)) {
if (desc->istate & IRQS_PENDING) {
log_suspend_abort_reason("Wakeup IRQ %d %s pending",
irq,
desc->action && desc->action->name ?
desc->action->name : "");
pr_info("Wakeup IRQ %d %s pending, suspend aborted\n",
irq,
desc->action && desc->action->name ?
Expand Down
6 changes: 5 additions & 1 deletion kernel/power/process.c
Expand Up @@ -17,7 +17,7 @@
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/kmod.h>

#include <linux/wakeup_reason.h>
/*
* Timeout for stopping processes
*/
Expand All @@ -37,6 +37,7 @@ static int try_to_freeze_tasks(bool user_only)
unsigned int elapsed_msecs;
bool wakeup = false;
int sleep_usecs = USEC_PER_MSEC;
char suspend_abort[MAX_SUSPEND_ABORT_LEN];

do_gettimeofday(&start);

Expand Down Expand Up @@ -70,6 +71,9 @@ static int try_to_freeze_tasks(bool user_only)
break;

if (pm_wakeup_pending()) {
pm_get_active_wakeup_sources(suspend_abort,
MAX_SUSPEND_ABORT_LEN);
log_suspend_abort_reason(suspend_abort);
wakeup = true;
break;
}
Expand Down
49 changes: 29 additions & 20 deletions kernel/power/suspend.c
Expand Up @@ -28,6 +28,7 @@
#include <linux/ftrace.h>
#include <linux/rtc.h>
#include <trace/events/power.h>
#include <linux/wakeup_reason.h>

#include "power.h"

Expand Down Expand Up @@ -66,6 +67,16 @@ void freeze_wake(void)
}
EXPORT_SYMBOL_GPL(freeze_wake);

static bool valid_state(suspend_state_t state)
{
/*
* PM_SUSPEND_STANDBY and PM_SUSPEND_MEM states need low level
* support and need to be valid to the low level
* implementation, no valid callback implies that none are valid.
*/
return suspend_ops && suspend_ops->valid && suspend_ops->valid(state);
}

/**
* suspend_set_ops - Set the global suspend method table.
* @ops: Suspend operations to use.
Expand All @@ -84,18 +95,6 @@ void suspend_set_ops(const struct platform_suspend_ops *ops)
}
EXPORT_SYMBOL_GPL(suspend_set_ops);

bool valid_state(suspend_state_t state)
{
if (state == PM_SUSPEND_FREEZE)
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.
*
Expand Down Expand Up @@ -144,7 +143,7 @@ static int suspend_prepare(suspend_state_t state)
error = suspend_freeze_processes();
if (!error)
return 0;

log_suspend_abort_reason("One or more tasks refusing to freeze");
suspend_stats.failed_freeze++;
dpm_save_failed_step(SUSPEND_FREEZE);
Finish:
Expand Down Expand Up @@ -174,7 +173,8 @@ void __attribute__ ((weak)) arch_suspend_enable_irqs(void)
*/
static int suspend_enter(suspend_state_t state, bool *wakeup)
{
int error;
char suspend_abort[MAX_SUSPEND_ABORT_LEN];
int error, last_dev;

if (need_suspend_ops(state) && suspend_ops->prepare) {
error = suspend_ops->prepare();
Expand All @@ -184,7 +184,11 @@ static int suspend_enter(suspend_state_t state, bool *wakeup)

error = dpm_suspend_end(PMSG_SUSPEND);
if (error) {
last_dev = suspend_stats.last_failed_dev + REC_FAILED_NUM - 1;
last_dev %= REC_FAILED_NUM;
printk(KERN_ERR "PM: Some devices failed to power down\n");
log_suspend_abort_reason("%s device failed to power down",
suspend_stats.failed_devs[last_dev]);
goto Platform_finish;
}

Expand All @@ -194,6 +198,9 @@ static int suspend_enter(suspend_state_t state, bool *wakeup)
goto Platform_wake;
}

if (suspend_test(TEST_PLATFORM))
goto Platform_wake;

/*
* PM_SUSPEND_FREEZE equals
* frozen processes + suspended devices + idle processors.
Expand All @@ -205,12 +212,11 @@ static int suspend_enter(suspend_state_t state, bool *wakeup)
goto Platform_wake;
}

if (suspend_test(TEST_PLATFORM))
goto Platform_wake;

error = disable_nonboot_cpus();
if (error || suspend_test(TEST_CPUS))
if (error || suspend_test(TEST_CPUS)) {
log_suspend_abort_reason("Disabling non-boot cpus failed");
goto Enable_cpus;
}

arch_suspend_disable_irqs();
BUG_ON(!irqs_disabled());
Expand All @@ -221,8 +227,10 @@ static int suspend_enter(suspend_state_t state, bool *wakeup)
if (!(suspend_test(TEST_CORE) || *wakeup)) {
error = suspend_ops->enter(state);
events_check_enabled = false;
} else if (*wakeup) {
error = -EBUSY;
} else {
pm_get_active_wakeup_sources(suspend_abort,
MAX_SUSPEND_ABORT_LEN);
log_suspend_abort_reason(suspend_abort);
}
syscore_resume();
}
Expand Down Expand Up @@ -270,6 +278,7 @@ int suspend_devices_and_enter(suspend_state_t state)
error = dpm_suspend_start(PMSG_SUSPEND);
if (error) {
printk(KERN_ERR "PM: Some devices failed to suspend\n");
log_suspend_abort_reason("Some devices failed to suspend");
goto Recover_platform;
}
suspend_test_finish("suspend devices");
Expand Down
42 changes: 34 additions & 8 deletions kernel/power/wakeup_reason.c
Expand Up @@ -31,6 +31,8 @@
#define MAX_WAKEUP_REASON_IRQS 32
static int irq_list[MAX_WAKEUP_REASON_IRQS];
static int irqcount;
static bool suspend_abort;
static char abort_reason[MAX_SUSPEND_ABORT_LEN];
static struct kobject *wakeup_reason;
static spinlock_t resume_reason_lock;

Expand All @@ -40,14 +42,18 @@ static ssize_t last_resume_reason_show(struct kobject *kobj, struct kobj_attribu
int irq_no, buf_offset = 0;
struct irq_desc *desc;
spin_lock(&resume_reason_lock);
for (irq_no = 0; irq_no < irqcount; irq_no++) {
desc = irq_to_desc(irq_list[irq_no]);
if (desc && desc->action && desc->action->name)
buf_offset += sprintf(buf + buf_offset, "%d %s\n",
irq_list[irq_no], desc->action->name);
else
buf_offset += sprintf(buf + buf_offset, "%d\n",
irq_list[irq_no]);
if (suspend_abort) {
buf_offset = sprintf(buf, "Abort: %s", abort_reason);
} else {
for (irq_no = 0; irq_no < irqcount; irq_no++) {
desc = irq_to_desc(irq_list[irq_no]);
if (desc && desc->action && desc->action->name)
buf_offset += sprintf(buf + buf_offset, "%d %s\n",
irq_list[irq_no], desc->action->name);
else
buf_offset += sprintf(buf + buf_offset, "%d\n",
irq_list[irq_no]);
}
}
spin_unlock(&resume_reason_lock);
return buf_offset;
Expand Down Expand Up @@ -89,6 +95,25 @@ void log_wakeup_reason(int irq)
spin_unlock(&resume_reason_lock);
}

void log_suspend_abort_reason(const char *fmt, ...)
{
va_list args;

spin_lock(&resume_reason_lock);

//Suspend abort reason has already been logged.
if (suspend_abort) {
spin_unlock(&resume_reason_lock);
return;
}

suspend_abort = true;
va_start(args, fmt);
snprintf(abort_reason, MAX_SUSPEND_ABORT_LEN, fmt, args);
va_end(args);
spin_unlock(&resume_reason_lock);
}

/* Detects a suspend and clears all the previous wake up reasons*/
static int wakeup_reason_pm_event(struct notifier_block *notifier,
unsigned long pm_event, void *unused)
Expand All @@ -97,6 +122,7 @@ static int wakeup_reason_pm_event(struct notifier_block *notifier,
case PM_SUSPEND_PREPARE:
spin_lock(&resume_reason_lock);
irqcount = 0;
suspend_abort = false;
spin_unlock(&resume_reason_lock);
break;
default:
Expand Down

0 comments on commit a74a6d4

Please sign in to comment.