From e7f36a2992b353d7d66fbb697d166d4de8718c99 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 28 Jan 2023 14:41:52 -0800 Subject: [PATCH] python: assume F4 MCU for bootstubs without bcdDevice set (#1229) * python: assume F4 MCU for bootstubs without bcdDevice set * cleanup --- python/__init__.py | 35 ++++++++++--- tests/hitl/1_program.py | 49 +++++++++++++----- tests/hitl/known_bootstub/bootstub.panda.bin | Bin 14848 -> 0 bytes .../hitl/known_bootstub/bootstub.panda_h7.bin | Bin ...bootstub_f4_first_dos_production.panda.bin | Bin 0 -> 14220 bytes .../bootstub_f4_only_bcd.panda.bin | Bin 0 -> 14208 bytes 6 files changed, 62 insertions(+), 22 deletions(-) delete mode 100755 tests/hitl/known_bootstub/bootstub.panda.bin mode change 100755 => 100644 tests/hitl/known_bootstub/bootstub.panda_h7.bin create mode 100644 tests/hitl/known_bootstub/bootstub_f4_first_dos_production.panda.bin create mode 100755 tests/hitl/known_bootstub/bootstub_f4_only_bcd.panda.bin diff --git a/python/__init__.py b/python/__init__.py index 71d416bd..424dd55c 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -227,8 +227,6 @@ class Panda: self._disable_checks = disable_checks self._handle = None - self._bcd_device = None - self.can_rx_overflow_buffer = b'' # connect and set mcu type @@ -253,13 +251,30 @@ class Panda: self._handle = None # try USB first, then SPI - self._handle, serial, self.bootstub, self._bcd_device = self.usb_connect(self._serial, claim=claim, wait=wait) + self._handle, serial, self.bootstub, bcd = self.usb_connect(self._serial, claim=claim, wait=wait) if self._handle is None: - self._handle, serial, self.bootstub, _ = self.spi_connect(self._serial) + self._handle, serial, self.bootstub, bcd = self.spi_connect(self._serial) if self._handle is None: raise Exception("failed to connect to panda") + # Some fallback logic to determine panda and MCU type for old bootstubs, + # since we now support multiple MCUs and need to know which fw to flash. + # Three cases to consider: + # A) oldest bootstubs don't have any way to distinguish + # MCU or panda type + # B) slightly newer (~2 weeks after first C3's built) bootstubs + # have the panda type set in the USB bcdDevice + # C) latest bootstubs also implement the endpoint for panda type + self._bcd_hw_type = None + ret = self._handle.controlRead(Panda.REQUEST_IN, 0xc1, 0, 0, 0x40) + missing_hw_type_endpoint = self.bootstub and ret.startswith(b'\xff\x00\xc1\x3e\xde\xad\xd0\x0d') + if missing_hw_type_endpoint and bcd is not None: + self._bcd_hw_type = bcd + + # For case A, we assume F4 MCU type, since all H7 pandas should be case B at worst + self._assume_f4_mcu = (self._bcd_hw_type is None) and missing_hw_type_endpoint + self._serial = serial self._mcu_type = self.get_mcu_type() self.health_version, self.can_version, self.can_health_version = self.get_packets_versions() @@ -584,10 +599,9 @@ class Panda: def get_type(self): ret = self._handle.controlRead(Panda.REQUEST_IN, 0xc1, 0, 0, 0x40) - # bootstub doesn't implement this call, so fallback to bcdDevice - invalid_type = self.bootstub and (ret is None or len(ret) != 1) - if invalid_type and self._bcd_device is not None: - ret = self._bcd_device + # old bootstubs don't implement this endpoint, see comment in Panda.device + if self._bcd_hw_type is not None and (ret is None or len(ret) != 1): + ret = self._bcd_hw_type return ret @@ -608,6 +622,11 @@ class Panda: return McuType.F4 elif hw_type in Panda.H7_DEVICES: return McuType.H7 + else: + # have to assume F4, see comment in Panda.connect + if self._assume_f4_mcu: + return McuType.F4 + raise ValueError(f"unknown HW type: {hw_type}") def has_obd(self): diff --git a/tests/hitl/1_program.py b/tests/hitl/1_program.py index 20c1be21..c129c99b 100644 --- a/tests/hitl/1_program.py +++ b/tests/hitl/1_program.py @@ -5,6 +5,8 @@ from panda import Panda, PandaDFU, McuType, BASEDIR from .helpers import test_all_pandas, panda_connect_and_init, check_signature +# TODO: make more comprehensive bootstub tests and run on a few production ones + current +# TODO: also test release-signed app @test_all_pandas @panda_connect_and_init def test_a_known_bootstub(p): @@ -12,25 +14,44 @@ def test_a_known_bootstub(p): Test that compiled app can work with known production bootstub """ known_bootstubs = { - McuType.F4: "bootstub.panda.bin", - McuType.H7: "bootstub.panda_h7.bin", + # covers the two cases listed in Panda.connect + McuType.F4: [ + # case A - no bcdDevice or panda type, has to assume F4 + "bootstub_f4_first_dos_production.panda.bin", + + # case B - just bcdDevice + "bootstub_f4_only_bcd.panda.bin", + ], + McuType.H7: ["bootstub.panda_h7.bin"], } - p.reset(enter_bootstub=True) - p.reset(enter_bootloader=True) + for kb in known_bootstubs[p.get_mcu_type()]: + app_ids = (p.get_mcu_type(), p.get_usb_serial()) + assert None not in app_ids - dfu_serial = PandaDFU.st_serial_to_dfu_serial(p._serial, p._mcu_type) - assert Panda.wait_for_dfu(dfu_serial, timeout=30) + p.reset(enter_bootstub=True) + p.reset(enter_bootloader=True) - dfu = PandaDFU(dfu_serial) - fn = known_bootstubs[p._mcu_type] - with open(os.path.join(BASEDIR, "tests/hitl/known_bootstub", fn), "rb") as f: - code = f.read() + dfu_serial = PandaDFU.st_serial_to_dfu_serial(p._serial, p._mcu_type) + assert Panda.wait_for_dfu(dfu_serial, timeout=30) - dfu.program_bootstub(code) - p.connect(True, True) - p.flash() - check_signature(p) + dfu = PandaDFU(dfu_serial) + with open(os.path.join(BASEDIR, "tests/hitl/known_bootstub", kb), "rb") as f: + code = f.read() + + dfu.program_bootstub(code) + + p.connect(claim=False, wait=True) + + # check for MCU or serial mismatch + with Panda(p._serial, claim=False) as np: + bootstub_ids = (np.get_mcu_type(), np.get_usb_serial()) + assert app_ids == bootstub_ids + + # ensure we can flash app and it jumps to app + p.flash() + check_signature(p) + assert not p.bootstub @test_all_pandas @panda_connect_and_init diff --git a/tests/hitl/known_bootstub/bootstub.panda.bin b/tests/hitl/known_bootstub/bootstub.panda.bin deleted file mode 100755 index 6f658e2de2fdd46bde874acd1a8c23bb92d5b655..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14848 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 E0dr`|wg3PC diff --git a/tests/hitl/known_bootstub/bootstub.panda_h7.bin b/tests/hitl/known_bootstub/bootstub.panda_h7.bin old mode 100755 new mode 100644 diff --git a/tests/hitl/known_bootstub/bootstub_f4_first_dos_production.panda.bin b/tests/hitl/known_bootstub/bootstub_f4_first_dos_production.panda.bin new file mode 100644 index 0000000000000000000000000000000000000000..786acf639e738c30ae58681a830e0b91ca281b24 GIT binary patch literal 14220 zcmch833wD$*7mKcUMfi^=`27tLN6dmnk6A1VNr_iQ0b%t0V5)yLpL$f0fZ11aY38K zF^Gza&OjIxMIDt<0gXyyY@N7_ien<8AR^$_ETSC}s0vBCzV}vl5S{t{|NFn^f2Q-C zb5Gs6_td%D*{ag5FedYG!r$_5fc);)+lE&6(zm1#ektHKz}Le9{^_nZ2$?-1K?5#9|K4P^Z{f7%z(jwp@2fb7{CO; zB!CMr4KM?6BcKXU16T}L4!8rb8gMV*0l+4}7QhpLX8=0^y8*8QngGp!4*{P74g&i3 zA-olE1oR0&5O5aI4(J3Z04?sv0g?dy0J(tSfKh;PfZnN{2)YC?8BhtB377+*_p4DZ z04#}K)4E$PqgR1m3s?`>2zU&z74RfrV4=#xg9-Su>n}e1>>P{I*NCa+de4G-n&p(Ns>r;vs{ zX_`k$&cw0WM>Gu%jCF#<%$lHj$Wb7B|VOOq=98k0)#FrxWIG zlJ7o_lHDZVb(|>xWijQJITqVR$uU9GsEXT@t|e%!d-%s8xA?KQI1{kok!aN)2br7D~^Ph`ds8{4K~ zhkup1Q`45pWUNe;nK-*}znzmAo79?7My!2MlPoit^>$k;Q|Pl@Xk|?C7=NgV8=iET zOgg1kB1RD)gBvn*hlS_g3=*?Hi02c`fkBtR%9@2b11ob2b+N3>E@Z}`h3R0>l#g?X zUDLX2zDU}P9BG@XO%*F;O=gULr@a`Z+$xw;Ma_so^Q#st5T5Z4^ezypgo@McOPMhT zs@hsxPNhEAa;oo7`b9U1`^4p%4SnaE_w8XXxI*;d+8Q0)`RqdO z+?EukU~2B{;)>I+u{*>mVti|5F;`tz%%6$js*A@LJF3@IzgMlfd78RshlpFmKH}%W z!MU+t|M9ezqnwU{(<>Zz{!53s@KA7EZv5BcX)S9Jbhw}5;Mx*#|7Z$#{>RF!!hZyN zdzGQ~J@i`ZxUR4%80X^LfNHL-zR&@j>)3JrPboVtY^YjWy#sa4)mj&KGqDis+oxD- zZOL}b6=R(=FLdIwX1TEi`&Jj4z`d4Ma^~Ax(PExKd?4)^J86}h-burXE=X85!N#>Q zEZeF`lNDk~5r4r6l`7D)dQ9mt`Q$FfqM@^{SEMhMvt-ig>z3nO#aRp*Nj@`tnD@zxd!kcXmyV zwzHLS5le=rwuBgo@HuoU@0wM!W)~aMu*@S&!!`Pe0c~k)Ju@QKr?JfPu)EMQK*loy z)xX-ZO0?xd499F@wldbr%kI~faIUl(Ux+1dw+jTl2rB@aYD*ZaJ%)q^5bh!gu`Y*f zp?3r11Ub&SwyxxM&wY(vQ~tFjksh@C{a@tn<+W>e5i6Brt(`eQ<%C3q7lLdtCvL(% zgFMg>ok3$GGp4uB*pwtQ6{vSf!Td&I-DGE4i7CJ7y&Lx5@C<0C@O-c$q~xCuvM$Eb zP)jAFay@1~DN=iYE35QcPZKiL-COU-;?6kO<}-ii z)}A&BZ@0%_^g~a{6MSod0X`7wFr_DAIyAK%7sRduX)&1r@kCsYa?_SOkSIHz1$ z5i-QJig8_}eU{ASZ`i%lp4c*F?>h8(1LuHk-J#B5(@A+FbG0IUpuD!{WRMK|xy4|; z-Vt+#aHTStI?Fpv7$Zy*8oV>zV;v3dV?jEumUv={#U6V-5G#J@P^4D^MU6?N$>n5F zocER8b+1+3Ugl*AtLoysnchm_K5vShYpLk)VWf8{Rkt(aj_j$bOW7NFJ~Ilk7H+dM zZ3gC8Yf%&BO`j3jfw-5?6t1-=x#>QHs>S+{0BvJzj4MyIa$Tf&2cEUVUFF?eqIYe* z-Ld;~=#>V$=CG-z>7-Fmq~d@Pd!O!E%D1So?lnVs_3g~4>N?I_O1MD}2v+Df_9D}k z?!YRJP$bu7QZ}6|65ju@sEPKiZPbbhnEm*2i~{3htf~1N`qk}Lq+4;i3&b4E%0Dml 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 literal 0 HcmV?d00001 diff --git a/tests/hitl/known_bootstub/bootstub_f4_only_bcd.panda.bin b/tests/hitl/known_bootstub/bootstub_f4_only_bcd.panda.bin new file mode 100755 index 0000000000000000000000000000000000000000..000fd26fe47b95ff212f30ecaa02f6b99e413b53 GIT binary patch literal 14208 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 literal 0 HcmV?d00001