Skip to content

Wire Protocol

Syax89 edited this page Jul 8, 2026 · 1 revision

Wire Protocol

HID-over-SPI "V0" framing

This device speaks the pre-release HidSpiDeviceV0 protocol, not the published v1.0 spec. See Architecture for why a custom state machine was needed instead of the generic upstream driver.

Response header (device → host)

[TYPE:4 | VERSION:4] [u16 LE report_length] [0x5A sync byte]
    version = 2

The header is found by scanning read-back bytes for the 0x5A sync marker and checking that the byte three positions before it has version nibble 2spi_hid_seq_hdr_type() in driver/spi-hid-core.c implements this scan.

Type Name Meaning
1 DATA Runtime input report
3 RESET_RSP Device reset notification
5 GET_FEAT_RESP Response to a GET_FEATURE request
7 DEVICE_DESC 28-byte device descriptor (VID/PID/register map)
8 RPT_DESC HID report descriptor (936 bytes on this device)

Body (after the header)

[content_length:u16 LE] [content_id:u8] [payload...]

Request writes (host → device)

Writes use opcode 0x02 (write) at a target register (e.g. 0x0001 for DESCREQ, 0x0002 for the second descriptor request, 0x0003 for feature requests), followed by a 16-bit length and the command body. Reads use opcode 0x0B ("read approval") with a 0xFF constant trailer.

Opcode-doubling quirk: spi_hid_seq_write() needs the opcode byte (0x02) duplicated at the very front of the buffer it's given — the actual bytes that reach the SPI bus are unaffected (still exactly what Windows sends), but without the duplicate leading byte, something in amd_spi_exec_segment() (see below) consumes/misaligns the real first byte. This was found empirically and is required for every write through this function, including SET_FEATURE (confirmed live — removing it makes things measurably worse, not better). Why the underlying controller behaves this way is still not fully understood.

AMD FCH SPI controller (V2 path)

driver/spi-amd.c drives the AMD FCH (Cezanne) SPI controller directly via MMIO (0xFEC10000 base). Key registers:

Offset Name Purpose
0x00 CTRL0 Main control
0x1D ALT_CS Alternate chip select
0x20 ENA_REG Enable bits (SPI100 speed tier selection)
0x22 SPI100_SPEED_CONFIG Speed tiers (NORM/FAST/ALT/TPM nibbles)
0x45 OPCODE_REG Opcode for the V2 transaction path
0x47 CMD_TRIGGER Bit 7 starts the transaction
0x48 TX_COUNT Bytes to transmit
0x4B RX_COUNT Bytes to receive
0x80 FIFO 70-byte hardware FIFO, one byte per address

A full transaction is: fill the FIFO at 0x80 (TX bytes), set TX_COUNT/RX_COUNT, set OPCODE_REG, set CMD_TRIGGER bit 7, busy-wait for completion, then read RX bytes back out of the FIFO. This exact sequence was cross-checked against AMD's own AmdSpiHcProtocolDxe.efi (UEFI) and coreboot's FCH SPI driver — the register set and order match precisely.

The 70-byte FIFO and 64-byte "pages"

The hardware FIFO is only 70 bytes. Any transfer larger than that (like the 936-byte report descriptor) must be split into multiple PIO segments. Windows's amdspi.sys instead uses a separate DMA-capable path for bulk reads — this driver emulates the same effect with chunked PIO reads.

Empirically, the safest chunk size is 64 bytes (not the FIFO's own 70-byte ceiling) — this matches a "64-byte page" quirk in the device itself: a single byte at position n·64+58 of every fetched page comes back as a spurious 0xFF, independent of SPI clock speed and of the host's own chunk boundaries. See Report Descriptor for how this is worked around.