diff --git a/panda/python/__init__.py b/panda/python/__init__.py index 0c4b8d9ada..ec5f5d4382 100644 --- a/panda/python/__init__.py +++ b/panda/python/__init__.py @@ -275,7 +275,7 @@ class Panda: @classmethod def usb_connect(cls, serial, claim=True, no_error=False): - handle, usb_serial, bootstub = None, None, None + handle, usb_serial, bootstub, bootstub_hw_type = None, None, None, None context = usb1.USBContext() context.open() try: @@ -301,13 +301,17 @@ class Panda: handle.claimInterface(0) # handle.setInterfaceAltSetting(0, 0) # Issue in USB stack + if bootstub: + bcd = device.getbcdDevice() + if type(bcd) is int and 0 <= bcd <= 0xffff and bcd != 0x2300: + bootstub_hw_type = bytes([bcd >> 8]) break except Exception: logger.exception("USB connect error") usb_handle = None if handle is not None: - usb_handle = PandaUsbHandle(handle) + usb_handle = PandaUsbHandle(handle, bootstub_hw_type=bootstub_hw_type) else: context.close() @@ -616,7 +620,14 @@ class Panda: return bytes(part_1 + part_2) def get_type(self): - return self._handle.controlRead(Panda.REQUEST_IN, 0xc1, 0, 0, 0x40) + result = self._handle.controlRead(Panda.REQUEST_IN, 0xc1, 0, 0, 0x40) + # Older USB bootstubs identify the board in bcdDevice instead of endpoint C1. + # Accept that identity only for the exact legacy unsupported-request response. + if (self.bootstub and isinstance(self._handle, PandaUsbHandle) and result is not None and + result.startswith(b"\xff\x00\xc1\x3e\xde\xad\xd0\x0d") and + self._handle.bootstub_hw_type in self.SUPPORTED_DEVICES): + return self._handle.bootstub_hw_type + return result def get_packets_versions(self): dat = self._handle.controlRead(Panda.REQUEST_IN, 0xdd, 0, 0, 8) diff --git a/panda/python/usb.py b/panda/python/usb.py index 0a012896be..dde80d9621 100644 --- a/panda/python/usb.py +++ b/panda/python/usb.py @@ -4,8 +4,9 @@ from .base import BaseHandle, BaseSTBootloaderHandle, TIMEOUT from .constants import McuType class PandaUsbHandle(BaseHandle): - def __init__(self, libusb_handle): + def __init__(self, libusb_handle, *, bootstub_hw_type: bytes | None = None): self._libusb_handle = libusb_handle + self.bootstub_hw_type = bootstub_hw_type def close(self): self._libusb_handle.close() diff --git a/panda/tests/test_legacy_usb_bootstub.py b/panda/tests/test_legacy_usb_bootstub.py new file mode 100644 index 0000000000..c2534fc22b --- /dev/null +++ b/panda/tests/test_legacy_usb_bootstub.py @@ -0,0 +1,90 @@ +"""Actual Panda USB construction preserves supported pre-7.0 bootstub identities.""" +import struct +from unittest.mock import Mock + +import pytest +from panda import Panda, McuType + +LEGACY_RESPONSE = b"\xff\x00\xc1\x3e\xde\xad\xd0\x0d" + bytes(56) +SERIAL = "010002000300040005000600" + + +@pytest.fixture(params=["linux", "darwin"]) +def usb_device(monkeypatch, request): + monkeypatch.setattr("panda.sys.platform", request.param) + device = Mock() + device.getVendorID.return_value = 0x3801 + device.getProductID.return_value = 0xddee + device.getSerialNumber.return_value = SERIAL + device.getbcdDevice.return_value = 0x0700 + handle = device.open.return_value + response = {"hardware": LEGACY_RESPONSE} + + def control_read(_kind, request, _value, _index, _length, _timeout): + if request == 0xc1: + return response["hardware"] + if request == 0xdd: + return struct.pack("