fix: skip the profile restore when the EC is wedged on resume - #26
Open
little-schlapps wants to merge 1 commit into
Open
little-schlapps wants to merge 1 commit into
little-schlapps wants to merge 1 commit into
Conversation
waitForEC returned true on timeout and let the restore proceed regardless, because a machine with no battery reports the same way as an EC that is not answering. That escape hatch is the remaining path to the hard lock the wait was added to prevent: applyCustomHW writes PPT through asus_wmi, the write blocks inside acpi_evaluate_object holding the ACPI global mutex, and every other ACPI consumer piles up behind it until the NMI watchdog reports lockups across every core. Observed on a GZ302EA after a 2.5 hour s2idle suspend that came back with the EC down (atkbd re-enable failed, ucsi_acpi re-enable timed out with -110, BAT0/energy_now read ENODEV). The daemon logged "EC still unresponsive after resume; restoring anyway" and hung in ppt_pl2_sppt_store; asusd was caught holding the mutex spinning in acpi_os_read_port. Soft lockups spread to about 20 of 32 cores over the next four minutes and the machine needed a hard reset. The two cases are separable. FindBatteryCapacityPath globs BAT*/capacity and falls back to a BAT0 path that will not exist, so a machine with no battery fails the read with ENOENT while a wedged EC fails an existing path with ENODEV. Replace the bool probe with a three-state ecStatus and return what the last probe saw instead of a blanket "go ahead": ecAbsent still restores, ecWedged does not. Gate only applyCustomHW rather than the whole restore. Lighting goes over hidraw and never touches the EC, and restoreVolatileState is what clears the suspending flag — skipping it wholesale would stand the reconcile watcher down until its staleness ceiling expired. The fan curve and TDP floor the skip leaves behind are what that watcher already puts back once the EC recovers, so the cost is a delay rather than a setting lost for the session. classifyECRead is split out from probeEC so the ENOENT/ENODEV distinction can be tested without redirecting sysfs, which internal/daemon cannot do because cli's path vars are unexported. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
waitForEC(added in #20) returnstrueon timeout and lets the restore proceed anyway:The comment explains why — a machine with no battery reports the same way as a wedged EC, and must still get its profile back. But that escape hatch is the remaining path to the hard lock the wait was added to prevent.
applyCustomHWwrites PPT throughasus_wmi, the write blocks insideacpi_evaluate_objectholding the ACPI global mutex, and every other ACPI consumer piles up behind it.What I saw
A GZ302EA (BIOS 308, kernel 7.0.0) came back from a 2.5 hour s2idle suspend with the EC down:
The daemon waited, gave up, and went ahead:
Then:
Soft lockups spread to ~20 of 32 cores over four minutes. Hard reset required.
The fix
The two cases are separable.
FindBatteryCapacityPathglobsBAT*/capacityand falls back to aBAT0path that will not exist, so:ENOENTENODEVecResponds() boolbecomesprobeEC() ecStatusoverecReady/ecAbsent/ecWedged, andwaitForECWithreturns what the last probe actually saw.ecAbsentstill restores;ecWedgeddoes not.Only
applyCustomHWis gated, not the whole restore:restoreVolatileStateis what clears the suspending flag, so skipping it wholesale would stand the reconcile watcher down until its staleness ceiling expiredclassifyECReadis split out fromprobeECso theENOENT/ENODEVdistinction is testable without redirecting sysfs, whichinternal/daemoncannot do becausecli's path vars are unexported.Tests
TestClassifyECReadSeparatesAbsentFromWedged— table over nil /ENOENT/ wrappedENOENT/ENODEV/ETIMEDOUTTestWaitForECReportsAWedgedECOnTimeout— new, the regression aboveTestWaitForECRestoresAnywayOnTimeout→TestWaitForECRestoresAnywayWhenThereIsNoBattery, narrowed to the batteryless case it was actually protectinggo test ./...and theapimodule pass;gofmtandgo vetclean. I don't havegolangci-lintto hand, so that check is unrun.Possibly #23
#23 reports a hard lock on resume still happening on v1.3.2 — the release that carries the #20 wait. This escape hatch is the obvious candidate, but that issue has no logs attached, so I can't confirm it's the same path. Deliberately not using a closing keyword. The line that would settle it is
EC still unresponsive after resume; restoring anywayinjournalctl --user -u z13ctlfrom the resume that locked up; I'll ask there.Note this also applies to
feat/v2-refactor—waitForECWithis byte-identical on that branch, escape hatch included.🤖 Generated with Claude Code