Watch
1
0
Fork
You've already forked SouveraineOS
0

LOCK-DPMS-LESSONS: distilled lock/wake/DPMS operating knowledge

Plus queue item 11: suspend-resume FTS calibration race (dt2w wake from
s2idle wedges the touch controller; dpms serialization didn't cover it).
This commit is contained in:
Fimeg 2026-07-17 07:56:04 -04:00
commit c8acee9579
2 changed files with 120 additions and 0 deletions

113
docs/LOCK-DPMS-LESSONS.md Normal file
View file

@ -0,0 +1,113 @@
# Lock / DPMS / wake — the lessons doc
The hard-won rules for everything between "screen goes dark" and "user is
back in." Doctrine lives in SESSION-AUTHORITY-DOCTRINE.md; the boot
choreography lives in session-authority-boot-order.md; the raw archaeology
lives in DUMP-power-idle-lock-2026-07-15.md. This page is the distilled
operating knowledge — what we know is true on blueline, with the receipts.
## 1. Ordering: lock, then off
Blanking an unlocked session leaks the desktop for a frame on wake, and a
wake that races the lock can land input on an unlocked surface.
- Power button (`blueline-power-button`): screen on → `session lock` IPC →
wait ≤2s for the compositor-acked `"locked": true` → blank. If the lock
IPC fails, blank anyway — dark-but-unlocked is recoverable, lit-and-
unlocked in a pocket is not.
- Suspend: `PrepareForSleep` → lock secure → release the delay inhibitor.
Fail-closed, ~5s budget.
- Idle: the native IdleCoordinator locks (300s) well before hypridle's
dpms-off (600s) and suspend (900s), so idle blanking always finds the
session already locked.
## 2. One DPMS authority
`blueline-screen-toggle` serializes every panel power transition. Nothing
else calls `hyprctl dispatch dpms` — when hypridle still had a raw dpms
call after the toggle became authority, double-tap wake raced `on-resume`
and the touch controller calibrated against a half-ramped panel
(Pixel3Arch `73d0cd0`). Any new wake source (power button, dt2w, proximity
someday) goes through the toggle, or it will reintroduce the race.
## 3. The FTS touch controller has opinions
- On panel-unprepare it drops to gesture mode (that is what makes dt2w
work while the screen is off).
- On sense-on it self-calibrates against the panel. If the panel is still
ramping, calibration is garbage and touch goes deaf or erratic. The
kernel signature is a continuous spew of
`fts_status_event_handler: … invalid … Strength soft Force cal`.
A single line after a wake is noise; a stream that keeps flowing minutes
later is a wedged controller.
- The serialized DPMS path fixed this for screen-off/on cycles. **The
suspend-resume path still races** (found 2026-07-17): s2idle exit
re-runs panel-prepare → FTS reset → Sense ON with nobody pacing it, and
a dt2w wake from suspend produced exactly the wedged-controller spew and
a dead swipe-to-unlock on the lockscreen. Open gap; candidate fixes are
kernel-side (pace sense-on after panel ramp — check what Android does,
Android is the reference) or a post-resume rebind hook, which needs care
because rebinding while the panel is off may drop the dt2w feature flags.
- Recovery without reboot:
`echo 2-0049 > /sys/bus/i2c/drivers/fts/unbind && sleep 1 && echo 2-0049 > /sys/bus/i2c/drivers/fts/bind`
(root). Firmware re-verifies, sense restarts. Expect a line or two of
invalid-cal immediately after; continuous spew means it did not take.
## 4. Suspend truths
- Suspend is s2idle. The USB gadget (usb0 NCM/ACM) dies with it — an ssh
session dropping and `usb0: Lost carrier` in the journal during suspend
entry is *normal*, not a crash. Do not chase gadget "flaps" without
first checking `journalctl` for `PM: suspend entry`.
- dt2w wakes the phone from s2idle (gesture mode survives suspend).
- NetworkManager/ModemManager sleep-monitor churn around suspend entry is
choreography, not failure.
- Warm reboots poison TZ/remoteproc state on blueline — recovery from a
bad state is a true cold boot (unplugged power-off), never `reboot`.
## 5. Lock architecture (who holds what)
- souveraine-sessiond takes ext-session-lock at boot *before* the shell
exists; no desktop surface can precede the lock. The shell inherits the
lock later over the bridge socket (`misc.allow_session_lock_restore`).
- The sessiond↔shell handoff necessarily drops one lock client for ~1.05s.
Hyprland's `lockdead_screen_delay` default (1000ms) painted a red
"lockscreen crashed" flash in that gap; it is 4000ms in the tracked
hyprland.lua. The session stays locked throughout — the flash was
cosmetic.
- Shell heartbeat loss → sessiond fail-closes: re-acquires the lock and
presents its bare fallback PIN surface. Proven in anger 2026-07-17 when
the shell died silently post-unlock. PAM at the fallback surface is a
real unlock.
- `screenLocked` (request) and `screenLockSecure` (compositor ack) are
different facts. Personal-tier gates key off *secure*. The compositor
can end a lock unilaterally (`ext_session_lock_v1_finished`); the shell
resyncs `screenLocked` when that happens (LockScreen.qml
onLockStateChanged) — before that fix, a stale `lockRequested:true`
both lied to the gates and blocked re-lock.
- Unlock is never an agent verb.
## 6. Shell surface rules
- Lock-path QML deploys only while the phone is UNLOCKED.
`WlSessionLock.surfaceComponent` cannot change while the lock is active;
hot-deploying over an active lock wedges the lockscreen until reboot.
- Two-stage lock: glance first, PIN pad on swipe-up (60px) / tap / any
key; retreats on swipe-down (80px, empty PIN only) or 25s idle.
- hypridle's `after_sleep_cmd` fires the `lockFocus` global shortcut on
EVERY wake. It must only refocus the (hidden) PIN field — refocus must
never imply PIN reveal, or every wake would skip the glance stage.
- The shell runs under `souveraine-shell.service` (journal keeps stderr,
restart on failure, non-clean exits append to
`~/.local/state/souveraine/crashes.log`, gives up after 8 tries and
leaves the fail-closed lock in charge). It was a raw exec once; the
crash reason died with it and unlock landed on a black desktop.
## 7. Open gaps
- Suspend-resume FTS calibration race (§3) — the one live regression.
- Crash *reporting* (queue item 10): crashes.log + coredumps + failed
units surfaced as banner/sensoria, not just survived.
- Proximity wake must never touch lock state
(DUMP-power-idle-lock-2026-07-15.md §2) — same doctrine as dt2w: wake
sources move the panel, only sessiond moves the lock.

View file

@ -138,6 +138,13 @@ authority. Screen off → wake (to lockscreen). Tap-to-wake unchanged.
shell banner / sensoria event on new crashes.log entries + new
coredumpctl entries and --failed user units, so a crash is *reported*,
not just survived. Pairs with queue item 5's notification plumbing.
11. **Suspend-resume FTS calibration race** (found 2026-07-17): s2idle
exit re-runs panel-prepare → FTS reset → Sense ON unpaced; dt2w wake
from suspend wedged the controller (continuous invalid Force cal spew,
dead swipe on the lockscreen). The 73d0cd0 serialization only covered
dpms off/on. Candidates: kernel-side pacing (Android is the reference)
or a post-resume rebind hook (careful: rebind with panel off may drop
dt2w flags). Lessons doc: LOCK-DPMS-LESSONS.md §3.
## Boot password prompt (seen post-reboot, patched live)