RPBP v1 — SYS subsystem¶
Subsystem id: 0x00. Carries device-level operations that do not
belong to a bus: capability discovery, identification, uptime, LED
control, reboot-to-bootsel, self-test. Every RPBP device MUST
implement every opcode in this document — there is no negotiation
for SYS.
All SYS operations are issued on the control channel (channel 0)
via CMD_REQUEST / CMD_RESPONSE. Binary encoding is canonical;
CBOR is permitted and is the default for GET_CAPABILITIES.
Opcode summary¶
| Opcode | Name | CBOR-default | Purpose |
|---|---|---|---|
0x00 |
GET_CAPABILITIES |
yes | Return the CBOR capability map. |
0x01 |
ECHO |
no | Round-trip test. Response equals request bytes. |
0x02 |
REBOOT_BOOTSEL |
no | Jump into the ROM USB mass-storage bootloader for OTA. |
0x03 |
UPTIME |
no | Return microseconds since boot (u64). |
0x04 |
GET_VBUS_MV |
no | Return measured VBUS in millivolts (u16). |
0x05 |
SET_LED |
no | Set the status RGB LED colour + mode. |
0x06 |
SELFTEST |
no | Run the on-device loopback self-test. |
0x07 |
GET_IDENTITY |
yes | Board / firmware / serial identity. |
0x08 |
RESET |
no | Soft-reset the device (re-enumerates). |
0x09 |
UART_CLAIM |
no | Driver asks CDC pump to stop touching a UART. |
0x0A |
UART_RELEASE |
no | Release a previously-claimed UART. |
Opcodes 0x0B..0x7F are reserved for additive MINOR-version
extensions. 0x80..0xFF are vendor-reserved and MUST NOT appear in
upstream firmware.
0x00 — GET_CAPABILITIES¶
Return the CBOR capability map documented in
../capabilities.md. Uses the CBOR flag.
Request (CBOR flag set):
Response (CBOR flag set):
Maps larger than MAX_PAYLOAD_LEN are transmitted using fragmentation
(see ../fragmentation.md).
0x01 — ECHO¶
Round-trip test for bring-up and latency measurement.
Request (binary):
0 1 2 N
+--------+--------+----------------------------+
| 0x00 | 0x01 | payload |
+--------+--------+----------------------------+
Response (binary):
0 1 2 3 N
+--------+--------+--------+--------------------+
| 0x00 | 0x01 | OK | payload |
+--------+--------+--------+--------------------+
The device MUST return the payload bytes exactly as received. Any
deviation fails the bring-up self-test. Maximum payload size is
MAX_PAYLOAD_LEN - 4 (to leave room for subsys/opcode/status).
0x02 — REBOOT_BOOTSEL¶
Request the device to enter the RP2350 ROM USB mass-storage bootloader so that the host can flash a new UF2 image.
Request (binary):
Response (binary):
The device MUST send the response before triggering the reboot. The USB reconnection as the RP2350 mass-storage device happens within ~300 ms; the host is expected to tolerate the resulting USB disconnect. No argument is required — the RP2350 ROM manages its own activity LED.
0x03 — UPTIME¶
Return microseconds since device boot as a 64-bit unsigned integer.
Request (binary):
Response (binary):
0 1 2 3 10
+--------+--------+--------+----------------------------------+
| 0x00 | 0x03 | OK | uptime_us (LE u64) |
+--------+--------+--------+----------------------------------+
0x04 — GET_VBUS_MV¶
Read the VBUS voltage through the on-board resistor divider on GP46. Useful for diagnosing host-side supply issues.
Request (binary):
Response (binary):
0 1 2 3 4 5
+--------+--------+--------+--------+--------+
| 0x00 | 0x04 | OK | vbus_mv (LE u16) |
+--------+--------+--------+--------+--------+
Accuracy: ±50 mV at the measurement point. A reading outside the
4500..5500 mV band SHOULD lead the host to warn the user.
0x05 — SET_LED¶
Control the on-board status RGB LED. Overrides the firmware's own state-machine colouring until the device is reset.
Request (binary):
0 1 2 3 4 5
+--------+--------+--------+--------+--------+
| 0x00 | 0x05 | R | G | B | mode |
+--------+--------+--------+--------+--------+------+
| bright |
+--------+
R,G,B(u8 each) — colour at full brightness.mode(u8) —0 = off,1 = solid,2 = blink 1 Hz,3 = pulse 1 Hz,4 = fast-blink 4 Hz,5..255reserved.bright(u8) — global brightness 0..100 (%). Values >100 clamped.
Response (binary):
status = EINVAL when mode is out of range. status = OK
otherwise. To return control to the firmware's own state machine,
send mode = 0 then wait ~100 ms; the firmware reclaims the LED.
Firmware default scheduler (no override)¶
When no SET_LED override is active, the firmware drives the RGB LED from a small prioritised state machine. Higher priority always wins; lower-priority scenes are suppressed until the higher one clears.
| Priority | Scene | Pattern | Trigger |
|---|---|---|---|
| 1 (⬆) | HOST_OVERRIDE |
as per SET_LED mode/colour | mode ≠ 0 set via this opcode |
| 2 | WATCHDOG_BOOT |
red double-blink @ 2 Hz, ≤ 10 s | watchdog_caused_reboot() latched at boot |
| 3 | USB_DETACHED |
blue breathing @ 1 Hz (triangle) | tud_ready() = false (not enumerated / not configured) |
| 4 (⬇) | IDLE |
green breathing @ 1 Hz (triangle) | fallback — everything nominal |
The animation phase is derived from absolute boot time
(time_us_64()), so a scene transition (e.g. USB_DETACHED →
IDLE on enumeration) keeps the breathing curve in-phase rather
than restarting it — visually smoother.
Per-channel perceptual calibration (~30 % R / 100 % G / 60 % B) is
applied by the firmware so (R=G=B=0xFF) at 100 % brightness
renders as visually balanced white on the rev-B hardware (discrete
common-anode RGB with per-colour current-limit resistors). Hosts
should therefore send colour as their natural intent and not
pre-compensate.
0x06 — SELFTEST¶
Run an internal suite of loopback and sanity checks.
Request (binary):
0 1 2 3 4 5
+--------+--------+--------+--------+--------+
| 0x00 | 0x06 | test_mask (LE u32) |
+--------+--------+-----------------------------+
test_mask (u32, LE) — bitmap of tests to run. 0xFFFF_FFFF means
"all available". Bits above the device's known range are silently
ignored. Current rev-B firmware implements 10 tests:
| Bit | Test | What it verifies |
|---|---|---|
| 0 | pin_broker integrity | Every broker slot has consistent (owner, function) tags. |
| 1 | CRC-32C known-vector | rpbp_crc32c("123456789") == 0xE3069283 (RFC 3720). |
| 2 | clk_sys range | clock_get_hz(clk_sys) within ±5 % of 150 MHz. |
| 3 | ADC temp sensible | On-chip temperature sensor reads a realistic bench value (5–85 °C). |
| 4 | I²C #0 baudrate set | i2c_hw_baudrate(0) > 0 — bus init ran and stuck. |
| 5 | SPI #0 SET_FREQ | spi_hw_set_freq(0, 1 MHz) returns ~1 MHz (±50 %). |
| 6 | PWM LED running | The R channel of the status LED is slice-enabled. |
| 7 | CAN bitrate | can_hw_bitrate() > 0 — can2040 start() succeeded. |
| 8 | Core 1 alive | io_control stats counter advances within a 2 ms window. |
| 9 | Watchdog enabled | watchdog_hw->ctrl bit 30 (ENABLE) set. |
Bits 10..31 are reserved for future additive tests. Hosts that pass
a test_mask with unknown bits set get only the known bits in
pass_mask; they can derive "unsupported" by checking
(test_mask & ~pass_mask).
Response (binary):
0 1 2 3 4 5 6 7
+--------+--------+--------+--------+--------+--------+--------+--------+
| 0x00 | 0x06 | OK | pass_mask (LE u32) | fails |
+--------+--------+--------+-----------------------------------+--------+
| (optional per-failure blobs ...) |
+-----------------------------------------------------------------------+
pass_mask(u32, LE) — bits set for tests that passed.fails(u8) — count of following per-failure blobs.- Per-failure blob format:
status != OK only on catastrophic internal error; test failures
are reported via pass_mask, not via status.
0x07 — GET_IDENTITY¶
Shortcut for GET_CAPABILITIES when the host only needs
identification strings (board, firmware version, serial). Returns a
compact CBOR map.
Request (CBOR flag):
Response (CBOR flag):
{
"s": 0, "o": 7, "st": 0,
"r": {
"fw": "0.1.0+abc123",
"board": "rpbridge-rev-B",
"serial": h'1122334455667788',
"proto": [1, 0, 0]
}
}
Always fits in a single non-fragmented frame.
0x08 — RESET¶
Soft-reset the device. Equivalent to toggling the RUN pin: the USB
link drops, the device reboots, and the composite re-enumerates. The
firmware state is discarded (unlike REBOOT_BOOTSEL, which starts
the ROM bootloader).
Request (binary):
delay (u8) — delay in milliseconds before the reset, 0..200. Allows
the host to receive the response and tear down cleanly before the
link drops.
Response (binary):
0x09 — UART_CLAIM¶
Driver-coordinated UART hand-off (see ADR-0005 #M9b). When a UART instance is claimed, the firmware's CDC pump stops forwarding bytes between its CDC-ACM(s) and the UART hardware:
- CDC → UART: host writes into the CDC-ACM's TX FIFO are silently drained; the bytes never reach the UART hardware.
- UART → CDC: bytes coming in on the UART do NOT appear on the CDC RX side. The CDC goes dark from the host's perspective.
- The UART hardware itself keeps running; bytes arriving on RX accumulate in the HW ring (or overflow-drop once the ring fills — claim-holder's responsibility to service them).
Claims are per UART instance:
uart_idx |
Instance | Physical UART |
|---|---|---|
0 |
UART0 HW |
phy-bank (RS232/RS485/TTL) |
1 |
UART2 PIO |
GP33/GP36 PIO UART |
Indexes ≥ 2 return EINVAL.
Request (binary):
Response (binary):
status = OK on successful claim (idempotent — re-claiming an
already-claimed instance is OK). status = EINVAL if uart_idx ≥ 2.
Claims are NOT persisted across resets: a soft reset / re-enumeration clears every claim.
0x0A — UART_RELEASE¶
Release a previously-claimed UART instance. The CDC pump resumes
bidirectional forwarding starting with the next cdc_glue_poll()
tick.
Request (binary):
Response (binary):
status = OK on successful release (idempotent — releasing a free
instance is OK). status = EINVAL if uart_idx ≥ 2.
RPBP v1 has no session/identity tracking, so any host that can speak
RPBP can issue claim/release. Coordination between multiple potential
claim holders is a higher-layer concern (the Linux driver handles it
by holding the claim for the lifetime of an ioctl-scoped operation).
Error behaviour¶
Unknown opcodes on subsystem 0 return CMD_RESPONSE { status = ENOENT }
with an empty result_args section.
Payload-length mismatches for fixed-size requests return
CMD_RESPONSE { status = EMSGSIZE }.
CBOR-encoded SYS requests that fail to parse return
CMD_RESPONSE { status = EPROTO } in binary form (so even a broken
CBOR request can be diagnosed on the host).