summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/acpi/apei/ghes.c2
-rw-r--r--drivers/acpi/ec.c10
-rw-r--r--drivers/acpi/internal.h6
-rw-r--r--drivers/acpi/scan.c108
-rw-r--r--drivers/acpi/sleep.c11
-rw-r--r--drivers/acpi/sleep.h8
-rw-r--r--drivers/acpi/x86/s2idle.c61
7 files changed, 175 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");