UPDATEOne firmware session on one chip

UPDATE carries five ops against one target chip. Every frame leads with the op and the chip. Opcode 0x17.

UPDATE 0x17 · payload 2..508 bytes

Reply

PAYLOAD
OffsetFieldTypeNotes
0opu80=BEGIN 1=DATA 2=END 3=ABORT 4=ACTIVATE
1targetu80=device chip, 1=host chip; ignored by ACTIVATE
2..bodyvariesper op, below
ORDER
  BEGIN --> DATA --> DATA --> ... --> END --> (staged, inert)
    |                                          |
    +-- ABORT ---------------------------------+--> ACTIVATE --> reboot
EFFECT

A target can be staged and activated later. Both chips may be staged before one ACTIVATE commits them together. QUERY(FIRMWARE) reads what each chip runs. Library binding: update_firmware.

BEGINErase the spare slot and open a session

UPDATE op 0 · [target u8][size u32][sha256 u8[32]]
PAYLOAD
OffsetFieldTypeNotes
2sizeu32total image bytes, little-endian; >983040 is refused with TOOBIG
6sha256u8[32]digest of the whole image, checked at END
EFFECT

Puts the whole box in update mode: injection and clip playback stop, the host chip stops polling the real device, and the clone disconnects from the game PC.

Then the box erases the whole target slot and replies READY with the credit in arg. Library binding: stage_firmware.

The erase runs up front. A 64 KB block erase disables the cache for 50-100 ms, longer than any receive buffer on either link can absorb mid-stream.

EXAMPLE

Open a 364784-byte device-chip image (size is 0x000590F0):

A5SOF
17TYPE
00SEQ
26 00LEN
00op
00target
F0 90 05 00size
32 bytessha256
lo hiCRC16

DATAOne chunk, in order

UPDATE op 1 · [target u8][seq u16][bytes 1..504]
PAYLOAD
OffsetFieldTypeNotes
2sequ16chunk index, little-endian; the byte offset is seq * 504
4..bytesu8[]1-504 image bytes

504 is what the frame has left: 512 less the op, the target and a two-byte index, rounded down to a multiple of four so every flash write is aligned.

FLOW CONTROL

READY's arg is how many chunks the box takes before it acknowledges.

Read it. The device chip asks for 16 and the host chip for 6, because a relayed chunk waits in the inter-chip link's receive ring while the host chip writes the previous chunk to flash.

The window is a correctness requirement.

  PC   |-- credit chunks --|                       |-- credit chunks --|
  box                       |-- write, ACK next --|                     |-- ACK next --|
                            ^
                            cache is off here; nothing may be in flight
QuantityValue
Flash page write0.3-0.7 ms, both cores stalled.
UART0 RX FIFO128 bytes, 213 µs at 6 Mbaud.
Credit window16 chunks to the device chip (8064 bytes), 6 to the host chip.
Inter-chip link ring8192 bytes. Caps the relayed window.

A sender ignoring its credit overruns the smaller hop. The lost chunk leaves the window one short, so nothing acknowledges the window. The session ends on the idle timer with the sender still waiting. Library binding: stage_firmware.

RESENDS

A chunk one behind the next expected seq (a lost acknowledgement) is acknowledged without being rewritten. Anything older is a SEQGAP and drops the session.

EXAMPLE

Chunk 3 of a device-chip image, a full 504 bytes of payload:

A5SOF
17TYPE
2ASEQ
FC 01LEN
01op
00target
03 00seq
...bytes
lo hiCRC16

ENDVerify the digest and stop

UPDATE op 2 · [target u8]
EFFECT

Checks the digest and image header, then replies STAGED with the byte count, or BADSHA if the image isn't what BEGIN promised. Library binding: stage_firmware.

A staged image isn't bootable. END doesn't set the boot partition, since that would take effect on any next boot, a power cut included. ACTIVATE commits.

TIMEOUT

Ten seconds with no traffic ends update mode, so a vanished client can't leave the clone down. A staged image survives the timeout: it is inert, and its transfer already completed.

EXAMPLE

Close the device-chip session:

A5SOF
17TYPE
3CSEQ
02 00LEN
02op
00target
lo hiCRC16

ABORTDiscard the transfer

UPDATE op 3 · [target u8]
EFFECT

Drops whatever is staged or in flight for that target and replies OK. Leaving update mode doesn't reboot: the host chip re-sends its descriptor snapshot and the clone is rebuilt as on a re-attach. Library binding: abort_update.

DURING ACTIVATE

Once ACTIVATE is accepted, the box replies BUSY to new commands. The one exception is ABORT while the box waits on the host chip: the box abandons the wait and replies TIMEOUT to the ACTIVATE.

Past that wait, the boot partition is written, so nothing is left to abort. Cancelling the reboot would leave the box running one image with the loader pointed at the other.

Abandoning an activate disarms both chips, whichever target the frame names.

One ACTIVATE commits everything staged, so abandoning it abandons everything staged. A leftover image would let a later ACTIVATE commit it alone and put the chips on different versions. Outside an activate, ABORT is per-target.

EXAMPLE

Discard whatever the host chip is holding:

A5SOF
17TYPE
41SEQ
02 00LEN
03op
01target
lo hiCRC16

ACTIVATECommit every staged image and boot it

UPDATE op 4 · [target u8] (ignored)
EFFECT

Points each staged chip at its new slot and reboots into it, host chip first. The device chip is the only transport, so it goes last. Library binding: activate_firmware.

  ACTIVATE
     |
     +--> host commits, reboots
     |         |
     |         +--> back on the link at the new slot
     |                    |
     +--------------------+--> device commits, replies OK, reboots

Only staged targets are committed. A failure before ACTIVATE leaves both chips on their running slots, so it costs only a retry.

The reply is OK once both chips are committed, NOSTAGE if nothing was staged, or TIMEOUT if the host chip never came back on the new slot. The reply always names target 0, so match it on the op alone.

A refused commit leaves the image staged. The host chip goes first, so if it refuses, the device chip never commits.

Both chips are still on their running slots, but the device image stays staged and armed: the next ACTIVATE would commit it alone and put the chips on different versions.

After a failed ACTIVATE, either retry the whole update, or first send ABORT for each staged target (as the dashboard and both reference clients do).

EXAMPLE

Commit everything staged:

A5SOF
17TYPE
55SEQ
02 00LEN
04op
00target
lo hiCRC16

UPDATE_RESPReply to one op

One reply per UPDATE frame, and one per acknowledged window of DATA. Opcode 0x18.

UPDATE_RESP 0x18 · payload 7 bytes

Reply

PAYLOAD
OffsetFieldTypeNotes
0opu8echoes the op
1targetu8echoes the target; always 0 for ACTIVATE
2statusu8below
3argu32per status, little-endian

SEQ echoes the command frame, except for DATA acknowledgements, which carry a rolling SEQ because one covers a whole window. Correlate on the op, not SEQ.

STATUS
NameValueargMeaning
OK0x000Accepted.
READY0x01creditSlot erased. This many chunks before an acknowledgement.
ACK0x02next seqThe window landed. Send from this index.
STAGED0x03bytes writtenVerified and inert until ACTIVATE.
BUSY0x100A session is already open on this target.
NOSLOT0x110No second app slot (single-app layout).
TOOBIG0x12slot sizeThe declared size does not fit, or a chunk overran it.
SEQGAP0x13expected seqA chunk out of order. The session is dropped.
WRITEFAIL0x14error codeThe flash write failed.
BADSHA0x150The image does not match the digest.
BADIMAGE0x16error codeThe bytes are not a bootable image.
LINKDOWN0x170The host chip was addressed and the inter-chip link is down.
TIMEOUT0x180The host chip did not come back on the new image.
NOSTAGE0x190ACTIVATE with nothing staged.
BADSTATE0x1Aexpected opAn op out of order, or a malformed body.
PROBATION0x1B0The running image has not confirmed itself yet. Wait and retry.
UNTOUCHED0x1C0Refused before the slot was touched, so anything staged stays staged and bootable.

Every refusal reaches the library as Error::Update.

EXAMPLE

READY for a device-chip BEGIN, credit 16:

A5SOF
18TYPE
00SEQ
07 00LEN
00op
00target
01status
10 00 00 00arg
lo hiCRC16

RollbackReverts an image that won't run

A freshly booted image is on probation until it confirms itself. The bootloader reverts an image that never does. QUERY(FIRMWARE) reports each chip's booted slot and image state.

  boot new slot --> pending-verify --> chip confirms itself --> valid
                          |
                          +-- panics, or never confirms --> bootloader picks the old slot
ChipConfirmed byGrace
Device10 s of a running main loop.30 s
HostA completed clock exchange over the link, only.40 s

The host chip's image arrives over the link, its only way back. A host image that runs but can't use the link must therefore revert by itself.

Measured on hardware: an image that panics in its entry point is back on the old slot in 0.8 s. An image built to ignore the link reverted and restored the link unaided.

Rollback covers an image that won't run, not one that runs wrongly. While a chip is on probation the box refuses another update with PROBATION.

Stable update pathFixed from protocol 5 on

A client that can update one box can update every later one. From protocol 5 (v3.2.0) on, no protocol changes these:

PartWhere
Frame: SOF, header, CRC, the 512-byte payload limitFrame layout
Control rate: 6 Mbaud, and 4 Mbaud on firmware before v3.4.0Serial link
QUERY(VERSION), its reply's first ten data bytes (proto_ver, version, MAC) and the name after them, and the helloVERSION, Ready hello
QUERY(FIRMWARE) and the 17 bytes of its reply; anything new follows themFIRMWARE
UPDATE and UPDATE_RESP: ops, targets, bodies, the 504-byte chunk, the credit and how it is read, every status and its arg, update mode, staging and ACTIVATEThis page

A status may be added. A client reads an unknown status as a refusal. The dashboard connects to any box on protocol 5 or later, and can update but not control a box on another protocol.