SANBlaze I3C Controller Platform
Version 1531 User Guide
Release Date: May 2026
Table of Contents
1. Introduction
V1531 adds three capabilities to the iRiser firmware: a framed ring buffer
where every write carries a 4-byte header for unambiguous demux of back-to-back
entries; an explicit-offset read protocol on TARGET_CMD_DATA_READ that lets
driver software request the framed header once per entry and stream the
remaining payload as offset-keyed continuation reads; and autonomous Hot Join
handling that detects a target asserting the I3C reserved address 0x02, runs
ENTDAA from a configurable starting address (default 0x1D), and publishes a
framed announcement entry to the ring.
The V1530 read contract is fully preserved. Existing host code using
i3c_data_read continues to see a header-stripped byte stream identical to
what V1530 produced. The new framed protocol is opt-in via a wValue flag
on the read control transfer.
All V1530 features and fixes are preserved unchanged.
2. Installation
Package Contents
The release package i3c_v1531_release.gz contains firmware, tools, and
documentation. Extract with the -P flag to install system binaries:
mkdir /tmp/sb_i3c
cp i3c_v1531_release.gz /tmp/sb_i3c
cd /tmp/sb_i3c
tar -xzvPf i3c_v1531_release.gz
Key package contents:
Path |
Description |
|---|---|
|
Host CLI tool (updated for V1531) |
|
Firmware image (UF2 format) |
|
Bind device to i2c-tiny-usb driver |
|
Unbind device from driver |
|
Find device by slot |
|
List all I3C devices |
|
Firmware update utility |
|
Automated test suite |
After extraction, all tools are installed system-wide and available without a path prefix.
Firmware Update
Set your slot first. Every
sb_i3cand related command targets a specific iRiser slot via-d <N>. To keep examples in this guide directly cut-and-pasteable, they all reference a$SLOTshell variable. Set it once at the start of your session and the rest of the examples just work:SLOT=2 # substitute your own slot numberTo list available slots:
sb_i3c_find_allTo verify the slot you set:sb_i3c -d $SLOT statusWorkflow examples wrap in a subshell
( ... )for two reasons. First, the subshell aborts cleanly ifSLOTisn’t set (the:?guard kills the subshell, not your interactive shell). Second, an earlyechomakes the active slot number visible in the output so you can spot a wrong-slot paste before any real commands run. The two resolution lines you’ll see throughout:( : ${SLOT:?Set your slot number with SLOT=N first} echo "# Executing example on user SLOT=$SLOT" I3C_TARGET=$(sb_i3c -d $SLOT i3c_target_status | grep "addr:" | gawk -F" " '{ print $2 }') I2C_DEV=$(sb_i3c -d $SLOT status | grep "I2C Dev" | gawk -F" " '{ print $3 }' | gawk -F"-" '{ print $2 }') ... )The
:guard line andechoare in every workflow example. The other two appear only in examples that actually need the local target address or the I2C bus number.Command-output blocks in this guide may show specific slot numbers in their output (e.g.
slot=2fromsb_i3c_find_all); those reflect one particular reference system and shouldn’t be changed when you cut and paste — they’re sample output, not commands.
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
sb_i3c_update -d $SLOT /etc/iRiser/sanblaze_i3c_fw_v1531.uf2
)
The device reboots automatically. Wait 3 seconds before issuing commands.
Verify the update:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
sb_i3c -d $SLOT status
# Expected output includes:
# Version: 1531
# ENTDAA Start: 0x1D
)
CLI binary must be updated alongside firmware. The status response struct grew by one byte in V1531 to carry the ENTDAA starting DA. A V1530 CLI paired with V1531 firmware will display incorrect status values.
Hardware Requirement: Bus Termination
V1529 introduced the requirement for correct bus termination on the drive side of the I2C mux. A missing jumper on the drive-side mux leaves the bus unterminated, causing pattern-sensitive ACK failures that are difficult to distinguish from firmware bugs. Symptom: intermittent SMBus errors on specific byte values that pass consistently on the FT260 path.
Verify the drive-side mux jumper is installed before diagnosing SMBus reliability issues.
3. Quick Start
Discover and Configure Drive
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
ENTDAA_DA=$(sb_i3c -d $SLOT status | grep "ENTDAA Start" | gawk -F" " '{ print $3 }')
# Power cycle the drive for a clean starting state
sb_i2c2 -d $SLOT -f power -w 0
sleep 1
sb_i2c2 -d $SLOT -f power -w 1
sleep 3 # let the drive boot — NVMe drives may need 3+ seconds before responding to bus traffic
# Re-establish I3C mode (mode is not preserved across power cycles;
# subsequent I3C commands will fail and may fault the firmware otherwise)
sb_i3c -d $SLOT set_mode i3c
# Reset all dynamic addresses (forces drive back to its static address)
# and reset the bus so ENTDAA reliably finds the drive
sb_i3c -d $SLOT rstdaa
sb_i3c -d $SLOT targetreset
sleep 0.5
sb_i3c -d $SLOT entdaa $ENTDAA_DA
# Enable IBI events (required for MCTP)
sb_i3c -d $SLOT enec $ENTDAA_DA
# Set maximum write and read length (optional — the drive's default already
# permits MCTP; included here as part of the standard bring-up).
# Use 0x7E for the broadcast form, or the drive's address for the Direct form.
# Both verify the value by read-back and exit non-zero if it did not take.
sb_i3c -d $SLOT setmwl $ENTDAA_DA 69
sb_i3c -d $SLOT setmrl $ENTDAA_DA 69
# Verify drive is present
sb_i3c -d $SLOT getpid $ENTDAA_DA
# Output (6 bytes — drive-specific): PID: 0xMM 0xMM 0xMM 0xMM 0xLL 0xLL
# bytes 0..3 = manufacturer ID (MIPI Provisional ID assigned per vendor)
# bytes 4..5 = instance ID assigned by the device
)
The sb_i3c_init helper packages the power cycle, ENTDAA, ENEC, and
SETMWL/SETMRL steps into a single command — use that for routine bring-up
once you understand what the manual sequence above does.
Hot Join Quick Path
To demonstrate Hot Join, power-cycle the drive then provoke it with any
CCC. The drive holds its HJN request until it sees bus activity, at which
point the firmware’s autonomous handler picks up the HJN, runs ENTDAA, and
commits the announcement to the ring buffer — no entdaa call needed
from the host.
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
# Drain any pending IBI before triggering the Hot Join
sb_i3c -d $SLOT i3c_poll
# Power cycle — drive will re-assert Hot Join when it comes up
sb_i2c2 -d $SLOT -f power -w 0
sleep 1
sb_i2c2 -d $SLOT -f power -w 1
sleep 3 # let the drive boot — NVMe drives may need 3+ seconds before responding to bus traffic
# Re-establish I3C mode (not preserved across power cycles)
sb_i3c -d $SLOT set_mode i3c
# Provoke the drive into asserting Hot Join. After power-up the drive
# waits for several I3C bus events before HJNing; one CCC is often not
# enough. rstdaa + targetreset is a reliable pair (both are broadcast
# CCCs and safe no-ops on an un-enumerated drive).
sb_i3c -d $SLOT rstdaa
sleep 0.5
sb_i3c -d $SLOT targetreset
sleep 2 # let the firmware's autonomous handler complete ENTDAA
# and commit the HJN entry to the ring
# Read the Hot Join entry committed to the ring
sb_i3c -d $SLOT ring_read
# Expected when a Hot Join completes (8-byte payload is drive-specific):
# [Entry 0] origin=IBI da=0x1D mdb=0xFE len=8
# <PID0> <PID1> <PID2> <PID3> <PID4> <PID5> <BCR> <DCR>
)
The drive lands at the configured starting DA (default 0x1D). Configure with
sb_i3c -d $SLOT set_entdaa_start 0xNN if a different DA is wanted.
Quick Target Mode Verification
After firmware update, verify the local target is working with framed reads:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
sb_i3c -d $SLOT i3c_target_enable
I3C_TARGET=$(sb_i3c -d $SLOT i3c_target_status | grep "addr:" | gawk -F" " '{ print $2 }')
sb_i3c -d $SLOT i3c_target_buf_reset
sb_i3c -d $SLOT i3c_sdr_write $I3C_TARGET 0xAA 0xBB 0xCC 0xDD
# Header-blind read (V1530-compatible)
sb_i3c -d $SLOT i3c_data_read
# Expected: DATA(4): 0xAA 0xBB 0xCC 0xDD
# Header-aware read (V1531)
sb_i3c -d $SLOT i3c_sdr_write $I3C_TARGET 0xAA 0xBB 0xCC 0xDD
sb_i3c -d $SLOT ring_read
# Expected (da reflects the queried target address):
# [Entry 0] origin=DATA da=<I3C_TARGET> mdb=0x00 len=4
# AA BB CC DD
)
4. Linux i2c-tools Interface
The SANBlaze I3C device presents a standard i2c-tiny-usb interface to Linux,
enabling use of standard i2c-tools (i2cdetect, i2cget, i2cset,
i2ctransfer).
Finding Your Device
Use sb_i3c_find to discover the USB path, TTY, and I2C bus number for a slot:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
sb_i3c_find -d $SLOT
# USB Path: 1-5.2.7.4
# TTY: /dev/ttyACM6
# Version: 1531
# I2C Dev: /dev/i2c-15
)
Use sb_i3c_find_all to list all slots with I3C capability:
sb_i3c_find_all
# slot=2 usb=1-5.2.7.4 tty=/dev/ttyACM6 ver=1531 i2c=/dev/i2c-15
Driver Binding
If the i2c-tiny-usb driver is not automatically bound, the safe sequence is to unbind first (no-op if not currently bound), then bind — that way the block always finishes with the driver attached:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
sb_i3c_unbind -d $SLOT # Unbind (idempotent — safe if not bound)
sb_i3c_bind -d $SLOT # Bind
)
Common i2c-tools Commands
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
I2C_DEV=$(sb_i3c -d $SLOT status | grep "I2C Dev" | gawk -F" " '{ print $3 }' | gawk -F"-" '{ print $2 }')
# Scan for devices on the TinyUSB I2C bus
i2cdetect -y $I2C_DEV
# Read a single byte from address 0x53
i2cget -y $I2C_DEV 0x53
# Write to address 0x71
i2cset -y $I2C_DEV 0x71 0x00
# Write-restart-read (required for devices that need an offset before reading)
i2ctransfer -y $I2C_DEV w1@0x6a 0x00 r16
# Write multiple bytes
i2ctransfer -y $I2C_DEV w4@0x24 0x01 0x02 0x03 0x04
)
Note: Devices that require a write-restart-read sequence will hold SDA low if a plain read is attempted without first writing the offset. Clear with:
sb_i3c -d $SLOT i3c_poll
5. IBI Behavior
IBI (In-Band Interrupt) is the I3C mechanism by which a target device signals the controller that it needs attention. Understanding IBI behavior is important for reliable operation.
When IBI Is Asserted
I3C targets commonly assert IBI after:
Initial power-on (typically a Hot Join — see section 6)
First CCC command after a power cycle
Completion of certain operations (device-specific)
When IBI is asserted, the target holds SDA low. This is normal device behavior.
While IBI is pending, i2cdetect will show no devices — this does not indicate
a firmware problem.
Autonomous IBI Handling (V1530+)
The firmware’s main loop runs the autonomous IBI poller whenever the bus is idle
and an IBI-style SDA-low-during-idle event is detected. For a regular IBI from
an enumerated device, the handler reads the IBI bytes and either delivers them
to a pending MCTP transaction or commits them as an unsolicited framed ring
entry. Every handled IBI also writes an ibi_async entry to the error log
with the source DA and MDB for post-mortem analysis.
For a Hot Join request (DA = 0x02), the handler takes a different branch — see section 6 below.
Polling and Clearing IBI
For a manual IBI peek (e.g. while debugging):
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
sb_i3c -d $SLOT i3c_poll
)
Example output when IBI is pending:
IBI: 0xFF 0xFF 0xFF 0xFF ...
Example output when bus is clear:
No IBI Bytes Found
In normal operation, the autonomous handler clears IBIs without intervention.
i3c_poll is useful for diagnostics or when the autonomous path is disabled.
6. Hot Join
Hot Join is the I3C mechanism by which a target device announces itself and requests dynamic-address assignment after the bus is already initialized. A device that wasn’t present at init time, or that was previously enumerated and came back from a power cycle, asserts the reserved Hot Join address (0x02) to request service.
V1531 handles Hot Join autonomously on the iRiser. The host does not need to
run entdaa after a drive comes online — the firmware does it.
What Happens on a Hot Join Event
The drive asserts SDA low during bus idle and arbitrates with the reserved Hot Join address (0x02, with W bit on the wire).
The firmware’s autonomous IBI poller detects the event, reads the arbitration byte, and identifies it as a Hot Join request.
The internal target table is cleared (iRiser hardware is one-drive-per-slot, so any previously-tracked drive is by definition gone).
ENTDAA runs against the configured starting DA (default 0x1D, settable).
On success, the firmware learns the device’s 6-byte PID, BCR, and DCR.
The device is recorded in slot 0 of the target table.
A framed Hot Join announcement entry is committed to the ring buffer with
MDB = 0xFEand an 8-byte payload of[PID0..5, BCR, DCR].
Verifying Hot Join in the CLI
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
sb_i3c -d $SLOT i3c_err_clear
# Power-cycle the drive to trigger Hot Join
sb_i2c2 -d $SLOT -f power -w 0
sleep 1
sb_i2c2 -d $SLOT -f power -w 1
sleep 3 # let the drive boot — NVMe drives may need 3+ seconds before responding to bus traffic
# Re-establish I3C mode (not preserved across power cycles)
sb_i3c -d $SLOT set_mode i3c
# Provoke the drive: drive needs multiple bus events before HJNing.
# rstdaa + targetreset is a reliable trigger pair.
sb_i3c -d $SLOT rstdaa
sleep 0.5
sb_i3c -d $SLOT targetreset
sleep 2 # let the firmware's autonomous handler complete ENTDAA
# and commit the HJN entry to the ring
sb_i3c -d $SLOT ring_read
# Expected (8-byte payload is drive-specific):
# ring_read: 12 bytes total
# [Entry 0] origin=IBI da=0x1D mdb=0xFE len=8
# <PID0> <PID1> <PID2> <PID3> <PID4> <PID5> <BCR> <DCR>
sb_i3c -d $SLOT i3c_err_get
# Expected sequence (one block per Hot Join):
# ctx="hjn_request" addr=0x02
# ctx="hjn_table_clear" addr=0x1D
# ctx="hjn_picked_da" addr=0x1D
# ctx="hjn_entdaa_ok" addr=0x1D ccc=<BCR>
# ctx="hjn_new_device" addr=0x1D ccc=<slot index>
# ctx="hjn_assigned" addr=0x1D ccc=<BCR>
)
The [HJN] debug stream (visible on the CDC console at debug 1 or higher)
shows the same sequence in human-readable form:
[HJN] request detected (da=0x02)
[HJN] picked DA=0x1D, running ENTDAA
[HJN] ENTDAA ok da=0x1D pid=<6 bytes> bcr=0x<BCR> dcr=0x<DCR>
[HJN] recorded slot=0 da=0x1D
[HJN] assigned da=0x1D, ring entry committed
Configuring the Starting DA
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
sb_i3c -d $SLOT set_entdaa_start # query current value
sb_i3c -d $SLOT set_entdaa_start 0x20 # set new value
sb_i3c -d $SLOT status
# Expected:
# ENTDAA Start: 0x20
)
Valid range: 0x08 through 0x7D, excluding 0x3E (the I3C-reserved Hot Join
request address itself) and 0x7E/0x7F (broadcast/reserved). The value
persists until firmware reboot.
Failure Modes
If ENTDAA fails on the bus (drive not actually present, signal-integrity
issue, etc.), the handler logs hjn_entdaa_failed and returns without
committing a ring entry. The drive will not be reachable until either the
power cycle is repeated or the drive re-asserts Hot Join.
If the target table is full (only possible in multi-drive deployments that
disable the every-HJN clear), the handler logs hjn_table_full but still
commits the ring entry so the driver sees the new assignment.
7. MCTP Operations
Overview
MCTP (Management Component Transport Protocol) over I3C enables communication with drive management interfaces. The firmware supports three MCTP read paths with increasing levels of autonomy:
Path |
Command |
Use Case |
|---|---|---|
Manual |
private_write + i3c_poll + mctp_read |
Step-by-step debugging |
Autonomous |
mctp_command |
CLI-based testing and development |
Ring buffer |
mctp_to_ring |
Customer application integration |
Maximum transfer unit (SETMWL / SETMRL): After ENTDAA and ENEC you may set the maximum write/read length. This is not required for MCTP on the drive under test — its power-on default already exceeds the packet size and MCTP works without any SETMWL call. Set it only if a specific maximum is needed (the value must be at least the packet size):
sb_i3c -d $SLOT setmwl $ENTDAA_DA 69 sb_i3c -d $SLOT setmrl $ENTDAA_DA 69
Multi-Packet MCTP over SMBus (V1529)
In I2C/SMBus mode, drives may return responses as multiple sequential SMBus
frames, each addressed to the local target (0x24). V1529 firmware ACKs all
packets and commits each complete frame to the ring buffer atomically. The
mi tool reads packets one at a time, using the MCTP SOM/EOM flags to determine
when the response is complete.
Commands returning large responses (VPD Read, Identify Controller) will produce
multiple committed frames in sequence. With V1531 framing, each frame is a
separate ring entry with its own 4-byte header — the driver can demux them
cleanly. mi continues to use the V1530-compatible byte-stream path and
behaves unchanged.
Manual Path (Debugging)
For detailed visibility into each phase:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
ENTDAA_DA=$(sb_i3c -d $SLOT status | grep "ENTDAA Start" | gawk -F" " '{ print $3 }')
# 1. Send MCTP command packets
sb_i3c -d $SLOT private_write --hold $ENTDAA_DA <pkt1_bytes>
sb_i3c -d $SLOT private_write $ENTDAA_DA <pkt2_bytes> # no --hold = fires burst
# 2. Poll for IBI (drive signals response ready)
sb_i3c -d $SLOT i3c_poll
# 3. Read all response packets
sb_i3c -d $SLOT mctp_read $ENTDAA_DA
# Output: packet-by-packet hex dump with MCTP headers decoded
)
Autonomous Path (mctp_command)
Single command handles burst fire, IBI polling, and multi-packet read:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
ENTDAA_DA=$(sb_i3c -d $SLOT status | grep "ENTDAA Start" | gawk -F" " '{ print $3 }')
sb_i3c -d $SLOT private_write --hold $ENTDAA_DA <pkt1_bytes>
sb_i3c -d $SLOT private_write --hold $ENTDAA_DA <pkt2_bytes>
sb_i3c -d $SLOT mctp_command $ENTDAA_DA
)
Ring Buffer Path (mctp_to_ring)
Routes the complete MCTP response into the target ring buffer for customer application integration:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
ENTDAA_DA=$(sb_i3c -d $SLOT status | grep "ENTDAA Start" | gawk -F" " '{ print $3 }')
sb_i3c -d $SLOT private_write --hold $ENTDAA_DA <pkt1_bytes>
sb_i3c -d $SLOT private_write --hold $ENTDAA_DA <pkt2_bytes>
sb_i3c -d $SLOT mctp_to_ring $ENTDAA_DA
# Read header-blind (V1530-compatible byte stream)
sb_i3c -d $SLOT i3c_data_read
# Or read with framing (V1531 — multiple back-to-back responses demuxed)
sb_i3c -d $SLOT ring_read
)
The V1530 byte-stream contract is preserved on i3c_data_read without
--with-header. New host code that wants to demux multiple back-to-back MCTP
responses (for example MPR followed by Success in a deferred-write transaction)
should use ring_read or i3c_data_read --with-header.
NVMe-MI Firmware Download
V1531 adds fw_download, a direct CLI command that streams an NVMe-MI FW
Image Download to the drive over I3C. It builds each 4 KB FW Download
command, fragments it into MCTP packets, and — with --read_to_ring —
reads back and validates the drive’s NVMe-MI status after every chunk,
stopping immediately if the drive rejects one. A fw_dl.sh helper that
drives the same download through the mi tool chain (including an I2C
fallback) is described at the end of this section.
Prerequisite: an enumerated drive
fw_download needs the drive enumerated and MCTP-capable — i.e. your
standard bring-up (ENTDAA, ENEC) completed. There is no manual Maximum
Write/Read Length step: the drive’s power-on default already permits MCTP,
and the per-packet SMBus PEC that the drive requires is computed and appended
by fw_download automatically. A drive power cycle before the download is
still recommended so the download buffer starts empty (see “Starting offset”
below).
: ${SLOT:?Set your slot number with SLOT=N first}
# Power cycle, then your normal bring-up (ENTDAA / ENEC) — that's all MCTP needs.
Downloading an image
# Validated download — checks each chunk's NVMe status and stops on any
# rejection (recommended for a real download):
sb_i3c -d $SLOT fw_download 0x1D /path/to/fw-image.bin --read_to_ring
# Send-only — pushes the image without reading responses (no validation;
# use only for throughput measurement):
sb_i3c -d $SLOT fw_download 0x1D /path/to/fw-image.bin
Progress is shown as a single bar that fills as the image transfers:
Downloading 4.2M firmware file fw-image.bin, please wait:
[.............................. ] 21384 of 71808 packets
and ends with a one-line summary:
[......................................................................] 71808 of 71808 packets
Done: 4.2M firmware (1088 chunks, 71808 packets) in 1:20 (74 ms/chunk). All chunks accepted. [0 NAK retries]
The summary line ends with the download’s NAK-retry statistics. [0 NAK retries] is the normal case. If the drive NAKs a packet’s address phase,
the firmware retries that packet automatically and the line reports how
many retries were needed and that they recovered. If a packet cannot be
delivered even after retry, the download is marked FAILED, the count
of unrecovered packets is shown, and fw_download exits non-zero — the
staged image is incomplete and must not be committed. Re-run the download.
Download time depends on drive and host state and is not deterministic; treat the elapsed figure as indicative, not a guarantee. Note that the I3C clock rate has little effect on it (see “Choosing an I3C clock” below).
Per-chunk validation (--read_to_ring)
With --read_to_ring, the drive’s NVMe-MI FW Download response is read
back after each 4 KB chunk and its status decoded. Any status other than
Success stops the download at that chunk and prints the offset and decoded
NVMe status — for example, a chunk whose offset overlaps a range already
staged in the drive’s download buffer:
ERROR: chunk 1 (offset 0x0): NVMe status SCT=0x1 SC=0x14 (Overlapping Range) — download stopped
Because a download writes into the drive’s staging buffer, per-chunk validation is strongly recommended — it fails loud at the exact rejected chunk instead of continuing past it. The validation adds only a few seconds across a full image.
fw_download is download-only: it stages the image in the drive’s
download buffer and does not issue an NVMe Firmware Commit / activate.
Activation is a separate, deliberate step.
Starting offset and the download buffer
The drive rejects a chunk whose offset overlaps a range already staged
in its download buffer, returning Overlapping Range. Start from a clean
buffer — a drive power cycle clears it. To resume or write a sub-range,
pass an explicit start (and optional end) offset:
# Start at 0x1000, skipping the first 4 KB chunk
sb_i3c -d $SLOT fw_download 0x1D /path/to/fw-image.bin 0x1000 --read_to_ring
Choosing an I3C clock
Download time is dominated by host/USB transfer overhead rather than the I3C wire, so a faster I3C clock buys very little — a full image takes about 84 seconds at 4 MHz and about 80 seconds at 8 MHz. Choose the clock for reliability, not speed, and prefer a moderate clock with margin: marginal signal integrity can cause a hard hang mid-download rather than a clean, recoverable error.
The highest reliable clock is specific to your setup (cabling, bus loading,
and any inline analyzer or instrumentation all lower it). To find a good
clock, bring the bus up at a candidate rate and confirm the drive
enumerates — the PID: line and Ready banner both appear on success:
: ${SLOT:?Set your slot number with SLOT=N first}
# Try a candidate clock; success prints a PID line and the Ready banner.
sb_i3c_init -d $SLOT -p i3c -c 8000 | egrep "PID:|Ready"
If enumeration succeeds, that clock is safe to use for the download. If it
fails or is unreliable, step the clock down (e.g. 6000, then 4000) and
retry until enumeration is dependable, then run fw_download at that
clock. Do not push to the highest clock that merely appears to work; back
off for margin.
Options
Option |
Effect |
|---|---|
|
Read back and validate each chunk’s NVMe-MI status; stop on any non-Success. Recommended. |
|
Send only the first N chunks — a quick end-to-end check without writing the whole image. |
|
Restrict the download to a 4 KB-aligned byte range. |
|
Verbose per-chunk detail instead of the progress bar. |
Alternative: mi-based download (fw_dl.sh)
fw_dl.sh runs the same download through the mi/SMBus host tool chain
and includes an I2C-mode fallback for drives that do not support MCTP over
I3C. The FW image path inside the script is fixed to the customer’s test
image; edit FWFILE= at the top to point at a different image.
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
../SANBlaze_I3C_Test/fw_dl.sh -d $SLOT -p i3c -c 4000 # 4 MHz I3C
# or
../SANBlaze_I3C_Test/fw_dl.sh -d $SLOT -p i3c -c 1000 # 1 MHz I3C
# or fall back to I2C mode for a drive that doesn't do I3C MCTP
../SANBlaze_I3C_Test/fw_dl.sh -d $SLOT -p i2c -c 100
)
Each invocation runs sb_i3c_init (power-cycle, ENTDAA, ENEC,
SETMWL/SETMRL) then hands the image to the mi tool. It is the established
path and offers the I2C fallback for drives that do not support MCTP over
I3C. If a chunk fails partway through, retry the command; the drive tracks
progress internally and skips already-written offsets.
8. Private Transfers
I3C Private Transfers use a 0x7E broadcast + Repeated START preamble before the addressed transaction. Required for MCTP communication with most NVMe drives.
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
ENTDAA_DA=$(sb_i3c -d $SLOT status | grep "ENTDAA Start" | gawk -F" " '{ print $3 }')
# Single private write
sb_i3c -d $SLOT private_write $ENTDAA_DA 0x01 0x02 0x03 ...
# Buffered (held) private write for burst
sb_i3c -d $SLOT private_write --hold $ENTDAA_DA 0x01 0x02 ...
# Private read
sb_i3c -d $SLOT private_read $ENTDAA_DA 64
)
Testing Private Transfers Without a Drive
The blaster’s local target (0x24) captures incoming private writes into the same ring buffer used by drive-originated traffic, so the private-transfer path can be exercised end-to-end without needing a specific drive in the system. Useful as a smoke test after firmware updates and when debugging the host-side burst protocol.
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
ENTDAA_DA=$(sb_i3c -d $SLOT status | grep "ENTDAA Start" | gawk -F" " '{ print $3 }')
sb_i3c -d $SLOT i3c_target_enable
I3C_TARGET=$(sb_i3c -d $SLOT i3c_target_status | grep "addr:" | gawk -F" " '{ print $2 }')
sb_i3c -d $SLOT i3c_target_buf_reset
# Buffered (--hold) write of 5 bytes to the local target
sb_i3c -d $SLOT private_write $I3C_TARGET --hold $ENTDAA_DA 1 2 3 4
# Expected: PRIVATE_WRITE OK: 5 bytes to <I3C_TARGET>
# Plain write of 4 bytes — fires the buffered bytes followed by these 4
sb_i3c -d $SLOT private_write $I3C_TARGET 5 6 7 8
# Expected: PRIVATE_WRITE OK: 4 bytes to <I3C_TARGET>
# Read back what landed in the local target's ring
sb_i3c -d $SLOT i3c_data_read
# Expected: DATA(9): 0x1D 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08
)
The 9 bytes confirm both the held write (5 bytes) and the non-held write (4 bytes) reached the ring in order. This is the same burst-write mechanism described in section 9; the local target just receives instead of a remote drive.
9. Burst Write System
The burst write system buffers multiple private_write --hold packets in
firmware and transmits them back-to-back as a single burst when mctp_command
or mctp_to_ring is called. This eliminates USB latency between packets and is
required for multi-packet MCTP commands.
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
ENTDAA_DA=$(sb_i3c -d $SLOT status | grep "ENTDAA Start" | gawk -F" " '{ print $3 }')
# Buffer packet 1 (69 bytes) — not sent yet
sb_i3c -d $SLOT private_write --hold $ENTDAA_DA <pkt1_bytes>
# Output: PRIVATE_WRITE HELD: 69 bytes to 0x1D
# Buffer packet 2 (13 bytes) — not sent yet
sb_i3c -d $SLOT private_write --hold $ENTDAA_DA <pkt2_bytes>
# Output: PRIVATE_WRITE HELD: 13 bytes to 0x1D
# Fire all buffered packets in one burst
sb_i3c -d $SLOT mctp_command $ENTDAA_DA
# or
sb_i3c -d $SLOT mctp_to_ring $ENTDAA_DA
)
The burst buffer holds up to 512 bytes across multiple packets. Each packet is sent as a complete I3C Private Write transaction with START and STOP.
Full-Speed Delivery and NAK Retry (V1531)
Bursts transmit at full bus speed with no pre-emptive inter-burst delay. Rapid back-to-back multi-chunk MCTP writes (e.g. VPD Write, NVMe-MI FW Download) can intermittently hit an address NAK on a mid-stream chunk when the drive’s I3C receiver needs recovery time between transactions. Rather than slowing every burst to avoid that, the firmware reacts to it: when a packet’s address phase NAKs, it backs off briefly (100 µs) and retries the same packet, replaying only the address phase — no data is sent until the address is accepted, so a retry can never double-send data. Retries continue until the address is accepted or a short per-packet ceiling (5 ms) is reached.
In practice the retry path is rarely taken — the drive accepts full-speed
writes cleanly across a whole image — so it costs nothing on the normal
path while still recovering the occasional NAK. If a packet exhausts the
retry ceiling, the operation treats it as an unrecoverable delivery
failure (see the fw_download summary line in Section 7). The earlier
fixed 2 ms inter-burst pacing floor is retained in the firmware but
disabled by default; it can be restored if a specific drive is found to
need a fixed recovery window instead of reactive retry.
Firing Without a Drive
mctp_command and mctp_to_ring require a drive to respond. To exercise
the burst mechanism without a drive in the system, fire the held bytes by
issuing a plain private_write (without --hold) — the held bytes are
sent first, followed by the new bytes, in one burst. Direct the whole
sequence at the local target (0x24) and read it back from the ring. See
the Testing Private Transfers Without a Drive example in section 8 for
the full command sequence.
10. Ring Buffer Framing
Frame Format
Every entry committed to the target ring buffer (8192 bytes, no change in size from V1530) is prefixed with a 4-byte header:
byte 0: [DA | flags] bit 7 = origin (1 = IBI, 0 = DATA write)
bits 6:0 = device dynamic address
byte 1: MDB For IBI entries:
- Mandatory Data Byte from the IBI
- 0xFE = Hot Join announcement (special)
For DATA writes: 0x00 (no MDB applies)
byte 2: size_hi high byte of payload length (big-endian)
byte 3: size_lo low byte of payload length
byte 4..N+3: payload exactly 'size' bytes
Multiple entries pack back-to-back with no padding between them. A typical mctp_to_ring drain might look like:
[0x1D 0x00 0x00 0x09] [09 bytes of MPR response payload]
[0x1D 0x00 0x00 0x05] [05 bytes of Success response payload]
Per-Packet MCTP Commits
Multi-packet MCTP responses (e.g. a 4K Identify Controller) produce one ring
entry per MCTP packet, not one entry for the whole response. A 4K Identify
typically yields ~64 entries of ~69 bytes each (4-byte frame header + MCTP
header + 64-byte payload + 1 PEC byte). Driver software reading via
ring_read (or wValue=1 on the wire) sees the per-packet structure
directly and can demux by tag/MDB without reassembly.
The header-blind i3c_data_read path is unaffected — partial reads slide
past entry headers transparently and present a single concatenated byte
stream identical to V1530.
Two Read Modes
The host chooses between two read modes via the wValue field on
TARGET_CMD_DATA_READ:
V1530-compatible (header-blind): wValue = 0, wIndex = 0. Output is a
continuous byte stream of payload only — headers are stripped transparently.
Partial reads (host buffer smaller than the current entry) slide the header
forward in the ring with an updated remaining-length field, so the next read
picks up where this one left off. Existing scripts using i3c_data_read see
this mode by default and need no changes.
Header-aware (framed): wValue = 1 to start a new packet read, then
wValue = 0 with wIndex = K to continue reading the same packet at byte
offset K. Used by ring_read and by driver software that wants to demux
back-to-back entries. See the V1531 Ring Buffer Framing Driver Integration
memo for detailed wire-level documentation.
CLI Surface
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
# V1530-compatible byte stream (no frame headers)
sb_i3c -d $SLOT i3c_data_read # text output
sb_i3c -d $SLOT i3c_data_dump > out.bin # binary output (length-prefixed)
# Framed output with headers visible
sb_i3c -d $SLOT i3c_data_read --with-header
sb_i3c -d $SLOT i3c_data_dump --with-header > out.bin
# Decoded human-readable framed dump
sb_i3c -d $SLOT ring_read
# Example output:
# ring_read: 12 bytes total
# [Entry 0] origin=DATA da=0x24 mdb=0x00 len=8
# 01 02 03 04 05 06 07 08
)
MDB Sentinel Values
MDB |
Meaning |
|---|---|
|
Non-IBI DATA write — sent when a host or other initiator writes to our target address on the bus |
|
Hot Join announcement — payload is |
Other |
Mandatory Data Byte from an IBI raised by an enumerated device |
Stress Testing the Framing Path
A bash script stress_test_ring (provided with the release) writes
random-sized chunks to the local target and drains via ring_read,
verifying entry count, payload total, and byte-for-byte content match.
See section 13 for full usage. Pass -v for chunk-by-chunk visibility:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
stress_test_ring -d $SLOT -v
)
11. Diagnostics
PIO Dump
Comprehensive bus and PIO state machine diagnostic:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
sb_i3c -d $SLOT pio_dump
)
Output includes:
Bus state: SDA/SCL levels and GPIO function assignments
PIO0 SM1 (Initiator): program counter, enabled, FIFO state, stall flags
PIO1 SM0 (Target): state and GPIO configuration
PIO2 SM0 (Target 0x7E): state for Private transfer monitoring
IRQ flags for all PIO blocks
Diagnosis line (e.g., “All OK - bus idle” or specific fault identification)
Disassembled PIO instructions at current program counter
The test suite automatically runs pio_dump on any test failure.
Debug Levels
Set the firmware’s debug verbosity to print more or less info to the USB CDC console:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
sb_i3c -d $SLOT set_debug_level 0 # Quiet (default)
sb_i3c -d $SLOT set_debug_level 1 # Key events (MCTP packet summaries, IBI, HJN, errors)
sb_i3c -d $SLOT set_debug_level 2 # Verbose (ring buffer commits, PIO state, SMBus trim, linger)
)
V1531 adds [HJN] debug-stream messages at level 1+ that trace every Hot
Join event from detection through ring commit. The full IBI / ring trace
including [PIO] START/was_init, [XFER_END], [CCC] complete, [LINGER] SCL low, [IRQ3], [RING] committed, and SMBus trim events is available
at level 2.
Capturing the Debug Log
Debug output goes to the firmware’s USB CDC serial console. The exact
device path is reported in status as TTY: — it varies by which USB
ports were enumerated at boot, so always read it fresh rather than
hardcoding /dev/ttyACM0:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
TTY=$(sb_i3c -d $SLOT status | grep "TTY:" | gawk -F" " '{ print $2 }')
echo $TTY # confirm — e.g. /dev/ttyACM4
)
To capture a log while reproducing an issue, run two shells. In the first shell, start watching the console:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
TTY=$(sb_i3c -d $SLOT status | grep "TTY:" | gawk -F" " '{ print $2 }')
cat $TTY
)
In a second shell, raise the debug level, exercise the bug, then put debug back to quiet when done:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
sb_i3c -d $SLOT set_debug_level 2
sb_i3c_test -d $SLOT -t 41 # or any other reproducer
sb_i3c -d $SLOT set_debug_level 0
)
The first shell will print the debug stream live as the test runs. Ctrl-C to stop, then redirect a fresh capture to a file if you want to save it for a support ticket:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
TTY=$(sb_i3c -d $SLOT status | grep "TTY:" | gawk -F" " '{ print $2 }')
cat $TTY > /tmp/sb_i3c_debug.log
)
cat is the simplest viewer. screen $TTY 115200 works too if you want
scrollback and line editing; the console runs at 115200 baud with no flow
control. The second-shell example above already sets debug back to 0 at
the end so the firmware isn’t spending cycles formatting log lines no one
is reading.
Error Log
V1531 expanded the error-log entries to include both IBI handling and Hot Join decision points:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
sb_i3c -d $SLOT i3c_err_get
# Example:
# [0] ERR=0 txn=0 addr=0x02 ccc=0x00 ts=110297 ctx="hjn_request"
# [1] ERR=0 txn=0 addr=0x1D ccc=0x00 ts=110297 ctx="hjn_table_clear"
# [2] ERR=0 txn=0 addr=0x1D ccc=0x00 ts=110297 ctx="hjn_picked_da"
# [3] ERR=0 txn=0 addr=0x1D ccc=<BCR> ts=110301 ctx="hjn_entdaa_ok"
# [4] ERR=0 txn=0 addr=0x1D ccc=0x00 ts=110301 ctx="hjn_new_device"
# [5] ERR=0 txn=0 addr=0x1D ccc=<BCR> ts=110301 ctx="hjn_assigned"
# [6] ERR=0 txn=1 addr=0x1D ccc=<MDB> ts=234120 ctx="ibi_async"
sb_i3c -d $SLOT i3c_err_clear
)
12. Command Reference
New in V1531
Command |
Description |
|---|---|
|
Read N bytes (default 8192) from ring with framing; decode each entry and pretty-print with origin/DA/MDB/length/payload |
|
Query (no arg) or set the firmware’s ENTDAA starting DA. Default 0x1D |
|
Direct NVMe-MI FW Image Download over I3C. Streams the image as MCTP FW Download commands (SMBus PEC applied automatically). |
Modified in V1531
Command |
Change |
|---|---|
|
New |
|
Same flag added to the binary dump variant |
|
New line: |
|
First argument selects the CCC form: |
|
Same form selection, range check and read-back verification as |
|
Response length decoded MSB-first (I3C byte order). Previously reported a byte-swapped value (e.g. 17664 for a length of 69). |
|
CLI now surfaces the drive-side NAK status as a non-zero exit code (previously always reported success) |
Release Script (Maintainers)
tools/i3c_release.sh gains a -r rebuild mode for the case where source
changes were made directly in the release tree (not in _TIP):
./tools/i3c_release.sh -r 1 531
Rebuilds the code in place, compares each output file against the existing
tarball, prompts file-by-file for which changes to include, backs up the
old tarball with a timestamp, and produces a new tarball with only the
approved changes. CVS bookkeeping (CVS/ dirs, .cvsignore) is filtered
out of both the diff and the resulting tarball.
A -u (update) mode runs the identical flow but never rebuilds the
binaries:
./tools/i3c_release.sh -u 1 531
The existing firmware and CLI artifacts are packaged as-is. Use this for a
docs-only release: the firmware embeds a build timestamp, so rebuilding
produces a byte-different image even when no source has changed, which shows
up as a spurious change in the tarball diff. Both -r and the default mode
now prompt before building, defaulting to no.
Build artifacts (.uf2, .elf, .elf.map, sb_i3c) are published to the
release-tree root rather than living only under the build directories, so
clean no longer disturbs their revision history. The linker map is kept at
the root for debugging but is not shipped in the customer tarball.
New in V1528 (carried forward)
Command |
Description |
|---|---|
|
I3C Private Write; –hold buffers for burst |
|
I3C Private Read (up to 128 bytes) |
|
Read all MCTP response packets from drive |
|
Burst fire + IBI poll + read (autonomous) |
|
Burst fire + IBI poll + read → ring buffer |
|
Force Private mode for all transfers |
|
PIO/bus diagnostic dump |
|
Set Maximum Write Length (required, 8..65535). |
|
Set Maximum Read Length (required, 8..65535). |
Modified in V1528 (carried forward)
Command |
Change |
|---|---|
|
Writes to persistent storage only; applied by set_mode |
|
Writes to persistent storage only; applied by set_mode |
|
Reads and applies stored per-mode clock automatically |
Existing Commands (unchanged)
Command |
Description |
|---|---|
|
Firmware version and device info (V1531 adds ENTDAA Start line) |
|
Reset I3C bus |
|
Dynamic address assignment |
|
Get Provisional ID |
|
Get Bus Characteristics Register |
|
Get Device Characteristics Register |
|
Get device status |
|
Enable events (IBI) |
|
Disable events |
|
Set firmware debug verbosity (was |
|
SDR write |
|
SDR read |
|
Write-restart-read (no STOP between phases) |
|
Poll for IBI events |
|
Get error log |
|
Clear error log |
|
Scan for I2C devices |
|
Scan for I3C devices |
|
Set bus termination |
|
Set output drive strength (2–12 mA) |
|
Enable local target mode |
|
Disable local target mode |
|
Set local target address |
|
Show local target state |
|
Clear local target ring buffer |
13. Test Suite
Run the full test suite:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
sb_i3c_test -d $SLOT
)
Options:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
sb_i3c_test -d $SLOT -t 41 # Run single test
sb_i3c_test -d $SLOT -t 30-52 # Run test range
sb_i3c_test -d $SLOT -v # Verbose output
sb_i3c_test -d $SLOT -N # Skip hardware init (caller ensures drive present)
)
V1531: Update
EXPECTED_VERSIONto1531and include1531inACCEPTED_VERSIONS. All V1530 tests run unchanged.
TEST16 note: The 60-byte i2ctransfer target-receive loopback test is host-system-timing sensitive on some configurations. The test may require a second pass; the pass count is noted in the test status line as
(passed on attempt N/3)when a retry succeeded. A deterministic root-cause fix is deferred to a future release.
MCTP Tests
Test |
Description |
|---|---|
38 |
SETMWL 69 — CCC 0x09 on the wire in a broadcast frame (see command reference) |
39 |
SETMRL 69 — CCC 0x0A on the wire in a broadcast frame (see command reference) |
41 |
MCTP 4K Identify via manual burst write + IBI + mctp_read |
50 |
MCTP discovery (Get Endpoint ID) via mctp_command |
51 |
MCTP 4K Identify via mctp_command (autonomous) |
52 |
MCTP 4K Identify via mctp_to_ring (ring buffer path) |
55 |
Multi-packet MCTP, header-blind read (V1530-compat byte stream) |
56 |
Multi-packet MCTP, framed read (one ring entry per MCTP packet) |
Test 52 verifies the complete customer path: burst write → mctp_to_ring →
i3c_data_read → verify byte count → confirm ring drains to empty. With V1531
framing in place, i3c_data_read produces the V1530-compatible byte stream
unchanged — TEST52 passes without modification.
Tests 55 and 56 verify the per-packet ring-commit behavior introduced in
V1531: a 4K Identify Controller response produces ~64 ring entries (one per
MCTP packet) when read via ring_read, and the same data concatenated into
~4445 bytes when read via the V1530-compatible i3c_data_read path. Both
tests use the drive path’s exact transfer command
(mi -T i3c -d $SLOT -t 2 6 1 -c 129 -k -1).
Ring Framing Stress Test (V1531)
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
stress_test_ring -d $SLOT # standard run
stress_test_ring -d $SLOT -v # verbose: per-chunk writes + raw ring_read
stress_test_ring -d $SLOT -n 4000 # custom byte target (default 6000)
stress_test_ring -d $SLOT -s 42 # reproducible (fixed PRNG seed)
)
Writes payload to the local target (0x24) in random-sized chunks
(1–200 bytes each, default 6 KB total), then drains via ring_read and
verifies:
Entry count matches the number of chunks written
Sum of
len=fields matches cumulative bytes writtenByte-for-byte content matches the deterministic counter pattern
Ring is empty after drain (single-read semantics)
Every entry has
origin=DATA da=0x24 mdb=0x00
Useful as a regression check after firmware updates. In verbose mode the
script prints each chunk in ring_read-style format so writes and reads can
be visually compared.
14. Troubleshooting
Intermittent SMBus Errors on Specific Byte Values
If SMBus commands fail intermittently on byte values such as 0xD3, 0xF5, or 0xC1 (even-popcount bytes) but pass consistently on the FT260 path, suspect missing bus termination. Check the drive-side mux jumper. Unterminated bus causes pattern-sensitive ACK failures that mimic firmware bugs.
Drive Not Responding After MCTP Read
After a 4K MCTP transaction, the drive’s MCTP state machine is exhausted. Use double targetreset to recover:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
ENTDAA_DA=$(sb_i3c -d $SLOT status | grep "ENTDAA Start" | gawk -F" " '{ print $3 }')
sb_i3c -d $SLOT targetreset
sleep 0.5
sb_i3c -d $SLOT targetreset
sleep 0.5
sb_i3c -d $SLOT entdaa $ENTDAA_DA
sb_i3c -d $SLOT enec $ENTDAA_DA
sb_i3c -d $SLOT setmwl $ENTDAA_DA 69
sb_i3c -d $SLOT setmrl $ENTDAA_DA 69
)
Hot Join Not Detected
If a drive comes online but no Hot Join announcement appears in ring_read:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
sb_i3c -d $SLOT i3c_err_get
# Look for: "hjn_request" — absent means the autonomous IBI handler
# didn't see the request
sb_i3c -d $SLOT pio_dump
# Look for: stuck PIO state machine, signal-integrity issues
)
If hjn_request appears but the announcement still isn’t in the ring, check
for hjn_entdaa_failed — drive didn’t accept the ENTDAA. Try set_entdaa_start
with a different DA in case 0x1D is contested.
Drive Lands at Wrong DA After Hot Join
set_entdaa_start should be configured before the drive’s Hot Join event. If
the value was changed after the drive was already enumerated, the firmware
will use the new starting DA only on the next Hot Join (e.g. after a drive
power cycle). Verify with sb_i3c -d $SLOT status and confirm the ENTDAA Start: line shows the expected value.
Ring Read Returns Different Entry Counts on Repeated Calls
Each ring_read drains the ring — subsequent reads see only what was
committed since the last drain. This is by design (single-read semantics).
If you expect entries to be present and the ring is empty, check that the
producer (mctp_to_ring, sdr_write, etc.) was issued between reads.
ring_read Output Shows “truncated”
The 4-byte header’s length field claims more payload than is actually in the
ring. This usually indicates a corrupted commit (firmware bug). Capture the
PIO state with pio_dump, the err log with i3c_err_get, and a debug-level-2
ttyACM capture, then file a support ticket.
I3C Tests Skipping Unexpectedly with -N Flag
If Python test wrappers show “Drive not available in I3C mode” when the drive
is present, the i3c_drive_unavailable flag file from a previous failed run
may be stale. The V1529 setup_i3c() fix clears this file automatically. If
running an older wrapper, clear manually:
rm -f /path/to/luns/1/i3c_drive_unavailable
rm -f /path/to/luns/1/i3c_drive_ready
i2cdetect Shows No Devices
Check for a pending IBI first — this is the most common cause:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
sb_i3c -d $SLOT i3c_poll
# Poll repeatedly until: No IBI Bytes Found
)
If IBI is clear, check bus state:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
sb_i3c -d $SLOT status
# Look for: Bus State: SDA=1, SCL=1 (both high = idle)
)
If the bus is stuck, power cycle the target device. Remember that mode is not preserved across power cycles — re-establish whichever mode your workflow needs before issuing more bus traffic:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
sb_i2c2 -d $SLOT -f power -w 0
sleep 1
sb_i2c2 -d $SLOT -f power -w 1
sleep 3 # let the drive boot — NVMe drives may need 3+ seconds before responding to bus traffic
# Re-establish your operating mode — use i3c OR i2c, not both
sb_i3c -d $SLOT set_mode i3c
# sb_i3c -d $SLOT set_mode i2c
)
Dual-Controller Riser: Wrong /dev/i2c-N After Boot
Risers with two SANBlaze controllers on a shared hub (port 5 = active
controller, port 6 = auxiliary) occasionally fail to enumerate port-5 on
boot. In V1531, when this happens sb_i3c_find emits a clear error and
refuses to write a wrong bus number into /tmp/NVMe/iRiser/<slot>/i3c_dev.
To recover:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
# Re-run discovery — port-5 typically comes up on the next bounce
sb_i3c_find_all
cat /tmp/NVMe/iRiser/$SLOT/i3c_dev # confirm correct bus number
# If port-5 still fails after several attempts, power-cycle the iRiser slot
)
If sb_i3c_find reports that the auxiliary port enumerated but port-5
(the active controller) did NOT, that’s the port-5-enumeration-fail
symptom — cache files are wiped automatically so the next healthy
invocation re-discovers.
Bus Stuck After Operation
If SDA or SCL is held low after an operation:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
sb_i3c -d $SLOT i3c_poll # Clear any pending IBI
sb_i3c -d $SLOT targetreset # Reset bus state
sb_i3c -d $SLOT pio_dump # Inspect PIO state if still stuck
)
burst_addr_nak in the Error Log
The error log context burst_addr_nak indicates the firmware attempted a
burst write and read back NAK on the address byte. In V1531 the firmware
retries a NAKed packet automatically (Section 9), so isolated
burst_addr_nak entries are expected and self-recovering. If you see them
persistently, or a fw_download reports unrecovered packets:
Confirm you’re running V1531. Occasional post-flash confusion is the most common cause — a stale firmware binary in place while you believe V1531 is loaded. Verify with
sb_i3c -d $SLOT statusand check the firmware version line.Try a slower I3C clock.
sb_i3c_init -d $SLOT -p i3c -c 1000(1 MHz) gives more inter-bit time on the wire and often lets marginal drives keep up. If 1 MHz works and 4 MHz doesn’t for a specific drive, that’s useful data — capture it for SANBlaze support.Cross-check with I2C mode. Try the same command via
sb_i3c -d $SLOT set_mode i2cand re-run. If the command works in I2C and fails in I3C, the drive-side I3C receiver is the likely suspect; if both fail, the issue is upstream (host tool, framing, drive state).
Multi-Chunk MCTP Write Fails on Mid-Stream Chunks
Symptom (seen historically at 4 MHz I3C on rapid MCTP-over-SMBus
multi-chunk writes like VPD Write or FW download): first two chunks send
cleanly, subsequent chunks fail. V1531’s automatic per-packet NAK retry
(Section 9) recovers the great majority of these transparently. If a
fw_download still reports unrecovered packets against a specific drive:
Re-run the download — the drive’s staging buffer is cleared by a power cycle, and a fresh attempt usually succeeds.
Fall back to a slower I3C clock (e.g.
-c 4000or-c 1000onsb_i3c_init) to give the drive more recovery margin per transaction.Capture an analyzer trace of the failure and contact SANBlaze support; the per-packet retry ceiling and the (default-disabled) fixed pacing floor are both tunable in the firmware but require a rebuild.
Local Target Tests Fail
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
# Ensure target is enabled
sb_i3c -d $SLOT i3c_target_enable
# Verify target address
sb_i3c -d $SLOT i3c_target_set_addr 0x24
# Flush stale data from buffer
sb_i3c -d $SLOT i3c_target_buf_reset
)
MCTP_COMMAND Returns “No response data”
The CLI polls too fast before firmware finishes the bus transaction. V1528+ CLI includes automatic retry (100ms intervals, up to 30 seconds). If still failing, verify the drive was discovered and IBI enabled.
Short MCTP Response (< 4000 bytes)
Drive sent an error packet (SOM=EOM=1 in a single packet). Check byte 3 bit 6 for EOM flag. Common cause: drive MCTP state not reset from previous transaction.
Watchdog Reboot at Idle
V1528 resolved idle watchdog crashes present in intermediate builds. If this recurs, verify firmware version matches the release binary (not an intermediate build). Expected version: 1531.
PIO Dump Shows Stall
If pio_dump shows tx_stall=1 with tx_empty=0, the PIO state machine is
blocked waiting for the bus. Run targetreset to clear.
Error Log
Read and clear the firmware error log:
(
: ${SLOT:?Set your slot number with SLOT=N first}
echo "# Executing example on user SLOT=$SLOT"
sb_i3c -d $SLOT i3c_err_get # Read error log
sb_i3c -d $SLOT i3c_err_clear # Clear after review
)
15. Support
When reporting an issue, please include:
Firmware version:
sb_i3c -d $SLOT statusTest script output if applicable
Error log:
sb_i3c -d $SLOT i3c_err_getPIO dump if bus behavior is unexpected:
sb_i3c -d $SLOT pio_dumpRing contents if framing-related:
sb_i3c -d $SLOT ring_readDebug level 2 log from the firmware’s USB CDC console if SMBus frame assembly is suspected — see Capturing the Debug Log in section 11 for the full procedure
The exact command sequence that reproduces the issue
Whether the issue reproduces on the FT260 path (slot 2 vs slot 1)
SANBlaze Technology Inc. — Confidential Copyright 2024-2026 SANBlaze