kernel/power: Import Sony Wakelock

*52.1.A.3.74

Signed-off-by: Sorayukii <sorayukii69@gmail.com>
This commit is contained in:
exer 2023-01-16 18:43:32 +00:00 • committed by Sorayukii
commit 7ee5becf00
8 changed files with 825 additions and 4 deletions

View file

@ -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)
{

View file

@ -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) \

View file

@ -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;

View file

@ -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/).

View file

@ -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

View file

@ -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();
}

View file

@ -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();

View 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");