Bug 296243 - acpi(4): firmware-replayed wake button (Notify 0x80) re-suspends immediately after resume; grace-window guard races device resume
Summary: acpi(4): firmware-replayed wake button (Notify 0x80) re-suspends immediately ...
Status: Closed FIXED
Alias: None
Product: Base System
Classification: Unclassified
Component: kern (show other bugs)
Version: 16.0-CURRENT
Hardware: amd64 Any
: --- Affects Some People
Assignee: freebsd-acpi (Nobody)
URL: https://reviews.freebsd.org/D57712
Keywords:
Depends on:
Blocks:
 
Reported: 2026-06-24 02:14 UTC by Devin Teske
Modified: 2026-07-02 23:44 UTC (History)
1 user (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Devin Teske freebsd_committer freebsd_triage 2026-06-24 02:14:49 UTC
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.
Comment 1 commit-hook freebsd_committer freebsd_triage 2026-07-02 23:43:13 UTC
A commit in branch main references this bug:

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(+)