Date: Wed, 24 Jun 2026 02:14:49 +0000 From: bugzilla-noreply@freebsd.org To: bugs@FreeBSD.org Subject: [Bug 296243] acpi(4): firmware-replayed wake button (Notify 0x80) causes resume->suspend loop; grace-window guard races device resume Message-ID: <bug-296243-227@https.bugs.freebsd.org/bugzilla/>
index | next in thread | raw e-mail
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=296243 Bug ID: 296243 Summary: acpi(4): firmware-replayed wake button (Notify 0x80) causes resume->suspend loop; grace-window guard races device resume Product: Base System Version: CURRENT Hardware: amd64 OS: Any Status: New Severity: Affects Some People Priority: --- Component: kern Assignee: bugs@FreeBSD.org Reporter: dteske@FreeBSD.org Overview -------- On the Framework Laptop 12 (the only hardware on which this has been observed), the power/sleep button press that wakes the system from S3 is replayed by firmware shortly after resume as an ordinary button press -- ACPI Notify value 0x80 -- instead of the wakeup notification (Notify 0x02) the ACPI specification requires for a button that is also a wake source. The kernel honors the replayed press as a fresh suspend request, so the machine suspends again immediately after waking. It does not re-wake on its own -- each occurrence follows a deliberate power-button wake -- but because every such wake reproduces the effect, the machine cannot practically be kept awake with the power button. While developing a guard for this (review D57712), instrumentation revealed a second, subtler defect in the guard itself. The base kernel has no such guard; it is introduced by D57712. The version of D57712 currently under review records the resume time AFTER device resume completes, but the replayed Notify is delivered and evaluated on a different thread, concurrently with device resume -- so the timestamp can be read before it has been written, defeating the guard. The correction is a revision to D57712 (the early-timestamp placement, below), not a change to any pre-existing base kernel code. Affected hardware (observed) ---------------------------- - Framework Laptop 12, Intel Raptor Lake-P. - Power button is a control-method device behind the embedded controller (Insyde firmware + a Framework fork of Chrome-EC). Symptom / reproduction ---------------------- 1. Boot FreeBSD-CURRENT (amd64). 2. Suspend: "acpiconf -s 3". 3. Wake with the power button. 4. The system resumes, then immediately suspends again -- the replayed wake press is honored as a fresh suspend request. (It does not re-wake by itself; pressing the power button again to wake reproduces the same result.) Root cause ---------- The EC latches the physical key press that woke the system and flushes it through its normal _Qxx query path as soon as it is reinitialized on resume. It arrives as a control-method Notify 0x80 on the power button device, indistinguishable from a genuine press. Confirmed via instrumentation: acpi_button0: notify 0x80 Two threads are involved: - The resume thread runs acpi_EnterSleepState(), which performs the wake sequence: DEVICE_RESUME(root_bus) (re-initializing the EC, which drains _Qxx and queues the replayed Notify), then EVENTHANDLER_INVOKE(power_resume), etc. - The replayed Notify is dispatched ASYNCHRONOUSLY on the ACPICA notify taskqueue thread, where it converges at acpi_event_power_button_sleep() in sys/dev/acpica/acpi.c. Captured call path (kdb_backtrace at that point): acpi_button_resume_replay() acpi_event_power_button_sleep() acpi_task_execute() taskqueue_run_locked() taskqueue_thread_loop() fork_exit() acpi_event_power_button_sleep() is both the registered fixed-feature power button handler AND the function the control-method notify path calls, so it is the single convergence point for button-initiated sleep regardless of ACPI source (fixed event vs. one or more namespace buttons). The existing post-wake scrubber in acpi.c (AcpiClearEvent(ACPI_EVENT_POWER_BUTTON), "cleared fixed power button status", mandated for the Fixed Power Button) already neutralizes a fixed-feature replay; the spurious re-suspend manifests because this replay takes the control-method Notify 0x80 path, which that scrubber does not cover. Measurement reference points (important) ---------------------------------------- The replay's "latency after resume" depends entirely on WHERE in the resume sequence the clock is started. Two placements for that timestamp were evaluated. The late timestamp (D57712 as currently under review) calls getsbinuptime() on the resume thread AFTER EVENTHANDLER_INVOKE(power_resume) -- that is, after device resume has already completed. Replay latency measured from this point: ~162 ms. This late timestamp is racy. The replay is queued earlier (during DEVICE_RESUME) and is evaluated on the taskqueue thread. If that thread runs before the resume thread reaches the timestamp, the value is still unset when the guard evaluates it. Proof (instrumented, late timestamp): acpi0: power button event: resume_sbt=0 now=646336359891 elapsed=150520810 us window=500112 us -> pass (guard saw resume_sbt=0) acpi0: recorded resume_sbt=651477099345 (timestamp written ~1.2 s LATER) Whether the original guard fired at all therefore depended on taskqueue-vs-resume-thread scheduling. The early timestamp (proposed revision to D57712) calls getsbinuptime() on the resume thread BEFORE DEVICE_RESUME() -- that is, before the EC re-init that drains _Qxx. Replay latency measured from this point, across 7 cycles: cycle elapsed 1 620.2 ms 2 609.1 ms 3 593.2 ms 4 605.2 ms 5 600.2 ms 6 608.2 ms 7 608.2 ms min 593.2, max 620.2, mean 606.3, spread 27 ms The early timestamp is race-free: it is always set before the replay can be queued or evaluated. All 7 cycles were correctly recognized and suppressed; the machine stayed awake every time. The two numbers describe the SAME replay event from different reference points. The early timestamp precedes the late one by the device-resume span (~0.45 s on this machine), which is exactly why the same event measures ~0.6 s early versus ~0.16 s late. The move from D57712's submitted "~162 ms / 0.5 s window" to the revised "~600 ms / ~1 s window" is entirely this change of reference point within the patch -- adopted to eliminate the race -- not a change in the hardware's behavior nor a correction of an underestimate. Sizing the window from the race-free (early) timestamp, ~1 s gives comfortable margin over the worst observed ~620 ms while remaining well within the pre-usable resume period, so it ignores no genuine input. Spec reference -------------- ACPI 6.5, "Control Method Power Button": "Upon waking from a G1 sleeping state, the AML event handler generates a notify command with the code of 0x2 to indicate it was responsible for waking the system." Spec-compliant firmware reports 0x02, which takes a different path and never reaches this guard, so the change is inert on compliant systems. Cross-vendor scope ------------------ Per testing reported on D57712 (seuros): of ~12 vendor platforms plus several coreboot machines, none replay the wake as 0x80; the behavior appears specific to this EC today, though other non-compliant firmware could land on the same path. Proposed fix ------------ 1. Record the resume timestamp before DEVICE_RESUME() (the early, deterministic, race-free placement). 2. Ignore a button-initiated suspend within a grace window (~1 s, sized from the measured replay latency from the early timestamp) of that timestamp; self-clearing, inert on compliant firmware. Tracked in review https://reviews.freebsd.org/D57712. A follow-up refactor (suggested by olce) to move the acpi_event_{power,sleep}_button_{sleep,wakeup}() functions from acpi.c into acpi_button.c, with an in-flight guard for concurrent button sources, is orthogonal and can be done separately. -- You are receiving this mail because: You are the assignee for the bug.home | help
Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?bug-296243-227>
