test: use pristine upstream panda SPI firmware (stage 2d)

This commit is contained in:
1okko
2026-10-20 21:37:32 +08:00
parent f426cf8ac8
commit c200eec550
11 changed files with 139 additions and 370 deletions
+8 -29
View File
@@ -121,8 +121,6 @@ ensure_health_packet_version = partial(ensure_version, "health", "HEALTH_PACKET_
class Panda:
SERIAL_DEBUG = 0
SERIAL_SOM_DEBUG = 4
USB_VIDS = (0xbbaa, 0x3801) # 0x3801 is comma's registered VID
USB_PIDS = (0xddee, 0xddcc)
@@ -697,16 +695,6 @@ class Panda:
def set_canfd_auto(self, bus, auto):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe8, bus, int(auto), b'')
def set_uart_baud(self, uart, rate):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe4, uart, int(rate / 300), b'')
def set_uart_parity(self, uart, parity):
# parity, 0=off, 1=even, 2=odd
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe2, uart, parity, b'')
def set_uart_callback(self, uart, install):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe3, uart, int(install), b'')
# ******************* can *******************
# The panda will NAK CAN writes when there is CAN congestion.
@@ -752,24 +740,15 @@ class Panda:
"""
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf1, bus, 0, b'')
# ******************* serial *******************
def serial_read(self, port_number, maxlen=1024):
ret = b''
while 1:
r = bytes(self._handle.controlRead(Panda.REQUEST_IN, 0xe0, port_number, 0, 0x40))
if len(r) == 0 or len(ret) >= maxlen:
def debug_read(self, maxlen=1024):
"""Read up to maxlen bytes of internal debug logs."""
ret = bytearray()
while len(ret) < maxlen:
dat = self._handle.controlRead(Panda.REQUEST_IN, 0xb6, 0, 0, min(0x40, maxlen - len(ret)))
if not dat:
break
ret += r
return ret
def serial_write(self, port_number, ln):
ret = 0
if isinstance(ln, str):
ln = bytes(ln, 'utf-8')
for i in range(0, len(ln), 0x20):
ret += self._handle.bulkWrite(2, struct.pack("B", port_number) + ln[i:i + 0x20])
return ret
ret.extend(dat)
return bytes(ret)
def send_heartbeat(self, engaged=True):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf3, engaged, 0, b'')
+83 -36
View File
@@ -1,4 +1,5 @@
import binascii
import ctypes
import os
import math
import time
@@ -11,24 +12,11 @@ from .base import BaseHandle, BaseSTBootloaderHandle, TIMEOUT
from .constants import McuType, MCU_TYPE_BY_IDCODE
from .utils import logger
# No fcntl on Windows
try:
import fcntl
except ImportError:
fcntl = None # type: ignore
# No spidev on MacOS/Windows
try:
import spidev
except ImportError:
spidev = None
# Constants
SYNC = 0x5A
HACK = 0x79
DACK = 0x85
NACK = 0x1F
CHECKSUM_START = 0xAB
# Protocol, see board/drivers/spi.h
SLAVE_READY = 0x3C
ACK = 0xC3
NACK = 0x5A
INFO_ENDPOINT = 0xF0
REQ_HEADER = struct.Struct("<BIHH") # endpoint, id, tx_len, rx_len
RESP_HEADER = struct.Struct("<BIH") # status, id, len
@@ -66,40 +54,99 @@ class PandaSpiMissingAck(PandaSpiException):
SPI_LOCK = threading.Lock()
SPI_DEVICES = {}
class SpiDevice:
"""
Provides locked, thread-safe access to a panda's SPI interface.
"""Provides locked, thread-safe access to a panda's SPI interface.
xfer2 returns a view valid until the next transfer; xfer returns an owned copy.
"""
MAX_SPEED = 50000000 # max of the SDM845
def __init__(self, speed=MAX_SPEED):
assert speed <= self.MAX_SPEED
# Linux asm-generic ioctl ABI (including aarch64 and x86_64), linux/spi/spidev.h.
SPI_IOC_RD_BITS_PER_WORD = 0x80016B03
SPI_IOC_WR_MAX_SPEED_HZ = 0x40046B04
SPI_IOC_MESSAGE_1 = 0x40206B00
SPI_IOC_TRANSFER = struct.Struct('=QQIIHBBBBBB')
MAX_TRANSFER_SIZE = 4096
def __init__(self, speed=MAX_SPEED):
try:
import fcntl
except ImportError as e:
raise PandaSpiUnavailable("SPI requires Linux") from e
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:
raise PandaSpiUnavailable("spidev is not installed")
self._ioctl = fcntl.ioctl
self._flock = fcntl.flock
self._lock_ex, self._lock_un = fcntl.LOCK_EX, fcntl.LOCK_UN
with SPI_LOCK:
if speed not in SPI_DEVICES:
SPI_DEVICES[speed] = spidev.SpiDev()
SPI_DEVICES[speed].open(0, 0)
SPI_DEVICES[speed].max_speed_hz = speed
self._spidev = SPI_DEVICES[speed]
file = open(DEV_PATH, 'r+b', buffering=0)
try:
self._ioctl(file.fileno(), self.SPI_IOC_WR_MAX_SPEED_HZ, struct.pack('=I', speed))
bits = self._ioctl(file.fileno(), self.SPI_IOC_RD_BITS_PER_WORD, b'\x00')[0]
except BaseException:
file.close()
raise
SPI_DEVICES[speed] = (file, bits)
self._file, bits = SPI_DEVICES[speed]
self._fd = self._file.fileno()
self._buffer = bytearray(self.MAX_TRANSFER_SIZE)
self._view = memoryview(self._buffer)
address = ctypes.addressof(ctypes.c_char.from_buffer(self._buffer))
# The kernel supports using the same buffer for transmit and receive.
self._transfer = bytearray(self.SPI_IOC_TRANSFER.pack(address, address, 0, speed, 0, bits, 0, 0, 0, 0, 0))
self._transfer_words = memoryview(self._transfer).cast('I')
@contextmanager
def acquire(self):
try:
SPI_LOCK.acquire()
fcntl.flock(self._spidev, fcntl.LOCK_EX)
yield self._spidev
finally:
fcntl.flock(self._spidev, fcntl.LOCK_UN)
SPI_LOCK.release()
with SPI_LOCK:
self._flock(self._fd, self._lock_ex)
try:
yield self
finally:
self._flock(self._fd, self._lock_un)
def fileno(self) -> int:
return self._fd
def close(self):
pass
pass # Connections are shared and cached by speed.
@classmethod
def _check_length(cls, length: int):
if not 0 < length <= cls.MAX_TRANSFER_SIZE:
raise ValueError(f"SPI transfer length must be between 1 and {cls.MAX_TRANSFER_SIZE}")
def xfer2(self, data) -> memoryview:
length = len(data)
if not 0 < length <= self.MAX_TRANSFER_SIZE:
raise ValueError(f"SPI transfer length must be between 1 and {self.MAX_TRANSFER_SIZE}")
self._view[:length] = bytes(data)
self._transfer_words[4] = length # len field at byte offset 16
if self._ioctl(self._fd, self.SPI_IOC_MESSAGE_1, self._transfer) != length:
raise OSError("Short SPI transfer")
return self._view[:length]
# Both operations use one SPI_IOC_MESSAGE(1) for the single-block calls in panda.
def xfer(self, data) -> bytes:
return bytes(self.xfer2(data))
def readbytes(self, length: int) -> bytes:
self._check_length(length)
data = os.read(self.fileno(), length)
if len(data) != length:
raise OSError("Short SPI read")
return data
def writebytes(self, data):
data = bytes(data)
self._check_length(len(data))
if os.write(self.fileno(), data) != len(data):
raise OSError("Short SPI write")
class PandaSpiHandle(BaseHandle):
"""