All changes verified on Dabao (Baochip-1x) hardware.
USB enumeration:
- 1d50:6197 enumerates at high-speed (480 Mbps)
- bInterfaceClass 11 CCID present and stable
- wMaxPacketSize 0x0200 on bulk endpoints
- 5+ minutes stable, zero errors
End-to-end CCID confirmed:
- pcsc_scan detects reader: Baochip Dabao CCID (HBZFHW)
- Card inserted, ATR: 3B DA 18 FF 81 B1 FE 75 1F 03 00 31 C5 73 C0 01 40 00 90 00 0C
- Identified as: OpenPGP Card V2
Changes in services/usb-bao1x/:
- Correct USB CCID 1.1 descriptor layout (54 bytes); fixed missing bNumClockSupported/bNumDataRatesSupported that caused pcscd to read dwFeatures from wrong offset
- CCID_BULK_MAX_PACKET = 512 (high-speed); CCID_WIRE_MAX = 271 (short APDU framing)
- Removed CCID interrupt IN; eliminated EP collision with NKRO
- Removed Pddb::new() from before cu.init(); blocked USB init
- Moved Keyboard::new() to after SE0; KPC/SFR_IOX conflict on PF5
- SE0 Low->500ms->cu.init()->150ms->High; matches boot1 timing
- Deferred listener cleared on USB reset and unplug
- GetSlotStatus handled inline in IRQ path (100ms libccid window)
- Bulk OUT primed in set_device_address after ep_enable loop
- ccid-openpgp does not depend on pddb
- dabao-ccid xtask recipe added
- hosted-baosec wires ux-api/hosted-baosec for blitstr2
Changes in libs/bao1x-hal/:
- bulk_xfer: ep_out_ready rolled back on failed TRB queue
- set_device_address: prime bulk OUT after ep_enable loop
docs/:
- CCID_TEST_REPORT.md: Dabao hardware results, ATR, pcsc_scan output
- CCID_PROTOCOL_AND_HIL.md: corrected features and build targets
- code_map.md: dabao-ccid and 512 MPS documented
cargo check -p usb-bao1x --features hosted-baosec,ccid-openpgp: PASS cargo test -p usb-bao1x --lib ccid_framing: 7/7 PASS cargo xtask dabao-ccid --no-verify: PASS
Closes #875 Signed-off-by: Supermagnum 12345678+Supermagnum@users.noreply.github.com
Activity
57Have you tried doing a cargo clean and then rebuild - if you did a test build with a configuration that wasn't dabao somewhere along the way, some old target files can hang out that screw up the linking of the image and can lead to a problem similar to this.
You should be able to get data out of the hard wired serial that would reveal the error - there's plenty of debug output that happens that doesn't go through the USB serial. That being said, not enumerating could be a number of problems, could be anything from a loader issue, to the USB stack failing to boot, to the USB stack being stalled trying to talk to a process or server that doesn't exist.
In the end probably the most useful thing you could do is to try and capture the serial output from the board. If you have two Dabaos, you could use one dabao to "listen" to the other and pipe it over USB serial - sort of a poor person's USB-serial adapter - but yah, you would need some sort of adapter in the end to make progress on a bug like this. Debugging the USB stack is pretty hard to do over USB since you're modifying the same transport layer that you rely upon for debugging.
Have you tried doing a
cargo cleanand then rebuild - if you did a test build with a configuration that wasn't dabao somewhere along the way, some oldtargetfiles can hang out that screw up the linking of the image and can lead to a problem similar to this.You should be able to get data out of the hard wired serial that would reveal the error - there's plenty of debug output that happens that doesn't go through the USB serial. That being said, not enumerating could be a number of problems, could be anything from a loader issue, to the USB stack failing to boot, to the USB stack being stalled trying to talk to a process or server that doesn't exist.
In the end probably the most useful thing you could do is to try and capture the serial output from the board. If you have two Dabaos, you could use one dabao to "listen" to the other and pipe it over USB serial - sort of a poor person's USB-serial adapter - but yah, you would need some sort of adapter in the end to make progress on a bug like this. Debugging the USB stack is pretty hard to do over USB since you're modifying the same transport layer that you rely upon for debugging.
I will try that later today, and check If I can get someone to solder up a Seeed Studio XIAO as a UART reader. It is fast enough. I do not have the proper equipment to see what I am doing, and a 100 W soldering iron used to solder SO239 connectors is not suited for such fiddly soldering..
Will keep you informed.
It was a stale build, that is now resolved.
Note: On the GetSlotStatus/WriteUSB timeout — clarifying the trade-off, not a new bug
Following up on the docs/ccid/OPENPGP_APDU_FOLLOW_UP_GetSlotStatus_Timeout.md write-up: worth being explicit that the enq != deq skip in force_prime_bulk_out (bao1x-hal/src/usb/driver.rs) isn't an oversight — it was added deliberately to fix an earlier synthetic-retire bug. The current WriteUSB/OUT-starvation hypothesis means there are now two failure modes sitting on either side of that same trade-off: the synthetic-retire bug it was added to prevent, and the OUT-starvation case it appears to allow under this PR's 8-process priming pattern (openpgp-apdu's LinkStatus polling plus the CCID deferred-park routine both triggering soft primes, alongside the periodic force-prime).
To be clear on scope: this isn't a framing bug in this PR's CCID code. Inline GetSlotStatus/IccPowerOn handling in ccid_transport.rs/ccid_framing.rs already works correctly — confirmed on real hardware, ATR reads back exactly right on the 7-process dabao-ccid build. The suspected root sits in bao1x-hal's OUT-arming/recovery path, but it's specifically this PR's priming pattern that appears to trigger it, so it's not purely a "someone else needs to wire this up" situation either — a real fix likely needs someone with visibility into both the original synthetic-retire fix and this PR's usage pattern, since naively relaxing the guard risks reintroducing the bug it was added to prevent.
Separately, worth not conflating this with a different, already-expected gap: gpg --card-status also hangs on the known-good 7-process dabao-ccid build, but that's a ReadUSB timeout after a successful ATR, caused by the lack of a deferred XfrBlock/APDU handler on that image — a different mechanism entirely from the documented WriteUSB/OUT case above, and not something to fix as part of resolving this,- To actually resolve the WriteUSB case: enable irq-pending-trace, reproduce on the 8-process image, and capture the bulk-TRB trace (out_enq/out_deq/force-prime skips) to confirm whether GetSlotStatus frames ever reach drain_complete_messages on the device side when pcscd reports the timeout. Happy to help with that trace capture if useful, but wanted to get the scoping written down clearly first so this doesn't get chased as a CCID-framing bug when the evidence points elsewhere.
Update on the GetSlotStatus/WriteUSB timeout — one hang fixed, one confirmed and rescoped
Following up on the docs/ccid/OPENPGP_APDU_FOLLOW_UP_GetSlotStatus_Timeout.md write-up. Two separate things have now been resolved via correlated usbmon + device-side trace captures.
- The 7-process dabao-ccid hang — fixed.
This was not the WriteUSB/OUT-starvation hypothesis (H2). Captured with usbmon (host) against bulk_trb_trace_poll.py (device) during a live hang: both GetSlotStatus and the SELECT XfrBlock bulk-OUT transfers completed successfully — out_armed/out_consumed stayed in lockstep throughout. The actual cause was pcscd's ReadUSB() timing out ~13s after a successful XfrBlock OUT, because this image had no deferred CcidTx handler wired up to ever answer it — the request was accepted and silently dropped.
Fix (commits 61d3d8160 + 30befab2f, on top of dde702588): IrqCcidRx now always sends a bulk-IN reply when no listener has claimed the deferred slot — XfrBlock → a stub DataBlock with SW 6A82 (file/applet not found), PowerOff/Abort → SlotStatus OK, anything else → CMD_NOT_SUPPORTED. This doesn't add OpenPGP applet support to the 7-process image — it makes the image correctly report that no applet exists, instead of hanging as if one might eventually answer. Verified on hardware: gpg --card-status now returns in ~16ms with a clean scdaemon: no supported card application found (SW 6A82), instead of a 45s timeout. pcsc_scan/ATR unaffected. Unit tests 17/17 (+3 new framing tests).
- H2 (WriteUSB/OUT-starvation under the 8-process openpgp-apdu build) — confirmed, but broader than originally scoped.
Built the actual 8-process dabao-ccid openpgp-apdu image with irq-pending-trace and captured the same way. Findings:
The board enumerates (~3s after boot — not the pure never-enumerates case in Supermagnum/xous-core#1), but the CCID path dies ~3–10s later. Host-side: repeated WriteUSB() … LIBUSB_ERROR_TIMEOUT every ~5s; usbmon shows bulk-OUT submissions that never complete cleanly (st=-2, no successful st=0). This is the genuine H2 signature — distinct from the 7-process bug above, where OUTs completed fine and only the reply was missing. Device-side: attempted to poll bulk_trb_trace via the EP0 vendor control requests (0x42/0x43/0x44) — all timed out, and even string descriptor reads failed. EP0 itself is unresponsive, not just the bulk-OUT endpoint.
That last point changes the scope: this isn't cleanly isolated to the enq != deq skip in force_prime_bulk_out as originally hypothesized. A dead EP0 alongside dead bulk-OUT suggests the entire USB IRQ-servicing path stalls a few seconds into the 8-process bring-up — broader than one endpoint's re-arm guard. The guard is still a plausible contributor, but this evidence doesn't isolate it from a wider stall.
To be clear on what's still confirmed working: inline GetSlotStatus/IccPowerOn handling in ccid_transport.rs/ccid_framing.rs is not the problem — ATR reads back correctly on the 7-process build, and this isn't a CCID-framing bug on either image.
Next step, not yet attempted: resolving H2 fully likely needs UART-level visibility into the USB IRQ handler/task a few seconds into 8-process bring-up, since EP0 dying means no further USB-side telemetry is available past that point. This is a distinct issue from the 7-process fix above — different image, different mechanism, and the 7-process fix is unaffected by it either way.
H2 rescoped again: it's an early post-enum stall, not sustained OUT-ring desync
Three repeated usbmon-only capture cycles (no code changes, same 30befab2f 8-process + irq-pending-trace build) pinned this down precisely:
Deterministic across all 3 runs: the last successful transaction is always the same CTRL IN (HID report-descriptor read), and the first EP0 failure (st=-2) follows at ~2.36s every time — not a wide race window. The device stays connected and addressed (1d50:6197 persists) but every subsequent URB — control or bulk — times out. Not a disconnect/re-enum; a genuine stall with the stack still electrically present. No successful bulk transaction ever occurs on any of the 3 runs — meaning the earlier H2 "WriteUSB timeout during sustained gpg traffic" framing was actually downstream noise: the real failure is a full USB-IRQ-path stall in the ~2.36s window right after enumeration, well before gpg/pcscd even get a chance to exercise bulk-OUT under load.
This has reached the ceiling of what host-side capture can determine — usbmon establishes WHAT (stall, not disconnect) and WHEN (2.36s, HID-descriptor-read-to-stall) with high confidence, but not WHY the firmware stops servicing USB interrupts. That requires device-side visibility (UART), but the window to instrument is now extremely narrow: whatever happens between the last HID descriptor fetch and ~2.36 seconds later during 8-process bring-up.
I hope this is helpful even if I am unable to complete it due to sight issues. Soldering on tiny pads is difficult for me.
Update: 8-process stall — EP0 telemetry rules out two hypotheses, surfaces a plausible third
Following up on the earlier H2 rescoping: ran a full round of correlated telemetry (usbmon + irq-pending-trace's 0x42/0x43/0x44/0x45 vendor requests) on the actual 8-process dabao-ccid openpgp-apdu build to pin down why EP0 and bulk both go permanently unresponsive ~2.36s after enum (idle) or as fast as ~0.4-0.5s under aggressive EP0 polling.
Ruled out:
Double-lock/dropped-IRQ (the try_lock failure path in composite_handler that the code's own comment flags as "actually broken, stack will crash"): lock_contended stayed at 0 across all captures; composite_handler entries/exits tracked each other 1:1 right up until EP0 died, so the interrupt handler itself was healthy throughout. EV_ENABLE stuck disabled with a skipped restore: enable_cleared_nonempty/disable_with_pending never moved; handler kept firing normally until the cliff. A late-stage bulk TRB enq/deq divergence at time of death: the out_enq=512/out_deq=0 state was a steady pre-existing condition from early in the capture (the force_prime_bulk_out synthetic-retire-skip path, matching the already-documented "leave it like this for now" behavior), not something that changed at the failure instant.
New finding: failure onset is clearly load-dependent, not purely time-dependent — idle boot gives ~2.36s before the stall; polling EP0 vendor requests shortens this to ~0.4-0.5s regardless of which specific register is being read. This points at something that accumulates per EP0 control transaction rather than a fixed-time race.
A plausible mechanism (code-review finding, not yet empirically confirmed): EP0 uses a single fixed, non-ringed response buffer (ep0_buf), unlike bulk endpoints which have an indexed ring. The hardware's own SetupTagMismatchError completion code — which exists specifically to detect a stale/overlapping response — is already handled in handle_event_inner by being folded into the normal-success path with no logging or recovery:
rust if comp_code == CompletionCode::Success || comp_code == CompletionCode::SetupTagMismatchError { // treated identically — no recovery action
Frequent EP0 control traffic increases the chance of a new SETUP arriving while the single shared buffer is still in use by the prior transaction, and if a mismatch fires without any downstream tracking, it can't currently be distinguished from normal operation. This is a plausible candidate for the eventual stack-wide unresponsiveness, but I want to flag it as unconfirmed — establishing it properly would need either a new counter on SetupTagMismatchError occurrences (not currently tracked anywhere in the telemetry) or a targeted hardware test, neither of which I've been able to do yet.
Full flight-ring captures, usbmon logs, and the ordered event timelines from this investigation are available if useful for anyone with deeper Corigine/USB-controller context.
Board-side note: stock dabao/dabao-ccid kernel images currently fail to boot on my Dabao hardware — isolated away from this PR's code
While rebasing this branch onto current upstream/dev (cherry-picking my own commits after the dev history rewrite — pure git surgery, no logic changes), I hit a hardware issue reflashing my board that's worth recording here in case it's relevant to anyone else testing this PR on real hardware.
Local verification, all passing before/after the rebase:
cargo xtask dabao-ccid --no-verify — builds and signs cleanly cargo check -p usb-bao1x --features hosted-baosec,ccid-openpgp — pass cargo test -p usb-bao1x --lib --features hosted-baosec,ccid-openpgp — 14/14 (10 CCID framing + 4 EP-budget) cargo test -p openpgp-apdu — 6/6 This PR's own CI (9 build jobs — boot0/boot1/alt-boot1, bare-metal Dabao, stock dabao, stock baosec, both hosted targets, Renode image, slot-map check) — all green
Hardware issue: partway through reflashing to reconfirm the rebased branch on real hardware, my board stopped completing the boot1→kernel handoff — for stock dabao, unmodified, no CCID at all, not just the CCID image. Bootloader (1d50:6196) stays healthy and recoverable via RESET, bootwait toggles correctly via the REPL console, UF2 copies transfer cleanly with matching sizes, but boot consistently ends in a brief -71 then silence — never reaching 1d50:6197.
I isolated this with cargo xtask bao1x-baremetal-dabao --no-verify (the bare-metal USB bring-up target already in this PR's CI matrix): that image enumerates fine via a plain RESET, clean high-speed enum, no cycling. So boot1's handoff mechanism and USB re-enumeration are confirmed healthy on this board — the failure is isolated to the full Xous kernel / multi-process bring-up path (ticktimer → xous-names → log → services), not to boot1, not to CCID-specific code, and not to this branch's changes specifically, since stock dabao fails identically.
I don't have UART hardware as previously mentioned, so I can't see kernel-side boot output past the point the CDC-ACM console drops at handoff — no process list, no panic output. If anyone testing this PR on their own Dabao hits the same "stock image won't reach 1d50:6197" symptom, or knows of something beyond bootwait that can gate the boot1→kernel jump, I'd appreciate a pointer. For now this looks like a board/session-state issue on my end rather than anything introduced by this PR's code, since the isolation points below the application layer entirely — but flagging it here since it's blocking me from doing a final hardware reconfirmation of the rebased branch before considering it fully re-verified.