openpilot v0.9.2 release

date: 2023-05-18T22:02:42
master commit: c7d3b28b93faa6c955fb24bc64031512ee985ee9
This commit is contained in:
Vehicle Researcher
2023-05-18 22:02:42 +00:00
parent bcd4bb4821
commit cfd8323ccb
446 changed files with 8909 additions and 6008 deletions
+179 -23
View File
@@ -1,3 +1,4 @@
import binascii
import os
import fcntl
import math
@@ -7,9 +8,10 @@ import logging
import threading
from contextlib import contextmanager
from functools import reduce
from typing import List
from typing import List, Optional
from .base import BaseHandle
from .base import BaseHandle, BaseSTBootloaderHandle, TIMEOUT
from .constants import McuType, MCU_TYPE_BY_IDCODE
try:
import spidev
@@ -23,10 +25,10 @@ DACK = 0x85
NACK = 0x1F
CHECKSUM_START = 0xAB
ACK_TIMEOUT_SECONDS = 0.1
MIN_ACK_TIMEOUT_MS = 100
MAX_XFER_RETRY_COUNT = 5
USB_MAX_SIZE = 0x40
XFER_SIZE = 1000
DEV_PATH = "/dev/spidev0.0"
@@ -56,7 +58,14 @@ class SpiDevice:
"""
Provides locked, thread-safe access to a panda's SPI interface.
"""
def __init__(self, speed=30000000):
# 50MHz is the max of the 845. older rev comma three
# may not support the full 50MHz
MAX_SPEED = 50000000
def __init__(self, speed=MAX_SPEED):
assert speed <= self.MAX_SPEED
if not os.path.exists(DEV_PATH):
raise PandaSpiUnavailable(f"SPI device not found: {DEV_PATH}")
if spidev is None:
@@ -94,10 +103,12 @@ class PandaSpiHandle(BaseHandle):
cksum ^= b
return cksum
def _wait_for_ack(self, spi, ack_val: int) -> None:
def _wait_for_ack(self, spi, ack_val: int, timeout: int, tx: int) -> None:
timeout_s = max(MIN_ACK_TIMEOUT_MS, timeout) * 1e-3
start = time.monotonic()
while (time.monotonic() - start) < ACK_TIMEOUT_SECONDS:
dat = spi.xfer2(b"\x12")[0]
while (timeout == 0) or ((time.monotonic() - start) < timeout_s):
dat = spi.xfer2([tx, ])[0]
if dat == NACK:
raise PandaSpiNackResponse
elif dat == ack_val:
@@ -105,7 +116,7 @@ class PandaSpiHandle(BaseHandle):
raise PandaSpiMissingAck
def _transfer(self, spi, endpoint: int, data, max_rx_len: int = 1000) -> bytes:
def _transfer(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 1000) -> bytes:
logging.debug("starting transfer: endpoint=%d, max_rx_len=%d", endpoint, max_rx_len)
logging.debug("==============================================")
@@ -119,7 +130,7 @@ class PandaSpiHandle(BaseHandle):
spi.xfer2(packet)
logging.debug("- waiting for header ACK")
self._wait_for_ack(spi, HACK)
self._wait_for_ack(spi, HACK, timeout, 0x11)
# send data
logging.debug("- sending data")
@@ -127,11 +138,13 @@ class PandaSpiHandle(BaseHandle):
spi.xfer2(packet)
logging.debug("- waiting for data ACK")
self._wait_for_ack(spi, DACK)
self._wait_for_ack(spi, DACK, timeout, 0x13)
# get response length, then response
response_len_bytes = bytes(spi.xfer2(b"\x00" * 2))
response_len = struct.unpack("<H", response_len_bytes)[0]
if response_len > max_rx_len:
raise PandaSpiException("response length greater than max")
logging.debug("- receiving response")
dat = bytes(spi.xfer2(b"\x00" * (response_len + 1)))
@@ -141,34 +154,177 @@ class PandaSpiHandle(BaseHandle):
return dat[:-1]
except PandaSpiException as e:
exc = e
logging.debug("SPI transfer failed, %d retries left", n, exc_info=True)
logging.debug("SPI transfer failed, %d retries left", MAX_XFER_RETRY_COUNT - n - 1, exc_info=True)
raise exc
# libusb1 functions
def close(self):
self.dev.close()
def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = 0):
def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = TIMEOUT):
with self.dev.acquire() as spi:
return self._transfer(spi, 0, struct.pack("<BHHH", request, value, index, 0))
return self._transfer(spi, 0, struct.pack("<BHHH", request, value, index, 0), timeout)
def controlRead(self, request_type: int, request: int, value: int, index: int, length: int, timeout: int = 0):
def controlRead(self, request_type: int, request: int, value: int, index: int, length: int, timeout: int = TIMEOUT):
with self.dev.acquire() as spi:
return self._transfer(spi, 0, struct.pack("<BHHH", request, value, index, length))
return self._transfer(spi, 0, struct.pack("<BHHH", request, value, index, length), timeout)
# TODO: implement these properly
def bulkWrite(self, endpoint: int, data: List[int], timeout: int = 0) -> int:
def bulkWrite(self, endpoint: int, data: List[int], timeout: int = TIMEOUT) -> int:
with self.dev.acquire() as spi:
for x in range(math.ceil(len(data) / USB_MAX_SIZE)):
self._transfer(spi, endpoint, data[USB_MAX_SIZE*x:USB_MAX_SIZE*(x+1)])
for x in range(math.ceil(len(data) / XFER_SIZE)):
self._transfer(spi, endpoint, data[XFER_SIZE*x:XFER_SIZE*(x+1)], timeout)
return len(data)
def bulkRead(self, endpoint: int, length: int, timeout: int = 0) -> bytes:
def bulkRead(self, endpoint: int, length: int, timeout: int = TIMEOUT) -> bytes:
ret: List[int] = []
with self.dev.acquire() as spi:
for _ in range(math.ceil(length / USB_MAX_SIZE)):
d = self._transfer(spi, endpoint, [], max_rx_len=USB_MAX_SIZE)
for _ in range(math.ceil(length / XFER_SIZE)):
d = self._transfer(spi, endpoint, [], timeout, max_rx_len=XFER_SIZE)
ret += d
if len(d) < USB_MAX_SIZE:
if len(d) < XFER_SIZE:
break
return bytes(ret)
class STBootloaderSPIHandle(BaseSTBootloaderHandle):
"""
Implementation of the STM32 SPI bootloader protocol described in:
https://www.st.com/resource/en/application_note/an4286-spi-protocol-used-in-the-stm32-bootloader-stmicroelectronics.pdf
"""
SYNC = 0x5A
ACK = 0x79
NACK = 0x1F
def __init__(self):
self.dev = SpiDevice(speed=1000000)
# say hello
try:
with self.dev.acquire() as spi:
spi.xfer([self.SYNC, ])
try:
self._get_ack(spi)
except (PandaSpiNackResponse, PandaSpiMissingAck):
# NACK ok here, will only ACK the first time
pass
self._mcu_type = MCU_TYPE_BY_IDCODE[self.get_chip_id()]
except PandaSpiException:
raise PandaSpiException("failed to connect to panda") # pylint: disable=W0707
def _get_ack(self, spi, timeout=1.0):
data = 0x00
start_time = time.monotonic()
while data not in (self.ACK, self.NACK) and (time.monotonic() - start_time < timeout):
data = spi.xfer([0x00, ])[0]
time.sleep(0.001)
spi.xfer([self.ACK, ])
if data == self.NACK:
raise PandaSpiNackResponse
elif data != self.ACK:
raise PandaSpiMissingAck
def _cmd_no_retry(self, cmd: int, data: Optional[List[bytes]] = None, read_bytes: int = 0, predata=None) -> bytes:
ret = b""
with self.dev.acquire() as spi:
# sync + command
spi.xfer([self.SYNC, ])
spi.xfer([cmd, cmd ^ 0xFF])
self._get_ack(spi, timeout=0.1)
# "predata" - for commands that send the first data without a checksum
if predata is not None:
spi.xfer(predata)
self._get_ack(spi)
# send data
if data is not None:
for d in data:
if predata is not None:
spi.xfer(d + self._checksum(predata + d))
else:
spi.xfer(d + self._checksum(d))
self._get_ack(spi, timeout=20)
# receive
if read_bytes > 0:
ret = spi.xfer([0x00, ]*(read_bytes + 1))[1:]
if data is None or len(data) == 0:
self._get_ack(spi)
return bytes(ret)
def _cmd(self, cmd: int, data: Optional[List[bytes]] = None, read_bytes: int = 0, predata=None) -> bytes:
exc = PandaSpiException()
for n in range(MAX_XFER_RETRY_COUNT):
try:
return self._cmd_no_retry(cmd, data, read_bytes, predata)
except PandaSpiException as e:
exc = e
logging.debug("SPI transfer failed, %d retries left", MAX_XFER_RETRY_COUNT - n - 1, exc_info=True)
raise exc
def _checksum(self, data: bytes) -> bytes:
if len(data) == 1:
ret = data[0] ^ 0xFF
else:
ret = reduce(lambda a, b: a ^ b, data)
return bytes([ret, ])
# *** Bootloader commands ***
def read(self, address: int, length: int):
data = [struct.pack('>I', address), struct.pack('B', length - 1)]
return self._cmd(0x11, data=data, read_bytes=length)
def get_chip_id(self) -> int:
r = self._cmd(0x02, read_bytes=3)
assert r[0] == 1 # response length - 1
return ((r[1] << 8) + r[2])
def go_cmd(self, address: int) -> None:
self._cmd(0x21, data=[struct.pack('>I', address), ])
# *** helpers ***
def get_uid(self):
dat = self.read(McuType.H7.config.uid_address, 12)
return binascii.hexlify(dat).decode()
def erase_sector(self, sector: int):
p = struct.pack('>H', 0) # number of sectors to erase
d = struct.pack('>H', sector)
self._cmd(0x44, data=[d, ], predata=p)
# *** PandaDFU API ***
def erase_app(self):
self.erase_sector(1)
def erase_bootstub(self):
self.erase_sector(0)
def get_mcu_type(self):
return self._mcu_type
def clear_status(self):
pass
def close(self):
self.dev.close()
def program(self, address, dat):
bs = 256 # max block size for writing to flash over SPI
dat += b"\xFF" * ((bs - len(dat)) % bs)
for i in range(0, len(dat) // bs):
block = dat[i * bs:(i + 1) * bs]
self._cmd(0x31, data=[
struct.pack('>I', address + i*bs),
bytes([len(block) - 1]) + block,
])
def jump(self, address):
self.go_cmd(self._mcu_type.config.bootstub_address)