mirror of https://github.com/commaai/panda.git
261 lines
7.6 KiB
C
261 lines
7.6 KiB
C
#pragma once
|
|
|
|
#include "crc.h"
|
|
|
|
#define SPI_TIMEOUT_US 10000U
|
|
|
|
// got max rate from hitting a non-existent endpoint
|
|
// in a tight loop, plus some buffer
|
|
#define SPI_IRQ_RATE 16000U
|
|
|
|
#ifdef STM32H7
|
|
#define SPI_BUF_SIZE 2048U
|
|
// H7 DMA2 located in D2 domain, so we need to use SRAM1/SRAM2
|
|
__attribute__((section(".sram12"))) uint8_t spi_buf_rx[SPI_BUF_SIZE];
|
|
__attribute__((section(".sram12"))) uint8_t spi_buf_tx[SPI_BUF_SIZE];
|
|
#else
|
|
#define SPI_BUF_SIZE 1024U
|
|
uint8_t spi_buf_rx[SPI_BUF_SIZE];
|
|
uint8_t spi_buf_tx[SPI_BUF_SIZE];
|
|
#endif
|
|
|
|
#define SPI_CHECKSUM_START 0xABU
|
|
#define SPI_SYNC_BYTE 0x5AU
|
|
#define SPI_HACK 0x79U
|
|
#define SPI_DACK 0x85U
|
|
#define SPI_NACK 0x1FU
|
|
|
|
// SPI states
|
|
enum {
|
|
SPI_STATE_HEADER,
|
|
SPI_STATE_HEADER_ACK,
|
|
SPI_STATE_HEADER_NACK,
|
|
SPI_STATE_DATA_RX,
|
|
SPI_STATE_DATA_RX_ACK,
|
|
SPI_STATE_DATA_TX
|
|
};
|
|
|
|
bool spi_tx_dma_done = false;
|
|
uint8_t spi_state = SPI_STATE_HEADER;
|
|
uint8_t spi_endpoint;
|
|
uint16_t spi_data_len_mosi;
|
|
uint16_t spi_data_len_miso;
|
|
uint16_t spi_checksum_error_count = 0;
|
|
bool spi_can_tx_ready = false;
|
|
|
|
const unsigned char version_text[] = "VERSION";
|
|
|
|
#define SPI_HEADER_SIZE 7U
|
|
|
|
// low level SPI prototypes
|
|
void llspi_init(void);
|
|
void llspi_mosi_dma(uint8_t *addr, int len);
|
|
void llspi_miso_dma(uint8_t *addr, int len);
|
|
|
|
void can_tx_comms_resume_spi(void) {
|
|
spi_can_tx_ready = true;
|
|
}
|
|
|
|
uint16_t spi_version_packet(uint8_t *out) {
|
|
// this protocol version request is a stable portion of
|
|
// the panda's SPI protocol. its contents match that of the
|
|
// panda USB descriptors and are sufficent to list/enumerate
|
|
// a panda, determine panda type, and bootstub status.
|
|
|
|
// the response is:
|
|
// VERSION + 2 byte data length + data + CRC8
|
|
|
|
// echo "VERSION"
|
|
(void)memcpy(out, version_text, 7);
|
|
|
|
// write response
|
|
uint16_t data_len = 0;
|
|
uint16_t data_pos = 7U + 2U;
|
|
|
|
// write serial
|
|
(void)memcpy(&out[data_pos], ((uint8_t *)UID_BASE), 12);
|
|
data_len += 12U;
|
|
|
|
// HW type
|
|
out[data_pos + data_len] = hw_type;
|
|
data_len += 1U;
|
|
|
|
// bootstub
|
|
out[data_pos + data_len] = USB_PID & 0xFFU;
|
|
data_len += 1U;
|
|
|
|
// SPI protocol version
|
|
out[data_pos + data_len] = 0x2;
|
|
data_len += 1U;
|
|
|
|
// data length
|
|
out[7] = data_len & 0xFFU;
|
|
out[8] = (data_len >> 8) & 0xFFU;
|
|
|
|
// CRC8
|
|
uint16_t resp_len = data_pos + data_len;
|
|
out[resp_len] = crc_checksum(out, resp_len, 0xD5U);
|
|
resp_len += 1U;
|
|
|
|
return resp_len;
|
|
}
|
|
|
|
void spi_init(void) {
|
|
// platform init
|
|
llspi_init();
|
|
|
|
// Start the first packet!
|
|
spi_state = SPI_STATE_HEADER;
|
|
llspi_mosi_dma(spi_buf_rx, SPI_HEADER_SIZE);
|
|
}
|
|
|
|
bool validate_checksum(const uint8_t *data, uint16_t len) {
|
|
// TODO: can speed this up by casting the bulk to uint32_t and xor-ing the bytes afterwards
|
|
uint8_t checksum = SPI_CHECKSUM_START;
|
|
for(uint16_t i = 0U; i < len; i++){
|
|
checksum ^= data[i];
|
|
}
|
|
return checksum == 0U;
|
|
}
|
|
|
|
void spi_rx_done(void) {
|
|
uint16_t response_len = 0U;
|
|
uint8_t next_rx_state = SPI_STATE_HEADER_NACK;
|
|
bool checksum_valid = false;
|
|
|
|
// parse header
|
|
spi_endpoint = spi_buf_rx[1];
|
|
spi_data_len_mosi = (spi_buf_rx[3] << 8) | spi_buf_rx[2];
|
|
spi_data_len_miso = (spi_buf_rx[5] << 8) | spi_buf_rx[4];
|
|
|
|
if (memcmp(spi_buf_rx, version_text, 7) == 0) {
|
|
response_len = spi_version_packet(spi_buf_tx);
|
|
next_rx_state = SPI_STATE_HEADER_NACK;;
|
|
} else if (spi_state == SPI_STATE_HEADER) {
|
|
checksum_valid = validate_checksum(spi_buf_rx, SPI_HEADER_SIZE);
|
|
if ((spi_buf_rx[0] == SPI_SYNC_BYTE) && checksum_valid) {
|
|
// response: ACK and start receiving data portion
|
|
spi_buf_tx[0] = SPI_HACK;
|
|
next_rx_state = SPI_STATE_HEADER_ACK;
|
|
response_len = 1U;
|
|
} else {
|
|
// response: NACK and reset state machine
|
|
print("- incorrect header sync or checksum "); hexdump(spi_buf_rx, SPI_HEADER_SIZE);
|
|
spi_buf_tx[0] = SPI_NACK;
|
|
next_rx_state = SPI_STATE_HEADER_NACK;
|
|
response_len = 1U;
|
|
}
|
|
} else if (spi_state == SPI_STATE_DATA_RX) {
|
|
// We got everything! Based on the endpoint specified, call the appropriate handler
|
|
bool response_ack = false;
|
|
checksum_valid = validate_checksum(&(spi_buf_rx[SPI_HEADER_SIZE]), spi_data_len_mosi + 1U);
|
|
if (checksum_valid) {
|
|
if (spi_endpoint == 0U) {
|
|
if (spi_data_len_mosi >= sizeof(ControlPacket_t)) {
|
|
ControlPacket_t ctrl = {0};
|
|
(void)memcpy(&ctrl, &spi_buf_rx[SPI_HEADER_SIZE], sizeof(ControlPacket_t));
|
|
response_len = comms_control_handler(&ctrl, &spi_buf_tx[3]);
|
|
response_ack = true;
|
|
} else {
|
|
print("SPI: insufficient data for control handler\n");
|
|
}
|
|
} else if ((spi_endpoint == 1U) || (spi_endpoint == 0x81U)) {
|
|
if (spi_data_len_mosi == 0U) {
|
|
response_len = comms_can_read(&(spi_buf_tx[3]), spi_data_len_miso);
|
|
response_ack = true;
|
|
} else {
|
|
print("SPI: did not expect data for can_read\n");
|
|
}
|
|
} else if (spi_endpoint == 2U) {
|
|
comms_endpoint2_write(&spi_buf_rx[SPI_HEADER_SIZE], spi_data_len_mosi);
|
|
response_ack = true;
|
|
} else if (spi_endpoint == 3U) {
|
|
if (spi_data_len_mosi > 0U) {
|
|
if (spi_can_tx_ready) {
|
|
spi_can_tx_ready = false;
|
|
comms_can_write(&spi_buf_rx[SPI_HEADER_SIZE], spi_data_len_mosi);
|
|
response_ack = true;
|
|
} else {
|
|
response_ack = false;
|
|
print("SPI: CAN NACK\n");
|
|
}
|
|
} else {
|
|
print("SPI: did expect data for can_write\n");
|
|
}
|
|
} else if (spi_endpoint == 0xABU) {
|
|
// test endpoint, send max response length
|
|
response_len = spi_data_len_miso;
|
|
response_ack = true;
|
|
} else {
|
|
print("SPI: unexpected endpoint"); puth(spi_endpoint); print("\n");
|
|
}
|
|
} else {
|
|
// Checksum was incorrect
|
|
response_ack = false;
|
|
print("- incorrect data checksum ");
|
|
puth4(spi_data_len_mosi);
|
|
print("\n");
|
|
hexdump(spi_buf_rx, SPI_HEADER_SIZE);
|
|
hexdump(&(spi_buf_rx[SPI_HEADER_SIZE]), MIN(spi_data_len_mosi, 64));
|
|
print("\n");
|
|
}
|
|
|
|
if (!response_ack) {
|
|
spi_buf_tx[0] = SPI_NACK;
|
|
next_rx_state = SPI_STATE_HEADER_NACK;
|
|
response_len = 1U;
|
|
} else {
|
|
// Setup response header
|
|
spi_buf_tx[0] = SPI_DACK;
|
|
spi_buf_tx[1] = response_len & 0xFFU;
|
|
spi_buf_tx[2] = (response_len >> 8) & 0xFFU;
|
|
|
|
// Add checksum
|
|
uint8_t checksum = SPI_CHECKSUM_START;
|
|
for(uint16_t i = 0U; i < (response_len + 3U); i++) {
|
|
checksum ^= spi_buf_tx[i];
|
|
}
|
|
spi_buf_tx[response_len + 3U] = checksum;
|
|
response_len += 4U;
|
|
|
|
next_rx_state = SPI_STATE_DATA_TX;
|
|
}
|
|
} else {
|
|
print("SPI: RX unexpected state: "); puth(spi_state); print("\n");
|
|
}
|
|
|
|
// send out response
|
|
if (response_len == 0U) {
|
|
print("SPI: no response\n");
|
|
spi_buf_tx[0] = SPI_NACK;
|
|
spi_state = SPI_STATE_HEADER_NACK;
|
|
response_len = 1U;
|
|
}
|
|
llspi_miso_dma(spi_buf_tx, response_len);
|
|
|
|
spi_state = next_rx_state;
|
|
if (!checksum_valid && (spi_checksum_error_count < UINT16_MAX)) {
|
|
spi_checksum_error_count += 1U;
|
|
}
|
|
}
|
|
|
|
void spi_tx_done(bool reset) {
|
|
if ((spi_state == SPI_STATE_HEADER_NACK) || reset) {
|
|
// Reset state
|
|
spi_state = SPI_STATE_HEADER;
|
|
llspi_mosi_dma(spi_buf_rx, SPI_HEADER_SIZE);
|
|
} else if (spi_state == SPI_STATE_HEADER_ACK) {
|
|
// ACK was sent, queue up the RX buf for the data + checksum
|
|
spi_state = SPI_STATE_DATA_RX;
|
|
llspi_mosi_dma(&spi_buf_rx[SPI_HEADER_SIZE], spi_data_len_mosi + 1U);
|
|
} else if (spi_state == SPI_STATE_DATA_TX) {
|
|
// Reset state
|
|
spi_state = SPI_STATE_HEADER;
|
|
llspi_mosi_dma(spi_buf_rx, SPI_HEADER_SIZE);
|
|
} else {
|
|
spi_state = SPI_STATE_HEADER;
|
|
llspi_mosi_dma(spi_buf_rx, SPI_HEADER_SIZE);
|
|
print("SPI: TX unexpected state: "); puth(spi_state); print("\n");
|
|
}
|
|
}
|