From 0352e20632e1b0502a262a04290ddddfcd477f73 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 4 Oct 2025 15:29:31 -0700 Subject: [PATCH 01/17] just rx tx --- board/drivers/spi.h | 200 ++++++++++------------- board/drivers/spi_declarations.h | 19 ++- python/spi.py | 267 +++++++++++++++++++------------ test.sh | 2 +- 4 files changed, 269 insertions(+), 219 deletions(-) diff --git a/board/drivers/spi.h b/board/drivers/spi.h index d291fe06a8d..1d0b48ae568 100644 --- a/board/drivers/spi.h +++ b/board/drivers/spi.h @@ -8,7 +8,7 @@ uint8_t spi_buf_tx[SPI_BUF_SIZE]; uint16_t spi_error_count = 0; -static uint8_t spi_state = SPI_STATE_HEADER; +static uint8_t spi_state = SPI_STATE_RX_FRAME; static uint16_t spi_data_len_mosi; static bool spi_can_tx_ready = false; static const unsigned char version_text[] = "VERSION"; @@ -62,8 +62,8 @@ void spi_init(void) { llspi_init(); // Start the first packet! - spi_state = SPI_STATE_HEADER; - llspi_mosi_dma(spi_buf_rx, SPI_HEADER_SIZE); + spi_state = SPI_STATE_RX_FRAME; + llspi_mosi_dma(spi_buf_rx, SPI_BUF_SIZE); } static bool validate_checksum(const uint8_t *data, uint16_t len) { @@ -77,152 +77,128 @@ static bool validate_checksum(const uint8_t *data, uint16_t len) { void spi_rx_done(void) { uint16_t response_len = 0U; - uint8_t next_rx_state = SPI_STATE_HEADER_NACK; bool checksum_valid = false; static uint8_t spi_endpoint; static uint16_t spi_data_len_miso; - // 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]; - + // VERSION request remains special and can be issued by sending a frame + // that begins with 'VERSION' (rest padded). Respond with the same stable + // format at the start of the TX frame. 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 - #ifdef DEBUG_SPI - print("- incorrect header sync or checksum "); hexdump(spi_buf_rx, SPI_HEADER_SIZE); - #endif - spi_buf_tx[0] = SPI_NACK; - next_rx_state = SPI_STATE_HEADER_NACK; - response_len = 1U; + } else { + // Parse combined header + data in a single fixed-size RX frame + 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]; + + // Clamp lengths to available buffer space to avoid OOB + uint16_t max_mosi = (uint16_t)(SPI_BUF_SIZE - SPI_HEADER_SIZE - 1U); + if (spi_data_len_mosi > max_mosi) { + spi_data_len_mosi = max_mosi; + } + uint16_t max_miso = (uint16_t)(SPI_BUF_SIZE - 4U); // DACK + LEN(2) + CRC8 + if (spi_data_len_miso > max_miso) { + spi_data_len_miso = max_miso; } - } else if (spi_state == SPI_STATE_DATA_RX) { - // We got everything! Based on the endpoint specified, call the appropriate handler + + // Validate header + bool header_ok = false; + if (spi_buf_rx[0] == SPI_SYNC_BYTE) { + header_ok = validate_checksum(spi_buf_rx, SPI_HEADER_SIZE); + } + 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((uint8_t*)&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); + if (header_ok) { + // Validate data+checksum. Data checksum byte sits immediately after data + checksum_valid = validate_checksum(&(spi_buf_rx[SPI_HEADER_SIZE]), spi_data_len_mosi + 1U); + + if (checksum_valid) { + // Dispatch + if (spi_endpoint == 0U) { + if (spi_data_len_mosi >= sizeof(ControlPacket_t)) { + ControlPacket_t ctrl = {0}; + (void)memcpy((uint8_t*)&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 { - response_ack = false; - print("SPI: CAN NACK\n"); + 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: expected data for can_write\n"); + } + } else if (spi_endpoint == 0xABU) { + // test endpoint: mimics panda -> device transfer + response_len = spi_data_len_miso; + response_ack = true; + } else if (spi_endpoint == 0xACU) { + // test endpoint: mimics device -> panda transfer (with NACK) + response_ack = false; } else { - print("SPI: did expect data for can_write\n"); + print("SPI: unexpected endpoint"); puth(spi_endpoint); print("\n"); } - } else if (spi_endpoint == 0xABU) { - // test endpoint: mimics panda -> device transfer - response_len = spi_data_len_miso; - response_ack = true; - } else if (spi_endpoint == 0xACU) { - // test endpoint: mimics device -> panda transfer (with NACK) - response_ack = false; - } else { - print("SPI: unexpected endpoint"); puth(spi_endpoint); print("\n"); } - } else { - // Checksum was incorrect - response_ack = false; - #ifdef DEBUG_SPI - 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"); - #endif + } + + if (!header_ok || !checksum_valid) { + spi_error_count += 1U; } if (!response_ack) { spi_buf_tx[0] = SPI_NACK; - next_rx_state = SPI_STATE_HEADER_NACK; response_len = 1U; } else { + // Enforce response length limits + if (response_len > spi_data_len_miso) { + response_len = spi_data_len_miso; + } + // 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 + // Add checksum over header + payload 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 now refers to header + payload + 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_error_count += 1U; - } + // Transmit the full fixed-size TX frame + llspi_miso_dma(spi_buf_tx, SPI_BUF_SIZE); + spi_state = SPI_STATE_TX_FRAME; } 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"); - } + (void)reset; + // After sending a full TX frame, always prepare to receive the next full RX frame + spi_state = SPI_STATE_RX_FRAME; + llspi_mosi_dma(spi_buf_rx, SPI_BUF_SIZE); } void can_tx_comms_resume_spi(void) { diff --git a/board/drivers/spi_declarations.h b/board/drivers/spi_declarations.h index 23254f0e87a..d4a52026541 100644 --- a/board/drivers/spi_declarations.h +++ b/board/drivers/spi_declarations.h @@ -13,24 +13,29 @@ __attribute__((section(".sram12"))) extern uint8_t spi_buf_rx[SPI_BUF_SIZE]; __attribute__((section(".sram12"))) extern uint8_t spi_buf_tx[SPI_BUF_SIZE]; +// Protocol constants #define SPI_CHECKSUM_START 0xABU #define SPI_SYNC_BYTE 0x5AU #define SPI_HACK 0x79U #define SPI_DACK 0x85U #define SPI_NACK 0x1FU -// SPI states +// SPI states (simplified): alternate full-size RX then full-size TX 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 + SPI_STATE_RX_FRAME = 0, + SPI_STATE_TX_FRAME = 1 }; extern uint16_t spi_error_count; +// Fixed frame contains a 7-byte header followed by data and a 1-byte data checksum. +// header: [SYNC(1) | EP(1) | MOSI_LEN(2 LE) | MISO_MAX(2 LE) | HDR_CKSUM(1)] +// data: MOSI_LEN bytes starting at offset SPI_HEADER_SIZE +// data checksum byte immediately following data. +// The device responds in the next frame with: +// [DACK(1) | RESP_LEN(2 LE) | RESP_DATA | RESP_CKSUM(1)] +// VERSION requests remain special: if the RX frame begins with 'VERSION', the TX frame +// will contain 'VERSION' + 2 byte length + data + CRC8 at the start. #define SPI_HEADER_SIZE 7U // low level SPI prototypes diff --git a/python/spi.py b/python/spi.py index 8ee5aecb9e2..2480ac28afb 100644 --- a/python/spi.py +++ b/python/spi.py @@ -131,103 +131,159 @@ def _calc_checksum(self, data: bytes) -> int: cksum ^= b return cksum - def _wait_for_ack(self, spi, ack_val: int, timeout: int, tx: int, length: int = 1) -> bytes: - timeout_s = max(MIN_ACK_TIMEOUT_MS, timeout) * 1e-3 + # New fixed-frame protocol helpers + def _build_tx_frame(self, endpoint: int, data: bytes, max_rx_len: int) -> bytes: + # header without checksum + header = self.HEADER.pack(SYNC, endpoint, len(data), max_rx_len) + frame = bytearray(SPI_BUF_SIZE) + # header + checksum + frame[0:len(header)] = header + frame[len(header)] = self._calc_checksum(header) + # payload + checksum + payload_off = len(header) + 1 + if len(data) > 0: + frame[payload_off:payload_off+len(data)] = data + frame[payload_off + len(data)] = self._calc_checksum(data) + return bytes(frame) + + def _parse_rx_frame(self, frame: bytes, max_rx_len: int) -> bytes: + if len(frame) < 4: + raise PandaSpiMissingAck + status = frame[0] + if status == NACK: + raise PandaSpiNackResponse + if status != DACK: + raise PandaSpiMissingAck + response_len = struct.unpack(' max_rx_len: + raise PandaSpiException(f"response length greater than max ({max_rx_len} {response_len})") + used = 3 + response_len + 1 + if used > len(frame): + raise PandaSpiMissingAck + if self._calc_checksum(frame[:used]) != 0: + raise PandaSpiBadChecksum + return bytes(frame[3:3+response_len]) + + def _read_response_polled_spidev(self, spi, timeout_ms: int, max_rx_len: int) -> bytes: + # Poll first byte until we see DACK or NACK; until TX DMA arms, the slave + # returns its under-run value (e.g., 0xCD). Once DACK/NACK is observed, that + # is the first byte of the TX frame, so we must clock the remaining bytes of + # the frame to let the device finish TX DMA and re-arm RX. + deadline = time.monotonic() + (timeout_ms * 1e-3 if timeout_ms > 0 else 3600.0) + + status = 0 + while True: + status = spi.xfer2([0x00])[0] + if status in (DACK, NACK): + break + if timeout_ms != 0 and time.monotonic() > deadline: + raise PandaSpiMissingAck - start = time.monotonic() - while (timeout == 0) or ((time.monotonic() - start) < timeout_s): - dat = spi.xfer2([tx, ] * length) - if dat[0] == ack_val: - return bytes(dat) - elif dat[0] == NACK: - raise PandaSpiNackResponse + if status == NACK: + # Drain rest of frame so device can re-arm RX, then raise + spi.xfer2([0x00] * (SPI_BUF_SIZE - 1)) + raise PandaSpiNackResponse - raise PandaSpiMissingAck + # status == DACK: read len (2), payload, checksum, then drain padding + len_bytes = spi.xfer2([0x00, 0x00]) + response_len = struct.unpack(' max_rx_len: + # Drain the frame before erroring + to_drain = response_len + 1 + if to_drain > 0: + spi.xfer2([0x00] * to_drain) + pad = SPI_BUF_SIZE - (1 + 2 + to_drain) + if pad > 0: + spi.xfer2([0x00] * pad) + raise PandaSpiException(f"response length greater than max ({max_rx_len} {response_len})") + + data_ck = spi.xfer2([0x00] * (response_len + 1)) + frame_head = bytes([DACK]) + bytes(len_bytes) + bytes(data_ck) + if self._calc_checksum(frame_head) != 0: + # Drain padding then raise + pad = SPI_BUF_SIZE - len(frame_head) + if pad > 0: + spi.xfer2([0x00] * pad) + raise PandaSpiBadChecksum + + # Drain remaining padding (if any) to complete the frame + pad = SPI_BUF_SIZE - len(frame_head) + if pad > 0: + spi.xfer2([0x00] * pad) + + return bytes(data_ck[:-1]) def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes: max_rx_len = max(USBPACKET_MAX_SIZE, max_rx_len) - - logger.debug("- send header") - packet = self.HEADER.pack(SYNC, endpoint, len(data), max_rx_len) - packet += bytes([self._calc_checksum(packet), ]) - spi.xfer2(packet) - - logger.debug("- waiting for header ACK") - self._wait_for_ack(spi, HACK, MIN_ACK_TIMEOUT_MS, 0x11) - - logger.debug("- sending data") - packet = bytes([*data, self._calc_checksum(data)]) - spi.xfer2(packet) + # Build and send a single fixed-size TX frame + tx_frame = self._build_tx_frame(endpoint, bytes(data), max_rx_len) + spi.xfer2(list(tx_frame)) if expect_disconnect: logger.debug("- expecting disconnect, returning") return b"" - else: - logger.debug("- waiting for data ACK") - preread_len = USBPACKET_MAX_SIZE + 1 # read enough for a controlRead - dat = self._wait_for_ack(spi, DACK, timeout, 0x13, length=3 + preread_len) - - # get response length, then response - response_len = struct.unpack(" max_rx_len: - raise PandaSpiException(f"response length greater than max ({max_rx_len} {response_len})") - - # read rest - remaining = (response_len + 1) - preread_len - if remaining > 0: - dat += bytes(spi.readbytes(remaining)) - - dat = dat[:3 + response_len + 1] - if self._calc_checksum(dat) != 0: - raise PandaSpiBadChecksum - return dat[3:-1] + # Poll for response status and then drain the full frame + return self._read_response_polled_spidev(spi, timeout, max_rx_len) def _transfer_spidev2(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = USBPACKET_MAX_SIZE, expect_disconnect: bool = False) -> bytes: + # Fallback to the same fixed-frame logic using spidev2 when available max_rx_len = max(USBPACKET_MAX_SIZE, max_rx_len) - header = self.HEADER.pack(SYNC, endpoint, len(data), max_rx_len) - - header_ack = bytearray(1) - - # ACK + <2 bytes for response length> + data + checksum - data_rx = bytearray(3+max_rx_len+1) + tx_frame = self._build_tx_frame(endpoint, bytes(data), max_rx_len) + # Send TX frame self._spi2.submitTransferList(spidev2.SPITransferList(( - # header - {'tx_buf': header + bytes([self._calc_checksum(header), ]), 'delay_usecs': 0, 'cs_change': True}, - {'rx_buf': header_ack, 'delay_usecs': 0, 'cs_change': True}, - - # send data - {'tx_buf': bytes([*data, self._calc_checksum(data)]), 'delay_usecs': 0, 'cs_change': True}, - {'rx_buf': data_rx, 'delay_usecs': 0, 'cs_change': True}, + {'tx_buf': tx_frame, 'delay_usecs': 0, 'cs_change': True}, ))) - if header_ack[0] != HACK: - raise PandaSpiMissingAck - if expect_disconnect: - logger.debug("- expecting disconnect, returning") return b"" - else: - dat = bytes(data_rx) - if dat[0] != DACK: - if dat[0] == NACK: - raise PandaSpiNackResponse - print("trying again") - dat = self._wait_for_ack(spi, DACK, timeout, 0x13, length=3 + max_rx_len) - - # get response length, then response - response_len = struct.unpack(" max_rx_len: - raise PandaSpiException(f"response length greater than max ({max_rx_len} {response_len})") + # Poll one byte at a time until DACK/NACK + deadline = time.monotonic() + (timeout * 1e-3 if timeout > 0 else 3600.0) + status_b = bytearray(1) + status = 0 + while True: + self._spi2.submitTransferList(spidev2.SPITransferList(({'rx_buf': status_b, 'delay_usecs': 0, 'cs_change': True},))) + status = status_b[0] + if status in (DACK, NACK): + break + if timeout != 0 and time.monotonic() > deadline: + raise PandaSpiMissingAck - dat = dat[:3 + response_len + 1] - if self._calc_checksum(dat) != 0: - raise PandaSpiBadChecksum + if status == NACK: + # Drain rest of frame + pad = bytearray(SPI_BUF_SIZE - 1) + self._spi2.submitTransferList(spidev2.SPITransferList(({'rx_buf': pad, 'delay_usecs': 0, 'cs_change': True},))) + raise PandaSpiNackResponse - return dat[3:-1] + # Read len + len_b = bytearray(2) + self._spi2.submitTransferList(spidev2.SPITransferList(({'rx_buf': len_b, 'delay_usecs': 0, 'cs_change': True},))) + response_len = struct.unpack(' max_rx_len: + # Drain remainder + drain = bytearray(response_len + 1) + self._spi2.submitTransferList(spidev2.SPITransferList(({'rx_buf': drain, 'delay_usecs': 0, 'cs_change': True},))) + pad = bytearray(SPI_BUF_SIZE - (1 + 2 + len(drain))) + if len(pad) > 0: + self._spi2.submitTransferList(spidev2.SPITransferList(({'rx_buf': pad, 'delay_usecs': 0, 'cs_change': True},))) + raise PandaSpiException(f"response length greater than max ({max_rx_len} {response_len})") + + data_ck = bytearray(response_len + 1) + self._spi2.submitTransferList(spidev2.SPITransferList(({'rx_buf': data_ck, 'delay_usecs': 0, 'cs_change': True},))) + frame_head = bytes([DACK]) + bytes(len_b) + bytes(data_ck) + if self._calc_checksum(frame_head) != 0: + pad = bytearray(SPI_BUF_SIZE - len(frame_head)) + if len(pad) > 0: + self._spi2.submitTransferList(spidev2.SPITransferList(({'rx_buf': pad, 'delay_usecs': 0, 'cs_change': True},))) + raise PandaSpiBadChecksum + + pad = bytearray(SPI_BUF_SIZE - len(frame_head)) + if len(pad) > 0: + self._spi2.submitTransferList(spidev2.SPITransferList(({'rx_buf': pad, 'delay_usecs': 0, 'cs_change': True},))) + return bytes(data_ck[:-1]) def _transfer(self, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes: logger.debug("starting transfer: endpoint=%d, max_rx_len=%d", endpoint, max_rx_len) @@ -248,45 +304,58 @@ def _transfer(self, endpoint: int, data, timeout: int, max_rx_len: int = 1000, e exc = e logger.debug("SPI transfer failed, retrying", exc_info=True) - # ensure slave is in a consistent state and ready for the next transfer - # (e.g. slave TX buffer isn't stuck full) - nack_cnt = 0 - attempts = 5 - while (nack_cnt <= 3) and (attempts > 0): - attempts -= 1 - try: - self._wait_for_ack(spi, NACK, MIN_ACK_TIMEOUT_MS, 0x11, length=XFER_SIZE//2) - nack_cnt += 1 - except PandaSpiException: - nack_cnt = 0 + # No resynchronization dance needed with fixed frames; just retry raise exc def get_protocol_version(self) -> bytes: vers_str = b"VERSION" + def _get_version(spi) -> bytes: - spi.writebytes(vers_str) + # Send a fixed-size frame whose prefix is 'VERSION', padded otherwise + tx = bytearray(SPI_BUF_SIZE) + tx[:len(vers_str)] = vers_str + spi.xfer2(list(tx)) - logger.debug("- waiting for echo") - start = time.monotonic() + # Poll until the TX frame begins with 'VERSION', then parse and drain + deadline = time.monotonic() + 0.5 # short deadline, outer loop retries while True: - version_bytes = spi.readbytes(len(vers_str) + 2) - if bytes(version_bytes).startswith(vers_str): - break - if (time.monotonic() - start) > 0.001: + # Poll one byte until 'V' + b0 = spi.xfer2([0x00])[0] + if b0 == vers_str[0]: + # Read remaining of 'VERSION' + tail = bytes(spi.xfer2([0x00] * (len(vers_str) - 1))) + if tail == vers_str[1:]: + break + if time.monotonic() > deadline: raise PandaSpiMissingAck - rlen = struct.unpack(" 1000: + # Drain rest of frame + to_drain = rlen + 1 + if to_drain > 0: + spi.xfer2([0x00] * to_drain) + pad = SPI_BUF_SIZE - (len(vers_str) + 2 + to_drain) + if pad > 0: + spi.xfer2([0x00] * pad) raise PandaSpiException("response length greater than max") - # get response - dat = spi.readbytes(rlen + 1) - resp = dat[:-1] - calculated_crc = crc8(bytes(version_bytes + resp)) - if calculated_crc != dat[-1]: + data_ck = bytes(spi.xfer2([0x00] * (rlen + 1))) + # Verify CRC8 over 'VERSION'+len+data + if crc8(vers_str + len_bytes + data_ck[:-1]) != data_ck[-1]: + pad = SPI_BUF_SIZE - (len(vers_str) + 2 + len(data_ck)) + if pad > 0: + spi.xfer2([0x00] * pad) raise PandaSpiBadChecksum - return bytes(resp) + + # Drain remainder of the frame + pad = SPI_BUF_SIZE - (len(vers_str) + 2 + len(data_ck)) + if pad > 0: + spi.xfer2([0x00] * pad) + return data_ck[:-1] exc = PandaSpiException() with self.dev.acquire() as spi: diff --git a/test.sh b/test.sh index 6412ce74bb3..243fc7db430 100755 --- a/test.sh +++ b/test.sh @@ -12,7 +12,7 @@ scons -j8 # *** lint *** ruff check . -mypy python/ +#mypy python/ # *** test *** From c26eaf7be94911396994986732bf652556adddfb Mon Sep 17 00:00:00 2001 From: Comma Device Date: Sat, 4 Oct 2025 22:39:29 +0000 Subject: [PATCH 02/17] lil cleanup --- board/drivers/spi.h | 17 ++++------------- board/drivers/spi_declarations.h | 11 +---------- board/stm32h7/llspi.h | 2 +- 3 files changed, 6 insertions(+), 24 deletions(-) diff --git a/board/drivers/spi.h b/board/drivers/spi.h index 1d0b48ae568..148a59d2ef0 100644 --- a/board/drivers/spi.h +++ b/board/drivers/spi.h @@ -8,7 +8,6 @@ uint8_t spi_buf_tx[SPI_BUF_SIZE]; uint16_t spi_error_count = 0; -static uint8_t spi_state = SPI_STATE_RX_FRAME; static uint16_t spi_data_len_mosi; static bool spi_can_tx_ready = false; static const unsigned char version_text[] = "VERSION"; @@ -61,8 +60,7 @@ void spi_init(void) { // platform init llspi_init(); - // Start the first packet! - spi_state = SPI_STATE_RX_FRAME; + // start listening! llspi_mosi_dma(spi_buf_rx, SPI_BUF_SIZE); } @@ -81,9 +79,6 @@ void spi_rx_done(void) { static uint8_t spi_endpoint; static uint16_t spi_data_len_miso; - // VERSION request remains special and can be issued by sending a frame - // that begins with 'VERSION' (rest padded). Respond with the same stable - // format at the start of the TX frame. if (memcmp(spi_buf_rx, version_text, 7) == 0) { response_len = spi_version_packet(spi_buf_tx); } else { @@ -180,7 +175,7 @@ void spi_rx_done(void) { // Add checksum over header + payload uint8_t checksum = SPI_CHECKSUM_START; - for(uint16_t i = 0U; i < (response_len + 3U); i++) { + for (uint16_t i = 0U; i < (response_len + 3U); i++) { checksum ^= spi_buf_tx[i]; } spi_buf_tx[response_len + 3U] = checksum; @@ -189,15 +184,11 @@ void spi_rx_done(void) { } } - // Transmit the full fixed-size TX frame + // send out the response llspi_miso_dma(spi_buf_tx, SPI_BUF_SIZE); - spi_state = SPI_STATE_TX_FRAME; } -void spi_tx_done(bool reset) { - (void)reset; - // After sending a full TX frame, always prepare to receive the next full RX frame - spi_state = SPI_STATE_RX_FRAME; +void spi_tx_done() { llspi_mosi_dma(spi_buf_rx, SPI_BUF_SIZE); } diff --git a/board/drivers/spi_declarations.h b/board/drivers/spi_declarations.h index d4a52026541..1f9e13b1b86 100644 --- a/board/drivers/spi_declarations.h +++ b/board/drivers/spi_declarations.h @@ -16,16 +16,9 @@ __attribute__((section(".sram12"))) extern uint8_t spi_buf_tx[SPI_BUF_SIZE]; // Protocol constants #define SPI_CHECKSUM_START 0xABU #define SPI_SYNC_BYTE 0x5AU -#define SPI_HACK 0x79U #define SPI_DACK 0x85U #define SPI_NACK 0x1FU -// SPI states (simplified): alternate full-size RX then full-size TX -enum { - SPI_STATE_RX_FRAME = 0, - SPI_STATE_TX_FRAME = 1 -}; - extern uint16_t spi_error_count; // Fixed frame contains a 7-byte header followed by data and a 1-byte data checksum. @@ -34,8 +27,6 @@ extern uint16_t spi_error_count; // data checksum byte immediately following data. // The device responds in the next frame with: // [DACK(1) | RESP_LEN(2 LE) | RESP_DATA | RESP_CKSUM(1)] -// VERSION requests remain special: if the RX frame begins with 'VERSION', the TX frame -// will contain 'VERSION' + 2 byte length + data + CRC8 at the start. #define SPI_HEADER_SIZE 7U // low level SPI prototypes @@ -46,4 +37,4 @@ void llspi_miso_dma(uint8_t *addr, int len); void can_tx_comms_resume_spi(void); void spi_init(void); void spi_rx_done(void); -void spi_tx_done(bool reset); +void spi_tx_done(void); diff --git a/board/stm32h7/llspi.h b/board/stm32h7/llspi.h index 05f8e22f9a8..48446a46e53 100644 --- a/board/stm32h7/llspi.h +++ b/board/stm32h7/llspi.h @@ -74,7 +74,7 @@ static void SPI4_IRQ_Handler(void) { if (spi_tx_dma_done && ((SPI4->SR & SPI_SR_TXC) != 0U)) { spi_tx_dma_done = false; - spi_tx_done(false); + spi_tx_done(); } } From 8babd70f1f582d13353ef8fd0023c050e3897611 Mon Sep 17 00:00:00 2001 From: Comma Device Date: Sat, 4 Oct 2025 23:26:25 +0000 Subject: [PATCH 03/17] version works after many retries --- board/drivers/spi.h | 2 +- python/__init__.py | 5 +-- python/spi.py | 83 +++++++++++++++++++++++++++++---------------- 3 files changed, 58 insertions(+), 32 deletions(-) diff --git a/board/drivers/spi.h b/board/drivers/spi.h index 148a59d2ef0..70224e9e997 100644 --- a/board/drivers/spi.h +++ b/board/drivers/spi.h @@ -185,7 +185,7 @@ void spi_rx_done(void) { } // send out the response - llspi_miso_dma(spi_buf_tx, SPI_BUF_SIZE); + llspi_miso_dma(spi_buf_tx, response_len); } void spi_tx_done() { diff --git a/python/__init__.py b/python/__init__.py index aa5f49dd6fb..562b930d780 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -147,9 +147,10 @@ def __init__(self, serial: str | None = None, claim: bool = True, disable_checks self._can_speed_kbps = can_speed_kbps if cli and serial is None: - self._connect_serial = self._cli_select_panda() + #self._connect_serial = self._cli_select_panda() + self._connect_serial = None else: - self._connect_serial = serial + self._connect_serial = serial # connect and set mcu type self.connect(claim) diff --git a/python/spi.py b/python/spi.py index 2480ac28afb..213b53b06d8 100644 --- a/python/spi.py +++ b/python/spi.py @@ -308,63 +308,82 @@ def _transfer(self, endpoint: int, data, timeout: int, max_rx_len: int = 1000, e raise exc + def _wait_for_ack(self, spi, ack_val: int, timeout: int, tx: int, length: int = 1) -> bytes: + timeout_s = max(MIN_ACK_TIMEOUT_MS, timeout) * 1e-3 + + start = time.monotonic() + while (timeout == 0) or ((time.monotonic() - start) < timeout_s): + dat = spi.xfer2([tx, ] * length) + if dat[0] == ack_val: + return bytes(dat) + elif dat[0] == NACK: + raise PandaSpiNackResponse + + raise PandaSpiMissingAck + def get_protocol_version(self) -> bytes: vers_str = b"VERSION" def _get_version(spi) -> bytes: - # Send a fixed-size frame whose prefix is 'VERSION', padded otherwise tx = bytearray(SPI_BUF_SIZE) tx[:len(vers_str)] = vers_str - spi.xfer2(list(tx)) + a = spi.xfer2(list(tx)) + print("a", bytes(a[:30])) - # Poll until the TX frame begins with 'VERSION', then parse and drain - deadline = time.monotonic() + 0.5 # short deadline, outer loop retries + # poll for response + deadline = time.monotonic() + 0.5 while True: # Poll one byte until 'V' - b0 = spi.xfer2([0x00])[0] - if b0 == vers_str[0]: - # Read remaining of 'VERSION' - tail = bytes(spi.xfer2([0x00] * (len(vers_str) - 1))) - if tail == vers_str[1:]: - break + response = spi.xfer2([0x00, ]*SPI_BUF_SIZE) + print(bytes(response[:30])) + if bytes(response).startswith(vers_str): + break + + # TODO: needs something to sync them back up + #if response[0] == NACK: + # raise PandaSpiMissingAck if time.monotonic() > deadline: raise PandaSpiMissingAck # We are aligned at start of VERSION block - len_bytes = bytes(spi.xfer2([0x00, 0x00])) + len_bytes = bytes(response[7:7+2]) rlen = struct.unpack(' 1000: - # Drain rest of frame - to_drain = rlen + 1 - if to_drain > 0: - spi.xfer2([0x00] * to_drain) - pad = SPI_BUF_SIZE - (len(vers_str) + 2 + to_drain) - if pad > 0: - spi.xfer2([0x00] * pad) raise PandaSpiException("response length greater than max") - data_ck = bytes(spi.xfer2([0x00] * (rlen + 1))) + data_ck = response[9:9+rlen+1] # Verify CRC8 over 'VERSION'+len+data - if crc8(vers_str + len_bytes + data_ck[:-1]) != data_ck[-1]: - pad = SPI_BUF_SIZE - (len(vers_str) + 2 + len(data_ck)) - if pad > 0: - spi.xfer2([0x00] * pad) + if crc8(vers_str + len_bytes + bytes(data_ck[:-1])) != data_ck[-1]: raise PandaSpiBadChecksum + print("got it!!") + exit() - # Drain remainder of the frame - pad = SPI_BUF_SIZE - (len(vers_str) + 2 + len(data_ck)) - if pad > 0: - spi.xfer2([0x00] * pad) return data_ck[:-1] exc = PandaSpiException() with self.dev.acquire() as spi: - for _ in range(10): + #for _ in range(10): + for _ in range(30): try: return _get_version(spi) except PandaSpiException as e: exc = e logger.debug("SPI get protocol version failed, retrying", exc_info=True) + + # ensure slave is in a consistent state and ready for the next transfer + # (e.g. slave TX buffer isn't stuck full) + #print("recovering") + #nack_cnt = 0 + #attempts = 5 + #while (nack_cnt <= 3) and (attempts > 0): + # attempts -= 1 + # try: + # self._wait_for_ack(spi, NACK, MIN_ACK_TIMEOUT_MS, 0x11, length=XFER_SIZE) + # nack_cnt += 1 + # except PandaSpiException: + # nack_cnt = 0 + #print("recover done", nack_cnt, attempts) + raise exc # libusb1 functions @@ -473,7 +492,13 @@ def _cmd(self, cmd: int, data: list[bytes] | None = None, read_bytes: int = 0, p return self._cmd_no_retry(cmd, data, read_bytes, predata) except PandaSpiException as e: exc = e - logger.debug("SPI transfer failed, %d retries left", MAX_XFER_RETRY_COUNT - n - 1, exc_info=True) + import traceback + traceback.print_exception(type(e), e, e.__traceback__, limit=None) + print() + print() + print() + print() + #logger.debug("SPI transfer failed, %d retries left", MAX_XFER_RETRY_COUNT - n - 1, exc_info=True) raise exc def _checksum(self, data: bytes) -> bytes: From 325a0615cb579353ac59bc328cad1b89b35c2299 Mon Sep 17 00:00:00 2001 From: Comma Device Date: Sun, 5 Oct 2025 00:04:33 +0000 Subject: [PATCH 04/17] better underrun behavior --- board/stm32h7/llspi.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/board/stm32h7/llspi.h b/board/stm32h7/llspi.h index 48446a46e53..490f339ed44 100644 --- a/board/stm32h7/llspi.h +++ b/board/stm32h7/llspi.h @@ -4,6 +4,7 @@ void llspi_mosi_dma(uint8_t *addr, int len) { register_clear_bits(&(SPI4->CFG1), SPI_CFG1_RXDMAEN); DMA2_Stream2->CR &= ~DMA_SxCR_EN; register_clear_bits(&(SPI4->CR1), SPI_CR1_SPE); + register_set(&(SPI4->UDRDR), 0xdd, 0xFFFFU); // set under-run value for debugging // drain the bus while ((SPI4->SR & SPI_SR_RXP) != 0U) { @@ -23,6 +24,7 @@ void llspi_mosi_dma(uint8_t *addr, int len) { DMA2_Stream2->CR |= DMA_SxCR_EN; register_set_bits(&(SPI4->CFG1), SPI_CFG1_RXDMAEN); register_set_bits(&(SPI4->CR1), SPI_CR1_SPE); + SPI4->TXDR = 0xdddddddd; // match the UDR bytes } // panda -> master DMA start @@ -31,6 +33,7 @@ void llspi_miso_dma(uint8_t *addr, int len) { DMA2_Stream3->CR &= ~DMA_SxCR_EN; register_clear_bits(&(SPI4->CFG1), SPI_CFG1_TXDMAEN); register_clear_bits(&(SPI4->CR1), SPI_CR1_SPE); + register_set(&(SPI4->UDRDR), 0xcc, 0xFFFFU); // set under-run value for debugging // setup source and length register_set(&(DMA2_Stream3->M0AR), (uint32_t)addr, 0xFFFFFFFFU); @@ -96,8 +99,7 @@ void llspi_init(void) { // Enable SPI register_set(&(SPI4->IER), 0, 0x3FFU); - register_set(&(SPI4->CFG1), (7U << SPI_CFG1_DSIZE_Pos), SPI_CFG1_DSIZE_Msk); - register_set(&(SPI4->UDRDR), 0xcd, 0xFFFFU); // set under-run value for debugging + register_set(&(SPI4->CFG1), (7U << SPI_CFG1_DSIZE_Pos) | (0b01 << SPI_CFG1_UDRDET_Pos), SPI_CFG1_DSIZE_Msk | SPI_CFG1_UDRDET_Msk); register_set(&(SPI4->CR1), SPI_CR1_SPE, 0xFFFFU); register_set(&(SPI4->CR2), 0, 0xFFFFU); From ea60e4c26a759868664e9431cb5094505f1b954d Mon Sep 17 00:00:00 2001 From: Comma Device Date: Sun, 5 Oct 2025 00:14:27 +0000 Subject: [PATCH 05/17] version works pretty good --- python/spi.py | 48 ++++++++++++++++++------------------------------ 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/python/spi.py b/python/spi.py index 213b53b06d8..3f1fe701808 100644 --- a/python/spi.py +++ b/python/spi.py @@ -321,6 +321,18 @@ def _wait_for_ack(self, spi, ack_val: int, timeout: int, tx: int, length: int = raise PandaSpiMissingAck + def _resync(self, spi): + # ensure slave is in a consistent state and ready for the next transfer + # (e.g. slave TX buffer isn't stuck full) + print("recovering") + attempts = 5 + while attempts > 0: + x = spi.xfer2([0x00, ]*SPI_BUF_SIZE) + if x[0] == NACK and set(bytes(x[1:])) == {0xcc, }: + break + attempts -= 1 + print("recover done", attempts) + def get_protocol_version(self) -> bytes: vers_str = b"VERSION" @@ -330,22 +342,11 @@ def _get_version(spi) -> bytes: a = spi.xfer2(list(tx)) print("a", bytes(a[:30])) - # poll for response - deadline = time.monotonic() + 0.5 - while True: - # Poll one byte until 'V' - response = spi.xfer2([0x00, ]*SPI_BUF_SIZE) - print(bytes(response[:30])) - if bytes(response).startswith(vers_str): - break - - # TODO: needs something to sync them back up - #if response[0] == NACK: - # raise PandaSpiMissingAck - if time.monotonic() > deadline: - raise PandaSpiMissingAck - - # We are aligned at start of VERSION block + response = spi.xfer2([0x00, ]*SPI_BUF_SIZE) + print(bytes(response[:30])) + if not bytes(response).startswith(vers_str): + raise PandaSpiMissingAck + len_bytes = bytes(response[7:7+2]) rlen = struct.unpack(' 1000: @@ -369,20 +370,7 @@ def _get_version(spi) -> bytes: except PandaSpiException as e: exc = e logger.debug("SPI get protocol version failed, retrying", exc_info=True) - - # ensure slave is in a consistent state and ready for the next transfer - # (e.g. slave TX buffer isn't stuck full) - #print("recovering") - #nack_cnt = 0 - #attempts = 5 - #while (nack_cnt <= 3) and (attempts > 0): - # attempts -= 1 - # try: - # self._wait_for_ack(spi, NACK, MIN_ACK_TIMEOUT_MS, 0x11, length=XFER_SIZE) - # nack_cnt += 1 - # except PandaSpiException: - # nack_cnt = 0 - #print("recover done", nack_cnt, attempts) + self._resync(spi) raise exc From 5ab88be7731c471fd000045042b7b38c64d2d596 Mon Sep 17 00:00:00 2001 From: Comma Device Date: Sun, 5 Oct 2025 00:18:01 +0000 Subject: [PATCH 06/17] version works, onto controlRead --- python/spi.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/python/spi.py b/python/spi.py index 3f1fe701808..54c4be1fb80 100644 --- a/python/spi.py +++ b/python/spi.py @@ -215,7 +215,6 @@ def _read_response_polled_spidev(self, spi, timeout_ms: int, max_rx_len: int) -> def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes: max_rx_len = max(USBPACKET_MAX_SIZE, max_rx_len) - # Build and send a single fixed-size TX frame tx_frame = self._build_tx_frame(endpoint, bytes(data), max_rx_len) spi.xfer2(list(tx_frame)) @@ -303,8 +302,10 @@ def _transfer(self, endpoint: int, data, timeout: int, max_rx_len: int = 1000, e except PandaSpiException as e: exc = e logger.debug("SPI transfer failed, retrying", exc_info=True) + self._resync(spi) - # No resynchronization dance needed with fixed frames; just retry + if n > 5: + exit() raise exc @@ -353,13 +354,10 @@ def _get_version(spi) -> bytes: raise PandaSpiException("response length greater than max") data_ck = response[9:9+rlen+1] - # Verify CRC8 over 'VERSION'+len+data if crc8(vers_str + len_bytes + bytes(data_ck[:-1])) != data_ck[-1]: raise PandaSpiBadChecksum - print("got it!!") - exit() - return data_ck[:-1] + return bytes(data_ck[:-1]) exc = PandaSpiException() with self.dev.acquire() as spi: From f541e48e67446d34f3bcca3d8345789d690ec155 Mon Sep 17 00:00:00 2001 From: Comma Device Date: Sun, 5 Oct 2025 00:23:41 +0000 Subject: [PATCH 07/17] health test runs --- python/spi.py | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/python/spi.py b/python/spi.py index 54c4be1fb80..a37d602bf14 100644 --- a/python/spi.py +++ b/python/spi.py @@ -173,44 +173,26 @@ def _read_response_polled_spidev(self, spi, timeout_ms: int, max_rx_len: int) -> status = 0 while True: - status = spi.xfer2([0x00])[0] + response = spi.xfer2([0x00]*SPI_BUF_SIZE) + status = response[0] + if status == NACK: + raise PandaSpiNackResponse if status in (DACK, NACK): break if timeout_ms != 0 and time.monotonic() > deadline: raise PandaSpiMissingAck - if status == NACK: - # Drain rest of frame so device can re-arm RX, then raise - spi.xfer2([0x00] * (SPI_BUF_SIZE - 1)) - raise PandaSpiNackResponse - # status == DACK: read len (2), payload, checksum, then drain padding - len_bytes = spi.xfer2([0x00, 0x00]) + len_bytes = response[1:3] response_len = struct.unpack(' max_rx_len: - # Drain the frame before erroring - to_drain = response_len + 1 - if to_drain > 0: - spi.xfer2([0x00] * to_drain) - pad = SPI_BUF_SIZE - (1 + 2 + to_drain) - if pad > 0: - spi.xfer2([0x00] * pad) raise PandaSpiException(f"response length greater than max ({max_rx_len} {response_len})") - data_ck = spi.xfer2([0x00] * (response_len + 1)) + data_ck = response[3:3+response_len+1] #spi.xfer2([0x00] * (response_len + 1)) frame_head = bytes([DACK]) + bytes(len_bytes) + bytes(data_ck) if self._calc_checksum(frame_head) != 0: - # Drain padding then raise - pad = SPI_BUF_SIZE - len(frame_head) - if pad > 0: - spi.xfer2([0x00] * pad) raise PandaSpiBadChecksum - # Drain remaining padding (if any) to complete the frame - pad = SPI_BUF_SIZE - len(frame_head) - if pad > 0: - spi.xfer2([0x00] * pad) - return bytes(data_ck[:-1]) def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes: From 7b7c6cf82b83e957257461d6a30792435a86c6d3 Mon Sep 17 00:00:00 2001 From: Comma Device Date: Sun, 5 Oct 2025 00:24:59 +0000 Subject: [PATCH 08/17] lil cleanup --- python/spi.py | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/python/spi.py b/python/spi.py index a37d602bf14..d156f6c42b1 100644 --- a/python/spi.py +++ b/python/spi.py @@ -291,30 +291,15 @@ def _transfer(self, endpoint: int, data, timeout: int, max_rx_len: int = 1000, e raise exc - def _wait_for_ack(self, spi, ack_val: int, timeout: int, tx: int, length: int = 1) -> bytes: - timeout_s = max(MIN_ACK_TIMEOUT_MS, timeout) * 1e-3 - - start = time.monotonic() - while (timeout == 0) or ((time.monotonic() - start) < timeout_s): - dat = spi.xfer2([tx, ] * length) - if dat[0] == ack_val: - return bytes(dat) - elif dat[0] == NACK: - raise PandaSpiNackResponse - - raise PandaSpiMissingAck - def _resync(self, spi): # ensure slave is in a consistent state and ready for the next transfer - # (e.g. slave TX buffer isn't stuck full) - print("recovering") + # (e.g. slave isn't stuck trying to RX/TX a massive buffer) attempts = 5 while attempts > 0: x = spi.xfer2([0x00, ]*SPI_BUF_SIZE) if x[0] == NACK and set(bytes(x[1:])) == {0xcc, }: break attempts -= 1 - print("recover done", attempts) def get_protocol_version(self) -> bytes: vers_str = b"VERSION" @@ -323,10 +308,8 @@ def _get_version(spi) -> bytes: tx = bytearray(SPI_BUF_SIZE) tx[:len(vers_str)] = vers_str a = spi.xfer2(list(tx)) - print("a", bytes(a[:30])) response = spi.xfer2([0x00, ]*SPI_BUF_SIZE) - print(bytes(response[:30])) if not bytes(response).startswith(vers_str): raise PandaSpiMissingAck @@ -343,8 +326,7 @@ def _get_version(spi) -> bytes: exc = PandaSpiException() with self.dev.acquire() as spi: - #for _ in range(10): - for _ in range(30): + for _ in range(10): try: return _get_version(spi) except PandaSpiException as e: From 72ce67d8bc772e358fe76e64db8761546b1175dd Mon Sep 17 00:00:00 2001 From: Comma Device Date: Sun, 5 Oct 2025 00:32:47 +0000 Subject: [PATCH 09/17] cleanup --- python/spi.py | 144 +++++++------------------------------------------- 1 file changed, 19 insertions(+), 125 deletions(-) diff --git a/python/spi.py b/python/spi.py index d156f6c42b1..e38f3257f5c 100644 --- a/python/spi.py +++ b/python/spi.py @@ -16,10 +16,6 @@ import spidev except ImportError: spidev = None -try: - import spidev2 -except ImportError: - spidev2 = None # Constants SYNC = 0x5A @@ -121,8 +117,6 @@ class PandaSpiHandle(BaseHandle): def __init__(self) -> None: self.dev = SpiDevice() - if spidev2 is not None and "SPI2" in os.environ: - self._spi2 = spidev2.SPIBus("/dev/spidev0.0", "w+b", bits_per_word=8, speed_hz=50_000_000) # helpers def _calc_checksum(self, data: bytes) -> int: @@ -131,58 +125,35 @@ def _calc_checksum(self, data: bytes) -> int: cksum ^= b return cksum - # New fixed-frame protocol helpers - def _build_tx_frame(self, endpoint: int, data: bytes, max_rx_len: int) -> bytes: - # header without checksum - header = self.HEADER.pack(SYNC, endpoint, len(data), max_rx_len) + def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes: + max_rx_len = max(USBPACKET_MAX_SIZE, max_rx_len) + + # *** TX to panda *** frame = bytearray(SPI_BUF_SIZE) - # header + checksum - frame[0:len(header)] = header - frame[len(header)] = self._calc_checksum(header) - # payload + checksum - payload_off = len(header) + 1 + frame[0:self.HEADER.size] = self.HEADER.pack(SYNC, endpoint, len(data), max_rx_len) + frame[self.HEADER.size] = self._calc_checksum(frame[:self.HEADER.size]) + data_offset = self.HEADER.size + 1 if len(data) > 0: - frame[payload_off:payload_off+len(data)] = data - frame[payload_off + len(data)] = self._calc_checksum(data) - return bytes(frame) + frame[data_offset:data_offset+len(data)] = data + frame[data_offset + len(data)] = self._calc_checksum(data) + spi.xfer2(frame) - def _parse_rx_frame(self, frame: bytes, max_rx_len: int) -> bytes: - if len(frame) < 4: - raise PandaSpiMissingAck - status = frame[0] - if status == NACK: - raise PandaSpiNackResponse - if status != DACK: - raise PandaSpiMissingAck - response_len = struct.unpack(' max_rx_len: - raise PandaSpiException(f"response length greater than max ({max_rx_len} {response_len})") - used = 3 + response_len + 1 - if used > len(frame): - raise PandaSpiMissingAck - if self._calc_checksum(frame[:used]) != 0: - raise PandaSpiBadChecksum - return bytes(frame[3:3+response_len]) + # *** RX from panda *** + if expect_disconnect: + logger.debug("- expecting disconnect, returning") + return b"" - def _read_response_polled_spidev(self, spi, timeout_ms: int, max_rx_len: int) -> bytes: - # Poll first byte until we see DACK or NACK; until TX DMA arms, the slave - # returns its under-run value (e.g., 0xCD). Once DACK/NACK is observed, that - # is the first byte of the TX frame, so we must clock the remaining bytes of - # the frame to let the device finish TX DMA and re-arm RX. - deadline = time.monotonic() + (timeout_ms * 1e-3 if timeout_ms > 0 else 3600.0) + deadline = time.monotonic() + (timeout * 1e-3 if timeout > 0 else 3600.0) status = 0 - while True: - response = spi.xfer2([0x00]*SPI_BUF_SIZE) + while status != DACK: + response = spi.readbytes(SPI_BUF_SIZE) status = response[0] if status == NACK: raise PandaSpiNackResponse - if status in (DACK, NACK): - break - if timeout_ms != 0 and time.monotonic() > deadline: + if timeout != 0 and time.monotonic() > deadline: raise PandaSpiMissingAck - # status == DACK: read len (2), payload, checksum, then drain padding len_bytes = response[1:3] response_len = struct.unpack(' max_rx_len: @@ -195,77 +166,6 @@ def _read_response_polled_spidev(self, spi, timeout_ms: int, max_rx_len: int) -> return bytes(data_ck[:-1]) - def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes: - max_rx_len = max(USBPACKET_MAX_SIZE, max_rx_len) - tx_frame = self._build_tx_frame(endpoint, bytes(data), max_rx_len) - spi.xfer2(list(tx_frame)) - - if expect_disconnect: - logger.debug("- expecting disconnect, returning") - return b"" - - # Poll for response status and then drain the full frame - return self._read_response_polled_spidev(spi, timeout, max_rx_len) - - def _transfer_spidev2(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = USBPACKET_MAX_SIZE, expect_disconnect: bool = False) -> bytes: - # Fallback to the same fixed-frame logic using spidev2 when available - max_rx_len = max(USBPACKET_MAX_SIZE, max_rx_len) - - tx_frame = self._build_tx_frame(endpoint, bytes(data), max_rx_len) - - # Send TX frame - self._spi2.submitTransferList(spidev2.SPITransferList(( - {'tx_buf': tx_frame, 'delay_usecs': 0, 'cs_change': True}, - ))) - - if expect_disconnect: - return b"" - - # Poll one byte at a time until DACK/NACK - deadline = time.monotonic() + (timeout * 1e-3 if timeout > 0 else 3600.0) - status_b = bytearray(1) - status = 0 - while True: - self._spi2.submitTransferList(spidev2.SPITransferList(({'rx_buf': status_b, 'delay_usecs': 0, 'cs_change': True},))) - status = status_b[0] - if status in (DACK, NACK): - break - if timeout != 0 and time.monotonic() > deadline: - raise PandaSpiMissingAck - - if status == NACK: - # Drain rest of frame - pad = bytearray(SPI_BUF_SIZE - 1) - self._spi2.submitTransferList(spidev2.SPITransferList(({'rx_buf': pad, 'delay_usecs': 0, 'cs_change': True},))) - raise PandaSpiNackResponse - - # Read len - len_b = bytearray(2) - self._spi2.submitTransferList(spidev2.SPITransferList(({'rx_buf': len_b, 'delay_usecs': 0, 'cs_change': True},))) - response_len = struct.unpack(' max_rx_len: - # Drain remainder - drain = bytearray(response_len + 1) - self._spi2.submitTransferList(spidev2.SPITransferList(({'rx_buf': drain, 'delay_usecs': 0, 'cs_change': True},))) - pad = bytearray(SPI_BUF_SIZE - (1 + 2 + len(drain))) - if len(pad) > 0: - self._spi2.submitTransferList(spidev2.SPITransferList(({'rx_buf': pad, 'delay_usecs': 0, 'cs_change': True},))) - raise PandaSpiException(f"response length greater than max ({max_rx_len} {response_len})") - - data_ck = bytearray(response_len + 1) - self._spi2.submitTransferList(spidev2.SPITransferList(({'rx_buf': data_ck, 'delay_usecs': 0, 'cs_change': True},))) - frame_head = bytes([DACK]) + bytes(len_b) + bytes(data_ck) - if self._calc_checksum(frame_head) != 0: - pad = bytearray(SPI_BUF_SIZE - len(frame_head)) - if len(pad) > 0: - self._spi2.submitTransferList(spidev2.SPITransferList(({'rx_buf': pad, 'delay_usecs': 0, 'cs_change': True},))) - raise PandaSpiBadChecksum - - pad = bytearray(SPI_BUF_SIZE - len(frame_head)) - if len(pad) > 0: - self._spi2.submitTransferList(spidev2.SPITransferList(({'rx_buf': pad, 'delay_usecs': 0, 'cs_change': True},))) - return bytes(data_ck[:-1]) - def _transfer(self, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes: logger.debug("starting transfer: endpoint=%d, max_rx_len=%d", endpoint, max_rx_len) logger.debug("==============================================") @@ -278,17 +178,11 @@ def _transfer(self, endpoint: int, data, timeout: int, max_rx_len: int = 1000, e logger.debug("\ntry #%d", n) with self.dev.acquire() as spi: try: - fn = self._transfer_spidev - #fn = self._transfer_spidev2 - return fn(spi, endpoint, data, timeout, max_rx_len, expect_disconnect) + return self._transfer_spidev(spi, endpoint, data, timeout, max_rx_len, expect_disconnect) except PandaSpiException as e: exc = e logger.debug("SPI transfer failed, retrying", exc_info=True) self._resync(spi) - - if n > 5: - exit() - raise exc def _resync(self, spi): From 40ec20b8f85ef3c650e3c64bd671e4f4cd1a2d9e Mon Sep 17 00:00:00 2001 From: Comma Device Date: Sun, 5 Oct 2025 00:54:04 +0000 Subject: [PATCH 10/17] lil cleaner --- board/drivers/spi.h | 25 +++++-------------------- board/drivers/spi_declarations.h | 6 +++--- board/stm32h7/llspi.h | 6 ++++-- python/spi.py | 7 ++++--- 4 files changed, 16 insertions(+), 28 deletions(-) diff --git a/board/drivers/spi.h b/board/drivers/spi.h index 70224e9e997..051ca4d8a1c 100644 --- a/board/drivers/spi.h +++ b/board/drivers/spi.h @@ -60,8 +60,8 @@ void spi_init(void) { // platform init llspi_init(); - // start listening! - llspi_mosi_dma(spi_buf_rx, SPI_BUF_SIZE); + // Start the first packet! + llspi_mosi_dma(spi_buf_rx); } static bool validate_checksum(const uint8_t *data, uint16_t len) { @@ -82,21 +82,11 @@ void spi_rx_done(void) { if (memcmp(spi_buf_rx, version_text, 7) == 0) { response_len = spi_version_packet(spi_buf_tx); } else { - // Parse combined header + data in a single fixed-size RX frame + // 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]; - // Clamp lengths to available buffer space to avoid OOB - uint16_t max_mosi = (uint16_t)(SPI_BUF_SIZE - SPI_HEADER_SIZE - 1U); - if (spi_data_len_mosi > max_mosi) { - spi_data_len_mosi = max_mosi; - } - uint16_t max_miso = (uint16_t)(SPI_BUF_SIZE - 4U); // DACK + LEN(2) + CRC8 - if (spi_data_len_miso > max_miso) { - spi_data_len_miso = max_miso; - } - // Validate header bool header_ok = false; if (spi_buf_rx[0] == SPI_SYNC_BYTE) { @@ -163,13 +153,8 @@ void spi_rx_done(void) { spi_buf_tx[0] = SPI_NACK; response_len = 1U; } else { - // Enforce response length limits - if (response_len > spi_data_len_miso) { - response_len = spi_data_len_miso; - } - // Setup response header - spi_buf_tx[0] = SPI_DACK; + spi_buf_tx[0] = SPI_ACK; spi_buf_tx[1] = response_len & 0xFFU; spi_buf_tx[2] = (response_len >> 8) & 0xFFU; @@ -189,7 +174,7 @@ void spi_rx_done(void) { } void spi_tx_done() { - llspi_mosi_dma(spi_buf_rx, SPI_BUF_SIZE); + llspi_mosi_dma(spi_buf_rx); } void can_tx_comms_resume_spi(void) { diff --git a/board/drivers/spi_declarations.h b/board/drivers/spi_declarations.h index 1f9e13b1b86..34f193688d8 100644 --- a/board/drivers/spi_declarations.h +++ b/board/drivers/spi_declarations.h @@ -8,7 +8,7 @@ // in a tight loop, plus some buffer #define SPI_IRQ_RATE 16000U -#define SPI_BUF_SIZE 4096U +#define SPI_BUF_SIZE 68U // H7 DMA2 located in D2 domain, so we need to use SRAM1/SRAM2 __attribute__((section(".sram12"))) extern uint8_t spi_buf_rx[SPI_BUF_SIZE]; __attribute__((section(".sram12"))) extern uint8_t spi_buf_tx[SPI_BUF_SIZE]; @@ -16,7 +16,7 @@ __attribute__((section(".sram12"))) extern uint8_t spi_buf_tx[SPI_BUF_SIZE]; // Protocol constants #define SPI_CHECKSUM_START 0xABU #define SPI_SYNC_BYTE 0x5AU -#define SPI_DACK 0x85U +#define SPI_ACK 0x85U #define SPI_NACK 0x1FU extern uint16_t spi_error_count; @@ -31,7 +31,7 @@ extern uint16_t spi_error_count; // low level SPI prototypes void llspi_init(void); -void llspi_mosi_dma(uint8_t *addr, int len); +void llspi_mosi_dma(uint8_t *addr); void llspi_miso_dma(uint8_t *addr, int len); void can_tx_comms_resume_spi(void); diff --git a/board/stm32h7/llspi.h b/board/stm32h7/llspi.h index 490f339ed44..d32dfc8e451 100644 --- a/board/stm32h7/llspi.h +++ b/board/stm32h7/llspi.h @@ -1,5 +1,7 @@ +#include "board/drivers/spi_declarations.h" + // master -> panda DMA start -void llspi_mosi_dma(uint8_t *addr, int len) { +void llspi_mosi_dma(uint8_t *addr) { // disable DMA + SPI register_clear_bits(&(SPI4->CFG1), SPI_CFG1_RXDMAEN); DMA2_Stream2->CR &= ~DMA_SxCR_EN; @@ -18,7 +20,7 @@ void llspi_mosi_dma(uint8_t *addr, int len) { // setup destination and length register_set(&(DMA2_Stream2->M0AR), (uint32_t)addr, 0xFFFFFFFFU); - DMA2_Stream2->NDTR = len; + DMA2_Stream2->NDTR = SPI_BUF_SIZE; // enable DMA + SPI DMA2_Stream2->CR |= DMA_SxCR_EN; diff --git a/python/spi.py b/python/spi.py index e38f3257f5c..0cc91edcef7 100644 --- a/python/spi.py +++ b/python/spi.py @@ -20,7 +20,7 @@ # Constants SYNC = 0x5A HACK = 0x79 -DACK = 0x85 +ACK = 0x85 NACK = 0x1F CHECKSUM_START = 0xAB @@ -28,6 +28,7 @@ MAX_XFER_RETRY_COUNT = 5 SPI_BUF_SIZE = 4096 # from panda/board/drivers/spi.h +SPI_BUF_SIZE = 68 # from panda/board/drivers/spi.h XFER_SIZE = SPI_BUF_SIZE - 0x40 # give some room for SPI protocol overhead DEV_PATH = "/dev/spidev0.0" @@ -146,7 +147,7 @@ def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: i deadline = time.monotonic() + (timeout * 1e-3 if timeout > 0 else 3600.0) status = 0 - while status != DACK: + while status != ACK: response = spi.readbytes(SPI_BUF_SIZE) status = response[0] if status == NACK: @@ -160,7 +161,7 @@ def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: i raise PandaSpiException(f"response length greater than max ({max_rx_len} {response_len})") data_ck = response[3:3+response_len+1] #spi.xfer2([0x00] * (response_len + 1)) - frame_head = bytes([DACK]) + bytes(len_bytes) + bytes(data_ck) + frame_head = bytes([ACK]) + bytes(len_bytes) + bytes(data_ck) if self._calc_checksum(frame_head) != 0: raise PandaSpiBadChecksum From 53929d2fce7d26bc55052588ca6cdcbf0d441e6e Mon Sep 17 00:00:00 2001 From: Comma Device Date: Sun, 5 Oct 2025 00:56:37 +0000 Subject: [PATCH 11/17] lil more --- board/drivers/spi_declarations.h | 2 +- python/spi.py | 9 +-------- test.sh | 2 +- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/board/drivers/spi_declarations.h b/board/drivers/spi_declarations.h index 34f193688d8..633fcaa601c 100644 --- a/board/drivers/spi_declarations.h +++ b/board/drivers/spi_declarations.h @@ -8,7 +8,7 @@ // in a tight loop, plus some buffer #define SPI_IRQ_RATE 16000U -#define SPI_BUF_SIZE 68U +#define SPI_BUF_SIZE 4096U // H7 DMA2 located in D2 domain, so we need to use SRAM1/SRAM2 __attribute__((section(".sram12"))) extern uint8_t spi_buf_rx[SPI_BUF_SIZE]; __attribute__((section(".sram12"))) extern uint8_t spi_buf_tx[SPI_BUF_SIZE]; diff --git a/python/spi.py b/python/spi.py index 0cc91edcef7..595f88f690c 100644 --- a/python/spi.py +++ b/python/spi.py @@ -28,7 +28,6 @@ MAX_XFER_RETRY_COUNT = 5 SPI_BUF_SIZE = 4096 # from panda/board/drivers/spi.h -SPI_BUF_SIZE = 68 # from panda/board/drivers/spi.h XFER_SIZE = SPI_BUF_SIZE - 0x40 # give some room for SPI protocol overhead DEV_PATH = "/dev/spidev0.0" @@ -337,13 +336,7 @@ def _cmd(self, cmd: int, data: list[bytes] | None = None, read_bytes: int = 0, p return self._cmd_no_retry(cmd, data, read_bytes, predata) except PandaSpiException as e: exc = e - import traceback - traceback.print_exception(type(e), e, e.__traceback__, limit=None) - print() - print() - print() - print() - #logger.debug("SPI transfer failed, %d retries left", MAX_XFER_RETRY_COUNT - n - 1, exc_info=True) + logger.debug("SPI transfer failed, %d retries left", MAX_XFER_RETRY_COUNT - n - 1, exc_info=True) raise exc def _checksum(self, data: bytes) -> bytes: diff --git a/test.sh b/test.sh index 243fc7db430..6412ce74bb3 100755 --- a/test.sh +++ b/test.sh @@ -12,7 +12,7 @@ scons -j8 # *** lint *** ruff check . -#mypy python/ +mypy python/ # *** test *** From 951e2b02a21b4b12badb58ac5d21672322d63fae Mon Sep 17 00:00:00 2001 From: Comma Device Date: Sun, 5 Oct 2025 01:13:21 +0000 Subject: [PATCH 12/17] fix tests --- python/spi.py | 2 +- tests/hitl/5_spi.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/python/spi.py b/python/spi.py index 595f88f690c..da47310bc15 100644 --- a/python/spi.py +++ b/python/spi.py @@ -201,7 +201,7 @@ def get_protocol_version(self) -> bytes: def _get_version(spi) -> bytes: tx = bytearray(SPI_BUF_SIZE) tx[:len(vers_str)] = vers_str - a = spi.xfer2(list(tx)) + spi.xfer2(list(tx)) response = spi.xfer2([0x00, ]*SPI_BUF_SIZE) if not bytes(response).startswith(vers_str): diff --git a/tests/hitl/5_spi.py b/tests/hitl/5_spi.py index 106a1c7d4b2..c41819d6614 100644 --- a/tests/hitl/5_spi.py +++ b/tests/hitl/5_spi.py @@ -10,7 +10,7 @@ class TestSpi: def _ping(self, mocker, panda): # should work with no retries - spy = mocker.spy(panda._handle, '_wait_for_ack') + spy = mocker.spy(panda._handle, '_cmd_no_retry') panda.health() assert spy.call_count == 2 mocker.stop(spy) @@ -41,17 +41,17 @@ def test_protocol_version_data(self, p): assert bstub == (0xEE if bootstub else 0xCC) def test_all_comm_types(self, mocker, p): - spy = mocker.spy(p._handle, '_wait_for_ack') + spy = mocker.spy(p._handle, '_cmd_no_retry') # controlRead + controlWrite p.health() p.can_clear(0) - assert spy.call_count == 2*2 + assert spy.call_count == 2 # bulkRead + bulkWrite p.can_recv() p.can_send(0x123, b"somedata", 0) - assert spy.call_count == 2*4 + assert spy.call_count == 4 def test_bad_header(self, mocker, p): with patch('panda.python.spi.SYNC', return_value=0): From 8c38e5dac4b1e661c132a300ceb5ca9a49b7178b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 4 Oct 2025 18:14:07 -0700 Subject: [PATCH 13/17] try variable length --- board/stm32h7/llspi.h | 43 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/board/stm32h7/llspi.h b/board/stm32h7/llspi.h index d32dfc8e451..95c3e760e3c 100644 --- a/board/stm32h7/llspi.h +++ b/board/stm32h7/llspi.h @@ -1,6 +1,31 @@ #include "board/drivers/spi_declarations.h" // master -> panda DMA start +static volatile bool spi_rx_active = false; + +// helper: finish current MOSI RX DMA +static inline void llspi_mosi_finish(uint8_t *base_addr) { + // stop RX DMA + register_clear_bits(&(SPI4->CFG1), SPI_CFG1_RXDMAEN); + DMA2_Stream2->CR &= ~DMA_SxCR_EN; + + // drain any residual bytes in RX FIFO directly into the buffer (best effort) + uint16_t rx_len = (uint16_t)(SPI_BUF_SIZE - DMA2_Stream2->NDTR); + while ((SPI4->SR & SPI_SR_RXP) != 0U) { + if (rx_len < SPI_BUF_SIZE) { + base_addr[rx_len++] = (uint8_t)SPI4->RXDR; + } else { + volatile uint8_t dat_ovf = (uint8_t)SPI4->RXDR; + (void)dat_ovf; // drop if overflow + } + } + + // clear status flags (EOT, UDR, OVR, etc.) + SPI4->IFCR |= (0x1FFU << 3U); + + spi_rx_active = false; +} + void llspi_mosi_dma(uint8_t *addr) { // disable DMA + SPI register_clear_bits(&(SPI4->CFG1), SPI_CFG1_RXDMAEN); @@ -14,9 +39,9 @@ void llspi_mosi_dma(uint8_t *addr) { (void)dat; } - // clear all pending + // clear all pending and enable EOT interrupt so we can end on CS rising SPI4->IFCR |= (0x1FFU << 3U); - register_set(&(SPI4->IER), 0, 0x3FFU); + register_set(&(SPI4->IER), (1U << SPI_IER_EOTIE_Pos), 0x3FFU); // setup destination and length register_set(&(DMA2_Stream2->M0AR), (uint32_t)addr, 0xFFFFFFFFU); @@ -27,6 +52,8 @@ void llspi_mosi_dma(uint8_t *addr) { register_set_bits(&(SPI4->CFG1), SPI_CFG1_RXDMAEN); register_set_bits(&(SPI4->CR1), SPI_CR1_SPE); SPI4->TXDR = 0xdddddddd; // match the UDR bytes + + spi_rx_active = true; } // panda -> master DMA start @@ -59,7 +86,11 @@ static void DMA2_Stream2_IRQ_Handler(void) { // Clear interrupt flag DMA2->LIFCR = DMA_LIFCR_CTCIF2; - spi_rx_done(); + // If RX was active, finalize and dispatch + if (spi_rx_active) { + llspi_mosi_finish(spi_buf_rx); + spi_rx_done(); + } } // panda -> master DMA finished @@ -81,6 +112,12 @@ static void SPI4_IRQ_Handler(void) { spi_tx_dma_done = false; spi_tx_done(); } + + // Early end of MOSI on EOT (e.g., NSS rising) + if (spi_rx_active && ((SPI4->SR & SPI_SR_EOT) != 0U)) { + llspi_mosi_finish(spi_buf_rx); + spi_rx_done(); + } } From b203a56f26c4c0d28c80510937bcbacde532dc89 Mon Sep 17 00:00:00 2001 From: Comma Device Date: Sun, 5 Oct 2025 01:43:27 +0000 Subject: [PATCH 14/17] Revert "try variable length" This reverts commit 8c38e5dac4b1e661c132a300ceb5ca9a49b7178b. --- board/stm32h7/llspi.h | 43 +++---------------------------------------- 1 file changed, 3 insertions(+), 40 deletions(-) diff --git a/board/stm32h7/llspi.h b/board/stm32h7/llspi.h index 95c3e760e3c..d32dfc8e451 100644 --- a/board/stm32h7/llspi.h +++ b/board/stm32h7/llspi.h @@ -1,31 +1,6 @@ #include "board/drivers/spi_declarations.h" // master -> panda DMA start -static volatile bool spi_rx_active = false; - -// helper: finish current MOSI RX DMA -static inline void llspi_mosi_finish(uint8_t *base_addr) { - // stop RX DMA - register_clear_bits(&(SPI4->CFG1), SPI_CFG1_RXDMAEN); - DMA2_Stream2->CR &= ~DMA_SxCR_EN; - - // drain any residual bytes in RX FIFO directly into the buffer (best effort) - uint16_t rx_len = (uint16_t)(SPI_BUF_SIZE - DMA2_Stream2->NDTR); - while ((SPI4->SR & SPI_SR_RXP) != 0U) { - if (rx_len < SPI_BUF_SIZE) { - base_addr[rx_len++] = (uint8_t)SPI4->RXDR; - } else { - volatile uint8_t dat_ovf = (uint8_t)SPI4->RXDR; - (void)dat_ovf; // drop if overflow - } - } - - // clear status flags (EOT, UDR, OVR, etc.) - SPI4->IFCR |= (0x1FFU << 3U); - - spi_rx_active = false; -} - void llspi_mosi_dma(uint8_t *addr) { // disable DMA + SPI register_clear_bits(&(SPI4->CFG1), SPI_CFG1_RXDMAEN); @@ -39,9 +14,9 @@ void llspi_mosi_dma(uint8_t *addr) { (void)dat; } - // clear all pending and enable EOT interrupt so we can end on CS rising + // clear all pending SPI4->IFCR |= (0x1FFU << 3U); - register_set(&(SPI4->IER), (1U << SPI_IER_EOTIE_Pos), 0x3FFU); + register_set(&(SPI4->IER), 0, 0x3FFU); // setup destination and length register_set(&(DMA2_Stream2->M0AR), (uint32_t)addr, 0xFFFFFFFFU); @@ -52,8 +27,6 @@ void llspi_mosi_dma(uint8_t *addr) { register_set_bits(&(SPI4->CFG1), SPI_CFG1_RXDMAEN); register_set_bits(&(SPI4->CR1), SPI_CR1_SPE); SPI4->TXDR = 0xdddddddd; // match the UDR bytes - - spi_rx_active = true; } // panda -> master DMA start @@ -86,11 +59,7 @@ static void DMA2_Stream2_IRQ_Handler(void) { // Clear interrupt flag DMA2->LIFCR = DMA_LIFCR_CTCIF2; - // If RX was active, finalize and dispatch - if (spi_rx_active) { - llspi_mosi_finish(spi_buf_rx); - spi_rx_done(); - } + spi_rx_done(); } // panda -> master DMA finished @@ -112,12 +81,6 @@ static void SPI4_IRQ_Handler(void) { spi_tx_dma_done = false; spi_tx_done(); } - - // Early end of MOSI on EOT (e.g., NSS rising) - if (spi_rx_active && ((SPI4->SR & SPI_SR_EOT) != 0U)) { - llspi_mosi_finish(spi_buf_rx); - spi_rx_done(); - } } From 047dc57e7a43ffd2017c99552bdf0d3dd02c6b2e Mon Sep 17 00:00:00 2001 From: Comma Device Date: Sun, 5 Oct 2025 19:14:04 +0000 Subject: [PATCH 15/17] nice debugging --- board/config.h | 2 +- board/drivers/spi.h | 3 +++ board/drivers/spi_declarations.h | 2 +- board/drivers/uart.h | 2 +- board/drivers/uart_declarations.h | 2 +- python/spi.py | 14 +++++++---- scripts/health_test.py | 18 -------------- scripts/spi_test.py | 41 +++++++++++++++++++++++++++++++ 8 files changed, 57 insertions(+), 27 deletions(-) delete mode 100755 scripts/health_test.py create mode 100755 scripts/spi_test.py diff --git a/board/config.h b/board/config.h index 1b7d938cc81..d488a31f738 100644 --- a/board/config.h +++ b/board/config.h @@ -5,7 +5,7 @@ //#define DEBUG //#define DEBUG_UART //#define DEBUG_USB -//#define DEBUG_SPI +#define DEBUG_SPI //#define DEBUG_FAULTS //#define DEBUG_COMMS //#define DEBUG_FAN diff --git a/board/drivers/spi.h b/board/drivers/spi.h index 051ca4d8a1c..4b3b791b6c7 100644 --- a/board/drivers/spi.h +++ b/board/drivers/spi.h @@ -1,5 +1,6 @@ #pragma once + #include "board/drivers/spi_declarations.h" #include "board/crc.h" @@ -146,6 +147,8 @@ void spi_rx_done(void) { } if (!header_ok || !checksum_valid) { + print("error: "); hexdump(spi_buf_rx, 24); print("\n"); + if (false) puth4(4); spi_error_count += 1U; } diff --git a/board/drivers/spi_declarations.h b/board/drivers/spi_declarations.h index 633fcaa601c..067f992fd89 100644 --- a/board/drivers/spi_declarations.h +++ b/board/drivers/spi_declarations.h @@ -8,7 +8,7 @@ // in a tight loop, plus some buffer #define SPI_IRQ_RATE 16000U -#define SPI_BUF_SIZE 4096U +#define SPI_BUF_SIZE 128U // H7 DMA2 located in D2 domain, so we need to use SRAM1/SRAM2 __attribute__((section(".sram12"))) extern uint8_t spi_buf_rx[SPI_BUF_SIZE]; __attribute__((section(".sram12"))) extern uint8_t spi_buf_tx[SPI_BUF_SIZE]; diff --git a/board/drivers/uart.h b/board/drivers/uart.h index aeacf84b851..760428e4a56 100644 --- a/board/drivers/uart.h +++ b/board/drivers/uart.h @@ -136,7 +136,7 @@ static void puth4(unsigned int i) { #endif #if defined(DEBUG_SPI) || defined(BOOTSTUB) || defined(DEBUG_USB) || defined(DEBUG_COMMS) -static void hexdump(const void *a, int l) { +void hexdump(const void *a, int l) { if (a != NULL) { for (int i=0; i < l; i++) { if ((i != 0) && ((i & 0xf) == 0)) print("\n"); diff --git a/board/drivers/uart_declarations.h b/board/drivers/uart_declarations.h index 14ec26110f9..ad82c39fc85 100644 --- a/board/drivers/uart_declarations.h +++ b/board/drivers/uart_declarations.h @@ -35,5 +35,5 @@ void puth(unsigned int i); static void puth4(unsigned int i); #endif #if defined(DEBUG_SPI) || defined(DEBUG_USB) || defined(DEBUG_COMMS) -static void hexdump(const void *a, int l); +void hexdump(const void *a, int l); #endif diff --git a/python/spi.py b/python/spi.py index da47310bc15..18d92188306 100644 --- a/python/spi.py +++ b/python/spi.py @@ -27,7 +27,7 @@ MIN_ACK_TIMEOUT_MS = 100 MAX_XFER_RETRY_COUNT = 5 -SPI_BUF_SIZE = 4096 # from panda/board/drivers/spi.h +SPI_BUF_SIZE = 128 # from panda/board/drivers/spi.h XFER_SIZE = SPI_BUF_SIZE - 0x40 # give some room for SPI protocol overhead DEV_PATH = "/dev/spidev0.0" @@ -117,8 +117,8 @@ class PandaSpiHandle(BaseHandle): def __init__(self) -> None: self.dev = SpiDevice() + self.no_retry = "NO_RETRY" in os.environ - # helpers def _calc_checksum(self, data: bytes) -> int: cksum = CHECKSUM_START for b in data: @@ -129,7 +129,7 @@ def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: i max_rx_len = max(USBPACKET_MAX_SIZE, max_rx_len) # *** TX to panda *** - frame = bytearray(SPI_BUF_SIZE) + frame = bytearray(b"\xea" * SPI_BUF_SIZE) frame[0:self.HEADER.size] = self.HEADER.pack(SYNC, endpoint, len(data), max_rx_len) frame[self.HEADER.size] = self._calc_checksum(frame[:self.HEADER.size]) data_offset = self.HEADER.size + 1 @@ -145,9 +145,11 @@ def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: i deadline = time.monotonic() + (timeout * 1e-3 if timeout > 0 else 3600.0) + cnt = 0 status = 0 while status != ACK: - response = spi.readbytes(SPI_BUF_SIZE) + cnt += 1 + response = spi.xfer2(cnt.to_bytes() * SPI_BUF_SIZE) # cnt is nice for debugging status = response[0] if status == NACK: raise PandaSpiNackResponse @@ -159,7 +161,7 @@ def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: i if response_len > max_rx_len: raise PandaSpiException(f"response length greater than max ({max_rx_len} {response_len})") - data_ck = response[3:3+response_len+1] #spi.xfer2([0x00] * (response_len + 1)) + data_ck = response[3:3+response_len+1] frame_head = bytes([ACK]) + bytes(len_bytes) + bytes(data_ck) if self._calc_checksum(frame_head) != 0: raise PandaSpiBadChecksum @@ -182,6 +184,8 @@ def _transfer(self, endpoint: int, data, timeout: int, max_rx_len: int = 1000, e except PandaSpiException as e: exc = e logger.debug("SPI transfer failed, retrying", exc_info=True) + if self.no_retry: + break self._resync(spi) raise exc diff --git a/scripts/health_test.py b/scripts/health_test.py deleted file mode 100755 index 1195c2d7f21..00000000000 --- a/scripts/health_test.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python3 -import time -from panda import Panda - -if __name__ == "__main__": - i = 0 - pi = 0 - - panda = Panda() - while True: - st = time.monotonic() - while time.monotonic() - st < 1: - panda.health() - i += 1 - print(i, panda.health(), "\n") - print(f"Speed: {i - pi}Hz") - pi = i - diff --git a/scripts/spi_test.py b/scripts/spi_test.py new file mode 100755 index 00000000000..07e6627c103 --- /dev/null +++ b/scripts/spi_test.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +import os +import sys +import time +from datetime import datetime +from collections import defaultdict, deque + +from panda import Panda, PandaSpiException + +if __name__ == "__main__": + s = defaultdict(lambda: deque(maxlen=30)) + def avg(k): + return sum(s[k])/len(s[k]) + + + start = datetime.now() + p = Panda() + p.serial_read(0) # drain + le = p.health()['spi_error_count'] + while True: + cnt = 0 + st = time.monotonic() + while time.monotonic() - st < 1: + try: + p.get_type() # get_type has no processing on panda side + except PandaSpiException: + print(f"ERROR after {datetime.now() - start}\n\n") + sys.stdout.write("\033[1;32;40m" + p.serial_read(0).decode('utf8') + "\033[00m") + sys.stdout.flush() + sys.exit() + cnt += 1 + s['hz'].append(cnt) + + err = p.health()['spi_error_count'] + s['err'].append((err - le) & 0xFFFFFFFF) + le = err + + print( + f"{avg('hz'):04.0f}Hz {avg('err'):04.0f} errors [{cnt:04d}Hz {s['err'][-1]:04d} errors]" + ) + From cfef11bb9a8d7a346b0fd8090fe03e54df95113d Mon Sep 17 00:00:00 2001 From: Comma Device Date: Mon, 6 Oct 2025 01:14:06 +0000 Subject: [PATCH 16/17] never any errors --- SConscript | 6 +++--- board/drivers/interrupts.h | 3 +++ board/drivers/timers.h | 8 +++++--- board/main.c | 5 +++++ board/stm32h7/llspi.h | 28 +++++++++++++++++++++++++++- board/stm32h7/lluart.h | 2 +- board/stm32h7/peripherals.h | 2 +- python/__init__.py | 2 +- python/spi.py | 3 ++- scripts/spi_test.py | 4 +--- test.py | 8 ++++++++ 11 files changed, 57 insertions(+), 14 deletions(-) create mode 100644 test.py diff --git a/SConscript b/SConscript index cd974b8104b..14447364ca0 100644 --- a/SConscript +++ b/SConscript @@ -64,10 +64,10 @@ def build_project(project_name, project, main, extra_flags): project_dir = Dir(f'./board/obj/{project_name}/') flags = project["FLAGS"] + extra_flags + common_flags + [ - "-Wall", - "-Wextra", + #"-Wall", + #"-Wextra", "-Wstrict-prototypes", - "-Werror", + #"-Werror", "-mlittle-endian", "-mthumb", "-nostdlib", diff --git a/board/drivers/interrupts.h b/board/drivers/interrupts.h index 1188a8ec07b..2d9ef80b478 100644 --- a/board/drivers/interrupts.h +++ b/board/drivers/interrupts.h @@ -15,6 +15,9 @@ static uint32_t busy_time = 0U; float interrupt_load = 0.0f; void handle_interrupt(IRQn_Type irq_type){ + if (irq_type != 0x3a && irq_type != 0x3b && irq_type != 0x54) { + print("irq: "); puth(irq_type); print("\n"); + } static uint8_t interrupt_depth = 0U; static uint32_t last_time = 0U; ENTER_CRITICAL(); diff --git a/board/drivers/timers.h b/board/drivers/timers.h index 4c046a2b49b..5153951c3c8 100644 --- a/board/drivers/timers.h +++ b/board/drivers/timers.h @@ -16,16 +16,18 @@ uint32_t microsecond_timer_get(void) { } void interrupt_timer_init(void) { + /* enable_interrupt_timer(); REGISTER_INTERRUPT(INTERRUPT_TIMER_IRQ, interrupt_timer_handler, 2U, FAULT_INTERRUPT_RATE_INTERRUPTS) register_set(&(INTERRUPT_TIMER->PSC), ((uint16_t)(15.25*APB1_TIMER_FREQ)-1U), 0xFFFFU); register_set(&(INTERRUPT_TIMER->DIER), TIM_DIER_UIE, 0x5F5FU); register_set(&(INTERRUPT_TIMER->CR1), TIM_CR1_CEN, 0x3FU); INTERRUPT_TIMER->SR = 0; - NVIC_EnableIRQ(INTERRUPT_TIMER_IRQ); + */ + NVIC_DisableIRQ(INTERRUPT_TIMER_IRQ); } void tick_timer_init(void) { - timer_init(TICK_TIMER, (uint16_t)((15.25*APB2_TIMER_FREQ)/8U)); - NVIC_EnableIRQ(TICK_TIMER_IRQ); + //timer_init(TICK_TIMER, (uint16_t)((15.25*APB2_TIMER_FREQ)/8U)); + NVIC_DisableIRQ(TICK_TIMER_IRQ); } diff --git a/board/main.c b/board/main.c index ac0fda9ba43..a04ace977c5 100644 --- a/board/main.c +++ b/board/main.c @@ -121,6 +121,11 @@ static void tick_handler(void) { static bool relay_malfunction_prev = false; if (TICK_TIMER->SR != 0U) { + /* + uint32_t sr = FLASH->SR1; + uint32_t cr = FLASH->CR1; + print("sr "); puth4(sr); print(" cr "); puth4(cr); print(" lock "); puth(flash_is_locked()); print("\n"); + */ // siren current_board->set_siren((loop_counter & 1U) && (siren_enabled || (siren_countdown > 0U))); diff --git a/board/stm32h7/llspi.h b/board/stm32h7/llspi.h index d32dfc8e451..e33d8d26c0b 100644 --- a/board/stm32h7/llspi.h +++ b/board/stm32h7/llspi.h @@ -29,6 +29,8 @@ void llspi_mosi_dma(uint8_t *addr) { SPI4->TXDR = 0xdddddddd; // match the UDR bytes } +static uint32_t dma_start_ts = 0; + // panda -> master DMA start void llspi_miso_dma(uint8_t *addr, int len) { // disable DMA + SPI @@ -51,8 +53,10 @@ void llspi_miso_dma(uint8_t *addr, int len) { register_set_bits(&(SPI4->CFG1), SPI_CFG1_TXDMAEN); DMA2_Stream3->CR |= DMA_SxCR_EN; register_set_bits(&(SPI4->CR1), SPI_CR1_SPE); + dma_start_ts = microsecond_timer_get(); } +static uint32_t dma_done_ts = 0; static bool spi_tx_dma_done = false; // master -> panda DMA finished static void DMA2_Stream2_IRQ_Handler(void) { @@ -64,10 +68,29 @@ static void DMA2_Stream2_IRQ_Handler(void) { // panda -> master DMA finished static void DMA2_Stream3_IRQ_Handler(void) { + dma_done_ts = microsecond_timer_get(); ENTER_CRITICAL(); DMA2->LIFCR = DMA_LIFCR_CTCIF3; - spi_tx_dma_done = true; + //spi_tx_dma_done = true; + + //bool timed_out = false; + uint32_t start_time = microsecond_timer_get(); + while (!(SPI4->SR & SPI_SR_TXC)) { + if (get_ts_elapsed(microsecond_timer_get(), start_time) > SPI_TIMEOUT_US) { + //timed_out = true; + break; + } + } + uint32_t diff = get_ts_elapsed(microsecond_timer_get(), start_time); + //print("diff: 0x"); puth4(get_ts_elapsed(start_time, dma_start_ts)); print(", 0x"); puth4(diff); print("\n"); + + // clear out FIFO + volatile uint8_t dat = SPI4->TXDR; + (void)dat; + (void)dma_start_ts; + (void)dat; + spi_tx_done(); EXIT_CRITICAL(); } @@ -78,6 +101,9 @@ static void SPI4_IRQ_Handler(void) { SPI4->IFCR |= (0x1FFU << 3U); if (spi_tx_dma_done && ((SPI4->SR & SPI_SR_TXC) != 0U)) { + uint32_t now = microsecond_timer_get(); + uint32_t diff = get_ts_elapsed(now, dma_done_ts); + print("diff: 0x"); puth4(diff); print("\n"); spi_tx_dma_done = false; spi_tx_done(); } diff --git a/board/stm32h7/lluart.h b/board/stm32h7/lluart.h index e18f1e9f6f3..8a79182c3b0 100644 --- a/board/stm32h7/lluart.h +++ b/board/stm32h7/lluart.h @@ -97,6 +97,6 @@ void uart_init(uart_ring *q, unsigned int baud) { q->uart->CR1 |= USART_CR1_RXNEIE; // Enable UART interrupts - NVIC_EnableIRQ(UART7_IRQn); + //NVIC_EnableIRQ(UART7_IRQn); } } diff --git a/board/stm32h7/peripherals.h b/board/stm32h7/peripherals.h index 8eb384307f3..f1b79687e46 100644 --- a/board/stm32h7/peripherals.h +++ b/board/stm32h7/peripherals.h @@ -92,7 +92,7 @@ void peripherals_init(void) { RCC->AHB2ENR |= RCC_AHB2ENR_SRAM1EN | RCC_AHB2ENR_SRAM2EN; // Supplemental - RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN; // DAC DMA + //RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN; // DAC DMA RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN; // SPI DMA RCC->APB4ENR |= RCC_APB4ENR_SYSCFGEN; RCC->AHB4ENR |= RCC_AHB4ENR_BDMAEN; // Audio DMA diff --git a/python/__init__.py b/python/__init__.py index 562b930d780..cf936bdacc1 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -746,7 +746,7 @@ def serial_read(self, port_number): ret = [] while 1: lret = bytes(self._handle.controlRead(Panda.REQUEST_IN, 0xe0, port_number, 0, 0x40)) - if len(lret) == 0: + if len(lret) == 0 or len(ret) > 1024: break ret.append(lret) return b''.join(ret) diff --git a/python/spi.py b/python/spi.py index 18d92188306..de1711f37b2 100644 --- a/python/spi.py +++ b/python/spi.py @@ -137,6 +137,7 @@ def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: i frame[data_offset:data_offset+len(data)] = data frame[data_offset + len(data)] = self._calc_checksum(data) spi.xfer2(frame) + #spi.xfer2(frame, 50_000_000, 1000, 8) # *** RX from panda *** if expect_disconnect: @@ -148,7 +149,7 @@ def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: i cnt = 0 status = 0 while status != ACK: - cnt += 1 + cnt = (cnt+1) % 0xff response = spi.xfer2(cnt.to_bytes() * SPI_BUF_SIZE) # cnt is nice for debugging status = response[0] if status == NACK: diff --git a/scripts/spi_test.py b/scripts/spi_test.py index 07e6627c103..eda2e61efc8 100755 --- a/scripts/spi_test.py +++ b/scripts/spi_test.py @@ -12,10 +12,8 @@ def avg(k): return sum(s[k])/len(s[k]) - - start = datetime.now() p = Panda() - p.serial_read(0) # drain + start = datetime.now() le = p.health()['spi_error_count'] while True: cnt = 0 diff --git a/test.py b/test.py new file mode 100644 index 00000000000..d18de27c289 --- /dev/null +++ b/test.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 +from panda.python.spi import PandaSpiHandle + + +h = PandaSpiHandle() +with h.dev.acquire() as spi: + for _ in range(10): + print(_, bytes(spi.xfer2([0]*4096)[:10])) From 80a66b4ce8a189e657341f2a339701b9fb2fcd48 Mon Sep 17 00:00:00 2001 From: Comma Device Date: Sat, 18 Oct 2025 01:56:19 +0000 Subject: [PATCH 17/17] this fixes it... --- board/drivers/interrupts.h | 6 ++++++ board/drivers/spi_declarations.h | 2 +- board/drivers/timers.h | 2 -- board/stm32h7/llspi.h | 16 ++++++++++------ board/stm32h7/llusb.h | 8 ++++---- scripts/spi_test.py | 13 ++++++++++++- 6 files changed, 33 insertions(+), 14 deletions(-) diff --git a/board/drivers/interrupts.h b/board/drivers/interrupts.h index 2d9ef80b478..912bdc0e8b5 100644 --- a/board/drivers/interrupts.h +++ b/board/drivers/interrupts.h @@ -18,6 +18,10 @@ void handle_interrupt(IRQn_Type irq_type){ if (irq_type != 0x3a && irq_type != 0x3b && irq_type != 0x54) { print("irq: "); puth(irq_type); print("\n"); } + + interrupts[irq_type].handler(); + return; + static uint8_t interrupt_depth = 0U; static uint32_t last_time = 0U; ENTER_CRITICAL(); @@ -49,6 +53,8 @@ void handle_interrupt(IRQn_Type irq_type){ // Every second void interrupt_timer_handler(void) { + INTERRUPT_TIMER->SR = 0; + return; if (INTERRUPT_TIMER->SR != 0U) { for (uint16_t i = 0U; i < NUM_INTERRUPTS; i++) { // Log IRQ call rate faults diff --git a/board/drivers/spi_declarations.h b/board/drivers/spi_declarations.h index 067f992fd89..f3397a87579 100644 --- a/board/drivers/spi_declarations.h +++ b/board/drivers/spi_declarations.h @@ -2,7 +2,7 @@ #include "board/crc.h" -#define SPI_TIMEOUT_US 10000U +#define SPI_TIMEOUT_US 1000U // got max rate from hitting a non-existent endpoint // in a tight loop, plus some buffer diff --git a/board/drivers/timers.h b/board/drivers/timers.h index 5153951c3c8..14aea7ff0bc 100644 --- a/board/drivers/timers.h +++ b/board/drivers/timers.h @@ -16,14 +16,12 @@ uint32_t microsecond_timer_get(void) { } void interrupt_timer_init(void) { - /* enable_interrupt_timer(); REGISTER_INTERRUPT(INTERRUPT_TIMER_IRQ, interrupt_timer_handler, 2U, FAULT_INTERRUPT_RATE_INTERRUPTS) register_set(&(INTERRUPT_TIMER->PSC), ((uint16_t)(15.25*APB1_TIMER_FREQ)-1U), 0xFFFFU); register_set(&(INTERRUPT_TIMER->DIER), TIM_DIER_UIE, 0x5F5FU); register_set(&(INTERRUPT_TIMER->CR1), TIM_CR1_CEN, 0x3FU); INTERRUPT_TIMER->SR = 0; - */ NVIC_DisableIRQ(INTERRUPT_TIMER_IRQ); } diff --git a/board/stm32h7/llspi.h b/board/stm32h7/llspi.h index e33d8d26c0b..151538a6a2d 100644 --- a/board/stm32h7/llspi.h +++ b/board/stm32h7/llspi.h @@ -29,6 +29,7 @@ void llspi_mosi_dma(uint8_t *addr) { SPI4->TXDR = 0xdddddddd; // match the UDR bytes } +static uint32_t dma_len = 0; static uint32_t dma_start_ts = 0; // panda -> master DMA start @@ -39,9 +40,11 @@ void llspi_miso_dma(uint8_t *addr, int len) { register_clear_bits(&(SPI4->CR1), SPI_CR1_SPE); register_set(&(SPI4->UDRDR), 0xcc, 0xFFFFU); // set under-run value for debugging + if (len < 0x44) len = 0x44; // setup source and length register_set(&(DMA2_Stream3->M0AR), (uint32_t)addr, 0xFFFFFFFFU); DMA2_Stream3->NDTR = len; + dma_len = (uint32_t)len; // clear under-run while we were reading SPI4->IFCR |= (0x1FFU << 3U); @@ -74,16 +77,17 @@ static void DMA2_Stream3_IRQ_Handler(void) { DMA2->LIFCR = DMA_LIFCR_CTCIF3; //spi_tx_dma_done = true; - //bool timed_out = false; - uint32_t start_time = microsecond_timer_get(); + bool timed_out = false; while (!(SPI4->SR & SPI_SR_TXC)) { - if (get_ts_elapsed(microsecond_timer_get(), start_time) > SPI_TIMEOUT_US) { - //timed_out = true; + if (get_ts_elapsed(microsecond_timer_get(), dma_done_ts) > SPI_TIMEOUT_US) { + timed_out = true; break; } } - uint32_t diff = get_ts_elapsed(microsecond_timer_get(), start_time); - //print("diff: 0x"); puth4(get_ts_elapsed(start_time, dma_start_ts)); print(", 0x"); puth4(diff); print("\n"); + uint32_t txc = get_ts_elapsed(microsecond_timer_get(), dma_done_ts); + uint32_t es = get_ts_elapsed(dma_done_ts, dma_start_ts); + print("DMA end-start: 0x"); puth4(es); print(", TXC 0x"); puth4(txc); print(", tot 0x"); puth4(txc+es); print(", len 0x"); puth4(dma_len); print("\n"); + if (timed_out) print("timed out!!\n"); // clear out FIFO volatile uint8_t dat = SPI4->TXDR; diff --git a/board/stm32h7/llusb.h b/board/stm32h7/llusb.h index f1bcf16ad5e..4e0e15d3257 100644 --- a/board/stm32h7/llusb.h +++ b/board/stm32h7/llusb.h @@ -5,7 +5,7 @@ USB_OTG_GlobalTypeDef *USBx = USB_OTG_HS; static void OTG_HS_IRQ_Handler(void) { NVIC_DisableIRQ(OTG_HS_IRQn); usb_irqhandler(); - NVIC_EnableIRQ(OTG_HS_IRQn); + NVIC_DisableIRQ(OTG_HS_IRQn); } void usb_init(void) { @@ -61,7 +61,7 @@ void usb_init(void) { USBx->GINTMSK = 0U; // Clear any pending interrupts USBx->GINTSTS = 0xBFFFFFFFU; - // Enable interrupts matching to the Device mode ONLY + // Disable interrupts matching to the Device mode ONLY USBx->GINTMSK = USB_OTG_GINTMSK_USBRST | USB_OTG_GINTMSK_ENUMDNEM | USB_OTG_GINTMSK_OTGINT | USB_OTG_GINTMSK_RXFLVLM | USB_OTG_GINTMSK_GONAKEFFM | USB_OTG_GINTMSK_GINAKEFFM | USB_OTG_GINTMSK_OEPINT | USB_OTG_GINTMSK_IEPINT | @@ -69,11 +69,11 @@ void usb_init(void) { // Set USB Turnaround time USBx->GUSBCFG |= ((USBD_FS_TRDT_VALUE << 10) & USB_OTG_GUSBCFG_TRDT); - // Enables the controller's Global Int in the AHB Config reg + // Disables the controller's Global Int in the AHB Config reg USBx->GAHBCFG |= USB_OTG_GAHBCFG_GINT; // Soft disconnect disable: USBx_DEVICE->DCTL &= ~(USB_OTG_DCTL_SDIS); // enable the IRQ - NVIC_EnableIRQ(OTG_HS_IRQn); + NVIC_DisableIRQ(OTG_HS_IRQn); } diff --git a/scripts/spi_test.py b/scripts/spi_test.py index eda2e61efc8..9986ab399b0 100755 --- a/scripts/spi_test.py +++ b/scripts/spi_test.py @@ -5,14 +5,25 @@ from datetime import datetime from collections import defaultdict, deque +os.environ['NO_RETRY'] = '1' # no spi retries from panda import Panda, PandaSpiException + if __name__ == "__main__": s = defaultdict(lambda: deque(maxlen=30)) def avg(k): return sum(s[k])/len(s[k]) + core = 6 + from openpilot.common.realtime import config_realtime_process + #config_realtime_process(core, 10) + #os.system(f"echo {core} | sudo tee /proc/irq/10/smp_affinity_list") + #os.system("sudo chrt -f -p 1 $(pgrep -f spi0)") + #print(f"sudo taskset -pc {core} $(pgrep -f spi0)") + #os.system(f"sudo taskset -pc {core} $(pgrep -f spi0)") + p = Panda() + p.reset() start = datetime.now() le = p.health()['spi_error_count'] while True: @@ -34,6 +45,6 @@ def avg(k): le = err print( - f"{avg('hz'):04.0f}Hz {avg('err'):04.0f} errors [{cnt:04d}Hz {s['err'][-1]:04d} errors]" + f"{avg('hz'):04.0f}Hz {avg('err'):04.0f} errors [{cnt:04d}Hz {s['err'][-1]:04d} errors] {datetime.now() - start}" )