Messages in this thread Patch in this message | ![/](/images/icornerl.gif) | | From | Vimal Kumar <> | Subject | [PATCH v2] PM / sleep: Mechanism to find source aborting kernel suspend transition | Date | Sun, 10 Dec 2023 15:33:01 +0530 |
| |
Sometimes kernel suspend transitions can be aborted unconditionally by manipulating pm_abort_suspend value using "hard" wakeup triggers or through "pm_system_wakeup()".
There is no way to trace the source path of module or subsystem which aborted the suspend transitions. This change will create a list of wakeup sources aborting suspend in progress through "hard" events as well as subsytems aborting suspend using "pm_system_wakeup()".
Example: Existing suspend failure logs: [ 349.708359] PM: Some devices failed to suspend, or early wake event detected [ 350.327842] PM: suspend exit
Suspend failure logs with this change: [ 518.761835] PM: Some devices failed to suspend, or early wake event detected [ 519.486939] PM: wakeup source or subsystem uart_suspend_port aborted suspend [ 519.500594] PM: suspend exit
Here we can clearly identify the module triggerring abort suspend.
Co-developed-by: Chinmoy Ghosh <chinmoyghosh2001@gmail.com> Signed-off-by: Chinmoy Ghosh <chinmoyghosh2001@gmail.com> Co-developed-by: Mintu Patel <mintupatel89@gmail.com> Signed-off-by: Mintu Patel <mintupatel89@gmail.com> Co-developed-by: Vishal Badole <badolevishal1116@gmail.com> Signed-off-by: Vishal Badole <badolevishal1116@gmail.com> Signed-off-by: Vimal Kumar <vimal.kumar32@gmail.com> --- Changes in v2: - Make pm_abort_suspend_list static. - Added locks. - Removed list_empty check as not needed. - Fix Doc format. - Fix function name as per global functions. - Removed use of unlikely/likely. - Removed extra logging for kmalloc failure. - Use *info instead of struct name. --- drivers/base/power/wakeup.c | 82 ++++++++++++++++++++++++++++++++++++- include/linux/suspend.h | 2 + kernel/power/suspend.c | 1 + 3 files changed, 84 insertions(+), 1 deletion(-)
diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index a917219feea6..fa1e3369b0f6 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -73,6 +73,15 @@ static struct wakeup_source deleted_ws = { static DEFINE_IDA(wakeup_ida); +#define MAX_SUSPEND_ABORT_LEN 256 +static DEFINE_RAW_SPINLOCK(abort_suspend_lock); + +struct pm_abort_suspend_source { + struct list_head list; + char *source_triggering_abort_suspend; +}; +static LIST_HEAD(pm_abort_suspend_list); + /** * wakeup_source_create - Create a struct wakeup_source object. * @name: Name of the new wakeup source. @@ -575,6 +584,56 @@ static void wakeup_source_activate(struct wakeup_source *ws) trace_wakeup_source_activate(ws->name, cec); } +/** + * abort_suspend_list_clear - Clear pm_abort_suspend_list. + * + * The pm_abort_suspend_list will be cleared when system PM exits. + */ +void abort_suspend_list_clear(void) +{ + struct pm_abort_suspend_source *info, *tmp; + unsigned long flags; + + raw_spin_lock_irqsave(&abort_suspend_lock, flags); + list_for_each_entry_safe(info, tmp, &pm_abort_suspend_list, list) { + list_del(&info->list); + kfree(info); + } + raw_spin_unlock_irqrestore(&abort_suspend_lock, flags); +} +EXPORT_SYMBOL_GPL(abort_suspend_list_clear); + +/** + * pm_abort_suspend_source_add - Update pm_abort_suspend_list + * @source_name: Wakeup_source or function aborting suspend transitions. + * + * Add the source name responsible for updating the abort_suspend flag in the + * pm_abort_suspend_list. + */ +static void pm_abort_suspend_source_add(const char *source_name) +{ + struct pm_abort_suspend_source *info; + unsigned long flags; + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return; + + /* Initialize the list within the struct if it's not already initialized */ + if (list_empty(&info->list)) + INIT_LIST_HEAD(&info->list); + + info->source_triggering_abort_suspend = kstrdup(source_name, GFP_KERNEL); + if (!info->source_triggering_abort_suspend) { + kfree(info); + return; + } + + raw_spin_lock_irqsave(&abort_suspend_lock, flags); + list_add_tail(&info->list, &pm_abort_suspend_list); + raw_spin_unlock_irqrestore(&abort_suspend_lock, flags); +} + /** * wakeup_source_report_event - Report wakeup event using the given source. * @ws: Wakeup source to report the event for. @@ -590,8 +649,11 @@ static void wakeup_source_report_event(struct wakeup_source *ws, bool hard) if (!ws->active) wakeup_source_activate(ws); - if (hard) + if (hard) { + if (pm_suspend_target_state != PM_SUSPEND_ON) + pm_abort_suspend_source_add(ws->name); pm_system_wakeup(); + } } /** @@ -877,6 +939,7 @@ bool pm_wakeup_pending(void) { unsigned long flags; bool ret = false; + struct pm_abort_suspend_source *info; raw_spin_lock_irqsave(&events_lock, flags); if (events_check_enabled) { @@ -893,12 +956,29 @@ bool pm_wakeup_pending(void) pm_print_active_wakeup_sources(); } + if (atomic_read(&pm_abort_suspend) > 0) { + raw_spin_lock_irqsave(&abort_suspend_lock, flags); + list_for_each_entry(info, &pm_abort_suspend_list, list) { + pm_pr_dbg("wakeup source or subsystem %s aborted suspend\n", + info->source_triggering_abort_suspend); + } + raw_spin_unlock_irqrestore(&abort_suspend_lock, flags); + } + return ret || atomic_read(&pm_abort_suspend) > 0; } EXPORT_SYMBOL_GPL(pm_wakeup_pending); void pm_system_wakeup(void) { + char buf[MAX_SUSPEND_ABORT_LEN]; + + if (pm_suspend_target_state != PM_SUSPEND_ON) { + sprintf(buf, "%ps", __builtin_return_address(0)); + if (strcmp(buf, "pm_wakeup_ws_event")) + pm_abort_suspend_source_add(buf); + } + atomic_inc(&pm_abort_suspend); s2idle_wake(); } diff --git a/include/linux/suspend.h b/include/linux/suspend.h index ef503088942d..2eef5817b825 100644 --- a/include/linux/suspend.h +++ b/include/linux/suspend.h @@ -528,6 +528,7 @@ extern void pm_print_active_wakeup_sources(void); extern unsigned int lock_system_sleep(void); extern void unlock_system_sleep(unsigned int); +extern void abort_suspend_list_clear(void); #else /* !CONFIG_PM_SLEEP */ @@ -556,6 +557,7 @@ static inline void pm_system_irq_wakeup(unsigned int irq_number) {} static inline unsigned int lock_system_sleep(void) { return 0; } static inline void unlock_system_sleep(unsigned int flags) {} +static inline void abort_suspend_list_clear(void) {} #endif /* !CONFIG_PM_SLEEP */ diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c index fa3bf161d13f..49c830246930 100644 --- a/kernel/power/suspend.c +++ b/kernel/power/suspend.c @@ -623,6 +623,7 @@ int pm_suspend(suspend_state_t state) } else { suspend_stats.success++; } + abort_suspend_list_clear(); pr_info("suspend exit\n"); return error; } -- 2.25.1
| ![\](/images/icornerr.gif) |