From 23b5130de9d95fa049712da9230f3ce5e076593a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 26 Apr 2023 17:45:04 -0700 Subject: [PATCH 001/197] replay drive: fixes (#1362) * make explicit, there was no bug only because the if statement is identical to above * debug * Revert "debug" This reverts commit 2ec83677c3749f805681c4874b746696767f7634. * Revert "make explicit, there was no bug only because the if statement is identical to above" This reverts commit fcb2a106b9694727ca4b5a9acf4eda3312fc4271. * fixes * clean that up * and this --- tests/safety_replay/replay_drive.py | 14 ++++++-------- tests/safety_replay/test_safety_replay.py | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/tests/safety_replay/replay_drive.py b/tests/safety_replay/replay_drive.py index c9cc135d38..3274dc58e7 100755 --- a/tests/safety_replay/replay_drive.py +++ b/tests/safety_replay/replay_drive.py @@ -23,10 +23,10 @@ def replay_drive(lr, safety_mode, param, alternative_experience, segment=False): invalid_addrs = set() start_t = None - for msg in lr: + for msg in filter(lambda m: m.which() in ('can', 'sendcan'), lr): if start_t is None: start_t = msg.logMonoTime - safety.set_timer(((msg.logMonoTime // 1000)) % 0xFFFFFFFF) + safety.set_timer((msg.logMonoTime // 1000) % 0xFFFFFFFF) if msg.which() == 'sendcan': for canmsg in msg.sendcan: @@ -38,14 +38,12 @@ def replay_drive(lr, safety_mode, param, alternative_experience, segment=False): blocked_addrs[canmsg.address] += 1 if "DEBUG" in os.environ: - print("blocked bus %d msg %d at %f" % (canmsg.src, canmsg.address, (msg.logMonoTime - start_t) / (1e9))) + print("blocked bus %d msg %d at %f" % (canmsg.src, canmsg.address, (msg.logMonoTime - start_t) / 1e9)) tx_controls += safety.get_controls_allowed() tx_tot += 1 elif msg.which() == 'can': - for canmsg in msg.can: - # ignore msgs we sent - if canmsg.src >= 128: - continue + # ignore msgs we sent + for canmsg in filter(lambda m: m.src < 128, msg.can): to_push = package_can_msg(canmsg) recv = safety.safety_rx_hook(to_push) if not recv: @@ -82,7 +80,7 @@ def replay_drive(lr, safety_mode, param, alternative_experience, segment=False): r = Route(s.route_name.canonical_name) logs = r.log_paths()[s.segment_num:s.segment_num+1] if s.segment_num >= 0 else r.log_paths() - lr = MultiLogIterator(logs) + lr = MultiLogIterator(logs, sort_by_time=True) if None in (args.mode, args.param, args.alternative_experience): for msg in lr: diff --git a/tests/safety_replay/test_safety_replay.py b/tests/safety_replay/test_safety_replay.py index dc61eca4f6..4ac5d74330 100755 --- a/tests/safety_replay/test_safety_replay.py +++ b/tests/safety_replay/test_safety_replay.py @@ -47,7 +47,7 @@ failed = [] for route, mode, param, alt_exp in logs: - lr = LogReader(route) + lr = LogReader(route, sort_by_time=True) print("\nreplaying %s with safety mode %d, param %s, alternative experience %s" % (route, mode, param, alt_exp)) if not replay_drive(lr, mode, param, alt_exp): From f3d6d613c0b1015fb90e51023cf67abe9ae4726f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 26 Apr 2023 19:43:30 -0700 Subject: [PATCH 002/197] safety: don't check out of bounds array item (#1360) * safety: don't check out of bounds array item * better name * cleanup --------- Co-authored-by: Adeeb Shihadeh --- board/safety.h | 2 +- board/safety_declarations.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/board/safety.h b/board/safety.h index 80f7181c42..11efb72721 100644 --- a/board/safety.h +++ b/board/safety.h @@ -139,7 +139,7 @@ int get_addr_check_index(CANPacket_t *to_push, AddrCheckStruct addr_list[], cons for (int i = 0; i < len; i++) { // if multiple msgs are allowed, determine which one is present on the bus if (!addr_list[i].msg_seen) { - for (uint8_t j = 0U; addr_list[i].msg[j].addr != 0; j++) { + for (uint8_t j = 0U; (j < MAX_ADDR_CHECK_MSGS) && (addr_list[i].msg[j].addr != 0); j++) { if ((addr == addr_list[i].msg[j].addr) && (bus == addr_list[i].msg[j].bus) && (length == addr_list[i].msg[j].len)) { addr_list[i].index = j; diff --git a/board/safety_declarations.h b/board/safety_declarations.h index 6dde0d4a3a..aa6ac14627 100644 --- a/board/safety_declarations.h +++ b/board/safety_declarations.h @@ -8,6 +8,7 @@ const int MAX_WRONG_COUNTERS = 5; const uint8_t MAX_MISSED_MSGS = 10U; +#define MAX_ADDR_CHECK_MSGS 3U // sample struct that keeps 6 samples in memory struct sample_t { @@ -92,7 +93,7 @@ typedef struct { // params and flags about checksum, counter and frequency checks for each monitored address typedef struct { // const params - const CanMsgCheck msg[3]; // check either messages (e.g. honda steer). Array MUST terminate with an empty struct to know its length. + const CanMsgCheck msg[MAX_ADDR_CHECK_MSGS]; // check either messages (e.g. honda steer) // dynamic flags bool msg_seen; int index; // if multiple messages are allowed to be checked, this stores the index of the first one seen. only msg[msg_index] will be used From 048ddeea4a87b8f20f7cef69ee75de7df5b71f40 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 26 Apr 2023 21:15:55 -0700 Subject: [PATCH 003/197] safety: make addr index getter more explicit (#1361) make this explicit --- board/safety.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/board/safety.h b/board/safety.h index 11efb72721..a0a7c6e0e5 100644 --- a/board/safety.h +++ b/board/safety.h @@ -149,11 +149,13 @@ int get_addr_check_index(CANPacket_t *to_push, AddrCheckStruct addr_list[], cons } } - int idx = addr_list[i].index; - if ((addr == addr_list[i].msg[idx].addr) && (bus == addr_list[i].msg[idx].bus) && - (length == addr_list[i].msg[idx].len)) { - index = i; - break; + if (addr_list[i].msg_seen) { + int idx = addr_list[i].index; + if ((addr == addr_list[i].msg[idx].addr) && (bus == addr_list[i].msg[idx].bus) && + (length == addr_list[i].msg[idx].len)) { + index = i; + break; + } } } return index; From 5c21b140495ef72f36f90d26a6c740c101012752 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 26 Apr 2023 21:49:39 -0700 Subject: [PATCH 004/197] Ford: check 2nd speed message (#1365) * Add second speed * 2.5 m/s is 9 kph, 4 kph is the max in the field * add quality signal * qf signal name * comment * checksum matches except it goes to zero when turning the car off despite counter still active * update comments * now test it * spacing * Update board/safety/safety_ford.h * zzzz * not needed * whoops * test vehicle state mismatch * add to safety_helpers * probably misra * we might want to check it in both speed signals in case one doesn't exist on a car * Revert "we might want to check it in both speed signals in case one doesn't exist on a car" This reverts commit 3338931409327f9c0eaa6fbd0daa9a57ef229b46. * fix formatting * 2 m/s (7 kph) * revert mismatch stuff --- board/safety/safety_ford.h | 19 ++++++++++++++++-- tests/safety/test_ford.py | 40 +++++++++++++++++++++++++++----------- 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/board/safety/safety_ford.h b/board/safety/safety_ford.h index a0f218e705..cc4f9619b7 100644 --- a/board/safety/safety_ford.h +++ b/board/safety/safety_ford.h @@ -3,6 +3,7 @@ #define MSG_EngVehicleSpThrottle 0x204 // RX from PCM, for driver throttle input #define MSG_DesiredTorqBrk 0x213 // RX from ABS, for standstill state #define MSG_BrakeSysFeatures 0x415 // RX from ABS, for vehicle speed +#define MSG_EngVehicleSpThrottle2 0x202 // RX from PCM, for second vehicle speed #define MSG_Yaw_Data_FD1 0x91 // RX from RCM, for yaw rate #define MSG_Steering_Data_FD1 0x083 // TX by OP, various driver switches and LKAS/CC buttons #define MSG_ACCDATA_3 0x18A // TX by OP, ACC/TJA user interface @@ -28,6 +29,7 @@ const CanMsg FORD_TX_MSGS[] = { // this may be the cause of blocked messages AddrCheckStruct ford_addr_checks[] = { {.msg = {{MSG_BrakeSysFeatures, 0, 8, .check_checksum = true, .max_counter = 15U, .quality_flag=true, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_EngVehicleSpThrottle2, 0, 8, .check_checksum = true, .max_counter = 15U, .quality_flag=true, .expected_timestep = 20000U}, { 0 }, { 0 }}}, {.msg = {{MSG_Yaw_Data_FD1, 0, 8, .check_checksum = true, .max_counter = 255U, .quality_flag=true, .expected_timestep = 10000U}, { 0 }, { 0 }}}, // These messages have no counter or checksum {.msg = {{MSG_EngBrakeData, 0, 8, .expected_timestep = 100000U}, { 0 }, { 0 }}}, @@ -44,6 +46,9 @@ static uint8_t ford_get_counter(CANPacket_t *to_push) { if (addr == MSG_BrakeSysFeatures) { // Signal: VehVActlBrk_No_Cnt cnt = (GET_BYTE(to_push, 2) >> 2) & 0xFU; + } else if (addr == MSG_EngVehicleSpThrottle2) { + // Signal: VehVActlEng_No_Cnt + cnt = (GET_BYTE(to_push, 2) >> 3) & 0xFU; } else if (addr == MSG_Yaw_Data_FD1) { // Signal: VehRollYaw_No_Cnt cnt = GET_BYTE(to_push, 5); @@ -60,6 +65,9 @@ static uint32_t ford_get_checksum(CANPacket_t *to_push) { if (addr == MSG_BrakeSysFeatures) { // Signal: VehVActlBrk_No_Cs chksum = GET_BYTE(to_push, 3); + } else if (addr == MSG_EngVehicleSpThrottle2) { + // Signal: VehVActlEng_No_Cs + chksum = GET_BYTE(to_push, 1); } else if (addr == MSG_Yaw_Data_FD1) { // Signal: VehRollYawW_No_Cs chksum = GET_BYTE(to_push, 4); @@ -78,6 +86,11 @@ static uint32_t ford_compute_checksum(CANPacket_t *to_push) { chksum += GET_BYTE(to_push, 2) >> 6; // VehVActlBrk_D_Qf chksum += (GET_BYTE(to_push, 2) >> 2) & 0xFU; // VehVActlBrk_No_Cnt chksum = 0xFFU - chksum; + } else if (addr == MSG_EngVehicleSpThrottle2) { + chksum += (GET_BYTE(to_push, 2) >> 3) & 0xFU; // VehVActlEng_No_Cnt + chksum += (GET_BYTE(to_push, 4) >> 5) & 0x3U; // VehVActlEng_D_Qf + chksum += GET_BYTE(to_push, 6) + GET_BYTE(to_push, 7); // Veh_V_ActlEng + chksum = 0xFFU - chksum; } else if (addr == MSG_Yaw_Data_FD1) { chksum += GET_BYTE(to_push, 0) + GET_BYTE(to_push, 1); // VehRol_W_Actl chksum += GET_BYTE(to_push, 2) + GET_BYTE(to_push, 3); // VehYaw_W_Actl @@ -96,9 +109,11 @@ static bool ford_get_quality_flag_valid(CANPacket_t *to_push) { bool valid = false; if (addr == MSG_BrakeSysFeatures) { - valid = (GET_BYTE(to_push, 2) >> 6) == 0x3U; // VehVActlBrk_D_Qf + valid = (GET_BYTE(to_push, 2) >> 6) == 0x3U; // VehVActlBrk_D_Qf + } else if (addr == MSG_EngVehicleSpThrottle2) { + valid = ((GET_BYTE(to_push, 4) >> 5) & 0x3U) == 0x3U; // VehVActlEng_D_Qf } else if (addr == MSG_Yaw_Data_FD1) { - valid = (GET_BYTE(to_push, 6) >> 4) == 0xFU; // VehRolWActl_D_Qf & VehYawWActl_D_Qf + valid = (GET_BYTE(to_push, 6) >> 4) == 0xFU; // VehRolWActl_D_Qf & VehYawWActl_D_Qf } else { } return valid; diff --git a/tests/safety/test_ford.py b/tests/safety/test_ford.py index 8d34598621..2dd8e016bc 100755 --- a/tests/safety/test_ford.py +++ b/tests/safety/test_ford.py @@ -8,15 +8,16 @@ from panda.tests.libpanda import libpanda_py from panda.tests.safety.common import CANPackerPanda -MSG_EngBrakeData = 0x165 # RX from PCM, for driver brake pedal and cruise state -MSG_EngVehicleSpThrottle = 0x204 # RX from PCM, for driver throttle input -MSG_BrakeSysFeatures = 0x415 # RX from ABS, for vehicle speed -MSG_Yaw_Data_FD1 = 0x91 # RX from RCM, for yaw rate -MSG_Steering_Data_FD1 = 0x083 # TX by OP, various driver switches and LKAS/CC buttons -MSG_ACCDATA_3 = 0x18A # TX by OP, ACC/TJA user interface -MSG_Lane_Assist_Data1 = 0x3CA # TX by OP, Lane Keep Assist -MSG_LateralMotionControl = 0x3D3 # TX by OP, Traffic Jam Assist -MSG_IPMA_Data = 0x3D8 # TX by OP, IPMA and LKAS user interface +MSG_EngBrakeData = 0x165 # RX from PCM, for driver brake pedal and cruise state +MSG_EngVehicleSpThrottle = 0x204 # RX from PCM, for driver throttle input +MSG_BrakeSysFeatures = 0x415 # RX from ABS, for vehicle speed +MSG_EngVehicleSpThrottle2 = 0x202 # RX from PCM, for second vehicle speed +MSG_Yaw_Data_FD1 = 0x91 # RX from RCM, for yaw rate +MSG_Steering_Data_FD1 = 0x083 # TX by OP, various driver switches and LKAS/CC buttons +MSG_ACCDATA_3 = 0x18A # TX by OP, ACC/TJA user interface +MSG_Lane_Assist_Data1 = 0x3CA # TX by OP, Lane Keep Assist +MSG_LateralMotionControl = 0x3D3 # TX by OP, Traffic Jam Assist +MSG_IPMA_Data = 0x3D8 # TX by OP, IPMA and LKAS user interface def checksum(msg): @@ -39,6 +40,13 @@ def checksum(msg): chksum = 0xff - (chksum & 0xff) ret[3] = chksum + elif addr == MSG_EngVehicleSpThrottle2: + chksum = (dat[2] >> 3) & 0xf # VehVActlEng_No_Cnt + chksum += (dat[4] >> 5) & 0x3 # VehVActlEng_D_Qf + chksum += dat[6] + dat[7] # Veh_V_ActlEng + chksum = 0xff - (chksum & 0xff) + ret[1] = chksum + return addr, t, ret, bus @@ -61,6 +69,7 @@ class TestFordSafety(common.PandaSafetyTest): FWD_BUS_LOOKUP = {0: 2, 2: 0} cnt_speed = 0 + cnt_speed_2 = 0 cnt_yaw_rate = 0 def setUp(self): @@ -80,12 +89,18 @@ def _user_brake_msg(self, brake: bool): } return self.packer.make_can_msg_panda("EngBrakeData", 0, values) - # Vehicle speed + # ABS vehicle speed def _speed_msg(self, speed: float, quality_flag=True): values = {"Veh_V_ActlBrk": speed * 3.6, "VehVActlBrk_D_Qf": 3 if quality_flag else 0, "VehVActlBrk_No_Cnt": self.cnt_speed % 16} self.__class__.cnt_speed += 1 return self.packer.make_can_msg_panda("BrakeSysFeatures", 0, values, fix_checksum=checksum) + # PCM vehicle speed + def _speed_msg_2(self, speed: float, quality_flag=True): + values = {"Veh_V_ActlEng": speed * 3.6, "VehVActlEng_D_Qf": 3 if quality_flag else 0, "VehVActlEng_No_Cnt": self.cnt_speed_2 % 16} + self.__class__.cnt_speed_2 += 1 + return self.packer.make_can_msg_panda("EngVehicleSpThrottle2", 0, values, fix_checksum=checksum) + # Standstill state def _vehicle_moving_msg(self, speed: float): values = {"VehStop_D_Stat": 1 if speed <= self.STANDSTILL_THRESHOLD else 0} @@ -144,12 +159,14 @@ def _acc_button_msg(self, button: int, bus: int): def test_rx_hook(self): # checksum, counter, and quality flag checks for quality_flag in [True, False]: - for msg in ["speed", "yaw"]: + for msg in ["speed", "speed_2", "yaw"]: self.safety.set_controls_allowed(True) # send multiple times to verify counter checks for _ in range(10): if msg == "speed": to_push = self._speed_msg(0, quality_flag=quality_flag) + elif msg == "speed_2": + to_push = self._speed_msg_2(0, quality_flag=quality_flag) elif msg == "yaw": to_push = self._yaw_rate_msg(0, 0, quality_flag=quality_flag) @@ -157,6 +174,7 @@ def test_rx_hook(self): self.assertEqual(quality_flag, self.safety.get_controls_allowed()) # Mess with checksum to make it fail + to_push[0].data[1] = 0 # Speed 2 checksum to_push[0].data[3] = 0 # Speed checksum & half of yaw signal self.assertFalse(self._rx(to_push)) self.assertFalse(self.safety.get_controls_allowed()) From f444f1554f7c076244aa00ec30531716522eb017 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 26 Apr 2023 22:37:36 -0700 Subject: [PATCH 005/197] Ford: check second speed for mismatches (#1358) * Add second speed * 2.5 m/s is 9 kph, 4 kph is the max in the field * add quality signal * qf signal name * comment * checksum matches except it goes to zero when turning the car off despite counter still active * update comments * now test it * spacing * Update board/safety/safety_ford.h * zzzz * not needed * whoops * test vehicle state mismatch * add to safety_helpers * probably misra * we might want to check it in both speed signals in case one doesn't exist on a car * Revert "we might want to check it in both speed signals in case one doesn't exist on a car" This reverts commit 3338931409327f9c0eaa6fbd0daa9a57ef229b46. * fix formatting * 2 m/s (7 kph) * set controls_allowed directly like other safety models remove vehicle_state_mismatch * obv --- board/safety/safety_ford.h | 10 ++++++++++ tests/safety/test_ford.py | 15 +++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/board/safety/safety_ford.h b/board/safety/safety_ford.h index cc4f9619b7..8e4800a5b3 100644 --- a/board/safety/safety_ford.h +++ b/board/safety/safety_ford.h @@ -123,6 +123,7 @@ static bool ford_get_quality_flag_valid(CANPacket_t *to_push) { #define INACTIVE_CURVATURE_RATE 4096U #define INACTIVE_PATH_OFFSET 512U #define INACTIVE_PATH_ANGLE 1000U +#define FORD_MAX_SPEED_DELTA 2.0 // m/s static bool ford_lkas_msg_check(int addr) { return (addr == MSG_ACCDATA_3) @@ -150,6 +151,15 @@ static int ford_rx_hook(CANPacket_t *to_push) { vehicle_speed = ((GET_BYTE(to_push, 0) << 8) | GET_BYTE(to_push, 1)) * 0.01 / 3.6; } + if (addr == MSG_EngVehicleSpThrottle2) { + // Disable controls if speeds from ABS and PCM ECUs are too far apart. + // Signal: Veh_V_ActlEng + float filtered_pcm_speed = ((GET_BYTE(to_push, 6) << 8) | GET_BYTE(to_push, 7)) * 0.01 / 3.6; + if (ABS(filtered_pcm_speed - vehicle_speed) > FORD_MAX_SPEED_DELTA) { + controls_allowed = 0; + } + } + // Update gas pedal if (addr == MSG_EngVehicleSpThrottle) { // Pedal position: (0.1 * val) in percent diff --git a/tests/safety/test_ford.py b/tests/safety/test_ford.py index 2dd8e016bc..a8f4a06069 100755 --- a/tests/safety/test_ford.py +++ b/tests/safety/test_ford.py @@ -68,6 +68,8 @@ class TestFordSafety(common.PandaSafetyTest): FWD_BLACKLISTED_ADDRS = {2: [MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl, MSG_IPMA_Data]} FWD_BUS_LOOKUP = {0: 2, 2: 0} + MAX_SPEED_DELTA = 2.0 # m/s + cnt_speed = 0 cnt_speed_2 = 0 cnt_yaw_rate = 0 @@ -179,6 +181,19 @@ def test_rx_hook(self): self.assertFalse(self._rx(to_push)) self.assertFalse(self.safety.get_controls_allowed()) + def test_rx_hook_speed_mismatch(self): + # Ford relies on speed for driver curvature limiting, so it checks two sources + for speed in np.arange(0, 40, 1): + for speed_delta in np.arange(-5, 5, 0.1): + speed_2 = round(max(speed + speed_delta, 0), 1) + # Set controls allowed in between rx since first message can reset it + self._rx(self._speed_msg(speed)) + self.safety.set_controls_allowed(True) + self._rx(self._speed_msg_2(speed_2)) + + within_delta = abs(speed - speed_2) <= self.MAX_SPEED_DELTA + self.assertEqual(self.safety.get_controls_allowed(), within_delta) + def test_steer_allowed(self): path_offsets = np.arange(-5.12, 5.11, 1).round() path_angles = np.arange(-0.5, 0.5235, 0.1).round(1) From 7aef934d4cd740dc5f25a64cf2675b2ecf7f7525 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 26 Apr 2023 22:59:58 -0700 Subject: [PATCH 006/197] Fix undefined behavior in GET_BYTES macro (#1367) --- SConstruct | 4 ++++ board/drivers/can_common.h | 2 +- board/safety/safety_body.h | 2 +- board/safety/safety_honda.h | 4 ++-- board/safety/safety_hyundai.h | 10 +++++----- board/safety/safety_hyundai_canfd.h | 2 +- board/safety/safety_nissan.h | 2 +- board/safety/safety_subaru.h | 6 +++--- board/safety/safety_subaru_legacy.h | 6 +++--- board/safety/safety_toyota.h | 4 ++-- board/safety/safety_volkswagen_mqb.h | 2 +- board/safety_declarations.h | 11 +++++++++-- tests/libpanda/SConscript | 8 ++++++++ 13 files changed, 41 insertions(+), 22 deletions(-) diff --git a/SConstruct b/SConstruct index fd83d87523..62f55787ba 100644 --- a/SConstruct +++ b/SConstruct @@ -3,5 +3,9 @@ AddOption('--test', default=True, help='build test files') +AddOption('--ubsan', + action='store_true', + help='turn on UBSan') + # panda fw & test files SConscript('SConscript') diff --git a/board/drivers/can_common.h b/board/drivers/can_common.h index 01abc6a073..3f2f0830a3 100644 --- a/board/drivers/can_common.h +++ b/board/drivers/can_common.h @@ -199,7 +199,7 @@ void ignition_can_hook(CANPacket_t *to_push) { // GM exception if ((addr == 0x160) && (len == 5)) { // this message isn't all zeros when ignition is on - ignition_can = GET_BYTES_04(to_push) != 0U; + ignition_can = GET_BYTES(to_push, 0, 4) != 0U; ignition_can_cnt = 0U; } diff --git a/board/safety/safety_body.h b/board/safety/safety_body.h index a4d21ef011..61ebc46dc2 100644 --- a/board/safety/safety_body.h +++ b/board/safety/safety_body.h @@ -31,7 +31,7 @@ static int body_tx_hook(CANPacket_t *to_send) { } // Allow going into CAN flashing mode even if controls are not allowed - if (!controls_allowed && ((uint32_t)GET_BYTES_04(to_send) == 0xdeadfaceU) && ((uint32_t)GET_BYTES_48(to_send) == 0x0ab00b1eU)) { + if (!controls_allowed && (GET_BYTES(to_send, 0, 4) == 0xdeadfaceU) && (GET_BYTES(to_send, 4, 4) == 0x0ab00b1eU)) { tx = 1; } diff --git a/board/safety/safety_honda.h b/board/safety/safety_honda.h index 26a20bb6d1..f4852b12c7 100644 --- a/board/safety/safety_honda.h +++ b/board/safety/safety_honda.h @@ -347,7 +347,7 @@ static int honda_tx_hook(CANPacket_t *to_send) { // Bosch supplemental control check if (addr == 0xE5) { - if ((GET_BYTES_04(to_send) != 0x10800004U) || ((GET_BYTES_48(to_send) & 0x00FFFFFFU) != 0x0U)) { + if ((GET_BYTES(to_send, 0, 4) != 0x10800004U) || ((GET_BYTES(to_send, 4, 4) & 0x00FFFFFFU) != 0x0U)) { tx = 0; } } @@ -370,7 +370,7 @@ static int honda_tx_hook(CANPacket_t *to_send) { // Only tester present ("\x02\x3E\x80\x00\x00\x00\x00\x00") allowed on diagnostics address if (addr == 0x18DAB0F1) { - if ((GET_BYTES_04(to_send) != 0x00803E02U) || (GET_BYTES_48(to_send) != 0x0U)) { + if ((GET_BYTES(to_send, 0, 4) != 0x00803E02U) || (GET_BYTES(to_send, 4, 4) != 0x0U)) { tx = 0; } } diff --git a/board/safety/safety_hyundai.h b/board/safety/safety_hyundai.h index 8a18554bc0..8ba252a1b1 100644 --- a/board/safety/safety_hyundai.h +++ b/board/safety/safety_hyundai.h @@ -178,13 +178,13 @@ static int hyundai_rx_hook(CANPacket_t *to_push) { // SCC12 is on bus 2 for camera-based SCC cars, bus 0 on all others if (valid && (addr == 1057) && (((bus == 0) && !hyundai_camera_scc) || ((bus == 2) && hyundai_camera_scc))) { // 2 bits: 13-14 - int cruise_engaged = (GET_BYTES_04(to_push) >> 13) & 0x3U; + int cruise_engaged = (GET_BYTES(to_push, 0, 4) >> 13) & 0x3U; hyundai_common_cruise_state_check(cruise_engaged); } if (valid && (bus == 0)) { if (addr == 593) { - int torque_driver_new = ((GET_BYTES_04(to_push) & 0x7ffU) * 0.79) - 808; // scale down new driver torque signal to match previous one + int torque_driver_new = ((GET_BYTES(to_push, 0, 4) & 0x7ffU) * 0.79) - 808; // scale down new driver torque signal to match previous one // update array of samples update_sample(&torque_driver, torque_driver_new); } @@ -208,7 +208,7 @@ static int hyundai_rx_hook(CANPacket_t *to_push) { // sample wheel speed, averaging opposite corners if (addr == 902) { - uint32_t hyundai_speed = (GET_BYTES_04(to_push) & 0x3FFFU) + ((GET_BYTES_48(to_push) >> 16) & 0x3FFFU); // FL + RR + uint32_t hyundai_speed = (GET_BYTES(to_push, 0, 4) & 0x3FFFU) + ((GET_BYTES(to_push, 4, 4) >> 16) & 0x3FFFU); // FL + RR hyundai_speed /= 2; vehicle_moving = hyundai_speed > HYUNDAI_STANDSTILL_THRSLD; } @@ -275,7 +275,7 @@ static int hyundai_tx_hook(CANPacket_t *to_send) { // LKA STEER: safety check if (addr == 832) { - int desired_torque = ((GET_BYTES_04(to_send) >> 16) & 0x7ffU) - 1024U; + int desired_torque = ((GET_BYTES(to_send, 0, 4) >> 16) & 0x7ffU) - 1024U; bool steer_req = GET_BIT(to_send, 27U) != 0U; const SteeringLimits limits = hyundai_alt_limits ? HYUNDAI_STEERING_LIMITS_ALT : HYUNDAI_STEERING_LIMITS; @@ -286,7 +286,7 @@ static int hyundai_tx_hook(CANPacket_t *to_send) { // UDS: Only tester present ("\x02\x3E\x80\x00\x00\x00\x00\x00") allowed on diagnostics address if (addr == 2000) { - if ((GET_BYTES_04(to_send) != 0x00803E02U) || (GET_BYTES_48(to_send) != 0x0U)) { + if ((GET_BYTES(to_send, 0, 4) != 0x00803E02U) || (GET_BYTES(to_send, 4, 4) != 0x0U)) { tx = 0; } } diff --git a/board/safety/safety_hyundai_canfd.h b/board/safety/safety_hyundai_canfd.h index 188a4c6916..2dc6e9bbff 100644 --- a/board/safety/safety_hyundai_canfd.h +++ b/board/safety/safety_hyundai_canfd.h @@ -282,7 +282,7 @@ static int hyundai_canfd_tx_hook(CANPacket_t *to_send) { // UDS: only tester present ("\x02\x3E\x80\x00\x00\x00\x00\x00") allowed on diagnostics address if ((addr == 0x730) && hyundai_canfd_hda2) { - if ((GET_BYTES_04(to_send) != 0x00803E02U) || (GET_BYTES_48(to_send) != 0x0U)) { + if ((GET_BYTES(to_send, 0, 4) != 0x00803E02U) || (GET_BYTES(to_send, 4, 4) != 0x0U)) { tx = 0; } } diff --git a/board/safety/safety_nissan.h b/board/safety/safety_nissan.h index 3f8532d7f3..550a780050 100644 --- a/board/safety/safety_nissan.h +++ b/board/safety/safety_nissan.h @@ -52,7 +52,7 @@ static int nissan_rx_hook(CANPacket_t *to_push) { if (addr == 0x2) { // Current steering angle // Factor -0.1, little endian - int angle_meas_new = (GET_BYTES_04(to_push) & 0xFFFFU); + int angle_meas_new = (GET_BYTES(to_push, 0, 4) & 0xFFFFU); // Need to multiply by 10 here as LKAS and Steering wheel are different base unit angle_meas_new = to_signed(angle_meas_new, 16) * 10; diff --git a/board/safety/safety_subaru.h b/board/safety/safety_subaru.h index 450dabc4da..4c83aed857 100644 --- a/board/safety/safety_subaru.h +++ b/board/safety/safety_subaru.h @@ -93,7 +93,7 @@ static int subaru_rx_hook(CANPacket_t *to_push) { int addr = GET_ADDR(to_push); if ((addr == 0x119) && (bus == 0)) { int torque_driver_new; - torque_driver_new = ((GET_BYTES_04(to_push) >> 16) & 0x7FFU); + torque_driver_new = ((GET_BYTES(to_push, 0, 4) >> 16) & 0x7FFU); torque_driver_new = -1 * to_signed(torque_driver_new, 11); update_sample(&torque_driver, torque_driver_new); } @@ -106,7 +106,7 @@ static int subaru_rx_hook(CANPacket_t *to_push) { // update vehicle moving with any non-zero wheel speed if ((addr == 0x13a) && (bus == alt_bus)) { - vehicle_moving = ((GET_BYTES_04(to_push) >> 12) != 0U) || (GET_BYTES_48(to_push) != 0U); + vehicle_moving = ((GET_BYTES(to_push, 0, 4) >> 12) != 0U) || (GET_BYTES(to_push, 4, 4) != 0U); } if ((addr == 0x13c) && (bus == alt_bus)) { @@ -135,7 +135,7 @@ static int subaru_tx_hook(CANPacket_t *to_send) { // steer cmd checks if (addr == 0x122) { - int desired_torque = ((GET_BYTES_04(to_send) >> 16) & 0x1FFFU); + int desired_torque = ((GET_BYTES(to_send, 0, 4) >> 16) & 0x1FFFU); desired_torque = -1 * to_signed(desired_torque, 13); const SteeringLimits limits = subaru_gen2 ? SUBARU_GEN2_STEERING_LIMITS : SUBARU_STEERING_LIMITS; diff --git a/board/safety/safety_subaru_legacy.h b/board/safety/safety_subaru_legacy.h index 4b5b79437c..5cce0dda8e 100644 --- a/board/safety/safety_subaru_legacy.h +++ b/board/safety/safety_subaru_legacy.h @@ -45,11 +45,11 @@ static int subaru_legacy_rx_hook(CANPacket_t *to_push) { // update vehicle moving with any non-zero wheel speed if (addr == 0xD4) { - vehicle_moving = ((GET_BYTES_04(to_push) >> 12) != 0U) || (GET_BYTES_48(to_push) != 0U); + vehicle_moving = ((GET_BYTES(to_push, 0, 4) >> 12) != 0U) || (GET_BYTES(to_push, 4, 4) != 0U); } if (addr == 0xD1) { - brake_pressed = ((GET_BYTES_04(to_push) >> 16) & 0xFFU) > 0U; + brake_pressed = ((GET_BYTES(to_push, 0, 4) >> 16) & 0xFFU) > 0U; } if (addr == 0x140) { @@ -72,7 +72,7 @@ static int subaru_legacy_tx_hook(CANPacket_t *to_send) { // steer cmd checks if (addr == 0x164) { - int desired_torque = ((GET_BYTES_04(to_send) >> 8) & 0x1FFFU); + int desired_torque = ((GET_BYTES(to_send, 0, 4) >> 8) & 0x1FFFU); desired_torque = -1 * to_signed(desired_torque, 13); if (steer_torque_cmd_checks(desired_torque, -1, SUBARU_L_STEERING_LIMITS)) { diff --git a/board/safety/safety_toyota.h b/board/safety/safety_toyota.h index 7aec33a0d9..5ea6b6e107 100644 --- a/board/safety/safety_toyota.h +++ b/board/safety/safety_toyota.h @@ -109,7 +109,7 @@ static int toyota_rx_hook(CANPacket_t *to_push) { if (addr == 0xaa) { // check that all wheel speeds are at zero value with offset - bool standstill = (GET_BYTES_04(to_push) == 0x6F1A6F1AU) && (GET_BYTES_48(to_push) == 0x6F1A6F1AU); + bool standstill = (GET_BYTES(to_push, 0, 4) == 0x6F1A6F1AU) && (GET_BYTES(to_push, 4, 4) == 0x6F1A6F1AU); vehicle_moving = !standstill; } @@ -181,7 +181,7 @@ static int toyota_tx_hook(CANPacket_t *to_send) { // AEB: block all actuation. only used when DSU is unplugged if (addr == 0x283) { // only allow the checksum, which is the last byte - bool block = (GET_BYTES_04(to_send) != 0U) || (GET_BYTE(to_send, 4) != 0U) || (GET_BYTE(to_send, 5) != 0U); + bool block = (GET_BYTES(to_send, 0, 4) != 0U) || (GET_BYTE(to_send, 4) != 0U) || (GET_BYTE(to_send, 5) != 0U); if (block) { tx = 0; } diff --git a/board/safety/safety_volkswagen_mqb.h b/board/safety/safety_volkswagen_mqb.h index 9277e8e018..4cd9c2ec31 100644 --- a/board/safety/safety_volkswagen_mqb.h +++ b/board/safety/safety_volkswagen_mqb.h @@ -183,7 +183,7 @@ static int volkswagen_mqb_rx_hook(CANPacket_t *to_push) { // Signal: Motor_20.MO_Fahrpedalrohwert_01 if (addr == MSG_MOTOR_20) { - gas_pressed = ((GET_BYTES_04(to_push) >> 12) & 0xFFU) != 0U; + gas_pressed = ((GET_BYTES(to_push, 0, 4) >> 12) & 0xFFU) != 0U; } // Signal: Motor_14.MO_Fahrer_bremst (ECU detected brake pedal switch F63) diff --git a/board/safety_declarations.h b/board/safety_declarations.h index aa6ac14627..947ad7d22d 100644 --- a/board/safety_declarations.h +++ b/board/safety_declarations.h @@ -2,10 +2,17 @@ #define GET_BIT(msg, b) (((msg)->data[((b) / 8U)] >> ((b) % 8U)) & 0x1U) #define GET_BYTE(msg, b) ((msg)->data[(b)]) -#define GET_BYTES_04(msg) ((msg)->data[0] | ((msg)->data[1] << 8U) | ((msg)->data[2] << 16U) | ((msg)->data[3] << 24U)) -#define GET_BYTES_48(msg) ((msg)->data[4] | ((msg)->data[5] << 8U) | ((msg)->data[6] << 16U) | ((msg)->data[7] << 24U)) #define GET_FLAG(value, mask) (((__typeof__(mask))(value) & (mask)) == (mask)) +uint32_t GET_BYTES(const CANPacket_t *msg, int start, int len) { + uint32_t ret = 0U; + for (int i = 0; i < len; i++) { + const uint8_t shift = i * 8; + ret |= (((uint32_t)msg->data[start + i]) << shift); + } + return ret; +} + const int MAX_WRONG_COUNTERS = 5; const uint8_t MAX_MISSED_MSGS = 10U; #define MAX_ADDR_CHECK_MSGS 3U diff --git a/tests/libpanda/SConscript b/tests/libpanda/SConscript index 71ed743897..000ca63cac 100644 --- a/tests/libpanda/SConscript +++ b/tests/libpanda/SConscript @@ -10,4 +10,12 @@ env = Environment( ) env.PrependENVPath('PATH', '/opt/homebrew/bin') +if GetOption('ubsan'): + flags = [ + "-fsanitize=undefined", + "-fno-sanitize-recover=undefined", + ] + env['CFLAGS'] += flags + env['LINKFLAGS'] += flags + env.SharedLibrary("libpanda.so", ["panda.c",]) From 1e8c5d59c7aed45aaca580c99197a1248abd0209 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 26 Apr 2023 23:15:28 -0700 Subject: [PATCH 007/197] CI: run tests with UBSan (#1363) * CI: run tests with UBSan * try this --- .github/workflows/test.yaml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7e3574f05e..81712bbc93 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -46,6 +46,8 @@ jobs: run: ${{ env.RUN }} "scons -j4" - name: Build panda with SPI support run: ${{ env.RUN }} "ENABLE_SPI=1 scons -j4" + - name: Build with UBSan + run: ${{ env.RUN }} "scons -j4 --ubsan" unit_tests: name: unit tests @@ -63,6 +65,9 @@ jobs: safety: name: safety runs-on: ubuntu-20.04 + strategy: + matrix: + flags: ['', '--ubsan'] timeout-minutes: 20 steps: - uses: actions/checkout@v2 @@ -75,21 +80,25 @@ jobs: scons -c && \ scons -j$(nproc) opendbc/ cereal/ && \ cd panda && \ - scons -j$(nproc) && \ + scons -j$(nproc) ${{ matrix.flags }} && \ tests/safety/test.sh" safety_replay: name: safety replay runs-on: ubuntu-20.04 + strategy: + matrix: + flags: ['', '--ubsan'] timeout-minutes: 20 steps: - uses: actions/checkout@v2 - name: Build Docker image run: eval "$BUILD" - name: Run safety replay - run: ${{ env.RUN }} "cd tests/safety_replay && - scons -u .. && - ./test_safety_replay.py" + run: | + ${{ env.RUN }} "scons -j$(nproc) ${{ matrix.flags }} && \ + cd tests/safety_replay && \ + ./test_safety_replay.py" misra: name: misra c2012 From 4269b74a8488bc88c5e3e9c355b623751bd2656b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 26 Apr 2023 23:55:04 -0700 Subject: [PATCH 008/197] safety: use max_limit_check (#1368) use max_limit_check --- board/safety.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/board/safety.h b/board/safety.h index a0a7c6e0e5..bfab19b77e 100644 --- a/board/safety.h +++ b/board/safety.h @@ -424,7 +424,7 @@ bool dist_to_meas_check(int val, int val_last, struct sample_t *val_meas, int lowest_allowed = MAX(lowest_allowed_rl, MIN(val_last + MAX_RATE_DOWN, MIN(val_meas->min, 0) - MAX_ERROR)); // check for violation - return (val < lowest_allowed) || (val > highest_allowed); + return max_limit_check(val, highest_allowed, lowest_allowed); } // check that commanded value isn't fighting against driver @@ -447,7 +447,7 @@ bool driver_limit_check(int val, int val_last, struct sample_t *val_driver, MIN(driver_min_limit, 0))); // check for violation - return (val < lowest_allowed) || (val > highest_allowed); + return max_limit_check(val, highest_allowed, lowest_allowed); } @@ -459,7 +459,7 @@ bool rt_rate_limit_check(int val, int val_last, const int MAX_RT_DELTA) { int lowest_val = MIN(val_last, 0) - MAX_RT_DELTA; // check for violation - return (val < lowest_val) || (val > highest_val); + return max_limit_check(val, highest_val, lowest_val); } From c9c3cb38f6102ed3f72cee9720293be6c581e7ee Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 27 Apr 2023 00:40:29 -0700 Subject: [PATCH 009/197] Ford safety: curvature error limit (#1353) * set ford vehicle speed * parse yaw rate signals * misra * misra * misra * misra * draft * update module * already checked * and set it properly * some stuff * draft * clean up (will fail tests because we don't send yaw rate yet) * could do something like this * this is better and less prone to bugs * match simple op limiting, debugging * set checksum for messages in tests * clean up * fix that * one m/s fudge * fix sign of yaw rate * interpolate detects size * forgot OP flips the curvature sign. it matches yaw on can * all my debugging crap * make replay work for ford * fix panda blocking messages (array is fixed size so size-1 is 0 rate at high speed) * uncomment safety test limits * revert * round for zero blocked msgs * fix limits * meas safety checks that down rate is >=, not < * test pass * lots of comments and draft what one meas torque check would look like * fix that * add curvature meas * add debugging stuff * Revert "add debugging stuff" This reverts commit 449783fc625954868a1cfca48a655cac7b074cc1. * messy but at least one test passes now * draft * add max_steer * some safety clean up * and that * start with a test that works * another test that works (sort of, we need more strict panda safety without false positives) * no max curvature check (not safety related), allow any rate limits * add new function * also need to consider max val here, since OP will send up to that * and now use the function * lower to 10 * compilation fixes * clean up (no rate limiting) * remove that too * curvature diff test * more clean up * debug * ? * better names * more official * use _curvature_meas_msg_array here * bit faster * no i don't * revert that * why not just use angle_meas? * bb ll * bb deb * clean up debug vals * more * revert replay drive debugging changes * Update board/safety.h * rm line * only need to round the final thing * not needed, under 10 ms * make a class variable * fix a misra? * another misra? better * ? * 12.1 * need to explicitly convert * add one to not false trigger the violation (float rounding) * not really needed * rm line * cmt * use clamp * rename * in struct * comment * use max_limit_check * draft clean up * Revert "draft clean up" This reverts commit d1a0e8acd1d0eb2bd7b75618c7df14e15c16e134. * make a global make a global * this is fine --- board/safety.h | 14 ++++++++++- board/safety/safety_ford.h | 49 +++++++++++++++++++++++++++++-------- board/safety_declarations.h | 5 +++- tests/safety/test_ford.py | 31 +++++++++++++++++++++++ 4 files changed, 87 insertions(+), 12 deletions(-) diff --git a/board/safety.h b/board/safety.h index bfab19b77e..c811e64feb 100644 --- a/board/safety.h +++ b/board/safety.h @@ -411,7 +411,19 @@ bool max_limit_check(int val, const int MAX_VAL, const int MIN_VAL) { return (val > MAX_VAL) || (val < MIN_VAL); } -// check that commanded value isn't too far from measured +// check that commanded angle value isn't too far from measured +bool angle_dist_to_meas_check(int val, struct sample_t *val_meas, const int MAX_ERROR, const int MAX_VAL) { + + // val must always be near val_meas, limited to the maximum value + // add 1 to not false trigger the violation + int highest_allowed = CLAMP(val_meas->max + MAX_ERROR + 1, -MAX_VAL, MAX_VAL); + int lowest_allowed = CLAMP(val_meas->min - MAX_ERROR - 1, -MAX_VAL, MAX_VAL); + + // check for violation + return max_limit_check(val, highest_allowed, lowest_allowed); +} + +// check that commanded torque value isn't too far from measured bool dist_to_meas_check(int val, int val_last, struct sample_t *val_meas, const int MAX_RATE_UP, const int MAX_RATE_DOWN, const int MAX_ERROR) { diff --git a/board/safety/safety_ford.h b/board/safety/safety_ford.h index 8e4800a5b3..00ce4f8485 100644 --- a/board/safety/safety_ford.h +++ b/board/safety/safety_ford.h @@ -124,6 +124,7 @@ static bool ford_get_quality_flag_valid(CANPacket_t *to_push) { #define INACTIVE_PATH_OFFSET 512U #define INACTIVE_PATH_ANGLE 1000U #define FORD_MAX_SPEED_DELTA 2.0 // m/s +#define FORD_CURVATURE_DELTA_LIMIT_SPEED 10.0 // m/s static bool ford_lkas_msg_check(int addr) { return (addr == MSG_ACCDATA_3) @@ -132,6 +133,13 @@ static bool ford_lkas_msg_check(int addr) { || (addr == MSG_IPMA_Data); } +// Curvature rate limits +const SteeringLimits FORD_STEERING_LIMITS = { + .max_steer = 1000, + .angle_deg_to_can = 50000, // 1 / (2e-5) rad to can + .max_angle_error = 100, // 0.002 * FORD_STEERING_LIMITS.angle_deg_to_can +}; + static int ford_rx_hook(CANPacket_t *to_push) { bool valid = addr_safety_check(to_push, &ford_rx_checks, ford_get_checksum, ford_compute_checksum, ford_get_counter, ford_get_quality_flag_valid); @@ -151,6 +159,7 @@ static int ford_rx_hook(CANPacket_t *to_push) { vehicle_speed = ((GET_BYTE(to_push, 0) << 8) | GET_BYTE(to_push, 1)) * 0.01 / 3.6; } + // Check vehicle speed against a second source if (addr == MSG_EngVehicleSpThrottle2) { // Disable controls if speeds from ABS and PCM ECUs are too far apart. // Signal: Veh_V_ActlEng @@ -160,6 +169,17 @@ static int ford_rx_hook(CANPacket_t *to_push) { } } + // Update vehicle yaw rate + if (addr == MSG_Yaw_Data_FD1) { + // Signal: VehYaw_W_Actl + float ford_yaw_rate = (((GET_BYTE(to_push, 2) << 8U) | GET_BYTE(to_push, 3)) * 0.0002) - 6.5; + float current_curvature = ford_yaw_rate / MAX(vehicle_speed, 0.1); + // convert current curvature into units on CAN for comparison with desired curvature + int current_curvature_can = current_curvature * (float)FORD_STEERING_LIMITS.angle_deg_to_can + + ((current_curvature > 0.) ? 0.5 : -0.5); + update_sample(&angle_meas, current_curvature_can); + } + // Update gas pedal if (addr == MSG_EngVehicleSpThrottle) { // Pedal position: (0.1 * val) in percent @@ -225,20 +245,29 @@ static int ford_tx_hook(CANPacket_t *to_send) { // Safety check for LateralMotionControl action if (addr == MSG_LateralMotionControl) { // Signal: LatCtl_D_Rq - unsigned int steer_control_type = (GET_BYTE(to_send, 4) >> 2) & 0x7U; - unsigned int curvature = (GET_BYTE(to_send, 0) << 3) | (GET_BYTE(to_send, 1) >> 5); - unsigned int curvature_rate = ((GET_BYTE(to_send, 1) & 0x1FU) << 8) | GET_BYTE(to_send, 2); - unsigned int path_angle = (GET_BYTE(to_send, 3) << 3) | (GET_BYTE(to_send, 4) >> 5); - unsigned int path_offset = (GET_BYTE(to_send, 5) << 2) | (GET_BYTE(to_send, 6) >> 6); + bool steer_control_enabled = ((GET_BYTE(to_send, 4) >> 2) & 0x7U) != 0U; + unsigned int raw_curvature = (GET_BYTE(to_send, 0) << 3) | (GET_BYTE(to_send, 1) >> 5); + unsigned int raw_curvature_rate = ((GET_BYTE(to_send, 1) & 0x1FU) << 8) | GET_BYTE(to_send, 2); + unsigned int raw_path_angle = (GET_BYTE(to_send, 3) << 3) | (GET_BYTE(to_send, 4) >> 5); + unsigned int raw_path_offset = (GET_BYTE(to_send, 5) << 2) | (GET_BYTE(to_send, 6) >> 6); // These signals are not yet tested with the current safety limits - if ((curvature_rate != INACTIVE_CURVATURE_RATE) || (path_angle != INACTIVE_PATH_ANGLE) || (path_offset != INACTIVE_PATH_OFFSET)) { - tx = 0; + bool violation = (raw_curvature_rate != INACTIVE_CURVATURE_RATE) || (raw_path_angle != INACTIVE_PATH_ANGLE) || (raw_path_offset != INACTIVE_PATH_OFFSET); + + int desired_curvature = raw_curvature - INACTIVE_CURVATURE; // /FORD_STEERING_LIMITS.angle_deg_to_can to get real curvature + if (controls_allowed) { + if (vehicle_speed > FORD_CURVATURE_DELTA_LIMIT_SPEED) { + violation |= angle_dist_to_meas_check(desired_curvature, &angle_meas, + FORD_STEERING_LIMITS.max_angle_error, FORD_STEERING_LIMITS.max_steer); + } } - // No steer control allowed when controls are not allowed - bool steer_control_enabled = (steer_control_type != 0U) || (curvature != INACTIVE_CURVATURE); - if (!controls_allowed && steer_control_enabled) { + // No curvature command if controls is not allowed + if (!controls_allowed && ((desired_curvature != 0) || steer_control_enabled)) { + violation = true; + } + + if (violation) { tx = 0; } } diff --git a/board/safety_declarations.h b/board/safety_declarations.h index 947ad7d22d..1835e76ea7 100644 --- a/board/safety_declarations.h +++ b/board/safety_declarations.h @@ -68,6 +68,7 @@ typedef struct { const int angle_deg_to_can; const struct lookup_t angle_rate_up_lookup; const struct lookup_t angle_rate_down_lookup; + const int max_angle_error; // used to limit error between meas and cmd } SteeringLimits; typedef struct { @@ -124,6 +125,8 @@ uint32_t get_ts_elapsed(uint32_t ts, uint32_t ts_last); int to_signed(int d, int bits); void update_sample(struct sample_t *sample, int sample_new); bool max_limit_check(int val, const int MAX, const int MIN); +bool angle_dist_to_meas_check(int val, struct sample_t *val_meas, + const int MAX_ERROR, const int MAX_VAL); bool dist_to_meas_check(int val, int val_last, struct sample_t *val_meas, const int MAX_RATE_UP, const int MAX_RATE_DOWN, const int MAX_ERROR); bool driver_limit_check(int val, int val_last, struct sample_t *val_driver, @@ -208,7 +211,7 @@ uint32_t heartbeat_engaged_mismatches = 0; // count of mismatches between heart // for safety modes with angle steering control uint32_t ts_angle_last = 0; int desired_angle_last = 0; -struct sample_t angle_meas; // last 6 steer angles +struct sample_t angle_meas; // last 6 steer angles/curvatures // This can be set with a USB command // It enables features that allow alternative experiences, like not disengaging on gas press diff --git a/tests/safety/test_ford.py b/tests/safety/test_ford.py index a8f4a06069..ff75cb672f 100755 --- a/tests/safety/test_ford.py +++ b/tests/safety/test_ford.py @@ -50,6 +50,11 @@ def checksum(msg): return addr, t, ret, bus +def round_curvature_can(curvature): + # rounds curvature as if it was sent on CAN + return round(curvature * 5, 4) / 5 + + class Buttons: CANCEL = 0 RESUME = 1 @@ -68,8 +73,15 @@ class TestFordSafety(common.PandaSafetyTest): FWD_BLACKLISTED_ADDRS = {2: [MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl, MSG_IPMA_Data]} FWD_BUS_LOOKUP = {0: 2, 2: 0} + # Max allowed delta between car speeds MAX_SPEED_DELTA = 2.0 # m/s + # Curvature control limits + DEG_TO_CAN = 50000 # 1 / (2e-5) rad to can + MAX_CURVATURE = 0.02 + MAX_CURVATURE_DELTA = 0.002 + CURVATURE_DELTA_LIMIT_SPEED = 10.0 # m/s + cnt_speed = 0 cnt_speed_2 = 0 cnt_yaw_rate = 0 @@ -80,6 +92,11 @@ def setUp(self): self.safety.set_safety_hooks(Panda.SAFETY_FORD, 0) self.safety.init_tests() + def _reset_curvature_measurements(self, curvature, speed): + self._rx(self._speed_msg(speed)) + for _ in range(6): + self._rx(self._yaw_rate_msg(curvature, speed)) + # Driver brake pedal def _user_brake_msg(self, brake: bool): # brake pedal and cruise state share same message, so we have to send @@ -213,6 +230,20 @@ def test_steer_allowed(self): should_tx = should_tx and (not enabled or controls_allowed) self.assertEqual(should_tx, self._tx(self._tja_command_msg(steer_control_enabled, path_offset, path_angle, curvature, curvature_rate))) + def test_steer_meas_delta(self): + """This safety model enforces a maximum distance from measured and commanded curvature, only above a certain speed""" + self.safety.set_controls_allowed(1) + + for speed in np.linspace(0, 50, 11): + for initial_curvature in np.linspace(-self.MAX_CURVATURE, self.MAX_CURVATURE, 51): + self._reset_curvature_measurements(initial_curvature, speed) + + limit_command = speed > self.CURVATURE_DELTA_LIMIT_SPEED + for new_curvature in np.linspace(-self.MAX_CURVATURE, self.MAX_CURVATURE, 51): + too_far_away = round_curvature_can(abs(new_curvature - initial_curvature)) > self.MAX_CURVATURE_DELTA + should_tx = not limit_command or not too_far_away + self.assertEqual(should_tx, self._tx(self._tja_command_msg(True, 0, 0, new_curvature, 0))) + def test_prevent_lkas_action(self): self.safety.set_controls_allowed(1) self.assertFalse(self._tx(self._lkas_command_msg(1))) From 07680692d99c2856e5fff6e01f5bf0d3d49c88f6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 27 Apr 2023 17:27:18 -0700 Subject: [PATCH 010/197] Ford: add subtests (#1371) * subtest here * and here * bit faster --- tests/safety/test_ford.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/safety/test_ford.py b/tests/safety/test_ford.py index ff75cb672f..5ccf5500d1 100755 --- a/tests/safety/test_ford.py +++ b/tests/safety/test_ford.py @@ -228,21 +228,25 @@ def test_steer_allowed(self): should_tx = path_offset == 0 and path_angle == 0 and curvature_rate == 0 should_tx = should_tx and (not enabled or controls_allowed) - self.assertEqual(should_tx, self._tx(self._tja_command_msg(steer_control_enabled, path_offset, path_angle, curvature, curvature_rate))) + with self.subTest(controls_allowed=controls_allowed, steer_control_enabled=steer_control_enabled, + path_offset=path_offset, path_angle=path_angle, curvature_rate=curvature_rate, + curvature=curvature): + self.assertEqual(should_tx, self._tx(self._tja_command_msg(steer_control_enabled, path_offset, path_angle, curvature, curvature_rate))) def test_steer_meas_delta(self): """This safety model enforces a maximum distance from measured and commanded curvature, only above a certain speed""" self.safety.set_controls_allowed(1) for speed in np.linspace(0, 50, 11): - for initial_curvature in np.linspace(-self.MAX_CURVATURE, self.MAX_CURVATURE, 51): + for initial_curvature in np.linspace(-self.MAX_CURVATURE, self.MAX_CURVATURE, 21): self._reset_curvature_measurements(initial_curvature, speed) limit_command = speed > self.CURVATURE_DELTA_LIMIT_SPEED - for new_curvature in np.linspace(-self.MAX_CURVATURE, self.MAX_CURVATURE, 51): + for new_curvature in np.linspace(-self.MAX_CURVATURE, self.MAX_CURVATURE, 41): too_far_away = round_curvature_can(abs(new_curvature - initial_curvature)) > self.MAX_CURVATURE_DELTA should_tx = not limit_command or not too_far_away - self.assertEqual(should_tx, self._tx(self._tja_command_msg(True, 0, 0, new_curvature, 0))) + with self.subTest(speed=speed, initial_curvature=initial_curvature, new_curvature=new_curvature): + self.assertEqual(should_tx, self._tx(self._tja_command_msg(True, 0, 0, new_curvature, 0))) def test_prevent_lkas_action(self): self.safety.set_controls_allowed(1) From 240370cdd1df17ae13a9f24b8ebf72c3a45c551b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 27 Apr 2023 17:44:32 -0700 Subject: [PATCH 011/197] ford: allow 0 curvature when controls are allowed if not steering (#1370) * test that we can send curvature=0 if enabled=False while control_allowed=True (let's say EPS faults, OP doesn't want to actuate) * draft * only check error if apply bit is 1 * subtest * no subtest * fix safety and tests * tests say this works * clean up * subtests are sloww * Revert "subtests are sloww" This reverts commit 560c6745c8dddfcce87113e36e3661274fbb239b. * fix test from merge * add comment * add comment * add another comment * spacing --- board/safety/safety_ford.h | 7 ++++++- tests/safety/test_ford.py | 34 +++++++++++++++++++++++----------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/board/safety/safety_ford.h b/board/safety/safety_ford.h index 00ce4f8485..09363e8703 100644 --- a/board/safety/safety_ford.h +++ b/board/safety/safety_ford.h @@ -255,13 +255,18 @@ static int ford_tx_hook(CANPacket_t *to_send) { bool violation = (raw_curvature_rate != INACTIVE_CURVATURE_RATE) || (raw_path_angle != INACTIVE_PATH_ANGLE) || (raw_path_offset != INACTIVE_PATH_OFFSET); int desired_curvature = raw_curvature - INACTIVE_CURVATURE; // /FORD_STEERING_LIMITS.angle_deg_to_can to get real curvature - if (controls_allowed) { + if (controls_allowed && steer_control_enabled) { if (vehicle_speed > FORD_CURVATURE_DELTA_LIMIT_SPEED) { violation |= angle_dist_to_meas_check(desired_curvature, &angle_meas, FORD_STEERING_LIMITS.max_angle_error, FORD_STEERING_LIMITS.max_steer); } } + // If steer control is not enabled, curvature must be 0 + if (!steer_control_enabled && (desired_curvature != 0)) { + violation = true; + } + // No curvature command if controls is not allowed if (!controls_allowed && ((desired_curvature != 0) || steer_control_enabled)) { violation = true; diff --git a/tests/safety/test_ford.py b/tests/safety/test_ford.py index 5ccf5500d1..4ca6bf4751 100755 --- a/tests/safety/test_ford.py +++ b/tests/safety/test_ford.py @@ -224,10 +224,14 @@ def test_steer_allowed(self): for curvature_rate in curvature_rates: for curvature in curvatures: self.safety.set_controls_allowed(controls_allowed) - enabled = steer_control_enabled or curvature != 0 should_tx = path_offset == 0 and path_angle == 0 and curvature_rate == 0 - should_tx = should_tx and (not enabled or controls_allowed) + if steer_control_enabled: + should_tx = should_tx and controls_allowed + else: + # when request bit is 0, only allow curvature of 0 since the signal range + # is not large enough to enforce it tracking measured + should_tx = should_tx and curvature == 0 with self.subTest(controls_allowed=controls_allowed, steer_control_enabled=steer_control_enabled, path_offset=path_offset, path_angle=path_angle, curvature_rate=curvature_rate, curvature=curvature): @@ -237,16 +241,24 @@ def test_steer_meas_delta(self): """This safety model enforces a maximum distance from measured and commanded curvature, only above a certain speed""" self.safety.set_controls_allowed(1) - for speed in np.linspace(0, 50, 11): - for initial_curvature in np.linspace(-self.MAX_CURVATURE, self.MAX_CURVATURE, 21): - self._reset_curvature_measurements(initial_curvature, speed) + for steer_control_enabled in (True, False): + for speed in np.linspace(0, 50, 11): + for initial_curvature in np.linspace(-self.MAX_CURVATURE, self.MAX_CURVATURE, 21): + self._reset_curvature_measurements(initial_curvature, speed) - limit_command = speed > self.CURVATURE_DELTA_LIMIT_SPEED - for new_curvature in np.linspace(-self.MAX_CURVATURE, self.MAX_CURVATURE, 41): - too_far_away = round_curvature_can(abs(new_curvature - initial_curvature)) > self.MAX_CURVATURE_DELTA - should_tx = not limit_command or not too_far_away - with self.subTest(speed=speed, initial_curvature=initial_curvature, new_curvature=new_curvature): - self.assertEqual(should_tx, self._tx(self._tja_command_msg(True, 0, 0, new_curvature, 0))) + limit_command = speed > self.CURVATURE_DELTA_LIMIT_SPEED + for new_curvature in np.linspace(-self.MAX_CURVATURE, self.MAX_CURVATURE, 41): + too_far_away = round_curvature_can(abs(new_curvature - initial_curvature)) > self.MAX_CURVATURE_DELTA + + if steer_control_enabled: + should_tx = not limit_command or not too_far_away + else: + # enforce angle error limit is disabled when steer request bit is 0 + should_tx = new_curvature == 0 + + with self.subTest(steer_control_enabled=steer_control_enabled, speed=speed, + initial_curvature=initial_curvature, new_curvature=new_curvature): + self.assertEqual(should_tx, self._tx(self._tja_command_msg(steer_control_enabled, 0, 0, new_curvature, 0))) def test_prevent_lkas_action(self): self.safety.set_controls_allowed(1) From aac46a2ef29edadd8798eb8997a931bf165fa0ae Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 27 Apr 2023 21:41:38 -0700 Subject: [PATCH 012/197] angle safety: enforce disabled angle when not steering (#1372) * enforce disabled angle when bit not set, below we enforce bit to be 0 when not controls allowed * test * other test * combine tests * comment --- board/safety.h | 2 +- tests/safety/common.py | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/board/safety.h b/board/safety.h index c811e64feb..875b402bd3 100644 --- a/board/safety.h +++ b/board/safety.h @@ -644,7 +644,7 @@ bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const desired_angle_last = desired_angle; // Angle should be the same as current angle while not steering - violation |= (!controls_allowed && + violation |= (!steer_control_enabled && ((desired_angle < (angle_meas.min - 1)) || (desired_angle > (angle_meas.max + 1)))); diff --git a/tests/safety/common.py b/tests/safety/common.py index be5e914c0b..db321c9a0b 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -584,11 +584,21 @@ def test_angle_cmd_when_enabled(self): self.assertTrue(self._tx(self._angle_cmd_msg(a, False))) def test_angle_cmd_when_disabled(self): - self.safety.set_controls_allowed(0) + # Tests that only angles close to the meas are allowed while + # steer actuation bit is 0, regardless of controls allowed. + for controls_allowed in (True, False): + self.safety.set_controls_allowed(controls_allowed) - self._set_prev_desired_angle(0) - self.assertFalse(self._tx(self._angle_cmd_msg(0, True))) - self.assertFalse(self.safety.get_controls_allowed()) + for steer_control_enabled in (True, False): + for angle_meas in np.arange(-90, 91, 10): + self._angle_meas_msg_array(angle_meas) + + for angle_cmd in np.arange(-90, 91, 10): + self._set_prev_desired_angle(angle_cmd) + + # controls_allowed is checked if actuation bit is 1, else the angle must be close to meas (inactive) + should_tx = controls_allowed if steer_control_enabled else angle_cmd == angle_meas + self.assertEqual(should_tx, self._tx(self._angle_cmd_msg(angle_cmd, steer_control_enabled))) @add_regen_tests From 69a0b6ed34118ca5a92c2e70e2d231dbb98e19d5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 27 Apr 2023 23:19:26 -0700 Subject: [PATCH 013/197] safety: use max_limit_check helper in angle safety (#1373) * better * ? * ? * Revert "?" This reverts commit ae70c7ff3b41e49ccc149b7b202fdcde0a120e8d. * Revert "?" This reverts commit db7fd644a9cedfc46583651fe7581e4862b0f77e. --- board/safety.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/board/safety.h b/board/safety.h index 875b402bd3..c5a97c1a43 100644 --- a/board/safety.h +++ b/board/safety.h @@ -644,9 +644,9 @@ bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const desired_angle_last = desired_angle; // Angle should be the same as current angle while not steering - violation |= (!steer_control_enabled && - ((desired_angle < (angle_meas.min - 1)) || - (desired_angle > (angle_meas.max + 1)))); + if (!steer_control_enabled) { + violation |= max_limit_check(desired_angle, angle_meas.max + 1, angle_meas.min - 1); + } // No angle control allowed when controls are not allowed violation |= !controls_allowed && steer_control_enabled; From 4160d8d71cbb5cd40107834ad4756c4dbffc63b0 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 27 Apr 2023 23:33:54 -0700 Subject: [PATCH 014/197] safety: common angle safety function (#1369) * draft clean up * always check * add angle to name * ford sends curvature when not steering and bit is 0. it's not torque, curvature of 0 will still apply torque * need this to be generic * formatting * rm * test that we can send curvature=0 if enabled=False while control_allowed=True (let's say EPS faults, OP doesn't want to actuate) * revert ford tests to master * this should preserve behavior * this is fine to remove * and this should also be covered * yeet * change opt name, combine checks, much better! * one more * modes * Add comment * misra has a bug :( * ugh * make this clear * ? * order * fix * comments --- board/safety.h | 32 +++++++++++++++++--------------- board/safety/safety_ford.h | 30 +++++++++++------------------- board/safety_declarations.h | 7 ++++++- 3 files changed, 34 insertions(+), 35 deletions(-) diff --git a/board/safety.h b/board/safety.h index c5a97c1a43..6966db36cf 100644 --- a/board/safety.h +++ b/board/safety.h @@ -411,18 +411,6 @@ bool max_limit_check(int val, const int MAX_VAL, const int MIN_VAL) { return (val > MAX_VAL) || (val < MIN_VAL); } -// check that commanded angle value isn't too far from measured -bool angle_dist_to_meas_check(int val, struct sample_t *val_meas, const int MAX_ERROR, const int MAX_VAL) { - - // val must always be near val_meas, limited to the maximum value - // add 1 to not false trigger the violation - int highest_allowed = CLAMP(val_meas->max + MAX_ERROR + 1, -MAX_VAL, MAX_VAL); - int lowest_allowed = CLAMP(val_meas->min - MAX_ERROR - 1, -MAX_VAL, MAX_VAL); - - // check for violation - return max_limit_check(val, highest_allowed, lowest_allowed); -} - // check that commanded torque value isn't too far from measured bool dist_to_meas_check(int val, int val_last, struct sample_t *val_meas, const int MAX_RATE_UP, const int MAX_RATE_DOWN, const int MAX_ERROR) { @@ -627,7 +615,7 @@ bool steer_torque_cmd_checks(int desired_torque, int steer_req, const SteeringLi bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const SteeringLimits limits) { bool violation = false; - if (controls_allowed && steer_control_enabled) { + if (!limits.disable_angle_rate_limits && controls_allowed && steer_control_enabled) { // convert floating point angle rate limits to integers in the scale of the desired angle on CAN, // add 1 to not false trigger the violation. also fudge the speed by 1 m/s so rate limits are // always slightly above openpilot's in case we read an updated speed in between angle commands @@ -643,9 +631,23 @@ bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const } desired_angle_last = desired_angle; - // Angle should be the same as current angle while not steering + // check that commanded angle value isn't too far from measured, used to limit torque for some safety modes + if (limits.enforce_angle_error && controls_allowed && steer_control_enabled) { + if (vehicle_speed > limits.angle_error_limit_speed) { + // val must always be near angle_meas, limited to the maximum value + // add 1 to not false trigger the violation + int highest_allowed = CLAMP(angle_meas.max + limits.max_angle_error + 1, -limits.max_steer, limits.max_steer); + int lowest_allowed = CLAMP(angle_meas.min - limits.max_angle_error - 1, -limits.max_steer, limits.max_steer); + + // check for violation + violation |= max_limit_check(desired_angle, highest_allowed, lowest_allowed); + } + } + + // Angle should either be 0 or same as current angle while not steering if (!steer_control_enabled) { - violation |= max_limit_check(desired_angle, angle_meas.max + 1, angle_meas.min - 1); + violation |= (limits.inactive_angle_is_zero ? (desired_angle != 0) : + max_limit_check(desired_angle, angle_meas.max + 1, angle_meas.min - 1)); } // No angle control allowed when controls are not allowed diff --git a/board/safety/safety_ford.h b/board/safety/safety_ford.h index 09363e8703..50a31465e9 100644 --- a/board/safety/safety_ford.h +++ b/board/safety/safety_ford.h @@ -124,7 +124,6 @@ static bool ford_get_quality_flag_valid(CANPacket_t *to_push) { #define INACTIVE_PATH_OFFSET 512U #define INACTIVE_PATH_ANGLE 1000U #define FORD_MAX_SPEED_DELTA 2.0 // m/s -#define FORD_CURVATURE_DELTA_LIMIT_SPEED 10.0 // m/s static bool ford_lkas_msg_check(int addr) { return (addr == MSG_ACCDATA_3) @@ -136,8 +135,15 @@ static bool ford_lkas_msg_check(int addr) { // Curvature rate limits const SteeringLimits FORD_STEERING_LIMITS = { .max_steer = 1000, - .angle_deg_to_can = 50000, // 1 / (2e-5) rad to can - .max_angle_error = 100, // 0.002 * FORD_STEERING_LIMITS.angle_deg_to_can + .angle_deg_to_can = 50000, // 1 / (2e-5) rad to can + .max_angle_error = 100, // 0.002 * FORD_STEERING_LIMITS.angle_deg_to_can + + // no blending at low speed due to lack of torque wind-up and inaccurate current curvature + .angle_error_limit_speed = 10.0, // m/s + + .disable_angle_rate_limits = true, + .enforce_angle_error = true, + .inactive_angle_is_zero = true, }; static int ford_rx_hook(CANPacket_t *to_push) { @@ -254,23 +260,9 @@ static int ford_tx_hook(CANPacket_t *to_send) { // These signals are not yet tested with the current safety limits bool violation = (raw_curvature_rate != INACTIVE_CURVATURE_RATE) || (raw_path_angle != INACTIVE_PATH_ANGLE) || (raw_path_offset != INACTIVE_PATH_OFFSET); + // Check angle error and steer_control_enabled int desired_curvature = raw_curvature - INACTIVE_CURVATURE; // /FORD_STEERING_LIMITS.angle_deg_to_can to get real curvature - if (controls_allowed && steer_control_enabled) { - if (vehicle_speed > FORD_CURVATURE_DELTA_LIMIT_SPEED) { - violation |= angle_dist_to_meas_check(desired_curvature, &angle_meas, - FORD_STEERING_LIMITS.max_angle_error, FORD_STEERING_LIMITS.max_steer); - } - } - - // If steer control is not enabled, curvature must be 0 - if (!steer_control_enabled && (desired_curvature != 0)) { - violation = true; - } - - // No curvature command if controls is not allowed - if (!controls_allowed && ((desired_curvature != 0) || steer_control_enabled)) { - violation = true; - } + violation |= steer_angle_cmd_checks(desired_curvature, steer_control_enabled, FORD_STEERING_LIMITS); if (violation) { tx = 0; diff --git a/board/safety_declarations.h b/board/safety_declarations.h index 1835e76ea7..59d5bb9967 100644 --- a/board/safety_declarations.h +++ b/board/safety_declarations.h @@ -68,7 +68,12 @@ typedef struct { const int angle_deg_to_can; const struct lookup_t angle_rate_up_lookup; const struct lookup_t angle_rate_down_lookup; - const int max_angle_error; // used to limit error between meas and cmd + const int max_angle_error; // used to limit error between meas and cmd while enabled + const float angle_error_limit_speed; // minimum speed to start limiting angle error + + const bool disable_angle_rate_limits; + const bool enforce_angle_error; // enables max_angle_error check + const bool inactive_angle_is_zero; // if false, enforces angle near meas when disabled (default) } SteeringLimits; typedef struct { From 001f9917ef50660a03f01feeabc73b42babc2e24 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 28 Apr 2023 14:25:35 -0700 Subject: [PATCH 015/197] ford: disable counter check for second speed (#1375) add comment and disable counter for second speed --- board/safety/safety_ford.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/board/safety/safety_ford.h b/board/safety/safety_ford.h index 50a31465e9..2e6e923634 100644 --- a/board/safety/safety_ford.h +++ b/board/safety/safety_ford.h @@ -29,7 +29,8 @@ const CanMsg FORD_TX_MSGS[] = { // this may be the cause of blocked messages AddrCheckStruct ford_addr_checks[] = { {.msg = {{MSG_BrakeSysFeatures, 0, 8, .check_checksum = true, .max_counter = 15U, .quality_flag=true, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{MSG_EngVehicleSpThrottle2, 0, 8, .check_checksum = true, .max_counter = 15U, .quality_flag=true, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + // TODO: MSG_EngVehicleSpThrottle2 has a counter that skips by 2, understand and enable counter check + {.msg = {{MSG_EngVehicleSpThrottle2, 0, 8, .check_checksum = true, .quality_flag=true, .expected_timestep = 20000U}, { 0 }, { 0 }}}, {.msg = {{MSG_Yaw_Data_FD1, 0, 8, .check_checksum = true, .max_counter = 255U, .quality_flag=true, .expected_timestep = 10000U}, { 0 }, { 0 }}}, // These messages have no counter or checksum {.msg = {{MSG_EngBrakeData, 0, 8, .expected_timestep = 100000U}, { 0 }, { 0 }}}, From 99809f7071c783cd16547771e2dea3c34d669aa4 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 28 Apr 2023 15:02:57 -0700 Subject: [PATCH 016/197] angle safety tests: more clear helper function name here too --- tests/safety/common.py | 6 +++--- tests/safety/test_ford.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/safety/common.py b/tests/safety/common.py index db321c9a0b..ddc1b68236 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -532,7 +532,7 @@ def _set_prev_desired_angle(self, t): t = int(t * self.DEG_TO_CAN) self.safety.set_desired_angle_last(t) - def _angle_meas_msg_array(self, angle): + def _reset_angle_measurement(self, angle): for _ in range(6): self._rx(self._angle_meas_msg(angle)) @@ -546,7 +546,7 @@ def test_angle_cmd_when_enabled(self): max_delta_down = np.interp(s, self.ANGLE_DELTA_BP, self.ANGLE_DELTA_VU) # first test against false positives - self._angle_meas_msg_array(a) + self._reset_angle_measurement(a) self._rx(self._speed_msg(s)) # pylint: disable=no-member self._set_prev_desired_angle(a) @@ -591,7 +591,7 @@ def test_angle_cmd_when_disabled(self): for steer_control_enabled in (True, False): for angle_meas in np.arange(-90, 91, 10): - self._angle_meas_msg_array(angle_meas) + self._reset_angle_measurement(angle_meas) for angle_cmd in np.arange(-90, 91, 10): self._set_prev_desired_angle(angle_cmd) diff --git a/tests/safety/test_ford.py b/tests/safety/test_ford.py index 4ca6bf4751..a0d69230da 100755 --- a/tests/safety/test_ford.py +++ b/tests/safety/test_ford.py @@ -92,7 +92,7 @@ def setUp(self): self.safety.set_safety_hooks(Panda.SAFETY_FORD, 0) self.safety.init_tests() - def _reset_curvature_measurements(self, curvature, speed): + def _reset_curvature_measurement(self, curvature, speed): self._rx(self._speed_msg(speed)) for _ in range(6): self._rx(self._yaw_rate_msg(curvature, speed)) @@ -244,7 +244,7 @@ def test_steer_meas_delta(self): for steer_control_enabled in (True, False): for speed in np.linspace(0, 50, 11): for initial_curvature in np.linspace(-self.MAX_CURVATURE, self.MAX_CURVATURE, 21): - self._reset_curvature_measurements(initial_curvature, speed) + self._reset_curvature_measurement(initial_curvature, speed) limit_command = speed > self.CURVATURE_DELTA_LIMIT_SPEED for new_curvature in np.linspace(-self.MAX_CURVATURE, self.MAX_CURVATURE, 41): From 63db708491aef4fc8d278368d99c0e4fdf86f1dd Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 28 Apr 2023 17:36:35 -0700 Subject: [PATCH 017/197] driver torque limit: make driver torque message abstract (#1377) * abs * uncomment * fixy --- tests/safety/common.py | 4 ++++ tests/safety/test_mazda.py | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/safety/common.py b/tests/safety/common.py index ddc1b68236..2c053723f0 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -334,6 +334,10 @@ def setUpClass(cls): cls.safety = None raise unittest.SkipTest + @abc.abstractmethod + def _torque_driver_msg(self, torque): + pass + def test_non_realtime_limit_up(self): self.safety.set_torque_driver(0, 0) super().test_non_realtime_limit_up() diff --git a/tests/safety/test_mazda.py b/tests/safety/test_mazda.py index 79397782d2..7db588dea3 100755 --- a/tests/safety/test_mazda.py +++ b/tests/safety/test_mazda.py @@ -35,9 +35,9 @@ def _torque_meas_msg(self, torque): values = {"STEER_TORQUE_MOTOR": torque} return self.packer.make_can_msg_panda("STEER_TORQUE", 0, values) -# def _torque_driver_msg(self, torque): -# values = {"STEER_TORQUE_DRIVER": torque} -# return self.packer.make_can_msg_panda("STEER_TORQUE", 0, values) + def _torque_driver_msg(self, torque): + values = {"STEER_TORQUE_SENSOR": torque} + return self.packer.make_can_msg_panda("STEER_TORQUE", 0, values) def _torque_cmd_msg(self, torque, steer_req=1): values = {"LKAS_REQUEST": torque} From dbe941004a03ebfcf82bac61b0b94999a7e12abe Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 28 Apr 2023 17:45:02 -0700 Subject: [PATCH 018/197] safety: reset torque_meas on init properly (#1250) * reset min torque meas too * add test that fails * cmt * cmt * clean up test * don't be random * above test tests this pretty much * test angle too * fix * also test driver torque * whoops * whoops --- board/safety.h | 2 +- tests/libpanda/safety_helpers.h | 8 ++++++++ tests/libpanda/safety_helpers.py | 4 ++++ tests/safety/common.py | 33 ++++++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/board/safety.h b/board/safety.h index 6966db36cf..0692dfb31a 100644 --- a/board/safety.h +++ b/board/safety.h @@ -345,7 +345,7 @@ int set_safety_hooks(uint16_t mode, uint16_t param) { valid_steer_req_count = 0; invalid_steer_req_count = 0; - torque_meas.max = 0; + torque_meas.min = 0; torque_meas.max = 0; torque_driver.min = 0; torque_driver.max = 0; diff --git a/tests/libpanda/safety_helpers.h b/tests/libpanda/safety_helpers.h index d74d8a2621..c53b5301e8 100644 --- a/tests/libpanda/safety_helpers.h +++ b/tests/libpanda/safety_helpers.h @@ -125,6 +125,14 @@ void set_desired_angle_last(int t){ desired_angle_last = t; } +int get_angle_meas_min(void){ + return angle_meas.min; +} + +int get_angle_meas_max(void){ + return angle_meas.max; +} + // ***** car specific helpers ***** diff --git a/tests/libpanda/safety_helpers.py b/tests/libpanda/safety_helpers.py index a6ba4b0412..891a48a6b3 100644 --- a/tests/libpanda/safety_helpers.py +++ b/tests/libpanda/safety_helpers.py @@ -26,6 +26,8 @@ def setup_safety_helpers(ffi): void set_desired_torque_last(int t); void set_rt_torque_last(int t); void set_desired_angle_last(int t); + int get_angle_meas_min(void); + int get_angle_meas_max(void); bool get_cruise_engaged_prev(void); bool get_vehicle_moving(void); @@ -68,6 +70,8 @@ def get_torque_driver_max(self) -> int: ... def set_desired_torque_last(self, t: int) -> None: ... def set_rt_torque_last(self, t: int) -> None: ... def set_desired_angle_last(self, t: int) -> None: ... + def get_angle_meas_min(self) -> int: ... + def get_angle_meas_max(self) -> int: ... def get_cruise_engaged_prev(self) -> bool: ... def get_vehicle_moving(self) -> bool: ... diff --git a/tests/safety/common.py b/tests/safety/common.py index 2c053723f0..c0cf7219d8 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -403,6 +403,17 @@ def test_realtime_limits(self): self.assertTrue(self._tx(self._torque_cmd_msg(sign * (self.MAX_RT_DELTA - 1)))) self.assertTrue(self._tx(self._torque_cmd_msg(sign * (self.MAX_RT_DELTA + 1)))) + def test_reset_driver_torque_measurements(self): + # Tests that the driver torque measurement sample_t is reset on safety mode init + for t in np.linspace(-self.MAX_TORQUE, self.MAX_TORQUE, 6): + self.assertTrue(self._rx(self._torque_driver_msg(t))) + + # reset sample_t by reinitializing the safety mode + self.setUp() + + self.assertEqual(self.safety.get_torque_driver_min(), 0) + self.assertEqual(self.safety.get_torque_driver_max(), 0) + class MotorTorqueSteeringSafetyTest(TorqueSteeringSafetyTestBase, abc.ABC): @@ -509,6 +520,17 @@ def test_torque_measurements(self): self.assertTrue(self.safety.get_torque_meas_min() in min_range) self.assertTrue(self.safety.get_torque_meas_max() in max_range) + def test_reset_torque_measurements(self): + # Tests that the torque measurement sample_t is reset on safety mode init + for t in np.linspace(-self.MAX_TORQUE, self.MAX_TORQUE, 6): + self.assertTrue(self._rx(self._torque_meas_msg(t))) + + # reset sample_t by reinitializing the safety mode + self.setUp() + + self.assertEqual(self.safety.get_torque_meas_min(), 0) + self.assertEqual(self.safety.get_torque_meas_max(), 0) + class AngleSteeringSafetyTest(PandaSafetyTestBase): @@ -604,6 +626,17 @@ def test_angle_cmd_when_disabled(self): should_tx = controls_allowed if steer_control_enabled else angle_cmd == angle_meas self.assertEqual(should_tx, self._tx(self._angle_cmd_msg(angle_cmd, steer_control_enabled))) + def test_reset_angle_measurements(self): + # Tests that the angle measurement sample_t is reset on safety mode init + for a in np.linspace(-90, 90, 6): + self.assertTrue(self._rx(self._angle_meas_msg(a))) + + # reset sample_t by reinitializing the safety mode + self.setUp() + + self.assertEqual(self.safety.get_angle_meas_min(), 0) + self.assertEqual(self.safety.get_angle_meas_max(), 0) + @add_regen_tests class PandaSafetyTest(PandaSafetyTestBase): From a5718ce5dd9d3ca568a6b023b2aec5b2bfda5b2d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 1 May 2023 19:31:13 -0700 Subject: [PATCH 019/197] spi: different ack dummy bytes for debugging (#1382) different ack vals Co-authored-by: Comma Device --- python/spi.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/spi.py b/python/spi.py index 2e6beb5435..c098ebbb51 100644 --- a/python/spi.py +++ b/python/spi.py @@ -98,12 +98,12 @@ def _calc_checksum(self, data: List[int]) -> int: cksum ^= b return cksum - def _wait_for_ack(self, spi, ack_val: int, timeout: 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 (timeout == 0) or ((time.monotonic() - start) < timeout_s): - dat = spi.xfer2(b"\x12")[0] + dat = spi.xfer2([tx, ])[0] if dat == NACK: raise PandaSpiNackResponse elif dat == ack_val: @@ -125,7 +125,7 @@ def _transfer(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 10 spi.xfer2(packet) logging.debug("- waiting for header ACK") - self._wait_for_ack(spi, HACK, timeout) + self._wait_for_ack(spi, HACK, timeout, 0x11) # send data logging.debug("- sending data") @@ -133,7 +133,7 @@ def _transfer(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 10 spi.xfer2(packet) logging.debug("- waiting for data ACK") - self._wait_for_ack(spi, DACK, timeout) + self._wait_for_ack(spi, DACK, timeout, 0x13) # get response length, then response response_len_bytes = bytes(spi.xfer2(b"\x00" * 2)) From f9485c8d5d716d3429a0a24288fb83256d6a9d9e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 1 May 2023 20:01:03 -0700 Subject: [PATCH 020/197] Ford: more clear steering safety check (#1383) one line --- tests/safety/test_ford.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/safety/test_ford.py b/tests/safety/test_ford.py index a0d69230da..9c998857d7 100755 --- a/tests/safety/test_ford.py +++ b/tests/safety/test_ford.py @@ -226,12 +226,9 @@ def test_steer_allowed(self): self.safety.set_controls_allowed(controls_allowed) should_tx = path_offset == 0 and path_angle == 0 and curvature_rate == 0 - if steer_control_enabled: - should_tx = should_tx and controls_allowed - else: - # when request bit is 0, only allow curvature of 0 since the signal range - # is not large enough to enforce it tracking measured - should_tx = should_tx and curvature == 0 + # when request bit is 0, only allow curvature of 0 since the signal range + # is not large enough to enforce it tracking measured + should_tx = should_tx and (controls_allowed if steer_control_enabled else curvature == 0) with self.subTest(controls_allowed=controls_allowed, steer_control_enabled=steer_control_enabled, path_offset=path_offset, path_angle=path_angle, curvature_rate=curvature_rate, curvature=curvature): From 8e1ca608b0fc3de389d637a1bfb930369220ac96 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 1 May 2023 20:18:23 -0700 Subject: [PATCH 021/197] H7: remove busy wait on TXC (#1381) * works * cleanup * less irq * less diff --------- Co-authored-by: Comma Device --- board/drivers/spi.h | 1 + board/stm32h7/interrupt_handlers.h | 1 + board/stm32h7/llspi.h | 41 ++++++++++++++++++++---------- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/board/drivers/spi.h b/board/drivers/spi.h index 0320212241..6a52cb4e01 100644 --- a/board/drivers/spi.h +++ b/board/drivers/spi.h @@ -27,6 +27,7 @@ enum { SPI_STATE_DATA_TX }; +bool spi_tx_dma_done = false; uint8_t spi_state = SPI_STATE_HEADER; uint8_t spi_endpoint; uint16_t spi_data_len_mosi; diff --git a/board/stm32h7/interrupt_handlers.h b/board/stm32h7/interrupt_handlers.h index 0811ffe523..fb1298d77b 100644 --- a/board/stm32h7/interrupt_handlers.h +++ b/board/stm32h7/interrupt_handlers.h @@ -46,6 +46,7 @@ void TIM8_CC_IRQHandler(void) {handle_interrupt(TIM8_CC_IRQn);} void DMA1_Stream7_IRQHandler(void) {handle_interrupt(DMA1_Stream7_IRQn);} void TIM5_IRQHandler(void) {handle_interrupt(TIM5_IRQn);} void SPI3_IRQHandler(void) {handle_interrupt(SPI3_IRQn);} +void SPI4_IRQHandler(void) {handle_interrupt(SPI4_IRQn);} void UART4_IRQHandler(void) {handle_interrupt(UART4_IRQn);} void UART5_IRQHandler(void) {handle_interrupt(UART5_IRQn);} void TIM6_DAC_IRQHandler(void) {handle_interrupt(TIM6_DAC_IRQn);} diff --git a/board/stm32h7/llspi.h b/board/stm32h7/llspi.h index 9e21659b83..1e2595973c 100644 --- a/board/stm32h7/llspi.h +++ b/board/stm32h7/llspi.h @@ -34,6 +34,9 @@ void llspi_miso_dma(uint8_t *addr, int len) { // clear under-run while we were reading SPI4->IFCR |= SPI_IFCR_UDRC; + // setup interrupt on TXC + register_set(&(SPI4->IER), (1U << SPI_IER_EOTIE_Pos), 0x3FFU); + // enable DMA register_set_bits(&(SPI4->CFG1), SPI_CFG1_TXDMAEN); DMA2_Stream3->CR |= DMA_SxCR_EN; @@ -52,28 +55,38 @@ void DMA2_Stream2_IRQ_Handler(void) { // panda -> master DMA finished void DMA2_Stream3_IRQ_Handler(void) { - // Clear interrupt flag + ENTER_CRITICAL(); + DMA2->LIFCR = DMA_LIFCR_CTCIF3; + spi_tx_dma_done = true; + + EXIT_CRITICAL(); +} - // Wait until the transaction is actually finished and clear the DR. - // Timeout to prevent hang when the master clock stops. - 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; - } +// panda TX finished +void SPI4_IRQ_Handler(void) { + ENTER_CRITICAL(); + + // clear flag + SPI4->IFCR |= SPI_IFCR_EOTC; + + if (spi_tx_dma_done && ((SPI4->SR & SPI_SR_TXC) != 0)) { + spi_tx_dma_done = false; + + register_set(&(SPI4->IER), 0, 0x3FFU); + + volatile uint8_t dat = SPI4->TXDR; + (void)dat; + spi_handle_tx(false); } - volatile uint8_t dat = SPI4->TXDR; - (void)dat; - spi_handle_tx(timed_out); + EXIT_CRITICAL(); } void llspi_init(void) { // We expect less than 50 transactions (including control messages and CAN buffers) at the 100Hz boardd interval. Can be raised if needed. + REGISTER_INTERRUPT(SPI4_IRQn, SPI4_IRQ_Handler, 5000U, FAULT_INTERRUPT_RATE_SPI_DMA) REGISTER_INTERRUPT(DMA2_Stream2_IRQn, DMA2_Stream2_IRQ_Handler, 5000U, FAULT_INTERRUPT_RATE_SPI_DMA) REGISTER_INTERRUPT(DMA2_Stream3_IRQn, DMA2_Stream3_IRQ_Handler, 5000U, FAULT_INTERRUPT_RATE_SPI_DMA) @@ -88,6 +101,7 @@ void llspi_init(void) { register_set(&(DMA2_Stream3->PAR), (uint32_t)&(SPI4->TXDR), 0xFFFFFFFFU); // 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->CR1), SPI_CR1_SPE, 0xFFFFU); @@ -95,4 +109,5 @@ void llspi_init(void) { NVIC_EnableIRQ(DMA2_Stream2_IRQn); NVIC_EnableIRQ(DMA2_Stream3_IRQn); + NVIC_EnableIRQ(SPI4_IRQn); } From 641d9c4f3a47f2a95dfca643082f83b10b2b102f Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Tue, 2 May 2023 09:55:00 +0200 Subject: [PATCH 022/197] Tesla: DAS_control is on bus 2 (#1374) these are on bus 2 --- board/safety/safety_tesla.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/board/safety/safety_tesla.h b/board/safety/safety_tesla.h index 96a8e42275..b82d896464 100644 --- a/board/safety/safety_tesla.h +++ b/board/safety/safety_tesla.h @@ -34,7 +34,7 @@ const CanMsg TESLA_PT_TX_MSGS[] = { #define TESLA_PT_TX_LEN (sizeof(TESLA_PT_TX_MSGS) / sizeof(TESLA_PT_TX_MSGS[0])) AddrCheckStruct tesla_addr_checks[] = { - {.msg = {{0x2b9, 0, 8, .expected_timestep = 40000U}, { 0 }, { 0 }}}, // DAS_control (25Hz) + {.msg = {{0x2b9, 2, 8, .expected_timestep = 40000U}, { 0 }, { 0 }}}, // DAS_control (25Hz) {.msg = {{0x370, 0, 8, .expected_timestep = 40000U}, { 0 }, { 0 }}}, // EPAS_sysStatus (25Hz) {.msg = {{0x108, 0, 8, .expected_timestep = 10000U}, { 0 }, { 0 }}}, // DI_torque1 (100Hz) {.msg = {{0x118, 0, 6, .expected_timestep = 10000U}, { 0 }, { 0 }}}, // DI_torque2 (100Hz) @@ -49,7 +49,7 @@ AddrCheckStruct tesla_pt_addr_checks[] = { {.msg = {{0x106, 0, 8, .expected_timestep = 10000U}, { 0 }, { 0 }}}, // DI_torque1 (100Hz) {.msg = {{0x116, 0, 6, .expected_timestep = 10000U}, { 0 }, { 0 }}}, // DI_torque2 (100Hz) {.msg = {{0x1f8, 0, 8, .expected_timestep = 20000U}, { 0 }, { 0 }}}, // BrakeMessage (50Hz) - {.msg = {{0x2bf, 0, 8, .expected_timestep = 40000U}, { 0 }, { 0 }}}, // DAS_control (25Hz) + {.msg = {{0x2bf, 2, 8, .expected_timestep = 40000U}, { 0 }, { 0 }}}, // DAS_control (25Hz) {.msg = {{0x256, 0, 8, .expected_timestep = 100000U}, { 0 }, { 0 }}}, // DI_state (10Hz) }; #define TESLA_PT_ADDR_CHECK_LEN (sizeof(tesla_pt_addr_checks) / sizeof(tesla_pt_addr_checks[0])) From 2fb25c4a0bfe7d2294522cf13d1f1def5080e2e2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 2 May 2023 01:21:33 -0700 Subject: [PATCH 023/197] Subaru: fix Impreza replay route (#1384) * fix * impreza route --- tests/safety_replay/test_safety_replay.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/safety_replay/test_safety_replay.py b/tests/safety_replay/test_safety_replay.py index 4ac5d74330..beee236976 100755 --- a/tests/safety_replay/test_safety_replay.py +++ b/tests/safety_replay/test_safety_replay.py @@ -17,8 +17,7 @@ ReplayRoute("38bfd238edecbcd7|2019-06-07--10-15-25.bz2", Panda.SAFETY_TOYOTA, 66), # TOYOTA.PRIUS ReplayRoute("f89c604cf653e2bf|2018-09-29--13-46-50.bz2", Panda.SAFETY_GM), # GM.VOLT ReplayRoute("6fb4948a7ebe670e|2019-11-12--00-35-53.bz2", Panda.SAFETY_CHRYSLER), # CHRYSLER.PACIFICA_2018_HYBRID - ReplayRoute("791340bc01ed993d|2019-04-08--10-26-00.bz2", Panda.SAFETY_SUBARU, # SUBARU.OUTBACK - Panda.FLAG_SUBARU_GEN2), + ReplayRoute("0744286ead2fbb96|2023-05-01--16-27-01--35--rlog.bz2", Panda.SAFETY_SUBARU), # SUBARU.IMPREZA ReplayRoute("76b83eb0245de90e|2020-03-05--19-16-05.bz2", Panda.SAFETY_VOLKSWAGEN_MQB), # VOLKSWAGEN.GOLF (stock ACC) ReplayRoute("3cfdec54aa035f3f|2022-10-13--14-58-58.bz2", Panda.SAFETY_VOLKSWAGEN_MQB, # VOLKSWAGEN.GOLF (openpilot long) Panda.FLAG_VOLKSWAGEN_LONG_CONTROL), From c43f33191cd205966a275cec2b2afda6c9a6c7b6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 2 May 2023 13:07:50 -0700 Subject: [PATCH 024/197] Subaru: add gen 2 Outback route (#1385) gen2 outback route --- tests/safety_replay/test_safety_replay.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/safety_replay/test_safety_replay.py b/tests/safety_replay/test_safety_replay.py index beee236976..5e6e5f0907 100755 --- a/tests/safety_replay/test_safety_replay.py +++ b/tests/safety_replay/test_safety_replay.py @@ -18,6 +18,8 @@ ReplayRoute("f89c604cf653e2bf|2018-09-29--13-46-50.bz2", Panda.SAFETY_GM), # GM.VOLT ReplayRoute("6fb4948a7ebe670e|2019-11-12--00-35-53.bz2", Panda.SAFETY_CHRYSLER), # CHRYSLER.PACIFICA_2018_HYBRID ReplayRoute("0744286ead2fbb96|2023-05-01--16-27-01--35--rlog.bz2", Panda.SAFETY_SUBARU), # SUBARU.IMPREZA + ReplayRoute("bad6ae3584ece5b5|2023-04-29--11-23-48--7--rlog.bz2", Panda.SAFETY_SUBARU, # SUBARU.OUTBACK + Panda.FLAG_SUBARU_GEN2, ALT_EXP.DISABLE_DISENGAGE_ON_GAS), ReplayRoute("76b83eb0245de90e|2020-03-05--19-16-05.bz2", Panda.SAFETY_VOLKSWAGEN_MQB), # VOLKSWAGEN.GOLF (stock ACC) ReplayRoute("3cfdec54aa035f3f|2022-10-13--14-58-58.bz2", Panda.SAFETY_VOLKSWAGEN_MQB, # VOLKSWAGEN.GOLF (openpilot long) Panda.FLAG_VOLKSWAGEN_LONG_CONTROL), From 1d1284e863e32bdbe6b6f9db5a011cdcbdeeb4c5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 2 May 2023 13:18:26 -0700 Subject: [PATCH 025/197] Revert "Subaru: add gen 2 Outback route (#1385)" This reverts commit c43f33191cd205966a275cec2b2afda6c9a6c7b6. --- tests/safety_replay/test_safety_replay.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/safety_replay/test_safety_replay.py b/tests/safety_replay/test_safety_replay.py index 5e6e5f0907..beee236976 100755 --- a/tests/safety_replay/test_safety_replay.py +++ b/tests/safety_replay/test_safety_replay.py @@ -18,8 +18,6 @@ ReplayRoute("f89c604cf653e2bf|2018-09-29--13-46-50.bz2", Panda.SAFETY_GM), # GM.VOLT ReplayRoute("6fb4948a7ebe670e|2019-11-12--00-35-53.bz2", Panda.SAFETY_CHRYSLER), # CHRYSLER.PACIFICA_2018_HYBRID ReplayRoute("0744286ead2fbb96|2023-05-01--16-27-01--35--rlog.bz2", Panda.SAFETY_SUBARU), # SUBARU.IMPREZA - ReplayRoute("bad6ae3584ece5b5|2023-04-29--11-23-48--7--rlog.bz2", Panda.SAFETY_SUBARU, # SUBARU.OUTBACK - Panda.FLAG_SUBARU_GEN2, ALT_EXP.DISABLE_DISENGAGE_ON_GAS), ReplayRoute("76b83eb0245de90e|2020-03-05--19-16-05.bz2", Panda.SAFETY_VOLKSWAGEN_MQB), # VOLKSWAGEN.GOLF (stock ACC) ReplayRoute("3cfdec54aa035f3f|2022-10-13--14-58-58.bz2", Panda.SAFETY_VOLKSWAGEN_MQB, # VOLKSWAGEN.GOLF (openpilot long) Panda.FLAG_VOLKSWAGEN_LONG_CONTROL), From 14cfb78ab2d6d8a11d30bbcd504b8b6391311d31 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 2 May 2023 13:45:21 -0700 Subject: [PATCH 026/197] Subaru: add gen 2 Outback route (#1386) gen2 outback route --- tests/safety_replay/test_safety_replay.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/safety_replay/test_safety_replay.py b/tests/safety_replay/test_safety_replay.py index beee236976..5e6e5f0907 100755 --- a/tests/safety_replay/test_safety_replay.py +++ b/tests/safety_replay/test_safety_replay.py @@ -18,6 +18,8 @@ ReplayRoute("f89c604cf653e2bf|2018-09-29--13-46-50.bz2", Panda.SAFETY_GM), # GM.VOLT ReplayRoute("6fb4948a7ebe670e|2019-11-12--00-35-53.bz2", Panda.SAFETY_CHRYSLER), # CHRYSLER.PACIFICA_2018_HYBRID ReplayRoute("0744286ead2fbb96|2023-05-01--16-27-01--35--rlog.bz2", Panda.SAFETY_SUBARU), # SUBARU.IMPREZA + ReplayRoute("bad6ae3584ece5b5|2023-04-29--11-23-48--7--rlog.bz2", Panda.SAFETY_SUBARU, # SUBARU.OUTBACK + Panda.FLAG_SUBARU_GEN2, ALT_EXP.DISABLE_DISENGAGE_ON_GAS), ReplayRoute("76b83eb0245de90e|2020-03-05--19-16-05.bz2", Panda.SAFETY_VOLKSWAGEN_MQB), # VOLKSWAGEN.GOLF (stock ACC) ReplayRoute("3cfdec54aa035f3f|2022-10-13--14-58-58.bz2", Panda.SAFETY_VOLKSWAGEN_MQB, # VOLKSWAGEN.GOLF (openpilot long) Panda.FLAG_VOLKSWAGEN_LONG_CONTROL), From 69ad3dd8e885b61931ad65aa0cd014d694a2efe5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 3 May 2023 00:07:23 +0000 Subject: [PATCH 027/197] angle safety: add comment about rate limits at 0 (#1389) * up when 0 * Revert "up when 0" This reverts commit fc9b459651826e5ca8d597920d4fea49eedb15d5. * add comment * whoops --- board/safety.h | 1 + 1 file changed, 1 insertion(+) diff --git a/board/safety.h b/board/safety.h index 0692dfb31a..64d3e22836 100644 --- a/board/safety.h +++ b/board/safety.h @@ -623,6 +623,7 @@ bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const int delta_angle_up = (interpolate(limits.angle_rate_up_lookup, vehicle_speed - 1.) * limits.angle_deg_to_can) + 1.; int delta_angle_down = (interpolate(limits.angle_rate_down_lookup, vehicle_speed - 1.) * limits.angle_deg_to_can) + 1.; + // allow down limits at zero since small floats will be rounded to 0 int highest_desired_angle = desired_angle_last + ((desired_angle_last > 0) ? delta_angle_up : delta_angle_down); int lowest_desired_angle = desired_angle_last - ((desired_angle_last >= 0) ? delta_angle_down : delta_angle_up); From 22b245372852c002c06bd272fdcfe5d5e4db828b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 3 May 2023 00:34:24 +0000 Subject: [PATCH 028/197] angle safety: consistent rate limit variable names (#1390) consistent names --- tests/safety/common.py | 10 +++++----- tests/safety/test_nissan.py | 6 +++--- tests/safety/test_tesla.py | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/safety/common.py b/tests/safety/common.py index c0cf7219d8..2754d73cf1 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -536,9 +536,9 @@ class AngleSteeringSafetyTest(PandaSafetyTestBase): DEG_TO_CAN: int - ANGLE_DELTA_BP: List[float] - ANGLE_DELTA_V: List[float] # windup limit - ANGLE_DELTA_VU: List[float] # unwind limit + ANGLE_RATE_BP: List[float] + ANGLE_RATE_UP: List[float] # windup limit + ANGLE_RATE_DOWN: List[float] # unwind limit @classmethod def setUpClass(cls): @@ -568,8 +568,8 @@ def test_angle_cmd_when_enabled(self): angles = [-300, -100, -10, 0, 10, 100, 300] for a in angles: for s in speeds: - max_delta_up = np.interp(s, self.ANGLE_DELTA_BP, self.ANGLE_DELTA_V) - max_delta_down = np.interp(s, self.ANGLE_DELTA_BP, self.ANGLE_DELTA_VU) + max_delta_up = np.interp(s, self.ANGLE_RATE_BP, self.ANGLE_RATE_UP) + max_delta_down = np.interp(s, self.ANGLE_RATE_BP, self.ANGLE_RATE_DOWN) # first test against false positives self._reset_angle_measurement(a) diff --git a/tests/safety/test_nissan.py b/tests/safety/test_nissan.py index bdd15f177b..6c0fac1193 100755 --- a/tests/safety/test_nissan.py +++ b/tests/safety/test_nissan.py @@ -19,9 +19,9 @@ class TestNissanSafety(common.PandaSafetyTest, common.AngleSteeringSafetyTest): # Angle control limits DEG_TO_CAN = -100 - ANGLE_DELTA_BP = [0., 5., 15.] - ANGLE_DELTA_V = [5., .8, .15] # windup limit - ANGLE_DELTA_VU = [5., 3.5, .4] # unwind limit + ANGLE_RATE_BP = [0., 5., 15.] + ANGLE_RATE_UP = [5., .8, .15] # windup limit + ANGLE_RATE_DOWN = [5., 3.5, .4] # unwind limit def setUp(self): self.packer = CANPackerPanda("nissan_x_trail_2017") diff --git a/tests/safety/test_tesla.py b/tests/safety/test_tesla.py index fffbfcf4d2..97cc318463 100755 --- a/tests/safety/test_tesla.py +++ b/tests/safety/test_tesla.py @@ -71,9 +71,9 @@ class TestTeslaSteeringSafety(TestTeslaSafety, common.AngleSteeringSafetyTest): # Angle control limits DEG_TO_CAN = 10 - ANGLE_DELTA_BP = [0., 5., 15.] - ANGLE_DELTA_V = [5., .8, .15] # windup limit - ANGLE_DELTA_VU = [5., 3.5, .4] # unwind limit + ANGLE_RATE_BP = [0., 5., 15.] + ANGLE_RATE_UP = [5., .8, .15] # windup limit + ANGLE_RATE_DOWN = [5., 3.5, .4] # unwind limit def setUp(self): self.packer = CANPackerPanda("tesla_can") From aa30b15de5a128faafd8a1c4809d502c2a894609 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 3 May 2023 03:45:33 +0000 Subject: [PATCH 029/197] Ford safety: more clear variable names (#1392) * that's a better name * that too --- board/safety.h | 2 +- board/safety/safety_ford.h | 2 +- board/safety_declarations.h | 2 +- tests/safety/test_ford.py | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/board/safety.h b/board/safety.h index 64d3e22836..670b03ceb7 100644 --- a/board/safety.h +++ b/board/safety.h @@ -634,7 +634,7 @@ bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const // check that commanded angle value isn't too far from measured, used to limit torque for some safety modes if (limits.enforce_angle_error && controls_allowed && steer_control_enabled) { - if (vehicle_speed > limits.angle_error_limit_speed) { + if (vehicle_speed > limits.angle_error_min_speed) { // val must always be near angle_meas, limited to the maximum value // add 1 to not false trigger the violation int highest_allowed = CLAMP(angle_meas.max + limits.max_angle_error + 1, -limits.max_steer, limits.max_steer); diff --git a/board/safety/safety_ford.h b/board/safety/safety_ford.h index 2e6e923634..4e60b19d1e 100644 --- a/board/safety/safety_ford.h +++ b/board/safety/safety_ford.h @@ -140,7 +140,7 @@ const SteeringLimits FORD_STEERING_LIMITS = { .max_angle_error = 100, // 0.002 * FORD_STEERING_LIMITS.angle_deg_to_can // no blending at low speed due to lack of torque wind-up and inaccurate current curvature - .angle_error_limit_speed = 10.0, // m/s + .angle_error_min_speed = 10.0, // m/s .disable_angle_rate_limits = true, .enforce_angle_error = true, diff --git a/board/safety_declarations.h b/board/safety_declarations.h index 59d5bb9967..a8bff31e5b 100644 --- a/board/safety_declarations.h +++ b/board/safety_declarations.h @@ -69,7 +69,7 @@ typedef struct { const struct lookup_t angle_rate_up_lookup; const struct lookup_t angle_rate_down_lookup; const int max_angle_error; // used to limit error between meas and cmd while enabled - const float angle_error_limit_speed; // minimum speed to start limiting angle error + const float angle_error_min_speed; // minimum speed to start limiting angle error const bool disable_angle_rate_limits; const bool enforce_angle_error; // enables max_angle_error check diff --git a/tests/safety/test_ford.py b/tests/safety/test_ford.py index 9c998857d7..ee2f6b28ee 100755 --- a/tests/safety/test_ford.py +++ b/tests/safety/test_ford.py @@ -79,8 +79,8 @@ class TestFordSafety(common.PandaSafetyTest): # Curvature control limits DEG_TO_CAN = 50000 # 1 / (2e-5) rad to can MAX_CURVATURE = 0.02 - MAX_CURVATURE_DELTA = 0.002 - CURVATURE_DELTA_LIMIT_SPEED = 10.0 # m/s + MAX_CURVATURE_ERROR = 0.002 + CURVATURE_ERROR_MIN_SPEED = 10.0 # m/s cnt_speed = 0 cnt_speed_2 = 0 @@ -243,9 +243,9 @@ def test_steer_meas_delta(self): for initial_curvature in np.linspace(-self.MAX_CURVATURE, self.MAX_CURVATURE, 21): self._reset_curvature_measurement(initial_curvature, speed) - limit_command = speed > self.CURVATURE_DELTA_LIMIT_SPEED + limit_command = speed > self.CURVATURE_ERROR_MIN_SPEED for new_curvature in np.linspace(-self.MAX_CURVATURE, self.MAX_CURVATURE, 41): - too_far_away = round_curvature_can(abs(new_curvature - initial_curvature)) > self.MAX_CURVATURE_DELTA + too_far_away = round_curvature_can(abs(new_curvature - initial_curvature)) > self.MAX_CURVATURE_ERROR if steer_control_enabled: should_tx = not limit_command or not too_far_away From 3a3e117d01cf8c02a3bb84ba7b3b6e4034c72302 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 3 May 2023 04:35:00 +0000 Subject: [PATCH 030/197] Ford: remove unused function (#1393) we can just round this now --- tests/safety/test_ford.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/safety/test_ford.py b/tests/safety/test_ford.py index ee2f6b28ee..1a59728ba9 100755 --- a/tests/safety/test_ford.py +++ b/tests/safety/test_ford.py @@ -50,11 +50,6 @@ def checksum(msg): return addr, t, ret, bus -def round_curvature_can(curvature): - # rounds curvature as if it was sent on CAN - return round(curvature * 5, 4) / 5 - - class Buttons: CANCEL = 0 RESUME = 1 @@ -200,7 +195,7 @@ def test_rx_hook(self): def test_rx_hook_speed_mismatch(self): # Ford relies on speed for driver curvature limiting, so it checks two sources - for speed in np.arange(0, 40, 1): + for speed in np.arange(0, 40, 0.5): for speed_delta in np.arange(-5, 5, 0.1): speed_2 = round(max(speed + speed_delta, 0), 1) # Set controls allowed in between rx since first message can reset it @@ -245,7 +240,7 @@ def test_steer_meas_delta(self): limit_command = speed > self.CURVATURE_ERROR_MIN_SPEED for new_curvature in np.linspace(-self.MAX_CURVATURE, self.MAX_CURVATURE, 41): - too_far_away = round_curvature_can(abs(new_curvature - initial_curvature)) > self.MAX_CURVATURE_ERROR + too_far_away = round(abs(new_curvature - initial_curvature), 5) > self.MAX_CURVATURE_ERROR if steer_control_enabled: should_tx = not limit_command or not too_far_away From 810df6b5c18619a048aaf296f332a627fa8f517d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 3 May 2023 05:01:44 +0000 Subject: [PATCH 031/197] safety replay: safety tick (#1364) * make explicit, there was no bug only because the if statement is identical to above * debug * Revert "debug" This reverts commit 2ec83677c3749f805681c4874b746696767f7634. * Revert "make explicit, there was no bug only because the if statement is identical to above" This reverts commit fcb2a106b9694727ca4b5a9acf4eda3312fc4271. * fixes * remove * allow 1 second * remove * clean up * call every can msg * safety_tick_rx_invalid like test_models * order * skip end * try that --- tests/safety_replay/replay_drive.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/safety_replay/replay_drive.py b/tests/safety_replay/replay_drive.py index 3274dc58e7..77ee61d5d8 100755 --- a/tests/safety_replay/replay_drive.py +++ b/tests/safety_replay/replay_drive.py @@ -19,15 +19,21 @@ def replay_drive(lr, safety_mode, param, alternative_experience, segment=False): lr.reset() rx_tot, rx_invalid, tx_tot, tx_blocked, tx_controls, tx_controls_blocked = 0, 0, 0, 0, 0, 0 + safety_tick_rx_invalid = False blocked_addrs = Counter() invalid_addrs = set() - start_t = None - for msg in filter(lambda m: m.which() in ('can', 'sendcan'), lr): - if start_t is None: - start_t = msg.logMonoTime + can_msgs = [m for m in lr if m.which() in ('can', 'sendcan')] + start_t = can_msgs[0].logMonoTime + end_t = can_msgs[-1].logMonoTime + for msg in can_msgs: safety.set_timer((msg.logMonoTime // 1000) % 0xFFFFFFFF) + # skip start and end of route, warm up/down period + if msg.logMonoTime - start_t > 1e9 and end_t - msg.logMonoTime > 1e9: + safety.safety_tick_current_rx_checks() + safety_tick_rx_invalid |= not safety.addr_checks_valid() or safety_tick_rx_invalid + if msg.which() == 'sendcan': for canmsg in msg.sendcan: to_send = package_can_msg(canmsg) @@ -54,6 +60,7 @@ def replay_drive(lr, safety_mode, param, alternative_experience, segment=False): print("\nRX") print("total rx msgs:", rx_tot) print("invalid rx msgs:", rx_invalid) + print("safety tick rx invalid:", safety_tick_rx_invalid) print("invalid addrs:", invalid_addrs) print("\nTX") print("total openpilot msgs:", tx_tot) @@ -62,7 +69,7 @@ def replay_drive(lr, safety_mode, param, alternative_experience, segment=False): print("blocked with controls allowed:", tx_controls_blocked) print("blocked addrs:", blocked_addrs) - return tx_controls_blocked == 0 and rx_invalid == 0 + return tx_controls_blocked == 0 and rx_invalid == 0 and not safety_tick_rx_invalid if __name__ == "__main__": from tools.lib.route import Route, SegmentName From ba5c2948e4ed23a1ec61e96d2197571fb599d8e4 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 3 May 2023 14:15:34 -0700 Subject: [PATCH 032/197] python: default to 60s timeout for recover() (#1394) --- python/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/__init__.py b/python/__init__.py index ee09a55c87..741c157227 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -498,7 +498,7 @@ def flash(self, fn=None, code=None, reconnect=True): if reconnect: self.reconnect() - def recover(self, timeout: Optional[int] = None, reset: bool = True) -> bool: + def recover(self, timeout: Optional[int] = 60, reset: bool = True) -> bool: dfu_serial = self.get_dfu_serial() if reset: From 4b0427551412fd1ac4ca31d8aa376b090afb0435 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 3 May 2023 21:19:52 +0000 Subject: [PATCH 033/197] Ford: test angle error speed in steering signal test (#1395) * test above and below limit speed * need this now --- tests/safety/test_ford.py | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/tests/safety/test_ford.py b/tests/safety/test_ford.py index 1a59728ba9..9200379e59 100755 --- a/tests/safety/test_ford.py +++ b/tests/safety/test_ford.py @@ -212,22 +212,25 @@ def test_steer_allowed(self): curvature_rates = np.arange(-0.001024, 0.00102375, 0.001).round(3) curvatures = np.arange(-0.02, 0.02094, 0.01).round(2) - for controls_allowed in (True, False): - for steer_control_enabled in (True, False): - for path_offset in path_offsets: - for path_angle in path_angles: - for curvature_rate in curvature_rates: - for curvature in curvatures: - self.safety.set_controls_allowed(controls_allowed) - - should_tx = path_offset == 0 and path_angle == 0 and curvature_rate == 0 - # when request bit is 0, only allow curvature of 0 since the signal range - # is not large enough to enforce it tracking measured - should_tx = should_tx and (controls_allowed if steer_control_enabled else curvature == 0) - with self.subTest(controls_allowed=controls_allowed, steer_control_enabled=steer_control_enabled, - path_offset=path_offset, path_angle=path_angle, curvature_rate=curvature_rate, - curvature=curvature): - self.assertEqual(should_tx, self._tx(self._tja_command_msg(steer_control_enabled, path_offset, path_angle, curvature, curvature_rate))) + for speed in (self.CURVATURE_ERROR_MIN_SPEED - 1, + self.CURVATURE_ERROR_MIN_SPEED + 1): + for controls_allowed in (True, False): + for steer_control_enabled in (True, False): + for path_offset in path_offsets: + for path_angle in path_angles: + for curvature_rate in curvature_rates: + for curvature in curvatures: + self.safety.set_controls_allowed(controls_allowed) + self._reset_curvature_measurement(curvature, speed) + + should_tx = path_offset == 0 and path_angle == 0 and curvature_rate == 0 + # when request bit is 0, only allow curvature of 0 since the signal range + # is not large enough to enforce it tracking measured + should_tx = should_tx and (controls_allowed if steer_control_enabled else curvature == 0) + with self.subTest(controls_allowed=controls_allowed, steer_control_enabled=steer_control_enabled, + path_offset=path_offset, path_angle=path_angle, curvature_rate=curvature_rate, + curvature=curvature): + self.assertEqual(should_tx, self._tx(self._tja_command_msg(steer_control_enabled, path_offset, path_angle, curvature, curvature_rate))) def test_steer_meas_delta(self): """This safety model enforces a maximum distance from measured and commanded curvature, only above a certain speed""" From 776bb69945e2ab5bf4cfb993b18fcc3f543e95b4 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 3 May 2023 21:37:04 +0000 Subject: [PATCH 034/197] Ford: test angle measurement (#1396) * test angle meas readback (ensures speed and yaw rate are used properly) * revert * rm * tuple * temp * clean up * stash * Revert "stash" This reverts commit cab96b3e2b46d7b43ef539b96e0f6d3918d21e55. * use global * test negative * name --- tests/safety/test_ford.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/safety/test_ford.py b/tests/safety/test_ford.py index 9200379e59..798210b8c3 100755 --- a/tests/safety/test_ford.py +++ b/tests/safety/test_ford.py @@ -206,6 +206,25 @@ def test_rx_hook_speed_mismatch(self): within_delta = abs(speed - speed_2) <= self.MAX_SPEED_DELTA self.assertEqual(self.safety.get_controls_allowed(), within_delta) + def test_angle_measurements(self): + """Tests rx hook correctly parses the curvature measurement from the vehicle speed and yaw rate""" + for speed in np.arange(0.5, 40, 0.5): + for curvature in np.arange(0, self.MAX_CURVATURE * 2, 2e-3): + self._rx(self._speed_msg(speed)) + for c in (curvature, -curvature, 0, 0, 0, 0): + self._rx(self._yaw_rate_msg(c, speed)) + + self.assertEqual(self.safety.get_angle_meas_min(), round(-curvature * self.DEG_TO_CAN)) + self.assertEqual(self.safety.get_angle_meas_max(), round(curvature * self.DEG_TO_CAN)) + + self._rx(self._yaw_rate_msg(0, speed)) + self.assertEqual(self.safety.get_angle_meas_min(), round(-curvature * self.DEG_TO_CAN)) + self.assertEqual(self.safety.get_angle_meas_max(), 0) + + self._rx(self._yaw_rate_msg(0, speed)) + self.assertEqual(self.safety.get_angle_meas_min(), 0) + self.assertEqual(self.safety.get_angle_meas_max(), 0) + def test_steer_allowed(self): path_offsets = np.arange(-5.12, 5.11, 1).round() path_angles = np.arange(-0.5, 0.5235, 0.1).round(1) From 5a9603600ec247ac01ffe818efd7cc363af8ec9b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 4 May 2023 02:51:08 +0000 Subject: [PATCH 035/197] safety tests: reset safety helper (#1398) * reset safety * use it * clean it up * two lines * fix python linting --- tests/libpanda/safety_helpers.h | 8 ++++++++ tests/libpanda/safety_helpers.py | 4 ++++ tests/safety/common.py | 10 +++++++--- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/tests/libpanda/safety_helpers.h b/tests/libpanda/safety_helpers.h index c53b5301e8..02480b1ba5 100644 --- a/tests/libpanda/safety_helpers.h +++ b/tests/libpanda/safety_helpers.h @@ -79,6 +79,14 @@ bool get_acc_main_on(void){ return acc_main_on; } +int get_current_safety_mode(void){ + return current_safety_mode; +} + +int get_current_safety_param(void){ + return current_safety_param; +} + int get_hw_type(void){ return hw_type; } diff --git a/tests/libpanda/safety_helpers.py b/tests/libpanda/safety_helpers.py index 891a48a6b3..494f2c0425 100644 --- a/tests/libpanda/safety_helpers.py +++ b/tests/libpanda/safety_helpers.py @@ -16,6 +16,8 @@ def setup_safety_helpers(ffi): bool get_brake_pressed_prev(void); bool get_regen_braking_prev(void); bool get_acc_main_on(void); + int get_current_safety_mode(void); + int get_current_safety_param(void); void set_torque_meas(int min, int max); int get_torque_meas_min(void); @@ -60,6 +62,8 @@ def get_gas_pressed_prev(self) -> bool: ... def get_brake_pressed_prev(self) -> bool: ... def get_regen_braking_prev(self) -> bool: ... def get_acc_main_on(self) -> bool: ... + def get_current_safety_mode(self) -> int: ... + def get_current_safety_param(self) -> int: ... def set_torque_meas(self, min: int, max: int) -> None: ... # pylint: disable=redefined-builtin def get_torque_meas_min(self) -> int: ... diff --git a/tests/safety/common.py b/tests/safety/common.py index 2754d73cf1..4dbbbe437e 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -58,6 +58,10 @@ def setUpClass(cls): cls.safety = None raise unittest.SkipTest + def _reset_safety_hooks(self): + self.safety.set_safety_hooks(self.safety.get_current_safety_mode(), + self.safety.get_current_safety_param()) + def _rx(self, msg): return self.safety.safety_rx_hook(msg) @@ -409,7 +413,7 @@ def test_reset_driver_torque_measurements(self): self.assertTrue(self._rx(self._torque_driver_msg(t))) # reset sample_t by reinitializing the safety mode - self.setUp() + self._reset_safety_hooks() self.assertEqual(self.safety.get_torque_driver_min(), 0) self.assertEqual(self.safety.get_torque_driver_max(), 0) @@ -526,7 +530,7 @@ def test_reset_torque_measurements(self): self.assertTrue(self._rx(self._torque_meas_msg(t))) # reset sample_t by reinitializing the safety mode - self.setUp() + self._reset_safety_hooks() self.assertEqual(self.safety.get_torque_meas_min(), 0) self.assertEqual(self.safety.get_torque_meas_max(), 0) @@ -632,7 +636,7 @@ def test_reset_angle_measurements(self): self.assertTrue(self._rx(self._angle_meas_msg(a))) # reset sample_t by reinitializing the safety mode - self.setUp() + self._reset_safety_hooks() self.assertEqual(self.safety.get_angle_meas_min(), 0) self.assertEqual(self.safety.get_angle_meas_max(), 0) From 3a64b6ccb51301a9f1c7e8f9fd121940d50d01d8 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 4 May 2023 03:37:56 +0000 Subject: [PATCH 036/197] safety: make vehicle_speed a sample (#1391) * convert vehicle_speed into sample_t, change no behavior * draft * round * test * clean up * round * round all * use min * remove round macro from this PR * reset speed measurement * debug * bbd * rm * revert * test above and below * need this now * misra pt 1 * misra pt 2 * misra pt 3 * i don't understand this one, not different from other cases * fix test * test * revert that * draft * test the sample_t works properly for safety modes that use it (angle only) * can combine these tests * test decimals * global * misra comment * suggestions * fix * use new helper --- board/safety.h | 9 +++++---- board/safety/safety_ford.h | 6 +++--- board/safety/safety_nissan.h | 2 +- board/safety/safety_tesla.h | 5 +++-- board/safety_declarations.h | 4 +++- tests/libpanda/safety_helpers.h | 8 ++++++++ tests/libpanda/safety_helpers.py | 4 ++++ tests/safety/common.py | 31 ++++++++++++++++++++++++++++++- 8 files changed, 57 insertions(+), 12 deletions(-) diff --git a/board/safety.h b/board/safety.h index 670b03ceb7..753fbf8b72 100644 --- a/board/safety.h +++ b/board/safety.h @@ -332,7 +332,6 @@ int set_safety_hooks(uint16_t mode, uint16_t param) { regen_braking = false; regen_braking_prev = false; cruise_engaged_prev = false; - vehicle_speed = 0; vehicle_moving = false; acc_main_on = false; cruise_button_prev = 0; @@ -345,6 +344,8 @@ int set_safety_hooks(uint16_t mode, uint16_t param) { valid_steer_req_count = 0; invalid_steer_req_count = 0; + vehicle_speed.min = 0; + vehicle_speed.max = 0; torque_meas.min = 0; torque_meas.max = 0; torque_driver.min = 0; @@ -620,8 +621,8 @@ bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const // add 1 to not false trigger the violation. also fudge the speed by 1 m/s so rate limits are // always slightly above openpilot's in case we read an updated speed in between angle commands // TODO: this speed fudge can be much lower, look at data to determine the lowest reasonable offset - int delta_angle_up = (interpolate(limits.angle_rate_up_lookup, vehicle_speed - 1.) * limits.angle_deg_to_can) + 1.; - int delta_angle_down = (interpolate(limits.angle_rate_down_lookup, vehicle_speed - 1.) * limits.angle_deg_to_can) + 1.; + int delta_angle_up = (interpolate(limits.angle_rate_up_lookup, (vehicle_speed.min / VEHICLE_SPEED_FACTOR) - 1.) * limits.angle_deg_to_can) + 1.; + int delta_angle_down = (interpolate(limits.angle_rate_down_lookup, (vehicle_speed.min / VEHICLE_SPEED_FACTOR) - 1.) * limits.angle_deg_to_can) + 1.; // allow down limits at zero since small floats will be rounded to 0 int highest_desired_angle = desired_angle_last + ((desired_angle_last > 0) ? delta_angle_up : delta_angle_down); @@ -634,7 +635,7 @@ bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const // check that commanded angle value isn't too far from measured, used to limit torque for some safety modes if (limits.enforce_angle_error && controls_allowed && steer_control_enabled) { - if (vehicle_speed > limits.angle_error_min_speed) { + if ((vehicle_speed.values[0] / VEHICLE_SPEED_FACTOR) > limits.angle_error_min_speed) { // val must always be near angle_meas, limited to the maximum value // add 1 to not false trigger the violation int highest_allowed = CLAMP(angle_meas.max + limits.max_angle_error + 1, -limits.max_steer, limits.max_steer); diff --git a/board/safety/safety_ford.h b/board/safety/safety_ford.h index 4e60b19d1e..0fbbf9cbcc 100644 --- a/board/safety/safety_ford.h +++ b/board/safety/safety_ford.h @@ -163,7 +163,7 @@ static int ford_rx_hook(CANPacket_t *to_push) { // Update vehicle speed if (addr == MSG_BrakeSysFeatures) { // Signal: Veh_V_ActlBrk - vehicle_speed = ((GET_BYTE(to_push, 0) << 8) | GET_BYTE(to_push, 1)) * 0.01 / 3.6; + update_sample(&vehicle_speed, (((GET_BYTE(to_push, 0) << 8) | GET_BYTE(to_push, 1)) * 0.01 / 3.6 * VEHICLE_SPEED_FACTOR) + 0.5); } // Check vehicle speed against a second source @@ -171,7 +171,7 @@ static int ford_rx_hook(CANPacket_t *to_push) { // Disable controls if speeds from ABS and PCM ECUs are too far apart. // Signal: Veh_V_ActlEng float filtered_pcm_speed = ((GET_BYTE(to_push, 6) << 8) | GET_BYTE(to_push, 7)) * 0.01 / 3.6; - if (ABS(filtered_pcm_speed - vehicle_speed) > FORD_MAX_SPEED_DELTA) { + if (ABS(filtered_pcm_speed - ((float)vehicle_speed.values[0] / VEHICLE_SPEED_FACTOR)) > FORD_MAX_SPEED_DELTA) { controls_allowed = 0; } } @@ -180,7 +180,7 @@ static int ford_rx_hook(CANPacket_t *to_push) { if (addr == MSG_Yaw_Data_FD1) { // Signal: VehYaw_W_Actl float ford_yaw_rate = (((GET_BYTE(to_push, 2) << 8U) | GET_BYTE(to_push, 3)) * 0.0002) - 6.5; - float current_curvature = ford_yaw_rate / MAX(vehicle_speed, 0.1); + float current_curvature = ford_yaw_rate / MAX(vehicle_speed.values[0] / VEHICLE_SPEED_FACTOR, 0.1); // convert current curvature into units on CAN for comparison with desired curvature int current_curvature_can = current_curvature * (float)FORD_STEERING_LIMITS.angle_deg_to_can + ((current_curvature > 0.) ? 0.5 : -0.5); diff --git a/board/safety/safety_nissan.h b/board/safety/safety_nissan.h index 550a780050..1f15e9b147 100644 --- a/board/safety/safety_nissan.h +++ b/board/safety/safety_nissan.h @@ -65,7 +65,7 @@ static int nissan_rx_hook(CANPacket_t *to_push) { uint16_t right_rear = (GET_BYTE(to_push, 0) << 8) | (GET_BYTE(to_push, 1)); uint16_t left_rear = (GET_BYTE(to_push, 2) << 8) | (GET_BYTE(to_push, 3)); vehicle_moving = (right_rear | left_rear) != 0U; - vehicle_speed = (right_rear + left_rear) / 2.0 * 0.005 / 3.6; + update_sample(&vehicle_speed, ((right_rear + left_rear) / 2.0 * 0.005 / 3.6 * VEHICLE_SPEED_FACTOR) + 0.5); } // X-Trail 0x15c, Leaf 0x239 diff --git a/board/safety/safety_tesla.h b/board/safety/safety_tesla.h index b82d896464..de4001bc78 100644 --- a/board/safety/safety_tesla.h +++ b/board/safety/safety_tesla.h @@ -80,8 +80,9 @@ static int tesla_rx_hook(CANPacket_t *to_push) { if(addr == (tesla_powertrain ? 0x116 : 0x118)) { // Vehicle speed: ((0.05 * val) - 25) * MPH_TO_MPS - vehicle_speed = (((((GET_BYTE(to_push, 3) & 0x0FU) << 8) | (GET_BYTE(to_push, 2))) * 0.05) - 25) * 0.447; - vehicle_moving = ABS(vehicle_speed) > 0.1; + float speed = (((((GET_BYTE(to_push, 3) & 0x0FU) << 8) | (GET_BYTE(to_push, 2))) * 0.05) - 25) * 0.447; + vehicle_moving = ABS(speed) > 0.1; + update_sample(&vehicle_speed, (speed * VEHICLE_SPEED_FACTOR) + 0.5); } if(addr == (tesla_powertrain ? 0x106 : 0x108)) { diff --git a/board/safety_declarations.h b/board/safety_declarations.h index a8bff31e5b..4de886c65f 100644 --- a/board/safety_declarations.h +++ b/board/safety_declarations.h @@ -16,6 +16,8 @@ uint32_t GET_BYTES(const CANPacket_t *msg, int start, int len) { const int MAX_WRONG_COUNTERS = 5; const uint8_t MAX_MISSED_MSGS = 10U; #define MAX_ADDR_CHECK_MSGS 3U +// used to represent floating point vehicle speed in a sample_t +#define VEHICLE_SPEED_FACTOR 100.0 // sample struct that keeps 6 samples in memory struct sample_t { @@ -193,7 +195,7 @@ bool brake_pressed_prev = false; bool regen_braking = false; bool regen_braking_prev = false; bool cruise_engaged_prev = false; -float vehicle_speed = 0; +struct sample_t vehicle_speed; bool vehicle_moving = false; bool acc_main_on = false; // referred to as "ACC off" in ISO 15622:2018 int cruise_button_prev = 0; diff --git a/tests/libpanda/safety_helpers.h b/tests/libpanda/safety_helpers.h index 02480b1ba5..f02c4da343 100644 --- a/tests/libpanda/safety_helpers.h +++ b/tests/libpanda/safety_helpers.h @@ -79,6 +79,14 @@ bool get_acc_main_on(void){ return acc_main_on; } +int get_vehicle_speed_min(void){ + return vehicle_speed.min; +} + +int get_vehicle_speed_max(void){ + return vehicle_speed.max; +} + int get_current_safety_mode(void){ return current_safety_mode; } diff --git a/tests/libpanda/safety_helpers.py b/tests/libpanda/safety_helpers.py index 494f2c0425..e44cc23dda 100644 --- a/tests/libpanda/safety_helpers.py +++ b/tests/libpanda/safety_helpers.py @@ -16,6 +16,8 @@ def setup_safety_helpers(ffi): bool get_brake_pressed_prev(void); bool get_regen_braking_prev(void); bool get_acc_main_on(void); + int get_vehicle_speed_min(void); + int get_vehicle_speed_max(void); int get_current_safety_mode(void); int get_current_safety_param(void); @@ -62,6 +64,8 @@ def get_gas_pressed_prev(self) -> bool: ... def get_brake_pressed_prev(self) -> bool: ... def get_regen_braking_prev(self) -> bool: ... def get_acc_main_on(self) -> bool: ... + def get_vehicle_speed_min(self) -> int: ... + def get_vehicle_speed_max(self) -> int: ... def get_current_safety_mode(self) -> int: ... def get_current_safety_param(self) -> int: ... diff --git a/tests/safety/common.py b/tests/safety/common.py index 4dbbbe437e..0c2c4c8334 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -10,6 +10,7 @@ from panda.tests.libpanda import libpanda_py MAX_WRONG_COUNTERS = 5 +VEHICLE_SPEED_FACTOR = 100 def sign_of(a): @@ -550,6 +551,10 @@ def setUpClass(cls): cls.safety = None raise unittest.SkipTest + @abc.abstractmethod + def _speed_msg(self, speed): + pass + @abc.abstractmethod def _angle_cmd_msg(self, angle: float, enabled: bool): pass @@ -566,6 +571,10 @@ def _reset_angle_measurement(self, angle): for _ in range(6): self._rx(self._angle_meas_msg(angle)) + def _reset_speed_measurement(self, speed): + for _ in range(6): + self._rx(self._speed_msg(speed)) + def test_angle_cmd_when_enabled(self): # when controls are allowed, angle cmd rate limit is enforced speeds = [0., 1., 5., 10., 15., 50.] @@ -577,7 +586,7 @@ def test_angle_cmd_when_enabled(self): # first test against false positives self._reset_angle_measurement(a) - self._rx(self._speed_msg(s)) # pylint: disable=no-member + self._reset_speed_measurement(s) self._set_prev_desired_angle(a) self.safety.set_controls_allowed(1) @@ -641,6 +650,26 @@ def test_reset_angle_measurements(self): self.assertEqual(self.safety.get_angle_meas_min(), 0) self.assertEqual(self.safety.get_angle_meas_max(), 0) + def test_vehicle_speed_measurements(self): + """ + Tests: + - rx hook correctly parses and rounds the vehicle speed + - sample is reset on safety mode init + """ + for speed in np.arange(0, 80, 0.5): + for i in range(6): + self.assertTrue(self._rx(self._speed_msg(speed + i * 0.1))) + + # assert close by one decimal place + self.assertLessEqual(abs(self.safety.get_vehicle_speed_min() - speed * VEHICLE_SPEED_FACTOR), 1) + self.assertLessEqual(abs(self.safety.get_vehicle_speed_max() - (speed + 0.5) * VEHICLE_SPEED_FACTOR), 1) + + # reset sample_t by reinitializing the safety mode + self._reset_safety_hooks() + + self.assertEqual(self.safety.get_vehicle_speed_min(), 0) + self.assertEqual(self.safety.get_vehicle_speed_max(), 0) + @add_regen_tests class PandaSafetyTest(PandaSafetyTestBase): From b44df8151a15fa3afe8a8943e332eb63605a2245 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 4 May 2023 04:09:09 +0000 Subject: [PATCH 037/197] safety tests: use rx/tx helpers (#1399) use rx/tx helpers --- tests/safety/common.py | 6 +++--- tests/safety/hyundai_common.py | 4 ++-- tests/safety/test_honda.py | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/safety/common.py b/tests/safety/common.py index 0c2c4c8334..54f085940c 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -865,15 +865,15 @@ def test_vehicle_moving(self): self.assertFalse(self.safety.get_vehicle_moving()) # not moving - self.safety.safety_rx_hook(self._vehicle_moving_msg(0)) + self._rx(self._vehicle_moving_msg(0)) self.assertFalse(self.safety.get_vehicle_moving()) # speed is at threshold - self.safety.safety_rx_hook(self._vehicle_moving_msg(self.STANDSTILL_THRESHOLD)) + self._rx(self._vehicle_moving_msg(self.STANDSTILL_THRESHOLD)) self.assertFalse(self.safety.get_vehicle_moving()) # past threshold - self.safety.safety_rx_hook(self._vehicle_moving_msg(self.STANDSTILL_THRESHOLD + 1)) + self._rx(self._vehicle_moving_msg(self.STANDSTILL_THRESHOLD + 1)) self.assertTrue(self.safety.get_vehicle_moving()) def test_tx_hook_on_wrong_safety_mode(self): diff --git a/tests/safety/hyundai_common.py b/tests/safety/hyundai_common.py index fde8f77ce2..ad93809714 100644 --- a/tests/safety/hyundai_common.py +++ b/tests/safety/hyundai_common.py @@ -141,10 +141,10 @@ def test_tester_present_allowed(self): addr, bus = self.DISABLED_ECU_UDS_MSG tester_present = libpanda_py.make_CANPacket(addr, bus, b"\x02\x3E\x80\x00\x00\x00\x00\x00") - self.assertTrue(self.safety.safety_tx_hook(tester_present)) + self.assertTrue(self._tx(tester_present)) not_tester_present = libpanda_py.make_CANPacket(addr, bus, b"\x03\xAA\xAA\x00\x00\x00\x00\x00") - self.assertFalse(self.safety.safety_tx_hook(not_tester_present)) + self.assertFalse(self._tx(not_tester_present)) def test_disabled_ecu_alive(self): """ diff --git a/tests/safety/test_honda.py b/tests/safety/test_honda.py index 8e6bed0f8c..16de6e5719 100755 --- a/tests/safety/test_honda.py +++ b/tests/safety/test_honda.py @@ -297,7 +297,7 @@ def test_acc_hud_safety_check(self): for pcm_gas in range(0, 255): for pcm_speed in range(0, 100): send = pcm_gas <= self.MAX_GAS if controls_allowed else pcm_gas == 0 and pcm_speed == 0 - self.assertEqual(send, self.safety.safety_tx_hook(self._send_acc_hud_msg(pcm_gas, pcm_speed))) + self.assertEqual(send, self._tx(self._send_acc_hud_msg(pcm_gas, pcm_speed))) def test_fwd_hook(self): # normal operation, not forwarding AEB @@ -483,10 +483,10 @@ def test_spam_cancel_safety_check(self): def test_diagnostics(self): tester_present = libpanda_py.make_CANPacket(0x18DAB0F1, self.PT_BUS, b"\x02\x3E\x80\x00\x00\x00\x00\x00") - self.assertTrue(self.safety.safety_tx_hook(tester_present)) + self.assertTrue(self._tx(tester_present)) not_tester_present = libpanda_py.make_CANPacket(0x18DAB0F1, self.PT_BUS, b"\x03\xAA\xAA\x00\x00\x00\x00\x00") - self.assertFalse(self.safety.safety_tx_hook(not_tester_present)) + self.assertFalse(self._tx(not_tester_present)) def test_radar_alive(self): # If the radar knockout failed, make sure the relay malfunction is shown @@ -500,7 +500,7 @@ def test_gas_safety_check(self): accel = 0 if gas < 0 else gas / 1000 self.safety.set_controls_allowed(controls_allowed) send = gas <= self.MAX_GAS if controls_allowed else gas == self.NO_GAS - self.assertEqual(send, self.safety.safety_tx_hook(self._send_gas_brake_msg(gas, accel)), (controls_allowed, gas, accel)) + self.assertEqual(send, self._tx(self._send_gas_brake_msg(gas, accel)), (controls_allowed, gas, accel)) def test_brake_safety_check(self): for controls_allowed in [True, False]: From 94cd9a0788070e4e802e8a6a1c1cc7dae95e8f70 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 4 May 2023 04:59:35 +0000 Subject: [PATCH 038/197] Ford: curvature rate limits (#1258) * ford curvature rate limits draft * make common angle tests work with curvature * comment * no need for this * extra line * fix test * generic curvature test class * more reasonable limits * adjust limits * draft * works * works * clean up * add vehicle speed * works * clean up * clean up * more clean up * more clean up * lower * double * add updated bp * remove * can clean that up * draft * this works! * think that's the correct placement * try this * closer * use min * add/sub one to not falsely trip * remove old angle error safety * i'm not sure if clamp is more readable * fix that * fix * stash * fix these tests * ternary * floats are a pain * draft, works kinda * even better * round that * need tolerance * this should work (adding tol=1 wouldn't let us have multiple rate limits) * test works * clamp breaks if low is higher than high :((( down from 150 blocked msgs to 7! * no blocked msgs!!! * test a whole bunch * stash * stash * clean up test * clean up test to be more like torque (+ speeds) * clean up * cmt * test up * up and down are good * rename and remove * this is tested * uncomment * this is tested by ensuring we move towards error at a minimum rate * not used any more * revert common * clean up test_ford a bit more * some clean up (combine variables where it makes sense) * yeah can't use clamp since min isn't always < max, min(max(.. handles this * clean up * revert that * comments * cmt * another * that's old * misra! * Update board/safety/safety_ford.h * Update board/safety/safety_ford.h * add todo, fix test case * more clear, matches panda * add comment * Update tests/safety/test_ford.py * more fine speed increments * rm comment * better names * this is expected behavior (tested by common checks) * CURVATURE_ERROR_LIMIT_SPEED * better name? * pretty clean! * same for up * only used in one place now * these are now clear * common term * make vehicle_speed a sample_t * need to use values[0] * speed is a float * Revert "speed is a float" This reverts commit 01af02f1d35026d983cc2ba6d9ba364c87c800ee. * Revert "need to use values[0]" This reverts commit 8f6d68345a92f7285d07ca071285e903ed7871bb. * Revert "make vehicle_speed a sample_t" This reverts commit ecd8dc86b6b97cc8bff7da697353a8d90c358b12. * safety fixes for new speed sample * test fixes for new speed sample * fix misra and make intermediate variable * this isn't needed --- board/safety.h | 34 +++++++++------- board/safety/safety_ford.h | 9 ++++- board/safety_declarations.h | 1 - tests/safety/test_ford.py | 78 +++++++++++++++++++++++++++---------- 4 files changed, 85 insertions(+), 37 deletions(-) diff --git a/board/safety.h b/board/safety.h index 753fbf8b72..426800cc54 100644 --- a/board/safety.h +++ b/board/safety.h @@ -616,7 +616,7 @@ bool steer_torque_cmd_checks(int desired_torque, int steer_req, const SteeringLi bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const SteeringLimits limits) { bool violation = false; - if (!limits.disable_angle_rate_limits && controls_allowed && steer_control_enabled) { + if (controls_allowed && steer_control_enabled) { // convert floating point angle rate limits to integers in the scale of the desired angle on CAN, // add 1 to not false trigger the violation. also fudge the speed by 1 m/s so rate limits are // always slightly above openpilot's in case we read an updated speed in between angle commands @@ -628,23 +628,29 @@ bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const int highest_desired_angle = desired_angle_last + ((desired_angle_last > 0) ? delta_angle_up : delta_angle_down); int lowest_desired_angle = desired_angle_last - ((desired_angle_last >= 0) ? delta_angle_down : delta_angle_up); - // check for violation; - violation |= max_limit_check(desired_angle, highest_desired_angle, lowest_desired_angle); - } - desired_angle_last = desired_angle; + // check that commanded angle value isn't too far from measured, used to limit torque for some safety modes + // ensure we start moving in direction of meas while respecting rate limits if error is exceeded + if (limits.enforce_angle_error && ((vehicle_speed.values[0] / VEHICLE_SPEED_FACTOR) > limits.angle_error_min_speed)) { + // the rate limits above are liberally above openpilot's to avoid false positives. + // likewise, allow a lower rate for moving towards meas when error is exceeded + int delta_angle_up_lower = interpolate(limits.angle_rate_up_lookup, (vehicle_speed.max / VEHICLE_SPEED_FACTOR) + 1.) * limits.angle_deg_to_can; + int delta_angle_down_lower = interpolate(limits.angle_rate_down_lookup, (vehicle_speed.max / VEHICLE_SPEED_FACTOR) + 1.) * limits.angle_deg_to_can; + + int highest_desired_angle_lower = desired_angle_last + ((desired_angle_last > 0) ? delta_angle_up_lower : delta_angle_down_lower); + int lowest_desired_angle_lower = desired_angle_last - ((desired_angle_last >= 0) ? delta_angle_down_lower : delta_angle_up_lower); - // check that commanded angle value isn't too far from measured, used to limit torque for some safety modes - if (limits.enforce_angle_error && controls_allowed && steer_control_enabled) { - if ((vehicle_speed.values[0] / VEHICLE_SPEED_FACTOR) > limits.angle_error_min_speed) { - // val must always be near angle_meas, limited to the maximum value - // add 1 to not false trigger the violation - int highest_allowed = CLAMP(angle_meas.max + limits.max_angle_error + 1, -limits.max_steer, limits.max_steer); - int lowest_allowed = CLAMP(angle_meas.min - limits.max_angle_error - 1, -limits.max_steer, limits.max_steer); + lowest_desired_angle = MIN(MAX(lowest_desired_angle, angle_meas.min - limits.max_angle_error - 1), highest_desired_angle_lower); + highest_desired_angle = MAX(MIN(highest_desired_angle, angle_meas.max + limits.max_angle_error + 1), lowest_desired_angle_lower); - // check for violation - violation |= max_limit_check(desired_angle, highest_allowed, lowest_allowed); + // don't enforce above the max steer + lowest_desired_angle = CLAMP(lowest_desired_angle, -limits.max_steer, limits.max_steer); + highest_desired_angle = CLAMP(highest_desired_angle, -limits.max_steer, limits.max_steer); } + + // check for violation; + violation |= max_limit_check(desired_angle, highest_desired_angle, lowest_desired_angle); } + desired_angle_last = desired_angle; // Angle should either be 0 or same as current angle while not steering if (!steer_control_enabled) { diff --git a/board/safety/safety_ford.h b/board/safety/safety_ford.h index 0fbbf9cbcc..867b335d76 100644 --- a/board/safety/safety_ford.h +++ b/board/safety/safety_ford.h @@ -138,11 +138,18 @@ const SteeringLimits FORD_STEERING_LIMITS = { .max_steer = 1000, .angle_deg_to_can = 50000, // 1 / (2e-5) rad to can .max_angle_error = 100, // 0.002 * FORD_STEERING_LIMITS.angle_deg_to_can + .angle_rate_up_lookup = { + {5., 25., 25.}, + {0.0002, 0.0001, 0.0001} + }, + .angle_rate_down_lookup = { + {5., 25., 25.}, + {0.000225, 0.00015, 0.00015} + }, // no blending at low speed due to lack of torque wind-up and inaccurate current curvature .angle_error_min_speed = 10.0, // m/s - .disable_angle_rate_limits = true, .enforce_angle_error = true, .inactive_angle_is_zero = true, }; diff --git a/board/safety_declarations.h b/board/safety_declarations.h index 4de886c65f..a35f653f76 100644 --- a/board/safety_declarations.h +++ b/board/safety_declarations.h @@ -73,7 +73,6 @@ typedef struct { const int max_angle_error; // used to limit error between meas and cmd while enabled const float angle_error_min_speed; // minimum speed to start limiting angle error - const bool disable_angle_rate_limits; const bool enforce_angle_error; // enables max_angle_error check const bool inactive_angle_is_zero; // if false, enforces angle near meas when disabled (default) } SteeringLimits; diff --git a/tests/safety/test_ford.py b/tests/safety/test_ford.py index 798210b8c3..828d7b883a 100755 --- a/tests/safety/test_ford.py +++ b/tests/safety/test_ford.py @@ -77,6 +77,10 @@ class TestFordSafety(common.PandaSafetyTest): MAX_CURVATURE_ERROR = 0.002 CURVATURE_ERROR_MIN_SPEED = 10.0 # m/s + ANGLE_RATE_BP = [5., 25., 25.] + ANGLE_RATE_UP = [0.0002, 0.0001, 0.0001] # windup limit + ANGLE_RATE_DOWN = [0.000225, 0.00015, 0.00015] # unwind limit + cnt_speed = 0 cnt_speed_2 = 0 cnt_yaw_rate = 0 @@ -87,9 +91,13 @@ def setUp(self): self.safety.set_safety_hooks(Panda.SAFETY_FORD, 0) self.safety.init_tests() + def _set_prev_desired_angle(self, t): + t = round(t * self.DEG_TO_CAN) + self.safety.set_desired_angle_last(t) + def _reset_curvature_measurement(self, curvature, speed): - self._rx(self._speed_msg(speed)) for _ in range(6): + self._rx(self._speed_msg(speed)) self._rx(self._yaw_rate_msg(curvature, speed)) # Driver brake pedal @@ -240,6 +248,7 @@ def test_steer_allowed(self): for curvature_rate in curvature_rates: for curvature in curvatures: self.safety.set_controls_allowed(controls_allowed) + self._set_prev_desired_angle(curvature) self._reset_curvature_measurement(curvature, speed) should_tx = path_offset == 0 and path_angle == 0 and curvature_rate == 0 @@ -251,28 +260,55 @@ def test_steer_allowed(self): curvature=curvature): self.assertEqual(should_tx, self._tx(self._tja_command_msg(steer_control_enabled, path_offset, path_angle, curvature, curvature_rate))) - def test_steer_meas_delta(self): - """This safety model enforces a maximum distance from measured and commanded curvature, only above a certain speed""" - self.safety.set_controls_allowed(1) - - for steer_control_enabled in (True, False): - for speed in np.linspace(0, 50, 11): - for initial_curvature in np.linspace(-self.MAX_CURVATURE, self.MAX_CURVATURE, 21): - self._reset_curvature_measurement(initial_curvature, speed) + def test_curvature_rate_limit_up(self): + """ + When the curvature error is exceeded, commanded curvature must start moving towards meas respecting rate limits. + Since panda allows higher rate limits to avoid false positives, we need to allow a lower rate to move towards meas. + """ + self.safety.set_controls_allowed(True) + small_curvature = 2 / self.DEG_TO_CAN # significant small amount of curvature to cross boundary - limit_command = speed > self.CURVATURE_ERROR_MIN_SPEED - for new_curvature in np.linspace(-self.MAX_CURVATURE, self.MAX_CURVATURE, 41): - too_far_away = round(abs(new_curvature - initial_curvature), 5) > self.MAX_CURVATURE_ERROR - - if steer_control_enabled: - should_tx = not limit_command or not too_far_away - else: - # enforce angle error limit is disabled when steer request bit is 0 - should_tx = new_curvature == 0 + for speed in np.arange(0, 40, 0.5): + limit_command = speed > self.CURVATURE_ERROR_MIN_SPEED + max_delta_up = np.interp(speed, self.ANGLE_RATE_BP, self.ANGLE_RATE_UP) + max_delta_up_lower = np.interp(speed + 1, self.ANGLE_RATE_BP, self.ANGLE_RATE_UP) + + cases = [ + (not limit_command, 0), + (not limit_command, max_delta_up_lower - small_curvature), + (True, max_delta_up_lower), + (True, max_delta_up), + (False, max_delta_up + small_curvature), + ] + + for sign in (-1, 1): + self._reset_curvature_measurement(sign * (self.MAX_CURVATURE_ERROR + 1e-3), speed) + for should_tx, curvature in cases: + self._set_prev_desired_angle(sign * small_curvature) + self.assertEqual(should_tx, self._tx(self._tja_command_msg(True, 0, 0, sign * (small_curvature + curvature), 0))) + + def test_curvature_rate_limit_down(self): + self.safety.set_controls_allowed(True) + small_curvature = 2 / self.DEG_TO_CAN # significant small amount of curvature to cross boundary - with self.subTest(steer_control_enabled=steer_control_enabled, speed=speed, - initial_curvature=initial_curvature, new_curvature=new_curvature): - self.assertEqual(should_tx, self._tx(self._tja_command_msg(steer_control_enabled, 0, 0, new_curvature, 0))) + for speed in np.arange(0, 40, 0.5): + limit_command = speed > self.CURVATURE_ERROR_MIN_SPEED + max_delta_down = np.interp(speed, self.ANGLE_RATE_BP, self.ANGLE_RATE_DOWN) + max_delta_down_lower = np.interp(speed + 1, self.ANGLE_RATE_BP, self.ANGLE_RATE_DOWN) + + cases = [ + (not limit_command, self.MAX_CURVATURE), + (not limit_command, self.MAX_CURVATURE - max_delta_down_lower + small_curvature), + (True, self.MAX_CURVATURE - max_delta_down_lower), + (True, self.MAX_CURVATURE - max_delta_down), + (False, self.MAX_CURVATURE - max_delta_down - small_curvature), + ] + + for sign in (-1, 1): + self._reset_curvature_measurement(sign * (self.MAX_CURVATURE - self.MAX_CURVATURE_ERROR - 1e-3), speed) + for should_tx, curvature in cases: + self._set_prev_desired_angle(sign * self.MAX_CURVATURE) + self.assertEqual(should_tx, self._tx(self._tja_command_msg(True, 0, 0, sign * curvature, 0))) def test_prevent_lkas_action(self): self.safety.set_controls_allowed(1) From cedb5fd1a6c0823703280b1941edfed9602a287d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 4 May 2023 06:08:10 +0000 Subject: [PATCH 039/197] Ford: remove safety mode from debug flag (#1400) remove from debug --- board/safety.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/board/safety.h b/board/safety.h index 426800cc54..966c503e1b 100644 --- a/board/safety.h +++ b/board/safety.h @@ -307,6 +307,7 @@ const safety_hook_config safety_hook_registry[] = { {SAFETY_HYUNDAI_LEGACY, &hyundai_legacy_hooks}, {SAFETY_MAZDA, &mazda_hooks}, {SAFETY_BODY, &body_hooks}, + {SAFETY_FORD, &ford_hooks}, #ifdef CANFD {SAFETY_HYUNDAI_CANFD, &hyundai_canfd_hooks}, #endif @@ -315,7 +316,6 @@ const safety_hook_config safety_hook_registry[] = { {SAFETY_SUBARU_LEGACY, &subaru_legacy_hooks}, {SAFETY_VOLKSWAGEN_PQ, &volkswagen_pq_hooks}, {SAFETY_ALLOUTPUT, &alloutput_hooks}, - {SAFETY_FORD, &ford_hooks}, #endif }; From fea1feb598d3300d5392570051d9b6ba061e490f Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Thu, 4 May 2023 13:22:01 +0200 Subject: [PATCH 040/197] ADC cleanup (#1401) * cleanup function names * cleanup voltage reading * define constant * fix misra * remove comments --- board/boards/black.h | 2 +- board/boards/board_declarations.h | 2 +- board/boards/dos.h | 2 +- board/boards/grey.h | 2 +- board/boards/pedal.h | 2 +- board/boards/red.h | 2 +- board/boards/red_v2.h | 2 +- board/boards/tres.h | 2 +- board/boards/uno.h | 4 ++-- board/boards/white.h | 6 +++--- board/config.h | 2 ++ board/drivers/harness.h | 4 ++-- board/main_comms.h | 4 ++-- board/pedal/main.c | 4 ++-- board/stm32fx/board.h | 1 + board/stm32fx/lladc.h | 17 +++++------------ board/stm32fx/stm32fx_config.h | 1 - board/stm32h7/board.h | 1 + board/stm32h7/lladc.h | 18 +++++------------- board/stm32h7/stm32h7_config.h | 1 - 20 files changed, 33 insertions(+), 46 deletions(-) diff --git a/board/boards/black.h b/board/boards/black.h index e4d5bb8e58..d1e839d882 100644 --- a/board/boards/black.h +++ b/board/boards/black.h @@ -191,7 +191,7 @@ const board board_black = { .has_canfd = false, .has_rtc_battery = false, .fan_max_rpm = 0U, - .adc_scale = 8862U, + .avdd_mV = 3300U, .fan_stall_recovery = false, .fan_enable_cooldown_time = 0U, .init = black_init, diff --git a/board/boards/board_declarations.h b/board/boards/board_declarations.h index db5c742295..2ee2990d51 100644 --- a/board/boards/board_declarations.h +++ b/board/boards/board_declarations.h @@ -25,7 +25,7 @@ struct board { const bool has_canfd; const bool has_rtc_battery; const uint16_t fan_max_rpm; - const uint16_t adc_scale; + const uint16_t avdd_mV; const bool fan_stall_recovery; const uint8_t fan_enable_cooldown_time; board_init init; diff --git a/board/boards/dos.h b/board/boards/dos.h index 2af19d7cb0..a021d01f2b 100644 --- a/board/boards/dos.h +++ b/board/boards/dos.h @@ -215,7 +215,7 @@ const board board_dos = { .has_canfd = false, .has_rtc_battery = true, .fan_max_rpm = 6500U, - .adc_scale = 8862U, + .avdd_mV = 3300U, .fan_stall_recovery = true, .fan_enable_cooldown_time = 3U, .init = dos_init, diff --git a/board/boards/grey.h b/board/boards/grey.h index ccc7787203..0adb5e2a1c 100644 --- a/board/boards/grey.h +++ b/board/boards/grey.h @@ -45,7 +45,7 @@ const board board_grey = { .has_canfd = false, .has_rtc_battery = false, .fan_max_rpm = 0U, - .adc_scale = 8862U, + .avdd_mV = 3300U, .fan_stall_recovery = false, .fan_enable_cooldown_time = 0U, .init = grey_init, diff --git a/board/boards/pedal.h b/board/boards/pedal.h index 509a4bb4b0..d545a9faf1 100644 --- a/board/boards/pedal.h +++ b/board/boards/pedal.h @@ -83,7 +83,7 @@ const board board_pedal = { .has_canfd = false, .has_rtc_battery = false, .fan_max_rpm = 0U, - .adc_scale = 8862U, + .avdd_mV = 3300U, .fan_stall_recovery = false, .fan_enable_cooldown_time = 0U, .init = pedal_init, diff --git a/board/boards/red.h b/board/boards/red.h index 2be859d9f6..1656421319 100644 --- a/board/boards/red.h +++ b/board/boards/red.h @@ -178,7 +178,7 @@ const board board_red = { .has_canfd = true, .has_rtc_battery = false, .fan_max_rpm = 0U, - .adc_scale = 5539U, + .avdd_mV = 3300U, .fan_stall_recovery = false, .fan_enable_cooldown_time = 0U, .init = red_init, diff --git a/board/boards/red_v2.h b/board/boards/red_v2.h index 3e8f2f2e0e..a9dc4d4564 100644 --- a/board/boards/red_v2.h +++ b/board/boards/red_v2.h @@ -22,7 +22,7 @@ const board board_red_v2 = { .has_canfd = true, .has_rtc_battery = true, .fan_max_rpm = 0U, - .adc_scale = 5539U, + .avdd_mV = 3300U, .fan_stall_recovery = false, .fan_enable_cooldown_time = 0U, .init = red_panda_v2_init, diff --git a/board/boards/tres.h b/board/boards/tres.h index c7c954ab5a..7bd6494752 100644 --- a/board/boards/tres.h +++ b/board/boards/tres.h @@ -94,7 +94,7 @@ const board board_tres = { .has_canfd = true, .has_rtc_battery = true, .fan_max_rpm = 6600U, - .adc_scale = 3021U, + .avdd_mV = 1800U, .fan_stall_recovery = false, .fan_enable_cooldown_time = 3U, .init = tres_init, diff --git a/board/boards/uno.h b/board/boards/uno.h index abf8331c6f..bf4c74bbff 100644 --- a/board/boards/uno.h +++ b/board/boards/uno.h @@ -215,7 +215,7 @@ void uno_init(void) { } // Switch to phone usb mode if harness connection is powered by less than 7V - if(adc_get_voltage(current_board->adc_scale) < 7000U){ + if((adc_get_mV(ADCCHAN_VIN) * VIN_READOUT_DIVIDER) < 7000U){ uno_set_usb_switch(true); } else { uno_set_usb_switch(false); @@ -251,7 +251,7 @@ const board board_uno = { .has_canfd = false, .has_rtc_battery = true, .fan_max_rpm = 5100U, - .adc_scale = 8862U, + .avdd_mV = 3300U, .fan_stall_recovery = false, .fan_enable_cooldown_time = 0U, .init = uno_init, diff --git a/board/boards/white.h b/board/boards/white.h index ebca3b2233..45d860181b 100644 --- a/board/boards/white.h +++ b/board/boards/white.h @@ -142,7 +142,7 @@ void white_set_can_mode(uint8_t mode){ } uint32_t white_read_current(void){ - return adc_get(ADCCHAN_CURRENT); + return adc_get_raw(ADCCHAN_CURRENT); } bool white_check_ignition(void){ @@ -214,7 +214,7 @@ void white_grey_common_init(void) { white_set_can_mode(CAN_MODE_NORMAL); // Init usb power mode - uint32_t voltage = adc_get_voltage(current_board->adc_scale); + uint32_t voltage = adc_get_mV(ADCCHAN_VIN) * VIN_READOUT_DIVIDER; // Init in CDP mode only if panda is powered by 12V. // Otherwise a PC would not be able to flash a standalone panda if (voltage > 8000U) { // 8V threshold @@ -247,7 +247,7 @@ const board board_white = { .has_canfd = false, .has_rtc_battery = false, .fan_max_rpm = 0U, - .adc_scale = 8862U, + .avdd_mV = 3300U, .fan_stall_recovery = false, .fan_enable_cooldown_time = 0U, .init = white_init, diff --git a/board/config.h b/board/config.h index 6bc3f80df6..12b5140076 100644 --- a/board/config.h +++ b/board/config.h @@ -15,6 +15,8 @@ #define USBPACKET_MAX_SIZE 0x40U #define MAX_CAN_MSGS_PER_BULK_TRANSFER 51U +#define VIN_READOUT_DIVIDER 11U + // USB definitions #define USB_VID 0xBBAAU diff --git a/board/drivers/harness.h b/board/drivers/harness.h index c2b5ac95f8..e4b7698e5f 100644 --- a/board/drivers/harness.h +++ b/board/drivers/harness.h @@ -53,8 +53,8 @@ uint8_t harness_detect_orientation(void) { uint8_t ret = HARNESS_STATUS_NC; #ifndef BOOTSTUB - uint32_t sbu1_voltage = adc_get(current_board->harness_config->adc_channel_SBU1); - uint32_t sbu2_voltage = adc_get(current_board->harness_config->adc_channel_SBU2); + uint32_t sbu1_voltage = adc_get_raw(current_board->harness_config->adc_channel_SBU1); + uint32_t sbu2_voltage = adc_get_raw(current_board->harness_config->adc_channel_SBU2); // Detect connection and orientation if((sbu1_voltage < HARNESS_CONNECTED_THRESHOLD) || (sbu2_voltage < HARNESS_CONNECTED_THRESHOLD)){ diff --git a/board/main_comms.h b/board/main_comms.h index 3ccb07cd26..2fe028571e 100644 --- a/board/main_comms.h +++ b/board/main_comms.h @@ -9,10 +9,10 @@ int get_health_pkt(void *dat) { struct health_t * health = (struct health_t*)dat; health->uptime_pkt = uptime_cnt; - health->voltage_pkt = adc_get_voltage(current_board->adc_scale); + health->voltage_pkt = adc_get_mV(ADCCHAN_VIN) * VIN_READOUT_DIVIDER; health->current_pkt = current_board->read_current(); - //Use the GPIO pin to determine ignition or use a CAN based logic + // Use the GPIO pin to determine ignition or use a CAN based logic health->ignition_line_pkt = (uint8_t)(current_board->check_ignition()); health->ignition_can_pkt = (uint8_t)(ignition_can); diff --git a/board/pedal/main.c b/board/pedal/main.c index 71df7fe4e2..2b36fca826 100644 --- a/board/pedal/main.c +++ b/board/pedal/main.c @@ -244,8 +244,8 @@ void TIM3_IRQ_Handler(void) { void pedal(void) { // read/write - pdl0 = adc_get(ADCCHAN_ACCEL0); - pdl1 = adc_get(ADCCHAN_ACCEL1); + pdl0 = adc_get_raw(ADCCHAN_ACCEL0); + pdl1 = adc_get_raw(ADCCHAN_ACCEL1); // write the pedal to the DAC if (state == NO_FAULT) { diff --git a/board/stm32fx/board.h b/board/stm32fx/board.h index 878c1be9ec..76593a5294 100644 --- a/board/stm32fx/board.h +++ b/board/stm32fx/board.h @@ -5,6 +5,7 @@ #include "boards/unused_funcs.h" // ///// Board definition and detection ///// // +#include "stm32fx/lladc.h" #include "drivers/harness.h" #ifdef PANDA #include "drivers/fan.h" diff --git a/board/stm32fx/lladc.h b/board/stm32fx/lladc.h index 0d3e8e028a..31c17f4b25 100644 --- a/board/stm32fx/lladc.h +++ b/board/stm32fx/lladc.h @@ -5,7 +5,7 @@ #define ADCCHAN_ACCEL0 10 #define ADCCHAN_ACCEL1 11 -#define ADCCHAN_VOLTAGE 12 +#define ADCCHAN_VIN 12 #define ADCCHAN_CURRENT 13 void register_set(volatile uint32_t *addr, uint32_t val, uint32_t mask); @@ -16,9 +16,9 @@ void adc_init(void) { register_set(&(ADC1->SMPR1), ADC_SMPR1_SMP12 | ADC_SMPR1_SMP13, 0x7FFFFFFU); } -uint32_t adc_get(unsigned int channel) { +uint16_t adc_get_raw(uint8_t channel) { // Select channel - register_set(&(ADC1->JSQR), (channel << 15U), 0x3FFFFFU); + register_set(&(ADC1->JSQR), ((uint32_t) channel << 15U), 0x3FFFFFU); // Start conversion ADC1->SR &= ~(ADC_SR_JEOC); @@ -28,13 +28,6 @@ uint32_t adc_get(unsigned int channel) { return ADC1->JDR1; } -uint32_t adc_get_voltage(uint16_t scale) { - // REVC has a 10, 1 (1/11) voltage divider - // Here is the calculation for the scale (s) - // ADCV = VIN_S * (1/11) * (4095/3.3) - // RETVAL = ADCV * s = VIN_S*1000 - // s = 1000/((4095/3.3)*(1/11)) = 8.8623046875 - - // Avoid needing floating point math, so output in mV - return (adc_get(ADCCHAN_VOLTAGE) * scale) / 1000U; +uint16_t adc_get_mV(uint8_t channel) { + return (adc_get_raw(channel) * current_board->avdd_mV) / 4095U; } diff --git a/board/stm32fx/stm32fx_config.h b/board/stm32fx/stm32fx_config.h index 073cb07774..9398c47d58 100644 --- a/board/stm32fx/stm32fx_config.h +++ b/board/stm32fx/stm32fx_config.h @@ -63,7 +63,6 @@ #include "stm32fx/peripherals.h" #include "stm32fx/interrupt_handlers.h" #include "drivers/timers.h" -#include "stm32fx/lladc.h" #include "stm32fx/board.h" #include "stm32fx/clock.h" diff --git a/board/stm32h7/board.h b/board/stm32h7/board.h index 086db86b3c..d991e433b1 100644 --- a/board/stm32h7/board.h +++ b/board/stm32h7/board.h @@ -5,6 +5,7 @@ #include "boards/unused_funcs.h" // ///// Board definition and detection ///// // +#include "stm32h7/lladc.h" #include "drivers/harness.h" #include "drivers/fan.h" #include "stm32h7/llfan.h" diff --git a/board/stm32h7/lladc.h b/board/stm32h7/lladc.h index 3abfef96b7..29d5e19d69 100644 --- a/board/stm32h7/lladc.h +++ b/board/stm32h7/lladc.h @@ -1,6 +1,6 @@ // 5VOUT_S = ADC12_INP5 // VOLT_S = ADC1_INP2 -#define ADCCHAN_VOLTAGE 2 +#define ADCCHAN_VIN 2 void adc_init(void) { ADC1->CR &= ~(ADC_CR_DEEPPWD); //Reset deep-power-down mode @@ -17,10 +17,10 @@ void adc_init(void) { while(!(ADC1->ISR & ADC_ISR_ADRDY)); } -uint32_t adc_get(unsigned int channel) { +uint16_t adc_get_raw(uint8_t channel) { ADC1->SQR1 &= ~(ADC_SQR1_L); - ADC1->SQR1 = (channel << 6U); + ADC1->SQR1 = ((uint32_t) channel << 6U); ADC1->SMPR1 = (0x7U << (channel * 3U) ); ADC1->PCSEL_RES0 = (0x1U << channel); @@ -36,14 +36,6 @@ uint32_t adc_get(unsigned int channel) { return res; } -uint32_t adc_get_voltage(uint16_t scale) { - // REVC has a 10, 1 (1/11) voltage divider - // Here is the calculation for the scale (s) - // ADCV = VIN_S * (1/11) * (65535/3.3) - // RETVAL = ADCV * s = VIN_S*1000 - // s = 1000/((65535/3.3)*(1/11)) = 0.553902494 - // s = 1000/((65535/1.8)*(1/11)) = 0.3021 - - // Avoid needing floating point math, so output in mV - return (adc_get(ADCCHAN_VOLTAGE) * scale) / 10000U; +uint16_t adc_get_mV(uint8_t channel) { + return (adc_get_raw(channel) * current_board->avdd_mV) / 65535U; } diff --git a/board/stm32h7/stm32h7_config.h b/board/stm32h7/stm32h7_config.h index 6c4499cdee..c9feec2076 100644 --- a/board/stm32h7/stm32h7_config.h +++ b/board/stm32h7/stm32h7_config.h @@ -67,7 +67,6 @@ separate IRQs for RX and TX. #include "stm32h7/peripherals.h" #include "stm32h7/interrupt_handlers.h" #include "drivers/timers.h" -#include "stm32h7/lladc.h" #if !defined(BOOTSTUB) && defined(PANDA) #include "drivers/uart.h" From e7f2e72c3dabb418226253f7fd57bf3f5e0a36df Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 5 May 2023 16:46:38 -0700 Subject: [PATCH 041/197] setup independent watchdog (#1404) * setup independent watchdog * builds * same file * configurable timeout * disable for now * no feed * rm --------- Co-authored-by: Comma Device --- board/drivers/watchdog.h | 30 ++++++++++++++++++++++++++++++ board/pedal/main.c | 2 +- board/stm32fx/clock.h | 14 -------------- board/stm32fx/stm32fx_config.h | 3 +++ board/stm32h7/stm32h7_config.h | 3 +++ 5 files changed, 37 insertions(+), 15 deletions(-) create mode 100644 board/drivers/watchdog.h diff --git a/board/drivers/watchdog.h b/board/drivers/watchdog.h new file mode 100644 index 0000000000..d0ee32cb2d --- /dev/null +++ b/board/drivers/watchdog.h @@ -0,0 +1,30 @@ +// TODO: why doesn't it define these? +#ifdef STM32F2 +#define IWDG_PR_PR_Msk 0x7U +#define IWDG_RLR_RL_Msk 0xFFFU +#endif + +typedef enum { + WATCHDOG_50_MS = (400U - 1U), + WATCHDOG_500_MS = 4000U, +} WatchdogTimeout; + +void watchdog_feed(void) { + IND_WDG->KR = 0xAAAAU; +} + +void watchdog_init(WatchdogTimeout timeout) { + // enable watchdog + IND_WDG->KR = 0xCCCCU; + IND_WDG->KR = 0x5555U; + + // 32KHz / 4 prescaler = 8000Hz + register_set(&(IND_WDG->PR), 0x0U, IWDG_PR_PR_Msk); + register_set(&(IND_WDG->RLR), timeout, IWDG_RLR_RL_Msk); + + // wait for watchdog to be updated + while (IND_WDG->SR != 0U); + + // start the countdown + watchdog_feed(); +} diff --git a/board/pedal/main.c b/board/pedal/main.c index 2b36fca826..e6b197cf73 100644 --- a/board/pedal/main.c +++ b/board/pedal/main.c @@ -302,7 +302,7 @@ int main(void) { timer_init(TIM3, 15); NVIC_EnableIRQ(TIM3_IRQn); - watchdog_init(); + watchdog_init(WATCHDOG_50_MS); print("**** INTERRUPTS ON ****\n"); enable_interrupts(); diff --git a/board/stm32fx/clock.h b/board/stm32fx/clock.h index fb918d3125..19be574438 100644 --- a/board/stm32fx/clock.h +++ b/board/stm32fx/clock.h @@ -32,17 +32,3 @@ void clock_init(void) { // *** running on PLL *** } - -void watchdog_init(void) { - // setup watchdog - IWDG->KR = 0x5555U; - register_set(&(IWDG->PR), 0x0U, 0x7U); // divider/4 - - // 0 = 0.125 ms, let's have a 50ms watchdog - register_set(&(IWDG->RLR), (400U-1U), 0xFFFU); - IWDG->KR = 0xCCCCU; -} - -void watchdog_feed(void) { - IWDG->KR = 0xAAAAU; -} diff --git a/board/stm32fx/stm32fx_config.h b/board/stm32fx/stm32fx_config.h index 9398c47d58..17ddf9cd3a 100644 --- a/board/stm32fx/stm32fx_config.h +++ b/board/stm32fx/stm32fx_config.h @@ -36,6 +36,8 @@ #define INTERRUPT_TIMER_IRQ TIM6_DAC_IRQn #define INTERRUPT_TIMER TIM6 +#define IND_WDG IWDG + #define PROVISION_CHUNK_ADDRESS 0x1FFF79E0U #define DEVICE_SERIAL_NUMBER_ADDRESS 0x1FFF79C0U @@ -65,6 +67,7 @@ #include "drivers/timers.h" #include "stm32fx/board.h" #include "stm32fx/clock.h" +#include "drivers/watchdog.h" #if defined(PANDA) || defined(BOOTSTUB) #include "drivers/spi.h" diff --git a/board/stm32h7/stm32h7_config.h b/board/stm32h7/stm32h7_config.h index c9feec2076..4d27ed6ba9 100644 --- a/board/stm32h7/stm32h7_config.h +++ b/board/stm32h7/stm32h7_config.h @@ -44,6 +44,8 @@ separate IRQs for RX and TX. #define INTERRUPT_TIMER_IRQ TIM6_DAC_IRQn #define INTERRUPT_TIMER TIM6 +#define IND_WDG IWDG1 + #define PROVISION_CHUNK_ADDRESS 0x080FFFE0U #define DEVICE_SERIAL_NUMBER_ADDRESS 0x080FFFC0U @@ -67,6 +69,7 @@ separate IRQs for RX and TX. #include "stm32h7/peripherals.h" #include "stm32h7/interrupt_handlers.h" #include "drivers/timers.h" +#include "drivers/watchdog.h" #if !defined(BOOTSTUB) && defined(PANDA) #include "drivers/uart.h" From 35609dfdcef606f5f5fe7ba4ccea0edda173ba14 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 6 May 2023 21:25:25 -0700 Subject: [PATCH 042/197] safety utils: add ROUND helper (#1397) * add round macro * Update board/utils.h * function * one line * misra * use here too --- board/safety.h | 4 ++++ board/safety/safety_ford.h | 6 ++---- board/safety/safety_nissan.h | 2 +- board/safety/safety_tesla.h | 2 +- board/safety_declarations.h | 1 + 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/board/safety.h b/board/safety.h index 966c503e1b..7a16c90634 100644 --- a/board/safety.h +++ b/board/safety.h @@ -494,6 +494,10 @@ float interpolate(struct lookup_t xy, float x) { return ret; } +int ROUND(float val) { + return val + ((val > 0.0) ? 0.5 : -0.5); +} + // Safety checks for longitudinal actuation bool longitudinal_accel_checks(int desired_accel, const LongitudinalLimits limits) { bool accel_valid = get_longitudinal_allowed() && !max_limit_check(desired_accel, limits.max_accel, limits.min_accel); diff --git a/board/safety/safety_ford.h b/board/safety/safety_ford.h index 867b335d76..dafca91ee4 100644 --- a/board/safety/safety_ford.h +++ b/board/safety/safety_ford.h @@ -170,7 +170,7 @@ static int ford_rx_hook(CANPacket_t *to_push) { // Update vehicle speed if (addr == MSG_BrakeSysFeatures) { // Signal: Veh_V_ActlBrk - update_sample(&vehicle_speed, (((GET_BYTE(to_push, 0) << 8) | GET_BYTE(to_push, 1)) * 0.01 / 3.6 * VEHICLE_SPEED_FACTOR) + 0.5); + update_sample(&vehicle_speed, ROUND(((GET_BYTE(to_push, 0) << 8) | GET_BYTE(to_push, 1)) * 0.01 / 3.6 * VEHICLE_SPEED_FACTOR)); } // Check vehicle speed against a second source @@ -189,9 +189,7 @@ static int ford_rx_hook(CANPacket_t *to_push) { float ford_yaw_rate = (((GET_BYTE(to_push, 2) << 8U) | GET_BYTE(to_push, 3)) * 0.0002) - 6.5; float current_curvature = ford_yaw_rate / MAX(vehicle_speed.values[0] / VEHICLE_SPEED_FACTOR, 0.1); // convert current curvature into units on CAN for comparison with desired curvature - int current_curvature_can = current_curvature * (float)FORD_STEERING_LIMITS.angle_deg_to_can + - ((current_curvature > 0.) ? 0.5 : -0.5); - update_sample(&angle_meas, current_curvature_can); + update_sample(&angle_meas, ROUND(current_curvature * (float)FORD_STEERING_LIMITS.angle_deg_to_can)); } // Update gas pedal diff --git a/board/safety/safety_nissan.h b/board/safety/safety_nissan.h index 1f15e9b147..1f44238e4e 100644 --- a/board/safety/safety_nissan.h +++ b/board/safety/safety_nissan.h @@ -65,7 +65,7 @@ static int nissan_rx_hook(CANPacket_t *to_push) { uint16_t right_rear = (GET_BYTE(to_push, 0) << 8) | (GET_BYTE(to_push, 1)); uint16_t left_rear = (GET_BYTE(to_push, 2) << 8) | (GET_BYTE(to_push, 3)); vehicle_moving = (right_rear | left_rear) != 0U; - update_sample(&vehicle_speed, ((right_rear + left_rear) / 2.0 * 0.005 / 3.6 * VEHICLE_SPEED_FACTOR) + 0.5); + update_sample(&vehicle_speed, ROUND((right_rear + left_rear) / 2.0 * 0.005 / 3.6 * VEHICLE_SPEED_FACTOR)); } // X-Trail 0x15c, Leaf 0x239 diff --git a/board/safety/safety_tesla.h b/board/safety/safety_tesla.h index de4001bc78..484cb84123 100644 --- a/board/safety/safety_tesla.h +++ b/board/safety/safety_tesla.h @@ -82,7 +82,7 @@ static int tesla_rx_hook(CANPacket_t *to_push) { // Vehicle speed: ((0.05 * val) - 25) * MPH_TO_MPS float speed = (((((GET_BYTE(to_push, 3) & 0x0FU) << 8) | (GET_BYTE(to_push, 2))) * 0.05) - 25) * 0.447; vehicle_moving = ABS(speed) > 0.1; - update_sample(&vehicle_speed, (speed * VEHICLE_SPEED_FACTOR) + 0.5); + update_sample(&vehicle_speed, ROUND(speed * VEHICLE_SPEED_FACTOR)); } if(addr == (tesla_powertrain ? 0x106 : 0x108)) { diff --git a/board/safety_declarations.h b/board/safety_declarations.h index a35f653f76..393e55467e 100644 --- a/board/safety_declarations.h +++ b/board/safety_declarations.h @@ -141,6 +141,7 @@ bool driver_limit_check(int val, int val_last, struct sample_t *val_driver, bool get_longitudinal_allowed(void); bool rt_rate_limit_check(int val, int val_last, const int MAX_RT_DELTA); float interpolate(struct lookup_t xy, float x); +int ROUND(float val); void gen_crc_lookup_table_8(uint8_t poly, uint8_t crc_lut[]); void gen_crc_lookup_table_16(uint16_t poly, uint16_t crc_lut[]); bool msg_allowed(CANPacket_t *to_send, const CanMsg msg_list[], int len); From 2c937656cfb0710ae03d25ff86d167345cbbf26d Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Mon, 8 May 2023 13:30:23 +0200 Subject: [PATCH 043/197] Continuous harness detection (#1402) * read in mV and make threshold board-dependent * refactor and add SBU voltages to health * refactor relay driving * more refactoring and readout lock * avoid race condition * fix misra * continuous detection * 1Hz is fine * another race condition * use harness detection to trigger bootkick * update orientation detection test * more in-depth harness tests * fix ignition * fix * raise threshold --- board/boards/black.h | 6 +- board/boards/board_declarations.h | 2 +- board/boards/dos.h | 12 ++-- board/boards/red.h | 6 +- board/boards/red_chiplet.h | 4 +- board/boards/tres.h | 4 +- board/boards/uno.h | 9 +-- board/boards/unused_funcs.h | 3 +- board/drivers/harness.h | 108 ++++++++++++++++++++---------- board/health.h | 4 +- board/main.c | 6 +- board/main_comms.h | 5 +- board/stm32fx/llexti.h | 2 +- board/stm32fx/stm32fx_config.h | 3 - board/stm32h7/llexti.h | 2 +- board/stm32h7/stm32h7_config.h | 3 - python/__init__.py | 10 ++- tests/hitl/2_health.py | 56 +++++++++++++--- 18 files changed, 163 insertions(+), 82 deletions(-) diff --git a/board/boards/black.h b/board/boards/black.h index d1e839d882..216def774f 100644 --- a/board/boards/black.h +++ b/board/boards/black.h @@ -25,7 +25,7 @@ void black_enable_can_transceiver(uint8_t transceiver, bool enabled) { void black_enable_can_transceivers(bool enabled) { for(uint8_t i=1U; i<=4U; i++){ // Leave main CAN always on for CAN-based ignition detection - if((car_harness_status == HARNESS_STATUS_FLIPPED) ? (i == 3U) : (i == 1U)){ + if((harness.status == HARNESS_STATUS_FLIPPED) ? (i == 3U) : (i == 1U)){ black_enable_can_transceiver(i, true); } else { black_enable_can_transceiver(i, enabled); @@ -83,7 +83,7 @@ void black_set_can_mode(uint8_t mode){ switch (mode) { case CAN_MODE_NORMAL: case CAN_MODE_OBD_CAN2: - if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(car_harness_status == HARNESS_STATUS_FLIPPED)) { + if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(harness.status == HARNESS_STATUS_FLIPPED)) { // B12,B13: disable OBD mode set_gpio_mode(GPIOB, 12, MODE_INPUT); set_gpio_mode(GPIOB, 13, MODE_INPUT); @@ -160,7 +160,7 @@ void black_init(void) { black_set_can_mode(CAN_MODE_NORMAL); // flip CAN0 and CAN2 if we are flipped - if (car_harness_status == HARNESS_STATUS_FLIPPED) { + if (harness.status == HARNESS_STATUS_FLIPPED) { can_flip_buses(0, 2); } } diff --git a/board/boards/board_declarations.h b/board/boards/board_declarations.h index 2ee2990d51..db9bbd4028 100644 --- a/board/boards/board_declarations.h +++ b/board/boards/board_declarations.h @@ -11,7 +11,7 @@ typedef void (*board_set_ir_power)(uint8_t percentage); typedef void (*board_set_fan_enabled)(bool enabled); typedef void (*board_set_phone_power)(bool enabled); typedef void (*board_set_siren)(bool enabled); -typedef void (*board_board_tick)(bool ignition, bool usb_enum, bool heartbeat_seen); +typedef void (*board_board_tick)(bool ignition, bool usb_enum, bool heartbeat_seen, bool harness_inserted); typedef bool (*board_read_som_gpio)(void); struct board { diff --git a/board/boards/dos.h b/board/boards/dos.h index a021d01f2b..d2bf7d3bf3 100644 --- a/board/boards/dos.h +++ b/board/boards/dos.h @@ -25,7 +25,7 @@ void dos_enable_can_transceiver(uint8_t transceiver, bool enabled) { void dos_enable_can_transceivers(bool enabled) { for(uint8_t i=1U; i<=4U; i++){ // Leave main CAN always on for CAN-based ignition detection - if((car_harness_status == HARNESS_STATUS_FLIPPED) ? (i == 3U) : (i == 1U)){ + if((harness.status == HARNESS_STATUS_FLIPPED) ? (i == 3U) : (i == 1U)){ dos_enable_can_transceiver(i, true); } else { dos_enable_can_transceiver(i, enabled); @@ -53,9 +53,9 @@ void dos_set_bootkick(bool enabled){ set_gpio_output(GPIOC, 4, !enabled); } -void dos_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen) { - if (ignition && !usb_enum) { - // enable bootkick if ignition seen +void dos_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen, bool harness_inserted) { + if ((ignition && !usb_enum) || harness_inserted) { + // enable bootkick if ignition seen or if plugged into a harness dos_set_bootkick(true); } else if (heartbeat_seen) { // disable once openpilot is up @@ -69,7 +69,7 @@ void dos_set_can_mode(uint8_t mode){ switch (mode) { case CAN_MODE_NORMAL: case CAN_MODE_OBD_CAN2: - if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(car_harness_status == HARNESS_STATUS_FLIPPED)) { + if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(harness.status == HARNESS_STATUS_FLIPPED)) { // B12,B13: disable OBD mode set_gpio_mode(GPIOB, 12, MODE_INPUT); set_gpio_mode(GPIOB, 13, MODE_INPUT); @@ -177,7 +177,7 @@ void dos_init(void) { dos_set_can_mode(CAN_MODE_NORMAL); // flip CAN0 and CAN2 if we are flipped - if (car_harness_status == HARNESS_STATUS_FLIPPED) { + if (harness.status == HARNESS_STATUS_FLIPPED) { can_flip_buses(0, 2); } diff --git a/board/boards/red.h b/board/boards/red.h index 1656421319..e7dccc00ed 100644 --- a/board/boards/red.h +++ b/board/boards/red.h @@ -22,7 +22,7 @@ void red_enable_can_transceiver(uint8_t transceiver, bool enabled) { } void red_enable_can_transceivers(bool enabled) { - uint8_t main_bus = (car_harness_status == HARNESS_STATUS_FLIPPED) ? 3U : 1U; + uint8_t main_bus = (harness.status == HARNESS_STATUS_FLIPPED) ? 3U : 1U; for (uint8_t i=1U; i<=4U; i++) { // Leave main CAN always on for CAN-based ignition detection if (i == main_bus) { @@ -53,7 +53,7 @@ void red_set_can_mode(uint8_t mode) { switch (mode) { case CAN_MODE_NORMAL: case CAN_MODE_OBD_CAN2: - if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(car_harness_status == HARNESS_STATUS_FLIPPED)) { + if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(harness.status == HARNESS_STATUS_FLIPPED)) { // B12,B13: disable normal mode set_gpio_pullup(GPIOB, 12, PULL_NONE); set_gpio_mode(GPIOB, 12, MODE_ANALOG); @@ -147,7 +147,7 @@ void red_init(void) { red_set_can_mode(CAN_MODE_NORMAL); // flip CAN0 and CAN2 if we are flipped - if (car_harness_status == HARNESS_STATUS_FLIPPED) { + if (harness.status == HARNESS_STATUS_FLIPPED) { can_flip_buses(0, 2); } } diff --git a/board/boards/red_chiplet.h b/board/boards/red_chiplet.h index c4816a9e90..8a3f8dbd98 100644 --- a/board/boards/red_chiplet.h +++ b/board/boards/red_chiplet.h @@ -24,7 +24,7 @@ void red_chiplet_enable_can_transceiver(uint8_t transceiver, bool enabled) { } void red_chiplet_enable_can_transceivers(bool enabled) { - uint8_t main_bus = (car_harness_status == HARNESS_STATUS_FLIPPED) ? 3U : 1U; + uint8_t main_bus = (harness.status == HARNESS_STATUS_FLIPPED) ? 3U : 1U; for (uint8_t i=1U; i<=4U; i++) { // Leave main CAN always on for CAN-based ignition detection if (i == main_bus) { @@ -92,7 +92,7 @@ void red_chiplet_init(void) { red_set_can_mode(CAN_MODE_NORMAL); // flip CAN0 and CAN2 if we are flipped - if (car_harness_status == HARNESS_STATUS_FLIPPED) { + if (harness.status == HARNESS_STATUS_FLIPPED) { can_flip_buses(0, 2); } } diff --git a/board/boards/tres.h b/board/boards/tres.h index 7bd6494752..095d9f8cc1 100644 --- a/board/boards/tres.h +++ b/board/boards/tres.h @@ -19,9 +19,9 @@ void tres_set_bootkick(bool enabled){ } bool tres_ignition_prev = false; -void tres_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen) { +void tres_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen, bool harness_inserted) { UNUSED(usb_enum); - if (ignition && !tres_ignition_prev) { + if ((ignition && !tres_ignition_prev) || harness_inserted) { // enable bootkick on rising edge of ignition tres_set_bootkick(true); } else if (heartbeat_seen) { diff --git a/board/boards/uno.h b/board/boards/uno.h index bf4c74bbff..9d019b37ad 100644 --- a/board/boards/uno.h +++ b/board/boards/uno.h @@ -27,7 +27,7 @@ void uno_enable_can_transceiver(uint8_t transceiver, bool enabled) { void uno_enable_can_transceivers(bool enabled) { for(uint8_t i=1U; i<=4U; i++){ // Leave main CAN always on for CAN-based ignition detection - if((car_harness_status == HARNESS_STATUS_FLIPPED) ? (i == 3U) : (i == 1U)){ + if((harness.status == HARNESS_STATUS_FLIPPED) ? (i == 3U) : (i == 1U)){ uno_enable_can_transceiver(i, true); } else { uno_enable_can_transceiver(i, enabled); @@ -102,7 +102,7 @@ void uno_set_can_mode(uint8_t mode){ switch (mode) { case CAN_MODE_NORMAL: case CAN_MODE_OBD_CAN2: - if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(car_harness_status == HARNESS_STATUS_FLIPPED)) { + if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(harness.status == HARNESS_STATUS_FLIPPED)) { // B12,B13: disable OBD mode set_gpio_mode(GPIOB, 12, MODE_INPUT); set_gpio_mode(GPIOB, 13, MODE_INPUT); @@ -126,10 +126,11 @@ void uno_set_can_mode(uint8_t mode){ } } -void uno_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen) { +void uno_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen, bool harness_inserted) { UNUSED(ignition); UNUSED(usb_enum); UNUSED(heartbeat_seen); + UNUSED(harness_inserted); if (bootkick_timer != 0U) { bootkick_timer--; } else { @@ -210,7 +211,7 @@ void uno_init(void) { uno_set_can_mode(CAN_MODE_NORMAL); // flip CAN0 and CAN2 if we are flipped - if (car_harness_status == HARNESS_STATUS_FLIPPED) { + if (harness.status == HARNESS_STATUS_FLIPPED) { can_flip_buses(0, 2); } diff --git a/board/boards/unused_funcs.h b/board/boards/unused_funcs.h index d3b4a4c6c8..f5478f90d9 100644 --- a/board/boards/unused_funcs.h +++ b/board/boards/unused_funcs.h @@ -22,10 +22,11 @@ uint32_t unused_read_current(void) { return 0U; } -void unused_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen) { +void unused_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen, bool harness_inserted) { UNUSED(ignition); UNUSED(usb_enum); UNUSED(heartbeat_seen); + UNUSED(harness_inserted); } bool unused_read_som_gpio(void) { diff --git a/board/drivers/harness.h b/board/drivers/harness.h index e4b7698e5f..bbb57d30fa 100644 --- a/board/drivers/harness.h +++ b/board/drivers/harness.h @@ -1,8 +1,16 @@ -uint8_t car_harness_status = 0U; #define HARNESS_STATUS_NC 0U #define HARNESS_STATUS_NORMAL 1U #define HARNESS_STATUS_FLIPPED 2U +struct harness_t { + uint8_t status; + uint16_t sbu1_voltage_mV; + uint16_t sbu2_voltage_mV; + bool relay_driven; + bool sbu_adc_lock; +}; +struct harness_t harness; + struct harness_configuration { const bool has_harness; GPIO_TypeDef *GPIO_SBU1; @@ -17,26 +25,40 @@ struct harness_configuration { uint8_t adc_channel_SBU2; }; -// this function will be the API for tici void set_intercept_relay(bool intercept) { - if (car_harness_status != HARNESS_STATUS_NC) { - if (intercept) { - print("switching harness to intercept (relay on)\n"); - } else { - print("switching harness to passthrough (relay off)\n"); - } + bool drive_relay = intercept; + if (harness.status == HARNESS_STATUS_NC) { + // no harness, no relay to drive + drive_relay = false; + } - if(car_harness_status == HARNESS_STATUS_NORMAL){ - set_gpio_output(current_board->harness_config->GPIO_relay_SBU2, current_board->harness_config->pin_relay_SBU2, !intercept); - } else { - set_gpio_output(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, !intercept); - } + if (drive_relay) { + harness.relay_driven = true; + } + + // wait until we're not reading the analog voltages anymore + while (harness.sbu_adc_lock == true) {} + + if (harness.status == HARNESS_STATUS_NORMAL) { + set_gpio_output(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, true); + set_gpio_output(current_board->harness_config->GPIO_relay_SBU2, current_board->harness_config->pin_relay_SBU2, !drive_relay); + } else { + set_gpio_output(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, !drive_relay); + set_gpio_output(current_board->harness_config->GPIO_relay_SBU2, current_board->harness_config->pin_relay_SBU2, true); + } + + if (!drive_relay) { + harness.relay_driven = false; } } bool harness_check_ignition(void) { bool ret = false; - switch(car_harness_status){ + + // wait until we're not reading the analog voltages anymore + while (harness.sbu_adc_lock == true) {} + + switch(harness.status){ case HARNESS_STATUS_NORMAL: ret = !get_gpio_input(current_board->harness_config->GPIO_SBU1, current_board->harness_config->pin_SBU1); break; @@ -50,27 +72,46 @@ bool harness_check_ignition(void) { } uint8_t harness_detect_orientation(void) { - uint8_t ret = HARNESS_STATUS_NC; + uint8_t ret = harness.status; #ifndef BOOTSTUB - uint32_t sbu1_voltage = adc_get_raw(current_board->harness_config->adc_channel_SBU1); - uint32_t sbu2_voltage = adc_get_raw(current_board->harness_config->adc_channel_SBU2); - - // Detect connection and orientation - if((sbu1_voltage < HARNESS_CONNECTED_THRESHOLD) || (sbu2_voltage < HARNESS_CONNECTED_THRESHOLD)){ - if (sbu1_voltage < sbu2_voltage) { - // orientation flipped (PANDA_SBU1->HARNESS_SBU1(relay), PANDA_SBU2->HARNESS_SBU2(ign)) - ret = HARNESS_STATUS_FLIPPED; + // We can't detect orientation if the relay is being driven + if (!harness.relay_driven && current_board->harness_config->has_harness) { + harness.sbu_adc_lock = true; + set_gpio_mode(current_board->harness_config->GPIO_SBU1, current_board->harness_config->pin_SBU1, MODE_ANALOG); + set_gpio_mode(current_board->harness_config->GPIO_SBU2, current_board->harness_config->pin_SBU2, MODE_ANALOG); + + harness.sbu1_voltage_mV = adc_get_mV(current_board->harness_config->adc_channel_SBU1); + harness.sbu2_voltage_mV = adc_get_mV(current_board->harness_config->adc_channel_SBU2); + uint16_t detection_threshold = current_board->avdd_mV / 2U; + + // Detect connection and orientation + if((harness.sbu1_voltage_mV < detection_threshold) || (harness.sbu2_voltage_mV < detection_threshold)){ + if (harness.sbu1_voltage_mV < harness.sbu2_voltage_mV) { + // orientation flipped (PANDA_SBU1->HARNESS_SBU1(relay), PANDA_SBU2->HARNESS_SBU2(ign)) + ret = HARNESS_STATUS_FLIPPED; + } else { + // orientation normal (PANDA_SBU2->HARNESS_SBU1(relay), PANDA_SBU1->HARNESS_SBU2(ign)) + ret = HARNESS_STATUS_NORMAL; + } } else { - // orientation normal (PANDA_SBU2->HARNESS_SBU1(relay), PANDA_SBU1->HARNESS_SBU2(ign)) - ret = HARNESS_STATUS_NORMAL; + ret = HARNESS_STATUS_NC; } + + // Pins are not 5V tolerant in ADC mode + set_gpio_mode(current_board->harness_config->GPIO_SBU1, current_board->harness_config->pin_SBU1, MODE_INPUT); + set_gpio_mode(current_board->harness_config->GPIO_SBU2, current_board->harness_config->pin_SBU2, MODE_INPUT); + harness.sbu_adc_lock = false; } #endif return ret; } +void harness_tick(void) { + harness.status = harness_detect_orientation(); +} + void harness_init(void) { // delay such that the connection is fully made before trying orientation detection current_board->set_led(LED_BLUE, true); @@ -78,18 +119,13 @@ void harness_init(void) { current_board->set_led(LED_BLUE, false); // try to detect orientation - uint8_t ret = harness_detect_orientation(); - if (ret != HARNESS_STATUS_NC) { - print("detected car harness with orientation "); puth2(ret); print("\n"); - car_harness_status = ret; - - // set the SBU lines to be inputs before using the relay. The lines are not 5V tolerant in ADC mode! - set_gpio_mode(current_board->harness_config->GPIO_SBU1, current_board->harness_config->pin_SBU1, MODE_INPUT); - set_gpio_mode(current_board->harness_config->GPIO_SBU2, current_board->harness_config->pin_SBU2, MODE_INPUT); - - // keep busses connected by default - set_intercept_relay(false); + harness.status = harness_detect_orientation(); + if (harness.status != HARNESS_STATUS_NC) { + print("detected car harness with orientation "); puth2(harness.status); print("\n"); } else { print("failed to detect car harness!\n"); } + + // keep buses connected by default + set_intercept_relay(false); } diff --git a/board/health.h b/board/health.h index de6f0e34ee..4dce741fbc 100644 --- a/board/health.h +++ b/board/health.h @@ -1,6 +1,6 @@ // When changing these structs, python/__init__.py needs to be kept up to date! -#define HEALTH_PACKET_VERSION 13 +#define HEALTH_PACKET_VERSION 14 struct __attribute__((packed)) health_t { uint32_t uptime_pkt; uint32_t voltage_pkt; @@ -27,6 +27,8 @@ struct __attribute__((packed)) health_t { uint8_t safety_rx_checks_invalid; uint16_t spi_checksum_error_count; uint8_t fan_stall_count; + uint16_t sbu1_voltage_mV; + uint16_t sbu2_voltage_mV; }; #define CAN_HEALTH_PACKET_VERSION 4 diff --git a/board/main.c b/board/main.c index 5fc8207fca..395d71c337 100644 --- a/board/main.c +++ b/board/main.c @@ -144,6 +144,7 @@ void __attribute__ ((noinline)) enable_fpu(void) { // called at 8Hz uint8_t loop_counter = 0U; +uint8_t previous_harness_status = HARNESS_STATUS_NC; void tick_handler(void) { if (TICK_TIMER->SR != 0) { // siren @@ -180,8 +181,11 @@ void tick_handler(void) { current_board->set_led(LED_BLUE, (uptime_cnt & 1U) && (power_save_status == POWER_SAVE_STATUS_ENABLED)); // tick drivers at 1Hz + harness_tick(); + const bool recent_heartbeat = heartbeat_counter == 0U; - current_board->board_tick(check_started(), usb_enumerated, recent_heartbeat); + current_board->board_tick(check_started(), usb_enumerated, recent_heartbeat, ((harness.status != previous_harness_status) && (harness.status != HARNESS_STATUS_NC))); + previous_harness_status = harness.status; // increase heartbeat counter and cap it at the uint32 limit if (heartbeat_counter < __UINT32_MAX__) { diff --git a/board/main_comms.h b/board/main_comms.h index 2fe028571e..b58ebd1df2 100644 --- a/board/main_comms.h +++ b/board/main_comms.h @@ -23,7 +23,7 @@ int get_health_pkt(void *dat) { health->tx_buffer_overflow_pkt = tx_buffer_overflow; health->rx_buffer_overflow_pkt = rx_buffer_overflow; health->gmlan_send_errs_pkt = gmlan_send_errs; - health->car_harness_status_pkt = car_harness_status; + health->car_harness_status_pkt = harness.status; health->safety_mode_pkt = (uint8_t)(current_safety_mode); health->safety_param_pkt = current_safety_param; health->alternative_experience_pkt = alternative_experience; @@ -41,6 +41,9 @@ int get_health_pkt(void *dat) { health->fan_power = fan_state.power; health->fan_stall_count = fan_state.total_stall_count; + health->sbu1_voltage_mV = harness.sbu1_voltage_mV; + health->sbu2_voltage_mV = harness.sbu2_voltage_mV; + return sizeof(*health); } diff --git a/board/stm32fx/llexti.h b/board/stm32fx/llexti.h index d4362cd18d..6de13ab7f4 100644 --- a/board/stm32fx/llexti.h +++ b/board/stm32fx/llexti.h @@ -2,7 +2,7 @@ void EXTI_IRQ_Handler(void); void exti_irq_init(void) { SYSCFG->EXTICR[2] &= ~(SYSCFG_EXTICR3_EXTI8_Msk); - if (car_harness_status == HARNESS_STATUS_FLIPPED) { + if (harness.status == HARNESS_STATUS_FLIPPED) { // CAN2_RX current_board->enable_can_transceiver(3U, false); SYSCFG->EXTICR[2] |= (SYSCFG_EXTICR3_EXTI8_PA); diff --git a/board/stm32fx/stm32fx_config.h b/board/stm32fx/stm32fx_config.h index 17ddf9cd3a..a73239749f 100644 --- a/board/stm32fx/stm32fx_config.h +++ b/board/stm32fx/stm32fx_config.h @@ -23,9 +23,6 @@ #define MAX_LED_FADE 8192U -// Threshold voltage (mV) for either of the SBUs to be below before deciding harness is connected -#define HARNESS_CONNECTED_THRESHOLD 2500U - #define NUM_INTERRUPTS 102U // There are 102 external interrupt sources (see stm32f413.h) #define TICK_TIMER_IRQ TIM1_BRK_TIM9_IRQn diff --git a/board/stm32h7/llexti.h b/board/stm32h7/llexti.h index 7b7c260d8e..46d0acab67 100644 --- a/board/stm32h7/llexti.h +++ b/board/stm32h7/llexti.h @@ -1,7 +1,7 @@ void EXTI_IRQ_Handler(void); void exti_irq_init(void) { - if (car_harness_status == HARNESS_STATUS_FLIPPED) { + if (harness.status == HARNESS_STATUS_FLIPPED) { // CAN2_RX IRQ on falling edge (EXTI10) current_board->enable_can_transceiver(3U, false); SYSCFG->EXTICR[2] &= ~(SYSCFG_EXTICR3_EXTI10_Msk); diff --git a/board/stm32h7/stm32h7_config.h b/board/stm32h7/stm32h7_config.h index 4d27ed6ba9..41ae768a9f 100644 --- a/board/stm32h7/stm32h7_config.h +++ b/board/stm32h7/stm32h7_config.h @@ -30,9 +30,6 @@ separate IRQs for RX and TX. #define MAX_LED_FADE 10240U -// Threshold voltage (mV) for either of the SBUs to be below before deciding harness is connected -#define HARNESS_CONNECTED_THRESHOLD 40000U - // There are 163 external interrupt sources (see stm32f735xx.h) #define NUM_INTERRUPTS 163U diff --git a/python/__init__.py b/python/__init__.py index 741c157227..dc1b365f4f 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -182,9 +182,9 @@ class Panda: HW_TYPE_TRES = b'\x09' CAN_PACKET_VERSION = 4 - HEALTH_PACKET_VERSION = 13 + HEALTH_PACKET_VERSION = 14 CAN_HEALTH_PACKET_VERSION = 4 - HEALTH_STRUCT = struct.Struct(" 0.9 * supply_voltage_mV + assert health['sbu2_voltage_mV'] > 0.9 * supply_voltage_mV + else: + relay_line = 'sbu1_voltage_mV' if (detected_orientation == Panda.HARNESS_STATUS_FLIPPED) else 'sbu2_voltage_mV' + ignition_line = 'sbu2_voltage_mV' if (detected_orientation == Panda.HARNESS_STATUS_FLIPPED) else 'sbu1_voltage_mV' + + assert health[relay_line] < 0.1 * supply_voltage_mV + assert health[ignition_line] > health[relay_line] + if ignition: + assert health[ignition_line] < 0.3 * supply_voltage_mV + else: + assert health[ignition_line] > 0.9 * supply_voltage_mV + + @pytest.mark.skip_panda_types((Panda.HW_TYPE_DOS, )) def test_voltage(p): From 2eef7f3fffbf01d11266d1a35d9dcbb6b9e26aa6 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 7 May 2023 21:00:05 -0700 Subject: [PATCH 044/197] reset heartbeat_engaged once heartbeat is lost --- board/main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/board/main.c b/board/main.c index 395d71c337..363abc79c9 100644 --- a/board/main.c +++ b/board/main.c @@ -236,6 +236,9 @@ void tick_handler(void) { heartbeat_lost = true; } + // clear heartbeat engaged state + heartbeat_engaged = false; + if (current_safety_mode != SAFETY_SILENT) { set_safety_mode(SAFETY_SILENT, 0U); } From 2dfb9815ede0745fda74ca4cc516c1e2c34c088c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 8 May 2023 14:18:43 -0700 Subject: [PATCH 045/197] HITL tests: disable partial tests on branches (#1406) Co-authored-by: Bruce Wayne --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index adf641aea2..9614d67f98 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -62,7 +62,7 @@ pipeline { agent any environment { CI = "1" - PARTIAL_TESTS = "${env.BRANCH_NAME == 'master' ? ' ' : '1'}" + //PARTIAL_TESTS = "${env.BRANCH_NAME == 'master' ? ' ' : '1'}" PYTHONWARNINGS= "error" DOCKER_IMAGE_TAG = "panda:build-${env.GIT_COMMIT}" From 98a15f2a90bf04f4eb68654163ef9601eff84280 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 8 May 2023 15:02:18 -0700 Subject: [PATCH 046/197] harness: fix register divergent on GEN1 (#1405) * harness: fix register divergent on GEN1 * has harness * revert that --------- Co-authored-by: Bruce Wayne --- board/drivers/harness.h | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/board/drivers/harness.h b/board/drivers/harness.h index bbb57d30fa..6bf3fe1b9c 100644 --- a/board/drivers/harness.h +++ b/board/drivers/harness.h @@ -26,29 +26,31 @@ struct harness_configuration { }; void set_intercept_relay(bool intercept) { - bool drive_relay = intercept; - if (harness.status == HARNESS_STATUS_NC) { - // no harness, no relay to drive - drive_relay = false; - } + if (current_board->harness_config->has_harness) { + bool drive_relay = intercept; + if (harness.status == HARNESS_STATUS_NC) { + // no harness, no relay to drive + drive_relay = false; + } - if (drive_relay) { - harness.relay_driven = true; - } + if (drive_relay) { + harness.relay_driven = true; + } - // wait until we're not reading the analog voltages anymore - while (harness.sbu_adc_lock == true) {} + // wait until we're not reading the analog voltages anymore + while (harness.sbu_adc_lock == true) {} - if (harness.status == HARNESS_STATUS_NORMAL) { - set_gpio_output(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, true); - set_gpio_output(current_board->harness_config->GPIO_relay_SBU2, current_board->harness_config->pin_relay_SBU2, !drive_relay); - } else { - set_gpio_output(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, !drive_relay); - set_gpio_output(current_board->harness_config->GPIO_relay_SBU2, current_board->harness_config->pin_relay_SBU2, true); - } + if (harness.status == HARNESS_STATUS_NORMAL) { + set_gpio_output(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, true); + set_gpio_output(current_board->harness_config->GPIO_relay_SBU2, current_board->harness_config->pin_relay_SBU2, !drive_relay); + } else { + set_gpio_output(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, !drive_relay); + set_gpio_output(current_board->harness_config->GPIO_relay_SBU2, current_board->harness_config->pin_relay_SBU2, true); + } - if (!drive_relay) { - harness.relay_driven = false; + if (!drive_relay) { + harness.relay_driven = false; + } } } From da7c57748648e786c99621271b5a88d11bcd90fa Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 8 May 2023 16:43:25 -0700 Subject: [PATCH 047/197] spi dfu: fast timeout for header (#1407) --- python/spi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/spi.py b/python/spi.py index c098ebbb51..c753bf4ddc 100644 --- a/python/spi.py +++ b/python/spi.py @@ -228,7 +228,7 @@ def _cmd_no_retry(self, cmd: int, data: Optional[List[bytes]] = None, read_bytes # sync + command spi.xfer([self.SYNC, ]) spi.xfer([cmd, cmd ^ 0xFF]) - self._get_ack(spi) + self._get_ack(spi, timeout=0.1) # "predata" - for commands that send the first data without a checksum if predata is not None: From 1e131f2b179479f4154bd94213b0f12ce872677e Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Thu, 11 May 2023 17:21:34 +0200 Subject: [PATCH 048/197] RX/TX lost reversal for bxcan (#1408) this should be reversed --- board/drivers/bxcan.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/board/drivers/bxcan.h b/board/drivers/bxcan.h index 25105dc536..815ef0fe1f 100644 --- a/board/drivers/bxcan.h +++ b/board/drivers/bxcan.h @@ -111,7 +111,7 @@ void process_can(uint8_t can_number) { // check for empty mailbox CANPacket_t to_send; if ((CAN->TSR & (CAN_TSR_TERR0 | CAN_TSR_ALST0)) != 0) { // last TX failed due to error arbitration lost - can_health[can_number].total_rx_lost_cnt += 1U; + can_health[can_number].total_tx_lost_cnt += 1U; CAN->TSR |= (CAN_TSR_TERR0 | CAN_TSR_ALST0); } if ((CAN->TSR & CAN_TSR_TME0) == CAN_TSR_TME0) { @@ -167,7 +167,7 @@ void can_rx(uint8_t can_number) { uint8_t bus_number = BUS_NUM_FROM_CAN_NUM(can_number); if ((CAN->RF0R & (CAN_RF0R_FOVR0)) != 0) { // RX message lost due to FIFO overrun - can_health[can_number].total_tx_lost_cnt += 1U; + can_health[can_number].total_rx_lost_cnt += 1U; CAN->RF0R &= ~(CAN_RF0R_FOVR0); } From 3d4b7b1afc189ab8ef4b02889c5f8e74730e9a51 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 11 May 2023 13:02:20 -0700 Subject: [PATCH 049/197] remove unused sconscript --- tests/SConscript | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 tests/SConscript diff --git a/tests/SConscript b/tests/SConscript deleted file mode 100644 index 86e9f933d4..0000000000 --- a/tests/SConscript +++ /dev/null @@ -1,13 +0,0 @@ -env = Environment( - CC='gcc', - CFLAGS=[ - '-nostdlib', - '-fno-builtin', - '-std=gnu11', - ], - CPPPATH=[".", "../board"], -) - -env.PrependENVPath('PATH', '/opt/homebrew/bin') -env.SharedLibrary("safety/libpandasafety.so", ["safety/test.c"]) -env.SharedLibrary("usbprotocol/libpandaprotocol.so", ["usbprotocol/test.c"]) From 822a7367c1912d8b19263bfbda15bf29b188d67d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Fri, 12 May 2023 11:06:08 -0700 Subject: [PATCH 050/197] macOS: use homebrew gcc using version suffix. update setup instructions (#1409) * Add version suffix to gcc on Darwin * Update macos setup intructions * Delete unused SConscript at tests/ * Move gcc stuff to tests/libpanda/SConscript --- README.md | 4 ++-- tests/libpanda/SConscript | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 90bc836477..1b325d801b 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ sudo apt-get install dfu-util gcc-arm-none-eabi python3-pip libffi-dev git ``` ```bash # macOS -brew tap ArmMbed/homebrew-formulae -brew install python dfu-util arm-none-eabi-gcc gcc@12 +brew install --cask gcc-arm-embedded +brew install python3 dfu-util gcc@12 ``` Clone panda repository: diff --git a/tests/libpanda/SConscript b/tests/libpanda/SConscript index 000ca63cac..f9f052e458 100644 --- a/tests/libpanda/SConscript +++ b/tests/libpanda/SConscript @@ -1,5 +1,14 @@ +import platform + +CC = 'gcc' +system = platform.system() +if system == 'Darwin': + # gcc installed by homebrew has version suffix (e.g. gcc-12) in order to be + # distinguishable from system one - which acts as a symlink to clang + CC += '-12' + env = Environment( - CC='gcc', + CC=CC, CFLAGS=[ '-nostdlib', '-fno-builtin', @@ -8,7 +17,8 @@ env = Environment( ], CPPPATH=[".", "../../board/"], ) -env.PrependENVPath('PATH', '/opt/homebrew/bin') +if system == "Darwin": + env.PrependENVPath('PATH', '/opt/homebrew/bin') if GetOption('ubsan'): flags = [ From 03435947bb249dc3ea7338300db5196877ff1b8d Mon Sep 17 00:00:00 2001 From: Igor Biletskyy Date: Fri, 12 May 2023 19:20:42 -0700 Subject: [PATCH 051/197] H7: fix lockup on disconnected bus (#1410) * fix h7 lockup * love MISRA! * EW and EP isn't actual errors --- board/drivers/fdcan.h | 7 ++++++- board/stm32h7/llfdcan.h | 7 ++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/board/drivers/fdcan.h b/board/drivers/fdcan.h index 4dcc3fdf46..706f49439f 100644 --- a/board/drivers/fdcan.h +++ b/board/drivers/fdcan.h @@ -64,7 +64,12 @@ void update_can_health_pkt(uint8_t can_number, bool error_irq) { if ((CANx->IR & (FDCAN_IR_TEFL)) != 0) { can_health[can_number].total_tx_lost_cnt += 1U; } - llcan_clear_send(CANx); + // Actually reset can core only on arbitration or data phase errors + if ((CANx->IR & (FDCAN_IR_PED | FDCAN_IR_PEA)) != 0) { + llcan_clear_send(CANx); + } + // Clear error interrupts + CANx->IR |= (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EW | FDCAN_IR_EP | FDCAN_IR_ELO | FDCAN_IR_BO | FDCAN_IR_TEFL | FDCAN_IR_RF0L); } EXIT_CRITICAL(); } diff --git a/board/stm32h7/llfdcan.h b/board/stm32h7/llfdcan.h index e5791fc528..311c6ae9b0 100644 --- a/board/stm32h7/llfdcan.h +++ b/board/stm32h7/llfdcan.h @@ -244,7 +244,8 @@ bool llcan_init(FDCAN_GlobalTypeDef *CANx) { } void llcan_clear_send(FDCAN_GlobalTypeDef *CANx) { - CANx->TXBCR = 0xFFFFU; // Abort message transmission on error interrupt - // Clear error interrupts - CANx->IR |= (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EW | FDCAN_IR_EP | FDCAN_IR_ELO | FDCAN_IR_BO | FDCAN_IR_TEFL | FDCAN_IR_RF0L); + // from datasheet: "Transmit cancellation is not intended for Tx FIFO operation." + // so we need to clear pending transmission manually by resetting FDCAN core + bool ret = llcan_init(CANx); + UNUSED(ret); } From 675b1e847f918a0906a3f4a0bd41289c5fd9bad3 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 12 May 2023 22:14:23 -0700 Subject: [PATCH 052/197] iso-tp: revert CAN frame length check (#1411) revert --- python/uds.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/uds.py b/python/uds.py index f727b7507e..06d8d82574 100644 --- a/python/uds.py +++ b/python/uds.py @@ -463,8 +463,9 @@ def recv(self, timeout=None) -> Tuple[Optional[bytes], bool]: print(f"ISO-TP: RESPONSE - {hex(self._can_client.rx_addr)} 0x{bytes.hex(self.rx_dat)}") def _isotp_rx_next(self, rx_data: bytes) -> ISOTP_FRAME_TYPE: - # ISO 15765-2 specifies an eight byte CAN frame for ISO-TP communication - assert len(rx_data) == self.max_len, f"isotp - rx: invalid CAN frame length: {len(rx_data)}" + # TODO: Handle CAN frame data optimization, which is allowed with some frame types + # # ISO 15765-2 specifies an eight byte CAN frame for ISO-TP communication + # assert len(rx_data) == self.max_len, f"isotp - rx: invalid CAN frame length: {len(rx_data)}" if rx_data[0] >> 4 == ISOTP_FRAME_TYPE.SINGLE: self.rx_len = rx_data[0] & 0xFF From f95f4a52946da442125f44ff7baeb642b9eb8d3d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 15 May 2023 14:15:48 -0700 Subject: [PATCH 053/197] stricter simple watchdog (#1413) --- board/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/board/main.c b/board/main.c index 363abc79c9..b8831c9d15 100644 --- a/board/main.c +++ b/board/main.c @@ -374,8 +374,8 @@ int main(void) { // enable CAN TXs current_board->enable_can_transceivers(true); - // init watchdog for heartbeat loop, trigger after 4 8Hz cycles - simple_watchdog_init(FAULT_HEARTBEAT_LOOP_WATCHDOG, (4U * 1000000U / 8U)); + // init watchdog for heartbeat loop, fed at 8Hz + simple_watchdog_init(FAULT_HEARTBEAT_LOOP_WATCHDOG, (3U * 1000000U / 8U)); // 8Hz timer REGISTER_INTERRUPT(TICK_TIMER_IRQ, tick_handler, 10U, FAULT_INTERRUPT_RATE_TICK) From 0a738b7ee8a49d153142ea0dd8c8c57693035845 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 15 May 2023 20:22:40 -0400 Subject: [PATCH 054/197] Subaru: rename legacy to preglobal (#1415) * rename * missing some variables * fix find/replace error --- board/safety.h | 6 +-- ...aru_legacy.h => safety_subaru_preglobal.h} | 38 +++++++++---------- python/__init__.py | 2 +- ...aru_legacy.py => test_subaru_preglobal.py} | 4 +- tests/safety_replay/test_safety_replay.py | 2 +- 5 files changed, 26 insertions(+), 26 deletions(-) rename board/safety/{safety_subaru_legacy.h => safety_subaru_preglobal.h} (65%) rename tests/safety/{test_subaru_legacy.py => test_subaru_preglobal.py} (92%) diff --git a/board/safety.h b/board/safety.h index 7a16c90634..7d681aa837 100644 --- a/board/safety.h +++ b/board/safety.h @@ -11,7 +11,7 @@ #include "safety/safety_hyundai.h" #include "safety/safety_chrysler.h" #include "safety/safety_subaru.h" -#include "safety/safety_subaru_legacy.h" +#include "safety/safety_subaru_preglobal.h" #include "safety/safety_mazda.h" #include "safety/safety_nissan.h" #include "safety/safety_volkswagen_mqb.h" @@ -44,7 +44,7 @@ #define SAFETY_NOOUTPUT 19U #define SAFETY_HONDA_BOSCH 20U #define SAFETY_VOLKSWAGEN_PQ 21U -#define SAFETY_SUBARU_LEGACY 22U +#define SAFETY_SUBARU_PREGLOBAL 22U #define SAFETY_HYUNDAI_LEGACY 23U #define SAFETY_HYUNDAI_COMMUNITY 24U #define SAFETY_STELLANTIS 25U @@ -313,7 +313,7 @@ const safety_hook_config safety_hook_registry[] = { #endif #ifdef ALLOW_DEBUG {SAFETY_TESLA, &tesla_hooks}, - {SAFETY_SUBARU_LEGACY, &subaru_legacy_hooks}, + {SAFETY_SUBARU_PREGLOBAL, &subaru_preglobal_hooks}, {SAFETY_VOLKSWAGEN_PQ, &volkswagen_pq_hooks}, {SAFETY_ALLOUTPUT, &alloutput_hooks}, #endif diff --git a/board/safety/safety_subaru_legacy.h b/board/safety/safety_subaru_preglobal.h similarity index 65% rename from board/safety/safety_subaru_legacy.h rename to board/safety/safety_subaru_preglobal.h index 5cce0dda8e..673e3655c5 100644 --- a/board/safety/safety_subaru_legacy.h +++ b/board/safety/safety_subaru_preglobal.h @@ -1,4 +1,4 @@ -const SteeringLimits SUBARU_L_STEERING_LIMITS = { +const SteeringLimits SUBARU_PG_STEERING_LIMITS = { .max_steer = 2047, .max_rt_delta = 940, .max_rt_interval = 250000, @@ -9,24 +9,24 @@ const SteeringLimits SUBARU_L_STEERING_LIMITS = { .type = TorqueDriverLimited, }; -const CanMsg SUBARU_L_TX_MSGS[] = { +const CanMsg SUBARU_PG_TX_MSGS[] = { {0x161, 0, 8}, {0x164, 0, 8} }; -#define SUBARU_L_TX_MSGS_LEN (sizeof(SUBARU_L_TX_MSGS) / sizeof(SUBARU_L_TX_MSGS[0])) +#define SUBARU_PG_TX_MSGS_LEN (sizeof(SUBARU_PG_TX_MSGS) / sizeof(SUBARU_PG_TX_MSGS[0])) // TODO: do checksum and counter checks after adding the signals to the outback dbc file -AddrCheckStruct subaru_l_addr_checks[] = { +AddrCheckStruct subaru_preglobal_addr_checks[] = { {.msg = {{0x140, 0, 8, .expected_timestep = 10000U}, { 0 }, { 0 }}}, {.msg = {{0x371, 0, 8, .expected_timestep = 20000U}, { 0 }, { 0 }}}, {.msg = {{0x144, 0, 8, .expected_timestep = 50000U}, { 0 }, { 0 }}}, }; -#define SUBARU_L_ADDR_CHECK_LEN (sizeof(subaru_l_addr_checks) / sizeof(subaru_l_addr_checks[0])) -addr_checks subaru_l_rx_checks = {subaru_l_addr_checks, SUBARU_L_ADDR_CHECK_LEN}; +#define SUBARU_PG_ADDR_CHECK_LEN (sizeof(subaru_preglobal_addr_checks) / sizeof(subaru_preglobal_addr_checks[0])) +addr_checks subaru_preglobal_rx_checks = {subaru_preglobal_addr_checks, SUBARU_PG_ADDR_CHECK_LEN}; -static int subaru_legacy_rx_hook(CANPacket_t *to_push) { +static int subaru_preglobal_rx_hook(CANPacket_t *to_push) { - bool valid = addr_safety_check(to_push, &subaru_l_rx_checks, NULL, NULL, NULL, NULL); + bool valid = addr_safety_check(to_push, &subaru_preglobal_rx_checks, NULL, NULL, NULL, NULL); if (valid && (GET_BUS(to_push) == 0U)) { int addr = GET_ADDR(to_push); @@ -61,12 +61,12 @@ static int subaru_legacy_rx_hook(CANPacket_t *to_push) { return valid; } -static int subaru_legacy_tx_hook(CANPacket_t *to_send) { +static int subaru_preglobal_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); - if (!msg_allowed(to_send, SUBARU_L_TX_MSGS, SUBARU_L_TX_MSGS_LEN)) { + if (!msg_allowed(to_send, SUBARU_PG_TX_MSGS, SUBARU_PG_TX_MSGS_LEN)) { tx = 0; } @@ -75,7 +75,7 @@ static int subaru_legacy_tx_hook(CANPacket_t *to_send) { int desired_torque = ((GET_BYTES(to_send, 0, 4) >> 8) & 0x1FFFU); desired_torque = -1 * to_signed(desired_torque, 13); - if (steer_torque_cmd_checks(desired_torque, -1, SUBARU_L_STEERING_LIMITS)) { + if (steer_torque_cmd_checks(desired_torque, -1, SUBARU_PG_STEERING_LIMITS)) { tx = 0; } @@ -83,7 +83,7 @@ static int subaru_legacy_tx_hook(CANPacket_t *to_send) { return tx; } -static int subaru_legacy_fwd_hook(int bus_num, int addr) { +static int subaru_preglobal_fwd_hook(int bus_num, int addr) { int bus_fwd = -1; if (bus_num == 0) { @@ -103,15 +103,15 @@ static int subaru_legacy_fwd_hook(int bus_num, int addr) { return bus_fwd; } -static const addr_checks* subaru_legacy_init(uint16_t param) { +static const addr_checks* subaru_preglobal_init(uint16_t param) { UNUSED(param); - return &subaru_l_rx_checks; + return &subaru_preglobal_rx_checks; } -const safety_hooks subaru_legacy_hooks = { - .init = subaru_legacy_init, - .rx = subaru_legacy_rx_hook, - .tx = subaru_legacy_tx_hook, +const safety_hooks subaru_preglobal_hooks = { + .init = subaru_preglobal_init, + .rx = subaru_preglobal_rx_hook, + .tx = subaru_preglobal_tx_hook, .tx_lin = nooutput_tx_lin_hook, - .fwd = subaru_legacy_fwd_hook, + .fwd = subaru_preglobal_fwd_hook, }; diff --git a/python/__init__.py b/python/__init__.py index dc1b365f4f..ebffb3d461 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -150,7 +150,7 @@ class Panda: SAFETY_NOOUTPUT = 19 SAFETY_HONDA_BOSCH = 20 SAFETY_VOLKSWAGEN_PQ = 21 - SAFETY_SUBARU_LEGACY = 22 + SAFETY_SUBARU_PREGLOBAL = 22 SAFETY_HYUNDAI_LEGACY = 23 SAFETY_HYUNDAI_COMMUNITY = 24 SAFETY_STELLANTIS = 25 diff --git a/tests/safety/test_subaru_legacy.py b/tests/safety/test_subaru_preglobal.py similarity index 92% rename from tests/safety/test_subaru_legacy.py rename to tests/safety/test_subaru_preglobal.py index c00afcf011..40362e3728 100755 --- a/tests/safety/test_subaru_legacy.py +++ b/tests/safety/test_subaru_preglobal.py @@ -6,7 +6,7 @@ from panda.tests.safety.common import CANPackerPanda -class TestSubaruLegacySafety(common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest): +class TestSubaruPreglobalSafety(common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest): TX_MSGS = [[0x161, 0], [0x164, 0]] STANDSTILL_THRESHOLD = 0 # kph RELAY_MALFUNCTION_ADDR = 0x164 @@ -27,7 +27,7 @@ class TestSubaruLegacySafety(common.PandaSafetyTest, common.DriverTorqueSteering def setUp(self): self.packer = CANPackerPanda("subaru_outback_2015_generated") self.safety = libpanda_py.libpanda - self.safety.set_safety_hooks(Panda.SAFETY_SUBARU_LEGACY, 0) + self.safety.set_safety_hooks(Panda.SAFETY_SUBARU_PREGLOBAL, 0) self.safety.init_tests() def _set_prev_torque(self, t): diff --git a/tests/safety_replay/test_safety_replay.py b/tests/safety_replay/test_safety_replay.py index 5e6e5f0907..2dcc07cd1a 100755 --- a/tests/safety_replay/test_safety_replay.py +++ b/tests/safety_replay/test_safety_replay.py @@ -28,7 +28,7 @@ ReplayRoute("fbbfa6af821552b9|2020-03-03--08-09-43.bz2", Panda.SAFETY_NISSAN), # NISSAN.XTRAIL ReplayRoute("5b7c365c50084530_2020-04-15--16-13-24--3--rlog.bz2", Panda.SAFETY_HYUNDAI), # HYUNDAI.SONATA ReplayRoute("610ebb9faaad6b43|2020-06-13--15-28-36.bz2", Panda.SAFETY_HYUNDAI_LEGACY), # HYUNDAI.IONIQ_EV_LTD - ReplayRoute("5ab784f361e19b78_2020-06-08--16-30-41.bz2", Panda.SAFETY_SUBARU_LEGACY), # SUBARU.OUTBACK_PREGLOBAL + ReplayRoute("5ab784f361e19b78_2020-06-08--16-30-41.bz2", Panda.SAFETY_SUBARU_PREGLOBAL), # SUBARU.OUTBACK_PREGLOBAL ReplayRoute("bb50caf5f0945ab1|2021-06-19--17-20-18.bz2", Panda.SAFETY_TESLA), # TESLA.AP2_MODELS ReplayRoute("bd6a637565e91581_2021-10-29--22-18-31--1--rlog.bz2", Panda.SAFETY_MAZDA), # MAZDA.CX9_2021 ReplayRoute("1a5d045d2c531a6d_2022-06-07--22-03-00--1--rlog.bz2", Panda.SAFETY_HONDA_BOSCH, # HONDA.CIVIC_2022 From 3c9d2772763b2e0f6e55ad9fe0b9867d8f9cb27a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 May 2023 15:52:50 -0700 Subject: [PATCH 055/197] safety: always allow inactive gas command (#1418) * always allow inactive gas * rewrite this * revert * no functional change but test it correctly * also here --- board/safety.h | 10 +++------- board/safety/safety_honda.h | 1 - tests/safety/test_gm.py | 2 +- tests/safety/test_honda.py | 4 ++-- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/board/safety.h b/board/safety.h index 7d681aa837..4d7efd2a0f 100644 --- a/board/safety.h +++ b/board/safety.h @@ -510,13 +510,9 @@ bool longitudinal_speed_checks(int desired_speed, const LongitudinalLimits limit } bool longitudinal_gas_checks(int desired_gas, const LongitudinalLimits limits) { - bool violation = false; - if (!get_longitudinal_allowed()) { - violation |= desired_gas != limits.inactive_gas; - } else { - violation |= max_limit_check(desired_gas, limits.max_gas, limits.min_gas); - } - return violation; + bool gas_valid = get_longitudinal_allowed() && !max_limit_check(desired_gas, limits.max_gas, limits.min_gas); + bool gas_inactive = desired_gas == limits.inactive_gas; + return !(gas_valid || gas_inactive); } bool longitudinal_brake_checks(int desired_brake, const LongitudinalLimits limits) { diff --git a/board/safety/safety_honda.h b/board/safety/safety_honda.h index f4852b12c7..e8100cad72 100644 --- a/board/safety/safety_honda.h +++ b/board/safety/safety_honda.h @@ -23,7 +23,6 @@ const LongitudinalLimits HONDA_BOSCH_LONG_LIMITS = { .min_accel = -350, .max_gas = 2000, - .min_gas = -30000, .inactive_gas = -30000, }; diff --git a/tests/safety/test_gm.py b/tests/safety/test_gm.py index 02a7b6d9d0..f6b604ad31 100755 --- a/tests/safety/test_gm.py +++ b/tests/safety/test_gm.py @@ -153,7 +153,7 @@ def test_gas_safety_check(self): for gas_regen in range(0, 2 ** 12 - 1): self.safety.set_controls_allowed(enabled) should_tx = ((enabled and self.MAX_REGEN <= gas_regen <= self.MAX_GAS) or - (not enabled and gas_regen == self.INACTIVE_REGEN)) + gas_regen == self.INACTIVE_REGEN) self.assertEqual(should_tx, self._tx(self._send_gas_msg(gas_regen)), (enabled, gas_regen)) diff --git a/tests/safety/test_honda.py b/tests/safety/test_honda.py index 16de6e5719..f8e17c24e2 100755 --- a/tests/safety/test_honda.py +++ b/tests/safety/test_honda.py @@ -296,7 +296,7 @@ def test_acc_hud_safety_check(self): self.safety.set_controls_allowed(controls_allowed) for pcm_gas in range(0, 255): for pcm_speed in range(0, 100): - send = pcm_gas <= self.MAX_GAS if controls_allowed else pcm_gas == 0 and pcm_speed == 0 + send = (controls_allowed and pcm_gas <= self.MAX_GAS) or (pcm_gas == 0 and pcm_speed == 0) self.assertEqual(send, self._tx(self._send_acc_hud_msg(pcm_gas, pcm_speed))) def test_fwd_hook(self): @@ -499,7 +499,7 @@ def test_gas_safety_check(self): for gas in np.arange(self.NO_GAS, self.MAX_GAS + 2000, 100): accel = 0 if gas < 0 else gas / 1000 self.safety.set_controls_allowed(controls_allowed) - send = gas <= self.MAX_GAS if controls_allowed else gas == self.NO_GAS + send = (controls_allowed and 0 <= gas <= self.MAX_GAS) or gas == self.NO_GAS self.assertEqual(send, self._tx(self._send_gas_brake_msg(gas, accel)), (controls_allowed, gas, accel)) def test_brake_safety_check(self): From a93bc954c5cbce71325181ae9a9351ef1e8f07bf Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 17 May 2023 17:13:12 -0700 Subject: [PATCH 056/197] SPI HITL tests + cleanup (#1417) * start comms hitl tests * pull that out * revert that * more test * fix warnings * fix linter * another simple case --------- Co-authored-by: Comma Device --- Jenkinsfile | 2 +- board/drivers/spi.h | 75 +++++++++++++++++++++++++------------------ board/stm32fx/llspi.h | 8 +++-- board/stm32h7/llspi.h | 4 +-- python/spi.py | 13 +++++--- requirements.txt | 1 + tests/hitl/8_spi.py | 58 +++++++++++++++++++++++++++++++++ 7 files changed, 121 insertions(+), 40 deletions(-) create mode 100644 tests/hitl/8_spi.py diff --git a/Jenkinsfile b/Jenkinsfile index 9614d67f98..51684cea7b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -94,7 +94,7 @@ pipeline { phone_steps("panda-tres", [ ["build", "scons -j4"], ["flash", "cd tests/ && ./ci_reset_internal_hw.py"], - ["test", "cd tests/hitl && HW_TYPES=9 pytest --durations=0 2*.py [5-7]*.py -k 'not test_fan_controller and not test_fan_overshoot'"], + ["test", "cd tests/hitl && HW_TYPES=9 pytest --durations=0 2*.py [5-9]*.py -k 'not test_fan_controller and not test_fan_overshoot'"], ]) } } diff --git a/board/drivers/spi.h b/board/drivers/spi.h index 6a52cb4e01..f78bd0b5f2 100644 --- a/board/drivers/spi.h +++ b/board/drivers/spi.h @@ -64,8 +64,9 @@ bool check_checksum(uint8_t *data, uint16_t len) { return checksum == 0U; } -void spi_handle_rx(void) { - uint8_t next_rx_state = SPI_STATE_HEADER; +void spi_rx_done(void) { + uint16_t response_len = 0U; + uint8_t next_rx_state = SPI_STATE_HEADER_NACK; bool checksum_valid = false; // parse header @@ -79,17 +80,17 @@ void spi_handle_rx(void) { // 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 print("- incorrect header sync or checksum "); hexdump(spi_buf_rx, SPI_HEADER_SIZE); spi_buf_tx[0] = SPI_NACK; next_rx_state = SPI_STATE_HEADER_NACK; + response_len = 1U; } - llspi_miso_dma(spi_buf_tx, 1); } else if (spi_state == SPI_STATE_DATA_RX) { // We got everything! Based on the endpoint specified, call the appropriate handler - uint16_t response_len = 0U; - bool reponse_ack = false; + bool response_ack = false; checksum_valid = check_checksum(&(spi_buf_rx[SPI_HEADER_SIZE]), spi_data_len_mosi + 1U); if (checksum_valid) { if (spi_endpoint == 0U) { @@ -97,24 +98,24 @@ void spi_handle_rx(void) { ControlPacket_t ctrl; (void)memcpy(&ctrl, &spi_buf_rx[SPI_HEADER_SIZE], sizeof(ControlPacket_t)); response_len = comms_control_handler(&ctrl, &spi_buf_tx[3]); - reponse_ack = true; + 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); - reponse_ack = true; + 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); - reponse_ack = true; + response_ack = true; } else if (spi_endpoint == 3U) { if (spi_data_len_mosi > 0U) { comms_can_write(&spi_buf_rx[SPI_HEADER_SIZE], spi_data_len_mosi); - reponse_ack = true; + response_ack = true; } else { print("SPI: did expect data for can_write\n"); } @@ -123,42 +124,54 @@ void spi_handle_rx(void) { } } else { // Checksum was incorrect - reponse_ack = false; - print("- incorrect data checksum\n"); + response_ack = false; + print("- incorrect data checksum "); + puth2(spi_data_len_mosi); + print(" "); + hexdump(&(spi_buf_rx[SPI_HEADER_SIZE]), MIN(spi_data_len_mosi, 64)); } - // Setup response header - spi_buf_tx[0] = reponse_ack ? SPI_DACK : SPI_NACK; - spi_buf_tx[1] = response_len & 0xFFU; - spi_buf_tx[2] = (response_len >> 8) & 0xFFU; + if (!response_ack) { + spi_buf_tx[0] = SPI_NACK; + next_rx_state = SPI_STATE_HEADER_NACK; + response_len = 1U; + } else { + // 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 + 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 += 4U; - // Add checksum - uint8_t checksum = SPI_CHECKSUM_START; - for(uint16_t i = 0U; i < (response_len + 3U); i++) { - checksum ^= spi_buf_tx[i]; + next_rx_state = SPI_STATE_DATA_TX; } - spi_buf_tx[response_len + 3U] = checksum; - - // Write response - llspi_miso_dma(spi_buf_tx, 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_checksum_error_count < __UINT16_MAX__)) { spi_checksum_error_count += 1U; } } -void spi_handle_tx(bool timed_out) { - if (timed_out) { - print("SPI: TX timeout\n"); - } - - if ((spi_state == SPI_STATE_HEADER_NACK) || timed_out) { +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); diff --git a/board/stm32fx/llspi.h b/board/stm32fx/llspi.h index 3aa7236bd6..2ee7ffa385 100644 --- a/board/stm32fx/llspi.h +++ b/board/stm32fx/llspi.h @@ -36,7 +36,7 @@ void DMA2_Stream2_IRQ_Handler(void) { ENTER_CRITICAL(); DMA2->LIFCR = DMA_LIFCR_CTCIF2; - spi_handle_rx(); + spi_rx_done(); EXIT_CRITICAL(); } @@ -60,7 +60,11 @@ void DMA2_Stream3_IRQ_Handler(void) { (void)dat; SPI1->DR = 0U; - spi_handle_tx(timed_out); + if (timed_out) { + print("SPI: TX timeout\n"); + } + + spi_tx_done(timed_out); } // ***************************** SPI init ***************************** diff --git a/board/stm32h7/llspi.h b/board/stm32h7/llspi.h index 1e2595973c..8d640bed66 100644 --- a/board/stm32h7/llspi.h +++ b/board/stm32h7/llspi.h @@ -48,7 +48,7 @@ void DMA2_Stream2_IRQ_Handler(void) { ENTER_CRITICAL(); DMA2->LIFCR = DMA_LIFCR_CTCIF2; - spi_handle_rx(); + spi_rx_done(); EXIT_CRITICAL(); } @@ -77,7 +77,7 @@ void SPI4_IRQ_Handler(void) { volatile uint8_t dat = SPI4->TXDR; (void)dat; - spi_handle_tx(false); + spi_tx_done(false); } EXIT_CRITICAL(); diff --git a/python/spi.py b/python/spi.py index c753bf4ddc..e427bea868 100644 --- a/python/spi.py +++ b/python/spi.py @@ -58,7 +58,14 @@ class SpiDevice: """ Provides locked, thread-safe access to a panda's SPI interface. """ - def __init__(self, speed): + + # 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: @@ -87,9 +94,7 @@ class PandaSpiHandle(BaseHandle): A class that mimics a libusb1 handle for panda SPI communications. """ def __init__(self): - # 50MHz is the max of the 845. older rev comma three - # may not support the full 50MHz - self.dev = SpiDevice(50000000) + self.dev = SpiDevice() # helpers def _calc_checksum(self, data: List[int]) -> int: diff --git a/requirements.txt b/requirements.txt index 6b063e91b4..1cb0faf384 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,3 +14,4 @@ pylint==2.15.4 scons==4.4.0 flaky spidev +pytest-mock diff --git a/tests/hitl/8_spi.py b/tests/hitl/8_spi.py new file mode 100644 index 0000000000..da3e5641eb --- /dev/null +++ b/tests/hitl/8_spi.py @@ -0,0 +1,58 @@ +import pytest +import random +from unittest.mock import patch + +from panda import Panda +from panda.python.spi import PandaSpiNackResponse + +pytestmark = [ + pytest.mark.test_panda_types((Panda.HW_TYPE_TRES, )) +] + +class TestSpi: + def _ping(self, mocker, panda): + # should work with no retries + spy = mocker.spy(panda._handle, '_wait_for_ack') + panda.health() + assert spy.call_count == 2 + mocker.stop(spy) + + def test_all_comm_types(self, mocker, p): + spy = mocker.spy(p._handle, '_wait_for_ack') + + # controlRead + controlWrite + p.health() + p.can_clear(0) + assert spy.call_count == 2*2 + + # bulkRead + bulkWrite + p.can_recv() + p.can_send(0x123, b"somedata", 0) + assert spy.call_count == 2*4 + + def test_bad_header(self, mocker, p): + with patch('panda.python.spi.SYNC', return_value=0): + with pytest.raises(PandaSpiNackResponse): + p.health() + self._ping(mocker, p) + + def test_bad_checksum(self, mocker, p): + cnt = p.health()['spi_checksum_error_count'] + with patch('panda.python.spi.PandaSpiHandle._calc_checksum', return_value=0): + with pytest.raises(PandaSpiNackResponse): + p.health() + self._ping(mocker, p) + assert (p.health()['spi_checksum_error_count'] - cnt) > 0 + + def test_non_existent_endpoint(self, mocker, p): + for _ in range(10): + ep = random.randint(4, 20) + with pytest.raises(PandaSpiNackResponse): + p._handle.bulkRead(ep, random.randint(1, 1000)) + + self._ping(mocker, p) + + with pytest.raises(PandaSpiNackResponse): + p._handle.bulkWrite(ep, b"abc") + + self._ping(mocker, p) From 463795d497c775f54cafa09c23f85c18870234d9 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 17 May 2023 17:21:26 -0700 Subject: [PATCH 057/197] CI: automatically update on-device dependencies (#1420) * CI: automatically update on-device dependencies * unset that * bump --- Dockerfile | 4 ++-- requirements.txt | 14 +++++++------- tests/setup_device_ci.sh | 11 +++++++++++ 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index c885e0bbbf..aeb45e3d50 100644 --- a/Dockerfile +++ b/Dockerfile @@ -50,8 +50,8 @@ RUN curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-instal ENV PATH="/root/.pyenv/bin:/root/.pyenv/shims:${PATH}" ENV PANDA_PATH=/tmp/openpilot/panda -ENV OPENPILOT_REF="ee0dd36a3c775dbd82493c84f4e7272c1eb3fcbd" -ENV OPENDBC_REF="342c0320dd271fb585db3cced397c5122078af85" +ENV OPENPILOT_REF="7b68a13048df675c0d3b5686d478c6c3f469d631" +ENV OPENDBC_REF="98a700dce5e64a338b4ab2025465fd02c2ac1146" COPY requirements.txt /tmp/ RUN pyenv install 3.8.10 && \ diff --git a/requirements.txt b/requirements.txt index 1cb0faf384..362276ca07 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,17 +1,17 @@ -libusb1==2.0.1 +libusb1>=2.0.1 numpy hexdump>=3.3 -pycryptodome==3.9.8 +pycryptodome>=3.9.8 tqdm>=4.14.0 pytest parameterized requests -flake8==3.7.9 -cffi==1.14.3 +flake8>=3.7.9 +cffi>=1.14.3 crcmod -pre-commit==2.13.0 -pylint==2.15.4 -scons==4.4.0 +pre-commit>=2.13.0 +pylint>=2.15.4 +scons>=4.4.0 flaky spidev pytest-mock diff --git a/tests/setup_device_ci.sh b/tests/setup_device_ci.sh index 6bf0e509fb..1c6f8a65b3 100755 --- a/tests/setup_device_ci.sh +++ b/tests/setup_device_ci.sh @@ -77,3 +77,14 @@ du -hs $SOURCE_DIR $SOURCE_DIR/.git rsync -a --delete $SOURCE_DIR $TEST_DIR echo "$TEST_DIR synced with $GIT_COMMIT, t=$SECONDS" + + +unset PYTHONWARNINGS +if pip install --dry-run -r $SOURCE_DIR/requirements.txt | grep -i "would install" ; then + echo "updating dependencies" + sudo mount -o rw,remount / + sudo $(which pip) install -r $SOURCE_DIR/requirements.txt + sudo mount -o ro,remount / +else + echo "packages up to date" +fi From be2ca72f81327659b4d76adf50c7a18398ca0ec4 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 17 May 2023 18:03:45 -0700 Subject: [PATCH 058/197] Revert "CI: automatically update on-device dependencies (#1420)" This reverts commit 463795d497c775f54cafa09c23f85c18870234d9. --- Dockerfile | 4 ++-- requirements.txt | 14 +++++++------- tests/setup_device_ci.sh | 11 ----------- 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/Dockerfile b/Dockerfile index aeb45e3d50..c885e0bbbf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -50,8 +50,8 @@ RUN curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-instal ENV PATH="/root/.pyenv/bin:/root/.pyenv/shims:${PATH}" ENV PANDA_PATH=/tmp/openpilot/panda -ENV OPENPILOT_REF="7b68a13048df675c0d3b5686d478c6c3f469d631" -ENV OPENDBC_REF="98a700dce5e64a338b4ab2025465fd02c2ac1146" +ENV OPENPILOT_REF="ee0dd36a3c775dbd82493c84f4e7272c1eb3fcbd" +ENV OPENDBC_REF="342c0320dd271fb585db3cced397c5122078af85" COPY requirements.txt /tmp/ RUN pyenv install 3.8.10 && \ diff --git a/requirements.txt b/requirements.txt index 362276ca07..1cb0faf384 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,17 +1,17 @@ -libusb1>=2.0.1 +libusb1==2.0.1 numpy hexdump>=3.3 -pycryptodome>=3.9.8 +pycryptodome==3.9.8 tqdm>=4.14.0 pytest parameterized requests -flake8>=3.7.9 -cffi>=1.14.3 +flake8==3.7.9 +cffi==1.14.3 crcmod -pre-commit>=2.13.0 -pylint>=2.15.4 -scons>=4.4.0 +pre-commit==2.13.0 +pylint==2.15.4 +scons==4.4.0 flaky spidev pytest-mock diff --git a/tests/setup_device_ci.sh b/tests/setup_device_ci.sh index 1c6f8a65b3..6bf0e509fb 100755 --- a/tests/setup_device_ci.sh +++ b/tests/setup_device_ci.sh @@ -77,14 +77,3 @@ du -hs $SOURCE_DIR $SOURCE_DIR/.git rsync -a --delete $SOURCE_DIR $TEST_DIR echo "$TEST_DIR synced with $GIT_COMMIT, t=$SECONDS" - - -unset PYTHONWARNINGS -if pip install --dry-run -r $SOURCE_DIR/requirements.txt | grep -i "would install" ; then - echo "updating dependencies" - sudo mount -o rw,remount / - sudo $(which pip) install -r $SOURCE_DIR/requirements.txt - sudo mount -o ro,remount / -else - echo "packages up to date" -fi From 2ed514807e51f7a9c2cf160ac18a093474a0ca2b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 17 May 2023 20:59:37 -0700 Subject: [PATCH 059/197] H7: restart SPI peripheral between transfers (#1424) * H7: restart SPI peripheral between transfers * fix misra --------- Co-authored-by: Comma Device --- board/drivers/spi.h | 12 +++++++----- board/stm32fx/llspi.h | 5 ++--- board/stm32h7/llspi.h | 26 +++++++++++++------------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/board/drivers/spi.h b/board/drivers/spi.h index f78bd0b5f2..a185974989 100644 --- a/board/drivers/spi.h +++ b/board/drivers/spi.h @@ -3,6 +3,10 @@ #define SPI_BUF_SIZE 1024U #define SPI_TIMEOUT_US 10000U +// we expect less than 50 transactions (including control messages and +// CAN buffers) at the 100Hz boardd interval, plus some buffer +#define SPI_IRQ_RATE 6500U + #ifdef STM32H7 __attribute__((section(".ram_d1"))) uint8_t spi_buf_rx[SPI_BUF_SIZE]; __attribute__((section(".ram_d1"))) uint8_t spi_buf_tx[SPI_BUF_SIZE]; @@ -43,10 +47,6 @@ void llspi_mosi_dma(uint8_t *addr, int len); void llspi_miso_dma(uint8_t *addr, int len); void spi_init(void) { - // clear buffers (for debugging) - (void)memset(spi_buf_rx, 0, SPI_BUF_SIZE); - (void)memset(spi_buf_tx, 0, SPI_BUF_SIZE); - // platform init llspi_init(); @@ -127,8 +127,10 @@ void spi_rx_done(void) { response_ack = false; print("- incorrect data checksum "); puth2(spi_data_len_mosi); - print(" "); + print("\n"); + hexdump(spi_buf_rx, SPI_HEADER_SIZE); hexdump(&(spi_buf_rx[SPI_HEADER_SIZE]), MIN(spi_data_len_mosi, 64)); + print("\n"); } if (!response_ack) { diff --git a/board/stm32fx/llspi.h b/board/stm32fx/llspi.h index 2ee7ffa385..31e419b6f7 100644 --- a/board/stm32fx/llspi.h +++ b/board/stm32fx/llspi.h @@ -69,9 +69,8 @@ void DMA2_Stream3_IRQ_Handler(void) { // ***************************** SPI init ***************************** void llspi_init(void) { - // We expect less than 50 transactions (including control messages and CAN buffers) at the 100Hz boardd interval. Can be raised if needed. - REGISTER_INTERRUPT(DMA2_Stream2_IRQn, DMA2_Stream2_IRQ_Handler, 5000U, FAULT_INTERRUPT_RATE_SPI_DMA) - REGISTER_INTERRUPT(DMA2_Stream3_IRQn, DMA2_Stream3_IRQ_Handler, 5000U, FAULT_INTERRUPT_RATE_SPI_DMA) + REGISTER_INTERRUPT(DMA2_Stream2_IRQn, DMA2_Stream2_IRQ_Handler, SPI_IRQ_RATE, FAULT_INTERRUPT_RATE_SPI_DMA) + REGISTER_INTERRUPT(DMA2_Stream3_IRQn, DMA2_Stream3_IRQ_Handler, SPI_IRQ_RATE, FAULT_INTERRUPT_RATE_SPI_DMA) // Setup MOSI DMA register_set(&(DMA2_Stream2->CR), (DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_0 | DMA_SxCR_MINC | DMA_SxCR_TCIE), 0x1E077EFEU); diff --git a/board/stm32h7/llspi.h b/board/stm32h7/llspi.h index 8d640bed66..ad1dc4d7bd 100644 --- a/board/stm32h7/llspi.h +++ b/board/stm32h7/llspi.h @@ -11,6 +11,10 @@ void llspi_mosi_dma(uint8_t *addr, int len) { (void)dat; } + // clear all pending + SPI4->IFCR |= (0x1FFU << 3U); + register_set(&(SPI4->IER), 0, 0x3FFU); + // setup destination and length register_set(&(DMA2_Stream2->M0AR), (uint32_t)addr, 0xFFFFFFFFU); DMA2_Stream2->NDTR = len; @@ -23,23 +27,25 @@ void llspi_mosi_dma(uint8_t *addr, int len) { // panda -> master DMA start void llspi_miso_dma(uint8_t *addr, int len) { - // disable DMA + // disable DMA + SPI DMA2_Stream3->CR &= ~DMA_SxCR_EN; register_clear_bits(&(SPI4->CFG1), SPI_CFG1_TXDMAEN); + register_clear_bits(&(SPI4->CR1), SPI_CR1_SPE); // setup source and length register_set(&(DMA2_Stream3->M0AR), (uint32_t)addr, 0xFFFFFFFFU); DMA2_Stream3->NDTR = len; // clear under-run while we were reading - SPI4->IFCR |= SPI_IFCR_UDRC; + SPI4->IFCR |= (0x1FFU << 3U); // setup interrupt on TXC register_set(&(SPI4->IER), (1U << SPI_IER_EOTIE_Pos), 0x3FFU); - // enable DMA + // enable DMA + SPI register_set_bits(&(SPI4->CFG1), SPI_CFG1_TXDMAEN); DMA2_Stream3->CR |= DMA_SxCR_EN; + register_set_bits(&(SPI4->CR1), SPI_CR1_SPE); } // master -> panda DMA finished @@ -68,15 +74,10 @@ void SPI4_IRQ_Handler(void) { ENTER_CRITICAL(); // clear flag - SPI4->IFCR |= SPI_IFCR_EOTC; + SPI4->IFCR |= (0x1FFU << 3U); if (spi_tx_dma_done && ((SPI4->SR & SPI_SR_TXC) != 0)) { spi_tx_dma_done = false; - - register_set(&(SPI4->IER), 0, 0x3FFU); - - volatile uint8_t dat = SPI4->TXDR; - (void)dat; spi_tx_done(false); } @@ -85,10 +86,9 @@ void SPI4_IRQ_Handler(void) { void llspi_init(void) { - // We expect less than 50 transactions (including control messages and CAN buffers) at the 100Hz boardd interval. Can be raised if needed. - REGISTER_INTERRUPT(SPI4_IRQn, SPI4_IRQ_Handler, 5000U, FAULT_INTERRUPT_RATE_SPI_DMA) - REGISTER_INTERRUPT(DMA2_Stream2_IRQn, DMA2_Stream2_IRQ_Handler, 5000U, FAULT_INTERRUPT_RATE_SPI_DMA) - REGISTER_INTERRUPT(DMA2_Stream3_IRQn, DMA2_Stream3_IRQ_Handler, 5000U, FAULT_INTERRUPT_RATE_SPI_DMA) + REGISTER_INTERRUPT(SPI4_IRQn, SPI4_IRQ_Handler, SPI_IRQ_RATE, FAULT_INTERRUPT_RATE_SPI_DMA) + REGISTER_INTERRUPT(DMA2_Stream2_IRQn, DMA2_Stream2_IRQ_Handler, SPI_IRQ_RATE, FAULT_INTERRUPT_RATE_SPI_DMA) + REGISTER_INTERRUPT(DMA2_Stream3_IRQn, DMA2_Stream3_IRQ_Handler, SPI_IRQ_RATE, FAULT_INTERRUPT_RATE_SPI_DMA) // Setup MOSI DMA register_set(&(DMAMUX1_Channel10->CCR), 83U, 0xFFFFFFFFU); From 14bd994f83122634fdda722710b2d372b90f5967 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 18 May 2023 00:21:29 -0700 Subject: [PATCH 060/197] Ford: add longitudinal safety (#1359) * add ACCDATA msg * ford long safety * long flag * split stock and op long tests * use accel limits for brake * add comment * fix acceleration limits fix acceleration limits * fix gas limits * temp bug * fix min gas, allow -0.5 (engine braking?) * fix test * fix tests * yay it caught this * pylint * base name * typing * Revert "typing" This reverts commit 7fb5e304cb8eb6c9afff9fbe4266bc0a67f49c68. * temp fix * rm line * this is handled by PandaSafetyTest * revert * move --------- Co-authored-by: Cameron Clough --- board/safety/safety_ford.h | 71 ++++++++++++++++++++++++++---- python/__init__.py | 2 + tests/safety/common.py | 2 + tests/safety/test_ford.py | 89 ++++++++++++++++++++++++++++++++------ 4 files changed, 143 insertions(+), 21 deletions(-) diff --git a/board/safety/safety_ford.h b/board/safety/safety_ford.h index dafca91ee4..446754c10a 100644 --- a/board/safety/safety_ford.h +++ b/board/safety/safety_ford.h @@ -6,6 +6,7 @@ #define MSG_EngVehicleSpThrottle2 0x202 // RX from PCM, for second vehicle speed #define MSG_Yaw_Data_FD1 0x91 // RX from RCM, for yaw rate #define MSG_Steering_Data_FD1 0x083 // TX by OP, various driver switches and LKAS/CC buttons +#define MSG_ACCDATA 0x186 // TX by OP, ACC controls #define MSG_ACCDATA_3 0x18A // TX by OP, ACC/TJA user interface #define MSG_Lane_Assist_Data1 0x3CA // TX by OP, Lane Keep Assist #define MSG_LateralMotionControl 0x3D3 // TX by OP, Traffic Jam Assist @@ -15,7 +16,7 @@ #define FORD_MAIN_BUS 0U #define FORD_CAM_BUS 2U -const CanMsg FORD_TX_MSGS[] = { +const CanMsg FORD_STOCK_TX_MSGS[] = { {MSG_Steering_Data_FD1, 0, 8}, {MSG_Steering_Data_FD1, 2, 8}, {MSG_ACCDATA_3, 0, 8}, @@ -23,7 +24,18 @@ const CanMsg FORD_TX_MSGS[] = { {MSG_LateralMotionControl, 0, 8}, {MSG_IPMA_Data, 0, 8}, }; -#define FORD_TX_LEN (sizeof(FORD_TX_MSGS) / sizeof(FORD_TX_MSGS[0])) +#define FORD_STOCK_TX_LEN (sizeof(FORD_STOCK_TX_MSGS) / sizeof(FORD_STOCK_TX_MSGS[0])) + +const CanMsg FORD_LONG_TX_MSGS[] = { + {MSG_Steering_Data_FD1, 0, 8}, + {MSG_Steering_Data_FD1, 2, 8}, + {MSG_ACCDATA, 0, 8}, + {MSG_ACCDATA_3, 0, 8}, + {MSG_Lane_Assist_Data1, 0, 8}, + {MSG_LateralMotionControl, 0, 8}, + {MSG_IPMA_Data, 0, 8}, +}; +#define FORD_LONG_TX_LEN (sizeof(FORD_LONG_TX_MSGS) / sizeof(FORD_LONG_TX_MSGS[0])) // warning: quality flags are not yet checked in openpilot's CAN parser, // this may be the cause of blocked messages @@ -120,6 +132,24 @@ static bool ford_get_quality_flag_valid(CANPacket_t *to_push) { return valid; } +const uint16_t FORD_PARAM_LONGITUDINAL = 1; + +bool ford_longitudinal = false; + +const LongitudinalLimits FORD_LONG_LIMITS = { + // acceleration cmd limits (used for brakes) + // Signal: AccBrkTot_A_Rq + .max_accel = 5641, // 1.9999 m/s^s + .min_accel = 4231, // -3.4991 m/s^2 + .inactive_accel = 5128, // -0.0008 m/s^2 + + // gas cmd limits + // Signal: AccPrpl_A_Rq + .max_gas = 700, // 2.0 m/s^2 + .min_gas = 450, // -0.5 m/s^2 + .inactive_gas = 0, // -5.0 m/s^2 +}; + #define INACTIVE_CURVATURE 1000U #define INACTIVE_CURVATURE_RATE 4096U #define INACTIVE_PATH_OFFSET 512U @@ -219,12 +249,29 @@ static int ford_rx_hook(CANPacket_t *to_push) { } static int ford_tx_hook(CANPacket_t *to_send) { - int tx = 1; int addr = GET_ADDR(to_send); - if (!msg_allowed(to_send, FORD_TX_MSGS, FORD_TX_LEN)) { - tx = 0; + if (ford_longitudinal) { + tx = msg_allowed(to_send, FORD_LONG_TX_MSGS, FORD_LONG_TX_LEN); + } else { + tx = msg_allowed(to_send, FORD_STOCK_TX_MSGS, FORD_STOCK_TX_LEN); + } + + // Safety check for ACCDATA accel and brake requests + if (addr == MSG_ACCDATA) { + // Signal: AccPrpl_A_Rq + int gas = ((GET_BYTE(to_send, 6) & 0x3U) << 8) | GET_BYTE(to_send, 7); + // Signal: AccBrkTot_A_Rq + int accel = ((GET_BYTE(to_send, 0) & 0x1FU) << 8) | GET_BYTE(to_send, 1); + + bool violation = false; + violation |= longitudinal_accel_checks(accel, FORD_LONG_LIMITS); + violation |= longitudinal_gas_checks(gas, FORD_LONG_LIMITS); + + if (violation) { + tx = 0; + } } // Safety check for Steering_Data_FD1 button signals @@ -289,8 +336,14 @@ static int ford_fwd_hook(int bus_num, int addr) { break; } case FORD_CAM_BUS: { - // Block stock LKAS messages - if (!ford_lkas_msg_check(addr)) { + if (ford_lkas_msg_check(addr)) { + // Block stock LKAS and UI messages + bus_fwd = -1; + } else if (ford_longitudinal && (addr == MSG_ACCDATA)) { + // Block stock ACC message + bus_fwd = -1; + } else { + // Forward remaining traffic bus_fwd = FORD_MAIN_BUS; } break; @@ -307,7 +360,9 @@ static int ford_fwd_hook(int bus_num, int addr) { static const addr_checks* ford_init(uint16_t param) { UNUSED(param); - +#ifdef ALLOW_DEBUG + ford_longitudinal = GET_FLAG(param, FORD_PARAM_LONGITUDINAL); +#endif return &ford_rx_checks; } diff --git a/python/__init__.py b/python/__init__.py index ebffb3d461..0849f0a752 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -235,6 +235,8 @@ class Panda: FLAG_GM_HW_CAM = 1 FLAG_GM_HW_CAM_LONG = 2 + FLAG_FORD_LONG_CONTROL = 1 + def __init__(self, serial: Optional[str] = None, claim: bool = True, disable_checks: bool = True): self._connect_serial = serial self._disable_checks = disable_checks diff --git a/tests/safety/common.py b/tests/safety/common.py index 54f085940c..d9743c4b07 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -900,6 +900,8 @@ def test_tx_hook_on_wrong_safety_mode(self): continue if {attr, current_test}.issubset({'TestGmCameraSafety', 'TestGmCameraLongitudinalSafety'}): continue + if {attr, current_test}.issubset({'TestFordStockSafety', 'TestFordLongitudinalSafety'}): + continue if attr.startswith('TestHyundaiCanfd') and current_test.startswith('TestHyundaiCanfd'): continue if {attr, current_test}.issubset({'TestVolkswagenMqbSafety', 'TestVolkswagenMqbStockSafety', 'TestVolkswagenMqbLongSafety'}): diff --git a/tests/safety/test_ford.py b/tests/safety/test_ford.py index 828d7b883a..0d00680fe9 100755 --- a/tests/safety/test_ford.py +++ b/tests/safety/test_ford.py @@ -14,6 +14,7 @@ MSG_EngVehicleSpThrottle2 = 0x202 # RX from PCM, for second vehicle speed MSG_Yaw_Data_FD1 = 0x91 # RX from RCM, for yaw rate MSG_Steering_Data_FD1 = 0x083 # TX by OP, various driver switches and LKAS/CC buttons +MSG_ACCDATA = 0x186 # TX by OP, ACC controls MSG_ACCDATA_3 = 0x18A # TX by OP, ACC/TJA user interface MSG_Lane_Assist_Data1 = 0x3CA # TX by OP, Lane Keep Assist MSG_LateralMotionControl = 0x3D3 # TX by OP, Traffic Jam Assist @@ -56,18 +57,15 @@ class Buttons: TJA_TOGGLE = 2 -class TestFordSafety(common.PandaSafetyTest): +# Ford safety has two different configurations tested here: +# * stock longitudinal +# * openpilot longitudinal + +class TestFordSafetyBase(common.PandaSafetyTest): STANDSTILL_THRESHOLD = 1 RELAY_MALFUNCTION_ADDR = MSG_IPMA_Data RELAY_MALFUNCTION_BUS = 0 - TX_MSGS = [ - [MSG_Steering_Data_FD1, 0], [MSG_Steering_Data_FD1, 2], [MSG_ACCDATA_3, 0], [MSG_Lane_Assist_Data1, 0], - [MSG_LateralMotionControl, 0], [MSG_IPMA_Data, 0], - ] - FWD_BLACKLISTED_ADDRS = {2: [MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl, MSG_IPMA_Data]} - FWD_BUS_LOOKUP = {0: 2, 2: 0} - # Max allowed delta between car speeds MAX_SPEED_DELTA = 2.0 # m/s @@ -85,11 +83,13 @@ class TestFordSafety(common.PandaSafetyTest): cnt_speed_2 = 0 cnt_yaw_rate = 0 - def setUp(self): - self.packer = CANPackerPanda("ford_lincoln_base_pt") - self.safety = libpanda_py.libpanda - self.safety.set_safety_hooks(Panda.SAFETY_FORD, 0) - self.safety.init_tests() + packer: CANPackerPanda + safety: libpanda_py.Panda + + @classmethod + def setUpClass(cls): + if cls.__name__ == "TestFordSafetyBase": + raise unittest.SkipTest def _set_prev_desired_angle(self, t): t = round(t * self.DEG_TO_CAN) @@ -335,5 +335,68 @@ def test_acc_buttons(self): self.assertEqual(enabled, self._tx(self._acc_button_msg(Buttons.CANCEL, bus))) +class TestFordStockSafety(TestFordSafetyBase): + TX_MSGS = [ + [MSG_Steering_Data_FD1, 0], [MSG_Steering_Data_FD1, 2], [MSG_ACCDATA_3, 0], [MSG_Lane_Assist_Data1, 0], + [MSG_LateralMotionControl, 0], [MSG_IPMA_Data, 0], + ] + FWD_BLACKLISTED_ADDRS = {2: [MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl, MSG_IPMA_Data]} + FWD_BUS_LOOKUP = {0: 2, 2: 0} + + def setUp(self): + self.packer = CANPackerPanda("ford_lincoln_base_pt") + self.safety = libpanda_py.libpanda + self.safety.set_safety_hooks(Panda.SAFETY_FORD, 0) + self.safety.init_tests() + + +class TestFordLongitudinalSafety(TestFordSafetyBase): + TX_MSGS = [ + [MSG_Steering_Data_FD1, 0], [MSG_Steering_Data_FD1, 2], [MSG_ACCDATA, 0], [MSG_ACCDATA_3, 0], [MSG_Lane_Assist_Data1, 0], + [MSG_LateralMotionControl, 0], [MSG_IPMA_Data, 0], + ] + FWD_BLACKLISTED_ADDRS = {2: [MSG_ACCDATA, MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl, MSG_IPMA_Data]} + FWD_BUS_LOOKUP = {0: 2, 2: 0} + + MAX_ACCEL = 2.0 # accel is used for brakes, but openpilot can set positive values + MIN_ACCEL = -3.5 + INACTIVE_ACCEL = 0.0 + + MAX_GAS = 2.0 + MIN_GAS = -0.5 + INACTIVE_GAS = -5.0 + + def setUp(self): + self.packer = CANPackerPanda("ford_lincoln_base_pt") + self.safety = libpanda_py.libpanda + self.safety.set_safety_hooks(Panda.SAFETY_FORD, Panda.FLAG_FORD_LONG_CONTROL) + self.safety.init_tests() + + # ACC command + def _acc_command_msg(self, gas: float, brake: float): + values = { + "AccPrpl_A_Rq": gas, # [-5|5.23] m/s^2 + "AccBrkTot_A_Rq": brake, # [-20|11.9449] m/s^2 + } + return self.packer.make_can_msg_panda("ACCDATA", 0, values) + + def test_gas_safety_check(self): + # Test that uses _acc_command_msg function to test gas limits (setting accel to inactive_accel) + for controls_allowed in (True, False): + self.safety.set_controls_allowed(controls_allowed) + for gas in np.arange(self.MIN_GAS - 2, self.MAX_GAS + 2, 0.05): + gas = round(gas, 2) # floats might not hit exact boundary conditions without rounding + should_tx = (controls_allowed and self.MIN_GAS <= gas <= self.MAX_GAS) or gas == self.INACTIVE_GAS + self.assertEqual(should_tx, self._tx(self._acc_command_msg(gas, self.INACTIVE_ACCEL))) + + def test_brake_safety_check(self): + for controls_allowed in (True, False): + self.safety.set_controls_allowed(controls_allowed) + for brake in np.arange(self.MIN_ACCEL - 2, self.MAX_ACCEL + 2, 0.05): + brake = round(brake, 2) # floats might not hit exact boundary conditions without rounding + should_tx = (controls_allowed and self.MIN_ACCEL <= brake <= self.MAX_ACCEL) or brake == self.INACTIVE_ACCEL + self.assertEqual(should_tx, self._tx(self._acc_command_msg(self.INACTIVE_GAS, brake)), (controls_allowed, brake)) + + if __name__ == "__main__": unittest.main() From 803211a35a437682c9ef4c8e45b792da37254e87 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 18 May 2023 00:23:02 -0700 Subject: [PATCH 061/197] tests: remove temporary comment --- tests/safety/test_ford.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/safety/test_ford.py b/tests/safety/test_ford.py index 0d00680fe9..ee301f6b72 100755 --- a/tests/safety/test_ford.py +++ b/tests/safety/test_ford.py @@ -381,7 +381,6 @@ def _acc_command_msg(self, gas: float, brake: float): return self.packer.make_can_msg_panda("ACCDATA", 0, values) def test_gas_safety_check(self): - # Test that uses _acc_command_msg function to test gas limits (setting accel to inactive_accel) for controls_allowed in (True, False): self.safety.set_controls_allowed(controls_allowed) for gas in np.arange(self.MIN_GAS - 2, self.MAX_GAS + 2, 0.05): From 831a296e39ad8d6922f15f8e4e2353714643f7ed Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 18 May 2023 13:43:04 -0700 Subject: [PATCH 062/197] Ford: test inactive gas (#1429) test inactive gas --- tests/safety/test_ford.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/safety/test_ford.py b/tests/safety/test_ford.py index ee301f6b72..36208cdff2 100755 --- a/tests/safety/test_ford.py +++ b/tests/safety/test_ford.py @@ -383,7 +383,7 @@ def _acc_command_msg(self, gas: float, brake: float): def test_gas_safety_check(self): for controls_allowed in (True, False): self.safety.set_controls_allowed(controls_allowed) - for gas in np.arange(self.MIN_GAS - 2, self.MAX_GAS + 2, 0.05): + for gas in np.concatenate((np.arange(self.MIN_GAS - 2, self.MAX_GAS + 2, 0.05), [self.INACTIVE_GAS])): gas = round(gas, 2) # floats might not hit exact boundary conditions without rounding should_tx = (controls_allowed and self.MIN_GAS <= gas <= self.MAX_GAS) or gas == self.INACTIVE_GAS self.assertEqual(should_tx, self._tx(self._acc_command_msg(gas, self.INACTIVE_ACCEL))) @@ -394,7 +394,7 @@ def test_brake_safety_check(self): for brake in np.arange(self.MIN_ACCEL - 2, self.MAX_ACCEL + 2, 0.05): brake = round(brake, 2) # floats might not hit exact boundary conditions without rounding should_tx = (controls_allowed and self.MIN_ACCEL <= brake <= self.MAX_ACCEL) or brake == self.INACTIVE_ACCEL - self.assertEqual(should_tx, self._tx(self._acc_command_msg(self.INACTIVE_GAS, brake)), (controls_allowed, brake)) + self.assertEqual(should_tx, self._tx(self._acc_command_msg(self.INACTIVE_GAS, brake))) if __name__ == "__main__": From 622106d7d4c581213c35581ce262f79a4ec2dedb Mon Sep 17 00:00:00 2001 From: Igor Biletskyy Date: Thu, 18 May 2023 13:54:30 -0700 Subject: [PATCH 063/197] Revert "H7: fix lockup on disconnected bus" (#1430) Revert "H7: fix lockup on disconnected bus (#1410)" This reverts commit 03435947bb249dc3ea7338300db5196877ff1b8d. --- board/drivers/fdcan.h | 7 +------ board/stm32h7/llfdcan.h | 7 +++---- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/board/drivers/fdcan.h b/board/drivers/fdcan.h index 706f49439f..4dcc3fdf46 100644 --- a/board/drivers/fdcan.h +++ b/board/drivers/fdcan.h @@ -64,12 +64,7 @@ void update_can_health_pkt(uint8_t can_number, bool error_irq) { if ((CANx->IR & (FDCAN_IR_TEFL)) != 0) { can_health[can_number].total_tx_lost_cnt += 1U; } - // Actually reset can core only on arbitration or data phase errors - if ((CANx->IR & (FDCAN_IR_PED | FDCAN_IR_PEA)) != 0) { - llcan_clear_send(CANx); - } - // Clear error interrupts - CANx->IR |= (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EW | FDCAN_IR_EP | FDCAN_IR_ELO | FDCAN_IR_BO | FDCAN_IR_TEFL | FDCAN_IR_RF0L); + llcan_clear_send(CANx); } EXIT_CRITICAL(); } diff --git a/board/stm32h7/llfdcan.h b/board/stm32h7/llfdcan.h index 311c6ae9b0..e5791fc528 100644 --- a/board/stm32h7/llfdcan.h +++ b/board/stm32h7/llfdcan.h @@ -244,8 +244,7 @@ bool llcan_init(FDCAN_GlobalTypeDef *CANx) { } void llcan_clear_send(FDCAN_GlobalTypeDef *CANx) { - // from datasheet: "Transmit cancellation is not intended for Tx FIFO operation." - // so we need to clear pending transmission manually by resetting FDCAN core - bool ret = llcan_init(CANx); - UNUSED(ret); + CANx->TXBCR = 0xFFFFU; // Abort message transmission on error interrupt + // Clear error interrupts + CANx->IR |= (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EW | FDCAN_IR_EP | FDCAN_IR_ELO | FDCAN_IR_BO | FDCAN_IR_TEFL | FDCAN_IR_RF0L); } From 2a538337537e6830f02565c2c36af4f886c963e4 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 18 May 2023 14:14:26 -0700 Subject: [PATCH 064/197] Ford safety: prevent disable AEB (#1428) * Ford AEB safety * whoops * working tests * debugging * clean up * stock aeb test * split up test * rm * cmt * rm * just test stateless sigs for simple branch * clean up * rm * just test this bit * it's signed now * cmt * cmt * clean up * ohh --- board/safety/safety_ford.h | 5 +++++ tests/safety/test_ford.py | 17 ++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/board/safety/safety_ford.h b/board/safety/safety_ford.h index 446754c10a..93c1e3a187 100644 --- a/board/safety/safety_ford.h +++ b/board/safety/safety_ford.h @@ -264,11 +264,16 @@ static int ford_tx_hook(CANPacket_t *to_send) { int gas = ((GET_BYTE(to_send, 6) & 0x3U) << 8) | GET_BYTE(to_send, 7); // Signal: AccBrkTot_A_Rq int accel = ((GET_BYTE(to_send, 0) & 0x1FU) << 8) | GET_BYTE(to_send, 1); + // Signal: CmbbDeny_B_Actl + int cmbb_deny = GET_BIT(to_send, 37U); bool violation = false; violation |= longitudinal_accel_checks(accel, FORD_LONG_LIMITS); violation |= longitudinal_gas_checks(gas, FORD_LONG_LIMITS); + // Safety check for stock AEB + violation |= cmbb_deny != 0; // do not prevent stock AEB actuation + if (violation) { tx = 0; } diff --git a/tests/safety/test_ford.py b/tests/safety/test_ford.py index 36208cdff2..e7e874029f 100755 --- a/tests/safety/test_ford.py +++ b/tests/safety/test_ford.py @@ -373,13 +373,24 @@ def setUp(self): self.safety.init_tests() # ACC command - def _acc_command_msg(self, gas: float, brake: float): + def _acc_command_msg(self, gas: float, brake: float, cmbb_deny: bool = False): values = { - "AccPrpl_A_Rq": gas, # [-5|5.23] m/s^2 - "AccBrkTot_A_Rq": brake, # [-20|11.9449] m/s^2 + "AccPrpl_A_Rq": gas, # [-5|5.23] m/s^2 + "AccBrkTot_A_Rq": brake, # [-20|11.9449] m/s^2 + "CmbbDeny_B_Actl": 1 if cmbb_deny else 0, # [0|1] deny AEB actuation } return self.packer.make_can_msg_panda("ACCDATA", 0, values) + def test_stock_aeb(self): + # Test that CmbbDeny_B_Actl is never 1, it prevents the ABS module from actuating AEB requests from ACCDATA_2 + for controls_allowed in (True, False): + self.safety.set_controls_allowed(controls_allowed) + for cmbb_deny in (True, False): + should_tx = not cmbb_deny + self.assertEqual(should_tx, self._tx(self._acc_command_msg(self.INACTIVE_GAS, self.INACTIVE_ACCEL, cmbb_deny))) + should_tx = controls_allowed and not cmbb_deny + self.assertEqual(should_tx, self._tx(self._acc_command_msg(self.MAX_GAS, self.MAX_ACCEL, cmbb_deny))) + def test_gas_safety_check(self): for controls_allowed in (True, False): self.safety.set_controls_allowed(controls_allowed) From f5c28f811df33a698b86df19ddc3a03a44642afa Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 18 May 2023 15:46:06 -0700 Subject: [PATCH 065/197] tres: run fan tests (#1422) --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 51684cea7b..0a43e1619f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -94,7 +94,7 @@ pipeline { phone_steps("panda-tres", [ ["build", "scons -j4"], ["flash", "cd tests/ && ./ci_reset_internal_hw.py"], - ["test", "cd tests/hitl && HW_TYPES=9 pytest --durations=0 2*.py [5-9]*.py -k 'not test_fan_controller and not test_fan_overshoot'"], + ["test", "cd tests/hitl && HW_TYPES=9 pytest --durations=0 2*.py [5-9]*.py"], ]) } } From 52f96bac685b129c5b90e274fe39386e76da512e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 19 May 2023 22:43:34 -0700 Subject: [PATCH 066/197] spi: nack on can tx endpoint if buffer is full (#1426) * spi: nack on can tx endpoint if buffer is full * handle in python lib * fix timeout * fix timeout * fix linter * cleanup * fix --------- Co-authored-by: Comma Device --- board/can_comms.h | 9 ++++++--- board/config.h | 3 ++- board/drivers/bxcan.h | 2 +- board/drivers/can_common.h | 2 +- board/drivers/fdcan.h | 2 +- board/drivers/spi.h | 18 ++++++++++++++---- board/drivers/usb.h | 7 +++---- board/flasher.h | 2 +- board/pedal/main.c | 2 +- python/__init__.py | 8 +++++--- python/spi.py | 13 +++++++++---- tests/hitl/8_spi.py | 8 ++++---- tests/libpanda/panda.c | 5 +++-- 13 files changed, 51 insertions(+), 30 deletions(-) diff --git a/board/can_comms.h b/board/can_comms.h index 02bc0aef31..93c32c71d4 100644 --- a/board/can_comms.h +++ b/board/can_comms.h @@ -110,8 +110,11 @@ void comms_can_reset(void) { } // TODO: make this more general! -void usb_cb_ep3_out_complete(void) { - if (can_tx_check_min_slots_free(MAX_CAN_MSGS_PER_BULK_TRANSFER)) { - usb_outep3_resume_if_paused(); +void refresh_can_tx_slots_available(void) { + if (can_tx_check_min_slots_free(MAX_CAN_MSGS_PER_USB_BULK_TRANSFER)) { + can_tx_comms_resume_usb(); + } + if (can_tx_check_min_slots_free(MAX_CAN_MSGS_PER_SPI_BULK_TRANSFER)) { + can_tx_comms_resume_spi(); } } diff --git a/board/config.h b/board/config.h index 12b5140076..903161876c 100644 --- a/board/config.h +++ b/board/config.h @@ -13,7 +13,8 @@ #define CAN_INIT_TIMEOUT_MS 500U #define DEEPSLEEP_WAKEUP_DELAY 3U #define USBPACKET_MAX_SIZE 0x40U -#define MAX_CAN_MSGS_PER_BULK_TRANSFER 51U +#define MAX_CAN_MSGS_PER_USB_BULK_TRANSFER 51U +#define MAX_CAN_MSGS_PER_SPI_BULK_TRANSFER 170U #define VIN_READOUT_DIVIDER 11U diff --git a/board/drivers/bxcan.h b/board/drivers/bxcan.h index 815ef0fe1f..15e34d038a 100644 --- a/board/drivers/bxcan.h +++ b/board/drivers/bxcan.h @@ -151,7 +151,7 @@ void process_can(uint8_t can_number) { can_health[can_number].total_tx_checksum_error_cnt += 1U; } - usb_cb_ep3_out_complete(); + refresh_can_tx_slots_available(); } } diff --git a/board/drivers/can_common.h b/board/drivers/can_common.h index 3f2f0830a3..be6b5b2a5d 100644 --- a/board/drivers/can_common.h +++ b/board/drivers/can_common.h @@ -147,7 +147,7 @@ void can_clear(can_ring *q) { q->r_ptr = 0; EXIT_CRITICAL(); // handle TX buffer full with zero ECUs awake on the bus - usb_cb_ep3_out_complete(); + refresh_can_tx_slots_available(); } // assign CAN numbering diff --git a/board/drivers/fdcan.h b/board/drivers/fdcan.h index 4dcc3fdf46..a5b97ba13e 100644 --- a/board/drivers/fdcan.h +++ b/board/drivers/fdcan.h @@ -119,7 +119,7 @@ void process_can(uint8_t can_number) { can_health[can_number].total_tx_checksum_error_cnt += 1U; } - usb_cb_ep3_out_complete(); + refresh_can_tx_slots_available(); } } diff --git a/board/drivers/spi.h b/board/drivers/spi.h index a185974989..ed88f6ea81 100644 --- a/board/drivers/spi.h +++ b/board/drivers/spi.h @@ -37,7 +37,7 @@ uint8_t spi_endpoint; uint16_t spi_data_len_mosi; uint16_t spi_data_len_miso; uint16_t spi_checksum_error_count = 0; - +bool spi_can_tx_ready = false; #define SPI_HEADER_SIZE 7U @@ -46,6 +46,10 @@ void llspi_init(void); void llspi_mosi_dma(uint8_t *addr, int len); void llspi_miso_dma(uint8_t *addr, int len); +void can_tx_comms_resume_spi(void) { + spi_can_tx_ready = true; +} + void spi_init(void) { // platform init llspi_init(); @@ -114,8 +118,14 @@ void spi_rx_done(void) { response_ack = true; } else if (spi_endpoint == 3U) { if (spi_data_len_mosi > 0U) { - comms_can_write(&spi_buf_rx[SPI_HEADER_SIZE], spi_data_len_mosi); - response_ack = true; + 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: did expect data for can_write\n"); } @@ -126,7 +136,7 @@ void spi_rx_done(void) { // Checksum was incorrect response_ack = false; print("- incorrect data checksum "); - puth2(spi_data_len_mosi); + 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)); diff --git a/board/drivers/usb.h b/board/drivers/usb.h index 469299d74e..d10bed15e3 100644 --- a/board/drivers/usb.h +++ b/board/drivers/usb.h @@ -27,8 +27,7 @@ bool usb_enumerated = false; uint16_t usb_last_frame_num = 0U; void usb_init(void); -void usb_cb_ep3_out_complete(void); -void usb_outep3_resume_if_paused(void); +void refresh_can_tx_slots_available(void); // **** supporting defines **** @@ -808,7 +807,7 @@ void usb_irqhandler(void) { #endif // NAK cleared by process_can (if tx buffers have room) outep3_processing = false; - usb_cb_ep3_out_complete(); + refresh_can_tx_slots_available(); } else if ((USBx_OUTEP(3)->DOEPINT & 0x2000) != 0) { #ifdef DEBUG_USB print(" OUT3 PACKET WTF\n"); @@ -926,7 +925,7 @@ void usb_irqhandler(void) { //USBx->GINTMSK = 0xFFFFFFFF & ~(USB_OTG_GINTMSK_NPTXFEM | USB_OTG_GINTMSK_PTXFEM | USB_OTG_GINTSTS_SOF | USB_OTG_GINTSTS_EOPF); } -void usb_outep3_resume_if_paused(void) { +void can_tx_comms_resume_usb(void) { ENTER_CRITICAL(); if (!outep3_processing && (USBx_OUTEP(3)->DOEPCTL & USB_OTG_DOEPCTL_NAKSTS) != 0) { USBx_OUTEP(3)->DOEPTSIZ = (32U << 19) | 0x800U; diff --git a/board/flasher.h b/board/flasher.h index 97d81096e5..bcd98daf0f 100644 --- a/board/flasher.h +++ b/board/flasher.h @@ -110,7 +110,7 @@ int comms_can_read(uint8_t *data, uint32_t max_len) { return 0; } -void usb_cb_ep3_out_complete(void) {} +void refresh_can_tx_slots_available(void) {} void comms_endpoint2_write(uint8_t *data, uint32_t len) { current_board->set_led(LED_RED, 0); diff --git a/board/pedal/main.c b/board/pedal/main.c index e6b197cf73..eb40466106 100644 --- a/board/pedal/main.c +++ b/board/pedal/main.c @@ -54,7 +54,7 @@ void comms_endpoint2_write(uint8_t *data, uint32_t len) { UNUSED(data); UNUSED(len); } -void usb_cb_ep3_out_complete(void) {} +void refresh_can_tx_slots_available(void) {} int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { unsigned int resp_len = 0; diff --git a/python/__init__.py b/python/__init__.py index 0849f0a752..7425ff1f97 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -401,14 +401,16 @@ def spi_list(): return [] def reset(self, enter_bootstub=False, enter_bootloader=False, reconnect=True): + # no response is expected since it resets right away + timeout = 5000 if isinstance(self._handle, PandaSpiHandle) else 15000 try: if enter_bootloader: - self._handle.controlWrite(Panda.REQUEST_IN, 0xd1, 0, 0, b'') + self._handle.controlWrite(Panda.REQUEST_IN, 0xd1, 0, 0, b'', timeout=timeout) else: if enter_bootstub: - self._handle.controlWrite(Panda.REQUEST_IN, 0xd1, 1, 0, b'') + self._handle.controlWrite(Panda.REQUEST_IN, 0xd1, 1, 0, b'', timeout=timeout) else: - self._handle.controlWrite(Panda.REQUEST_IN, 0xd8, 0, 0, b'') + self._handle.controlWrite(Panda.REQUEST_IN, 0xd8, 0, 0, b'', timeout=timeout) except Exception: pass if not enter_bootloader and reconnect: diff --git a/python/spi.py b/python/spi.py index e427bea868..8a0fe5cc96 100644 --- a/python/spi.py +++ b/python/spi.py @@ -120,8 +120,11 @@ def _transfer(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 10 logging.debug("starting transfer: endpoint=%d, max_rx_len=%d", endpoint, max_rx_len) logging.debug("==============================================") + n = 0 + start_time = time.monotonic() exc = PandaSpiException() - for n in range(MAX_XFER_RETRY_COUNT): + while (time.monotonic() - start_time) < timeout*1e-3: + n += 1 logging.debug("\ntry #%d", n+1) try: logging.debug("- send header") @@ -129,16 +132,18 @@ def _transfer(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 10 packet += bytes([reduce(lambda x, y: x^y, packet) ^ CHECKSUM_START]) spi.xfer2(packet) + to = timeout - (time.monotonic() - start_time)*1e3 logging.debug("- waiting for header ACK") - self._wait_for_ack(spi, HACK, timeout, 0x11) + self._wait_for_ack(spi, HACK, int(to), 0x11) # send data logging.debug("- sending data") packet = bytes([*data, self._calc_checksum(data)]) spi.xfer2(packet) + to = timeout - (time.monotonic() - start_time)*1e3 logging.debug("- waiting for data ACK") - self._wait_for_ack(spi, DACK, timeout, 0x13) + self._wait_for_ack(spi, DACK, int(to), 0x13) # get response length, then response response_len_bytes = bytes(spi.xfer2(b"\x00" * 2)) @@ -154,7 +159,7 @@ def _transfer(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 10 return dat[:-1] except PandaSpiException as e: exc = e - logging.debug("SPI transfer failed, %d retries left", MAX_XFER_RETRY_COUNT - n - 1, exc_info=True) + logging.debug("SPI transfer failed, retrying", exc_info=True) raise exc # libusb1 functions diff --git a/tests/hitl/8_spi.py b/tests/hitl/8_spi.py index da3e5641eb..3d8a33b0b6 100644 --- a/tests/hitl/8_spi.py +++ b/tests/hitl/8_spi.py @@ -33,14 +33,14 @@ def test_all_comm_types(self, mocker, p): def test_bad_header(self, mocker, p): with patch('panda.python.spi.SYNC', return_value=0): with pytest.raises(PandaSpiNackResponse): - p.health() + p._handle.controlRead(Panda.REQUEST_IN, 0xd2, 0, 0, p.HEALTH_STRUCT.size, timeout=50) self._ping(mocker, p) def test_bad_checksum(self, mocker, p): cnt = p.health()['spi_checksum_error_count'] with patch('panda.python.spi.PandaSpiHandle._calc_checksum', return_value=0): with pytest.raises(PandaSpiNackResponse): - p.health() + p._handle.controlRead(Panda.REQUEST_IN, 0xd2, 0, 0, p.HEALTH_STRUCT.size, timeout=50) self._ping(mocker, p) assert (p.health()['spi_checksum_error_count'] - cnt) > 0 @@ -48,11 +48,11 @@ def test_non_existent_endpoint(self, mocker, p): for _ in range(10): ep = random.randint(4, 20) with pytest.raises(PandaSpiNackResponse): - p._handle.bulkRead(ep, random.randint(1, 1000)) + p._handle.bulkRead(ep, random.randint(1, 1000), timeout=50) self._ping(mocker, p) with pytest.raises(PandaSpiNackResponse): - p._handle.bulkWrite(ep, b"abc") + p._handle.bulkWrite(ep, b"abc", timeout=50) self._ping(mocker, p) diff --git a/tests/libpanda/panda.c b/tests/libpanda/panda.c index 7f1ecdb133..8efb6de4d5 100644 --- a/tests/libpanda/panda.c +++ b/tests/libpanda/panda.c @@ -8,8 +8,9 @@ void process_can(uint8_t can_number) { } //int safety_tx_hook(CANPacket_t *to_send) { return 1; } typedef struct harness_configuration harness_configuration; -void usb_cb_ep3_out_complete(void); -void usb_outep3_resume_if_paused(void) { }; +void refresh_can_tx_slots_available(void); +void can_tx_comms_resume_usb(void) { }; +void can_tx_comms_resume_spi(void) { }; #include "health.h" #include "faults.h" From c898ec7ce8b7d9a5656b18de810f7b80e8ca15d4 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 20 May 2023 22:00:13 -0700 Subject: [PATCH 067/197] refresh can tx slots available after write (#1432) Co-authored-by: Comma Device --- board/can_comms.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/board/can_comms.h b/board/can_comms.h index 93c32c71d4..980d12ba8d 100644 --- a/board/can_comms.h +++ b/board/can_comms.h @@ -100,6 +100,8 @@ void comms_can_write(uint8_t *data, uint32_t len) { pos += can_write_buffer.ptr; } } + + refresh_can_tx_slots_available(); } void comms_can_reset(void) { From 4d7abcc2e6152e203cfa7cb3f3c2538fb881ace7 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 21 May 2023 20:05:21 -0700 Subject: [PATCH 068/197] PandaDFU: don't reset in program_bootstub (#1433) --- python/dfu.py | 3 +-- tests/hitl/1_program.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/dfu.py b/python/dfu.py index f724bf15d9..f1d20842ba 100644 --- a/python/dfu.py +++ b/python/dfu.py @@ -110,10 +110,9 @@ def program_bootstub(self, code_bootstub): self._handle.erase_bootstub() self._handle.erase_app() self._handle.program(self._mcu_type.config.bootstub_address, code_bootstub) - self.reset() def recover(self): with open(self._mcu_type.config.bootstub_path, "rb") as f: code = f.read() self.program_bootstub(code) - + self.reset() diff --git a/tests/hitl/1_program.py b/tests/hitl/1_program.py index eb0c6a5af3..3070a8c34c 100644 --- a/tests/hitl/1_program.py +++ b/tests/hitl/1_program.py @@ -41,6 +41,7 @@ def test_a_known_bootstub(p): code = f.read() dfu.program_bootstub(code) + dfu.reset() p.connect(claim=False, wait=True) From ab18f5aa994c78c178d4b41ab68a3b021a8daee4 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 21 May 2023 20:30:13 -0700 Subject: [PATCH 069/197] update release cert path --- release/make_release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release/make_release.sh b/release/make_release.sh index ce046b8868..150c510736 100755 --- a/release/make_release.sh +++ b/release/make_release.sh @@ -2,7 +2,7 @@ set -e DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" -export CERT=$DIR/../../xx/pandaextra/certs/release +export CERT=/home/batman/xx/pandaextra/certs/release if [ ! -f "$CERT" ]; then echo "No release cert found, cannot build release." From a8f851870d8fd551aa5d069277e5d2384deaf198 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 21 May 2023 20:31:01 -0700 Subject: [PATCH 070/197] ci: bump up safety test timeout --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 81712bbc93..20044cba4a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -74,7 +74,7 @@ jobs: - name: Build Docker image run: eval "$BUILD" - name: Run safety tests - timeout-minutes: 3 + timeout-minutes: 4 run: | ${{ env.RUN }} "cd .. && \ scons -c && \ From 4dd2735e38b63940ca1e1f42b4522ca4c1a0a1cc Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 21 May 2023 21:19:19 -0700 Subject: [PATCH 071/197] cleanup fw filename conventions (#1434) * cleanup fn * import os * fix path --- python/__init__.py | 7 ++++--- python/constants.py | 14 +++++++------- python/dfu.py | 6 ++++-- release/make_release.sh | 5 +---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/python/__init__.py b/python/__init__.py index 7425ff1f97..f5ef57312b 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -14,7 +14,7 @@ from itertools import accumulate from .base import BaseHandle -from .constants import McuType +from .constants import FW_PATH, McuType from .dfu import PandaDFU from .isotp import isotp_send, isotp_recv from .spi import PandaSpiHandle, PandaSpiException @@ -485,7 +485,7 @@ def flash_static(handle, code, mcu_type): def flash(self, fn=None, code=None, reconnect=True): if not fn: - fn = self._mcu_type.config.app_path + fn = os.path.join(FW_PATH, self._mcu_type.config.app_fn) assert os.path.isfile(fn) logging.debug("flash: main version is %s", self.get_version()) if not self.bootstub: @@ -536,7 +536,8 @@ def wait_for_dfu(dfu_serial: str, timeout: Optional[int] = None) -> bool: def up_to_date(self) -> bool: current = self.get_signature() - expected = Panda.get_signature_from_firmware(self.get_mcu_type().config.app_path) + fn = os.path.join(FW_PATH, self.get_mcu_type().config.app_fn) + expected = Panda.get_signature_from_firmware(fn) return (current == expected) def call_control_api(self, msg): diff --git a/python/constants.py b/python/constants.py index c55fd2c9b8..4c3e778ad1 100644 --- a/python/constants.py +++ b/python/constants.py @@ -3,7 +3,7 @@ from typing import List, NamedTuple BASEDIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../") - +FW_PATH = os.path.join(BASEDIR, "board/obj/") class McuConfig(NamedTuple): mcu: str @@ -13,9 +13,9 @@ class McuConfig(NamedTuple): sector_sizes: List[int] serial_number_address: int app_address: int - app_path: str + app_fn: str bootstub_address: int - bootstub_path: str + bootstub_fn: str Fx = ( 0x1FFF7A10, @@ -23,9 +23,9 @@ class McuConfig(NamedTuple): [0x4000 for _ in range(4)] + [0x10000] + [0x20000 for _ in range(11)], 0x1FFF79C0, 0x8004000, - os.path.join(BASEDIR, "board", "obj", "panda.bin.signed"), + "panda.bin.signed", 0x8000000, - os.path.join(BASEDIR, "board", "obj", "bootstub.panda.bin"), + "bootstub.panda.bin", ) F2Config = McuConfig("STM32F2", 0x411, *Fx) F4Config = McuConfig("STM32F4", 0x463, *Fx) @@ -39,9 +39,9 @@ class McuConfig(NamedTuple): [0x20000 for _ in range(7)], 0x080FFFC0, 0x8020000, - os.path.join(BASEDIR, "board", "obj", "panda_h7.bin.signed"), + "panda_h7.bin.signed", 0x8000000, - os.path.join(BASEDIR, "board", "obj", "bootstub.panda_h7.bin"), + "bootstub.panda_h7.bin", ) @enum.unique diff --git a/python/dfu.py b/python/dfu.py index f1d20842ba..bebc243ced 100644 --- a/python/dfu.py +++ b/python/dfu.py @@ -1,3 +1,4 @@ +import os import usb1 import struct import binascii @@ -6,7 +7,7 @@ from .base import BaseSTBootloaderHandle from .spi import STBootloaderSPIHandle, PandaSpiException from .usb import STBootloaderUSBHandle -from .constants import McuType +from .constants import FW_PATH, McuType class PandaDFU: @@ -112,7 +113,8 @@ def program_bootstub(self, code_bootstub): self._handle.program(self._mcu_type.config.bootstub_address, code_bootstub) def recover(self): - with open(self._mcu_type.config.bootstub_path, "rb") as f: + fn = os.path.join(FW_PATH, self._mcu_type.config.bootstub_fn) + with open(fn, "rb") as f: code = f.read() self.program_bootstub(code) self.reset() diff --git a/release/make_release.sh b/release/make_release.sh index 150c510736..5628c4998d 100755 --- a/release/make_release.sh +++ b/release/make_release.sh @@ -19,7 +19,4 @@ rm obj/* scons -u cd obj RELEASE_NAME=$(awk '{print $1}' version) -rm panda.bin panda_h7.bin -mv panda.bin.signed panda.bin -mv panda_h7.bin.signed panda_h7.bin -zip -j ../../release/panda-$RELEASE_NAME.zip version panda.bin bootstub.panda.bin panda_h7.bin bootstub.panda_h7.bin +zip -j ../../release/panda-$RELEASE_NAME.zip version panda.bin.signed bootstub.panda.bin panda_h7.bin.signed bootstub.panda_h7.bin From 2a5058d858b36ff7dc5477119a54f0aab137b266 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 22 May 2023 00:01:54 -0700 Subject: [PATCH 072/197] export FW_PATH --- __init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index 8b43e99380..d3a558f437 100644 --- a/__init__.py +++ b/__init__.py @@ -1,4 +1,4 @@ -from .python.constants import McuType, BASEDIR # noqa: F401 +from .python.constants import McuType, BASEDIR, FW_PATH # noqa: F401 from .python.serial import PandaSerial # noqa: F401 from .python import (Panda, PandaDFU, # noqa: F401 pack_can_buffer, unpack_can_buffer, calculate_checksum, From 2da9b8d17319be80d48402eb73bb67a36c001d30 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 23 May 2023 20:50:09 -0700 Subject: [PATCH 073/197] python: wait on any DFU panda (#1435) --- python/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/python/__init__.py b/python/__init__.py index f5ef57312b..9d4722d5fe 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -525,13 +525,15 @@ def recover(self, timeout: Optional[int] = 60, reset: bool = True) -> bool: return True @staticmethod - def wait_for_dfu(dfu_serial: str, timeout: Optional[int] = None) -> bool: + def wait_for_dfu(dfu_serial: Optional[str], timeout: Optional[int] = None) -> bool: t_start = time.monotonic() - while dfu_serial not in PandaDFU.list(): + dfu_list = PandaDFU.list() + while (dfu_serial is None and len(dfu_list) == 0) or (dfu_serial is not None and dfu_serial not in dfu_list): logging.debug("waiting for DFU...") time.sleep(0.1) if timeout is not None and (time.monotonic() - t_start) > timeout: return False + dfu_list = PandaDFU.list() return True def up_to_date(self) -> bool: From 00c26894876484d2c5a5b63a1c33c28f0f9b15dd Mon Sep 17 00:00:00 2001 From: Bruce Wayne Date: Wed, 24 May 2023 16:23:37 -0700 Subject: [PATCH 074/197] dfu: fix small writes --- python/usb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/usb.py b/python/usb.py index 2e236a9999..1487f101bc 100644 --- a/python/usb.py +++ b/python/usb.py @@ -77,7 +77,7 @@ def program(self, address, dat): self._status() # Program - bs = self._mcu_type.config.block_size + bs = min(len(dat), self._mcu_type.config.block_size) dat += b"\xFF" * ((bs - len(dat)) % bs) for i in range(0, len(dat) // bs): ldat = dat[i * bs:(i + 1) * bs] From 5847d7dbc05df3bc76243b6704b9d98544eb53df Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Thu, 1 Jun 2023 23:10:36 +0200 Subject: [PATCH 075/197] Tesla safety: fix wrong message rate (#1277) * half the freq, double the rate * fix test * fix names --- board/safety/safety_tesla.h | 4 ++-- tests/safety/test_tesla.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/board/safety/safety_tesla.h b/board/safety/safety_tesla.h index 484cb84123..676670aaaf 100644 --- a/board/safety/safety_tesla.h +++ b/board/safety/safety_tesla.h @@ -2,11 +2,11 @@ const SteeringLimits TESLA_STEERING_LIMITS = { .angle_deg_to_can = 10, .angle_rate_up_lookup = { {0., 5., 15.}, - {5., .8, .15} + {10., 1.6, .3} }, .angle_rate_down_lookup = { {0., 5., 15.}, - {5., 3.5, .4} + {10., 7.0, .8} }, }; diff --git a/tests/safety/test_tesla.py b/tests/safety/test_tesla.py index 97cc318463..83e58352bb 100755 --- a/tests/safety/test_tesla.py +++ b/tests/safety/test_tesla.py @@ -72,8 +72,8 @@ class TestTeslaSteeringSafety(TestTeslaSafety, common.AngleSteeringSafetyTest): DEG_TO_CAN = 10 ANGLE_RATE_BP = [0., 5., 15.] - ANGLE_RATE_UP = [5., .8, .15] # windup limit - ANGLE_RATE_DOWN = [5., 3.5, .4] # unwind limit + ANGLE_RATE_UP = [10., 1.6, .3] # windup limit + ANGLE_RATE_DOWN = [10., 7.0, .8] # unwind limit def setUp(self): self.packer = CANPackerPanda("tesla_can") From e1e9bd048dbc45c27b77c8e4449917b4b202f8a2 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 3 Jun 2023 15:22:47 -0700 Subject: [PATCH 076/197] benchmarking script --- tests/benchmark.py | 43 +++++++++++++++++++++++++++++++++++++++++++ tests/hitl/helpers.py | 10 ++++++++++ 2 files changed, 53 insertions(+) create mode 100755 tests/benchmark.py diff --git a/tests/benchmark.py b/tests/benchmark.py new file mode 100755 index 0000000000..c2b0c85c93 --- /dev/null +++ b/tests/benchmark.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +import time +from contextlib import contextmanager + +from panda import Panda, PandaDFU +from panda.tests.hitl.helpers import get_random_can_messages + + +@contextmanager +def print_time(desc): + start = time.perf_counter() + yield + end = time.perf_counter() + print(f"{end - start:.2f}s - {desc}") + + +if __name__ == "__main__": + with print_time("Panda()"): + p = Panda() + + with print_time("PandaDFU.list()"): + PandaDFU.list() + + fxn = [ + 'reset', + 'reconnect', + 'up_to_date', + 'health', + #'flash', + ] + for f in fxn: + with print_time(f"Panda.{f}()"): + getattr(p, f)() + + p.set_can_loopback(True) + + for n in range(6): + msgs = get_random_can_messages(int(10**n)) + with print_time(f"Panda.can_send_many() - {len(msgs)} msgs"): + p.can_send_many(msgs) + + with print_time("Panda.can_recv()"): + m = p.can_recv() diff --git a/tests/hitl/helpers.py b/tests/hitl/helpers.py index 97b0b9607d..ee04784042 100644 --- a/tests/hitl/helpers.py +++ b/tests/hitl/helpers.py @@ -2,6 +2,16 @@ import random +def get_random_can_messages(n): + m = [] + for _ in range(n): + bus = random.randrange(3) + addr = random.randrange(1 << 29) + dat = bytes([random.getrandbits(8) for _ in range(random.randrange(1, 9))]) + m.append([addr, None, dat, bus]) + return m + + def time_many_sends(p, bus, p_recv=None, msg_count=100, two_pandas=False): if p_recv is None: p_recv = p From 17ca4171df12b8b4d96e58c8e742e1e6b41a4a0c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 3 Jun 2023 15:53:16 -0700 Subject: [PATCH 077/197] H7 SPI: disable IRQs only when necessary (#1437) less irq disable Co-authored-by: Comma Device --- board/stm32h7/llspi.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/board/stm32h7/llspi.h b/board/stm32h7/llspi.h index ad1dc4d7bd..c04934f554 100644 --- a/board/stm32h7/llspi.h +++ b/board/stm32h7/llspi.h @@ -51,12 +51,9 @@ void llspi_miso_dma(uint8_t *addr, int len) { // master -> panda DMA finished void DMA2_Stream2_IRQ_Handler(void) { // Clear interrupt flag - ENTER_CRITICAL(); DMA2->LIFCR = DMA_LIFCR_CTCIF2; spi_rx_done(); - - EXIT_CRITICAL(); } // panda -> master DMA finished @@ -71,8 +68,6 @@ void DMA2_Stream3_IRQ_Handler(void) { // panda TX finished void SPI4_IRQ_Handler(void) { - ENTER_CRITICAL(); - // clear flag SPI4->IFCR |= (0x1FFU << 3U); @@ -80,8 +75,6 @@ void SPI4_IRQ_Handler(void) { spi_tx_dma_done = false; spi_tx_done(false); } - - EXIT_CRITICAL(); } From 92ed48ae5c2c0244548d99be96afae876e4eb8bd Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 4 Jun 2023 01:00:17 -0700 Subject: [PATCH 078/197] HITL tests: add timeouts (#1352) * set timeout * closest * more timeout * fan test is slow * retry * break * bump --- requirements.txt | 1 + tests/hitl/1_program.py | 4 ++++ tests/hitl/4_can_loopback.py | 2 ++ tests/hitl/7_internal.py | 1 + tests/hitl/conftest.py | 8 ++++++++ 5 files changed, 16 insertions(+) diff --git a/requirements.txt b/requirements.txt index 1cb0faf384..1cfecd2989 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ hexdump>=3.3 pycryptodome==3.9.8 tqdm>=4.14.0 pytest +pytest-timeouts parameterized requests flake8==3.7.9 diff --git a/tests/hitl/1_program.py b/tests/hitl/1_program.py index 3070a8c34c..d77d466de6 100644 --- a/tests/hitl/1_program.py +++ b/tests/hitl/1_program.py @@ -1,5 +1,6 @@ import os import time +import pytest from panda import Panda, PandaDFU, McuType, BASEDIR @@ -10,6 +11,7 @@ def check_signature(p): # TODO: make more comprehensive bootstub tests and run on a few production ones + current # TODO: also test release-signed app +@pytest.mark.execution_timeout(30) def test_a_known_bootstub(p): """ Test that compiled app can work with known production bootstub @@ -55,10 +57,12 @@ def test_a_known_bootstub(p): check_signature(p) assert not p.bootstub +@pytest.mark.execution_timeout(15) def test_b_recover(p): assert p.recover(timeout=30) check_signature(p) +@pytest.mark.execution_timeout(25) def test_c_flash(p): # test flash from bootstub serial = p._serial diff --git a/tests/hitl/4_can_loopback.py b/tests/hitl/4_can_loopback.py index f0241d2b78..f7e8bb640d 100644 --- a/tests/hitl/4_can_loopback.py +++ b/tests/hitl/4_can_loopback.py @@ -11,6 +11,7 @@ from panda.tests.hitl.helpers import time_many_sends, clear_can_buffers @flaky(max_runs=3, min_passes=1) +@pytest.mark.execution_timeout(35) def test_send_recv(p, panda_jungle): def test(p_send, p_recv): p_send.set_can_loopback(False) @@ -45,6 +46,7 @@ def test(p_send, p_recv): @flaky(max_runs=6, min_passes=1) +@pytest.mark.execution_timeout(30) def test_latency(p, panda_jungle): def test(p_send, p_recv): p_send.set_can_loopback(False) diff --git a/tests/hitl/7_internal.py b/tests/hitl/7_internal.py index 08374cbdbf..dc24bcc96b 100644 --- a/tests/hitl/7_internal.py +++ b/tests/hitl/7_internal.py @@ -8,6 +8,7 @@ pytest.mark.test_panda_types(Panda.INTERNAL_DEVICES) ] +@pytest.mark.execution_timeout(50) def test_fan_controller(p): start_health = p.health() diff --git a/tests/hitl/conftest.py b/tests/hitl/conftest.py index 05fa21ffdb..9bfe75a6c4 100644 --- a/tests/hitl/conftest.py +++ b/tests/hitl/conftest.py @@ -87,6 +87,14 @@ def pytest_configure(config): "markers", "panda_expect_can_error: mark test to ignore CAN health errors" ) +def pytest_collection_modifyitems(items): + for item in items: + if item.get_closest_marker('execution_timeout') is None: + item.add_marker(pytest.mark.execution_timeout(10)) + + item.add_marker(pytest.mark.setup_timeout(18)) + item.add_marker(pytest.mark.teardown_timeout(12)) + def pytest_make_parametrize_id(config, val, argname): if val in _all_pandas: From a29ee424bcd0d4e5de38c02e77aa90cebb1f7393 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 4 Jun 2023 14:54:40 -0700 Subject: [PATCH 079/197] python: remove duplicate enter_bootloader (#1442) --- python/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/python/__init__.py b/python/__init__.py index 9d4722d5fe..a8d89d2766 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -622,12 +622,6 @@ def can_health(self, can_number): # ******************* control ******************* - def enter_bootloader(self): - try: - self._handle.controlWrite(Panda.REQUEST_OUT, 0xd1, 0, 0, b'') - except Exception: - logging.exception("exception while entering bootloader") - def get_version(self): return self._handle.controlRead(Panda.REQUEST_IN, 0xd6, 0, 0, 0x40).decode('utf8') From aa03163a92c09fa69d042e15683eed230e948012 Mon Sep 17 00:00:00 2001 From: vanillagorillaa <31773928+vanillagorillaa@users.noreply.github.com> Date: Sun, 4 Jun 2023 16:06:46 -0700 Subject: [PATCH 080/197] Nissan: use generated DBCs (#1438) * update nissan dbcs * update opendbc ref * Update Dockerfile * Update Dockerfile * fix * try again * need that too * cp --------- Co-authored-by: Adeeb Shihadeh --- .pre-commit-config.yaml | 2 +- Dockerfile | 9 +++++---- tests/safety/test_nissan.py | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 55b89e6075..1d811c7243 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,5 +32,5 @@ repos: - -rn - -sn - -j0 - - --disable=C,R,W0613,W0511,W0212,W0201,W0311,W0106,W0603,W0621,W0703,W1203,W1514,E1136 + - --disable=C,R,W0613,W0511,W0212,W0201,W0311,W0106,W0603,W0621,W0703,W0719,W1203,W1514,E1136 - --generated-members="usb1.*" diff --git a/Dockerfile b/Dockerfile index c885e0bbbf..c28d3f0188 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,6 +33,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ pkg-config \ python \ python-dev \ + qt5-default \ unzip \ wget \ zlib1g-dev \ @@ -50,8 +51,8 @@ RUN curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-instal ENV PATH="/root/.pyenv/bin:/root/.pyenv/shims:${PATH}" ENV PANDA_PATH=/tmp/openpilot/panda -ENV OPENPILOT_REF="ee0dd36a3c775dbd82493c84f4e7272c1eb3fcbd" -ENV OPENDBC_REF="342c0320dd271fb585db3cced397c5122078af85" +ENV OPENPILOT_REF="e276d2a417a5133fb91c93b2ef30df68a7d5f225" +ENV OPENDBC_REF="9ae9fbfe56f79dca66c673a6479751a15ad61780" COPY requirements.txt /tmp/ RUN pyenv install 3.8.10 && \ @@ -69,13 +70,13 @@ RUN cd /tmp && \ cd /tmp/tmppilot && \ git fetch origin $OPENPILOT_REF && \ git checkout $OPENPILOT_REF && \ - git submodule update --init cereal opendbc rednose_repo && \ + git submodule update --init cereal opendbc rednose_repo body && \ git -C opendbc fetch && \ git -C opendbc checkout $OPENDBC_REF && \ git -C opendbc reset --hard HEAD && \ git -C opendbc clean -xfd && \ mkdir /tmp/openpilot && \ - cp -pR SConstruct site_scons/ tools/ selfdrive/ system/ common/ cereal/ opendbc/ rednose/ third_party/ /tmp/openpilot && \ + cp -pR SConstruct site_scons/ tools/ selfdrive/ system/ common/ cereal/ opendbc/ rednose/ third_party/ body/ /tmp/openpilot && \ rm -rf /tmp/openpilot/panda && \ rm -rf /tmp/tmppilot diff --git a/tests/safety/test_nissan.py b/tests/safety/test_nissan.py index 6c0fac1193..80d97f50f6 100755 --- a/tests/safety/test_nissan.py +++ b/tests/safety/test_nissan.py @@ -24,7 +24,7 @@ class TestNissanSafety(common.PandaSafetyTest, common.AngleSteeringSafetyTest): ANGLE_RATE_DOWN = [5., 3.5, .4] # unwind limit def setUp(self): - self.packer = CANPackerPanda("nissan_x_trail_2017") + self.packer = CANPackerPanda("nissan_x_trail_2017_generated") self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_NISSAN, 0) self.safety.init_tests() @@ -81,7 +81,7 @@ def test_acc_buttons(self): class TestNissanLeafSafety(TestNissanSafety): def setUp(self): - self.packer = CANPackerPanda("nissan_leaf_2018") + self.packer = CANPackerPanda("nissan_leaf_2018_generated") self.safety = libpanda_py.libpanda self.safety.set_safety_hooks(Panda.SAFETY_NISSAN, 0) self.safety.init_tests() From a4ed5bb943b3a4e76d23e7729e6127bf4ff335b5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 4 Jun 2023 21:17:44 -0700 Subject: [PATCH 081/197] adjust timeouts (#1444) --- tests/hitl/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/hitl/conftest.py b/tests/hitl/conftest.py index 9bfe75a6c4..148d3730b5 100644 --- a/tests/hitl/conftest.py +++ b/tests/hitl/conftest.py @@ -92,8 +92,8 @@ def pytest_collection_modifyitems(items): if item.get_closest_marker('execution_timeout') is None: item.add_marker(pytest.mark.execution_timeout(10)) - item.add_marker(pytest.mark.setup_timeout(18)) - item.add_marker(pytest.mark.teardown_timeout(12)) + item.add_marker(pytest.mark.setup_timeout(20)) + item.add_marker(pytest.mark.teardown_timeout(20)) def pytest_make_parametrize_id(config, val, argname): From bf2c0071036bae4e0c00c37ae43237fce6063e31 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 6 Jun 2023 18:48:19 -0700 Subject: [PATCH 082/197] H7: fix CAN RX delay (#1446) --- board/drivers/fdcan.h | 132 +++++++++++++++++++++--------------------- 1 file changed, 65 insertions(+), 67 deletions(-) diff --git a/board/drivers/fdcan.h b/board/drivers/fdcan.h index a5b97ba13e..4fe583dfb5 100644 --- a/board/drivers/fdcan.h +++ b/board/drivers/fdcan.h @@ -134,85 +134,83 @@ void can_rx(uint8_t can_number) { FDCAN_GlobalTypeDef *CANx = CANIF_FROM_CAN_NUM(can_number); uint8_t bus_number = BUS_NUM_FROM_CAN_NUM(can_number); - // Rx FIFO 0 new message - if((CANx->IR & FDCAN_IR_RF0N) != 0) { - CANx->IR |= FDCAN_IR_RF0N; - while((CANx->RXF0S & FDCAN_RXF0S_F0FL) != 0) { - can_health[can_number].total_rx_cnt += 1U; - - // can is live - pending_can_live = 1; - - // get the index of the next RX FIFO element (0 to FDCAN_RX_FIFO_0_EL_CNT - 1) - uint8_t rx_fifo_idx = (uint8_t)((CANx->RXF0S >> FDCAN_RXF0S_F0GI_Pos) & 0x3F); - - // Recommended to offset get index by at least +1 if RX FIFO is in overwrite mode and full (datasheet) - if((CANx->RXF0S & FDCAN_RXF0S_F0F) == FDCAN_RXF0S_F0F) { - rx_fifo_idx = ((rx_fifo_idx + 1U) >= FDCAN_RX_FIFO_0_EL_CNT) ? 0U : (rx_fifo_idx + 1U); - can_health[can_number].total_rx_lost_cnt += 1U; // At least one message was lost - } + // Clear all new messages from Rx FIFO 0 + CANx->IR |= FDCAN_IR_RF0N; + while((CANx->RXF0S & FDCAN_RXF0S_F0FL) != 0) { + can_health[can_number].total_rx_cnt += 1U; - uint32_t RxFIFO0SA = FDCAN_START_ADDRESS + (can_number * FDCAN_OFFSET); - CANPacket_t to_push; - canfd_fifo *fifo; + // can is live + pending_can_live = 1; - // getting address - fifo = (canfd_fifo *)(RxFIFO0SA + (rx_fifo_idx * FDCAN_RX_FIFO_0_EL_SIZE)); + // get the index of the next RX FIFO element (0 to FDCAN_RX_FIFO_0_EL_CNT - 1) + uint8_t rx_fifo_idx = (uint8_t)((CANx->RXF0S >> FDCAN_RXF0S_F0GI_Pos) & 0x3F); - to_push.returned = 0U; - to_push.rejected = 0U; - to_push.extended = (fifo->header[0] >> 30) & 0x1U; - to_push.addr = ((to_push.extended != 0U) ? (fifo->header[0] & 0x1FFFFFFFU) : ((fifo->header[0] >> 18) & 0x7FFU)); - to_push.bus = bus_number; - to_push.data_len_code = ((fifo->header[1] >> 16) & 0xFU); + // Recommended to offset get index by at least +1 if RX FIFO is in overwrite mode and full (datasheet) + if((CANx->RXF0S & FDCAN_RXF0S_F0F) == FDCAN_RXF0S_F0F) { + rx_fifo_idx = ((rx_fifo_idx + 1U) >= FDCAN_RX_FIFO_0_EL_CNT) ? 0U : (rx_fifo_idx + 1U); + can_health[can_number].total_rx_lost_cnt += 1U; // At least one message was lost + } - bool canfd_frame = ((fifo->header[1] >> 21) & 0x1U); - bool brs_frame = ((fifo->header[1] >> 20) & 0x1U); + uint32_t RxFIFO0SA = FDCAN_START_ADDRESS + (can_number * FDCAN_OFFSET); + CANPacket_t to_push; + canfd_fifo *fifo; - uint8_t data_len_w = (dlc_to_len[to_push.data_len_code] / 4U); - data_len_w += ((dlc_to_len[to_push.data_len_code] % 4U) > 0U) ? 1U : 0U; - for (unsigned int i = 0; i < data_len_w; i++) { - WORD_TO_BYTE_ARRAY(&to_push.data[i*4U], fifo->data_word[i]); - } - can_set_checksum(&to_push); - - // forwarding (panda only) - int bus_fwd_num = safety_fwd_hook(bus_number, to_push.addr); - if (bus_fwd_num != -1) { - CANPacket_t to_send; - - to_send.returned = 0U; - to_send.rejected = 0U; - to_send.extended = to_push.extended; - to_send.addr = to_push.addr; - to_send.bus = to_push.bus; - to_send.data_len_code = to_push.data_len_code; - (void)memcpy(to_send.data, to_push.data, dlc_to_len[to_push.data_len_code]); - can_set_checksum(&to_send); - - can_send(&to_send, bus_fwd_num, true); - can_health[can_number].total_fwd_cnt += 1U; - } + // getting address + fifo = (canfd_fifo *)(RxFIFO0SA + (rx_fifo_idx * FDCAN_RX_FIFO_0_EL_SIZE)); - safety_rx_invalid += safety_rx_hook(&to_push) ? 0U : 1U; - ignition_can_hook(&to_push); + to_push.returned = 0U; + to_push.rejected = 0U; + to_push.extended = (fifo->header[0] >> 30) & 0x1U; + to_push.addr = ((to_push.extended != 0U) ? (fifo->header[0] & 0x1FFFFFFFU) : ((fifo->header[0] >> 18) & 0x7FFU)); + to_push.bus = bus_number; + to_push.data_len_code = ((fifo->header[1] >> 16) & 0xFU); - current_board->set_led(LED_BLUE, true); - rx_buffer_overflow += can_push(&can_rx_q, &to_push) ? 0U : 1U; + bool canfd_frame = ((fifo->header[1] >> 21) & 0x1U); + bool brs_frame = ((fifo->header[1] >> 20) & 0x1U); - // Enable CAN FD and BRS if CAN FD message was received - if (!(bus_config[can_number].canfd_enabled) && (canfd_frame)) { - bus_config[can_number].canfd_enabled = true; - } - if (!(bus_config[can_number].brs_enabled) && (brs_frame)) { - bus_config[can_number].brs_enabled = true; - } + uint8_t data_len_w = (dlc_to_len[to_push.data_len_code] / 4U); + data_len_w += ((dlc_to_len[to_push.data_len_code] % 4U) > 0U) ? 1U : 0U; + for (unsigned int i = 0; i < data_len_w; i++) { + WORD_TO_BYTE_ARRAY(&to_push.data[i*4U], fifo->data_word[i]); + } + can_set_checksum(&to_push); - // update read index - CANx->RXF0A = rx_fifo_idx; + // forwarding (panda only) + int bus_fwd_num = safety_fwd_hook(bus_number, to_push.addr); + if (bus_fwd_num != -1) { + CANPacket_t to_send; + + to_send.returned = 0U; + to_send.rejected = 0U; + to_send.extended = to_push.extended; + to_send.addr = to_push.addr; + to_send.bus = to_push.bus; + to_send.data_len_code = to_push.data_len_code; + (void)memcpy(to_send.data, to_push.data, dlc_to_len[to_push.data_len_code]); + can_set_checksum(&to_send); + + can_send(&to_send, bus_fwd_num, true); + can_health[can_number].total_fwd_cnt += 1U; } + safety_rx_invalid += safety_rx_hook(&to_push) ? 0U : 1U; + ignition_can_hook(&to_push); + + current_board->set_led(LED_BLUE, true); + rx_buffer_overflow += can_push(&can_rx_q, &to_push) ? 0U : 1U; + + // Enable CAN FD and BRS if CAN FD message was received + if (!(bus_config[can_number].canfd_enabled) && (canfd_frame)) { + bus_config[can_number].canfd_enabled = true; + } + if (!(bus_config[can_number].brs_enabled) && (brs_frame)) { + bus_config[can_number].brs_enabled = true; + } + + // update read index + CANx->RXF0A = rx_fifo_idx; } + // Error handling bool error_irq = ((CANx->IR & (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EW | FDCAN_IR_EP | FDCAN_IR_ELO | FDCAN_IR_BO | FDCAN_IR_TEFL | FDCAN_IR_RF0L)) != 0); update_can_health_pkt(can_number, error_irq); From 1a9a94c519cb74e9e0f57bea4d316a54c27ea8aa Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 7 Jun 2023 19:43:43 -0700 Subject: [PATCH 083/197] python: add helper to wait until panda comes up (#1447) Co-authored-by: Comma Device --- python/__init__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/python/__init__.py b/python/__init__.py index a8d89d2766..d1230d97e2 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -536,6 +536,18 @@ def wait_for_dfu(dfu_serial: Optional[str], timeout: Optional[int] = None) -> bo dfu_list = PandaDFU.list() return True + @staticmethod + def wait_for_panda(serial: Optional[str], timeout: int) -> bool: + t_start = time.monotonic() + serials = Panda.list() + while (serial is None and len(serials) == 0) or (serial is not None and serial not in serials): + logging.debug("waiting for panda...") + time.sleep(0.1) + if timeout is not None and (time.monotonic() - t_start) > timeout: + return False + serials = Panda.list() + return True + def up_to_date(self) -> bool: current = self.get_signature() fn = os.path.join(FW_PATH, self.get_mcu_type().config.app_fn) From f2f596b342c569c19bdd245416e97b7bfd960fea Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 8 Jun 2023 11:17:35 -0700 Subject: [PATCH 084/197] more robust HITL test prep (#1449) --- Jenkinsfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 0a43e1619f..6bf0cb49d9 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -117,7 +117,9 @@ pipeline { stage('prep') { steps { script { - docker_run("reset hardware", 3, "python ./tests/ci_reset_hw.py") + retry (3) { + docker_run("reset hardware", 3, "python ./tests/ci_reset_hw.py") + } } } } From 237f5bd6e4b93531f8f13f3a583524744bbaa586 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 8 Jun 2023 11:31:50 -0700 Subject: [PATCH 085/197] fix debug console spam during IRQ call rate fault (#1448) * fix debug console spam during IRQ call rate fault * flip * fix build --- board/drivers/interrupts.h | 12 ++++++++---- board/faults.h | 18 ++++++++++-------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/board/drivers/interrupts.h b/board/drivers/interrupts.h index ad1ec5c020..c03d40d56c 100644 --- a/board/drivers/interrupts.h +++ b/board/drivers/interrupts.h @@ -46,8 +46,7 @@ void handle_interrupt(IRQn_Type irq_type){ interrupts[irq_type].handler(); // Check that the interrupts don't fire too often - if(check_interrupt_rate && (interrupts[irq_type].call_counter > interrupts[irq_type].max_call_rate)){ - print("Interrupt 0x"); puth(irq_type); print(" fired too often (0x"); puth(interrupts[irq_type].call_counter); print("/s)!\n"); + if (check_interrupt_rate && (interrupts[irq_type].call_counter > interrupts[irq_type].max_call_rate)) { fault_occurred(interrupts[irq_type].call_rate_fault); } @@ -64,8 +63,13 @@ void handle_interrupt(IRQn_Type irq_type){ // Every second void interrupt_timer_handler(void) { if (INTERRUPT_TIMER->SR != 0) { - // Reset interrupt counters - for(uint16_t i=0U; i interrupts[i].max_call_rate)) { + print("Interrupt 0x"); puth(i); print(" fired too often (0x"); puth(interrupts[i].call_counter); print("/s)!\n"); + } + + // Reset interrupt counters interrupts[i].call_counter = 0U; } diff --git a/board/faults.h b/board/faults.h index 294ac1176f..6c6bef475d 100644 --- a/board/faults.h +++ b/board/faults.h @@ -38,18 +38,20 @@ uint8_t fault_status = FAULT_STATUS_NONE; uint32_t faults = 0U; void fault_occurred(uint32_t fault) { - faults |= fault; - if((PERMANENT_FAULTS & fault) != 0U){ - print("Permanent fault occurred: 0x"); puth(fault); print("\n"); - fault_status = FAULT_STATUS_PERMANENT; - } else { - print("Temporary fault occurred: 0x"); puth(fault); print("\n"); - fault_status = FAULT_STATUS_TEMPORARY; + if ((faults & fault) == 0U) { + if ((PERMANENT_FAULTS & fault) != 0U) { + print("Permanent fault occurred: 0x"); puth(fault); print("\n"); + fault_status = FAULT_STATUS_PERMANENT; + } else { + print("Temporary fault occurred: 0x"); puth(fault); print("\n"); + fault_status = FAULT_STATUS_TEMPORARY; + } } + faults |= fault; } void fault_recovered(uint32_t fault) { - if((PERMANENT_FAULTS & fault) == 0U){ + if ((PERMANENT_FAULTS & fault) == 0U) { faults &= ~fault; } else { print("Cannot recover from a permanent fault!\n"); From b56340590485bba2428538259e020f176127458c Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Fri, 9 Jun 2023 16:25:49 +0200 Subject: [PATCH 086/197] Fix multiple resets due to not getting an ACK on reset (#1450) * add send_once * cleaner * add debug log * also in the USB one --------- Co-authored-by: Comma Device --- python/__init__.py | 8 ++++---- python/spi.py | 45 +++++++++++++++++++++++++-------------------- python/usb.py | 2 +- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/python/__init__.py b/python/__init__.py index d1230d97e2..68f01439a2 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -405,12 +405,12 @@ def reset(self, enter_bootstub=False, enter_bootloader=False, reconnect=True): timeout = 5000 if isinstance(self._handle, PandaSpiHandle) else 15000 try: if enter_bootloader: - self._handle.controlWrite(Panda.REQUEST_IN, 0xd1, 0, 0, b'', timeout=timeout) + self._handle.controlWrite(Panda.REQUEST_IN, 0xd1, 0, 0, b'', timeout=timeout, expect_disconnect=True) else: if enter_bootstub: - self._handle.controlWrite(Panda.REQUEST_IN, 0xd1, 1, 0, b'', timeout=timeout) + self._handle.controlWrite(Panda.REQUEST_IN, 0xd1, 1, 0, b'', timeout=timeout, expect_disconnect=True) else: - self._handle.controlWrite(Panda.REQUEST_IN, 0xd8, 0, 0, b'', timeout=timeout) + self._handle.controlWrite(Panda.REQUEST_IN, 0xd8, 0, 0, b'', timeout=timeout, expect_disconnect=True) except Exception: pass if not enter_bootloader and reconnect: @@ -479,7 +479,7 @@ def flash_static(handle, code, mcu_type): # reset logging.warning("flash: resetting") try: - handle.controlWrite(Panda.REQUEST_IN, 0xd8, 0, 0, b'') + handle.controlWrite(Panda.REQUEST_IN, 0xd8, 0, 0, b'', expect_disconnect=True) except Exception: pass diff --git a/python/spi.py b/python/spi.py index 8a0fe5cc96..22d257b295 100644 --- a/python/spi.py +++ b/python/spi.py @@ -116,7 +116,7 @@ def _wait_for_ack(self, spi, ack_val: int, timeout: int, tx: int) -> None: raise PandaSpiMissingAck - def _transfer(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 1000) -> bytes: + def _transfer(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes: logging.debug("starting transfer: endpoint=%d, max_rx_len=%d", endpoint, max_rx_len) logging.debug("==============================================") @@ -125,7 +125,7 @@ def _transfer(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 10 exc = PandaSpiException() while (time.monotonic() - start_time) < timeout*1e-3: n += 1 - logging.debug("\ntry #%d", n+1) + logging.debug("\ntry #%d", n) try: logging.debug("- send header") packet = struct.pack(" max_rx_len: - raise PandaSpiException("response length greater than max") - - logging.debug("- receiving response") - dat = bytes(spi.xfer2(b"\x00" * (response_len + 1))) - if self._calc_checksum([DACK, *response_len_bytes, *dat]) != 0: - raise PandaSpiBadChecksum - - return dat[:-1] + if expect_disconnect: + logging.debug("- expecting disconnect, returning") + return b"" + else: + to = timeout - (time.monotonic() - start_time)*1e3 + logging.debug("- waiting for data ACK") + self._wait_for_ack(spi, DACK, int(to), 0x13) + + # get response length, then response + response_len_bytes = bytes(spi.xfer2(b"\x00" * 2)) + response_len = struct.unpack(" max_rx_len: + raise PandaSpiException("response length greater than max") + + logging.debug("- receiving response") + dat = bytes(spi.xfer2(b"\x00" * (response_len + 1))) + if self._calc_checksum([DACK, *response_len_bytes, *dat]) != 0: + raise PandaSpiBadChecksum + + return dat[:-1] except PandaSpiException as e: exc = e logging.debug("SPI transfer failed, retrying", 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 = TIMEOUT): + def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = TIMEOUT, expect_disconnect: bool = False): with self.dev.acquire() as spi: - return self._transfer(spi, 0, struct.pack(" Date: Sun, 11 Jun 2023 19:06:46 -0700 Subject: [PATCH 087/197] python: move wait into connect() (#1456) * python: move wait into connect() * cleanup * set that * add that back --------- Co-authored-by: Comma Device --- python/__init__.py | 80 ++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/python/__init__.py b/python/__init__.py index 68f01439a2..ab095c0ba7 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -265,10 +265,14 @@ def close(self): def connect(self, claim=True, wait=False): self.close() - # try USB first, then SPI - self._handle, serial, self.bootstub, bcd = self.usb_connect(self._connect_serial, claim=claim, wait=wait) - if self._handle is None: - self._handle, serial, self.bootstub, bcd = self.spi_connect(self._connect_serial) + self._handle = None + while self._handle is None: + # try USB first, then SPI + self._handle, serial, self.bootstub, bcd = self.usb_connect(self._connect_serial, claim=claim) + if self._handle is None: + self._handle, serial, self.bootstub, bcd = self.spi_connect(self._connect_serial) + if not wait: + break if self._handle is None: raise Exception("failed to connect to panda") @@ -325,46 +329,44 @@ def spi_connect(serial): return handle, spi_serial, bootstub, None @staticmethod - def usb_connect(serial, claim=True, wait=False): + def usb_connect(serial, claim=True): handle, usb_serial, bootstub, bcd = None, None, None, None - while 1: - context = usb1.USBContext() - context.open() - try: - for device in context.getDeviceList(skip_on_error=True): - if device.getVendorID() == 0xbbaa and device.getProductID() in (0xddcc, 0xddee): - try: - this_serial = device.getSerialNumber() - except Exception: - continue - - if serial is None or this_serial == serial: - logging.debug("opening device %s %s", this_serial, hex(device.getProductID())) - - usb_serial = this_serial - bootstub = device.getProductID() == 0xddee - handle = device.open() - if sys.platform not in ("win32", "cygwin", "msys", "darwin"): - handle.setAutoDetachKernelDriver(True) - if claim: - handle.claimInterface(0) - # handle.setInterfaceAltSetting(0, 0) # Issue in USB stack - - # bcdDevice wasn't always set to the hw type, ignore if it's the old constant - this_bcd = device.getbcdDevice() - if this_bcd is not None and this_bcd != 0x2300: - bcd = bytearray([this_bcd >> 8, ]) - - break - except Exception: - logging.exception("USB connect error") - if not wait or handle is not None: - break - context.close() + context = usb1.USBContext() + context.open() + try: + for device in context.getDeviceList(skip_on_error=True): + if device.getVendorID() == 0xbbaa and device.getProductID() in (0xddcc, 0xddee): + try: + this_serial = device.getSerialNumber() + except Exception: + continue + + if serial is None or this_serial == serial: + logging.debug("opening device %s %s", this_serial, hex(device.getProductID())) + + usb_serial = this_serial + bootstub = device.getProductID() == 0xddee + handle = device.open() + if sys.platform not in ("win32", "cygwin", "msys", "darwin"): + handle.setAutoDetachKernelDriver(True) + if claim: + handle.claimInterface(0) + # handle.setInterfaceAltSetting(0, 0) # Issue in USB stack + + # bcdDevice wasn't always set to the hw type, ignore if it's the old constant + this_bcd = device.getbcdDevice() + if this_bcd is not None and this_bcd != 0x2300: + bcd = bytearray([this_bcd >> 8, ]) + + break + except Exception: + logging.exception("USB connect error") usb_handle = None if handle is not None: usb_handle = PandaUsbHandle(handle) + else: + context.close() return usb_handle, usb_serial, bootstub, bcd From adc0c12f7b403e04222799bde4f4ee0673e46160 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 11 Jun 2023 19:56:09 -0700 Subject: [PATCH 088/197] HITL tests: enable flashing tests on tres --- Jenkinsfile | 2 +- tests/hitl/1_program.py | 2 +- tests/hitl/conftest.py | 2 ++ .../hitl/known_bootstub/bootstub.panda_h7.bin | Bin 12812 -> 16824 bytes 4 files changed, 4 insertions(+), 2 deletions(-) mode change 100644 => 100755 tests/hitl/known_bootstub/bootstub.panda_h7.bin diff --git a/Jenkinsfile b/Jenkinsfile index 6bf0cb49d9..bcb70dfb85 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -94,7 +94,7 @@ pipeline { phone_steps("panda-tres", [ ["build", "scons -j4"], ["flash", "cd tests/ && ./ci_reset_internal_hw.py"], - ["test", "cd tests/hitl && HW_TYPES=9 pytest --durations=0 2*.py [5-9]*.py"], + ["test", "cd tests/hitl && HW_TYPES=9 pytest --durations=0 [0-2]*.py [5-9]*.py"], ]) } } diff --git a/tests/hitl/1_program.py b/tests/hitl/1_program.py index d77d466de6..7d957bf427 100644 --- a/tests/hitl/1_program.py +++ b/tests/hitl/1_program.py @@ -57,7 +57,7 @@ def test_a_known_bootstub(p): check_signature(p) assert not p.bootstub -@pytest.mark.execution_timeout(15) +@pytest.mark.execution_timeout(25) def test_b_recover(p): assert p.recover(timeout=30) check_signature(p) diff --git a/tests/hitl/conftest.py b/tests/hitl/conftest.py index 148d3730b5..0ebef62225 100644 --- a/tests/hitl/conftest.py +++ b/tests/hitl/conftest.py @@ -145,6 +145,8 @@ def func_fixture_panda(request, module_panda): if p.bootstub: p.reset() + assert not p.bootstub + # TODO: would be nice to make these common checks in the teardown # show up as failed tests instead of "errors" diff --git a/tests/hitl/known_bootstub/bootstub.panda_h7.bin b/tests/hitl/known_bootstub/bootstub.panda_h7.bin old mode 100644 new mode 100755 index f587637f9276cce6021ce38369ddaa159019e613..a6d856bd2c7006ca022f8bb565fcd841451fe442 GIT binary patch delta 10267 zcmb7q33wD$*7mLH?sR3PLjqYLy#(l_SpoqGi_+8$1akK#k>_DI@OLu+mtq_LKXa4_x{(kzL zI``ajw{!13=iFQM+>#?388;!U{>>c1b^Zq+zxoZxQ1$>lB#v+?00UqYU<@D|FaCW5Jir3L!06uwO)I|rw-~&ofQJDq0jmM)0Pg_~0h$4=0Huz_x2gT(n@rO{=52Wc z4bp}N=wCl?@5F$d@OLIb2rwG=e-i#RZy-HTc;HF-x6=c1{~ym~%5eWvCI5-{f2w#F zH1qr0v~%~JO2_n0HBq4N(6{V1mfbVu&~G#o7*;>^(B7~KOxRF}O`Ixxtu-@S1r>LX zTe>OlDj<4SK|d}D9L(Er@U&Eyr3$E3jKJ(#q|I&B6vfps*>N74t2VYtb$a%ot6(3> zvyQ~7kNT2*7kVVb7>sXnZ6lmtkbx^Q7_T^VC|xteYMm)})@JguIn zS*xo+Si@bNNo83FF(y}6-zuwdeC=dy)=Ml_3S&51#ts}w)xw2vBNHw}M%=lSR)2CZ(MRyI_+e`V|5=_a9%^-YV~fei z*jXm$p~mXN57$;Wne2zFP0keOJnLR(oK4kU(&a%zYvhM(nQ7;m98L*3Pbn@#(OBz)YhGdc}m5x@Tqb(P5tffEYDCJ#V4Ijf-AC4}L&w@mD zWi?|`p=@=dESAWOaXfE^V*cw^=~xSjyR1`wlEpmv7r(2;DXfc1VLF7DqsFl*YlPWR zW0Q_zpp2PoXU!4q&gS(2hE*Fhc3FH$xEwi-Stks;C0ap6b+r2qb=Lj0%*6Yvne@Fj zH9C;Whj+;0uFycYX}db>)*oeYXQ<1sgJzZCd)0h2x<7dKDUJzfnL(p!YPqau2$Q`{ zQ9(_$urS)NSQ&baGIYth@JC4*I#pXV)TSCyS=+BLCY!3Pvifc*YQ})#!fy4TUvblZ z%(>Y%=N;`h*y9Oc)L%!x%N`vpJRS3*oAXB7HO5GTEJny0W0WBr1J{6xHfTW+IR>iG z7-g|HR1$E{QQ2EAa8_CLv=ALz$^pXcS|e-Vys~&Hq&2Dx>`jRuj$s=Swp42jv&Zr@ z|7+xPxAITZd>!j9)p^T{398Uz-mZuAJOf%5{}LRaeh(f&#j^O5l1r(Nm0U`_sN||a z{Y}ZG)M4bRuX~m-Mw(i$NN7QQa*KKs5(J%*MfXvKNy%LbGaOWt!lTpQJ~*&&NU|xe6}|l{ghD8dRl1X+S*)D%?n^F$ymX)O>|UDN`s0oub1i z3bA-bpi@a9VvI0wpse6U7z#C*FCAtyx_>@Osp+HhMiSZ5aK~xstlkzN)opm5mI8WP z7n$3JXFs21DIW$apDmp&{HVKc(yt{LMdEhp)6xahNck5~3#C~hH#TlKrS!LPJ+^;K zkh#L_*x^H{2<-|jh=!C(0zy^nJ?_8yE}#np25!(%1~}>{wW{GqRls3K)xfHw8enoX z3^?f10{8ehV9^&2+~tb^?({_hU-3l&|LltfzTk@i{?RuG_#ZwU@b|u0;BS0`fxq%4 z0RPpO=*Hu;FA2Eamkj)cF9rB>Un=mYzG1*8eZzrI_|kxn`Sid(p8@!Y&j{@GnSh&o z>A-*RjQ~F28wvc5ZxrxbJ~MEGF9Udw&jP&LHyZdA-x%QE`NrzqSa!Z~NbK-U0Di%j z4g9QcK5(6HBJgJ4B;XCcJAmE3$-qzga)6)kO#yz~Hx;8*X9s@JR|vew7YAJG8={xp;yu22B)#e5Y>~@KoPi;K@E6tYXOxxtf_`&XUFbJu`1Cu4WAC&L|>_Z}iN-exkXsmwKNaouQ}LzE#PV5P{|Sa8Ob|jwwlj&E^;QX`Mfc1R_2O^ucdp!4#@*r2@( ztRY=yD|@JEriT77D9S}7&^r9pNv-+=iXf%-DuR?+uLwpOVC|R4VtscO3^3*I z=%HoCpi3@D^t-byN-65ZmUWMU9CcCaAg48?L+*Y>jykX!MUGN)6`q^AnJO@;qmf0s z(qs&%A&MZS7T%(!bTh^nWn6QiqXbzN@0O{B+y=hdK()np69eV!?WPkci?4MLOyqqZ zVM3@ofGZ$T0P6C+B|_xT@ysCM&Y?5h{t)ZnuxIF!C4VR9iItSPu$g1{Av`hAEgcej zW%?x8&eKUq?2?(WSZibB+FdP}uQ_`D0Yq-&q<;Tzk?2;Z0h=t2I3kN9r5k>1cSwzZ zRH{U0_=;Z^lcjEdBtO{AT5tR_x2d*DXYy1Ne3$NC+9g@EA@@u>)xnwDba03KC%@=T&Rmo!dP0>`PN_tR8K$T17 zQl-lA{BB9FH}m&fNqN-VJ`MV@u2%5jKL4FADZF5Mw7G2hkBnk+`0VFWhF`?8j2 zO{0-ABK5YMV@*p>Fn!!~r_rr546~D+h%DsX)bIne}pFGc!FtAgjAN z*nI*QvM~>~CnSzg@=b*%&}j;7EHr{n3K@$_x<07c zXe0)`y|T<55_O@HE^BuG4mXvVoT>J_u6>mzEJIZ&xtQ`dLnk4zFX%Gr4GEw`gvHhy zFR+^oA+a+^^l?S{a+5PJz^F`Rv5w?&#z@^rGFHv>*6EMIq5#w!Yt5O8Lq8n|(ph+7 zP{XX#DG?7Lz-WLPKr%4vuWDO))NFGzl z(At~j-I6-XdMR&0jNNuglNVit(y=B-8NonT1U)!Ug~Shn!oh@icIr{#R6^$DZ76)= znp%$&g6!d$zzm|k-UGj}5i9PqYZ@zE8eyPl(E0?L3=g=`&|^YIVwyY0OO{CHa zAu&5hV=uZQF7%n`aeRsJ{~> zUB`1n;wMTIWrjH99PfjL$c?qr4ur(!K6J3FuAF-7kSHBB6ULed&;v#TCIWH*(*aQc zLfk;q;i%ctR50=ZEUz*!sCq5k9&lGV-rVAl_&6>FI5?FV(2-F&Bys5CL!c->Brbt& z`l(!nQh^tv$WdyZB1fsaZ|2g2Wk|HRgQFGK9ujRBZKQ#!&;St{IfTR>=5Cytq@n5Z!_TbosmfUFa)KOY-4*Y%CEUxjLr()A}=Zi$mP||qGP9?G6(t!<2i+%09wt5EA;44gkb}*UCLK6#=J{mB)^GD z8$$V)j+5g}xSEQm(FYBgqa5aDx7PUe zWi9FMm|-*;W?<0EAnO@_CmsLWz0)13e6n+gst;bxGbabU-MuE~D=2zL(d5F(NSE|Z z21Rpy^=qDYIu7-03|PZ)c90m$JnMCTjW?}0 z49kzZ~|Y+e)J>sBlHM$w`a$YoO&q z0KBC*JC$N&wDBc)(vi|pPjgJwPbV0I=SXM~=KVSKH2tJm z4brRXN%l7B)uCq6KxypALOoqVTqY@-+QL`|%ZLM@z{ zlzIrRF93A7>LQhCUX7aGP^>#gPS?odN3HuHPc5nNWSUtW6t}ht`_lC8S+mSe)mCC5 zft?;lF)@X$GuD;Y{qbjZu*t*xoXdPkUTliZe}*gf{Jw5>Gs(#5_+f2iBQx!X8e$?N z8_KNbNygmv%x2SCCR?_cTV>O^ria#=Y7YJU>7bGTQQ5yW)%a5Xa+$4FSzf<9u{Nhw zW$L=}X)Om?&eZ9?qU))wX;`bm+mePF%QuIvsB62f6x4QtZ*L8NI*FoNlXn7te|h9` z?Un7BdF!Mr{@Qcohnlyqyj8miIkfuMFQ=j8r%FlPH`lKmSE%SS-(OCyjW|zh`U*2- zt!Y^QntwQd#!oqYSKR(u6~dY?E;F?V0`+@W%HkZ^;ym~8?B>07Ma}2elAJ{NR27U; zz?xV<;9VKL=%)vpfm;wlCfZgE-Z+(zQ#Pz5Lg$1Xu65|VCDy6eAZ z(JTI$%9Xfr3N_j}Z&2(*xb9h*h7}-&((q1c5qyg*ru5Jo_Ed0PGHXm$oWvH1`syP+ zaCzM!F~6HdI7B0}5{cdwQ{z*F6{ExXWJ!o#1~toS#5R%O-zBLL@o=D~V1-)CIeU6g zJRU;8j_Vfsc3^&UlpT=<1$upe9wLA*6%q}vs)khT6h1VJV4T9YhV@f8IP4T>?DMMY zq;i+D?OL?yv*ftV7_ec3G*YMB#KG0j%|&0jL^~GL7Nst%7Nkt!QKK=cI<~q06`c-m zb#@ykyMtnGu&zMuFqH+x#eJl;FOB4=>jP=AjqiOo8OoUwOu^1gDPC9|6#o`Xb|e=j zJBRUGZQAy|Hcfk?eS?i{PeR$;zM&55)s40kCw*<$b}dpR*akb|iV|B#m%UOZi{By$ zd%4@&@*5P;_2x@qp|~l+q}rKIrRTOuCkj;D83? zC2th3;nhl%D31(^bNbZA{K7qnL2e=>boaL9Z%-*?@DL`7s zjK|w^z{^?x8y>pJTZQK+$2sRYEsMqH{;7MJYlMR_q}g36`%OM5<^~; zvUJCBs`{>*m|NPHxqSX8V~R3XvDZr_vKUKPht8Ow@Gkf2^o@RcQ+Kbd*B?;w5qDbl zbm$zqBIbMya55%wUT>T+hL)Hj{-rlgUyo~}FT1b7iBExg6??5GbLrF|h~>zI&a~rD zJzX*19e?k;Uyi-^UE_3N=!kn6i}2`(^^8aOWyECWoG@zSFy*5kM8cT>f-G$WSCoDFkJT&1IJ(We}hRoB0N7T z0bAhBQ4TjirLzzvXpj| zr(PKL3})(eNy~3P)Z9r1Wpsp%9`lvf=;YtQTKln^yQ<1{qM4_Ho0>M8_8b0@x>;iK zH=}-2#Ab<|NKB=vyP96HjXs;1zd<7LZo@E5`ZG-=<9J7!iHfnNn$^vIoOD%m(#hy= z+S5#II?Z|C-`vR#(sUU33&TjdEZ!&o7dWaxS0MGT_G~w?Tx=)l6IZwSE%^fE7g$yN zDL)y-rg!)cLwht{qZ<5x&N%`f*w}P-6nr5;oB*TMio>LJ%*AU3RmCRfgH?;2*VU(` z#5x8`nDhyy=Jcfd?I*59Bo$(Kn|j$X*1&U~LjFD|O(nc!HZUc^hvs2gdl7;0$`sC- zlh~S|aKk)&SPovxS3OKJO52%?vUa2MONpDpUe{He3(GjOUl^YeXErb2*J?GT^=#~U znV*B|$Mx=iE^~Rq&k;;f{xeOP74K}SYBo7DDuhQetnR^xvNuYZ`W5~#i@WR5KX8hX z{;B67{|@If>MehH(Ye{Lx;_{N_3N;PGmMeG(Y@I7+D+aoevDiHK`hZ*@vQ~b2ONKy zvMNF6c*AhRL+&{#)!nU}^Kq<~MVO8a3Y7q=tS% zkU|io3@N5t@BKdbgI<`rUO1N!zCu`=(ijwf*DH&w5EGf4(X$pU=lJ>pNaFngJ-5X}2{f0cC&EY5JxN!;{`XpJvN9hIfvk8+_$+hA654(G%?N$=VR}OtoXgAT zpF0R69tw(8Qb>#ohQy!yhC7BAB{_HCK!PtbFx$_{DaEOd5zaR|xvl5e8I0G2RCu#P5aZ!`qCti{TmnAj6Pb>^to3;BrM%BxacJfjmJdLo&a?<_umS?s; z``q)}w(odx=S#o+UHvP&UfuoL>wES#yz%B+Z|{5O-TepN|HHw?#->9bcn^Pgr1_(d zd`DZ39cw*);=~`@+D@MQ_~TDL{q)mQr#}1a^Upv36Mld0|9#Q_yP2XH$p7rKQ>P&P z$;Tg`JlWRv#}g-xx4MrVYdPxs=%eN%A0GC8aHy%V@!%icKd}GZclN#g)|+oM?A`PF zYr9|F^-BHke*4nS7k6ym_WX0tZhdCU)0;PKSnqMKdva~<6RRIz^;peH*NWdf`pEJs z=R?buE~%_2FI)WJ1NSdlxS;gDd+(V)Z|>YVcbCkbJ*&9bQB+iDw-*%f{LGp8`FVMH zZkx@Pi=TDiH)DXXQZ{lSl%GFyCeIfX*zJWyMULX)S+i%C+&yRR+cOkh5y}Bai-Og==NaW2+ut{Y35BC)c?>>o;uLT!eW(53fiFqx=c^ zXB2T`F|SI*S7}lB$P91-@GTZ}cMQJKO2C(CpuY!{1O5*90q_u@3Xq2iPu{8!mC_3F z%P`_2Gt@Ia`u|h+WN;~-cFv6_@y<6xU zZPwngv|{q4ilu^nOd6vStYh@aoQfk#n8>gywI)oy&5-{A+OdmX delta 6350 zcmai23wRV&mcCWhkIIuy2#}-$=?8&8^9Tu$AZVISY&z*+c)0|SArHU`GinGK#SrWu zs}mGaES*Ie6v)i1tFrPiO6&^3CpwNZi5LgW2#hW?k0CY?sP0a>YyVpzjO(zw$@l+t z>eRXSo_p@Cb8hwF&CJ%79zs~t7vl+^V*6*1pZpElQ1zhvaRT8#2Rebj0N(=N0~dfF zfa^dXz$6l055xgOffOJ;k$CtF2sU5}FmWj1X8<`sK9CFDod6xI2-i~JUSJte0o)Hf z0IUNZ1w6nLz|+8X;NO5E3S8+v)zd&hAFo+=HRRj9-|j&f2tI zLsb0z(6{WfEc-{>p$E0u3|lhc&|Y0OqbrA5`2_JBeI~P5N2Ur}(S+10w4tmektXTaP&RH-e#nv79JRts69FO^ST2ohv>rST7RN zQLgI6VbRl~6B;|6{1t|)suLGQTbKm#k?1>D(GiX&5>vEOk>72Pc79f%$cNhVS`vy# zMnXx9aOgnoJ5>$E0+Ul!7b6&jh4!a}p$@L2w6_rhH7HdL%*^hCRdqvKt*Y0|H2YO0 zCw9s%$C=K&HquV}UN5G^7;RLlX{miDy`-XHZ-YCns?O0`>TfJqUA=sbRajMFE8J(YerQ z=3$iBk|MIIftj|dj?3VLXY6caqR&`E>#%I6lW$v^`~3SZLMwRYwUB+DRP1GgO55P9*G^!92dJ{ zCo^7A8#kF<_L#UWb`rB*oE>XqHj2w*2UT}cj}6pYwe-XCi8V=~V8 z*I7*SYW(vaol9?F&3ua@M=N>@XJ%b(=Xtv#bM2%Z*}P0|(YX?w^m!5 z2DZ%5QsyG)VR4Bf4?}cW^CFib_l61jnhz-kI?h}Q=^}I*a|t8{I*qv)(iuZtTbRTl;6dsRoJ4^&+=q?4+SN{7SK;f@B1V1=2k8k_N2NuPcsLa`58{RhYl$+)z^md2u|%7B zNNb^sHtVn$1}w&1M`)-?62Jpk7Lv@Cam8N-PGlAM$+{14MVcsN%W}le5*CYvi3Zld ziccnv5~aii9+z|;j|g7(O%elsOrkkvgT#T?OB(PmB`vsC(t#h6^x$7eJh)nl0sNbq!N6gWpp2WLqp@I=WB9xqwIV0U z$onezeeZ7Y5pO=&>n#Q!_PW6>-V*RZ?_BU(UPFtenV@e!xiPu= zrHv;9MV{attaB2|NxXa6V~maKU~O?7w0Oo8CkhLM`9k8xsA|#vbU{)(BQ6_};-L;x zdqlmR+8J8-62{D$tU`D7jyXEaxHf1N2sGW*^p#0Q>819KzurM~?vCCrcS4w_Ti^95j zODYXiI}HXj2mp;;o^|w5gvov_>j%s>`0;2c1k=te>QZlXS+H*GAo=Z*>ij+rqw)S;Af5ume^pf3P;1 z$GEC07h0<-6?se0?EG9P)Ae`l3TW|skD_tz3g|o#f1_~hu7I8tT+|dQ@{5Y8fG=+g z5VHGvsJ!u@B5zf+FlAx+E42~r{&8hjK$B&^Sdbm>a$M9F#22Hr)m=`Itm1=>YJyrA z)Z*;&JccBerIpEPxPu-p+cBZF$=mv^6(?!|cN;I_CI8bI9`WWO!y8nwpG*Q#M zzYP-AD)IxtLFq|VqDg(4YDcAd)s9L_Zaziohc`9B2}s9OUmap_9fvVp9+=QNrpPPQ zW>mT>XoVpySogwEZ#Kbjs%l4zj$5^((s)%D4{5flqmoJ0(L$6PqJ@Y~VR(>chXqFcU*kX_sn8gZ;K@MSvDYno>ZbsGF$c5sN&{_quiXEYJC23@TzT%tM6b0|ps4AHnxJj}N-rT`kMGl1tbAr8Tu9WFylM4o_* zVt3eaP>aZ8lzexpbBWNT>F-)7Y_E3PhxXb_gPr*ni}?;$L>{hYpFBv5m0Wk4Gga8m z^>-m7&Ch{uu3{BlM$0M1*+tnND3nMxMEWQMDMyvAM{w+4n+e@?WlZr)e+f!NE+f*?gcc&vY7CT<2i2M!CkDPfdq^?Lx@z83{pT{NJLY=JH3S9!7 zLZU^T!pG>}zM#}Usd0Wf9vwl4_H5LqDQUQS7k9)Q1u3AqK@Y43dqlo+M7(G8Qt{2v zC)i(x#V0d9Nuys9gb-5{evo*sgCphN4<}zAj-97gww2?W*kxg{XWa0#7sIr&nn*-` zE=+4~X>X`Pb7cbV9bwTlJ}Gl!*kC^A6JV4E<-=iG8BY01dpl+iXda-N`CUHIKR#0v zk>lIMG@Hp&d{4G;d<(G=|F%X~2^pKwRMqtHCGD`?P28mdZfxUaa@4r*U~)TS!iN)I z)wuBIJLWV$N^B2ykcq_|W7jn|WefVA=k7Rt`HnSTZdq4O@ z9IOANN5Ayd%Q=vJ^wP`C`d2S!)%W%s^VDDIF@%c0diC-o+|E(&9*K;t{|IJ} z^!%>=de2c+H|wk3p4Dh%zGcym&N#E#^~GhT9)-ZPcP<e!N39?bZ!Uj$%u7X0|Jq z6q{w;nk^jv0F$k%G35ghsgSS2sJ10wXdzKfD&h ziH@P}q~hfE3FR-BD{?bZ()B)^1p914vm>T6zJ%b6VjR9^#~j9N6?i8-t{@L9-2SY# z^(n|m0~vHoL|zcmSY{UOPL9a;dO~ArZ0lzf zH`jC*jBtKmvv5swxo!OkoBqU~fdjUY9S3cbI`-S9b;O$T&veY>ZJ`s)-UR`xf6r+A z{i|y%ZpLi983e}qt3YPfD7R-r<(Iye07IS+WEzg* zKr*49WPaAMy5<JI&LQq0#mt#Fa4Ht* zrQlFYoZ7+{9BMl2Q{*xI*N{6mBXa7=*F+YmCo978M-vcIx*JX%-FNDTBl}Jbn<)N% z%D*uAVtC3X=J%o}JD1rhzMDOg*&udh-_4YX#+;dPAB3N)>8jEBhY*x(u_|W?`%R^I zB*!r_E=>3G$)+UNwsD#5+sxN}#LPRd`o6+q92G}S9f55-ZK~Ttx51kZU1yHKn2CC3 z_0LY8vnZf3u3w|bO`#2IG&vh8FZ-yQ)a8j6uFY9nVikT?y+X*=91kQnIocV^gYM0N zEYmfOw(IBEy;Q~*NB=afqy7Gy_ceN)io2EyS9Hl;#x(cALYmaeRpD@ql3Du1~N0RKLE~FV~)Ia5IR8#q^L*XY=$v@D^`2U|z5H ziO=PxW$f#F#m8MwtlBBO>_bjb!z(2alN~?);lF%fsXB907$RQEo#y$xpWDAYpy}JR zUV|^O_af(fd<(H8x)V$2UYpbxk>`aBb0hN15YCRlf4t>k>{9wk!eDuK%*k|yQLkx;v0F>)XyLK>m=O@f5%pe z!1J0G#RME3ND@mrW8rvxH$Sc%Bp$|(1!;uK-KgX_ePjYvw*D#nsDcZTr0E4=O% z$Bh&E*TYaYjYi9m z6c>4s+a8v4`t7Y$!Mzj%O<1<|i*++T@;nRA3IReGnx)B-WWsNXCH%+0$r!?KL|C7R z2ZrJ2YRDd-0@wsR3Ty!G2b$1F=FNU+Pfw%}UW-18{_lR`|7S?ILsGIS%TCCD E1Cj8sN&o-= From 0cc91a7f7b820701ebe6b4fe43e6b11cf6c9be50 Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Tue, 13 Jun 2023 17:00:56 +0200 Subject: [PATCH 089/197] Logging (#1445) * try 1 * some fixes * fix some misra * first poc working * more things * more misra fixes * fix misra * add rate limiting * fix misra * add some unit tests through libpanda * add more tests and fix some stuff * fix misra again * add startup log hitl test * list * don't fail on wrong timestamps * improvements * fix tests * expected logs test? * not sure why this passed * oh, it doesn't reset * only show last few * guess at expected logs * needs this * ugh * reduce compiler warnings * adjust expected logs * this is correct * is it really 1? * min max * reduce spam in SPI test * some cleanup --- .github/workflows/test.yaml | 2 + __init__.py | 2 +- board/drivers/logging.h | 189 ++++++++++++++++++++++++++++ board/drivers/logging_definitions.h | 9 ++ board/drivers/rtc.h | 21 +--- board/drivers/rtc_definitions.h | 9 ++ board/fake_stm.h | 62 +++++++++ board/faults.h | 1 + board/flasher.h | 4 +- board/main.c | 5 + board/main_comms.h | 7 ++ board/stm32fx/llflash.h | 17 ++- board/stm32fx/stm32fx_config.h | 10 +- board/stm32h7/llflash.h | 21 ++-- board/stm32h7/stm32h7_config.h | 10 +- python/__init__.py | 31 ++++- tests/hitl/0_dfu.py | 2 + tests/hitl/1_program.py | 3 + tests/hitl/2_health.py | 16 ++- tests/hitl/8_spi.py | 4 +- tests/hitl/conftest.py | 19 +++ tests/libpanda/libpanda_py.py | 36 ++++++ tests/libpanda/panda.c | 11 ++ tests/logging/test.sh | 4 + tests/logging/test_logging.py | 77 ++++++++++++ 25 files changed, 525 insertions(+), 47 deletions(-) create mode 100644 board/drivers/logging.h create mode 100644 board/drivers/logging_definitions.h create mode 100644 board/drivers/rtc_definitions.h create mode 100755 tests/logging/test.sh create mode 100755 tests/logging/test_logging.py diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 20044cba4a..df7ca75195 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -61,6 +61,8 @@ jobs: run: $RUN "scons -j4" - name: Test communication protocols run: $RUN "cd tests/usbprotocol && ./test.sh" + - name: Test logging + run: $RUN "cd tests/logging && ./test.sh" safety: name: safety diff --git a/__init__.py b/__init__.py index d3a558f437..21e35b9e98 100644 --- a/__init__.py +++ b/__init__.py @@ -1,5 +1,5 @@ from .python.constants import McuType, BASEDIR, FW_PATH # noqa: F401 from .python.serial import PandaSerial # noqa: F401 from .python import (Panda, PandaDFU, # noqa: F401 - pack_can_buffer, unpack_can_buffer, calculate_checksum, + pack_can_buffer, unpack_can_buffer, calculate_checksum, unpack_log, DLC_TO_LEN, LEN_TO_DLC, ALTERNATIVE_EXPERIENCE, USBPACKET_MAX_SIZE, CANPACKET_HEAD_SIZE) diff --git a/board/drivers/logging.h b/board/drivers/logging.h new file mode 100644 index 0000000000..6d21c87c22 --- /dev/null +++ b/board/drivers/logging.h @@ -0,0 +1,189 @@ + +#include "logging_definitions.h" + +#define BANK_SIZE 0x20000U +#define BANK_LOG_CAPACITY (BANK_SIZE / sizeof(log_t)) +#define TOTAL_LOG_CAPACITY (BANK_LOG_CAPACITY * 2U) + +#define LOGGING_MAX_LOGS_PER_MINUTE 10U + +struct logging_state_t { + uint16_t read_index; + uint16_t write_index; + uint16_t last_id; + + uint8_t rate_limit_counter; + uint8_t rate_limit_log_count; +}; +struct logging_state_t log_state = { 0 }; +log_t *log_arr = (log_t *) LOGGING_FLASH_BASE_A; + +uint16_t logging_next_id(uint16_t id) { + return (id + 1U) % 0xFFFEU; +} + +uint16_t logging_next_index(uint16_t index) { + return (index + 1U) % TOTAL_LOG_CAPACITY; +} + +void logging_erase_bank(uint8_t flash_sector) { + print("erasing sector "); puth(flash_sector); print("\n"); + flash_unlock(); + if (!flash_erase_sector(flash_sector)) { + print("failed to erase sector "); puth(flash_sector); print("\n"); + } + flash_lock(); +} + +void logging_erase(void) { + logging_erase_bank(LOGGING_FLASH_SECTOR_A); + logging_erase_bank(LOGGING_FLASH_SECTOR_B); + log_state.read_index = 0U; + log_state.write_index = 0U; +} + +void logging_find_read_index(void) { + // Figure out the read index by the last empty slot + log_state.read_index = 0xFFFFU; + for (uint16_t i = 0U; i < TOTAL_LOG_CAPACITY; i++) { + if (log_arr[i].id == 0xFFFFU) { + log_state.read_index = logging_next_index(i); + } + } +} + +void logging_init(void) { + COMPILE_TIME_ASSERT(sizeof(log_t) == 64U); + COMPILE_TIME_ASSERT((LOGGING_FLASH_BASE_A + BANK_SIZE) == LOGGING_FLASH_BASE_B); + + // Make sure all empty-ID logs are fully empty + log_t empty_log; + (void) memset(&empty_log, 0xFF, sizeof(log_t)); + + for (uint16_t i = 0U; i < TOTAL_LOG_CAPACITY; i++) { + if ((log_arr[i].id == 0xFFFFU) && (memcmp(&log_arr[i], &empty_log, sizeof(log_t)) != 0)) { + logging_erase(); + break; + } + } + + logging_find_read_index(); + + // At initialization, the read index should always be at the beginning of a bank + // If not, clean slate + if ((log_state.read_index != 0U) && (log_state.read_index != BANK_LOG_CAPACITY)) { + logging_erase(); + } + + // Figure out the write index + log_state.write_index = log_state.read_index; + log_state.last_id = log_arr[log_state.write_index].id - 1U; + for (uint16_t i = 0U; i < TOTAL_LOG_CAPACITY; i++) { + bool done = false; + if (log_arr[log_state.write_index].id == 0xFFFFU) { + // Found the first empty slot after the read pointer + done = true; + } else if (log_arr[log_state.write_index].id != logging_next_id(log_state.last_id)) { + // Discontinuity in the index, shouldn't happen! + logging_erase(); + done = true; + } else { + log_state.last_id = log_arr[log_state.write_index].id; + log_state.write_index = logging_next_index(log_state.write_index); + } + + if (done) { + break; + } + } + + // Reset rate limit + log_state.rate_limit_counter = 0U; + log_state.rate_limit_log_count = 0U; +} + +// Call at 1Hz +void logging_tick(void) { + flush_write_buffer(); + + log_state.rate_limit_counter++; + if (log_state.rate_limit_counter >= 60U) { + log_state.rate_limit_counter = 0U; + log_state.rate_limit_log_count = 0U; + } +} + +void log(const char* msg){ + if (log_state.rate_limit_log_count < LOGGING_MAX_LOGS_PER_MINUTE) { + ENTER_CRITICAL(); + log_t new_log = {0}; + new_log.id = logging_next_id(log_state.last_id); + log_state.last_id = new_log.id; + new_log.uptime = uptime_cnt; + if (current_board->has_rtc_battery) { + new_log.timestamp = rtc_get_time(); + } + + uint8_t i = 0U; + for (const char *in = msg; *in; in++) { + new_log.msg[i] = *in; + i++; + if (i >= sizeof(new_log.msg)) { + print("log message too long\n"); + break; + } + } + + // If we are at the beginning of a bank, erase it first and move the read pointer if needed + switch (log_state.write_index) { + case ((2U * BANK_LOG_CAPACITY) - 1U): + logging_erase_bank(LOGGING_FLASH_SECTOR_A); + if ((log_state.read_index < BANK_LOG_CAPACITY)) { + log_state.read_index = BANK_LOG_CAPACITY; + } + break; + case (BANK_LOG_CAPACITY - 1U): + // beginning to write in bank B + logging_erase_bank(LOGGING_FLASH_SECTOR_B); + if ((log_state.read_index > BANK_LOG_CAPACITY)) { + log_state.read_index = 0U; + } + break; + default: + break; + } + + // Write! + void *addr = &log_arr[log_state.write_index]; + uint32_t data[sizeof(log_t) / sizeof(uint32_t)]; + (void) memcpy(data, &new_log, sizeof(log_t)); + + flash_unlock(); + for (uint8_t j = 0U; j < sizeof(log_t) / sizeof(uint32_t); j++) { + flash_write_word(&((uint32_t *) addr)[j], data[j]); + } + flash_lock(); + + // Update the write index + log_state.write_index = logging_next_index(log_state.write_index); + EXIT_CRITICAL(); + + log_state.rate_limit_log_count++; + } else { + fault_occurred(FAULT_LOGGING_RATE_LIMIT); + } +} + +uint8_t logging_read(uint8_t *buffer) { + uint8_t ret = 0U; + if ((log_arr[log_state.read_index].id != 0xFFFFU) && (log_state.read_index != log_state.write_index)) { + // Read the log + (void) memcpy(buffer, &log_arr[log_state.read_index], sizeof(log_t)); + + // Update the read index + log_state.read_index = logging_next_index(log_state.read_index); + + ret = sizeof(log_t); + } + return ret; +} diff --git a/board/drivers/logging_definitions.h b/board/drivers/logging_definitions.h new file mode 100644 index 0000000000..9c1bc02e1c --- /dev/null +++ b/board/drivers/logging_definitions.h @@ -0,0 +1,9 @@ + +// Flash is writable in 32-byte lines, this struct is designed to fit in two lines. +// This also matches the USB transfer size. +typedef struct __attribute__((packed)) log_t { + uint16_t id; + timestamp_t timestamp; + uint32_t uptime; + char msg[50]; +} log_t; diff --git a/board/drivers/rtc.h b/board/drivers/rtc.h index df121e3e89..231d88f2bf 100644 --- a/board/drivers/rtc.h +++ b/board/drivers/rtc.h @@ -1,14 +1,7 @@ -#define YEAR_OFFSET 2000U -typedef struct __attribute__((packed)) timestamp_t { - uint16_t year; - uint8_t month; - uint8_t day; - uint8_t weekday; - uint8_t hour; - uint8_t minute; - uint8_t second; -} timestamp_t; +#include "rtc_definitions.h" + +#define YEAR_OFFSET 2000U uint8_t to_bcd(uint16_t value){ return (((value / 10U) & 0x0FU) << 4U) | ((value % 10U) & 0x0FU); @@ -50,14 +43,6 @@ void rtc_set_time(timestamp_t time){ timestamp_t rtc_get_time(void){ timestamp_t result; - // Init with zero values in case there is no RTC running - result.year = 0U; - result.month = 0U; - result.day = 0U; - result.weekday = 0U; - result.hour = 0U; - result.minute = 0U; - result.second = 0U; // Wait until the register sync flag is set while((RTC->ISR & RTC_ISR_RSF) == 0){} diff --git a/board/drivers/rtc_definitions.h b/board/drivers/rtc_definitions.h new file mode 100644 index 0000000000..d308eb7467 --- /dev/null +++ b/board/drivers/rtc_definitions.h @@ -0,0 +1,9 @@ +typedef struct __attribute__((packed)) timestamp_t { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t weekday; + uint8_t hour; + uint8_t minute; + uint8_t second; +} timestamp_t; diff --git a/board/fake_stm.h b/board/fake_stm.h index b73a4e8985..66baa1d311 100644 --- a/board/fake_stm.h +++ b/board/fake_stm.h @@ -2,8 +2,10 @@ #include #include #include +#include #include "utils.h" +#include "drivers/rtc_definitions.h" #define CANFD #define ALLOW_DEBUG @@ -31,3 +33,63 @@ uint32_t microsecond_timer_get(void); uint32_t microsecond_timer_get(void) { return MICROSECOND_TIMER->CNT; } + +// Register functions +void register_set_bits(volatile uint32_t *addr, uint32_t val) {} + +// RTC +timestamp_t rtc_get_time() { + timestamp_t result; + result.year = 1996; + result.month = 4; + result.day = 23; + result.weekday = 2; + result.hour = 4; + result.minute = 20; + result.second = 20; + return result; +} + +// Logging and flash +uint8_t fake_logging_bank[0x40000] __attribute__ ((aligned (4))); +#define LOGGING_FLASH_BASE_A (&fake_logging_bank[0]) +#define LOGGING_FLASH_BASE_B (&fake_logging_bank[0x20000]) +#define LOGGING_FLASH_SECTOR_A 5 +#define LOGGING_FLASH_SECTOR_B 6 + +bool flash_locked = true; +void flash_unlock(void) { + flash_locked = false; +} +void flash_lock(void) { + flash_locked = true; +} + +void *memset(void *str, int c, unsigned int n); + +bool flash_erase_sector(uint8_t sector) { + if (flash_locked) { + return false; + } + + switch (sector) { + case LOGGING_FLASH_SECTOR_A: + memset(LOGGING_FLASH_BASE_A, 0xFF, sizeof(fake_logging_bank)/2); + return true; + case LOGGING_FLASH_SECTOR_B: + memset(LOGGING_FLASH_BASE_B, 0xFF, sizeof(fake_logging_bank)/2); + return true; + default: + return false; + } +} + +void flash_write_word(void *prog_ptr, uint32_t data) { + if (flash_locked || prog_ptr < (void *) LOGGING_FLASH_BASE_A || prog_ptr >= (void *) (LOGGING_FLASH_BASE_A + sizeof(fake_logging_bank))) { + return; + } + + *(uint32_t *)prog_ptr = data; +} + +void flush_write_buffer(void) {} \ No newline at end of file diff --git a/board/faults.h b/board/faults.h index 6c6bef475d..a741ea1c41 100644 --- a/board/faults.h +++ b/board/faults.h @@ -30,6 +30,7 @@ #define FAULT_INTERRUPT_RATE_UART_7 (1U << 24) #define FAULT_SIREN_MALFUNCTION (1U << 25) #define FAULT_HEARTBEAT_LOOP_WATCHDOG (1U << 26) +#define FAULT_LOGGING_RATE_LIMIT (1U << 27) // Permanent faults #define PERMANENT_FAULTS 0U diff --git a/board/flasher.h b/board/flasher.h index bcd98daf0f..351a899915 100644 --- a/board/flasher.h +++ b/board/flasher.h @@ -1,6 +1,5 @@ // flasher state variables uint32_t *prog_ptr = NULL; -bool unlocked = false; void spi_init(void); @@ -33,13 +32,12 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { resp[1] = 0xff; } current_board->set_led(LED_GREEN, 1); - unlocked = true; prog_ptr = (uint32_t *)APP_START_ADDRESS; break; // **** 0xb2: erase sector case 0xb2: sec = req->param1; - if (flash_erase_sector(sec, unlocked)) { + if (flash_erase_sector(sec)) { resp[1] = 0xff; } break; diff --git a/board/main.c b/board/main.c index b8831c9d15..ad92d7216e 100644 --- a/board/main.c +++ b/board/main.c @@ -6,6 +6,7 @@ #include "drivers/gmlan_alt.h" #include "drivers/kline_init.h" #include "drivers/simple_watchdog.h" +#include "drivers/logging.h" #include "early_init.h" #include "provision.h" @@ -182,6 +183,7 @@ void tick_handler(void) { // tick drivers at 1Hz harness_tick(); + logging_tick(); const bool recent_heartbeat = heartbeat_counter == 0U; current_board->board_tick(check_started(), usb_enumerated, recent_heartbeat, ((harness.status != previous_harness_status) && (harness.status != HARNESS_STATUS_NC))); @@ -328,6 +330,7 @@ int main(void) { peripherals_init(); detect_board_type(); adc_init(); + logging_init(); // print hello print("\n\n\n************************ MAIN START ************************\n"); @@ -347,6 +350,8 @@ int main(void) { // panda has an FPU, let's use it! enable_fpu(); + log("main start"); + if (current_board->has_gps) { uart_init(&uart_ring_gps, 9600); } else { diff --git a/board/main_comms.h b/board/main_comms.h index b58ebd1df2..19e642bc0c 100644 --- a/board/main_comms.h +++ b/board/main_comms.h @@ -478,6 +478,13 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { UNUSED(ret); } break; + // *** 0xfd: read logs + case 0xfd: + if (req->param1 == 1U) { + logging_find_read_index(); + } + resp_len = logging_read(resp); + break; default: print("NO HANDLER "); puth(req->request); diff --git a/board/stm32fx/llflash.h b/board/stm32fx/llflash.h index 61adcd4584..7ae14ca5e6 100644 --- a/board/stm32fx/llflash.h +++ b/board/stm32fx/llflash.h @@ -7,22 +7,27 @@ void flash_unlock(void) { FLASH->KEYR = 0xCDEF89AB; } -bool flash_erase_sector(uint8_t sector, bool unlocked) { +void flash_lock(void) { + FLASH->CR |= FLASH_CR_LOCK; +} + +bool flash_erase_sector(uint16_t sector) { // don't erase the bootloader(sector 0) - if (sector != 0 && sector < 12 && unlocked) { + bool ret = false; + if ((sector != 0U) && (sector < 12U) && (!flash_is_locked())) { FLASH->CR = (sector << 3) | FLASH_CR_SER; FLASH->CR |= FLASH_CR_STRT; - while (FLASH->SR & FLASH_SR_BSY); - return true; + while ((FLASH->SR & FLASH_SR_BSY) != 0U); + ret = true; } - return false; + return ret; } void flash_write_word(void *prog_ptr, uint32_t data) { uint32_t *pp = prog_ptr; FLASH->CR = FLASH_CR_PSIZE_1 | FLASH_CR_PG; *pp = data; - while (FLASH->SR & FLASH_SR_BSY); + while ((FLASH->SR & FLASH_SR_BSY) != 0U); } void flush_write_buffer(void) { } diff --git a/board/stm32fx/stm32fx_config.h b/board/stm32fx/stm32fx_config.h index a73239749f..8bf5496505 100644 --- a/board/stm32fx/stm32fx_config.h +++ b/board/stm32fx/stm32fx_config.h @@ -38,6 +38,11 @@ #define PROVISION_CHUNK_ADDRESS 0x1FFF79E0U #define DEVICE_SERIAL_NUMBER_ADDRESS 0x1FFF79C0U +#define LOGGING_FLASH_SECTOR_A 10U +#define LOGGING_FLASH_SECTOR_B 11U +#define LOGGING_FLASH_BASE_A 0x080C0000U +#define LOGGING_FLASH_BASE_B 0x080E0000U + #include "can_definitions.h" #include "comms_definitions.h" @@ -65,6 +70,7 @@ #include "stm32fx/board.h" #include "stm32fx/clock.h" #include "drivers/watchdog.h" +#include "stm32fx/llflash.h" #if defined(PANDA) || defined(BOOTSTUB) #include "drivers/spi.h" @@ -80,9 +86,7 @@ #include "stm32fx/llexti.h" #endif -#ifdef BOOTSTUB - #include "stm32fx/llflash.h" -#else +#ifndef BOOTSTUB #include "stm32fx/llbxcan.h" #endif diff --git a/board/stm32h7/llflash.h b/board/stm32h7/llflash.h index b95011a9ed..ece69c5c2c 100644 --- a/board/stm32h7/llflash.h +++ b/board/stm32h7/llflash.h @@ -7,27 +7,32 @@ void flash_unlock(void) { FLASH->KEYR1 = 0xCDEF89AB; } -bool flash_erase_sector(uint8_t sector, bool unlocked) { +void flash_lock(void) { + FLASH->CR1 |= FLASH_CR_LOCK; +} + +bool flash_erase_sector(uint16_t sector) { // don't erase the bootloader(sector 0) - if (sector != 0 && sector < 8 && unlocked) { + bool ret = false; + if ((sector != 0U) && (sector < 8U) && (!flash_is_locked())) { FLASH->CR1 = (sector << 8) | FLASH_CR_SER; FLASH->CR1 |= FLASH_CR_START; - while (FLASH->SR1 & FLASH_SR_QW); - return true; + while ((FLASH->SR1 & FLASH_SR_QW) != 0U); + ret = true; } - return false; + return ret; } void flash_write_word(void *prog_ptr, uint32_t data) { uint32_t *pp = prog_ptr; FLASH->CR1 |= FLASH_CR_PG; *pp = data; - while (FLASH->SR1 & FLASH_SR_QW); + while ((FLASH->SR1 & FLASH_SR_QW) != 0U); } void flush_write_buffer(void) { - if (FLASH->SR1 & FLASH_SR_WBNE) { + if ((FLASH->SR1 & FLASH_SR_WBNE) != 0U) { FLASH->CR1 |= FLASH_CR_FW; - while (FLASH->SR1 & FLASH_CR_FW); + while ((FLASH->SR1 & FLASH_CR_FW) != 0U); } } diff --git a/board/stm32h7/stm32h7_config.h b/board/stm32h7/stm32h7_config.h index 41ae768a9f..85e349e301 100644 --- a/board/stm32h7/stm32h7_config.h +++ b/board/stm32h7/stm32h7_config.h @@ -46,6 +46,11 @@ separate IRQs for RX and TX. #define PROVISION_CHUNK_ADDRESS 0x080FFFE0U #define DEVICE_SERIAL_NUMBER_ADDRESS 0x080FFFC0U +#define LOGGING_FLASH_SECTOR_A 5U +#define LOGGING_FLASH_SECTOR_B 6U +#define LOGGING_FLASH_BASE_A 0x080A0000U +#define LOGGING_FLASH_BASE_B 0x080C0000U + #include "can_definitions.h" #include "comms_definitions.h" @@ -67,6 +72,7 @@ separate IRQs for RX and TX. #include "stm32h7/interrupt_handlers.h" #include "drivers/timers.h" #include "drivers/watchdog.h" +#include "stm32h7/llflash.h" #if !defined(BOOTSTUB) && defined(PANDA) #include "drivers/uart.h" @@ -80,9 +86,7 @@ separate IRQs for RX and TX. #include "stm32h7/llexti.h" #endif -#ifdef BOOTSTUB - #include "stm32h7/llflash.h" -#else +#ifndef BOOTSTUB #include "stm32h7/llfdcan.h" #endif diff --git a/python/__init__.py b/python/__init__.py index ab095c0ba7..112857e455 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -122,6 +122,24 @@ def wrapper(self, *args, **kwargs): return fn(self, *args, **kwargs) return wrapper +def parse_timestamp(dat): + a = struct.unpack("HBBBBBB", dat) + if a[0] == 0: + return None + + try: + return datetime.datetime(a[0], a[1], a[2], a[4], a[5], a[6]) + except ValueError: + return None + +def unpack_log(dat): + return { + 'id': struct.unpack("H", dat[:2])[0], + 'timestamp': parse_timestamp(dat[2:10]), + 'uptime': struct.unpack("I", dat[10:14])[0], + 'msg': bytes(dat[14:]).decode('utf-8', 'ignore').strip('\x00'), + } + class ALTERNATIVE_EXPERIENCE: DEFAULT = 0 DISABLE_DISENGAGE_ON_GAS = 1 @@ -941,8 +959,7 @@ def set_datetime(self, dt): def get_datetime(self): dat = self._handle.controlRead(Panda.REQUEST_IN, 0xa0, 0, 0, 8) - a = struct.unpack("HBBBBBB", dat) - return datetime.datetime(a[0], a[1], a[2], a[4], a[5], a[6]) + return parse_timestamp(dat) # ****************** Timer ***************** def get_microsecond_timer(self): @@ -973,3 +990,13 @@ def set_siren(self, enabled): # ****************** Debug ***************** def set_green_led(self, enabled): self._handle.controlWrite(Panda.REQUEST_OUT, 0xf7, int(enabled), 0, b'') + + # ****************** Logging ***************** + def get_logs(self, get_all=False): + logs = [] + dat = self._handle.controlRead(Panda.REQUEST_IN, 0xfd, 1 if get_all else 0, 0, 0x40) + while len(dat) > 0: + if len(dat) == 0x40: + logs.append(unpack_log(dat)) + dat = self._handle.controlRead(Panda.REQUEST_IN, 0xfd, 0, 0, 0x40) + return logs diff --git a/tests/hitl/0_dfu.py b/tests/hitl/0_dfu.py index a8afa24a8a..f3b8ae04aa 100644 --- a/tests/hitl/0_dfu.py +++ b/tests/hitl/0_dfu.py @@ -4,6 +4,7 @@ from panda import Panda, PandaDFU from panda.python.spi import SpiDevice +@pytest.mark.expected_logs(1) def test_dfu(p): app_mcu_type = p.get_mcu_type() dfu_serial = p.get_dfu_serial() @@ -22,6 +23,7 @@ def test_dfu(p): p.reconnect() +@pytest.mark.expected_logs(1) @pytest.mark.test_panda_types((Panda.HW_TYPE_TRES, )) def test_dfu_with_spam(p): dfu_serial = p.get_dfu_serial() diff --git a/tests/hitl/1_program.py b/tests/hitl/1_program.py index 7d957bf427..b051636f5e 100644 --- a/tests/hitl/1_program.py +++ b/tests/hitl/1_program.py @@ -12,6 +12,7 @@ def check_signature(p): # TODO: make more comprehensive bootstub tests and run on a few production ones + current # TODO: also test release-signed app @pytest.mark.execution_timeout(30) +@pytest.mark.expected_logs(1, 2) def test_a_known_bootstub(p): """ Test that compiled app can work with known production bootstub @@ -58,11 +59,13 @@ def test_a_known_bootstub(p): assert not p.bootstub @pytest.mark.execution_timeout(25) +@pytest.mark.expected_logs(1) def test_b_recover(p): assert p.recover(timeout=30) check_signature(p) @pytest.mark.execution_timeout(25) +@pytest.mark.expected_logs(3) def test_c_flash(p): # test flash from bootstub serial = p._serial diff --git a/tests/hitl/2_health.py b/tests/hitl/2_health.py index 5ab6008d2b..1fe0b5a1fb 100644 --- a/tests/hitl/2_health.py +++ b/tests/hitl/2_health.py @@ -5,7 +5,7 @@ from panda_jungle import PandaJungle # pylint: disable=import-error from panda.tests.hitl.conftest import PandaGroup - +@pytest.mark.expected_logs(1) def test_ignition(p, panda_jungle): # Set harness orientation to #2, since the ignition line is on the wrong SBU bus :/ panda_jungle.set_harness_orientation(PandaJungle.HARNESS_ORIENTATION_2) @@ -71,6 +71,7 @@ def test_voltage(p): assert ((voltage > 11000) and (voltage < 13000)) time.sleep(0.1) +@pytest.mark.expected_logs(1) def test_hw_type(p): """ hw type should be same in bootstub as application @@ -123,3 +124,16 @@ def test_microsecond_timer(p): time_diff = (end_time - start_time) / 1e6 assert 0.98 < time_diff < 1.02, f"Timer not running at the correct speed! (got {time_diff:.2f}s instead of 1.0s)" + +@pytest.mark.expected_logs(1) +def test_logging(p): + p.reset() + + logs = p.get_logs(True) + assert len(logs) > 0 + assert len(p.get_logs()) == 0 + + # we assume the start log is relatively recent + start_logs = list(filter(lambda x: x['msg'] == 'main start', logs[-5:])) + assert len(start_logs) > 0 + assert start_logs[0]['uptime'] < 2 diff --git a/tests/hitl/8_spi.py b/tests/hitl/8_spi.py index 3d8a33b0b6..c4a4502e7d 100644 --- a/tests/hitl/8_spi.py +++ b/tests/hitl/8_spi.py @@ -48,11 +48,11 @@ def test_non_existent_endpoint(self, mocker, p): for _ in range(10): ep = random.randint(4, 20) with pytest.raises(PandaSpiNackResponse): - p._handle.bulkRead(ep, random.randint(1, 1000), timeout=50) + p._handle.bulkRead(ep, random.randint(1, 1000), timeout=35) self._ping(mocker, p) with pytest.raises(PandaSpiNackResponse): - p._handle.bulkWrite(ep, b"abc", timeout=50) + p._handle.bulkWrite(ep, b"abc", timeout=35) self._ping(mocker, p) diff --git a/tests/hitl/conftest.py b/tests/hitl/conftest.py index 0ebef62225..dbe33c7ff2 100644 --- a/tests/hitl/conftest.py +++ b/tests/hitl/conftest.py @@ -86,6 +86,9 @@ def pytest_configure(config): config.addinivalue_line( "markers", "panda_expect_can_error: mark test to ignore CAN health errors" ) + config.addinivalue_line( + "markers", "expected_logs(amount, ...): mark test to expect a certain amount of panda logs" + ) def pytest_collection_modifyitems(items): for item in items: @@ -130,6 +133,8 @@ def func_fixture_panda(request, module_panda): # TODO: reset is slow (2+ seconds) p.reset() + logs = p.get_logs() + last_log_id = logs[-1]['id'] if len(logs) > 0 else 0 # Run test yield p @@ -154,6 +159,20 @@ def func_fixture_panda(request, module_panda): assert p.health()['faults'] == 0 assert p.health()['fault_status'] == 0 + # Make sure that there are no unexpected logs + min_expected_logs = 0 + max_expected_logs = 0 + mark = request.node.get_closest_marker('expected_logs') + if mark: + assert len(mark.args) > 0, "Missing expected logs argument in mark" + min_expected_logs = mark.args[0] + max_expected_logs = mark.args[1] if len(mark.args) > 1 else min_expected_logs + + logs.extend(p.get_logs(True)) + log_id = logs[-1]['id'] if len(logs) > 0 else last_log_id + + assert min_expected_logs <= ((log_id - last_log_id) % 0xFFFE) <= max_expected_logs, f"Unexpected amount of logs. Last 5: {logs[-5:]}" + # Check for SPI errors #assert p.health()['spi_checksum_error_count'] == 0 diff --git a/tests/libpanda/libpanda_py.py b/tests/libpanda/libpanda_py.py index 2beec5ccad..b95ceb9fab 100644 --- a/tests/libpanda/libpanda_py.py +++ b/tests/libpanda/libpanda_py.py @@ -54,6 +54,34 @@ uint32_t can_slots_empty(can_ring *q); """) +ffi.cdef(""" + typedef struct timestamp_t { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t weekday; + uint8_t hour; + uint8_t minute; + uint8_t second; + } timestamp_t; + + typedef struct { + uint16_t id; + timestamp_t timestamp; + uint32_t uptime; + char msg[50]; + } log_t; + + extern uint32_t *logging_bank; + extern uint32_t logging_bank_size; + extern uint32_t logging_rate_limit; + + void logging_init(void); + void logging_tick(void); + void log(const char* msg); + uint8_t logging_read(uint8_t *buffer); +""") + setup_safety_helpers(ffi) class CANPacket: @@ -73,6 +101,14 @@ def safety_tx_hook(self, to_push: CANPacket) -> int: ... def safety_fwd_hook(self, bus_num: int, addr: int) -> int: ... def set_safety_hooks(self, mode: int, param: int) -> int: ... + # logging + def logging_init(self) -> None: ... + def logging_tick(self) -> None: ... + def log(self, msg: bytearray) -> None: ... + def logging_read(self, buffer: bytearray) -> int: ... + logging_bank: bytearray + logging_bank_size: int + logging_rate_limit: int libpanda: Panda = ffi.dlopen(libpanda_fn) diff --git a/tests/libpanda/panda.c b/tests/libpanda/panda.c index 8efb6de4d5..050e102ad4 100644 --- a/tests/libpanda/panda.c +++ b/tests/libpanda/panda.c @@ -19,6 +19,13 @@ void can_tx_comms_resume_spi(void) { }; #include "safety.h" #include "main_declarations.h" #include "drivers/can_common.h" +#include "drivers/logging.h" + +// Board +const board fake_board = { + .has_rtc_battery = true, +}; +const board *current_board = &fake_board; can_ring *rx_q = &can_rx_q; can_ring *txgmlan_q = &can_txgmlan_q; @@ -26,6 +33,10 @@ can_ring *tx1_q = &can_tx1_q; can_ring *tx2_q = &can_tx2_q; can_ring *tx3_q = &can_tx3_q; +uint32_t *logging_bank = (uint32_t *) fake_logging_bank; +uint32_t logging_bank_size = sizeof(fake_logging_bank); +uint32_t logging_rate_limit = LOGGING_MAX_LOGS_PER_MINUTE; + #include "comms_definitions.h" #include "can_comms.h" diff --git a/tests/logging/test.sh b/tests/logging/test.sh new file mode 100755 index 0000000000..9e084772f3 --- /dev/null +++ b/tests/logging/test.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -e + +python -m unittest discover . diff --git a/tests/logging/test_logging.py b/tests/logging/test_logging.py new file mode 100755 index 0000000000..dc24f8b2a1 --- /dev/null +++ b/tests/logging/test_logging.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 + +import random +import unittest +import datetime + +from panda import unpack_log +from panda.tests.libpanda import libpanda_py + +lpp = libpanda_py.libpanda + +class TestLogging(unittest.TestCase): + def setUp(self): + # Clear buffer and setup state + for i in range(lpp.logging_bank_size // 4): + lpp.logging_bank[i] = 0xFFFFFFFF + lpp.logging_init() + + def _get_logs(self): + logs = [] + while True: + log = libpanda_py.ffi.new("uint8_t[64]") + if lpp.logging_read(log) == 0: + break + logs.append(log) + return logs + + def _log(self, msg): + lpp.log(libpanda_py.ffi.new("char[]", msg.encode("utf-8"))) + + def test_random_buffer_init(self): + for i in range(lpp.logging_bank_size // 4): + lpp.logging_bank[i] = random.randint(0, 0xFFFFFFFF) + + lpp.logging_init() + + for i in range(lpp.logging_bank_size // 4): + assert lpp.logging_bank[i] == 0xFFFFFFFF + self.assertEqual(lpp.logging_read(libpanda_py.ffi.new("uint8_t[64]")), 0) + + def test_rate_limit(self): + for _ in range(lpp.logging_rate_limit + 5): + self._log("test") + self.assertEqual(len(self._get_logs()), lpp.logging_rate_limit) + self.assertEqual(len(self._get_logs()), 0) + + for _ in range(62): + lpp.logging_tick() + + for _ in range(lpp.logging_rate_limit + 5): + self._log("test") + + self.assertEqual(len(self._get_logs()), lpp.logging_rate_limit) + self.assertEqual(len(self._get_logs()), 0) + + def test_logging(self): + msg = "testing 123" + self._log(msg) + logs = self._get_logs() + self.assertEqual(len(logs), 1) + log = unpack_log(bytes(logs[0])) + self.assertEqual(log['msg'], msg) + self.assertEqual(log['uptime'], 0) + self.assertEqual(log['id'], 1) + self.assertEqual(log['timestamp'], datetime.datetime(1996, 4, 23, 4, 20, 20)) + + def test_bank_overflow(self): + for i in range(1, 10000): + self._log("test") + + if i % 5 == 0: + lpp.logging_init() + expected_messages = (i % 2048) + (2048 if i >= 2048 else 0) + self.assertEqual(len(self._get_logs()), expected_messages) + +if __name__ == "__main__": + unittest.main() \ No newline at end of file From 3a7a0e0bae342b05962bae66be34e4fa03e71f30 Mon Sep 17 00:00:00 2001 From: Igor Biletskyy Date: Tue, 13 Jun 2023 14:34:15 -0700 Subject: [PATCH 090/197] H7: fix lockup on disconnected bus (#1431) * fix h7 lockup * love MISRA! * EW and EP isn't actual errors * cleaner * typo * oops * increase error limit before reset --- board/drivers/bxcan.h | 1 - board/drivers/fdcan.h | 9 +++++++-- board/stm32h7/llfdcan.h | 9 +++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/board/drivers/bxcan.h b/board/drivers/bxcan.h index 15e34d038a..fec58f7863 100644 --- a/board/drivers/bxcan.h +++ b/board/drivers/bxcan.h @@ -74,7 +74,6 @@ void update_can_health_pkt(uint8_t can_number, bool error_irq) { if (error_irq) { can_health[can_number].total_error_cnt += 1U; - CAN->MSR = CAN_MSR_ERRI; llcan_clear_send(CAN); } diff --git a/board/drivers/fdcan.h b/board/drivers/fdcan.h index 4fe583dfb5..15b95f2e17 100644 --- a/board/drivers/fdcan.h +++ b/board/drivers/fdcan.h @@ -64,7 +64,12 @@ void update_can_health_pkt(uint8_t can_number, bool error_irq) { if ((CANx->IR & (FDCAN_IR_TEFL)) != 0) { can_health[can_number].total_tx_lost_cnt += 1U; } - llcan_clear_send(CANx); + // Actually reset can core only on arbitration or data phase errors and when CEL couter reaches at least 100 errors + if (((CANx->IR & (FDCAN_IR_PED | FDCAN_IR_PEA)) != 0) && (((ecr_reg & FDCAN_ECR_CEL) >> FDCAN_ECR_CEL_Pos) == 100U)) { + llcan_clear_send(CANx); + } + // Clear error interrupts + CANx->IR |= (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EP | FDCAN_IR_BO | FDCAN_IR_RF0L); } EXIT_CRITICAL(); } @@ -212,7 +217,7 @@ void can_rx(uint8_t can_number) { } // Error handling - bool error_irq = ((CANx->IR & (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EW | FDCAN_IR_EP | FDCAN_IR_ELO | FDCAN_IR_BO | FDCAN_IR_TEFL | FDCAN_IR_RF0L)) != 0); + bool error_irq = ((CANx->IR & (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EP | FDCAN_IR_BO | FDCAN_IR_RF0L)) != 0); update_can_health_pkt(can_number, error_irq); } diff --git a/board/stm32h7/llfdcan.h b/board/stm32h7/llfdcan.h index e5791fc528..2ed0e1d1f1 100644 --- a/board/stm32h7/llfdcan.h +++ b/board/stm32h7/llfdcan.h @@ -213,7 +213,7 @@ bool llcan_init(FDCAN_GlobalTypeDef *CANx) { CANx->IE &= 0x0U; // Reset all interrupts // Messages for INT0 CANx->IE |= FDCAN_IE_RF0NE; // Rx FIFO 0 new message - CANx->IE |= FDCAN_IE_PEDE | FDCAN_IE_PEAE | FDCAN_IE_BOE | FDCAN_IE_EPE | FDCAN_IE_ELOE | FDCAN_IE_TEFLE | FDCAN_IE_RF0LE; + CANx->IE |= FDCAN_IE_PEDE | FDCAN_IE_PEAE | FDCAN_IE_BOE | FDCAN_IE_EPE | FDCAN_IE_RF0LE; // Messages for INT1 (Only TFE works??) CANx->ILS |= FDCAN_ILS_TFEL; @@ -244,7 +244,8 @@ bool llcan_init(FDCAN_GlobalTypeDef *CANx) { } void llcan_clear_send(FDCAN_GlobalTypeDef *CANx) { - CANx->TXBCR = 0xFFFFU; // Abort message transmission on error interrupt - // Clear error interrupts - CANx->IR |= (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EW | FDCAN_IR_EP | FDCAN_IR_ELO | FDCAN_IR_BO | FDCAN_IR_TEFL | FDCAN_IR_RF0L); + // from datasheet: "Transmit cancellation is not intended for Tx FIFO operation." + // so we need to clear pending transmission manually by resetting FDCAN core + bool ret = llcan_init(CANx); + UNUSED(ret); } From 116485443d6b0f3eba4d171f3d7b06db4fd2cbe3 Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Wed, 14 Jun 2023 13:33:47 +0200 Subject: [PATCH 091/197] UART overwrite mode (#1457) * overwrite mode * typo --- board/drivers/uart.h | 32 +++++++++++++++++++++++--------- board/stm32fx/lluart.h | 6 ++++++ board/stm32h7/lluart.h | 6 ++++++ 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/board/drivers/uart.h b/board/drivers/uart.h index b0fc2ed1be..824e7d7592 100644 --- a/board/drivers/uart.h +++ b/board/drivers/uart.h @@ -16,9 +16,10 @@ typedef struct uart_ring { USART_TypeDef *uart; void (*callback)(struct uart_ring*); bool dma_rx; + bool overwrite; } uart_ring; -#define UART_BUFFER(x, size_rx, size_tx, uart_ptr, callback_ptr, rx_dma) \ +#define UART_BUFFER(x, size_rx, size_tx, uart_ptr, callback_ptr, rx_dma, overwrite_mode) \ uint8_t elems_rx_##x[size_rx]; \ uint8_t elems_tx_##x[size_tx]; \ uart_ring uart_ring_##x = { \ @@ -32,7 +33,8 @@ typedef struct uart_ring { .rx_fifo_size = (size_rx), \ .uart = (uart_ptr), \ .callback = (callback_ptr), \ - .dma_rx = (rx_dma) \ + .dma_rx = (rx_dma), \ + .overwrite = (overwrite_mode) \ }; // ***************************** Function prototypes ***************************** @@ -43,22 +45,22 @@ void uart_send_break(uart_ring *u); // ******************************** UART buffers ******************************** // gps = USART1 -UART_BUFFER(gps, FIFO_SIZE_DMA, FIFO_SIZE_INT, USART1, NULL, true) +UART_BUFFER(gps, FIFO_SIZE_DMA, FIFO_SIZE_INT, USART1, NULL, true, false) // lin1, K-LINE = UART5 // lin2, L-LINE = USART3 -UART_BUFFER(lin1, FIFO_SIZE_INT, FIFO_SIZE_INT, UART5, NULL, false) -UART_BUFFER(lin2, FIFO_SIZE_INT, FIFO_SIZE_INT, USART3, NULL, false) +UART_BUFFER(lin1, FIFO_SIZE_INT, FIFO_SIZE_INT, UART5, NULL, false, false) +UART_BUFFER(lin2, FIFO_SIZE_INT, FIFO_SIZE_INT, USART3, NULL, false, false) // debug = USART2 -UART_BUFFER(debug, FIFO_SIZE_INT, FIFO_SIZE_INT, USART2, debug_ring_callback, false) +UART_BUFFER(debug, FIFO_SIZE_INT, FIFO_SIZE_INT, USART2, debug_ring_callback, false, true) // SOM debug = UART7 #ifdef STM32H7 - UART_BUFFER(som_debug, FIFO_SIZE_INT, FIFO_SIZE_INT, UART7, NULL, false) + UART_BUFFER(som_debug, FIFO_SIZE_INT, FIFO_SIZE_INT, UART7, NULL, false, true) #else // UART7 is not available on F4 - UART_BUFFER(som_debug, 1U, 1U, NULL, NULL, false) + UART_BUFFER(som_debug, 1U, 1U, NULL, NULL, false, true) #endif uart_ring *get_ring_by_number(int a) { @@ -106,7 +108,13 @@ bool injectc(uart_ring *q, char elem) { uint16_t next_w_ptr; ENTER_CRITICAL(); - next_w_ptr = (q->w_ptr_rx + 1U) % q->tx_fifo_size; + next_w_ptr = (q->w_ptr_rx + 1U) % q->rx_fifo_size; + + if ((next_w_ptr == q->r_ptr_rx) && q->overwrite) { + // overwrite mode: drop oldest byte + q->r_ptr_rx = (q->r_ptr_rx + 1U) % q->rx_fifo_size; + } + if (next_w_ptr != q->r_ptr_rx) { q->elems_rx[q->w_ptr_rx] = elem; q->w_ptr_rx = next_w_ptr; @@ -123,6 +131,12 @@ bool putc(uart_ring *q, char elem) { ENTER_CRITICAL(); next_w_ptr = (q->w_ptr_tx + 1U) % q->tx_fifo_size; + + if ((next_w_ptr == q->r_ptr_tx) && q->overwrite) { + // overwrite mode: drop oldest byte + q->r_ptr_tx = (q->r_ptr_tx + 1U) % q->tx_fifo_size; + } + if (next_w_ptr != q->r_ptr_tx) { q->elems_tx[q->w_ptr_tx] = elem; q->w_ptr_tx = next_w_ptr; diff --git a/board/stm32fx/lluart.h b/board/stm32fx/lluart.h index cc5b731349..4cdd337210 100644 --- a/board/stm32fx/lluart.h +++ b/board/stm32fx/lluart.h @@ -29,6 +29,12 @@ void uart_rx_ring(uart_ring *q){ uint8_t c = q->uart->DR; // This read after reading SR clears a bunch of interrupts uint16_t next_w_ptr = (q->w_ptr_rx + 1U) % q->rx_fifo_size; + + if ((next_w_ptr == q->r_ptr_rx) && q->overwrite) { + // overwrite mode: drop oldest byte + q->r_ptr_rx = (q->r_ptr_rx + 1U) % q->rx_fifo_size; + } + // Do not overwrite buffer data if (next_w_ptr != q->r_ptr_rx) { q->elems_rx[q->w_ptr_rx] = c; diff --git a/board/stm32h7/lluart.h b/board/stm32h7/lluart.h index 89e47606c8..6d1a363e27 100644 --- a/board/stm32h7/lluart.h +++ b/board/stm32h7/lluart.h @@ -16,6 +16,12 @@ void uart_rx_ring(uart_ring *q){ uint8_t c = q->uart->RDR; // This read after reading SR clears a bunch of interrupts uint16_t next_w_ptr = (q->w_ptr_rx + 1U) % q->rx_fifo_size; + + if ((next_w_ptr == q->r_ptr_rx) && q->overwrite) { + // overwrite mode: drop oldest byte + q->r_ptr_rx = (q->r_ptr_rx + 1U) % q->rx_fifo_size; + } + // Do not overwrite buffer data if (next_w_ptr != q->r_ptr_rx) { q->elems_rx[q->w_ptr_rx] = c; From 054344de6b4eb0dea5892650312d6f8c8bbbfc25 Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Wed, 14 Jun 2023 15:28:49 +0200 Subject: [PATCH 092/197] Flash bounds checking outside of bootstub (#1459) * add bounds checking on flash operations outside of bootstub * fix test --- board/drivers/logging.h | 2 +- board/fake_stm.h | 1 + board/stm32fx/llflash.h | 29 +++++++++++++++++++++++------ board/stm32fx/stm32fx_config.h | 1 + board/stm32h7/llflash.h | 31 ++++++++++++++++++++++++------- board/stm32h7/stm32h7_config.h | 1 + 6 files changed, 51 insertions(+), 14 deletions(-) diff --git a/board/drivers/logging.h b/board/drivers/logging.h index 6d21c87c22..117de58712 100644 --- a/board/drivers/logging.h +++ b/board/drivers/logging.h @@ -1,7 +1,7 @@ #include "logging_definitions.h" -#define BANK_SIZE 0x20000U +#define BANK_SIZE LOGGING_FLASH_SECTOR_SIZE #define BANK_LOG_CAPACITY (BANK_SIZE / sizeof(log_t)) #define TOTAL_LOG_CAPACITY (BANK_LOG_CAPACITY * 2U) diff --git a/board/fake_stm.h b/board/fake_stm.h index 66baa1d311..e6709c78e0 100644 --- a/board/fake_stm.h +++ b/board/fake_stm.h @@ -56,6 +56,7 @@ uint8_t fake_logging_bank[0x40000] __attribute__ ((aligned (4))); #define LOGGING_FLASH_BASE_B (&fake_logging_bank[0x20000]) #define LOGGING_FLASH_SECTOR_A 5 #define LOGGING_FLASH_SECTOR_B 6 +#define LOGGING_FLASH_SECTOR_SIZE 0x20000U bool flash_locked = true; void flash_unlock(void) { diff --git a/board/stm32fx/llflash.h b/board/stm32fx/llflash.h index 7ae14ca5e6..52f628487f 100644 --- a/board/stm32fx/llflash.h +++ b/board/stm32fx/llflash.h @@ -12,9 +12,17 @@ void flash_lock(void) { } bool flash_erase_sector(uint16_t sector) { +#ifdef BOOTSTUB // don't erase the bootloader(sector 0) + uint16_t min_sector = 1U; + uint16_t max_sector = 11U; +#else + uint16_t min_sector = LOGGING_FLASH_SECTOR_A; + uint16_t max_sector = LOGGING_FLASH_SECTOR_B; +#endif + bool ret = false; - if ((sector != 0U) && (sector < 12U) && (!flash_is_locked())) { + if ((sector >= min_sector) && (sector <= max_sector) && (!flash_is_locked())) { FLASH->CR = (sector << 3) | FLASH_CR_SER; FLASH->CR |= FLASH_CR_STRT; while ((FLASH->SR & FLASH_SR_BSY) != 0U); @@ -23,11 +31,20 @@ bool flash_erase_sector(uint16_t sector) { return ret; } -void flash_write_word(void *prog_ptr, uint32_t data) { - uint32_t *pp = prog_ptr; - FLASH->CR = FLASH_CR_PSIZE_1 | FLASH_CR_PG; - *pp = data; - while ((FLASH->SR & FLASH_SR_BSY) != 0U); +void flash_write_word(uint32_t *prog_ptr, uint32_t data) { + #ifndef BOOTSTUB + // don't write to any region besides the logging region + if ((prog_ptr >= (uint32_t *)LOGGING_FLASH_BASE_A) && (prog_ptr < (uint32_t *)(LOGGING_FLASH_BASE_B + LOGGING_FLASH_SECTOR_SIZE))) { + #endif + + uint32_t *pp = prog_ptr; + FLASH->CR = FLASH_CR_PSIZE_1 | FLASH_CR_PG; + *pp = data; + while ((FLASH->SR & FLASH_SR_BSY) != 0U); + + #ifndef BOOTSTUB + } + #endif } void flush_write_buffer(void) { } diff --git a/board/stm32fx/stm32fx_config.h b/board/stm32fx/stm32fx_config.h index 8bf5496505..1573426da8 100644 --- a/board/stm32fx/stm32fx_config.h +++ b/board/stm32fx/stm32fx_config.h @@ -42,6 +42,7 @@ #define LOGGING_FLASH_SECTOR_B 11U #define LOGGING_FLASH_BASE_A 0x080C0000U #define LOGGING_FLASH_BASE_B 0x080E0000U +#define LOGGING_FLASH_SECTOR_SIZE 0x20000U #include "can_definitions.h" #include "comms_definitions.h" diff --git a/board/stm32h7/llflash.h b/board/stm32h7/llflash.h index ece69c5c2c..800d9414b4 100644 --- a/board/stm32h7/llflash.h +++ b/board/stm32h7/llflash.h @@ -12,9 +12,17 @@ void flash_lock(void) { } bool flash_erase_sector(uint16_t sector) { - // don't erase the bootloader(sector 0) + #ifdef BOOTSTUB + // don't erase the bootloader(sector 0) + uint16_t min_sector = 1U; + uint16_t max_sector = 7U; + #else + uint16_t min_sector = LOGGING_FLASH_SECTOR_A; + uint16_t max_sector = LOGGING_FLASH_SECTOR_B; + #endif + bool ret = false; - if ((sector != 0U) && (sector < 8U) && (!flash_is_locked())) { + if ((sector >= min_sector) && (sector <= max_sector) && (!flash_is_locked())) { FLASH->CR1 = (sector << 8) | FLASH_CR_SER; FLASH->CR1 |= FLASH_CR_START; while ((FLASH->SR1 & FLASH_SR_QW) != 0U); @@ -23,11 +31,20 @@ bool flash_erase_sector(uint16_t sector) { return ret; } -void flash_write_word(void *prog_ptr, uint32_t data) { - uint32_t *pp = prog_ptr; - FLASH->CR1 |= FLASH_CR_PG; - *pp = data; - while ((FLASH->SR1 & FLASH_SR_QW) != 0U); +void flash_write_word(uint32_t *prog_ptr, uint32_t data) { + #ifndef BOOTSTUB + // don't write to any region besides the logging region + if ((prog_ptr >= (uint32_t *)LOGGING_FLASH_BASE_A) && (prog_ptr < (uint32_t *)(LOGGING_FLASH_BASE_B + LOGGING_FLASH_SECTOR_SIZE))) { + #endif + + uint32_t *pp = prog_ptr; + FLASH->CR1 |= FLASH_CR_PG; + *pp = data; + while ((FLASH->SR1 & FLASH_SR_QW) != 0U); + + #ifndef BOOTSTUB + } + #endif } void flush_write_buffer(void) { diff --git a/board/stm32h7/stm32h7_config.h b/board/stm32h7/stm32h7_config.h index 85e349e301..4e6f99bfa6 100644 --- a/board/stm32h7/stm32h7_config.h +++ b/board/stm32h7/stm32h7_config.h @@ -50,6 +50,7 @@ separate IRQs for RX and TX. #define LOGGING_FLASH_SECTOR_B 6U #define LOGGING_FLASH_BASE_A 0x080A0000U #define LOGGING_FLASH_BASE_B 0x080C0000U +#define LOGGING_FLASH_SECTOR_SIZE 0x20000U #include "can_definitions.h" #include "comms_definitions.h" From fc994f5cf9401a11d9f0fe7e1aaac833bb08d233 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 16 Jun 2023 13:59:07 -0700 Subject: [PATCH 093/197] HITL: check FW version before every test (#1463) --- tests/hitl/conftest.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/hitl/conftest.py b/tests/hitl/conftest.py index dbe33c7ff2..850c47fb3e 100644 --- a/tests/hitl/conftest.py +++ b/tests/hitl/conftest.py @@ -136,6 +136,9 @@ def func_fixture_panda(request, module_panda): logs = p.get_logs() last_log_id = logs[-1]['id'] if len(logs) > 0 else 0 + # ensure FW hasn't changed + assert p.up_to_date() + # Run test yield p From c6283759e6a3adbee53b6708172d1a115fb52dd4 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 16 Jun 2023 14:06:32 -0700 Subject: [PATCH 094/197] increase fan test timeout, can take a while to recover --- tests/hitl/7_internal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/hitl/7_internal.py b/tests/hitl/7_internal.py index dc24bcc96b..0a88d1fb2b 100644 --- a/tests/hitl/7_internal.py +++ b/tests/hitl/7_internal.py @@ -8,7 +8,7 @@ pytest.mark.test_panda_types(Panda.INTERNAL_DEVICES) ] -@pytest.mark.execution_timeout(50) +@pytest.mark.execution_timeout(2*60) def test_fan_controller(p): start_health = p.health() From 55ef91d9ab802d4dafcdc2fa14de58e612c02d31 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Sat, 17 Jun 2023 01:11:21 +0100 Subject: [PATCH 095/197] Ford: don't check vehicle roll quality flag (#1461) --- board/safety/safety_ford.h | 2 +- tests/safety/test_ford.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/board/safety/safety_ford.h b/board/safety/safety_ford.h index 93c1e3a187..b25262ca20 100644 --- a/board/safety/safety_ford.h +++ b/board/safety/safety_ford.h @@ -126,7 +126,7 @@ static bool ford_get_quality_flag_valid(CANPacket_t *to_push) { } else if (addr == MSG_EngVehicleSpThrottle2) { valid = ((GET_BYTE(to_push, 4) >> 5) & 0x3U) == 0x3U; // VehVActlEng_D_Qf } else if (addr == MSG_Yaw_Data_FD1) { - valid = (GET_BYTE(to_push, 6) >> 4) == 0xFU; // VehRolWActl_D_Qf & VehYawWActl_D_Qf + valid = ((GET_BYTE(to_push, 6) >> 4) & 0x3U) == 0x3U; // VehYawWActl_D_Qf } else { } return valid; diff --git a/tests/safety/test_ford.py b/tests/safety/test_ford.py index e7e874029f..dfc317fa1d 100755 --- a/tests/safety/test_ford.py +++ b/tests/safety/test_ford.py @@ -131,7 +131,7 @@ def _vehicle_moving_msg(self, speed: float): # Current curvature def _yaw_rate_msg(self, curvature: float, speed: float, quality_flag=True): values = {"VehYaw_W_Actl": curvature * speed, "VehYawWActl_D_Qf": 3 if quality_flag else 0, - "VehRolWActl_D_Qf": 3 if quality_flag else 0, "VehRollYaw_No_Cnt": self.cnt_yaw_rate % 256} + "VehRollYaw_No_Cnt": self.cnt_yaw_rate % 256} self.__class__.cnt_yaw_rate += 1 return self.packer.make_can_msg_panda("Yaw_Data_FD1", 0, values, fix_checksum=checksum) From fdd898bd08f6de2850a3f310553b3e16ac8801c4 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 16 Jun 2023 21:54:28 -0700 Subject: [PATCH 096/197] H7: split SPI/DMA faults (#1466) --- board/stm32h7/llspi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/board/stm32h7/llspi.h b/board/stm32h7/llspi.h index c04934f554..63235c25b0 100644 --- a/board/stm32h7/llspi.h +++ b/board/stm32h7/llspi.h @@ -79,7 +79,7 @@ void SPI4_IRQ_Handler(void) { void llspi_init(void) { - REGISTER_INTERRUPT(SPI4_IRQn, SPI4_IRQ_Handler, SPI_IRQ_RATE, FAULT_INTERRUPT_RATE_SPI_DMA) + REGISTER_INTERRUPT(SPI4_IRQn, SPI4_IRQ_Handler, SPI_IRQ_RATE, FAULT_INTERRUPT_RATE_SPI) REGISTER_INTERRUPT(DMA2_Stream2_IRQn, DMA2_Stream2_IRQ_Handler, SPI_IRQ_RATE, FAULT_INTERRUPT_RATE_SPI_DMA) REGISTER_INTERRUPT(DMA2_Stream3_IRQn, DMA2_Stream3_IRQ_Handler, SPI_IRQ_RATE, FAULT_INTERRUPT_RATE_SPI_DMA) From 6eb3a7324fede0021e8c3d7aadfccae2e976e488 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 16 Jun 2023 21:56:11 -0700 Subject: [PATCH 097/197] tres: enable fan overshoot test (#1465) * tres: enable fan overshoot test * rm --- tests/hitl/7_internal.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/hitl/7_internal.py b/tests/hitl/7_internal.py index 0a88d1fb2b..d4e792721c 100644 --- a/tests/hitl/7_internal.py +++ b/tests/hitl/7_internal.py @@ -47,8 +47,10 @@ def test_fan_cooldown(p): assert p.get_fan_rpm() <= 7000 time.sleep(0.5) -@pytest.mark.skip(reason="fan controller overshoots on fans that need stall recovery") def test_fan_overshoot(p): + if p.get_type() == Panda.HW_TYPE_DOS: + pytest.skip("fan controller overshoots on fans that need stall recovery") + # make sure it's stopped completely p.set_fan_power(0) while p.get_fan_rpm() > 0: From 58b80f3b77fd81fc13c2682b5d51c616f2dcdcc9 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 19 Jun 2023 20:44:02 -0700 Subject: [PATCH 098/197] H7: adjust SPI IRQ call rate (#1469) * H7: adjust SPI IRQ call rate * u --------- Co-authored-by: Comma Device --- board/stm32h7/llspi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/board/stm32h7/llspi.h b/board/stm32h7/llspi.h index 63235c25b0..1947803ac2 100644 --- a/board/stm32h7/llspi.h +++ b/board/stm32h7/llspi.h @@ -79,7 +79,7 @@ void SPI4_IRQ_Handler(void) { void llspi_init(void) { - REGISTER_INTERRUPT(SPI4_IRQn, SPI4_IRQ_Handler, SPI_IRQ_RATE, FAULT_INTERRUPT_RATE_SPI) + REGISTER_INTERRUPT(SPI4_IRQn, SPI4_IRQ_Handler, (SPI_IRQ_RATE * 2U), FAULT_INTERRUPT_RATE_SPI) REGISTER_INTERRUPT(DMA2_Stream2_IRQn, DMA2_Stream2_IRQ_Handler, SPI_IRQ_RATE, FAULT_INTERRUPT_RATE_SPI_DMA) REGISTER_INTERRUPT(DMA2_Stream3_IRQn, DMA2_Stream3_IRQ_Handler, SPI_IRQ_RATE, FAULT_INTERRUPT_RATE_SPI_DMA) From 0f4e8f79f51ebb5267ae274216e3e35f8ce675f1 Mon Sep 17 00:00:00 2001 From: Igor Biletskyy Date: Tue, 20 Jun 2023 11:47:43 -0700 Subject: [PATCH 099/197] add IRQ rate helper function (#1471) init --- board/drivers/interrupts.h | 7 +++++-- board/main_comms.h | 11 +++++++++++ python/__init__.py | 4 ++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/board/drivers/interrupts.h b/board/drivers/interrupts.h index c03d40d56c..9cb46d4b29 100644 --- a/board/drivers/interrupts.h +++ b/board/drivers/interrupts.h @@ -2,6 +2,7 @@ typedef struct interrupt { IRQn_Type irq_type; void (*handler)(void); uint32_t call_counter; + uint32_t call_rate; uint32_t max_call_rate; // Call rate is defined as the amount of calls each second uint32_t call_rate_fault; } interrupt; @@ -17,11 +18,12 @@ void unused_interrupt_handler(void) { interrupt interrupts[NUM_INTERRUPTS]; -#define REGISTER_INTERRUPT(irq_num, func_ptr, call_rate, rate_fault) \ +#define REGISTER_INTERRUPT(irq_num, func_ptr, call_rate_max, rate_fault) \ interrupts[irq_num].irq_type = (irq_num); \ interrupts[irq_num].handler = (func_ptr); \ interrupts[irq_num].call_counter = 0U; \ - interrupts[irq_num].max_call_rate = (call_rate); \ + interrupts[irq_num].call_rate = 0U; \ + interrupts[irq_num].max_call_rate = (call_rate_max); \ interrupts[irq_num].call_rate_fault = (rate_fault); bool check_interrupt_rate = false; @@ -70,6 +72,7 @@ void interrupt_timer_handler(void) { } // Reset interrupt counters + interrupts[i].call_rate = interrupts[i].call_counter; interrupts[i].call_counter = 0U; } diff --git a/board/main_comms.h b/board/main_comms.h index 19e642bc0c..1e47cc6916 100644 --- a/board/main_comms.h +++ b/board/main_comms.h @@ -181,6 +181,17 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { (void)memcpy(resp, ((uint8_t *)UID_BASE), 12); resp_len = 12; break; + case 0xc4: + // **** 0xc4: get interrupt call rate + if (req->param1 < NUM_INTERRUPTS) { + uint32_t load = interrupts[req->param1].call_rate; + resp[0] = (load & 0x000000FFU); + resp[1] = ((load & 0x0000FF00U) >> 8U); + resp[2] = ((load & 0x00FF0000U) >> 16U); + resp[3] = ((load & 0xFF000000U) >> 24U); + resp_len = 4U; + } + break; // **** 0xd0: fetch serial (aka the provisioned dongle ID) case 0xd0: // addresses are OTP diff --git a/python/__init__.py b/python/__init__.py index 112857e455..0a266d4716 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -736,6 +736,10 @@ def get_uid(self): def get_secret(self): return self._handle.controlRead(Panda.REQUEST_IN, 0xd0, 1, 0, 0x10) + def get_interrupt_call_rate(self, irqnum): + dat = self._handle.controlRead(Panda.REQUEST_IN, 0xc4, int(irqnum), 0, 4) + return struct.unpack("I", dat)[0] + # ******************* configuration ******************* def set_power_save(self, power_save_enabled=0): From 9d56e80390f524160c609535047e07bf76cafa52 Mon Sep 17 00:00:00 2001 From: Igor Biletskyy Date: Tue, 20 Jun 2023 12:29:20 -0700 Subject: [PATCH 100/197] Change can_health request method (#1472) * init * fix typo * remove tx lost as fake and add rx lsot --- board/drivers/bxcan.h | 9 ++++----- board/drivers/fdcan.h | 17 +++++++++-------- board/main_comms.h | 1 + 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/board/drivers/bxcan.h b/board/drivers/bxcan.h index fec58f7863..14a4ff2255 100644 --- a/board/drivers/bxcan.h +++ b/board/drivers/bxcan.h @@ -91,14 +91,15 @@ void update_can_health_pkt(uint8_t can_number, bool error_irq) { can_health[can_number].transmit_error_cnt = ((esr_reg & CAN_ESR_TEC) >> CAN_ESR_TEC_Pos); } -// CAN error +// ***************************** CAN ***************************** +// CANx_SCE IRQ Handler void can_sce(uint8_t can_number) { ENTER_CRITICAL(); update_can_health_pkt(can_number, true); EXIT_CRITICAL(); } -// ***************************** CAN ***************************** +// CANx_TX IRQ Handler void process_can(uint8_t can_number) { if (can_number != 0xffU) { @@ -154,12 +155,11 @@ void process_can(uint8_t can_number) { } } - update_can_health_pkt(can_number, false); EXIT_CRITICAL(); } } -// CAN receive handlers +// CANx_RX0 IRQ Handler // blink blue when we are receiving CAN messages void can_rx(uint8_t can_number) { CAN_TypeDef *CAN = CANIF_FROM_CAN_NUM(can_number); @@ -214,7 +214,6 @@ void can_rx(uint8_t can_number) { rx_buffer_overflow += can_push(&can_rx_q, &to_push) ? 0U : 1U; // next - update_can_health_pkt(can_number, false); CAN->RF0R |= CAN_RF0R_RFOM0; } } diff --git a/board/drivers/fdcan.h b/board/drivers/fdcan.h index 15b95f2e17..3cc8b8a395 100644 --- a/board/drivers/fdcan.h +++ b/board/drivers/fdcan.h @@ -32,7 +32,6 @@ void can_set_gmlan(uint8_t bus) { print("GMLAN not available on red panda\n"); } -// ***************************** CAN ***************************** void update_can_health_pkt(uint8_t can_number, bool error_irq) { ENTER_CRITICAL(); @@ -61,11 +60,11 @@ void update_can_health_pkt(uint8_t can_number, bool error_irq) { if (error_irq) { can_health[can_number].total_error_cnt += 1U; - if ((CANx->IR & (FDCAN_IR_TEFL)) != 0) { - can_health[can_number].total_tx_lost_cnt += 1U; + if ((CANx->IR & (FDCAN_IR_RF0L)) != 0) { + can_health[can_number].total_rx_lost_cnt += 1U; } // Actually reset can core only on arbitration or data phase errors and when CEL couter reaches at least 100 errors - if (((CANx->IR & (FDCAN_IR_PED | FDCAN_IR_PEA)) != 0) && (((ecr_reg & FDCAN_ECR_CEL) >> FDCAN_ECR_CEL_Pos) == 100U)) { + if (((CANx->IR & (FDCAN_IR_PED | FDCAN_IR_PEA)) != 0) && (((ecr_reg & FDCAN_ECR_CEL) >> FDCAN_ECR_CEL_Pos) >= 100U)) { llcan_clear_send(CANx); } // Clear error interrupts @@ -74,6 +73,8 @@ void update_can_health_pkt(uint8_t can_number, bool error_irq) { EXIT_CRITICAL(); } +// ***************************** CAN ***************************** +// FDCANx_IT1 IRQ Handler (TX) void process_can(uint8_t can_number) { if (can_number != 0xffU) { ENTER_CRITICAL(); @@ -128,12 +129,11 @@ void process_can(uint8_t can_number) { } } - update_can_health_pkt(can_number, false); EXIT_CRITICAL(); } } -// CAN receive handlers +// FDCANx_IT0 IRQ Handler (RX and errors) // blink blue when we are receiving CAN messages void can_rx(uint8_t can_number) { FDCAN_GlobalTypeDef *CANx = CANIF_FROM_CAN_NUM(can_number); @@ -217,8 +217,9 @@ void can_rx(uint8_t can_number) { } // Error handling - bool error_irq = ((CANx->IR & (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EP | FDCAN_IR_BO | FDCAN_IR_RF0L)) != 0); - update_can_health_pkt(can_number, error_irq); + if ((CANx->IR & (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EP | FDCAN_IR_BO | FDCAN_IR_RF0L | FDCAN_IR_ELO)) != 0) { + update_can_health_pkt(can_number, true); + } } void FDCAN1_IT0_IRQ_Handler(void) { can_rx(0); } diff --git a/board/main_comms.h b/board/main_comms.h index 1e47cc6916..743a4d79cb 100644 --- a/board/main_comms.h +++ b/board/main_comms.h @@ -167,6 +167,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { case 0xc2: COMPILE_TIME_ASSERT(sizeof(can_health_t) <= USBPACKET_MAX_SIZE); if (req->param1 < 3U) { + update_can_health_pkt(req->param1, false); can_health[req->param1].can_speed = (bus_config[req->param1].can_speed / 10U); can_health[req->param1].can_data_speed = (bus_config[req->param1].can_data_speed / 10U); can_health[req->param1].canfd_enabled = bus_config[req->param1].canfd_enabled; From 1c574f8a9986cc5de8e5ddef9d092dea04f54dad Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 20 Jun 2023 19:34:05 -0700 Subject: [PATCH 101/197] HITL tests: reset jungle on init (#1473) Co-authored-by: Comma Device --- tests/hitl/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/hitl/conftest.py b/tests/hitl/conftest.py index 850c47fb3e..d87ec9347d 100644 --- a/tests/hitl/conftest.py +++ b/tests/hitl/conftest.py @@ -67,6 +67,7 @@ def init_all_pandas(): def init_jungle(): if _panda_jungle is None: return + _panda_jungle.reset() clear_can_buffers(_panda_jungle) _panda_jungle.set_panda_power(True) _panda_jungle.set_can_loopback(False) From bcd8c57a702abca52a7d718b6017fddd697e7c62 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 20 Jun 2023 23:17:57 -0700 Subject: [PATCH 102/197] uds: fix TypeError for invalid subfunctions (#1474) fix TypeError --- python/uds.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/uds.py b/python/uds.py index 06d8d82574..f52118c80e 100644 --- a/python/uds.py +++ b/python/uds.py @@ -229,7 +229,7 @@ def __str__(self): class InvalidServiceIdError(Exception): pass -class InvalidSubFunctioneError(Exception): +class InvalidSubFunctionError(Exception): pass _negative_response_codes = { @@ -630,7 +630,7 @@ def _uds_request(self, service_type: SERVICE_TYPE, subfunction: int = None, data resp_sfn = resp[1] if len(resp) > 1 else None if subfunction != resp_sfn: resp_sfn_hex = hex(resp_sfn) if resp_sfn is not None else None - raise InvalidSubFunctioneError(f'invalid response subfunction: {resp_sfn_hex:x}') + raise InvalidSubFunctionError(f'invalid response subfunction: {resp_sfn_hex}') # return data (exclude service id and sub-function id) return resp[(1 if subfunction is None else 2):] From 01887e03ba5f16032326d4afd6335a4f04b1b408 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 21 Jun 2023 15:18:37 -0700 Subject: [PATCH 103/197] HITL: temp disable tres dfu tests --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index bcb70dfb85..e70a542ca8 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -94,7 +94,7 @@ pipeline { phone_steps("panda-tres", [ ["build", "scons -j4"], ["flash", "cd tests/ && ./ci_reset_internal_hw.py"], - ["test", "cd tests/hitl && HW_TYPES=9 pytest --durations=0 [0-2]*.py [5-9]*.py"], + ["test", "cd tests/hitl && HW_TYPES=9 pytest --durations=0 [1-2]*.py [5-9]*.py"], ]) } } From 5889e3784f7a353b181b5dbec13fc1828e528259 Mon Sep 17 00:00:00 2001 From: Igor Biletskyy Date: Wed, 21 Jun 2023 18:56:10 -0700 Subject: [PATCH 104/197] H7: fix FDCAN_IR register check (#1480) * init * typo * misra * leftover --- board/drivers/bxcan.h | 6 +++--- board/drivers/fdcan.h | 14 ++++++++------ board/main_comms.h | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/board/drivers/bxcan.h b/board/drivers/bxcan.h index 14a4ff2255..cc8be6c6c3 100644 --- a/board/drivers/bxcan.h +++ b/board/drivers/bxcan.h @@ -68,11 +68,11 @@ void can_set_gmlan(uint8_t bus) { } } -void update_can_health_pkt(uint8_t can_number, bool error_irq) { +void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg) { CAN_TypeDef *CAN = CANIF_FROM_CAN_NUM(can_number); uint32_t esr_reg = CAN->ESR; - if (error_irq) { + if (ir_reg != 0U) { can_health[can_number].total_error_cnt += 1U; llcan_clear_send(CAN); } @@ -95,7 +95,7 @@ void update_can_health_pkt(uint8_t can_number, bool error_irq) { // CANx_SCE IRQ Handler void can_sce(uint8_t can_number) { ENTER_CRITICAL(); - update_can_health_pkt(can_number, true); + update_can_health_pkt(can_number, 1U); EXIT_CRITICAL(); } diff --git a/board/drivers/fdcan.h b/board/drivers/fdcan.h index 3cc8b8a395..c0a9021a14 100644 --- a/board/drivers/fdcan.h +++ b/board/drivers/fdcan.h @@ -32,7 +32,7 @@ void can_set_gmlan(uint8_t bus) { print("GMLAN not available on red panda\n"); } -void update_can_health_pkt(uint8_t can_number, bool error_irq) { +void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg) { ENTER_CRITICAL(); FDCAN_GlobalTypeDef *CANx = CANIF_FROM_CAN_NUM(can_number); @@ -58,13 +58,13 @@ void update_can_health_pkt(uint8_t can_number, bool error_irq) { can_health[can_number].transmit_error_cnt = ((ecr_reg & FDCAN_ECR_TEC) >> FDCAN_ECR_TEC_Pos); - if (error_irq) { + if (ir_reg != 0U) { can_health[can_number].total_error_cnt += 1U; - if ((CANx->IR & (FDCAN_IR_RF0L)) != 0) { + if ((ir_reg & (FDCAN_IR_RF0L)) != 0) { can_health[can_number].total_rx_lost_cnt += 1U; } // Actually reset can core only on arbitration or data phase errors and when CEL couter reaches at least 100 errors - if (((CANx->IR & (FDCAN_IR_PED | FDCAN_IR_PEA)) != 0) && (((ecr_reg & FDCAN_ECR_CEL) >> FDCAN_ECR_CEL_Pos) >= 100U)) { + if (((ir_reg & (FDCAN_IR_PED | FDCAN_IR_PEA)) != 0) && (((ecr_reg & FDCAN_ECR_CEL) >> FDCAN_ECR_CEL_Pos) >= 100U)) { llcan_clear_send(CANx); } // Clear error interrupts @@ -139,6 +139,8 @@ void can_rx(uint8_t can_number) { FDCAN_GlobalTypeDef *CANx = CANIF_FROM_CAN_NUM(can_number); uint8_t bus_number = BUS_NUM_FROM_CAN_NUM(can_number); + uint32_t ir_reg = CANx->IR; + // Clear all new messages from Rx FIFO 0 CANx->IR |= FDCAN_IR_RF0N; while((CANx->RXF0S & FDCAN_RXF0S_F0FL) != 0) { @@ -217,8 +219,8 @@ void can_rx(uint8_t can_number) { } // Error handling - if ((CANx->IR & (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EP | FDCAN_IR_BO | FDCAN_IR_RF0L | FDCAN_IR_ELO)) != 0) { - update_can_health_pkt(can_number, true); + if ((ir_reg & (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EP | FDCAN_IR_BO | FDCAN_IR_RF0L)) != 0) { + update_can_health_pkt(can_number, ir_reg); } } diff --git a/board/main_comms.h b/board/main_comms.h index 743a4d79cb..b5f758cb2e 100644 --- a/board/main_comms.h +++ b/board/main_comms.h @@ -167,7 +167,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { case 0xc2: COMPILE_TIME_ASSERT(sizeof(can_health_t) <= USBPACKET_MAX_SIZE); if (req->param1 < 3U) { - update_can_health_pkt(req->param1, false); + update_can_health_pkt(req->param1, 0U); can_health[req->param1].can_speed = (bus_config[req->param1].can_speed / 10U); can_health[req->param1].can_data_speed = (bus_config[req->param1].can_data_speed / 10U); can_health[req->param1].canfd_enabled = bus_config[req->param1].canfd_enabled; From 7ec16c5edd749fe9289ef9df56852dd7cd92eea5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 22 Jun 2023 20:15:42 -0700 Subject: [PATCH 105/197] query_fw_versions: sub-address support (#1482) * first draft * cleaner and supports scanning all subaddrs for all addrs * clean up * full * rm * and now print * revert * consistency * add comment * add subaddr to tqdm description * without a stupid `a = 1; if a == 1...` check, comment is best workaround --- examples/query_fw_versions.py | 66 ++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/examples/query_fw_versions.py b/examples/query_fw_versions.py index 82a204185e..1f54ed6c0b 100755 --- a/examples/query_fw_versions.py +++ b/examples/query_fw_versions.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import argparse +from typing import List, Optional from tqdm import tqdm from panda import Panda from panda.python.uds import UdsClient, MessageTimeoutError, NegativeResponseError, SESSION_TYPE, DATA_IDENTIFIER_TYPE @@ -11,6 +12,7 @@ parser.add_argument("--no-obd", action="store_true", help="Bus 1 will not be multiplexed to the OBD-II port") parser.add_argument("--debug", action="store_true") parser.add_argument("--addr") + parser.add_argument("--sub_addr", "--subaddr", help="A hex sub-address or `scan` to scan the full sub-address range") parser.add_argument("--bus") parser.add_argument('-s', '--serial', help="Serial number of panda to use") args = parser.parse_args() @@ -22,6 +24,17 @@ addrs += [0x18da0000 + (i << 8) + 0xf1 for i in range(256)] results = {} + sub_addrs: List[Optional[int]] = [None] + if args.sub_addr: + if args.sub_addr == "scan": + sub_addrs = list(range(0xff + 1)) + else: + sub_addrs = [int(args.sub_addr, base=16)] + if sub_addrs[0] > 0xff: # type: ignore + print(f"Invalid sub-address: 0x{sub_addrs[0]:X}, needs to be in range 0x0 to 0xff") + parser.print_help() + exit() + uds_data_ids = {} for std_id in DATA_IDENTIFIER_TYPE: uds_data_ids[std_id.value] = std_id.name @@ -51,42 +64,47 @@ # skip functional broadcast addrs if addr == 0x7df or addr == 0x18db33f1: continue - t.set_description(hex(addr)) if args.bus: bus = int(args.bus) else: bus = 1 if panda.has_obd() else 0 rx_addr = addr + int(args.rxoffset, base=16) if args.rxoffset else None - uds_client = UdsClient(panda, addr, rx_addr, bus, timeout=0.2, debug=args.debug) - # Check for anything alive at this address, and switch to the highest - # available diagnostic session without security access - try: - uds_client.tester_present() - uds_client.diagnostic_session_control(SESSION_TYPE.DEFAULT) - uds_client.diagnostic_session_control(SESSION_TYPE.EXTENDED_DIAGNOSTIC) - except NegativeResponseError: - pass - except MessageTimeoutError: - continue - # Run queries against all standard UDS data identifiers, plus selected - # non-standardized identifier ranges if requested - resp = {} - for uds_data_id in sorted(uds_data_ids): + # Try all sub-addresses for addr. By default, this is None + for sub_addr in sub_addrs: + sub_addr_str = hex(sub_addr) if sub_addr is not None else None + t.set_description(f"{hex(addr)}, {sub_addr_str}") + uds_client = UdsClient(panda, addr, rx_addr, bus, sub_addr=sub_addr, timeout=0.2, debug=args.debug) + # Check for anything alive at this address, and switch to the highest + # available diagnostic session without security access try: - data = uds_client.read_data_by_identifier(uds_data_id) # type: ignore - if data: - resp[uds_data_id] = data - except (NegativeResponseError, MessageTimeoutError): + uds_client.tester_present() + uds_client.diagnostic_session_control(SESSION_TYPE.DEFAULT) + uds_client.diagnostic_session_control(SESSION_TYPE.EXTENDED_DIAGNOSTIC) + except NegativeResponseError: pass + except MessageTimeoutError: + continue + + # Run queries against all standard UDS data identifiers, plus selected + # non-standardized identifier ranges if requested + resp = {} + for uds_data_id in sorted(uds_data_ids): + try: + data = uds_client.read_data_by_identifier(uds_data_id) # type: ignore + if data: + resp[uds_data_id] = data + except (NegativeResponseError, MessageTimeoutError): + pass - if resp.keys(): - results[addr] = resp + if resp.keys(): + results[(addr, sub_addr)] = resp if len(results.items()): - for addr, resp in results.items(): - print(f"\n\n*** Results for address 0x{addr:X} ***\n\n") + for (addr, sub_addr), resp in results.items(): + sub_addr_str = f", sub-address 0x{sub_addr:X}" if sub_addr is not None else "" + print(f"\n\n*** Results for address 0x{addr:X}{sub_addr_str} ***\n\n") for rid, dat in resp.items(): print(f"0x{rid:02X} {uds_data_ids[rid]}: {dat}") else: From b2cf1976796dbd9f700a76543466c8c9c9665827 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 23 Jun 2023 16:00:59 -0400 Subject: [PATCH 106/197] Subaru: Signals Cleanup (#1470) cleanup --- board/safety/safety_subaru.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/board/safety/safety_subaru.h b/board/safety/safety_subaru.h index 4c83aed857..447c1ea42b 100644 --- a/board/safety/safety_subaru.h +++ b/board/safety/safety_subaru.h @@ -159,7 +159,7 @@ static int subaru_fwd_hook(int bus_num, int addr) { // 0x122 ES_LKAS // 0x321 ES_DashStatus // 0x322 ES_LKAS_State - // 0x323 INFOTAINMENT_STATUS + // 0x323 ES_Infotainment bool block_lkas = (addr == 0x122) || (addr == 0x321) || (addr == 0x322) || (addr == 0x323); if (!block_lkas) { bus_fwd = 0; // Main CAN From 7af22677f74b14d590a047e54eee4674f48867d5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 23 Jun 2023 16:33:12 -0700 Subject: [PATCH 107/197] CanClient: check invalid sub-addresses (#1483) * check/catch invalid sub-addresses * clean up * break to next addr on first receipt of bad subaddr --- examples/query_fw_versions.py | 7 +++++-- python/uds.py | 5 +++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/examples/query_fw_versions.py b/examples/query_fw_versions.py index 1f54ed6c0b..69aa5aeb32 100755 --- a/examples/query_fw_versions.py +++ b/examples/query_fw_versions.py @@ -3,7 +3,8 @@ from typing import List, Optional from tqdm import tqdm from panda import Panda -from panda.python.uds import UdsClient, MessageTimeoutError, NegativeResponseError, SESSION_TYPE, DATA_IDENTIFIER_TYPE +from panda.python.uds import UdsClient, MessageTimeoutError, NegativeResponseError, InvalidSubAddressError, \ + SESSION_TYPE, DATA_IDENTIFIER_TYPE if __name__ == "__main__": parser = argparse.ArgumentParser() @@ -86,6 +87,8 @@ pass except MessageTimeoutError: continue + except InvalidSubAddressError: + break # Run queries against all standard UDS data identifiers, plus selected # non-standardized identifier ranges if requested @@ -95,7 +98,7 @@ data = uds_client.read_data_by_identifier(uds_data_id) # type: ignore if data: resp[uds_data_id] = data - except (NegativeResponseError, MessageTimeoutError): + except (NegativeResponseError, MessageTimeoutError, InvalidSubAddressError): pass if resp.keys(): diff --git a/python/uds.py b/python/uds.py index f52118c80e..975d5e4c6d 100644 --- a/python/uds.py +++ b/python/uds.py @@ -232,6 +232,9 @@ class InvalidServiceIdError(Exception): class InvalidSubFunctionError(Exception): pass +class InvalidSubAddressError(Exception): + pass + _negative_response_codes = { 0x00: 'positive response', 0x10: 'general reject', @@ -345,6 +348,8 @@ def _recv_buffer(self, drain: bool = False) -> None: # Cut off sub addr in first byte if self.sub_addr is not None: + if rx_data[0] != self.sub_addr: + raise InvalidSubAddressError(f"isotp - rx: invalid sub-address: {rx_data[0]}, expected: {self.sub_addr}") rx_data = rx_data[1:] self.rx_buff.append(rx_data) From 82cbc449b3a30abc4f112433167f083a506313b1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 23 Jun 2023 20:37:50 -0700 Subject: [PATCH 108/197] query_fw_versions: print rare and useful exception --- examples/query_fw_versions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/query_fw_versions.py b/examples/query_fw_versions.py index 69aa5aeb32..d16e5e9890 100755 --- a/examples/query_fw_versions.py +++ b/examples/query_fw_versions.py @@ -88,6 +88,7 @@ except MessageTimeoutError: continue except InvalidSubAddressError: + print(f'*** Skipping address {hex(addr)}: {e}') break # Run queries against all standard UDS data identifiers, plus selected From 7babe4cf6fd1ff470ed189058b721ba4175317cd Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 24 Jun 2023 05:21:08 -0700 Subject: [PATCH 109/197] query_fw_versions.py: fix undefined variable --- examples/query_fw_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/query_fw_versions.py b/examples/query_fw_versions.py index d16e5e9890..4f4e3fa66b 100755 --- a/examples/query_fw_versions.py +++ b/examples/query_fw_versions.py @@ -87,7 +87,7 @@ pass except MessageTimeoutError: continue - except InvalidSubAddressError: + except InvalidSubAddressError as e: print(f'*** Skipping address {hex(addr)}: {e}') break From 6309bb8a6ae650b25bd6d45a18333f657b3b28a6 Mon Sep 17 00:00:00 2001 From: Igor Biletksyy Date: Mon, 26 Jun 2023 14:16:34 -0700 Subject: [PATCH 110/197] Normal to have REC and TEC counters > 0, but less than threshould --- tests/hitl/conftest.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/hitl/conftest.py b/tests/hitl/conftest.py index d87ec9347d..93cfa4393f 100644 --- a/tests/hitl/conftest.py +++ b/tests/hitl/conftest.py @@ -187,8 +187,10 @@ def func_fixture_panda(request, module_panda): for i in range(3): can_health = p.can_health(i) assert can_health['bus_off_cnt'] == 0 - assert can_health['receive_error_cnt'] == 0 - assert can_health['transmit_error_cnt'] == 0 + assert can_health['receive_error_cnt'] < 127 + assert can_health['transmit_error_cnt'] < 255 + assert can_health['error_passive'] == 0 + assert can_health['error_warning'] == 0 assert can_health['total_rx_lost_cnt'] == 0 assert can_health['total_tx_lost_cnt'] == 0 assert can_health['total_error_cnt'] == 0 From 3a06eeec3c72c2e8ec48c42c6dbaf707cf5c648e Mon Sep 17 00:00:00 2001 From: Igor Biletksyy Date: Tue, 27 Jun 2023 16:03:43 -0700 Subject: [PATCH 111/197] bxCAN: check overrun flag on error irq --- board/drivers/bxcan.h | 11 ++++++----- board/stm32fx/llbxcan.h | 3 +++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/board/drivers/bxcan.h b/board/drivers/bxcan.h index cc8be6c6c3..523e68d6fb 100644 --- a/board/drivers/bxcan.h +++ b/board/drivers/bxcan.h @@ -74,6 +74,12 @@ void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg) { if (ir_reg != 0U) { can_health[can_number].total_error_cnt += 1U; + + // RX message lost due to FIFO overrun + if ((CAN->RF0R & (CAN_RF0R_FOVR0)) != 0) { + can_health[can_number].total_rx_lost_cnt += 1U; + CAN->RF0R &= ~(CAN_RF0R_FOVR0); + } llcan_clear_send(CAN); } @@ -165,11 +171,6 @@ void can_rx(uint8_t can_number) { CAN_TypeDef *CAN = CANIF_FROM_CAN_NUM(can_number); uint8_t bus_number = BUS_NUM_FROM_CAN_NUM(can_number); - if ((CAN->RF0R & (CAN_RF0R_FOVR0)) != 0) { // RX message lost due to FIFO overrun - can_health[can_number].total_rx_lost_cnt += 1U; - CAN->RF0R &= ~(CAN_RF0R_FOVR0); - } - while ((CAN->RF0R & CAN_RF0R_FMP0) != 0) { can_health[can_number].total_rx_cnt += 1U; diff --git a/board/stm32fx/llbxcan.h b/board/stm32fx/llbxcan.h index 0fdfd398a9..91c0d5fccc 100644 --- a/board/stm32fx/llbxcan.h +++ b/board/stm32fx/llbxcan.h @@ -110,6 +110,9 @@ bool llcan_init(CAN_TypeDef *CAN_obj) { // enable certain CAN interrupts register_set_bits(&(CAN_obj->IER), CAN_IER_TMEIE | CAN_IER_FMPIE0 | CAN_IER_ERRIE | CAN_IER_LECIE | CAN_IER_BOFIE | CAN_IER_EPVIE | CAN_IER_EWGIE | CAN_IER_FOVIE0 | CAN_IER_FFIE0); + // clear overrun flag on init + CAN_obj->RF0R &= ~(CAN_RF0R_FOVR0); + if (CAN_obj == CAN1) { NVIC_EnableIRQ(CAN1_TX_IRQn); NVIC_EnableIRQ(CAN1_RX0_IRQn); From 945a379af9af336c391bd61120dd44761b285a5f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 27 Jun 2023 16:10:10 -0700 Subject: [PATCH 112/197] tmp disable that --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index e70a542ca8..6bf0cb49d9 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -94,7 +94,7 @@ pipeline { phone_steps("panda-tres", [ ["build", "scons -j4"], ["flash", "cd tests/ && ./ci_reset_internal_hw.py"], - ["test", "cd tests/hitl && HW_TYPES=9 pytest --durations=0 [1-2]*.py [5-9]*.py"], + ["test", "cd tests/hitl && HW_TYPES=9 pytest --durations=0 2*.py [5-9]*.py"], ]) } } From 73798d7dcea45cf317cca4ca359b6d0baf7dda56 Mon Sep 17 00:00:00 2001 From: Igor Biletskyy Date: Wed, 28 Jun 2023 12:33:17 -0700 Subject: [PATCH 113/197] Disable CAN irqs when transceivers are disabled in power save mode (#1485) init test MISRA 15.7 --- board/main.c | 3 ++- board/power_saving.h | 15 +++++++++++ board/stm32fx/llbxcan.h | 56 ++++++++++++++++++++++++++++------------- board/stm32h7/llfdcan.h | 41 +++++++++++++++++++++--------- 4 files changed, 85 insertions(+), 30 deletions(-) diff --git a/board/main.c b/board/main.c index ad92d7216e..6ceec2e414 100644 --- a/board/main.c +++ b/board/main.c @@ -11,7 +11,6 @@ #include "early_init.h" #include "provision.h" -#include "power_saving.h" #include "safety.h" #include "health.h" @@ -24,6 +23,8 @@ #include "drivers/bxcan.h" #endif +#include "power_saving.h" + #include "obj/gitversion.h" #include "can_comms.h" diff --git a/board/power_saving.h b/board/power_saving.h index cc271e5da5..64baad0320 100644 --- a/board/power_saving.h +++ b/board/power_saving.h @@ -18,6 +18,13 @@ void set_power_save_state(int state) { uart_ring *ur = get_ring_by_number(1); for (unsigned int i = 0; i < sizeof(UBLOX_SLEEP_MSG) - 1U; i++) while (!putc(ur, UBLOX_SLEEP_MSG[i])); } + // Disable CAN interrupts + if (harness.status == HARNESS_STATUS_FLIPPED) { + llcan_irq_disable(cans[0]); + } else { + llcan_irq_disable(cans[2]); + } + llcan_irq_disable(cans[1]); } else { print("disable power savings\n"); if (current_board->has_gps) { @@ -25,6 +32,14 @@ void set_power_save_state(int state) { uart_ring *ur = get_ring_by_number(1); for (unsigned int i = 0; i < sizeof(UBLOX_WAKE_MSG) - 1U; i++) while (!putc(ur, UBLOX_WAKE_MSG[i])); } + + if (harness.status == HARNESS_STATUS_FLIPPED) { + llcan_irq_enable(cans[0]); + } else { + llcan_irq_enable(cans[2]); + } + llcan_irq_enable(cans[1]); + enable = true; } diff --git a/board/stm32fx/llbxcan.h b/board/stm32fx/llbxcan.h index 91c0d5fccc..64f9905146 100644 --- a/board/stm32fx/llbxcan.h +++ b/board/stm32fx/llbxcan.h @@ -75,6 +75,44 @@ bool llcan_set_speed(CAN_TypeDef *CAN_obj, uint32_t speed, bool loopback, bool s return ret; } +void llcan_irq_disable(CAN_TypeDef *CAN_obj) { + if (CAN_obj == CAN1) { + NVIC_DisableIRQ(CAN1_TX_IRQn); + NVIC_DisableIRQ(CAN1_RX0_IRQn); + NVIC_DisableIRQ(CAN1_SCE_IRQn); + } else if (CAN_obj == CAN2) { + NVIC_DisableIRQ(CAN2_TX_IRQn); + NVIC_DisableIRQ(CAN2_RX0_IRQn); + NVIC_DisableIRQ(CAN2_SCE_IRQn); + #ifdef CAN3 + } else if (CAN_obj == CAN3) { + NVIC_DisableIRQ(CAN3_TX_IRQn); + NVIC_DisableIRQ(CAN3_RX0_IRQn); + NVIC_DisableIRQ(CAN3_SCE_IRQn); + #endif + } else { + } +} + +void llcan_irq_enable(CAN_TypeDef *CAN_obj) { + if (CAN_obj == CAN1) { + NVIC_EnableIRQ(CAN1_TX_IRQn); + NVIC_EnableIRQ(CAN1_RX0_IRQn); + NVIC_EnableIRQ(CAN1_SCE_IRQn); + } else if (CAN_obj == CAN2) { + NVIC_EnableIRQ(CAN2_TX_IRQn); + NVIC_EnableIRQ(CAN2_RX0_IRQn); + NVIC_EnableIRQ(CAN2_SCE_IRQn); + #ifdef CAN3 + } else if (CAN_obj == CAN3) { + NVIC_EnableIRQ(CAN3_TX_IRQn); + NVIC_EnableIRQ(CAN3_RX0_IRQn); + NVIC_EnableIRQ(CAN3_SCE_IRQn); + #endif + } else { + } +} + bool llcan_init(CAN_TypeDef *CAN_obj) { bool ret = true; @@ -113,23 +151,7 @@ bool llcan_init(CAN_TypeDef *CAN_obj) { // clear overrun flag on init CAN_obj->RF0R &= ~(CAN_RF0R_FOVR0); - if (CAN_obj == CAN1) { - NVIC_EnableIRQ(CAN1_TX_IRQn); - NVIC_EnableIRQ(CAN1_RX0_IRQn); - NVIC_EnableIRQ(CAN1_SCE_IRQn); - } else if (CAN_obj == CAN2) { - NVIC_EnableIRQ(CAN2_TX_IRQn); - NVIC_EnableIRQ(CAN2_RX0_IRQn); - NVIC_EnableIRQ(CAN2_SCE_IRQn); - #ifdef CAN3 - } else if (CAN_obj == CAN3) { - NVIC_EnableIRQ(CAN3_TX_IRQn); - NVIC_EnableIRQ(CAN3_RX0_IRQn); - NVIC_EnableIRQ(CAN3_SCE_IRQn); - #endif - } else { - print("Invalid CAN: initialization failed\n"); - } + llcan_irq_enable(CAN_obj); } return ret; } diff --git a/board/stm32h7/llfdcan.h b/board/stm32h7/llfdcan.h index 2ed0e1d1f1..60dc0da7a0 100644 --- a/board/stm32h7/llfdcan.h +++ b/board/stm32h7/llfdcan.h @@ -158,6 +158,34 @@ bool llcan_set_speed(FDCAN_GlobalTypeDef *CANx, uint32_t speed, uint32_t data_sp return ret; } +void llcan_irq_disable(FDCAN_GlobalTypeDef *CANx) { + if (CANx == FDCAN1) { + NVIC_DisableIRQ(FDCAN1_IT0_IRQn); + NVIC_DisableIRQ(FDCAN1_IT1_IRQn); + } else if (CANx == FDCAN2) { + NVIC_DisableIRQ(FDCAN2_IT0_IRQn); + NVIC_DisableIRQ(FDCAN2_IT1_IRQn); + } else if (CANx == FDCAN3) { + NVIC_DisableIRQ(FDCAN3_IT0_IRQn); + NVIC_DisableIRQ(FDCAN3_IT1_IRQn); + } else { + } +} + +void llcan_irq_enable(FDCAN_GlobalTypeDef *CANx) { + if (CANx == FDCAN1) { + NVIC_EnableIRQ(FDCAN1_IT0_IRQn); + NVIC_EnableIRQ(FDCAN1_IT1_IRQn); + } else if (CANx == FDCAN2) { + NVIC_EnableIRQ(FDCAN2_IT0_IRQn); + NVIC_EnableIRQ(FDCAN2_IT1_IRQn); + } else if (CANx == FDCAN3) { + NVIC_EnableIRQ(FDCAN3_IT0_IRQn); + NVIC_EnableIRQ(FDCAN3_IT1_IRQn); + } else { + } +} + bool llcan_init(FDCAN_GlobalTypeDef *CANx) { uint32_t can_number = CAN_NUM_FROM_CANIF(CANx); bool ret = fdcan_request_init(CANx); @@ -224,18 +252,7 @@ bool llcan_init(FDCAN_GlobalTypeDef *CANx) { print(CAN_NAME_FROM_CANIF(CANx)); print(" llcan_init timed out (2)!\n"); } - if (CANx == FDCAN1) { - NVIC_EnableIRQ(FDCAN1_IT0_IRQn); - NVIC_EnableIRQ(FDCAN1_IT1_IRQn); - } else if (CANx == FDCAN2) { - NVIC_EnableIRQ(FDCAN2_IT0_IRQn); - NVIC_EnableIRQ(FDCAN2_IT1_IRQn); - } else if (CANx == FDCAN3) { - NVIC_EnableIRQ(FDCAN3_IT0_IRQn); - NVIC_EnableIRQ(FDCAN3_IT1_IRQn); - } else { - print("Invalid CAN: initialization failed\n"); - } + llcan_irq_enable(CANx); } else { print(CAN_NAME_FROM_CANIF(CANx)); print(" llcan_init timed out (1)!\n"); From 04f0c4261c3ca6781056cb451a07a77459e97728 Mon Sep 17 00:00:00 2001 From: Igor Biletksyy Date: Wed, 28 Jun 2023 12:34:18 -0700 Subject: [PATCH 114/197] Revert "HITL tests: reset jungle on init (#1473)" This reverts commit 1c574f8a9986cc5de8e5ddef9d092dea04f54dad. --- tests/hitl/conftest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/hitl/conftest.py b/tests/hitl/conftest.py index 93cfa4393f..8f4d405b9a 100644 --- a/tests/hitl/conftest.py +++ b/tests/hitl/conftest.py @@ -67,7 +67,6 @@ def init_all_pandas(): def init_jungle(): if _panda_jungle is None: return - _panda_jungle.reset() clear_can_buffers(_panda_jungle) _panda_jungle.set_panda_power(True) _panda_jungle.set_can_loopback(False) From 8a8bcb865e60d6e461f8a96edbd46f9d8801ed15 Mon Sep 17 00:00:00 2001 From: Igor Biletksyy Date: Wed, 28 Jun 2023 12:39:47 -0700 Subject: [PATCH 115/197] H7: clear irqs on reset --- board/stm32h7/llfdcan.h | 1 + 1 file changed, 1 insertion(+) diff --git a/board/stm32h7/llfdcan.h b/board/stm32h7/llfdcan.h index 60dc0da7a0..0382e8ce2e 100644 --- a/board/stm32h7/llfdcan.h +++ b/board/stm32h7/llfdcan.h @@ -263,6 +263,7 @@ bool llcan_init(FDCAN_GlobalTypeDef *CANx) { void llcan_clear_send(FDCAN_GlobalTypeDef *CANx) { // from datasheet: "Transmit cancellation is not intended for Tx FIFO operation." // so we need to clear pending transmission manually by resetting FDCAN core + CANx->IR |= 0x3FCFFFFFU; // clear all interrupts bool ret = llcan_init(CANx); UNUSED(ret); } From a35e9a1edb1e685f00f4dd9d874043f56ea771fc Mon Sep 17 00:00:00 2001 From: Igor Biletskyy Date: Wed, 28 Jun 2023 14:40:00 -0700 Subject: [PATCH 116/197] CAN health: add IRQ call and core reset counters (#1453) * initial * old method * comment --- board/drivers/bxcan.h | 32 +++++++++++++++++++++----------- board/drivers/fdcan.h | 14 ++++++++++++-- board/health.h | 6 +++++- python/__init__.py | 8 ++++++-- 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/board/drivers/bxcan.h b/board/drivers/bxcan.h index 523e68d6fb..7feec4cfbf 100644 --- a/board/drivers/bxcan.h +++ b/board/drivers/bxcan.h @@ -3,6 +3,11 @@ // CAN3_TX, CAN3_RX0, CAN3_SCE CAN_TypeDef *cans[] = {CAN1, CAN2, CAN3}; +uint8_t can_irq_number[3][3] = { + { CAN1_TX_IRQn, CAN1_RX0_IRQn, CAN1_SCE_IRQn }, + { CAN2_TX_IRQn, CAN2_RX0_IRQn, CAN2_SCE_IRQn }, + { CAN3_TX_IRQn, CAN3_RX0_IRQn, CAN3_SCE_IRQn }, +}; bool can_set_speed(uint8_t can_number) { bool ret = true; @@ -72,17 +77,6 @@ void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg) { CAN_TypeDef *CAN = CANIF_FROM_CAN_NUM(can_number); uint32_t esr_reg = CAN->ESR; - if (ir_reg != 0U) { - can_health[can_number].total_error_cnt += 1U; - - // RX message lost due to FIFO overrun - if ((CAN->RF0R & (CAN_RF0R_FOVR0)) != 0) { - can_health[can_number].total_rx_lost_cnt += 1U; - CAN->RF0R &= ~(CAN_RF0R_FOVR0); - } - llcan_clear_send(CAN); - } - can_health[can_number].bus_off = ((esr_reg & CAN_ESR_BOFF) >> CAN_ESR_BOFF_Pos); can_health[can_number].bus_off_cnt += can_health[can_number].bus_off; can_health[can_number].error_warning = ((esr_reg & CAN_ESR_EWGF) >> CAN_ESR_EWGF_Pos); @@ -95,6 +89,22 @@ void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg) { can_health[can_number].receive_error_cnt = ((esr_reg & CAN_ESR_REC) >> CAN_ESR_REC_Pos); can_health[can_number].transmit_error_cnt = ((esr_reg & CAN_ESR_TEC) >> CAN_ESR_TEC_Pos); + + can_health[can_number].irq0_call_rate = interrupts[can_irq_number[can_number][0]].call_rate; + can_health[can_number].irq1_call_rate = interrupts[can_irq_number[can_number][1]].call_rate; + can_health[can_number].irq2_call_rate = interrupts[can_irq_number[can_number][2]].call_rate; + + if (ir_reg != 0U) { + can_health[can_number].total_error_cnt += 1U; + + // RX message lost due to FIFO overrun + if ((CAN->RF0R & (CAN_RF0R_FOVR0)) != 0) { + can_health[can_number].total_rx_lost_cnt += 1U; + CAN->RF0R &= ~(CAN_RF0R_FOVR0); + } + can_health[can_number].can_core_reset_cnt += 1U; + llcan_clear_send(CAN); + } } // ***************************** CAN ***************************** diff --git a/board/drivers/fdcan.h b/board/drivers/fdcan.h index c0a9021a14..3b43128a03 100644 --- a/board/drivers/fdcan.h +++ b/board/drivers/fdcan.h @@ -11,6 +11,12 @@ typedef struct { FDCAN_GlobalTypeDef *cans[] = {FDCAN1, FDCAN2, FDCAN3}; +uint8_t can_irq_number[3][2] = { + { FDCAN1_IT0_IRQn, FDCAN1_IT1_IRQn }, + { FDCAN2_IT0_IRQn, FDCAN2_IT1_IRQn }, + { FDCAN3_IT0_IRQn, FDCAN3_IT1_IRQn }, +}; + bool can_set_speed(uint8_t can_number) { bool ret = true; FDCAN_GlobalTypeDef *CANx = CANIF_FROM_CAN_NUM(can_number); @@ -57,14 +63,19 @@ void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg) { can_health[can_number].receive_error_cnt = ((ecr_reg & FDCAN_ECR_REC) >> FDCAN_ECR_REC_Pos); can_health[can_number].transmit_error_cnt = ((ecr_reg & FDCAN_ECR_TEC) >> FDCAN_ECR_TEC_Pos); + can_health[can_number].irq0_call_rate = interrupts[can_irq_number[can_number][0]].call_rate; + can_health[can_number].irq1_call_rate = interrupts[can_irq_number[can_number][1]].call_rate; + if (ir_reg != 0U) { can_health[can_number].total_error_cnt += 1U; if ((ir_reg & (FDCAN_IR_RF0L)) != 0) { can_health[can_number].total_rx_lost_cnt += 1U; } - // Actually reset can core only on arbitration or data phase errors and when CEL couter reaches at least 100 errors + // Reset CAN core when CEL couter reaches at least 100 errors if (((ir_reg & (FDCAN_IR_PED | FDCAN_IR_PEA)) != 0) && (((ecr_reg & FDCAN_ECR_CEL) >> FDCAN_ECR_CEL_Pos) >= 100U)) { + can_health[can_number].can_core_reset_cnt += 1U; + can_health[can_number].total_tx_lost_cnt += (FDCAN_TX_FIFO_EL_CNT - (FDCAN_TXFQS_TFFL & FDCAN_TXFQS_TFFL_Msk)); // TX FIFO msgs will be lost after reset llcan_clear_send(CANx); } // Clear error interrupts @@ -128,7 +139,6 @@ void process_can(uint8_t can_number) { refresh_can_tx_slots_available(); } } - EXIT_CRITICAL(); } } diff --git a/board/health.h b/board/health.h index 4dce741fbc..9d9dcfb381 100644 --- a/board/health.h +++ b/board/health.h @@ -31,7 +31,7 @@ struct __attribute__((packed)) health_t { uint16_t sbu2_voltage_mV; }; -#define CAN_HEALTH_PACKET_VERSION 4 +#define CAN_HEALTH_PACKET_VERSION 5 typedef struct __attribute__((packed)) { uint8_t bus_off; uint32_t bus_off_cnt; @@ -55,4 +55,8 @@ typedef struct __attribute__((packed)) { uint8_t canfd_enabled; uint8_t brs_enabled; uint8_t canfd_non_iso; + uint32_t irq0_call_rate; + uint32_t irq1_call_rate; + uint32_t irq2_call_rate; + uint32_t can_core_reset_cnt; } can_health_t; diff --git a/python/__init__.py b/python/__init__.py index 0a266d4716..705e62cfeb 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -201,9 +201,9 @@ class Panda: CAN_PACKET_VERSION = 4 HEALTH_PACKET_VERSION = 14 - CAN_HEALTH_PACKET_VERSION = 4 + CAN_HEALTH_PACKET_VERSION = 5 HEALTH_STRUCT = struct.Struct(" Date: Fri, 30 Jun 2023 17:26:39 +0200 Subject: [PATCH 117/197] Faster log retrieval (#1484) improve get_logs --- board/drivers/logging.h | 12 ++++++++---- board/main_comms.h | 7 ++++++- python/__init__.py | 8 +++++--- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/board/drivers/logging.h b/board/drivers/logging.h index 117de58712..28e4275230 100644 --- a/board/drivers/logging.h +++ b/board/drivers/logging.h @@ -42,16 +42,20 @@ void logging_erase(void) { log_state.write_index = 0U; } -void logging_find_read_index(void) { +void logging_find_read_index(uint16_t last_id) { // Figure out the read index by the last empty slot - log_state.read_index = 0xFFFFU; + log_state.read_index = BANK_LOG_CAPACITY; for (uint16_t i = 0U; i < TOTAL_LOG_CAPACITY; i++) { - if (log_arr[i].id == 0xFFFFU) { + if (log_arr[i].id == last_id) { log_state.read_index = logging_next_index(i); } } } +void logging_init_read_index(void) { + return logging_find_read_index(0xFFFFU); +} + void logging_init(void) { COMPILE_TIME_ASSERT(sizeof(log_t) == 64U); COMPILE_TIME_ASSERT((LOGGING_FLASH_BASE_A + BANK_SIZE) == LOGGING_FLASH_BASE_B); @@ -67,7 +71,7 @@ void logging_init(void) { } } - logging_find_read_index(); + logging_init_read_index(); // At initialization, the read index should always be at the beginning of a bank // If not, clean slate diff --git a/board/main_comms.h b/board/main_comms.h index b5f758cb2e..c1591a0608 100644 --- a/board/main_comms.h +++ b/board/main_comms.h @@ -493,8 +493,13 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { // *** 0xfd: read logs case 0xfd: if (req->param1 == 1U) { - logging_find_read_index(); + logging_init_read_index(); } + + if (req->param2 != 0xFFFFU) { + logging_find_read_index(req->param2); + } + resp_len = logging_read(resp); break; default: diff --git a/python/__init__.py b/python/__init__.py index 705e62cfeb..ffa0b1bec0 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -1000,11 +1000,13 @@ def set_green_led(self, enabled): self._handle.controlWrite(Panda.REQUEST_OUT, 0xf7, int(enabled), 0, b'') # ****************** Logging ***************** - def get_logs(self, get_all=False): + def get_logs(self, last_id=None, get_all=False): + assert (last_id is None) or (0 <= last_id < 0xFFFF) + logs = [] - dat = self._handle.controlRead(Panda.REQUEST_IN, 0xfd, 1 if get_all else 0, 0, 0x40) + dat = self._handle.controlRead(Panda.REQUEST_IN, 0xfd, 1 if get_all else 0, last_id if last_id is not None else 0xFFFF, 0x40) while len(dat) > 0: if len(dat) == 0x40: logs.append(unpack_log(dat)) - dat = self._handle.controlRead(Panda.REQUEST_IN, 0xfd, 0, 0, 0x40) + dat = self._handle.controlRead(Panda.REQUEST_IN, 0xfd, 0, 0xFFFF, 0x40) return logs From 21843092ff6df550feecc857436cdaae1d5fbc96 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 1 Jul 2023 17:43:34 -0700 Subject: [PATCH 118/197] bump to python 3.11.4 (#1481) * bump to python 3.11.4 * Update .pre-commit-config.yaml * lint * import * no | yet * rm ignores * Update tests/libpanda/libpanda_py.py --------- Co-authored-by: Maxime Desroches --- .pre-commit-config.yaml | 2 +- Dockerfile | 4 ++-- python/uds.py | 10 +++++----- tests/libpanda/libpanda_py.py | 11 +++++++++-- tests/libpanda/safety_helpers.py | 3 ++- tests/usbprotocol/test_comms.py | 2 +- 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1d811c7243..cabcb94431 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: - id: check-merge-conflict - id: check-symlinks - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.910-1 + rev: v1.4.0 hooks: - id: mypy additional_dependencies: ['git+https://github.com/numpy/numpy-stubs', 'types-requests', 'types-atomicwrites', diff --git a/Dockerfile b/Dockerfile index c28d3f0188..d1684abcd7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,8 +55,8 @@ ENV OPENPILOT_REF="e276d2a417a5133fb91c93b2ef30df68a7d5f225" ENV OPENDBC_REF="9ae9fbfe56f79dca66c673a6479751a15ad61780" COPY requirements.txt /tmp/ -RUN pyenv install 3.8.10 && \ - pyenv global 3.8.10 && \ +RUN pyenv install 3.11.4 && \ + pyenv global 3.11.4 && \ pyenv rehash && \ pip install --no-cache-dir -r /tmp/requirements.txt diff --git a/python/uds.py b/python/uds.py index 975d5e4c6d..bcba4985b9 100644 --- a/python/uds.py +++ b/python/uds.py @@ -302,7 +302,7 @@ def get_dtc_status_names(status): class CanClient(): def __init__(self, can_send: Callable[[int, bytes, int], None], can_recv: Callable[[], List[Tuple[int, int, bytes, int]]], - tx_addr: int, rx_addr: int, bus: int, sub_addr: int = None, debug: bool = False): + tx_addr: int, rx_addr: int, bus: int, sub_addr: Optional[int] = None, debug: bool = False): self.tx = can_send self.rx = can_recv self.tx_addr = tx_addr @@ -571,7 +571,7 @@ def get_rx_addr_for_tx_addr(tx_addr, rx_offset=0x8): class UdsClient(): - def __init__(self, panda, tx_addr: int, rx_addr: int = None, bus: int = 0, sub_addr: int = None, timeout: float = 1, + def __init__(self, panda, tx_addr: int, rx_addr: Optional[int] = None, bus: int = 0, sub_addr: Optional[int] = None, timeout: float = 1, debug: bool = False, tx_timeout: float = 1, response_pending_timeout: float = 10): self.bus = bus self.tx_addr = tx_addr @@ -584,7 +584,7 @@ def __init__(self, panda, tx_addr: int, rx_addr: int = None, bus: int = 0, sub_a self.response_pending_timeout = response_pending_timeout # generic uds request - def _uds_request(self, service_type: SERVICE_TYPE, subfunction: int = None, data: bytes = None) -> bytes: + def _uds_request(self, service_type: SERVICE_TYPE, subfunction: Optional[int] = None, data: Optional[bytes] = None) -> bytes: req = bytes([service_type]) if subfunction is not None: req += bytes([subfunction]) @@ -672,7 +672,7 @@ def communication_control(self, control_type: CONTROL_TYPE, message_type: MESSAG def tester_present(self, ): self._uds_request(SERVICE_TYPE.TESTER_PRESENT, subfunction=0x00) - def access_timing_parameter(self, timing_parameter_type: TIMING_PARAMETER_TYPE, parameter_values: bytes = None): + def access_timing_parameter(self, timing_parameter_type: TIMING_PARAMETER_TYPE, parameter_values: Optional[bytes] = None): write_custom_values = timing_parameter_type == TIMING_PARAMETER_TYPE.SET_TO_GIVEN_VALUES read_values = (timing_parameter_type == TIMING_PARAMETER_TYPE.READ_CURRENTLY_ACTIVE or timing_parameter_type == TIMING_PARAMETER_TYPE.READ_EXTENDED_SET) @@ -715,7 +715,7 @@ def response_on_event(self, response_event_type: RESPONSE_EVENT_TYPE, store_even "data": resp[2:], # TODO: parse the reset of response } - def link_control(self, link_control_type: LINK_CONTROL_TYPE, baud_rate_type: BAUD_RATE_TYPE = None): + def link_control(self, link_control_type: LINK_CONTROL_TYPE, baud_rate_type: Optional[BAUD_RATE_TYPE] = None): data: Optional[bytes] if link_control_type == LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_FIXED_BAUDRATE: diff --git a/tests/libpanda/libpanda_py.py b/tests/libpanda/libpanda_py.py index b95ceb9fab..e8f063bf97 100644 --- a/tests/libpanda/libpanda_py.py +++ b/tests/libpanda/libpanda_py.py @@ -1,6 +1,6 @@ import os from cffi import FFI -from typing import List +from typing import Any, List, Protocol from panda import LEN_TO_DLC from panda.tests.libpanda.safety_helpers import PandaSafety, setup_safety_helpers @@ -94,7 +94,14 @@ class CANPacket: addr: int data: List[int] -class Panda(PandaSafety): +class Panda(PandaSafety, Protocol): + # CAN + tx1_q: Any + tx2_q: Any + tx3_q: Any + txgmlan_q: Any + def can_set_checksum(self, p: CANPacket) -> None: ... + # safety def safety_rx_hook(self, to_send: CANPacket) -> int: ... def safety_tx_hook(self, to_push: CANPacket) -> int: ... diff --git a/tests/libpanda/safety_helpers.py b/tests/libpanda/safety_helpers.py index e44cc23dda..23f642b20c 100644 --- a/tests/libpanda/safety_helpers.py +++ b/tests/libpanda/safety_helpers.py @@ -1,4 +1,5 @@ # panda safety helpers, from safety_helpers.c +from typing import Protocol def setup_safety_helpers(ffi): ffi.cdef(""" @@ -49,7 +50,7 @@ def setup_safety_helpers(ffi): int get_honda_hw(void); """) -class PandaSafety: +class PandaSafety(Protocol): def set_controls_allowed(self, c: bool) -> None: ... def get_controls_allowed(self) -> bool: ... def get_longitudinal_allowed(self) -> bool: ... diff --git a/tests/usbprotocol/test_comms.py b/tests/usbprotocol/test_comms.py index 311b7a9e62..9b020f2347 100755 --- a/tests/usbprotocol/test_comms.py +++ b/tests/usbprotocol/test_comms.py @@ -156,4 +156,4 @@ def test_can_receive_usb(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() From 5285eec35ae290ad711f8a7fc7fc5af38c204d25 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 1 Jul 2023 18:03:53 -0700 Subject: [PATCH 119/197] pre-commit: autoupdate hooks (#1347) * Update pre-commit hook versions * fix that --------- Co-authored-by: adeebshihadeh --- .pre-commit-config.yaml | 6 +++--- python/uds.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cabcb94431..1330b0ce21 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,19 +1,19 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.4.0 hooks: - id: check-ast - id: check-yaml - id: check-merge-conflict - id: check-symlinks - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.4.0 + rev: v1.4.1 hooks: - id: mypy additional_dependencies: ['git+https://github.com/numpy/numpy-stubs', 'types-requests', 'types-atomicwrites', 'types-pycurl'] - repo: https://github.com/PyCQA/flake8 - rev: 4.0.1 + rev: 6.0.0 hooks: - id: flake8 args: diff --git a/python/uds.py b/python/uds.py index bcba4985b9..5374b5199d 100644 --- a/python/uds.py +++ b/python/uds.py @@ -307,7 +307,7 @@ def __init__(self, can_send: Callable[[int, bytes, int], None], can_recv: Callab self.rx = can_recv self.tx_addr = tx_addr self.rx_addr = rx_addr - self.rx_buff = deque() # type: Deque[bytes] + self.rx_buff: Deque[bytes] = deque() self.sub_addr = sub_addr self.bus = bus self.debug = debug From 1f50a84b868d5dbb3fb8b1d9356b1d8deadb3589 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 1 Jul 2023 18:57:53 -0700 Subject: [PATCH 120/197] update old comment (#1282) --- board/main.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/board/main.c b/board/main.c index 6ceec2e414..7fedf74073 100644 --- a/board/main.c +++ b/board/main.c @@ -253,14 +253,11 @@ void tick_handler(void) { // Also disable IR when the heartbeat goes missing current_board->set_ir_power(0U); - // TODO: need a SPI equivalent - // If enumerated but no heartbeat (phone up, boardd not running), or when the SOM GPIO is pulled high by the ABL, - // turn the fan on to cool the device - if(usb_enumerated || current_board->read_som_gpio()){ - fan_set_power(50U); - } else { - fan_set_power(0U); - } + // Run fan when device is up, but not talking to us + // * bootloader enables the SOM GPIO on boot + // * fallback to USB enumerated where supported + bool enabled = usb_enumerated || current_board->read_som_gpio(); + fan_set_power(enabled ? 50U : 0U); } } From 5abb2b58da0a60df6ee1fe67115e613a074dbad0 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 2 Jul 2023 15:51:35 -0700 Subject: [PATCH 121/197] SPI: add protcol version request (#1488) * SPI: add protcol version request * quick test * fix linter * misra --------- Co-authored-by: Comma Device --- board/drivers/spi.h | 26 +++++++++++++++++++++++++- python/spi.py | 38 ++++++++++++++++++++++++++++++++++++++ tests/hitl/8_spi.py | 4 ++++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/board/drivers/spi.h b/board/drivers/spi.h index ed88f6ea81..d356c45c93 100644 --- a/board/drivers/spi.h +++ b/board/drivers/spi.h @@ -78,7 +78,31 @@ void spi_rx_done(void) { 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]; - if (spi_state == SPI_STATE_HEADER) { + if (memcmp(spi_buf_rx, "VERSION", 7) == 0) { + // protocol version request, respond with: + // VERSION + 2 byte data length + data + data complement + + // echo "VERSION" + (void)memcpy(spi_buf_tx, "VERSION", 7); + + // write response + uint16_t data_pos = 9; + uint16_t data_len = 1; + spi_buf_tx[data_pos] = 0x1; + + // response complement + for (uint16_t i = 0U; i < data_len; i++) { + spi_buf_tx[data_pos + data_len + i] = spi_buf_tx[data_pos + i] ^ 0xFFU; + } + + // data length + data_len *= 2U; + spi_buf_tx[7] = data_len & 0xFFU; + spi_buf_tx[8] = (data_len >> 8) & 0xFFU; + + response_len = 7U + 2U + data_len; + next_rx_state = SPI_STATE_HEADER_NACK;; + } else if (spi_state == SPI_STATE_HEADER) { checksum_valid = check_checksum(spi_buf_rx, SPI_HEADER_SIZE); if ((spi_buf_rx[0] == SPI_SYNC_BYTE) && checksum_valid) { // response: ACK and start receiving data portion diff --git a/python/spi.py b/python/spi.py index 22d257b295..ba1a350779 100644 --- a/python/spi.py +++ b/python/spi.py @@ -167,6 +167,44 @@ def _transfer(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 10 raise exc + def get_protocol_version(self) -> bytes: + vers_str = b"VERSION" + def _get_version(spi) -> bytes: + spi.writebytes(vers_str) + + logging.debug("- waiting for echo") + start = time.monotonic() + while True: + r = spi.readbytes(len(vers_str)) + if bytes(r) == vers_str: + break + if (time.monotonic() - start) > 0.5: + raise PandaSpiException("timed out waiting for version echo") + + # get response + logging.debug("- receiving response") + b = bytes(spi.readbytes(2)) + rlen = struct.unpack(" 1000: + raise PandaSpiException("response length greater than max") + + dat = bytes(spi.readbytes(rlen)) + resp = dat[:rlen//2] + resp_comp = dat[rlen//2:] + if resp != bytes([x ^ 0xff for x in resp_comp]): + raise PandaSpiException("data complement doesn't match") + return resp + + exc = PandaSpiException() + with self.dev.acquire() as spi: + for _ in range(10): + try: + return _get_version(spi) + except PandaSpiException as e: + exc = e + logging.debug("SPI get protocol version failed, retrying", exc_info=True) + raise exc + # libusb1 functions def close(self): self.dev.close() diff --git a/tests/hitl/8_spi.py b/tests/hitl/8_spi.py index c4a4502e7d..934630ff50 100644 --- a/tests/hitl/8_spi.py +++ b/tests/hitl/8_spi.py @@ -17,6 +17,10 @@ def _ping(self, mocker, panda): assert spy.call_count == 2 mocker.stop(spy) + def test_protocol_version(self, p): + v = p._handle.get_protocol_version() + assert v == b"\x01" + def test_all_comm_types(self, mocker, p): spy = mocker.spy(p._handle, '_wait_for_ack') From abcbfb44b45f717df5767a7ef1d7e602fa15d3ac Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 2 Jul 2023 23:13:57 -0700 Subject: [PATCH 122/197] SPI: send serial with protocol version (#1489) * SPI: send serial with protocol version * misra * fix test --------- Co-authored-by: Comma Device --- board/drivers/spi.h | 13 +++++++++++-- tests/hitl/8_spi.py | 7 ++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/board/drivers/spi.h b/board/drivers/spi.h index d356c45c93..a219152b64 100644 --- a/board/drivers/spi.h +++ b/board/drivers/spi.h @@ -86,9 +86,18 @@ void spi_rx_done(void) { (void)memcpy(spi_buf_tx, "VERSION", 7); // write response + uint16_t data_len = 0; uint16_t data_pos = 9; - uint16_t data_len = 1; - spi_buf_tx[data_pos] = 0x1; + + // write serial + #ifdef UID_BASE + (void)memcpy(&spi_buf_tx[data_pos], ((uint8_t *)UID_BASE), 12); + #endif + data_len += 12U; + + // SPI protocol version + spi_buf_tx[data_pos + data_len] = 0x1; + data_len += 1U; // response complement for (uint16_t i = 0U; i < data_len; i++) { diff --git a/tests/hitl/8_spi.py b/tests/hitl/8_spi.py index 934630ff50..c1e2a86a26 100644 --- a/tests/hitl/8_spi.py +++ b/tests/hitl/8_spi.py @@ -1,3 +1,4 @@ +import binascii import pytest import random from unittest.mock import patch @@ -19,7 +20,11 @@ def _ping(self, mocker, panda): def test_protocol_version(self, p): v = p._handle.get_protocol_version() - assert v == b"\x01" + + uid = binascii.hexlify(v[:-1]).decode() + assert uid == p.get_uid() + + assert v[-1:] == b"\x01" def test_all_comm_types(self, mocker, p): spy = mocker.spy(p._handle, '_wait_for_ack') From d693e89df2ec21e14a25b0723fe0d828dd82b1c9 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 2 Jul 2023 23:50:30 -0700 Subject: [PATCH 123/197] SPI: send hw type with version (#1490) Co-authored-by: Comma Device --- board/drivers/spi.h | 4 ++++ tests/hitl/8_spi.py | 7 +++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/board/drivers/spi.h b/board/drivers/spi.h index a219152b64..90dd610905 100644 --- a/board/drivers/spi.h +++ b/board/drivers/spi.h @@ -95,6 +95,10 @@ void spi_rx_done(void) { #endif data_len += 12U; + // HW type + spi_buf_tx[data_pos + data_len] = hw_type; + data_len += 1U; + // SPI protocol version spi_buf_tx[data_pos + data_len] = 0x1; data_len += 1U; diff --git a/tests/hitl/8_spi.py b/tests/hitl/8_spi.py index c1e2a86a26..eb942c578f 100644 --- a/tests/hitl/8_spi.py +++ b/tests/hitl/8_spi.py @@ -21,10 +21,13 @@ def _ping(self, mocker, panda): def test_protocol_version(self, p): v = p._handle.get_protocol_version() - uid = binascii.hexlify(v[:-1]).decode() + uid = binascii.hexlify(v[:12]).decode() assert uid == p.get_uid() - assert v[-1:] == b"\x01" + hwtype = v[12] + assert hwtype == ord(p.get_type()) + + assert v[-1] == 1 def test_all_comm_types(self, mocker, p): spy = mocker.spy(p._handle, '_wait_for_ack') From 14fd5ff5a3443cb3f81f3472518bf9d91fa1e834 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 3 Jul 2023 15:23:12 -0700 Subject: [PATCH 124/197] SPI: send bootstub status in version request (#1492) * mv first * switch to crc8 * bootstub * test * cleanup * little more * misra --------- Co-authored-by: Comma Device --- board/crc.h | 2 ++ board/drivers/spi.h | 84 ++++++++++++++++++++++++++------------------- python/spi.py | 25 +++++++------- python/utils.py | 12 +++++++ tests/hitl/8_spi.py | 18 ++++++---- 5 files changed, 87 insertions(+), 54 deletions(-) create mode 100644 python/utils.py diff --git a/board/crc.h b/board/crc.h index 0d62dd316e..6ceaba07ef 100644 --- a/board/crc.h +++ b/board/crc.h @@ -1,3 +1,5 @@ +#pragma once + uint8_t crc_checksum(uint8_t *dat, int len, const uint8_t poly) { uint8_t crc = 0xFFU; int i; diff --git a/board/drivers/spi.h b/board/drivers/spi.h index 90dd610905..34b4ab2593 100644 --- a/board/drivers/spi.h +++ b/board/drivers/spi.h @@ -1,5 +1,7 @@ #pragma once +#include "crc.h" + #define SPI_BUF_SIZE 1024U #define SPI_TIMEOUT_US 10000U @@ -50,6 +52,52 @@ void can_tx_comms_resume_spi(void) { spi_can_tx_ready = true; } +uint16_t spi_version_packet(uint8_t *out) { + // this protocol version request is a stable portion of + // the panda's SPI protocol. its contents match that of the + // panda USB descriptors and are sufficent to list/enumerate + // a panda, determine panda type, and bootstub status. + + // the response is: + // VERSION + 2 byte data length + data + CRC8 + + // echo "VERSION" + (void)memcpy(out, "VERSION", 7); + + // write response + uint16_t data_len = 0; + uint16_t data_pos = 7U + 2U; + + // write serial + #ifdef UID_BASE + (void)memcpy(&out[data_pos], ((uint8_t *)UID_BASE), 12); + data_len += 12U; + #endif + + // HW type + out[data_pos + data_len] = hw_type; + data_len += 1U; + + // bootstub + out[data_pos + data_len] = USB_PID & 0xFFU; + data_len += 1U; + + // SPI protocol version + out[data_pos + data_len] = 0x1; + data_len += 1U; + + // data length + out[7] = data_len & 0xFFU; + out[8] = (data_len >> 8) & 0xFFU; + + // CRC8 + uint16_t resp_len = data_pos + data_len; + out[resp_len] = crc_checksum(out, resp_len, 0xD5U); + resp_len += 1U; + + return resp_len; +} + void spi_init(void) { // platform init llspi_init(); @@ -79,41 +127,7 @@ void spi_rx_done(void) { spi_data_len_miso = (spi_buf_rx[5] << 8) | spi_buf_rx[4]; if (memcmp(spi_buf_rx, "VERSION", 7) == 0) { - // protocol version request, respond with: - // VERSION + 2 byte data length + data + data complement - - // echo "VERSION" - (void)memcpy(spi_buf_tx, "VERSION", 7); - - // write response - uint16_t data_len = 0; - uint16_t data_pos = 9; - - // write serial - #ifdef UID_BASE - (void)memcpy(&spi_buf_tx[data_pos], ((uint8_t *)UID_BASE), 12); - #endif - data_len += 12U; - - // HW type - spi_buf_tx[data_pos + data_len] = hw_type; - data_len += 1U; - - // SPI protocol version - spi_buf_tx[data_pos + data_len] = 0x1; - data_len += 1U; - - // response complement - for (uint16_t i = 0U; i < data_len; i++) { - spi_buf_tx[data_pos + data_len + i] = spi_buf_tx[data_pos + i] ^ 0xFFU; - } - - // data length - data_len *= 2U; - spi_buf_tx[7] = data_len & 0xFFU; - spi_buf_tx[8] = (data_len >> 8) & 0xFFU; - - response_len = 7U + 2U + data_len; + response_len = spi_version_packet(spi_buf_tx); next_rx_state = SPI_STATE_HEADER_NACK;; } else if (spi_state == SPI_STATE_HEADER) { checksum_valid = check_checksum(spi_buf_rx, SPI_HEADER_SIZE); diff --git a/python/spi.py b/python/spi.py index ba1a350779..26b188439e 100644 --- a/python/spi.py +++ b/python/spi.py @@ -12,6 +12,7 @@ from .base import BaseHandle, BaseSTBootloaderHandle, TIMEOUT from .constants import McuType, MCU_TYPE_BY_IDCODE +from .utils import crc8_pedal try: import spidev @@ -175,25 +176,23 @@ def _get_version(spi) -> bytes: logging.debug("- waiting for echo") start = time.monotonic() while True: - r = spi.readbytes(len(vers_str)) - if bytes(r) == vers_str: + version_bytes = spi.readbytes(len(vers_str) + 2) + if bytes(version_bytes).startswith(vers_str): break if (time.monotonic() - start) > 0.5: - raise PandaSpiException("timed out waiting for version echo") + raise PandaSpiMissingAck - # get response - logging.debug("- receiving response") - b = bytes(spi.readbytes(2)) - rlen = struct.unpack(" 1000: raise PandaSpiException("response length greater than max") - dat = bytes(spi.readbytes(rlen)) - resp = dat[:rlen//2] - resp_comp = dat[rlen//2:] - if resp != bytes([x ^ 0xff for x in resp_comp]): - raise PandaSpiException("data complement doesn't match") - return resp + # get response + dat = spi.readbytes(rlen + 1) + resp = dat[:-1] + calculated_crc = crc8_pedal(bytes(version_bytes + resp)) + if calculated_crc != dat[-1]: + raise PandaSpiBadChecksum + return bytes(resp) exc = PandaSpiException() with self.dev.acquire() as spi: diff --git a/python/utils.py b/python/utils.py new file mode 100644 index 0000000000..f91da64130 --- /dev/null +++ b/python/utils.py @@ -0,0 +1,12 @@ +def crc8_pedal(data): + crc = 0xFF # standard init value + poly = 0xD5 # standard crc8: x8+x7+x6+x4+x2+1 + size = len(data) + for i in range(size - 1, -1, -1): + crc ^= data[i] + for _ in range(8): + if ((crc & 0x80) != 0): + crc = ((crc << 1) ^ poly) & 0xFF + else: + crc <<= 1 + return crc diff --git a/tests/hitl/8_spi.py b/tests/hitl/8_spi.py index eb942c578f..04d674f2a6 100644 --- a/tests/hitl/8_spi.py +++ b/tests/hitl/8_spi.py @@ -18,16 +18,22 @@ def _ping(self, mocker, panda): assert spy.call_count == 2 mocker.stop(spy) + @pytest.mark.expected_logs(2) def test_protocol_version(self, p): - v = p._handle.get_protocol_version() + for bootstub in (False, True): + p.reset(enter_bootstub=bootstub) + v = p._handle.get_protocol_version() - uid = binascii.hexlify(v[:12]).decode() - assert uid == p.get_uid() + uid = binascii.hexlify(v[:12]).decode() + assert uid == p.get_uid() - hwtype = v[12] - assert hwtype == ord(p.get_type()) + hwtype = v[12] + assert hwtype == ord(p.get_type()) - assert v[-1] == 1 + bstub = v[13] + assert bstub == (0xEE if bootstub else 0xCC) + + assert v[14:] == b"\x01" def test_all_comm_types(self, mocker, p): spy = mocker.spy(p._handle, '_wait_for_ack') From a687b183eee04147caef36fd95f17e91546bb55f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 3 Jul 2023 22:06:35 -0700 Subject: [PATCH 125/197] SPI: update max IRQ rate (#1493) --- board/drivers/spi.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/board/drivers/spi.h b/board/drivers/spi.h index 34b4ab2593..2c85184871 100644 --- a/board/drivers/spi.h +++ b/board/drivers/spi.h @@ -5,9 +5,9 @@ #define SPI_BUF_SIZE 1024U #define SPI_TIMEOUT_US 10000U -// we expect less than 50 transactions (including control messages and -// CAN buffers) at the 100Hz boardd interval, plus some buffer -#define SPI_IRQ_RATE 6500U +// got max rate from hitting a non-existent endpoint +// in a tight loop, plus some buffer +#define SPI_IRQ_RATE 16000U #ifdef STM32H7 __attribute__((section(".ram_d1"))) uint8_t spi_buf_rx[SPI_BUF_SIZE]; From c97f572208e5de22dbca6272d09fe15244a32581 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 6 Jul 2023 15:31:40 -0700 Subject: [PATCH 126/197] safety_toyota: use GET_BIT (#1496) * use GET_BIT * same for gas pressed * brake pressed too * fix * another fix --- board/safety/safety_toyota.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/board/safety/safety_toyota.h b/board/safety/safety_toyota.h index 5ea6b6e107..0bc7f3c6df 100644 --- a/board/safety/safety_toyota.h +++ b/board/safety/safety_toyota.h @@ -103,7 +103,7 @@ static int toyota_rx_hook(CANPacket_t *to_push) { // sample gas pedal if (!gas_interceptor_detected) { - gas_pressed = ((GET_BYTE(to_push, 0) >> 4) & 1U) == 0U; + gas_pressed = GET_BIT(to_push, 4U) == 0U; } } @@ -115,8 +115,8 @@ static int toyota_rx_hook(CANPacket_t *to_push) { // most cars have brake_pressed on 0x226, corolla and rav4 on 0x224 if (((addr == 0x224) && toyota_alt_brake) || ((addr == 0x226) && !toyota_alt_brake)) { - int byte = (addr == 0x224) ? 0 : 4; - brake_pressed = ((GET_BYTE(to_push, byte) >> 5) & 1U) != 0U; + uint8_t bit = (addr == 0x224) ? 5U : 37U; + brake_pressed = GET_BIT(to_push, bit) != 0U; } // sample gas interceptor @@ -191,8 +191,8 @@ static int toyota_tx_hook(CANPacket_t *to_send) { // only sent to prevent dash errors, no actuation is accepted if (addr == 0x191) { // check the STEER_REQUEST, STEER_REQUEST_2, SETME_X64 STEER_ANGLE_CMD signals - bool lta_request = (GET_BYTE(to_send, 0) & 1U) != 0U; - bool lta_request2 = ((GET_BYTE(to_send, 3) >> 1) & 1U) != 0U; + bool lta_request = GET_BIT(to_send, 0U) != 0U; + bool lta_request2 = GET_BIT(to_send, 25U) != 0U; int setme_x64 = GET_BYTE(to_send, 5); int lta_angle = (GET_BYTE(to_send, 1) << 8) | GET_BYTE(to_send, 2); lta_angle = to_signed(lta_angle, 16); From c1e79333fb4f6eb58ded57b2f808641031caacc9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 6 Jul 2023 17:06:31 -0700 Subject: [PATCH 127/197] angle safety: make angle conversion factor a float (#1498) * make this a float * no need to cast to float now --- board/safety/safety_ford.h | 2 +- board/safety_declarations.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/board/safety/safety_ford.h b/board/safety/safety_ford.h index b25262ca20..98cf204d99 100644 --- a/board/safety/safety_ford.h +++ b/board/safety/safety_ford.h @@ -219,7 +219,7 @@ static int ford_rx_hook(CANPacket_t *to_push) { float ford_yaw_rate = (((GET_BYTE(to_push, 2) << 8U) | GET_BYTE(to_push, 3)) * 0.0002) - 6.5; float current_curvature = ford_yaw_rate / MAX(vehicle_speed.values[0] / VEHICLE_SPEED_FACTOR, 0.1); // convert current curvature into units on CAN for comparison with desired curvature - update_sample(&angle_meas, ROUND(current_curvature * (float)FORD_STEERING_LIMITS.angle_deg_to_can)); + update_sample(&angle_meas, ROUND(current_curvature * FORD_STEERING_LIMITS.angle_deg_to_can)); } // Update gas pedal diff --git a/board/safety_declarations.h b/board/safety_declarations.h index 393e55467e..b2f0ea9854 100644 --- a/board/safety_declarations.h +++ b/board/safety_declarations.h @@ -67,7 +67,7 @@ typedef struct { const bool has_steer_req_tolerance; // angle cmd limits - const int angle_deg_to_can; + const float angle_deg_to_can; const struct lookup_t angle_rate_up_lookup; const struct lookup_t angle_rate_down_lookup; const int max_angle_error; // used to limit error between meas and cmd while enabled From 8ba568388176b8fc6443c124ad9ecd4e33174642 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 7 Jul 2023 04:48:30 -0400 Subject: [PATCH 128/197] Subaru: improve readability in panda code (#1380) * improve readability * cleanup for PR * switch to #define * i guess i forgot how #define works * fix #define * improve for preglobal too * rename because of conflicts * rename with subaru in name * missed some * missed that one too * made it pretty * whitespace * infotainment match opendbc * whitespace * whitespace * parameterize the bus too * fix that * fix misra violation * multiline --------- Co-authored-by: Shane Smiskol --- board/safety/safety_subaru.h | 93 +++++++++++++++----------- board/safety/safety_subaru_preglobal.h | 56 ++++++++++------ 2 files changed, 89 insertions(+), 60 deletions(-) diff --git a/board/safety/safety_subaru.h b/board/safety/safety_subaru.h index 447c1ea42b..ff75ffa6e6 100644 --- a/board/safety/safety_subaru.h +++ b/board/safety/safety_subaru.h @@ -20,40 +20,58 @@ const SteeringLimits SUBARU_GEN2_STEERING_LIMITS = { .type = TorqueDriverLimited, }; +#define MSG_SUBARU_Brake_Status 0x13c +#define MSG_SUBARU_CruiseControl 0x240 +#define MSG_SUBARU_Throttle 0x40 +#define MSG_SUBARU_Steering_Torque 0x119 +#define MSG_SUBARU_Wheel_Speeds 0x13a + +#define MSG_SUBARU_ES_LKAS 0x122 +#define MSG_SUBARU_ES_Brake 0x220 +#define MSG_SUBARU_ES_Distance 0x221 +#define MSG_SUBARU_ES_Status 0x222 +#define MSG_SUBARU_ES_DashStatus 0x321 +#define MSG_SUBARU_ES_LKAS_State 0x322 +#define MSG_SUBARU_ES_Infotainment 0x323 + +#define SUBARU_MAIN_BUS 0 +#define SUBARU_ALT_BUS 1 +#define SUBARU_CAM_BUS 2 + const CanMsg SUBARU_TX_MSGS[] = { - {0x122, 0, 8}, - {0x221, 0, 8}, - {0x321, 0, 8}, - {0x322, 0, 8}, - {0x323, 0, 8}, + {MSG_SUBARU_ES_LKAS, SUBARU_MAIN_BUS, 8}, + {MSG_SUBARU_ES_Distance, SUBARU_MAIN_BUS, 8}, + {MSG_SUBARU_ES_DashStatus, SUBARU_MAIN_BUS, 8}, + {MSG_SUBARU_ES_LKAS_State, SUBARU_MAIN_BUS, 8}, + {MSG_SUBARU_ES_Infotainment, SUBARU_MAIN_BUS, 8}, }; #define SUBARU_TX_MSGS_LEN (sizeof(SUBARU_TX_MSGS) / sizeof(SUBARU_TX_MSGS[0])) const CanMsg SUBARU_GEN2_TX_MSGS[] = { - {0x122, 0, 8}, - {0x221, 1, 8}, - {0x321, 0, 8}, - {0x322, 0, 8}, - {0x323, 0, 8} + {MSG_SUBARU_ES_LKAS, SUBARU_MAIN_BUS, 8}, + {MSG_SUBARU_ES_Distance, SUBARU_ALT_BUS, 8}, + {MSG_SUBARU_ES_DashStatus, SUBARU_MAIN_BUS, 8}, + {MSG_SUBARU_ES_LKAS_State, SUBARU_MAIN_BUS, 8}, + {MSG_SUBARU_ES_Infotainment, SUBARU_MAIN_BUS, 8} }; #define SUBARU_GEN2_TX_MSGS_LEN (sizeof(SUBARU_GEN2_TX_MSGS) / sizeof(SUBARU_GEN2_TX_MSGS[0])) AddrCheckStruct subaru_addr_checks[] = { - {.msg = {{ 0x40, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, - {.msg = {{0x119, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{0x13a, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{0x13c, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{0x240, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 50000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_SUBARU_Throttle, SUBARU_MAIN_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_SUBARU_Steering_Torque, SUBARU_MAIN_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_SUBARU_Wheel_Speeds, SUBARU_MAIN_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_SUBARU_Brake_Status, SUBARU_MAIN_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_SUBARU_CruiseControl, SUBARU_MAIN_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 50000U}, { 0 }, { 0 }}}, }; #define SUBARU_ADDR_CHECK_LEN (sizeof(subaru_addr_checks) / sizeof(subaru_addr_checks[0])) addr_checks subaru_rx_checks = {subaru_addr_checks, SUBARU_ADDR_CHECK_LEN}; AddrCheckStruct subaru_gen2_addr_checks[] = { - {.msg = {{ 0x40, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, - {.msg = {{0x119, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{0x13a, 1, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{0x13c, 1, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{0x240, 1, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 50000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_SUBARU_Throttle, SUBARU_MAIN_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_SUBARU_Steering_Torque, SUBARU_MAIN_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_SUBARU_Wheel_Speeds, SUBARU_ALT_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_SUBARU_Brake_Status, SUBARU_ALT_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_SUBARU_CruiseControl, SUBARU_ALT_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 50000U}, { 0 }, { 0 }}}, }; #define SUBARU_GEN2_ADDR_CHECK_LEN (sizeof(subaru_gen2_addr_checks) / sizeof(subaru_gen2_addr_checks[0])) addr_checks subaru_gen2_rx_checks = {subaru_gen2_addr_checks, SUBARU_GEN2_ADDR_CHECK_LEN}; @@ -88,10 +106,10 @@ static int subaru_rx_hook(CANPacket_t *to_push) { if (valid) { const int bus = GET_BUS(to_push); - const int alt_bus = subaru_gen2 ? 1 : 0; + const int alt_bus = subaru_gen2 ? SUBARU_ALT_BUS : SUBARU_MAIN_BUS; int addr = GET_ADDR(to_push); - if ((addr == 0x119) && (bus == 0)) { + if ((addr == MSG_SUBARU_Steering_Torque) && (bus == SUBARU_MAIN_BUS)) { int torque_driver_new; torque_driver_new = ((GET_BYTES(to_push, 0, 4) >> 16) & 0x7FFU); torque_driver_new = -1 * to_signed(torque_driver_new, 11); @@ -99,25 +117,25 @@ static int subaru_rx_hook(CANPacket_t *to_push) { } // enter controls on rising edge of ACC, exit controls on ACC off - if ((addr == 0x240) && (bus == alt_bus)) { + if ((addr == MSG_SUBARU_CruiseControl) && (bus == alt_bus)) { bool cruise_engaged = GET_BIT(to_push, 41U) != 0U; pcm_cruise_check(cruise_engaged); } // update vehicle moving with any non-zero wheel speed - if ((addr == 0x13a) && (bus == alt_bus)) { + if ((addr == MSG_SUBARU_Wheel_Speeds) && (bus == alt_bus)) { vehicle_moving = ((GET_BYTES(to_push, 0, 4) >> 12) != 0U) || (GET_BYTES(to_push, 4, 4) != 0U); } - if ((addr == 0x13c) && (bus == alt_bus)) { + if ((addr == MSG_SUBARU_Brake_Status) && (bus == alt_bus)) { brake_pressed = ((GET_BYTE(to_push, 7) >> 6) & 1U); } - if ((addr == 0x40) && (bus == 0)) { + if ((addr == MSG_SUBARU_Throttle) && (bus == SUBARU_MAIN_BUS)) { gas_pressed = GET_BYTE(to_push, 4) != 0U; } - generic_rx_checks((addr == 0x122) && (bus == 0)); + generic_rx_checks((addr == MSG_SUBARU_ES_LKAS) && (bus == SUBARU_MAIN_BUS)); } return valid; } @@ -134,7 +152,7 @@ static int subaru_tx_hook(CANPacket_t *to_send) { } // steer cmd checks - if (addr == 0x122) { + if (addr == MSG_SUBARU_ES_LKAS) { int desired_torque = ((GET_BYTES(to_send, 0, 4) >> 16) & 0x1FFFU); desired_torque = -1 * to_signed(desired_torque, 13); @@ -150,19 +168,18 @@ static int subaru_tx_hook(CANPacket_t *to_send) { static int subaru_fwd_hook(int bus_num, int addr) { int bus_fwd = -1; - if (bus_num == 0) { - bus_fwd = 2; // forward to camera + if (bus_num == SUBARU_MAIN_BUS) { + bus_fwd = SUBARU_CAM_BUS; // forward to camera } - if (bus_num == 2) { + if (bus_num == SUBARU_CAM_BUS) { // Global platform - // 0x122 ES_LKAS - // 0x321 ES_DashStatus - // 0x322 ES_LKAS_State - // 0x323 ES_Infotainment - bool block_lkas = (addr == 0x122) || (addr == 0x321) || (addr == 0x322) || (addr == 0x323); + bool block_lkas = ((addr == MSG_SUBARU_ES_LKAS) || + (addr == MSG_SUBARU_ES_DashStatus) || + (addr == MSG_SUBARU_ES_LKAS_State) || + (addr == MSG_SUBARU_ES_Infotainment)); if (!block_lkas) { - bus_fwd = 0; // Main CAN + bus_fwd = SUBARU_MAIN_BUS; // Main CAN } } @@ -188,5 +205,3 @@ const safety_hooks subaru_hooks = { .tx_lin = nooutput_tx_lin_hook, .fwd = subaru_fwd_hook, }; - - diff --git a/board/safety/safety_subaru_preglobal.h b/board/safety/safety_subaru_preglobal.h index 673e3655c5..b30af3d4aa 100644 --- a/board/safety/safety_subaru_preglobal.h +++ b/board/safety/safety_subaru_preglobal.h @@ -9,17 +9,32 @@ const SteeringLimits SUBARU_PG_STEERING_LIMITS = { .type = TorqueDriverLimited, }; +// Preglobal platform +// 0x161 is ES_CruiseThrottle +// 0x164 is ES_LKAS + +#define MSG_SUBARU_PG_CruiseControl 0x144 +#define MSG_SUBARU_PG_Throttle 0x140 +#define MSG_SUBARU_PG_Wheel_Speeds 0xD4 +#define MSG_SUBARU_PG_Brake_Pedal 0xD1 +#define MSG_SUBARU_PG_ES_LKAS 0x164 +#define MSG_SUBARU_PG_ES_Distance 0x161 +#define MSG_SUBARU_PG_Steering_Torque 0x371 + +#define SUBARU_PG_MAIN_BUS 0 +#define SUBARU_PG_CAM_BUS 2 + const CanMsg SUBARU_PG_TX_MSGS[] = { - {0x161, 0, 8}, - {0x164, 0, 8} + {MSG_SUBARU_PG_ES_Distance, SUBARU_PG_MAIN_BUS, 8}, + {MSG_SUBARU_PG_ES_LKAS, SUBARU_PG_MAIN_BUS, 8} }; #define SUBARU_PG_TX_MSGS_LEN (sizeof(SUBARU_PG_TX_MSGS) / sizeof(SUBARU_PG_TX_MSGS[0])) // TODO: do checksum and counter checks after adding the signals to the outback dbc file AddrCheckStruct subaru_preglobal_addr_checks[] = { - {.msg = {{0x140, 0, 8, .expected_timestep = 10000U}, { 0 }, { 0 }}}, - {.msg = {{0x371, 0, 8, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{0x144, 0, 8, .expected_timestep = 50000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_SUBARU_PG_Throttle, SUBARU_PG_MAIN_BUS, 8, .expected_timestep = 10000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_SUBARU_PG_Steering_Torque, SUBARU_PG_MAIN_BUS, 8, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_SUBARU_PG_CruiseControl, SUBARU_PG_MAIN_BUS, 8, .expected_timestep = 50000U}, { 0 }, { 0 }}}, }; #define SUBARU_PG_ADDR_CHECK_LEN (sizeof(subaru_preglobal_addr_checks) / sizeof(subaru_preglobal_addr_checks[0])) addr_checks subaru_preglobal_rx_checks = {subaru_preglobal_addr_checks, SUBARU_PG_ADDR_CHECK_LEN}; @@ -28,9 +43,11 @@ static int subaru_preglobal_rx_hook(CANPacket_t *to_push) { bool valid = addr_safety_check(to_push, &subaru_preglobal_rx_checks, NULL, NULL, NULL, NULL); - if (valid && (GET_BUS(to_push) == 0U)) { + const int bus = GET_BUS(to_push); + + if (valid && (bus == SUBARU_PG_MAIN_BUS)) { int addr = GET_ADDR(to_push); - if (addr == 0x371) { + if (addr == MSG_SUBARU_PG_Steering_Torque) { int torque_driver_new; torque_driver_new = (GET_BYTE(to_push, 3) >> 5) + (GET_BYTE(to_push, 4) << 3); torque_driver_new = to_signed(torque_driver_new, 11); @@ -38,25 +55,25 @@ static int subaru_preglobal_rx_hook(CANPacket_t *to_push) { } // enter controls on rising edge of ACC, exit controls on ACC off - if (addr == 0x144) { + if (addr == MSG_SUBARU_PG_CruiseControl) { bool cruise_engaged = GET_BIT(to_push, 49U) != 0U; pcm_cruise_check(cruise_engaged); } // update vehicle moving with any non-zero wheel speed - if (addr == 0xD4) { + if (addr == MSG_SUBARU_PG_Wheel_Speeds) { vehicle_moving = ((GET_BYTES(to_push, 0, 4) >> 12) != 0U) || (GET_BYTES(to_push, 4, 4) != 0U); } - if (addr == 0xD1) { + if (addr == MSG_SUBARU_PG_Brake_Pedal) { brake_pressed = ((GET_BYTES(to_push, 0, 4) >> 16) & 0xFFU) > 0U; } - if (addr == 0x140) { + if (addr == MSG_SUBARU_PG_Throttle) { gas_pressed = GET_BYTE(to_push, 0) != 0U; } - generic_rx_checks((addr == 0x164)); + generic_rx_checks((addr == MSG_SUBARU_PG_ES_LKAS)); } return valid; } @@ -71,7 +88,7 @@ static int subaru_preglobal_tx_hook(CANPacket_t *to_send) { } // steer cmd checks - if (addr == 0x164) { + if (addr == MSG_SUBARU_PG_ES_LKAS) { int desired_torque = ((GET_BYTES(to_send, 0, 4) >> 8) & 0x1FFFU); desired_torque = -1 * to_signed(desired_torque, 13); @@ -86,17 +103,14 @@ static int subaru_preglobal_tx_hook(CANPacket_t *to_send) { static int subaru_preglobal_fwd_hook(int bus_num, int addr) { int bus_fwd = -1; - if (bus_num == 0) { - bus_fwd = 2; // Camera CAN + if (bus_num == SUBARU_PG_MAIN_BUS) { + bus_fwd = SUBARU_PG_CAM_BUS; // Camera CAN } - if (bus_num == 2) { - // Preglobal platform - // 0x161 is ES_CruiseThrottle - // 0x164 is ES_LKAS - int block_msg = ((addr == 0x161) || (addr == 0x164)); + if (bus_num == SUBARU_PG_CAM_BUS) { + int block_msg = ((addr == MSG_SUBARU_PG_ES_Distance) || (addr == MSG_SUBARU_PG_ES_LKAS)); if (!block_msg) { - bus_fwd = 0; // Main CAN + bus_fwd = SUBARU_PG_MAIN_BUS; // Main CAN } } From 6b9b448259d1790ed8e18abde09ee72446996b3a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 12 Jul 2023 20:34:46 -0700 Subject: [PATCH 129/197] log device boot time (#1503) * log device boot time * misra * only log real boots * set gpio * true * initial * log bootkick * fix RPv2 --------- Co-authored-by: Comma Device Co-authored-by: Igor Biletksyy --- board/boards/board_declarations.h | 2 +- board/boards/dos.h | 5 ++++- board/boards/red_v2.h | 3 ++- board/boards/tres.h | 5 ++++- board/boards/uno.h | 3 ++- board/boards/unused_funcs.h | 3 ++- board/main.c | 23 ++++++++++++++++++++++- tests/print_logs.py | 8 ++++++++ tests/setup_device_ci.sh | 6 ++++++ 9 files changed, 51 insertions(+), 7 deletions(-) create mode 100755 tests/print_logs.py diff --git a/board/boards/board_declarations.h b/board/boards/board_declarations.h index db9bbd4028..c0c6436906 100644 --- a/board/boards/board_declarations.h +++ b/board/boards/board_declarations.h @@ -11,7 +11,7 @@ typedef void (*board_set_ir_power)(uint8_t percentage); typedef void (*board_set_fan_enabled)(bool enabled); typedef void (*board_set_phone_power)(bool enabled); typedef void (*board_set_siren)(bool enabled); -typedef void (*board_board_tick)(bool ignition, bool usb_enum, bool heartbeat_seen, bool harness_inserted); +typedef bool (*board_board_tick)(bool ignition, bool usb_enum, bool heartbeat_seen, bool harness_inserted); typedef bool (*board_read_som_gpio)(void); struct board { diff --git a/board/boards/dos.h b/board/boards/dos.h index d2bf7d3bf3..0178ff016f 100644 --- a/board/boards/dos.h +++ b/board/boards/dos.h @@ -53,9 +53,11 @@ void dos_set_bootkick(bool enabled){ set_gpio_output(GPIOC, 4, !enabled); } -void dos_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen, bool harness_inserted) { +bool dos_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen, bool harness_inserted) { + bool ret = false; if ((ignition && !usb_enum) || harness_inserted) { // enable bootkick if ignition seen or if plugged into a harness + ret = true; dos_set_bootkick(true); } else if (heartbeat_seen) { // disable once openpilot is up @@ -63,6 +65,7 @@ void dos_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen, bool harn } else { } + return ret; } void dos_set_can_mode(uint8_t mode){ diff --git a/board/boards/red_v2.h b/board/boards/red_v2.h index a9dc4d4564..d6724d8bb7 100644 --- a/board/boards/red_v2.h +++ b/board/boards/red_v2.h @@ -36,5 +36,6 @@ const board board_red_v2 = { .set_fan_enabled = unused_set_fan_enabled, .set_ir_power = unused_set_ir_power, .set_phone_power = unused_set_phone_power, - .set_siren = unused_set_siren + .set_siren = unused_set_siren, + .read_som_gpio = unused_read_som_gpio }; diff --git a/board/boards/tres.h b/board/boards/tres.h index 095d9f8cc1..97dac7aa0f 100644 --- a/board/boards/tres.h +++ b/board/boards/tres.h @@ -19,10 +19,12 @@ void tres_set_bootkick(bool enabled){ } bool tres_ignition_prev = false; -void tres_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen, bool harness_inserted) { +bool tres_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen, bool harness_inserted) { UNUSED(usb_enum); + bool ret = false; if ((ignition && !tres_ignition_prev) || harness_inserted) { // enable bootkick on rising edge of ignition + ret = true; tres_set_bootkick(true); } else if (heartbeat_seen) { // disable once openpilot is up @@ -31,6 +33,7 @@ void tres_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen, bool har } tres_ignition_prev = ignition; + return ret; } void tres_set_fan_enabled(bool enabled) { diff --git a/board/boards/uno.h b/board/boards/uno.h index 9d019b37ad..ad773f6e49 100644 --- a/board/boards/uno.h +++ b/board/boards/uno.h @@ -126,7 +126,7 @@ void uno_set_can_mode(uint8_t mode){ } } -void uno_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen, bool harness_inserted) { +bool uno_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen, bool harness_inserted) { UNUSED(ignition); UNUSED(usb_enum); UNUSED(heartbeat_seen); @@ -136,6 +136,7 @@ void uno_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen, bool harn } else { uno_set_bootkick(false); } + return false; } bool uno_check_ignition(void){ diff --git a/board/boards/unused_funcs.h b/board/boards/unused_funcs.h index f5478f90d9..4e6214e6a7 100644 --- a/board/boards/unused_funcs.h +++ b/board/boards/unused_funcs.h @@ -22,11 +22,12 @@ uint32_t unused_read_current(void) { return 0U; } -void unused_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen, bool harness_inserted) { +bool unused_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen, bool harness_inserted) { UNUSED(ignition); UNUSED(usb_enum); UNUSED(heartbeat_seen); UNUSED(harness_inserted); + return false; } bool unused_read_som_gpio(void) { diff --git a/board/main.c b/board/main.c index 7fedf74073..e9e42e2be8 100644 --- a/board/main.c +++ b/board/main.c @@ -147,6 +147,8 @@ void __attribute__ ((noinline)) enable_fpu(void) { // called at 8Hz uint8_t loop_counter = 0U; uint8_t previous_harness_status = HARNESS_STATUS_NC; +uint32_t waiting_to_boot_count = 0; +bool waiting_to_boot = false; void tick_handler(void) { if (TICK_TIMER->SR != 0) { // siren @@ -187,9 +189,28 @@ void tick_handler(void) { logging_tick(); const bool recent_heartbeat = heartbeat_counter == 0U; - current_board->board_tick(check_started(), usb_enumerated, recent_heartbeat, ((harness.status != previous_harness_status) && (harness.status != HARNESS_STATUS_NC))); + const bool harness_inserted = (harness.status != previous_harness_status) && (harness.status != HARNESS_STATUS_NC); + const bool just_bootkicked = current_board->board_tick(check_started(), usb_enumerated, recent_heartbeat, harness_inserted); previous_harness_status = harness.status; + // log device boot time + const bool som_running = current_board->read_som_gpio(); + if (just_bootkicked && !som_running) { + log("bootkick"); + waiting_to_boot = true; + } + if (waiting_to_boot) { + if (som_running) { + log("device booted"); + waiting_to_boot = false; + } else if (waiting_to_boot_count == 10U) { + log("not booted after 10s"); + } else { + + } + waiting_to_boot_count += 1U; + } + // increase heartbeat counter and cap it at the uint32 limit if (heartbeat_counter < __UINT32_MAX__) { heartbeat_counter += 1U; diff --git a/tests/print_logs.py b/tests/print_logs.py new file mode 100755 index 0000000000..dedcdeddbf --- /dev/null +++ b/tests/print_logs.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 + +from panda import Panda + +if __name__ == "__main__": + p = Panda() + for l in p.get_logs(): + print(f"{l['id']:<6d} {l['timestamp']} {l['uptime']:6d} - {l['msg']}") diff --git a/tests/setup_device_ci.sh b/tests/setup_device_ci.sh index 6bf0e509fb..e6a8c048a7 100755 --- a/tests/setup_device_ci.sh +++ b/tests/setup_device_ci.sh @@ -60,6 +60,12 @@ git fetch --all git checkout -f master git reset --hard origin/master +# setup device/SOM state +SOM_ST_IO=49 +echo $SOM_ST_IO > /sys/class/gpio/export || true +echo out > /sys/class/gpio/gpio${SOM_ST_IO}/direction +echo 1 > /sys/class/gpio/gpio${SOM_ST_IO}/value + # checkout panda commit cd $SOURCE_DIR From 6d2b70a1475922983f59c57e68e68a6f61099268 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 13 Jul 2023 19:01:45 -0700 Subject: [PATCH 130/197] Subaru: Use macros for gen1/gen2 (#1505) * macro * revert these for first PR --- board/safety/safety_subaru.h | 38 +++++++++++++++++------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/board/safety/safety_subaru.h b/board/safety/safety_subaru.h index ff75ffa6e6..b88d41038a 100644 --- a/board/safety/safety_subaru.h +++ b/board/safety/safety_subaru.h @@ -38,40 +38,38 @@ const SteeringLimits SUBARU_GEN2_STEERING_LIMITS = { #define SUBARU_ALT_BUS 1 #define SUBARU_CAM_BUS 2 +#define SUBARU_COMMON_TX_MSGS(alt_bus) \ + {MSG_SUBARU_ES_LKAS, SUBARU_MAIN_BUS, 8}, \ + {MSG_SUBARU_ES_Distance, alt_bus, 8}, \ + {MSG_SUBARU_ES_DashStatus, SUBARU_MAIN_BUS, 8}, \ + {MSG_SUBARU_ES_LKAS_State, SUBARU_MAIN_BUS, 8}, \ + {MSG_SUBARU_ES_Infotainment, SUBARU_MAIN_BUS, 8}, \ + +#define SUBARU_COMMON_ADDR_CHECKS(alt_bus) \ + {.msg = {{MSG_SUBARU_Throttle, SUBARU_MAIN_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, \ + {.msg = {{MSG_SUBARU_Steering_Torque, SUBARU_MAIN_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, \ + {.msg = {{MSG_SUBARU_Wheel_Speeds, alt_bus, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, \ + {.msg = {{MSG_SUBARU_Brake_Status, alt_bus, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, \ + {.msg = {{MSG_SUBARU_CruiseControl, alt_bus, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 50000U}, { 0 }, { 0 }}}, \ + const CanMsg SUBARU_TX_MSGS[] = { - {MSG_SUBARU_ES_LKAS, SUBARU_MAIN_BUS, 8}, - {MSG_SUBARU_ES_Distance, SUBARU_MAIN_BUS, 8}, - {MSG_SUBARU_ES_DashStatus, SUBARU_MAIN_BUS, 8}, - {MSG_SUBARU_ES_LKAS_State, SUBARU_MAIN_BUS, 8}, - {MSG_SUBARU_ES_Infotainment, SUBARU_MAIN_BUS, 8}, + SUBARU_COMMON_TX_MSGS(SUBARU_MAIN_BUS) }; #define SUBARU_TX_MSGS_LEN (sizeof(SUBARU_TX_MSGS) / sizeof(SUBARU_TX_MSGS[0])) const CanMsg SUBARU_GEN2_TX_MSGS[] = { - {MSG_SUBARU_ES_LKAS, SUBARU_MAIN_BUS, 8}, - {MSG_SUBARU_ES_Distance, SUBARU_ALT_BUS, 8}, - {MSG_SUBARU_ES_DashStatus, SUBARU_MAIN_BUS, 8}, - {MSG_SUBARU_ES_LKAS_State, SUBARU_MAIN_BUS, 8}, - {MSG_SUBARU_ES_Infotainment, SUBARU_MAIN_BUS, 8} + SUBARU_COMMON_TX_MSGS(SUBARU_ALT_BUS) }; #define SUBARU_GEN2_TX_MSGS_LEN (sizeof(SUBARU_GEN2_TX_MSGS) / sizeof(SUBARU_GEN2_TX_MSGS[0])) AddrCheckStruct subaru_addr_checks[] = { - {.msg = {{MSG_SUBARU_Throttle, SUBARU_MAIN_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, - {.msg = {{MSG_SUBARU_Steering_Torque, SUBARU_MAIN_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{MSG_SUBARU_Wheel_Speeds, SUBARU_MAIN_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{MSG_SUBARU_Brake_Status, SUBARU_MAIN_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{MSG_SUBARU_CruiseControl, SUBARU_MAIN_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 50000U}, { 0 }, { 0 }}}, + SUBARU_COMMON_ADDR_CHECKS(SUBARU_MAIN_BUS) }; #define SUBARU_ADDR_CHECK_LEN (sizeof(subaru_addr_checks) / sizeof(subaru_addr_checks[0])) addr_checks subaru_rx_checks = {subaru_addr_checks, SUBARU_ADDR_CHECK_LEN}; AddrCheckStruct subaru_gen2_addr_checks[] = { - {.msg = {{MSG_SUBARU_Throttle, SUBARU_MAIN_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, - {.msg = {{MSG_SUBARU_Steering_Torque, SUBARU_MAIN_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{MSG_SUBARU_Wheel_Speeds, SUBARU_ALT_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{MSG_SUBARU_Brake_Status, SUBARU_ALT_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{MSG_SUBARU_CruiseControl, SUBARU_ALT_BUS, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 50000U}, { 0 }, { 0 }}}, + SUBARU_COMMON_ADDR_CHECKS(SUBARU_ALT_BUS) }; #define SUBARU_GEN2_ADDR_CHECK_LEN (sizeof(subaru_gen2_addr_checks) / sizeof(subaru_gen2_addr_checks[0])) addr_checks subaru_gen2_rx_checks = {subaru_gen2_addr_checks, SUBARU_GEN2_ADDR_CHECK_LEN}; From c8acdc6f61411f0d94908b40a3dbb8ae34c1dafd Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 14 Jul 2023 12:43:49 -0700 Subject: [PATCH 131/197] Subaru: cleanup tests (#1508) * macro * base tests * leave it same as ford * call base setupclass * Update common.py * Update test_hyundai_canfd.py * merge --- tests/safety/common.py | 3 +- tests/safety/test_hyundai_canfd.py | 4 +- tests/safety/test_subaru.py | 62 ++++++++++++++++++++++-------- 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/tests/safety/common.py b/tests/safety/common.py index d9743c4b07..24b3f9b726 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -335,6 +335,7 @@ class DriverTorqueSteeringSafetyTest(TorqueSteeringSafetyTestBase, abc.ABC): @classmethod def setUpClass(cls): + super().setUpClass() if cls.__name__ == "DriverTorqueSteeringSafetyTest": cls.safety = None raise unittest.SkipTest @@ -894,7 +895,7 @@ def test_tx_hook_on_wrong_safety_mode(self): continue if attr.startswith('TestToyota') and current_test.startswith('TestToyota'): continue - if {attr, current_test}.issubset({'TestSubaruSafety', 'TestSubaruGen2Safety'}): + if {attr, current_test}.issubset({'TestSubaruGen1Safety', 'TestSubaruGen2Safety'}): continue if {attr, current_test}.issubset({'TestVolkswagenPqSafety', 'TestVolkswagenPqStockSafety', 'TestVolkswagenPqLongSafety'}): continue diff --git a/tests/safety/test_hyundai_canfd.py b/tests/safety/test_hyundai_canfd.py index 4e49eab6a9..39b3845ba7 100755 --- a/tests/safety/test_hyundai_canfd.py +++ b/tests/safety/test_hyundai_canfd.py @@ -42,6 +42,7 @@ class TestHyundaiCanfdBase(HyundaiButtonBase, common.PandaSafetyTest, common.Dri @classmethod def setUpClass(cls): + super().setUpClass() if cls.__name__ == "TestHyundaiCanfdBase": cls.packer = None cls.safety = None @@ -95,7 +96,8 @@ class TestHyundaiCanfdHDA1Base(TestHyundaiCanfdBase): @classmethod def setUpClass(cls): - if cls.__name__ in ("TestHyundaiCanfdHDA1", "TestHyundaiCanfdHDA1AltButtons") or cls.__name__.endswith('Base'): + super().setUpClass() + if cls.__name__ in ("TestHyundaiCanfdHDA1", "TestHyundaiCanfdHDA1AltButtons"): cls.packer = None cls.safety = None raise unittest.SkipTest diff --git a/tests/safety/test_subaru.py b/tests/safety/test_subaru.py index fea72bfce9..f633ef193d 100755 --- a/tests/safety/test_subaru.py +++ b/tests/safety/test_subaru.py @@ -6,13 +6,39 @@ from panda.tests.safety.common import CANPackerPanda -class TestSubaruSafety(common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest): - TX_MSGS = [[0x122, 0], [0x221, 0], [0x321, 0], [0x322, 0], [0x323, 0]] - STANDSTILL_THRESHOLD = 0 # kph - RELAY_MALFUNCTION_ADDR = 0x122 - RELAY_MALFUNCTION_BUS = 0 - FWD_BLACKLISTED_ADDRS = {2: [0x122, 0x321, 0x322, 0x323]} - FWD_BUS_LOOKUP = {0: 2, 2: 0} +MSG_SUBARU_Brake_Status = 0x13c +MSG_SUBARU_CruiseControl = 0x240 +MSG_SUBARU_Throttle = 0x40 +MSG_SUBARU_Steering_Torque = 0x119 +MSG_SUBARU_Wheel_Speeds = 0x13a +MSG_SUBARU_ES_LKAS = 0x122 +MSG_SUBARU_ES_Brake = 0x220 +MSG_SUBARU_ES_Distance = 0x221 +MSG_SUBARU_ES_Status = 0x222 +MSG_SUBARU_ES_DashStatus = 0x321 +MSG_SUBARU_ES_LKAS_State = 0x322 +MSG_SUBARU_ES_Infotainment = 0x323 + +SUBARU_MAIN_BUS = 0 +SUBARU_ALT_BUS = 1 +SUBARU_CAM_BUS = 2 + + +def lkas_tx_msgs(alt_bus): + return [[MSG_SUBARU_ES_LKAS, SUBARU_MAIN_BUS], + [MSG_SUBARU_ES_Distance, alt_bus], + [MSG_SUBARU_ES_DashStatus, SUBARU_MAIN_BUS], + [MSG_SUBARU_ES_LKAS_State, SUBARU_MAIN_BUS], + [MSG_SUBARU_ES_Infotainment, SUBARU_MAIN_BUS]] + + +class TestSubaruSafetyBase(common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest): + FLAGS = 0 + STANDSTILL_THRESHOLD = 0 # kph + RELAY_MALFUNCTION_ADDR = MSG_SUBARU_ES_LKAS + RELAY_MALFUNCTION_BUS = SUBARU_MAIN_BUS + FWD_BUS_LOOKUP = {SUBARU_MAIN_BUS: SUBARU_CAM_BUS, SUBARU_CAM_BUS: SUBARU_MAIN_BUS} + FWD_BLACKLISTED_ADDRS = {SUBARU_CAM_BUS: [MSG_SUBARU_ES_LKAS, MSG_SUBARU_ES_DashStatus, MSG_SUBARU_ES_LKAS_State, MSG_SUBARU_ES_Infotainment]} MAX_RATE_UP = 50 MAX_RATE_DOWN = 70 @@ -24,12 +50,12 @@ class TestSubaruSafety(common.PandaSafetyTest, common.DriverTorqueSteeringSafety DRIVER_TORQUE_ALLOWANCE = 60 DRIVER_TORQUE_FACTOR = 50 - ALT_BUS = 0 + ALT_BUS = SUBARU_MAIN_BUS def setUp(self): self.packer = CANPackerPanda("subaru_global_2017_generated") self.safety = libpanda_py.libpanda - self.safety.set_safety_hooks(Panda.SAFETY_SUBARU, 0) + self.safety.set_safety_hooks(Panda.SAFETY_SUBARU, self.FLAGS) self.safety.init_tests() def _set_prev_torque(self, t): @@ -63,19 +89,21 @@ def _pcm_status_msg(self, enable): return self.packer.make_can_msg_panda("CruiseControl", self.ALT_BUS, values) -class TestSubaruGen2Safety(TestSubaruSafety): - TX_MSGS = [[0x122, 0], [0x221, 1], [0x321, 0], [0x322, 0], [0x323, 0]] - ALT_BUS = 1 +class TestSubaruGen2SafetyBase(TestSubaruSafetyBase): + ALT_BUS = SUBARU_ALT_BUS MAX_RATE_UP = 40 MAX_RATE_DOWN = 40 MAX_TORQUE = 1000 - def setUp(self): - self.packer = CANPackerPanda("subaru_global_2017_generated") - self.safety = libpanda_py.libpanda - self.safety.set_safety_hooks(Panda.SAFETY_SUBARU, Panda.FLAG_SUBARU_GEN2) - self.safety.init_tests() +class TestSubaruGen1Safety(TestSubaruSafetyBase): + FLAGS = 0 + TX_MSGS = lkas_tx_msgs(SUBARU_MAIN_BUS) + + +class TestSubaruGen2Safety(TestSubaruGen2SafetyBase): + FLAGS = Panda.FLAG_SUBARU_GEN2 + TX_MSGS = lkas_tx_msgs(SUBARU_ALT_BUS) if __name__ == "__main__": From 8e31711aa2b4a49b5055350c33601d6ea2d64062 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 14 Jul 2023 18:21:28 -0700 Subject: [PATCH 132/197] safety helpers: cruise engaged prev setter (#1511) * bbb * Apply suggestions from code review --- tests/libpanda/safety_helpers.h | 4 ++++ tests/libpanda/safety_helpers.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/tests/libpanda/safety_helpers.h b/tests/libpanda/safety_helpers.h index f02c4da343..5409168a2e 100644 --- a/tests/libpanda/safety_helpers.h +++ b/tests/libpanda/safety_helpers.h @@ -71,6 +71,10 @@ bool get_cruise_engaged_prev(void){ return cruise_engaged_prev; } +void set_cruise_engaged_prev(bool engaged){ + cruise_engaged_prev = engaged; +} + bool get_vehicle_moving(void){ return vehicle_moving; } diff --git a/tests/libpanda/safety_helpers.py b/tests/libpanda/safety_helpers.py index 23f642b20c..252fbf3cb8 100644 --- a/tests/libpanda/safety_helpers.py +++ b/tests/libpanda/safety_helpers.py @@ -35,6 +35,7 @@ def setup_safety_helpers(ffi): int get_angle_meas_max(void); bool get_cruise_engaged_prev(void); + void set_cruise_engaged_prev(bool engaged); bool get_vehicle_moving(void); int get_hw_type(void); void set_timer(uint32_t t); @@ -83,6 +84,7 @@ def get_angle_meas_min(self) -> int: ... def get_angle_meas_max(self) -> int: ... def get_cruise_engaged_prev(self) -> bool: ... + def set_cruise_engaged_prev(self, enabled: bool) -> None: ... def get_vehicle_moving(self) -> bool: ... def get_hw_type(self) -> int: ... def set_timer(self, t: int) -> None: ... From ad9ec716ac4b5f420c7c33e687dc09a495de9872 Mon Sep 17 00:00:00 2001 From: Igor Biletskyy Date: Fri, 14 Jul 2023 19:00:43 -0700 Subject: [PATCH 133/197] H7: CAN core reset on no ACK and high transmit error counter (#1502) * init * comment --- board/drivers/fdcan.h | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/board/drivers/fdcan.h b/board/drivers/fdcan.h index 3b43128a03..118d6c8ca2 100644 --- a/board/drivers/fdcan.h +++ b/board/drivers/fdcan.h @@ -17,6 +17,8 @@ uint8_t can_irq_number[3][2] = { { FDCAN3_IT0_IRQn, FDCAN3_IT1_IRQn }, }; +#define CAN_ACK_ERROR 3U + bool can_set_speed(uint8_t can_number) { bool ret = true; FDCAN_GlobalTypeDef *CANx = CANIF_FROM_CAN_NUM(can_number); @@ -68,20 +70,22 @@ void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg) { if (ir_reg != 0U) { + // Clear error interrupts + CANx->IR |= (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EP | FDCAN_IR_BO | FDCAN_IR_RF0L); can_health[can_number].total_error_cnt += 1U; + // Check for RX FIFO overflow if ((ir_reg & (FDCAN_IR_RF0L)) != 0) { can_health[can_number].total_rx_lost_cnt += 1U; } - // Reset CAN core when CEL couter reaches at least 100 errors - if (((ir_reg & (FDCAN_IR_PED | FDCAN_IR_PEA)) != 0) && (((ecr_reg & FDCAN_ECR_CEL) >> FDCAN_ECR_CEL_Pos) >= 100U)) { + // While multiplexing between buses 1 and 3 we are getting ACK errors that overwhelm CAN core + // By resseting CAN core when no ACK is detected for a while(until TEC counter reaches 127) it can recover faster + if (((can_health[can_number].last_error == CAN_ACK_ERROR) || (can_health[can_number].last_data_error == CAN_ACK_ERROR)) && (can_health[can_number].transmit_error_cnt > 127U)) { can_health[can_number].can_core_reset_cnt += 1U; - can_health[can_number].total_tx_lost_cnt += (FDCAN_TX_FIFO_EL_CNT - (FDCAN_TXFQS_TFFL & FDCAN_TXFQS_TFFL_Msk)); // TX FIFO msgs will be lost after reset + can_health[can_number].total_tx_lost_cnt += (FDCAN_TX_FIFO_EL_CNT - (CANx->TXFQS & FDCAN_TXFQS_TFFL)); // TX FIFO msgs will be lost after reset llcan_clear_send(CANx); } - // Clear error interrupts - CANx->IR |= (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EP | FDCAN_IR_BO | FDCAN_IR_RF0L); - } - EXIT_CRITICAL(); + } + EXIT_CRITICAL(); } // ***************************** CAN ***************************** From a9a322700831190c3d7fb9f7343b3cd7da380a5c Mon Sep 17 00:00:00 2001 From: Igor Biletskyy Date: Fri, 14 Jul 2023 19:36:04 -0700 Subject: [PATCH 134/197] can_health: remove enter and exit critical calls from health updater (#1513) init --- board/drivers/bxcan.h | 2 -- board/drivers/fdcan.h | 5 +---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/board/drivers/bxcan.h b/board/drivers/bxcan.h index 7feec4cfbf..478bd5858b 100644 --- a/board/drivers/bxcan.h +++ b/board/drivers/bxcan.h @@ -110,9 +110,7 @@ void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg) { // ***************************** CAN ***************************** // CANx_SCE IRQ Handler void can_sce(uint8_t can_number) { - ENTER_CRITICAL(); update_can_health_pkt(can_number, 1U); - EXIT_CRITICAL(); } // CANx_TX IRQ Handler diff --git a/board/drivers/fdcan.h b/board/drivers/fdcan.h index 118d6c8ca2..7cab232d36 100644 --- a/board/drivers/fdcan.h +++ b/board/drivers/fdcan.h @@ -41,8 +41,6 @@ void can_set_gmlan(uint8_t bus) { } void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg) { - ENTER_CRITICAL(); - FDCAN_GlobalTypeDef *CANx = CANIF_FROM_CAN_NUM(can_number); uint32_t psr_reg = CANx->PSR; uint32_t ecr_reg = CANx->ECR; @@ -84,8 +82,7 @@ void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg) { can_health[can_number].total_tx_lost_cnt += (FDCAN_TX_FIFO_EL_CNT - (CANx->TXFQS & FDCAN_TXFQS_TFFL)); // TX FIFO msgs will be lost after reset llcan_clear_send(CANx); } - } - EXIT_CRITICAL(); + } } // ***************************** CAN ***************************** From 70c77631240eeaa4f83c2cb5e23a58d13d299dfe Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 15 Jul 2023 12:26:24 -0700 Subject: [PATCH 135/197] SPI kernel driver (#1497) * kernel driver * fix checksum check * cleanup --------- Co-authored-by: Comma Device --- .gitignore | 1 + drivers/spi/.gitignore | 7 + drivers/spi/Makefile | 14 + drivers/spi/load.sh | 27 ++ drivers/spi/patch | 33 ++ drivers/spi/pull-src.sh | 12 + drivers/spi/spi_panda.h | 160 +++++++ drivers/spi/spidev_panda.c | 891 +++++++++++++++++++++++++++++++++++++ python/spi.py | 48 +- 9 files changed, 1192 insertions(+), 1 deletion(-) create mode 100644 drivers/spi/.gitignore create mode 100644 drivers/spi/Makefile create mode 100755 drivers/spi/load.sh create mode 100644 drivers/spi/patch create mode 100755 drivers/spi/pull-src.sh create mode 100644 drivers/spi/spi_panda.h create mode 100644 drivers/spi/spidev_panda.c diff --git a/.gitignore b/.gitignore index 403d034ee1..a3f6520bfd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*.tmp *.pyc .*.swp .*.swo diff --git a/drivers/spi/.gitignore b/drivers/spi/.gitignore new file mode 100644 index 0000000000..49dc09d89f --- /dev/null +++ b/drivers/spi/.gitignore @@ -0,0 +1,7 @@ +spidev.c +*.ko +*.cmd +*.mod +*.symvers +*.order +*.mod.c diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile new file mode 100644 index 0000000000..9a2a0abf67 --- /dev/null +++ b/drivers/spi/Makefile @@ -0,0 +1,14 @@ +obj-m += spidev_panda.o + +KDIR := /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +# GCC9 bug, apply kernel patch instead? +# https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=0b999ae3614d09d97a1575936bcee884f912b10e +ccflags-y := -Wno-missing-attributes + +default: + $(MAKE) -C $(KDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KDIR) M=$(PWD) clean diff --git a/drivers/spi/load.sh b/drivers/spi/load.sh new file mode 100755 index 0000000000..b8efdbf708 --- /dev/null +++ b/drivers/spi/load.sh @@ -0,0 +1,27 @@ +#!/bin/bash +set -e + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +cd $DIR + +make -j8 + +sudo su -c "echo spi0.0 > /sys/bus/spi/drivers/spidev/unbind" || true + +sudo dmesg -C + +#sudo rmmod -f spidev_panda +sudo rmmod spidev_panda || true +sudo insmod spidev_panda.ko + +sudo su -c "echo 'file $DIR/spidev_panda.c +p' > /sys/kernel/debug/dynamic_debug/control" +sudo su -c "echo 'file $DIR/spi_panda.h +p' > /sys/kernel/debug/dynamic_debug/control" + +sudo lsmod + +echo "loaded" +ls -la /dev/spi* +sudo chmod 666 /dev/spi* +ipython -c "from panda import Panda; print(Panda.list())" +KERN=1 ipython -c "from panda import Panda; print(Panda.list())" +dmesg diff --git a/drivers/spi/patch b/drivers/spi/patch new file mode 100644 index 0000000000..a146303424 --- /dev/null +++ b/drivers/spi/patch @@ -0,0 +1,33 @@ +53c53,54 +< #define SPIDEV_MAJOR 153 /* assigned */ +--- +> int SPIDEV_MAJOR = 0; +> //#define SPIDEV_MAJOR 153 /* assigned */ +354a356,358 +> +> #include "spi_panda.h" +> +413,414c417,419 +< retval = __put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0, +< (__u8 __user *)arg); +--- +> retval = panda_transfer(spidev, spi, arg); +> //retval = __put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0, +> // (__u8 __user *)arg); +697,698d701 +< { .compatible = "rohm,dh2228fv" }, +< { .compatible = "lineartechnology,ltc2488" }, +831c834 +< .name = "spidev", +--- +> .name = "spidev_panda", +856c859 +< status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops); +--- +> status = register_chrdev(0, "spi", &spidev_fops); +860c863,865 +< spidev_class = class_create(THIS_MODULE, "spidev"); +--- +> SPIDEV_MAJOR = status; +> +> spidev_class = class_create(THIS_MODULE, "spidev_panda"); diff --git a/drivers/spi/pull-src.sh b/drivers/spi/pull-src.sh new file mode 100755 index 0000000000..f74b94b9e7 --- /dev/null +++ b/drivers/spi/pull-src.sh @@ -0,0 +1,12 @@ +#!/usr/bin/bash +set -e + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +cd $DIR + +rm -f spidev.c +wget https://raw.githubusercontent.com/commaai/agnos-kernel-sdm845/master/drivers/spi/spidev.c + +# diff spidev.c spidev_panda.c > patch +# git diff --no-index spidev.c spidev_panda.c +patch -o spidev_panda.c spidev.c -i patch diff --git a/drivers/spi/spi_panda.h b/drivers/spi/spi_panda.h new file mode 100644 index 0000000000..c7da681fc2 --- /dev/null +++ b/drivers/spi/spi_panda.h @@ -0,0 +1,160 @@ +#include +#include +#include + +#define SPI_SYNC 0x5AU +#define SPI_HACK 0x79U +#define SPI_DACK 0x85U +#define SPI_NACK 0x1FU +#define SPI_CHECKSUM_START 0xABU + +struct __attribute__((packed)) spi_header { + u8 sync; + u8 endpoint; + uint16_t tx_len; + uint16_t max_rx_len; +}; + +struct spi_panda_transfer { + __u64 rx_buf; + __u64 tx_buf; + __u32 tx_length; + __u32 rx_length_max; + __u32 timeout; + __u8 endpoint; + __u8 expect_disconnect; +}; + +static u8 panda_calc_checksum(u8 *buf, u16 length) { + int i; + u8 checksum = SPI_CHECKSUM_START; + for (i = 0U; i < length; i++) { + checksum ^= buf[i]; + } + return checksum; +} + +static long panda_wait_for_ack(struct spidev_data *spidev, u8 ack_val, u8 length) { + int i; + int ret; + for (i = 0; i < 1000; i++) { + ret = spidev_sync_read(spidev, length); + if (ret < 0) { + return ret; + } + + if (spidev->rx_buffer[0] == ack_val) { + return 0; + } else if (spidev->rx_buffer[0] == SPI_NACK) { + return -2; + } + if (i > 20) usleep_range(10, 20); + } + return -1; +} + +static long panda_transfer_raw(struct spidev_data *spidev, struct spi_device *spi, unsigned long arg) { + u16 rx_len; + long retval = -1; + struct spi_header header; + struct spi_panda_transfer pt; + + struct spi_transfer t = { + .len = 0, + .tx_buf = spidev->tx_buffer, + .rx_buf = spidev->rx_buffer, + .speed_hz = spidev->spi->max_speed_hz, + }; + + struct spi_message m; + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + // read struct from user + if (!access_ok(VERIFY_WRITE, arg, sizeof(pt))) { + return -1; + } + if (copy_from_user(&pt, (void __user *)arg, sizeof(pt))) { + return -1; + } + dev_dbg(&spi->dev, "ep: %d, tx len: %d\n", pt.endpoint, pt.tx_length); + + // send header + header.sync = 0x5a; + header.endpoint = pt.endpoint; + header.tx_len = pt.tx_length; + header.max_rx_len = pt.rx_length_max; + memcpy(spidev->tx_buffer, &header, sizeof(header)); + spidev->tx_buffer[sizeof(header)] = panda_calc_checksum(spidev->tx_buffer, sizeof(header)); + + t.len = sizeof(header) + 1; + retval = spidev_sync(spidev, &m); + if (retval < 0) { + dev_dbg(&spi->dev, "spi xfer failed %ld\n", retval); + return retval; + } + + // wait for ACK + retval = panda_wait_for_ack(spidev, SPI_HACK, 1); + if (retval < 0) { + dev_dbg(&spi->dev, "no header ack %ld\n", retval); + return retval; + } + + // send data + dev_dbg(&spi->dev, "sending data\n"); + retval = copy_from_user(spidev->tx_buffer, (const u8 __user *)(uintptr_t)pt.tx_buf, pt.tx_length); + spidev->tx_buffer[pt.tx_length] = panda_calc_checksum(spidev->tx_buffer, pt.tx_length); + t.len = pt.tx_length + 1; + retval = spidev_sync(spidev, &m); + + if (pt.expect_disconnect) { + return 0; + } + + // wait for ACK + retval = panda_wait_for_ack(spidev, SPI_DACK, 3); + if (retval < 0) { + dev_dbg(&spi->dev, "no data ack\n"); + return retval; + } + + // get response + t.rx_buf = spidev->rx_buffer + 3; + rx_len = (spidev->rx_buffer[2] << 8) | (spidev->rx_buffer[1]); + dev_dbg(&spi->dev, "rx len %u\n", rx_len); + if (rx_len > pt.rx_length_max) { + dev_dbg(&spi->dev, "RX len greater than max\n"); + return -1; + } + + // do the read + t.len = rx_len + 1; + retval = spidev_sync(spidev, &m); + if (retval < 0) { + dev_dbg(&spi->dev, "spi xfer failed %ld\n", retval); + return retval; + } + if (panda_calc_checksum(spidev->rx_buffer, 3 + rx_len + 1) != 0) { + dev_dbg(&spi->dev, "bad checksum\n"); + return -1; + } + + retval = copy_to_user((u8 __user *)(uintptr_t)pt.rx_buf, spidev->rx_buffer + 3, rx_len); + + return rx_len; +} + +static long panda_transfer(struct spidev_data *spidev, struct spi_device *spi, unsigned long arg) { + int i; + int ret; + dev_dbg(&spi->dev, "=== XFER start ===\n"); + for (i = 0; i < 20; i++) { + ret = panda_transfer_raw(spidev, spi, arg); + if (ret >= 0) { + break; + } + } + dev_dbg(&spi->dev, "took %d tries\n", i+1); + return ret; +} diff --git a/drivers/spi/spidev_panda.c b/drivers/spi/spidev_panda.c new file mode 100644 index 0000000000..f21fe3387b --- /dev/null +++ b/drivers/spi/spidev_panda.c @@ -0,0 +1,891 @@ +/* + * Simple synchronous userspace interface to SPI devices + * + * Copyright (C) 2006 SWAPP + * Andrea Paterniani + * Copyright (C) 2007 David Brownell (simplification, cleanup) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + + +/* + * This supports access to SPI devices using normal userspace I/O calls. + * Note that while traditional UNIX/POSIX I/O semantics are half duplex, + * and often mask message boundaries, full SPI support requires full duplex + * transfers. There are several kinds of internal message boundaries to + * handle chipselect management and other protocol options. + * + * SPI has a character major number assigned. We allocate minor numbers + * dynamically using a bitmask. You must use hotplug tools, such as udev + * (or mdev with busybox) to create and destroy the /dev/spidevB.C device + * nodes, since there is no fixed association of minor numbers with any + * particular SPI bus or device. + */ +int SPIDEV_MAJOR = 0; +//#define SPIDEV_MAJOR 153 /* assigned */ +#define N_SPI_MINORS 32 /* ... up to 256 */ + +static DECLARE_BITMAP(minors, N_SPI_MINORS); + + +/* Bit masks for spi_device.mode management. Note that incorrect + * settings for some settings can cause *lots* of trouble for other + * devices on a shared bus: + * + * - CS_HIGH ... this device will be active when it shouldn't be + * - 3WIRE ... when active, it won't behave as it should + * - NO_CS ... there will be no explicit message boundaries; this + * is completely incompatible with the shared bus model + * - READY ... transfers may proceed when they shouldn't. + * + * REVISIT should changing those flags be privileged? + */ +#define SPI_MODE_MASK (SPI_CPHA | SPI_CPOL | SPI_CS_HIGH \ + | SPI_LSB_FIRST | SPI_3WIRE | SPI_LOOP \ + | SPI_NO_CS | SPI_READY | SPI_TX_DUAL \ + | SPI_TX_QUAD | SPI_RX_DUAL | SPI_RX_QUAD) + +struct spidev_data { + dev_t devt; + spinlock_t spi_lock; + struct spi_device *spi; + struct list_head device_entry; + + /* TX/RX buffers are NULL unless this device is open (users > 0) */ + struct mutex buf_lock; + unsigned users; + u8 *tx_buffer; + u8 *rx_buffer; + u32 speed_hz; +}; + +static LIST_HEAD(device_list); +static DEFINE_MUTEX(device_list_lock); + +static unsigned bufsiz = 4096; +module_param(bufsiz, uint, S_IRUGO); +MODULE_PARM_DESC(bufsiz, "data bytes in biggest supported SPI message"); + +/*-------------------------------------------------------------------------*/ + +static ssize_t +spidev_sync(struct spidev_data *spidev, struct spi_message *message) +{ + DECLARE_COMPLETION_ONSTACK(done); + int status; + struct spi_device *spi; + + spin_lock_irq(&spidev->spi_lock); + spi = spidev->spi; + spin_unlock_irq(&spidev->spi_lock); + + if (spi == NULL) + status = -ESHUTDOWN; + else + status = spi_sync(spi, message); + + if (status == 0) + status = message->actual_length; + + return status; +} + +static inline ssize_t +spidev_sync_write(struct spidev_data *spidev, size_t len) +{ + struct spi_transfer t = { + .tx_buf = spidev->tx_buffer, + .len = len, + .speed_hz = spidev->speed_hz, + }; + struct spi_message m; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + return spidev_sync(spidev, &m); +} + +static inline ssize_t +spidev_sync_read(struct spidev_data *spidev, size_t len) +{ + struct spi_transfer t = { + .rx_buf = spidev->rx_buffer, + .len = len, + .speed_hz = spidev->speed_hz, + }; + struct spi_message m; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + return spidev_sync(spidev, &m); +} + +/*-------------------------------------------------------------------------*/ + +/* Read-only message with current device setup */ +static ssize_t +spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) +{ + struct spidev_data *spidev; + ssize_t status = 0; + + /* chipselect only toggles at start or end of operation */ + if (count > bufsiz) + return -EMSGSIZE; + + spidev = filp->private_data; + + mutex_lock(&spidev->buf_lock); + status = spidev_sync_read(spidev, count); + if (status > 0) { + unsigned long missing; + + missing = copy_to_user(buf, spidev->rx_buffer, status); + if (missing == status) + status = -EFAULT; + else + status = status - missing; + } + mutex_unlock(&spidev->buf_lock); + + return status; +} + +/* Write-only message with current device setup */ +static ssize_t +spidev_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + struct spidev_data *spidev; + ssize_t status = 0; + unsigned long missing; + + /* chipselect only toggles at start or end of operation */ + if (count > bufsiz) + return -EMSGSIZE; + + spidev = filp->private_data; + + mutex_lock(&spidev->buf_lock); + missing = copy_from_user(spidev->tx_buffer, buf, count); + if (missing == 0) + status = spidev_sync_write(spidev, count); + else + status = -EFAULT; + mutex_unlock(&spidev->buf_lock); + + return status; +} + +static int spidev_message(struct spidev_data *spidev, + struct spi_ioc_transfer *u_xfers, unsigned n_xfers) +{ + struct spi_message msg; + struct spi_transfer *k_xfers; + struct spi_transfer *k_tmp; + struct spi_ioc_transfer *u_tmp; + unsigned n, total, tx_total, rx_total; + u8 *tx_buf, *rx_buf; + int status = -EFAULT; + + spi_message_init(&msg); + k_xfers = kcalloc(n_xfers, sizeof(*k_tmp), GFP_KERNEL); + if (k_xfers == NULL) + return -ENOMEM; + + /* Construct spi_message, copying any tx data to bounce buffer. + * We walk the array of user-provided transfers, using each one + * to initialize a kernel version of the same transfer. + */ + tx_buf = spidev->tx_buffer; + rx_buf = spidev->rx_buffer; + total = 0; + tx_total = 0; + rx_total = 0; + for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers; + n; + n--, k_tmp++, u_tmp++) { + k_tmp->len = u_tmp->len; + + total += k_tmp->len; + /* Since the function returns the total length of transfers + * on success, restrict the total to positive int values to + * avoid the return value looking like an error. Also check + * each transfer length to avoid arithmetic overflow. + */ + if (total > INT_MAX || k_tmp->len > INT_MAX) { + status = -EMSGSIZE; + goto done; + } + + if (u_tmp->rx_buf) { + /* this transfer needs space in RX bounce buffer */ + rx_total += k_tmp->len; + if (rx_total > bufsiz) { + status = -EMSGSIZE; + goto done; + } + k_tmp->rx_buf = rx_buf; + if (!access_ok(VERIFY_WRITE, (u8 __user *) + (uintptr_t) u_tmp->rx_buf, + u_tmp->len)) + goto done; + rx_buf += k_tmp->len; + } + if (u_tmp->tx_buf) { + /* this transfer needs space in TX bounce buffer */ + tx_total += k_tmp->len; + if (tx_total > bufsiz) { + status = -EMSGSIZE; + goto done; + } + k_tmp->tx_buf = tx_buf; + if (copy_from_user(tx_buf, (const u8 __user *) + (uintptr_t) u_tmp->tx_buf, + u_tmp->len)) + goto done; + tx_buf += k_tmp->len; + } + + k_tmp->cs_change = !!u_tmp->cs_change; + k_tmp->tx_nbits = u_tmp->tx_nbits; + k_tmp->rx_nbits = u_tmp->rx_nbits; + k_tmp->bits_per_word = u_tmp->bits_per_word; + k_tmp->delay_usecs = u_tmp->delay_usecs; + k_tmp->speed_hz = u_tmp->speed_hz; + if (!k_tmp->speed_hz) + k_tmp->speed_hz = spidev->speed_hz; +#ifdef VERBOSE + dev_dbg(&spidev->spi->dev, + " xfer len %u %s%s%s%dbits %u usec %uHz\n", + u_tmp->len, + u_tmp->rx_buf ? "rx " : "", + u_tmp->tx_buf ? "tx " : "", + u_tmp->cs_change ? "cs " : "", + u_tmp->bits_per_word ? : spidev->spi->bits_per_word, + u_tmp->delay_usecs, + u_tmp->speed_hz ? : spidev->spi->max_speed_hz); +#endif + spi_message_add_tail(k_tmp, &msg); + } + + status = spidev_sync(spidev, &msg); + if (status < 0) + goto done; + + /* copy any rx data out of bounce buffer */ + rx_buf = spidev->rx_buffer; + for (n = n_xfers, u_tmp = u_xfers; n; n--, u_tmp++) { + if (u_tmp->rx_buf) { + if (__copy_to_user((u8 __user *) + (uintptr_t) u_tmp->rx_buf, rx_buf, + u_tmp->len)) { + status = -EFAULT; + goto done; + } + rx_buf += u_tmp->len; + } + } + status = total; + +done: + kfree(k_xfers); + return status; +} + +static struct spi_ioc_transfer * +spidev_get_ioc_message(unsigned int cmd, struct spi_ioc_transfer __user *u_ioc, + unsigned *n_ioc) +{ + struct spi_ioc_transfer *ioc; + u32 tmp; + + /* Check type, command number and direction */ + if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC + || _IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0)) + || _IOC_DIR(cmd) != _IOC_WRITE) + return ERR_PTR(-ENOTTY); + + tmp = _IOC_SIZE(cmd); + if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) + return ERR_PTR(-EINVAL); + *n_ioc = tmp / sizeof(struct spi_ioc_transfer); + if (*n_ioc == 0) + return NULL; + + /* copy into scratch area */ + ioc = kmalloc(tmp, GFP_KERNEL); + if (!ioc) + return ERR_PTR(-ENOMEM); + if (__copy_from_user(ioc, u_ioc, tmp)) { + kfree(ioc); + return ERR_PTR(-EFAULT); + } + return ioc; +} + + +#include "spi_panda.h" + +static long +spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + int err = 0; + int retval = 0; + struct spidev_data *spidev; + struct spi_device *spi; + u32 tmp; + unsigned n_ioc; + struct spi_ioc_transfer *ioc; + + /* Check type and command number */ + if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC) + return -ENOTTY; + + /* Check access direction once here; don't repeat below. + * IOC_DIR is from the user perspective, while access_ok is + * from the kernel perspective; so they look reversed. + */ + if (_IOC_DIR(cmd) & _IOC_READ) + err = !access_ok(VERIFY_WRITE, + (void __user *)arg, _IOC_SIZE(cmd)); + if (err == 0 && _IOC_DIR(cmd) & _IOC_WRITE) + err = !access_ok(VERIFY_READ, + (void __user *)arg, _IOC_SIZE(cmd)); + if (err) + return -EFAULT; + + /* guard against device removal before, or while, + * we issue this ioctl. + */ + spidev = filp->private_data; + spin_lock_irq(&spidev->spi_lock); + spi = spi_dev_get(spidev->spi); + spin_unlock_irq(&spidev->spi_lock); + + if (spi == NULL) + return -ESHUTDOWN; + + /* use the buffer lock here for triple duty: + * - prevent I/O (from us) so calling spi_setup() is safe; + * - prevent concurrent SPI_IOC_WR_* from morphing + * data fields while SPI_IOC_RD_* reads them; + * - SPI_IOC_MESSAGE needs the buffer locked "normally". + */ + mutex_lock(&spidev->buf_lock); + + switch (cmd) { + /* read requests */ + case SPI_IOC_RD_MODE: + retval = __put_user(spi->mode & SPI_MODE_MASK, + (__u8 __user *)arg); + break; + case SPI_IOC_RD_MODE32: + retval = __put_user(spi->mode & SPI_MODE_MASK, + (__u32 __user *)arg); + break; + case SPI_IOC_RD_LSB_FIRST: + retval = panda_transfer(spidev, spi, arg); + //retval = __put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0, + // (__u8 __user *)arg); + break; + case SPI_IOC_RD_BITS_PER_WORD: + retval = __put_user(spi->bits_per_word, (__u8 __user *)arg); + break; + case SPI_IOC_RD_MAX_SPEED_HZ: + retval = __put_user(spidev->speed_hz, (__u32 __user *)arg); + break; + + /* write requests */ + case SPI_IOC_WR_MODE: + case SPI_IOC_WR_MODE32: + if (cmd == SPI_IOC_WR_MODE) + retval = __get_user(tmp, (u8 __user *)arg); + else + retval = __get_user(tmp, (u32 __user *)arg); + if (retval == 0) { + u32 save = spi->mode; + + if (tmp & ~SPI_MODE_MASK) { + retval = -EINVAL; + break; + } + + tmp |= spi->mode & ~SPI_MODE_MASK; + spi->mode = (u16)tmp; + retval = spi_setup(spi); + if (retval < 0) + spi->mode = save; + else + dev_dbg(&spi->dev, "spi mode %x\n", tmp); + } + break; + case SPI_IOC_WR_LSB_FIRST: + retval = __get_user(tmp, (__u8 __user *)arg); + if (retval == 0) { + u32 save = spi->mode; + + if (tmp) + spi->mode |= SPI_LSB_FIRST; + else + spi->mode &= ~SPI_LSB_FIRST; + retval = spi_setup(spi); + if (retval < 0) + spi->mode = save; + else + dev_dbg(&spi->dev, "%csb first\n", + tmp ? 'l' : 'm'); + } + break; + case SPI_IOC_WR_BITS_PER_WORD: + retval = __get_user(tmp, (__u8 __user *)arg); + if (retval == 0) { + u8 save = spi->bits_per_word; + + spi->bits_per_word = tmp; + retval = spi_setup(spi); + if (retval < 0) + spi->bits_per_word = save; + else + dev_dbg(&spi->dev, "%d bits per word\n", tmp); + } + break; + case SPI_IOC_WR_MAX_SPEED_HZ: + retval = __get_user(tmp, (__u32 __user *)arg); + if (retval == 0) { + u32 save = spi->max_speed_hz; + + spi->max_speed_hz = tmp; + retval = spi_setup(spi); + if (retval >= 0) + spidev->speed_hz = tmp; + else + dev_dbg(&spi->dev, "%d Hz (max)\n", tmp); + spi->max_speed_hz = save; + } + break; + + default: + /* segmented and/or full-duplex I/O request */ + /* Check message and copy into scratch area */ + ioc = spidev_get_ioc_message(cmd, + (struct spi_ioc_transfer __user *)arg, &n_ioc); + if (IS_ERR(ioc)) { + retval = PTR_ERR(ioc); + break; + } + if (!ioc) + break; /* n_ioc is also 0 */ + + /* translate to spi_message, execute */ + retval = spidev_message(spidev, ioc, n_ioc); + kfree(ioc); + break; + } + + mutex_unlock(&spidev->buf_lock); + spi_dev_put(spi); + return retval; +} + +#ifdef CONFIG_COMPAT +static long +spidev_compat_ioc_message(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct spi_ioc_transfer __user *u_ioc; + int retval = 0; + struct spidev_data *spidev; + struct spi_device *spi; + unsigned n_ioc, n; + struct spi_ioc_transfer *ioc; + + u_ioc = (struct spi_ioc_transfer __user *) compat_ptr(arg); + if (!access_ok(VERIFY_READ, u_ioc, _IOC_SIZE(cmd))) + return -EFAULT; + + /* guard against device removal before, or while, + * we issue this ioctl. + */ + spidev = filp->private_data; + spin_lock_irq(&spidev->spi_lock); + spi = spi_dev_get(spidev->spi); + spin_unlock_irq(&spidev->spi_lock); + + if (spi == NULL) + return -ESHUTDOWN; + + /* SPI_IOC_MESSAGE needs the buffer locked "normally" */ + mutex_lock(&spidev->buf_lock); + + /* Check message and copy into scratch area */ + ioc = spidev_get_ioc_message(cmd, u_ioc, &n_ioc); + if (IS_ERR(ioc)) { + retval = PTR_ERR(ioc); + goto done; + } + if (!ioc) + goto done; /* n_ioc is also 0 */ + + /* Convert buffer pointers */ + for (n = 0; n < n_ioc; n++) { + ioc[n].rx_buf = (uintptr_t) compat_ptr(ioc[n].rx_buf); + ioc[n].tx_buf = (uintptr_t) compat_ptr(ioc[n].tx_buf); + } + + /* translate to spi_message, execute */ + retval = spidev_message(spidev, ioc, n_ioc); + kfree(ioc); + +done: + mutex_unlock(&spidev->buf_lock); + spi_dev_put(spi); + return retval; +} + +static long +spidev_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + if (_IOC_TYPE(cmd) == SPI_IOC_MAGIC + && _IOC_NR(cmd) == _IOC_NR(SPI_IOC_MESSAGE(0)) + && _IOC_DIR(cmd) == _IOC_WRITE) + return spidev_compat_ioc_message(filp, cmd, arg); + + return spidev_ioctl(filp, cmd, (unsigned long)compat_ptr(arg)); +} +#else +#define spidev_compat_ioctl NULL +#endif /* CONFIG_COMPAT */ + +static int spidev_open(struct inode *inode, struct file *filp) +{ + struct spidev_data *spidev; + int status = -ENXIO; + + mutex_lock(&device_list_lock); + + list_for_each_entry(spidev, &device_list, device_entry) { + if (spidev->devt == inode->i_rdev) { + status = 0; + break; + } + } + + if (status) { + pr_debug("spidev: nothing for minor %d\n", iminor(inode)); + goto err_find_dev; + } + + if (!spidev->tx_buffer) { + spidev->tx_buffer = kmalloc(bufsiz, GFP_KERNEL); + if (!spidev->tx_buffer) { + dev_dbg(&spidev->spi->dev, "open/ENOMEM\n"); + status = -ENOMEM; + goto err_find_dev; + } + } + + if (!spidev->rx_buffer) { + spidev->rx_buffer = kmalloc(bufsiz, GFP_KERNEL); + if (!spidev->rx_buffer) { + dev_dbg(&spidev->spi->dev, "open/ENOMEM\n"); + status = -ENOMEM; + goto err_alloc_rx_buf; + } + } + + spidev->users++; + filp->private_data = spidev; + nonseekable_open(inode, filp); + + mutex_unlock(&device_list_lock); + return 0; + +err_alloc_rx_buf: + kfree(spidev->tx_buffer); + spidev->tx_buffer = NULL; +err_find_dev: + mutex_unlock(&device_list_lock); + return status; +} + +static int spidev_release(struct inode *inode, struct file *filp) +{ + struct spidev_data *spidev; + + mutex_lock(&device_list_lock); + spidev = filp->private_data; + filp->private_data = NULL; + + /* last close? */ + spidev->users--; + if (!spidev->users) { + int dofree; + + kfree(spidev->tx_buffer); + spidev->tx_buffer = NULL; + + kfree(spidev->rx_buffer); + spidev->rx_buffer = NULL; + + spin_lock_irq(&spidev->spi_lock); + if (spidev->spi) + spidev->speed_hz = spidev->spi->max_speed_hz; + + /* ... after we unbound from the underlying device? */ + dofree = (spidev->spi == NULL); + spin_unlock_irq(&spidev->spi_lock); + + if (dofree) + kfree(spidev); + } + mutex_unlock(&device_list_lock); + + return 0; +} + +static const struct file_operations spidev_fops = { + .owner = THIS_MODULE, + /* REVISIT switch to aio primitives, so that userspace + * gets more complete API coverage. It'll simplify things + * too, except for the locking. + */ + .write = spidev_write, + .read = spidev_read, + .unlocked_ioctl = spidev_ioctl, + .compat_ioctl = spidev_compat_ioctl, + .open = spidev_open, + .release = spidev_release, + .llseek = no_llseek, +}; + +/*-------------------------------------------------------------------------*/ + +/* The main reason to have this class is to make mdev/udev create the + * /dev/spidevB.C character device nodes exposing our userspace API. + * It also simplifies memory management. + */ + +static struct class *spidev_class; + +#ifdef CONFIG_OF +static const struct of_device_id spidev_dt_ids[] = { + { .compatible = "commaai,panda" }, + {}, +}; +MODULE_DEVICE_TABLE(of, spidev_dt_ids); +#endif + +#ifdef CONFIG_ACPI + +/* Dummy SPI devices not to be used in production systems */ +#define SPIDEV_ACPI_DUMMY 1 + +static const struct acpi_device_id spidev_acpi_ids[] = { + /* + * The ACPI SPT000* devices are only meant for development and + * testing. Systems used in production should have a proper ACPI + * description of the connected peripheral and they should also use + * a proper driver instead of poking directly to the SPI bus. + */ + { "SPT0001", SPIDEV_ACPI_DUMMY }, + { "SPT0002", SPIDEV_ACPI_DUMMY }, + { "SPT0003", SPIDEV_ACPI_DUMMY }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, spidev_acpi_ids); + +static void spidev_probe_acpi(struct spi_device *spi) +{ + const struct acpi_device_id *id; + + if (!has_acpi_companion(&spi->dev)) + return; + + id = acpi_match_device(spidev_acpi_ids, &spi->dev); + if (WARN_ON(!id)) + return; + + if (id->driver_data == SPIDEV_ACPI_DUMMY) + dev_warn(&spi->dev, "do not use this driver in production systems!\n"); +} +#else +static inline void spidev_probe_acpi(struct spi_device *spi) {} +#endif + +/*-------------------------------------------------------------------------*/ + +static int spidev_probe(struct spi_device *spi) +{ + struct spidev_data *spidev; + int status; + unsigned long minor; + + /* + * spidev should never be referenced in DT without a specific + * compatible string, it is a Linux implementation thing + * rather than a description of the hardware. + */ + if (spi->dev.of_node && !of_match_device(spidev_dt_ids, &spi->dev)) { + dev_err(&spi->dev, "buggy DT: spidev listed directly in DT\n"); + WARN_ON(spi->dev.of_node && + !of_match_device(spidev_dt_ids, &spi->dev)); + } + + spidev_probe_acpi(spi); + + /* Allocate driver data */ + spidev = kzalloc(sizeof(*spidev), GFP_KERNEL); + if (!spidev) + return -ENOMEM; + + /* Initialize the driver data */ + spidev->spi = spi; + spin_lock_init(&spidev->spi_lock); + mutex_init(&spidev->buf_lock); + + INIT_LIST_HEAD(&spidev->device_entry); + + /* If we can allocate a minor number, hook up this device. + * Reusing minors is fine so long as udev or mdev is working. + */ + mutex_lock(&device_list_lock); + minor = find_first_zero_bit(minors, N_SPI_MINORS); + if (minor < N_SPI_MINORS) { + struct device *dev; + + spidev->devt = MKDEV(SPIDEV_MAJOR, minor); + dev = device_create(spidev_class, &spi->dev, spidev->devt, + spidev, "spidev%d.%d", + spi->master->bus_num, spi->chip_select); + status = PTR_ERR_OR_ZERO(dev); + } else { + dev_dbg(&spi->dev, "no minor number available!\n"); + status = -ENODEV; + } + if (status == 0) { + set_bit(minor, minors); + list_add(&spidev->device_entry, &device_list); + } + mutex_unlock(&device_list_lock); + + spidev->speed_hz = spi->max_speed_hz; + + if (status == 0) + spi_set_drvdata(spi, spidev); + else + kfree(spidev); + + return status; +} + +static int spidev_remove(struct spi_device *spi) +{ + struct spidev_data *spidev = spi_get_drvdata(spi); + + /* make sure ops on existing fds can abort cleanly */ + spin_lock_irq(&spidev->spi_lock); + spidev->spi = NULL; + spin_unlock_irq(&spidev->spi_lock); + + /* prevent new opens */ + mutex_lock(&device_list_lock); + list_del(&spidev->device_entry); + device_destroy(spidev_class, spidev->devt); + clear_bit(MINOR(spidev->devt), minors); + if (spidev->users == 0) + kfree(spidev); + mutex_unlock(&device_list_lock); + + return 0; +} + +static struct spi_driver spidev_spi_driver = { + .driver = { + .name = "spidev_panda", + .of_match_table = of_match_ptr(spidev_dt_ids), + .acpi_match_table = ACPI_PTR(spidev_acpi_ids), + }, + .probe = spidev_probe, + .remove = spidev_remove, + + /* NOTE: suspend/resume methods are not necessary here. + * We don't do anything except pass the requests to/from + * the underlying controller. The refrigerator handles + * most issues; the controller driver handles the rest. + */ +}; + +/*-------------------------------------------------------------------------*/ + +static int __init spidev_init(void) +{ + int status; + + /* Claim our 256 reserved device numbers. Then register a class + * that will key udev/mdev to add/remove /dev nodes. Last, register + * the driver which manages those device numbers. + */ + BUILD_BUG_ON(N_SPI_MINORS > 256); + status = register_chrdev(0, "spi", &spidev_fops); + if (status < 0) + return status; + + SPIDEV_MAJOR = status; + + spidev_class = class_create(THIS_MODULE, "spidev_panda"); + if (IS_ERR(spidev_class)) { + unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); + return PTR_ERR(spidev_class); + } + + status = spi_register_driver(&spidev_spi_driver); + if (status < 0) { + class_destroy(spidev_class); + unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); + } + return status; +} +module_init(spidev_init); + +static void __exit spidev_exit(void) +{ + spi_unregister_driver(&spidev_spi_driver); + class_destroy(spidev_class); + unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); +} +module_exit(spidev_exit); + +MODULE_AUTHOR("Andrea Paterniani, "); +MODULE_DESCRIPTION("User mode SPI device interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("spi:spidev"); diff --git a/python/spi.py b/python/spi.py index 26b188439e..b73aea1dba 100644 --- a/python/spi.py +++ b/python/spi.py @@ -1,4 +1,5 @@ import binascii +import ctypes import os import fcntl import math @@ -16,8 +17,10 @@ try: import spidev + import spidev2 except ImportError: spidev = None + spidev2 = None # Constants SYNC = 0x5A @@ -55,6 +58,17 @@ class PandaSpiTransferFailed(PandaSpiException): SPI_LOCK = threading.Lock() +class PandaSpiTransfer(ctypes.Structure): + _fields_ = [ + ('rx_buf', ctypes.c_uint64), + ('tx_buf', ctypes.c_uint64), + ('tx_length', ctypes.c_uint32), + ('rx_length_max', ctypes.c_uint32), + ('timeout', ctypes.c_uint32), + ('endpoint', ctypes.c_uint8), + ('expect_disconnect', ctypes.c_uint8), + ] + class SpiDevice: """ Provides locked, thread-safe access to a panda's SPI interface. @@ -90,6 +104,7 @@ def close(self): self._spidev.close() + class PandaSpiHandle(BaseHandle): """ A class that mimics a libusb1 handle for panda SPI communications. @@ -97,6 +112,21 @@ class PandaSpiHandle(BaseHandle): def __init__(self): self.dev = SpiDevice() + self._transfer = self._transfer_spidev + + if "KERN" in os.environ: + self._transfer = self._transfer_kernel_driver + + self.tx_buf = bytearray(1024) + self.rx_buf = bytearray(1024) + tx_buf_raw = ctypes.c_char.from_buffer(self.tx_buf) + rx_buf_raw = ctypes.c_char.from_buffer(self.rx_buf) + + self.ioctl_data = PandaSpiTransfer() + self.ioctl_data.tx_buf = ctypes.addressof(tx_buf_raw) + self.ioctl_data.rx_buf = ctypes.addressof(rx_buf_raw) + self.fileno = self.dev._spidev.fileno() + # helpers def _calc_checksum(self, data: List[int]) -> int: cksum = CHECKSUM_START @@ -117,7 +147,23 @@ def _wait_for_ack(self, spi, ack_val: int, timeout: int, tx: int) -> None: raise PandaSpiMissingAck - def _transfer(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes: + def _transfer_kernel_driver(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes: + self.tx_buf[:len(data)] = data + self.ioctl_data.endpoint = endpoint + self.ioctl_data.tx_length = len(data) + self.ioctl_data.rx_length_max = max_rx_len + self.ioctl_data.expect_disconnect = int(expect_disconnect) + + # TODO: use our own ioctl request + try: + ret = fcntl.ioctl(self.fileno, spidev2.SPI_IOC_RD_LSB_FIRST, self.ioctl_data) + except OSError as e: + raise PandaSpiException from e + if ret < 0: + raise PandaSpiException(f"ioctl returned {ret}") + return bytes(self.rx_buf[:ret]) + + def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes: logging.debug("starting transfer: endpoint=%d, max_rx_len=%d", endpoint, max_rx_len) logging.debug("==============================================") From 6e5009125a37b31d0d1a6c16a975e5616c4a5c0b Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Sun, 16 Jul 2023 12:13:09 +0100 Subject: [PATCH 136/197] Ford: namespaced constants (#1516) --- board/safety/safety_ford.h | 130 ++++++++++++++++++------------------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/board/safety/safety_ford.h b/board/safety/safety_ford.h index 98cf204d99..5fcdcdc444 100644 --- a/board/safety/safety_ford.h +++ b/board/safety/safety_ford.h @@ -1,53 +1,53 @@ // Safety-relevant CAN messages for Ford vehicles. -#define MSG_EngBrakeData 0x165 // RX from PCM, for driver brake pedal and cruise state -#define MSG_EngVehicleSpThrottle 0x204 // RX from PCM, for driver throttle input -#define MSG_DesiredTorqBrk 0x213 // RX from ABS, for standstill state -#define MSG_BrakeSysFeatures 0x415 // RX from ABS, for vehicle speed -#define MSG_EngVehicleSpThrottle2 0x202 // RX from PCM, for second vehicle speed -#define MSG_Yaw_Data_FD1 0x91 // RX from RCM, for yaw rate -#define MSG_Steering_Data_FD1 0x083 // TX by OP, various driver switches and LKAS/CC buttons -#define MSG_ACCDATA 0x186 // TX by OP, ACC controls -#define MSG_ACCDATA_3 0x18A // TX by OP, ACC/TJA user interface -#define MSG_Lane_Assist_Data1 0x3CA // TX by OP, Lane Keep Assist -#define MSG_LateralMotionControl 0x3D3 // TX by OP, Traffic Jam Assist -#define MSG_IPMA_Data 0x3D8 // TX by OP, IPMA and LKAS user interface +#define FORD_EngBrakeData 0x165 // RX from PCM, for driver brake pedal and cruise state +#define FORD_EngVehicleSpThrottle 0x204 // RX from PCM, for driver throttle input +#define FORD_DesiredTorqBrk 0x213 // RX from ABS, for standstill state +#define FORD_BrakeSysFeatures 0x415 // RX from ABS, for vehicle speed +#define FORD_EngVehicleSpThrottle2 0x202 // RX from PCM, for second vehicle speed +#define FORD_Yaw_Data_FD1 0x91 // RX from RCM, for yaw rate +#define FORD_Steering_Data_FD1 0x083 // TX by OP, various driver switches and LKAS/CC buttons +#define FORD_ACCDATA 0x186 // TX by OP, ACC controls +#define FORD_ACCDATA_3 0x18A // TX by OP, ACC/TJA user interface +#define FORD_Lane_Assist_Data1 0x3CA // TX by OP, Lane Keep Assist +#define FORD_LateralMotionControl 0x3D3 // TX by OP, Traffic Jam Assist +#define FORD_IPMA_Data 0x3D8 // TX by OP, IPMA and LKAS user interface // CAN bus numbers. #define FORD_MAIN_BUS 0U #define FORD_CAM_BUS 2U const CanMsg FORD_STOCK_TX_MSGS[] = { - {MSG_Steering_Data_FD1, 0, 8}, - {MSG_Steering_Data_FD1, 2, 8}, - {MSG_ACCDATA_3, 0, 8}, - {MSG_Lane_Assist_Data1, 0, 8}, - {MSG_LateralMotionControl, 0, 8}, - {MSG_IPMA_Data, 0, 8}, + {FORD_Steering_Data_FD1, 0, 8}, + {FORD_Steering_Data_FD1, 2, 8}, + {FORD_ACCDATA_3, 0, 8}, + {FORD_Lane_Assist_Data1, 0, 8}, + {FORD_LateralMotionControl, 0, 8}, + {FORD_IPMA_Data, 0, 8}, }; #define FORD_STOCK_TX_LEN (sizeof(FORD_STOCK_TX_MSGS) / sizeof(FORD_STOCK_TX_MSGS[0])) const CanMsg FORD_LONG_TX_MSGS[] = { - {MSG_Steering_Data_FD1, 0, 8}, - {MSG_Steering_Data_FD1, 2, 8}, - {MSG_ACCDATA, 0, 8}, - {MSG_ACCDATA_3, 0, 8}, - {MSG_Lane_Assist_Data1, 0, 8}, - {MSG_LateralMotionControl, 0, 8}, - {MSG_IPMA_Data, 0, 8}, + {FORD_Steering_Data_FD1, 0, 8}, + {FORD_Steering_Data_FD1, 2, 8}, + {FORD_ACCDATA, 0, 8}, + {FORD_ACCDATA_3, 0, 8}, + {FORD_Lane_Assist_Data1, 0, 8}, + {FORD_LateralMotionControl, 0, 8}, + {FORD_IPMA_Data, 0, 8}, }; #define FORD_LONG_TX_LEN (sizeof(FORD_LONG_TX_MSGS) / sizeof(FORD_LONG_TX_MSGS[0])) // warning: quality flags are not yet checked in openpilot's CAN parser, // this may be the cause of blocked messages AddrCheckStruct ford_addr_checks[] = { - {.msg = {{MSG_BrakeSysFeatures, 0, 8, .check_checksum = true, .max_counter = 15U, .quality_flag=true, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - // TODO: MSG_EngVehicleSpThrottle2 has a counter that skips by 2, understand and enable counter check - {.msg = {{MSG_EngVehicleSpThrottle2, 0, 8, .check_checksum = true, .quality_flag=true, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{MSG_Yaw_Data_FD1, 0, 8, .check_checksum = true, .max_counter = 255U, .quality_flag=true, .expected_timestep = 10000U}, { 0 }, { 0 }}}, + {.msg = {{FORD_BrakeSysFeatures, 0, 8, .check_checksum = true, .max_counter = 15U, .quality_flag=true, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + // TODO: FORD_EngVehicleSpThrottle2 has a counter that skips by 2, understand and enable counter check + {.msg = {{FORD_EngVehicleSpThrottle2, 0, 8, .check_checksum = true, .quality_flag=true, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{FORD_Yaw_Data_FD1, 0, 8, .check_checksum = true, .max_counter = 255U, .quality_flag=true, .expected_timestep = 10000U}, { 0 }, { 0 }}}, // These messages have no counter or checksum - {.msg = {{MSG_EngBrakeData, 0, 8, .expected_timestep = 100000U}, { 0 }, { 0 }}}, - {.msg = {{MSG_EngVehicleSpThrottle, 0, 8, .expected_timestep = 10000U}, { 0 }, { 0 }}}, - {.msg = {{MSG_DesiredTorqBrk, 0, 8, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{FORD_EngBrakeData, 0, 8, .expected_timestep = 100000U}, { 0 }, { 0 }}}, + {.msg = {{FORD_EngVehicleSpThrottle, 0, 8, .expected_timestep = 10000U}, { 0 }, { 0 }}}, + {.msg = {{FORD_DesiredTorqBrk, 0, 8, .expected_timestep = 20000U}, { 0 }, { 0 }}}, }; #define FORD_ADDR_CHECK_LEN (sizeof(ford_addr_checks) / sizeof(ford_addr_checks[0])) addr_checks ford_rx_checks = {ford_addr_checks, FORD_ADDR_CHECK_LEN}; @@ -56,13 +56,13 @@ static uint8_t ford_get_counter(CANPacket_t *to_push) { int addr = GET_ADDR(to_push); uint8_t cnt; - if (addr == MSG_BrakeSysFeatures) { + if (addr == FORD_BrakeSysFeatures) { // Signal: VehVActlBrk_No_Cnt cnt = (GET_BYTE(to_push, 2) >> 2) & 0xFU; - } else if (addr == MSG_EngVehicleSpThrottle2) { + } else if (addr == FORD_EngVehicleSpThrottle2) { // Signal: VehVActlEng_No_Cnt cnt = (GET_BYTE(to_push, 2) >> 3) & 0xFU; - } else if (addr == MSG_Yaw_Data_FD1) { + } else if (addr == FORD_Yaw_Data_FD1) { // Signal: VehRollYaw_No_Cnt cnt = GET_BYTE(to_push, 5); } else { @@ -75,13 +75,13 @@ static uint32_t ford_get_checksum(CANPacket_t *to_push) { int addr = GET_ADDR(to_push); uint8_t chksum; - if (addr == MSG_BrakeSysFeatures) { + if (addr == FORD_BrakeSysFeatures) { // Signal: VehVActlBrk_No_Cs chksum = GET_BYTE(to_push, 3); - } else if (addr == MSG_EngVehicleSpThrottle2) { + } else if (addr == FORD_EngVehicleSpThrottle2) { // Signal: VehVActlEng_No_Cs chksum = GET_BYTE(to_push, 1); - } else if (addr == MSG_Yaw_Data_FD1) { + } else if (addr == FORD_Yaw_Data_FD1) { // Signal: VehRollYawW_No_Cs chksum = GET_BYTE(to_push, 4); } else { @@ -94,17 +94,17 @@ static uint32_t ford_compute_checksum(CANPacket_t *to_push) { int addr = GET_ADDR(to_push); uint8_t chksum = 0; - if (addr == MSG_BrakeSysFeatures) { + if (addr == FORD_BrakeSysFeatures) { chksum += GET_BYTE(to_push, 0) + GET_BYTE(to_push, 1); // Veh_V_ActlBrk chksum += GET_BYTE(to_push, 2) >> 6; // VehVActlBrk_D_Qf chksum += (GET_BYTE(to_push, 2) >> 2) & 0xFU; // VehVActlBrk_No_Cnt chksum = 0xFFU - chksum; - } else if (addr == MSG_EngVehicleSpThrottle2) { + } else if (addr == FORD_EngVehicleSpThrottle2) { chksum += (GET_BYTE(to_push, 2) >> 3) & 0xFU; // VehVActlEng_No_Cnt chksum += (GET_BYTE(to_push, 4) >> 5) & 0x3U; // VehVActlEng_D_Qf chksum += GET_BYTE(to_push, 6) + GET_BYTE(to_push, 7); // Veh_V_ActlEng chksum = 0xFFU - chksum; - } else if (addr == MSG_Yaw_Data_FD1) { + } else if (addr == FORD_Yaw_Data_FD1) { chksum += GET_BYTE(to_push, 0) + GET_BYTE(to_push, 1); // VehRol_W_Actl chksum += GET_BYTE(to_push, 2) + GET_BYTE(to_push, 3); // VehYaw_W_Actl chksum += GET_BYTE(to_push, 5); // VehRollYaw_No_Cnt @@ -121,11 +121,11 @@ static bool ford_get_quality_flag_valid(CANPacket_t *to_push) { int addr = GET_ADDR(to_push); bool valid = false; - if (addr == MSG_BrakeSysFeatures) { + if (addr == FORD_BrakeSysFeatures) { valid = (GET_BYTE(to_push, 2) >> 6) == 0x3U; // VehVActlBrk_D_Qf - } else if (addr == MSG_EngVehicleSpThrottle2) { + } else if (addr == FORD_EngVehicleSpThrottle2) { valid = ((GET_BYTE(to_push, 4) >> 5) & 0x3U) == 0x3U; // VehVActlEng_D_Qf - } else if (addr == MSG_Yaw_Data_FD1) { + } else if (addr == FORD_Yaw_Data_FD1) { valid = ((GET_BYTE(to_push, 6) >> 4) & 0x3U) == 0x3U; // VehYawWActl_D_Qf } else { } @@ -150,17 +150,17 @@ const LongitudinalLimits FORD_LONG_LIMITS = { .inactive_gas = 0, // -5.0 m/s^2 }; -#define INACTIVE_CURVATURE 1000U -#define INACTIVE_CURVATURE_RATE 4096U -#define INACTIVE_PATH_OFFSET 512U -#define INACTIVE_PATH_ANGLE 1000U +#define FORD_INACTIVE_CURVATURE 1000U +#define FORD_INACTIVE_CURVATURE_RATE 4096U +#define FORD_INACTIVE_PATH_OFFSET 512U +#define FORD_INACTIVE_PATH_ANGLE 1000U #define FORD_MAX_SPEED_DELTA 2.0 // m/s static bool ford_lkas_msg_check(int addr) { - return (addr == MSG_ACCDATA_3) - || (addr == MSG_Lane_Assist_Data1) - || (addr == MSG_LateralMotionControl) - || (addr == MSG_IPMA_Data); + return (addr == FORD_ACCDATA_3) + || (addr == FORD_Lane_Assist_Data1) + || (addr == FORD_LateralMotionControl) + || (addr == FORD_IPMA_Data); } // Curvature rate limits @@ -192,19 +192,19 @@ static int ford_rx_hook(CANPacket_t *to_push) { int addr = GET_ADDR(to_push); // Update in motion state from standstill signal - if (addr == MSG_DesiredTorqBrk) { + if (addr == FORD_DesiredTorqBrk) { // Signal: VehStop_D_Stat vehicle_moving = ((GET_BYTE(to_push, 3) >> 3) & 0x3U) == 0U; } // Update vehicle speed - if (addr == MSG_BrakeSysFeatures) { + if (addr == FORD_BrakeSysFeatures) { // Signal: Veh_V_ActlBrk update_sample(&vehicle_speed, ROUND(((GET_BYTE(to_push, 0) << 8) | GET_BYTE(to_push, 1)) * 0.01 / 3.6 * VEHICLE_SPEED_FACTOR)); } // Check vehicle speed against a second source - if (addr == MSG_EngVehicleSpThrottle2) { + if (addr == FORD_EngVehicleSpThrottle2) { // Disable controls if speeds from ABS and PCM ECUs are too far apart. // Signal: Veh_V_ActlEng float filtered_pcm_speed = ((GET_BYTE(to_push, 6) << 8) | GET_BYTE(to_push, 7)) * 0.01 / 3.6; @@ -214,7 +214,7 @@ static int ford_rx_hook(CANPacket_t *to_push) { } // Update vehicle yaw rate - if (addr == MSG_Yaw_Data_FD1) { + if (addr == FORD_Yaw_Data_FD1) { // Signal: VehYaw_W_Actl float ford_yaw_rate = (((GET_BYTE(to_push, 2) << 8U) | GET_BYTE(to_push, 3)) * 0.0002) - 6.5; float current_curvature = ford_yaw_rate / MAX(vehicle_speed.values[0] / VEHICLE_SPEED_FACTOR, 0.1); @@ -223,14 +223,14 @@ static int ford_rx_hook(CANPacket_t *to_push) { } // Update gas pedal - if (addr == MSG_EngVehicleSpThrottle) { + if (addr == FORD_EngVehicleSpThrottle) { // Pedal position: (0.1 * val) in percent // Signal: ApedPos_Pc_ActlArb gas_pressed = (((GET_BYTE(to_push, 0) & 0x03U) << 8) | GET_BYTE(to_push, 1)) > 0U; } // Update brake pedal and cruise state - if (addr == MSG_EngBrakeData) { + if (addr == FORD_EngBrakeData) { // Signal: BpedDrvAppl_D_Actl brake_pressed = ((GET_BYTE(to_push, 0) >> 4) & 0x3U) == 2U; @@ -259,7 +259,7 @@ static int ford_tx_hook(CANPacket_t *to_send) { } // Safety check for ACCDATA accel and brake requests - if (addr == MSG_ACCDATA) { + if (addr == FORD_ACCDATA) { // Signal: AccPrpl_A_Rq int gas = ((GET_BYTE(to_send, 6) & 0x3U) << 8) | GET_BYTE(to_send, 7); // Signal: AccBrkTot_A_Rq @@ -282,7 +282,7 @@ static int ford_tx_hook(CANPacket_t *to_send) { // Safety check for Steering_Data_FD1 button signals // Note: Many other signals in this message are not relevant to safety (e.g. blinkers, wiper switches, high beam) // which we passthru in OP. - if (addr == MSG_Steering_Data_FD1) { + if (addr == FORD_Steering_Data_FD1) { // Violation if resume button is pressed while controls not allowed, or // if cancel button is pressed when cruise isn't engaged. bool violation = false; @@ -295,7 +295,7 @@ static int ford_tx_hook(CANPacket_t *to_send) { } // Safety check for Lane_Assist_Data1 action - if (addr == MSG_Lane_Assist_Data1) { + if (addr == FORD_Lane_Assist_Data1) { // Do not allow steering using Lane_Assist_Data1 (Lane-Departure Aid). // This message must be sent for Lane Centering to work, and can include // values such as the steering angle or lane curvature for debugging, @@ -307,7 +307,7 @@ static int ford_tx_hook(CANPacket_t *to_send) { } // Safety check for LateralMotionControl action - if (addr == MSG_LateralMotionControl) { + if (addr == FORD_LateralMotionControl) { // Signal: LatCtl_D_Rq bool steer_control_enabled = ((GET_BYTE(to_send, 4) >> 2) & 0x7U) != 0U; unsigned int raw_curvature = (GET_BYTE(to_send, 0) << 3) | (GET_BYTE(to_send, 1) >> 5); @@ -316,10 +316,10 @@ static int ford_tx_hook(CANPacket_t *to_send) { unsigned int raw_path_offset = (GET_BYTE(to_send, 5) << 2) | (GET_BYTE(to_send, 6) >> 6); // These signals are not yet tested with the current safety limits - bool violation = (raw_curvature_rate != INACTIVE_CURVATURE_RATE) || (raw_path_angle != INACTIVE_PATH_ANGLE) || (raw_path_offset != INACTIVE_PATH_OFFSET); + bool violation = (raw_curvature_rate != FORD_INACTIVE_CURVATURE_RATE) || (raw_path_angle != FORD_INACTIVE_PATH_ANGLE) || (raw_path_offset != FORD_INACTIVE_PATH_OFFSET); // Check angle error and steer_control_enabled - int desired_curvature = raw_curvature - INACTIVE_CURVATURE; // /FORD_STEERING_LIMITS.angle_deg_to_can to get real curvature + int desired_curvature = raw_curvature - FORD_INACTIVE_CURVATURE; // /FORD_STEERING_LIMITS.angle_deg_to_can to get real curvature violation |= steer_angle_cmd_checks(desired_curvature, steer_control_enabled, FORD_STEERING_LIMITS); if (violation) { @@ -344,7 +344,7 @@ static int ford_fwd_hook(int bus_num, int addr) { if (ford_lkas_msg_check(addr)) { // Block stock LKAS and UI messages bus_fwd = -1; - } else if (ford_longitudinal && (addr == MSG_ACCDATA)) { + } else if (ford_longitudinal && (addr == FORD_ACCDATA)) { // Block stock ACC message bus_fwd = -1; } else { From 0c3355462483cf3150b7d481c76cbf0659c16c7a Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Sun, 16 Jul 2023 15:09:37 +0100 Subject: [PATCH 137/197] Ford CAN FD safety (#1455) --- board/safety/safety_ford.h | 69 ++++++++++++++++++++-- python/__init__.py | 1 + tests/safety/common.py | 2 +- tests/safety/test_ford.py | 117 ++++++++++++++++++++++++++++--------- 4 files changed, 153 insertions(+), 36 deletions(-) diff --git a/board/safety/safety_ford.h b/board/safety/safety_ford.h index 5fcdcdc444..4f71650c90 100644 --- a/board/safety/safety_ford.h +++ b/board/safety/safety_ford.h @@ -9,7 +9,8 @@ #define FORD_ACCDATA 0x186 // TX by OP, ACC controls #define FORD_ACCDATA_3 0x18A // TX by OP, ACC/TJA user interface #define FORD_Lane_Assist_Data1 0x3CA // TX by OP, Lane Keep Assist -#define FORD_LateralMotionControl 0x3D3 // TX by OP, Traffic Jam Assist +#define FORD_LateralMotionControl 0x3D3 // TX by OP, Lateral Control message +#define FORD_LateralMotionControl2 0x3D6 // TX by OP, alternate Lateral Control message #define FORD_IPMA_Data 0x3D8 // TX by OP, IPMA and LKAS user interface // CAN bus numbers. @@ -37,6 +38,27 @@ const CanMsg FORD_LONG_TX_MSGS[] = { }; #define FORD_LONG_TX_LEN (sizeof(FORD_LONG_TX_MSGS) / sizeof(FORD_LONG_TX_MSGS[0])) +const CanMsg FORD_CANFD_STOCK_TX_MSGS[] = { + {FORD_Steering_Data_FD1, 0, 8}, + {FORD_Steering_Data_FD1, 2, 8}, + {FORD_ACCDATA_3, 0, 8}, + {FORD_Lane_Assist_Data1, 0, 8}, + {FORD_LateralMotionControl2, 0, 8}, + {FORD_IPMA_Data, 0, 8}, +}; +#define FORD_CANFD_STOCK_TX_LEN (sizeof(FORD_CANFD_STOCK_TX_MSGS) / sizeof(FORD_CANFD_STOCK_TX_MSGS[0])) + +const CanMsg FORD_CANFD_LONG_TX_MSGS[] = { + {FORD_Steering_Data_FD1, 0, 8}, + {FORD_Steering_Data_FD1, 2, 8}, + {FORD_ACCDATA, 0, 8}, + {FORD_ACCDATA_3, 0, 8}, + {FORD_Lane_Assist_Data1, 0, 8}, + {FORD_LateralMotionControl2, 0, 8}, + {FORD_IPMA_Data, 0, 8}, +}; +#define FORD_CANFD_LONG_TX_LEN (sizeof(FORD_CANFD_LONG_TX_MSGS) / sizeof(FORD_CANFD_LONG_TX_MSGS[0])) + // warning: quality flags are not yet checked in openpilot's CAN parser, // this may be the cause of blocked messages AddrCheckStruct ford_addr_checks[] = { @@ -133,8 +155,10 @@ static bool ford_get_quality_flag_valid(CANPacket_t *to_push) { } const uint16_t FORD_PARAM_LONGITUDINAL = 1; +const uint16_t FORD_PARAM_CANFD = 2; bool ford_longitudinal = false; +bool ford_canfd = false; const LongitudinalLimits FORD_LONG_LIMITS = { // acceleration cmd limits (used for brakes) @@ -154,12 +178,16 @@ const LongitudinalLimits FORD_LONG_LIMITS = { #define FORD_INACTIVE_CURVATURE_RATE 4096U #define FORD_INACTIVE_PATH_OFFSET 512U #define FORD_INACTIVE_PATH_ANGLE 1000U + +#define FORD_CANFD_INACTIVE_CURVATURE_RATE 1024U + #define FORD_MAX_SPEED_DELTA 2.0 // m/s static bool ford_lkas_msg_check(int addr) { return (addr == FORD_ACCDATA_3) || (addr == FORD_Lane_Assist_Data1) || (addr == FORD_LateralMotionControl) + || (addr == FORD_LateralMotionControl2) || (addr == FORD_IPMA_Data); } @@ -249,13 +277,20 @@ static int ford_rx_hook(CANPacket_t *to_push) { } static int ford_tx_hook(CANPacket_t *to_send) { - int tx = 1; int addr = GET_ADDR(to_send); - - if (ford_longitudinal) { - tx = msg_allowed(to_send, FORD_LONG_TX_MSGS, FORD_LONG_TX_LEN); + int tx; + if (ford_canfd) { + if (ford_longitudinal) { + tx = msg_allowed(to_send, FORD_CANFD_LONG_TX_MSGS, FORD_CANFD_LONG_TX_LEN); + } else { + tx = msg_allowed(to_send, FORD_CANFD_STOCK_TX_MSGS, FORD_CANFD_STOCK_TX_LEN); + } } else { - tx = msg_allowed(to_send, FORD_STOCK_TX_MSGS, FORD_STOCK_TX_LEN); + if (ford_longitudinal) { + tx = msg_allowed(to_send, FORD_LONG_TX_MSGS, FORD_LONG_TX_LEN); + } else { + tx = msg_allowed(to_send, FORD_STOCK_TX_MSGS, FORD_STOCK_TX_LEN); + } } // Safety check for ACCDATA accel and brake requests @@ -327,6 +362,27 @@ static int ford_tx_hook(CANPacket_t *to_send) { } } + // Safety check for LateralMotionControl2 action + if (addr == FORD_LateralMotionControl2) { + // Signal: LatCtl_D2_Rq + bool steer_control_enabled = ((GET_BYTE(to_send, 0) >> 4) & 0x7U) != 0U; + unsigned int raw_curvature = (GET_BYTE(to_send, 2) << 3) | (GET_BYTE(to_send, 3) >> 5); + unsigned int raw_curvature_rate = (GET_BYTE(to_send, 6) << 3) | (GET_BYTE(to_send, 7) >> 5); + unsigned int raw_path_angle = ((GET_BYTE(to_send, 3) & 0x1FU) << 6) | (GET_BYTE(to_send, 4) >> 2); + unsigned int raw_path_offset = ((GET_BYTE(to_send, 4) & 0x3U) << 8) | GET_BYTE(to_send, 5); + + // These signals are not yet tested with the current safety limits + bool violation = (raw_curvature_rate != FORD_CANFD_INACTIVE_CURVATURE_RATE) || (raw_path_angle != FORD_INACTIVE_PATH_ANGLE) || (raw_path_offset != FORD_INACTIVE_PATH_OFFSET); + + // Check angle error and steer_control_enabled + int desired_curvature = raw_curvature - FORD_INACTIVE_CURVATURE; // /FORD_STEERING_LIMITS.angle_deg_to_can to get real curvature + violation |= steer_angle_cmd_checks(desired_curvature, steer_control_enabled, FORD_STEERING_LIMITS); + + if (violation) { + tx = 0; + } + } + // 1 allows the message through return tx; } @@ -367,6 +423,7 @@ static const addr_checks* ford_init(uint16_t param) { UNUSED(param); #ifdef ALLOW_DEBUG ford_longitudinal = GET_FLAG(param, FORD_PARAM_LONGITUDINAL); + ford_canfd = GET_FLAG(param, FORD_PARAM_CANFD); #endif return &ford_rx_checks; } diff --git a/python/__init__.py b/python/__init__.py index ffa0b1bec0..26a70ff260 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -254,6 +254,7 @@ class Panda: FLAG_GM_HW_CAM_LONG = 2 FLAG_FORD_LONG_CONTROL = 1 + FLAG_FORD_CANFD = 2 def __init__(self, serial: Optional[str] = None, claim: bool = True, disable_checks: bool = True): self._connect_serial = serial diff --git a/tests/safety/common.py b/tests/safety/common.py index 24b3f9b726..1b141d85e6 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -901,7 +901,7 @@ def test_tx_hook_on_wrong_safety_mode(self): continue if {attr, current_test}.issubset({'TestGmCameraSafety', 'TestGmCameraLongitudinalSafety'}): continue - if {attr, current_test}.issubset({'TestFordStockSafety', 'TestFordLongitudinalSafety'}): + if attr.startswith('TestFord') and current_test.startswith('TestFord'): continue if attr.startswith('TestHyundaiCanfd') and current_test.startswith('TestHyundaiCanfd'): continue diff --git a/tests/safety/test_ford.py b/tests/safety/test_ford.py index dfc317fa1d..7d2a128f53 100755 --- a/tests/safety/test_ford.py +++ b/tests/safety/test_ford.py @@ -17,7 +17,8 @@ MSG_ACCDATA = 0x186 # TX by OP, ACC controls MSG_ACCDATA_3 = 0x18A # TX by OP, ACC/TJA user interface MSG_Lane_Assist_Data1 = 0x3CA # TX by OP, Lane Keep Assist -MSG_LateralMotionControl = 0x3D3 # TX by OP, Traffic Jam Assist +MSG_LateralMotionControl = 0x3D3 # TX by OP, Lateral Control message +MSG_LateralMotionControl2 = 0x3D6 # TX by OP, alternate Lateral Control message MSG_IPMA_Data = 0x3D8 # TX by OP, IPMA and LKAS user interface @@ -57,18 +58,26 @@ class Buttons: TJA_TOGGLE = 2 -# Ford safety has two different configurations tested here: -# * stock longitudinal -# * openpilot longitudinal +# Ford safety has four different configurations tested here: +# * CAN with stock longitudinal +# * CAN with openpilot longitudinal +# * CAN FD with stock longitudinal +# * CAN FD with openpilot longitudinal class TestFordSafetyBase(common.PandaSafetyTest): STANDSTILL_THRESHOLD = 1 RELAY_MALFUNCTION_ADDR = MSG_IPMA_Data RELAY_MALFUNCTION_BUS = 0 + FWD_BLACKLISTED_ADDRS = {2: [MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl, + MSG_LateralMotionControl2, MSG_IPMA_Data]} + FWD_BUS_LOOKUP = {0: 2, 2: 0} + # Max allowed delta between car speeds MAX_SPEED_DELTA = 2.0 # m/s + STEER_MESSAGE = 0 + # Curvature control limits DEG_TO_CAN = 50000 # 1 / (2e-5) rad to can MAX_CURVATURE = 0.02 @@ -158,16 +167,26 @@ def _lkas_command_msg(self, action: int): } return self.packer.make_can_msg_panda("Lane_Assist_Data1", 0, values) - # TJA command - def _tja_command_msg(self, enabled: bool, path_offset: float, path_angle: float, curvature: float, curvature_rate: float): - values = { - "LatCtl_D_Rq": 1 if enabled else 0, - "LatCtlPathOffst_L_Actl": path_offset, # Path offset [-5.12|5.11] meter - "LatCtlPath_An_Actl": path_angle, # Path angle [-0.5|0.5235] radians - "LatCtlCurv_NoRate_Actl": curvature_rate, # Curvature rate [-0.001024|0.00102375] 1/meter^2 - "LatCtlCurv_No_Actl": curvature, # Curvature [-0.02|0.02094] 1/meter - } - return self.packer.make_can_msg_panda("LateralMotionControl", 0, values) + # LCA command + def _lat_ctl_msg(self, enabled: bool, path_offset: float, path_angle: float, curvature: float, curvature_rate: float): + if self.STEER_MESSAGE == MSG_LateralMotionControl: + values = { + "LatCtl_D_Rq": 1 if enabled else 0, + "LatCtlPathOffst_L_Actl": path_offset, # Path offset [-5.12|5.11] meter + "LatCtlPath_An_Actl": path_angle, # Path angle [-0.5|0.5235] radians + "LatCtlCurv_NoRate_Actl": curvature_rate, # Curvature rate [-0.001024|0.00102375] 1/meter^2 + "LatCtlCurv_No_Actl": curvature, # Curvature [-0.02|0.02094] 1/meter + } + return self.packer.make_can_msg_panda("LateralMotionControl", 0, values) + elif self.STEER_MESSAGE == MSG_LateralMotionControl2: + values = { + "LatCtl_D2_Rq": 1 if enabled else 0, + "LatCtlPathOffst_L_Actl": path_offset, # Path offset [-5.12|5.11] meter + "LatCtlPath_An_Actl": path_angle, # Path angle [-0.5|0.5235] radians + "LatCtlCrv_NoRate2_Actl": curvature_rate, # Curvature rate [-0.001024|0.001023] 1/meter^2 + "LatCtlCurv_No_Actl": curvature, # Curvature [-0.02|0.02094] 1/meter + } + return self.packer.make_can_msg_panda("LateralMotionControl2", 0, values) # Cruise control buttons def _acc_button_msg(self, button: int, bus: int): @@ -258,7 +277,7 @@ def test_steer_allowed(self): with self.subTest(controls_allowed=controls_allowed, steer_control_enabled=steer_control_enabled, path_offset=path_offset, path_angle=path_angle, curvature_rate=curvature_rate, curvature=curvature): - self.assertEqual(should_tx, self._tx(self._tja_command_msg(steer_control_enabled, path_offset, path_angle, curvature, curvature_rate))) + self.assertEqual(should_tx, self._tx(self._lat_ctl_msg(steer_control_enabled, path_offset, path_angle, curvature, curvature_rate))) def test_curvature_rate_limit_up(self): """ @@ -285,7 +304,7 @@ def test_curvature_rate_limit_up(self): self._reset_curvature_measurement(sign * (self.MAX_CURVATURE_ERROR + 1e-3), speed) for should_tx, curvature in cases: self._set_prev_desired_angle(sign * small_curvature) - self.assertEqual(should_tx, self._tx(self._tja_command_msg(True, 0, 0, sign * (small_curvature + curvature), 0))) + self.assertEqual(should_tx, self._tx(self._lat_ctl_msg(True, 0, 0, sign * (small_curvature + curvature), 0))) def test_curvature_rate_limit_down(self): self.safety.set_controls_allowed(True) @@ -308,7 +327,7 @@ def test_curvature_rate_limit_down(self): self._reset_curvature_measurement(sign * (self.MAX_CURVATURE - self.MAX_CURVATURE_ERROR - 1e-3), speed) for should_tx, curvature in cases: self._set_prev_desired_angle(sign * self.MAX_CURVATURE) - self.assertEqual(should_tx, self._tx(self._tja_command_msg(True, 0, 0, sign * curvature, 0))) + self.assertEqual(should_tx, self._tx(self._lat_ctl_msg(True, 0, 0, sign * curvature, 0))) def test_prevent_lkas_action(self): self.safety.set_controls_allowed(1) @@ -336,12 +355,12 @@ def test_acc_buttons(self): class TestFordStockSafety(TestFordSafetyBase): + STEER_MESSAGE = MSG_LateralMotionControl + TX_MSGS = [ [MSG_Steering_Data_FD1, 0], [MSG_Steering_Data_FD1, 2], [MSG_ACCDATA_3, 0], [MSG_Lane_Assist_Data1, 0], [MSG_LateralMotionControl, 0], [MSG_IPMA_Data, 0], ] - FWD_BLACKLISTED_ADDRS = {2: [MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl, MSG_IPMA_Data]} - FWD_BUS_LOOKUP = {0: 2, 2: 0} def setUp(self): self.packer = CANPackerPanda("ford_lincoln_base_pt") @@ -350,13 +369,24 @@ def setUp(self): self.safety.init_tests() -class TestFordLongitudinalSafety(TestFordSafetyBase): +class TestFordCANFDStockSafety(TestFordSafetyBase): + STEER_MESSAGE = MSG_LateralMotionControl2 + TX_MSGS = [ - [MSG_Steering_Data_FD1, 0], [MSG_Steering_Data_FD1, 2], [MSG_ACCDATA, 0], [MSG_ACCDATA_3, 0], [MSG_Lane_Assist_Data1, 0], - [MSG_LateralMotionControl, 0], [MSG_IPMA_Data, 0], + [MSG_Steering_Data_FD1, 0], [MSG_Steering_Data_FD1, 2], [MSG_ACCDATA_3, 0], [MSG_Lane_Assist_Data1, 0], + [MSG_LateralMotionControl2, 0], [MSG_IPMA_Data, 0], ] - FWD_BLACKLISTED_ADDRS = {2: [MSG_ACCDATA, MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl, MSG_IPMA_Data]} - FWD_BUS_LOOKUP = {0: 2, 2: 0} + + def setUp(self): + self.packer = CANPackerPanda("ford_lincoln_base_pt") + self.safety = libpanda_py.libpanda + self.safety.set_safety_hooks(Panda.SAFETY_FORD, Panda.FLAG_FORD_CANFD) + self.safety.init_tests() + + +class TestFordLongitudinalSafetyBase(TestFordSafetyBase): + FWD_BLACKLISTED_ADDRS = {2: [MSG_ACCDATA, MSG_ACCDATA_3, MSG_Lane_Assist_Data1, MSG_LateralMotionControl, + MSG_LateralMotionControl2, MSG_IPMA_Data]} MAX_ACCEL = 2.0 # accel is used for brakes, but openpilot can set positive values MIN_ACCEL = -3.5 @@ -366,11 +396,10 @@ class TestFordLongitudinalSafety(TestFordSafetyBase): MIN_GAS = -0.5 INACTIVE_GAS = -5.0 - def setUp(self): - self.packer = CANPackerPanda("ford_lincoln_base_pt") - self.safety = libpanda_py.libpanda - self.safety.set_safety_hooks(Panda.SAFETY_FORD, Panda.FLAG_FORD_LONG_CONTROL) - self.safety.init_tests() + @classmethod + def setUpClass(cls): + if cls.__name__ == "TestFordLongitudinalSafetyBase": + raise unittest.SkipTest # ACC command def _acc_command_msg(self, gas: float, brake: float, cmbb_deny: bool = False): @@ -408,5 +437,35 @@ def test_brake_safety_check(self): self.assertEqual(should_tx, self._tx(self._acc_command_msg(self.INACTIVE_GAS, brake))) +class TestFordLongitudinalSafety(TestFordLongitudinalSafetyBase): + STEER_MESSAGE = MSG_LateralMotionControl + + TX_MSGS = [ + [MSG_Steering_Data_FD1, 0], [MSG_Steering_Data_FD1, 2], [MSG_ACCDATA, 0], [MSG_ACCDATA_3, 0], [MSG_Lane_Assist_Data1, 0], + [MSG_LateralMotionControl, 0], [MSG_IPMA_Data, 0], + ] + + def setUp(self): + self.packer = CANPackerPanda("ford_lincoln_base_pt") + self.safety = libpanda_py.libpanda + self.safety.set_safety_hooks(Panda.SAFETY_FORD, Panda.FLAG_FORD_LONG_CONTROL) + self.safety.init_tests() + + +class TestFordCANFDLongitudinalSafety(TestFordLongitudinalSafetyBase): + STEER_MESSAGE = MSG_LateralMotionControl2 + + TX_MSGS = [ + [MSG_Steering_Data_FD1, 0], [MSG_Steering_Data_FD1, 2], [MSG_ACCDATA, 0], [MSG_ACCDATA_3, 0], [MSG_Lane_Assist_Data1, 0], + [MSG_LateralMotionControl2, 0], [MSG_IPMA_Data, 0], + ] + + def setUp(self): + self.packer = CANPackerPanda("ford_lincoln_base_pt") + self.safety = libpanda_py.libpanda + self.safety.set_safety_hooks(Panda.SAFETY_FORD, Panda.FLAG_FORD_LONG_CONTROL | Panda.FLAG_FORD_CANFD) + self.safety.init_tests() + + if __name__ == "__main__": unittest.main() From e8da4eab2e5d1ce5219a587e8c5909989d72907b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 15 Jul 2023 22:53:34 -0700 Subject: [PATCH 138/197] SPI: increase buffer size on H7 --- board/drivers/spi.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/board/drivers/spi.h b/board/drivers/spi.h index 2c85184871..5f1711100f 100644 --- a/board/drivers/spi.h +++ b/board/drivers/spi.h @@ -2,7 +2,6 @@ #include "crc.h" -#define SPI_BUF_SIZE 1024U #define SPI_TIMEOUT_US 10000U // got max rate from hitting a non-existent endpoint @@ -10,9 +9,11 @@ #define SPI_IRQ_RATE 16000U #ifdef STM32H7 +#define SPI_BUF_SIZE 2048U __attribute__((section(".ram_d1"))) uint8_t spi_buf_rx[SPI_BUF_SIZE]; -__attribute__((section(".ram_d1"))) uint8_t spi_buf_tx[SPI_BUF_SIZE]; +__attribute__((section(".ram_d2"))) uint8_t spi_buf_tx[SPI_BUF_SIZE]; #else +#define SPI_BUF_SIZE 1024U uint8_t spi_buf_rx[SPI_BUF_SIZE]; uint8_t spi_buf_tx[SPI_BUF_SIZE]; #endif From 5d873444b2cf801ba73f4a457993260df3a412b8 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 16 Jul 2023 19:33:18 -0700 Subject: [PATCH 139/197] SPI: connect by VERSION command (#1495) * SPI: connect by VERSION command * shorter timeout * add exception * simple test * fallback * bootstub check * update comments --------- Co-authored-by: Comma Device --- __init__.py | 1 + python/__init__.py | 32 ++++++++++++++++++++++++++------ python/spi.py | 8 +++++++- tests/hitl/8_spi.py | 16 ++++++++++++++-- 4 files changed, 48 insertions(+), 9 deletions(-) diff --git a/__init__.py b/__init__.py index 21e35b9e98..19cbee3e4f 100644 --- a/__init__.py +++ b/__init__.py @@ -1,4 +1,5 @@ from .python.constants import McuType, BASEDIR, FW_PATH # noqa: F401 +from .python.spi import PandaSpiException, PandaProtocolMismatch # noqa: F401 from .python.serial import PandaSerial # noqa: F401 from .python import (Panda, PandaDFU, # noqa: F401 pack_can_buffer, unpack_can_buffer, calculate_checksum, unpack_log, diff --git a/python/__init__.py b/python/__init__.py index 26a70ff260..0349d161b7 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -17,7 +17,7 @@ from .constants import FW_PATH, McuType from .dfu import PandaDFU from .isotp import isotp_send, isotp_recv -from .spi import PandaSpiHandle, PandaSpiException +from .spi import PandaSpiHandle, PandaSpiException, PandaProtocolMismatch from .usb import PandaUsbHandle __version__ = '0.0.10' @@ -326,16 +326,30 @@ def connect(self, claim=True, wait=False): self.set_power_save(0) @staticmethod - def spi_connect(serial): + def spi_connect(serial, ignore_version=False): # get UID to confirm slave is present and up handle = None spi_serial = None bootstub = None + spi_version = None try: handle = PandaSpiHandle() - dat = handle.controlRead(Panda.REQUEST_IN, 0xc3, 0, 0, 12, timeout=100) - spi_serial = binascii.hexlify(dat).decode() - bootstub = Panda.flasher_present(handle) + + # connect by protcol version + try: + dat = handle.get_protocol_version() + spi_serial = binascii.hexlify(dat[:12]).decode() + pid = dat[13] + if pid not in (0xcc, 0xee): + raise PandaSpiException("invalid bootstub status") + bootstub = pid == 0xee + spi_version = dat[14] + except PandaSpiException: + # fallback, we'll raise a protocol mismatch below + dat = handle.controlRead(Panda.REQUEST_IN, 0xc3, 0, 0, 12, timeout=100) + spi_serial = binascii.hexlify(dat).decode() + bootstub = Panda.flasher_present(handle) + spi_version = 0 except PandaSpiException: pass @@ -345,6 +359,12 @@ def spi_connect(serial): spi_serial = None bootstub = False + # ensure our protocol version matches the panda + if handle is not None and not ignore_version: + if spi_version != handle.PROTOCOL_VERSION: + err = f"panda protocol mismatch: expected {handle.PROTOCOL_VERSION}, got {spi_version}. reflash panda" + raise PandaProtocolMismatch(err) + return handle, spi_serial, bootstub, None @staticmethod @@ -416,7 +436,7 @@ def usb_list(): @staticmethod def spi_list(): - _, serial, _, _ = Panda.spi_connect(None) + _, serial, _, _ = Panda.spi_connect(None, ignore_version=True) if serial is not None: return [serial, ] return [] diff --git a/python/spi.py b/python/spi.py index b73aea1dba..55bfa6d2e4 100644 --- a/python/spi.py +++ b/python/spi.py @@ -40,6 +40,9 @@ class PandaSpiException(Exception): pass +class PandaProtocolMismatch(PandaSpiException): + pass + class PandaSpiUnavailable(PandaSpiException): pass @@ -109,6 +112,9 @@ class PandaSpiHandle(BaseHandle): """ A class that mimics a libusb1 handle for panda SPI communications. """ + + PROTOCOL_VERSION = 1 + def __init__(self): self.dev = SpiDevice() @@ -225,7 +231,7 @@ def _get_version(spi) -> bytes: version_bytes = spi.readbytes(len(vers_str) + 2) if bytes(version_bytes).startswith(vers_str): break - if (time.monotonic() - start) > 0.5: + if (time.monotonic() - start) > 0.01: raise PandaSpiMissingAck rlen = struct.unpack(" Date: Mon, 17 Jul 2023 20:17:18 -0700 Subject: [PATCH 140/197] instructions for flashing internal panda (#1521) * instructions for flashing internal panda * Update board/README.md --- Jenkinsfile | 4 ++-- board/README.md | 5 +++-- tests/{ci_reset_internal_hw.py => reflash_internal_panda.py} | 4 +++- 3 files changed, 8 insertions(+), 5 deletions(-) rename tests/{ci_reset_internal_hw.py => reflash_internal_panda.py} (91%) diff --git a/Jenkinsfile b/Jenkinsfile index 6bf0cb49d9..5d74e6e451 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -82,7 +82,7 @@ pipeline { steps { phone_steps("panda-dos", [ ["build", "scons -j4"], - ["flash", "cd tests/ && ./ci_reset_internal_hw.py"], + ["flash", "cd tests/ && ./reflash_internal_panda.py"], ["test", "cd tests/hitl && HW_TYPES=6 pytest --durations=0 [2-7]*.py -k 'not test_send_recv'"], ]) } @@ -93,7 +93,7 @@ pipeline { steps { phone_steps("panda-tres", [ ["build", "scons -j4"], - ["flash", "cd tests/ && ./ci_reset_internal_hw.py"], + ["flash", "cd tests/ && ./reflash_internal_panda.py"], ["test", "cd tests/hitl && HW_TYPES=9 pytest --durations=0 2*.py [5-9]*.py"], ]) } diff --git a/board/README.md b/board/README.md index ad9091411f..a6831e3a97 100644 --- a/board/README.md +++ b/board/README.md @@ -4,8 +4,8 @@ Programming **Panda** ``` -./recover.py # flash bootstub ./flash.py # flash application +./recover.py # flash bootstub ``` Troubleshooting @@ -13,7 +13,8 @@ Troubleshooting If your panda will not flash and green LED is on, use `recover.py`. If panda is blinking fast with green LED, use `flash.py`. + Otherwise if LED is off and panda can't be seen with `lsusb` command, use [panda paw](https://comma.ai/shop/products/panda-paw) to go into DFU mode. -[dfu-util](http://github.com/dsigma/dfu-util.git) for flashing +If your device has an internal panda and none of the above works, try running `../tests/reflash_internal_panda.py`. diff --git a/tests/ci_reset_internal_hw.py b/tests/reflash_internal_panda.py similarity index 91% rename from tests/ci_reset_internal_hw.py rename to tests/reflash_internal_panda.py index e18b966b6f..c2ad9f8964 100755 --- a/tests/ci_reset_internal_hw.py +++ b/tests/reflash_internal_panda.py @@ -27,6 +27,7 @@ def gpio_set(pin, high): gpio_set(GPIO.HUB_RST_N, 1) # flash bootstub + print("resetting into DFU") gpio_set(GPIO.STM_RST_N, 1) gpio_set(GPIO.STM_BOOT0, 1) time.sleep(1) @@ -34,6 +35,7 @@ def gpio_set(pin, high): gpio_set(GPIO.STM_BOOT0, 0) time.sleep(1) + print("flashing bootstub") PandaDFU(None).recover() gpio_set(GPIO.STM_RST_N, 1) @@ -41,7 +43,7 @@ def gpio_set(pin, high): gpio_set(GPIO.STM_RST_N, 0) time.sleep(1) - # flash app + print("flashing app") p = Panda() assert p.bootstub p.flash() From 268f6bc2fbd29a4a7a8d7ddcb0d04825cb98a173 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 18 Jul 2023 00:03:17 -0700 Subject: [PATCH 141/197] python: lock hw device inside loop (#1522) --- __init__.py | 4 +- python/__init__.py | 1 - python/constants.py | 2 + python/spi.py | 132 +++++++++++++++++++++++--------------------- 4 files changed, 72 insertions(+), 67 deletions(-) diff --git a/__init__.py b/__init__.py index 19cbee3e4f..dfb2bbf1c9 100644 --- a/__init__.py +++ b/__init__.py @@ -1,6 +1,6 @@ -from .python.constants import McuType, BASEDIR, FW_PATH # noqa: F401 +from .python.constants import McuType, BASEDIR, FW_PATH, USBPACKET_MAX_SIZE # noqa: F401 from .python.spi import PandaSpiException, PandaProtocolMismatch # noqa: F401 from .python.serial import PandaSerial # noqa: F401 from .python import (Panda, PandaDFU, # noqa: F401 pack_can_buffer, unpack_can_buffer, calculate_checksum, unpack_log, - DLC_TO_LEN, LEN_TO_DLC, ALTERNATIVE_EXPERIENCE, USBPACKET_MAX_SIZE, CANPACKET_HEAD_SIZE) + DLC_TO_LEN, LEN_TO_DLC, ALTERNATIVE_EXPERIENCE, CANPACKET_HEAD_SIZE) diff --git a/python/__init__.py b/python/__init__.py index 0349d161b7..5558a9ab33 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -26,7 +26,6 @@ LOGLEVEL = os.environ.get('LOGLEVEL', 'INFO').upper() logging.basicConfig(level=LOGLEVEL, format='%(message)s') -USBPACKET_MAX_SIZE = 0x40 CANPACKET_HEAD_SIZE = 0x6 DLC_TO_LEN = [0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64] LEN_TO_DLC = {length: dlc for (dlc, length) in enumerate(DLC_TO_LEN)} diff --git a/python/constants.py b/python/constants.py index 4c3e778ad1..16409ac312 100644 --- a/python/constants.py +++ b/python/constants.py @@ -5,6 +5,8 @@ BASEDIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../") FW_PATH = os.path.join(BASEDIR, "board/obj/") +USBPACKET_MAX_SIZE = 0x40 + class McuConfig(NamedTuple): mcu: str mcu_idcode: int diff --git a/python/spi.py b/python/spi.py index 55bfa6d2e4..f14a04ef49 100644 --- a/python/spi.py +++ b/python/spi.py @@ -9,10 +9,10 @@ import threading from contextlib import contextmanager from functools import reduce -from typing import List, Optional +from typing import Callable, List, Optional from .base import BaseHandle, BaseSTBootloaderHandle, TIMEOUT -from .constants import McuType, MCU_TYPE_BY_IDCODE +from .constants import McuType, MCU_TYPE_BY_IDCODE, USBPACKET_MAX_SIZE from .utils import crc8_pedal try: @@ -115,13 +115,13 @@ class PandaSpiHandle(BaseHandle): PROTOCOL_VERSION = 1 - def __init__(self): + def __init__(self) -> None: self.dev = SpiDevice() - self._transfer = self._transfer_spidev + self._transfer_raw: Callable[[SpiDevice, int, bytes, int, int, bool], bytes] = self._transfer_spidev if "KERN" in os.environ: - self._transfer = self._transfer_kernel_driver + self._transfer_raw = self._transfer_kernel_driver self.tx_buf = bytearray(1024) self.rx_buf = bytearray(1024) @@ -134,25 +134,65 @@ def __init__(self): self.fileno = self.dev._spidev.fileno() # helpers - def _calc_checksum(self, data: List[int]) -> int: + def _calc_checksum(self, data: bytes) -> int: cksum = CHECKSUM_START for b in data: cksum ^= b return cksum - def _wait_for_ack(self, spi, ack_val: int, timeout: int, tx: int) -> None: + 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, ])[0] - if dat == NACK: + dat = spi.xfer2([tx, ] * length) + if dat[0] == NACK: raise PandaSpiNackResponse - elif dat == ack_val: - return + elif dat[0] == ack_val: + return bytes(dat) raise PandaSpiMissingAck + 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) + + logging.debug("- send header") + packet = struct.pack(" max_rx_len: + raise PandaSpiException(f"response length greater than max ({max_rx_len} {response_len})") + + # read rest + remaining = response_len - 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] + def _transfer_kernel_driver(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes: self.tx_buf[:len(data)] = data self.ioctl_data.endpoint = endpoint @@ -169,7 +209,7 @@ def _transfer_kernel_driver(self, spi, endpoint: int, data, timeout: int, max_rx raise PandaSpiException(f"ioctl returned {ret}") return bytes(self.rx_buf[:ret]) - def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes: + def _transfer(self, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes: logging.debug("starting transfer: endpoint=%d, max_rx_len=%d", endpoint, max_rx_len) logging.debug("==============================================") @@ -179,44 +219,12 @@ def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: i while (time.monotonic() - start_time) < timeout*1e-3: n += 1 logging.debug("\ntry #%d", n) - try: - logging.debug("- send header") - packet = struct.pack(" max_rx_len: - raise PandaSpiException("response length greater than max") - - logging.debug("- receiving response") - dat = bytes(spi.xfer2(b"\x00" * (response_len + 1))) - if self._calc_checksum([DACK, *response_len_bytes, *dat]) != 0: - raise PandaSpiBadChecksum - - return dat[:-1] - except PandaSpiException as e: - exc = e - logging.debug("SPI transfer failed, retrying", exc_info=True) + with self.dev.acquire() as spi: + try: + return self._transfer_raw(spi, endpoint, data, timeout, max_rx_len, expect_disconnect) + except PandaSpiException as e: + exc = e + logging.debug("SPI transfer failed, retrying", exc_info=True) raise exc @@ -261,28 +269,24 @@ def close(self): self.dev.close() def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = TIMEOUT, expect_disconnect: bool = False): - with self.dev.acquire() as spi: - return self._transfer(spi, 0, struct.pack(" int: - with self.dev.acquire() as spi: - 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) + for x in range(math.ceil(len(data) / XFER_SIZE)): + self._transfer(endpoint, data[XFER_SIZE*x:XFER_SIZE*(x+1)], timeout) + return len(data) 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 / XFER_SIZE)): - d = self._transfer(spi, endpoint, [], timeout, max_rx_len=XFER_SIZE) - ret += d - if len(d) < XFER_SIZE: - break + for _ in range(math.ceil(length / XFER_SIZE)): + d = self._transfer(endpoint, [], timeout, max_rx_len=XFER_SIZE) + ret += d + if len(d) < XFER_SIZE: + break return bytes(ret) From 5ca2d96b54c618bc822a75f1a5721c06078f3c08 Mon Sep 17 00:00:00 2001 From: Comma Device Date: Tue, 18 Jul 2023 01:06:37 -0700 Subject: [PATCH 142/197] python lib: ensure full response is read in remaining --- python/spi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/spi.py b/python/spi.py index f14a04ef49..c6f3fd5b7e 100644 --- a/python/spi.py +++ b/python/spi.py @@ -183,7 +183,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})") # read rest - remaining = response_len - preread_len + remaining = (response_len + 1) - preread_len if remaining > 0: dat += bytes(spi.readbytes(remaining)) From 05e0f4a9441351a3577c3ec83a145dfe4f13be08 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 18 Jul 2023 12:05:14 -0700 Subject: [PATCH 143/197] IsoTpMessage: fix length mask for single frames (#1523) * Update uds.py * this was completely broken * wait nevermind --- python/uds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/uds.py b/python/uds.py index 5374b5199d..9b25dffe66 100644 --- a/python/uds.py +++ b/python/uds.py @@ -473,7 +473,7 @@ def _isotp_rx_next(self, rx_data: bytes) -> ISOTP_FRAME_TYPE: # assert len(rx_data) == self.max_len, f"isotp - rx: invalid CAN frame length: {len(rx_data)}" if rx_data[0] >> 4 == ISOTP_FRAME_TYPE.SINGLE: - self.rx_len = rx_data[0] & 0xFF + self.rx_len = rx_data[0] & 0x0F assert self.rx_len < self.max_len, f"isotp - rx: invalid single frame length: {self.rx_len}" self.rx_dat = rx_data[1:1 + self.rx_len] self.rx_idx = 0 From 0acb7b9ee4ba228a34c9eea732594521e6958924 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 18 Jul 2023 12:31:16 -0700 Subject: [PATCH 144/197] CI: Bump refs (#1520) * bump refs * bump opendbc * bump refs * bump refs --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index d1684abcd7..93de15554a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -51,8 +51,8 @@ RUN curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-instal ENV PATH="/root/.pyenv/bin:/root/.pyenv/shims:${PATH}" ENV PANDA_PATH=/tmp/openpilot/panda -ENV OPENPILOT_REF="e276d2a417a5133fb91c93b2ef30df68a7d5f225" -ENV OPENDBC_REF="9ae9fbfe56f79dca66c673a6479751a15ad61780" +ENV OPENPILOT_REF="29837b872802db8b445653f63c2c1ab67e7a27af" +ENV OPENDBC_REF="3ef35ed2298a3a9d199f9145409547710065884c" COPY requirements.txt /tmp/ RUN pyenv install 3.11.4 && \ From 72032e2850cffdf8d59d7e7ac48bf85464d4b1b3 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 19 Jul 2023 23:32:31 -0700 Subject: [PATCH 145/197] Subaru: use generator for limits (#1527) * generator * remove torque reference * fix misra * misra --- board/safety/safety_subaru.h | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/board/safety/safety_subaru.h b/board/safety/safety_subaru.h index b88d41038a..8127b6d65e 100644 --- a/board/safety/safety_subaru.h +++ b/board/safety/safety_subaru.h @@ -1,24 +1,18 @@ -const SteeringLimits SUBARU_STEERING_LIMITS = { - .max_steer = 2047, - .max_rt_delta = 940, - .max_rt_interval = 250000, - .max_rate_up = 50, - .max_rate_down = 70, - .driver_torque_factor = 50, - .driver_torque_allowance = 60, - .type = TorqueDriverLimited, -}; +#define SUBARU_STEERING_LIMITS_GENERATOR(steer_max, rate_up, rate_down) \ + { \ + .max_steer = (steer_max), \ + .max_rt_delta = 940, \ + .max_rt_interval = 250000, \ + .max_rate_up = (rate_up), \ + .max_rate_down = (rate_down), \ + .driver_torque_factor = 50, \ + .driver_torque_allowance = 60, \ + .type = TorqueDriverLimited, \ + } \ + +const SteeringLimits SUBARU_STEERING_LIMITS = SUBARU_STEERING_LIMITS_GENERATOR(2047, 50, 70); +const SteeringLimits SUBARU_GEN2_STEERING_LIMITS = SUBARU_STEERING_LIMITS_GENERATOR(1000, 40, 40); -const SteeringLimits SUBARU_GEN2_STEERING_LIMITS = { - .max_steer = 1000, - .max_rt_delta = 940, - .max_rt_interval = 250000, - .max_rate_up = 40, - .max_rate_down = 40, - .driver_torque_factor = 50, - .driver_torque_allowance = 60, - .type = TorqueDriverLimited, -}; #define MSG_SUBARU_Brake_Status 0x13c #define MSG_SUBARU_CruiseControl 0x240 From 8258d18e878a8948ecbdd829d88deb07bfcb8573 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 20 Jul 2023 11:07:13 -0700 Subject: [PATCH 146/197] spi: bump version --- board/drivers/spi.h | 2 +- python/spi.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/board/drivers/spi.h b/board/drivers/spi.h index 5f1711100f..af9dd8a574 100644 --- a/board/drivers/spi.h +++ b/board/drivers/spi.h @@ -84,7 +84,7 @@ uint16_t spi_version_packet(uint8_t *out) { data_len += 1U; // SPI protocol version - out[data_pos + data_len] = 0x1; + out[data_pos + data_len] = 0x2; data_len += 1U; // data length diff --git a/python/spi.py b/python/spi.py index c6f3fd5b7e..abba7940a4 100644 --- a/python/spi.py +++ b/python/spi.py @@ -113,7 +113,7 @@ class PandaSpiHandle(BaseHandle): A class that mimics a libusb1 handle for panda SPI communications. """ - PROTOCOL_VERSION = 1 + PROTOCOL_VERSION = 2 def __init__(self) -> None: self.dev = SpiDevice() From 1eac0d7634ee54bed532d1e1968f05ac4897d973 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 20 Jul 2023 11:25:06 -0700 Subject: [PATCH 147/197] HITL: remove old check, it's always checked on panda connect now --- tests/hitl/8_spi.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/hitl/8_spi.py b/tests/hitl/8_spi.py index b3ef487ae2..d609446d5d 100644 --- a/tests/hitl/8_spi.py +++ b/tests/hitl/8_spi.py @@ -45,8 +45,6 @@ def test_protocol_version_data(self, p): bstub = v[13] assert bstub == (0xEE if bootstub else 0xCC) - assert v[14:] == b"\x01" - def test_all_comm_types(self, mocker, p): spy = mocker.spy(p._handle, '_wait_for_ack') From dd78b2bf6c9d63ef59e81d0c400e85c8b477a8be Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 20 Jul 2023 13:07:06 -0700 Subject: [PATCH 148/197] python lib: allow infinite timeout --- python/base.py | 3 +-- python/spi.py | 13 ++++++------- python/usb.py | 3 +-- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/python/base.py b/python/base.py index 5bfa564892..d19a2b8612 100644 --- a/python/base.py +++ b/python/base.py @@ -1,5 +1,4 @@ from abc import ABC, abstractmethod -from typing import List from .constants import McuType @@ -24,7 +23,7 @@ def controlRead(self, request_type: int, request: int, value: int, index: int, l ... @abstractmethod - def bulkWrite(self, endpoint: int, data: List[int], timeout: int = TIMEOUT) -> int: + def bulkWrite(self, endpoint: int, data: bytes, timeout: int = TIMEOUT) -> int: ... @abstractmethod diff --git a/python/spi.py b/python/spi.py index abba7940a4..ad0225b1e0 100644 --- a/python/spi.py +++ b/python/spi.py @@ -32,7 +32,7 @@ MIN_ACK_TIMEOUT_MS = 100 MAX_XFER_RETRY_COUNT = 5 -XFER_SIZE = 1000 +XFER_SIZE = 0x40*31 DEV_PATH = "/dev/spidev0.0" @@ -164,7 +164,6 @@ def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: i logging.debug("- waiting for header ACK") self._wait_for_ack(spi, HACK, MIN_ACK_TIMEOUT_MS, 0x11) - # send data logging.debug("- sending data") packet = bytes([*data, self._calc_checksum(data)]) spi.xfer2(packet) @@ -187,6 +186,7 @@ def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: i if remaining > 0: dat += bytes(spi.readbytes(remaining)) + dat = dat[:3 + response_len + 1] if self._calc_checksum(dat) != 0: raise PandaSpiBadChecksum @@ -216,7 +216,7 @@ def _transfer(self, endpoint: int, data, timeout: int, max_rx_len: int = 1000, e n = 0 start_time = time.monotonic() exc = PandaSpiException() - while (time.monotonic() - start_time) < timeout*1e-3: + while (timeout == 0) or (time.monotonic() - start_time) < timeout*1e-3: n += 1 logging.debug("\ntry #%d", n) with self.dev.acquire() as spi: @@ -274,20 +274,19 @@ def controlWrite(self, request_type: int, request: int, value: int, index: int, def controlRead(self, request_type: int, request: int, value: int, index: int, length: int, timeout: int = TIMEOUT): return self._transfer(0, struct.pack(" int: + def bulkWrite(self, endpoint: int, data: bytes, timeout: int = TIMEOUT) -> int: for x in range(math.ceil(len(data) / XFER_SIZE)): self._transfer(endpoint, data[XFER_SIZE*x:XFER_SIZE*(x+1)], timeout) return len(data) def bulkRead(self, endpoint: int, length: int, timeout: int = TIMEOUT) -> bytes: - ret: List[int] = [] + ret = b"" for _ in range(math.ceil(length / XFER_SIZE)): d = self._transfer(endpoint, [], timeout, max_rx_len=XFER_SIZE) ret += d if len(d) < XFER_SIZE: break - return bytes(ret) + return ret class STBootloaderSPIHandle(BaseSTBootloaderHandle): diff --git a/python/usb.py b/python/usb.py index 140b4cf9eb..0f1bff7c16 100644 --- a/python/usb.py +++ b/python/usb.py @@ -1,5 +1,4 @@ import struct -from typing import List from .base import BaseHandle, BaseSTBootloaderHandle, TIMEOUT from .constants import McuType @@ -17,7 +16,7 @@ def controlWrite(self, request_type: int, request: int, value: int, index: int, def controlRead(self, request_type: int, request: int, value: int, index: int, length: int, timeout: int = TIMEOUT): return self._libusb_handle.controlRead(request_type, request, value, index, length, timeout) - def bulkWrite(self, endpoint: int, data: List[int], timeout: int = TIMEOUT) -> int: + def bulkWrite(self, endpoint: int, data: bytes, timeout: int = TIMEOUT) -> int: return self._libusb_handle.bulkWrite(endpoint, data, timeout) # type: ignore def bulkRead(self, endpoint: int, length: int, timeout: int = TIMEOUT) -> bytes: From 933a19af848104d2a6e5b50e3aca441960fe7a80 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 21 Jul 2023 00:06:59 -0700 Subject: [PATCH 149/197] CI: bump refs (#1529) ci bump refs --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 93de15554a..7dc77ec72d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -51,8 +51,8 @@ RUN curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-instal ENV PATH="/root/.pyenv/bin:/root/.pyenv/shims:${PATH}" ENV PANDA_PATH=/tmp/openpilot/panda -ENV OPENPILOT_REF="29837b872802db8b445653f63c2c1ab67e7a27af" -ENV OPENDBC_REF="3ef35ed2298a3a9d199f9145409547710065884c" +ENV OPENPILOT_REF="80bbba14f74e57bbe90216dfd0a99f6f68d77ca2" +ENV OPENDBC_REF="5880fbbccf5a670631b51836f20e446de643795a" COPY requirements.txt /tmp/ RUN pyenv install 3.11.4 && \ From ed8ff7e48ad41b5b7c0ce9df5bb2bb5024b6429d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 24 Jul 2023 15:01:19 -0700 Subject: [PATCH 150/197] H7: explicitly disable GMLAN for now (#1532) --- board/drivers/gmlan_alt.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/board/drivers/gmlan_alt.h b/board/drivers/gmlan_alt.h index 266a683e6e..53f46c2e02 100644 --- a/board/drivers/gmlan_alt.h +++ b/board/drivers/gmlan_alt.h @@ -272,6 +272,7 @@ bool bitbang_gmlan(CANPacket_t *to_bang) { gmlan_send_ok = true; gmlan_alt_mode = BITBANG; +#ifndef STM32H7 if (gmlan_sendmax == -1) { int len = get_bit_message(pkt_stuffed, to_bang); gmlan_fail_count = 0; @@ -285,5 +286,8 @@ bool bitbang_gmlan(CANPacket_t *to_bang) { // 33kbps setup_timer(); } +#else + UNUSED(to_bang); +#endif return gmlan_send_ok; } From 1b706040cd0b9af80a1604bb15d498f178655754 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 1 Aug 2023 11:14:27 -0700 Subject: [PATCH 151/197] pre-commit: autoupdate hooks (#1536) * Update pre-commit hook versions * fix --------- Co-authored-by: adeebshihadeh --- .pre-commit-config.yaml | 2 +- python/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1330b0ce21..5b040d111d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: additional_dependencies: ['git+https://github.com/numpy/numpy-stubs', 'types-requests', 'types-atomicwrites', 'types-pycurl'] - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 + rev: 6.1.0 hooks: - id: flake8 args: diff --git a/python/__init__.py b/python/__init__.py index 5558a9ab33..d4698c468a 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -894,7 +894,7 @@ def serial_read(self, port_number): def serial_write(self, port_number, ln): ret = 0 - if type(ln) == str: + 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]) From 7f1944cf430cd9307a6eb41178fc8604541899af Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 1 Aug 2023 16:17:42 -0700 Subject: [PATCH 152/197] debug endpoint for custom clock source period (#1538) * debug endpoint for custom clock source period * cleanup --------- Co-authored-by: Comma Device --- board/drivers/clock_source.h | 4 ++++ board/main_comms.h | 4 ++++ python/__init__.py | 3 +++ 3 files changed, 11 insertions(+) diff --git a/board/drivers/clock_source.h b/board/drivers/clock_source.h index d1b5724f16..11b2fa3247 100644 --- a/board/drivers/clock_source.h +++ b/board/drivers/clock_source.h @@ -1,6 +1,10 @@ #define CLOCK_SOURCE_PERIOD_MS 50U #define CLOCK_SOURCE_PULSE_LEN_MS 2U +void clock_source_set_period(uint8_t period) { + register_set(&(TIM1->ARR), ((period*10U) - 1U), 0xFFFFU); +} + void clock_source_init(void) { // Setup timer register_set(&(TIM1->PSC), ((APB2_TIMER_FREQ*100U)-1U), 0xFFFFU); // Tick on 0.1 ms diff --git a/board/main_comms.h b/board/main_comms.h index c1591a0608..b0b9823c9b 100644 --- a/board/main_comms.h +++ b/board/main_comms.h @@ -396,6 +396,10 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { can_loopback = (req->param1 > 0U); can_init_all(); break; + // **** 0xe6: set custom clock source period + case 0xe6: + clock_source_set_period(req->param1); + break; // **** 0xe7: set power save state case 0xe7: set_power_save_state(req->param1); diff --git a/python/__init__.py b/python/__init__.py index d4698c468a..60a84ff41a 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -1019,6 +1019,9 @@ def set_siren(self, enabled): def set_green_led(self, enabled): self._handle.controlWrite(Panda.REQUEST_OUT, 0xf7, int(enabled), 0, b'') + def set_clock_source_period(self, period): + self._handle.controlWrite(Panda.REQUEST_OUT, 0xe6, period, 0, b'') + # ****************** Logging ***************** def get_logs(self, last_id=None, get_all=False): assert (last_id is None) or (0 <= last_id < 0xFFFF) From a945053ade930d325f79d36c917f8013072a3860 Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Tue, 1 Aug 2023 16:33:34 -0700 Subject: [PATCH 153/197] Force relay drive (#1539) force drive Co-authored-by: Comma Device --- board/drivers/harness.h | 13 +++++++------ board/main.c | 8 ++++---- board/main_comms.h | 6 +++++- python/__init__.py | 3 +++ 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/board/drivers/harness.h b/board/drivers/harness.h index 6bf3fe1b9c..36f73f2573 100644 --- a/board/drivers/harness.h +++ b/board/drivers/harness.h @@ -25,7 +25,8 @@ struct harness_configuration { uint8_t adc_channel_SBU2; }; -void set_intercept_relay(bool intercept) { +// The ignition relay is only used for testing purposes +void set_intercept_relay(bool intercept, bool ignition_relay) { if (current_board->harness_config->has_harness) { bool drive_relay = intercept; if (harness.status == HARNESS_STATUS_NC) { @@ -33,7 +34,7 @@ void set_intercept_relay(bool intercept) { drive_relay = false; } - if (drive_relay) { + if (drive_relay || ignition_relay) { harness.relay_driven = true; } @@ -41,14 +42,14 @@ void set_intercept_relay(bool intercept) { while (harness.sbu_adc_lock == true) {} if (harness.status == HARNESS_STATUS_NORMAL) { - set_gpio_output(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, true); + set_gpio_output(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, !ignition_relay); set_gpio_output(current_board->harness_config->GPIO_relay_SBU2, current_board->harness_config->pin_relay_SBU2, !drive_relay); } else { set_gpio_output(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, !drive_relay); - set_gpio_output(current_board->harness_config->GPIO_relay_SBU2, current_board->harness_config->pin_relay_SBU2, true); + set_gpio_output(current_board->harness_config->GPIO_relay_SBU2, current_board->harness_config->pin_relay_SBU2, !ignition_relay); } - if (!drive_relay) { + if (!(drive_relay || ignition_relay)) { harness.relay_driven = false; } } @@ -129,5 +130,5 @@ void harness_init(void) { } // keep buses connected by default - set_intercept_relay(false); + set_intercept_relay(false, false); } diff --git a/board/main.c b/board/main.c index e9e42e2be8..1cb1b1b4bc 100644 --- a/board/main.c +++ b/board/main.c @@ -82,21 +82,21 @@ void set_safety_mode(uint16_t mode, uint16_t param) { switch (mode_copy) { case SAFETY_SILENT: - set_intercept_relay(false); + set_intercept_relay(false, false); if (current_board->has_obd) { current_board->set_can_mode(CAN_MODE_NORMAL); } can_silent = ALL_CAN_SILENT; break; case SAFETY_NOOUTPUT: - set_intercept_relay(false); + set_intercept_relay(false, false); if (current_board->has_obd) { current_board->set_can_mode(CAN_MODE_NORMAL); } can_silent = ALL_CAN_LIVE; break; case SAFETY_ELM327: - set_intercept_relay(false); + set_intercept_relay(false, false); heartbeat_counter = 0U; heartbeat_lost = false; if (current_board->has_obd) { @@ -109,7 +109,7 @@ void set_safety_mode(uint16_t mode, uint16_t param) { can_silent = ALL_CAN_LIVE; break; default: - set_intercept_relay(true); + set_intercept_relay(true, false); heartbeat_counter = 0U; heartbeat_lost = false; if (current_board->has_obd) { diff --git a/board/main_comms.h b/board/main_comms.h index b0b9823c9b..00d5e7a0b1 100644 --- a/board/main_comms.h +++ b/board/main_comms.h @@ -182,8 +182,8 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { (void)memcpy(resp, ((uint8_t *)UID_BASE), 12); resp_len = 12; break; + // **** 0xc4: get interrupt call rate case 0xc4: - // **** 0xc4: get interrupt call rate if (req->param1 < NUM_INTERRUPTS) { uint32_t load = interrupts[req->param1].call_rate; resp[0] = (load & 0x000000FFU); @@ -193,6 +193,10 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { resp_len = 4U; } break; + // **** 0xc5: DEBUG: drive relay + case 0xc5: + set_intercept_relay((req->param1 & 0x1U), (req->param1 & 0x2U)); + break; // **** 0xd0: fetch serial (aka the provisioned dongle ID) case 0xd0: // addresses are OTP diff --git a/python/__init__.py b/python/__init__.py index 60a84ff41a..592a4bff4b 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -1022,6 +1022,9 @@ def set_green_led(self, enabled): def set_clock_source_period(self, period): self._handle.controlWrite(Panda.REQUEST_OUT, 0xe6, period, 0, b'') + def force_relay_drive(self, intercept_relay_drive, ignition_relay_drive): + self._handle.controlWrite(Panda.REQUEST_OUT, 0xc5, (int(intercept_relay_drive) | int(ignition_relay_drive) << 1), 0, b'') + # ****************** Logging ***************** def get_logs(self, last_id=None, get_all=False): assert (last_id is None) or (0 <= last_id < 0xFFFF) From b4700a876f20f22eb6981d74ca411cef98372e00 Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Tue, 1 Aug 2023 20:48:10 -0700 Subject: [PATCH 154/197] Stable ADC on H7 (#1540) * 256x oversampling * reduce to 128x * fix misra --------- Co-authored-by: Comma Device --- board/stm32h7/lladc.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/board/stm32h7/lladc.h b/board/stm32h7/lladc.h index 29d5e19d69..0a52b78d37 100644 --- a/board/stm32h7/lladc.h +++ b/board/stm32h7/lladc.h @@ -18,12 +18,12 @@ void adc_init(void) { } uint16_t adc_get_raw(uint8_t channel) { - ADC1->SQR1 &= ~(ADC_SQR1_L); ADC1->SQR1 = ((uint32_t) channel << 6U); - ADC1->SMPR1 = (0x7U << (channel * 3U) ); + ADC1->SMPR1 = (0x2U << (channel * 3U)); ADC1->PCSEL_RES0 = (0x1U << channel); + ADC1->CFGR2 = (127U << ADC_CFGR2_OVSR_Pos) | (0x7U << ADC_CFGR2_OVSS_Pos) | ADC_CFGR2_ROVSE; ADC1->CR |= ADC_CR_ADSTART; while (!(ADC1->ISR & ADC_ISR_EOC)); From feef2e6c71bed5fc6d60000330eb86ba05367666 Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Tue, 1 Aug 2023 21:31:15 -0700 Subject: [PATCH 155/197] Harness tick at 8Hz (#1541) harness tick at 8Hz Co-authored-by: Comma Device --- board/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/board/main.c b/board/main.c index 1cb1b1b4bc..72da6b84f3 100644 --- a/board/main.c +++ b/board/main.c @@ -157,6 +157,7 @@ void tick_handler(void) { // tick drivers at 8Hz fan_tick(); usb_tick(); + harness_tick(); simple_watchdog_kick(); // decimated to 1Hz @@ -185,7 +186,6 @@ void tick_handler(void) { current_board->set_led(LED_BLUE, (uptime_cnt & 1U) && (power_save_status == POWER_SAVE_STATUS_ENABLED)); // tick drivers at 1Hz - harness_tick(); logging_tick(); const bool recent_heartbeat = heartbeat_counter == 0U; From 1154eb2d75f6a0b416d587b22177c759acf5f8e4 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 2 Aug 2023 15:13:01 -0700 Subject: [PATCH 156/197] Subaru: cleanup panda logic (#1543) cleanup subaru --- board/safety/safety_subaru.h | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/board/safety/safety_subaru.h b/board/safety/safety_subaru.h index 8127b6d65e..fcf806ffe0 100644 --- a/board/safety/safety_subaru.h +++ b/board/safety/safety_subaru.h @@ -98,7 +98,7 @@ static int subaru_rx_hook(CANPacket_t *to_push) { if (valid) { const int bus = GET_BUS(to_push); - const int alt_bus = subaru_gen2 ? SUBARU_ALT_BUS : SUBARU_MAIN_BUS; + const int alt_main_bus = subaru_gen2 ? SUBARU_ALT_BUS : SUBARU_MAIN_BUS; int addr = GET_ADDR(to_push); if ((addr == MSG_SUBARU_Steering_Torque) && (bus == SUBARU_MAIN_BUS)) { @@ -109,18 +109,18 @@ static int subaru_rx_hook(CANPacket_t *to_push) { } // enter controls on rising edge of ACC, exit controls on ACC off - if ((addr == MSG_SUBARU_CruiseControl) && (bus == alt_bus)) { + if ((addr == MSG_SUBARU_CruiseControl) && (bus == alt_main_bus)) { bool cruise_engaged = GET_BIT(to_push, 41U) != 0U; pcm_cruise_check(cruise_engaged); } // update vehicle moving with any non-zero wheel speed - if ((addr == MSG_SUBARU_Wheel_Speeds) && (bus == alt_bus)) { + if ((addr == MSG_SUBARU_Wheel_Speeds) && (bus == alt_main_bus)) { vehicle_moving = ((GET_BYTES(to_push, 0, 4) >> 12) != 0U) || (GET_BYTES(to_push, 4, 4) != 0U); } - if ((addr == MSG_SUBARU_Brake_Status) && (bus == alt_bus)) { - brake_pressed = ((GET_BYTE(to_push, 7) >> 6) & 1U); + if ((addr == MSG_SUBARU_Brake_Status) && (bus == alt_main_bus)) { + brake_pressed = GET_BIT(to_push, 62U) != 0U; } if ((addr == MSG_SUBARU_Throttle) && (bus == SUBARU_MAIN_BUS)) { @@ -136,6 +136,7 @@ static int subaru_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); + bool violation = false; if (subaru_gen2) { tx = msg_allowed(to_send, SUBARU_GEN2_TX_MSGS, SUBARU_GEN2_TX_MSGS_LEN); @@ -149,10 +150,11 @@ static int subaru_tx_hook(CANPacket_t *to_send) { desired_torque = -1 * to_signed(desired_torque, 13); const SteeringLimits limits = subaru_gen2 ? SUBARU_GEN2_STEERING_LIMITS : SUBARU_STEERING_LIMITS; - if (steer_torque_cmd_checks(desired_torque, -1, limits)) { - tx = 0; - } + violation |= steer_torque_cmd_checks(desired_torque, -1, limits); + } + if (violation){ + tx = 0; } return tx; } From 3e429ac0f8607e8558c51d7ae1a6279d276d1523 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 2 Aug 2023 21:09:09 -0700 Subject: [PATCH 157/197] Honda: remove duplicated relay transition time check (#1544) already checked --- board/safety/safety_honda.h | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/board/safety/safety_honda.h b/board/safety/safety_honda.h index e8100cad72..83e671a5d3 100644 --- a/board/safety/safety_honda.h +++ b/board/safety/safety_honda.h @@ -235,20 +235,18 @@ static int honda_rx_hook(CANPacket_t *to_push) { int bus_rdr_car = (honda_hw == HONDA_BOSCH) ? 0 : 2; // radar bus, car side bool stock_ecu_detected = false; - if (safety_mode_cnt > RELAY_TRNS_TIMEOUT) { - // If steering controls messages are received on the destination bus, it's an indication - // that the relay might be malfunctioning - if ((addr == 0xE4) || (addr == 0x194)) { - if (((honda_hw != HONDA_NIDEC) && (bus == bus_rdr_car)) || ((honda_hw == HONDA_NIDEC) && (bus == 0))) { - stock_ecu_detected = true; - } - } - // If Honda Bosch longitudinal mode is selected we need to ensure the radar is turned off - // Verify this by ensuring ACC_CONTROL (0x1DF) is not received on the PT bus - if (honda_bosch_long && !honda_bosch_radarless && (bus == pt_bus) && (addr == 0x1DF)) { + // If steering controls messages are received on the destination bus, it's an indication + // that the relay might be malfunctioning + if ((addr == 0xE4) || (addr == 0x194)) { + if (((honda_hw != HONDA_NIDEC) && (bus == bus_rdr_car)) || ((honda_hw == HONDA_NIDEC) && (bus == 0))) { stock_ecu_detected = true; } } + // If Honda Bosch longitudinal mode is selected we need to ensure the radar is turned off + // Verify this by ensuring ACC_CONTROL (0x1DF) is not received on the PT bus + if (honda_bosch_long && !honda_bosch_radarless && (bus == pt_bus) && (addr == 0x1DF)) { + stock_ecu_detected = true; + } generic_rx_checks(stock_ecu_detected); } From f37f8d90bac131b00bbb0e18514b162f475fabdf Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 3 Aug 2023 19:07:41 -0700 Subject: [PATCH 158/197] cleanup build, prep for jungle (#1549) * cleanup build, prep for jungle * cleanup * update pedal obj path --- SConscript | 179 +++++++++++++++++++++++++++++++++++ board/SConscript | 191 ++------------------------------------ board/can_definitions.h | 1 - board/pedal/SConscript | 31 +++++++ board/pedal/recover.sh | 4 +- tests/libpanda/SConscript | 1 + tests/pedal/test_pedal.py | 4 +- 7 files changed, 223 insertions(+), 188 deletions(-) create mode 100644 board/pedal/SConscript diff --git a/SConscript b/SConscript index ed3aff754d..84f82b91e3 100644 --- a/SConscript +++ b/SConscript @@ -1,6 +1,185 @@ +import os +import subprocess + + +PREFIX = "arm-none-eabi-" +BUILDER = "DEV" + +common_flags = [] + +if os.getenv("RELEASE"): + BUILD_TYPE = "RELEASE" + cert_fn = os.getenv("CERT") + assert cert_fn is not None, 'No certificate file specified. Please set CERT env variable' + assert os.path.exists(cert_fn), 'Certificate file not found. Please specify absolute path' +else: + BUILD_TYPE = "DEBUG" + cert_fn = File("#certs/debug").srcnode().abspath + common_flags += ["-DALLOW_DEBUG"] + + if os.getenv("DEBUG"): + common_flags += ["-DDEBUG"] + +def objcopy(source, target, env, for_signature): + return '$OBJCOPY -O binary %s %s' % (source[0], target[0]) + +def get_version(builder, build_type): + try: + git = subprocess.check_output(["git", "rev-parse", "--short=8", "HEAD"], encoding='utf8').strip() + except subprocess.CalledProcessError: + git = "unknown" + return f"{builder}-{git}-{build_type}" + +def get_key_header(name): + from Crypto.PublicKey import RSA + + public_fn = File(f'#certs/{name}.pub').srcnode().abspath + with open(public_fn) as f: + rsa = RSA.importKey(f.read()) + assert(rsa.size_in_bits() == 1024) + + rr = pow(2**1024, 2, rsa.n) + n0inv = 2**32 - pow(rsa.n, -1, 2**32) + + r = [ + f"RSAPublicKey {name}_rsa_key = {{", + f" .len = 0x20,", + f" .n0inv = {n0inv}U,", + f" .n = {to_c_uint32(rsa.n)},", + f" .rr = {to_c_uint32(rr)},", + f" .exponent = {rsa.e},", + f"}};", + ] + return r + +def to_c_uint32(x): + nums = [] + for _ in range(0x20): + nums.append(x % (2**32)) + x //= (2**32) + return "{" + 'U,'.join(map(str, nums)) + "U}" + + +def build_project(project_name, project, extra_flags): + linkerscript_fn = File(project["LINKER_SCRIPT"]).srcnode().abspath + + flags = project["PROJECT_FLAGS"] + extra_flags + common_flags + [ + "-Wall", + "-Wextra", + "-Wstrict-prototypes", + "-Werror", + "-mlittle-endian", + "-mthumb", + "-nostdlib", + "-fno-builtin", + "-std=gnu11", + "-fmax-errors=3", + f"-T{linkerscript_fn}", + ] + + includes = [ + ".", + "..", + Dir("#").abspath, + Dir("#board/").abspath, + Dir("#board/stm32fx/inc").abspath, + Dir("#board/stm32h7/inc").abspath, + ] + + env = Environment( + ENV=os.environ, + CC=PREFIX + 'gcc', + AS=PREFIX + 'gcc', + OBJCOPY=PREFIX + 'objcopy', + OBJDUMP=PREFIX + 'objdump', + CFLAGS=flags, + ASFLAGS=flags, + LINKFLAGS=flags, + CPPPATH=includes, + ASCOM="$AS $ASFLAGS -o $TARGET -c $SOURCES", + BUILDERS={ + 'Objcopy': Builder(generator=objcopy, suffix='.bin', src_suffix='.elf') + } + ) + + startup = env.Object(f"obj/startup_{project_name}", project["STARTUP_FILE"]) + + # Bootstub + crypto_obj = [ + env.Object(f"rsa-{project_name}", "#crypto/rsa.c"), + env.Object(f"sha-{project_name}", "#crypto/sha.c") + ] + bootstub_obj = env.Object(f"bootstub-{project_name}", project.get("BOOTSTUB", "#board/bootstub.c")) + bootstub_elf = env.Program(f"obj/bootstub.{project_name}.elf", + [startup] + crypto_obj + [bootstub_obj]) + env.Objcopy(f"obj/bootstub.{project_name}.bin", bootstub_elf) + + # Build main + main_obj = env.Object(f"main-{project_name}", project["MAIN"]) + main_elf = env.Program(f"obj/{project_name}.elf", [startup, main_obj], + LINKFLAGS=[f"-Wl,--section-start,.isr_vector={project['APP_START_ADDRESS']}"] + flags) + main_bin = env.Objcopy(f"obj/{project_name}.bin", main_elf) + + # Sign main + sign_py = File("#crypto/sign.py").srcnode().abspath + env.Command(f"obj/{project_name}.bin.signed", main_bin, f"SETLEN=1 {sign_py} $SOURCE $TARGET {cert_fn}") + + +base_project_f4 = { + "MAIN": "main.c", + "STARTUP_FILE": "#board/stm32fx/startup_stm32f413xx.s", + "LINKER_SCRIPT": "#board/stm32fx/stm32f4_flash.ld", + "APP_START_ADDRESS": "0x8004000", + "PROJECT_FLAGS": [ + "-mcpu=cortex-m4", + "-mhard-float", + "-DSTM32F4", + "-DSTM32F413xx", + "-mfpu=fpv4-sp-d16", + "-fsingle-precision-constant", + "-Os", + "-g", + ], +} + +base_project_h7 = { + "MAIN": "main.c", + "STARTUP_FILE": "#board/stm32h7/startup_stm32h7x5xx.s", + "LINKER_SCRIPT": "#board/stm32h7/stm32h7x5_flash.ld", + "APP_START_ADDRESS": "0x8020000", + "PROJECT_FLAGS": [ + "-mcpu=cortex-m7", + "-mhard-float", + "-DSTM32H7", + "-DSTM32H725xx", + "-mfpu=fpv5-d16", + "-fsingle-precision-constant", + "-Os", + "-g", + ], +} + +Export('base_project_f4', 'base_project_h7', 'build_project') + + +# Common autogenerated includes +with open("board/obj/gitversion.h", "w") as f: + f.write(f'const uint8_t gitversion[] = "{get_version(BUILDER, BUILD_TYPE)}";\n') + +with open("board/obj/version", "w") as f: + f.write(f'{get_version(BUILDER, BUILD_TYPE)}') + +certs = [get_key_header(n) for n in ["debug", "release"]] +with open("board/obj/cert.h", "w") as f: + for cert in certs: + f.write("\n".join(cert) + "\n") + # panda fw SConscript('board/SConscript') +# pedal fw +SConscript('board/pedal/SConscript') + # test files if GetOption('test'): SConscript('tests/libpanda/SConscript') diff --git a/board/SConscript b/board/SConscript index dc095e712a..3b115a62f9 100644 --- a/board/SConscript +++ b/board/SConscript @@ -1,194 +1,19 @@ import os import copy -import subprocess -PREFIX = "arm-none-eabi-" -BUILDER = "DEV" +Import('build_project', 'base_project_f4', 'base_project_h7') -common_flags = [] -build_projects = {} - -build_projects["pedal"] = { - "MAIN": "pedal/main.c", - "STARTUP_FILE": "stm32fx/startup_stm32f205xx.s", - "LINKER_SCRIPT": "stm32fx/stm32f2_flash.ld", - "APP_START_ADDRESS": "0x8004000", - "PROJECT_FLAGS": [ - "-mcpu=cortex-m3", - "-msoft-float", - "-DSTM32F2", - "-DSTM32F205xx", - "-O2", - "-DPEDAL", - ], -} - -build_projects["pedal_usb"] = copy.deepcopy(build_projects["pedal"]) -build_projects["pedal_usb"]["PROJECT_FLAGS"].append("-DPEDAL_USB") - -build_projects["panda"] = { - "MAIN": "main.c", - "STARTUP_FILE": "stm32fx/startup_stm32f413xx.s", - "LINKER_SCRIPT": "stm32fx/stm32f4_flash.ld", - "APP_START_ADDRESS": "0x8004000", - "PROJECT_FLAGS": [ - "-mcpu=cortex-m4", - "-mhard-float", - "-DSTM32F4", - "-DSTM32F413xx", - "-mfpu=fpv4-sp-d16", - "-fsingle-precision-constant", - "-Os", - "-g", - "-DPANDA", - ], +build_projects = { + "panda": base_project_f4, + "panda_h7": base_project_h7, } -build_projects["panda_h7"] = { - "MAIN": "main.c", - "STARTUP_FILE": "stm32h7/startup_stm32h7x5xx.s", - "LINKER_SCRIPT": "stm32h7/stm32h7x5_flash.ld", - "APP_START_ADDRESS": "0x8020000", - "PROJECT_FLAGS": [ - "-mcpu=cortex-m7", - "-mhard-float", - "-DSTM32H7", - "-DSTM32H725xx", - "-mfpu=fpv5-d16", - "-fsingle-precision-constant", - "-Os", - "-g", +for project_name, project in build_projects.items(): + flags = [ "-DPANDA", - ], -} - -if os.getenv("RELEASE"): - BUILD_TYPE = "RELEASE" - cert_fn = os.getenv("CERT") - assert cert_fn is not None, 'No certificate file specified. Please set CERT env variable' - assert os.path.exists(cert_fn), 'Certificate file not found. Please specify absolute path' -else: - BUILD_TYPE = "DEBUG" - cert_fn = File("../certs/debug").srcnode().abspath - common_flags += ["-DALLOW_DEBUG"] - -if os.getenv("DEBUG"): - common_flags += ["-DDEBUG"] - -includes = [ - "stm32fx/inc", - "stm32h7/inc", - "..", - ".", -] - -def get_version(builder, build_type): - try: - git = subprocess.check_output(["git", "rev-parse", "--short=8", "HEAD"], encoding='utf8').strip() - except subprocess.CalledProcessError: - git = "unknown" - return f"{builder}-{git}-{build_type}" - - -def to_c_uint32(x): - nums = [] - for _ in range(0x20): - nums.append(x % (2**32)) - x //= (2**32) - return "{" + 'U,'.join(map(str, nums)) + "U}" - - -def get_key_header(name): - from Crypto.PublicKey import RSA - - public_fn = File(f'../certs/{name}.pub').srcnode().abspath - with open(public_fn) as f: - rsa = RSA.importKey(f.read()) - assert(rsa.size_in_bits() == 1024) - - rr = pow(2**1024, 2, rsa.n) - n0inv = 2**32 - pow(rsa.n, -1, 2**32) - - r = [ - f"RSAPublicKey {name}_rsa_key = {{", - f" .len = 0x20,", - f" .n0inv = {n0inv}U,", - f" .n = {to_c_uint32(rsa.n)},", - f" .rr = {to_c_uint32(rr)},", - f" .exponent = {rsa.e},", - f"}};", + "-DPANDA_BUS_CNT=4", ] - return r - -def objcopy(source, target, env, for_signature): - return '$OBJCOPY -O binary %s %s' % (source[0], target[0]) - -# Common autogenerated includes -with open("obj/gitversion.h", "w") as f: - f.write(f'const uint8_t gitversion[] = "{get_version(BUILDER, BUILD_TYPE)}";\n') - -with open("obj/version", "w") as f: - f.write(f'{get_version(BUILDER, BUILD_TYPE)}') - -certs = [get_key_header(n) for n in ["debug", "release"]] -with open("obj/cert.h", "w") as f: - for cert in certs: - f.write("\n".join(cert) + "\n") - - -for project_name in build_projects: - project = build_projects[project_name] - linkerscript_fn = File(project["LINKER_SCRIPT"]).srcnode().abspath - - flags = [ - "-Wall", - "-Wextra", - "-Wstrict-prototypes", - "-Werror", - "-mlittle-endian", - "-mthumb", - "-nostdlib", - "-fno-builtin", - f"-T{linkerscript_fn}", - "-std=gnu11", - ] + project["PROJECT_FLAGS"] + common_flags - if ("ENABLE_SPI" in os.environ or "h7" in project_name) and not project_name.startswith('pedal'): flags.append('-DENABLE_SPI') - project_env = Environment( - ENV=os.environ, - CC=PREFIX + 'gcc', - AS=PREFIX + 'gcc', - OBJCOPY=PREFIX + 'objcopy', - OBJDUMP=PREFIX + 'objdump', - ASCOM="$AS $ASFLAGS -o $TARGET -c $SOURCES", - CFLAGS=flags, - ASFLAGS=flags, - LINKFLAGS=flags, - CPPPATH=includes, - BUILDERS={ - 'Objcopy': Builder(generator=objcopy, suffix='.bin', src_suffix='.elf') - } - ) - - startup = project_env.Object(f"obj/startup_{project_name}", project["STARTUP_FILE"]) - - # Bootstub - crypto_obj = [ - project_env.Object(f"rsa-{project_name}", "../crypto/rsa.c"), - project_env.Object(f"sha-{project_name}", "../crypto/sha.c") - ] - bootstub_obj = project_env.Object(f"bootstub-{project_name}", "bootstub.c") - bootstub_elf = project_env.Program(f"obj/bootstub.{project_name}.elf", [startup] + crypto_obj + [bootstub_obj]) - bootstub_bin = project_env.Objcopy(f"obj/bootstub.{project_name}.bin", bootstub_elf) - - # Build main - main_obj = project_env.Object(f"main-{project_name}", project["MAIN"]) - main_elf = project_env.Program(f"obj/{project_name}.elf", [startup, main_obj], - LINKFLAGS=[f"-Wl,--section-start,.isr_vector={project['APP_START_ADDRESS']}"] + flags) - main_bin = project_env.Objcopy(f"obj/{project_name}.bin", main_elf) - - # Sign main - sign_py = File("../crypto/sign.py").srcnode().abspath - panda_bin_signed = project_env.Command(f"obj/{project_name}.bin.signed", main_bin, f"SETLEN=1 {sign_py} $SOURCE $TARGET {cert_fn}") + build_project(project_name, project, flags) diff --git a/board/can_definitions.h b/board/can_definitions.h index daae4d0443..b12d0e43f3 100644 --- a/board/can_definitions.h +++ b/board/can_definitions.h @@ -1,7 +1,6 @@ #pragma once const uint8_t PANDA_CAN_CNT = 3U; -const uint8_t PANDA_BUS_CNT = 4U; // bump this when changing the CAN packet #define CAN_PACKET_VERSION 4 diff --git a/board/pedal/SConscript b/board/pedal/SConscript new file mode 100644 index 0000000000..5452fa66ea --- /dev/null +++ b/board/pedal/SConscript @@ -0,0 +1,31 @@ +import copy + +Import('build_project') + +build_projects = {} + +build_projects["pedal"] = { + "MAIN": "#board/pedal/main.c", + "BOOTSTUB": "#board/bootstub.c", + "STARTUP_FILE": "#board/stm32fx/startup_stm32f205xx.s", + "LINKER_SCRIPT": "#board/stm32fx/stm32f2_flash.ld", + "APP_START_ADDRESS": "0x8004000", + "PROJECT_FLAGS": [ + "-mcpu=cortex-m3", + "-msoft-float", + "-DSTM32F2", + "-DSTM32F205xx", + "-O2", + "-DPEDAL", + ], +} + +# build with the USB driver enabled +build_projects["pedal_usb"] = copy.deepcopy(build_projects["pedal"]) +build_projects["pedal_usb"]["PROJECT_FLAGS"].append("-DPEDAL_USB") + +for project_name, project in build_projects.items(): + flags = [ + "-DPANDA_BUS_CNT=4", + ] + build_project(project_name, project, flags) diff --git a/board/pedal/recover.sh b/board/pedal/recover.sh index 98c5e19862..f81f672778 100755 --- a/board/pedal/recover.sh +++ b/board/pedal/recover.sh @@ -7,5 +7,5 @@ cd .. PEDAL=1 scons -u -j$(nproc) cd pedal -$DFU_UTIL -d 0483:df11 -a 0 -s 0x08004000 -D ../obj/pedal.bin.signed -$DFU_UTIL -d 0483:df11 -a 0 -s 0x08000000:leave -D ../obj/bootstub.pedal.bin +$DFU_UTIL -d 0483:df11 -a 0 -s 0x08004000 -D obj/pedal.bin.signed +$DFU_UTIL -d 0483:df11 -a 0 -s 0x08000000:leave -D obj/bootstub.pedal.bin diff --git a/tests/libpanda/SConscript b/tests/libpanda/SConscript index f9f052e458..cf24949e82 100644 --- a/tests/libpanda/SConscript +++ b/tests/libpanda/SConscript @@ -14,6 +14,7 @@ env = Environment( '-fno-builtin', '-std=gnu11', '-Wfatal-errors', + '-DPANDA_BUS_CNT=4', ], CPPPATH=[".", "../../board/"], ) diff --git a/tests/pedal/test_pedal.py b/tests/pedal/test_pedal.py index 05a0512267..416a20fa0c 100755 --- a/tests/pedal/test_pedal.py +++ b/tests/pedal/test_pedal.py @@ -46,7 +46,7 @@ def _listen_can_frames(self): def test_usb_fw(self): subprocess.check_output(f"cd {BASEDIR} && PEDAL=1 PEDAL_USB=1 scons", shell=True) - self._flash_over_can(PEDAL_BUS, f"{BASEDIR}board/obj/pedal_usb.bin.signed") + self._flash_over_can(PEDAL_BUS, f"{BASEDIR}/board/pedal/obj/pedal_usb.bin.signed") time.sleep(2) with Panda(PEDAL_SERIAL) as p: self.assertTrue(p.get_type() == Panda.HW_TYPE_PEDAL) @@ -54,7 +54,7 @@ def test_usb_fw(self): def test_nonusb_fw(self): subprocess.check_output(f"cd {BASEDIR} && PEDAL=1 scons", shell=True) - self._flash_over_can(PEDAL_BUS, f"{BASEDIR}board/obj/pedal.bin.signed") + self._flash_over_can(PEDAL_BUS, f"{BASEDIR}board/pedal/obj/pedal.bin.signed") time.sleep(2) self.assertTrue(self._listen_can_frames() > 40) From 5af25e2a67062106e7bc908d3e94bf4841a1b733 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 3 Aug 2023 20:14:05 -0700 Subject: [PATCH 159/197] fix paths when building with different SConstruct --- SConscript | 34 ++++++++++++++++++---------------- board/pedal/SConscript | 8 ++++---- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/SConscript b/SConscript index 84f82b91e3..4454f0dfc2 100644 --- a/SConscript +++ b/SConscript @@ -7,6 +7,8 @@ BUILDER = "DEV" common_flags = [] +panda_root = Dir('.').abspath + if os.getenv("RELEASE"): BUILD_TYPE = "RELEASE" cert_fn = os.getenv("CERT") @@ -14,7 +16,7 @@ if os.getenv("RELEASE"): assert os.path.exists(cert_fn), 'Certificate file not found. Please specify absolute path' else: BUILD_TYPE = "DEBUG" - cert_fn = File("#certs/debug").srcnode().abspath + cert_fn = File("./certs/debug").srcnode().abspath common_flags += ["-DALLOW_DEBUG"] if os.getenv("DEBUG"): @@ -33,7 +35,7 @@ def get_version(builder, build_type): def get_key_header(name): from Crypto.PublicKey import RSA - public_fn = File(f'#certs/{name}.pub').srcnode().abspath + public_fn = File(f'./certs/{name}.pub').srcnode().abspath with open(public_fn) as f: rsa = RSA.importKey(f.read()) assert(rsa.size_in_bits() == 1024) @@ -78,12 +80,12 @@ def build_project(project_name, project, extra_flags): ] includes = [ - ".", - "..", - Dir("#").abspath, - Dir("#board/").abspath, - Dir("#board/stm32fx/inc").abspath, - Dir("#board/stm32h7/inc").abspath, + '.', + '..', + panda_root, + f"{panda_root}/board/", + f"{panda_root}/board/stm32fx/inc", + f"{panda_root}/board/stm32h7/inc", ] env = Environment( @@ -106,10 +108,10 @@ def build_project(project_name, project, extra_flags): # Bootstub crypto_obj = [ - env.Object(f"rsa-{project_name}", "#crypto/rsa.c"), - env.Object(f"sha-{project_name}", "#crypto/sha.c") + env.Object(f"rsa-{project_name}", f"{panda_root}/crypto/rsa.c"), + env.Object(f"sha-{project_name}", f"{panda_root}/crypto/sha.c") ] - bootstub_obj = env.Object(f"bootstub-{project_name}", project.get("BOOTSTUB", "#board/bootstub.c")) + bootstub_obj = env.Object(f"bootstub-{project_name}", File(project.get("BOOTSTUB", f"{panda_root}/board/bootstub.c"))) bootstub_elf = env.Program(f"obj/bootstub.{project_name}.elf", [startup] + crypto_obj + [bootstub_obj]) env.Objcopy(f"obj/bootstub.{project_name}.bin", bootstub_elf) @@ -121,14 +123,14 @@ def build_project(project_name, project, extra_flags): main_bin = env.Objcopy(f"obj/{project_name}.bin", main_elf) # Sign main - sign_py = File("#crypto/sign.py").srcnode().abspath + sign_py = File(f"{panda_root}/crypto/sign.py").srcnode().abspath env.Command(f"obj/{project_name}.bin.signed", main_bin, f"SETLEN=1 {sign_py} $SOURCE $TARGET {cert_fn}") base_project_f4 = { "MAIN": "main.c", - "STARTUP_FILE": "#board/stm32fx/startup_stm32f413xx.s", - "LINKER_SCRIPT": "#board/stm32fx/stm32f4_flash.ld", + "STARTUP_FILE": File("./board/stm32fx/startup_stm32f413xx.s"), + "LINKER_SCRIPT": File("./board/stm32fx/stm32f4_flash.ld"), "APP_START_ADDRESS": "0x8004000", "PROJECT_FLAGS": [ "-mcpu=cortex-m4", @@ -144,8 +146,8 @@ base_project_f4 = { base_project_h7 = { "MAIN": "main.c", - "STARTUP_FILE": "#board/stm32h7/startup_stm32h7x5xx.s", - "LINKER_SCRIPT": "#board/stm32h7/stm32h7x5_flash.ld", + "STARTUP_FILE": File("./board/stm32h7/startup_stm32h7x5xx.s"), + "LINKER_SCRIPT": File("./board/stm32h7/stm32h7x5_flash.ld"), "APP_START_ADDRESS": "0x8020000", "PROJECT_FLAGS": [ "-mcpu=cortex-m7", diff --git a/board/pedal/SConscript b/board/pedal/SConscript index 5452fa66ea..e993111c85 100644 --- a/board/pedal/SConscript +++ b/board/pedal/SConscript @@ -5,10 +5,10 @@ Import('build_project') build_projects = {} build_projects["pedal"] = { - "MAIN": "#board/pedal/main.c", - "BOOTSTUB": "#board/bootstub.c", - "STARTUP_FILE": "#board/stm32fx/startup_stm32f205xx.s", - "LINKER_SCRIPT": "#board/stm32fx/stm32f2_flash.ld", + "MAIN": "main.c", + "BOOTSTUB": "../bootstub.c", + "STARTUP_FILE": "../stm32fx/startup_stm32f205xx.s", + "LINKER_SCRIPT": "../stm32fx/stm32f2_flash.ld", "APP_START_ADDRESS": "0x8004000", "PROJECT_FLAGS": [ "-mcpu=cortex-m3", From e7894e67702cb9f7e1140cf5da387a7c273bdd11 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 3 Aug 2023 23:10:16 -0700 Subject: [PATCH 160/197] Move CanHandle to python/ (#1550) --- __init__.py | 1 + python/base.py | 2 +- {tests/pedal => python}/canhandle.py | 9 +++++++-- tests/pedal/enter_canloader.py | 3 +-- tests/pedal/test_pedal.py | 3 +-- 5 files changed, 11 insertions(+), 7 deletions(-) rename {tests/pedal => python}/canhandle.py (91%) diff --git a/__init__.py b/__init__.py index dfb2bbf1c9..25120f7f7d 100644 --- a/__init__.py +++ b/__init__.py @@ -1,6 +1,7 @@ from .python.constants import McuType, BASEDIR, FW_PATH, USBPACKET_MAX_SIZE # noqa: F401 from .python.spi import PandaSpiException, PandaProtocolMismatch # noqa: F401 from .python.serial import PandaSerial # noqa: F401 +from .python.canhandle import CanHandle # noqa: F401 from .python import (Panda, PandaDFU, # noqa: F401 pack_can_buffer, unpack_can_buffer, calculate_checksum, unpack_log, DLC_TO_LEN, LEN_TO_DLC, ALTERNATIVE_EXPERIENCE, CANPACKET_HEAD_SIZE) diff --git a/python/base.py b/python/base.py index d19a2b8612..509d3ebf77 100644 --- a/python/base.py +++ b/python/base.py @@ -15,7 +15,7 @@ def close(self) -> None: ... @abstractmethod - def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = TIMEOUT) -> int: + def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = TIMEOUT, expect_disconnect: bool = False): ... @abstractmethod diff --git a/tests/pedal/canhandle.py b/python/canhandle.py similarity index 91% rename from tests/pedal/canhandle.py rename to python/canhandle.py index 0d7582f048..ff6e625552 100644 --- a/tests/pedal/canhandle.py +++ b/python/canhandle.py @@ -1,8 +1,10 @@ import struct import signal +from .base import BaseHandle -class CanHandle(object): + +class CanHandle(BaseHandle): def __init__(self, p, bus): self.p = p self.bus = bus @@ -29,7 +31,10 @@ def _handle_timeout(signum, frame): return ret - def controlWrite(self, request_type, request, value, index, data, timeout=0): + def close(self): + pass + + def controlWrite(self, request_type, request, value, index, data, timeout=0, expect_disconnect=False): # ignore data in reply, panda doesn't use it return self.controlRead(request_type, request, value, index, 0, timeout) diff --git a/tests/pedal/enter_canloader.py b/tests/pedal/enter_canloader.py index bdd0d62865..7465c0a072 100755 --- a/tests/pedal/enter_canloader.py +++ b/tests/pedal/enter_canloader.py @@ -1,8 +1,7 @@ #!/usr/bin/env python3 import time import argparse -from panda import Panda, McuType -from panda.tests.pedal.canhandle import CanHandle +from panda import Panda, CanHandle, McuType if __name__ == "__main__": diff --git a/tests/pedal/test_pedal.py b/tests/pedal/test_pedal.py index 416a20fa0c..7888ada759 100755 --- a/tests/pedal/test_pedal.py +++ b/tests/pedal/test_pedal.py @@ -3,9 +3,8 @@ import time import subprocess import unittest -from panda import Panda, BASEDIR +from panda import Panda, CanHandle, BASEDIR from panda_jungle import PandaJungle # pylint: disable=import-error -from panda.tests.pedal.canhandle import CanHandle JUNGLE_SERIAL = os.getenv("PEDAL_JUNGLE") From d7120be9043d2cd8510022570c65e50831a4e38b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 3 Aug 2023 23:55:13 -0700 Subject: [PATCH 161/197] panda jungle (#1547) * mv jungle * match pedal style * fix linter * fix fw path * fix paths * safety! --- .gitignore | 2 +- Dockerfile | 7 - SConscript | 3 + __init__.py | 4 + board/bootstub.c | 4 + board/config.h | 14 +- board/drivers/can_common.h | 14 +- board/drivers/fdcan.h | 3 + board/drivers/gpio.h | 11 + board/jungle/README.md | 26 ++ board/jungle/SConscript | 19 ++ board/jungle/__init__.py | 140 +++++++++ board/jungle/boards/board_declarations.h | 62 ++++ board/jungle/boards/board_v1.h | 168 +++++++++++ board/jungle/boards/board_v2.h | 278 ++++++++++++++++++ board/jungle/flash.py | 17 ++ board/jungle/jungle_health.h | 24 ++ board/jungle/main.c | 194 ++++++++++++ board/jungle/main_comms.h | 252 ++++++++++++++++ board/jungle/recover.py | 26 ++ board/jungle/scripts/can_health.py | 13 + board/jungle/scripts/can_printer.py | 35 +++ board/jungle/scripts/debug_console.py | 38 +++ board/jungle/scripts/echo_loopback_test.py | 68 +++++ board/jungle/scripts/get_version.py | 9 + board/jungle/scripts/health_test.py | 21 ++ board/jungle/scripts/loopback_test.py | 140 +++++++++ .../scripts/panda_identification_test.py | 45 +++ board/jungle/scripts/start.py | 18 ++ board/jungle/stm32fx/board.h | 7 + board/jungle/stm32h7/board.h | 9 + board/jungle/stm32h7/lladc.h | 54 ++++ board/stm32fx/peripherals.h | 2 +- board/stm32fx/stm32fx_config.h | 14 +- board/stm32h7/peripherals.h | 7 +- board/stm32h7/stm32h7_config.h | 2 +- python/__init__.py | 39 +-- requirements.txt | 1 + tests/bulk_write_test.py | 5 +- tests/canfd/test_canfd.py | 2 +- tests/hitl/2_health.py | 2 +- tests/hitl/conftest.py | 2 +- tests/message_drop_test.py | 2 +- tests/pedal/test_pedal.py | 6 +- tests/setup_device_ci.sh | 10 - 45 files changed, 1755 insertions(+), 64 deletions(-) create mode 100644 board/jungle/README.md create mode 100644 board/jungle/SConscript create mode 100644 board/jungle/__init__.py create mode 100644 board/jungle/boards/board_declarations.h create mode 100644 board/jungle/boards/board_v1.h create mode 100644 board/jungle/boards/board_v2.h create mode 100755 board/jungle/flash.py create mode 100644 board/jungle/jungle_health.h create mode 100644 board/jungle/main.c create mode 100644 board/jungle/main_comms.h create mode 100755 board/jungle/recover.py create mode 100755 board/jungle/scripts/can_health.py create mode 100755 board/jungle/scripts/can_printer.py create mode 100755 board/jungle/scripts/debug_console.py create mode 100755 board/jungle/scripts/echo_loopback_test.py create mode 100755 board/jungle/scripts/get_version.py create mode 100755 board/jungle/scripts/health_test.py create mode 100755 board/jungle/scripts/loopback_test.py create mode 100755 board/jungle/scripts/panda_identification_test.py create mode 100755 board/jungle/scripts/start.py create mode 100644 board/jungle/stm32fx/board.h create mode 100644 board/jungle/stm32h7/board.h create mode 100644 board/jungle/stm32h7/lladc.h diff --git a/.gitignore b/.gitignore index a3f6520bfd..1f96749b0c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,7 @@ a.out .#* dist/ pandacan.egg-info/ -board/obj/ +obj/ examples/output.csv .DS_Store .vscode* diff --git a/Dockerfile b/Dockerfile index 7dc77ec72d..6049cbee45 100644 --- a/Dockerfile +++ b/Dockerfile @@ -80,13 +80,6 @@ RUN cd /tmp && \ rm -rf /tmp/openpilot/panda && \ rm -rf /tmp/tmppilot -RUN cd /tmp/openpilot && \ - git clone https://github.com/commaai/panda_jungle.git && \ - cd panda_jungle && \ - git fetch && \ - git checkout 3a791be1f1877a69cf45de16a670992380622297 && \ - rm -rf .git/ - RUN cd /tmp/openpilot && \ pip install --no-cache-dir -r opendbc/requirements.txt && \ pip install --no-cache-dir --upgrade aenum lru-dict pycurl tenacity atomicwrites serial smbus2 diff --git a/SConscript b/SConscript index 4454f0dfc2..73414160ae 100644 --- a/SConscript +++ b/SConscript @@ -182,6 +182,9 @@ SConscript('board/SConscript') # pedal fw SConscript('board/pedal/SConscript') +# panda jungle fw +SConscript('board/jungle/SConscript') + # test files if GetOption('test'): SConscript('tests/libpanda/SConscript') diff --git a/__init__.py b/__init__.py index 25120f7f7d..e8e05c52d5 100644 --- a/__init__.py +++ b/__init__.py @@ -5,3 +5,7 @@ from .python import (Panda, PandaDFU, # noqa: F401 pack_can_buffer, unpack_can_buffer, calculate_checksum, unpack_log, DLC_TO_LEN, LEN_TO_DLC, ALTERNATIVE_EXPERIENCE, CANPACKET_HEAD_SIZE) + + +# panda jungle +from .board.jungle import PandaJungle, PandaJungleDFU # noqa: F401 diff --git a/board/bootstub.c b/board/bootstub.c index a2bf7dad7a..e2977e938c 100644 --- a/board/bootstub.c +++ b/board/bootstub.c @@ -41,6 +41,10 @@ int main(void) { clock_init(); detect_board_type(); +#ifdef PANDA_JUNGLE + current_board->set_panda_power(true); +#endif + if (enter_bootloader_mode == ENTER_SOFTLOADER_MAGIC) { enter_bootloader_mode = 0; soft_flasher_start(); diff --git a/board/config.h b/board/config.h index 903161876c..507d19e7d3 100644 --- a/board/config.h +++ b/board/config.h @@ -21,10 +21,18 @@ // USB definitions #define USB_VID 0xBBAAU -#ifdef BOOTSTUB - #define USB_PID 0xDDEEU +#ifdef PANDA_JUNGLE + #ifdef BOOTSTUB + #define USB_PID 0xDDEFU + #else + #define USB_PID 0xDDCFU + #endif #else - #define USB_PID 0xDDCCU + #ifdef BOOTSTUB + #define USB_PID 0xDDEEU + #else + #define USB_PID 0xDDCCU + #endif #endif // platform includes diff --git a/board/drivers/can_common.h b/board/drivers/can_common.h index be6b5b2a5d..4ad5b767f5 100644 --- a/board/drivers/can_common.h +++ b/board/drivers/can_common.h @@ -8,6 +8,7 @@ typedef struct { typedef struct { uint8_t bus_lookup; uint8_t can_num_lookup; + int8_t forwarding_bus; uint32_t can_speed; uint32_t can_data_speed; bool canfd_enabled; @@ -157,14 +158,15 @@ void can_clear(can_ring *q) { // can number: numeric lookup for MCU CAN interfaces (0 = CAN1, 1 = CAN2, etc); // bus_lookup: Translates from 'can number' to 'bus number'. // can_num_lookup: Translates from 'bus number' to 'can number'. +// forwarding bus: If >= 0, forward all messages from this bus to the specified bus. // Helpers // Panda: Bus 0=CAN1 Bus 1=CAN2 Bus 2=CAN3 bus_config_t bus_config[] = { - { .bus_lookup = 0U, .can_num_lookup = 0U, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, - { .bus_lookup = 1U, .can_num_lookup = 1U, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, - { .bus_lookup = 2U, .can_num_lookup = 2U, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, - { .bus_lookup = 0xFFU, .can_num_lookup = 0xFFU, .can_speed = 333U, .can_data_speed = 333U, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, + { .bus_lookup = 0U, .can_num_lookup = 0U, .forwarding_bus = -1, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, + { .bus_lookup = 1U, .can_num_lookup = 1U, .forwarding_bus = -1, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, + { .bus_lookup = 2U, .can_num_lookup = 2U, .forwarding_bus = -1, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, + { .bus_lookup = 0xFFU, .can_num_lookup = 0xFFU, .forwarding_bus = -1, .can_speed = 333U, .can_data_speed = 333U, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false }, }; #define CANIF_FROM_CAN_NUM(num) (cans[num]) @@ -190,6 +192,10 @@ void can_flip_buses(uint8_t bus1, uint8_t bus2){ bus_config[bus2].can_num_lookup = bus1; } +void can_set_forwarding(uint8_t from, uint8_t to) { + bus_config[from].forwarding_bus = to; +} + void ignition_can_hook(CANPacket_t *to_push) { int bus = GET_BUS(to_push); int addr = GET_ADDR(to_push); diff --git a/board/drivers/fdcan.h b/board/drivers/fdcan.h index 7cab232d36..ec3cc1255e 100644 --- a/board/drivers/fdcan.h +++ b/board/drivers/fdcan.h @@ -195,6 +195,9 @@ void can_rx(uint8_t can_number) { // forwarding (panda only) int bus_fwd_num = safety_fwd_hook(bus_number, to_push.addr); + if (bus_fwd_num < 0) { + bus_fwd_num = bus_config[can_number].forwarding_bus; + } if (bus_fwd_num != -1) { CANPacket_t to_send; diff --git a/board/drivers/gpio.h b/board/drivers/gpio.h index 77af87658c..88af9319d8 100644 --- a/board/drivers/gpio.h +++ b/board/drivers/gpio.h @@ -10,6 +10,11 @@ #define OUTPUT_TYPE_PUSH_PULL 0U #define OUTPUT_TYPE_OPEN_DRAIN 1U +typedef struct { + GPIO_TypeDef *bank; + uint8_t pin; +} gpio_t; + void set_gpio_mode(GPIO_TypeDef *GPIO, unsigned int pin, unsigned int mode) { ENTER_CRITICAL(); uint32_t tmp = GPIO->MODER; @@ -63,6 +68,12 @@ int get_gpio_input(GPIO_TypeDef *GPIO, unsigned int pin) { return (GPIO->IDR & (1U << pin)) == (1U << pin); } +void gpio_set_all_output(const gpio_t *pins, uint8_t num_pins, bool enabled) { + for (uint8_t i = 0; i < num_pins; i++) { + set_gpio_output(pins[i].bank, pins[i].pin, enabled); + } +} + // Detection with internal pullup #define PULL_EFFECTIVE_DELAY 4096 bool detect_with_pull(GPIO_TypeDef *GPIO, int pin, int mode) { diff --git a/board/jungle/README.md b/board/jungle/README.md new file mode 100644 index 0000000000..4c271cbf21 --- /dev/null +++ b/board/jungle/README.md @@ -0,0 +1,26 @@ +Welcome to the jungle +====== + +Firmware for the Panda Jungle testing board. +Available for purchase at the [comma shop](https://comma.ai/shop/panda-jungle). + +## udev rules + +To make the jungle usable without root permissions, you might need to setup udev rules for it. +On ubuntu, this should do the trick: +``` bash +sudo tee /etc/udev/rules.d/12-panda_jungle.rules < bool: + dfu_serial = self.get_dfu_serial() + + if reset: + self.reset(enter_bootstub=True) + self.reset(enter_bootloader=True) + + if not self.wait_for_dfu(dfu_serial, timeout=timeout): + return False + + dfu = PandaJungleDFU(dfu_serial) + dfu.recover() + + # reflash after recover + self.connect(True, True) + self.flash() + return True + + def get_mcu_type(self) -> McuType: + hw_type = self.get_type() + if hw_type in PandaJungle.F4_DEVICES: + return McuType.F4 + elif hw_type in PandaJungle.H7_DEVICES: + return McuType.H7 + raise ValueError(f"unknown HW type: {hw_type}") + + # ******************* health ******************* + + @ensure_jungle_health_packet_version + def health(self): + dat = self._handle.controlRead(PandaJungle.REQUEST_IN, 0xd2, 0, 0, self.HEALTH_STRUCT.size) + a = self.HEALTH_STRUCT.unpack(dat) + return { + "uptime": a[0], + "ch1_power": a[1], + "ch2_power": a[2], + "ch3_power": a[3], + "ch4_power": a[4], + "ch5_power": a[5], + "ch6_power": a[6], + "ch1_sbu1_voltage": a[7] / 1000.0, + "ch1_sbu2_voltage": a[8] / 1000.0, + "ch2_sbu1_voltage": a[9] / 1000.0, + "ch2_sbu2_voltage": a[10] / 1000.0, + "ch3_sbu1_voltage": a[11] / 1000.0, + "ch3_sbu2_voltage": a[12] / 1000.0, + "ch4_sbu1_voltage": a[13] / 1000.0, + "ch4_sbu2_voltage": a[14] / 1000.0, + "ch5_sbu1_voltage": a[15] / 1000.0, + "ch5_sbu2_voltage": a[16] / 1000.0, + "ch6_sbu1_voltage": a[17] / 1000.0, + "ch6_sbu2_voltage": a[18] / 1000.0, + } + + # ******************* control ******************* + + # Returns tuple with health packet version and CAN packet/USB packet version + def get_packets_versions(self): + dat = self._handle.controlRead(PandaJungle.REQUEST_IN, 0xdd, 0, 0, 3) + if dat and len(dat) == 3: + a = struct.unpack("BBB", dat) + return (a[0], a[1], a[2]) + return (-1, -1, -1) + + # ******************* jungle stuff ******************* + + def set_panda_power(self, enabled): + self._handle.controlWrite(PandaJungle.REQUEST_OUT, 0xa0, int(enabled), 0, b'') + + def set_harness_orientation(self, mode): + self._handle.controlWrite(PandaJungle.REQUEST_OUT, 0xa1, int(mode), 0, b'') + + def set_ignition(self, enabled): + self._handle.controlWrite(PandaJungle.REQUEST_OUT, 0xa2, int(enabled), 0, b'') + + # ******************* serial ******************* + + def debug_read(self): + ret = [] + while 1: + lret = bytes(self._handle.controlRead(PandaJungle.REQUEST_IN, 0xe0, 0, 0, 0x40)) + if len(lret) == 0: + break + ret.append(lret) + return b''.join(ret) diff --git a/board/jungle/boards/board_declarations.h b/board/jungle/boards/board_declarations.h new file mode 100644 index 0000000000..a02b906b64 --- /dev/null +++ b/board/jungle/boards/board_declarations.h @@ -0,0 +1,62 @@ +// ******************** Prototypes ******************** +typedef void (*board_init)(void); +typedef void (*board_set_led)(uint8_t color, bool enabled); +typedef void (*board_board_tick)(void); +typedef bool (*board_get_button)(void); +typedef void (*board_set_panda_power)(bool enabled); +typedef void (*board_set_ignition)(bool enabled); +typedef void (*board_set_harness_orientation)(uint8_t orientation); +typedef void (*board_set_can_mode)(uint8_t mode); +typedef void (*board_enable_can_transciever)(uint8_t transciever, bool enabled); +typedef float (*board_get_channel_power)(uint8_t channel); +typedef uint16_t (*board_get_sbu_mV)(uint8_t channel, uint8_t sbu); + +struct board { + const char *board_type; + const bool has_canfd; + const uint16_t avdd_mV; + board_init init; + board_set_led set_led; + board_board_tick board_tick; + board_get_button get_button; + board_set_panda_power set_panda_power; + board_set_ignition set_ignition; + board_set_harness_orientation set_harness_orientation; + board_set_can_mode set_can_mode; + board_enable_can_transciever enable_can_transciever; + board_get_channel_power get_channel_power; + board_get_sbu_mV get_sbu_mV; + + // TODO: shouldn't need these + bool has_spi; + bool has_hw_gmlan; +}; + +// ******************* Definitions ******************** +#define HW_TYPE_UNKNOWN 0U +#define HW_TYPE_V1 1U +#define HW_TYPE_V2 2U + +// LED colors +#define LED_RED 0U +#define LED_GREEN 1U +#define LED_BLUE 2U + +// CAN modes +#define CAN_MODE_NORMAL 0U +#define CAN_MODE_GMLAN_CAN2 1U +#define CAN_MODE_GMLAN_CAN3 2U +#define CAN_MODE_OBD_CAN2 3U + +// Harness states +#define HARNESS_ORIENTATION_NONE 0U +#define HARNESS_ORIENTATION_1 1U +#define HARNESS_ORIENTATION_2 2U + +#define SBU1 0U +#define SBU2 1U + +// ********************* Globals ********************** +uint8_t harness_orientation = HARNESS_ORIENTATION_NONE; +uint8_t can_mode = CAN_MODE_NORMAL; +bool ignition = false; diff --git a/board/jungle/boards/board_v1.h b/board/jungle/boards/board_v1.h new file mode 100644 index 0000000000..5ad574faad --- /dev/null +++ b/board/jungle/boards/board_v1.h @@ -0,0 +1,168 @@ + +void board_v1_set_led(uint8_t color, bool enabled) { + switch (color){ + case LED_RED: + set_gpio_output(GPIOC, 9, !enabled); + break; + case LED_GREEN: + set_gpio_output(GPIOC, 7, !enabled); + break; + case LED_BLUE: + set_gpio_output(GPIOC, 6, !enabled); + break; + default: + break; + } +} + +void board_v1_set_can_mode(uint8_t mode) { + switch (mode) { + case CAN_MODE_NORMAL: + print("Setting normal CAN mode\n"); + // B12,B13: disable OBD mode + set_gpio_mode(GPIOB, 12, MODE_INPUT); + set_gpio_mode(GPIOB, 13, MODE_INPUT); + + // B5,B6: normal CAN2 mode + set_gpio_alternate(GPIOB, 5, GPIO_AF9_CAN2); + set_gpio_alternate(GPIOB, 6, GPIO_AF9_CAN2); + can_mode = CAN_MODE_NORMAL; + break; + case CAN_MODE_OBD_CAN2: + print("Setting OBD CAN mode\n"); + // B5,B6: disable normal CAN2 mode + set_gpio_mode(GPIOB, 5, MODE_INPUT); + set_gpio_mode(GPIOB, 6, MODE_INPUT); + + // B12,B13: OBD mode + set_gpio_alternate(GPIOB, 12, GPIO_AF9_CAN2); + set_gpio_alternate(GPIOB, 13, GPIO_AF9_CAN2); + can_mode = CAN_MODE_OBD_CAN2; + break; + default: + print("Tried to set unsupported CAN mode: "); puth(mode); print("\n"); + break; + } +} + +void board_v1_set_harness_orientation(uint8_t orientation) { + switch (orientation) { + case HARNESS_ORIENTATION_NONE: + set_gpio_output(GPIOA, 2, false); + set_gpio_output(GPIOA, 3, false); + set_gpio_output(GPIOA, 4, false); + set_gpio_output(GPIOA, 5, false); + harness_orientation = orientation; + break; + case HARNESS_ORIENTATION_1: + set_gpio_output(GPIOA, 2, false); + set_gpio_output(GPIOA, 3, ignition); + set_gpio_output(GPIOA, 4, true); + set_gpio_output(GPIOA, 5, false); + harness_orientation = orientation; + break; + case HARNESS_ORIENTATION_2: + set_gpio_output(GPIOA, 2, ignition); + set_gpio_output(GPIOA, 3, false); + set_gpio_output(GPIOA, 4, false); + set_gpio_output(GPIOA, 5, true); + harness_orientation = orientation; + break; + default: + print("Tried to set an unsupported harness orientation: "); puth(orientation); print("\n"); + break; + } +} + +bool panda_power = false; +void board_v1_set_panda_power(bool enable) { + panda_power = enable; + set_gpio_output(GPIOB, 14, enable); +} + +bool board_v1_get_button(void) { + return get_gpio_input(GPIOC, 8); +} + +void board_v1_set_ignition(bool enabled) { + ignition = enabled; + board_v1_set_harness_orientation(harness_orientation); +} + +void board_v1_enable_can_transciever(uint8_t transciever, bool enabled) { + switch (transciever){ + case 1U: + set_gpio_output(GPIOC, 1, !enabled); + break; + case 2U: + set_gpio_output(GPIOC, 13, !enabled); + break; + case 3U: + set_gpio_output(GPIOA, 0, !enabled); + break; + case 4U: + set_gpio_output(GPIOB, 10, !enabled); + break; + default: + print("Invalid CAN transciever ("); puth(transciever); print("): enabling failed\n"); + break; + } +} + +float board_v1_get_channel_power(uint8_t channel) { + UNUSED(channel); + return 0.0f; +} + +uint16_t board_v1_get_sbu_mV(uint8_t channel, uint8_t sbu) { + UNUSED(channel); UNUSED(sbu); + return 0U; +} + +void board_v1_init(void) { + common_init_gpio(); + + // A8,A15: normal CAN3 mode + set_gpio_alternate(GPIOA, 8, GPIO_AF11_CAN3); + set_gpio_alternate(GPIOA, 15, GPIO_AF11_CAN3); + + board_v1_set_can_mode(CAN_MODE_NORMAL); + + // Enable CAN transcievers + for(uint8_t i = 1; i <= 4; i++) { + board_v1_enable_can_transciever(i, true); + } + + // Disable LEDs + board_v1_set_led(LED_RED, false); + board_v1_set_led(LED_GREEN, false); + board_v1_set_led(LED_BLUE, false); + + // Set normal CAN mode + board_v1_set_can_mode(CAN_MODE_NORMAL); + + // Set to no harness orientation + board_v1_set_harness_orientation(HARNESS_ORIENTATION_NONE); + + // Enable panda power by default + board_v1_set_panda_power(true); +} + +void board_v1_tick(void) {} + +const board board_v1 = { + .board_type = "V1", + .has_canfd = false, + .avdd_mV = 3300U, + .init = &board_v1_init, + .set_led = &board_v1_set_led, + .board_tick = &board_v1_tick, + .get_button = &board_v1_get_button, + .set_panda_power = &board_v1_set_panda_power, + .set_ignition = &board_v1_set_ignition, + .set_harness_orientation = &board_v1_set_harness_orientation, + .set_can_mode = &board_v1_set_can_mode, + .enable_can_transciever = &board_v1_enable_can_transciever, + .get_channel_power = &board_v1_get_channel_power, + .get_sbu_mV = &board_v1_get_sbu_mV, +}; \ No newline at end of file diff --git a/board/jungle/boards/board_v2.h b/board/jungle/boards/board_v2.h new file mode 100644 index 0000000000..e474967351 --- /dev/null +++ b/board/jungle/boards/board_v2.h @@ -0,0 +1,278 @@ + +const gpio_t power_pins[] = { + {.bank = GPIOA, .pin = 0}, + {.bank = GPIOA, .pin = 1}, + {.bank = GPIOF, .pin = 12}, + {.bank = GPIOA, .pin = 5}, + {.bank = GPIOC, .pin = 5}, + {.bank = GPIOB, .pin = 2}, +}; + +const gpio_t sbu1_ignition_pins[] = { + {.bank = GPIOD, .pin = 0}, + {.bank = GPIOD, .pin = 5}, + {.bank = GPIOD, .pin = 12}, + {.bank = GPIOD, .pin = 14}, + {.bank = GPIOE, .pin = 5}, + {.bank = GPIOE, .pin = 9}, +}; + +const gpio_t sbu1_relay_pins[] = { + {.bank = GPIOD, .pin = 1}, + {.bank = GPIOD, .pin = 6}, + {.bank = GPIOD, .pin = 11}, + {.bank = GPIOD, .pin = 15}, + {.bank = GPIOE, .pin = 6}, + {.bank = GPIOE, .pin = 10}, +}; + +const gpio_t sbu2_ignition_pins[] = { + {.bank = GPIOD, .pin = 3}, + {.bank = GPIOD, .pin = 8}, + {.bank = GPIOD, .pin = 9}, + {.bank = GPIOE, .pin = 0}, + {.bank = GPIOE, .pin = 7}, + {.bank = GPIOE, .pin = 11}, +}; + +const gpio_t sbu2_relay_pins[] = { + {.bank = GPIOD, .pin = 4}, + {.bank = GPIOD, .pin = 10}, + {.bank = GPIOD, .pin = 13}, + {.bank = GPIOE, .pin = 1}, + {.bank = GPIOE, .pin = 8}, + {.bank = GPIOE, .pin = 12}, +}; + +const adc_channel_t sbu1_channels[] = { + {.adc = ADC3, .channel = 12}, + {.adc = ADC3, .channel = 2}, + {.adc = ADC3, .channel = 4}, + {.adc = ADC3, .channel = 6}, + {.adc = ADC3, .channel = 8}, + {.adc = ADC3, .channel = 10}, +}; + +const adc_channel_t sbu2_channels[] = { + {.adc = ADC1, .channel = 13}, + {.adc = ADC3, .channel = 3}, + {.adc = ADC3, .channel = 5}, + {.adc = ADC3, .channel = 7}, + {.adc = ADC3, .channel = 9}, + {.adc = ADC3, .channel = 11}, +}; + +void board_v2_set_led(uint8_t color, bool enabled) { + switch (color) { + case LED_RED: + set_gpio_output(GPIOE, 4, !enabled); + break; + case LED_GREEN: + set_gpio_output(GPIOE, 3, !enabled); + break; + case LED_BLUE: + set_gpio_output(GPIOE, 2, !enabled); + break; + default: + break; + } +} + +void board_v2_set_harness_orientation(uint8_t orientation) { + switch (orientation) { + case HARNESS_ORIENTATION_NONE: + gpio_set_all_output(sbu1_ignition_pins, sizeof(sbu1_ignition_pins) / sizeof(gpio_t), false); + gpio_set_all_output(sbu1_relay_pins, sizeof(sbu1_relay_pins) / sizeof(gpio_t), false); + gpio_set_all_output(sbu2_ignition_pins, sizeof(sbu2_ignition_pins) / sizeof(gpio_t), false); + gpio_set_all_output(sbu2_relay_pins, sizeof(sbu2_relay_pins) / sizeof(gpio_t), false); + harness_orientation = orientation; + break; + case HARNESS_ORIENTATION_1: + gpio_set_all_output(sbu1_ignition_pins, sizeof(sbu1_ignition_pins) / sizeof(gpio_t), false); + gpio_set_all_output(sbu1_relay_pins, sizeof(sbu1_relay_pins) / sizeof(gpio_t), true); + gpio_set_all_output(sbu2_ignition_pins, sizeof(sbu2_ignition_pins) / sizeof(gpio_t), ignition); + gpio_set_all_output(sbu2_relay_pins, sizeof(sbu2_relay_pins) / sizeof(gpio_t), false); + harness_orientation = orientation; + break; + case HARNESS_ORIENTATION_2: + gpio_set_all_output(sbu1_ignition_pins, sizeof(sbu1_ignition_pins) / sizeof(gpio_t), ignition); + gpio_set_all_output(sbu1_relay_pins, sizeof(sbu1_relay_pins) / sizeof(gpio_t), false); + gpio_set_all_output(sbu2_ignition_pins, sizeof(sbu2_ignition_pins) / sizeof(gpio_t), false); + gpio_set_all_output(sbu2_relay_pins, sizeof(sbu2_relay_pins) / sizeof(gpio_t), true); + harness_orientation = orientation; + break; + default: + print("Tried to set an unsupported harness orientation: "); puth(orientation); print("\n"); + break; + } +} + +void board_v2_set_can_mode(uint8_t mode) { + switch (mode) { + case CAN_MODE_NORMAL: + // B12,B13: disable normal mode + set_gpio_pullup(GPIOB, 12, PULL_NONE); + set_gpio_mode(GPIOB, 12, MODE_ANALOG); + + set_gpio_pullup(GPIOB, 13, PULL_NONE); + set_gpio_mode(GPIOB, 13, MODE_ANALOG); + + // B5,B6: FDCAN2 mode + set_gpio_pullup(GPIOB, 5, PULL_NONE); + set_gpio_alternate(GPIOB, 5, GPIO_AF9_FDCAN2); + + set_gpio_pullup(GPIOB, 6, PULL_NONE); + set_gpio_alternate(GPIOB, 6, GPIO_AF9_FDCAN2); + can_mode = CAN_MODE_NORMAL; + break; + case CAN_MODE_OBD_CAN2: + // B5,B6: disable normal mode + set_gpio_pullup(GPIOB, 5, PULL_NONE); + set_gpio_mode(GPIOB, 5, MODE_ANALOG); + + set_gpio_pullup(GPIOB, 6, PULL_NONE); + set_gpio_mode(GPIOB, 6, MODE_ANALOG); + // B12,B13: FDCAN2 mode + set_gpio_pullup(GPIOB, 12, PULL_NONE); + set_gpio_alternate(GPIOB, 12, GPIO_AF9_FDCAN2); + + set_gpio_pullup(GPIOB, 13, PULL_NONE); + set_gpio_alternate(GPIOB, 13, GPIO_AF9_FDCAN2); + can_mode = CAN_MODE_OBD_CAN2; + break; + default: + break; + } +} + +bool panda_power = false; +void board_v2_set_panda_power(bool enable) { + panda_power = enable; + gpio_set_all_output(power_pins, sizeof(power_pins) / sizeof(gpio_t), enable); +} + +bool board_v2_get_button(void) { + return get_gpio_input(GPIOG, 15); +} + +void board_v2_set_ignition(bool enabled) { + ignition = enabled; + board_v2_set_harness_orientation(harness_orientation); +} + +void board_v2_enable_can_transciever(uint8_t transciever, bool enabled) { + switch (transciever){ + case 1U: + set_gpio_output(GPIOG, 11, !enabled); + break; + case 2U: + set_gpio_output(GPIOB, 3, !enabled); + break; + case 3U: + set_gpio_output(GPIOD, 7, !enabled); + break; + case 4U: + set_gpio_output(GPIOB, 4, !enabled); + break; + default: + print("Invalid CAN transciever ("); puth(transciever); print("): enabling failed\n"); + break; + } +} + +float board_v2_get_channel_power(uint8_t channel) { + float ret = 0.0f; + if ((channel >= 1U) && (channel <= 6U)) { + uint16_t readout = adc_get_mV(ADC1, channel - 1U); // these are mapped nicely in hardware + + ret = (((float) readout / 33e6) - 0.8e-6) / 52e-6 * 12.0f; + } else { + print("Invalid channel ("); puth(channel); print(")\n"); + } + return ret; +} + +uint16_t board_v1_get_sbu_mV(uint8_t channel, uint8_t sbu) { + uint16_t ret = 0U; + if ((channel >= 1U) && (channel <= 6U)) { + switch(sbu){ + case SBU1: + ret = adc_get_mV(sbu1_channels[channel - 1U].adc, sbu1_channels[channel - 1U].channel); + break; + case SBU2: + ret = adc_get_mV(sbu2_channels[channel - 1U].adc, sbu2_channels[channel - 1U].channel); + break; + default: + print("Invalid SBU ("); puth(sbu); print(")\n"); + break; + } + } else { + print("Invalid channel ("); puth(channel); print(")\n"); + } + return ret; +} + +void board_v2_init(void) { + common_init_gpio(); + + // Disable LEDs + board_v2_set_led(LED_RED, false); + board_v2_set_led(LED_GREEN, false); + board_v2_set_led(LED_BLUE, false); + + // Normal CAN mode + board_v2_set_can_mode(CAN_MODE_NORMAL); + + // Enable CAN transcievers + for(uint8_t i = 1; i <= 4; i++) { + board_v2_enable_can_transciever(i, true); + } + + // Set to no harness orientation + board_v2_set_harness_orientation(HARNESS_ORIENTATION_NONE); + + // Enable panda power by default + board_v2_set_panda_power(true); + + // Current monitor channels + adc_init(ADC1); + register_set_bits(&SYSCFG->PMCR, SYSCFG_PMCR_PA0SO | SYSCFG_PMCR_PA1SO); // open up analog switches for PA0_C and PA1_C + set_gpio_mode(GPIOF, 11, MODE_ANALOG); + set_gpio_mode(GPIOA, 6, MODE_ANALOG); + set_gpio_mode(GPIOC, 4, MODE_ANALOG); + set_gpio_mode(GPIOB, 1, MODE_ANALOG); + + // SBU channels + adc_init(ADC3); + set_gpio_mode(GPIOC, 2, MODE_ANALOG); + set_gpio_mode(GPIOC, 3, MODE_ANALOG); + set_gpio_mode(GPIOF, 9, MODE_ANALOG); + set_gpio_mode(GPIOF, 7, MODE_ANALOG); + set_gpio_mode(GPIOF, 5, MODE_ANALOG); + set_gpio_mode(GPIOF, 3, MODE_ANALOG); + set_gpio_mode(GPIOF, 10, MODE_ANALOG); + set_gpio_mode(GPIOF, 8, MODE_ANALOG); + set_gpio_mode(GPIOF, 6, MODE_ANALOG); + set_gpio_mode(GPIOF, 4, MODE_ANALOG); + set_gpio_mode(GPIOC, 0, MODE_ANALOG); + set_gpio_mode(GPIOC, 1, MODE_ANALOG); +} + +void board_v2_tick(void) {} + +const board board_v2 = { + .board_type = "V2", + .has_canfd = true, + .avdd_mV = 3300U, + .init = &board_v2_init, + .set_led = &board_v2_set_led, + .board_tick = &board_v2_tick, + .get_button = &board_v2_get_button, + .set_panda_power = &board_v2_set_panda_power, + .set_ignition = &board_v2_set_ignition, + .set_harness_orientation = &board_v2_set_harness_orientation, + .set_can_mode = &board_v2_set_can_mode, + .enable_can_transciever = &board_v2_enable_can_transciever, + .get_channel_power = &board_v2_get_channel_power, + .get_sbu_mV = &board_v1_get_sbu_mV, +}; \ No newline at end of file diff --git a/board/jungle/flash.py b/board/jungle/flash.py new file mode 100755 index 0000000000..75a7f0c8ee --- /dev/null +++ b/board/jungle/flash.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +import os +import subprocess + +from panda import PandaJungle + +board_path = os.path.dirname(os.path.realpath(__file__)) + +if __name__ == "__main__": + subprocess.check_call(f"scons -C {board_path}/.. -u -j$(nproc) {board_path}", shell=True) + + serials = PandaJungle.list() + print(f"found {len(serials)} panda jungle(s) - {serials}") + for s in serials: + print("flashing", s) + with PandaJungle(serial=s) as p: + p.flash() diff --git a/board/jungle/jungle_health.h b/board/jungle/jungle_health.h new file mode 100644 index 0000000000..931ed3715e --- /dev/null +++ b/board/jungle/jungle_health.h @@ -0,0 +1,24 @@ +// When changing these structs, python/__init__.py needs to be kept up to date! + +#define JUNGLE_HEALTH_PACKET_VERSION 1 +struct __attribute__((packed)) jungle_health_t { + uint32_t uptime_pkt; + float ch1_power; + float ch2_power; + float ch3_power; + float ch4_power; + float ch5_power; + float ch6_power; + uint16_t ch1_sbu1_mV; + uint16_t ch1_sbu2_mV; + uint16_t ch2_sbu1_mV; + uint16_t ch2_sbu2_mV; + uint16_t ch3_sbu1_mV; + uint16_t ch3_sbu2_mV; + uint16_t ch4_sbu1_mV; + uint16_t ch4_sbu2_mV; + uint16_t ch5_sbu1_mV; + uint16_t ch5_sbu2_mV; + uint16_t ch6_sbu1_mV; + uint16_t ch6_sbu2_mV; +}; diff --git a/board/jungle/main.c b/board/jungle/main.c new file mode 100644 index 0000000000..806fbd3f7b --- /dev/null +++ b/board/jungle/main.c @@ -0,0 +1,194 @@ +// ********************* Includes ********************* +#include "board/config.h" + +#include "board/safety.h" +#include "board/drivers/gmlan_alt.h" + +#include "board/drivers/pwm.h" +#include "board/drivers/usb.h" + +#include "board/early_init.h" +#include "board/provision.h" + +#include "board/health.h" +#include "jungle_health.h" + +#include "board/drivers/can_common.h" + +#ifdef STM32H7 + #include "board/drivers/fdcan.h" +#else + #include "board/drivers/bxcan.h" +#endif + +#include "board/obj/gitversion.h" + +#include "board/can_comms.h" +#include "main_comms.h" + + +// ********************* Serial debugging ********************* + +void debug_ring_callback(uart_ring *ring) { + char rcv; + while (getc(ring, &rcv)) { + (void)injectc(ring, rcv); + } +} + +// ***************************** main code ***************************** + +// cppcheck-suppress unusedFunction ; used in headers not included in cppcheck +void __initialize_hardware_early(void) { + early_initialization(); +} + +void __attribute__ ((noinline)) enable_fpu(void) { + // enable the FPU + SCB->CPACR |= ((3UL << (10U * 2U)) | (3UL << (11U * 2U))); +} + +// called at 8Hz +uint8_t loop_counter = 0U; +uint16_t button_press_cnt = 0U; +void tick_handler(void) { + if (TICK_TIMER->SR != 0) { + // tick drivers at 8Hz + usb_tick(); + + // decimated to 1Hz + if ((loop_counter % 8) == 0U) { + #ifdef DEBUG + print("** blink "); + print("rx:"); puth4(can_rx_q.r_ptr); print("-"); puth4(can_rx_q.w_ptr); print(" "); + print("tx1:"); puth4(can_tx1_q.r_ptr); print("-"); puth4(can_tx1_q.w_ptr); print(" "); + print("tx2:"); puth4(can_tx2_q.r_ptr); print("-"); puth4(can_tx2_q.w_ptr); print(" "); + print("tx3:"); puth4(can_tx3_q.r_ptr); print("-"); puth4(can_tx3_q.w_ptr); print("\n"); + #endif + + current_board->board_tick(); + + // check registers + check_registers(); + + // turn off the blue LED, turned on by CAN + current_board->set_led(LED_BLUE, false); + + // Blink and OBD CAN +#ifdef FINAL_PROVISIONING + current_board->set_can_mode(can_mode == CAN_MODE_NORMAL ? CAN_MODE_OBD_CAN2 : CAN_MODE_NORMAL); +#endif + + // on to the next one + uptime_cnt += 1U; + } + + current_board->set_led(LED_GREEN, green_led_enabled); + + // Check on button + bool current_button_status = current_board->get_button(); + + if (current_button_status && button_press_cnt == 10) { + current_board->set_panda_power(!panda_power); + } + +#ifdef FINAL_PROVISIONING + // ign on for 0.3s, off for 0.2s + const bool ign = (loop_counter % (3+2)) < 3; + if (ign != ignition) { + current_board->set_ignition(ign); + } +#else + static bool prev_button_status = false; + if (!current_button_status && prev_button_status && button_press_cnt < 10){ + current_board->set_ignition(!ignition); + } + prev_button_status = current_button_status; +#endif + + button_press_cnt = current_button_status ? button_press_cnt + 1 : 0; + + loop_counter++; + } + TICK_TIMER->SR = 0; +} + + +int main(void) { + // Init interrupt table + init_interrupts(true); + + // shouldn't have interrupts here, but just in case + disable_interrupts(); + + // init early devices + clock_init(); + peripherals_init(); + detect_board_type(); + + // print hello + print("\n\n\n************************ MAIN START ************************\n"); + + // check for non-supported board types + if (hw_type == HW_TYPE_UNKNOWN) { + print("Unsupported board type\n"); + while (1) { /* hang */ } + } + + print("Config:\n"); + print(" Board type: "); print(current_board->board_type); print("\n"); + + // init board + current_board->init(); + + // we have an FPU, let's use it! + enable_fpu(); + + microsecond_timer_init(); + + // 8Hz timer + REGISTER_INTERRUPT(TICK_TIMER_IRQ, tick_handler, 10U, FAULT_INTERRUPT_RATE_TICK) + tick_timer_init(); + +#ifdef DEBUG + print("DEBUG ENABLED\n"); +#endif + // enable USB (right before interrupts or enum can fail!) + usb_init(); + + print("**** INTERRUPTS ON ****\n"); + enable_interrupts(); + + can_silent = ALL_CAN_LIVE; + set_safety_hooks(SAFETY_ALLOUTPUT, 0U); + + can_init_all(); + current_board->set_harness_orientation(HARNESS_ORIENTATION_1); + +#ifdef FINAL_PROVISIONING + print("---- FINAL PROVISIONING BUILD ---- \n"); + can_set_forwarding(0, 2); + can_set_forwarding(1, 2); +#endif + + // LED should keep on blinking all the time + uint64_t cnt = 0; + for (cnt=0;;cnt++) { + // useful for debugging, fade breaks = panda is overloaded + for (uint32_t fade = 0U; fade < MAX_LED_FADE; fade += 1U) { + current_board->set_led(LED_RED, true); + delay(fade >> 4); + current_board->set_led(LED_RED, false); + delay((MAX_LED_FADE - fade) >> 4); + } + + for (uint32_t fade = MAX_LED_FADE; fade > 0U; fade -= 1U) { + current_board->set_led(LED_RED, true); + delay(fade >> 4); + current_board->set_led(LED_RED, false); + delay((MAX_LED_FADE - fade) >> 4); + } + } + + return 0; +} diff --git a/board/jungle/main_comms.h b/board/jungle/main_comms.h new file mode 100644 index 0000000000..36ed6e79e2 --- /dev/null +++ b/board/jungle/main_comms.h @@ -0,0 +1,252 @@ +extern int _app_start[0xc000]; // Only first 3 sectors of size 0x4000 are used + +int get_jungle_health_pkt(void *dat) { + COMPILE_TIME_ASSERT(sizeof(struct jungle_health_t) <= USBPACKET_MAX_SIZE); + struct jungle_health_t * health = (struct jungle_health_t*)dat; + + health->uptime_pkt = uptime_cnt; + health->ch1_power = current_board->get_channel_power(1U); + health->ch2_power = current_board->get_channel_power(2U); + health->ch3_power = current_board->get_channel_power(3U); + health->ch4_power = current_board->get_channel_power(4U); + health->ch5_power = current_board->get_channel_power(5U); + health->ch6_power = current_board->get_channel_power(6U); + + health->ch1_sbu1_mV = current_board->get_sbu_mV(1U, SBU1); + health->ch1_sbu2_mV = current_board->get_sbu_mV(1U, SBU2); + health->ch2_sbu1_mV = current_board->get_sbu_mV(2U, SBU1); + health->ch2_sbu2_mV = current_board->get_sbu_mV(2U, SBU2); + health->ch3_sbu1_mV = current_board->get_sbu_mV(3U, SBU1); + health->ch3_sbu2_mV = current_board->get_sbu_mV(3U, SBU2); + health->ch4_sbu1_mV = current_board->get_sbu_mV(4U, SBU1); + health->ch4_sbu2_mV = current_board->get_sbu_mV(4U, SBU2); + health->ch5_sbu1_mV = current_board->get_sbu_mV(5U, SBU1); + health->ch5_sbu2_mV = current_board->get_sbu_mV(5U, SBU2); + health->ch6_sbu1_mV = current_board->get_sbu_mV(6U, SBU1); + health->ch6_sbu2_mV = current_board->get_sbu_mV(6U, SBU2); + + return sizeof(*health); +} + +// send on serial, first byte to select the ring +void comms_endpoint2_write(uint8_t *data, uint32_t len) { + UNUSED(data); + UNUSED(len); +} + +int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { + unsigned int resp_len = 0; + uint32_t time; + +#ifdef DEBUG_COMMS + print("raw control request: "); hexdump(req, sizeof(ControlPacket_t)); print("\n"); + print("- request "); puth(req->request); print("\n"); + print("- param1 "); puth(req->param1); print("\n"); + print("- param2 "); puth(req->param2); print("\n"); +#endif + + switch (req->request) { + // **** 0xa0: Set panda power. + case 0xa0: + current_board->set_panda_power((req->param1 == 1U)); + break; + // **** 0xa1: Set harness orientation. + case 0xa1: + current_board->set_harness_orientation(req->param1); + break; + // **** 0xa2: Set ignition. + case 0xa2: + current_board->set_ignition((req->param1 == 1U)); + break; + // **** 0xa8: get microsecond timer + case 0xa8: + time = microsecond_timer_get(); + resp[0] = (time & 0x000000FFU); + resp[1] = ((time & 0x0000FF00U) >> 8U); + resp[2] = ((time & 0x00FF0000U) >> 16U); + resp[3] = ((time & 0xFF000000U) >> 24U); + resp_len = 4U; + break; + // **** 0xc0: reset communications + case 0xc0: + comms_can_reset(); + break; + // **** 0xc1: get hardware type + case 0xc1: + resp[0] = hw_type; + resp_len = 1; + break; + // **** 0xc2: CAN health stats + case 0xc2: + COMPILE_TIME_ASSERT(sizeof(can_health_t) <= USBPACKET_MAX_SIZE); + if (req->param1 < 3U) { + can_health[req->param1].can_speed = (bus_config[req->param1].can_speed / 10U); + can_health[req->param1].can_data_speed = (bus_config[req->param1].can_data_speed / 10U); + can_health[req->param1].canfd_enabled = bus_config[req->param1].canfd_enabled; + can_health[req->param1].brs_enabled = bus_config[req->param1].brs_enabled; + can_health[req->param1].canfd_non_iso = bus_config[req->param1].canfd_non_iso; + resp_len = sizeof(can_health[req->param1]); + (void)memcpy(resp, &can_health[req->param1], resp_len); + } + break; + // **** 0xc3: fetch MCU UID + case 0xc3: + (void)memcpy(resp, ((uint8_t *)UID_BASE), 12); + resp_len = 12; + break; + // **** 0xd0: fetch serial (aka the provisioned dongle ID) + case 0xd0: + // addresses are OTP + if (req->param1 == 1U) { + (void)memcpy(resp, (uint8_t *)DEVICE_SERIAL_NUMBER_ADDRESS, 0x10); + resp_len = 0x10; + } else { + get_provision_chunk(resp); + resp_len = PROVISION_CHUNK_LEN; + } + break; + // **** 0xd1: enter bootloader mode + case 0xd1: + // this allows reflashing of the bootstub + switch (req->param1) { + case 0: + // only allow bootloader entry on debug builds + #ifdef ALLOW_DEBUG + print("-> entering bootloader\n"); + enter_bootloader_mode = ENTER_BOOTLOADER_MAGIC; + NVIC_SystemReset(); + #endif + break; + case 1: + print("-> entering softloader\n"); + enter_bootloader_mode = ENTER_SOFTLOADER_MAGIC; + NVIC_SystemReset(); + break; + default: + print("Bootloader mode invalid\n"); + break; + } + break; + // **** 0xd2: get health packet + case 0xd2: + resp_len = get_jungle_health_pkt(resp); + break; + // **** 0xd3: get first 64 bytes of signature + case 0xd3: + { + resp_len = 64; + char * code = (char*)_app_start; + int code_len = _app_start[0]; + (void)memcpy(resp, &code[code_len], resp_len); + } + break; + // **** 0xd4: get second 64 bytes of signature + case 0xd4: + { + resp_len = 64; + char * code = (char*)_app_start; + int code_len = _app_start[0]; + (void)memcpy(resp, &code[code_len + 64], resp_len); + } + break; + // **** 0xd6: get version + case 0xd6: + COMPILE_TIME_ASSERT(sizeof(gitversion) <= USBPACKET_MAX_SIZE); + (void)memcpy(resp, gitversion, sizeof(gitversion)); + resp_len = sizeof(gitversion) - 1U; + break; + // **** 0xd8: reset ST + case 0xd8: + NVIC_SystemReset(); + break; + // **** 0xdb: set OBD CAN multiplexing mode + case 0xdb: + if (req->param1 == 1U) { + // Enable OBD CAN + current_board->set_can_mode(CAN_MODE_OBD_CAN2); + } else { + // Disable OBD CAN + current_board->set_can_mode(CAN_MODE_NORMAL); + } + break; + // **** 0xdd: get healthpacket and CANPacket versions + case 0xdd: + resp[0] = JUNGLE_HEALTH_PACKET_VERSION; + resp[1] = CAN_PACKET_VERSION; + resp[2] = CAN_HEALTH_PACKET_VERSION; + resp_len = 3; + break; + // **** 0xde: set can bitrate + case 0xde: + if ((req->param1 < PANDA_BUS_CNT) && is_speed_valid(req->param2, speeds, sizeof(speeds)/sizeof(speeds[0]))) { + bus_config[req->param1].can_speed = req->param2; + bool ret = can_init(CAN_NUM_FROM_BUS_NUM(req->param1)); + UNUSED(ret); + } + break; + // **** 0xe0: debug read + case 0xe0: + // read + while ((resp_len < MIN(req->length, USBPACKET_MAX_SIZE)) && getc(get_ring_by_number(0), (char*)&resp[resp_len])) { + ++resp_len; + } + break; + // **** 0xe5: set CAN loopback (for testing) + case 0xe5: + can_loopback = (req->param1 > 0U); + can_init_all(); + break; + // **** 0xf1: Clear CAN ring buffer. + case 0xf1: + if (req->param1 == 0xFFFFU) { + print("Clearing CAN Rx queue\n"); + can_clear(&can_rx_q); + } else if (req->param1 < PANDA_BUS_CNT) { + print("Clearing CAN Tx queue\n"); + can_clear(can_queues[req->param1]); + } else { + print("Clearing CAN CAN ring buffer failed: wrong bus number\n"); + } + break; + // **** 0xf2: Clear debug ring buffer. + case 0xf2: + print("Clearing debug queue.\n"); + clear_uart_buff(get_ring_by_number(0)); + break; + // **** 0xf4: Set CAN transceiver enable pin + case 0xf4: + current_board->enable_can_transciever(req->param1, req->param2 > 0U); + break; + // **** 0xf5: Set CAN silent mode + case 0xf5: + can_silent = (req->param1 > 0U) ? ALL_CAN_SILENT : ALL_CAN_LIVE; + can_init_all(); + break; + // **** 0xf9: set CAN FD data bitrate + case 0xf9: + if ((req->param1 < PANDA_CAN_CNT) && + current_board->has_canfd && + is_speed_valid(req->param2, data_speeds, sizeof(data_speeds)/sizeof(data_speeds[0]))) { + bus_config[req->param1].can_data_speed = req->param2; + bus_config[req->param1].canfd_enabled = (req->param2 >= bus_config[req->param1].can_speed); + bus_config[req->param1].brs_enabled = (req->param2 > bus_config[req->param1].can_speed); + bool ret = can_init(CAN_NUM_FROM_BUS_NUM(req->param1)); + UNUSED(ret); + } + break; + // **** 0xfc: set CAN FD non-ISO mode + case 0xfc: + if ((req->param1 < PANDA_CAN_CNT) && current_board->has_canfd) { + bus_config[req->param1].canfd_non_iso = (req->param2 != 0U); + bool ret = can_init(CAN_NUM_FROM_BUS_NUM(req->param1)); + UNUSED(ret); + } + break; + default: + print("NO HANDLER "); + puth(req->request); + print("\n"); + break; + } + return resp_len; +} diff --git a/board/jungle/recover.py b/board/jungle/recover.py new file mode 100755 index 0000000000..98afb06748 --- /dev/null +++ b/board/jungle/recover.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import os +import time +import subprocess + +from panda import PandaJungle, PandaJungleDFU + +board_path = os.path.dirname(os.path.realpath(__file__)) + +if __name__ == "__main__": + subprocess.check_call(f"scons -C {board_path}/.. -u -j$(nproc) {board_path}", shell=True) + + for s in PandaJungle.list(): + print("putting", s, "in DFU mode") + with PandaJungle(serial=s) as p: + p.reset(enter_bootstub=True) + p.reset(enter_bootloader=True) + + # wait for reset panda jungles to come back up + time.sleep(1) + + dfu_serials = PandaJungleDFU.list() + print(f"found {len(dfu_serials)} panda jungle(s) in DFU - {dfu_serials}") + for s in dfu_serials: + print("flashing", s) + PandaJungleDFU(s).recover() diff --git a/board/jungle/scripts/can_health.py b/board/jungle/scripts/can_health.py new file mode 100755 index 0000000000..ff068b5baa --- /dev/null +++ b/board/jungle/scripts/can_health.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 + +import time +from panda import PandaJungle + +if __name__ == "__main__": + jungle = PandaJungle() + + while True: + for bus in range(3): + print(bus, jungle.can_health(bus)) + print() + time.sleep(1) diff --git a/board/jungle/scripts/can_printer.py b/board/jungle/scripts/can_printer.py new file mode 100755 index 0000000000..3e64806348 --- /dev/null +++ b/board/jungle/scripts/can_printer.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +import os +import time +from collections import defaultdict +import binascii + +from panda import PandaJungle + +# fake +def sec_since_boot(): + return time.time() + +def can_printer(): + p = PandaJungle() + + start = sec_since_boot() + lp = sec_since_boot() + msgs = defaultdict(list) + canbus = int(os.getenv("CAN", "0")) + while True: + can_recv = p.can_recv() + for address, _, dat, src in can_recv: + if src == canbus: + msgs[address].append(dat) + + if sec_since_boot() - lp > 0.1: + dd = chr(27) + "[2J" + dd += "%5.2f\n" % (sec_since_boot() - start) + for k,v in sorted(zip(list(msgs.keys()), [binascii.hexlify(x[-1]) for x in list(msgs.values())])): + dd += "%s(%6d) %s\n" % ("%04X(%4d)" % (k,k),len(msgs[k]), v) + print(dd) + lp = sec_since_boot() + +if __name__ == "__main__": + can_printer() diff --git a/board/jungle/scripts/debug_console.py b/board/jungle/scripts/debug_console.py new file mode 100755 index 0000000000..f8dd24ed2c --- /dev/null +++ b/board/jungle/scripts/debug_console.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +import os +import sys +import time + +from panda import PandaJungle + +setcolor = ["\033[1;32;40m", "\033[1;31;40m"] +unsetcolor = "\033[00m" + +if __name__ == "__main__": + while True: + try: + claim = os.getenv("CLAIM") is not None + + serials = PandaJungle.list() + if os.getenv("SERIAL"): + serials = [x for x in serials if x==os.getenv("SERIAL")] + + panda_jungles = list([PandaJungle(x, claim=claim) for x in serials]) + + if not len(panda_jungles): + sys.exit("no panda jungles found") + + while True: + for i, panda_jungle in enumerate(panda_jungles): + while True: + ret = panda_jungle.debug_read() + if len(ret) > 0: + sys.stdout.write(setcolor[i] + ret.decode('ascii') + unsetcolor) + sys.stdout.flush() + else: + break + time.sleep(0.01) + except Exception as e: + print(e) + print("panda jungle disconnected!") + time.sleep(0.5) diff --git a/board/jungle/scripts/echo_loopback_test.py b/board/jungle/scripts/echo_loopback_test.py new file mode 100755 index 0000000000..d68fa4f3ec --- /dev/null +++ b/board/jungle/scripts/echo_loopback_test.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +import os +import time +import contextlib +import random +from termcolor import cprint + +from panda import PandaJungle + +# This script is intended to be used in conjunction with the echo.py test script from panda. +# It sends messages on bus 0, 1, 2 and checks for a reversed response to be sent back. + +################################################################# +############################# UTILS ############################# +################################################################# +def print_colored(text, color): + cprint(text + " "*40, color, end="\r") + +def get_test_string(): + return b"test"+os.urandom(4) + +################################################################# +############################# TEST ############################## +################################################################# + +def test_loopback(): + for bus in range(3): + # Clear can + jungle.can_clear(bus) + # Send a random message + address = random.randint(1, 2000) + data = get_test_string() + jungle.can_send(address, data, bus) + time.sleep(0.1) + + # Make sure it comes back reversed + incoming = jungle.can_recv() + found = False + for message in incoming: + incomingAddress, _, incomingData, incomingBus = message + if incomingAddress == address and incomingData == data[::-1] and incomingBus == bus: + found = True + break + if not found: + cprint("\nFAILED", "red") + assert False + +################################################################# +############################# MAIN ############################## +################################################################# +jungle = None +counter = 0 + +if __name__ == "__main__": + # Connect to jungle silently + print_colored("Connecting to jungle", "blue") + with open(os.devnull, "w") as devnull: + with contextlib.redirect_stdout(devnull): + jungle = PandaJungle() + jungle.set_panda_power(True) + jungle.set_ignition(False) + + # Run test + while True: + jungle.can_clear(0xFFFF) + test_loopback() + counter += 1 + print_colored(str(counter) + " loopback cycles complete", "blue") diff --git a/board/jungle/scripts/get_version.py b/board/jungle/scripts/get_version.py new file mode 100755 index 0000000000..ad4a1c4264 --- /dev/null +++ b/board/jungle/scripts/get_version.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 +from panda import PandaJungle + +if __name__ == "__main__": + for p in PandaJungle.list(): + pp = PandaJungle(p) + print("%s: %s" % (pp.get_serial()[0], pp.get_version())) + + diff --git a/board/jungle/scripts/health_test.py b/board/jungle/scripts/health_test.py new file mode 100755 index 0000000000..039f840e0b --- /dev/null +++ b/board/jungle/scripts/health_test.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +import time +from pprint import pprint + +from panda import PandaJungle + +if __name__ == "__main__": + i = 0 + pi = 0 + + pj = PandaJungle() + while True: + st = time.monotonic() + while time.monotonic() - st < 1: + pj.health() + pj.can_health(0) + i += 1 + pprint(pj.health()) + print(f"Speed: {i - pi}Hz") + pi = i + diff --git a/board/jungle/scripts/loopback_test.py b/board/jungle/scripts/loopback_test.py new file mode 100755 index 0000000000..10caf42cc4 --- /dev/null +++ b/board/jungle/scripts/loopback_test.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +import os +import time +import contextlib +import random +from termcolor import cprint + +from panda import Panda, PandaJungle + +NUM_PANDAS_PER_TEST = 1 +FOR_RELEASE_BUILDS = os.getenv("RELEASE") is not None # Release builds do not have ALLOUTPUT mode + +BUS_SPEEDS = [125, 500, 1000] + +################################################################# +############################# UTILS ############################# +################################################################# +# To suppress the connection text +def silent_panda_connect(serial): + with open(os.devnull, "w") as devnull: + with contextlib.redirect_stdout(devnull): + panda = Panda(serial) + return panda + +def print_colored(text, color): + cprint(text + " "*40, color, end="\r") + +def connect_to_pandas(): + print_colored("Connecting to pandas", "blue") + # Connect to pandas + pandas = [] + for serial in panda_serials: + pandas.append(silent_panda_connect(serial)) + print_colored("Connected", "blue") + +def start_with_orientation(orientation): + print_colored("Restarting pandas with orientation " + str(orientation), "blue") + jungle.set_panda_power(False) + jungle.set_harness_orientation(orientation) + time.sleep(4) + jungle.set_panda_power(True) + time.sleep(2) + connect_to_pandas() + +def can_loopback(sender): + receivers = list(filter((lambda p: (p != sender)), [jungle] + pandas)) + + for bus in range(4): + obd = False + if bus == 3: + obd = True + bus = 1 + + # Clear buses + for receiver in receivers: + receiver.set_obd(obd) + receiver.can_clear(bus) # TX + receiver.can_clear(0xFFFF) # RX + + # Send a random string + addr = 0x18DB33F1 if FOR_RELEASE_BUILDS else random.randint(1, 2000) + string = b"test"+os.urandom(4) + sender.set_obd(obd) + time.sleep(0.2) + sender.can_send(addr, string, bus) + time.sleep(0.2) + + # Check if all receivers have indeed received them in their receiving buffers + for receiver in receivers: + content = receiver.can_recv() + + # Check amount of messages + if len(content) != 1: + raise Exception("Amount of received CAN messages (" + str(len(content)) + ") does not equal 1. Bus: " + str(bus) +" OBD: " + str(obd)) + + # Check content + if content[0][0] != addr or content[0][2] != string: + raise Exception("Received CAN message content or address does not match") + + # Check bus + if content[0][3] != bus: + raise Exception("Received CAN message bus does not match") + +################################################################# +############################# TEST ############################## +################################################################# + +def test_loopback(): + # disable safety modes + for panda in pandas: + panda.set_safety_mode(Panda.SAFETY_ELM327 if FOR_RELEASE_BUILDS else Panda.SAFETY_ALLOUTPUT) + + # perform loopback with jungle as a sender + can_loopback(jungle) + + # perform loopback with each possible panda as a sender + for panda in pandas: + can_loopback(panda) + + # enable safety modes + for panda in pandas: + panda.set_safety_mode(Panda.SAFETY_SILENT) + +################################################################# +############################# MAIN ############################## +################################################################# +jungle = None +pandas = [] # type: ignore +panda_serials = [] +counter = 0 + +if __name__ == "__main__": + # Connect to jungle silently + print_colored("Connecting to jungle", "blue") + with open(os.devnull, "w") as devnull: + with contextlib.redirect_stdout(devnull): + jungle = PandaJungle() + jungle.set_panda_power(True) + jungle.set_ignition(False) + + # Connect to new pandas before starting tests + print_colored("Waiting for " + str(NUM_PANDAS_PER_TEST) + " pandas to be connected", "yellow") + while True: + connected_serials = Panda.list() + if len(connected_serials) == NUM_PANDAS_PER_TEST: + panda_serials = connected_serials + break + + start_with_orientation(PandaJungle.HARNESS_ORIENTATION_1) + + # Set bus speeds + for device in pandas + [jungle]: + for bus in range(len(BUS_SPEEDS)): + device.set_can_speed_kbps(bus, BUS_SPEEDS[bus]) + + # Run test + while True: + test_loopback() + counter += 1 + print_colored(str(counter) + " loopback cycles complete", "blue") diff --git a/board/jungle/scripts/panda_identification_test.py b/board/jungle/scripts/panda_identification_test.py new file mode 100755 index 0000000000..a61b0d608b --- /dev/null +++ b/board/jungle/scripts/panda_identification_test.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +import os +import time +import random +import contextlib + +from panda import PandaJungle +from panda import Panda + +PANDA_UNDER_TEST = Panda.HW_TYPE_UNO + +panda_jungle = PandaJungle() + +def silent_panda_connect(): + with open(os.devnull, "w") as devnull: + with contextlib.redirect_stdout(devnull): + panda = Panda() + return panda + +def reboot_panda(harness_orientation=PandaJungle.HARNESS_ORIENTATION_NONE, ignition=False): + print(f"Restarting panda with harness orientation: {harness_orientation} and ignition: {ignition}") + panda_jungle.set_panda_power(False) + panda_jungle.set_harness_orientation(harness_orientation) + panda_jungle.set_ignition(ignition) + time.sleep(2) + panda_jungle.set_panda_power(True) + time.sleep(2) + +count = 0 +if __name__ == "__main__": + while True: + ignition = random.randint(0, 1) + harness_orientation = random.randint(0, 2) + reboot_panda(harness_orientation, ignition) + + p = silent_panda_connect() + assert p.get_type() == PANDA_UNDER_TEST + assert p.health()['car_harness_status'] == harness_orientation + if harness_orientation != PandaJungle.HARNESS_ORIENTATION_NONE: + assert p.health()['ignition_line'] == ignition + + count += 1 + print(f"Passed {count} loops") + + diff --git a/board/jungle/scripts/start.py b/board/jungle/scripts/start.py new file mode 100755 index 0000000000..76afd14ac4 --- /dev/null +++ b/board/jungle/scripts/start.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +import sys + +from panda import PandaJungle + +if __name__ == "__main__": + jungle = PandaJungle() + + # If first argument specified, it sets the ignition (0 or 1) + if len(sys.argv) > 1: + if sys.argv[1] == '1': + jungle.set_ignition(True) + else: + jungle.set_ignition(False) + jungle.set_harness_orientation(1) + jungle.set_panda_power(True) + + diff --git a/board/jungle/stm32fx/board.h b/board/jungle/stm32fx/board.h new file mode 100644 index 0000000000..0adf7923b2 --- /dev/null +++ b/board/jungle/stm32fx/board.h @@ -0,0 +1,7 @@ +#include "boards/board_declarations.h" +#include "boards/board_v1.h" + +void detect_board_type(void) { + hw_type = HW_TYPE_V1; + current_board = &board_v1; +} diff --git a/board/jungle/stm32h7/board.h b/board/jungle/stm32h7/board.h new file mode 100644 index 0000000000..4fe4fa4637 --- /dev/null +++ b/board/jungle/stm32h7/board.h @@ -0,0 +1,9 @@ +#include "boards/board_declarations.h" + +#include "stm32h7/lladc.h" +#include "boards/board_v2.h" + +void detect_board_type(void) { + hw_type = HW_TYPE_V2; + current_board = &board_v2; +} diff --git a/board/jungle/stm32h7/lladc.h b/board/jungle/stm32h7/lladc.h new file mode 100644 index 0000000000..06b742dd38 --- /dev/null +++ b/board/jungle/stm32h7/lladc.h @@ -0,0 +1,54 @@ + +typedef struct { + ADC_TypeDef *adc; + uint8_t channel; +} adc_channel_t; + +void adc_init(ADC_TypeDef *adc) { + adc->CR &= ~(ADC_CR_DEEPPWD); // Reset deep-power-down mode + adc->CR |= ADC_CR_ADVREGEN; // Enable ADC regulator + while(!(adc->ISR & ADC_ISR_LDORDY) && (adc != ADC3)); + + if (adc != ADC3) { + adc->CR &= ~(ADC_CR_ADCALDIF); // Choose single-ended calibration + adc->CR |= ADC_CR_ADCALLIN; // Lineriality calibration + } + adc->CR |= ADC_CR_ADCAL; // Start calibrtation + while((adc->CR & ADC_CR_ADCAL) != 0); + + adc->ISR |= ADC_ISR_ADRDY; + adc->CR |= ADC_CR_ADEN; + while(!(adc->ISR & ADC_ISR_ADRDY)); +} + +uint16_t adc_get_raw(ADC_TypeDef *adc, uint8_t channel) { + adc->SQR1 &= ~(ADC_SQR1_L); + adc->SQR1 = ((uint32_t) channel << 6U); + + if (channel < 10U) { + adc->SMPR1 = (0x7U << (channel * 3U)); + } else { + adc->SMPR2 = (0x7U << ((channel - 10U) * 3U)); + } + adc->PCSEL_RES0 = (0x1U << channel); + + adc->CR |= ADC_CR_ADSTART; + while (!(adc->ISR & ADC_ISR_EOC)); + + uint16_t res = adc->DR; + + while (!(adc->ISR & ADC_ISR_EOS)); + adc->ISR |= ADC_ISR_EOS; + + return res; +} + +uint16_t adc_get_mV(ADC_TypeDef *adc, uint8_t channel) { + uint16_t ret = 0; + if ((adc == ADC1) || (adc == ADC2)) { + ret = (adc_get_raw(adc, channel) * current_board->avdd_mV) / 65535U; + } else if (adc == ADC3) { + ret = (adc_get_raw(adc, channel) * current_board->avdd_mV) / 4095U; + } else {} + return ret; +} diff --git a/board/stm32fx/peripherals.h b/board/stm32fx/peripherals.h index 3eeebf0eb6..d910886d3c 100644 --- a/board/stm32fx/peripherals.h +++ b/board/stm32fx/peripherals.h @@ -68,7 +68,7 @@ void peripherals_init(void) { RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN; RCC->APB1ENR |= RCC_APB1ENR_USART2EN; RCC->APB1ENR |= RCC_APB1ENR_USART3EN; - #ifdef PANDA + #ifndef PEDAL RCC->APB1ENR |= RCC_APB1ENR_UART5EN; #endif RCC->APB1ENR |= RCC_APB1ENR_CAN1EN; diff --git a/board/stm32fx/stm32fx_config.h b/board/stm32fx/stm32fx_config.h index 1573426da8..dd026e98e4 100644 --- a/board/stm32fx/stm32fx_config.h +++ b/board/stm32fx/stm32fx_config.h @@ -48,10 +48,10 @@ #include "comms_definitions.h" #ifndef BOOTSTUB - #ifdef PANDA - #include "main_declarations.h" - #else + #ifdef PEDAL #include "pedal/main_declarations.h" + #else + #include "main_declarations.h" #endif #else #include "bootstub_declarations.h" @@ -73,17 +73,17 @@ #include "drivers/watchdog.h" #include "stm32fx/llflash.h" -#if defined(PANDA) || defined(BOOTSTUB) +#if !defined(PEDAL) || defined(BOOTSTUB) #include "drivers/spi.h" #include "stm32fx/llspi.h" #endif -#if !defined(BOOTSTUB) && (defined(PANDA) || defined(PEDAL_USB)) +#if !defined(BOOTSTUB) && (!defined(PEDAL) || defined(PEDAL_USB)) #include "drivers/uart.h" #include "stm32fx/lluart.h" #endif -#if !defined(PEDAL_USB) && !defined(PEDAL) && !defined(BOOTSTUB) +#if defined(PANDA) && !defined(BOOTSTUB) #include "stm32fx/llexti.h" #endif @@ -91,7 +91,7 @@ #include "stm32fx/llbxcan.h" #endif -#if defined(PANDA) || defined(BOOTSTUB) || defined(PEDAL_USB) +#if !defined(PEDAL) || defined(PEDAL_USB) || defined(BOOTSTUB) #include "stm32fx/llusb.h" #endif diff --git a/board/stm32h7/peripherals.h b/board/stm32h7/peripherals.h index 7e3b2e5b16..cf397644ec 100644 --- a/board/stm32h7/peripherals.h +++ b/board/stm32h7/peripherals.h @@ -125,10 +125,15 @@ void peripherals_init(void) { RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; // clock source timer RCC->APB1HENR |= RCC_APB1HENR_FDCANEN; // FDCAN core enable - RCC->AHB1ENR |= RCC_AHB1ENR_ADC12EN; // Enable ADC clocks + RCC->AHB1ENR |= RCC_AHB1ENR_ADC12EN; // Enable ADC12 clocks RCC->APB4ENR |= RCC_APB4ENR_SYSCFGEN; +#ifdef PANDA_JUNGLE + RCC->AHB3ENR |= RCC_AHB3ENR_SDMMC1EN; // SDMMC + RCC->AHB4ENR |= RCC_AHB4ENR_ADC3EN; // Enable ADC3 clocks +#endif + // HS USB enable, also LP is needed for CSleep state(__WFI()) RCC->AHB1ENR |= RCC_AHB1ENR_USB1OTGHSEN; RCC->AHB1LPENR |= RCC_AHB1LPENR_USB1OTGHSLPEN; diff --git a/board/stm32h7/stm32h7_config.h b/board/stm32h7/stm32h7_config.h index 4e6f99bfa6..0cddad4fa1 100644 --- a/board/stm32h7/stm32h7_config.h +++ b/board/stm32h7/stm32h7_config.h @@ -75,7 +75,7 @@ separate IRQs for RX and TX. #include "drivers/watchdog.h" #include "stm32h7/llflash.h" -#if !defined(BOOTSTUB) && defined(PANDA) +#if !defined(BOOTSTUB) #include "drivers/uart.h" #include "stm32h7/lluart.h" #endif diff --git a/python/__init__.py b/python/__init__.py index 592a4bff4b..637a6b6146 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -184,6 +184,7 @@ class Panda: GMLAN_CAN2 = 1 GMLAN_CAN3 = 2 + USB_PIDS = (0xddee, 0xddcc) REQUEST_IN = usb1.ENDPOINT_IN | usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE REQUEST_OUT = usb1.ENDPOINT_OUT | usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE @@ -204,9 +205,9 @@ class Panda: HEALTH_STRUCT = struct.Struct(" /sys/class/gpio/export || true From 977d702770ec8eb0701cd2c7e20a93804fe16bcd Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 4 Aug 2023 10:40:53 -0700 Subject: [PATCH 162/197] cleanup old pedal stuff (#1552) --- board/pedal/{README => README.md} | 0 board/pedal/flash_can.sh | 4 ++-- board/pedal/recover.sh | 2 +- tests/pedal/test_pedal.py | 3 --- 4 files changed, 3 insertions(+), 6 deletions(-) rename board/pedal/{README => README.md} (100%) diff --git a/board/pedal/README b/board/pedal/README.md similarity index 100% rename from board/pedal/README rename to board/pedal/README.md diff --git a/board/pedal/flash_can.sh b/board/pedal/flash_can.sh index 0225bf8dff..b9edf25f12 100755 --- a/board/pedal/flash_can.sh +++ b/board/pedal/flash_can.sh @@ -2,7 +2,7 @@ set -e cd .. -PEDAL=1 scons -u -j$(nproc) +scons -u -j$(nproc) cd pedal -../../tests/pedal/enter_canloader.py ../obj/pedal.bin.signed +../../tests/pedal/enter_canloader.py obj/pedal.bin.signed diff --git a/board/pedal/recover.sh b/board/pedal/recover.sh index f81f672778..d7fe0aff5f 100755 --- a/board/pedal/recover.sh +++ b/board/pedal/recover.sh @@ -4,7 +4,7 @@ set -e DFU_UTIL="dfu-util" cd .. -PEDAL=1 scons -u -j$(nproc) +scons -u -j$(nproc) cd pedal $DFU_UTIL -d 0483:df11 -a 0 -s 0x08004000 -D obj/pedal.bin.signed diff --git a/tests/pedal/test_pedal.py b/tests/pedal/test_pedal.py index a4bc5724bc..04c293e0cc 100755 --- a/tests/pedal/test_pedal.py +++ b/tests/pedal/test_pedal.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 import os import time -import subprocess import unittest from panda import Panda, PandaJungle, CanHandle, McuType, BASEDIR @@ -44,7 +43,6 @@ def _listen_can_frames(self): return msgs def test_usb_fw(self): - subprocess.check_output(f"cd {BASEDIR} && PEDAL=1 PEDAL_USB=1 scons", shell=True) self._flash_over_can(PEDAL_BUS, f"{BASEDIR}/board/pedal/obj/pedal_usb.bin.signed") time.sleep(2) with Panda(PEDAL_SERIAL) as p: @@ -52,7 +50,6 @@ def test_usb_fw(self): self.assertTrue(self._listen_can_frames() > 40) def test_nonusb_fw(self): - subprocess.check_output(f"cd {BASEDIR} && PEDAL=1 scons", shell=True) self._flash_over_can(PEDAL_BUS, f"{BASEDIR}board/pedal/obj/pedal.bin.signed") time.sleep(2) self.assertTrue(self._listen_can_frames() > 40) From 1950ba6f83b2ccf31a609fb6eca6aed5e4840252 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 4 Aug 2023 10:46:56 -0700 Subject: [PATCH 163/197] cleanup bus count constant --- board/SConscript | 1 - board/can_definitions.h | 1 + board/jungle/SConscript | 1 - board/pedal/SConscript | 5 +---- tests/libpanda/SConscript | 3 +-- 5 files changed, 3 insertions(+), 8 deletions(-) diff --git a/board/SConscript b/board/SConscript index 3b115a62f9..45fa0f416e 100644 --- a/board/SConscript +++ b/board/SConscript @@ -11,7 +11,6 @@ build_projects = { for project_name, project in build_projects.items(): flags = [ "-DPANDA", - "-DPANDA_BUS_CNT=4", ] if ("ENABLE_SPI" in os.environ or "h7" in project_name) and not project_name.startswith('pedal'): flags.append('-DENABLE_SPI') diff --git a/board/can_definitions.h b/board/can_definitions.h index b12d0e43f3..daae4d0443 100644 --- a/board/can_definitions.h +++ b/board/can_definitions.h @@ -1,6 +1,7 @@ #pragma once const uint8_t PANDA_CAN_CNT = 3U; +const uint8_t PANDA_BUS_CNT = 4U; // bump this when changing the CAN packet #define CAN_PACKET_VERSION 4 diff --git a/board/jungle/SConscript b/board/jungle/SConscript index dfa325dd0b..3e40611a64 100644 --- a/board/jungle/SConscript +++ b/board/jungle/SConscript @@ -11,7 +11,6 @@ build_projects = { for project_name, project in build_projects.items(): flags = [ "-DPANDA_JUNGLE", - "-DPANDA_BUS_CNT=3", ] if os.getenv("FINAL_PROVISIONING"): flags += ["-DFINAL_PROVISIONING"] diff --git a/board/pedal/SConscript b/board/pedal/SConscript index e993111c85..bfa3c2c19d 100644 --- a/board/pedal/SConscript +++ b/board/pedal/SConscript @@ -25,7 +25,4 @@ build_projects["pedal_usb"] = copy.deepcopy(build_projects["pedal"]) build_projects["pedal_usb"]["PROJECT_FLAGS"].append("-DPEDAL_USB") for project_name, project in build_projects.items(): - flags = [ - "-DPANDA_BUS_CNT=4", - ] - build_project(project_name, project, flags) + build_project(project_name, project, []) diff --git a/tests/libpanda/SConscript b/tests/libpanda/SConscript index cf24949e82..7f4871cd5e 100644 --- a/tests/libpanda/SConscript +++ b/tests/libpanda/SConscript @@ -3,7 +3,7 @@ import platform CC = 'gcc' system = platform.system() if system == 'Darwin': - # gcc installed by homebrew has version suffix (e.g. gcc-12) in order to be + # gcc installed by homebrew has version suffix (e.g. gcc-12) in order to be # distinguishable from system one - which acts as a symlink to clang CC += '-12' @@ -14,7 +14,6 @@ env = Environment( '-fno-builtin', '-std=gnu11', '-Wfatal-errors', - '-DPANDA_BUS_CNT=4', ], CPPPATH=[".", "../../board/"], ) From 43bde4138de95e8385a5fe3f3a9850f0729a1ce2 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 4 Aug 2023 14:28:23 -0700 Subject: [PATCH 164/197] jungle: SBU provisioning tests (#1553) * jungle: SBU provisioning tests * update can_send * cleanup * fix misra * space --- .github/workflows/test.yaml | 2 ++ board/drivers/gpio.h | 6 ++++++ board/jungle/boards/board_declarations.h | 10 ++++++++- board/jungle/boards/board_v1.h | 8 +++++--- board/jungle/boards/board_v2.h | 17 +++++++++++----- board/jungle/main.c | 26 +++++++++++++++++++----- 6 files changed, 55 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index df7ca75195..cd18ec35da 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -48,6 +48,8 @@ jobs: run: ${{ env.RUN }} "ENABLE_SPI=1 scons -j4" - name: Build with UBSan run: ${{ env.RUN }} "scons -j4 --ubsan" + - name: Build jungle firmware with FINAL_PROVISIONING support + run: ${{ env.RUN }} "FINAL_PROVISIONING=1 scons -j4 board/jungle" unit_tests: name: unit tests diff --git a/board/drivers/gpio.h b/board/drivers/gpio.h index 88af9319d8..fe3194f3ba 100644 --- a/board/drivers/gpio.h +++ b/board/drivers/gpio.h @@ -74,6 +74,12 @@ void gpio_set_all_output(const gpio_t *pins, uint8_t num_pins, bool enabled) { } } +void gpio_set_bitmask(const gpio_t *pins, uint8_t num_pins, uint32_t bitmask) { + for (uint8_t i = 0; i < num_pins; i++) { + set_gpio_output(pins[i].bank, pins[i].pin, (bitmask >> i) & 1U); + } +} + // Detection with internal pullup #define PULL_EFFECTIVE_DELAY 4096 bool detect_with_pull(GPIO_TypeDef *GPIO, int pin, int mode) { diff --git a/board/jungle/boards/board_declarations.h b/board/jungle/boards/board_declarations.h index a02b906b64..dcb6ac24ea 100644 --- a/board/jungle/boards/board_declarations.h +++ b/board/jungle/boards/board_declarations.h @@ -5,6 +5,7 @@ typedef void (*board_board_tick)(void); typedef bool (*board_get_button)(void); typedef void (*board_set_panda_power)(bool enabled); typedef void (*board_set_ignition)(bool enabled); +typedef void (*board_set_individual_ignition)(uint8_t bitmask); typedef void (*board_set_harness_orientation)(uint8_t orientation); typedef void (*board_set_can_mode)(uint8_t mode); typedef void (*board_enable_can_transciever)(uint8_t transciever, bool enabled); @@ -14,6 +15,7 @@ typedef uint16_t (*board_get_sbu_mV)(uint8_t channel, uint8_t sbu); struct board { const char *board_type; const bool has_canfd; + const bool has_sbu_sense; const uint16_t avdd_mV; board_init init; board_set_led set_led; @@ -21,6 +23,7 @@ struct board { board_get_button get_button; board_set_panda_power set_panda_power; board_set_ignition set_ignition; + board_set_individual_ignition set_individual_ignition; board_set_harness_orientation set_harness_orientation; board_set_can_mode set_can_mode; board_enable_can_transciever enable_can_transciever; @@ -59,4 +62,9 @@ struct board { // ********************* Globals ********************** uint8_t harness_orientation = HARNESS_ORIENTATION_NONE; uint8_t can_mode = CAN_MODE_NORMAL; -bool ignition = false; +uint8_t ignition = 0U; + + +void unused_set_individual_ignition(uint8_t bitmask) { + UNUSED(bitmask); +} \ No newline at end of file diff --git a/board/jungle/boards/board_v1.h b/board/jungle/boards/board_v1.h index 5ad574faad..1dc95ad154 100644 --- a/board/jungle/boards/board_v1.h +++ b/board/jungle/boards/board_v1.h @@ -56,13 +56,13 @@ void board_v1_set_harness_orientation(uint8_t orientation) { break; case HARNESS_ORIENTATION_1: set_gpio_output(GPIOA, 2, false); - set_gpio_output(GPIOA, 3, ignition); + set_gpio_output(GPIOA, 3, (ignition != 0U)); set_gpio_output(GPIOA, 4, true); set_gpio_output(GPIOA, 5, false); harness_orientation = orientation; break; case HARNESS_ORIENTATION_2: - set_gpio_output(GPIOA, 2, ignition); + set_gpio_output(GPIOA, 2, (ignition != 0U)); set_gpio_output(GPIOA, 3, false); set_gpio_output(GPIOA, 4, false); set_gpio_output(GPIOA, 5, true); @@ -85,7 +85,7 @@ bool board_v1_get_button(void) { } void board_v1_set_ignition(bool enabled) { - ignition = enabled; + ignition = enabled ? 0xFFU : 0U; board_v1_set_harness_orientation(harness_orientation); } @@ -153,6 +153,7 @@ void board_v1_tick(void) {} const board board_v1 = { .board_type = "V1", .has_canfd = false, + .has_sbu_sense = false, .avdd_mV = 3300U, .init = &board_v1_init, .set_led = &board_v1_set_led, @@ -160,6 +161,7 @@ const board board_v1 = { .get_button = &board_v1_get_button, .set_panda_power = &board_v1_set_panda_power, .set_ignition = &board_v1_set_ignition, + .set_individual_ignition = &unused_set_individual_ignition, .set_harness_orientation = &board_v1_set_harness_orientation, .set_can_mode = &board_v1_set_can_mode, .enable_can_transciever = &board_v1_enable_can_transciever, diff --git a/board/jungle/boards/board_v2.h b/board/jungle/boards/board_v2.h index e474967351..c6174796a5 100644 --- a/board/jungle/boards/board_v2.h +++ b/board/jungle/boards/board_v2.h @@ -90,12 +90,12 @@ void board_v2_set_harness_orientation(uint8_t orientation) { case HARNESS_ORIENTATION_1: gpio_set_all_output(sbu1_ignition_pins, sizeof(sbu1_ignition_pins) / sizeof(gpio_t), false); gpio_set_all_output(sbu1_relay_pins, sizeof(sbu1_relay_pins) / sizeof(gpio_t), true); - gpio_set_all_output(sbu2_ignition_pins, sizeof(sbu2_ignition_pins) / sizeof(gpio_t), ignition); + gpio_set_bitmask(sbu2_ignition_pins, sizeof(sbu2_ignition_pins) / sizeof(gpio_t), ignition); gpio_set_all_output(sbu2_relay_pins, sizeof(sbu2_relay_pins) / sizeof(gpio_t), false); harness_orientation = orientation; break; case HARNESS_ORIENTATION_2: - gpio_set_all_output(sbu1_ignition_pins, sizeof(sbu1_ignition_pins) / sizeof(gpio_t), ignition); + gpio_set_bitmask(sbu1_ignition_pins, sizeof(sbu1_ignition_pins) / sizeof(gpio_t), ignition); gpio_set_all_output(sbu1_relay_pins, sizeof(sbu1_relay_pins) / sizeof(gpio_t), false); gpio_set_all_output(sbu2_ignition_pins, sizeof(sbu2_ignition_pins) / sizeof(gpio_t), false); gpio_set_all_output(sbu2_relay_pins, sizeof(sbu2_relay_pins) / sizeof(gpio_t), true); @@ -156,7 +156,12 @@ bool board_v2_get_button(void) { } void board_v2_set_ignition(bool enabled) { - ignition = enabled; + ignition = enabled ? 0xFFU : 0U; + board_v2_set_harness_orientation(harness_orientation); +} + +void board_v2_set_individual_ignition(uint8_t bitmask) { + ignition = bitmask; board_v2_set_harness_orientation(harness_orientation); } @@ -192,7 +197,7 @@ float board_v2_get_channel_power(uint8_t channel) { return ret; } -uint16_t board_v1_get_sbu_mV(uint8_t channel, uint8_t sbu) { +uint16_t board_v2_get_sbu_mV(uint8_t channel, uint8_t sbu) { uint16_t ret = 0U; if ((channel >= 1U) && (channel <= 6U)) { switch(sbu){ @@ -263,6 +268,7 @@ void board_v2_tick(void) {} const board board_v2 = { .board_type = "V2", .has_canfd = true, + .has_sbu_sense = true, .avdd_mV = 3300U, .init = &board_v2_init, .set_led = &board_v2_set_led, @@ -270,9 +276,10 @@ const board board_v2 = { .get_button = &board_v2_get_button, .set_panda_power = &board_v2_set_panda_power, .set_ignition = &board_v2_set_ignition, + .set_individual_ignition = &board_v2_set_individual_ignition, .set_harness_orientation = &board_v2_set_harness_orientation, .set_can_mode = &board_v2_set_can_mode, .enable_can_transciever = &board_v2_enable_can_transciever, .get_channel_power = &board_v2_get_channel_power, - .get_sbu_mV = &board_v1_get_sbu_mV, + .get_sbu_mV = &board_v2_get_sbu_mV, }; \ No newline at end of file diff --git a/board/jungle/main.c b/board/jungle/main.c index 806fbd3f7b..1d39074a94 100644 --- a/board/jungle/main.c +++ b/board/jungle/main.c @@ -49,7 +49,7 @@ void __attribute__ ((noinline)) enable_fpu(void) { } // called at 8Hz -uint8_t loop_counter = 0U; +uint32_t loop_counter = 0U; uint16_t button_press_cnt = 0U; void tick_handler(void) { if (TICK_TIMER->SR != 0) { @@ -93,12 +93,28 @@ void tick_handler(void) { } #ifdef FINAL_PROVISIONING - // ign on for 0.3s, off for 0.2s - const bool ign = (loop_counter % (3+2)) < 3; - if (ign != ignition) { - current_board->set_ignition(ign); + // Ignition blinking + uint8_t ignition_bitmask = 0U; + for (uint8_t i = 0U; i < 6U; i++) { + ignition_bitmask |= ((loop_counter % 12U) < ((uint32_t) i + 2U)) << i; + } + current_board->set_individual_ignition(ignition_bitmask); + + // SBU voltage reporting + if (current_board->has_sbu_sense) { + for (uint8_t i = 0U; i < 6U; i++) { + CANPacket_t pkt = { 0 }; + pkt.data_len_code = 8U; + pkt.addr = 0x100U + i; + *(uint16_t *) &pkt.data[0] = current_board->get_sbu_mV(i + 1U, SBU1); + *(uint16_t *) &pkt.data[2] = current_board->get_sbu_mV(i + 1U, SBU2); + pkt.data[4] = (ignition_bitmask >> i) & 1U; + can_set_checksum(&pkt); + can_send(&pkt, 0U, false); + } } #else + // toggle ignition on button press static bool prev_button_status = false; if (!current_button_status && prev_button_status && button_press_cnt < 10){ current_board->set_ignition(!ignition); From 2a72404ccb1001c7cb092ae7b579c4784f7cecf5 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 4 Aug 2023 17:16:35 -0700 Subject: [PATCH 165/197] Subaru: Add measured steering angle and vehicle speed (#1528) * correct vehicle speed and add measured angle * misra * misra * common test for angle * PR cleanup * use subaru prefix * clean it up a bit * minimize whitespace diff * need float for deg_to_can --- board/safety/safety_subaru.h | 14 ++++++- tests/safety/common.py | 78 +++++++++++++++++------------------- tests/safety/test_subaru.py | 13 ++++-- 3 files changed, 59 insertions(+), 46 deletions(-) diff --git a/board/safety/safety_subaru.h b/board/safety/safety_subaru.h index fcf806ffe0..438a0fba9e 100644 --- a/board/safety/safety_subaru.h +++ b/board/safety/safety_subaru.h @@ -106,6 +106,10 @@ static int subaru_rx_hook(CANPacket_t *to_push) { torque_driver_new = ((GET_BYTES(to_push, 0, 4) >> 16) & 0x7FFU); torque_driver_new = -1 * to_signed(torque_driver_new, 11); update_sample(&torque_driver, torque_driver_new); + + int angle_meas_new = (GET_BYTES(to_push, 4, 2) & 0xFFFFU); + angle_meas_new = ROUND(to_signed(angle_meas_new, 16)); + update_sample(&angle_meas, angle_meas_new); } // enter controls on rising edge of ACC, exit controls on ACC off @@ -116,7 +120,15 @@ static int subaru_rx_hook(CANPacket_t *to_push) { // update vehicle moving with any non-zero wheel speed if ((addr == MSG_SUBARU_Wheel_Speeds) && (bus == alt_main_bus)) { - vehicle_moving = ((GET_BYTES(to_push, 0, 4) >> 12) != 0U) || (GET_BYTES(to_push, 4, 4) != 0U); + uint32_t fr = (GET_BYTES(to_push, 1, 3) >> 4) & 0x1FFFU; + uint32_t rr = (GET_BYTES(to_push, 3, 3) >> 1) & 0x1FFFU; + uint32_t rl = (GET_BYTES(to_push, 4, 3) >> 6) & 0x1FFFU; + uint32_t fl = (GET_BYTES(to_push, 6, 2) >> 3) & 0x1FFFU; + + vehicle_moving = (fr > 0U) || (rr > 0U) || (rl > 0U) || (fl > 0U); + + float speed = (fr + rr + rl + fl) / 4U * 0.057; + update_sample(&vehicle_speed, ROUND(speed * VEHICLE_SPEED_FACTOR)); } if ((addr == MSG_SUBARU_Brake_Status) && (bus == alt_main_bus)) { diff --git a/tests/safety/common.py b/tests/safety/common.py index 1b141d85e6..f926e40988 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -537,11 +537,46 @@ def test_reset_torque_measurements(self): self.assertEqual(self.safety.get_torque_meas_min(), 0) self.assertEqual(self.safety.get_torque_meas_max(), 0) +class MeasurementSafetyTest(PandaSafetyTestBase): + DEG_TO_CAN: float = 1 -class AngleSteeringSafetyTest(PandaSafetyTestBase): + @classmethod + def setUpClass(cls): + if cls.__name__ == "MeasurementSafetyTest": + cls.safety = None + raise unittest.SkipTest + + @abc.abstractmethod + def _angle_meas_msg(self, angle: float): + pass - DEG_TO_CAN: int + @abc.abstractmethod + def _speed_msg(self, speed): + pass + + def common_measurement_test(self, msg_func, min_value, max_value, factor, get_min_func, get_max_func): + for val in np.arange(min_value, max_value, 0.5): + for i in range(6): + self.assertTrue(self._rx(msg_func(val + i * 0.1))) + # assert close by one decimal place + self.assertLessEqual(abs(get_min_func() - val * factor), 1 * abs(factor)) + self.assertLessEqual(abs(get_max_func() - (val + 0.5) * factor), 1 * abs(factor)) + + # reset sample_t by reinitializing the safety mode + self._reset_safety_hooks() + + self.assertEqual(get_min_func(), 0) + self.assertEqual(get_max_func(), 0) + + def test_vehicle_speed_measurements(self): + self.common_measurement_test(self._speed_msg, 0, 80, VEHICLE_SPEED_FACTOR, self.safety.get_vehicle_speed_min, self.safety.get_vehicle_speed_max) + + def test_steering_angle_measurements(self): + self.common_measurement_test(self._angle_meas_msg, -180, 180, self.DEG_TO_CAN, self.safety.get_angle_meas_min, self.safety.get_angle_meas_max) + + +class AngleSteeringSafetyTest(MeasurementSafetyTest): ANGLE_RATE_BP: List[float] ANGLE_RATE_UP: List[float] # windup limit ANGLE_RATE_DOWN: List[float] # unwind limit @@ -552,18 +587,10 @@ def setUpClass(cls): cls.safety = None raise unittest.SkipTest - @abc.abstractmethod - def _speed_msg(self, speed): - pass - @abc.abstractmethod def _angle_cmd_msg(self, angle: float, enabled: bool): pass - @abc.abstractmethod - def _angle_meas_msg(self, angle: float): - pass - def _set_prev_desired_angle(self, t): t = int(t * self.DEG_TO_CAN) self.safety.set_desired_angle_last(t) @@ -640,37 +667,6 @@ def test_angle_cmd_when_disabled(self): should_tx = controls_allowed if steer_control_enabled else angle_cmd == angle_meas self.assertEqual(should_tx, self._tx(self._angle_cmd_msg(angle_cmd, steer_control_enabled))) - def test_reset_angle_measurements(self): - # Tests that the angle measurement sample_t is reset on safety mode init - for a in np.linspace(-90, 90, 6): - self.assertTrue(self._rx(self._angle_meas_msg(a))) - - # reset sample_t by reinitializing the safety mode - self._reset_safety_hooks() - - self.assertEqual(self.safety.get_angle_meas_min(), 0) - self.assertEqual(self.safety.get_angle_meas_max(), 0) - - def test_vehicle_speed_measurements(self): - """ - Tests: - - rx hook correctly parses and rounds the vehicle speed - - sample is reset on safety mode init - """ - for speed in np.arange(0, 80, 0.5): - for i in range(6): - self.assertTrue(self._rx(self._speed_msg(speed + i * 0.1))) - - # assert close by one decimal place - self.assertLessEqual(abs(self.safety.get_vehicle_speed_min() - speed * VEHICLE_SPEED_FACTOR), 1) - self.assertLessEqual(abs(self.safety.get_vehicle_speed_max() - (speed + 0.5) * VEHICLE_SPEED_FACTOR), 1) - - # reset sample_t by reinitializing the safety mode - self._reset_safety_hooks() - - self.assertEqual(self.safety.get_vehicle_speed_min(), 0) - self.assertEqual(self.safety.get_vehicle_speed_max(), 0) - @add_regen_tests class PandaSafetyTest(PandaSafetyTestBase): diff --git a/tests/safety/test_subaru.py b/tests/safety/test_subaru.py index f633ef193d..a1e9e9a96a 100755 --- a/tests/safety/test_subaru.py +++ b/tests/safety/test_subaru.py @@ -3,7 +3,7 @@ from panda import Panda from panda.tests.libpanda import libpanda_py import panda.tests.safety.common as common -from panda.tests.safety.common import CANPackerPanda +from panda.tests.safety.common import CANPackerPanda, MeasurementSafetyTest MSG_SUBARU_Brake_Status = 0x13c @@ -32,7 +32,7 @@ def lkas_tx_msgs(alt_bus): [MSG_SUBARU_ES_Infotainment, SUBARU_MAIN_BUS]] -class TestSubaruSafetyBase(common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest): +class TestSubaruSafetyBase(common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest, MeasurementSafetyTest): FLAGS = 0 STANDSTILL_THRESHOLD = 0 # kph RELAY_MALFUNCTION_ADDR = MSG_SUBARU_ES_LKAS @@ -52,6 +52,8 @@ class TestSubaruSafetyBase(common.PandaSafetyTest, common.DriverTorqueSteeringSa ALT_BUS = SUBARU_MAIN_BUS + DEG_TO_CAN = -46.08 + def setUp(self): self.packer = CANPackerPanda("subaru_global_2017_generated") self.safety = libpanda_py.libpanda @@ -68,10 +70,13 @@ def _torque_driver_msg(self, torque): return self.packer.make_can_msg_panda("Steering_Torque", 0, values) def _speed_msg(self, speed): - # subaru safety doesn't use the scaled value, so undo the scaling - values = {s: speed * 0.057 for s in ["FR", "FL", "RR", "RL"]} + values = {s: speed for s in ["FR", "FL", "RR", "RL"]} return self.packer.make_can_msg_panda("Wheel_Speeds", self.ALT_BUS, values) + def _angle_meas_msg(self, angle): + values = {"Steering_Angle": angle} + return self.packer.make_can_msg_panda("Steering_Torque", 0, values) + def _user_brake_msg(self, brake): values = {"Brake": brake} return self.packer.make_can_msg_panda("Brake_Status", self.ALT_BUS, values) From 7b6806c2706b45bfd94a2449ac68c00580d33c86 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 4 Aug 2023 17:30:10 -0700 Subject: [PATCH 166/197] Safety: Add desired angle to safety helpers (#1554) add desired angle --- tests/libpanda/safety_helpers.h | 4 ++++ tests/libpanda/safety_helpers.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/tests/libpanda/safety_helpers.h b/tests/libpanda/safety_helpers.h index 5409168a2e..22a3c4f13a 100644 --- a/tests/libpanda/safety_helpers.h +++ b/tests/libpanda/safety_helpers.h @@ -145,6 +145,10 @@ void set_desired_angle_last(int t){ desired_angle_last = t; } +int get_desired_angle_last(void){ + return desired_angle_last; +} + int get_angle_meas_min(void){ return angle_meas.min; } diff --git a/tests/libpanda/safety_helpers.py b/tests/libpanda/safety_helpers.py index 252fbf3cb8..234b8ecd02 100644 --- a/tests/libpanda/safety_helpers.py +++ b/tests/libpanda/safety_helpers.py @@ -31,6 +31,7 @@ def setup_safety_helpers(ffi): void set_desired_torque_last(int t); void set_rt_torque_last(int t); void set_desired_angle_last(int t); + int get_desired_angle_last(); int get_angle_meas_min(void); int get_angle_meas_max(void); @@ -80,6 +81,7 @@ def get_torque_driver_max(self) -> int: ... def set_desired_torque_last(self, t: int) -> None: ... def set_rt_torque_last(self, t: int) -> None: ... def set_desired_angle_last(self, t: int) -> None: ... + def get_desired_angle_last(self) -> int: ... def get_angle_meas_min(self) -> int: ... def get_angle_meas_max(self) -> int: ... From 0f479a30a37b8d027b1429975b176d12588687b5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 4 Aug 2023 18:11:29 -0700 Subject: [PATCH 167/197] CI: reflash jungle (#1546) --- tests/ci_reset_hw.py | 8 +++++++- tests/setup_device_ci.sh | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/ci_reset_hw.py b/tests/ci_reset_hw.py index 6a5427318f..0280b1a36f 100755 --- a/tests/ci_reset_hw.py +++ b/tests/ci_reset_hw.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import concurrent.futures -from panda import Panda, PandaDFU +from panda import Panda, PandaDFU, PandaJungle from panda.tests.libs.resetter import Resetter # Reset + flash all CI hardware to get it into a consistent state @@ -38,3 +38,9 @@ def flash(serial): r.cycle_power(delay=0, ports=[1, 2]) r.close() + + # flash jungles + pjs = PandaJungle.list() + for s in pjs: + with PandaJungle(serial=s) as pj: + pj.flash() diff --git a/tests/setup_device_ci.sh b/tests/setup_device_ci.sh index e876b94375..4241311a7f 100755 --- a/tests/setup_device_ci.sh +++ b/tests/setup_device_ci.sh @@ -50,6 +50,10 @@ if [ ! -d "$SOURCE_DIR" ]; then git clone https://github.com/commaai/panda.git $SOURCE_DIR fi +# reflash jungle +cd board/jungle +./flash.py + # setup device/SOM state SOM_ST_IO=49 echo $SOM_ST_IO > /sys/class/gpio/export || true From 6e1d35b03331529829e2075d3491f2693034b6e9 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 4 Aug 2023 21:51:09 -0700 Subject: [PATCH 168/197] more complete jungle reflash (#1557) * more complete jungle reflash * no spi! * mv flash * mv that --- Jenkinsfile | 2 ++ board/jungle/__init__.py | 10 ++++++++++ tests/ci_reset_hw.py | 16 +++++++++++++--- tests/hitl/conftest.py | 3 +++ tests/setup_device_ci.sh | 4 ---- 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 5d74e6e451..d74ace662b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -83,6 +83,7 @@ pipeline { phone_steps("panda-dos", [ ["build", "scons -j4"], ["flash", "cd tests/ && ./reflash_internal_panda.py"], + ["flash jungle", "cd board/jungle && ./flash.py"], ["test", "cd tests/hitl && HW_TYPES=6 pytest --durations=0 [2-7]*.py -k 'not test_send_recv'"], ]) } @@ -94,6 +95,7 @@ pipeline { phone_steps("panda-tres", [ ["build", "scons -j4"], ["flash", "cd tests/ && ./reflash_internal_panda.py"], + ["flash jungle", "cd board/jungle && ./flash.py"], ["test", "cd tests/hitl && HW_TYPES=9 pytest --durations=0 2*.py [5-9]*.py"], ]) } diff --git a/board/jungle/__init__.py b/board/jungle/__init__.py index 62910ceaf0..c6e005a592 100644 --- a/board/jungle/__init__.py +++ b/board/jungle/__init__.py @@ -48,6 +48,10 @@ class PandaJungle(Panda): HARNESS_ORIENTATION_1 = 1 HARNESS_ORIENTATION_2 = 2 + @classmethod + def spi_connect(cls, serial, ignore_version=False): + return None, None, None, None + def flash(self, fn=None, code=None, reconnect=True): if not fn: fn = os.path.join(FW_PATH, self._mcu_type.config.app_fn.replace("panda", "panda_jungle")) @@ -79,6 +83,12 @@ def get_mcu_type(self) -> McuType: return McuType.H7 raise ValueError(f"unknown HW type: {hw_type}") + def up_to_date(self) -> bool: + current = self.get_signature() + fn = os.path.join(FW_PATH, self.get_mcu_type().config.app_fn.replace("panda", "panda_jungle")) + expected = Panda.get_signature_from_firmware(fn) + return (current == expected) + # ******************* health ******************* @ensure_jungle_health_packet_version diff --git a/tests/ci_reset_hw.py b/tests/ci_reset_hw.py index 0280b1a36f..abc16bf9b7 100755 --- a/tests/ci_reset_hw.py +++ b/tests/ci_reset_hw.py @@ -4,6 +4,13 @@ from panda import Panda, PandaDFU, PandaJungle from panda.tests.libs.resetter import Resetter +# all jungles used in the HITL tests +JUNGLES = [ + "058010800f51363038363036", + "23002d000851393038373731" +] + + # Reset + flash all CI hardware to get it into a consistent state # * ports 1-2 are jungles # * port 3 is for the USB hubs @@ -36,11 +43,14 @@ def flash(serial): pf.flash() list(exc.map(flash, pandas, timeout=20)) - r.cycle_power(delay=0, ports=[1, 2]) - r.close() - + print("flashing jungle") # flash jungles pjs = PandaJungle.list() + assert set(pjs) == set(JUNGLES), f"Got different jungles than expected:\ngot {set(pjs)}\nexpected {set(JUNGLES)}" for s in pjs: with PandaJungle(serial=s) as pj: + print(f"- flashing {s}") pj.flash() + + r.cycle_power(delay=0, ports=[1, 2]) + r.close() diff --git a/tests/hitl/conftest.py b/tests/hitl/conftest.py index 08d04ec0bd..22a0b23e85 100644 --- a/tests/hitl/conftest.py +++ b/tests/hitl/conftest.py @@ -75,6 +75,9 @@ def init_jungle(): for bus, speed in BUS_SPEEDS: _panda_jungle.set_can_speed_kbps(bus, speed) + # ensure FW hasn't changed + assert _panda_jungle.up_to_date() + def pytest_configure(config): config.addinivalue_line( diff --git a/tests/setup_device_ci.sh b/tests/setup_device_ci.sh index 4241311a7f..e876b94375 100755 --- a/tests/setup_device_ci.sh +++ b/tests/setup_device_ci.sh @@ -50,10 +50,6 @@ if [ ! -d "$SOURCE_DIR" ]; then git clone https://github.com/commaai/panda.git $SOURCE_DIR fi -# reflash jungle -cd board/jungle -./flash.py - # setup device/SOM state SOM_ST_IO=49 echo $SOM_ST_IO > /sys/class/gpio/export || true From b976cf426aea845c9e2ea76222ef73ca58c008da Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 4 Aug 2023 22:16:49 -0700 Subject: [PATCH 169/197] libpanda: silence build warning --- tests/libpanda/SConscript | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/libpanda/SConscript b/tests/libpanda/SConscript index 7f4871cd5e..c1f8dd683b 100644 --- a/tests/libpanda/SConscript +++ b/tests/libpanda/SConscript @@ -14,6 +14,7 @@ env = Environment( '-fno-builtin', '-std=gnu11', '-Wfatal-errors', + '-Wno-pointer-to-int-cast', ], CPPPATH=[".", "../../board/"], ) From 3e21991ce213ff1a23ffcb61b76fbf7653e5ec57 Mon Sep 17 00:00:00 2001 From: Igor Biletskyy Date: Fri, 4 Aug 2023 22:47:10 -0700 Subject: [PATCH 170/197] jungle: disable transceivers while doing CAN multiplexing (#1555) * init * keep unneeded trans off --------- Co-authored-by: Adeeb Shihadeh --- board/jungle/boards/board_v1.h | 46 ++++++++++++++++++---------------- board/jungle/boards/board_v2.h | 46 ++++++++++++++++++---------------- 2 files changed, 50 insertions(+), 42 deletions(-) diff --git a/board/jungle/boards/board_v1.h b/board/jungle/boards/board_v1.h index 1dc95ad154..d1a6299a4e 100644 --- a/board/jungle/boards/board_v1.h +++ b/board/jungle/boards/board_v1.h @@ -15,7 +15,29 @@ void board_v1_set_led(uint8_t color, bool enabled) { } } +void board_v1_enable_can_transciever(uint8_t transciever, bool enabled) { + switch (transciever){ + case 1U: + set_gpio_output(GPIOC, 1, !enabled); + break; + case 2U: + set_gpio_output(GPIOC, 13, !enabled); + break; + case 3U: + set_gpio_output(GPIOA, 0, !enabled); + break; + case 4U: + set_gpio_output(GPIOB, 10, !enabled); + break; + default: + print("Invalid CAN transciever ("); puth(transciever); print("): enabling failed\n"); + break; + } +} + void board_v1_set_can_mode(uint8_t mode) { + board_v1_enable_can_transciever(2U, false); + board_v1_enable_can_transciever(4U, false); switch (mode) { case CAN_MODE_NORMAL: print("Setting normal CAN mode\n"); @@ -27,6 +49,7 @@ void board_v1_set_can_mode(uint8_t mode) { set_gpio_alternate(GPIOB, 5, GPIO_AF9_CAN2); set_gpio_alternate(GPIOB, 6, GPIO_AF9_CAN2); can_mode = CAN_MODE_NORMAL; + board_v1_enable_can_transciever(2U, true); break; case CAN_MODE_OBD_CAN2: print("Setting OBD CAN mode\n"); @@ -38,6 +61,7 @@ void board_v1_set_can_mode(uint8_t mode) { set_gpio_alternate(GPIOB, 12, GPIO_AF9_CAN2); set_gpio_alternate(GPIOB, 13, GPIO_AF9_CAN2); can_mode = CAN_MODE_OBD_CAN2; + board_v1_enable_can_transciever(4U, true); break; default: print("Tried to set unsupported CAN mode: "); puth(mode); print("\n"); @@ -89,26 +113,6 @@ void board_v1_set_ignition(bool enabled) { board_v1_set_harness_orientation(harness_orientation); } -void board_v1_enable_can_transciever(uint8_t transciever, bool enabled) { - switch (transciever){ - case 1U: - set_gpio_output(GPIOC, 1, !enabled); - break; - case 2U: - set_gpio_output(GPIOC, 13, !enabled); - break; - case 3U: - set_gpio_output(GPIOA, 0, !enabled); - break; - case 4U: - set_gpio_output(GPIOB, 10, !enabled); - break; - default: - print("Invalid CAN transciever ("); puth(transciever); print("): enabling failed\n"); - break; - } -} - float board_v1_get_channel_power(uint8_t channel) { UNUSED(channel); return 0.0f; @@ -167,4 +171,4 @@ const board board_v1 = { .enable_can_transciever = &board_v1_enable_can_transciever, .get_channel_power = &board_v1_get_channel_power, .get_sbu_mV = &board_v1_get_sbu_mV, -}; \ No newline at end of file +}; diff --git a/board/jungle/boards/board_v2.h b/board/jungle/boards/board_v2.h index c6174796a5..73a8a0bed5 100644 --- a/board/jungle/boards/board_v2.h +++ b/board/jungle/boards/board_v2.h @@ -107,7 +107,29 @@ void board_v2_set_harness_orientation(uint8_t orientation) { } } +void board_v2_enable_can_transciever(uint8_t transciever, bool enabled) { + switch (transciever){ + case 1U: + set_gpio_output(GPIOG, 11, !enabled); + break; + case 2U: + set_gpio_output(GPIOB, 3, !enabled); + break; + case 3U: + set_gpio_output(GPIOD, 7, !enabled); + break; + case 4U: + set_gpio_output(GPIOB, 4, !enabled); + break; + default: + print("Invalid CAN transciever ("); puth(transciever); print("): enabling failed\n"); + break; + } +} + void board_v2_set_can_mode(uint8_t mode) { + board_v2_enable_can_transciever(2U, false); + board_v2_enable_can_transciever(4U, false); switch (mode) { case CAN_MODE_NORMAL: // B12,B13: disable normal mode @@ -124,6 +146,7 @@ void board_v2_set_can_mode(uint8_t mode) { set_gpio_pullup(GPIOB, 6, PULL_NONE); set_gpio_alternate(GPIOB, 6, GPIO_AF9_FDCAN2); can_mode = CAN_MODE_NORMAL; + board_v2_enable_can_transciever(2U, true); break; case CAN_MODE_OBD_CAN2: // B5,B6: disable normal mode @@ -139,6 +162,7 @@ void board_v2_set_can_mode(uint8_t mode) { set_gpio_pullup(GPIOB, 13, PULL_NONE); set_gpio_alternate(GPIOB, 13, GPIO_AF9_FDCAN2); can_mode = CAN_MODE_OBD_CAN2; + board_v2_enable_can_transciever(4U, true); break; default: break; @@ -165,26 +189,6 @@ void board_v2_set_individual_ignition(uint8_t bitmask) { board_v2_set_harness_orientation(harness_orientation); } -void board_v2_enable_can_transciever(uint8_t transciever, bool enabled) { - switch (transciever){ - case 1U: - set_gpio_output(GPIOG, 11, !enabled); - break; - case 2U: - set_gpio_output(GPIOB, 3, !enabled); - break; - case 3U: - set_gpio_output(GPIOD, 7, !enabled); - break; - case 4U: - set_gpio_output(GPIOB, 4, !enabled); - break; - default: - print("Invalid CAN transciever ("); puth(transciever); print("): enabling failed\n"); - break; - } -} - float board_v2_get_channel_power(uint8_t channel) { float ret = 0.0f; if ((channel >= 1U) && (channel <= 6U)) { @@ -282,4 +286,4 @@ const board board_v2 = { .enable_can_transciever = &board_v2_enable_can_transciever, .get_channel_power = &board_v2_get_channel_power, .get_sbu_mV = &board_v2_get_sbu_mV, -}; \ No newline at end of file +}; From dc07975446cdc39c6205ec97b9f8093e57717797 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 4 Aug 2023 23:52:18 -0700 Subject: [PATCH 171/197] python: fast reconnect (#1558) --- python/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/python/__init__.py b/python/__init__.py index 637a6b6146..0a7e6e95c8 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -464,23 +464,21 @@ def connected(self) -> bool: def reconnect(self): if self._handle_open: self.close() - time.sleep(1.0) success = False # wait up to 15 seconds - for i in range(0, 15): + for _ in range(0, 15*10): try: self.connect() success = True break except Exception: - logging.debug("reconnecting is taking %d seconds...", i + 1) try: dfu = PandaDFU(self.get_dfu_serial()) dfu.recover() except Exception: pass - time.sleep(1.0) + time.sleep(0.1) if not success: raise Exception("reconnect failed") From 130092e66932715db91d366f56ecb66472ee639d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 5 Aug 2023 14:10:33 -0700 Subject: [PATCH 172/197] python: replace warning with logging --- python/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/__init__.py b/python/__init__.py index 0a7e6e95c8..3037f2de60 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -7,7 +7,6 @@ import hashlib import binascii import datetime -import warnings import logging from functools import wraps from typing import Optional @@ -427,7 +426,7 @@ def usb_list(cls): if len(serial) == 24: ret.append(serial) else: - warnings.warn(f"found device with panda descriptors but invalid serial: {serial}", RuntimeWarning) + logging.warning(f"found device with panda descriptors but invalid serial: {serial}", RuntimeWarning) except Exception: continue except Exception: From d2d207b88d6b0529b40cc26352f14b4c64bb350a Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Sun, 6 Aug 2023 01:04:09 +0200 Subject: [PATCH 173/197] In-circuit debugging (#1560) * openocd script and readme * do not disable SWD on boot --- board/debug/README.md | 26 ++++++++++++++++++++++++++ board/debug/debug_h7.sh | 3 +++ board/stm32h7/stm32h7_config.h | 2 +- 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 board/debug/README.md create mode 100755 board/debug/debug_h7.sh diff --git a/board/debug/README.md b/board/debug/README.md new file mode 100644 index 0000000000..d89eab818a --- /dev/null +++ b/board/debug/README.md @@ -0,0 +1,26 @@ +# In-circuit debugging using openocd and gdb + +## Hardware +Connect an ST-Link V2 programmer to the SWD pins on the board. The pins that need to be connected are: +- GND +- VTref +- SWDIO +- SWCLK +- NRST + +Make sure you're using a genuine one for boards that do not have a 3.3V panda power rail. For example, the tres runs at 1.8V, which is not supported by the clones. + +## Openocd +Install openocd. For Ubuntu 20.04, the one in the package manager works fine: `sudo apt install openocd`. + +To run, use `./debug_f4.sh (TODO)` or `./debug_h7.sh` depending on the panda. + +## GDB +You need `gdb-multiarch`. + +Once openocd is running, you can connect from gdb as follows: +``` +$ gdb-multiarch +(gdb) target ext :3333 +``` +To reset and break, use `monitor reset halt`. diff --git a/board/debug/debug_h7.sh b/board/debug/debug_h7.sh new file mode 100755 index 0000000000..a2bd88434e --- /dev/null +++ b/board/debug/debug_h7.sh @@ -0,0 +1,3 @@ +#!/bin/bash -e + +sudo openocd -f "interface/stlink.cfg" -c "transport select hla_swd" -f "target/stm32h7x.cfg" -c "init" diff --git a/board/stm32h7/stm32h7_config.h b/board/stm32h7/stm32h7_config.h index 0cddad4fa1..f00b5794de 100644 --- a/board/stm32h7/stm32h7_config.h +++ b/board/stm32h7/stm32h7_config.h @@ -98,7 +98,7 @@ separate IRQs for RX and TX. void early_gpio_float(void) { RCC->AHB4ENR = RCC_AHB4ENR_GPIOAEN | RCC_AHB4ENR_GPIOBEN | RCC_AHB4ENR_GPIOCEN | RCC_AHB4ENR_GPIODEN | RCC_AHB4ENR_GPIOEEN | RCC_AHB4ENR_GPIOFEN | RCC_AHB4ENR_GPIOGEN | RCC_AHB4ENR_GPIOHEN; - GPIOA->MODER = 0; GPIOB->MODER = 0; GPIOC->MODER = 0; GPIOD->MODER = 0; GPIOE->MODER = 0; GPIOF->MODER = 0; GPIOG->MODER = 0; GPIOH->MODER = 0; + GPIOA->MODER = 0xAB000000; GPIOB->MODER = 0; GPIOC->MODER = 0; GPIOD->MODER = 0; GPIOE->MODER = 0; GPIOF->MODER = 0; GPIOG->MODER = 0; GPIOH->MODER = 0; GPIOA->ODR = 0; GPIOB->ODR = 0; GPIOC->ODR = 0; GPIOD->ODR = 0; GPIOE->ODR = 0; GPIOF->ODR = 0; GPIOG->ODR = 0; GPIOH->ODR = 0; GPIOA->PUPDR = 0; GPIOB->PUPDR = 0; GPIOC->PUPDR = 0; GPIOD->PUPDR = 0; GPIOE->PUPDR = 0; GPIOF->PUPDR = 0; GPIOG->PUPDR = 0; GPIOH->PUPDR = 0; } From c66b98b2a67441faa4cfcd36c3c9d9f90474cd08 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 6 Aug 2023 12:29:54 -0700 Subject: [PATCH 174/197] finish esp/gps removal (#1559) --- board/boards/black.h | 43 ++------ board/boards/board_declarations.h | 10 +- board/boards/dos.h | 3 +- board/boards/grey.h | 34 +----- board/boards/pedal.h | 8 +- board/boards/red.h | 3 +- board/boards/red_v2.h | 3 +- board/boards/tres.h | 3 +- board/boards/uno.h | 48 +++------ board/boards/unused_funcs.h | 3 +- board/boards/white.h | 37 ++----- board/drivers/uart.h | 23 ++--- board/early_init.h | 6 +- board/main.c | 7 -- board/main_comms.h | 27 ----- board/power_saving.h | 18 +--- board/stm32fx/lluart.h | 154 ++++------------------------ board/stm32fx/peripherals.h | 5 - board/stm32h7/lluart.h | 57 +++-------- python/__init__.py | 7 -- tests/gps_stability_test.py | 165 ------------------------------ tests/hitl/5_gps.py | 21 ---- tests/hitl/{8_spi.py => 5_spi.py} | 0 tests/hitl/conftest.py | 4 +- tests/location_listener.py | 45 -------- 25 files changed, 90 insertions(+), 644 deletions(-) delete mode 100755 tests/gps_stability_test.py delete mode 100644 tests/hitl/5_gps.py rename tests/hitl/{8_spi.py => 5_spi.py} (100%) delete mode 100755 tests/location_listener.py diff --git a/board/boards/black.h b/board/boards/black.h index 216def774f..d6e6bec8e1 100644 --- a/board/boards/black.h +++ b/board/boards/black.h @@ -49,36 +49,10 @@ void black_set_led(uint8_t color, bool enabled) { } } -void black_set_gps_load_switch(bool enabled) { - set_gpio_output(GPIOC, 12, enabled); -} - void black_set_usb_load_switch(bool enabled) { set_gpio_output(GPIOB, 1, !enabled); } -void black_set_gps_mode(uint8_t mode) { - switch (mode) { - case GPS_DISABLED: - // GPS OFF - set_gpio_output(GPIOC, 12, 0); - set_gpio_output(GPIOC, 5, 0); - break; - case GPS_ENABLED: - // GPS ON - set_gpio_output(GPIOC, 12, 1); - set_gpio_output(GPIOC, 5, 1); - break; - case GPS_BOOTMODE: - set_gpio_output(GPIOC, 12, 1); - set_gpio_output(GPIOC, 5, 0); - break; - default: - print("Invalid GPS mode\n"); - break; - } -} - void black_set_can_mode(uint8_t mode){ switch (mode) { case CAN_MODE_NORMAL: @@ -124,8 +98,9 @@ void black_init(void) { set_gpio_mode(GPIOC, 0, MODE_ANALOG); set_gpio_mode(GPIOC, 3, MODE_ANALOG); - // Set default state of GPS - current_board->set_gps_mode(GPS_ENABLED); + // GPS OFF + set_gpio_output(GPIOC, 5, 0); + set_gpio_output(GPIOC, 12, 0); // C10: OBD_SBU1_RELAY (harness relay driving output) // C11: OBD_SBU2_RELAY (harness relay driving output) @@ -136,9 +111,6 @@ void black_init(void) { set_gpio_output(GPIOC, 10, 1); set_gpio_output(GPIOC, 11, 1); - // Turn on GPS load switch. - black_set_gps_load_switch(true); - // Turn on USB load switch. black_set_usb_load_switch(true); @@ -165,6 +137,12 @@ void black_init(void) { } } +void black_init_bootloader(void) { + // GPS OFF + set_gpio_output(GPIOC, 5, 0); + set_gpio_output(GPIOC, 12, 0); +} + const harness_configuration black_harness_config = { .has_harness = true, .GPIO_SBU1 = GPIOC, @@ -183,7 +161,6 @@ const board board_black = { .board_type = "Black", .board_tick = unused_board_tick, .harness_config = &black_harness_config, - .has_gps = true, .has_hw_gmlan = false, .has_obd = true, .has_lin = false, @@ -195,10 +172,10 @@ const board board_black = { .fan_stall_recovery = false, .fan_enable_cooldown_time = 0U, .init = black_init, + .init_bootloader = black_init_bootloader, .enable_can_transceiver = black_enable_can_transceiver, .enable_can_transceivers = black_enable_can_transceivers, .set_led = black_set_led, - .set_gps_mode = black_set_gps_mode, .set_can_mode = black_set_can_mode, .check_ignition = black_check_ignition, .read_current = unused_read_current, diff --git a/board/boards/board_declarations.h b/board/boards/board_declarations.h index c0c6436906..e8b1cdcffa 100644 --- a/board/boards/board_declarations.h +++ b/board/boards/board_declarations.h @@ -1,9 +1,9 @@ // ******************** Prototypes ******************** typedef void (*board_init)(void); +typedef void (*board_init_bootloader)(void); typedef void (*board_enable_can_transceiver)(uint8_t transceiver, bool enabled); typedef void (*board_enable_can_transceivers)(bool enabled); typedef void (*board_set_led)(uint8_t color, bool enabled); -typedef void (*board_set_gps_mode)(uint8_t mode); typedef void (*board_set_can_mode)(uint8_t mode); typedef bool (*board_check_ignition)(void); typedef uint32_t (*board_read_current)(void); @@ -17,7 +17,6 @@ typedef bool (*board_read_som_gpio)(void); struct board { const char *board_type; const harness_configuration *harness_config; - const bool has_gps; const bool has_hw_gmlan; const bool has_obd; const bool has_lin; @@ -29,10 +28,10 @@ struct board { const bool fan_stall_recovery; const uint8_t fan_enable_cooldown_time; board_init init; + board_init_bootloader init_bootloader; board_enable_can_transceiver enable_can_transceiver; board_enable_can_transceivers enable_can_transceivers; board_set_led set_led; - board_set_gps_mode set_gps_mode; board_set_can_mode set_can_mode; board_check_ignition check_ignition; board_read_current read_current; @@ -68,11 +67,6 @@ struct board { #define USB_POWER_CDP 2U #define USB_POWER_DCP 3U -// GPS modes -#define GPS_DISABLED 0U -#define GPS_ENABLED 1U -#define GPS_BOOTMODE 2U - // CAN modes #define CAN_MODE_NORMAL 0U #define CAN_MODE_GMLAN_CAN2 1U diff --git a/board/boards/dos.h b/board/boards/dos.h index 0178ff016f..a5c2e4bcea 100644 --- a/board/boards/dos.h +++ b/board/boards/dos.h @@ -206,7 +206,6 @@ const board board_dos = { .board_type = "Dos", .board_tick = dos_board_tick, .harness_config = &dos_harness_config, - .has_gps = false, .has_hw_gmlan = false, .has_obd = true, .has_lin = false, @@ -222,10 +221,10 @@ const board board_dos = { .fan_stall_recovery = true, .fan_enable_cooldown_time = 3U, .init = dos_init, + .init_bootloader = unused_init_bootloader, .enable_can_transceiver = dos_enable_can_transceiver, .enable_can_transceivers = dos_enable_can_transceivers, .set_led = dos_set_led, - .set_gps_mode = unused_set_gps_mode, .set_can_mode = dos_set_can_mode, .check_ignition = dos_check_ignition, .read_current = unused_read_current, diff --git a/board/boards/grey.h b/board/boards/grey.h index 0adb5e2a1c..a74f894480 100644 --- a/board/boards/grey.h +++ b/board/boards/grey.h @@ -4,40 +4,10 @@ // Most hardware functionality is similar to white panda -void grey_init(void) { - white_grey_common_init(); - - // Set default state of GPS - current_board->set_gps_mode(GPS_ENABLED); -} - -void grey_set_gps_mode(uint8_t mode) { - switch (mode) { - case GPS_DISABLED: - // GPS OFF - set_gpio_output(GPIOC, 14, 0); - set_gpio_output(GPIOC, 5, 0); - break; - case GPS_ENABLED: - // GPS ON - set_gpio_output(GPIOC, 14, 1); - set_gpio_output(GPIOC, 5, 1); - break; - case GPS_BOOTMODE: - set_gpio_output(GPIOC, 14, 1); - set_gpio_output(GPIOC, 5, 0); - break; - default: - print("Invalid ESP/GPS mode\n"); - break; - } -} - const board board_grey = { .board_type = "Grey", .board_tick = unused_board_tick, .harness_config = &white_harness_config, - .has_gps = true, .has_hw_gmlan = true, .has_obd = false, .has_lin = true, @@ -48,11 +18,11 @@ const board board_grey = { .avdd_mV = 3300U, .fan_stall_recovery = false, .fan_enable_cooldown_time = 0U, - .init = grey_init, + .init = white_grey_init, + .init_bootloader = white_grey_init_bootloader, .enable_can_transceiver = white_enable_can_transceiver, .enable_can_transceivers = white_enable_can_transceivers, .set_led = white_set_led, - .set_gps_mode = grey_set_gps_mode, .set_can_mode = white_set_can_mode, .check_ignition = white_check_ignition, .read_current = white_read_current, diff --git a/board/boards/pedal.h b/board/boards/pedal.h index d545a9faf1..f2ae367e5d 100644 --- a/board/boards/pedal.h +++ b/board/boards/pedal.h @@ -30,11 +30,6 @@ void pedal_set_led(uint8_t color, bool enabled) { } } -void pedal_set_gps_mode(uint8_t mode) { - UNUSED(mode); - print("Trying to set ESP/GPS mode on pedal. This is not supported.\n"); -} - void pedal_set_can_mode(uint8_t mode){ switch (mode) { case CAN_MODE_NORMAL: @@ -75,7 +70,6 @@ const board board_pedal = { .board_type = "Pedal", .board_tick = unused_board_tick, .harness_config = &pedal_harness_config, - .has_gps = false, .has_hw_gmlan = false, .has_obd = false, .has_lin = false, @@ -87,10 +81,10 @@ const board board_pedal = { .fan_stall_recovery = false, .fan_enable_cooldown_time = 0U, .init = pedal_init, + .init_bootloader = unused_init_bootloader, .enable_can_transceiver = pedal_enable_can_transceiver, .enable_can_transceivers = pedal_enable_can_transceivers, .set_led = pedal_set_led, - .set_gps_mode = pedal_set_gps_mode, .set_can_mode = pedal_set_can_mode, .check_ignition = pedal_check_ignition, .read_current = unused_read_current, diff --git a/board/boards/red.h b/board/boards/red.h index e7dccc00ed..43e71bdf94 100644 --- a/board/boards/red.h +++ b/board/boards/red.h @@ -170,7 +170,6 @@ const board board_red = { .board_type = "Red", .board_tick = unused_board_tick, .harness_config = &red_harness_config, - .has_gps = false, .has_hw_gmlan = false, .has_obd = true, .has_lin = false, @@ -182,10 +181,10 @@ const board board_red = { .fan_stall_recovery = false, .fan_enable_cooldown_time = 0U, .init = red_init, + .init_bootloader = unused_init_bootloader, .enable_can_transceiver = red_enable_can_transceiver, .enable_can_transceivers = red_enable_can_transceivers, .set_led = red_set_led, - .set_gps_mode = unused_set_gps_mode, .set_can_mode = red_set_can_mode, .check_ignition = red_check_ignition, .read_current = unused_read_current, diff --git a/board/boards/red_v2.h b/board/boards/red_v2.h index d6724d8bb7..4f0a2cb622 100644 --- a/board/boards/red_v2.h +++ b/board/boards/red_v2.h @@ -14,7 +14,6 @@ const board board_red_v2 = { .board_type = "Red_v2", .board_tick = unused_board_tick, .harness_config = &red_chiplet_harness_config, - .has_gps = false, .has_hw_gmlan = false, .has_obd = true, .has_lin = false, @@ -26,10 +25,10 @@ const board board_red_v2 = { .fan_stall_recovery = false, .fan_enable_cooldown_time = 0U, .init = red_panda_v2_init, + .init_bootloader = unused_init_bootloader, .enable_can_transceiver = red_chiplet_enable_can_transceiver, .enable_can_transceivers = red_chiplet_enable_can_transceivers, .set_led = red_set_led, - .set_gps_mode = unused_set_gps_mode, .set_can_mode = red_set_can_mode, .check_ignition = red_check_ignition, .read_current = unused_read_current, diff --git a/board/boards/tres.h b/board/boards/tres.h index 97dac7aa0f..0b9b94a682 100644 --- a/board/boards/tres.h +++ b/board/boards/tres.h @@ -89,7 +89,6 @@ const board board_tres = { .board_type = "Tres", .board_tick = tres_board_tick, .harness_config = &red_chiplet_harness_config, - .has_gps = false, .has_hw_gmlan = false, .has_obd = true, .has_lin = false, @@ -101,10 +100,10 @@ const board board_tres = { .fan_stall_recovery = false, .fan_enable_cooldown_time = 3U, .init = tres_init, + .init_bootloader = unused_init_bootloader, .enable_can_transceiver = red_chiplet_enable_can_transceiver, .enable_can_transceivers = red_chiplet_enable_can_transceivers, .set_led = red_set_led, - .set_gps_mode = unused_set_gps_mode, .set_can_mode = red_set_can_mode, .check_ignition = red_check_ignition, .read_current = unused_read_current, diff --git a/board/boards/uno.h b/board/boards/uno.h index ad773f6e49..e0dc0d2c5f 100644 --- a/board/boards/uno.h +++ b/board/boards/uno.h @@ -51,10 +51,6 @@ void uno_set_led(uint8_t color, bool enabled) { } } -void uno_set_gps_load_switch(bool enabled) { - set_gpio_output(GPIOC, 12, enabled); -} - void uno_set_bootkick(bool enabled){ if (enabled) { set_gpio_output(GPIOB, 14, false); @@ -73,31 +69,6 @@ void uno_set_phone_power(bool enabled){ set_gpio_output(GPIOB, 4, enabled); } -void uno_set_gps_mode(uint8_t mode) { - switch (mode) { - case GPS_DISABLED: - // GPS OFF - set_gpio_output(GPIOB, 1, 0); - set_gpio_output(GPIOC, 5, 0); - uno_set_gps_load_switch(false); - break; - case GPS_ENABLED: - // GPS ON - set_gpio_output(GPIOB, 1, 1); - set_gpio_output(GPIOC, 5, 1); - uno_set_gps_load_switch(true); - break; - case GPS_BOOTMODE: - set_gpio_output(GPIOB, 1, 1); - set_gpio_output(GPIOC, 5, 0); - uno_set_gps_load_switch(true); - break; - default: - print("Invalid ESP/GPS mode\n"); - break; - } -} - void uno_set_can_mode(uint8_t mode){ switch (mode) { case CAN_MODE_NORMAL: @@ -168,8 +139,10 @@ void uno_init(void) { set_gpio_mode(GPIOC, 0, MODE_ANALOG); set_gpio_mode(GPIOC, 3, MODE_ANALOG); - // Set default state of GPS - current_board->set_gps_mode(GPS_ENABLED); + // GPS off + set_gpio_output(GPIOB, 1, 0); + set_gpio_output(GPIOC, 5, 0); + set_gpio_output(GPIOC, 12, 0); // C10: OBD_SBU1_RELAY (harness relay driving output) // C11: OBD_SBU2_RELAY (harness relay driving output) @@ -183,9 +156,6 @@ void uno_init(void) { // C8: FAN PWM aka TIM3_CH3 set_gpio_alternate(GPIOC, 8, GPIO_AF2_TIM3); - // Turn on GPS load switch. - uno_set_gps_load_switch(true); - // Turn on phone regulator uno_set_phone_power(true); @@ -227,6 +197,13 @@ void uno_init(void) { uno_bootkick(); } +void uno_init_bootloader(void) { + // GPS off + set_gpio_output(GPIOB, 1, 0); + set_gpio_output(GPIOC, 5, 0); + set_gpio_output(GPIOC, 12, 0); +} + const harness_configuration uno_harness_config = { .has_harness = true, .GPIO_SBU1 = GPIOC, @@ -245,7 +222,6 @@ const board board_uno = { .board_type = "Uno", .board_tick = uno_board_tick, .harness_config = &uno_harness_config, - .has_gps = true, .has_hw_gmlan = false, .has_obd = true, .has_lin = false, @@ -257,10 +233,10 @@ const board board_uno = { .fan_stall_recovery = false, .fan_enable_cooldown_time = 0U, .init = uno_init, + .init_bootloader = uno_init_bootloader, .enable_can_transceiver = uno_enable_can_transceiver, .enable_can_transceivers = uno_enable_can_transceivers, .set_led = uno_set_led, - .set_gps_mode = uno_set_gps_mode, .set_can_mode = uno_set_can_mode, .check_ignition = uno_check_ignition, .read_current = unused_read_current, diff --git a/board/boards/unused_funcs.h b/board/boards/unused_funcs.h index 4e6214e6a7..24279d2bf9 100644 --- a/board/boards/unused_funcs.h +++ b/board/boards/unused_funcs.h @@ -1,5 +1,4 @@ -void unused_set_gps_mode(uint8_t mode) { - UNUSED(mode); +void unused_init_bootloader(void) { } void unused_set_ir_power(uint8_t percentage) { diff --git a/board/boards/white.h b/board/boards/white.h index 45d860181b..f2dedf0107 100644 --- a/board/boards/white.h +++ b/board/boards/white.h @@ -65,23 +65,6 @@ void white_set_usb_power_mode(uint8_t mode){ } } -void white_set_gps_mode(uint8_t mode) { - switch (mode) { - case GPS_DISABLED: - // ESP OFF - set_gpio_output(GPIOC, 14, 0); - set_gpio_output(GPIOC, 5, 0); - break; - case GPS_BOOTMODE: - set_gpio_output(GPIOC, 14, 1); - set_gpio_output(GPIOC, 5, 0); - break; - default: - print("Invalid ESP/GPS mode\n"); - break; - } -} - void white_set_can_mode(uint8_t mode){ switch (mode) { case CAN_MODE_NORMAL: @@ -150,7 +133,7 @@ bool white_check_ignition(void){ return !get_gpio_input(GPIOA, 1); } -void white_grey_common_init(void) { +void white_grey_init(void) { common_init_gpio(); // C3: current sense @@ -222,13 +205,16 @@ void white_grey_common_init(void) { } else { white_set_usb_power_mode(USB_POWER_CLIENT); } -} -void white_init(void) { - white_grey_common_init(); + // ESP/GPS off + set_gpio_output(GPIOC, 5, 0); + set_gpio_output(GPIOC, 14, 0); +} - // Set ESP off by default - current_board->set_gps_mode(GPS_DISABLED); +void white_grey_init_bootloader(void) { + // ESP/GPS off + set_gpio_output(GPIOC, 5, 0); + set_gpio_output(GPIOC, 14, 0); } const harness_configuration white_harness_config = { @@ -239,7 +225,6 @@ const board board_white = { .board_type = "White", .board_tick = unused_board_tick, .harness_config = &white_harness_config, - .has_gps = false, .has_hw_gmlan = true, .has_obd = false, .has_lin = true, @@ -250,11 +235,11 @@ const board board_white = { .avdd_mV = 3300U, .fan_stall_recovery = false, .fan_enable_cooldown_time = 0U, - .init = white_init, + .init = white_grey_init, + .init_bootloader = white_grey_init_bootloader, .enable_can_transceiver = white_enable_can_transceiver, .enable_can_transceivers = white_enable_can_transceivers, .set_led = white_set_led, - .set_gps_mode = white_set_gps_mode, .set_can_mode = white_set_can_mode, .check_ignition = white_check_ignition, .read_current = white_read_current, diff --git a/board/drivers/uart.h b/board/drivers/uart.h index 824e7d7592..cde8137402 100644 --- a/board/drivers/uart.h +++ b/board/drivers/uart.h @@ -1,8 +1,7 @@ -// IRQs: USART1, USART2, USART3, UART5 +// IRQs: USART2, USART3, UART5 // ***************************** Definitions ***************************** #define FIFO_SIZE_INT 0x400U -#define FIFO_SIZE_DMA 0x1000U typedef struct uart_ring { volatile uint16_t w_ptr_tx; @@ -15,11 +14,10 @@ typedef struct uart_ring { uint32_t rx_fifo_size; USART_TypeDef *uart; void (*callback)(struct uart_ring*); - bool dma_rx; bool overwrite; } uart_ring; -#define UART_BUFFER(x, size_rx, size_tx, uart_ptr, callback_ptr, rx_dma, overwrite_mode) \ +#define UART_BUFFER(x, size_rx, size_tx, uart_ptr, callback_ptr, overwrite_mode) \ uint8_t elems_rx_##x[size_rx]; \ uint8_t elems_tx_##x[size_tx]; \ uart_ring uart_ring_##x = { \ @@ -33,7 +31,6 @@ typedef struct uart_ring { .rx_fifo_size = (size_rx), \ .uart = (uart_ptr), \ .callback = (callback_ptr), \ - .dma_rx = (rx_dma), \ .overwrite = (overwrite_mode) \ }; @@ -44,23 +41,20 @@ void uart_send_break(uart_ring *u); // ******************************** UART buffers ******************************** -// gps = USART1 -UART_BUFFER(gps, FIFO_SIZE_DMA, FIFO_SIZE_INT, USART1, NULL, true, false) - // lin1, K-LINE = UART5 // lin2, L-LINE = USART3 -UART_BUFFER(lin1, FIFO_SIZE_INT, FIFO_SIZE_INT, UART5, NULL, false, false) -UART_BUFFER(lin2, FIFO_SIZE_INT, FIFO_SIZE_INT, USART3, NULL, false, false) +UART_BUFFER(lin1, FIFO_SIZE_INT, FIFO_SIZE_INT, UART5, NULL, false) +UART_BUFFER(lin2, FIFO_SIZE_INT, FIFO_SIZE_INT, USART3, NULL, false) // debug = USART2 -UART_BUFFER(debug, FIFO_SIZE_INT, FIFO_SIZE_INT, USART2, debug_ring_callback, false, true) +UART_BUFFER(debug, FIFO_SIZE_INT, FIFO_SIZE_INT, USART2, debug_ring_callback, true) // SOM debug = UART7 #ifdef STM32H7 - UART_BUFFER(som_debug, FIFO_SIZE_INT, FIFO_SIZE_INT, UART7, NULL, false, true) + UART_BUFFER(som_debug, FIFO_SIZE_INT, FIFO_SIZE_INT, UART7, NULL, true) #else // UART7 is not available on F4 - UART_BUFFER(som_debug, 1U, 1U, NULL, NULL, false, true) + UART_BUFFER(som_debug, 1U, 1U, NULL, NULL, true) #endif uart_ring *get_ring_by_number(int a) { @@ -69,9 +63,6 @@ uart_ring *get_ring_by_number(int a) { case 0: ring = &uart_ring_debug; break; - case 1: - ring = &uart_ring_gps; - break; case 2: ring = &uart_ring_lin1; break; diff --git a/board/early_init.h b/board/early_init.h index 6c60b20fbb..ae652aebce 100644 --- a/board/early_init.h +++ b/board/early_init.h @@ -51,9 +51,9 @@ void early_initialization(void) { detect_board_type(); if (enter_bootloader_mode == ENTER_BOOTLOADER_MAGIC) { - #ifdef PANDA - current_board->set_gps_mode(GPS_DISABLED); - #endif + #ifdef PANDA + current_board->init_bootloader(); + #endif current_board->set_led(LED_GREEN, 1); jump_to_bootloader(); } diff --git a/board/main.c b/board/main.c index 72da6b84f3..56abbed527 100644 --- a/board/main.c +++ b/board/main.c @@ -371,13 +371,6 @@ int main(void) { log("main start"); - if (current_board->has_gps) { - uart_init(&uart_ring_gps, 9600); - } else { - // enable ESP uart - uart_init(&uart_ring_gps, 115200); - } - if (current_board->has_lin) { // enable LIN uart_init(&uart_ring_lin1, 10400); diff --git a/board/main_comms.h b/board/main_comms.h index 00d5e7a0b1..faa18b849a 100644 --- a/board/main_comms.h +++ b/board/main_comms.h @@ -262,28 +262,6 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { case 0xd8: NVIC_SystemReset(); break; - // **** 0xd9: set ESP power - case 0xd9: - if (req->param1 == 1U) { - current_board->set_gps_mode(GPS_ENABLED); - } else if (req->param1 == 2U) { - current_board->set_gps_mode(GPS_BOOTMODE); - } else { - current_board->set_gps_mode(GPS_DISABLED); - } - break; - // **** 0xda: reset ESP, with optional boot mode - case 0xda: - current_board->set_gps_mode(GPS_DISABLED); - delay(1000000); - if (req->param1 == 1U) { - current_board->set_gps_mode(GPS_BOOTMODE); - } else { - current_board->set_gps_mode(GPS_ENABLED); - } - delay(1000000); - current_board->set_gps_mode(GPS_ENABLED); - break; // **** 0xdb: set GMLAN (white/grey) or OBD CAN (black) multiplexing mode case 0xdb: if(current_board->has_obd){ @@ -343,11 +321,6 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { break; } - // TODO: Remove this again and fix boardd code to hande the message bursts instead of single chars - if (ur == &uart_ring_gps) { - dma_pointer_handler(ur, DMA2_Stream5->NDTR); - } - // read while ((resp_len < MIN(req->length, USBPACKET_MAX_SIZE)) && getc(ur, (char*)&resp[resp_len])) { diff --git a/board/power_saving.h b/board/power_saving.h index 64baad0320..2b7d7c9872 100644 --- a/board/power_saving.h +++ b/board/power_saving.h @@ -13,11 +13,7 @@ void set_power_save_state(int state) { bool enable = false; if (state == POWER_SAVE_STATUS_ENABLED) { print("enable power savings\n"); - if (current_board->has_gps) { - const char UBLOX_SLEEP_MSG[] = "\xb5\x62\x06\x04\x04\x00\x01\x00\x08\x00\x17\x78"; - uart_ring *ur = get_ring_by_number(1); - for (unsigned int i = 0; i < sizeof(UBLOX_SLEEP_MSG) - 1U; i++) while (!putc(ur, UBLOX_SLEEP_MSG[i])); - } + // Disable CAN interrupts if (harness.status == HARNESS_STATUS_FLIPPED) { llcan_irq_disable(cans[0]); @@ -27,11 +23,6 @@ void set_power_save_state(int state) { llcan_irq_disable(cans[1]); } else { print("disable power savings\n"); - if (current_board->has_gps) { - const char UBLOX_WAKE_MSG[] = "\xb5\x62\x06\x04\x04\x00\x01\x00\x09\x00\x18\x7a"; - uart_ring *ur = get_ring_by_number(1); - for (unsigned int i = 0; i < sizeof(UBLOX_WAKE_MSG) - 1U; i++) while (!putc(ur, UBLOX_WAKE_MSG[i])); - } if (harness.status == HARNESS_STATUS_FLIPPED) { llcan_irq_enable(cans[0]); @@ -45,13 +36,6 @@ void set_power_save_state(int state) { current_board->enable_can_transceivers(enable); - // Switch EPS/GPS - if (enable) { - current_board->set_gps_mode(GPS_ENABLED); - } else { - current_board->set_gps_mode(GPS_DISABLED); - } - if(current_board->has_hw_gmlan){ // turn on GMLAN set_gpio_output(GPIOB, 14, enable); diff --git a/board/stm32fx/lluart.h b/board/stm32fx/lluart.h index 4cdd337210..aed99a77f0 100644 --- a/board/stm32fx/lluart.h +++ b/board/stm32fx/lluart.h @@ -21,31 +21,28 @@ void uart_tx_ring(uart_ring *q){ } void uart_rx_ring(uart_ring *q){ - // Do not read out directly if DMA enabled - if (q->dma_rx == false) { - ENTER_CRITICAL(); + ENTER_CRITICAL(); - // Read out RX buffer - uint8_t c = q->uart->DR; // This read after reading SR clears a bunch of interrupts + // Read out RX buffer + uint8_t c = q->uart->DR; // This read after reading SR clears a bunch of interrupts - uint16_t next_w_ptr = (q->w_ptr_rx + 1U) % q->rx_fifo_size; + uint16_t next_w_ptr = (q->w_ptr_rx + 1U) % q->rx_fifo_size; - if ((next_w_ptr == q->r_ptr_rx) && q->overwrite) { - // overwrite mode: drop oldest byte - q->r_ptr_rx = (q->r_ptr_rx + 1U) % q->rx_fifo_size; - } + if ((next_w_ptr == q->r_ptr_rx) && q->overwrite) { + // overwrite mode: drop oldest byte + q->r_ptr_rx = (q->r_ptr_rx + 1U) % q->rx_fifo_size; + } - // Do not overwrite buffer data - if (next_w_ptr != q->r_ptr_rx) { - q->elems_rx[q->w_ptr_rx] = c; - q->w_ptr_rx = next_w_ptr; - if (q->callback != NULL) { - q->callback(q); - } + // Do not overwrite buffer data + if (next_w_ptr != q->r_ptr_rx) { + q->elems_rx[q->w_ptr_rx] = c; + q->w_ptr_rx = next_w_ptr; + if (q->callback != NULL) { + q->callback(q); } - - EXIT_CRITICAL(); } + + EXIT_CRITICAL(); } void uart_send_break(uart_ring *u) { @@ -53,34 +50,6 @@ void uart_send_break(uart_ring *u) { u->uart->CR1 |= USART_CR1_SBK; } -// This function should be called on: -// * Half-transfer DMA interrupt -// * Full-transfer DMA interrupt -// * UART IDLE detection -uint32_t prev_w_index = 0; -void dma_pointer_handler(uart_ring *q, uint32_t dma_ndtr) { - ENTER_CRITICAL(); - uint32_t w_index = (q->rx_fifo_size - dma_ndtr); - - // Check for new data - if (w_index != prev_w_index){ - // Check for overflow - if ( - ((prev_w_index < q->r_ptr_rx) && (q->r_ptr_rx <= w_index)) || // No rollover - ((w_index < prev_w_index) && ((q->r_ptr_rx <= w_index) || (prev_w_index < q->r_ptr_rx))) // Rollover - ){ - // We lost data. Set the new read pointer to the oldest byte still available - q->r_ptr_rx = (w_index + 1U) % q->rx_fifo_size; - } - - // Set write pointer - q->w_ptr_rx = w_index; - } - - prev_w_index = w_index; - EXIT_CRITICAL(); -} - // This read after reading SR clears all error interrupts. We don't want compiler warnings, nor optimizations #define UART_READ_DR(uart) volatile uint8_t t = (uart)->DR; UNUSED(t); @@ -106,103 +75,28 @@ void uart_interrupt_handler(uart_ring *q) { // Send if necessary uart_tx_ring(q); - // Run DMA pointer handler if the line is idle - if(q->dma_rx && (status & USART_SR_IDLE)){ - // Reset IDLE flag - UART_READ_DR(q->uart) - - if(q == &uart_ring_gps){ - dma_pointer_handler(&uart_ring_gps, DMA2_Stream5->NDTR); - } else { - #ifdef DEBUG_UART - print("No IDLE dma_pointer_handler implemented for this UART."); - #endif - } - } - EXIT_CRITICAL(); } -void USART1_IRQ_Handler(void) { uart_interrupt_handler(&uart_ring_gps); } void USART2_IRQ_Handler(void) { uart_interrupt_handler(&uart_ring_debug); } void USART3_IRQ_Handler(void) { uart_interrupt_handler(&uart_ring_lin2); } void UART5_IRQ_Handler(void) { uart_interrupt_handler(&uart_ring_lin1); } -void DMA2_Stream5_IRQ_Handler(void) { - ENTER_CRITICAL(); - - // Handle errors - if((DMA2->HISR & DMA_HISR_TEIF5) || (DMA2->HISR & DMA_HISR_DMEIF5) || (DMA2->HISR & DMA_HISR_FEIF5)){ - #ifdef DEBUG_UART - print("Encountered UART DMA error. Clearing and restarting DMA...\n"); - #endif - - // Clear flags - DMA2->HIFCR = DMA_HIFCR_CTEIF5 | DMA_HIFCR_CDMEIF5 | DMA_HIFCR_CFEIF5; - - // Re-enable the DMA if necessary - DMA2_Stream5->CR |= DMA_SxCR_EN; - } - - // Re-calculate write pointer and reset flags - dma_pointer_handler(&uart_ring_gps, DMA2_Stream5->NDTR); - DMA2->HIFCR = DMA_HIFCR_CTCIF5 | DMA_HIFCR_CHTIF5; - - EXIT_CRITICAL(); -} - // ***************************** Hardware setup ***************************** -void dma_rx_init(uart_ring *q) { - // Initialization is UART-dependent - if(q == &uart_ring_gps){ - // DMA2, stream 5, channel 4 - - // Disable FIFO mode (enable direct) - DMA2_Stream5->FCR &= ~DMA_SxFCR_DMDIS; - - // Setup addresses - DMA2_Stream5->PAR = (uint32_t)&(USART1->DR); // Source - DMA2_Stream5->M0AR = (uint32_t)q->elems_rx; // Destination - DMA2_Stream5->NDTR = q->rx_fifo_size; // Number of bytes to copy - - // Circular, Increment memory, byte size, periph -> memory, enable - // Transfer complete, half transfer, transfer error and direct mode error interrupt enable - DMA2_Stream5->CR = DMA_SxCR_CHSEL_2 | DMA_SxCR_MINC | DMA_SxCR_CIRC | DMA_SxCR_HTIE | DMA_SxCR_TCIE | DMA_SxCR_TEIE | DMA_SxCR_DMEIE | DMA_SxCR_EN; - - // Enable DMA receiver in UART - q->uart->CR3 |= USART_CR3_DMAR; - - // Enable UART IDLE interrupt - q->uart->CR1 |= USART_CR1_IDLEIE; - - // Enable interrupt - NVIC_EnableIRQ(DMA2_Stream5_IRQn); - } else { - print("Tried to initialize RX DMA for an unsupported UART\n"); - } -} - #define __DIV(_PCLK_, _BAUD_) (((_PCLK_) * 25U) / (4U * (_BAUD_))) #define __DIVMANT(_PCLK_, _BAUD_) (__DIV((_PCLK_), (_BAUD_)) / 100U) #define __DIVFRAQ(_PCLK_, _BAUD_) ((((__DIV((_PCLK_), (_BAUD_)) - (__DIVMANT((_PCLK_), (_BAUD_)) * 100U)) * 16U) + 50U) / 100U) #define __USART_BRR(_PCLK_, _BAUD_) ((__DIVMANT((_PCLK_), (_BAUD_)) << 4) | (__DIVFRAQ((_PCLK_), (_BAUD_)) & 0x0FU)) void uart_set_baud(USART_TypeDef *u, unsigned int baud) { - if (u == USART1) { - // USART1 is on APB2 - u->BRR = __USART_BRR(48000000U, baud); - } else { - u->BRR = __USART_BRR(24000000U, baud); - } + u->BRR = __USART_BRR(24000000U, baud); } void uart_init(uart_ring *q, int baud) { if(q->uart != NULL){ // Register interrupts (max data rate: 115200 baud) - if(q->uart == USART1){ - REGISTER_INTERRUPT(USART1_IRQn, USART1_IRQ_Handler, 150000U, FAULT_INTERRUPT_RATE_UART_1) - } else if (q->uart == USART2){ + if (q->uart == USART2){ REGISTER_INTERRUPT(USART2_IRQn, USART2_IRQ_Handler, 150000U, FAULT_INTERRUPT_RATE_UART_2) } else if (q->uart == USART3){ REGISTER_INTERRUPT(USART3_IRQn, USART3_IRQ_Handler, 150000U, FAULT_INTERRUPT_RATE_UART_3) @@ -211,9 +105,6 @@ void uart_init(uart_ring *q, int baud) { } else { // UART not used. Skip registering interrupts } - if(q->dma_rx){ - REGISTER_INTERRUPT(DMA2_Stream5_IRQn, DMA2_Stream5_IRQ_Handler, 100U, FAULT_INTERRUPT_RATE_UART_DMA) // Called twice per buffer - } // Set baud and enable peripheral with TX and RX mode uart_set_baud(q->uart, baud); @@ -223,9 +114,7 @@ void uart_init(uart_ring *q, int baud) { } // Enable UART interrupts - if(q->uart == USART1){ - NVIC_EnableIRQ(USART1_IRQn); - } else if (q->uart == USART2){ + if (q->uart == USART2){ NVIC_EnableIRQ(USART2_IRQn); } else if (q->uart == USART3){ NVIC_EnableIRQ(USART3_IRQn); @@ -234,10 +123,5 @@ void uart_init(uart_ring *q, int baud) { } else { // UART not used. Skip enabling interrupts } - - // Initialise RX DMA if used - if(q->dma_rx){ - dma_rx_init(q); - } } } diff --git a/board/stm32fx/peripherals.h b/board/stm32fx/peripherals.h index d910886d3c..33a1f6ffef 100644 --- a/board/stm32fx/peripherals.h +++ b/board/stm32fx/peripherals.h @@ -36,10 +36,6 @@ void common_init_gpio(void) { gpio_usb_init(); - // A9,A10: USART 1 for talking to the GPS - set_gpio_alternate(GPIOA, 9, GPIO_AF7_USART1); - set_gpio_alternate(GPIOA, 10, GPIO_AF7_USART1); - // B8,B9: CAN 1 #ifdef STM32F4 set_gpio_alternate(GPIOB, 8, GPIO_AF8_CAN1); @@ -84,7 +80,6 @@ void peripherals_init(void) { RCC->APB1ENR |= RCC_APB1ENR_TIM6EN; // interrupt timer RCC->APB1ENR |= RCC_APB1ENR_TIM12EN; // gmlan_alt RCC->APB1ENR |= RCC_APB1ENR_PWREN; // for RTC config - RCC->APB2ENR |= RCC_APB2ENR_USART1EN; RCC->AHB2ENR |= RCC_AHB2ENR_OTGFSEN; RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; // clock source timer RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; diff --git a/board/stm32h7/lluart.h b/board/stm32h7/lluart.h index 6d1a363e27..640ea18d13 100644 --- a/board/stm32h7/lluart.h +++ b/board/stm32h7/lluart.h @@ -1,7 +1,3 @@ - -void dma_pointer_handler(uart_ring *q, uint32_t dma_ndtr) { UNUSED(q); UNUSED(dma_ndtr); } -void dma_rx_init(uart_ring *q) { UNUSED(q); } - #define __DIV(_PCLK_, _BAUD_) (((_PCLK_) * 25U) / (4U * (_BAUD_))) #define __DIVMANT(_PCLK_, _BAUD_) (__DIV((_PCLK_), (_BAUD_)) / 100U) #define __DIVFRAQ(_PCLK_, _BAUD_) ((((__DIV((_PCLK_), (_BAUD_)) - (__DIVMANT((_PCLK_), (_BAUD_)) * 100U)) * 16U) + 50U) / 100U) @@ -9,30 +5,28 @@ void dma_rx_init(uart_ring *q) { UNUSED(q); } void uart_rx_ring(uart_ring *q){ // Do not read out directly if DMA enabled - if (q->dma_rx == false) { - ENTER_CRITICAL(); + ENTER_CRITICAL(); - // Read out RX buffer - uint8_t c = q->uart->RDR; // This read after reading SR clears a bunch of interrupts + // Read out RX buffer + uint8_t c = q->uart->RDR; // This read after reading SR clears a bunch of interrupts - uint16_t next_w_ptr = (q->w_ptr_rx + 1U) % q->rx_fifo_size; + uint16_t next_w_ptr = (q->w_ptr_rx + 1U) % q->rx_fifo_size; - if ((next_w_ptr == q->r_ptr_rx) && q->overwrite) { - // overwrite mode: drop oldest byte - q->r_ptr_rx = (q->r_ptr_rx + 1U) % q->rx_fifo_size; - } + if ((next_w_ptr == q->r_ptr_rx) && q->overwrite) { + // overwrite mode: drop oldest byte + q->r_ptr_rx = (q->r_ptr_rx + 1U) % q->rx_fifo_size; + } - // Do not overwrite buffer data - if (next_w_ptr != q->r_ptr_rx) { - q->elems_rx[q->w_ptr_rx] = c; - q->w_ptr_rx = next_w_ptr; - if (q->callback != NULL) { - q->callback(q); - } + // Do not overwrite buffer data + if (next_w_ptr != q->r_ptr_rx) { + q->elems_rx[q->w_ptr_rx] = c; + q->w_ptr_rx = next_w_ptr; + if (q->callback != NULL) { + q->callback(q); } - - EXIT_CRITICAL(); } + + EXIT_CRITICAL(); } void uart_tx_ring(uart_ring *q){ @@ -96,16 +90,6 @@ void uart_interrupt_handler(uart_ring *q) { // Send if necessary uart_tx_ring(q); - // Run DMA pointer handler if the line is idle - if(q->dma_rx && (status & USART_ISR_IDLE)){ - // Reset IDLE flag - UART_READ_RDR(q->uart) - - #ifdef DEBUG_UART - print("No IDLE dma_pointer_handler implemented for this UART."); - #endif - } - EXIT_CRITICAL(); } @@ -115,10 +99,6 @@ void uart_init(uart_ring *q, int baud) { if (q->uart == UART7) { REGISTER_INTERRUPT(UART7_IRQn, UART7_IRQ_Handler, 150000U, FAULT_INTERRUPT_RATE_UART_7) - if (q->dma_rx) { - // TODO - } - uart_set_baud(q->uart, baud); q->uart->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; @@ -127,10 +107,5 @@ void uart_init(uart_ring *q, int baud) { // Enable UART interrupts NVIC_EnableIRQ(UART7_IRQn); - - // Initialise RX DMA if used - if (q->dma_rx) { - dma_rx_init(q); - } } } diff --git a/python/__init__.py b/python/__init__.py index 3037f2de60..6c714ca3b5 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -770,13 +770,6 @@ def set_power_save(self, power_save_enabled=0): def enable_deepsleep(self): self._handle.controlWrite(Panda.REQUEST_OUT, 0xfb, 0, 0, b'') - def set_esp_power(self, on): - self._handle.controlWrite(Panda.REQUEST_OUT, 0xd9, int(on), 0, b'') - - def esp_reset(self, bootmode=0): - self._handle.controlWrite(Panda.REQUEST_OUT, 0xda, int(bootmode), 0, b'') - time.sleep(0.2) - def set_safety_mode(self, mode=SAFETY_SILENT, param=0): self._handle.controlWrite(Panda.REQUEST_OUT, 0xdc, mode, param, b'') diff --git a/tests/gps_stability_test.py b/tests/gps_stability_test.py deleted file mode 100755 index f8cb13caab..0000000000 --- a/tests/gps_stability_test.py +++ /dev/null @@ -1,165 +0,0 @@ -#!/usr/bin/env python3 -# flake8: noqa - -import os -import sys -import time -import random -import threading - -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda, PandaSerial # noqa: E402 - -INIT_GPS_BAUD = 9600 -GPS_BAUD = 460800 - -def connect(): - pandas = Panda.list() - print(pandas) - - # make sure two pandas are connected - if len(pandas) != 2: - print("Connect white and grey/black panda to run this test!") - assert False - - # connect - pandas[0] = Panda(pandas[0]) - pandas[1] = Panda(pandas[1]) - - white_panda = None - gps_panda = None - - # find out which one is white (for spamming the CAN buses) - if pandas[0].get_type() == Panda.HW_TYPE_WHITE_PANDA and pandas[1].get_type() != Panda.HW_TYPE_WHITE_PANDA: - white_panda = pandas[0] - gps_panda = pandas[1] - elif pandas[0].get_type() != Panda.HW_TYPE_WHITE_PANDA and pandas[1].get_type() == Panda.HW_TYPE_WHITE_PANDA: - white_panda = pandas[1] - gps_panda = pandas[0] - else: - print("Connect white and grey/black panda to run this test!") - assert False - return white_panda, gps_panda - -def spam_buses_thread(panda): - try: - panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT) - while True: - at = random.randint(1, 2000) - st = (b"test" + os.urandom(10))[0:8] - bus = random.randint(0, 2) - panda.can_send(at, st, bus) - except Exception as e: - print(e) - -def read_can_thread(panda): - try: - while True: - panda.can_recv() - except Exception as e: - print(e) - -def init_gps(panda): - def add_nmea_checksum(msg): - d = msg[1:] - cs = 0 - for i in d: - cs ^= ord(i) - return msg + "*%02X" % cs - - ser = PandaSerial(panda, 1, INIT_GPS_BAUD) - - # Power cycle the gps by toggling reset - print("Resetting GPS") - panda.set_esp_power(0) - time.sleep(0.5) - panda.set_esp_power(1) - time.sleep(0.5) - - # Upping baud rate - print("Upping GPS baud rate") - msg = str.encode(add_nmea_checksum("$PUBX,41,1,0007,0003,%d,0" % GPS_BAUD) + "\r\n") - ser.write(msg) - time.sleep(1) # needs a wait for it to actually send - - # Reconnecting with the correct baud - ser = PandaSerial(panda, 1, GPS_BAUD) - - # Sending all config messages boardd sends - print("Sending config") - ser.write(b"\xB5\x62\x06\x00\x14\x00\x03\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x1E\x7F") - ser.write(b"\xB5\x62\x06\x3E\x00\x00\x44\xD2") - ser.write(b"\xB5\x62\x06\x00\x14\x00\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x19\x35") - ser.write(b"\xB5\x62\x06\x00\x14\x00\x01\x00\x00\x00\xC0\x08\x00\x00\x00\x08\x07\x00\x01\x00\x01\x00\x00\x00\x00\x00\xF4\x80") - ser.write(b"\xB5\x62\x06\x00\x14\x00\x04\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1D\x85") - ser.write(b"\xB5\x62\x06\x00\x00\x00\x06\x18") - ser.write(b"\xB5\x62\x06\x00\x01\x00\x01\x08\x22") - ser.write(b"\xB5\x62\x06\x00\x01\x00\x02\x09\x23") - ser.write(b"\xB5\x62\x06\x00\x01\x00\x03\x0A\x24") - ser.write(b"\xB5\x62\x06\x08\x06\x00\x64\x00\x01\x00\x00\x00\x79\x10") - ser.write(b"\xB5\x62\x06\x24\x24\x00\x05\x00\x04\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x5A\x63") - ser.write(b"\xB5\x62\x06\x1E\x14\x00\x00\x00\x00\x00\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3C\x37") - ser.write(b"\xB5\x62\x06\x24\x00\x00\x2A\x84") - ser.write(b"\xB5\x62\x06\x23\x00\x00\x29\x81") - ser.write(b"\xB5\x62\x06\x1E\x00\x00\x24\x72") - ser.write(b"\xB5\x62\x06\x01\x03\x00\x01\x07\x01\x13\x51") - ser.write(b"\xB5\x62\x06\x01\x03\x00\x02\x15\x01\x22\x70") - ser.write(b"\xB5\x62\x06\x01\x03\x00\x02\x13\x01\x20\x6C") - - print("Initialized GPS") - -received_messages = 0 -received_bytes = 0 -send_something = False -def gps_read_thread(panda): - global received_messages, received_bytes, send_something - ser = PandaSerial(panda, 1, GPS_BAUD) - while True: - ret = ser.read(1024) - time.sleep(0.001) - if len(ret): - received_messages += 1 - received_bytes += len(ret) - if send_something: - ser.write("test") - send_something = False - - -CHECK_PERIOD = 5 -MIN_BYTES = 10000 -MAX_BYTES = 50000 - -min_failures = 0 -max_failures = 0 - -if __name__ == "__main__": - white_panda, gps_panda = connect() - - # Start spamming the CAN buses with the white panda. Also read the messages to add load on the GPS panda - threading.Thread(target=spam_buses_thread, args=(white_panda,)).start() - threading.Thread(target=read_can_thread, args=(gps_panda,)).start() - - # Start GPS checking - init_gps(gps_panda) - - read_thread = threading.Thread(target=gps_read_thread, args=(gps_panda,)) - read_thread.start() - while True: - time.sleep(CHECK_PERIOD) - if(received_bytes < MIN_BYTES): - print("Panda is not sending out enough data! Got " + str(received_messages) + " (" + str(received_bytes) + "B) in the last " + str(CHECK_PERIOD) + " seconds") - send_something = True - min_failures += 1 - elif(received_bytes > MAX_BYTES): - print("Panda is not sending out too much data! Got " + str(received_messages) + " (" + str(received_bytes) + "B) in the last " + str(CHECK_PERIOD) + " seconds") - print("Probably not on the right baud rate, got reset somehow? Resetting...") - max_failures += 1 - init_gps(gps_panda) - else: - print("Got " + str(received_messages) + " (" + str(received_bytes) + "B) messages in the last " + str(CHECK_PERIOD) + " seconds.") - if(min_failures > 0): - print("Total min failures: ", min_failures) - if(max_failures > 0): - print("Total max failures: ", max_failures) - received_messages = 0 - received_bytes = 0 diff --git a/tests/hitl/5_gps.py b/tests/hitl/5_gps.py deleted file mode 100644 index 3cead333ed..0000000000 --- a/tests/hitl/5_gps.py +++ /dev/null @@ -1,21 +0,0 @@ -import time -import pytest - -from panda import PandaSerial -from panda.tests.hitl.conftest import PandaGroup - - -@pytest.mark.test_panda_types(PandaGroup.GPS) -def test_gps_version(p): - serial = PandaSerial(p, 1, 9600) - # Reset and check twice to make sure the enabling works - for _ in range(2): - # Reset GPS - p.set_esp_power(0) - time.sleep(2) - p.set_esp_power(1) - time.sleep(1) - - # Read startup message and check if version is contained - dat = serial.read(0x1000) # Read one full panda DMA buffer. This should include the startup message - assert b'HPG 1.40ROV' in dat \ No newline at end of file diff --git a/tests/hitl/8_spi.py b/tests/hitl/5_spi.py similarity index 100% rename from tests/hitl/8_spi.py rename to tests/hitl/5_spi.py diff --git a/tests/hitl/conftest.py b/tests/hitl/conftest.py index 22a0b23e85..50ef2a7134 100644 --- a/tests/hitl/conftest.py +++ b/tests/hitl/conftest.py @@ -25,7 +25,6 @@ class PandaGroup: H7 = (Panda.HW_TYPE_RED_PANDA, Panda.HW_TYPE_RED_PANDA_V2, Panda.HW_TYPE_TRES) GEN2 = (Panda.HW_TYPE_BLACK_PANDA, Panda.HW_TYPE_UNO, Panda.HW_TYPE_DOS) + H7 - GPS = (Panda.HW_TYPE_BLACK_PANDA, Panda.HW_TYPE_UNO) GMLAN = (Panda.HW_TYPE_WHITE_PANDA, Panda.HW_TYPE_GREY_PANDA) TESTED = (Panda.HW_TYPE_WHITE_PANDA, Panda.HW_TYPE_BLACK_PANDA, Panda.HW_TYPE_RED_PANDA, Panda.HW_TYPE_RED_PANDA_V2, Panda.HW_TYPE_UNO) @@ -33,7 +32,7 @@ class PandaGroup: if PARTIAL_TESTS: # minimal set of pandas to get most of our coverage # * red panda covers GEN2, STM32H7 - # * black panda covers STM32F4, GEN2, and GPS + # * black panda covers STM32F4, and GEN2 PandaGroup.TESTED = (Panda.HW_TYPE_BLACK_PANDA, Panda.HW_TYPE_RED_PANDA) # type: ignore elif HW_TYPES is not None: PandaGroup.TESTED = [bytes([int(x), ]) for x in HW_TYPES.strip().split(",")] # type: ignore @@ -222,7 +221,6 @@ def cnnct(s): p.set_can_loopback(False) p.set_gmlan(None) - p.set_esp_power(False) p.set_power_save(False) for bus, speed in BUS_SPEEDS: p.set_can_speed_kbps(bus, speed) diff --git a/tests/location_listener.py b/tests/location_listener.py deleted file mode 100755 index d997327ead..0000000000 --- a/tests/location_listener.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python3 -import os -import time -import sys - -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda, PandaSerial # noqa: 402 - -def add_nmea_checksum(msg): - d = msg[1:] - cs = 0 - for i in d: - cs ^= ord(i) - return msg + "*%02X" % cs - -if __name__ == "__main__": - panda = Panda() - ser = PandaSerial(panda, 1, 9600) - - # power cycle by toggling reset - print("resetting") - panda.set_esp_power(0) - time.sleep(0.5) - panda.set_esp_power(1) - time.sleep(0.5) - print("done") - print(ser.read(1024)) - - # upping baud rate - baudrate = 460800 - - print("upping baud rate") - msg = str.encode(add_nmea_checksum("$PUBX,41,1,0007,0003,%d,0" % baudrate) + "\r\n") - print(msg) - ser.write(msg) - time.sleep(0.1) # needs a wait for it to actually send - - # new panda serial - ser = PandaSerial(panda, 1, baudrate) - - while True: - ret = ser.read(1024) - if len(ret) > 0: - sys.stdout.write(ret.decode('ascii', 'ignore')) - sys.stdout.flush() From 33a95d6bea32928c9ee553493999e3e0c3fd19e9 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 6 Aug 2023 13:47:37 -0700 Subject: [PATCH 175/197] pedal: fix warning on invalid serial (#1563) * pedal: fix warning on invalid serial * fix * fix: * shouldn't need that * fix --------- Co-authored-by: Bruce Wayne --- board/drivers/usb.h | 5 +++++ python/__init__.py | 2 +- tests/ci_reset_hw.py | 2 +- tests/hitl/conftest.py | 3 +-- tests/pedal/test_pedal.py | 3 +-- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/board/drivers/usb.h b/board/drivers/usb.h index d10bed15e3..c291d7e64c 100644 --- a/board/drivers/usb.h +++ b/board/drivers/usb.h @@ -198,8 +198,13 @@ uint16_t string_product_desc[] = { // default serial number when we're not a panda uint16_t string_serial_desc[] = { +#ifdef PEDAL + STRING_DESCRIPTOR_HEADER(5), + 'p', 'e', 'd', 'a', 'l' +#else STRING_DESCRIPTOR_HEADER(4), 'n', 'o', 'n', 'e' +#endif }; // a string containing the default configuration index diff --git a/python/__init__.py b/python/__init__.py index 6c714ca3b5..cb0b0fb795 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -423,7 +423,7 @@ def usb_list(cls): if device.getVendorID() == 0xbbaa and device.getProductID() in cls.USB_PIDS: try: serial = device.getSerialNumber() - if len(serial) == 24: + if len(serial) == 24 or serial == "pedal": ret.append(serial) else: logging.warning(f"found device with panda descriptors but invalid serial: {serial}", RuntimeWarning) diff --git a/tests/ci_reset_hw.py b/tests/ci_reset_hw.py index abc16bf9b7..2cdd3f1f4f 100755 --- a/tests/ci_reset_hw.py +++ b/tests/ci_reset_hw.py @@ -34,7 +34,7 @@ def recover(serial): pandas = Panda.list() print(pandas) - assert len(pandas) == 7 + assert len(pandas) >= 7 with concurrent.futures.ProcessPoolExecutor(max_workers=len(pandas)) as exc: def flash(serial): diff --git a/tests/hitl/conftest.py b/tests/hitl/conftest.py index 50ef2a7134..d5627380be 100644 --- a/tests/hitl/conftest.py +++ b/tests/hitl/conftest.py @@ -16,7 +16,6 @@ BUS_SPEEDS = [(0, SPEED_NORMAL), (1, SPEED_NORMAL), (2, SPEED_NORMAL), (3, SPEED_GMLAN)] -PEDAL_SERIAL = 'none' JUNGLE_SERIAL = os.getenv("PANDAS_JUNGLE") PANDAS_EXCLUDE = os.getenv("PANDAS_EXCLUDE", "").strip().split(" ") PARTIAL_TESTS = os.environ.get("PARTIAL_TESTS", "0") == "1" @@ -48,7 +47,7 @@ def init_all_pandas(): _panda_jungle.set_panda_power(True) for serial in Panda.list(): - if serial not in PANDAS_EXCLUDE and serial != PEDAL_SERIAL: + if serial not in PANDAS_EXCLUDE: with Panda(serial=serial) as p: ptype = bytes(p.get_type()) if ptype in PandaGroup.TESTED: diff --git a/tests/pedal/test_pedal.py b/tests/pedal/test_pedal.py index 04c293e0cc..0d0676c45a 100755 --- a/tests/pedal/test_pedal.py +++ b/tests/pedal/test_pedal.py @@ -7,7 +7,6 @@ JUNGLE_SERIAL = os.getenv("PEDAL_JUNGLE") -PEDAL_SERIAL = 'none' PEDAL_BUS = 1 class TestPedal(unittest.TestCase): @@ -45,7 +44,7 @@ def _listen_can_frames(self): def test_usb_fw(self): self._flash_over_can(PEDAL_BUS, f"{BASEDIR}/board/pedal/obj/pedal_usb.bin.signed") time.sleep(2) - with Panda(PEDAL_SERIAL) as p: + with Panda('pedal') as p: self.assertTrue(p.get_type() == Panda.HW_TYPE_PEDAL) self.assertTrue(self._listen_can_frames() > 40) From fc506e7b3980b91c97a9eade7c2dadbf8c53e150 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 6 Aug 2023 14:29:28 -0700 Subject: [PATCH 176/197] switch to ruff linter (#1564) * switch to ruff linter * cleanup --- .pre-commit-config.yaml | 52 ++++++++++++------------------------- README.md | 2 +- examples/tesla_tester.py | 4 +-- pyproject.toml | 6 +++++ requirements.txt | 3 +-- tests/elm_car_simulator.py | 7 ++--- tests/safety/common.py | 4 +-- tests/safety/test_subaru.py | 2 +- 8 files changed, 34 insertions(+), 46 deletions(-) create mode 100644 pyproject.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5b040d111d..183e7746bb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,36 +1,18 @@ repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 - hooks: - - id: check-ast - - id: check-yaml - - id: check-merge-conflict - - id: check-symlinks -- repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.4.1 - hooks: - - id: mypy - additional_dependencies: ['git+https://github.com/numpy/numpy-stubs', 'types-requests', 'types-atomicwrites', - 'types-pycurl'] -- repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 - hooks: - - id: flake8 - args: - - --select=F,E112,E113,E304,E501,E502,E701,E702,E703,E71,E72,E731,W191,W6 - - --exclude=tests/gmbitbang/* - - --max-line-length=160 - - --statistics -- repo: local - hooks: - - id: pylint - name: pylint - entry: pylint - language: system - types: [python] - args: - - -rn - - -sn - - -j0 - - --disable=C,R,W0613,W0511,W0212,W0201,W0311,W0106,W0603,W0621,W0703,W0719,W1203,W1514,E1136 - - --generated-members="usb1.*" +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-ast + - id: check-yaml + - id: check-merge-conflict + - id: check-symlinks +- repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.4.1 + hooks: + - id: mypy + additional_dependencies: ['git+https://github.com/numpy/numpy-stubs', 'types-requests', 'types-atomicwrites', + 'types-pycurl'] +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.282 + hooks: + - id: ruff diff --git a/README.md b/README.md index 1b325d801b..3c8760b40d 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ to ensure that the behavior remains unchanged. * compiling the code and flashing it through USB. * receiving, sending, and forwarding CAN messages on all buses, over USB. -In addition, we run the [pylint](https://www.pylint.org/) and [flake8](https://github.com/PyCQA/flake8) linters on all python files within the panda repo. +In addition, we run the [ruff linter](https://github.com/astral-sh/ruff) on all python files within the panda repo. ## Hardware diff --git a/examples/tesla_tester.py b/examples/tesla_tester.py index c849b6378d..966e39d103 100644 --- a/examples/tesla_tester.py +++ b/examples/tesla_tester.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# flake8: noqa import binascii from panda import Panda @@ -16,7 +15,8 @@ def tesla_tester(): print("Setting Panda to output mode...") p.set_safety_mode(Panda.SAFETY_ALLOUTPUT) - # BDY 0x248 is the MCU_commands message, which includes folding mirrors, opening the trunk, frunk, setting the cars lock state and more. For our test, we will edit the 3rd byte, which is MCU_lockRequest. 0x01 will lock, 0x02 will unlock: + # BDY 0x248 is the MCU_commands message, which includes folding mirrors, opening the trunk, frunk, setting the cars lock state and more. + # For our test, we will edit the 3rd byte, which is MCU_lockRequest. 0x01 will lock, 0x02 will unlock: print("Unlocking Tesla...") p.can_send(0x248, b"\x00\x00\x02\x00\x00\x00\x00\x00", body_bus_num) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000..0a5a4e5cac --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +# https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml +[tool.ruff] +select = ["E", "F", "W"] +ignore = ["W292", "E741"] +line-length = 160 +target-version="py311" diff --git a/requirements.txt b/requirements.txt index 274170fd00..6693a0af83 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +ruff libusb1==2.0.1 numpy hexdump>=3.3 @@ -7,11 +8,9 @@ pytest pytest-timeouts parameterized requests -flake8==3.7.9 cffi==1.14.3 crcmod pre-commit==2.13.0 -pylint==2.15.4 scons==4.4.0 flaky spidev diff --git a/tests/elm_car_simulator.py b/tests/elm_car_simulator.py index 8c8360a1e7..9d745a79f8 100755 --- a/tests/elm_car_simulator.py +++ b/tests/elm_car_simulator.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# flake8: noqa """Used to Reverse/Test ELM protocol auto detect and OBD message response without a car.""" @@ -100,8 +99,10 @@ def __lin_monitor(self): print("Invalid bytes at start of message") print(" BUFF", lin_buff) continue - if len(lin_buff) < msglen + 4: continue - if lin_checksum(lin_buff[:-1]) != lin_buff[-1]: continue + if len(lin_buff) < msglen + 4: + continue + if lin_checksum(lin_buff[:-1]) != lin_buff[-1]: + continue self.__lin_process_msg(lin_buff[0] & 0xF8, # Priority lin_buff[1], lin_buff[2], lin_buff[3:-1]) lin_buff = bytearray() diff --git a/tests/safety/common.py b/tests/safety/common.py index f926e40988..afce6c1873 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -553,7 +553,7 @@ def _angle_meas_msg(self, angle: float): @abc.abstractmethod def _speed_msg(self, speed): pass - + def common_measurement_test(self, msg_func, min_value, max_value, factor, get_min_func, get_max_func): for val in np.arange(min_value, max_value, 0.5): for i in range(6): @@ -571,7 +571,7 @@ def common_measurement_test(self, msg_func, min_value, max_value, factor, get_mi def test_vehicle_speed_measurements(self): self.common_measurement_test(self._speed_msg, 0, 80, VEHICLE_SPEED_FACTOR, self.safety.get_vehicle_speed_min, self.safety.get_vehicle_speed_max) - + def test_steering_angle_measurements(self): self.common_measurement_test(self._angle_meas_msg, -180, 180, self.DEG_TO_CAN, self.safety.get_angle_meas_min, self.safety.get_angle_meas_max) diff --git a/tests/safety/test_subaru.py b/tests/safety/test_subaru.py index a1e9e9a96a..17cfc51171 100755 --- a/tests/safety/test_subaru.py +++ b/tests/safety/test_subaru.py @@ -25,7 +25,7 @@ def lkas_tx_msgs(alt_bus): - return [[MSG_SUBARU_ES_LKAS, SUBARU_MAIN_BUS], + return [[MSG_SUBARU_ES_LKAS, SUBARU_MAIN_BUS], [MSG_SUBARU_ES_Distance, alt_bus], [MSG_SUBARU_ES_DashStatus, SUBARU_MAIN_BUS], [MSG_SUBARU_ES_LKAS_State, SUBARU_MAIN_BUS], From 3dc3b58e2011f174371ff7a685348986b13aad4c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 6 Aug 2023 14:59:22 -0700 Subject: [PATCH 177/197] ruff: enable bugbear checks (#1565) --- board/jungle/scripts/can_printer.py | 2 +- board/jungle/scripts/echo_loopback_test.py | 2 +- pyproject.toml | 2 +- python/isotp.py | 10 +- python/spi.py | 2 +- tests/black_loopback_test.py | 122 --------------------- tests/black_white_loopback_test.py | 14 +-- tests/black_white_relay_endurance.py | 14 +-- tests/black_white_relay_test.py | 14 +-- tests/can_printer.py | 7 +- tests/hitl/helpers.py | 3 +- tests/loopback_test.py | 5 +- tests/safety/common.py | 3 +- tests/safety/test_honda.py | 2 +- tests/standalone_test.py | 2 +- tests/tucan_loopback.py | 5 +- 16 files changed, 34 insertions(+), 175 deletions(-) delete mode 100755 tests/black_loopback_test.py diff --git a/board/jungle/scripts/can_printer.py b/board/jungle/scripts/can_printer.py index 3e64806348..675fc508a1 100755 --- a/board/jungle/scripts/can_printer.py +++ b/board/jungle/scripts/can_printer.py @@ -26,7 +26,7 @@ def can_printer(): if sec_since_boot() - lp > 0.1: dd = chr(27) + "[2J" dd += "%5.2f\n" % (sec_since_boot() - start) - for k,v in sorted(zip(list(msgs.keys()), [binascii.hexlify(x[-1]) for x in list(msgs.values())])): + for k,v in sorted(zip(list(msgs.keys()), [binascii.hexlify(x[-1]) for x in list(msgs.values())], strict=True)): dd += "%s(%6d) %s\n" % ("%04X(%4d)" % (k,k),len(msgs[k]), v) print(dd) lp = sec_since_boot() diff --git a/board/jungle/scripts/echo_loopback_test.py b/board/jungle/scripts/echo_loopback_test.py index d68fa4f3ec..78b65b5341 100755 --- a/board/jungle/scripts/echo_loopback_test.py +++ b/board/jungle/scripts/echo_loopback_test.py @@ -43,7 +43,7 @@ def test_loopback(): break if not found: cprint("\nFAILED", "red") - assert False + raise AssertionError ################################################################# ############################# MAIN ############################## diff --git a/pyproject.toml b/pyproject.toml index 0a5a4e5cac..53f2b5aba8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ # https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml [tool.ruff] -select = ["E", "F", "W"] +select = ["E", "F", "W", "B"] ignore = ["W292", "E741"] line-length = 160 target-version="py311" diff --git a/python/isotp.py b/python/isotp.py index 45acb6abae..3334deb8ed 100644 --- a/python/isotp.py +++ b/python/isotp.py @@ -6,10 +6,8 @@ def msg(x): if DEBUG: print("S:", binascii.hexlify(x)) - if len(x) <= 7: - ret = bytes([len(x)]) + x - else: - assert False + assert len(x) <= 7 + ret = bytes([len(x)]) + x return ret.ljust(8, b"\x00") kmsgs = [] @@ -56,7 +54,7 @@ def isotp_recv_subaddr(panda, addr, bus, sendaddr, subaddr): dat = msg[2:] else: print(binascii.hexlify(msg)) - assert False + raise AssertionError return dat[0:tlen] @@ -133,7 +131,7 @@ def isotp_recv(panda, addr, bus=0, sendaddr=None, subaddr=None): tlen = msg[0] & 0xf dat = msg[1:] else: - assert False + raise AssertionError dat = dat[0:tlen] if DEBUG: diff --git a/python/spi.py b/python/spi.py index ad0225b1e0..0be28f49c1 100644 --- a/python/spi.py +++ b/python/spi.py @@ -314,7 +314,7 @@ def __init__(self): self._mcu_type = MCU_TYPE_BY_IDCODE[self.get_chip_id()] except PandaSpiException: - raise PandaSpiException("failed to connect to panda") # pylint: disable=W0707 + raise PandaSpiException("failed to connect to panda") from None def _get_ack(self, spi, timeout=1.0): data = 0x00 diff --git a/tests/black_loopback_test.py b/tests/black_loopback_test.py deleted file mode 100755 index bcfcaefede..0000000000 --- a/tests/black_loopback_test.py +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/env python3 - -# Loopback test between two black pandas (+ harness and power) -# Tests all buses, including OBD CAN, which is on the same bus as CAN0 in this test. -# To be sure, the test should be run with both harness orientations - - -import os -import sys -import time -import random -import argparse - -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 - -def get_test_string(): - return b"test" + os.urandom(10) - -def run_test(sleep_duration): - pandas = Panda.list() - print(pandas) - - # make sure two pandas are connected - if len(pandas) != 2: - print("Connect white/grey and black panda to run this test!") - assert False - - # connect - pandas[0] = Panda(pandas[0]) - pandas[1] = Panda(pandas[1]) - - # find out the hardware types - if not pandas[0].is_black() or not pandas[1].is_black(): - print("Connect two black pandas to run this test!") - assert False - - for panda in pandas: - # disable safety modes - panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT) - - # test health packet - print("panda health", panda.health()) - - # setup test array (send bus, sender obd, reciever obd, expected busses) - test_array = [ - (0, False, False, [0]), - (1, False, False, [1]), - (2, False, False, [2]), - (0, False, True, [0, 1]), - (1, False, True, []), - (2, False, True, [2]), - (0, True, False, [0]), - (1, True, False, [0]), - (2, True, False, [2]), - (0, True, True, [0, 1]), - (1, True, True, [0, 1]), - (2, True, True, [2]) - ] - - # test both orientations - print("***************** TESTING (0 --> 1) *****************") - test_buses(pandas[0], pandas[1], test_array, sleep_duration) - print("***************** TESTING (1 --> 0) *****************") - test_buses(pandas[1], pandas[0], test_array, sleep_duration) - - -def test_buses(send_panda, recv_panda, test_array, sleep_duration): - for send_bus, send_obd, recv_obd, recv_buses in test_array: - send_panda.send_heartbeat() - recv_panda.send_heartbeat() - print("\nSend bus:", send_bus, " Send OBD:", send_obd, " Recv OBD:", recv_obd) - - # set OBD on pandas - send_panda.set_gmlan(True if send_obd else None) - recv_panda.set_gmlan(True if recv_obd else None) - - # clear and flush - send_panda.can_clear(send_bus) - for recv_bus in recv_buses: - recv_panda.can_clear(recv_bus) - send_panda.can_recv() - recv_panda.can_recv() - - # send the characters - at = random.randint(1, 2000) - st = get_test_string()[0:8] - send_panda.can_send(at, st, send_bus) - time.sleep(0.1) - - # check for receive - _ = send_panda.can_recv() # cans echo - cans_loop = recv_panda.can_recv() - - loop_buses = [] - for loop in cans_loop: - print(" Loop on bus", str(loop[3])) - loop_buses.append(loop[3]) - if len(cans_loop) == 0: - print(" No loop") - - # test loop buses - recv_buses.sort() - loop_buses.sort() - assert recv_buses == loop_buses - print(" TEST PASSED") - - time.sleep(sleep_duration) - print("\n") - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("-n", type=int, help="Number of test iterations to run") - parser.add_argument("-sleep", type=int, help="Sleep time between tests", default=0) - args = parser.parse_args() - - if args.n is None: - while True: - run_test(sleep_duration=args.sleep) - else: - for i in range(args.n): - run_test(sleep_duration=args.sleep) diff --git a/tests/black_white_loopback_test.py b/tests/black_white_loopback_test.py index f5c6170fe4..8914430a17 100755 --- a/tests/black_white_loopback_test.py +++ b/tests/black_white_loopback_test.py @@ -30,8 +30,7 @@ def run_test(sleep_duration): # make sure two pandas are connected if len(pandas) != 2: - print("Connect white/grey and black panda to run this test!") - assert False + raise Exception("Connect white/grey and black panda to run this test!") # connect pandas[0] = Panda(pandas[0]) @@ -48,8 +47,7 @@ def run_test(sleep_duration): black_panda = pandas[1] other_panda = pandas[0] else: - print("Connect white/grey and black panda to run this test!") - assert False + raise Exception("Connect white/grey and black panda to run this test!") # disable safety modes black_panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT) @@ -130,8 +128,7 @@ def test_buses(black_panda, other_panda, direction, test_array, sleep_duration): loop_buses.append(loop[3]) if len(cans_loop) == 0: print(" No loop") - if not os.getenv("NOASSERT"): - assert False + assert not os.getenv("NOASSERT") # test loop buses recv_buses.sort() @@ -141,8 +138,7 @@ def test_buses(black_panda, other_panda, direction, test_array, sleep_duration): zero_bus_errors += 1 else: nonzero_bus_errors += 1 - if not os.getenv("NOASSERT"): - assert False + assert not os.getenv("NOASSERT") else: print(" TEST PASSED") @@ -159,5 +155,5 @@ def test_buses(black_panda, other_panda, direction, test_array, sleep_duration): while True: run_test(sleep_duration=args.sleep) else: - for i in range(args.n): + for _ in range(args.n): run_test(sleep_duration=args.sleep) diff --git a/tests/black_white_relay_endurance.py b/tests/black_white_relay_endurance.py index f44eafd207..cfdaeb330b 100755 --- a/tests/black_white_relay_endurance.py +++ b/tests/black_white_relay_endurance.py @@ -30,8 +30,7 @@ def run_test(sleep_duration): # make sure two pandas are connected if len(pandas) != 2: - print("Connect white/grey and black panda to run this test!") - assert False + raise Exception("Connect white/grey and black panda to run this test!") # connect pandas[0] = Panda(pandas[0]) @@ -48,8 +47,7 @@ def run_test(sleep_duration): black_panda = pandas[1] other_panda = pandas[0] else: - print("Connect white/grey and black panda to run this test!") - assert False + raise Exception("Connect white/grey and black panda to run this test!") # disable safety modes black_panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT) @@ -137,8 +135,7 @@ def test_buses(black_panda, other_panda, direction, test_array, sleep_duration): loop_buses.append(loop[3]) if len(cans_loop) == 0: print(" No loop") - if not os.getenv("NOASSERT"): - assert False + assert os.getenv("NOASSERT") # test loop buses recv_buses.sort() @@ -148,8 +145,7 @@ def test_buses(black_panda, other_panda, direction, test_array, sleep_duration): zero_bus_errors += 1 else: nonzero_bus_errors += 1 - if not os.getenv("NOASSERT"): - assert False + assert os.getenv("NOASSERT") else: print(" TEST PASSED") @@ -166,5 +162,5 @@ def test_buses(black_panda, other_panda, direction, test_array, sleep_duration): while True: run_test(sleep_duration=args.sleep) else: - for i in range(args.n): + for _ in range(args.n): run_test(sleep_duration=args.sleep) diff --git a/tests/black_white_relay_test.py b/tests/black_white_relay_test.py index 4de02ec849..4a3f58f7f4 100755 --- a/tests/black_white_relay_test.py +++ b/tests/black_white_relay_test.py @@ -29,8 +29,7 @@ def run_test(sleep_duration): # make sure two pandas are connected if len(pandas) != 2: - print("Connect white/grey and black panda to run this test!") - assert False + raise Exception("Connect white/grey and black panda to run this test!") # connect pandas[0] = Panda(pandas[0]) @@ -50,8 +49,7 @@ def run_test(sleep_duration): black_panda = pandas[1] other_panda = pandas[0] else: - print("Connect white/grey and black panda to run this test!") - assert False + raise Exception("Connect white/grey and black panda to run this test!") # disable safety modes black_panda.set_safety_mode(Panda.SAFETY_ALLOUTPUT) @@ -69,8 +67,7 @@ def run_test(sleep_duration): if not test_buses(black_panda, other_panda, (0, False, [0])): open_errors += 1 - print("Open error") - assert False + raise Exception("Open error") # Switch off relay black_panda.set_safety_mode(Panda.SAFETY_SILENT) @@ -78,8 +75,7 @@ def run_test(sleep_duration): if not test_buses(black_panda, other_panda, (0, False, [0, 2])): closed_errors += 1 - print("Close error") - assert False + raise Exception("Close error") counter += 1 print("Number of cycles:", counter, "Open errors:", open_errors, "Closed errors:", closed_errors, "Content errors:", content_errors) @@ -137,5 +133,5 @@ def test_buses(black_panda, other_panda, test_obj): while True: run_test(sleep_duration=args.sleep) else: - for i in range(args.n): + for _ in range(args.n): run_test(sleep_duration=args.sleep) diff --git a/tests/can_printer.py b/tests/can_printer.py index af0ed0e2ed..15ce89f688 100755 --- a/tests/can_printer.py +++ b/tests/can_printer.py @@ -1,13 +1,10 @@ #!/usr/bin/env python3 - import os -import sys import time from collections import defaultdict import binascii -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda # fake def sec_since_boot(): @@ -30,7 +27,7 @@ def can_printer(): if sec_since_boot() - lp > 0.1: dd = chr(27) + "[2J" dd += "%5.2f\n" % (sec_since_boot() - start) - for k, v in sorted(zip(list(msgs.keys()), [binascii.hexlify(x[-1]) for x in list(msgs.values())])): + for k, v in sorted(zip(list(msgs.keys()), [binascii.hexlify(x[-1]) for x in list(msgs.values())], strict=True)): dd += "%s(%6d) %s\n" % ("%04X(%4d)" % (k, k), len(msgs[k]), v) print(dd) lp = sec_since_boot() diff --git a/tests/hitl/helpers.py b/tests/hitl/helpers.py index ee04784042..aafad72db3 100644 --- a/tests/hitl/helpers.py +++ b/tests/hitl/helpers.py @@ -64,5 +64,4 @@ def clear_can_buffers(panda): r = panda.can_recv() time.sleep(0.05) if (time.monotonic() - st) > 10: - print("Unable to clear can buffers for panda ", panda.get_serial()) - assert False + raise Exception("Unable to clear can buffers for panda ", panda.get_serial()) diff --git a/tests/loopback_test.py b/tests/loopback_test.py index 84925952ae..630c55215e 100755 --- a/tests/loopback_test.py +++ b/tests/loopback_test.py @@ -20,8 +20,7 @@ def run_test(sleep_duration): print(pandas) if len(pandas) < 2: - print("Minimum two pandas are needed for test") - assert False + raise Exception("Minimum two pandas are needed for test") run_test_w_pandas(pandas, sleep_duration) @@ -116,5 +115,5 @@ def run_test_w_pandas(pandas, sleep_duration): while True: run_test(sleep_duration=args.sleep) else: - for i in range(args.n): + for _ in range(args.n): run_test(sleep_duration=args.sleep) diff --git a/tests/safety/common.py b/tests/safety/common.py index afce6c1873..0bec019fb1 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -884,7 +884,8 @@ def test_tx_hook_on_wrong_safety_mode(self): test = importlib.import_module("panda.tests.safety."+tf[:-3]) for attr in dir(test): if attr.startswith("Test") and attr != current_test: - tx = getattr(getattr(test, attr), "TX_MSGS") + tc = getattr(test, attr) + tx = tc.TX_MSGS if tx is not None and not attr.endswith('Base'): # No point in comparing different Tesla safety modes if 'Tesla' in attr and 'Tesla' in current_test: diff --git a/tests/safety/test_honda.py b/tests/safety/test_honda.py index f8e17c24e2..11b1c623e9 100755 --- a/tests/safety/test_honda.py +++ b/tests/safety/test_honda.py @@ -132,7 +132,7 @@ def test_rx_hook(self): self.assertFalse(self.safety.get_controls_allowed()) # restore counters for future tests with a couple of good messages - for i in range(2): + for _ in range(2): self.safety.set_controls_allowed(1) self._rx(self._button_msg(Btn.SET)) self._rx(self._speed_msg(0)) diff --git a/tests/standalone_test.py b/tests/standalone_test.py index 6580b06d14..dfd2c9a7ae 100755 --- a/tests/standalone_test.py +++ b/tests/standalone_test.py @@ -13,7 +13,7 @@ print(p.health()) t1 = time.time() - for i in range(100): + for _ in range(100): p.get_serial() t2 = time.time() print("100 requests took %.2f ms" % ((t2 - t1) * 1000)) diff --git a/tests/tucan_loopback.py b/tests/tucan_loopback.py index ff037f3e35..0780ffee00 100755 --- a/tests/tucan_loopback.py +++ b/tests/tucan_loopback.py @@ -20,8 +20,7 @@ def run_test(sleep_duration): print(pandas) if len(pandas) < 2: - print("Two pandas are needed for test") - assert False + raise Exception("Two pandas are needed for test") run_test_w_pandas(pandas, sleep_duration) @@ -114,5 +113,5 @@ def run_test_w_pandas(pandas, sleep_duration): while True: run_test(sleep_duration=args.sleep) else: - for i in range(args.n): + for _ in range(args.n): run_test(sleep_duration=args.sleep) From 77b09a31605e4746e82f81ffacfb90d5c5a8e297 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 6 Aug 2023 16:30:21 -0700 Subject: [PATCH 178/197] misc hitl test cleanup (#1562) * misc hitl test cleanup * adjust that * update * tres fix --------- Co-authored-by: Bruce Wayne Co-authored-by: Comma Device --- Jenkinsfile | 2 - tests/hitl/0_dfu.py | 45 ----------------------- tests/hitl/1_program.py | 25 +++++++++++-- tests/hitl/3_usb_to_can.py | 8 ---- tests/hitl/4_can_loopback.py | 71 ++++++------------------------------ tests/hitl/5_spi.py | 24 +++++++++++- tests/hitl/7_internal.py | 2 +- tests/hitl/conftest.py | 7 +--- tests/hitl/helpers.py | 7 +++- 9 files changed, 64 insertions(+), 127 deletions(-) delete mode 100644 tests/hitl/0_dfu.py diff --git a/Jenkinsfile b/Jenkinsfile index d74ace662b..4daeb27cef 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,7 +1,6 @@ def docker_run(String step_label, int timeout_mins, String cmd) { timeout(time: timeout_mins, unit: 'MINUTES') { sh script: "docker run --rm --privileged \ - --env PARTIAL_TESTS=${env.PARTIAL_TESTS} \ --env PYTHONWARNINGS=error \ --volume /dev/bus/usb:/dev/bus/usb \ --volume /var/run/dbus:/var/run/dbus \ @@ -62,7 +61,6 @@ pipeline { agent any environment { CI = "1" - //PARTIAL_TESTS = "${env.BRANCH_NAME == 'master' ? ' ' : '1'}" PYTHONWARNINGS= "error" DOCKER_IMAGE_TAG = "panda:build-${env.GIT_COMMIT}" diff --git a/tests/hitl/0_dfu.py b/tests/hitl/0_dfu.py deleted file mode 100644 index f3b8ae04aa..0000000000 --- a/tests/hitl/0_dfu.py +++ /dev/null @@ -1,45 +0,0 @@ -import pytest -import random - -from panda import Panda, PandaDFU -from panda.python.spi import SpiDevice - -@pytest.mark.expected_logs(1) -def test_dfu(p): - app_mcu_type = p.get_mcu_type() - dfu_serial = p.get_dfu_serial() - - p.reset(enter_bootstub=True) - p.reset(enter_bootloader=True) - assert Panda.wait_for_dfu(dfu_serial, timeout=20), "failed to enter DFU" - - dfu = PandaDFU(dfu_serial) - assert dfu.get_mcu_type() == app_mcu_type - - assert dfu_serial in PandaDFU.list() - - dfu._handle.clear_status() - dfu.reset() - p.reconnect() - - -@pytest.mark.expected_logs(1) -@pytest.mark.test_panda_types((Panda.HW_TYPE_TRES, )) -def test_dfu_with_spam(p): - dfu_serial = p.get_dfu_serial() - - # enter DFU - p.reset(enter_bootstub=True) - p.reset(enter_bootloader=True) - assert Panda.wait_for_dfu(dfu_serial, timeout=20), "failed to enter DFU" - - # send junk - for _ in range(10): - speed = 1000000 * random.randint(1, 5) - d = SpiDevice(speed=speed) - with d.acquire() as spi: - dat = [random.randint(0, 255) for _ in range(random.randint(1, 100))] - spi.xfer(dat) - - # should still show up - assert dfu_serial in PandaDFU.list() diff --git a/tests/hitl/1_program.py b/tests/hitl/1_program.py index b051636f5e..d4e6e4fd5d 100644 --- a/tests/hitl/1_program.py +++ b/tests/hitl/1_program.py @@ -9,11 +9,30 @@ def check_signature(p): assert not p.bootstub, "Flashed firmware not booting. Stuck in bootstub." assert p.up_to_date() + +@pytest.mark.expected_logs(1) +def test_dfu(p): + app_mcu_type = p.get_mcu_type() + dfu_serial = p.get_dfu_serial() + + p.reset(enter_bootstub=True) + p.reset(enter_bootloader=True) + assert Panda.wait_for_dfu(dfu_serial, timeout=19), "failed to enter DFU" + + dfu = PandaDFU(dfu_serial) + assert dfu.get_mcu_type() == app_mcu_type + + assert dfu_serial in PandaDFU.list() + + dfu._handle.clear_status() + dfu.reset() + p.reconnect() + # TODO: make more comprehensive bootstub tests and run on a few production ones + current # TODO: also test release-signed app @pytest.mark.execution_timeout(30) @pytest.mark.expected_logs(1, 2) -def test_a_known_bootstub(p): +def test_known_bootstub(p): """ Test that compiled app can work with known production bootstub """ @@ -60,13 +79,13 @@ def test_a_known_bootstub(p): @pytest.mark.execution_timeout(25) @pytest.mark.expected_logs(1) -def test_b_recover(p): +def test_recover(p): assert p.recover(timeout=30) check_signature(p) @pytest.mark.execution_timeout(25) @pytest.mark.expected_logs(3) -def test_c_flash(p): +def test_flash(p): # test flash from bootstub serial = p._serial assert serial is not None diff --git a/tests/hitl/3_usb_to_can.py b/tests/hitl/3_usb_to_can.py index 22cfde438e..9321eb4e85 100644 --- a/tests/hitl/3_usb_to_can.py +++ b/tests/hitl/3_usb_to_can.py @@ -1,4 +1,3 @@ -import sys import time import pytest from flaky import flaky @@ -60,9 +59,6 @@ def test_reliability(p): et = (time.monotonic() - st) * 1000.0 assert et < 20 - sys.stdout.write("P") - sys.stdout.flush() - @flaky(max_runs=6, min_passes=1) def test_throughput(p): # enable output mode @@ -90,10 +86,6 @@ def test_gmlan(p): p.set_safety_mode(Panda.SAFETY_ALLOUTPUT) p.set_can_loopback(True) - p.set_can_speed_kbps(1, SPEED_NORMAL) - p.set_can_speed_kbps(2, SPEED_NORMAL) - p.set_can_speed_kbps(3, SPEED_GMLAN) - # set gmlan on CAN2 for bus in [Panda.GMLAN_CAN2, Panda.GMLAN_CAN3, Panda.GMLAN_CAN2, Panda.GMLAN_CAN3]: p.set_gmlan(bus) diff --git a/tests/hitl/4_can_loopback.py b/tests/hitl/4_can_loopback.py index f7e8bb640d..4284b588ea 100644 --- a/tests/hitl/4_can_loopback.py +++ b/tests/hitl/4_can_loopback.py @@ -7,30 +7,17 @@ from collections import defaultdict from panda import Panda -from panda.tests.hitl.conftest import PandaGroup, PARTIAL_TESTS -from panda.tests.hitl.helpers import time_many_sends, clear_can_buffers +from panda.tests.hitl.conftest import PandaGroup +from panda.tests.hitl.helpers import time_many_sends, get_random_can_messages, clear_can_buffers @flaky(max_runs=3, min_passes=1) @pytest.mark.execution_timeout(35) def test_send_recv(p, panda_jungle): def test(p_send, p_recv): - p_send.set_can_loopback(False) - p_recv.set_can_loopback(False) - - p_send.can_send_many([(0x1ba, 0, b"message", 0)] * 2) - time.sleep(0.05) - p_recv.can_recv() - p_send.can_recv() - for bus in (0, 1, 2): for speed in (10, 20, 50, 100, 125, 250, 500, 1000): - p_send.set_can_speed_kbps(bus, speed) - p_recv.set_can_speed_kbps(bus, speed) - time.sleep(0.1) - - clear_can_buffers(p_send) - clear_can_buffers(p_recv) - time.sleep(0.1) + clear_can_buffers(p_send, speed) + clear_can_buffers(p_recv, speed) comp_kbps = time_many_sends(p_send, bus, p_recv, two_pandas=True) @@ -49,27 +36,10 @@ def test(p_send, p_recv): @pytest.mark.execution_timeout(30) def test_latency(p, panda_jungle): def test(p_send, p_recv): - p_send.set_can_loopback(False) - p_recv.set_can_loopback(False) - - p_send.set_can_speed_kbps(0, 500) - p_recv.set_can_speed_kbps(0, 500) - time.sleep(0.05) - - p_send.can_send_many([(0x1ba, 0, b"testmsg", 0)] * 10) - time.sleep(0.05) - p_recv.can_recv() - p_send.can_recv() - for bus in (0, 1, 2): for speed in (10, 20, 50, 100, 125, 250, 500, 1000): - p_send.set_can_speed_kbps(bus, speed) - p_recv.set_can_speed_kbps(bus, speed) - time.sleep(0.1) - - # clear can buffers - clear_can_buffers(p_send) - clear_can_buffers(p_recv) + clear_can_buffers(p_send, speed) + clear_can_buffers(p_recv, speed) latencies = [] comp_kbps_list = [] @@ -166,10 +136,6 @@ def test(p_send, p_recv, address=None): test(panda_jungle, p, 0x18DB33F1) def test_bulk_write(p, panda_jungle): - # TODO: doesn't work in partial test mode - if PARTIAL_TESTS: - return - # The TX buffers on pandas is 0x100 in length. NUM_MESSAGES_PER_BUS = 10000 @@ -204,27 +170,15 @@ def flood_tx(panda): if len(rx) != 4 * NUM_MESSAGES_PER_BUS: raise Exception("Did not receive all messages!") - # Set back to silent mode - p.set_safety_mode(Panda.SAFETY_SILENT) - def test_message_integrity(p): - clear_can_buffers(p) - p.set_safety_mode(Panda.SAFETY_ALLOUTPUT) - p.set_can_loopback(True) - - n = 250 - for i in range(n): + for i in range(250): sent_msgs = defaultdict(set) for _ in range(random.randrange(10)): - to_send = [] - for __ in range(random.randrange(100)): - bus = random.randrange(3) - addr = random.randrange(1, 1<<29) - dat = bytes([random.getrandbits(8) for _ in range(random.randrange(1, 9))]) - sent_msgs[bus].add((addr, dat)) - to_send.append([addr, None, dat, bus]) + to_send = get_random_can_messages(random.randrange(100)) + for m in to_send: + sent_msgs[m[3]].add((m[0], m[2])) p.can_send_many(to_send, timeout=0) start_time = time.monotonic() @@ -233,13 +187,12 @@ def test_message_integrity(p): for msg in recvd: if msg[3] >= 128: k = (msg[0], bytes(msg[2])) - assert k in sent_msgs[msg[3]-128], f"message {k} was never sent on bus {bus}" + bus = msg[3]-128 + assert k in sent_msgs[bus], f"message {k} was never sent on bus {bus}" sent_msgs[msg[3]-128].discard(k) # if a set isn't empty, messages got dropped for bus in range(3): assert not len(sent_msgs[bus]), f"loop {i}: bus {bus} missing {len(sent_msgs[bus])} messages" - # Set back to silent mode - p.set_safety_mode(Panda.SAFETY_SILENT) print("Got all messages intact") diff --git a/tests/hitl/5_spi.py b/tests/hitl/5_spi.py index d609446d5d..d67f7c4ea5 100644 --- a/tests/hitl/5_spi.py +++ b/tests/hitl/5_spi.py @@ -3,13 +3,33 @@ import random from unittest.mock import patch -from panda import Panda -from panda.python.spi import PandaProtocolMismatch, PandaSpiNackResponse +from panda import Panda, PandaDFU +from panda.python.spi import SpiDevice, PandaProtocolMismatch, PandaSpiNackResponse pytestmark = [ pytest.mark.test_panda_types((Panda.HW_TYPE_TRES, )) ] +@pytest.mark.skip("doesn't work, bootloader seems to ignore commands once it sees junk") +@pytest.mark.expected_logs(0) +def test_dfu_with_spam(p): + dfu_serial = p.get_dfu_serial() + + # enter DFU + p.reset(enter_bootstub=True) + p.reset(enter_bootloader=True) + assert Panda.wait_for_dfu(dfu_serial, timeout=19), "failed to enter DFU" + + # send junk + d = SpiDevice() + for _ in range(9): + with d.acquire() as spi: + dat = [random.randint(-1, 255) for _ in range(random.randint(1, 100))] + spi.xfer(dat) + + # should still show up + assert dfu_serial in PandaDFU.list() + class TestSpi: def _ping(self, mocker, panda): # should work with no retries diff --git a/tests/hitl/7_internal.py b/tests/hitl/7_internal.py index d4e792721c..9717a24959 100644 --- a/tests/hitl/7_internal.py +++ b/tests/hitl/7_internal.py @@ -49,7 +49,7 @@ def test_fan_cooldown(p): def test_fan_overshoot(p): if p.get_type() == Panda.HW_TYPE_DOS: - pytest.skip("fan controller overshoots on fans that need stall recovery") + pytest.skip("panda's fan controller overshoots on the comma three fans that need stall recovery") # make sure it's stopped completely p.set_fan_power(0) diff --git a/tests/hitl/conftest.py b/tests/hitl/conftest.py index d5627380be..d825c60e30 100644 --- a/tests/hitl/conftest.py +++ b/tests/hitl/conftest.py @@ -28,12 +28,7 @@ class PandaGroup: TESTED = (Panda.HW_TYPE_WHITE_PANDA, Panda.HW_TYPE_BLACK_PANDA, Panda.HW_TYPE_RED_PANDA, Panda.HW_TYPE_RED_PANDA_V2, Panda.HW_TYPE_UNO) -if PARTIAL_TESTS: - # minimal set of pandas to get most of our coverage - # * red panda covers GEN2, STM32H7 - # * black panda covers STM32F4, and GEN2 - PandaGroup.TESTED = (Panda.HW_TYPE_BLACK_PANDA, Panda.HW_TYPE_RED_PANDA) # type: ignore -elif HW_TYPES is not None: +if HW_TYPES is not None: PandaGroup.TESTED = [bytes([int(x), ]) for x in HW_TYPES.strip().split(",")] # type: ignore diff --git a/tests/hitl/helpers.py b/tests/hitl/helpers.py index aafad72db3..b75e187457 100644 --- a/tests/hitl/helpers.py +++ b/tests/hitl/helpers.py @@ -1,5 +1,6 @@ import time import random +from typing import Optional def get_random_can_messages(n): @@ -51,7 +52,11 @@ def time_many_sends(p, bus, p_recv=None, msg_count=100, two_pandas=False): return comp_kbps -def clear_can_buffers(panda): +def clear_can_buffers(panda, speed: Optional[int] = None): + if speed is not None: + for bus in range(3): + panda.set_can_speed_kbps(bus, speed) + # clear tx buffers for i in range(4): panda.can_clear(i) From 235067a22b5f7b714b6918d6d6b3d664fd65ce9d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 6 Aug 2023 20:51:26 -0700 Subject: [PATCH 179/197] run HITL tests in parallel (#1566) * seems to work * cleanup * run in ci * fail on first * fix jungle scope --------- Co-authored-by: Bruce Wayne --- Jenkinsfile | 3 +- requirements.txt | 3 +- tests/hitl/conftest.py | 47 ++++++++++++--------- tests/hitl/run_parallel_tests.sh | 8 ++++ tests/hitl/{test.sh => run_serial_tests.sh} | 2 +- 5 files changed, 41 insertions(+), 22 deletions(-) create mode 100755 tests/hitl/run_parallel_tests.sh rename tests/hitl/{test.sh => run_serial_tests.sh} (67%) diff --git a/Jenkinsfile b/Jenkinsfile index 4daeb27cef..14953f6927 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -133,7 +133,8 @@ pipeline { stage('HITL tests') { steps { script { - docker_run("HITL tests", 35, 'PANDAS_JUNGLE=23002d000851393038373731 PANDAS_EXCLUDE="1d0002000c51303136383232 2f002e000c51303136383232" ./tests/hitl/test.sh') + docker_run("parallel tests", 5, 'PANDAS_JUNGLE=23002d000851393038373731 PANDAS_EXCLUDE="1d0002000c51303136383232 2f002e000c51303136383232" ./tests/hitl/run_parallel_tests.sh') + docker_run("serial tests", 9, 'PANDAS_JUNGLE=23002d000851393038373731 PANDAS_EXCLUDE="1d0002000c51303136383232 2f002e000c51303136383232" ./tests/hitl/run_serial_tests.sh') } } } diff --git a/requirements.txt b/requirements.txt index 6693a0af83..41c5226aa2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,8 @@ hexdump>=3.3 pycryptodome==3.9.8 tqdm>=4.14.0 pytest +pytest-mock +pytest-xdist pytest-timeouts parameterized requests @@ -14,5 +16,4 @@ pre-commit==2.13.0 scons==4.4.0 flaky spidev -pytest-mock termcolor diff --git a/tests/hitl/conftest.py b/tests/hitl/conftest.py index d825c60e30..c337013f05 100644 --- a/tests/hitl/conftest.py +++ b/tests/hitl/conftest.py @@ -1,15 +1,14 @@ -import concurrent.futures import os -import time import pytest +import concurrent.futures -from panda import Panda, PandaDFU +from panda import Panda, PandaDFU, PandaJungle from panda.tests.hitl.helpers import clear_can_buffers -NO_JUNGLE = os.environ.get("NO_JUNGLE", "0") == "1" -if not NO_JUNGLE: - from panda import PandaJungle - +# needed to get output when using xdist +if "DEBUG" in os.environ: + import sys + sys.stdout = sys.stderr SPEED_NORMAL = 500 SPEED_GMLAN = 33.3 @@ -17,10 +16,15 @@ JUNGLE_SERIAL = os.getenv("PANDAS_JUNGLE") +NO_JUNGLE = os.environ.get("NO_JUNGLE", "0") == "1" PANDAS_EXCLUDE = os.getenv("PANDAS_EXCLUDE", "").strip().split(" ") -PARTIAL_TESTS = os.environ.get("PARTIAL_TESTS", "0") == "1" HW_TYPES = os.environ.get("HW_TYPES", None) +PARALLEL = "PARALLEL" in os.environ +NON_PARALLEL = "NON_PARALLEL" in os.environ +if PARALLEL: + NO_JUNGLE = True + class PandaGroup: H7 = (Panda.HW_TYPE_RED_PANDA, Panda.HW_TYPE_RED_PANDA_V2, Panda.HW_TYPE_TRES) GEN2 = (Panda.HW_TYPE_BLACK_PANDA, Panda.HW_TYPE_UNO, Panda.HW_TYPE_DOS) + H7 @@ -43,7 +47,7 @@ def init_all_pandas(): for serial in Panda.list(): if serial not in PANDAS_EXCLUDE: - with Panda(serial=serial) as p: + with Panda(serial=serial, claim=False) as p: ptype = bytes(p.get_type()) if ptype in PandaGroup.TESTED: _all_pandas[serial] = ptype @@ -54,7 +58,7 @@ def init_all_pandas(): print(f"{len(_all_pandas)} total pandas") init_all_pandas() -_all_panda_serials = list(_all_pandas.keys()) +_all_panda_serials = list(sorted(_all_pandas.keys())) def init_jungle(): @@ -86,6 +90,7 @@ def pytest_configure(config): "markers", "expected_logs(amount, ...): mark test to expect a certain amount of panda logs" ) +@pytest.hookimpl(tryfirst=True) def pytest_collection_modifyitems(items): for item in items: if item.get_closest_marker('execution_timeout') is None: @@ -94,6 +99,16 @@ def pytest_collection_modifyitems(items): item.add_marker(pytest.mark.setup_timeout(20)) item.add_marker(pytest.mark.teardown_timeout(20)) + # xdist grouping by panda + serial = item.name.split("serial=")[1].split(",")[0] + assert len(serial) == 24 + item.add_marker(pytest.mark.xdist_group(serial)) + + needs_jungle = "panda_jungle" in item.fixturenames + if PARALLEL and needs_jungle: + item.add_marker(pytest.mark.skip(reason="no jungle tests in PARALLEL mode")) + elif NON_PARALLEL and not needs_jungle: + item.add_marker(pytest.mark.skip(reason="only running jungle tests")) def pytest_make_parametrize_id(config, val, argname): if val in _all_pandas: @@ -103,12 +118,12 @@ def pytest_make_parametrize_id(config, val, argname): return None -@pytest.fixture(name='panda_jungle') +@pytest.fixture(name='panda_jungle', scope='function') def fixture_panda_jungle(request): init_jungle() return _panda_jungle -@pytest.fixture(name='p') +@pytest.fixture(name='p', scope='function') def func_fixture_panda(request, module_panda): p = module_panda @@ -201,12 +216,6 @@ def fixture_panda_setup(request): # Initialize jungle init_jungle() - # wait for all pandas to come up - for _ in range(50): - if set(_all_panda_serials).issubset(set(Panda.list())): - break - time.sleep(0.1) - # Connect to pandas def cnnct(s): if s == panda_serial: @@ -221,7 +230,7 @@ def cnnct(s): clear_can_buffers(p) p.set_power_save(False) return p - else: + elif not PARALLEL: with Panda(serial=s) as p: p.reset(reconnect=False) return None diff --git a/tests/hitl/run_parallel_tests.sh b/tests/hitl/run_parallel_tests.sh new file mode 100755 index 0000000000..b6b79d99f6 --- /dev/null +++ b/tests/hitl/run_parallel_tests.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -e + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +cd $DIR + +# n = number of pandas tested +PARALLEL=1 pytest --durations=0 *.py -n 5 --dist loadgroup -x diff --git a/tests/hitl/test.sh b/tests/hitl/run_serial_tests.sh similarity index 67% rename from tests/hitl/test.sh rename to tests/hitl/run_serial_tests.sh index 7382ac5988..31270f044c 100755 --- a/tests/hitl/test.sh +++ b/tests/hitl/run_serial_tests.sh @@ -4,4 +4,4 @@ set -e DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" cd $DIR -pytest --durations=0 --maxfail=1 *.py +NON_PARALLEL=1 pytest --durations=0 *.py -x From 972b0dca9477c3da6f787478401d58e26e7db843 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 6 Aug 2023 23:06:26 -0700 Subject: [PATCH 180/197] don't try to reflash bootstub when reconnecting --- python/__init__.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/python/__init__.py b/python/__init__.py index cb0b0fb795..e1b3a471e6 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -472,11 +472,7 @@ def reconnect(self): success = True break except Exception: - try: - dfu = PandaDFU(self.get_dfu_serial()) - dfu.recover() - except Exception: - pass + pass time.sleep(0.1) if not success: raise Exception("reconnect failed") From 5801582baf5022a3d3b91316a566bf3650583f38 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 7 Aug 2023 09:29:34 -0700 Subject: [PATCH 181/197] Subaru: convert measured angle to centigrees (#1567) convert to centigrees --- board/safety/safety_subaru.h | 3 ++- tests/safety/test_subaru.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/board/safety/safety_subaru.h b/board/safety/safety_subaru.h index 438a0fba9e..2304768986 100644 --- a/board/safety/safety_subaru.h +++ b/board/safety/safety_subaru.h @@ -108,7 +108,8 @@ static int subaru_rx_hook(CANPacket_t *to_push) { update_sample(&torque_driver, torque_driver_new); int angle_meas_new = (GET_BYTES(to_push, 4, 2) & 0xFFFFU); - angle_meas_new = ROUND(to_signed(angle_meas_new, 16)); + // convert Steering_Torque -> Steering_Angle to centidegrees, to match the ES_LKAS_ANGLE angle request units + angle_meas_new = ROUND(to_signed(angle_meas_new, 16) * 2.17); update_sample(&angle_meas, angle_meas_new); } diff --git a/tests/safety/test_subaru.py b/tests/safety/test_subaru.py index 17cfc51171..7cc2bdf8f8 100755 --- a/tests/safety/test_subaru.py +++ b/tests/safety/test_subaru.py @@ -52,7 +52,7 @@ class TestSubaruSafetyBase(common.PandaSafetyTest, common.DriverTorqueSteeringSa ALT_BUS = SUBARU_MAIN_BUS - DEG_TO_CAN = -46.08 + DEG_TO_CAN = -100 def setUp(self): self.packer = CANPackerPanda("subaru_global_2017_generated") From a651fe3c6bf7a4082644fb07e71b65db1afbae60 Mon Sep 17 00:00:00 2001 From: Igor Biletskyy Date: Mon, 7 Aug 2023 10:54:39 -0700 Subject: [PATCH 182/197] set OBD to False after test (#1568) * reset OBD after test * add comment --- tests/hitl/4_can_loopback.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/hitl/4_can_loopback.py b/tests/hitl/4_can_loopback.py index 4284b588ea..28986f58b8 100644 --- a/tests/hitl/4_can_loopback.py +++ b/tests/hitl/4_can_loopback.py @@ -135,6 +135,10 @@ def test(p_send, p_recv, address=None): test(p, panda_jungle, 0x18DB33F1) test(panda_jungle, p, 0x18DB33F1) + # TODO: why it's not being reset by fixtures reinit? + p.set_obd(False) + panda_jungle.set_obd(False) + def test_bulk_write(p, panda_jungle): # The TX buffers on pandas is 0x100 in length. NUM_MESSAGES_PER_BUS = 10000 From 83c000e1a4e795b2d642eb776f2057a236cbf02d Mon Sep 17 00:00:00 2001 From: Igor Biletskyy Date: Mon, 7 Aug 2023 12:24:16 -0700 Subject: [PATCH 183/197] panda: disable transceivers while doing CAN multiplexing (#1556) init --- board/boards/black.h | 7 +++++- board/boards/dos.h | 6 +++++- board/boards/red.h | 4 ++++ board/boards/red_chiplet.h | 44 +++++++++++++++++++++++++++++++++++++- board/boards/red_v2.h | 2 +- board/boards/tres.h | 2 +- board/boards/uno.h | 6 +++++- 7 files changed, 65 insertions(+), 6 deletions(-) diff --git a/board/boards/black.h b/board/boards/black.h index d6e6bec8e1..80b265394e 100644 --- a/board/boards/black.h +++ b/board/boards/black.h @@ -53,7 +53,9 @@ void black_set_usb_load_switch(bool enabled) { set_gpio_output(GPIOB, 1, !enabled); } -void black_set_can_mode(uint8_t mode){ +void black_set_can_mode(uint8_t mode) { + black_enable_can_transceiver(2U, false); + black_enable_can_transceiver(4U, false); switch (mode) { case CAN_MODE_NORMAL: case CAN_MODE_OBD_CAN2: @@ -65,6 +67,8 @@ void black_set_can_mode(uint8_t mode){ // B5,B6: normal CAN2 mode set_gpio_alternate(GPIOB, 5, GPIO_AF9_CAN2); set_gpio_alternate(GPIOB, 6, GPIO_AF9_CAN2); + black_enable_can_transceiver(2U, true); + } else { // B5,B6: disable normal CAN2 mode set_gpio_mode(GPIOB, 5, MODE_INPUT); @@ -73,6 +77,7 @@ void black_set_can_mode(uint8_t mode){ // B12,B13: OBD mode set_gpio_alternate(GPIOB, 12, GPIO_AF9_CAN2); set_gpio_alternate(GPIOB, 13, GPIO_AF9_CAN2); + black_enable_can_transceiver(4U, true); } break; default: diff --git a/board/boards/dos.h b/board/boards/dos.h index a5c2e4bcea..7e0a270036 100644 --- a/board/boards/dos.h +++ b/board/boards/dos.h @@ -68,7 +68,9 @@ bool dos_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen, bool harn return ret; } -void dos_set_can_mode(uint8_t mode){ +void dos_set_can_mode(uint8_t mode) { + dos_enable_can_transceiver(2U, false); + dos_enable_can_transceiver(4U, false); switch (mode) { case CAN_MODE_NORMAL: case CAN_MODE_OBD_CAN2: @@ -80,6 +82,7 @@ void dos_set_can_mode(uint8_t mode){ // B5,B6: normal CAN2 mode set_gpio_alternate(GPIOB, 5, GPIO_AF9_CAN2); set_gpio_alternate(GPIOB, 6, GPIO_AF9_CAN2); + dos_enable_can_transceiver(2U, true); } else { // B5,B6: disable normal CAN2 mode set_gpio_mode(GPIOB, 5, MODE_INPUT); @@ -88,6 +91,7 @@ void dos_set_can_mode(uint8_t mode){ // B12,B13: OBD mode set_gpio_alternate(GPIOB, 12, GPIO_AF9_CAN2); set_gpio_alternate(GPIOB, 13, GPIO_AF9_CAN2); + dos_enable_can_transceiver(4U, true); } break; default: diff --git a/board/boards/red.h b/board/boards/red.h index 43e71bdf94..987554698e 100644 --- a/board/boards/red.h +++ b/board/boards/red.h @@ -50,6 +50,8 @@ void red_set_led(uint8_t color, bool enabled) { } void red_set_can_mode(uint8_t mode) { + red_enable_can_transceiver(2U, false); + red_enable_can_transceiver(4U, false); switch (mode) { case CAN_MODE_NORMAL: case CAN_MODE_OBD_CAN2: @@ -67,6 +69,7 @@ void red_set_can_mode(uint8_t mode) { set_gpio_pullup(GPIOB, 6, PULL_NONE); set_gpio_alternate(GPIOB, 6, GPIO_AF9_FDCAN2); + red_enable_can_transceiver(2U, true); } else { // B5,B6: disable normal mode set_gpio_pullup(GPIOB, 5, PULL_NONE); @@ -80,6 +83,7 @@ void red_set_can_mode(uint8_t mode) { set_gpio_pullup(GPIOB, 13, PULL_NONE); set_gpio_alternate(GPIOB, 13, GPIO_AF9_FDCAN2); + red_enable_can_transceiver(4U, true); } break; default: diff --git a/board/boards/red_chiplet.h b/board/boards/red_chiplet.h index 8a3f8dbd98..1301cd3e54 100644 --- a/board/boards/red_chiplet.h +++ b/board/boards/red_chiplet.h @@ -35,6 +35,48 @@ void red_chiplet_enable_can_transceivers(bool enabled) { } } +void red_chiplet_set_can_mode(uint8_t mode) { + red_chiplet_enable_can_transceiver(2U, false); + red_chiplet_enable_can_transceiver(4U, false); + switch (mode) { + case CAN_MODE_NORMAL: + case CAN_MODE_OBD_CAN2: + if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(harness.status == HARNESS_STATUS_FLIPPED)) { + // B12,B13: disable normal mode + set_gpio_pullup(GPIOB, 12, PULL_NONE); + set_gpio_mode(GPIOB, 12, MODE_ANALOG); + + set_gpio_pullup(GPIOB, 13, PULL_NONE); + set_gpio_mode(GPIOB, 13, MODE_ANALOG); + + // B5,B6: FDCAN2 mode + set_gpio_pullup(GPIOB, 5, PULL_NONE); + set_gpio_alternate(GPIOB, 5, GPIO_AF9_FDCAN2); + + set_gpio_pullup(GPIOB, 6, PULL_NONE); + set_gpio_alternate(GPIOB, 6, GPIO_AF9_FDCAN2); + red_chiplet_enable_can_transceiver(2U, true); + } else { + // B5,B6: disable normal mode + set_gpio_pullup(GPIOB, 5, PULL_NONE); + set_gpio_mode(GPIOB, 5, MODE_ANALOG); + + set_gpio_pullup(GPIOB, 6, PULL_NONE); + set_gpio_mode(GPIOB, 6, MODE_ANALOG); + // B12,B13: FDCAN2 mode + set_gpio_pullup(GPIOB, 12, PULL_NONE); + set_gpio_alternate(GPIOB, 12, GPIO_AF9_FDCAN2); + + set_gpio_pullup(GPIOB, 13, PULL_NONE); + set_gpio_alternate(GPIOB, 13, GPIO_AF9_FDCAN2); + red_chiplet_enable_can_transceiver(4U, true); + } + break; + default: + break; + } +} + void red_chiplet_set_fan_or_usb_load_switch(bool enabled) { set_gpio_output(GPIOD, 3, enabled); } @@ -89,7 +131,7 @@ void red_chiplet_init(void) { red_set_led(LED_BLUE, false); // Set normal CAN mode - red_set_can_mode(CAN_MODE_NORMAL); + red_chiplet_set_can_mode(CAN_MODE_NORMAL); // flip CAN0 and CAN2 if we are flipped if (harness.status == HARNESS_STATUS_FLIPPED) { diff --git a/board/boards/red_v2.h b/board/boards/red_v2.h index 4f0a2cb622..df663032b0 100644 --- a/board/boards/red_v2.h +++ b/board/boards/red_v2.h @@ -29,7 +29,7 @@ const board board_red_v2 = { .enable_can_transceiver = red_chiplet_enable_can_transceiver, .enable_can_transceivers = red_chiplet_enable_can_transceivers, .set_led = red_set_led, - .set_can_mode = red_set_can_mode, + .set_can_mode = red_chiplet_set_can_mode, .check_ignition = red_check_ignition, .read_current = unused_read_current, .set_fan_enabled = unused_set_fan_enabled, diff --git a/board/boards/tres.h b/board/boards/tres.h index 0b9b94a682..c1f1f9ec22 100644 --- a/board/boards/tres.h +++ b/board/boards/tres.h @@ -104,7 +104,7 @@ const board board_tres = { .enable_can_transceiver = red_chiplet_enable_can_transceiver, .enable_can_transceivers = red_chiplet_enable_can_transceivers, .set_led = red_set_led, - .set_can_mode = red_set_can_mode, + .set_can_mode = red_chiplet_set_can_mode, .check_ignition = red_check_ignition, .read_current = unused_read_current, .set_fan_enabled = tres_set_fan_enabled, diff --git a/board/boards/uno.h b/board/boards/uno.h index e0dc0d2c5f..27c2e40297 100644 --- a/board/boards/uno.h +++ b/board/boards/uno.h @@ -69,7 +69,9 @@ void uno_set_phone_power(bool enabled){ set_gpio_output(GPIOB, 4, enabled); } -void uno_set_can_mode(uint8_t mode){ +void uno_set_can_mode(uint8_t mode) { + uno_enable_can_transceiver(2U, false); + uno_enable_can_transceiver(4U, false); switch (mode) { case CAN_MODE_NORMAL: case CAN_MODE_OBD_CAN2: @@ -81,6 +83,7 @@ void uno_set_can_mode(uint8_t mode){ // B5,B6: normal CAN2 mode set_gpio_alternate(GPIOB, 5, GPIO_AF9_CAN2); set_gpio_alternate(GPIOB, 6, GPIO_AF9_CAN2); + uno_enable_can_transceiver(2U, true); } else { // B5,B6: disable normal CAN2 mode set_gpio_mode(GPIOB, 5, MODE_INPUT); @@ -89,6 +92,7 @@ void uno_set_can_mode(uint8_t mode){ // B12,B13: OBD mode set_gpio_alternate(GPIOB, 12, GPIO_AF9_CAN2); set_gpio_alternate(GPIOB, 13, GPIO_AF9_CAN2); + uno_enable_can_transceiver(4U, true); } break; default: From 61e987f6e280f2b84b6f5ccffaca91ae9635f411 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 7 Aug 2023 18:38:01 -0700 Subject: [PATCH 184/197] remove safety replay test (#1569) --- .github/workflows/test.yaml | 17 ------- tests/safety_replay/test_safety_replay.py | 59 ----------------------- 2 files changed, 76 deletions(-) delete mode 100755 tests/safety_replay/test_safety_replay.py diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index cd18ec35da..89da1bb44c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -87,23 +87,6 @@ jobs: scons -j$(nproc) ${{ matrix.flags }} && \ tests/safety/test.sh" - safety_replay: - name: safety replay - runs-on: ubuntu-20.04 - strategy: - matrix: - flags: ['', '--ubsan'] - timeout-minutes: 20 - steps: - - uses: actions/checkout@v2 - - name: Build Docker image - run: eval "$BUILD" - - name: Run safety replay - run: | - ${{ env.RUN }} "scons -j$(nproc) ${{ matrix.flags }} && \ - cd tests/safety_replay && \ - ./test_safety_replay.py" - misra: name: misra c2012 runs-on: ubuntu-20.04 diff --git a/tests/safety_replay/test_safety_replay.py b/tests/safety_replay/test_safety_replay.py deleted file mode 100755 index 2dcc07cd1a..0000000000 --- a/tests/safety_replay/test_safety_replay.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python3 -from collections import namedtuple -import os -import requests - -from panda import Panda -from panda.python import ALTERNATIVE_EXPERIENCE as ALT_EXP -from panda.tests.safety_replay.replay_drive import replay_drive -from tools.lib.logreader import LogReader # pylint: disable=import-error - -BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/" - -ReplayRoute = namedtuple("ReplayRoute", ("route", "safety_mode", "param", "alternative_experience"), defaults=(0, 0)) - -logs = [ - ReplayRoute("2425568437959f9d|2019-12-22--16-24-37.bz2", Panda.SAFETY_HONDA_NIDEC), # HONDA.CIVIC (fcw presents: 0x1FA blocked as expected) - ReplayRoute("38bfd238edecbcd7|2019-06-07--10-15-25.bz2", Panda.SAFETY_TOYOTA, 66), # TOYOTA.PRIUS - ReplayRoute("f89c604cf653e2bf|2018-09-29--13-46-50.bz2", Panda.SAFETY_GM), # GM.VOLT - ReplayRoute("6fb4948a7ebe670e|2019-11-12--00-35-53.bz2", Panda.SAFETY_CHRYSLER), # CHRYSLER.PACIFICA_2018_HYBRID - ReplayRoute("0744286ead2fbb96|2023-05-01--16-27-01--35--rlog.bz2", Panda.SAFETY_SUBARU), # SUBARU.IMPREZA - ReplayRoute("bad6ae3584ece5b5|2023-04-29--11-23-48--7--rlog.bz2", Panda.SAFETY_SUBARU, # SUBARU.OUTBACK - Panda.FLAG_SUBARU_GEN2, ALT_EXP.DISABLE_DISENGAGE_ON_GAS), - ReplayRoute("76b83eb0245de90e|2020-03-05--19-16-05.bz2", Panda.SAFETY_VOLKSWAGEN_MQB), # VOLKSWAGEN.GOLF (stock ACC) - ReplayRoute("3cfdec54aa035f3f|2022-10-13--14-58-58.bz2", Panda.SAFETY_VOLKSWAGEN_MQB, # VOLKSWAGEN.GOLF (openpilot long) - Panda.FLAG_VOLKSWAGEN_LONG_CONTROL), - ReplayRoute("3cfdec54aa035f3f|2022-07-19--23-45-10.bz2", Panda.SAFETY_VOLKSWAGEN_PQ, # VOLKSWAGEN.PASSAT_NMS (openpilot longitudinal) - Panda.FLAG_VOLKSWAGEN_LONG_CONTROL), - ReplayRoute("fbbfa6af821552b9|2020-03-03--08-09-43.bz2", Panda.SAFETY_NISSAN), # NISSAN.XTRAIL - ReplayRoute("5b7c365c50084530_2020-04-15--16-13-24--3--rlog.bz2", Panda.SAFETY_HYUNDAI), # HYUNDAI.SONATA - ReplayRoute("610ebb9faaad6b43|2020-06-13--15-28-36.bz2", Panda.SAFETY_HYUNDAI_LEGACY), # HYUNDAI.IONIQ_EV_LTD - ReplayRoute("5ab784f361e19b78_2020-06-08--16-30-41.bz2", Panda.SAFETY_SUBARU_PREGLOBAL), # SUBARU.OUTBACK_PREGLOBAL - ReplayRoute("bb50caf5f0945ab1|2021-06-19--17-20-18.bz2", Panda.SAFETY_TESLA), # TESLA.AP2_MODELS - ReplayRoute("bd6a637565e91581_2021-10-29--22-18-31--1--rlog.bz2", Panda.SAFETY_MAZDA), # MAZDA.CX9_2021 - ReplayRoute("1a5d045d2c531a6d_2022-06-07--22-03-00--1--rlog.bz2", Panda.SAFETY_HONDA_BOSCH, # HONDA.CIVIC_2022 - Panda.FLAG_HONDA_RADARLESS, ALT_EXP.DISABLE_DISENGAGE_ON_GAS), -] - - -if __name__ == "__main__": - - # get all the routes - for route, _, _, _ in logs: - if not os.path.isfile(route): - with open(route, "wb") as f: - r = requests.get(BASE_URL + route, timeout=10) - r.raise_for_status() - f.write(r.content) - - failed = [] - for route, mode, param, alt_exp in logs: - lr = LogReader(route, sort_by_time=True) - - print("\nreplaying %s with safety mode %d, param %s, alternative experience %s" % (route, mode, param, alt_exp)) - if not replay_drive(lr, mode, param, alt_exp): - failed.append(route) - - for f in failed: - print(f"\n**** failed on {f} ****") - assert len(failed) == 0, "\nfailed on %d logs" % len(failed) From 413f430f4e1de7d6ff712dfdcff6823ba8f0243f Mon Sep 17 00:00:00 2001 From: Igor Biletskyy Date: Thu, 10 Aug 2023 16:25:56 -0700 Subject: [PATCH 185/197] jungle: add back set_can_sinent() (#1571) init --- board/jungle/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/board/jungle/__init__.py b/board/jungle/__init__.py index c6e005a592..9d7790c747 100644 --- a/board/jungle/__init__.py +++ b/board/jungle/__init__.py @@ -138,6 +138,9 @@ def set_harness_orientation(self, mode): def set_ignition(self, enabled): self._handle.controlWrite(PandaJungle.REQUEST_OUT, 0xa2, int(enabled), 0, b'') + def set_can_silent(self, silent): + self._handle.controlWrite(PandaJungle.REQUEST_OUT, 0xf5, int(silent), 0, b'') + # ******************* serial ******************* def debug_read(self): From 9df7802f71d68ad89588b699b6a367f6cc9bd90f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 10 Aug 2023 16:36:00 -0700 Subject: [PATCH 186/197] CI: bump up safety test timeout --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 89da1bb44c..c1be2c832c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -78,7 +78,7 @@ jobs: - name: Build Docker image run: eval "$BUILD" - name: Run safety tests - timeout-minutes: 4 + timeout-minutes: 5 run: | ${{ env.RUN }} "cd .. && \ scons -c && \ From 25f1444d73e4bb61e0b5aab04bef478fc14984d4 Mon Sep 17 00:00:00 2001 From: Igor Biletskyy Date: Thu, 10 Aug 2023 18:41:50 -0700 Subject: [PATCH 187/197] panda: add CAN-FD throughput test (#1572) init --- tests/canfd/test_canfd.py | 46 +++++++++++++++++++++++++++++++++------ tests/hitl/helpers.py | 6 ++--- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/tests/canfd/test_canfd.py b/tests/canfd/test_canfd.py index ad7082e19a..873bc796ba 100755 --- a/tests/canfd/test_canfd.py +++ b/tests/canfd/test_canfd.py @@ -3,8 +3,9 @@ import time import random from collections import defaultdict -from panda import Panda, calculate_checksum +from panda import Panda, calculate_checksum, DLC_TO_LEN from panda import PandaJungle +from panda.tests.hitl.helpers import time_many_sends H7_HW_TYPES = [Panda.HW_TYPE_RED_PANDA, Panda.HW_TYPE_RED_PANDA_V2] JUNGLE_SERIAL = os.getenv("JUNGLE") @@ -12,13 +13,11 @@ if os.getenv("H7_PANDAS_EXCLUDE"): H7_PANDAS_EXCLUDE = os.getenv("H7_PANDAS_EXCLUDE").strip().split(" ") # type: ignore -#TODO: REMOVE, temporary list of CAN FD lengths, one in panda python lib MUST be used -DLC_TO_LEN = [0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48] - def panda_reset(): panda_serials = [] panda_jungle = PandaJungle(JUNGLE_SERIAL) + panda_jungle.set_can_silent(True) panda_jungle.set_panda_power(False) time.sleep(1) panda_jungle.set_panda_power(True) @@ -40,6 +39,7 @@ def panda_init(serial, enable_canfd=False, enable_non_iso=False): p = Panda(serial=serial) p.set_power_save(False) for bus in range(3): + p.set_can_speed_kbps(0, 500) if enable_canfd: p.set_can_data_speed_kbps(bus, 2000) if enable_non_iso: @@ -47,6 +47,34 @@ def panda_init(serial, enable_canfd=False, enable_non_iso=False): p.set_safety_mode(Panda.SAFETY_ALLOUTPUT) return p +def test_canfd_throughput(p, p_recv=None): + two_pandas = p_recv is not None + p.set_safety_mode(Panda.SAFETY_ALLOUTPUT) + if two_pandas: + p_recv.set_safety_mode(Panda.SAFETY_ALLOUTPUT) + # enable output mode + else: + p.set_can_loopback(True) + + tests = [ + [500, 1000, 2000], # speeds + [93, 87, 78], # saturation thresholds + ] + + for i in range(len(tests[0])): + # set bus 0 data speed to speed + p.set_can_data_speed_kbps(0, tests[0][i]) + if p_recv is not None: + p_recv.set_can_data_speed_kbps(0, tests[0][i]) + time.sleep(0.05) + + comp_kbps = time_many_sends(p, 0, p_recv=p_recv, msg_count=400, two_pandas=two_pandas, msg_len=64) + + # bit count from https://en.wikipedia.org/wiki/CAN_bus + saturation_pct = (comp_kbps / tests[0][i]) * 100.0 + assert saturation_pct > tests[1][i] + assert saturation_pct < 100 + def canfd_test(p_send, p_recv): for n in range(100): sent_msgs = defaultdict(set) @@ -77,9 +105,6 @@ def canfd_test(p_send, p_recv): for bus in range(3): assert not len(sent_msgs[bus]), f"loop {n}: bus {bus} missing {len(sent_msgs[bus])} messages" - print("Got all messages intact") - - def setup_test(enable_non_iso=False): panda_serials = panda_reset() @@ -97,6 +122,7 @@ def setup_test(enable_non_iso=False): for bus in range(3): p_recv.can_send(0x200, b"dummymessage", bus) p_recv.can_recv() + p_send.can_recv() # Check if all tested buses on sending panda have swithed to CAN FD with BRS for bus in range(3): @@ -116,5 +142,11 @@ def main(): p_send, p_recv = setup_test(enable_non_iso=True) canfd_test(p_send, p_recv) + print("[TEST CAN-FD THROUGHPUT]") + panda_serials = panda_reset() + p_send = panda_init(panda_serials[0], enable_canfd=True) + p_recv = panda_init(panda_serials[1], enable_canfd=True) + test_canfd_throughput(p_send, p_recv) + if __name__ == "__main__": main() diff --git a/tests/hitl/helpers.py b/tests/hitl/helpers.py index b75e187457..d7ac4da0d6 100644 --- a/tests/hitl/helpers.py +++ b/tests/hitl/helpers.py @@ -13,14 +13,14 @@ def get_random_can_messages(n): return m -def time_many_sends(p, bus, p_recv=None, msg_count=100, two_pandas=False): +def time_many_sends(p, bus, p_recv=None, msg_count=100, two_pandas=False, msg_len=8): if p_recv is None: p_recv = p if p == p_recv and two_pandas: raise ValueError("Cannot have two pandas that are the same panda") msg_id = random.randint(0x100, 0x200) - to_send = [(msg_id, 0, b"\xaa" * 8, bus)] * msg_count + to_send = [(msg_id, 0, b"\xaa" * msg_len, bus)] * msg_count start_time = time.monotonic() p.can_send_many(to_send) @@ -47,7 +47,7 @@ def time_many_sends(p, bus, p_recv=None, msg_count=100, two_pandas=False): assert len(sent_echo) == msg_count end_time = (end_time - start_time) * 1000.0 - comp_kbps = (1 + 11 + 1 + 1 + 1 + 4 + 8 * 8 + 15 + 1 + 1 + 1 + 7) * msg_count / end_time + comp_kbps = (1 + 11 + 1 + 1 + 1 + 4 + (msg_len * 8) + 15 + 1 + 1 + 1 + 7) * msg_count / end_time return comp_kbps From 61590b681ae40582d8a02c9287361f6368044917 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 10 Aug 2023 23:49:28 -0700 Subject: [PATCH 188/197] GM: clean up longitudinal safety test (#1573) * clean up gm long safety test * move to long base * common brake --- tests/safety/test_gm.py | 55 +++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/tests/safety/test_gm.py b/tests/safety/test_gm.py index f6b604ad31..f3adc55cb8 100755 --- a/tests/safety/test_gm.py +++ b/tests/safety/test_gm.py @@ -17,6 +17,11 @@ class Buttons: class GmLongitudinalBase(common.PandaSafetyTest): # pylint: disable=no-member,abstract-method + MAX_GAS = 0 + MAX_REGEN = 0 + INACTIVE_REGEN = 0 + MAX_BRAKE = 400 + PCM_CRUISE = False # openpilot can control the PCM state if longitudinal def test_set_resume_buttons(self): @@ -41,6 +46,24 @@ def test_cancel_button(self): self._rx(self._button_msg(Buttons.CANCEL)) self.assertFalse(self.safety.get_controls_allowed()) + def test_brake_safety_check(self): + for enabled in [0, 1]: + for b in range(0, 500): + self.safety.set_controls_allowed(enabled) + if abs(b) > self.MAX_BRAKE or (not enabled and b != 0): + self.assertFalse(self._tx(self._send_brake_msg(b))) + else: + self.assertTrue(self._tx(self._send_brake_msg(b))) + + def test_gas_safety_check(self): + # Block if enabled and out of actuation range, disabled and not inactive regen, or if stock longitudinal + for enabled in [0, 1]: + for gas_regen in range(0, 2 ** 12 - 1): + self.safety.set_controls_allowed(enabled) + should_tx = ((enabled and self.MAX_REGEN <= gas_regen <= self.MAX_GAS) or + gas_regen == self.INACTIVE_REGEN) + self.assertEqual(should_tx, self._tx(self._send_gas_msg(gas_regen)), (enabled, gas_regen)) + # override these tests from PandaSafetyTest, GM longitudinal uses button enable def test_disable_control_allowed_from_cruise(self): pass @@ -70,11 +93,6 @@ class TestGmSafetyBase(common.PandaSafetyTest, common.DriverTorqueSteeringSafety DRIVER_TORQUE_ALLOWANCE = 65 DRIVER_TORQUE_FACTOR = 4 - MAX_GAS = 0 - MAX_REGEN = 0 - INACTIVE_REGEN = 0 - MAX_BRAKE = 0 - PCM_CRUISE = True # openpilot is tied to the PCM state if not longitudinal @classmethod @@ -138,24 +156,6 @@ def _button_msg(self, buttons): values = {"ACCButtons": buttons} return self.packer.make_can_msg_panda("ASCMSteeringButton", self.BUTTONS_BUS, values) - def test_brake_safety_check(self): - for enabled in [0, 1]: - for b in range(0, 500): - self.safety.set_controls_allowed(enabled) - if abs(b) > self.MAX_BRAKE or (not enabled and b != 0): - self.assertFalse(self._tx(self._send_brake_msg(b))) - else: - self.assertTrue(self._tx(self._send_brake_msg(b))) - - def test_gas_safety_check(self): - # Block if enabled and out of actuation range, disabled and not inactive regen, or if stock longitudinal - for enabled in [0, 1]: - for gas_regen in range(0, 2 ** 12 - 1): - self.safety.set_controls_allowed(enabled) - should_tx = ((enabled and self.MAX_REGEN <= gas_regen <= self.MAX_GAS) or - gas_regen == self.INACTIVE_REGEN) - self.assertEqual(should_tx, self._tx(self._send_gas_msg(gas_regen)), (enabled, gas_regen)) - class TestGmAscmSafety(GmLongitudinalBase, TestGmSafetyBase): TX_MSGS = [[384, 0], [1033, 0], [1034, 0], [715, 0], [880, 0], # pt bus @@ -169,7 +169,6 @@ class TestGmAscmSafety(GmLongitudinalBase, TestGmSafetyBase): MAX_GAS = 3072 MAX_REGEN = 1404 INACTIVE_REGEN = 1404 - MAX_BRAKE = 400 def setUp(self): self.packer = CANPackerPanda("gm_global_a_powertrain_generated") @@ -222,13 +221,6 @@ def test_buttons(self): self._rx(self._pcm_status_msg(enabled)) self.assertEqual(enabled, self._tx(self._button_msg(Buttons.CANCEL))) - # GM Cam safety mode does not allow longitudinal messages - def test_brake_safety_check(self): - pass - - def test_gas_safety_check(self): - pass - class TestGmCameraLongitudinalSafety(GmLongitudinalBase, TestGmCameraSafetyBase): TX_MSGS = [[384, 0], [789, 0], [715, 0], [880, 0], # pt bus @@ -239,7 +231,6 @@ class TestGmCameraLongitudinalSafety(GmLongitudinalBase, TestGmCameraSafetyBase) MAX_GAS = 3400 MAX_REGEN = 1514 INACTIVE_REGEN = 1554 - MAX_BRAKE = 400 def setUp(self): self.packer = CANPackerPanda("gm_global_a_powertrain_generated") From 20b1e505c35d7f0caeb9cf51aba7534f093f9ac4 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 10 Aug 2023 23:59:58 -0700 Subject: [PATCH 189/197] GM: move more longitudinal related function into class (#1574) move these into the long test --- tests/safety/test_gm.py | 42 ++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/safety/test_gm.py b/tests/safety/test_gm.py index f3adc55cb8..8b84d1cb4e 100755 --- a/tests/safety/test_gm.py +++ b/tests/safety/test_gm.py @@ -24,6 +24,27 @@ class GmLongitudinalBase(common.PandaSafetyTest): PCM_CRUISE = False # openpilot can control the PCM state if longitudinal + def _send_brake_msg(self, brake): + values = {"FrictionBrakeCmd": -brake} + return self.packer_chassis.make_can_msg_panda("EBCMFrictionBrakeCmd", self.BRAKE_BUS, values) + + def _send_gas_msg(self, gas): + values = {"GasRegenCmd": gas} + return self.packer.make_can_msg_panda("ASCMGasRegenCmd", 0, values) + + # override these tests from PandaSafetyTest, GM longitudinal uses button enable + def _pcm_status_msg(self, enable): + raise NotImplementedError + + def test_disable_control_allowed_from_cruise(self): + pass + + def test_enable_control_allowed_from_cruise(self): + pass + + def test_cruise_engaged_prev(self): + pass + def test_set_resume_buttons(self): """ SET and RESUME enter controls allowed on their falling and rising edges, respectively. @@ -64,19 +85,6 @@ def test_gas_safety_check(self): gas_regen == self.INACTIVE_REGEN) self.assertEqual(should_tx, self._tx(self._send_gas_msg(gas_regen)), (enabled, gas_regen)) - # override these tests from PandaSafetyTest, GM longitudinal uses button enable - def test_disable_control_allowed_from_cruise(self): - pass - - def test_enable_control_allowed_from_cruise(self): - pass - - def test_cruise_engaged_prev(self): - pass - - def _pcm_status_msg(self, enable): - pass - class TestGmSafetyBase(common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest): STANDSTILL_THRESHOLD = 10 * 0.0311 @@ -136,14 +144,6 @@ def _user_gas_msg(self, gas): values["CruiseState"] = self.safety.get_controls_allowed() return self.packer.make_can_msg_panda("AcceleratorPedal2", 0, values) - def _send_brake_msg(self, brake): - values = {"FrictionBrakeCmd": -brake} - return self.packer_chassis.make_can_msg_panda("EBCMFrictionBrakeCmd", self.BRAKE_BUS, values) - - def _send_gas_msg(self, gas): - values = {"GasRegenCmd": gas} - return self.packer.make_can_msg_panda("ASCMGasRegenCmd", 0, values) - def _torque_driver_msg(self, torque): values = {"LKADriverAppldTrq": torque} return self.packer.make_can_msg_panda("PSCMStatus", 0, values) From f29c753ba445615f537e58f0afc20a18e137ef0c Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Sat, 12 Aug 2023 13:51:26 -0700 Subject: [PATCH 190/197] Safety: generic limit safety check (#1575) * generic limit safety check * fix messagefunction --- tests/safety/common.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/tests/safety/common.py b/tests/safety/common.py index 0bec019fb1..24d5a593f0 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -3,7 +3,7 @@ import unittest import importlib import numpy as np -from typing import Dict, List, Optional +from typing import Callable, Dict, List, Optional from opendbc.can.packer import CANPacker # pylint: disable=import-error from panda import ALTERNATIVE_EXPERIENCE @@ -12,6 +12,7 @@ MAX_WRONG_COUNTERS = 5 VEHICLE_SPEED_FACTOR = 100 +MessageFunction = Callable[[float], libpanda_py.CANPacket] def sign_of(a): return 1 if a > 0 else -1 @@ -53,6 +54,8 @@ def _regen_test(self): class PandaSafetyTestBase(unittest.TestCase): + safety: libpanda_py.Panda + @classmethod def setUpClass(cls): if cls.__name__ == "PandaSafetyTestBase": @@ -69,6 +72,32 @@ def _rx(self, msg): def _tx(self, msg): return self.safety.safety_tx_hook(msg) + def _generic_limit_safety_check(self, msg_function: MessageFunction, min_allowed_value: float, max_allowed_value: float, + min_possible_value: float, max_possible_value: float, test_delta: float = 1, inactive_value: float = 0, + msg_allowed = True, additional_setup: Optional[Callable[[float], None]] = None): + """ + Enforces that a signal within a message is only allowed to be sent within a specific range, min_allowed_value -> max_allowed_value. + Tests the range of min_possible_value -> max_possible_value with a delta of test_delta. + Message is also only allowed to be sent when controls_allowed is true, unless the value is equal to inactive_value. + Message is never allowed if msg_allowed is false, for example when stock longitudinal is enabled and you are sending acceleration requests. + additional_setup is used for extra setup before each _tx, ex: for setting the previous torque for rate limits + """ + + # Ensure that we at least test the allowed_value range + self.assertGreater(max_possible_value, max_allowed_value) + self.assertLessEqual(min_possible_value, min_allowed_value) + + for controls_allowed in [False, True]: + # enforce we don't skip over 0 or inactive + for v in np.concatenate((np.arange(min_possible_value, max_possible_value, test_delta), np.array([0, inactive_value]))): + v = round(v, 2) # floats might not hit exact boundary conditions without rounding + self.safety.set_controls_allowed(controls_allowed) + if additional_setup is not None: + additional_setup(v) + should_tx = controls_allowed and min_allowed_value <= v <= max_allowed_value + should_tx = (should_tx or v == inactive_value) and msg_allowed + self.assertEqual(self._tx(msg_function(v)), should_tx, (controls_allowed, should_tx, v)) + class InterceptorSafetyTest(PandaSafetyTestBase): From 7dbdf6ba2bd551480397b13a45d302d95ac26a0e Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Sat, 12 Aug 2023 14:03:40 -0700 Subject: [PATCH 191/197] Safety Tests: Create common gas brake safety test (#1537) * create common gas brake safety test * remove unrelated subaru reference * also test below min_value * better name and hardcoded * use same as acceleration limits * revert gm * remove callable * added common brake test and moved generic test to base safety class * remove duplicate test * wip * wip * rename longitudinalaccelsafety * revert limits correct test * fix controls allowed test * move gm over to long gas brake test * assert that limits are reasonable * fix typing * fix linter again * fix linter again * fix linter again * like to make it clear * typo * fix from merge * clearer names * dont need thesemore --------- Co-authored-by: Shane Smiskol --- tests/safety/common.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/safety/common.py b/tests/safety/common.py index 24d5a593f0..7724705cff 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -204,6 +204,39 @@ def test_accel_actuation_limits(self, stock_longitudinal=False): self.assertEqual(should_tx, self._tx(self._accel_msg(accel))) +class LongitudinalGasBrakeSafetyTest(PandaSafetyTestBase, abc.ABC): + + MIN_BRAKE: int = 0 + MAX_BRAKE: Optional[int] = None + MAX_POSSIBLE_BRAKE: Optional[int] = None + + MIN_GAS: int = 0 + MAX_GAS: Optional[int] = None + INACTIVE_GAS = 0 + MAX_POSSIBLE_GAS: Optional[int] = None + + def test_gas_brake_limits_correct(self): + self.assertIsNotNone(self.MAX_POSSIBLE_BRAKE) + self.assertIsNotNone(self.MAX_POSSIBLE_GAS) + + self.assertGreater(self.MAX_BRAKE, self.MIN_BRAKE) + self.assertGreater(self.MAX_GAS, self.MIN_GAS) + + @abc.abstractmethod + def _send_gas_msg(self, gas: int): + pass + + @abc.abstractmethod + def _send_brake_msg(self, brake: int): + pass + + def test_brake_safety_check(self): + self._generic_limit_safety_check(self._send_brake_msg, self.MIN_BRAKE, self.MAX_BRAKE, 0, self.MAX_POSSIBLE_BRAKE, 1) + + def test_gas_safety_check(self): + self._generic_limit_safety_check(self._send_gas_msg, self.MIN_GAS, self.MAX_GAS, 0, self.MAX_POSSIBLE_GAS, 1, self.INACTIVE_GAS) + + class TorqueSteeringSafetyTestBase(PandaSafetyTestBase, abc.ABC): MAX_RATE_UP = 0 From a0344a11e742ffb3658b2d1587d58f2836d66e2f Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Sat, 12 Aug 2023 17:16:28 -0700 Subject: [PATCH 192/197] Subaru: cancel safety (#1576) * subaru cancel safety * misra --- board/safety/safety_subaru.h | 8 ++++++++ tests/safety/test_subaru.py | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/board/safety/safety_subaru.h b/board/safety/safety_subaru.h index 2304768986..864d041d3a 100644 --- a/board/safety/safety_subaru.h +++ b/board/safety/safety_subaru.h @@ -166,6 +166,14 @@ static int subaru_tx_hook(CANPacket_t *to_send) { violation |= steer_torque_cmd_checks(desired_torque, -1, limits); } + // Only allow ES_Distance when Cruise_Cancel is true, and Cruise_Throttle is "inactive" (1818) + if (addr == MSG_SUBARU_ES_Distance){ + int cruise_throttle = (GET_BYTES(to_send, 2, 2) & 0xFFFU); + bool cruise_cancel = GET_BIT(to_send, 56U) != 0U; + violation |= (cruise_throttle != 1818); + violation |= (!cruise_cancel); + } + if (violation){ tx = 0; } diff --git a/tests/safety/test_subaru.py b/tests/safety/test_subaru.py index 7cc2bdf8f8..8e87cb08c1 100755 --- a/tests/safety/test_subaru.py +++ b/tests/safety/test_subaru.py @@ -4,6 +4,7 @@ from panda.tests.libpanda import libpanda_py import panda.tests.safety.common as common from panda.tests.safety.common import CANPackerPanda, MeasurementSafetyTest +from functools import partial MSG_SUBARU_Brake_Status = 0x13c @@ -54,6 +55,8 @@ class TestSubaruSafetyBase(common.PandaSafetyTest, common.DriverTorqueSteeringSa DEG_TO_CAN = -100 + INACTIVE_GAS = 1818 + def setUp(self): self.packer = CANPackerPanda("subaru_global_2017_generated") self.safety = libpanda_py.libpanda @@ -93,6 +96,15 @@ def _pcm_status_msg(self, enable): values = {"Cruise_Activated": enable} return self.packer.make_can_msg_panda("CruiseControl", self.ALT_BUS, values) + def _cancel_msg(self, cancel, cruise_throttle=0): + values = {"Cruise_Cancel": cancel, "Cruise_Throttle": cruise_throttle} + return self.packer.make_can_msg_panda("ES_Distance", self.ALT_BUS, values) + + def test_cancel_message(self): + # test that we can only send the cancel message (ES_Distance) with inactive throttle (1818) and Cruise_Cancel=1 + for cancel in [True, False]: + self._generic_limit_safety_check(partial(self._cancel_msg, cancel), self.INACTIVE_GAS, self.INACTIVE_GAS, 0, 2**12, 1, self.INACTIVE_GAS, cancel) + class TestSubaruGen2SafetyBase(TestSubaruSafetyBase): ALT_BUS = SUBARU_ALT_BUS From 05295dc42a846f18202443bfec793c790c8134e1 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Sat, 12 Aug 2023 19:34:53 -0700 Subject: [PATCH 193/197] Safety: alternate longitudinal limits (#1577) alt limits --- board/safety.h | 6 ++++++ board/safety_declarations.h | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/board/safety.h b/board/safety.h index 4d7efd2a0f..4f9a28ec61 100644 --- a/board/safety.h +++ b/board/safety.h @@ -509,6 +509,12 @@ bool longitudinal_speed_checks(int desired_speed, const LongitudinalLimits limit return !get_longitudinal_allowed() && (desired_speed != limits.inactive_speed); } +bool longitudinal_alt_checks(int desired_alt, const LongitudinalLimits limits){ + bool alt_valid = get_longitudinal_allowed() && !max_limit_check(desired_alt, limits.max_alt, limits.min_alt); + bool alt_inactive = desired_alt == limits.inactive_alt; + return !(alt_valid || alt_inactive); +} + bool longitudinal_gas_checks(int desired_gas, const LongitudinalLimits limits) { bool gas_valid = get_longitudinal_allowed() && !max_limit_check(desired_gas, limits.max_gas, limits.min_gas); bool gas_inactive = desired_gas == limits.inactive_gas; diff --git a/board/safety_declarations.h b/board/safety_declarations.h index b2f0ea9854..0c40c560f0 100644 --- a/board/safety_declarations.h +++ b/board/safety_declarations.h @@ -90,6 +90,11 @@ typedef struct { const int inactive_gas; const int max_brake; + // alternative cmd limits, ex: transmission rpm on subaru + const int max_alt; + const int min_alt; + const int inactive_alt; + // speed cmd limits const int inactive_speed; } LongitudinalLimits; @@ -163,6 +168,7 @@ bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const bool longitudinal_accel_checks(int desired_accel, const LongitudinalLimits limits); bool longitudinal_speed_checks(int desired_speed, const LongitudinalLimits limits); bool longitudinal_gas_checks(int desired_gas, const LongitudinalLimits limits); +bool longitudinal_alt_checks(int desired_alt, const LongitudinalLimits limits); bool longitudinal_brake_checks(int desired_brake, const LongitudinalLimits limits); bool longitudinal_interceptor_checks(CANPacket_t *to_send); void pcm_cruise_check(bool cruise_engaged); From 4ea50fbb09c75c7ee45a268ac400737f2e5bfb5f Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 14 Aug 2023 12:52:02 -0700 Subject: [PATCH 194/197] Ruff: propgate config from OP (#1579) --- board/jungle/scripts/debug_console.py | 2 +- pyproject.toml | 5 +++-- python/__init__.py | 2 +- python/dfu.py | 2 +- python/serial.py | 2 +- tests/black_white_loopback_test.py | 5 +---- tests/black_white_relay_endurance.py | 4 +--- tests/black_white_relay_test.py | 4 +--- tests/debug_console.py | 5 ++--- tests/echo.py | 6 +----- tests/elm_car_simulator.py | 5 ++--- tests/gmlan_harness_test.py | 5 +---- tests/hitl/conftest.py | 2 +- tests/ir_test.py | 5 +---- tests/libpanda/safety_helpers.py | 4 ++-- tests/loopback_test.py | 6 ++---- tests/message_drop_test.py | 4 ++-- tests/rtc_test.py | 5 +---- tests/safety/common.py | 2 +- tests/spam_can.py | 4 +--- tests/standalone_test.py | 5 +---- tests/tucan_loopback.py | 6 ++---- 22 files changed, 30 insertions(+), 60 deletions(-) diff --git a/board/jungle/scripts/debug_console.py b/board/jungle/scripts/debug_console.py index f8dd24ed2c..f35388796f 100755 --- a/board/jungle/scripts/debug_console.py +++ b/board/jungle/scripts/debug_console.py @@ -17,7 +17,7 @@ if os.getenv("SERIAL"): serials = [x for x in serials if x==os.getenv("SERIAL")] - panda_jungles = list([PandaJungle(x, claim=claim) for x in serials]) + panda_jungles = [PandaJungle(x, claim=claim) for x in serials] if not len(panda_jungles): sys.exit("no panda jungles found") diff --git a/pyproject.toml b/pyproject.toml index 53f2b5aba8..bac2d7f081 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,7 @@ # https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml [tool.ruff] -select = ["E", "F", "W", "B"] -ignore = ["W292", "E741"] +select = ["E", "F", "W", "PIE", "C4", "ISC", "RUF100", "A"] +ignore = ["W292", "E741", "E402", "C408", "ISC003"] line-length = 160 target-version="py311" +flake8-implicit-str-concat.allow-multiline=false diff --git a/python/__init__.py b/python/__init__.py index e1b3a471e6..40066af46a 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -409,7 +409,7 @@ def usb_connect(cls, serial, claim=True): return usb_handle, usb_serial, bootstub, bcd @classmethod - def list(cls): + def list(cls): # noqa: A003 ret = cls.usb_list() ret += cls.spi_list() return list(set(ret)) diff --git a/python/dfu.py b/python/dfu.py index bebc243ced..39303d5286 100644 --- a/python/dfu.py +++ b/python/dfu.py @@ -59,7 +59,7 @@ def spi_connect(dfu_serial: Optional[str]) -> Optional[STBootloaderSPIHandle]: return handle @staticmethod - def list() -> List[str]: + def list() -> List[str]: # noqa: A003 ret = PandaDFU.usb_list() ret += PandaDFU.spi_list() return list(set(ret)) diff --git a/python/serial.py b/python/serial.py index 97fe8f1d90..9ac58862b5 100644 --- a/python/serial.py +++ b/python/serial.py @@ -8,7 +8,7 @@ def __init__(self, panda, port, baud): self.panda.set_uart_baud(self.port, baud) self.buf = b"" - def read(self, l=1): # noqa: E741 + def read(self, l=1): tt = self.panda.serial_read(self.port) if len(tt) > 0: self.buf += tt diff --git a/tests/black_white_loopback_test.py b/tests/black_white_loopback_test.py index 8914430a17..5b2312befb 100755 --- a/tests/black_white_loopback_test.py +++ b/tests/black_white_loopback_test.py @@ -6,13 +6,10 @@ import os -import sys import time import random import argparse - -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda def get_test_string(): return b"test" + os.urandom(10) diff --git a/tests/black_white_relay_endurance.py b/tests/black_white_relay_endurance.py index cfdaeb330b..db19e72539 100755 --- a/tests/black_white_relay_endurance.py +++ b/tests/black_white_relay_endurance.py @@ -6,13 +6,11 @@ import os -import sys import time import random import argparse -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda def get_test_string(): return b"test" + os.urandom(10) diff --git a/tests/black_white_relay_test.py b/tests/black_white_relay_test.py index 4a3f58f7f4..90b33be681 100755 --- a/tests/black_white_relay_test.py +++ b/tests/black_white_relay_test.py @@ -5,13 +5,11 @@ import os -import sys import time import random import argparse -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda def get_test_string(): return b"test" + os.urandom(10) diff --git a/tests/debug_console.py b/tests/debug_console.py index a74cd846e8..8755be1498 100755 --- a/tests/debug_console.py +++ b/tests/debug_console.py @@ -6,8 +6,7 @@ import select import codecs -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda setcolor = ["\033[1;32;40m", "\033[1;31;40m"] unsetcolor = "\033[00m" @@ -24,7 +23,7 @@ if os.getenv("SERIAL"): serials = [x for x in serials if x == os.getenv("SERIAL")] - pandas = list([Panda(x, claim=claim) for x in serials]) + pandas = [Panda(x, claim=claim) for x in serials] decoders = [codecs.getincrementaldecoder('utf-8')() for _ in pandas] if not len(pandas): diff --git a/tests/echo.py b/tests/echo.py index e7cf3e5b3c..90bf4a8c76 100755 --- a/tests/echo.py +++ b/tests/echo.py @@ -1,9 +1,5 @@ #!/usr/bin/env python3 -import os -import sys - -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda # This script is intended to be used in conjunction with the echo_loopback_test.py test script from panda jungle. # It sends a reversed response back for every message received containing b"test". diff --git a/tests/elm_car_simulator.py b/tests/elm_car_simulator.py index 9d745a79f8..27afdbad48 100755 --- a/tests/elm_car_simulator.py +++ b/tests/elm_car_simulator.py @@ -2,16 +2,15 @@ """Used to Reverse/Test ELM protocol auto detect and OBD message response without a car.""" -import sys import os +import sys import struct import binascii import time import threading from collections import deque -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda def lin_checksum(dat): return sum(dat) % 0x100 diff --git a/tests/gmlan_harness_test.py b/tests/gmlan_harness_test.py index f10f98fc3d..950918cff2 100755 --- a/tests/gmlan_harness_test.py +++ b/tests/gmlan_harness_test.py @@ -1,11 +1,8 @@ #!/usr/bin/env python3 -import os -import sys import time -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda WHITE_GMLAN_BUS = 3 OTHER_GMLAN_BUS = 1 diff --git a/tests/hitl/conftest.py b/tests/hitl/conftest.py index c337013f05..6deb2e0eee 100644 --- a/tests/hitl/conftest.py +++ b/tests/hitl/conftest.py @@ -58,7 +58,7 @@ def init_all_pandas(): print(f"{len(_all_pandas)} total pandas") init_all_pandas() -_all_panda_serials = list(sorted(_all_pandas.keys())) +_all_panda_serials = sorted(_all_pandas.keys()) def init_jungle(): diff --git a/tests/ir_test.py b/tests/ir_test.py index 8566f37715..e41decf255 100755 --- a/tests/ir_test.py +++ b/tests/ir_test.py @@ -1,10 +1,7 @@ #!/usr/bin/env python3 -import os -import sys import time -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda power = 0 if __name__ == "__main__": diff --git a/tests/libpanda/safety_helpers.py b/tests/libpanda/safety_helpers.py index 234b8ecd02..d10a82b004 100644 --- a/tests/libpanda/safety_helpers.py +++ b/tests/libpanda/safety_helpers.py @@ -72,10 +72,10 @@ def get_vehicle_speed_max(self) -> int: ... def get_current_safety_mode(self) -> int: ... def get_current_safety_param(self) -> int: ... - def set_torque_meas(self, min: int, max: int) -> None: ... # pylint: disable=redefined-builtin + def set_torque_meas(self, min: int, max: int) -> None: ... # noqa: A002 def get_torque_meas_min(self) -> int: ... def get_torque_meas_max(self) -> int: ... - def set_torque_driver(self, min: int, max: int) -> None: ... # pylint: disable=redefined-builtin + def set_torque_driver(self, min: int, max: int) -> None: ... # noqa: A002 def get_torque_driver_min(self) -> int: ... def get_torque_driver_max(self) -> int: ... def set_desired_torque_last(self, t: int) -> None: ... diff --git a/tests/loopback_test.py b/tests/loopback_test.py index 630c55215e..73bb91be71 100755 --- a/tests/loopback_test.py +++ b/tests/loopback_test.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 import os -import sys import time import random import argparse @@ -9,8 +8,7 @@ from hexdump import hexdump from itertools import permutations -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda def get_test_string(): return b"test" + os.urandom(10) @@ -25,7 +23,7 @@ def run_test(sleep_duration): run_test_w_pandas(pandas, sleep_duration) def run_test_w_pandas(pandas, sleep_duration): - h = list([Panda(x) for x in pandas]) + h = [Panda(x) for x in pandas] print("H", h) for hh in h: diff --git a/tests/message_drop_test.py b/tests/message_drop_test.py index ed4ea0deb7..99d35b043d 100755 --- a/tests/message_drop_test.py +++ b/tests/message_drop_test.py @@ -16,7 +16,7 @@ # Generate unique messages NUM_MESSAGES_PER_BUS = 10000 messages = [bytes(struct.pack("Q", i)) for i in range(NUM_MESSAGES_PER_BUS)] -tx_messages = list(itertools.chain.from_iterable(map(lambda msg: [[0xaa, None, msg, 0], [0xaa, None, msg, 1], [0xaa, None, msg, 2]], messages))) +tx_messages = list(itertools.chain.from_iterable(([[0xaa, None, msg, 0], [0xaa, None, msg, 1], [0xaa, None, msg, 2]] for msg in messages))) def flood_tx(panda): print('Sending!') @@ -65,6 +65,6 @@ def flood_tx(panda): # Check if we received everything for bus in range(3): - received_msgs = set(map(lambda m: bytes(m[2]), filter(lambda m, b=bus: m[3] == b, rx))) # type: ignore + received_msgs = {bytes(m[2]) for m in filter(lambda m, b=bus: m[3] == b, rx)} # type: ignore dropped_msgs = set(messages).difference(received_msgs) print(f"Bus {bus} dropped msgs: {len(list(dropped_msgs))} / {len(messages)}") diff --git a/tests/rtc_test.py b/tests/rtc_test.py index 24edf0976e..01c9f4ddd8 100755 --- a/tests/rtc_test.py +++ b/tests/rtc_test.py @@ -1,10 +1,7 @@ #!/usr/bin/env python -import os -import sys import datetime -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda if __name__ == "__main__": p = Panda() diff --git a/tests/safety/common.py b/tests/safety/common.py index 7724705cff..f4cc519fa1 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -987,7 +987,7 @@ def test_tx_hook_on_wrong_safety_mode(self): if attr.startswith('TestHonda'): # exceptions for common msgs across different hondas tx = list(filter(lambda m: m[0] not in [0x1FA, 0x30C, 0x33D], tx)) - all_tx.append(list([m[0], m[1], attr] for m in tx)) + all_tx.append([m[0], m[1], attr] for m in tx) # make sure we got all the msgs self.assertTrue(len(all_tx) >= len(test_files)-1) diff --git a/tests/spam_can.py b/tests/spam_can.py index 8c80fb45cb..3154cc0463 100755 --- a/tests/spam_can.py +++ b/tests/spam_can.py @@ -1,10 +1,8 @@ #!/usr/bin/env python3 import os -import sys import random -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda def get_test_string(): return b"test" + os.urandom(10) diff --git a/tests/standalone_test.py b/tests/standalone_test.py index dfd2c9a7ae..7ec5559697 100755 --- a/tests/standalone_test.py +++ b/tests/standalone_test.py @@ -1,11 +1,8 @@ #!/usr/bin/env python3 -import os -import sys import struct import time -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda if __name__ == "__main__": p = Panda() diff --git a/tests/tucan_loopback.py b/tests/tucan_loopback.py index 0780ffee00..1bf1254a16 100755 --- a/tests/tucan_loopback.py +++ b/tests/tucan_loopback.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 import os -import sys import time import random import argparse @@ -9,8 +8,7 @@ from hexdump import hexdump from itertools import permutations -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) -from panda import Panda # noqa: E402 +from panda import Panda def get_test_string(): return b"test" + os.urandom(10) @@ -25,7 +23,7 @@ def run_test(sleep_duration): run_test_w_pandas(pandas, sleep_duration) def run_test_w_pandas(pandas, sleep_duration): - h = list([Panda(x) for x in pandas]) + h = [Panda(x) for x in pandas] print("H", h) for hh in h: From 10a27278829fcba3804269bc7732b66cd5f18c6a Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 14 Aug 2023 19:26:00 -0700 Subject: [PATCH 195/197] Subaru: prepare tests for long and angle control (#1580) * prepare tests for long and angle control * fix names --- tests/safety/common.py | 2 +- tests/safety/test_subaru.py | 35 +++++++++++++++++++---------------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/tests/safety/common.py b/tests/safety/common.py index f4cc519fa1..2ff771d536 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -954,7 +954,7 @@ def test_tx_hook_on_wrong_safety_mode(self): continue if attr.startswith('TestToyota') and current_test.startswith('TestToyota'): continue - if {attr, current_test}.issubset({'TestSubaruGen1Safety', 'TestSubaruGen2Safety'}): + if {attr, current_test}.issubset({'TestSubaruGen1TorqueStockLongitudinalSafety', 'TestSubaruGen2TorqueStockLongitudinalSafety'}): continue if {attr, current_test}.issubset({'TestVolkswagenPqSafety', 'TestVolkswagenPqStockSafety', 'TestVolkswagenPqLongSafety'}): continue diff --git a/tests/safety/test_subaru.py b/tests/safety/test_subaru.py index 8e87cb08c1..bbf9143f20 100755 --- a/tests/safety/test_subaru.py +++ b/tests/safety/test_subaru.py @@ -33,7 +33,7 @@ def lkas_tx_msgs(alt_bus): [MSG_SUBARU_ES_Infotainment, SUBARU_MAIN_BUS]] -class TestSubaruSafetyBase(common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest, MeasurementSafetyTest): +class TestSubaruSafetyBase(common.PandaSafetyTest, MeasurementSafetyTest): FLAGS = 0 STANDSTILL_THRESHOLD = 0 # kph RELAY_MALFUNCTION_ADDR = MSG_SUBARU_ES_LKAS @@ -41,10 +41,6 @@ class TestSubaruSafetyBase(common.PandaSafetyTest, common.DriverTorqueSteeringSa FWD_BUS_LOOKUP = {SUBARU_MAIN_BUS: SUBARU_CAM_BUS, SUBARU_CAM_BUS: SUBARU_MAIN_BUS} FWD_BLACKLISTED_ADDRS = {SUBARU_CAM_BUS: [MSG_SUBARU_ES_LKAS, MSG_SUBARU_ES_DashStatus, MSG_SUBARU_ES_LKAS_State, MSG_SUBARU_ES_Infotainment]} - MAX_RATE_UP = 50 - MAX_RATE_DOWN = 70 - MAX_TORQUE = 2047 - MAX_RT_DELTA = 940 RT_INTERVAL = 250000 @@ -84,10 +80,6 @@ def _user_brake_msg(self, brake): values = {"Brake": brake} return self.packer.make_can_msg_panda("Brake_Status", self.ALT_BUS, values) - def _torque_cmd_msg(self, torque, steer_req=1): - values = {"LKAS_Output": torque} - return self.packer.make_can_msg_panda("ES_LKAS", 0, values) - def _user_gas_msg(self, gas): values = {"Throttle_Pedal": gas} return self.packer.make_can_msg_panda("Throttle", 0, values) @@ -96,6 +88,8 @@ def _pcm_status_msg(self, enable): values = {"Cruise_Activated": enable} return self.packer.make_can_msg_panda("CruiseControl", self.ALT_BUS, values) + +class TestSubaruStockLongitudinalSafetyBase(TestSubaruSafetyBase): def _cancel_msg(self, cancel, cruise_throttle=0): values = {"Cruise_Cancel": cancel, "Cruise_Throttle": cruise_throttle} return self.packer.make_can_msg_panda("ES_Distance", self.ALT_BUS, values) @@ -106,19 +100,28 @@ def test_cancel_message(self): self._generic_limit_safety_check(partial(self._cancel_msg, cancel), self.INACTIVE_GAS, self.INACTIVE_GAS, 0, 2**12, 1, self.INACTIVE_GAS, cancel) -class TestSubaruGen2SafetyBase(TestSubaruSafetyBase): - ALT_BUS = SUBARU_ALT_BUS +class TestSubaruTorqueSafetyBase(TestSubaruSafetyBase, common.DriverTorqueSteeringSafetyTest): + MAX_RATE_UP = 50 + MAX_RATE_DOWN = 70 + MAX_TORQUE = 2047 + + def _torque_cmd_msg(self, torque, steer_req=1): + values = {"LKAS_Output": torque} + return self.packer.make_can_msg_panda("ES_LKAS", 0, values) - MAX_RATE_UP = 40 - MAX_RATE_DOWN = 40 - MAX_TORQUE = 1000 -class TestSubaruGen1Safety(TestSubaruSafetyBase): +class TestSubaruGen1TorqueStockLongitudinalSafety(TestSubaruStockLongitudinalSafetyBase, TestSubaruTorqueSafetyBase): FLAGS = 0 TX_MSGS = lkas_tx_msgs(SUBARU_MAIN_BUS) -class TestSubaruGen2Safety(TestSubaruGen2SafetyBase): +class TestSubaruGen2TorqueStockLongitudinalSafety(TestSubaruStockLongitudinalSafetyBase, TestSubaruTorqueSafetyBase): + ALT_BUS = SUBARU_ALT_BUS + + MAX_RATE_UP = 40 + MAX_RATE_DOWN = 40 + MAX_TORQUE = 1000 + FLAGS = Panda.FLAG_SUBARU_GEN2 TX_MSGS = lkas_tx_msgs(SUBARU_ALT_BUS) From ab70a3c8679cbf174a764139a71b2229a4359a94 Mon Sep 17 00:00:00 2001 From: Igor Biletskyy Date: Mon, 14 Aug 2023 19:38:38 -0700 Subject: [PATCH 196/197] jungle: add pin header control (#1581) * init * style * clean + gpio output --- board/jungle/__init__.py | 5 +++++ board/jungle/boards/board_declarations.h | 9 ++++++++- board/jungle/boards/board_v1.h | 5 +++-- board/jungle/boards/board_v2.h | 21 ++++++++++++++++++++- board/jungle/main_comms.h | 4 ++++ 5 files changed, 40 insertions(+), 4 deletions(-) diff --git a/board/jungle/__init__.py b/board/jungle/__init__.py index 9d7790c747..1c90fa00c9 100644 --- a/board/jungle/__init__.py +++ b/board/jungle/__init__.py @@ -151,3 +151,8 @@ def debug_read(self): break ret.append(lret) return b''.join(ret) + + # ******************* header pins ******************* + + def set_header_pin(self, pin_num, enabled): + self._handle.controlWrite(Panda.REQUEST_OUT, 0xf7, int(pin_num), int(enabled), b'') diff --git a/board/jungle/boards/board_declarations.h b/board/jungle/boards/board_declarations.h index dcb6ac24ea..3fe4c2fab9 100644 --- a/board/jungle/boards/board_declarations.h +++ b/board/jungle/boards/board_declarations.h @@ -9,6 +9,7 @@ typedef void (*board_set_individual_ignition)(uint8_t bitmask); typedef void (*board_set_harness_orientation)(uint8_t orientation); typedef void (*board_set_can_mode)(uint8_t mode); typedef void (*board_enable_can_transciever)(uint8_t transciever, bool enabled); +typedef void (*board_enable_header_pin)(uint8_t pin_num, bool enabled); typedef float (*board_get_channel_power)(uint8_t channel); typedef uint16_t (*board_get_sbu_mV)(uint8_t channel, uint8_t sbu); @@ -27,6 +28,7 @@ struct board { board_set_harness_orientation set_harness_orientation; board_set_can_mode set_can_mode; board_enable_can_transciever enable_can_transciever; + board_enable_header_pin enable_header_pin; board_get_channel_power get_channel_power; board_get_sbu_mV get_sbu_mV; @@ -67,4 +69,9 @@ uint8_t ignition = 0U; void unused_set_individual_ignition(uint8_t bitmask) { UNUSED(bitmask); -} \ No newline at end of file +} + +void unused_board_enable_header_pin(uint8_t pin_num, bool enabled) { + UNUSED(pin_num); + UNUSED(enabled); +} diff --git a/board/jungle/boards/board_v1.h b/board/jungle/boards/board_v1.h index d1a6299a4e..8d41b94524 100644 --- a/board/jungle/boards/board_v1.h +++ b/board/jungle/boards/board_v1.h @@ -1,6 +1,6 @@ void board_v1_set_led(uint8_t color, bool enabled) { - switch (color){ + switch (color) { case LED_RED: set_gpio_output(GPIOC, 9, !enabled); break; @@ -16,7 +16,7 @@ void board_v1_set_led(uint8_t color, bool enabled) { } void board_v1_enable_can_transciever(uint8_t transciever, bool enabled) { - switch (transciever){ + switch (transciever) { case 1U: set_gpio_output(GPIOC, 1, !enabled); break; @@ -169,6 +169,7 @@ const board board_v1 = { .set_harness_orientation = &board_v1_set_harness_orientation, .set_can_mode = &board_v1_set_can_mode, .enable_can_transciever = &board_v1_enable_can_transciever, + .enable_header_pin = &unused_board_enable_header_pin, .get_channel_power = &board_v1_get_channel_power, .get_sbu_mV = &board_v1_get_sbu_mV, }; diff --git a/board/jungle/boards/board_v2.h b/board/jungle/boards/board_v2.h index 73a8a0bed5..0a6e27368c 100644 --- a/board/jungle/boards/board_v2.h +++ b/board/jungle/boards/board_v2.h @@ -108,7 +108,7 @@ void board_v2_set_harness_orientation(uint8_t orientation) { } void board_v2_enable_can_transciever(uint8_t transciever, bool enabled) { - switch (transciever){ + switch (transciever) { case 1U: set_gpio_output(GPIOG, 11, !enabled); break; @@ -127,6 +127,14 @@ void board_v2_enable_can_transciever(uint8_t transciever, bool enabled) { } } +void board_v2_enable_header_pin(uint8_t pin_num, bool enabled) { + if (pin_num < 8U) { + set_gpio_output(GPIOG, pin_num, enabled); + } else { + print("Invalid pin number ("); puth(pin_num); print("): enabling failed\n"); + } +} + void board_v2_set_can_mode(uint8_t mode) { board_v2_enable_can_transciever(2U, false); board_v2_enable_can_transciever(4U, false); @@ -265,6 +273,16 @@ void board_v2_init(void) { set_gpio_mode(GPIOF, 4, MODE_ANALOG); set_gpio_mode(GPIOC, 0, MODE_ANALOG); set_gpio_mode(GPIOC, 1, MODE_ANALOG); + + // Header pins + set_gpio_mode(GPIOG, 0, MODE_OUTPUT); + set_gpio_mode(GPIOG, 1, MODE_OUTPUT); + set_gpio_mode(GPIOG, 2, MODE_OUTPUT); + set_gpio_mode(GPIOG, 3, MODE_OUTPUT); + set_gpio_mode(GPIOG, 4, MODE_OUTPUT); + set_gpio_mode(GPIOG, 5, MODE_OUTPUT); + set_gpio_mode(GPIOG, 6, MODE_OUTPUT); + set_gpio_mode(GPIOG, 7, MODE_OUTPUT); } void board_v2_tick(void) {} @@ -284,6 +302,7 @@ const board board_v2 = { .set_harness_orientation = &board_v2_set_harness_orientation, .set_can_mode = &board_v2_set_can_mode, .enable_can_transciever = &board_v2_enable_can_transciever, + .enable_header_pin = &board_v2_enable_header_pin, .get_channel_power = &board_v2_get_channel_power, .get_sbu_mV = &board_v2_get_sbu_mV, }; diff --git a/board/jungle/main_comms.h b/board/jungle/main_comms.h index 36ed6e79e2..252c2d509c 100644 --- a/board/jungle/main_comms.h +++ b/board/jungle/main_comms.h @@ -222,6 +222,10 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { can_silent = (req->param1 > 0U) ? ALL_CAN_SILENT : ALL_CAN_LIVE; can_init_all(); break; + // **** 0xf7: enable/disable header pin by number + case 0xf7: + current_board->enable_header_pin(req->param1, req->param2 > 0U); + break; // **** 0xf9: set CAN FD data bitrate case 0xf9: if ((req->param1 < PANDA_CAN_CNT) && From d7f75ca7226f42ef67e21fe7b957b93192f862e8 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 14 Aug 2023 21:08:46 -0700 Subject: [PATCH 197/197] safety tests: test one of each MCU type --- tests/safety/test.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/safety/test.sh b/tests/safety/test.sh index 70164ec54e..1c427ee2ca 100755 --- a/tests/safety/test.sh +++ b/tests/safety/test.sh @@ -1,10 +1,7 @@ #!/usr/bin/env bash - -# Loop over all HW_TYPEs, see board/boards/board_declarations.h -# Make sure test fails if one HW_TYPE fails set -e -HW_TYPES=( 6 7 9 ) +HW_TYPES=( 6 9 ) for hw_type in "${HW_TYPES[@]}"; do echo "Testing HW_TYPE: $hw_type" HW_TYPE=$hw_type python -m unittest discover .