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/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/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..4ec9577e8b3 --- /dev/null +++ b/board/escc/SConscript @@ -0,0 +1,39 @@ +import os +import copy + +Import('build_project', 'base_project_h7') + +build_projects = {} + +# RENAMED from "escc" to "panda" +build_projects["panda"] = { + "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", + ], +} + +# 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", + "-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"]) \ No newline at end of file diff --git a/board/escc/flash.py b/board/escc/flash.py new file mode 100755 index 00000000000..2f01378e01b --- /dev/null +++ b/board/escc/flash.py @@ -0,0 +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__": + 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) + + # --- 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: + with Panda(serial=s) as p: + 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 new file mode 100755 index 00000000000..ccbc9a11997 --- /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/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 new file mode 100755 index 00000000000..58fb43d41e1 --- /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 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/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/main.c b/board/main.c index 7c068113a0e..54e889f9382 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) && @@ -124,6 +125,64 @@ 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 = { 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; + 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 +245,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 +296,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 +335,7 @@ static void tick_handler(void) { fan_set_power(enabled ? 50U : 0U); } } +#endif // check registers check_registers(); @@ -335,8 +398,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); diff --git a/board/safety.h b/board/safety.h index 5470e7b3646..4e609ae3438 100644 --- a/board/safety.h +++ b/board/safety.h @@ -23,6 +23,12 @@ #include "safety/safety_elm327.h" #include "safety/safety_body.h" +#include "safety_sunnypilot_common.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 +62,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 +385,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..c4d8ecf6b97 --- /dev/null +++ b/board/safety/safety_hyundai_escc.h @@ -0,0 +1,126 @@ +#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; + +// 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); + + + 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; + } + + // 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)) { + bus_dst = DEVNULL_BUS; + } + + return bus_dst; +} + +const safety_hooks hyundai_escc_hooks = { + .init = escc_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..d9f63f10cb7 --- /dev/null +++ b/board/safety_sunnypilot_common.h @@ -0,0 +1,27 @@ +#ifndef SAFETY_SUNNYPILOT_COMMON_H +#define SAFETY_SUNNYPILOT_COMMON_H + +#include + +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/board/stm32h7/llfdcan.h b/board/stm32h7/llfdcan.h index 64a40bd072e..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,8 +152,14 @@ bool llcan_init(FDCAN_GlobalTypeDef *FDCANx) { if (ret) { // Enable config change FDCANx->CCCR |= FDCAN_CCCR_CCE; - // Enable automatic retransmission - FDCANx->CCCR &= ~(FDCAN_CCCR_DAR); + 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); + } // Enable transmission pause feature FDCANx->CCCR |= FDCAN_CCCR_TXP; // Disable protocol exception handling 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") 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()