Skip to content

[RFC] fwk: epr: restore standing contract when the buck transition is abandoned - #2

Open
tyeth wants to merge 1 commit into
fix/epr-fail-recoveryfrom
fix/buck-epr-abandon-cleanup
Open

tyeth wants to merge 1 commit into
fix/epr-fail-recoveryfrom
fix/buck-epr-abandon-cleanup

Conversation

@tyeth

@tyeth tyeth commented Oct 20, 2026

Copy link
Copy Markdown
Owner

Stacks on #1 (base branch is fix/epr-fail-recovery; the two compose and this diff shows only the buck change).

Problem

On boards with the RAA489300 3-level buck (tulip, i.e. FW16 Ryzen AI 300), the EPR flow has a second, independent route into the latched slow state that #1 does not cover — abandonment before the CCG command is ever sent:

  1. enter_epr_mode() / exit_epr_mode() collapse the charger input current to 0mA (learn mode on), assert the PMF transition throttle, set the pd_epr_in_progress port bits, and hand off to buck_ready_for_epr() to sequence the buck mode change.
  2. buck_ready_for_epr() retries up to 4 times; on exhaustion it reverts the buck mode (LEVEL_BUCK_SPR / LEVEL_BUCK_EPR) and goes idle — and returns.
  3. The CCG INITIATE_EPR_ENTRY/EXIT command is never sent, so no EPR event will ever arrive; and because epr_flow_pending_deferred() is only scheduled from handle_pd_epr_mode() (i.e. after buck-ready succeeds), the event-loss backstop that [RFC] fwk: epr: make a failed EPR transition recoverable without AC removal #1 fixed never runs either.

Result: pd_epr_in_progress port bits, the 0mA input limit, learn mode, and the hard transition throttle are all left standing indefinitely — the same end state documented with full telemetry in tyeth/framework-system#1 (data: https://github.com/tyeth/fw16-gpu-watchdog), reachable without any PD message loss.

Why exactly these lines

The abandonment branch now performs the same cleanup the retry-exhausted branch of epr_flow_pending_deferred() already performs, in the same order:

  • pd_epr_in_progress &= EPR_PROCESS_MASK — clears the port bits while keeping the ENTER/EXIT process flags, which is exactly the precondition for the existing restore block in cypd_update_port_state() (restore input limit from the standing contract PDO, board_discharge_on_ac(0), level_buck_set_input_current_limit(), then clear_erp_progress_mask()). No new restore code is introduced.
  • update_pmf_events(BIT(PD_PROGRESS_ENTER/EXIT_EPR_MODE), 0) — releases the transition throttle the same way [RFC] fwk: epr: make a failed EPR transition recoverable without AC removal #1 does on the post-command failure paths (idempotent; the ENTER clear also restores the GPU ACDC GPIO; if other PMF events are set the throttle correctly stays).
  • cypd_update_port_state() on the active charge port — triggers that restore block immediately, mirroring the give-up branch of epr_flow_pending_deferred() line for line.

What this deliberately does not touch

  • The buck mode revert itself, the retry count (4) and WAIT_TRANSITION_TIME are unchanged.
  • epr_support / epr_active are not latched here: a buck readiness failure is a power-stage sequencing problem, not a charger EPR rejection, so a later transition attempt (next contract event or resume) stays possible. On an abandoned exit, the standing contract is the still-active EPR contract and epr_active stays 1, so exit can be retried.
  • The early-abort path in buck_ready_for_epr() ("Enter EPR aborted: not in S0 or no power") is unchanged: the no-power case is cleaned up by the existing disconnect path, and the not-in-S0 case is re-driven by the resume hook; widening this patch to cover it would touch suspend/resume ordering and is left for a separate discussion.
  • No refactoring or renaming: 12 added lines, 0 removed, one function.

Risk analysis

  • If charge_port == -1 (adapter gone mid-flow), the input-limit refresh is skipped exactly as in the existing give-up branch; the disconnect path (clear_erp_progress() + PD_PROGRESS_DISCONNECTED) already owns that cleanup, and the process flags are cleared by the next cypd_update_port_state() on any port.
  • Clearing all port bits (rather than only this flow's) matches the existing give-up branch; the buck context is a single global flow gated by phase, so there is no concurrent buck flow to disturb.
  • On an abandoned enter, the restore block recomputes the input limit from the current (SPR) PDO — the same values the pre-transition state used, after the buck has been reverted to SPR mode, so charger and limit agree.
  • Compile-untested (no Zephyr build environment); only symbols already used in this file. RFC: please build before flashing.

How a FW16 owner would test

  1. Build this branch (which includes [RFC] fwk: epr: make a failed EPR transition recoverable without AC removal #1) and flash on tulip with a 240W EPR charger.
  2. Regression: normal replug on the rear port still shows the healthy ~1.1s sequence and lands at 48V/5A.
  3. Provoke a buck readiness failure (e.g. force board_confirm_buck_transition_ready() to report not-ready in a test build, or interrupt input power timing during entry so the buck cannot settle). Confirm the console shows the five 3Level-Buck enter epr retry lines then 3Level-Buck enter epr failed, reverting to SPR mode, and that immediately afterwards: input current returns to the SPR contract value (not 0mA), learn mode is off (battery not discharging on AC), and the hard throttle (h_prochot_l / 0x3E22 hard mask) deasserts without waiting for pmf update timeout.
  4. Confirm a subsequent charger replug (or next contract renegotiation) can still enter EPR — nothing should be latched by this path.
  5. Long-run: resume the telemetry logger from the data repo and watch for any recurrence of the stuck 0/500-800mA input state across EPR transitions.

🤖 Generated with Claude Code

…oned

On boards with the RAA489300 3-level buck (tulip), enter_epr_mode() /
exit_epr_mode() first collapse the charger input current to 0mA (learn
mode on) and assert the PMF transition throttle, then hand off to
buck_ready_for_epr() to sequence the buck mode change before the CCG
EPR command is sent. If the buck readiness check exhausts its retries,
the handler reverts the buck mode and goes idle - but the CCG command
is never sent, so no EPR event ever arrives, and because
epr_flow_pending_deferred() is only scheduled from handle_pd_epr_mode()
(after buck ready), the event-loss backstop never runs either. The
pd_epr_in_progress port bits, the collapsed input current and the
transition throttle are all left standing: a second independent route
into the latched slow state that otherwise only a physical AC removal
clears.

Give the abandonment branch the same cleanup the retry-exhausted
branch of epr_flow_pending_deferred() already performs: clear the
in-progress port bits (keeping the process flags so the existing
restore block in cypd_update_port_state() runs), release the
ENTER/EXIT transition throttle events, and refresh the active charge
port so the input current limit is restored from the standing
contract. The buck mode revert, retry count and timing are unchanged;
on an abandoned exit the standing contract is the still-active EPR
contract and epr_active stays 1, so a later exit attempt remains
possible.

This stacks on "fwk: epr: make a failed EPR transition recoverable
without AC removal" (the two compose: that change covers the failure
paths after the CCG command is sent, this one covers the flow being
abandoned before it is sent).

Issue: tyeth/framework-system#1
Telemetry/data: https://github.com/tyeth/fw16-gpu-watchdog

Compile-untested: no Zephyr build environment was available; the
change uses only symbols already used in this file and was verified by
read-through.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tyeth

tyeth commented Oct 20, 2026

Copy link
Copy Markdown
Owner Author

Submitted upstream as FrameworkComputer#91 (issue FrameworkComputer#93).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant