[PATCH v3] ACPI: button: Report wakeup key only for power button wakeups

Baorui.Liu posted 1 patch 1 week ago
There is a newer version of this series
drivers/acpi/button.c | 13 ++++++++++++-
drivers/acpi/sleep.c  | 41 -----------------------------------------
2 files changed, 12 insertions(+), 42 deletions(-)
[PATCH v3] ACPI: button: Report wakeup key only for power button wakeups
Posted by Baorui.Liu 1 week ago
The ACPI button driver reports KEY_WAKEUP from the Power Button input
device to let userspace know that the system was resumed by a power
button wakeup.

However, reporting KEY_WAKEUP from generic system resume paths can make
userspace observe a Power Button wakeup even when the system was resumed
by a different wake source.

This is reproducible on an AMD Android 15 Xen guest. With a kernel
without this fix, a non-power-button S3 resume:

  echo mem > /sys/power/state
  xl trigger android s3resume

makes the Power Button input device report KEY_WAKEUP. The same test on
a kernel with this fix no longer reports KEY_WAKEUP from the Power Button
input device.

Track whether a power button event/notify is observed while the ACPI
button device is suspended, and report KEY_WAKEUP on resume only in that
case. Do not synthesize a Power Button input event from generic ACPI
sleep resume code.

Signed-off-by: Baorui.Liu <baorliu@amd.com>
---
 drivers/acpi/button.c | 13 ++++++++++++-
 drivers/acpi/sleep.c  | 41 -----------------------------------------
 2 files changed, 12 insertions(+), 42 deletions(-)

diff --git a/drivers/acpi/button.c b/drivers/acpi/button.c
index cdbb1023a8ee..7a708fe351fb 100644
--- a/drivers/acpi/button.c
+++ b/drivers/acpi/button.c
@@ -191,6 +191,7 @@ struct acpi_button {
 	bool last_state;
 	ktime_t last_time;
 	bool suspended;
+	bool wakeup_pending;
 	bool lid_state_initialized;
 	bool gpe_enabled;
 };
@@ -476,6 +477,9 @@ static void acpi_button_notify(acpi_handle handle, u32 event, void *data)
 
 	acpi_pm_wakeup_event(button->dev);
 
+	if (button->type == ACPI_BUTTON_TYPE_POWER && button->suspended)
+		button->wakeup_pending = true;
+
 	if (button->suspended || event == ACPI_BUTTON_NOTIFY_WAKE)
 		return;
 
@@ -498,6 +502,11 @@ static void acpi_button_notify_run(void *data)
 
 static u32 acpi_button_event(void *data)
 {
+	struct acpi_button *button = data;
+
+	if (button->type == ACPI_BUTTON_TYPE_POWER && button->suspended)
+		button->wakeup_pending = true;
+
 	acpi_os_execute(OSL_NOTIFY_HANDLER, acpi_button_notify_run, data);
 	return ACPI_INTERRUPT_HANDLED;
 }
@@ -508,6 +517,7 @@ static int acpi_button_suspend(struct device *dev)
 	struct acpi_button *button = dev_get_drvdata(dev);
 
 	button->suspended = true;
+	button->wakeup_pending = false;
 	return 0;
 }
 
@@ -523,12 +533,13 @@ static int acpi_button_resume(struct device *dev)
 		acpi_lid_initialize_state(button);
 	}
 
-	if (button->type == ACPI_BUTTON_TYPE_POWER) {
+	if (button->type == ACPI_BUTTON_TYPE_POWER && button->wakeup_pending) {
 		input = button->input;
 		input_report_key(input, KEY_WAKEUP, 1);
 		input_sync(input);
 		input_report_key(input, KEY_WAKEUP, 0);
 		input_sync(input);
+		button->wakeup_pending = false;
 	}
 	return 0;
 }
diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c
index 132a9df98471..7296e4bb0e4d 100644
--- a/drivers/acpi/sleep.c
+++ b/drivers/acpi/sleep.c
@@ -104,8 +104,6 @@ u32 acpi_target_system_state(void)
 }
 EXPORT_SYMBOL_GPL(acpi_target_system_state);
 
-static bool pwr_btn_event_pending;
-
 /*
  * The ACPI specification wants us to save NVS memory regions during hibernation
  * and to restore them during the subsequent resume.  Windows does that also for
@@ -484,7 +482,6 @@ static int acpi_pm_prepare(void)
  */
 static void acpi_pm_finish(void)
 {
-	struct acpi_device *pwr_btn_adev;
 	u32 acpi_state = acpi_target_sleep_state;
 
 	acpi_ec_unblock_transactions();
@@ -503,23 +500,6 @@ static void acpi_pm_finish(void)
 	acpi_target_sleep_state = ACPI_STATE_S0;
 
 	acpi_resume_power_resources();
-
-	/* If we were woken with the fixed power button, provide a small
-	 * hint to userspace in the form of a wakeup event on the fixed power
-	 * button device (if it can be found).
-	 *
-	 * We delay the event generation til now, as the PM layer requires
-	 * timekeeping to be running before we generate events. */
-	if (!pwr_btn_event_pending)
-		return;
-
-	pwr_btn_event_pending = false;
-	pwr_btn_adev = acpi_dev_get_first_match_dev(ACPI_BUTTON_HID_POWERF,
-						    NULL, -1);
-	if (pwr_btn_adev) {
-		pm_wakeup_event(&pwr_btn_adev->dev, 0);
-		acpi_dev_put(pwr_btn_adev);
-	}
 }
 
 /**
@@ -626,27 +606,6 @@ static int acpi_suspend_enter(suspend_state_t pm_state)
 	/* Reprogram control registers */
 	acpi_leave_sleep_state_prep(acpi_state);
 
-	/* ACPI 3.0 specs (P62) says that it's the responsibility
-	 * of the OSPM to clear the status bit [ implying that the
-	 * POWER_BUTTON event should not reach userspace ]
-	 *
-	 * However, we do generate a small hint for userspace in the form of
-	 * a wakeup event. We flag this condition for now and generate the
-	 * event later, as we're currently too early in resume to be able to
-	 * generate wakeup events.
-	 */
-	if (ACPI_SUCCESS(status) && (acpi_state == ACPI_STATE_S3)) {
-		acpi_event_status pwr_btn_status = ACPI_EVENT_FLAG_DISABLED;
-
-		acpi_get_event_status(ACPI_EVENT_POWER_BUTTON, &pwr_btn_status);
-
-		if (pwr_btn_status & ACPI_EVENT_FLAG_STATUS_SET) {
-			acpi_clear_event(ACPI_EVENT_POWER_BUTTON);
-			/* Flag for later */
-			pwr_btn_event_pending = true;
-		}
-	}
-
 	/*
 	 * Disable all GPE and clear their status bits before interrupts are
 	 * enabled. Some GPEs (like wakeup GPEs) have no handlers and this can
-- 
2.34.1
Re: [PATCH v3] ACPI: button: Report wakeup key only for power button wakeups
Posted by Rafael J. Wysocki (Intel) 6 days, 10 hours ago
On Thu, Sep 17, 2026 at 4:31 PM Baorui.Liu <baorliu@amd.com> wrote:
>
> The ACPI button driver reports KEY_WAKEUP from the Power Button input
> device to let userspace know that the system was resumed by a power
> button wakeup.
>
> However, reporting KEY_WAKEUP from generic system resume paths can make
> userspace observe a Power Button wakeup even when the system was resumed
> by a different wake source.
>
> This is reproducible on an AMD Android 15 Xen guest. With a kernel
> without this fix, a non-power-button S3 resume:
>
>   echo mem > /sys/power/state
>   xl trigger android s3resume
>
> makes the Power Button input device report KEY_WAKEUP. The same test on
> a kernel with this fix no longer reports KEY_WAKEUP from the Power Button
> input device.
>
> Track whether a power button event/notify is observed while the ACPI
> button device is suspended, and report KEY_WAKEUP on resume only in that
> case. Do not synthesize a Power Button input event from generic ACPI
> sleep resume code.
>
> Signed-off-by: Baorui.Liu <baorliu@amd.com>

Please see

https://sashiko.dev/#/patchset/20260917143116.445-1-baorliu%40amd.com

and tell me what you think.

Thanks!

> ---
>  drivers/acpi/button.c | 13 ++++++++++++-
>  drivers/acpi/sleep.c  | 41 -----------------------------------------
>  2 files changed, 12 insertions(+), 42 deletions(-)
>
> diff --git a/drivers/acpi/button.c b/drivers/acpi/button.c
> index cdbb1023a8ee..7a708fe351fb 100644
> --- a/drivers/acpi/button.c
> +++ b/drivers/acpi/button.c
> @@ -191,6 +191,7 @@ struct acpi_button {
>         bool last_state;
>         ktime_t last_time;
>         bool suspended;
> +       bool wakeup_pending;
>         bool lid_state_initialized;
>         bool gpe_enabled;
>  };
> @@ -476,6 +477,9 @@ static void acpi_button_notify(acpi_handle handle, u32 event, void *data)
>
>         acpi_pm_wakeup_event(button->dev);
>
> +       if (button->type == ACPI_BUTTON_TYPE_POWER && button->suspended)
> +               button->wakeup_pending = true;
> +
>         if (button->suspended || event == ACPI_BUTTON_NOTIFY_WAKE)
>                 return;
>
> @@ -498,6 +502,11 @@ static void acpi_button_notify_run(void *data)
>
>  static u32 acpi_button_event(void *data)
>  {
> +       struct acpi_button *button = data;
> +
> +       if (button->type == ACPI_BUTTON_TYPE_POWER && button->suspended)
> +               button->wakeup_pending = true;
> +
>         acpi_os_execute(OSL_NOTIFY_HANDLER, acpi_button_notify_run, data);
>         return ACPI_INTERRUPT_HANDLED;
>  }
> @@ -508,6 +517,7 @@ static int acpi_button_suspend(struct device *dev)
>         struct acpi_button *button = dev_get_drvdata(dev);
>
>         button->suspended = true;
> +       button->wakeup_pending = false;
>         return 0;
>  }
>
> @@ -523,12 +533,13 @@ static int acpi_button_resume(struct device *dev)
>                 acpi_lid_initialize_state(button);
>         }
>
> -       if (button->type == ACPI_BUTTON_TYPE_POWER) {
> +       if (button->type == ACPI_BUTTON_TYPE_POWER && button->wakeup_pending) {
>                 input = button->input;
>                 input_report_key(input, KEY_WAKEUP, 1);
>                 input_sync(input);
>                 input_report_key(input, KEY_WAKEUP, 0);
>                 input_sync(input);
> +               button->wakeup_pending = false;
>         }
>         return 0;
>  }
> diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c
> index 132a9df98471..7296e4bb0e4d 100644
> --- a/drivers/acpi/sleep.c
> +++ b/drivers/acpi/sleep.c
> @@ -104,8 +104,6 @@ u32 acpi_target_system_state(void)
>  }
>  EXPORT_SYMBOL_GPL(acpi_target_system_state);
>
> -static bool pwr_btn_event_pending;
> -
>  /*
>   * The ACPI specification wants us to save NVS memory regions during hibernation
>   * and to restore them during the subsequent resume.  Windows does that also for
> @@ -484,7 +482,6 @@ static int acpi_pm_prepare(void)
>   */
>  static void acpi_pm_finish(void)
>  {
> -       struct acpi_device *pwr_btn_adev;
>         u32 acpi_state = acpi_target_sleep_state;
>
>         acpi_ec_unblock_transactions();
> @@ -503,23 +500,6 @@ static void acpi_pm_finish(void)
>         acpi_target_sleep_state = ACPI_STATE_S0;
>
>         acpi_resume_power_resources();
> -
> -       /* If we were woken with the fixed power button, provide a small
> -        * hint to userspace in the form of a wakeup event on the fixed power
> -        * button device (if it can be found).
> -        *
> -        * We delay the event generation til now, as the PM layer requires
> -        * timekeeping to be running before we generate events. */
> -       if (!pwr_btn_event_pending)
> -               return;
> -
> -       pwr_btn_event_pending = false;
> -       pwr_btn_adev = acpi_dev_get_first_match_dev(ACPI_BUTTON_HID_POWERF,
> -                                                   NULL, -1);
> -       if (pwr_btn_adev) {
> -               pm_wakeup_event(&pwr_btn_adev->dev, 0);
> -               acpi_dev_put(pwr_btn_adev);
> -       }
>  }
>
>  /**
> @@ -626,27 +606,6 @@ static int acpi_suspend_enter(suspend_state_t pm_state)
>         /* Reprogram control registers */
>         acpi_leave_sleep_state_prep(acpi_state);
>
> -       /* ACPI 3.0 specs (P62) says that it's the responsibility
> -        * of the OSPM to clear the status bit [ implying that the
> -        * POWER_BUTTON event should not reach userspace ]
> -        *
> -        * However, we do generate a small hint for userspace in the form of
> -        * a wakeup event. We flag this condition for now and generate the
> -        * event later, as we're currently too early in resume to be able to
> -        * generate wakeup events.
> -        */
> -       if (ACPI_SUCCESS(status) && (acpi_state == ACPI_STATE_S3)) {
> -               acpi_event_status pwr_btn_status = ACPI_EVENT_FLAG_DISABLED;
> -
> -               acpi_get_event_status(ACPI_EVENT_POWER_BUTTON, &pwr_btn_status);
> -
> -               if (pwr_btn_status & ACPI_EVENT_FLAG_STATUS_SET) {
> -                       acpi_clear_event(ACPI_EVENT_POWER_BUTTON);
> -                       /* Flag for later */
> -                       pwr_btn_event_pending = true;
> -               }
> -       }
> -
>         /*
>          * Disable all GPE and clear their status bits before interrupts are
>          * enabled. Some GPEs (like wakeup GPEs) have no handlers and this can
> --
> 2.34.1
>
[PATCH v4] ACPI: button: Report wakeup key only for power button wakeups
Posted by Baorui.Liu 2 days, 22 hours ago
The ACPI button driver reports KEY_WAKEUP from the Power Button input
device to let userspace know that the system was resumed by a power
button wakeup.

However, reporting KEY_WAKEUP from generic system resume paths can make
userspace observe a Power Button wakeup even when the system was resumed
by a different wake source.

This is reproducible on an AMD Android 15 Xen guest. With a kernel
without this fix, a non-power-button S3 resume:

  echo mem > /sys/power/state
  xl trigger android s3resume

makes the Power Button input device report KEY_WAKEUP. The same test on
a kernel with this fix no longer reports KEY_WAKEUP from the Power Button
input device.

Track whether a power button event/notify is observed while the ACPI
button device is suspended, and report KEY_WAKEUP on resume only in that
case.

When the fixed power-button status bit is set during early S3 resume,
clear it to avoid a deferred KEY_POWER delivery to userspace, and mark
wakeup_pending so button resume can still report KEY_WAKEUP. Also clear
wakeup_pending before setting suspended to avoid a race with a power
button SCI during suspend entry.

Signed-off-by: Baorui.Liu <baorliu@amd.com>
---
 drivers/acpi/button.c | 46 ++++++++++++++++++++++++++++++++++++++++++-
 drivers/acpi/sleep.c  | 27 +++++++++++--------------
 include/acpi/button.h |  5 +++++
 3 files changed, 62 insertions(+), 16 deletions(-)

diff --git a/drivers/acpi/button.c b/drivers/acpi/button.c
index cdbb1023a8ee..c07de417df5d 100644
--- a/drivers/acpi/button.c
+++ b/drivers/acpi/button.c
@@ -191,6 +191,7 @@ struct acpi_button {
 	bool last_state;
 	ktime_t last_time;
 	bool suspended;
+	bool wakeup_pending;
 	bool lid_state_initialized;
 	bool gpe_enabled;
 };
@@ -397,6 +398,34 @@ static void acpi_lid_forget(struct acpi_device *adev)
 }
 
 /* Driver Interface */
+/**
+ * acpi_button_power_wakeup_pending - Mark fixed power button wakeup pending.
+ *
+ * Used when the fixed power-button status bit is observed and cleared during
+ * early S3 resume, so KEY_WAKEUP can still be reported from button resume
+ * without delivering a deferred KEY_POWER to userspace.
+ */
+void acpi_button_power_wakeup_pending(void)
+{
+	struct acpi_device *adev;
+	struct device *phys_dev;
+	struct acpi_button *button;
+
+	adev = acpi_dev_get_first_match_dev(ACPI_BUTTON_HID_POWERF, NULL, -1);
+	if (!adev)
+		return;
+
+	phys_dev = acpi_get_first_physical_node(adev);
+	if (phys_dev) {
+		button = dev_get_drvdata(phys_dev);
+		if (button && button->type == ACPI_BUTTON_TYPE_POWER)
+			button->wakeup_pending = true;
+	}
+
+	acpi_dev_put(adev);
+}
+EXPORT_SYMBOL_GPL(acpi_button_power_wakeup_pending);
+
 int acpi_lid_open(void)
 {
 	guard(mutex)(&acpi_lid_lock);
@@ -476,6 +505,9 @@ static void acpi_button_notify(acpi_handle handle, u32 event, void *data)
 
 	acpi_pm_wakeup_event(button->dev);
 
+	if (button->type == ACPI_BUTTON_TYPE_POWER && button->suspended)
+		button->wakeup_pending = true;
+
 	if (button->suspended || event == ACPI_BUTTON_NOTIFY_WAKE)
 		return;
 
@@ -498,6 +530,11 @@ static void acpi_button_notify_run(void *data)
 
 static u32 acpi_button_event(void *data)
 {
+	struct acpi_button *button = data;
+
+	if (button->type == ACPI_BUTTON_TYPE_POWER && button->suspended)
+		button->wakeup_pending = true;
+
 	acpi_os_execute(OSL_NOTIFY_HANDLER, acpi_button_notify_run, data);
 	return ACPI_INTERRUPT_HANDLED;
 }
@@ -507,6 +544,12 @@ static int acpi_button_suspend(struct device *dev)
 {
 	struct acpi_button *button = dev_get_drvdata(dev);
 
+	/*
+	 * Clear wakeup_pending before marking suspended. Otherwise a power
+	 * button SCI between the two stores could set wakeup_pending and then
+	 * be overwritten here, losing a real wakeup.
+	 */
+	button->wakeup_pending = false;
 	button->suspended = true;
 	return 0;
 }
@@ -523,12 +566,13 @@ static int acpi_button_resume(struct device *dev)
 		acpi_lid_initialize_state(button);
 	}
 
-	if (button->type == ACPI_BUTTON_TYPE_POWER) {
+	if (button->type == ACPI_BUTTON_TYPE_POWER && button->wakeup_pending) {
 		input = button->input;
 		input_report_key(input, KEY_WAKEUP, 1);
 		input_sync(input);
 		input_report_key(input, KEY_WAKEUP, 0);
 		input_sync(input);
+		button->wakeup_pending = false;
 	}
 	return 0;
 }
diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c
index 132a9df98471..30e268987e37 100644
--- a/drivers/acpi/sleep.c
+++ b/drivers/acpi/sleep.c
@@ -22,6 +22,7 @@
 #include <linux/syscore_ops.h>
 #include <asm/io.h>
 #include <trace/events/power.h>
+#include <acpi/button.h>
 
 #include "internal.h"
 #include "sleep.h"
@@ -504,12 +505,11 @@ static void acpi_pm_finish(void)
 
 	acpi_resume_power_resources();
 
-	/* If we were woken with the fixed power button, provide a small
-	 * hint to userspace in the form of a wakeup event on the fixed power
-	 * button device (if it can be found).
-	 *
-	 * We delay the event generation til now, as the PM layer requires
-	 * timekeeping to be running before we generate events. */
+	/*
+	 * If woken by the fixed power button, provide a wakeup event on that
+	 * device. KEY_WAKEUP input reporting is handled by the button driver
+	 * via wakeup_pending; do not synthesize KEY_POWER here.
+	 */
 	if (!pwr_btn_event_pending)
 		return;
 
@@ -626,14 +626,11 @@ static int acpi_suspend_enter(suspend_state_t pm_state)
 	/* Reprogram control registers */
 	acpi_leave_sleep_state_prep(acpi_state);
 
-	/* ACPI 3.0 specs (P62) says that it's the responsibility
-	 * of the OSPM to clear the status bit [ implying that the
-	 * POWER_BUTTON event should not reach userspace ]
-	 *
-	 * However, we do generate a small hint for userspace in the form of
-	 * a wakeup event. We flag this condition for now and generate the
-	 * event later, as we're currently too early in resume to be able to
-	 * generate wakeup events.
+	/*
+	 * ACPI 3.0 (P62): OSPM should clear the fixed power-button status bit
+	 * so the event does not reach userspace as KEY_POWER. Remember that a
+	 * power-button wake occurred so button resume can report KEY_WAKEUP
+	 * and acpi_pm_finish() can emit a PM wakeup event.
 	 */
 	if (ACPI_SUCCESS(status) && (acpi_state == ACPI_STATE_S3)) {
 		acpi_event_status pwr_btn_status = ACPI_EVENT_FLAG_DISABLED;
@@ -642,8 +639,8 @@ static int acpi_suspend_enter(suspend_state_t pm_state)
 
 		if (pwr_btn_status & ACPI_EVENT_FLAG_STATUS_SET) {
 			acpi_clear_event(ACPI_EVENT_POWER_BUTTON);
-			/* Flag for later */
 			pwr_btn_event_pending = true;
+			acpi_button_power_wakeup_pending();
 		}
 	}
 
diff --git a/include/acpi/button.h b/include/acpi/button.h
index af2fce5d2ee3..213c6cfd0264 100644
--- a/include/acpi/button.h
+++ b/include/acpi/button.h
@@ -8,11 +8,16 @@
 
 #if IS_ENABLED(CONFIG_ACPI_BUTTON)
 extern int acpi_lid_open(void);
+extern void acpi_button_power_wakeup_pending(void);
 #else
 static inline int acpi_lid_open(void)
 {
 	return 1;
 }
+
+static inline void acpi_button_power_wakeup_pending(void)
+{
+}
 #endif /* IS_ENABLED(CONFIG_ACPI_BUTTON) */
 
 #endif /* ACPI_BUTTON_H */
-- 
2.34.1