Files
Adeeb Shihadeh 0ddd3f254f simple spi (#2461)
(cherry picked from commit 835e4c31d9514f9d06c341479237c35349e75136)
2026-10-14 13:05:09 -05:00

199 lines
6.8 KiB
C

#pragma once
#include "board/drivers/drivers.h"
// H7 DMA2 located in D2 domain, so we need to use SRAM1/SRAM2
#ifdef STM32H7
__attribute__((section(".sram12"))) uint8_t spi_buf_rx[SPI_BUF_SIZE];
__attribute__((section(".sram12"))) uint8_t spi_buf_tx[SPI_BUF_SIZE];
__attribute__((section(".sram12"))) static uint8_t spi_buf_ready[1];
#else
uint8_t spi_buf_rx[SPI_BUF_SIZE];
uint8_t spi_buf_tx[SPI_BUF_SIZE];
static uint8_t spi_buf_ready[1];
#endif
// SPI protocol, see python/spi.py for the host side.
//
// The host sends each request in one transfer, until the first byte it gets back is SPI_SLAVE_READY:
// MOSI: endpoint, id (u32), tx_len (u16), rx_len (u16), data[tx_len], crc32 (u32)
// then reads rx_len bytes at a time, until the first byte is a response:
// MISO: SPI_ACK or SPI_NACK, id (u32), len (u16), data[len], crc32 (u32)
// The id is random for each new request, and stays the same when the host sends it again.
// The response always fits in rx_len, so the next transfer reads all of it.
// If a read gets SPI_SLAVE_READY instead, the host sends the request again.
// Anything that isn't a valid request is dropped, and we're ready for the next one.
// NACK means a valid request that we refused, e.g. CAN TX buffers are full.
//
// We queue what's next on every CS rising edge, whatever state the bus is in. If a transfer
// doesn't cleanly see what we queued, the host ends up sending its request again, and a
// repeat of the request we last handled (same id) gets the same response again instead
// of running twice. So it never matters how long we take to get to it.
#define SPI_SLAVE_READY 0x3CU
#define SPI_ACK 0xC3U
#define SPI_NACK 0x5AU
#define SPI_IDLE 0x00U // sent on underrun
typedef struct {
uint8_t endpoint;
uint32_t id;
uint16_t tx_len;
uint16_t rx_len;
} __attribute__((packed)) spi_req_header_t;
typedef struct {
uint8_t status;
uint32_t id;
uint16_t len;
} __attribute__((packed)) spi_resp_header_t;
#define SPI_CRC_SIZE 4U
#define SPI_RESP_OVERHEAD (sizeof(spi_resp_header_t) + SPI_CRC_SIZE)
#define SPI_ENDPOINT_INFO 0xF0U
uint16_t spi_error_count = 0;
// low level SPI prototypes
void llspi_init(void);
void llspi_queue(const uint8_t *tx, uint16_t tx_len);
uint16_t llspi_rx_count(void);
static bool spi_can_tx_ready = false;
// Handle one endpoint. Returns whether to ACK, and writes the response data length
static bool spi_handle_endpoint(uint8_t endpoint, const uint8_t *data, uint16_t data_len, uint16_t max_resp_len,
uint8_t *resp, uint16_t *resp_len) {
bool ack = false;
*resp_len = 0U;
if (endpoint == 0U) {
if (data_len >= sizeof(ControlPacket_t)) {
ControlPacket_t ctrl = {0};
(void)memcpy((uint8_t*)&ctrl, data, sizeof(ControlPacket_t));
*resp_len = (uint16_t)comms_control_handler(&ctrl, resp);
ack = true;
}
} else if ((endpoint == 1U) || (endpoint == 0x81U)) {
*resp_len = (uint16_t)comms_can_read(resp, max_resp_len);
ack = true;
} else if (endpoint == 2U) {
comms_endpoint2_write(data, data_len);
ack = true;
} else if (endpoint == 3U) {
// NACK while the CAN TX buffers are full, the host retries
if (spi_can_tx_ready) {
spi_can_tx_ready = false;
comms_can_write(data, data_len);
ack = true;
}
} else if (endpoint == SPI_ENDPOINT_INFO) {
// serial, HW type, bootstub
(void)memcpy(resp, ((uint8_t *)UID_BASE), 12);
resp[12] = hw_type;
resp[13] = USB_PID & 0xFFU;
*resp_len = 14U;
ack = true;
} else if (endpoint == 0xABU) {
// test endpoint: mimics panda -> device transfer
*resp_len = max_resp_len;
ack = true;
} else {
// 0xAC is a test endpoint that always NACKs
}
return ack;
}
// Handle a request and build the response in spi_buf_tx. Returns the response size the host
// will read, or 0 if it's not a valid request
static uint16_t spi_process_request(uint16_t n) {
static bool handled = false;
static uint32_t handled_id = 0U;
static uint32_t handled_crc = 0U;
// n can include filler from the host's reads if we were slow, it's ignored
spi_req_header_t req;
(void)memcpy((uint8_t *)&req, spi_buf_rx, sizeof(req));
uint32_t req_len = sizeof(req) + (uint32_t)req.tx_len + SPI_CRC_SIZE;
uint32_t crc = 0U;
if (req_len <= n) {
(void)memcpy((uint8_t *)&crc, &spi_buf_rx[req_len - SPI_CRC_SIZE], SPI_CRC_SIZE);
}
bool valid = (req_len <= n) && (crc32(spi_buf_rx, req_len - SPI_CRC_SIZE) == crc) &&
(req.rx_len >= SPI_RESP_OVERHEAD) && (req.rx_len <= SPI_BUF_SIZE);
if (valid && !(handled && (req.id == handled_id) && (crc == handled_crc))) {
// a new request. a repeat of the last one (same id) gets the same response again
uint16_t max_data_len = req.rx_len - SPI_RESP_OVERHEAD;
uint16_t data_len = 0U;
bool ack = spi_handle_endpoint(req.endpoint, &spi_buf_rx[sizeof(req)], req.tx_len, max_data_len,
&spi_buf_tx[sizeof(spi_resp_header_t)], &data_len);
// like USB, a response is cut off at what the host reads
data_len = ack ? MIN(data_len, max_data_len) : 0U;
spi_resp_header_t resp = {
.status = ack ? SPI_ACK : SPI_NACK,
.id = req.id,
.len = data_len,
};
(void)memcpy(spi_buf_tx, (uint8_t *)&resp, sizeof(resp));
uint32_t len = sizeof(resp) + resp.len;
uint32_t resp_crc = crc32(spi_buf_tx, len);
(void)memcpy(&spi_buf_tx[len], (uint8_t *)&resp_crc, SPI_CRC_SIZE);
handled = true;
handled_id = req.id;
handled_crc = crc;
} else if (!valid && (req_len <= n)) {
// looked like a request, but the CRC is wrong. the host's filler doesn't count, its length doesn't fit
spi_error_count += 1U;
} else {
// a repeat, host's filler, or a partial request
}
return valid ? req.rx_len : 0U;
}
// Called at the end of every transfer: CS is active low, so it rises when the host is done
void spi_cs_rising(void) {
// what we queued for the transfer that just ended
static bool response_queued = false;
static uint16_t resp_len = 0U;
// *** handle the transfer ***
bool send_response = response_queued;
uint16_t n = llspi_rx_count();
if (n > 0U) {
if (response_queued) {
// the host read the response, so we're ready for the next request
send_response = false;
} else {
// the host sent a request, we respond if it's valid
resp_len = spi_process_request(n);
send_response = resp_len > 0U;
}
}
// *** setup next transfer with master ***
if (send_response) {
// send response to the master
llspi_queue(spi_buf_tx, resp_len);
response_queued = true;
} else {
// read from the master
llspi_queue(spi_buf_ready, 1U);
response_queued = false;
}
}
void spi_init(void) {
crc32_init();
llspi_init();
// kick off the first read
spi_buf_ready[0] = SPI_SLAVE_READY;
llspi_queue(spi_buf_ready, 1U);
}
void can_tx_comms_resume_spi(void) {
spi_can_tx_ready = true;
}