-
Notifications
You must be signed in to change notification settings - Fork 0
Wire Protocol
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.
[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 2 — spi_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) |
[content_length:u16 LE] [content_id:u8] [payload...]
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.
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 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.