git: d1b62492f97f - main - acpi: ignore wake button press replayed by firmware on resume
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 02 Jul 2026 23:42:52 UTC
The branch main has been updated by dteske:
URL: https://cgit.FreeBSD.org/src/commit/?id=d1b62492f97f5044b498fa4624e3a007a43efa6f
commit d1b62492f97f5044b498fa4624e3a007a43efa6f
Author: Devin Teske <dteske@FreeBSD.org>
AuthorDate: 2026-06-20 16:17:40 +0000
Commit: Devin Teske <dteske@FreeBSD.org>
CommitDate: 2026-07-02 23:39:13 +0000
acpi: ignore wake button press replayed by firmware on resume
Some firmware delivers the power or sleep button press that woke the
system as an ordinary button press (Notify 0x80) shortly after resume,
rather than as the wakeup notification (Notify 0x02) the ACPI
specification requires for a button that is also a wake source.
On affected machines (e.g. the Framework Laptop 12, Intel Raptor Lake-P)
the power button is a control-method device behind the embedded
controller. The EC latches the key press that woke the system across the
sleep transition and flushes it through its normal _Qxx query path as
soon as it is reinitialized on resume. The replayed press is
indistinguishable from a genuine one, so the kernel honors it as a fresh
suspend request and the machine suspends again immediately after waking;
it cannot be kept awake with the button.
The event cannot be filtered at its source: it arrives over the same EC
query path that also carries legitimate events (lid, AC, thermal,
battery), so suppressing the drain would lose real notifications.
Instead, record the time of resume and ignore a button-initiated suspend
that arrives within a short grace window of it. The timestamp is taken
before DEVICE_RESUME() re-initializes the EC, so it is set before the
replay can be processed on the ACPICA notify taskqueue; otherwise the
replay can be evaluated before the timestamp is written and slip through.
Measured from that point, the replay lands at ~600 ms across many cycles
on a Framework Laptop 12, whereas a deliberate press cannot occur that
quickly -- it happens well after the display is back -- so a one-second
window separates the two without ignoring real presses for any
perceptible time.
Spec-compliant firmware reports the wake as Notify 0x02, which is handled
on a different path and never reaches this check, so there is no change
in behavior on such systems.
The replay window is a fixed compile-time constant rather than a tunable
on purpose: it tracks a hardware characteristic -- the EC's post-resume
replay latency -- not a user policy, so there is no value a user would
meaningfully choose.
PR: 296243
Reviewed by: adrian, imp (earlier revision), olce
MFC after: 2 weeks
Differential Revision: https://reviews.freebsd.org/D57712
---
sys/dev/acpica/acpi.c | 43 +++++++++++++++++++++++++++++++++++++++++++
sys/dev/acpica/acpivar.h | 1 +
2 files changed, 44 insertions(+)
diff --git a/sys/dev/acpica/acpi.c b/sys/dev/acpica/acpi.c
index 0033ba734dc1..93451667a2b2 100644
--- a/sys/dev/acpica/acpi.c
+++ b/sys/dev/acpica/acpi.c
@@ -3747,6 +3747,13 @@ backout:
slp_state &= ~ACPI_SS_GPE_SET;
}
if ((slp_state & ACPI_SS_DEV_SUSPEND) != 0) {
+ /*
+ * Record the resume time so a spurious power/sleep button press can be
+ * ignored for a grace period afterward (see the comment before
+ * ACPI_BUTTON_REPLAY_WINDOW). This must be taken before
+ * DEVICE_RESUME(), which re-initializes the EC that replays the press.
+ */
+ sc->acpi_resume_sbt = getsbinuptime();
EVENTHANDLER_INVOKE(acpi_pre_dev_resume, stype);
DEVICE_RESUME(root_bus);
slp_state &= ~ACPI_SS_DEV_SUSPEND;
@@ -4137,6 +4144,37 @@ acpi_system_eventhandler_wakeup(struct acpi_softc *const sc,
return_VOID;
}
+/*
+ * Grace window after wakeup during which a power/sleep button press for suspend
+ * is ignored. Some firmware wrongly reports the depress that caused the wakeup
+ * as an "S0 Power/Sleep Button Pressed" notify (value 0x80) instead of the
+ * spec-required "Device Wake" notify (0x02); honoring it re-enters sleep
+ * immediately after resume. On the Framework Laptop 12 the replayed event
+ * arrives within ~620 ms of the recorded resume time, so a one-second window
+ * was chosen. See https://bugs.freebsd.org/296243 for the traces, timing
+ * data, and analysis.
+ */
+#define ACPI_BUTTON_REPLAY_WINDOW SBT_1S
+
+static bool
+acpi_button_resume_replay(struct acpi_softc *sc, const char *which)
+{
+ sbintime_t elapsed;
+
+ if (sc->acpi_resume_sbt == 0)
+ return (false);
+ elapsed = getsbinuptime() - sc->acpi_resume_sbt;
+ if (elapsed < 0 || elapsed >= ACPI_BUTTON_REPLAY_WINDOW)
+ return (false);
+ if (bootverbose) {
+ device_printf(sc->acpi_dev,
+ "ignoring %s button press %jd us after resume "
+ "(firmware replayed the wake event)\n",
+ which, (intmax_t)(elapsed / SBT_1US));
+ }
+ return (true);
+}
+
/*
* ACPICA Event Handlers (FixedEvent, also called from button notify handler)
*/
@@ -4158,6 +4196,8 @@ acpi_event_power_button_sleep(struct acpi_softc *sc)
ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
#if defined(__amd64__) || defined(__i386__)
+ if (acpi_button_resume_replay(sc, "power"))
+ return_VALUE (ACPI_INTERRUPT_HANDLED);
if (ACPI_FAILURE(AcpiOsExecute(OSL_NOTIFY_HANDLER,
(ACPI_OSD_EXEC_CALLBACK)acpi_invoke_sleep_eventhandler,
&sc->acpi_power_button_stype)))
@@ -4186,6 +4226,9 @@ acpi_event_sleep_button_sleep(struct acpi_softc *sc)
{
ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+ if (acpi_button_resume_replay(sc, "sleep"))
+ return_VALUE (ACPI_INTERRUPT_HANDLED);
+
if (ACPI_FAILURE(AcpiOsExecute(OSL_NOTIFY_HANDLER,
(ACPI_OSD_EXEC_CALLBACK)acpi_invoke_sleep_eventhandler,
&sc->acpi_sleep_button_stype)))
diff --git a/sys/dev/acpica/acpivar.h b/sys/dev/acpica/acpivar.h
index 9facbaea595a..27300a00b8d1 100644
--- a/sys/dev/acpica/acpivar.h
+++ b/sys/dev/acpica/acpivar.h
@@ -56,6 +56,7 @@ struct acpi_softc {
int acpi_enabled;
enum power_stype acpi_stype;
int acpi_sleep_disabled;
+ sbintime_t acpi_resume_sbt; /* Uptime at last resume. */
/* Supported sleep states and types. */
bool acpi_supported_stypes[POWER_STYPE_COUNT];