From 14fd5ff5a3443cb3f81f3472518bf9d91fa1e834 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 3 Jul 2023 15:23:12 -0700 Subject: [PATCH] SPI: send bootstub status in version request (#1492) * mv first * switch to crc8 * bootstub * test * cleanup * little more * misra --------- Co-authored-by: Comma Device --- board/crc.h | 2 ++ board/drivers/spi.h | 84 ++++++++++++++++++++++++++------------------- python/spi.py | 25 +++++++------- python/utils.py | 12 +++++++ tests/hitl/8_spi.py | 18 ++++++---- 5 files changed, 87 insertions(+), 54 deletions(-) create mode 100644 python/utils.py diff --git a/board/crc.h b/board/crc.h index 0d62dd31..6ceaba07 100644 --- a/board/crc.h +++ b/board/crc.h @@ -1,3 +1,5 @@ +#pragma once + uint8_t crc_checksum(uint8_t *dat, int len, const uint8_t poly) { uint8_t crc = 0xFFU; int i; diff --git a/board/drivers/spi.h b/board/drivers/spi.h index 90dd6109..34b4ab25 100644 --- a/board/drivers/spi.h +++ b/board/drivers/spi.h @@ -1,5 +1,7 @@ #pragma once +#include "crc.h" + #define SPI_BUF_SIZE 1024U #define SPI_TIMEOUT_US 10000U @@ -50,6 +52,52 @@ 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", 7); + + // write response + uint16_t data_len = 0; + uint16_t data_pos = 7U + 2U; + + // write serial + #ifdef UID_BASE + (void)memcpy(&out[data_pos], ((uint8_t *)UID_BASE), 12); + data_len += 12U; + #endif + + // 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] = 0x1; + 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(); @@ -79,41 +127,7 @@ void spi_rx_done(void) { spi_data_len_miso = (spi_buf_rx[5] << 8) | spi_buf_rx[4]; if (memcmp(spi_buf_rx, "VERSION", 7) == 0) { - // protocol version request, respond with: - // VERSION + 2 byte data length + data + data complement - - // echo "VERSION" - (void)memcpy(spi_buf_tx, "VERSION", 7); - - // write response - uint16_t data_len = 0; - uint16_t data_pos = 9; - - // write serial - #ifdef UID_BASE - (void)memcpy(&spi_buf_tx[data_pos], ((uint8_t *)UID_BASE), 12); - #endif - data_len += 12U; - - // HW type - spi_buf_tx[data_pos + data_len] = hw_type; - data_len += 1U; - - // SPI protocol version - spi_buf_tx[data_pos + data_len] = 0x1; - data_len += 1U; - - // response complement - for (uint16_t i = 0U; i < data_len; i++) { - spi_buf_tx[data_pos + data_len + i] = spi_buf_tx[data_pos + i] ^ 0xFFU; - } - - // data length - data_len *= 2U; - spi_buf_tx[7] = data_len & 0xFFU; - spi_buf_tx[8] = (data_len >> 8) & 0xFFU; - - response_len = 7U + 2U + data_len; + response_len = spi_version_packet(spi_buf_tx); next_rx_state = SPI_STATE_HEADER_NACK;; } else if (spi_state == SPI_STATE_HEADER) { checksum_valid = check_checksum(spi_buf_rx, SPI_HEADER_SIZE); diff --git a/python/spi.py b/python/spi.py index ba1a3507..26b18843 100644 --- a/python/spi.py +++ b/python/spi.py @@ -12,6 +12,7 @@ from typing import List, Optional from .base import BaseHandle, BaseSTBootloaderHandle, TIMEOUT from .constants import McuType, MCU_TYPE_BY_IDCODE +from .utils import crc8_pedal try: import spidev @@ -175,25 +176,23 @@ class PandaSpiHandle(BaseHandle): logging.debug("- waiting for echo") start = time.monotonic() while True: - r = spi.readbytes(len(vers_str)) - if bytes(r) == vers_str: + version_bytes = spi.readbytes(len(vers_str) + 2) + if bytes(version_bytes).startswith(vers_str): break if (time.monotonic() - start) > 0.5: - raise PandaSpiException("timed out waiting for version echo") + raise PandaSpiMissingAck - # get response - logging.debug("- receiving response") - b = bytes(spi.readbytes(2)) - rlen = struct.unpack(" 1000: raise PandaSpiException("response length greater than max") - dat = bytes(spi.readbytes(rlen)) - resp = dat[:rlen//2] - resp_comp = dat[rlen//2:] - if resp != bytes([x ^ 0xff for x in resp_comp]): - raise PandaSpiException("data complement doesn't match") - return resp + # get response + dat = spi.readbytes(rlen + 1) + resp = dat[:-1] + calculated_crc = crc8_pedal(bytes(version_bytes + resp)) + if calculated_crc != dat[-1]: + raise PandaSpiBadChecksum + return bytes(resp) exc = PandaSpiException() with self.dev.acquire() as spi: diff --git a/python/utils.py b/python/utils.py new file mode 100644 index 00000000..f91da641 --- /dev/null +++ b/python/utils.py @@ -0,0 +1,12 @@ +def crc8_pedal(data): + crc = 0xFF # standard init value + poly = 0xD5 # standard crc8: x8+x7+x6+x4+x2+1 + size = len(data) + for i in range(size - 1, -1, -1): + crc ^= data[i] + for _ in range(8): + if ((crc & 0x80) != 0): + crc = ((crc << 1) ^ poly) & 0xFF + else: + crc <<= 1 + return crc diff --git a/tests/hitl/8_spi.py b/tests/hitl/8_spi.py index eb942c57..04d674f2 100644 --- a/tests/hitl/8_spi.py +++ b/tests/hitl/8_spi.py @@ -18,16 +18,22 @@ class TestSpi: assert spy.call_count == 2 mocker.stop(spy) + @pytest.mark.expected_logs(2) def test_protocol_version(self, p): - v = p._handle.get_protocol_version() + for bootstub in (False, True): + p.reset(enter_bootstub=bootstub) + v = p._handle.get_protocol_version() - uid = binascii.hexlify(v[:12]).decode() - assert uid == p.get_uid() + uid = binascii.hexlify(v[:12]).decode() + assert uid == p.get_uid() - hwtype = v[12] - assert hwtype == ord(p.get_type()) + hwtype = v[12] + assert hwtype == ord(p.get_type()) - assert v[-1] == 1 + bstub = v[13] + assert bstub == (0xEE if bootstub else 0xCC) + + assert v[14:] == b"\x01" def test_all_comm_types(self, mocker, p): spy = mocker.spy(p._handle, '_wait_for_ack')