Merge branch 'pm-sleep'
* pm-sleep: (29 commits) ACPI: PM: s2idle: Always set up EC GPE for system wakeup ACPI: PM: s2idle: Avoid rearming SCI for wakeup unnecessarily PM / wakeup: Unexport wakeup_source_sysfs_{add,remove}() PM / wakeup: Register wakeup class kobj after device is added PM / wakeup: Fix sysfs registration error path PM / wakeup: Show wakeup sources stats in sysfs PM / wakeup: Use wakeup_source_register() in wakelock.c PM / wakeup: Drop wakeup_source_init(), wakeup_source_prepare() PM: sleep: Replace strncmp() with str_has_prefix() PM: suspend: Fix platform_suspend_prepare_noirq() intel-hid: Disable button array during suspend-to-idle intel-hid: intel-vbtn: Avoid leaking wakeup_mode set ACPI: PM: s2idle: Execute LPS0 _DSM functions with suspended devices ACPI: EC: PM: Make acpi_ec_dispatch_gpe() print debug message ACPI: EC: PM: Consolidate some code depending on PM_SLEEP ACPI: PM: s2idle: Eliminate acpi_sleep_no_ec_events() ACPI: PM: s2idle: Switch EC over to polling during "noirq" suspend ACPI: PM: s2idle: Add acpi.sleep_no_lps0 module parameter ACPI: PM: s2idle: Rearrange lps0_device_attach() PM/sleep: Expose suspend stats in sysfs ...
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
obj-$(CONFIG_PM) += sysfs.o generic_ops.o common.o qos.o runtime.o wakeirq.o
|
||||
obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o
|
||||
obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o wakeup_stats.o
|
||||
obj-$(CONFIG_PM_TRACE_RTC) += trace.o
|
||||
obj-$(CONFIG_PM_GENERIC_DOMAINS) += domain.o domain_governor.o
|
||||
obj-$(CONFIG_HAVE_CLK) += clock_ops.o
|
||||
|
@@ -716,7 +716,7 @@ static void async_resume_noirq(void *data, async_cookie_t cookie)
|
||||
put_device(dev);
|
||||
}
|
||||
|
||||
void dpm_noirq_resume_devices(pm_message_t state)
|
||||
static void dpm_noirq_resume_devices(pm_message_t state)
|
||||
{
|
||||
struct device *dev;
|
||||
ktime_t starttime = ktime_get();
|
||||
@@ -760,13 +760,6 @@ void dpm_noirq_resume_devices(pm_message_t state)
|
||||
trace_suspend_resume(TPS("dpm_resume_noirq"), state.event, false);
|
||||
}
|
||||
|
||||
void dpm_noirq_end(void)
|
||||
{
|
||||
resume_device_irqs();
|
||||
device_wakeup_disarm_wake_irqs();
|
||||
cpuidle_resume();
|
||||
}
|
||||
|
||||
/**
|
||||
* dpm_resume_noirq - Execute "noirq resume" callbacks for all devices.
|
||||
* @state: PM transition of the system being carried out.
|
||||
@@ -777,7 +770,11 @@ void dpm_noirq_end(void)
|
||||
void dpm_resume_noirq(pm_message_t state)
|
||||
{
|
||||
dpm_noirq_resume_devices(state);
|
||||
dpm_noirq_end();
|
||||
|
||||
resume_device_irqs();
|
||||
device_wakeup_disarm_wake_irqs();
|
||||
|
||||
cpuidle_resume();
|
||||
}
|
||||
|
||||
static pm_callback_t dpm_subsys_resume_early_cb(struct device *dev,
|
||||
@@ -1291,11 +1288,6 @@ static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool a
|
||||
if (async_error)
|
||||
goto Complete;
|
||||
|
||||
if (pm_wakeup_pending()) {
|
||||
async_error = -EBUSY;
|
||||
goto Complete;
|
||||
}
|
||||
|
||||
if (dev->power.syscore || dev->power.direct_complete)
|
||||
goto Complete;
|
||||
|
||||
@@ -1362,14 +1354,7 @@ static int device_suspend_noirq(struct device *dev)
|
||||
return __device_suspend_noirq(dev, pm_transition, false);
|
||||
}
|
||||
|
||||
void dpm_noirq_begin(void)
|
||||
{
|
||||
cpuidle_pause();
|
||||
device_wakeup_arm_wake_irqs();
|
||||
suspend_device_irqs();
|
||||
}
|
||||
|
||||
int dpm_noirq_suspend_devices(pm_message_t state)
|
||||
static int dpm_noirq_suspend_devices(pm_message_t state)
|
||||
{
|
||||
ktime_t starttime = ktime_get();
|
||||
int error = 0;
|
||||
@@ -1426,7 +1411,11 @@ int dpm_suspend_noirq(pm_message_t state)
|
||||
{
|
||||
int ret;
|
||||
|
||||
dpm_noirq_begin();
|
||||
cpuidle_pause();
|
||||
|
||||
device_wakeup_arm_wake_irqs();
|
||||
suspend_device_irqs();
|
||||
|
||||
ret = dpm_noirq_suspend_devices(state);
|
||||
if (ret)
|
||||
dpm_resume_noirq(resume_event(state));
|
||||
|
@@ -149,3 +149,21 @@ static inline void device_pm_init(struct device *dev)
|
||||
device_pm_sleep_init(dev);
|
||||
pm_runtime_init(dev);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
|
||||
/* drivers/base/power/wakeup_stats.c */
|
||||
extern int wakeup_source_sysfs_add(struct device *parent,
|
||||
struct wakeup_source *ws);
|
||||
extern void wakeup_source_sysfs_remove(struct wakeup_source *ws);
|
||||
|
||||
extern int pm_wakeup_source_sysfs_add(struct device *parent);
|
||||
|
||||
#else /* !CONFIG_PM_SLEEP */
|
||||
|
||||
static inline int pm_wakeup_source_sysfs_add(struct device *parent)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_PM_SLEEP */
|
||||
|
@@ -5,6 +5,7 @@
|
||||
#include <linux/export.h>
|
||||
#include <linux/pm_qos.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/pm_wakeup.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include "power.h"
|
||||
@@ -667,8 +668,13 @@ int dpm_sysfs_add(struct device *dev)
|
||||
if (rc)
|
||||
goto err_wakeup;
|
||||
}
|
||||
rc = pm_wakeup_source_sysfs_add(dev);
|
||||
if (rc)
|
||||
goto err_latency;
|
||||
return 0;
|
||||
|
||||
err_latency:
|
||||
sysfs_unmerge_group(&dev->kobj, &pm_qos_latency_tolerance_attr_group);
|
||||
err_wakeup:
|
||||
sysfs_unmerge_group(&dev->kobj, &pm_wakeup_attr_group);
|
||||
err_runtime:
|
||||
|
@@ -72,22 +72,7 @@ static struct wakeup_source deleted_ws = {
|
||||
.lock = __SPIN_LOCK_UNLOCKED(deleted_ws.lock),
|
||||
};
|
||||
|
||||
/**
|
||||
* wakeup_source_prepare - Prepare a new wakeup source for initialization.
|
||||
* @ws: Wakeup source to prepare.
|
||||
* @name: Pointer to the name of the new wakeup source.
|
||||
*
|
||||
* Callers must ensure that the @name string won't be freed when @ws is still in
|
||||
* use.
|
||||
*/
|
||||
void wakeup_source_prepare(struct wakeup_source *ws, const char *name)
|
||||
{
|
||||
if (ws) {
|
||||
memset(ws, 0, sizeof(*ws));
|
||||
ws->name = name;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wakeup_source_prepare);
|
||||
static DEFINE_IDA(wakeup_ida);
|
||||
|
||||
/**
|
||||
* wakeup_source_create - Create a struct wakeup_source object.
|
||||
@@ -96,13 +81,31 @@ EXPORT_SYMBOL_GPL(wakeup_source_prepare);
|
||||
struct wakeup_source *wakeup_source_create(const char *name)
|
||||
{
|
||||
struct wakeup_source *ws;
|
||||
const char *ws_name;
|
||||
int id;
|
||||
|
||||
ws = kmalloc(sizeof(*ws), GFP_KERNEL);
|
||||
ws = kzalloc(sizeof(*ws), GFP_KERNEL);
|
||||
if (!ws)
|
||||
return NULL;
|
||||
goto err_ws;
|
||||
|
||||
ws_name = kstrdup_const(name, GFP_KERNEL);
|
||||
if (!ws_name)
|
||||
goto err_name;
|
||||
ws->name = ws_name;
|
||||
|
||||
id = ida_alloc(&wakeup_ida, GFP_KERNEL);
|
||||
if (id < 0)
|
||||
goto err_id;
|
||||
ws->id = id;
|
||||
|
||||
wakeup_source_prepare(ws, name ? kstrdup_const(name, GFP_KERNEL) : NULL);
|
||||
return ws;
|
||||
|
||||
err_id:
|
||||
kfree_const(ws->name);
|
||||
err_name:
|
||||
kfree(ws);
|
||||
err_ws:
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wakeup_source_create);
|
||||
|
||||
@@ -134,6 +137,13 @@ static void wakeup_source_record(struct wakeup_source *ws)
|
||||
spin_unlock_irqrestore(&deleted_ws.lock, flags);
|
||||
}
|
||||
|
||||
static void wakeup_source_free(struct wakeup_source *ws)
|
||||
{
|
||||
ida_free(&wakeup_ida, ws->id);
|
||||
kfree_const(ws->name);
|
||||
kfree(ws);
|
||||
}
|
||||
|
||||
/**
|
||||
* wakeup_source_destroy - Destroy a struct wakeup_source object.
|
||||
* @ws: Wakeup source to destroy.
|
||||
@@ -147,8 +157,7 @@ void wakeup_source_destroy(struct wakeup_source *ws)
|
||||
|
||||
__pm_relax(ws);
|
||||
wakeup_source_record(ws);
|
||||
kfree_const(ws->name);
|
||||
kfree(ws);
|
||||
wakeup_source_free(ws);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wakeup_source_destroy);
|
||||
|
||||
@@ -200,16 +209,26 @@ EXPORT_SYMBOL_GPL(wakeup_source_remove);
|
||||
|
||||
/**
|
||||
* wakeup_source_register - Create wakeup source and add it to the list.
|
||||
* @dev: Device this wakeup source is associated with (or NULL if virtual).
|
||||
* @name: Name of the wakeup source to register.
|
||||
*/
|
||||
struct wakeup_source *wakeup_source_register(const char *name)
|
||||
struct wakeup_source *wakeup_source_register(struct device *dev,
|
||||
const char *name)
|
||||
{
|
||||
struct wakeup_source *ws;
|
||||
int ret;
|
||||
|
||||
ws = wakeup_source_create(name);
|
||||
if (ws)
|
||||
if (ws) {
|
||||
if (!dev || device_is_registered(dev)) {
|
||||
ret = wakeup_source_sysfs_add(dev, ws);
|
||||
if (ret) {
|
||||
wakeup_source_free(ws);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
wakeup_source_add(ws);
|
||||
|
||||
}
|
||||
return ws;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wakeup_source_register);
|
||||
@@ -222,6 +241,7 @@ void wakeup_source_unregister(struct wakeup_source *ws)
|
||||
{
|
||||
if (ws) {
|
||||
wakeup_source_remove(ws);
|
||||
wakeup_source_sysfs_remove(ws);
|
||||
wakeup_source_destroy(ws);
|
||||
}
|
||||
}
|
||||
@@ -265,7 +285,7 @@ int device_wakeup_enable(struct device *dev)
|
||||
if (pm_suspend_target_state != PM_SUSPEND_ON)
|
||||
dev_dbg(dev, "Suspicious %s() during system transition!\n", __func__);
|
||||
|
||||
ws = wakeup_source_register(dev_name(dev));
|
||||
ws = wakeup_source_register(dev, dev_name(dev));
|
||||
if (!ws)
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -859,7 +879,7 @@ EXPORT_SYMBOL_GPL(pm_system_wakeup);
|
||||
|
||||
void pm_system_cancel_wakeup(void)
|
||||
{
|
||||
atomic_dec(&pm_abort_suspend);
|
||||
atomic_dec_if_positive(&pm_abort_suspend);
|
||||
}
|
||||
|
||||
void pm_wakeup_clear(bool reset)
|
||||
|
214
drivers/base/power/wakeup_stats.c
Normal file
214
drivers/base/power/wakeup_stats.c
Normal file
@@ -0,0 +1,214 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Wakeup statistics in sysfs
|
||||
*
|
||||
* Copyright (c) 2019 Linux Foundation
|
||||
* Copyright (c) 2019 Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
||||
* Copyright (c) 2019 Google Inc.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kdev_t.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/timekeeping.h>
|
||||
|
||||
#include "power.h"
|
||||
|
||||
static struct class *wakeup_class;
|
||||
|
||||
#define wakeup_attr(_name) \
|
||||
static ssize_t _name##_show(struct device *dev, \
|
||||
struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct wakeup_source *ws = dev_get_drvdata(dev); \
|
||||
\
|
||||
return sprintf(buf, "%lu\n", ws->_name); \
|
||||
} \
|
||||
static DEVICE_ATTR_RO(_name)
|
||||
|
||||
wakeup_attr(active_count);
|
||||
wakeup_attr(event_count);
|
||||
wakeup_attr(wakeup_count);
|
||||
wakeup_attr(expire_count);
|
||||
|
||||
static ssize_t active_time_ms_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct wakeup_source *ws = dev_get_drvdata(dev);
|
||||
ktime_t active_time =
|
||||
ws->active ? ktime_sub(ktime_get(), ws->last_time) : 0;
|
||||
|
||||
return sprintf(buf, "%lld\n", ktime_to_ms(active_time));
|
||||
}
|
||||
static DEVICE_ATTR_RO(active_time_ms);
|
||||
|
||||
static ssize_t total_time_ms_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct wakeup_source *ws = dev_get_drvdata(dev);
|
||||
ktime_t active_time;
|
||||
ktime_t total_time = ws->total_time;
|
||||
|
||||
if (ws->active) {
|
||||
active_time = ktime_sub(ktime_get(), ws->last_time);
|
||||
total_time = ktime_add(total_time, active_time);
|
||||
}
|
||||
return sprintf(buf, "%lld\n", ktime_to_ms(total_time));
|
||||
}
|
||||
static DEVICE_ATTR_RO(total_time_ms);
|
||||
|
||||
static ssize_t max_time_ms_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct wakeup_source *ws = dev_get_drvdata(dev);
|
||||
ktime_t active_time;
|
||||
ktime_t max_time = ws->max_time;
|
||||
|
||||
if (ws->active) {
|
||||
active_time = ktime_sub(ktime_get(), ws->last_time);
|
||||
if (active_time > max_time)
|
||||
max_time = active_time;
|
||||
}
|
||||
return sprintf(buf, "%lld\n", ktime_to_ms(max_time));
|
||||
}
|
||||
static DEVICE_ATTR_RO(max_time_ms);
|
||||
|
||||
static ssize_t last_change_ms_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct wakeup_source *ws = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "%lld\n", ktime_to_ms(ws->last_time));
|
||||
}
|
||||
static DEVICE_ATTR_RO(last_change_ms);
|
||||
|
||||
static ssize_t name_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct wakeup_source *ws = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "%s\n", ws->name);
|
||||
}
|
||||
static DEVICE_ATTR_RO(name);
|
||||
|
||||
static ssize_t prevent_suspend_time_ms_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct wakeup_source *ws = dev_get_drvdata(dev);
|
||||
ktime_t prevent_sleep_time = ws->prevent_sleep_time;
|
||||
|
||||
if (ws->active && ws->autosleep_enabled) {
|
||||
prevent_sleep_time = ktime_add(prevent_sleep_time,
|
||||
ktime_sub(ktime_get(), ws->start_prevent_time));
|
||||
}
|
||||
return sprintf(buf, "%lld\n", ktime_to_ms(prevent_sleep_time));
|
||||
}
|
||||
static DEVICE_ATTR_RO(prevent_suspend_time_ms);
|
||||
|
||||
static struct attribute *wakeup_source_attrs[] = {
|
||||
&dev_attr_name.attr,
|
||||
&dev_attr_active_count.attr,
|
||||
&dev_attr_event_count.attr,
|
||||
&dev_attr_wakeup_count.attr,
|
||||
&dev_attr_expire_count.attr,
|
||||
&dev_attr_active_time_ms.attr,
|
||||
&dev_attr_total_time_ms.attr,
|
||||
&dev_attr_max_time_ms.attr,
|
||||
&dev_attr_last_change_ms.attr,
|
||||
&dev_attr_prevent_suspend_time_ms.attr,
|
||||
NULL,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(wakeup_source);
|
||||
|
||||
static void device_create_release(struct device *dev)
|
||||
{
|
||||
kfree(dev);
|
||||
}
|
||||
|
||||
static struct device *wakeup_source_device_create(struct device *parent,
|
||||
struct wakeup_source *ws)
|
||||
{
|
||||
struct device *dev = NULL;
|
||||
int retval = -ENODEV;
|
||||
|
||||
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
||||
if (!dev) {
|
||||
retval = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
device_initialize(dev);
|
||||
dev->devt = MKDEV(0, 0);
|
||||
dev->class = wakeup_class;
|
||||
dev->parent = parent;
|
||||
dev->groups = wakeup_source_groups;
|
||||
dev->release = device_create_release;
|
||||
dev_set_drvdata(dev, ws);
|
||||
device_set_pm_not_required(dev);
|
||||
|
||||
retval = kobject_set_name(&dev->kobj, "wakeup%d", ws->id);
|
||||
if (retval)
|
||||
goto error;
|
||||
|
||||
retval = device_add(dev);
|
||||
if (retval)
|
||||
goto error;
|
||||
|
||||
return dev;
|
||||
|
||||
error:
|
||||
put_device(dev);
|
||||
return ERR_PTR(retval);
|
||||
}
|
||||
|
||||
/**
|
||||
* wakeup_source_sysfs_add - Add wakeup_source attributes to sysfs.
|
||||
* @parent: Device given wakeup source is associated with (or NULL if virtual).
|
||||
* @ws: Wakeup source to be added in sysfs.
|
||||
*/
|
||||
int wakeup_source_sysfs_add(struct device *parent, struct wakeup_source *ws)
|
||||
{
|
||||
struct device *dev;
|
||||
|
||||
dev = wakeup_source_device_create(parent, ws);
|
||||
if (IS_ERR(dev))
|
||||
return PTR_ERR(dev);
|
||||
ws->dev = dev;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_wakeup_source_sysfs_add - Add wakeup_source attributes to sysfs
|
||||
* for a device if they're missing.
|
||||
* @parent: Device given wakeup source is associated with
|
||||
*/
|
||||
int pm_wakeup_source_sysfs_add(struct device *parent)
|
||||
{
|
||||
if (!parent->power.wakeup || parent->power.wakeup->dev)
|
||||
return 0;
|
||||
|
||||
return wakeup_source_sysfs_add(parent, parent->power.wakeup);
|
||||
}
|
||||
|
||||
/**
|
||||
* wakeup_source_sysfs_remove - Remove wakeup_source attributes from sysfs.
|
||||
* @ws: Wakeup source to be removed from sysfs.
|
||||
*/
|
||||
void wakeup_source_sysfs_remove(struct wakeup_source *ws)
|
||||
{
|
||||
device_unregister(ws->dev);
|
||||
}
|
||||
|
||||
static int __init wakeup_sources_sysfs_init(void)
|
||||
{
|
||||
wakeup_class = class_create(THIS_MODULE, "wakeup");
|
||||
|
||||
return PTR_ERR_OR_ZERO(wakeup_class);
|
||||
}
|
||||
postcore_initcall(wakeup_sources_sysfs_init);
|
Reference in New Issue
Block a user