diff options
| -rw-r--r-- | drivers/acpi/apei/ghes.c | 2 | ||||
| -rw-r--r-- | drivers/acpi/ec.c | 10 | ||||
| -rw-r--r-- | drivers/acpi/internal.h | 6 | ||||
| -rw-r--r-- | drivers/acpi/scan.c | 108 | ||||
| -rw-r--r-- | drivers/acpi/sleep.c | 11 | ||||
| -rw-r--r-- | drivers/acpi/sleep.h | 8 | ||||
| -rw-r--r-- | drivers/acpi/x86/s2idle.c | 61 | ||||
| -rw-r--r-- | include/acpi/acpi_bus.h | 1 |
8 files changed, 176 insertions, 31 deletions
diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index 663dc2e601cf..5d888c82efea 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -899,7 +899,7 @@ EXPORT_SYMBOL_NS_GPL(cxl_cper_kfifo_get, "CXL"); static void ghes_log_hwerr(int sev, guid_t *sec_type) { - if (sev != CPER_SEV_RECOVERABLE) + if (sev != GHES_SEV_RECOVERABLE) return; if (guid_equal(sec_type, &CPER_SEC_PROC_ARM) || diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index a89f10256dbb..db24a9a252a8 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -2167,19 +2167,19 @@ static bool acpi_ec_work_in_progress(struct acpi_ec *ec) return ec->events_in_progress + ec->queries_in_progress > 0; } -bool acpi_ec_dispatch_gpe(void) +int acpi_ec_dispatch_gpe(void) { bool work_in_progress = false; if (!first_ec) - return acpi_any_gpe_status_set(U32_MAX); + return -ENODEV; /* * Report wakeup if the status bit is set for any enabled GPE other * than the EC one. */ if (acpi_any_gpe_status_set(first_ec->gpe)) - return true; + return 1; /* * Cancel the SCI wakeup and process all pending events in case there @@ -2207,7 +2207,7 @@ bool acpi_ec_dispatch_gpe(void) spin_unlock_irq(&first_ec->lock); if (!work_in_progress) - return false; + return 0; pm_pr_dbg("ACPI EC GPE dispatched\n"); @@ -2224,7 +2224,7 @@ bool acpi_ec_dispatch_gpe(void) spin_unlock_irq(&first_ec->lock); } while (work_in_progress && !pm_wakeup_pending()); - return false; + return 0; } #endif /* CONFIG_PM_SLEEP */ diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h index 011da4ed6a80..472c383eaaec 100644 --- a/drivers/acpi/internal.h +++ b/drivers/acpi/internal.h @@ -237,7 +237,7 @@ void acpi_ec_register_opregions(struct acpi_device *adev); #ifdef CONFIG_PM_SLEEP void acpi_ec_flush_work(void); -bool acpi_ec_dispatch_gpe(void); +int acpi_ec_dispatch_gpe(void); #endif #else @@ -257,9 +257,9 @@ static inline void acpi_ec_remove_query_handler(struct acpi_ec *ec, u8 query_bit static inline void acpi_ec_register_opregions(struct acpi_device *adev) {} static inline void acpi_ec_flush_work(void) {} -static inline bool acpi_ec_dispatch_gpe(void) +static inline int acpi_ec_dispatch_gpe(void) { - return false; + return -EOPNOTSUPP; } #endif diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 96b8b0dfe339..9218264f8da1 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -115,11 +115,7 @@ bool acpi_scan_is_offline(struct acpi_device *adev, bool uevent) bool offline = true; char *envp[] = { "EVENT=offline", NULL }; - /* - * acpi_container_offline() calls this for all of the container's - * children under the container's physical_node_lock lock. - */ - mutex_lock_nested(&adev->physical_node_lock, SINGLE_DEPTH_NESTING); + mutex_lock(&adev->physical_node_lock); list_for_each_entry(pn, &adev->physical_node_list, node) if (device_supports_offline(pn->dev) && !dev_offline(pn->dev)) { @@ -134,6 +130,59 @@ bool acpi_scan_is_offline(struct acpi_device *adev, bool uevent) return offline; } +static struct acpi_device_physical_node * +acpi_find_physical_node(struct acpi_device *adev, struct device *dev) +{ + struct acpi_device_physical_node *pn; + + lockdep_assert_held(&adev->physical_node_lock); + + list_for_each_entry(pn, &adev->physical_node_list, node) + if (pn->dev == dev) + return pn; + + return NULL; +} + +static struct device *acpi_next_offline_pending(struct acpi_device *adev) +{ + struct acpi_device_physical_node *pn; + + lockdep_assert_held(&adev->physical_node_lock); + + /* + * Find the first physical node that is still to be offlined, clear its + * offline_pending flag and return its device with a reference held. + */ + list_for_each_entry(pn, &adev->physical_node_list, node) + if (pn->offline_pending) { + pn->offline_pending = false; + return get_device(pn->dev); + } + + return NULL; +} + +static struct device *acpi_next_put_online(struct acpi_device *adev) +{ + struct acpi_device_physical_node *pn; + + lockdep_assert_held(&adev->physical_node_lock); + + /* + * Find the first physical node that has been offlined by + * acpi_bus_offline(), clear its put_online flag and return its device + * with a reference held. + */ + list_for_each_entry(pn, &adev->physical_node_list, node) + if (pn->put_online) { + pn->put_online = false; + return get_device(pn->dev); + } + + return NULL; +} + static acpi_status acpi_bus_offline(acpi_handle handle, u32 lvl, void *data, void **ret_p) { @@ -141,6 +190,7 @@ static acpi_status acpi_bus_offline(acpi_handle handle, u32 lvl, void *data, struct acpi_device_physical_node *pn; bool second_pass = (bool)data; acpi_status status = AE_OK; + struct device *dev; if (!device) return AE_OK; @@ -153,25 +203,43 @@ static acpi_status acpi_bus_offline(acpi_handle handle, u32 lvl, void *data, mutex_lock(&device->physical_node_lock); list_for_each_entry(pn, &device->physical_node_list, node) { - int ret; - if (second_pass) { /* Skip devices offlined by the first pass. */ - if (pn->put_online) - continue; + pn->offline_pending = !pn->put_online; } else { + pn->offline_pending = true; pn->put_online = false; } - ret = device_offline(pn->dev); + } + + /* + * Drop physical_node_lock around device_offline(), because the latter + * acquires locks, like the CPU hotplug ones, that must not nest under + * it. + */ + while ((dev = acpi_next_offline_pending(device))) { + int ret; + + mutex_unlock(&device->physical_node_lock); + ret = device_offline(dev); + mutex_lock(&device->physical_node_lock); + if (ret >= 0) { - pn->put_online = !ret; + /* The device may have been unbound in the meantime. */ + pn = acpi_find_physical_node(device, dev); + if (pn) + pn->put_online = !ret; } else { - *ret_p = pn->dev; + *ret_p = &device->dev; if (second_pass) { + dev_warn(dev, "Offline failed.\n"); status = AE_ERROR; - break; } } + + put_device(dev); + if (ACPI_FAILURE(status)) + break; } mutex_unlock(&device->physical_node_lock); @@ -183,18 +251,19 @@ static acpi_status acpi_bus_online(acpi_handle handle, u32 lvl, void *data, void **ret_p) { struct acpi_device *device = acpi_fetch_acpi_dev(handle); - struct acpi_device_physical_node *pn; + struct device *dev; if (!device) return AE_OK; mutex_lock(&device->physical_node_lock); - list_for_each_entry(pn, &device->physical_node_list, node) - if (pn->put_online) { - device_online(pn->dev); - pn->put_online = false; - } + while ((dev = acpi_next_put_online(device))) { + mutex_unlock(&device->physical_node_lock); + device_online(dev); + mutex_lock(&device->physical_node_lock); + put_device(dev); + } mutex_unlock(&device->physical_node_lock); @@ -236,7 +305,6 @@ static int acpi_scan_try_to_offline(struct acpi_device *device) (void **)&errdev); if (errdev) { - dev_warn(errdev, "Offline failed.\n"); acpi_bus_online(handle, 0, NULL, NULL); acpi_walk_namespace(ACPI_TYPE_ANY, handle, ACPI_UINT32_MAX, acpi_bus_online, diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c index a94bac4422d4..641056e0252b 100644 --- a/drivers/acpi/sleep.c +++ b/drivers/acpi/sleep.c @@ -760,6 +760,8 @@ bool acpi_s2idle_wake(void) return pm_wakeup_pending(); while (pm_wakeup_pending()) { + int ret; + /* * If IRQD_WAKEUP_ARMED is set for the SCI at this point, the * SCI has not triggered while suspended, so bail out (the @@ -788,11 +790,16 @@ bool acpi_s2idle_wake(void) /* * Check non-EC GPE wakeups and if there are none, cancel the - * SCI-related wakeup and dispatch the EC GPE. + * SCI-related wakeup and dispatch the EC GPE. If there is no + * EC to consult, check the GPEs armed for wakeup via _Ixx + * methods instead. */ - if (acpi_ec_dispatch_gpe()) { + ret = acpi_ec_dispatch_gpe(); + if (ret > 0) { pm_pr_dbg("ACPI non-EC GPE wakeup\n"); return true; + } else if (ret < 0 && acpi_x86_s2idle_check_ixx_gpes()) { + return true; } acpi_os_wait_events_complete(); diff --git a/drivers/acpi/sleep.h b/drivers/acpi/sleep.h index 9c3cb109c5d2..955e9c22bf0d 100644 --- a/drivers/acpi/sleep.h +++ b/drivers/acpi/sleep.h @@ -20,6 +20,14 @@ extern int acpi_s2idle_prepare(void); extern bool acpi_s2idle_wake(void); extern void acpi_s2idle_restore(void); extern void acpi_s2idle_end(void); +#if defined(CONFIG_X86) && defined(CONFIG_SUSPEND) +extern bool acpi_x86_s2idle_check_ixx_gpes(void); +#else +static inline bool acpi_x86_s2idle_check_ixx_gpes(void) +{ + return acpi_any_gpe_status_set(U32_MAX); +} +#endif extern void acpi_s2idle_setup(void); diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index c278ea14c0b3..b79bf0fa9a7f 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c @@ -24,6 +24,13 @@ #ifdef CONFIG_SUSPEND +/* + * First GPE armed for wake via an _Ixx method, U32_MAX if none. On + * platforms without an ACPI-enumerated EC, this GPE plays the role of + * the EC GPE in acpi_ec_dispatch_gpe(). + */ +static u32 acpi_s2idle_ixx_gpe = U32_MAX; + static void acpi_setup_ixx_gpes(void) { acpi_handle gpe_root; @@ -47,11 +54,65 @@ static void acpi_setup_ixx_gpes(void) acpi_mark_gpe_for_wake(NULL, i); acpi_set_gpe_wake_mask(NULL, i, ACPI_GPE_ENABLE); + if (acpi_s2idle_ixx_gpe == U32_MAX) + acpi_s2idle_ixx_gpe = i; pm_pr_dbg("ACPI: GPE0x%02x armed for wake via %s\n", i, gpe_nr_str); } } +/* + * acpi_x86_s2idle_check_ixx_gpes - Handle an SCI wakeup without an EC + * + * Called to decide whether or not to wake up the system when an SCI has + * triggered during suspend-to-idle on a platform without an EC instance, + * mirroring acpi_ec_dispatch_gpe() with the first GPE armed for wake via + * an _Ixx method playing the role of the EC GPE. + * + * Report a wakeup if the status bit is set for any enabled GPE other than + * the _Ixx one. If no _Ixx GPE has been armed, acpi_s2idle_ixx_gpe is + * U32_MAX and this amounts to checking all of the GPEs, which is the + * previous behavior. + * + * Otherwise, cancel the pending SCI-related wakeup, so it does not cause + * the pm_wakeup_pending() check in the caller to succeed by itself, and + * dispatch the _Ixx GPE if it is active, so the events behind it can be + * processed. If any of those events turn out to be genuine wake events, + * the drivers handling them are expected to call pm_wakeup_event() and + * the caller will catch that via pm_wakeup_pending() after synchronizing + * the event processing. + */ +bool acpi_x86_s2idle_check_ixx_gpes(void) +{ + acpi_event_status status = 0; + + /* + * Report wakeup if the status bit is set for any enabled GPE other + * than the _Ixx one. + */ + if (acpi_any_gpe_status_set(acpi_s2idle_ixx_gpe)) + return true; + + if (acpi_s2idle_ixx_gpe == U32_MAX) + return false; + + /* + * Cancel the SCI wakeup and dispatch the _Ixx GPE in case there are + * any wakeup events behind it. + */ + pm_system_cancel_wakeup(); + + acpi_get_gpe_status(NULL, acpi_s2idle_ixx_gpe, &status); + if (status & ACPI_EVENT_FLAG_STATUS_SET) { + pm_pr_dbg("ACPI _Ixx GPE 0x%02x dispatched\n", + acpi_s2idle_ixx_gpe); + + acpi_dispatch_gpe(NULL, acpi_s2idle_ixx_gpe); + } + + return false; +} + static bool sleep_no_lps0 __read_mostly; module_param(sleep_no_lps0, bool, 0644); MODULE_PARM_DESC(sleep_no_lps0, "Do not use the special LPS0 device interface"); diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index f59b2de865e6..fad52a96d613 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -327,6 +327,7 @@ struct acpi_device_physical_node { struct device *dev; unsigned int node_id; bool put_online:1; + bool offline_pending:1; }; struct acpi_device_properties { |
