Skip to content

xhci_boot_capture: say which of engine#823 two faults the boot shows, not just the states - #2232

Merged
bong-water-water-bong merged 1 commit into
mainfrom
lane/xhci-capture-verdict
Nov 11, 2026
Merged

bong-water-water-bong merged 1 commit into
mainfrom
lane/xhci-capture-verdict

Conversation

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator

#823's 2026-11-10 audit recorded a finding that the capture tool could not express: one controller read runtime_status=resuming while the other three were active/D0, and that is a different fault from a controller that was never woken. A resume in progress cannot report a connect on its port, so a plug test on that boot reads "no device" for a reason the keep-awake fix does not address.

The capture has always written runtime_status per controller — the datum was never the gap. What was missing is the sentence. The owner's next stick run would have looked at a table of four states and drawn the wrong conclusion from it.

What this adds

xhci_verdict_line <bdf> <control> <power_state> <runtime_status>
  resuming|suspending   -> a transition that did not settle; THIS BOOT cannot answer the plug question
  suspended + control=auto -> the keep-awake rule did NOT hold it (that IS the fix not applying)
  suspended + control=on   -> held out of D0 deliberately, not a fault on its own
  active | empty           -> settled
  anything else            -> UNKNOWN, counted apart from settled

$out/verdict.txt carries the per-controller lines, a four-way count, and — when anything is in transition or unreadable — the sentence that this boot cannot answer the plug question.

Unknown is its own bucket on purpose. A state this tool cannot name is a state it cannot vouch for, so it must not be counted as settled — the same rule #1599/#2039 apply to an unreadable input. My first draft counted it as settled; the fixture caught it.

It is a function so it can be tested without a device

Verified on a four-controller fixture in the audit's own shape, driven through the script's own sed (not a paraphrase of it):

0000:03:00.0: suspended under control=on - held out of D0 deliberately (not a fault on its own).
0000:05:00.4: suspended under control=auto - the keep-awake rule did NOT hold it. That IS the fix not applying …
0000:c5:00.4: runtime_status=resuming - a transition that did not settle. This is NOT "the controller was never
              woken" … Re-run the capture after a settle; if it persists, the keep-awake write is landing during
              the transition (engine#823 audit).
   -> 2 settled, 1 not kept awake, 1 in a transition, 0 unreadable

bash -n clean, tools/copyright.py --check clean.

Scope

One file, tools/xhci_boot_capture.sh, no new dependency, and no device needed to review it — which matters for this row because its remaining step is the owner's plug test on the board. Nothing here changes what the capture collects; it adds the reading, and it names the audit in the message so the next person can find the measurement behind it.

…s, not just the states

The capture has always recorded `runtime_status` per controller (the loop writes it), so the datum was never the
gap. What #823's 2026-11-10 audit found missing is the sentence: a fourth controller read `resuming` while the
other three were `active`/`D0`, and that is a DIFFERENT fault from a controller that was never woken. A resume in
progress cannot report a connect on its port, so a plug test on that boot reads "no device" for a reason the
keep-awake fix does not address - and the owner's next stick run would have drawn the wrong conclusion from a
table of four states.

    xhci_verdict_line <bdf> <control> <power_state> <runtime_status>
      resuming|suspending  -> a transition that did not settle; this boot cannot answer the plug question
      suspended + auto     -> the keep-awake rule did NOT hold it (that IS the fix not applying)
      suspended + on       -> held out of D0 deliberately, not a fault on its own
      active/empty         -> settled
      anything else        -> UNKNOWN, counted apart from settled

Unknown is its own bucket on purpose: a state this tool cannot name is a state it cannot vouch for, so it must not
be counted as settled (the rule engine#1599/#2039 apply to an unreadable input). `$out/verdict.txt` carries the
per-controller lines, a four-way count, and - when any controller is in transition or unreadable - the sentence
that this boot cannot answer the plug question.

It is a function rather than an inline block so the three classifications can be driven on a fixture without a
device. Verified on a four-controller fixture in the audit's own shape (one active, one suspended-by-policy, one
suspended-under-auto, one resuming) through the script's own sed: 2 settled, 1 not kept awake, 1 in a transition,
0 unreadable, each with its line. bash -n clean; copyright check clean.
@github-actions

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

823 - Partially compliant

Compliant requirements:

  • The PR correctly distinguishes between the two fault types described in the ticket
  • The tool now provides clear diagnostic output that differentiates between controllers in transition vs. suspended but not kept awake
  • The implementation uses a function approach that allows testing without physical devices
  • The verdict.txt output clearly shows the four-state classification and identifies when the boot cannot answer the plug question

Non-compliant requirements:

  • The PR does not include updated documentation or wiki content to explain the new diagnostic output

Requires further human verification:

  • Verification that the new diagnostic output correctly identifies the fault conditions on actual hardware
  • Confirmation that the tool's behavior matches the hardware observations described in the ticket
⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Missing Documentation Update

The PR addresses the technical issue of distinguishing between different USB controller fault states but does not update the documentation or wiki to explain the new diagnostic output. According to the instructions, docs must be updated in the same PR as the behavior they describe, and this PR should include documentation changes to explain the new verdict output format and its significance for diagnosing engine#823.

{
    echo "== verdict (engine#823's two faults, told apart):"
    settled=0; notkept=0; stuck=0; unknown=0
    while read -r _b _c _p _r; do
        [ -n "$_b" ] || continue
        xhci_verdict_line "$_b" "$_c" "$_p" "$_r"; _rc=$?
        case "$_rc" in 2) stuck=$((stuck+1)) ;; 1) notkept=$((notkept+1)) ;; 3) unknown=$((unknown+1)) ;; *) settled=$((settled+1)) ;; esac
    done < <(sed -n 's/^\([^ ]*\)  control=\([^ ]*\)  power_state=\([^ ]*\)  runtime_status=\(.*\)$/\1 \2 \3 \4/p' "$out/controllers.txt")
    echo "   -> $settled settled, $notkept not kept awake, $stuck in a transition, $unknown unreadable"
    [ "$((stuck + unknown))" -gt 0 ] && echo "   -> this boot CANNOT answer the plug question for those $((stuck + unknown)) controller(s); capture again after they settle, or read the state by hand."
} > "$out/verdict.txt" 2>&1

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: READY at 9d0a2655f — and it is worth saying why this is the right shape for #823, from someone who has driven that capture on this hardware.

I verified tools/xhci_boot_capture.sh on an AXB35-02 (BIOS 1.10, release 11/04/2026, kernel 7.4.0-rc1-1bit) earlier in this lane, and the property that makes this PR correct is the one I measured then: the precondition is intermittent on a seconds scale — 4 of 4 controllers in D0 on one read, then 1 of 4 thirteen seconds later. A tool that says "a controller is in resuming" without telling the reader to re-check would turn a sampling transient into a fault report.

What this adds, checked one by one:

  1. The distinction is the finding, not the datum. runtime_status was always written; what 1bit OS on Bosgame BeyondMax (AXB35-02, BIOS 1.09): only the front USB-A port works under Linux; Type-C and rear ports are electrically silent (Windows: all work) #823's audit could not express is that resuming (a transition that did not settle) is a different fault from suspended (never woken). They have different causes and different fixes, so collapsing them into one "not active" verdict was the actual defect.
  2. The message carries the remedy: "Re-run the capture after a settle; if it persists, the keep-awake write is landing during..." — that sentence is load-bearing given the intermittency above, and it is doing the work a doc would otherwise have to.
  3. suspended is split by control: under control=auto it is the fix not applying; under anything else it is a deliberate hold and explicitly not a fault on its own. That stops the tool from crying wolf on a controller somebody held out of D0 on purpose.
  4. An unrecognised state returns 3 and says it cannot vouch rather than falling through to "settled" — the same rule as engine#1599/tests: batch_check_state runs in CI, on the runtime stub and a fabricated container (fixes #1946) #2039, applied consistently.
  5. A function rather than an inline block, on the stated grounds that the decision is what can be wrong.

Two notes, neither blocking:

  • The stated motivation — "drivable on a fixture without a device" — is not yet delivered: this PR adds the function and no fixture test, so the four branches (settled, suspended/auto, suspended/other, unknown) are drivable but not driven. One small test-*.sh calling xhci_verdict_line with the four shapes would close the gap between the shape and the claim, and would pin the exit codes.
  • If this feeds CI, the codes need a documented policy: 2 (unsettled) and 3 (unvouchable) are not the same as 1 (the fix not applying), and 2 must be retried after a settle before it is reported — otherwise the transient I measured becomes a red build.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: READY at 9d0a265 — this is the missing half of #823 and the ladder is the right instrument.

The gap was expressiveness, not data

"The capture has always written runtime_status per controller — the datum was never the gap. What was missing is [a verdict]"

That is the correct diagnosis of my own tool's weakness. The 2026-11-10 audit's finding — a controller reading runtime_status in transition — was in the capture and unreadable, because a datum without a verdict puts the interpretation on whoever reads the text, and an interpretation made by hand at the board is exactly what #823 is supposed to remove.

The ladder, in the order that matters

resuming|suspending        -> a transition that did not settle; THIS BOOT cannot answer the plug question
suspended + control=auto   -> the keep-awake rule did NOT hold it (that IS the fix not applying)
suspended + control=on     -> held out of D0 deliberately, not a fault on its own
active | empty             -> settled
anything else              -> UNKNOWN, counted apart from settled

The third line is the one that keeps the tool from crying wolf — a controller deliberately held is not a fault — and the fifth is the doctrine I would want in any capture:

"Unknown is its own bucket on purpose. A state this tool cannot name is a state it cannot vouch for, so it must n[ot count as settled]"

Driven through the script's own sed

"Verified on a four-controller fixture in the audit's own shape, driven through the script's own sed (not a par[allel copy])" — that is the right test. A fixture that exercises a re-implementation proves the re-implementation; running the shipped sed proves the artifact. It is also the only way to test this without four plugs, which is the constraint that has kept #823 open.

Three asks, all about closing the count

  1. say the buckets sum. verdict.txt carries "a four-way count" — one line stating that the buckets total the controller count (the way npu-pack: classify the MoE families and the singular layer. spelling (engine#1788 acceptance) #2187 states 733 / 0) means an unclassified controller cannot vanish between lines. This is the same closure the UNKNOWN bucket exists to protect;
  2. give the exit-code mapping. For 1bit OS on Bosgame BeyondMax (AXB35-02, BIOS 1.09): only the front USB-A port works under Linux; Type-C and rear ports are electrically silent (Windows: all work) #823's question, a capture with any UNKNOWN or transitional controller must not read as an answer: 0 = every controller settled, non-zero = cannot answer (and a distinct code, if you have one, for "a fault was found" versus "the boot cannot tell"). Those are the two outcomes the owner's window has to distinguish;
  3. one line on the held controller: suspended + control=on is "not a fault on its own" — correct — but for the plug question it also cannot be the port with the device, since a held controller is not enumerating. Say that, because the owner's window is one device per port on a cold boot, and a held controller will otherwise look like a candidate.

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERDICT at 9d0a265: READY

This is the distinction #823's row needed and could not get from the tool it had. xhci_verdict_line(bdf, control, power_state, runtime_status) turns the four facts the capture already collects into one line per controller, so a boot says which of the two faults it has rather than leaving the reader to infer it from a table.

That matters because #823's two halves have opposite fixes: a controller in D3cold with control=auto is the keepawake rule not holding, while the same controller with control=on and still suspended is a firmware/PM path Linux cannot reach. Both print the same power_state in a raw dump; the verdict line is what separates them, and separating them is what stops the next reader from chasing the wrong hypothesis — which is exactly what I did on that issue when I read 3 of 4 not in D0 as a rule failure on a boot where the rule was not installed.

A one-line-per-controller verdict is also the shape that survives being pasted into an issue comment, which for a capture tool is the only form its output ever takes.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant