mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-28 00:53:45 +08:00
0ddd3f254f
(cherry picked from commit 835e4c31d9514f9d06c341479237c35349e75136)
199 lines
6.8 KiB
C
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;
|
|
}
|