Skip to content

firmware-ch32x: USB-PD DR_Swap — a PC on the output port enumerates the RP2040 CDC while being powered#6

Merged
cmd0s merged 1 commit into
mainfrom
PD_DataRole
Jul 17, 2026
Merged

firmware-ch32x: USB-PD DR_Swap — a PC on the output port enumerates the RP2040 CDC while being powered#6
cmd0s merged 1 commit into
mainfrom
PD_DataRole

Conversation

@cmd0s

@cmd0s cmd0s commented Jul 17, 2026

Copy link
Copy Markdown
Member

What

Dock-style behavior on the output USB-C port: a laptop attached instead of a Pi negotiates power as a sink and takes the USB host data role via PD DR_Swap, so the RP2040 WUPS CDC is reachable without a Pi in the loop. Companion desktop viewer: Web3-Pi-UPS-Service#3 (ups-live).

The full recipe (each piece required, discovered iteratively on the bench):

  1. DR_Swap Accept — STA_IDLE-only, NumDO==0 guard (type 0x09 collides with PD3.1 EPR_REQUEST), MessageID retransmission dedup, role toggle only after TX-confirmed Accept inside an IRQ-guarded RMW, Wait when busy, HRST on ambiguous TX failure; role-aware GoodCRC (0x61 DFP / 0x41 UFP)
  2. Answer post-swap host traffic — PD3 treats GoodCRC-then-silence as a protocol error (macOS looped Soft_Reset → re-swap): Not_Supported default (PD3-negotiated only), VCONN_Swap → Reject, Get_Source_Cap → inline SRC_CAP, Get_Sink_Cap → Not_Supported
  3. Discover Identity as UFP → honest ACK (USB Device Capable, Peripheral, VID 0x2E8A) — with a NAK macOS never sends Enter_USB and tears the port down; the RPi-27W identity reply is now gated to the DFP role
  4. Enter_USB (data 0x08) → Accept for USB2 + PDO#1 advertises USB Communications Capable (B26)

Also fixes two pre-existing protocol bugs (spec-incomplete Soft_Reset RX that escalated to Hard Reset + VBUS drop; STA_RX_REQ_WAIT timeout firing on our own dispatch latency) and adds a PD event trace ring (visible as [CH32X log] PD: ... via RP2040 debug/Probe) plus PD_Service() latency relief between the heavy I2C telemetry blocks.

Verification

  • Mac (target feature), macOS 26.5.2 / MBP M4: contract 15 V + DR_Swap + enumeration of the RP2040 CDC; stable multi-minute sessions incl. on battery; PD trace clean (no resets)
  • Pi 5 regression (core), on w3p-72h-3: 5 V/5 A contract (PDO#1), device-tree confirms received PDOs incl. B26 (0x0E0191F4) and max_current=5000 — the VDM 5 A unlock works; CDC enumerates; w3p-ups agent parses the stream with zero errors; no undervoltage
  • Build: riscv-none-embed-gcc 8.2.0, no warnings, 35 248 B text (62 K flash)

Before flashing the production run

Still outstanding (tracked for the release checklist, not blockers for main):

  • load soak on a Pi 5 under sustained draw
  • mains-pull battery-switchover E2E
  • Mac robustness matrix (swap → ups.power.cycle → re-swap, repeated swap/swap-back, sleep/wake)

…he RP2040 CDC while being powered

Dock-style behavior on the output USB-C port: a laptop attached instead
of a Pi negotiates power as a sink and takes the USB host data role, so
the RP2040 WUPS CDC is reachable without a Pi in the loop. Verified E2E
on MacBook Pro M4 (macOS 26.5.2): stable 15 V + /dev/cu.usbmodem* for
5+ min, including on battery.

Protocol (PD_Process.c/.h):
- DR_Swap Accept: STA_IDLE-only, NumDO==0 guard (type 0x09 collides with
  PD3.1 EPR_REQUEST), MessageID retransmission dedup (latch invalidated
  by any other dispatched message — 3-bit counter wraps), role TOGGLE
  only after TX-confirmed Accept inside an IRQ-guarded RMW (Flag word is
  shared with the ISR), Wait when busy, HRST on ambiguous TX failure;
  GoodCRC byte0 is now role-aware (0x61 DFP / 0x41 UFP)
- post-swap host traffic answered instead of ignored — PD3 treats
  GoodCRC-then-silence as a protocol error and macOS loops
  Soft_Reset -> renegotiate -> re-swap (CDC flapped sub-second):
  default -> Not_Supported (PD3-negotiated only; PD2 keeps the shipped
  silent behavior), VCONN_Swap -> Reject, Get_Source_Cap -> inline
  SRC_CAP (the STA_TX_SRC_CAP path has a 160 ms dwell, over
  tSenderResponse), Get_Sink_Cap -> Not_Supported
- Discover Identity received as UFP -> honest ACK (USB Device Capable,
  Peripheral, VID 0x2E8A); with a NAK macOS never sends Enter_USB and
  tears the port down right after electrical attach (and remembers the
  verdict per physical port). The RPi-27W identity reply is now gated
  on PD_Role==DFP so a post-swap host is never told we are a Pi PSU
- Enter_USB (DATA type 0x08) -> Accept for USB2, Reject for USB3/USB4
- PDO#1 advertises USB Communications Capable (B26) alongside the
  long-shipped Dual-Role Data bit
- spec-complete Soft_Reset RX: MessageID counter reset + SRC_CAP
  re-advertise (previously Accept-then-dead-air escalated through
  SinkWaitCap timeout to Hard Reset = VBUS drop)
- data-role restore to DFP + dedup reset on TX Hard Reset and on
  Power_Output_Restart via new PD_DataRole_Reset() (a swapped role must
  not leak into the fresh session after ups.power.cycle)
- STA_RX_REQ_WAIT timeout no longer fires while a received message is
  still awaiting dispatch (the state machine runs before RX dispatch;
  telemetry-heavy loop iterations exceeded the 29 ms window and our own
  latency HRST'd a valid Request)

Diagnostics + latency (main.c):
- PD event trace ring drained into system.log frames ("[CH32X log]
  PD: ...") visible on the Probe/USB-CDC debug stream; writers only
  store a byte so tracing adds zero latency to protocol paths;
  rate-limited to 1 frame per 4 ms (RP2040 forwarder drops a 3rd
  back-to-back frame)
- PD_Service() wrapper with its own ms-delta accounting, called between
  the heavy bit-bang I2C telemetry blocks: worst-case PD dispatch
  latency was 10-30 ms against tSenderResponse 24-30 ms (calling
  PD_Main_Proc repeatedly with the loop-top delta would multiply-count
  time and break the attach timing)

NOT for production units yet: PDO#1 changed (B26) — the mandatory Pi 5
regression run (5V/5A contract, VDM 5A unlock, CDC enumeration, load
soak) has not been done.
@cmd0s
cmd0s merged commit a6a2806 into main Jul 17, 2026
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