SANBlaze I3C Controller — Release Notes
V1528 (March 2026)
Previous release: V1527
Summary
V1528 adds complete MCTP over I3C support for drive health monitoring. The firmware can now send multi-packet MCTP commands to MCTP capable drives, read full 4096-byte Identify Controller responses across 65 packets, and route the data into the existing target ring buffer for seamless customer application integration. The release also adds I3C Private Transfer support, USB chunking for large payloads, PIO diagnostic tooling, and comprehensive test coverage.
New Features
MCTP over I3C (NVMe-MI)
mctp_command: Single CLI command fires buffered MCTP packets, polls for IBI, and reads all response packets autonomously. Returns decoded MCTP packet data with SOM/EOM flags, sequence numbers, and ASCII payload view.mctp_to_ring: Same as mctp_command but routes the complete response into the target ring buffer. Customer applications read data via the existingTARGET_CMD_DATA_READ(0x52) path with zero code changes. Ring buffer reports 0 bytes until the entire response is committed.mctp_read: Firmware-side multi-packet read with exact EOM byte counting. Reads up to 118 packets per transaction with per-packet watchdog updates.
SETMWL / SETMRL (CCC 0x89 / 0x8A)
setmwl <da7> <bytes>: Sets Maximum Write Length on the target via direct CCC 0x89. Payload is 2 bytes big-endian. Must be sent after ENTDAA and ENEC, before any MCTP traffic. Required by many I3C MCTP targets to establish maximum transfer unit.setmrl <da7> <bytes>: Sets Maximum Read Length on the target via direct CCC 0x8A. Same timing requirement and payload format as SETMWL.Both commands are visible on the protocol analyzer. Standard practice is to set both to 69 (0x00 0x45) to match the MCTP packet size.
I3C Clock Management
Separate clock values are now maintained for I2C and I3C modes.
i2c_clk <khz>andi3c_clk <khz>write the desired clock to persistent storage without touching hardware.set_mode i2c|i3capplies the stored clock for that mode automatically. Defaults: I2C 100 kHz, I3C 1250 kHz.This eliminates the previous behavior where switching modes could leave the hardware running at the wrong clock.
I3C Private Transfers
private_write [--hold] <da7> <bytes>: I3C Private Write with 0x7E broadcast + Restart preamble. The--holdflag buffers packets in firmware for back-to-back burst transmission (required for multi-packet MCTP commands).private_read <da7> <nbytes>: I3C Private Read with 128-byte chunk support (up from 56 bytes).Burst write system accumulates up to 512 bytes across multiple
--holdcalls, then fires all packets back-to-back with proper STOP separation.
USB Multi-Chunk Transfers
Payloads larger than 56 bytes (USB EP0 limitation) are automatically split into chunks with FIRST/LAST flags and reassembled by firmware. Enables 69-byte MCTP packet writes that previously required workarounds.
PIO Diagnostics
pio_dump: Reports bus state (SDA/SCL levels, GPIO functions), PIO state machine status (PC, enabled, FIFO state, stall flags), target state, IRQ flags, and disassembled PIO instructions at current PC. Automatically runs on test failure.
Private Force Mode
private_force <on|off>: Forces all subsequent transfers to use I3C Private mode (0x7E broadcast + Restart preamble). Useful for drives that require Private framing for all communication.
Bug Fixes
I2C Clock Set to 10 MHz by TinyDriver
Fixed: The Linux i2c-tiny-usb driver sends a SET_DELAY control request on attach. The firmware’s SET_DELAY handler computed clock frequency with a 1000x unit error, converting a 10 kHz intent into a 10 MHz hardware setting. This left the bus running at full RF speed, causing continuous SCL oscillation and bus noise. Fix: removed clock manipulation from the SET_DELAY handler entirely. The stored delay value is preserved but clock is now set exclusively by
set_mode i2c|i3cusing the separately stored per-mode clock value.
Drive Lockup Prevention
Fixed: Reading past end of EOM packet data (0xFF padding bytes) permanently wedged the MCTP capable drive’s I3C state machine. Root cause: drive interpreted reads beyond real data as protocol violation. Fix: compute exact byte count for EOM packet from NVMe-MI message structure.
Missing STOP Condition
Fixed: PIO open-drain STOP was electrically invisible when the MCTP capable drive released SDA/SCL to float (both sides high-impedance with only pullups). Fix: GPIO push-pull STOP sequence after read loop drives definitive low-then-high transitions. Pins restored to PIO0 function after STOP.
Pullup Ordering
Fixed: Re-enabling GPIO pullups after PIO STOP meant STOP executed without pullup support, causing ambiguous bus levels. Fix: pullups enabled before STOP opcode.
Stack Overflow
Fixed: 384-byte debug arrays (
dbg_tbit[128],dbg_raw[128],dbg_byte[128]) on stack corrupted MCTP packet data in adjacent memory. Removed entirely; replaced by computed exact byte count approach.
Watchdog Stability
Fixed: Timeout protection macros (
PIO_WAIT_TX_EMPTY,PIO_WAIT_IDLE) and defensive code additions caused idle-time watchdog reboots every ~128 seconds. Root cause: added complexity interfered with main loop timing. Fix: clean surgical patch on proven BK16 baseline with only three targeted changes.
USB Deadlock in Debug Output
Fixed: CDC debug printf in hot paths could deadlock USB stack when output buffer full. Added per-byte buffering with overflow protection.
Target PIO Re-enable
Fixed: Target PIOs were not re-enabled after initiator transactions, causing missed incoming writes. Added
target_pio_force_address()to bypass rate limiting on re-enable.
T-bit Overdriven During SDR Read (Critical)
Fixed:
i3c_sdr_read()ini3c_hl.cwas driving SDA with push-pull output during the T-bit clock cycle instead of reading it. The ninth PIO transfer bit was encoded asSDR_WBIT(continuetransfer), meaning the controller actively drove SDA high on every non-last byte, overpowering the target’s T=0 assertion. T=0 was only visible on the last byte of a packet (whencontinuetransfer=0). Fix: changed the ninth bit toSDR_RBIT(0)so SDA is sampled as input during the T-bit clock cycle. Thecontinuetransferparameter is now unused and suppressed.Impact: Without this fix, multi-packet reads (4K Identify, 65 packets) never detected T=0 mid-packet and continued clocking the drive after it had finished sending, causing drive lockup requiring targetreset to recover. The math-mode workaround (
expected=4096) was masking this bug. Math mode is no longer required; T-bit-only mode (pkt_size=N, expected=0) is now the correct and tested default.
TinyDriver i2c Read Preemption During Active I3C Transaction
Fixed: The Linux i2c-tiny-usb driver (TinyDriver) could issue an i2c read request via
unified_i2c_read()while firmware was mid-MCTP-read from the drive. This happened becausetud_task()is called inside the MCTP read loop to keep USB alive, and TinyDriver’s USB control request could be serviced during that call. The preempting read caused firmware to issue a STOP mid-packet, cutting off the drive response and hanging the bus. Fix:unified_i2c_read()now checksg_initiator_activeand returns -1 (NAK) immediately if an I3C initiator transaction is in progress. TinyDriver sees a NACK, stalls, and retries after its own timeout by which time the transaction is complete.
mctp_to_ring Default Expected Bytes
Fixed:
HOST_CMD_MCTP_TO_RING(0x62) defaulted toexpected=4096when called with no payload (as TinyDriver does:cmd=0x62 len=0). This activated math mode and stopped the read after a computed byte count, truncating short responses. Fix: default changed toexpected=0(T-bit-only mode). Callers that need math mode must pass explicit parameters.
Test Suite Updates
Test 38: SETMWL 69 — verifies direct CCC 0x89 accepted by drive after ENTDAA/ENEC
Test 39: SETMRL 69 — verifies direct CCC 0x8A accepted by drive after ENTDAA/ENEC
Test 41: MCTP 4K Identify via manual burst write + IBI poll + mctp_read
Test 50: MCTP discovery via mctp_command (single-packet Get Endpoint ID)
Test 51: MCTP 4K Identify via mctp_command (autonomous firmware path)
Test 52: MCTP 4K Identify via mctp_to_ring (ring buffer customer path). Verifies committed byte count, reads data via i3c_data_read, confirms ring buffer drains to empty.
Tests 42, 61, 62 removed (functionality covered by other tests or deferred).
PIO dump automatically captured on test failure for post-mortem analysis
SETMWL/SETMRL now issued as preamble in all MCTP tests (38–52)
TinyDriver (sb_i2c) Validation
MCTP over I3C has been validated end-to-end via the Linux i2c-tiny-usb driver path
using sb_i2c. TinyDriver sends the MCTP command via the existing I3C private write
path (cmd 0x35) and fires mctp_to_ring (cmd 0x62) with no payload. Firmware routes
the drive response into the ring buffer; the driver reads it back via
TARGET_CMD_DATA_READ (0x52).
Example command and response (Get Endpoint ID, MCTP discovery):
sb_i2c -n 1 -d 2 -V -0 5,1 -z -w 84 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 e2 00 06 07
smbus_rsp[0]:
3b 01 00 00 c5 84 88 00 00 00 20 00 00 01 02 00
01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 9c 10 9f
07 11
The response is 49 bytes of real data (T=0 at byte 48), no 0xFF padding, no drive lockup, and no targetreset required between successive commands.
CLI Changes (sb_i3c)
New commands:
Command |
Description |
|---|---|
|
I3C Private Write, optional burst buffering |
|
I3C Private Read (up to 128 bytes) |
|
Read all MCTP response packets |
|
Fire burst + IBI + read (autonomous) |
|
Fire burst + IBI + read → ring buffer |
|
Force Private mode for all transfers |
|
Dump PIO/bus diagnostic state |
|
Set Maximum Write Length (CCC 0x89, direct) |
|
Set Maximum Read Length (CCC 0x8A, direct) |
|
Binary ring buffer read (u16 status; u16 len; data) |
Modified commands:
Command |
Change |
|---|---|
|
Multi-chunk support for payloads > 56 bytes |
|
128-byte chunk support for Private reads |
|
Now writes to persistent storage only; hardware applied by set_mode |
|
Now writes to persistent storage only; hardware applied by set_mode |
|
Now reads and applies the stored per-mode clock automatically |
Firmware Command Reference
Cmd |
Hex |
Direction |
Description |
|---|---|---|---|
|
0x61 |
OUT/IN |
Read all MCTP packets (firmware-side) |
|
0x62 |
OUT/IN |
MCTP command → ring buffer |
|
0x63 |
OUT/IN |
MCTP command → USB retrieval |
|
0x5D |
IN |
PIO diagnostic dump |
|
0x5E |
OUT |
Private mode force on/off |
|
0x08 |
— |
I3C Private transfer flag |
|
0x10 |
— |
Burst buffer accumulation flag |
Upgrade Notes
Firmware binary replaces V1527 with no configuration changes.
CLI binary (sb_i3c) must be updated to match firmware for new command support.
Test script (sb_i3c_test.sh) updated with tests 50-52; existing tests unchanged.
Existing customer applications using
TARGET_CMD_DATA_READrequire no changes when using themctp_to_ringpath. Only the driver layer needs to send the new setup commands (see MCTP Ring Buffer Integration Guide).