| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * drivers/base/power/wakeup.c - System wakeup events framework |
|---|
| 3 | 4 | * |
|---|
| 4 | 5 | * Copyright (c) 2010 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc. |
|---|
| 5 | | - * |
|---|
| 6 | | - * This file is released under the GPLv2. |
|---|
| 7 | 6 | */ |
|---|
| 7 | +#define pr_fmt(fmt) "PM: " fmt |
|---|
| 8 | 8 | |
|---|
| 9 | 9 | #include <linux/device.h> |
|---|
| 10 | 10 | #include <linux/slab.h> |
|---|
| .. | .. |
|---|
| 27 | 27 | #define pm_suspend_target_state (PM_SUSPEND_ON) |
|---|
| 28 | 28 | #endif |
|---|
| 29 | 29 | |
|---|
| 30 | +#define list_for_each_entry_rcu_locked(pos, head, member) \ |
|---|
| 31 | + list_for_each_entry_rcu(pos, head, member, \ |
|---|
| 32 | + srcu_read_lock_held(&wakeup_srcu)) |
|---|
| 30 | 33 | /* |
|---|
| 31 | 34 | * If set, the suspend/hibernate code will abort transitions to a sleep state |
|---|
| 32 | 35 | * if wakeup events are registered during or immediately before the transition. |
|---|
| .. | .. |
|---|
| 34 | 37 | bool events_check_enabled __read_mostly; |
|---|
| 35 | 38 | |
|---|
| 36 | 39 | /* First wakeup IRQ seen by the kernel in the last cycle. */ |
|---|
| 37 | | -unsigned int pm_wakeup_irq __read_mostly; |
|---|
| 40 | +static unsigned int wakeup_irq[2] __read_mostly; |
|---|
| 41 | +static DEFINE_RAW_SPINLOCK(wakeup_irq_lock); |
|---|
| 38 | 42 | |
|---|
| 39 | 43 | /* If greater than 0 and the system is suspending, terminate the suspend. */ |
|---|
| 40 | 44 | static atomic_t pm_abort_suspend __read_mostly; |
|---|
| .. | .. |
|---|
| 253 | 257 | EXPORT_SYMBOL_GPL(wakeup_source_unregister); |
|---|
| 254 | 258 | |
|---|
| 255 | 259 | /** |
|---|
| 260 | + * wakeup_sources_read_lock - Lock wakeup source list for read. |
|---|
| 261 | + * |
|---|
| 262 | + * Returns an index of srcu lock for struct wakeup_srcu. |
|---|
| 263 | + * This index must be passed to the matching wakeup_sources_read_unlock(). |
|---|
| 264 | + */ |
|---|
| 265 | +int wakeup_sources_read_lock(void) |
|---|
| 266 | +{ |
|---|
| 267 | + return srcu_read_lock(&wakeup_srcu); |
|---|
| 268 | +} |
|---|
| 269 | +EXPORT_SYMBOL_GPL(wakeup_sources_read_lock); |
|---|
| 270 | + |
|---|
| 271 | +/** |
|---|
| 272 | + * wakeup_sources_read_unlock - Unlock wakeup source list. |
|---|
| 273 | + * @idx: return value from corresponding wakeup_sources_read_lock() |
|---|
| 274 | + */ |
|---|
| 275 | +void wakeup_sources_read_unlock(int idx) |
|---|
| 276 | +{ |
|---|
| 277 | + srcu_read_unlock(&wakeup_srcu, idx); |
|---|
| 278 | +} |
|---|
| 279 | +EXPORT_SYMBOL_GPL(wakeup_sources_read_unlock); |
|---|
| 280 | + |
|---|
| 281 | +/** |
|---|
| 282 | + * wakeup_sources_walk_start - Begin a walk on wakeup source list |
|---|
| 283 | + * |
|---|
| 284 | + * Returns first object of the list of wakeup sources. |
|---|
| 285 | + * |
|---|
| 286 | + * Note that to be safe, wakeup sources list needs to be locked by calling |
|---|
| 287 | + * wakeup_source_read_lock() for this. |
|---|
| 288 | + */ |
|---|
| 289 | +struct wakeup_source *wakeup_sources_walk_start(void) |
|---|
| 290 | +{ |
|---|
| 291 | + struct list_head *ws_head = &wakeup_sources; |
|---|
| 292 | + |
|---|
| 293 | + return list_entry_rcu(ws_head->next, struct wakeup_source, entry); |
|---|
| 294 | +} |
|---|
| 295 | +EXPORT_SYMBOL_GPL(wakeup_sources_walk_start); |
|---|
| 296 | + |
|---|
| 297 | +/** |
|---|
| 298 | + * wakeup_sources_walk_next - Get next wakeup source from the list |
|---|
| 299 | + * @ws: Previous wakeup source object |
|---|
| 300 | + * |
|---|
| 301 | + * Note that to be safe, wakeup sources list needs to be locked by calling |
|---|
| 302 | + * wakeup_source_read_lock() for this. |
|---|
| 303 | + */ |
|---|
| 304 | +struct wakeup_source *wakeup_sources_walk_next(struct wakeup_source *ws) |
|---|
| 305 | +{ |
|---|
| 306 | + struct list_head *ws_head = &wakeup_sources; |
|---|
| 307 | + |
|---|
| 308 | + return list_next_or_null_rcu(ws_head, &ws->entry, |
|---|
| 309 | + struct wakeup_source, entry); |
|---|
| 310 | +} |
|---|
| 311 | +EXPORT_SYMBOL_GPL(wakeup_sources_walk_next); |
|---|
| 312 | + |
|---|
| 313 | +/** |
|---|
| 256 | 314 | * device_wakeup_attach - Attach a wakeup source object to a device object. |
|---|
| 257 | 315 | * @dev: Device to handle. |
|---|
| 258 | 316 | * @ws: Wakeup source object to attach to @dev. |
|---|
| .. | .. |
|---|
| 356 | 414 | int srcuidx; |
|---|
| 357 | 415 | |
|---|
| 358 | 416 | srcuidx = srcu_read_lock(&wakeup_srcu); |
|---|
| 359 | | - list_for_each_entry_rcu(ws, &wakeup_sources, entry) |
|---|
| 417 | + list_for_each_entry_rcu_locked(ws, &wakeup_sources, entry) |
|---|
| 360 | 418 | dev_pm_arm_wake_irq(ws->wakeirq); |
|---|
| 361 | 419 | srcu_read_unlock(&wakeup_srcu, srcuidx); |
|---|
| 362 | 420 | } |
|---|
| .. | .. |
|---|
| 372 | 430 | int srcuidx; |
|---|
| 373 | 431 | |
|---|
| 374 | 432 | srcuidx = srcu_read_lock(&wakeup_srcu); |
|---|
| 375 | | - list_for_each_entry_rcu(ws, &wakeup_sources, entry) |
|---|
| 433 | + list_for_each_entry_rcu_locked(ws, &wakeup_sources, entry) |
|---|
| 376 | 434 | dev_pm_disarm_wake_irq(ws->wakeirq); |
|---|
| 377 | 435 | srcu_read_unlock(&wakeup_srcu, srcuidx); |
|---|
| 378 | 436 | } |
|---|
| .. | .. |
|---|
| 798 | 856 | EXPORT_SYMBOL_GPL(pm_wakeup_ws_event); |
|---|
| 799 | 857 | |
|---|
| 800 | 858 | /** |
|---|
| 801 | | - * pm_wakeup_event - Notify the PM core of a wakeup event. |
|---|
| 859 | + * pm_wakeup_dev_event - Notify the PM core of a wakeup event. |
|---|
| 802 | 860 | * @dev: Device the wakeup event is related to. |
|---|
| 803 | 861 | * @msec: Anticipated event processing time (in milliseconds). |
|---|
| 804 | 862 | * @hard: If set, abort suspends in progress and wake up from suspend-to-idle. |
|---|
| .. | .. |
|---|
| 856 | 914 | struct wakeup_source *last_activity_ws = NULL; |
|---|
| 857 | 915 | |
|---|
| 858 | 916 | srcuidx = srcu_read_lock(&wakeup_srcu); |
|---|
| 859 | | - list_for_each_entry_rcu(ws, &wakeup_sources, entry) { |
|---|
| 917 | + list_for_each_entry_rcu_locked(ws, &wakeup_sources, entry) { |
|---|
| 860 | 918 | if (ws->active) { |
|---|
| 861 | | - pr_debug("active wakeup source: %s\n", ws->name); |
|---|
| 919 | + pm_pr_dbg("active wakeup source: %s\n", ws->name); |
|---|
| 862 | 920 | active = 1; |
|---|
| 863 | 921 | } else if (!active && |
|---|
| 864 | 922 | (!last_activity_ws || |
|---|
| .. | .. |
|---|
| 869 | 927 | } |
|---|
| 870 | 928 | |
|---|
| 871 | 929 | if (!active && last_activity_ws) |
|---|
| 872 | | - pr_debug("last active wakeup source: %s\n", |
|---|
| 930 | + pm_pr_dbg("last active wakeup source: %s\n", |
|---|
| 873 | 931 | last_activity_ws->name); |
|---|
| 874 | 932 | srcu_read_unlock(&wakeup_srcu, srcuidx); |
|---|
| 875 | 933 | } |
|---|
| .. | .. |
|---|
| 900 | 958 | raw_spin_unlock_irqrestore(&events_lock, flags); |
|---|
| 901 | 959 | |
|---|
| 902 | 960 | if (ret) { |
|---|
| 961 | + pm_pr_dbg("Wakeup pending, aborting suspend\n"); |
|---|
| 962 | + pm_print_active_wakeup_sources(); |
|---|
| 903 | 963 | pm_get_active_wakeup_sources(suspend_abort, |
|---|
| 904 | 964 | MAX_SUSPEND_ABORT_LEN); |
|---|
| 905 | 965 | log_suspend_abort_reason(suspend_abort); |
|---|
| .. | .. |
|---|
| 921 | 981 | atomic_dec_if_positive(&pm_abort_suspend); |
|---|
| 922 | 982 | } |
|---|
| 923 | 983 | |
|---|
| 924 | | -void pm_wakeup_clear(bool reset) |
|---|
| 984 | +void pm_wakeup_clear(unsigned int irq_number) |
|---|
| 925 | 985 | { |
|---|
| 926 | | - pm_wakeup_irq = 0; |
|---|
| 927 | | - if (reset) |
|---|
| 986 | + raw_spin_lock_irq(&wakeup_irq_lock); |
|---|
| 987 | + |
|---|
| 988 | + if (irq_number && wakeup_irq[0] == irq_number) |
|---|
| 989 | + wakeup_irq[0] = wakeup_irq[1]; |
|---|
| 990 | + else |
|---|
| 991 | + wakeup_irq[0] = 0; |
|---|
| 992 | + |
|---|
| 993 | + wakeup_irq[1] = 0; |
|---|
| 994 | + |
|---|
| 995 | + raw_spin_unlock_irq(&wakeup_irq_lock); |
|---|
| 996 | + |
|---|
| 997 | + if (!irq_number) |
|---|
| 928 | 998 | atomic_set(&pm_abort_suspend, 0); |
|---|
| 929 | 999 | } |
|---|
| 930 | 1000 | |
|---|
| 931 | 1001 | void pm_system_irq_wakeup(unsigned int irq_number) |
|---|
| 932 | 1002 | { |
|---|
| 933 | | - if (pm_wakeup_irq == 0) { |
|---|
| 1003 | + unsigned long flags; |
|---|
| 1004 | + |
|---|
| 1005 | + raw_spin_lock_irqsave(&wakeup_irq_lock, flags); |
|---|
| 1006 | + |
|---|
| 1007 | + if (wakeup_irq[0] == 0) |
|---|
| 1008 | + wakeup_irq[0] = irq_number; |
|---|
| 1009 | + else if (wakeup_irq[1] == 0) |
|---|
| 1010 | + wakeup_irq[1] = irq_number; |
|---|
| 1011 | + else |
|---|
| 1012 | + irq_number = 0; |
|---|
| 1013 | + |
|---|
| 1014 | + raw_spin_unlock_irqrestore(&wakeup_irq_lock, flags); |
|---|
| 1015 | + |
|---|
| 1016 | + if (irq_number) { |
|---|
| 934 | 1017 | struct irq_desc *desc; |
|---|
| 935 | 1018 | const char *name = "null"; |
|---|
| 936 | 1019 | |
|---|
| .. | .. |
|---|
| 942 | 1025 | |
|---|
| 943 | 1026 | log_irq_wakeup_reason(irq_number); |
|---|
| 944 | 1027 | pr_warn("%s: %d triggered %s\n", __func__, irq_number, name); |
|---|
| 945 | | - |
|---|
| 946 | | - pm_wakeup_irq = irq_number; |
|---|
| 947 | 1028 | pm_system_wakeup(); |
|---|
| 948 | 1029 | } |
|---|
| 1030 | +} |
|---|
| 1031 | + |
|---|
| 1032 | +unsigned int pm_wakeup_irq(void) |
|---|
| 1033 | +{ |
|---|
| 1034 | + return wakeup_irq[0]; |
|---|
| 949 | 1035 | } |
|---|
| 950 | 1036 | |
|---|
| 951 | 1037 | /** |
|---|
| .. | .. |
|---|
| 1022 | 1108 | int srcuidx; |
|---|
| 1023 | 1109 | |
|---|
| 1024 | 1110 | srcuidx = srcu_read_lock(&wakeup_srcu); |
|---|
| 1025 | | - list_for_each_entry_rcu(ws, &wakeup_sources, entry) { |
|---|
| 1111 | + list_for_each_entry_rcu_locked(ws, &wakeup_sources, entry) { |
|---|
| 1026 | 1112 | spin_lock_irq(&ws->lock); |
|---|
| 1027 | 1113 | if (ws->autosleep_enabled != set) { |
|---|
| 1028 | 1114 | ws->autosleep_enabled = set; |
|---|
| .. | .. |
|---|
| 1038 | 1124 | srcu_read_unlock(&wakeup_srcu, srcuidx); |
|---|
| 1039 | 1125 | } |
|---|
| 1040 | 1126 | #endif /* CONFIG_PM_AUTOSLEEP */ |
|---|
| 1041 | | - |
|---|
| 1042 | | -static struct dentry *wakeup_sources_stats_dentry; |
|---|
| 1043 | 1127 | |
|---|
| 1044 | 1128 | /** |
|---|
| 1045 | 1129 | * print_wakeup_source_stats - Print wakeup source statistics information. |
|---|
| .. | .. |
|---|
| 1103 | 1187 | } |
|---|
| 1104 | 1188 | |
|---|
| 1105 | 1189 | *srcuidx = srcu_read_lock(&wakeup_srcu); |
|---|
| 1106 | | - list_for_each_entry_rcu(ws, &wakeup_sources, entry) { |
|---|
| 1190 | + list_for_each_entry_rcu_locked(ws, &wakeup_sources, entry) { |
|---|
| 1107 | 1191 | if (n-- <= 0) |
|---|
| 1108 | 1192 | return ws; |
|---|
| 1109 | 1193 | } |
|---|
| .. | .. |
|---|
| 1123 | 1207 | next_ws = ws; |
|---|
| 1124 | 1208 | break; |
|---|
| 1125 | 1209 | } |
|---|
| 1210 | + |
|---|
| 1211 | + if (!next_ws) |
|---|
| 1212 | + print_wakeup_source_stats(m, &deleted_ws); |
|---|
| 1126 | 1213 | |
|---|
| 1127 | 1214 | return next_ws; |
|---|
| 1128 | 1215 | } |
|---|
| .. | .. |
|---|
| 1170 | 1257 | |
|---|
| 1171 | 1258 | static int __init wakeup_sources_debugfs_init(void) |
|---|
| 1172 | 1259 | { |
|---|
| 1173 | | - wakeup_sources_stats_dentry = debugfs_create_file("wakeup_sources", |
|---|
| 1174 | | - S_IRUGO, NULL, NULL, &wakeup_sources_stats_fops); |
|---|
| 1260 | + debugfs_create_file("wakeup_sources", S_IRUGO, NULL, NULL, |
|---|
| 1261 | + &wakeup_sources_stats_fops); |
|---|
| 1175 | 1262 | return 0; |
|---|
| 1176 | 1263 | } |
|---|
| 1177 | 1264 | |
|---|