History

PM / wakeup: Show wakeup sources stats in sysfs

Add an ID and a device pointer to 'struct wakeup_source'. Use them to to expose wakeup sources statistics in sysfs under /sys/class/wakeup/wakeup<ID>/*.

Tri Vo committed 7 years ago

Commit c8377ad1 parent 2434aea11 files · +328 −13Browse files

Unified · Split

11 files
  1. Documentation/ABI/testing/sysfs-class-wakeup +76 −0
  2. drivers/acpi/device_pm.c +2 −1
  3. drivers/base/power/Makefile +1 −1
  4. drivers/base/power/power.h +9 −0
  5. drivers/base/power/wakeup.c +24 −4
  6. drivers/base/power/wakeup_stats.c +203 −0
  7. fs/eventpoll.c +2 −2
  8. include/linux/pm_wakeup.h +8 −2
  9. kernel/power/autosleep.c +1 −1
  10. kernel/power/wakelock.c +1 −1
  11. kernel/time/alarmtimer.c +1 −1
Documentation/ABI/testing/sysfs-class-wakeup+76−0
@@ -0,0 +1,76 @@
1+What: /sys/class/wakeup/
2+Date: June 2019
3+Contact: Tri Vo <trong@android.com>
4+Description:
5+ The /sys/class/wakeup/ directory contains pointers to all
6+ wakeup sources in the kernel at that moment in time.
7+
8+What: /sys/class/wakeup/.../name
9+Date: June 2019
10+Contact: Tri Vo <trong@android.com>
11+Description:
12+ This file contains the name of the wakeup source.
13+
14+What: /sys/class/wakeup/.../active_count
15+Date: June 2019
16+Contact: Tri Vo <trong@android.com>
17+Description:
18+ This file contains the number of times the wakeup source was
19+ activated.
20+
21+What: /sys/class/wakeup/.../event_count
22+Date: June 2019
23+Contact: Tri Vo <trong@android.com>
24+Description:
25+ This file contains the number of signaled wakeup events
26+ associated with the wakeup source.
27+
28+What: /sys/class/wakeup/.../wakeup_count
29+Date: June 2019
30+Contact: Tri Vo <trong@android.com>
31+Description:
32+ This file contains the number of times the wakeup source might
33+ abort suspend.
34+
35+What: /sys/class/wakeup/.../expire_count
36+Date: June 2019
37+Contact: Tri Vo <trong@android.com>
38+Description:
39+ This file contains the number of times the wakeup source's
40+ timeout has expired.
41+
42+What: /sys/class/wakeup/.../active_time_ms
43+Date: June 2019
44+Contact: Tri Vo <trong@android.com>
45+Description:
46+ This file contains the amount of time the wakeup source has
47+ been continuously active, in milliseconds. If the wakeup
48+ source is not active, this file contains '0'.
49+
50+What: /sys/class/wakeup/.../total_time_ms
51+Date: June 2019
52+Contact: Tri Vo <trong@android.com>
53+Description:
54+ This file contains the total amount of time this wakeup source
55+ has been active, in milliseconds.
56+
57+What: /sys/class/wakeup/.../max_time_ms
58+Date: June 2019
59+Contact: Tri Vo <trong@android.com>
60+Description:
61+ This file contains the maximum amount of time this wakeup
62+ source has been continuously active, in milliseconds.
63+
64+What: /sys/class/wakeup/.../last_change_ms
65+Date: June 2019
66+Contact: Tri Vo <trong@android.com>
67+Description:
68+ This file contains the monotonic clock time when the wakeup
69+ source was touched last time, in milliseconds.
70+
71+What: /sys/class/wakeup/.../prevent_suspend_time_ms
72+Date: June 2019
73+Contact: Tri Vo <trong@android.com>
74+Description:
75+ The file contains the total amount of time this wakeup source
76+ has been preventing autosleep, in milliseconds.
drivers/acpi/device_pm.c+2−1
@@ -497,7 +497,8 @@ acpi_status acpi_add_pm_notifier(struct acpi_device *adev, struct device *dev, · 496 unmodified lines
497497 goto out;
498498
499499 mutex_lock(&acpi_pm_notifier_lock);
500− adev->wakeup.ws = wakeup_source_register(dev_name(&adev->dev));
500+ adev->wakeup.ws = wakeup_source_register(&adev->dev,
501+ dev_name(&adev->dev));
501502 adev->wakeup.context.dev = dev;
502503 adev->wakeup.context.func = func;
503504 adev->wakeup.flags.notifier_present = true;
drivers/base/power/Makefile+1−1
@@ -1,6 +1,6 @@
11 # SPDX-License-Identifier: GPL-2.0
22 obj-$(CONFIG_PM) += sysfs.o generic_ops.o common.o qos.o runtime.o wakeirq.o
3−obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o
3+obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o wakeup_stats.o
44 obj-$(CONFIG_PM_TRACE_RTC) += trace.o
55 obj-$(CONFIG_PM_GENERIC_DOMAINS) += domain.o domain_governor.o
66 obj-$(CONFIG_HAVE_CLK) += clock_ops.o
drivers/base/power/power.h+9−0
@@ -149,3 +149,12 @@ static inline void device_pm_init(struct device *dev) · 148 unmodified lines
149149 device_pm_sleep_init(dev);
150150 pm_runtime_init(dev);
151151 }
152+
153+#ifdef CONFIG_PM_SLEEP
154+
155+/* drivers/base/power/wakeup_stats.c */
156+extern int wakeup_source_sysfs_add(struct device *parent,
157+ struct wakeup_source *ws);
158+extern void wakeup_source_sysfs_remove(struct wakeup_source *ws);
159+
160+#endif /* CONFIG_PM_SLEEP */
drivers/base/power/wakeup.c+24−4
@@ -72,6 +72,8 @@ static struct wakeup_source deleted_ws = { · 71 unmodified lines
7272 .lock = __SPIN_LOCK_UNLOCKED(deleted_ws.lock),
7373 };
7474
75+static DEFINE_IDA(wakeup_ida);
76+
7577 /**
7678 * wakeup_source_create - Create a struct wakeup_source object.
7779 * @name: Name of the new wakeup source.
@@ -80,6 +82,7 @@ struct wakeup_source *wakeup_source_create(const char *name) · 2 unmodified lines
8082 {
8183 struct wakeup_source *ws;
8284 const char *ws_name;
85+ int id;
8386
8487 ws = kzalloc(sizeof(*ws), GFP_KERNEL);
8588 if (!ws)
@@ -90,8 +93,15 @@ struct wakeup_source *wakeup_source_create(const char *name) · 4 unmodified lines
9093 goto err_name;
9194 ws->name = ws_name;
9295
96+ id = ida_alloc(&wakeup_ida, GFP_KERNEL);
97+ if (id < 0)
98+ goto err_id;
99+ ws->id = id;
100+
93101 return ws;
94102
103+err_id:
104+ kfree_const(ws->name);
95105 err_name:
96106 kfree(ws);
97107 err_ws:
@@ -140,6 +150,7 @@ void wakeup_source_destroy(struct wakeup_source *ws) · 42 unmodified lines
140150
141151 __pm_relax(ws);
142152 wakeup_source_record(ws);
153+ ida_free(&wakeup_ida, ws->id);
143154 kfree_const(ws->name);
144155 kfree(ws);
145156 }
@@ -193,16 +204,24 @@ EXPORT_SYMBOL_GPL(wakeup_source_remove); · 47 unmodified lines
193204
194205 /**
195206 * wakeup_source_register - Create wakeup source and add it to the list.
207+ * @dev: Device this wakeup source is associated with (or NULL if virtual).
196208 * @name: Name of the wakeup source to register.
197209 */
198−struct wakeup_source *wakeup_source_register(const char *name)
210+struct wakeup_source *wakeup_source_register(struct device *dev,
211+ const char *name)
199212 {
200213 struct wakeup_source *ws;
214+ int ret;
201215
202216 ws = wakeup_source_create(name);
203− if (ws)
217+ if (ws) {
218+ ret = wakeup_source_sysfs_add(dev, ws);
219+ if (ret) {
220+ wakeup_source_destroy(ws);
221+ return NULL;
222+ }
204223 wakeup_source_add(ws);
205−
224+ }
206225 return ws;
207226 }
208227 EXPORT_SYMBOL_GPL(wakeup_source_register);
@@ -215,6 +234,7 @@ void wakeup_source_unregister(struct wakeup_source *ws) · 6 unmodified lines
215234 {
216235 if (ws) {
217236 wakeup_source_remove(ws);
237+ wakeup_source_sysfs_remove(ws);
218238 wakeup_source_destroy(ws);
219239 }
220240 }
@@ -258,7 +278,7 @@ int device_wakeup_enable(struct device *dev) · 37 unmodified lines
258278 if (pm_suspend_target_state != PM_SUSPEND_ON)
259279 dev_dbg(dev, "Suspicious %s() during system transition!\n", __func__);
260280
261− ws = wakeup_source_register(dev_name(dev));
281+ ws = wakeup_source_register(dev, dev_name(dev));
262282 if (!ws)
263283 return -ENOMEM;
264284
drivers/base/power/wakeup_stats.c+203−0
@@ -0,0 +1,203 @@
1+// SPDX-License-Identifier: GPL-2.0
2+/*
3+ * Wakeup statistics in sysfs
4+ *
5+ * Copyright (c) 2019 Linux Foundation
6+ * Copyright (c) 2019 Greg Kroah-Hartman <gregkh@linuxfoundation.org>
7+ * Copyright (c) 2019 Google Inc.
8+ */
9+
10+#include <linux/device.h>
11+#include <linux/idr.h>
12+#include <linux/init.h>
13+#include <linux/kdev_t.h>
14+#include <linux/kernel.h>
15+#include <linux/kobject.h>
16+#include <linux/slab.h>
17+#include <linux/timekeeping.h>
18+
19+#include "power.h"
20+
21+static struct class *wakeup_class;
22+
23+#define wakeup_attr(_name) \
24+static ssize_t _name##_show(struct device *dev, \
25+ struct device_attribute *attr, char *buf) \
26+{ \
27+ struct wakeup_source *ws = dev_get_drvdata(dev); \
28+ \
29+ return sprintf(buf, "%lu\n", ws->_name); \
30+} \
31+static DEVICE_ATTR_RO(_name)
32+
33+wakeup_attr(active_count);
34+wakeup_attr(event_count);
35+wakeup_attr(wakeup_count);
36+wakeup_attr(expire_count);
37+
38+static ssize_t active_time_ms_show(struct device *dev,
39+ struct device_attribute *attr, char *buf)
40+{
41+ struct wakeup_source *ws = dev_get_drvdata(dev);
42+ ktime_t active_time =
43+ ws->active ? ktime_sub(ktime_get(), ws->last_time) : 0;
44+
45+ return sprintf(buf, "%lld\n", ktime_to_ms(active_time));
46+}
47+static DEVICE_ATTR_RO(active_time_ms);
48+
49+static ssize_t total_time_ms_show(struct device *dev,
50+ struct device_attribute *attr, char *buf)
51+{
52+ struct wakeup_source *ws = dev_get_drvdata(dev);
53+ ktime_t active_time;
54+ ktime_t total_time = ws->total_time;
55+
56+ if (ws->active) {
57+ active_time = ktime_sub(ktime_get(), ws->last_time);
58+ total_time = ktime_add(total_time, active_time);
59+ }
60+ return sprintf(buf, "%lld\n", ktime_to_ms(total_time));
61+}
62+static DEVICE_ATTR_RO(total_time_ms);
63+
64+static ssize_t max_time_ms_show(struct device *dev,
65+ struct device_attribute *attr, char *buf)
66+{
67+ struct wakeup_source *ws = dev_get_drvdata(dev);
68+ ktime_t active_time;
69+ ktime_t max_time = ws->max_time;
70+
71+ if (ws->active) {
72+ active_time = ktime_sub(ktime_get(), ws->last_time);
73+ if (active_time > max_time)
74+ max_time = active_time;
75+ }
76+ return sprintf(buf, "%lld\n", ktime_to_ms(max_time));
77+}
78+static DEVICE_ATTR_RO(max_time_ms);
79+
80+static ssize_t last_change_ms_show(struct device *dev,
81+ struct device_attribute *attr, char *buf)
82+{
83+ struct wakeup_source *ws = dev_get_drvdata(dev);
84+
85+ return sprintf(buf, "%lld\n", ktime_to_ms(ws->last_time));
86+}
87+static DEVICE_ATTR_RO(last_change_ms);
88+
89+static ssize_t name_show(struct device *dev, struct device_attribute *attr,
90+ char *buf)
91+{
92+ struct wakeup_source *ws = dev_get_drvdata(dev);
93+
94+ return sprintf(buf, "%s\n", ws->name);
95+}
96+static DEVICE_ATTR_RO(name);
97+
98+static ssize_t prevent_suspend_time_ms_show(struct device *dev,
99+ struct device_attribute *attr,
100+ char *buf)
101+{
102+ struct wakeup_source *ws = dev_get_drvdata(dev);
103+ ktime_t prevent_sleep_time = ws->prevent_sleep_time;
104+
105+ if (ws->active && ws->autosleep_enabled) {
106+ prevent_sleep_time = ktime_add(prevent_sleep_time,
107+ ktime_sub(ktime_get(), ws->start_prevent_time));
108+ }
109+ return sprintf(buf, "%lld\n", ktime_to_ms(prevent_sleep_time));
110+}
111+static DEVICE_ATTR_RO(prevent_suspend_time_ms);
112+
113+static struct attribute *wakeup_source_attrs[] = {
114+ &dev_attr_name.attr,
115+ &dev_attr_active_count.attr,
116+ &dev_attr_event_count.attr,
117+ &dev_attr_wakeup_count.attr,
118+ &dev_attr_expire_count.attr,
119+ &dev_attr_active_time_ms.attr,
120+ &dev_attr_total_time_ms.attr,
121+ &dev_attr_max_time_ms.attr,
122+ &dev_attr_last_change_ms.attr,
123+ &dev_attr_prevent_suspend_time_ms.attr,
124+ NULL,
125+};
126+ATTRIBUTE_GROUPS(wakeup_source);
127+
128+static void device_create_release(struct device *dev)
129+{
130+ kfree(dev);
131+}
132+
133+static struct device *wakeup_source_device_create(struct device *parent,
134+ struct wakeup_source *ws)
135+{
136+ struct device *dev = NULL;
137+ int retval = -ENODEV;
138+
139+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
140+ if (!dev) {
141+ retval = -ENOMEM;
142+ goto error;
143+ }
144+
145+ device_initialize(dev);
146+ dev->devt = MKDEV(0, 0);
147+ dev->class = wakeup_class;
148+ dev->parent = parent;
149+ dev->groups = wakeup_source_groups;
150+ dev->release = device_create_release;
151+ dev_set_drvdata(dev, ws);
152+ device_set_pm_not_required(dev);
153+
154+ retval = kobject_set_name(&dev->kobj, "wakeup%d", ws->id);
155+ if (retval)
156+ goto error;
157+
158+ retval = device_add(dev);
159+ if (retval)
160+ goto error;
161+
162+ return dev;
163+
164+error:
165+ put_device(dev);
166+ return ERR_PTR(retval);
167+}
168+
169+/**
170+ * wakeup_source_sysfs_add - Add wakeup_source attributes to sysfs.
171+ * @parent: Device given wakeup source is associated with (or NULL if virtual).
172+ * @ws: Wakeup source to be added in sysfs.
173+ */
174+int wakeup_source_sysfs_add(struct device *parent, struct wakeup_source *ws)
175+{
176+ struct device *dev;
177+
178+ dev = wakeup_source_device_create(parent, ws);
179+ if (IS_ERR(dev))
180+ return PTR_ERR(dev);
181+ ws->dev = dev;
182+
183+ return 0;
184+}
185+EXPORT_SYMBOL_GPL(wakeup_source_sysfs_add);
186+
187+/**
188+ * wakeup_source_sysfs_remove - Remove wakeup_source attributes from sysfs.
189+ * @ws: Wakeup source to be removed from sysfs.
190+ */
191+void wakeup_source_sysfs_remove(struct wakeup_source *ws)
192+{
193+ device_unregister(ws->dev);
194+}
195+EXPORT_SYMBOL_GPL(wakeup_source_sysfs_remove);
196+
197+static int __init wakeup_sources_sysfs_init(void)
198+{
199+ wakeup_class = class_create(THIS_MODULE, "wakeup");
200+
201+ return PTR_ERR_OR_ZERO(wakeup_class);
202+}
203+postcore_initcall(wakeup_sources_sysfs_init);
fs/eventpoll.c+2−2
@@ -1459,13 +1459,13 @@ static int ep_create_wakeup_source(struct epitem *epi) · 1458 unmodified lines
14591459 struct wakeup_source *ws;
14601460
14611461 if (!epi->ep->ws) {
1462− epi->ep->ws = wakeup_source_register("eventpoll");
1462+ epi->ep->ws = wakeup_source_register(NULL, "eventpoll");
14631463 if (!epi->ep->ws)
14641464 return -ENOMEM;
14651465 }
14661466
14671467 name = epi->ffd.file->f_path.dentry->d_name.name;
1468− ws = wakeup_source_register(name);
1468+ ws = wakeup_source_register(NULL, name);
14691469
14701470 if (!ws)
14711471 return -ENOMEM;
include/linux/pm_wakeup.h+8−2
@@ -21,6 +21,7 @@ struct wake_irq; · 20 unmodified lines
2121 * struct wakeup_source - Representation of wakeup sources
2222 *
2323 * @name: Name of the wakeup source
24+ * @id: Wakeup source id
2425 * @entry: Wakeup source list entry
2526 * @lock: Wakeup source lock
2627 * @wakeirq: Optional device specific wakeirq
@@ -35,11 +36,13 @@ struct wake_irq; · 8 unmodified lines
3536 * @relax_count: Number of times the wakeup source was deactivated.
3637 * @expire_count: Number of times the wakeup source's timeout has expired.
3738 * @wakeup_count: Number of times the wakeup source might abort suspend.
39+ * @dev: Struct device for sysfs statistics about the wakeup source.
3840 * @active: Status of the wakeup source.
3941 * @autosleep_enabled: Autosleep is active, so update @prevent_sleep_time.
4042 */
4143 struct wakeup_source {
4244 const char *name;
45+ int id;
4346 struct list_head entry;
4447 spinlock_t lock;
4548 struct wake_irq *wakeirq;
@@ -55,6 +58,7 @@ struct wakeup_source { · 9 unmodified lines
5558 unsigned long relax_count;
5659 unsigned long expire_count;
5760 unsigned long wakeup_count;
61+ struct device *dev;
5862 bool active:1;
5963 bool autosleep_enabled:1;
6064 };
@@ -85,7 +89,8 @@ extern struct wakeup_source *wakeup_source_create(const char *name); · 24 unmodified lines
8589 extern void wakeup_source_destroy(struct wakeup_source *ws);
8690 extern void wakeup_source_add(struct wakeup_source *ws);
8791 extern void wakeup_source_remove(struct wakeup_source *ws);
88−extern struct wakeup_source *wakeup_source_register(const char *name);
92+extern struct wakeup_source *wakeup_source_register(struct device *dev,
93+ const char *name);
8994 extern void wakeup_source_unregister(struct wakeup_source *ws);
9095 extern int device_wakeup_enable(struct device *dev);
9196 extern int device_wakeup_disable(struct device *dev);
@@ -122,7 +127,8 @@ static inline void wakeup_source_add(struct wakeup_source *ws) {} · 30 unmodified lines
122127
123128 static inline void wakeup_source_remove(struct wakeup_source *ws) {}
124129
125−static inline struct wakeup_source *wakeup_source_register(const char *name)
130+static inline struct wakeup_source *wakeup_source_register(struct device *dev,
131+ const char *name)
126132 {
127133 return NULL;
128134 }
kernel/power/autosleep.c+1−1
@@ -116,7 +116,7 @@ int pm_autosleep_set_state(suspend_state_t state) · 115 unmodified lines
116116
117117 int __init pm_autosleep_init(void)
118118 {
119− autosleep_ws = wakeup_source_register("autosleep");
119+ autosleep_ws = wakeup_source_register(NULL, "autosleep");
120120 if (!autosleep_ws)
121121 return -ENOMEM;
122122
kernel/power/wakelock.c+1−1
@@ -188,7 +188,7 @@ static struct wakelock *wakelock_lookup_add(const char *name, size_t len, · 187 unmodified lines
188188 return ERR_PTR(-ENOMEM);
189189 }
190190
191− wl->ws = wakeup_source_register(wl->name);
191+ wl->ws = wakeup_source_register(NULL, wl->name);
192192 if (!wl->ws) {
193193 kfree(wl->name);
194194 kfree(wl);
kernel/time/alarmtimer.c+1−1
@@ -97,7 +97,7 @@ static int alarmtimer_rtc_add_device(struct device *dev, · 96 unmodified lines
9797 if (!device_may_wakeup(rtc->dev.parent))
9898 return -1;
9999
100− __ws = wakeup_source_register("alarmtimer");
100+ __ws = wakeup_source_register(dev, "alarmtimer");
101101
102102 spin_lock_irqsave(&rtcdev_lock, flags);
103103 if (!rtcdev) {

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t