From afd5d9f8c70cd3b04d52a989ee25f870a1e76f8f Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 2 Jan 2024 16:36:09 +0100 Subject: [PATCH 1/7] Add escc flag to the scons build args Add initial README for ESCC board This commit introduces a new README for the ESCC board detailing the pinout configurations and connections between the Molex connector and IONIQ harness and radar components. The documentation provides a clear, structured guide to assist with proper wiring and setup. Improving comments for clarity and explicitness IDE is behaving now. Wd don't need this hack anymore Add ESCC build option and refine ESCC specific code Introduce --escc option in SCons to conditionally build ESCC firmware. Update build_project to support CPPDEFINES and modify main.c to handle ESCC-specific safety modes and behaviors more cleanly. Remove unnecessary send_escc_msg calls in Hyundai safety code The updates in the code remove redundant send_escc_msg calls in the Hyundai safety code. We don't really need to send the FCA or AEB data through the ESCC packet because we don't block it at all. So we don't really need to "package" it anymore on the ESCC message Add send_escc_msg call in case blocks The send_escc_msg function is now called within the individual case blocks for SCC11, SCC12, and FCA11, enhancing code readability. Before, it was called on any message that the radar sends, but this strategy was inefficient as it was piggybacking on their frequencies. Refactor `escc_tx_hook` function in Hyundai safety code The commented-out `escc_tx_hook` function in Hyundai ESCC safety code has been refactored and is now in use again, replacing the formerly used `alloutput_tx_hook`. Debugging functionality has been preserved and conditions for when the function is unused have also been handled. The resulting code is now cleaner and more efficient. Add ESCC firmware and build files, and safety mode for Hyundai ESCC This commit introduces ESCC firmware and the corresponding build files. We have also defined a new safety mode for Hyundai ESCC. The necessary scripts for flashing and recovery have been added, alongside the gitignore file for the ESCC board. Additionally, we have implemented new message processing and forwarding logic to handle ESCC specific messages in main and safety files. --- SConscript | 26 ++++--- SConstruct | 4 ++ board/config.h | 1 + board/escc/.gitignore | 1 + board/escc/README.MD | 81 ++++++++++++++++++++++ board/escc/SConscript | 34 +++++++++ board/escc/flash.py | 18 +++++ board/escc/flash_can.sh | 8 +++ board/escc/recover.sh | 11 +++ board/main.c | 94 ++++++++++++++++++++++++- board/safety.h | 11 +++ board/safety/safety_hyundai_escc.h | 107 +++++++++++++++++++++++++++++ board/safety_sunnypilot_common.h | 64 +++++++++++++++++ tests/escc/enter_canloader.py | 32 +++++++++ 14 files changed, 481 insertions(+), 11 deletions(-) create mode 100644 board/escc/.gitignore create mode 100644 board/escc/README.MD create mode 100644 board/escc/SConscript create mode 100755 board/escc/flash.py create mode 100755 board/escc/flash_can.sh create mode 100755 board/escc/recover.sh create mode 100644 board/safety/safety_hyundai_escc.h create mode 100644 board/safety_sunnypilot_common.h create mode 100755 tests/escc/enter_canloader.py diff --git a/SConscript b/SConscript index f6501e88cc1..1f5be8fcba2 100644 --- a/SConscript +++ b/SConscript @@ -62,7 +62,9 @@ def to_c_uint32(x): return "{" + 'U,'.join(map(str, nums)) + "U}" -def build_project(project_name, project, extra_flags): +def build_project(project_name, project, extra_flags, cppdefines=None): + cppdefines = cppdefines or [] + print(f"{project_name} -> CPPDEFINES [{', '.join(cppdefines)}]") linkerscript_fn = File(project["LINKER_SCRIPT"]).srcnode().relpath flags = project["PROJECT_FLAGS"] + extra_flags + common_flags + [ @@ -101,6 +103,7 @@ def build_project(project_name, project, extra_flags): 'Objcopy': Builder(generator=objcopy, suffix='.bin', src_suffix='.elf') }, tools=["default", "compilation_db"], + CPPDEFINES=cppdefines, ) startup = env.Object(f"obj/startup_{project_name}", project["STARTUP_FILE"]) @@ -179,12 +182,15 @@ with open("board/obj/cert.h", "w") as f: for cert in certs: f.write("\n".join(cert) + "\n") -# panda fw -SConscript('board/SConscript') - -# panda jungle fw -SConscript('board/jungle/SConscript') - -# test files -if GetOption('extras'): - SConscript('tests/libpanda/SConscript') +if not GetOption('escc'): # if we are building ESCC, we don't build anything else + # panda fw + SConscript('board/SConscript') + + # panda jungle fw + SConscript('board/jungle/SConscript') + + # test files + if GetOption('extras'): + SConscript('tests/libpanda/SConscript') +else: + SConscript('board/escc/SConscript') diff --git a/SConstruct b/SConstruct index 11ff21738e5..5e5a1008306 100644 --- a/SConstruct +++ b/SConstruct @@ -20,6 +20,10 @@ AddOption('--mutation', action='store_true', help='generate mutation-ready code') +AddOption('--escc', + action='store_true', + help='build escc firmware') + env = Environment( COMPILATIONDB_USE_ABSPATH=True, tools=["default", "compilation_db"], diff --git a/board/config.h b/board/config.h index 6fb95ada26f..529cec5930d 100644 --- a/board/config.h +++ b/board/config.h @@ -9,6 +9,7 @@ //#define DEBUG_FAULTS //#define DEBUG_COMMS //#define DEBUG_FAN +//#define DEBUG_CAN_LOOP #define CAN_INIT_TIMEOUT_MS 500U #define USBPACKET_MAX_SIZE 0x40U diff --git a/board/escc/.gitignore b/board/escc/.gitignore new file mode 100644 index 00000000000..94053f29250 --- /dev/null +++ b/board/escc/.gitignore @@ -0,0 +1 @@ +obj/* diff --git a/board/escc/README.MD b/board/escc/README.MD new file mode 100644 index 00000000000..6eea4ba81a3 --- /dev/null +++ b/board/escc/README.MD @@ -0,0 +1,81 @@ +## Documentation +[see more](https://github.com/sunnypilot/sunnypilot/blob/5d754169a6285e88e755ff9a592a851cb92b0abc/docs/escc/ESCC%20IONIQ%20PHEV%20Guide.md) + +### Detailed Connection List + +#### Molex 5018762640 Pinout: + +- Pin 1: GND +- Pin 2: IGN +- Pin 3: internal 120 Ohm Resistor, simply bridge to Pin 5 +- Pin 4: C-CAN HIGH +- Pin 5: internal 120 Ohm Resistor, simply bridge to Pin 3 +- Pin 6: C-CAN LOW +- Pin 8: L-CAN HIGH +- Pin 10: L-CAN LOW +- Pin 12: 12v +- Pin 14: 12v +- Pin 16: IGN +- Pin 18: L-CAN HIGH +- Pin 20: L-CAN LOW +- Pin 22: C-CAN HIGH +- Pin 24: C-CAN LOW +- Pin 26: GND + +#### **IONIQ 2203663 (Car Harness, Female):** + +- Pin 1: 12v -> Molex Pin 12 +- Pin 4: GND -> Molex Pin 1 +- Pin 7: C-CAN LOW -> Molex Pin 6 +- Pin 8: C-CAN HIGH -> Molex Pin 4 +- Pin 9: L-CAN LOW -> Molex Pin 10 +- Pin 10: IGPM (IGN) -> Molex Pin 2 +- Pin 18: L-CAN HIGH -> Molex Pin 8 + +#### **IONIQ 2203663 (Car Radar, Male):** + +- Molex Pin: 14 -> Pin 1: 12v +- Molex Pin: 26 -> Pin 4: GND +- Molex Pin: 24 -> Pin 7: C-CAN LOW +- Molex Pin: 22 -> Pin 8: C-CAN HIGH +- Molex Pin: 20 -> Pin 9: L-CAN LOW +- Molex Pin: 16 -> Pin 10: IGN +- Molex Pin: 18 -> Pin 18: L-CAN HIGH + + +``` +IONIQ 2203663 Molex 5018762640 IONIQ 2203663 +Car Harness (Female) Car Radar (Male) ++-----+-----+ +-----+-----+ +-----+-----+ +| 18 | 9 | | 2 | 1 | | 10 | 1 | +| 17 | 8 | | 4 | 3 | | 11 | 2 | +| 16 | 7 | | 6 | 5 | | 12 | 3 | +| 15 | 6 | | 8 | 7 | | 13 | 4 | +| 14 | 5 | NOTCH | 10 | 9 | | 14 | 5 | NOTCH +| 13 | 4 | | 12 | 11 | | 15 | 6 | +| 12 | 3 | | 14 | 13 | NOTCH | 16 | 7 | +| 11 | 2 | | 16 | 15 | | 17 | 8 | +| 10 | 1 | | 18 | 17 | | 18 | 9 | ++-----+-----+ | 20 | 19 | +-----+-----+ + | 22 | 21 | + | 24 | 23 | + | 26 | 25 | + +-----+-----+ + +Connections: +Molex Pin 3 (120 Ohm) <--> Molex Pin 5 (120 Ohm) +Harness Pin 1 (12V) <--> Molex Pin 12 (12V) +Harness Pin 4 (GND) <--> Molex Pin 1 (GND) +Harness Pin 10 (IGN) <--> Molex Pin 2 (IGN) +Harness Pin 8 (C-CAN H) <--> Molex Pin 4 (C-CAN H) +Harness Pin 7 (C-CAN L) <--> Molex Pin 6 (C-CAN L) +Harness Pin 18 (L-CAN H) <--> Molex Pin 8 (L-CAN H) +Harness Pin 9 (L-CAN L) <--> Molex Pin 10 (L-CAN L) + Molex Pin 20 (L-CAN L) <--> Radar Pin 9 (L-CAN L) + Molex Pin 18 (L-CAN H) <--> Radar Pin 18 (L-CAN H) + Molex Pin 24 (C-CAN L) <--> Radar Pin 7 (C-CAN L) + Molex Pin 22 (C-CAN H) <--> Radar Pin 8 (C-CAN H) + Molex Pin 16 (IGN) <--> Radar Pin 10 (IGN) + Molex Pin 14 (12V) <--> Radar Pin 1 (12V) + Molex Pin 26 (GND) <--> Radar Pin 4 (GND) +``` \ No newline at end of file diff --git a/board/escc/SConscript b/board/escc/SConscript new file mode 100644 index 00000000000..79a7c98e22b --- /dev/null +++ b/board/escc/SConscript @@ -0,0 +1,34 @@ +import os +import copy + +Import('build_project') + +build_projects = {} + +build_projects["escc"] = { + "MAIN": "../main.c", + "BOOTSTUB": "../bootstub.c", + "STARTUP_FILE": File("../stm32f4/startup_stm32f413xx.s"), + "LINKER_SCRIPT": File("../stm32f4/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", + ], +} + +for project_name, project in build_projects.items(): + flags = [ + "-DPANDA", + "-DESCC", + ] + if ("ENABLE_SPI" in os.environ or "h7" in project_name) and not project_name.startswith('pedal'): + flags.append('-DENABLE_SPI') + + build_project(project_name, project, flags, ["ESCC"]) diff --git a/board/escc/flash.py b/board/escc/flash.py new file mode 100755 index 00000000000..cc7e7212f69 --- /dev/null +++ b/board/escc/flash.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +import os +import subprocess + +from panda import Panda + +board_path = os.path.dirname(os.path.realpath(__file__)) + +if __name__ == "__main__": + print(board_path) + subprocess.check_call(f"scons -C {board_path}/../.. -j$(nproc) {board_path} --escc", shell=True) + + serials = Panda.list() + print(f"found {len(serials)} panda(s) - {serials}") + for s in serials: + print("flashing", s) + with Panda(serial=s) as p: + p.flash("obj/escc.bin.signed") diff --git a/board/escc/flash_can.sh b/board/escc/flash_can.sh new file mode 100755 index 00000000000..634f1acdd16 --- /dev/null +++ b/board/escc/flash_can.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env sh +set -e + +cd .. +scons -u -j$(nproc) --escc +cd escc + +../../tests/escc/enter_canloader.py obj/escc.bin.signed diff --git a/board/escc/recover.sh b/board/escc/recover.sh new file mode 100755 index 00000000000..6276742dac1 --- /dev/null +++ b/board/escc/recover.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env sh +set -e + +DFU_UTIL="dfu-util" + +cd .. +scons -u -j$(nproc) --escc +cd escc + +$DFU_UTIL -d 0483:df11 -a 0 -s 0x08004000 -D obj/escc.bin.signed +$DFU_UTIL -d 0483:df11 -a 0 -s 0x08000000:leave -D obj/bootstub.escc.bin diff --git a/board/main.c b/board/main.c index 7c068113a0e..45ff2554c52 100644 --- a/board/main.c +++ b/board/main.c @@ -124,6 +124,61 @@ bool is_car_safety_mode(uint16_t mode) { (mode != SAFETY_ELM327); } +#ifdef ESCC +// ***************************** ESCC code ***************************** +bool is_car_safety_mode_escc(uint16_t mode) { + return is_car_safety_mode(mode) && (mode != SAFETY_HYUNDAI_ESCC); +} +#define is_car_safety_mode is_car_safety_mode_escc + +#define CAN_ESCC_INPUT 0x2AC +#define CAN_ESCC_OUTPUT 0x2ABU + +void send_escc_msg(const ESCC_Msg *msg, const int bus_number) { +#ifdef DEBUG + + print("send_escc_msg\n"); + print("fca_cmd_act: "); putui(msg->fca_cmd_act); print("\n"); + print("aeb_cmd_act: "); putui(msg->aeb_cmd_act); print("\n"); + print("cf_vsm_warn_fca11: "); putui(msg->cf_vsm_warn_fca11); print("\n"); + print("cf_vsm_warn_scc12: "); putui(msg->cf_vsm_warn_scc12); print("\n"); + print("cf_vsm_deccmdact_scc12: "); putui(msg->cf_vsm_deccmdact_scc12); print("\n"); + print("cf_vsm_deccmdact_fca11: "); putui(msg->cf_vsm_deccmdact_fca11); print("\n"); + print("cr_vsm_deccmd_scc12: "); putui(msg->cr_vsm_deccmd_scc12); print("\n"); + print("cr_vsm_deccmd_fca11: "); putui(msg->cr_vsm_deccmd_fca11); print("\n"); + print("obj_valid: "); putui(msg->obj_valid); print("\n"); + print("acc_objstatus: "); putui(msg->acc_objstatus); print("\n"); + print("acc_obj_lat_pos_1: "); putui(msg->acc_obj_lat_pos_1); print("\n"); + print("acc_obj_lat_pos_2: "); putui(msg->acc_obj_lat_pos_2); print("\n"); + print("acc_obj_dist_1: "); putui(msg->acc_obj_dist_1); print("\n"); + print("acc_obj_dist_2: "); putui(msg->acc_obj_dist_2); print("\n"); + print("acc_obj_rel_spd_1: "); putui(msg->acc_obj_rel_spd_1); print("\n"); + print("acc_obj_rel_spd_2: "); putui(msg->acc_obj_rel_spd_2); print("\n"); +#endif + + uint8_t dat[8]; + dat[0] = (msg->fca_cmd_act) | (msg->cf_vsm_warn_fca11 << 1) | (msg->aeb_cmd_act << 3) | + (msg->cf_vsm_warn_scc12 << 4) | (msg->cf_vsm_deccmdact_scc12 << 6) | (msg->cf_vsm_deccmdact_fca11 << 7); + dat[1] = (msg->cr_vsm_deccmd_scc12); + dat[2] = (msg->obj_valid) | (msg->acc_objstatus << 1); + dat[3] = (msg->acc_obj_lat_pos_1); + dat[4] = (msg->acc_obj_lat_pos_2) | (msg->acc_obj_dist_1 << 1); + dat[5] = (msg->acc_obj_dist_2) | (msg->acc_obj_rel_spd_1 << 4); + dat[6] = (msg->acc_obj_rel_spd_2); + dat[7] = (msg->cr_vsm_deccmd_fca11); + + CANPacket_t to_send; + to_send.extended = CAN_ESCC_OUTPUT >= 0x800 ? 1 : 0; + to_send.addr = CAN_ESCC_OUTPUT; + to_send.bus = bus_number; + to_send.data_len_code = sizeof(dat); + memcpy(to_send.data, dat, sizeof(dat)); + + can_set_checksum(&to_send); + can_send(&to_send, bus_number, true); +} +#endif + // ***************************** main code ***************************** // cppcheck-suppress unusedFunction ; used in headers not included in cppcheck @@ -186,7 +241,9 @@ static void tick_handler(void) { 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"); + print("tx3:"); puth4(can_tx3_q.r_ptr); print("-"); puth4(can_tx3_q.w_ptr); print(" "); + print("SAFETY MODE:"); putui(current_safety_mode); + print("\n"); #endif // set green LED to be controls allowed @@ -235,6 +292,7 @@ static void tick_handler(void) { mads_heartbeat_engaged_check(); +#ifndef ESCC // Hearbet disabled when ESCC if (!heartbeat_disabled) { // if the heartbeat has been gone for a while, go to SILENT safety mode and enter power save if (heartbeat_counter >= (check_started() ? HEARTBEAT_IGNITION_CNT_ON : HEARTBEAT_IGNITION_CNT_OFF)) { @@ -273,6 +331,7 @@ static void tick_handler(void) { fan_set_power(enabled ? 50U : 0U); } } +#endif // check registers check_registers(); @@ -335,8 +394,13 @@ int main(void) { fan_init(); } +#ifdef ESCC + // init to SAFETY_HYUNDAI_ESCC and can all output + set_safety_mode(SAFETY_HYUNDAI_ESCC, 1U); +#else // init to SILENT and can silent set_safety_mode(SAFETY_SILENT, 0U); +#endif // enable CAN TXs current_board->enable_can_transceivers(true); @@ -367,7 +431,35 @@ int main(void) { enable_interrupts(); // LED should keep on blinking all the time +#if defined(ESCC) && defined(DEBUG_CAN_LOOP) + ESCC_Msg escc; + escc.fca_cmd_act = 1; + escc.aeb_cmd_act = 1; + escc.cf_vsm_warn_fca11 = 1; + escc.cf_vsm_warn_scc12 = 1; + escc.cf_vsm_deccmdact_scc12 = 1; + escc.cf_vsm_deccmdact_fca11 = 1; + escc.cr_vsm_deccmd_scc12 = 1; + escc.cr_vsm_deccmd_fca11 = 1; + escc.obj_valid = 1; + escc.acc_objstatus = 1; + escc.acc_obj_lat_pos_1 = 1; + escc.acc_obj_lat_pos_2 = 1; + escc.acc_obj_dist_1 = 1; + escc.acc_obj_dist_2 = 1; + escc.acc_obj_rel_spd_1 = 1; + escc.acc_obj_rel_spd_2 = 1; + int i = 0; +#endif + while (true) { + +#if defined(ESCC) && defined(DEBUG_CAN_LOOP) + send_escc_msg(&escc, i++); + if(i > 3) + i = 0; +#endif + if (power_save_status == POWER_SAVE_STATUS_DISABLED) { #ifdef DEBUG_FAULTS if (fault_status == FAULT_STATUS_NONE) { diff --git a/board/safety.h b/board/safety.h index 5470e7b3646..821bdd45eed 100644 --- a/board/safety.h +++ b/board/safety.h @@ -23,6 +23,10 @@ #include "safety/safety_elm327.h" #include "safety/safety_body.h" +#ifdef ESCC +#include "safety/safety_hyundai_escc.h" +#endif + // CAN-FD only safety modes #ifdef CANFD #include "safety/safety_hyundai_canfd.h" @@ -56,6 +60,10 @@ #define SAFETY_BODY 27U #define SAFETY_HYUNDAI_CANFD 28U +#ifdef ESCC +#define SAFETY_HYUNDAI_ESCC 29U +#endif + uint32_t GET_BYTES(const CANPacket_t *msg, int start, int len) { uint32_t ret = 0U; for (int i = 0; i < len; i++) { @@ -375,6 +383,9 @@ int set_safety_hooks(uint16_t mode, uint16_t param) { {SAFETY_GM, &gm_hooks}, {SAFETY_HONDA_BOSCH, &honda_bosch_hooks}, {SAFETY_HYUNDAI, &hyundai_hooks}, +#ifdef ESCC + {SAFETY_HYUNDAI_ESCC, &hyundai_escc_hooks}, +#endif {SAFETY_CHRYSLER, &chrysler_hooks}, {SAFETY_SUBARU, &subaru_hooks}, {SAFETY_VOLKSWAGEN_MQB, &volkswagen_mqb_hooks}, diff --git a/board/safety/safety_hyundai_escc.h b/board/safety/safety_hyundai_escc.h new file mode 100644 index 00000000000..e35497608e9 --- /dev/null +++ b/board/safety/safety_hyundai_escc.h @@ -0,0 +1,107 @@ +#pragma once + +#define DEVNULL_BUS (-1) +#define CAR_BUS 0 +#define RADAR_BUS 2 + +bool scc_block_allowed = false; +uint32_t sunnypilot_detected_last = 0; + +// Initialize bytes to send to 2AB +ESCC_Msg escc = {0}; + +static void escc_rx_hook(const CANPacket_t* to_push) { + const int bus = GET_BUS(to_push); + const int addr = GET_ADDR(to_push); + + + const int is_scc_msg = addr == 0x420 || addr == 0x421 || addr == 0x50A || addr == 0x389; + const int is_fca_msg = addr == 0x38D || addr == 0x483; +#ifdef DEBUG + print("escc_rx_hook: "); putui(bus); print(" - "); puth4(addr); print(" is_scc_msg: "); print(is_scc_msg?"yes":"no"); print(" is_fca_msg: "); print(is_fca_msg?"yes":"no"); print("\n"); +#endif + + if (bus == RADAR_BUS && (is_scc_msg || is_fca_msg)) { + switch (addr) { + // This messsage is blocked if scc_block_allowed is true, and ESCC is updated with the data and sent to sunnypilot + case 0x420: // SCC11: Forward radar points to sunnypilot + escc.obj_valid = (GET_BYTE(to_push, 2) & 0x1U); + escc.acc_objstatus = ((GET_BYTE(to_push, 2) >> 6) & 0x3U); + escc.acc_obj_lat_pos_1 = GET_BYTE(to_push, 3); + escc.acc_obj_lat_pos_2 = (GET_BYTE(to_push, 4) & 0x1U); + escc.acc_obj_dist_1 = ((GET_BYTE(to_push, 4) >> 1) & 0x7FU); + escc.acc_obj_dist_2 = (GET_BYTE(to_push, 5) & 0xFU); + escc.acc_obj_rel_spd_1 = ((GET_BYTE(to_push, 5) >> 4) & 0xFU); + escc.acc_obj_rel_spd_2 = GET_BYTE(to_push, 6); + send_escc_msg(&escc, CAR_BUS); + break; + + // This messsage is blocked if scc_block_allowed is true, and ESCC is updated with the data and sent to sunnypilot + case 0x421: // SCC12: Detect AEB, get the data and write it on the next ESCC msg to sunnypilot. + escc.aeb_cmd_act = GET_BYTE(to_push, 6) >> 6 & 1U; + escc.cf_vsm_warn_scc12 = GET_BYTE(to_push, 0) >> 4 & 0x3U; + escc.cf_vsm_deccmdact_scc12 = GET_BYTE(to_push, 0) >> 1 & 1U; + escc.cr_vsm_deccmd_scc12 = GET_BYTE(to_push, 2); + break; + + // This message is not blocked, and is sent straight to the car. + case 0x38D: // FCA11: Detect AEB, get the data and write it on the next ESCC msg to sunnypilot + escc.fca_cmd_act = GET_BYTE(to_push, 2) >> 4 & 1U; + escc.cf_vsm_warn_fca11 = GET_BYTE(to_push, 0) >> 3 & 0x3U; + escc.cf_vsm_deccmdact_fca11 = GET_BYTE(to_push, 3) >> 7 & 1U; + escc.cr_vsm_deccmd_fca11 = GET_BYTE(to_push, 1); + break; + + default: ; + } + } +} + +static bool escc_tx_hook(const CANPacket_t* to_send) { +#ifdef DEBUG + const int target_bus = GET_BUS(to_send); + const int addr = GET_ADDR(to_send); + print("escc_tx_hook: "); putui(target_bus); print(" - "); puth4(addr); print("\n"); + #else + UNUSED(to_send); + #endif + return true; +} + +static int escc_fwd_hook(const int bus_src, const int addr) { +#ifdef DEBUG + print("escc_fwd_hook: "); putui(bus_src); print(" - "); puth4(addr); print(" scc_block_allowed: "); print(scc_block_allowed?"yes":"no" ); print("\n"); +#endif + // SCC messages are SCC11 (0x420), SCC12 (0x421), SCC13 (0x50A), SCC14 (0x389) + const int is_scc_msg = addr == 0x420 || addr == 0x421 || addr == 0x50A || addr == 0x389; + + const uint32_t ts = MICROSECOND_TIMER->CNT; + + // Update the last detected timestamp if an SCC message is from CAR_BUS + if (bus_src == CAR_BUS && is_scc_msg) { + sunnypilot_detected_last = ts; + } + + // Default forwarding logic + int bus_dst = (bus_src == CAR_BUS) ? RADAR_BUS : CAR_BUS; + + // Update the scc_block_allowed status based on elapsed time + const uint32_t ts_elapsed = get_ts_elapsed(ts, sunnypilot_detected_last); + scc_block_allowed = (ts_elapsed <= 150000); + + // If we are allowed to block, and this is an scc msg coming from radar (or somehow we are sending it TO the radar) we block + if (scc_block_allowed && is_scc_msg && (bus_src == RADAR_BUS || bus_dst == RADAR_BUS)) + bus_dst = DEVNULL_BUS; + + return bus_dst; +} + +const safety_hooks hyundai_escc_hooks = { + .init = alloutput_init, + .rx = escc_rx_hook, + .tx = escc_tx_hook, + .fwd = escc_fwd_hook, + .get_counter = hyundai_get_counter, + .get_checksum = hyundai_get_checksum, + .compute_checksum = hyundai_compute_checksum, +}; diff --git a/board/safety_sunnypilot_common.h b/board/safety_sunnypilot_common.h new file mode 100644 index 00000000000..e2dc1db2ef4 --- /dev/null +++ b/board/safety_sunnypilot_common.h @@ -0,0 +1,64 @@ +#ifndef SAFETY_SUNNYPILOT_COMMON_H +#define SAFETY_SUNNYPILOT_COMMON_H + +void mads_acc_main_check(const bool main_on) { + if (main_on && mads_enabled) { + controls_allowed = true; + } + if (!main_on && acc_main_on_prev) { + disengageFromBrakes = false; + controls_allowed = false; + controls_allowed_long = false; + } + acc_main_on_prev = main_on; +} + +void mads_lkas_button_check(const bool lkas_pressed) { + if (lkas_pressed && !lkas_pressed_prev) { + controls_allowed = true; + } + lkas_pressed_prev = lkas_pressed; +} + +void mads_exit_controls_check(void) { + if (alternative_experience & ALT_EXP_MADS_DISABLE_DISENGAGE_LATERAL_ON_BRAKE) { + disengageFromBrakes = true; + controls_allowed_long = false; + } else { + if ((alternative_experience & ALT_EXP_ENABLE_MADS) && controls_allowed) { + disengageFromBrakes = true; + } + controls_allowed = false; + controls_allowed_long = false; + } +} + +void mads_resume_controls_check(void) { + disengageFromBrakes = false; + if (alternative_experience & ALT_EXP_ENABLE_MADS) { + controls_allowed = true; + } +} + +typedef struct { + uint8_t fca_cmd_act; + uint8_t aeb_cmd_act; + uint8_t cf_vsm_warn_fca11; + uint8_t cf_vsm_warn_scc12; + uint8_t cf_vsm_deccmdact_scc12; + uint8_t cf_vsm_deccmdact_fca11; + uint8_t cr_vsm_deccmd_scc12; + uint8_t cr_vsm_deccmd_fca11; + uint8_t obj_valid; + uint8_t acc_objstatus; + uint8_t acc_obj_lat_pos_1; + uint8_t acc_obj_lat_pos_2; + uint8_t acc_obj_dist_1; + uint8_t acc_obj_dist_2; + uint8_t acc_obj_rel_spd_1; + uint8_t acc_obj_rel_spd_2; +} ESCC_Msg; + +void send_escc_msg(const ESCC_Msg *msg, int bus_number); + +#endif diff --git a/tests/escc/enter_canloader.py b/tests/escc/enter_canloader.py new file mode 100755 index 00000000000..787799f245f --- /dev/null +++ b/tests/escc/enter_canloader.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +import time +import argparse +from panda import Panda, CanHandle, McuType + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Flash ESCC over can') + parser.add_argument('--recover', action='store_true') + parser.add_argument("fn", type=str, nargs='?', help="flash file") + args = parser.parse_args() + + p = Panda() + p.set_safety_mode(Panda.SAFETY_ALLOUTPUT) + + while 1: + if len(p.can_recv()) == 0: + break + + if args.recover: + p.can_send(0x200, b"\xce\xfa\xad\xde\x1e\x0b\xb0\x02", 0) + exit(0) + else: + p.can_send(0x200, b"\xce\xfa\xad\xde\x1e\x0b\xb0\x0a", 0) + + if args.fn: + time.sleep(0.1) + print("flashing", args.fn) + code = open(args.fn, "rb").read() + Panda.flash_static(CanHandle(p, 0), code, mcu_type=McuType.F4) + + print("can flash done") From 13f7a4f958da18133e6a2f8d3ad1b516a89b7401 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 1 Feb 2025 11:23:14 +0100 Subject: [PATCH 2/7] cleaning up --- board/config.h | 1 - board/main.c | 28 ---------------------------- 2 files changed, 29 deletions(-) diff --git a/board/config.h b/board/config.h index 529cec5930d..6fb95ada26f 100644 --- a/board/config.h +++ b/board/config.h @@ -9,7 +9,6 @@ //#define DEBUG_FAULTS //#define DEBUG_COMMS //#define DEBUG_FAN -//#define DEBUG_CAN_LOOP #define CAN_INIT_TIMEOUT_MS 500U #define USBPACKET_MAX_SIZE 0x40U diff --git a/board/main.c b/board/main.c index 45ff2554c52..5b0e6712557 100644 --- a/board/main.c +++ b/board/main.c @@ -431,35 +431,7 @@ int main(void) { enable_interrupts(); // LED should keep on blinking all the time -#if defined(ESCC) && defined(DEBUG_CAN_LOOP) - ESCC_Msg escc; - escc.fca_cmd_act = 1; - escc.aeb_cmd_act = 1; - escc.cf_vsm_warn_fca11 = 1; - escc.cf_vsm_warn_scc12 = 1; - escc.cf_vsm_deccmdact_scc12 = 1; - escc.cf_vsm_deccmdact_fca11 = 1; - escc.cr_vsm_deccmd_scc12 = 1; - escc.cr_vsm_deccmd_fca11 = 1; - escc.obj_valid = 1; - escc.acc_objstatus = 1; - escc.acc_obj_lat_pos_1 = 1; - escc.acc_obj_lat_pos_2 = 1; - escc.acc_obj_dist_1 = 1; - escc.acc_obj_dist_2 = 1; - escc.acc_obj_rel_spd_1 = 1; - escc.acc_obj_rel_spd_2 = 1; - int i = 0; -#endif - while (true) { - -#if defined(ESCC) && defined(DEBUG_CAN_LOOP) - send_escc_msg(&escc, i++); - if(i > 3) - i = 0; -#endif - if (power_save_status == POWER_SAVE_STATUS_DISABLED) { #ifdef DEBUG_FAULTS if (fault_status == FAULT_STATUS_NONE) { From f62a079fb31d66a09c5d5e82d06eddac9cf15a1d Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 1 Feb 2025 11:36:25 +0100 Subject: [PATCH 3/7] check if misra can be made happy --- board/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/board/main.c b/board/main.c index 5b0e6712557..2a5dde30c0d 100644 --- a/board/main.c +++ b/board/main.c @@ -117,6 +117,7 @@ void set_safety_mode(uint16_t mode, uint16_t param) { can_init_all(); } +// cppcheck-suppress misra-c2012-5.5 bool is_car_safety_mode(uint16_t mode) { return (mode != SAFETY_SILENT) && (mode != SAFETY_NOOUTPUT) && From 41cce692d4b8ad92dd6a2eeb625bd58d80f7f0ca Mon Sep 17 00:00:00 2001 From: Alan Bixby Date: Sat, 2 May 2026 13:08:40 -0400 Subject: [PATCH 4/7] add support for STM32H725 (red panda) flashing --- board/escc/SConscript | 11 ++++++--- board/escc/flash.py | 31 +++++++++++++++++++++---- board/escc/flash_can.sh | 2 +- board/escc/recover.py | 36 +++++++++++++++++++++++++++++ board/escc/recover.sh | 4 ++-- board/safety.h | 2 ++ board/safety_sunnypilot_common.h | 39 +------------------------------- 7 files changed, 76 insertions(+), 49 deletions(-) create mode 100644 board/escc/recover.py diff --git a/board/escc/SConscript b/board/escc/SConscript index 79a7c98e22b..4ec9577e8b3 100644 --- a/board/escc/SConscript +++ b/board/escc/SConscript @@ -1,11 +1,12 @@ import os import copy -Import('build_project') +Import('build_project', 'base_project_h7') build_projects = {} -build_projects["escc"] = { +# RENAMED from "escc" to "panda" +build_projects["panda"] = { "MAIN": "../main.c", "BOOTSTUB": "../bootstub.c", "STARTUP_FILE": File("../stm32f4/startup_stm32f413xx.s"), @@ -23,6 +24,10 @@ build_projects["escc"] = { ], } +# RENAMED from "escc_h7" to "panda_h7" +build_projects["panda_h7"] = copy.deepcopy(base_project_h7) +build_projects["panda_h7"]["MAIN"] = "../main.c" + for project_name, project in build_projects.items(): flags = [ "-DPANDA", @@ -31,4 +36,4 @@ for project_name, project in build_projects.items(): if ("ENABLE_SPI" in os.environ or "h7" in project_name) and not project_name.startswith('pedal'): flags.append('-DENABLE_SPI') - build_project(project_name, project, flags, ["ESCC"]) + build_project(project_name, project, flags, ["ESCC"]) \ No newline at end of file diff --git a/board/escc/flash.py b/board/escc/flash.py index cc7e7212f69..2f01378e01b 100755 --- a/board/escc/flash.py +++ b/board/escc/flash.py @@ -1,18 +1,39 @@ #!/usr/bin/env python3 import os import subprocess +import argparse +import shutil # Don't forget to import shutil here too! from panda import Panda board_path = os.path.dirname(os.path.realpath(__file__)) if __name__ == "__main__": - print(board_path) + parser = argparse.ArgumentParser() + parser.add_argument("--all", action="store_true", help="Recover all Panda devices") + args = parser.parse_args() + + # Note the ../.. and the --escc flag! subprocess.check_call(f"scons -C {board_path}/../.. -j$(nproc) {board_path} --escc", shell=True) - serials = Panda.list() - print(f"found {len(serials)} panda(s) - {serials}") + # --- HACK: SCons outputs our localized ESCC builds to 'board/escc/obj/'. + # However, the upstream Panda Python library (PandaDFU / p.flash) hardcodes + # its search path to the root 'board/obj/' directory. We copy the compiled + # binaries there so the hardware auto-picker can find them without us + # needing to modify and cause merge conflicts in the upstream library. --- + os.makedirs(f"{board_path}/../obj", exist_ok=True) + for f in os.listdir(f"{board_path}/obj"): + shutil.copy(os.path.join(f"{board_path}/obj", f), f"{board_path}/../obj") + + if args.all: + serials = Panda.list() + print(f"found {len(serials)} panda(s) - {serials}") + else: + serials = [None] + for s in serials: - print("flashing", s) with Panda(serial=s) as p: - p.flash("obj/escc.bin.signed") + print("flashing", p.get_usb_serial()) + # p.flash() auto-detects H7/F4 and picks the matching binary! + p.flash() + exit(1 if len(serials) == 0 else 0) \ No newline at end of file diff --git a/board/escc/flash_can.sh b/board/escc/flash_can.sh index 634f1acdd16..ccbc9a11997 100755 --- a/board/escc/flash_can.sh +++ b/board/escc/flash_can.sh @@ -5,4 +5,4 @@ cd .. scons -u -j$(nproc) --escc cd escc -../../tests/escc/enter_canloader.py obj/escc.bin.signed +../../tests/escc/enter_canloader.py obj/panda_h7.bin.signed \ No newline at end of file diff --git a/board/escc/recover.py b/board/escc/recover.py new file mode 100644 index 00000000000..77eb70abead --- /dev/null +++ b/board/escc/recover.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +import os +import time +import subprocess +import shutil + +from panda import Panda, PandaDFU + +board_path = os.path.dirname(os.path.realpath(__file__)) + +if __name__ == "__main__": + subprocess.check_call(f"scons -C {board_path}/../.. -j$(nproc) {board_path} --escc", shell=True) + + # --- HACK: SCons outputs our localized ESCC builds to 'board/escc/obj/'. + # However, the upstream Panda Python library (PandaDFU / p.flash) hardcodes + # its search path to the root 'board/obj/' directory. We copy the compiled + # binaries there so the hardware auto-picker can find them without us + # needing to modify and cause merge conflicts in the upstream library. --- + os.makedirs(f"{board_path}/../obj", exist_ok=True) + for f in os.listdir(f"{board_path}/obj"): + shutil.copy(os.path.join(f"{board_path}/obj", f), f"{board_path}/../obj") + + for s in Panda.list(): + with Panda(serial=s) as p: + print(f"putting {p.get_usb_serial()} in DFU mode") + p.reset(enter_bootstub=True) + p.reset(enter_bootloader=True) + + time.sleep(1) + + dfu_serials = PandaDFU.list() + print(f"found {len(dfu_serials)} panda(s) in DFU - {dfu_serials}") + for s in dfu_serials: + print("flashing", s) + PandaDFU(s).recover() + exit(1 if len(dfu_serials) == 0 else 0) \ No newline at end of file diff --git a/board/escc/recover.sh b/board/escc/recover.sh index 6276742dac1..58fb43d41e1 100755 --- a/board/escc/recover.sh +++ b/board/escc/recover.sh @@ -7,5 +7,5 @@ cd .. scons -u -j$(nproc) --escc cd escc -$DFU_UTIL -d 0483:df11 -a 0 -s 0x08004000 -D obj/escc.bin.signed -$DFU_UTIL -d 0483:df11 -a 0 -s 0x08000000:leave -D obj/bootstub.escc.bin +$DFU_UTIL -d 0483:df11 -a 0 -s 0x08020000 -D obj/escc_h7.bin.signed +$DFU_UTIL -d 0483:df11 -a 0 -s 0x08000000:leave -D obj/bootstub.escc_h7.bin \ No newline at end of file diff --git a/board/safety.h b/board/safety.h index 821bdd45eed..4e609ae3438 100644 --- a/board/safety.h +++ b/board/safety.h @@ -23,6 +23,8 @@ #include "safety/safety_elm327.h" #include "safety/safety_body.h" +#include "safety_sunnypilot_common.h" + #ifdef ESCC #include "safety/safety_hyundai_escc.h" #endif diff --git a/board/safety_sunnypilot_common.h b/board/safety_sunnypilot_common.h index e2dc1db2ef4..d9f63f10cb7 100644 --- a/board/safety_sunnypilot_common.h +++ b/board/safety_sunnypilot_common.h @@ -1,44 +1,7 @@ #ifndef SAFETY_SUNNYPILOT_COMMON_H #define SAFETY_SUNNYPILOT_COMMON_H -void mads_acc_main_check(const bool main_on) { - if (main_on && mads_enabled) { - controls_allowed = true; - } - if (!main_on && acc_main_on_prev) { - disengageFromBrakes = false; - controls_allowed = false; - controls_allowed_long = false; - } - acc_main_on_prev = main_on; -} - -void mads_lkas_button_check(const bool lkas_pressed) { - if (lkas_pressed && !lkas_pressed_prev) { - controls_allowed = true; - } - lkas_pressed_prev = lkas_pressed; -} - -void mads_exit_controls_check(void) { - if (alternative_experience & ALT_EXP_MADS_DISABLE_DISENGAGE_LATERAL_ON_BRAKE) { - disengageFromBrakes = true; - controls_allowed_long = false; - } else { - if ((alternative_experience & ALT_EXP_ENABLE_MADS) && controls_allowed) { - disengageFromBrakes = true; - } - controls_allowed = false; - controls_allowed_long = false; - } -} - -void mads_resume_controls_check(void) { - disengageFromBrakes = false; - if (alternative_experience & ALT_EXP_ENABLE_MADS) { - controls_allowed = true; - } -} +#include typedef struct { uint8_t fca_cmd_act; From d5e5bd8dcbf4d5b258f45231cc391989fa38dda7 Mon Sep 17 00:00:00 2001 From: Alan Bixby Date: Mon, 25 May 2026 15:03:55 -0400 Subject: [PATCH 5/7] Harden Hyundai ESCC radar forwarding - Add radar TX queue backpressure and FDCAN3 retransmit disable - Keep SCC block behavior while updating ESCC forwarding tests --- board/drivers/can_common_declarations.h | 2 + board/health.h | 2 + board/safety/safety_hyundai_escc.h | 31 ++++++++-- board/stm32h7/llfdcan.h | 9 ++- tests/safety/test_hyundai.py | 75 +++++++++++++++++++++++++ 5 files changed, 111 insertions(+), 8 deletions(-) diff --git a/board/drivers/can_common_declarations.h b/board/drivers/can_common_declarations.h index 57bf87d9c7c..d84b619b64c 100644 --- a/board/drivers/can_common_declarations.h +++ b/board/drivers/can_common_declarations.h @@ -1,5 +1,7 @@ #pragma once +#include "health.h" + typedef struct { volatile uint32_t w_ptr; volatile uint32_t r_ptr; diff --git a/board/health.h b/board/health.h index 74d822dc6ce..d4f30b23411 100644 --- a/board/health.h +++ b/board/health.h @@ -1,3 +1,5 @@ +#pragma once + // When changing these structs, python/__init__.py needs to be kept up to date! #define HEALTH_PACKET_VERSION 16 diff --git a/board/safety/safety_hyundai_escc.h b/board/safety/safety_hyundai_escc.h index e35497608e9..c4d8ecf6b97 100644 --- a/board/safety/safety_hyundai_escc.h +++ b/board/safety/safety_hyundai_escc.h @@ -1,8 +1,13 @@ #pragma once +#include "drivers/can_common_declarations.h" + #define DEVNULL_BUS (-1) #define CAR_BUS 0 #define RADAR_BUS 2 +// Secondary backstop for radar isolation. FDCAN3 DAR is the primary protection +// against a stuck TX FIFO; this keeps CAR->RADAR forwarding from exhausting tx3_q. +#define RADAR_TX_QUEUE_MIN_SLOTS 50U bool scc_block_allowed = false; uint32_t sunnypilot_detected_last = 0; @@ -10,6 +15,13 @@ uint32_t sunnypilot_detected_last = 0; // Initialize bytes to send to 2AB ESCC_Msg escc = {0}; +static safety_config escc_init(uint16_t param) { + scc_block_allowed = false; + sunnypilot_detected_last = 0U; + // Reuse alloutput controls setup; ESCC forwarding does not read alloutput_passthrough. + return alloutput_init(param); +} + static void escc_rx_hook(const CANPacket_t* to_push) { const int bus = GET_BUS(to_push); const int addr = GET_ADDR(to_push); @@ -72,7 +84,7 @@ static int escc_fwd_hook(const int bus_src, const int addr) { #ifdef DEBUG print("escc_fwd_hook: "); putui(bus_src); print(" - "); puth4(addr); print(" scc_block_allowed: "); print(scc_block_allowed?"yes":"no" ); print("\n"); #endif - // SCC messages are SCC11 (0x420), SCC12 (0x421), SCC13 (0x50A), SCC14 (0x389) + // SCC messages are SCC11 (0x420), SCC12 (0x421), SCC13 (0x50A), SCC14 (0x389) const int is_scc_msg = addr == 0x420 || addr == 0x421 || addr == 0x50A || addr == 0x389; const uint32_t ts = MICROSECOND_TIMER->CNT; @@ -82,22 +94,29 @@ static int escc_fwd_hook(const int bus_src, const int addr) { sunnypilot_detected_last = ts; } - // Default forwarding logic - int bus_dst = (bus_src == CAR_BUS) ? RADAR_BUS : CAR_BUS; - // Update the scc_block_allowed status based on elapsed time const uint32_t ts_elapsed = get_ts_elapsed(ts, sunnypilot_detected_last); scc_block_allowed = (ts_elapsed <= 150000); + int bus_dst = DEVNULL_BUS; + if (bus_src == CAR_BUS) { + bus_dst = (can_slots_empty(can_queues[RADAR_BUS]) >= RADAR_TX_QUEUE_MIN_SLOTS) ? RADAR_BUS : DEVNULL_BUS; + } else if (bus_src == RADAR_BUS) { + bus_dst = CAR_BUS; + } else { + // ESCC only bridges the car bus and isolated radar spur. + } + // If we are allowed to block, and this is an scc msg coming from radar (or somehow we are sending it TO the radar) we block - if (scc_block_allowed && is_scc_msg && (bus_src == RADAR_BUS || bus_dst == RADAR_BUS)) + if (scc_block_allowed && is_scc_msg && (bus_src == RADAR_BUS || bus_dst == RADAR_BUS)) { bus_dst = DEVNULL_BUS; + } return bus_dst; } const safety_hooks hyundai_escc_hooks = { - .init = alloutput_init, + .init = escc_init, .rx = escc_rx_hook, .tx = escc_tx_hook, .fwd = escc_fwd_hook, diff --git a/board/stm32h7/llfdcan.h b/board/stm32h7/llfdcan.h index 64a40bd072e..cc81028813f 100644 --- a/board/stm32h7/llfdcan.h +++ b/board/stm32h7/llfdcan.h @@ -151,8 +151,13 @@ bool llcan_init(FDCAN_GlobalTypeDef *FDCANx) { if (ret) { // Enable config change FDCANx->CCCR |= FDCAN_CCCR_CCE; - // Enable automatic retransmission - FDCANx->CCCR &= ~(FDCAN_CCCR_DAR); + // Disable automatic retransmission on FDCAN3/radar spur to prevent a missed ACK + // from pinning the single TX FIFO element and forcing core resets. + if (FDCANx == FDCAN3) { + FDCANx->CCCR |= FDCAN_CCCR_DAR; + } else { + FDCANx->CCCR &= ~(FDCAN_CCCR_DAR); + } // Enable transmission pause feature FDCANx->CCCR |= FDCAN_CCCR_TXP; // Disable protocol exception handling diff --git a/tests/safety/test_hyundai.py b/tests/safety/test_hyundai.py index 4d58cf750ce..2beb2dcaa86 100755 --- a/tests/safety/test_hyundai.py +++ b/tests/safety/test_hyundai.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 import random +import re import unittest +from pathlib import Path from panda import Panda from panda.tests.libpanda import libpanda_py import panda.tests.safety.common as common @@ -283,5 +285,78 @@ def test_disabled_ecu_alive(self): pass +class TestHyundaiESCCFwdSafety(common.PandaSafetyTestBase): + # Keep in sync with board/safety.h. + SAFETY_HYUNDAI_ESCC = 29 + TX_MSGS = [] + RADAR_TX_QUEUE_MIN_SLOTS = 50 + NON_SCC_ADDR = 0x123 + SCC_ADDRS = (0x420, 0x421, 0x50A, 0x389) + + def setUp(self): + self.safety = libpanda_py.libpanda + safety_h = Path(__file__).parents[2] / "board" / "safety.h" + match = re.search(r"#define\s+SAFETY_HYUNDAI_ESCC\s+(\d+)U", safety_h.read_text()) + self.assertIsNotNone(match) + self.assertEqual(self.SAFETY_HYUNDAI_ESCC, int(match.group(1))) + if self.safety.set_safety_hooks(self.SAFETY_HYUNDAI_ESCC, 0) != 0: + raise unittest.SkipTest("SAFETY_HYUNDAI_ESCC is not compiled into libpanda") + self.safety.init_tests() + self._clear_radar_tx_queue() + + def _clear_radar_tx_queue(self): + msg = libpanda_py.ffi.new('CANPacket_t *') + while self.safety.can_pop(self.safety.tx3_q, msg): + pass + + def _fill_radar_tx_queue_to_empty_slots(self, empty_slots): + msg = common.make_msg(2, self.NON_SCC_ADDR, 8) + while self.safety.can_slots_empty(self.safety.tx3_q) > empty_slots: + self.assertTrue(self.safety.can_push(self.safety.tx3_q, msg)) + + def test_car_to_radar_forwards_when_radar_queue_has_space(self): + self.assertGreaterEqual(self.safety.can_slots_empty(self.safety.tx3_q), self.RADAR_TX_QUEUE_MIN_SLOTS) + self.assertEqual(2, self.safety.safety_fwd_hook(0, self.NON_SCC_ADDR)) + + def test_car_to_radar_drops_when_radar_queue_below_threshold(self): + self._fill_radar_tx_queue_to_empty_slots(self.RADAR_TX_QUEUE_MIN_SLOTS - 1) + + self.assertEqual(self.RADAR_TX_QUEUE_MIN_SLOTS - 1, self.safety.can_slots_empty(self.safety.tx3_q)) + self.assertEqual(-1, self.safety.safety_fwd_hook(0, self.NON_SCC_ADDR)) + + def test_car_to_radar_threshold_boundary(self): + self._fill_radar_tx_queue_to_empty_slots(self.RADAR_TX_QUEUE_MIN_SLOTS) + self.assertEqual(self.RADAR_TX_QUEUE_MIN_SLOTS, self.safety.can_slots_empty(self.safety.tx3_q)) + self.assertEqual(2, self.safety.safety_fwd_hook(0, self.NON_SCC_ADDR)) + + self.assertTrue(self.safety.can_push(self.safety.tx3_q, common.make_msg(2, self.NON_SCC_ADDR, 8))) + self.assertEqual(self.RADAR_TX_QUEUE_MIN_SLOTS - 1, self.safety.can_slots_empty(self.safety.tx3_q)) + self.assertEqual(-1, self.safety.safety_fwd_hook(0, self.NON_SCC_ADDR)) + + def test_radar_to_car_forwards_when_radar_queue_below_threshold(self): + self._fill_radar_tx_queue_to_empty_slots(self.RADAR_TX_QUEUE_MIN_SLOTS - 1) + + self.assertEqual(0, self.safety.safety_fwd_hook(2, self.NON_SCC_ADDR)) + + def test_bus_one_is_not_forwarded(self): + self.assertEqual(-1, self.safety.safety_fwd_hook(1, self.NON_SCC_ADDR)) + + def test_sunnypilot_scc_block_preserved(self): + self.safety.set_timer(1000) + self.assertEqual(-1, self.safety.safety_fwd_hook(0, 0x420)) + + for addr in self.SCC_ADDRS: + with self.subTest(addr=addr): + self.assertEqual(-1, self.safety.safety_fwd_hook(2, addr)) + + def test_sunnypilot_scc_block_expires(self): + self.safety.set_timer(1000) + self.safety.safety_fwd_hook(0, 0x420) + + self.safety.set_timer(151001) + + self.assertEqual(0, self.safety.safety_fwd_hook(2, 0x420)) + + if __name__ == "__main__": unittest.main() From 8645ffb894b78c802d8a31919d95978c7e25aea0 Mon Sep 17 00:00:00 2001 From: Alan Bixby Date: Mon, 25 May 2026 18:37:01 -0400 Subject: [PATCH 6/7] Fix CAN retransmit disable for radar spur - Use logical bus number to gate DAR instead of FDCAN instance - Keep radar spur from stalling on missed ACKs and triggering resets --- board/stm32h7/llfdcan.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/board/stm32h7/llfdcan.h b/board/stm32h7/llfdcan.h index cc81028813f..67636e2ac59 100644 --- a/board/stm32h7/llfdcan.h +++ b/board/stm32h7/llfdcan.h @@ -1,4 +1,5 @@ #include "llfdcan_declarations.h" +#include "drivers/can_common_declarations.h" // kbps multiplied by 10 const uint32_t speeds[SPEEDS_ARRAY_SIZE] = {100U, 200U, 500U, 1000U, 1250U, 2500U, 5000U, 10000U}; @@ -151,9 +152,10 @@ bool llcan_init(FDCAN_GlobalTypeDef *FDCANx) { if (ret) { // Enable config change FDCANx->CCCR |= FDCAN_CCCR_CCE; - // Disable automatic retransmission on FDCAN3/radar spur to prevent a missed ACK - // from pinning the single TX FIFO element and forcing core resets. - if (FDCANx == FDCAN3) { + uint8_t bus_number = BUS_NUM_FROM_CAN_NUM(can_number); + // Disable automatic retransmission on the logical radar spur to prevent a + // missed ACK from pinning the single TX FIFO element and forcing core resets. + if (bus_number == 2U) { FDCANx->CCCR |= FDCAN_CCCR_DAR; } else { FDCANx->CCCR &= ~(FDCAN_CCCR_DAR); From f5626083b638052e8d25d72957626572ec099bdf Mon Sep 17 00:00:00 2001 From: Alan Bixby Date: Sun, 31 May 2026 16:47:32 -0400 Subject: [PATCH 7/7] Initialize ESCC CAN packet flags --- board/main.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/board/main.c b/board/main.c index 2a5dde30c0d..54e889f9382 100644 --- a/board/main.c +++ b/board/main.c @@ -168,7 +168,10 @@ void send_escc_msg(const ESCC_Msg *msg, const int bus_number) { dat[6] = (msg->acc_obj_rel_spd_2); dat[7] = (msg->cr_vsm_deccmd_fca11); - CANPacket_t to_send; + CANPacket_t to_send = { 0 }; + to_send.fd = 0U; + to_send.returned = 0U; + to_send.rejected = 0U; to_send.extended = CAN_ESCC_OUTPUT >= 0x800 ? 1 : 0; to_send.addr = CAN_ESCC_OUTPUT; to_send.bus = bus_number;