kernel/power: Import Sony Wakelock
*52.1.A.3.74 Signed-off-by: Sorayukii <sorayukii69@gmail.com>
This commit is contained in:
parent
57895da888
commit
7ee5becf00
8 changed files with 825 additions and 4 deletions
|
|
@ -34,6 +34,10 @@
|
|||
#include <linux/cpuidle.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/wakeup_reason.h>
|
||||
#ifdef CONFIG_PM_WAKEUP_TIMES
|
||||
#include <linux/math64.h>
|
||||
#include <linux/wait.h>
|
||||
#endif
|
||||
|
||||
#include "../base.h"
|
||||
#include "power.h"
|
||||
|
|
@ -57,6 +61,11 @@ static LIST_HEAD(dpm_late_early_list);
|
|||
static LIST_HEAD(dpm_noirq_list);
|
||||
|
||||
struct suspend_stats suspend_stats;
|
||||
#ifdef CONFIG_PM_WAKEUP_TIMES
|
||||
struct suspend_stats_queue suspend_stats_queue;
|
||||
static ktime_t suspend_start_time;
|
||||
static ktime_t resume_start_time;
|
||||
#endif
|
||||
static DEFINE_MUTEX(dpm_list_mtx);
|
||||
static pm_message_t pm_transition;
|
||||
|
||||
|
|
@ -383,6 +392,100 @@ static void dpm_show_time(ktime_t starttime, pm_message_t state, char *info)
|
|||
usecs / USEC_PER_MSEC, usecs % USEC_PER_MSEC);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_WAKEUP_TIMES
|
||||
void dpm_log_start_time(pm_message_t state)
|
||||
{
|
||||
switch (state.event) {
|
||||
case PM_EVENT_RESUME:
|
||||
resume_start_time = ktime_get_boottime();
|
||||
break;
|
||||
case PM_EVENT_SUSPEND:
|
||||
suspend_start_time = ktime_get_boottime();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dpm_log_start_time);
|
||||
|
||||
void dpm_log_wakeup_stats(pm_message_t state)
|
||||
{
|
||||
ktime_t *start_time, *avg_time, end_time, duration, prev_duration, sum;
|
||||
struct stats_wakeup_time *min_time, *max_time, *last_time, prev;
|
||||
u64 avg_ns;
|
||||
char buf[32] = {0};
|
||||
unsigned int nr = 0;
|
||||
|
||||
switch (state.event) {
|
||||
case PM_EVENT_RESUME:
|
||||
snprintf(buf, sizeof(buf), "%s", "resume time:");
|
||||
start_time = &resume_start_time;
|
||||
min_time = &suspend_stats.resume_min_time;
|
||||
max_time = &suspend_stats.resume_max_time;
|
||||
last_time = &suspend_stats.resume_last_time;
|
||||
avg_time = &suspend_stats.resume_avg_time;
|
||||
break;
|
||||
case PM_EVENT_SUSPEND:
|
||||
snprintf(buf, sizeof(buf), "%s", "suspend time:");
|
||||
start_time = &suspend_start_time;
|
||||
min_time = &suspend_stats.suspend_min_time;
|
||||
max_time = &suspend_stats.suspend_max_time;
|
||||
last_time = &suspend_stats.suspend_last_time;
|
||||
avg_time = &suspend_stats.suspend_avg_time;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ktime_to_ns(*start_time))
|
||||
return;
|
||||
|
||||
/* Calculate duration and update last time */
|
||||
end_time = ktime_get_boottime();
|
||||
prev = *last_time;
|
||||
prev_duration = ktime_sub(prev.end, prev.start);
|
||||
last_time->end = end_time;
|
||||
last_time->start = *start_time;
|
||||
duration = ktime_sub(end_time, *start_time);
|
||||
|
||||
/* Update max time */
|
||||
if (ktime_compare(duration,
|
||||
ktime_sub(max_time->end, max_time->start)) > 0)
|
||||
*max_time = *last_time;
|
||||
|
||||
/* Update min time */
|
||||
if (!ktime_to_ns(ktime_sub(min_time->end, min_time->start)))
|
||||
*min_time = *last_time;
|
||||
|
||||
if (ktime_compare(duration,
|
||||
ktime_sub(min_time->end, min_time->start)) < 0)
|
||||
*min_time = *last_time;
|
||||
|
||||
/* Compute the avg of current, previous and previous average times */
|
||||
if (ktime_to_ns(prev_duration))
|
||||
nr++;
|
||||
|
||||
if (ktime_to_ns(*avg_time))
|
||||
nr++;
|
||||
|
||||
sum = ktime_add(ktime_add(*avg_time, prev_duration), duration);
|
||||
avg_ns = div_u64(ktime_to_ns(sum), (nr + 1));
|
||||
*avg_time = ktime_set(0, avg_ns);
|
||||
*start_time = ktime_set(0, 0);
|
||||
|
||||
pr_debug("%s\n%s %llums\n%s %llums\n %s %llums\n%s %llums\n", buf,
|
||||
" min:",
|
||||
ktime_to_ms(ktime_sub(min_time->end, min_time->start)),
|
||||
" max:",
|
||||
ktime_to_ms(ktime_sub(max_time->end, max_time->start)),
|
||||
" last:", ktime_to_ms(duration),
|
||||
" avg:", ktime_to_ms(*avg_time));
|
||||
suspend_stats_queue.resume_done = 1;
|
||||
wake_up(&suspend_stats_queue.wait_queue);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dpm_log_wakeup_stats);
|
||||
#endif
|
||||
|
||||
static int dpm_run_callback(pm_callback_t cb, struct device *dev,
|
||||
pm_message_t state, char *info)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -704,6 +704,11 @@ extern int dpm_suspend_late(pm_message_t state);
|
|||
extern int dpm_suspend(pm_message_t state);
|
||||
extern int dpm_prepare(pm_message_t state);
|
||||
|
||||
#ifdef CONFIG_PM_WAKEUP_TIMES
|
||||
extern void dpm_log_start_time(pm_message_t state);
|
||||
extern void dpm_log_wakeup_stats(pm_message_t state);
|
||||
#endif
|
||||
|
||||
extern void __suspend_report_result(const char *function, void *fn, int ret);
|
||||
|
||||
#define suspend_report_result(fn, ret) \
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
#include <linux/pm.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/freezer.h>
|
||||
#ifdef CONFIG_PM_WAKEUP_TIMES
|
||||
#include <linux/ktime.h>
|
||||
#include <linux/wait.h>
|
||||
#endif
|
||||
#include <asm/errno.h>
|
||||
|
||||
#ifdef CONFIG_VT
|
||||
|
|
@ -50,6 +54,20 @@ enum suspend_stat_step {
|
|||
SUSPEND_RESUME
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PM_WAKEUP_TIMES
|
||||
struct stats_wakeup_time {
|
||||
ktime_t start;
|
||||
ktime_t end;
|
||||
};
|
||||
|
||||
struct suspend_stats_queue {
|
||||
int resume_done;
|
||||
wait_queue_head_t wait_queue;
|
||||
};
|
||||
|
||||
extern struct suspend_stats_queue suspend_stats_queue;
|
||||
#endif
|
||||
|
||||
struct suspend_stats {
|
||||
int success;
|
||||
int fail;
|
||||
|
|
@ -68,6 +86,16 @@ struct suspend_stats {
|
|||
int errno[REC_FAILED_NUM];
|
||||
int last_failed_step;
|
||||
enum suspend_stat_step failed_steps[REC_FAILED_NUM];
|
||||
#ifdef CONFIG_PM_WAKEUP_TIMES
|
||||
struct stats_wakeup_time suspend_min_time;
|
||||
struct stats_wakeup_time suspend_max_time;
|
||||
struct stats_wakeup_time suspend_last_time;
|
||||
ktime_t suspend_avg_time;
|
||||
struct stats_wakeup_time resume_min_time;
|
||||
struct stats_wakeup_time resume_max_time;
|
||||
struct stats_wakeup_time resume_last_time;
|
||||
ktime_t resume_avg_time;
|
||||
#endif
|
||||
};
|
||||
|
||||
extern struct suspend_stats suspend_stats;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,15 @@ config SUSPEND_SKIP_SYNC
|
|||
of suspend, or they are content with invoking sync() from
|
||||
user-space before invoking suspend. Say Y if that's your case.
|
||||
|
||||
config WAKELOCK
|
||||
bool "Android's method of preventing suspend"
|
||||
default y
|
||||
---help---
|
||||
This allows applications to prevent the CPU from suspending while
|
||||
they need it.
|
||||
|
||||
Say Y if you are running an android userspace.
|
||||
|
||||
config HIBERNATE_CALLBACKS
|
||||
bool
|
||||
|
||||
|
|
@ -229,6 +238,18 @@ config DPM_WATCHDOG_TIMEOUT
|
|||
default 120
|
||||
depends on DPM_WATCHDOG
|
||||
|
||||
config PM_WAKEUP_TIMES
|
||||
bool "Log time taken for device wakeup"
|
||||
def_bool y
|
||||
depends on PM && PM_SLEEP && DEBUG_FS
|
||||
---help---
|
||||
This enables code that calculates the total time spent for device
|
||||
suspend and resume. These statistics are then logged to
|
||||
<debugfs>/suspend_stats.
|
||||
|
||||
The statistics include minimum, maximum, average and the most recent
|
||||
times for resume and suspend.
|
||||
|
||||
config PM_TRACE
|
||||
bool
|
||||
help
|
||||
|
|
@ -340,3 +361,10 @@ config PM_GENERIC_DOMAINS_OF
|
|||
|
||||
config CPU_PM
|
||||
bool
|
||||
|
||||
config WAKEUP_IRQ_DEBUG
|
||||
bool "Debug info of wakeup irq"
|
||||
default n
|
||||
---help---
|
||||
This enables debug information about wakeup interrupts using
|
||||
sysfs (/sys/kernel/).
|
||||
|
|
|
|||
|
|
@ -16,3 +16,4 @@ obj-$(CONFIG_PM_WAKELOCKS) += wakelock.o
|
|||
obj-$(CONFIG_MAGIC_SYSRQ) += poweroff.o
|
||||
|
||||
obj-$(CONFIG_SUSPEND) += wakeup_reason.o
|
||||
obj-$(CONFIG_WAKEUP_IRQ_DEBUG) += wakeup_irq_debug.o
|
||||
|
|
|
|||
|
|
@ -15,12 +15,48 @@
|
|||
#include <linux/workqueue.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/random.h>
|
||||
#ifdef CONFIG_PM_WAKEUP_TIMES
|
||||
#include <linux/poll.h>
|
||||
#endif
|
||||
|
||||
#include "power.h"
|
||||
|
||||
DEFINE_MUTEX(pm_mutex);
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
/*
|
||||
* Enable/Disable suspend back off logic attribute
|
||||
*/
|
||||
static bool sbo_enabled = true;
|
||||
|
||||
/*
|
||||
* For how many percent an alive time is decayed
|
||||
* if suspend back-off keeps ongoing.
|
||||
*/
|
||||
static u32 sbo_decay_value = 20;
|
||||
|
||||
/*
|
||||
* Initial max back-off alive time
|
||||
*/
|
||||
static u32 sbo_initial_alive_time_msecs = 10000;
|
||||
|
||||
/*
|
||||
* A time in milliseconds, before which a short
|
||||
* sleep counter is increased to trigger a back-off.
|
||||
*/
|
||||
static u32 sbo_short_sleep_msecs = 1100;
|
||||
|
||||
/*
|
||||
* This variable regulates starting of suspend
|
||||
* back off functionality, after counter is reached
|
||||
* specified value.
|
||||
*/
|
||||
static u32 sbo_short_sleep_count = 10;
|
||||
|
||||
static u32 decay_alive_time_ms;
|
||||
static u32 suspend_short_count;
|
||||
static struct wakeup_source *ws;
|
||||
|
||||
/* Routines for PM-transition notifications */
|
||||
|
||||
|
|
@ -215,6 +251,51 @@ static int suspend_stats_show(struct seq_file *s, void *unused)
|
|||
suspend_stats.failed_steps[index]));
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_WAKEUP_TIMES
|
||||
seq_printf(s, "%s\n%s %lldms (%s %lldms %s %lldms)\n" \
|
||||
"%s %lldms (%s %lldms %s %lldms)\n" \
|
||||
"%s %lldms (%s %lldms %s %lldms)\n" \
|
||||
"%s %lldms\n",
|
||||
"suspend time:",
|
||||
" min:", ktime_to_ms(ktime_sub(
|
||||
suspend_stats.suspend_min_time.end,
|
||||
suspend_stats.suspend_min_time.start)),
|
||||
"start:", ktime_to_ms(suspend_stats.suspend_min_time.start),
|
||||
"end:", ktime_to_ms(suspend_stats.suspend_min_time.end),
|
||||
" max:", ktime_to_ms(ktime_sub(
|
||||
suspend_stats.suspend_max_time.end,
|
||||
suspend_stats.suspend_max_time.start)),
|
||||
"start:", ktime_to_ms(suspend_stats.suspend_max_time.start),
|
||||
"end:", ktime_to_ms(suspend_stats.suspend_max_time.end),
|
||||
" last:", ktime_to_ms(ktime_sub(
|
||||
suspend_stats.suspend_last_time.end,
|
||||
suspend_stats.suspend_last_time.start)),
|
||||
"start:", ktime_to_ms(suspend_stats.suspend_last_time.start),
|
||||
"end:", ktime_to_ms(suspend_stats.suspend_last_time.end),
|
||||
" avg:", ktime_to_ms(suspend_stats.suspend_avg_time));
|
||||
|
||||
seq_printf(s, "%s\n%s %lldms (%s %lldms %s %lldms)\n" \
|
||||
"%s %lldms (%s %lldms %s %lldms)\n" \
|
||||
"%s %lldms (%s %lldms %s %lldms)\n" \
|
||||
"%s %lldms\n",
|
||||
"resume time:",
|
||||
" min:", ktime_to_ms(ktime_sub(
|
||||
suspend_stats.resume_min_time.end,
|
||||
suspend_stats.resume_min_time.start)),
|
||||
"start:", ktime_to_ms(suspend_stats.resume_min_time.start),
|
||||
"end:", ktime_to_ms(suspend_stats.resume_min_time.end),
|
||||
" max:", ktime_to_ms(ktime_sub(
|
||||
suspend_stats.resume_max_time.end,
|
||||
suspend_stats.resume_max_time.start)),
|
||||
"start:", ktime_to_ms(suspend_stats.resume_max_time.start),
|
||||
"end:", ktime_to_ms(suspend_stats.resume_max_time.end),
|
||||
" last:", ktime_to_ms(ktime_sub(
|
||||
suspend_stats.resume_last_time.end,
|
||||
suspend_stats.resume_last_time.start)),
|
||||
"start:", ktime_to_ms(suspend_stats.resume_last_time.start),
|
||||
"end:", ktime_to_ms(suspend_stats.resume_last_time.end),
|
||||
" avg:", ktime_to_ms(suspend_stats.resume_avg_time));
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -223,10 +304,29 @@ static int suspend_stats_open(struct inode *inode, struct file *file)
|
|||
return single_open(file, suspend_stats_show, NULL);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_WAKEUP_TIMES
|
||||
static unsigned int suspend_stats_poll(struct file *filp,
|
||||
struct poll_table_struct *wait)
|
||||
{
|
||||
unsigned int mask = 0;
|
||||
|
||||
poll_wait(filp, &suspend_stats_queue.wait_queue, wait);
|
||||
if (suspend_stats_queue.resume_done) {
|
||||
mask |= (POLLIN | POLLRDNORM);
|
||||
suspend_stats_queue.resume_done = 0;
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct file_operations suspend_stats_operations = {
|
||||
.open = suspend_stats_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
#ifdef CONFIG_PM_WAKEUP_TIMES
|
||||
.poll = suspend_stats_poll,
|
||||
#endif
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
|
|
@ -234,6 +334,9 @@ static int __init pm_debugfs_init(void)
|
|||
{
|
||||
debugfs_create_file("suspend_stats", S_IFREG | S_IRUGO,
|
||||
NULL, NULL, &suspend_stats_operations);
|
||||
#ifdef CONFIG_PM_WAKEUP_TIMES
|
||||
init_waitqueue_head(&suspend_stats_queue.wait_queue);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -352,10 +455,147 @@ static suspend_state_t decode_state(const char *buf, size_t n)
|
|||
return PM_SUSPEND_ON;
|
||||
}
|
||||
|
||||
static inline u32
|
||||
decay_val(u32 val, u32 percent)
|
||||
{
|
||||
u32 ratio;
|
||||
|
||||
percent = clamp(percent, 0U, 100U);
|
||||
ratio = 1024 - ((1024 * percent) / 100);
|
||||
|
||||
return (val * ratio) / 1024;
|
||||
}
|
||||
|
||||
static ssize_t sbo_decay_value_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t n)
|
||||
{
|
||||
unsigned long val;
|
||||
|
||||
if (kstrtoul(buf, 10, &val))
|
||||
return -EINVAL;
|
||||
|
||||
if (val > 100)
|
||||
return -EINVAL;
|
||||
|
||||
sbo_decay_value = val;
|
||||
return n;
|
||||
}
|
||||
|
||||
static ssize_t sbo_decay_value_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", sbo_decay_value);
|
||||
}
|
||||
|
||||
power_attr(sbo_decay_value);
|
||||
|
||||
static ssize_t sbo_short_sleep_msecs_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t n)
|
||||
{
|
||||
unsigned long val;
|
||||
|
||||
if (kstrtoul(buf, 10, &val))
|
||||
return -EINVAL;
|
||||
|
||||
sbo_short_sleep_msecs = val;
|
||||
return n;
|
||||
}
|
||||
|
||||
static ssize_t sbo_short_sleep_msecs_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", sbo_short_sleep_msecs);
|
||||
}
|
||||
|
||||
power_attr(sbo_short_sleep_msecs);
|
||||
|
||||
static ssize_t sbo_short_sleep_count_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t n)
|
||||
{
|
||||
unsigned long val;
|
||||
|
||||
if (kstrtoul(buf, 10, &val))
|
||||
return -EINVAL;
|
||||
|
||||
sbo_short_sleep_count = val;
|
||||
return n;
|
||||
}
|
||||
|
||||
static ssize_t sbo_short_sleep_count_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", sbo_short_sleep_count);
|
||||
}
|
||||
|
||||
power_attr(sbo_short_sleep_count);
|
||||
|
||||
static ssize_t sbo_initial_alive_time_msecs_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t n)
|
||||
{
|
||||
unsigned long val;
|
||||
|
||||
if (kstrtoul(buf, 10, &val))
|
||||
return -EINVAL;
|
||||
|
||||
sbo_initial_alive_time_msecs = val;
|
||||
return n;
|
||||
}
|
||||
|
||||
static ssize_t sbo_initial_alive_time_msecs_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", sbo_initial_alive_time_msecs);
|
||||
}
|
||||
|
||||
power_attr(sbo_initial_alive_time_msecs);
|
||||
|
||||
static ssize_t sbo_enabled_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t n)
|
||||
{
|
||||
unsigned long val;
|
||||
|
||||
if (kstrtoul(buf, 10, &val))
|
||||
return -EINVAL;
|
||||
|
||||
if (val > 1)
|
||||
return -EINVAL;
|
||||
|
||||
sbo_enabled = !!val;
|
||||
return n;
|
||||
}
|
||||
|
||||
static ssize_t sbo_enabled_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", sbo_enabled);
|
||||
}
|
||||
|
||||
power_attr(sbo_enabled);
|
||||
|
||||
static void
|
||||
suspend_backoff(u32 timeout_msecs)
|
||||
{
|
||||
if (!sbo_enabled)
|
||||
return;
|
||||
|
||||
pr_info("suspend: too many immediate wakeups, back off (%u msecs)\n",
|
||||
timeout_msecs);
|
||||
|
||||
__pm_wakeup_event(ws, timeout_msecs);
|
||||
}
|
||||
|
||||
static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
const char *buf, size_t n)
|
||||
{
|
||||
suspend_state_t state;
|
||||
struct timespec ts_entry, ts_exit;
|
||||
u64 elapsed_msecs64;
|
||||
u32 elapsed_msecs32;
|
||||
int error;
|
||||
|
||||
error = pm_autosleep_lock();
|
||||
|
|
@ -368,9 +608,48 @@ static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|||
}
|
||||
|
||||
state = decode_state(buf, n);
|
||||
if (state < PM_SUSPEND_MAX)
|
||||
if (state < PM_SUSPEND_MAX) {
|
||||
/*
|
||||
* We want to prevent system from frequent periodic wake-ups
|
||||
* when sleeping time is less or equal certain interval.
|
||||
* It's done in order to save power in certain cases, one of
|
||||
* the examples is GPS tracking, but not only.
|
||||
*/
|
||||
getnstimeofday(&ts_entry);
|
||||
error = pm_suspend(state);
|
||||
else if (state == PM_SUSPEND_MAX)
|
||||
getnstimeofday(&ts_exit);
|
||||
|
||||
elapsed_msecs64 = timespec_to_ns(&ts_exit) -
|
||||
timespec_to_ns(&ts_entry);
|
||||
do_div(elapsed_msecs64, NSEC_PER_MSEC);
|
||||
elapsed_msecs32 = elapsed_msecs64;
|
||||
|
||||
if (elapsed_msecs32 <= sbo_short_sleep_msecs) {
|
||||
if (suspend_short_count == sbo_short_sleep_count) {
|
||||
if (decay_alive_time_ms >= MSEC_PER_SEC) {
|
||||
suspend_backoff(decay_alive_time_ms);
|
||||
decay_alive_time_ms =
|
||||
decay_val(decay_alive_time_ms,
|
||||
sbo_decay_value);
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
suspend_short_count++;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/* Start from scratch */
|
||||
suspend_short_count = 0;
|
||||
|
||||
/*
|
||||
* Randomize a bit an initial alive time value to be
|
||||
* not synced with any wake up sources, for example
|
||||
* IRQs.
|
||||
*/
|
||||
decay_alive_time_ms = sbo_initial_alive_time_msecs -
|
||||
get_random_int() % (sbo_initial_alive_time_msecs / 10);
|
||||
} else if (state == PM_SUSPEND_MAX)
|
||||
error = hibernate();
|
||||
else
|
||||
error = -EINVAL;
|
||||
|
|
@ -602,6 +881,11 @@ static struct attribute * g[] = {
|
|||
#ifdef CONFIG_PM_SLEEP
|
||||
&pm_async_attr.attr,
|
||||
&wakeup_count_attr.attr,
|
||||
&sbo_enabled_attr.attr,
|
||||
&sbo_decay_value_attr.attr,
|
||||
&sbo_short_sleep_msecs_attr.attr,
|
||||
&sbo_short_sleep_count_attr.attr,
|
||||
&sbo_initial_alive_time_msecs_attr.attr,
|
||||
#ifdef CONFIG_PM_AUTOSLEEP
|
||||
&autosleep_attr.attr,
|
||||
#endif
|
||||
|
|
@ -632,7 +916,7 @@ EXPORT_SYMBOL_GPL(pm_wq);
|
|||
|
||||
static int __init pm_start_workqueue(void)
|
||||
{
|
||||
pm_wq = alloc_workqueue("pm", WQ_FREEZABLE, 0);
|
||||
pm_wq = alloc_workqueue("pm", WQ_FREEZABLE | WQ_MEM_RECLAIM, 0);
|
||||
|
||||
return pm_wq ? 0 : -ENOMEM;
|
||||
}
|
||||
|
|
@ -652,6 +936,10 @@ static int __init pm_init(void)
|
|||
if (error)
|
||||
return error;
|
||||
pm_print_times_init();
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
ws = wakeup_source_register("suspend_backoff");
|
||||
#endif
|
||||
return pm_autosleep_init();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -381,7 +381,9 @@ static int suspend_enter(suspend_state_t state, bool *wakeup)
|
|||
|
||||
arch_suspend_disable_irqs();
|
||||
BUG_ON(!irqs_disabled());
|
||||
|
||||
#ifdef CONFIG_PM_WAKEUP_TIMES
|
||||
dpm_log_wakeup_stats(PMSG_SUSPEND);
|
||||
#endif
|
||||
error = syscore_suspend();
|
||||
if (!error) {
|
||||
*wakeup = pm_wakeup_pending();
|
||||
|
|
@ -401,6 +403,9 @@ static int suspend_enter(suspend_state_t state, bool *wakeup)
|
|||
syscore_resume();
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_WAKEUP_TIMES
|
||||
dpm_log_start_time(PMSG_RESUME);
|
||||
#endif
|
||||
arch_suspend_enable_irqs();
|
||||
BUG_ON(irqs_disabled());
|
||||
|
||||
|
|
@ -440,6 +445,9 @@ int suspend_devices_and_enter(suspend_state_t state)
|
|||
|
||||
suspend_console();
|
||||
suspend_test_start();
|
||||
#ifdef CONFIG_PM_WAKEUP_TIMES
|
||||
dpm_log_start_time(PMSG_SUSPEND);
|
||||
#endif
|
||||
error = dpm_suspend_start(PMSG_SUSPEND);
|
||||
if (error) {
|
||||
pr_err("PM: Some devices failed to suspend, or early wake event detected\n");
|
||||
|
|
@ -457,6 +465,9 @@ int suspend_devices_and_enter(suspend_state_t state)
|
|||
Resume_devices:
|
||||
suspend_test_start();
|
||||
dpm_resume_end(PMSG_RESUME);
|
||||
#ifdef CONFIG_PM_WAKEUP_TIMES
|
||||
dpm_log_wakeup_stats(PMSG_RESUME);
|
||||
#endif
|
||||
suspend_test_finish("resume devices");
|
||||
trace_suspend_resume(TPS("resume_console"), state, true);
|
||||
resume_console();
|
||||
|
|
|
|||
357
kernel/power/wakeup_irq_debug.c
Normal file
357
kernel/power/wakeup_irq_debug.c
Normal file
|
|
@ -0,0 +1,357 @@
|
|||
/*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/kernel_stat.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/wakeup_reason.h>
|
||||
#include <linux/irq.h>
|
||||
|
||||
#define MODULE_NAME "wakeup_debug"
|
||||
|
||||
struct irq_info {
|
||||
struct list_head list;
|
||||
unsigned int wakeup_count;
|
||||
unsigned int irq_count;
|
||||
char irq_name[128];
|
||||
int irq;
|
||||
};
|
||||
|
||||
struct wakeup_irq {
|
||||
struct list_head head;
|
||||
bool enabled;
|
||||
};
|
||||
|
||||
static int update_irq_table(struct wakeup_irq *p, int irq)
|
||||
{
|
||||
struct irq_info *i;
|
||||
|
||||
list_for_each_entry(i, &p->head, list) {
|
||||
if (i->irq == irq) {
|
||||
i->irq_count = kstat_irqs_cpu(i->irq, 0);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void add_irq_to_table(struct wakeup_irq *p, int irq)
|
||||
{
|
||||
struct irq_info *info;
|
||||
struct irq_desc *desc;
|
||||
|
||||
/*
|
||||
* TODO: add here a filter for root IRQs
|
||||
*/
|
||||
info = kzalloc(sizeof(*info), GFP_KERNEL);
|
||||
info->irq_count = kstat_irqs_cpu(irq, 0);
|
||||
info->irq = irq;
|
||||
|
||||
desc = irq_to_desc(irq);
|
||||
|
||||
/*
|
||||
* IRQ can be disabled and freed, thus
|
||||
* we save its name because of history
|
||||
*/
|
||||
if (desc && desc->action && desc->action->name)
|
||||
(void) scnprintf(
|
||||
info->irq_name, sizeof(info->irq_name),
|
||||
"%s", desc->action->name);
|
||||
|
||||
list_add_tail(&info->list, &p->head);
|
||||
}
|
||||
|
||||
static int wakeup_irq_suspend(struct device *dev)
|
||||
{
|
||||
struct irq_desc *desc;
|
||||
struct wakeup_irq *p;
|
||||
int irq, rv;
|
||||
|
||||
/* get private data */
|
||||
p = dev_get_drvdata(dev);
|
||||
if (!p->enabled)
|
||||
goto leave;
|
||||
|
||||
/*
|
||||
* we want to add IRQs to list by reverse way,
|
||||
* therefore all potential triggered IRQ (nested)
|
||||
* will be in the beginning of the list
|
||||
*/
|
||||
for_each_irq_desc_reverse(irq, desc) {
|
||||
raw_spin_lock_irq(&desc->lock);
|
||||
rv = irqd_is_wakeup_set(&desc->irq_data);
|
||||
raw_spin_unlock_irq(&desc->lock);
|
||||
|
||||
if (rv == 0)
|
||||
continue;
|
||||
|
||||
rv = update_irq_table(p, irq);
|
||||
if (rv)
|
||||
continue;
|
||||
|
||||
add_irq_to_table(p, irq);
|
||||
dev_dbg(dev, "IRQ %d was added to the list\n", irq);
|
||||
}
|
||||
|
||||
leave:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wakeup_irq_resume(struct device *dev)
|
||||
{
|
||||
struct irq_info *i;
|
||||
struct wakeup_irq *p;
|
||||
struct irq_desc *desc;
|
||||
unsigned long flags;
|
||||
int diff;
|
||||
|
||||
/* get private data */
|
||||
p = dev_get_drvdata(dev);
|
||||
if (!p->enabled)
|
||||
goto leave;
|
||||
|
||||
/*
|
||||
* go through all registered IRQs in our list
|
||||
*/
|
||||
list_for_each_entry(i, &p->head, list) {
|
||||
desc = irq_to_desc(i->irq);
|
||||
if (desc) {
|
||||
raw_spin_lock_irqsave(&desc->lock, flags);
|
||||
diff = kstat_irqs_cpu(i->irq, 0) - i->irq_count;
|
||||
raw_spin_unlock_irqrestore(&desc->lock, flags);
|
||||
|
||||
if (diff > 0) {
|
||||
i->wakeup_count++;
|
||||
|
||||
dev_dbg(dev, "%d, wake-up count: %d, active: %d (%s)\n",
|
||||
i->irq, i->wakeup_count,
|
||||
irqd_is_wakeup_set(&desc->irq_data),
|
||||
desc->action && desc->action->name ?
|
||||
desc->action->name : "");
|
||||
|
||||
log_wakeup_reason(i->irq);
|
||||
update_irq_table(p, i->irq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
leave:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t irq_stat_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct wakeup_irq *p;
|
||||
struct irq_info *i;
|
||||
int len = 0;
|
||||
|
||||
p = dev_get_drvdata(dev);
|
||||
|
||||
/* build header of the output */
|
||||
len += scnprintf(buf + len, PAGE_SIZE - len,
|
||||
"IRQ\tCount\tName\n");
|
||||
|
||||
list_for_each_entry(i, &p->head, list) {
|
||||
if (i->wakeup_count) {
|
||||
len += scnprintf(buf + len, PAGE_SIZE - len,
|
||||
"%d\t%d\t%s\n",
|
||||
i->irq, i->wakeup_count, i->irq_name);
|
||||
/*
|
||||
* Ensure we do not write more than PAGE_SIZE
|
||||
* bytes of data including null terminator and
|
||||
* add '\n' symbol to the buffer if it's full
|
||||
*/
|
||||
if (len >= PAGE_SIZE - 1) {
|
||||
buf[PAGE_SIZE - 2] = '\n';
|
||||
dev_err(dev, "%s:%d: buffer is full\n",
|
||||
__func__, __LINE__);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t enable_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct wakeup_irq *p;
|
||||
|
||||
p = dev_get_drvdata(dev);
|
||||
return scnprintf(buf, PAGE_SIZE, "%d\n", p->enabled);
|
||||
}
|
||||
|
||||
static ssize_t enable_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct wakeup_irq *p;
|
||||
int value, ret;
|
||||
|
||||
p = dev_get_drvdata(dev);
|
||||
|
||||
ret = kstrtoint(buf, 10, &value);
|
||||
if (ret) {
|
||||
dev_err(dev, "kstrtoint failed: %d\n", ret);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (value == 1)
|
||||
p->enabled = true;
|
||||
else if (value == 0)
|
||||
p->enabled = false;
|
||||
else
|
||||
goto error;
|
||||
|
||||
return count;
|
||||
|
||||
error:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops wakeup_irq_pm_ops = {
|
||||
.suspend_noirq = wakeup_irq_suspend,
|
||||
.resume_early = wakeup_irq_resume,
|
||||
};
|
||||
|
||||
static struct platform_device wakeup_irq_device = {
|
||||
.name = MODULE_NAME,
|
||||
.id = 0,
|
||||
};
|
||||
|
||||
static struct platform_driver wakeup_irq_driver = {
|
||||
.driver = {
|
||||
.name = MODULE_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &wakeup_irq_pm_ops,
|
||||
},
|
||||
};
|
||||
|
||||
static struct device_attribute wakeup_irq_attrs[] = {
|
||||
__ATTR(wakeup_irq_stat,
|
||||
S_IRUSR | S_IRGRP | S_IROTH, irq_stat_show, NULL),
|
||||
__ATTR(enable,
|
||||
S_IRUSR | S_IWUSR, enable_show, enable_store),
|
||||
};
|
||||
|
||||
static int create_sysfs_entries(struct device *dev)
|
||||
{
|
||||
int i, rc;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(wakeup_irq_attrs); i++) {
|
||||
rc = device_create_file(dev, &wakeup_irq_attrs[i]);
|
||||
if (rc < 0)
|
||||
goto revert;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
revert:
|
||||
for (; --i >= 0;)
|
||||
device_remove_file(dev, &wakeup_irq_attrs[i]);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int wakeup_irq_debug_init(void)
|
||||
{
|
||||
struct wakeup_irq *p;
|
||||
int rv = -1;
|
||||
|
||||
p = kzalloc(sizeof(struct wakeup_irq), GFP_KERNEL);
|
||||
if (p == NULL)
|
||||
goto out;
|
||||
|
||||
INIT_LIST_HEAD(&p->head);
|
||||
|
||||
/*
|
||||
* we need it for PM works correctly
|
||||
*/
|
||||
rv = platform_device_register(&wakeup_irq_device);
|
||||
if (rv) {
|
||||
pr_err("%s: wakeup_irq_device register failed %d\n",
|
||||
__func__, rv);
|
||||
goto free_mem;
|
||||
}
|
||||
|
||||
rv = platform_driver_register(&wakeup_irq_driver);
|
||||
if (rv) {
|
||||
pr_err("%s: wakeup_irq_driver register failed %d\n",
|
||||
__func__, rv);
|
||||
goto unregister_device;
|
||||
}
|
||||
|
||||
rv = create_sysfs_entries(&wakeup_irq_device.dev);
|
||||
if (rv < 0) {
|
||||
pr_err("%s: create_sysfs_entries failed %d\n",
|
||||
__func__, rv);
|
||||
goto unregister_driver;
|
||||
}
|
||||
|
||||
/*
|
||||
* save private data
|
||||
*/
|
||||
platform_set_drvdata(&wakeup_irq_device, p);
|
||||
|
||||
/*
|
||||
* by default it's disabled
|
||||
*/
|
||||
p->enabled = false;
|
||||
return 0;
|
||||
|
||||
unregister_driver:
|
||||
platform_driver_unregister(&wakeup_irq_driver);
|
||||
unregister_device:
|
||||
platform_device_unregister(&wakeup_irq_device);
|
||||
free_mem:
|
||||
kzfree(p);
|
||||
out:
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void wakeup_irq_debug_exit(void)
|
||||
{
|
||||
struct irq_info *info, *tmp_info;
|
||||
struct wakeup_irq *p;
|
||||
int i;
|
||||
|
||||
p = dev_get_drvdata(&wakeup_irq_device.dev);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(wakeup_irq_attrs); i++)
|
||||
device_remove_file(&wakeup_irq_device.dev,
|
||||
&wakeup_irq_attrs[i]);
|
||||
|
||||
list_for_each_entry_safe(info, tmp_info, &p->head, list) {
|
||||
list_del(&info->list);
|
||||
kzfree(info);
|
||||
}
|
||||
|
||||
platform_driver_unregister(&wakeup_irq_driver);
|
||||
platform_device_unregister(&wakeup_irq_device);
|
||||
kzfree(p);
|
||||
}
|
||||
|
||||
module_init(wakeup_irq_debug_init);
|
||||
module_exit(wakeup_irq_debug_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Wakeup IRQ Debug Module");
|
||||
MODULE_AUTHOR("Uladzislau Rezki");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
Loading…
Reference in a new issue