From dea0ca1a9175487e806a957e4eaf3232a3e3f275 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Mon, 2 Nov 2026 20:50:47 +0000 Subject: [PATCH] cereal: name the new panda faults (#39133) --- .github/workflows/tests.yaml | 1 + openpilot/cereal/log.capnp | 22 +- openpilot/selfdrive/pandad/SConscript | 1 + openpilot/selfdrive/pandad/panda_faults.h | 21 + openpilot/selfdrive/pandad/pandad.cc | 15 +- .../pandad/tests/test_panda_faults.cc | 39 ++ panda/SConscript | 11 + panda/board/body/main.c | 8 +- panda/board/bootstub.c | 4 + panda/board/crc.h | 12 +- panda/board/crypto/sign.py | 12 +- panda/board/drivers/can_common.h | 12 +- panda/board/drivers/clock_source.h | 4 +- panda/board/drivers/drivers.h | 17 +- panda/board/drivers/fdcan.h | 14 + panda/board/drivers/gpio.h | 6 +- panda/board/drivers/harness.h | 51 +++ panda/board/drivers/interrupts.h | 74 +++- panda/board/drivers/registers.h | 15 +- panda/board/drivers/self_test.h | 377 ++++++++++++++++++ panda/board/drivers/timers.h | 36 +- panda/board/early_init.h | 40 +- panda/board/health.h | 1 + panda/board/main.c | 94 ++++- panda/board/main_comms.h | 95 ++++- panda/board/stm32f4/llbxcan.h | 36 +- panda/board/stm32f4/llfan.h | 2 +- panda/board/stm32f4/llusb.h | 6 +- panda/board/stm32h7/clock.h | 36 ++ panda/board/stm32h7/interrupt_handlers.h | 4 +- panda/board/stm32h7/lladc.h | 41 +- panda/board/stm32h7/llfan.h | 2 +- panda/board/stm32h7/llfdcan.h | 36 +- panda/board/stm32h7/llflash.h | 2 +- panda/board/stm32h7/lli2c.h | 18 +- panda/board/stm32h7/llmpu.h | 103 +++++ panda/board/stm32h7/llspi.h | 16 +- panda/board/stm32h7/llusb.h | 6 +- panda/board/stm32h7/peripherals.h | 32 ++ panda/board/stm32h7/self_test.s | 377 ++++++++++++++++++ panda/board/stm32h7/sound.h | 4 +- panda/board/stm32h7/startup_stm32h7x5xx.s | 16 +- panda/board/stm32h7/stm32h7_config.h | 1 + panda/board/stm32h7/stm32h7x5_flash.ld | 36 ++ panda/board/sys/faults.h | 21 +- panda/board/sys/power_saving.h | 4 +- panda/board/sys/sys.h | 20 +- panda/python/__init__.py | 58 ++- panda/python/spi.py | 34 +- panda/tests/libpanda/panda.c | 3 + panda/tests/unit/test_diagnostic_requests.py | 86 ++++ tools/ci/run_predeploy.py | 2 +- tools/ci/tests/test_predeploy.py | 2 +- upstream-sync.json | 4 +- 54 files changed, 1826 insertions(+), 164 deletions(-) create mode 100644 openpilot/selfdrive/pandad/panda_faults.h create mode 100644 openpilot/selfdrive/pandad/tests/test_panda_faults.cc create mode 100644 panda/board/drivers/self_test.h create mode 100644 panda/board/stm32h7/llmpu.h create mode 100644 panda/board/stm32h7/self_test.s create mode 100644 panda/tests/unit/test_diagnostic_requests.py diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index b07040b499..fb11f69296 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -225,6 +225,7 @@ jobs: openpilot/selfdrive/pandad/tests/test_pandad_canprotocol openpilot/selfdrive/pandad/tests/test_aol_protocol openpilot/selfdrive/pandad/tests/test_aol_wire + openpilot/selfdrive/pandad/tests/test_panda_faults openpilot/selfdrive/pandad/tests/test_producer_timing openpilot/selfdrive/pandad/tests/test_producer_sink - name: Test map provider to host messaging diff --git a/openpilot/cereal/log.capnp b/openpilot/cereal/log.capnp index c2f669ab5c..604cc48daa 100644 --- a/openpilot/cereal/log.capnp +++ b/openpilot/cereal/log.capnp @@ -590,25 +590,27 @@ struct PandaState @0xa7649e2575e4591e { interruptRateGmlanDEPRECATED @6; interruptRateInterrupts @7; interruptRateSpiDma @8; - interruptRateSpiCs @9; - interruptRateUart1 @10; - interruptRateUart2 @11; - interruptRateUart3 @12; - interruptRateUart5 @13; - interruptRateUartDma @14; + selfTestCpu @9; + selfTestRam @10; + selfTestFlash @11; + ecc @12; + outputReadback @13; + dma @14; interruptRateUsb @15; - interruptRateTim1 @16; - interruptRateTim3 @17; + clock @16; + interruptMissing @17; registerDivergent @18; interruptRateKlineInit @19; interruptRateClockSource @20; interruptRateTick @21; interruptRateExti @22; interruptRateSpi @23; - interruptRateUart7 @24; + adc @24; sirenMalfunction @25; heartbeatLoopWatchdog @26; - # Update max fault type in boardd when adding faults + interruptRateSoundDma @27; + mpu @28; + timer @29; } enum PandaType @0x8a58adf93e5b3751 { diff --git a/openpilot/selfdrive/pandad/SConscript b/openpilot/selfdrive/pandad/SConscript index 16ad9f2e19..afdf48a92d 100644 --- a/openpilot/selfdrive/pandad/SConscript +++ b/openpilot/selfdrive/pandad/SConscript @@ -7,6 +7,7 @@ if GetOption('extras') or any(target.startswith('openpilot/selfdrive/pandad/test env.Program('tests/test_panda_usb', ['tests/test_panda_usb.cc', usb_test], LIBS=[common, 'pthread']) env.Program('tests/test_aol_protocol', ['tests/test_aol_protocol.cc']) env.Program('tests/test_aol_wire', ['tests/test_aol_wire.cc'], LIBS=[messaging, 'capnp', 'kj']) + env.Program('tests/test_panda_faults', ['tests/test_panda_faults.cc'], LIBS=[messaging, 'capnp', 'kj']) # Compose producer freshness, session negotiation and native ACK wire contracts. env.Program('tests/test_producer_timing', ['tests/test_producer_timing.cc'], LIBS=[messaging, 'capnp', 'kj']) env.Program('tests/test_producer_sink', ['tests/test_producer_sink.cc'], LIBS=[messaging, 'capnp', 'kj']) diff --git a/openpilot/selfdrive/pandad/panda_faults.h b/openpilot/selfdrive/pandad/panda_faults.h new file mode 100644 index 0000000000..76bcc2f36d --- /dev/null +++ b/openpilot/selfdrive/pandad/panda_faults.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +#include "openpilot/cereal/gen/cpp/log.capnp.h" + +inline void fill_panda_faults(cereal::PandaState::Builder &state, uint32_t mask) { + std::bitset<32> bits(mask); + static_assert(static_cast(cereal::PandaState::FaultType::TIMER) < 32); + for (size_t bit = static_cast(cereal::PandaState::FaultType::TIMER) + 1; bit < bits.size(); ++bit) { + bits.reset(bit); + } + auto faults = state.initFaults(bits.count()); + size_t index = 0; + for (size_t bit = 0; bit < bits.size(); ++bit) { + if (bits.test(bit)) { + faults.set(index++, static_cast(bit)); + } + } +} diff --git a/openpilot/selfdrive/pandad/pandad.cc b/openpilot/selfdrive/pandad/pandad.cc index 54b0b25a35..e2c5aa0c0a 100644 --- a/openpilot/selfdrive/pandad/pandad.cc +++ b/openpilot/selfdrive/pandad/pandad.cc @@ -17,7 +17,7 @@ #include #include #include -#include +#include "openpilot/selfdrive/pandad/panda_faults.h" #include #include #include @@ -276,18 +276,7 @@ std::optional send_panda_states(PubMaster *pm, const std::vector fill_panda_can_state(cs[j], can_health[j]); } - // Convert faults bitset to capnp list - std::bitset fault_bits(health.faults_pkt); - auto faults = ps.initFaults(fault_bits.count()); - - size_t j = 0; - for (size_t f = size_t(cereal::PandaState::FaultType::RELAY_MALFUNCTION); - f <= size_t(cereal::PandaState::FaultType::HEARTBEAT_LOOP_WATCHDOG); f++) { - if (fault_bits.test(f)) { - faults.set(j, cereal::PandaState::FaultType(f)); - j++; - } - } + fill_panda_faults(ps, health.faults_pkt); } if (snapshot && evt.getValid()) { snapshot->source_mono_time = evt.getLogMonoTime(); diff --git a/openpilot/selfdrive/pandad/tests/test_panda_faults.cc b/openpilot/selfdrive/pandad/tests/test_panda_faults.cc new file mode 100644 index 0000000000..44e26a4c4b --- /dev/null +++ b/openpilot/selfdrive/pandad/tests/test_panda_faults.cc @@ -0,0 +1,39 @@ +#include "openpilot/selfdrive/pandad/panda_faults.h" +#include "openpilot/selfdrive/pandad/aol_wire.h" + +#include + +#ifdef NDEBUG +#error "Native fault tests require active assertions" +#endif + +int main() { + for (uint32_t mask : {0U, 1U << 27, 1U << 28, 1U << 29, 1U << 30, 1U << 31, 0xFFFFFFFFU}) { + capnp::MallocMessageBuilder message; + auto event = message.initRoot(); + auto state = event.initPandaStates(1)[0]; + fill_panda_faults(state, mask); + auto bytes = capnp::messageToFlatArray(message); + capnp::FlatArrayMessageReader reader(bytes.asPtr()); + auto faults = reader.getRoot().getPandaStates()[0].getFaults(); + uint32_t represented = 0; + for (auto fault : faults) { + const unsigned int bit = static_cast(fault); + assert(bit <= 29U); + assert((represented & (1U << bit)) == 0U); + represented |= 1U << bit; + } + assert(represented == (mask & 0x3FFFFFFFU)); + if ((mask & 1U) == 0U) { + for (auto fault : faults) assert(fault != cereal::PandaState::FaultType::RELAY_MALFUNCTION); + } + AolSafetyWireFields wire = {1, true, 100, 200, 5, 34, true, false, true, false, "panda", "axis", + {100, {{0, "panda", 5, 34, 0, true, false, false, mask}}}}; + auto raw = encode_aol_safety_wire(wire); + assert(!raw.empty()); + auto aligned = kj::heapArray(raw.size() / sizeof(capnp::word)); + std::memcpy(aligned.begin(), raw.data(), raw.size()); + capnp::FlatArrayMessageReader raw_reader(aligned.asPtr()); + assert(raw_reader.getRoot().getPandaInventory()[0].getFaults() == mask); + } +} diff --git a/panda/SConscript b/panda/SConscript index 2aa077158d..a860f639b4 100644 --- a/panda/SConscript +++ b/panda/SConscript @@ -21,6 +21,15 @@ else: cert_fn = File("./board/crypto/certs/debug").srcnode().relpath common_flags += ["-DALLOW_DEBUG"] +fault_injection = os.getenv("PANDA_FAULT_INJECTION", "0") +if fault_injection not in ("0", "1"): + raise ValueError("PANDA_FAULT_INJECTION must be 0 or 1") +if fault_injection == "1": + if BUILD_TYPE != "DEBUG": + raise ValueError("Fault injection requires a dedicated debug bench build") + common_flags += ["-DPANDA_FAULT_INJECTION"] + BUILD_TYPE += "-FAULTTEST" + def objcopy(source, target, env, for_signature): return '$OBJCOPY -O binary %s %s' % (source[0], target[0]) @@ -88,6 +97,7 @@ def build_project(project_name, project, main, extra_flags): "-fmax-errors=1", f"-T{File(project['LINKER_SCRIPT']).srcnode().relpath}", "-fsingle-precision-constant", + "-fdata-sections", "-Os", "-g", ] @@ -128,6 +138,7 @@ def build_project(project_name, project, main, extra_flags): # Build + sign main (aka app) main_elf = env.Program(f"{project_dir}/main.elf", [ startup, + *([env.Object("./board/stm32h7/self_test.s")] if "-DSTM32H7" in project["FLAGS"] else []), main ], LINKFLAGS=[f"-Wl,--section-start,.isr_vector={project['APP_START_ADDRESS']}"] + flags) main_bin = env.Objcopy(f"{project_dir}/main.bin", main_elf) diff --git a/panda/board/body/main.c b/panda/board/body/main.c index f02ff559c7..4b2396e023 100644 --- a/panda/board/body/main.c +++ b/panda/board/body/main.c @@ -17,6 +17,10 @@ #include "board/body/main_comms.h" +// body has no safety modes +void fault_safe_state(void) { +} + static volatile uint32_t tick_count = 0U; static volatile uint32_t ignition_press_timestamp_us = 0U; static volatile bool ignition = false; @@ -94,11 +98,11 @@ int main(void) { REGISTER_INTERRUPT(EXTI15_10_IRQn, exti15_10_handler, 10000U, FAULT_INTERRUPT_RATE_EXTI); NVIC_ClearPendingIRQ(EXTI15_10_IRQn); - NVIC_EnableIRQ(EXTI15_10_IRQn); + nvic_enable(EXTI15_10_IRQn); REGISTER_INTERRUPT(TIM8_UP_TIM13_IRQn, bldc_tim8_handler, 100000U, FAULT_INTERRUPT_RATE_TICK); NVIC_ClearPendingIRQ(TIM8_UP_TIM13_IRQn); - NVIC_EnableIRQ(TIM8_UP_TIM13_IRQn); + nvic_enable(TIM8_UP_TIM13_IRQn); REGISTER_INTERRUPT(TICK_TIMER_IRQ, tick_handler, 10U, FAULT_INTERRUPT_RATE_TICK); diff --git a/panda/board/bootstub.c b/panda/board/bootstub.c index 8ef7fc1418..fa5d454416 100644 --- a/panda/board/bootstub.c +++ b/panda/board/bootstub.c @@ -23,6 +23,10 @@ void __initialize_hardware_early(void) { early_initialization(); } +// no CAN or safety mode in the bootstub +void fault_safe_state(void) { +} + void fail(void) { soft_flasher_start(); } diff --git a/panda/board/crc.h b/panda/board/crc.h index ce53984e9e..bbf350003e 100644 --- a/panda/board/crc.h +++ b/panda/board/crc.h @@ -13,10 +13,14 @@ void crc32_init(void) { } } -uint32_t crc32(const uint8_t *dat, uint32_t len) { - uint32_t crc = 0xFFFFFFFFU; +uint32_t crc32_update(uint32_t crc, const uint8_t *dat, uint32_t len) { + uint32_t ret = crc; for (uint32_t i = 0U; i < len; i++) { - crc = crc32_table[(crc ^ dat[i]) & 0xFFU] ^ (crc >> 8U); + ret = crc32_table[(ret ^ dat[i]) & 0xFFU] ^ (ret >> 8U); } - return crc ^ 0xFFFFFFFFU; + return ret; +} + +uint32_t crc32(const uint8_t *dat, uint32_t len) { + return crc32_update(0xFFFFFFFFU, dat, len) ^ 0xFFFFFFFFU; } diff --git a/panda/board/crypto/sign.py b/panda/board/crypto/sign.py index bde1ef18f1..809acc1032 100755 --- a/panda/board/crypto/sign.py +++ b/panda/board/crypto/sign.py @@ -5,6 +5,7 @@ import struct import hashlib import binascii import base64 +import zlib def read_der(dat): @@ -36,12 +37,11 @@ if __name__ == "__main__": with open(sys.argv[2], "wb") as f: if os.getenv("SETLEN") is not None: - # add the version at the end - dat += b"VERS" + struct.pack("I", VERSION) - # add the length at the beginning - x = struct.pack("I", len(dat)) + dat[4:] - # mock signature of dat[4:] - dd = hashlib.sha1(dat[4:]).digest() + # add the length at the beginning, then the crc32 for the flash self-test and the version at the end + x = struct.pack("I", len(dat) + 12) + dat[4:] + x += struct.pack("I", zlib.crc32(x)) + b"VERS" + struct.pack("I", VERSION) + # mock signature of x[4:] + dd = hashlib.sha1(x[4:]).digest() else: x = dat dd = hashlib.sha1(dat).digest() diff --git a/panda/board/drivers/can_common.h b/panda/board/drivers/can_common.h index 036a593e04..4187cee1ff 100644 --- a/panda/board/drivers/can_common.h +++ b/panda/board/drivers/can_common.h @@ -186,7 +186,17 @@ bool can_check_checksum(CANPacket_t *packet) { } void can_send(CANPacket_t *to_push, uint8_t bus_number, bool skip_tx_hook) { - if (skip_tx_hook || safety_tx_hook(to_push) != 0) { + bool tx_allowed = skip_tx_hook; + if (!tx_allowed) { + #ifdef STM32H7 + mpu_safety_open(); + #endif + tx_allowed = safety_tx_hook(to_push) != 0; + #ifdef STM32H7 + mpu_safety_close(); + #endif + } + if (tx_allowed) { if (bus_number < PANDA_CAN_CNT) { // add CAN packet to send queue tx_buffer_overflow += can_push(can_queues[bus_number], to_push) ? 0U : 1U; diff --git a/panda/board/drivers/clock_source.h b/panda/board/drivers/clock_source.h index 4436480e49..971b3d2555 100644 --- a/panda/board/drivers/clock_source.h +++ b/panda/board/drivers/clock_source.h @@ -28,8 +28,8 @@ void clock_source_init(bool enable_channel1) { // No interrupts - NVIC_DisableIRQ(TIM1_UP_TIM10_IRQn); - NVIC_DisableIRQ(TIM1_CC_IRQn); + nvic_disable(TIM1_UP_TIM10_IRQn); + nvic_disable(TIM1_CC_IRQn); // Set GPIO as timer channels if (enable_channel1) { diff --git a/panda/board/drivers/drivers.h b/panda/board/drivers/drivers.h index 3059ab3d5a..a67bd64a64 100644 --- a/panda/board/drivers/drivers.h +++ b/panda/board/drivers/drivers.h @@ -165,9 +165,16 @@ typedef struct interrupt { 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 min_call_rate; uint32_t call_rate_fault; } interrupt; +typedef struct { + uint32_t last_ts; + uint32_t prev_ts; + uint32_t mismatches; +} timer_check_t; +bool timer_check(timer_check_t *check, uint32_t *dt_out); void interrupt_timer_init(void); uint32_t microsecond_timer_get(void); @@ -186,12 +193,15 @@ void handle_interrupt(IRQn_Type irq_type); // Every second void interrupt_timer_handler(void); void init_interrupts(bool check_rate_limit); +void nvic_enable(IRQn_Type irq); +void nvic_disable(IRQn_Type irq); +void nvic_check(void); // ******************** registers ******************** // 10 bit hash with 23 as a prime -#define REGISTER_MAP_SIZE 0x3FFU +#define REGISTER_MAP_SIZE 0x400U #define HASHING_PRIME 23U // Do not put bits in the check mask that get changed by the hardware @@ -206,6 +216,11 @@ void register_clear_bits(volatile uint32_t *addr, uint32_t val); void check_registers(void); void init_registers(void); +// ******************** mpu ******************** + +void mpu_safety_open(void); +void mpu_safety_close(void); + // ******************** simple_watchdog ******************** typedef struct simple_watchdog_state_t { diff --git a/panda/board/drivers/fdcan.h b/panda/board/drivers/fdcan.h index 3b58d55af8..95f4360761 100644 --- a/panda/board/drivers/fdcan.h +++ b/panda/board/drivers/fdcan.h @@ -196,7 +196,13 @@ static void can_rx(uint8_t can_number) { can_set_checksum(&to_push); // forwarding + #ifdef STM32H7 + mpu_safety_open(); + #endif int bus_fwd_num = safety_fwd_hook(bus_number, to_push.addr); + #ifdef STM32H7 + mpu_safety_close(); + #endif if (bus_fwd_num >= 0) { CANPacket_t to_send; @@ -218,7 +224,15 @@ static void can_rx(uint8_t can_number) { body_can_rx(&to_push); #endif + #ifdef STM32H7 + + mpu_safety_open(); + + #endif safety_rx_invalid += safety_rx_hook(&to_push) ? 0U : 1U; + #ifdef STM32H7 + mpu_safety_close(); + #endif ignition_can_hook(&to_push); led_set(LED_BLUE, true); diff --git a/panda/board/drivers/gpio.h b/panda/board/drivers/gpio.h index bbbcdbb928..44af963391 100644 --- a/panda/board/drivers/gpio.h +++ b/panda/board/drivers/gpio.h @@ -20,7 +20,7 @@ void set_gpio_mode(GPIO_TypeDef *GPIO, unsigned int pin, unsigned int mode) { uint32_t tmp = GPIO->MODER; tmp &= ~(3UL << shift); tmp |= (mode << shift); - register_set(&(GPIO->MODER), tmp, 0xFFFFFFFFU); + register_set(&(GPIO->MODER), tmp, (3UL << shift)); EXIT_CRITICAL(); } } @@ -51,7 +51,7 @@ void set_gpio_alternate(GPIO_TypeDef *GPIO, unsigned int pin, unsigned int mode) uint32_t tmp = GPIO->AFR[pin >> 3U]; tmp &= ~((uint32_t)0xFU << ((pin & 7U) * 4U)); tmp |= mode << ((pin & 7U) * 4U); - register_set(&(GPIO->AFR[pin >> 3]), tmp, 0xFFFFFFFFU); + register_set(&(GPIO->AFR[pin >> 3]), tmp, (0xFUL << ((pin & 7U) * 4U))); set_gpio_mode(GPIO, pin, MODE_ALTERNATE); EXIT_CRITICAL(); } @@ -63,7 +63,7 @@ void set_gpio_pullup(GPIO_TypeDef *GPIO, unsigned int pin, unsigned int mode) { uint32_t tmp = GPIO->PUPDR; tmp &= ~(3UL << shift); tmp |= (mode << shift); - register_set(&(GPIO->PUPDR), tmp, 0xFFFFFFFFU); + register_set(&(GPIO->PUPDR), tmp, (3UL << shift)); EXIT_CRITICAL(); } } diff --git a/panda/board/drivers/harness.h b/panda/board/drivers/harness.h index 71a301b681..be151e0381 100644 --- a/panda/board/drivers/harness.h +++ b/panda/board/drivers/harness.h @@ -4,6 +4,11 @@ struct harness_t harness; +#if defined(ALLOW_DEBUG) && defined(PANDA_FAULT_INJECTION) +bool relay_readback_inject = false; +uint16_t harness_adc2_offset_mV = 0U; +#endif + // The ignition relay is only used for testing purposes void set_intercept_relay(bool intercept, bool ignition_relay) { bool drive_relay = intercept; @@ -32,6 +37,14 @@ void set_intercept_relay(bool intercept, bool ignition_relay) { } } +// pins go analog in stop mode +static bool relay_pin_mismatch(const GPIO_TypeDef *GPIO, unsigned int pin) { + bool is_output = ((GPIO->MODER >> (pin * 2U)) & 3U) == (uint32_t)MODE_OUTPUT; + bool driven = (GPIO->ODR & (1UL << pin)) != 0U; + bool read = get_gpio_input(GPIO, pin) != 0; + return is_output && (driven != read); +} + bool harness_check_ignition(void) { bool ret = false; @@ -53,6 +66,10 @@ bool harness_check_ignition(void) { static uint8_t harness_detect_orientation(void) { uint8_t ret = harness.status; + #ifdef STM32H7 + static uint32_t adc_mismatches = 0U; + static uint16_t sbu1_adc2_mV = 0U; + #endif #ifndef BOOTSTUB // We can't detect orientation if the relay is being driven @@ -65,6 +82,20 @@ static uint8_t harness_detect_orientation(void) { harness.sbu2_voltage_mV = adc_get_mV(¤t_board->harness_config->adc_signal_SBU2); uint16_t detection_threshold = current_board->avdd_mV / 2U; + #ifdef STM32H7 + // ADC_SM_4: SBU1 is on ADC1 and ADC2 + if (!adc2_busy) { + adc_signal_t sbu1_adc2 = current_board->harness_config->adc_signal_SBU1; + sbu1_adc2.adc = ADC2; + sbu1_adc2_mV = adc_get_mV(&sbu1_adc2); + #if defined(ALLOW_DEBUG) && defined(PANDA_FAULT_INJECTION) + sbu1_adc2_mV += harness_adc2_offset_mV; + #endif + adc_mismatches = (ABS((int32_t)harness.sbu1_voltage_mV - (int32_t)sbu1_adc2_mV) <= 100) ? 0U : (adc_mismatches + 1U); + } + + #endif + // 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) { @@ -86,11 +117,31 @@ static uint8_t harness_detect_orientation(void) { } #endif + // after the ADC lock, the safe state drives the relay + #ifdef STM32H7 + if (adc_mismatches >= 8U) { + fault_occurred_value(FAULT_ADC, ((uint32_t)harness.sbu1_voltage_mV << 16) | sbu1_adc2_mV); + } + #endif + return ret; } void harness_tick(void) { harness.status = harness_detect_orientation(); + + // GPIO_SM_2: relay pins read back what is driven, one tick of slack for switching + static uint32_t relay_mismatches = 0U; + const harness_configuration *cfg = current_board->harness_config; + bool mismatch = relay_pin_mismatch(cfg->GPIO_relay_SBU1, cfg->pin_relay_SBU1) || + relay_pin_mismatch(cfg->GPIO_relay_SBU2, cfg->pin_relay_SBU2); + #if defined(ALLOW_DEBUG) && defined(PANDA_FAULT_INJECTION) + mismatch = mismatch || relay_readback_inject; + #endif + relay_mismatches = mismatch ? (relay_mismatches + 1U) : 0U; + if (relay_mismatches >= 2U) { + fault_occurred(FAULT_OUTPUT_READBACK); + } } void harness_init(void) { diff --git a/panda/board/drivers/interrupts.h b/panda/board/drivers/interrupts.h index 92e0ebe53a..b9e8ecd136 100644 --- a/panda/board/drivers/interrupts.h +++ b/panda/board/drivers/interrupts.h @@ -5,11 +5,51 @@ static void unused_interrupt_handler(void) { // Something is wrong if this handler is called! print("Unused interrupt handler called!\n"); - fault_occurred(FAULT_UNUSED_INTERRUPT_HANDLED); + fault_occurred_value(FAULT_UNUSED_INTERRUPT_HANDLED, __get_IPSR()); } interrupt interrupts[NUM_INTERRUPTS]; +// NVIC_SM_0: every IRQ enable goes through nvic_enable/nvic_disable, except the stop mode wakeups, which exit through a reset +static uint32_t nvic_expected[8]; + +void nvic_enable(IRQn_Type irq) { + ENTER_CRITICAL(); + nvic_expected[(uint32_t)irq >> 5U] |= (1UL << ((uint32_t)irq & 0x1FU)); + NVIC_EnableIRQ(irq); + EXIT_CRITICAL(); +} + +void nvic_disable(IRQn_Type irq) { + ENTER_CRITICAL(); + nvic_expected[(uint32_t)irq >> 5U] &= ~(1UL << ((uint32_t)irq & 0x1FU)); + NVIC_DisableIRQ(irq); + EXIT_CRITICAL(); +} + +// logs the first divergent IRQ, 0x100 added if it's the priority +void nvic_check(void) { + uint32_t diff = 0U; + uint32_t first = 0U; + for (uint8_t i = 0U; i < 8U; i++) { + uint32_t d = NVIC->ISER[i] ^ nvic_expected[i]; + if ((diff == 0U) && (d != 0U)) { + first = ((uint32_t)i * 32U) + __CLZ(__RBIT(d)); + } + diff |= d; + } + // nothing sets a priority, all IRQs stay at 0 + for (uint8_t i = 0U; i < 240U; i++) { + if ((diff == 0U) && (NVIC->IP[i] != 0U)) { + first = 0x100U + (uint32_t)i; + } + diff |= NVIC->IP[i]; + } + if (diff != 0U) { + fault_occurred_value(FAULT_REGISTER_DIVERGENT, first); + } +} + static bool check_interrupt_rate = false; static uint32_t idle_time = 0U; @@ -46,15 +86,31 @@ void handle_interrupt(IRQn_Type irq_type){ EXIT_CRITICAL(); } +static uint32_t interrupt_timer_windows = 0U; + // Every second void interrupt_timer_handler(void) { + static timer_check_t interrupt_timer_period = {0}; if (INTERRUPT_TIMER->SR != 0U) { + // cleared first, a flag cleared on the way out can retrigger the vector + INTERRUPT_TIMER->SR = 0; + interrupt_timer_windows += 1U; + uint32_t dt = 0U; + if (timer_check(&interrupt_timer_period, &dt)) { + fault_occurred_value(FAULT_TIMER, dt); + } for (uint16_t i = 0U; i < NUM_INTERRUPTS; i++) { // Log IRQ call rate faults if (check_interrupt_rate && (interrupts[i].call_counter > interrupts[i].max_call_rate)) { print("Interrupt 0x"); puth(i); print(" fired too often (0x"); puth(interrupts[i].call_counter); print("/s)!\n"); } + // the first window spans boot + if (check_interrupt_rate && (interrupt_timer_windows > 1U) && (interrupts[i].call_counter < interrupts[i].min_call_rate)) { + print("Interrupt 0x"); puth(i); print(" stopped firing (0x"); puth(interrupts[i].call_counter); print("/s)!\n"); + fault_occurred(FAULT_INTERRUPT_MISSING); + } + // Reset interrupt counters interrupts[i].call_rate = interrupts[i].call_counter; interrupts[i].call_counter = 0U; @@ -67,8 +123,21 @@ void interrupt_timer_handler(void) { #endif idle_time = 0U; busy_time = 0U; + } else { + // TIM6 shares the vector with the DAC, which has no interrupts enabled + fault_occurred_value(FAULT_UNUSED_INTERRUPT_HANDLED, __get_IPSR()); + } +} + +// called at 1Hz from the tick, one miss is normal since the two timers drift +void interrupt_timer_check(void) { + static uint32_t last_windows = 0U; + static uint32_t misses = 0U; + misses = (interrupt_timer_windows == last_windows) ? (misses + 1U) : 0U; + last_windows = interrupt_timer_windows; + if (check_interrupt_rate && (misses >= 2U)) { + fault_occurred(FAULT_INTERRUPT_MISSING); } - INTERRUPT_TIMER->SR = 0; } void init_interrupts(bool check_rate_limit){ @@ -76,6 +145,7 @@ void init_interrupts(bool check_rate_limit){ for(uint16_t i=0U; i> 16U) ^ ((((input + 1U) & 0xFFFFU) * HASHING_PRIME) & 0xFFFFU)) & REGISTER_MAP_SIZE); + return (((input >> 16U) ^ ((((input + 1U) & 0xFFFFU) * HASHING_PRIME) & 0xFFFFU)) & (REGISTER_MAP_SIZE - 1U)); } // Do not put bits in the check mask that get changed by the hardware @@ -54,12 +54,21 @@ void register_clear_bits(volatile uint32_t *addr, uint32_t val) { // To be called periodically void check_registers(void){ + // log again every 60 calls, a divergence at boot is printed before the device starts logging + static uint8_t calls = 0U; + calls = (uint8_t)((calls + 1U) % 60U); for(uint16_t i=0U; iCR3); + #else + print(" CR: 0x"); puth(PWR->CR); + #endif + print("\n"); register_map[i].logged_fault = true; } fault_occurred(FAULT_REGISTER_DIVERGENT); diff --git a/panda/board/drivers/self_test.h b/panda/board/drivers/self_test.h new file mode 100644 index 0000000000..1b0c500473 --- /dev/null +++ b/panda/board/drivers/self_test.h @@ -0,0 +1,377 @@ +#pragma once + +#include "board/crc.h" + +// UM2331 CPU_SM_0, RAM_SM_0/7/8, FLASH_SM_0/7/9 +// at boot: all of RAM in board/stm32h7/self_test.s before the C runtime, then CPU, FPU and the whole flash image +// at 8Hz: CPU, FPU, one RAM block, an ECC scrub of the DMA RAMs and one flash chunk, ECC flags of every RAM in use +// UM2331 DMA_SM_3/4 at 8Hz: one DMA copy across the D1 to D2 interconnect, transfer errors of the sound DMAs, SPI's in llspi_queue + +// keep in sync with board/stm32h7/self_test.s +#define SELF_TEST_PASS 0x5AFEC0DEU + +// all IRQs share one priority, so each tick slice stays short +#define RAM_TEST_BLOCK_WORDS 64U +// consecutive blocks overlap by two words, for coupling across a block boundary +#define RAM_TEST_STEP ((RAM_TEST_BLOCK_WORDS - 2U) * 4U) +// kept clear of the frames of the running test +#define RAM_TEST_STACK_WINDOW 512U +// one alias per address bit above the block, AXI SRAM needs the most, 0x100 to 0x40000 +#define RAM_TEST_ALIASES 11U +// SRAM1/2 and SRAM4 words read per tick for ECC scrubbing +#define RAM_SCRUB_WORDS 64U +#define FLASH_CRC_CHUNK 0x400U + +extern uint32_t _dtcmram_start; +extern uint32_t _dtcmram_end; +extern uint32_t _axisram_start; +extern uint32_t _axisram_end; +extern uint32_t _itcmram_start; +extern uint32_t _itcmram_end; +extern uint32_t _sram12_start; +extern uint32_t _sram12_end; +extern uint32_t _sram4_start; +extern uint32_t _sram4_end; +extern uint32_t _stack_guard_end; +extern uint32_t self_test_cookie; + +typedef struct { + uint32_t start; + uint32_t end; +} ram_region; + +// RM0468 Table 11, every RAM in use, not the ETM and backup RAMs +static RAMECC_MonitorTypeDef *const ecc_monitors[] = { + RAMECC1_Monitor1, RAMECC1_Monitor2, RAMECC1_Monitor3, RAMECC1_Monitor4, RAMECC1_Monitor6, + RAMECC2_Monitor1, RAMECC2_Monitor2, RAMECC2_Monitor3, RAMECC3_Monitor1, +}; +#define ECC_MONITOR_CNT (sizeof(ecc_monitors) / sizeof(ecc_monitors[0])) + +#if defined(ALLOW_DEBUG) && defined(PANDA_FAULT_INJECTION) +#define SELF_TEST_CPU 0U +#define SELF_TEST_RAM 1U +#define SELF_TEST_FLASH 2U +#define SELF_TEST_RAM_ECC 3U +#define SELF_TEST_FLASH_ECC 4U +#define SELF_TEST_DMA 5U +static uint8_t self_test_fail = 0U; +static uint32_t flash_crc_flip = 0U; + +// the flash CRC injection goes through the CRC compare +void self_test_inject(uint8_t test) { + if (test == SELF_TEST_FLASH) { + flash_crc_flip = 1U; + } else if (test <= SELF_TEST_DMA) { + self_test_fail |= (uint8_t)(1U << test); + } else { + } +} + +static bool self_test_injected(uint8_t test) { + bool ret = (self_test_fail & (uint8_t)(1U << test)) != 0U; + self_test_fail &= (uint8_t)~(1U << test); + return ret; +} +#endif + +// board/stm32h7/self_test.s +uint32_t cpu_test(void); +uint32_t fpu_test(void); + +// march c-, word wide +static bool ram_test_march(volatile uint32_t *p, uint32_t n, uint32_t bg) { + const uint32_t inv = ~bg; + bool ok = true; + for (uint32_t i = 0U; i < n; i++) { + p[i] = bg; + } + for (uint32_t i = 0U; i < n; i++) { + ok = ok && (p[i] == bg); + p[i] = inv; + } + for (uint32_t i = 0U; i < n; i++) { + ok = ok && (p[i] == inv); + p[i] = bg; + } + for (uint32_t i = n; i > 0U; i--) { + ok = ok && (p[i - 1U] == bg); + p[i - 1U] = inv; + } + for (uint32_t i = n; i > 0U; i--) { + ok = ok && (p[i - 1U] == inv); + p[i - 1U] = bg; + } + for (uint32_t i = 0U; i < n; i++) { + ok = ok && (p[i] == bg); + } + return ok; +} + +// the stack guard faults on access and the running frames can't hold test patterns, both are covered at boot +static bool ram_test_skip(uint32_t start, uint32_t end) { + uint32_t sp = __get_MSP(); + bool guard_hit = (start < (uint32_t)&_stack_guard_end) && (end > (uint32_t)&_stack_guard); + bool stack_hit = (start < (sp + RAM_TEST_STACK_WINDOW)) && (end > (sp - RAM_TEST_STACK_WINDOW)); + return guard_hit || stack_hit; +} + +// a decoder fault on an address bit above the block makes the block share cells with an alias, which then reads the patterns +static bool ram_test_aliases_ok(volatile uint32_t *const *alias, const uint32_t *val, uint8_t n) { + bool ok = true; + for (uint8_t i = 0U; i < n; i++) { + ok = ok && (*alias[i] == val[i]); + } + return ok; +} + +// transparent, interrupts stay off while the block holds test patterns +// the march covers the address decoder within the block, the aliases the address bits above it (UM2331 RAM_SM_8), +// checked after both backgrounds since an alias can hold one of them already +static bool ram_test_block(uint32_t addr, uint32_t words, uint32_t start, uint32_t end) { + // outside the tested regions, copies are read back instead + __attribute__((section(".sram12"))) static volatile uint32_t ram_test_backup[RAM_TEST_BLOCK_WORDS]; + volatile uint32_t *p = (uint32_t *)addr; + volatile uint32_t *alias[RAM_TEST_ALIASES]; + uint32_t alias_val[RAM_TEST_ALIASES]; + uint8_t aliases = 0U; + for (uint32_t bit = RAM_TEST_BLOCK_WORDS * 4U; (bit < (end - start)) && (aliases < RAM_TEST_ALIASES); bit <<= 1U) { + uint32_t a = addr ^ bit; + if ((a >= start) && ((a + 4U) <= end) && !ram_test_skip(a, a + 4U)) { + alias[aliases] = (uint32_t *)a; + aliases++; + } + } + bool ok = true; + ENTER_CRITICAL() + // the block can hold the MPU protected safety state + mpu_safety_open(); + for (uint32_t i = 0U; i < words; i++) { + ram_test_backup[i] = p[i]; + } + for (uint32_t i = 0U; i < words; i++) { + ok = ok && (ram_test_backup[i] == p[i]); + } + for (uint8_t i = 0U; i < aliases; i++) { + alias_val[i] = *alias[i]; + } + ok = ram_test_march(p, words, 0x00000000U) && ok; + ok = ram_test_aliases_ok(alias, alias_val, aliases) && ok; + ok = ram_test_march(p, words, 0x55555555U) && ok; + ok = ram_test_aliases_ok(alias, alias_val, aliases) && ok; + for (uint32_t i = 0U; i < words; i++) { + p[i] = ram_test_backup[i]; + } + for (uint32_t i = 0U; i < words; i++) { + ok = ok && (p[i] == ram_test_backup[i]); + } + mpu_safety_close(); + EXIT_CRITICAL() + return ok; +} + +// next block of the running pass, wraps around all regions, failed is set to the block address on a failure +static bool ram_test_next(uint32_t *failed) { + // SRAM1/2 and SRAM4 hold the DMA buffers, which DMA can write mid test, and the test's own backup, they're covered at boot, by ECC and the scrub + static const ram_region ram_regions[] = { + {(uint32_t)&_dtcmram_start, (uint32_t)&_dtcmram_end}, + {(uint32_t)&_axisram_start, (uint32_t)&_axisram_end}, + {(uint32_t)&_itcmram_start, (uint32_t)&_itcmram_end}, + }; + static uint8_t region = 0U; + static uint32_t block = 0U; + if (block == 0U) { + block = ram_regions[region].start; + } + uint32_t words = MIN(RAM_TEST_BLOCK_WORDS, (ram_regions[region].end - block) / 4U); + bool ok = ram_test_skip(block, block + (words * 4U)) || ram_test_block(block, words, ram_regions[region].start, ram_regions[region].end); + *failed = block; + block += RAM_TEST_STEP; + if (block >= ram_regions[region].end) { + region = (uint8_t)((region + 1U) % (sizeof(ram_regions) / sizeof(ram_regions[0]))); + block = 0U; + } + return ok; +} + +// UM2331 RAM_SM_7, a read reveals an ECC error in RAM that isn't march tested at runtime +static void ram_scrub_next(void) { + static const ram_region scrub_regions[] = { + {(uint32_t)&_sram12_start, (uint32_t)&_sram12_end}, + {(uint32_t)&_sram4_start, (uint32_t)&_sram4_end}, + }; + static uint8_t region = 0U; + static uint32_t addr = 0U; + if (addr == 0U) { + addr = scrub_regions[region].start; + } + const volatile uint32_t *p = (const uint32_t *)addr; + uint32_t words = MIN(RAM_SCRUB_WORDS, (scrub_regions[region].end - addr) / 4U); + for (uint32_t i = 0U; i < words; i++) { + (void)p[i]; + } + addr += words * 4U; + if (addr >= scrub_regions[region].end) { + region = (uint8_t)((region + 1U) % (sizeof(scrub_regions) / sizeof(scrub_regions[0]))); + addr = 0U; + } +} + +// board/crypto/sign.py: length, image, crc32 of everything before it, version tag and version, signature +static uint32_t flash_crc_len(void) { + uint32_t len = (uint32_t)_app_start[0]; + return ((len >= 12U) && (len < sizeof(_app_start))) ? (len - 12U) : 0U; +} + +static uint32_t flash_crc_stored(void) { + const uint8_t *p = &((const uint8_t *)_app_start)[flash_crc_len()]; + return (uint32_t)p[0] | ((uint32_t)p[1] << 8U) | ((uint32_t)p[2] << 16U) | ((uint32_t)p[3] << 24U); +} + +// crc is set to the computed CRC once a pass completes +static bool flash_test_next(uint32_t *crc_out) { + static uint32_t offset = 0U; + static uint32_t crc = 0xFFFFFFFFU; + bool ok = true; + uint32_t len = flash_crc_len(); + uint32_t n = MIN(FLASH_CRC_CHUNK, len - offset); + crc = crc32_update(crc, &((const uint8_t *)_app_start)[offset], n); + offset += n; + if (offset >= len) { +#if defined(ALLOW_DEBUG) && defined(PANDA_FAULT_INJECTION) + crc ^= flash_crc_flip; + flash_crc_flip = 0U; +#endif + *crc_out = crc ^ 0xFFFFFFFFU; + ok = (len != 0U) && (*crc_out == flash_crc_stored()); + offset = 0U; + crc = 0xFFFFFFFFU; + } + return ok; +} + +#define DMA_TEST_WORDS 8U +static const uint32_t dma_test_pattern[DMA_TEST_WORDS] = { + 0x5AA5F00FU, 0xA55A0FF0U, 0x12345678U, 0x9ABCDEF0U, 0x0F1E2D3CU, 0x4B5A6978U, 0x87968574U, 0xC3D2E1F0U, +}; +__attribute__((section(".sram12"))) static uint32_t dma_test_buf[DMA_TEST_WORDS]; + +static void dma_test_start(void) { + uint32_t len = DMA_TEST_WORDS; +#if defined(ALLOW_DEBUG) && defined(PANDA_FAULT_INJECTION) + // a short copy leaves the last word cleared + if (self_test_injected(SELF_TEST_DMA)) { + len -= 1U; + } +#endif + for (uint8_t i = 0U; i < DMA_TEST_WORDS; i++) { + dma_test_buf[i] = 0U; + } + DMA2->HIFCR = DMA_HIFCR_CTCIF7 | DMA_HIFCR_CHTIF7 | DMA_HIFCR_CTEIF7 | DMA_HIFCR_CDMEIF7 | DMA_HIFCR_CFEIF7; + DMA2_Stream7->NDTR = len; + // the cleared buffer lands in SRAM1 before the DMA starts + __DMB(); + DMA2_Stream7->CR |= DMA_SxCR_EN; +} + +// flash to SRAM1, memory to memory needs the FIFO +static void dma_test_init(void) { + register_set(&(DMA2_Stream7->PAR), (uint32_t)dma_test_pattern, 0xFFFFFFFFU); + register_set(&(DMA2_Stream7->M0AR), (uint32_t)dma_test_buf, 0xFFFFFFFFU); + register_set(&(DMA2_Stream7->FCR), DMA_SxFCR_DMDIS | DMA_SxFCR_FTH, DMA_SxFCR_DMDIS | DMA_SxFCR_FTH); + register_set(&(DMA2_Stream7->CR), DMA_SxCR_DIR_1 | DMA_SxCR_PINC | DMA_SxCR_MINC | DMA_SxCR_PSIZE_1 | DMA_SxCR_MSIZE_1, 0x01FFFFFEU); + dma_test_start(); +} + +// 0 if it passed, else bits 0-7 the wrong words, 8-11 DMA1 stream 0/1 and BDMA channel 0/1 errors, 22-27 the DMA2 stream 7 flags with TCIF7 if not done +static uint32_t dma_test_next(void) { + uint32_t err = (DMA2->HISR & (DMA_HISR_TCIF7 | DMA_HISR_TEIF7 | DMA_HISR_DMEIF7 | DMA_HISR_FEIF7)) ^ DMA_HISR_TCIF7; + for (uint8_t i = 0U; i < DMA_TEST_WORDS; i++) { + err |= (dma_test_buf[i] != dma_test_pattern[i]) ? (1UL << i) : 0U; + } + // mic on DMA1 stream 0, sound and siren on DMA1 stream 1, SAI4 on BDMA channels 0 and 1, a sound stop clears them in between + err |= ((DMA1->LISR & DMA_LISR_TEIF0) != 0U) ? (1UL << 8) : 0U; + err |= ((DMA1->LISR & DMA_LISR_TEIF1) != 0U) ? (1UL << 9) : 0U; + err |= ((BDMA->ISR & BDMA_ISR_TEIF0) != 0U) ? (1UL << 10) : 0U; + err |= ((BDMA->ISR & BDMA_ISR_TEIF1) != 0U) ? (1UL << 11) : 0U; + dma_test_start(); + return err; +} + +// ram_addr is the failing block, 0 at boot, flash_crc the computed CRC +static void self_test_check(bool cpu_ok, bool ram_ok, uint32_t ram_addr, bool flash_ok, uint32_t flash_crc) { + bool cpu = cpu_ok; + bool ram = ram_ok; + bool flash = flash_ok; +#if defined(ALLOW_DEBUG) && defined(PANDA_FAULT_INJECTION) + cpu = cpu && !self_test_injected(SELF_TEST_CPU); + ram = ram && !self_test_injected(SELF_TEST_RAM); +#endif + if (!cpu) { + fault_occurred(FAULT_SELF_TEST_CPU); + } + if (!ram) { + fault_occurred_value(FAULT_SELF_TEST_RAM, ram_addr); + } + if (!flash) { + fault_occurred_value(FAULT_SELF_TEST_FLASH, flash_crc); + } + uint32_t flash_sr = FLASH->SR1; + uint32_t ecc_sr = 0U; + // the monitor index in the top 4 bits and the failing word within that RAM + uint32_t ecc_far = 0U; + for (uint8_t i = 0U; i < ECC_MONITOR_CNT; i++) { + uint32_t sr = ecc_monitors[i]->SR; + if ((ecc_far == 0U) && ((sr & (RAMECC_SR_DEDF | RAMECC_SR_DEBWDF)) != 0U)) { + ecc_far = ((uint32_t)i << 28U) | (ecc_monitors[i]->FAR & 0x0FFFFFFFU); + } + ecc_sr |= sr; + } +#if defined(ALLOW_DEBUG) && defined(PANDA_FAULT_INJECTION) + if (self_test_injected(SELF_TEST_FLASH_ECC)) { + flash_sr |= FLASH_SR_DBECCERR; + } + if (self_test_injected(SELF_TEST_RAM_ECC)) { + ecc_sr |= RAMECC_SR_DEDF; + } +#endif + // a CPU read with a double ECC error bus faults, this catches the DMA reads from flash + if ((flash_sr & FLASH_SR_DBECCERR) != 0U) { + fault_occurred_value(FAULT_ECC, FLASH->ECC_FA1); + } + if ((ecc_sr & (RAMECC_SR_DEDF | RAMECC_SR_DEBWDF)) != 0U) { + fault_occurred_value(FAULT_ECC, ecc_far); + } +} + +void self_test_init(void) { + crc32_init(); + + // the boot RAM test wrote every word, so no RAM holds an unset ECC code anymore + for (uint8_t i = 0U; i < ECC_MONITOR_CNT; i++) { + ecc_monitors[i]->SR = 0U; + register_set(&(ecc_monitors[i]->CR), RAMECC_CR_ECCELEN, 0x3CU); + } + + bool ram_ok = (self_test_cookie == SELF_TEST_PASS); + self_test_cookie = 0U; + uint32_t len = flash_crc_len(); + uint32_t crc = crc32((const uint8_t *)_app_start, len); + bool flash_ok = (len != 0U) && (crc == flash_crc_stored()); + self_test_check((cpu_test() == SELF_TEST_PASS) && (fpu_test() == SELF_TEST_PASS), ram_ok, 0U, flash_ok, crc); + dma_test_init(); +} + +// called at 8Hz +void self_test_tick(void) { + bool cpu_ok = (cpu_test() == SELF_TEST_PASS) && (fpu_test() == SELF_TEST_PASS); + uint32_t ram_addr = 0U; + bool ram_ok = ram_test_next(&ram_addr); + ram_scrub_next(); + uint32_t crc = 0U; + bool flash_ok = flash_test_next(&crc); + self_test_check(cpu_ok, ram_ok, ram_addr, flash_ok, crc); + uint32_t dma_err = dma_test_next(); + if (dma_err != 0U) { + fault_occurred_value(FAULT_DMA, dma_err); + } +} diff --git a/panda/board/drivers/timers.h b/panda/board/drivers/timers.h index 7fa3b6a7c2..a0de78c2eb 100644 --- a/panda/board/drivers/timers.h +++ b/panda/board/drivers/timers.h @@ -7,9 +7,17 @@ static void timer_init(TIM_TypeDef *TIM, int psc) { TIM->SR = 0; } +// drop an update that pended before init, read back so the clear lands first +static void timer_irq_enable(TIM_TypeDef *TIM, IRQn_Type irq) { + TIM->SR = 0; + (void)TIM->SR; + NVIC_ClearPendingIRQ(irq); + nvic_enable(irq); +} + void microsecond_timer_init(void) { - MICROSECOND_TIMER->PSC = (APB1_TIMER_FREQ - 1U); - MICROSECOND_TIMER->CR1 = TIM_CR1_CEN; + register_set(&(MICROSECOND_TIMER->PSC), (APB1_TIMER_FREQ - 1U), 0xFFFFU); + register_set(&(MICROSECOND_TIMER->CR1), TIM_CR1_CEN, 0x3FU); MICROSECOND_TIMER->EGR = TIM_EGR_UG; } @@ -17,17 +25,35 @@ uint32_t microsecond_timer_get(void) { return MICROSECOND_TIMER->CNT; } +// GTIM_SM_1: against the microsecond timer over two periods, one late interrupt can't fault +// returns true on a fault, raised by the caller so each timer logs as its own site +bool timer_check(timer_check_t *check, uint32_t *dt_out) { + bool fault = false; + uint32_t now = microsecond_timer_get(); + if (check->prev_ts != 0U) { + uint32_t dt = get_ts_elapsed(now, check->prev_ts); + bool ok = (dt > 1900000U) && (dt < 2100000U); + check->mismatches = ok ? 0U : (check->mismatches + 1U); + fault = check->mismatches >= 2U; + *dt_out = dt; + } + check->prev_ts = check->last_ts; + check->last_ts = now; + return fault; +} + void interrupt_timer_init(void) { enable_interrupt_timer(); REGISTER_INTERRUPT(INTERRUPT_TIMER_IRQ, interrupt_timer_handler, 2U, FAULT_INTERRUPT_RATE_INTERRUPTS) register_set(&(INTERRUPT_TIMER->PSC), ((uint16_t)(15.25*APB1_TIMER_FREQ)-1U), 0xFFFFU); register_set(&(INTERRUPT_TIMER->DIER), TIM_DIER_UIE, 0x5F5FU); register_set(&(INTERRUPT_TIMER->CR1), TIM_CR1_CEN, 0x3FU); - INTERRUPT_TIMER->SR = 0; - NVIC_EnableIRQ(INTERRUPT_TIMER_IRQ); + // load the prescaler now, so the first window covers all of boot + INTERRUPT_TIMER->EGR = TIM_EGR_UG; + timer_irq_enable(INTERRUPT_TIMER, INTERRUPT_TIMER_IRQ); } void tick_timer_init(void) { timer_init(TICK_TIMER, (uint16_t)((15.25*APB2_TIMER_FREQ)/8U)); - NVIC_EnableIRQ(TICK_TIMER_IRQ); + timer_irq_enable(TICK_TIMER, TICK_TIMER_IRQ); } diff --git a/panda/board/early_init.h b/panda/board/early_init.h index b5c3661952..8d94b6999f 100644 --- a/panda/board/early_init.h +++ b/panda/board/early_init.h @@ -50,7 +50,7 @@ void early_initialization(void) { } // setup interrupt table - SCB->VTOR = (uint32_t)&g_pfnVectors; + register_set(&(SCB->VTOR), (uint32_t)&g_pfnVectors, SCB_VTOR_TBLOFF_Msk); // early GPIOs float everything early_gpio_float(); @@ -69,7 +69,43 @@ void early_initialization(void) { #define NMI_COOKIE 0x4E4D4921U #define HARDFAULT_COOKIE 0x48464C54U extern uint32_t fault_cookie; +#ifdef STM32H7 +extern uint32_t fault_cfsr; +extern uint32_t fault_addr; +extern uint32_t fault_pc; +// frame is the stacked r0-r3, r12, lr, pc and xpsr, panda only runs on the MSP +static void fault_record(uint32_t cookie, const uint32_t *frame) { + fault_cookie = cookie; + if (cookie == HARDFAULT_COOKIE) { + fault_cfsr = SCB->CFSR; + fault_addr = ((fault_cfsr & SCB_CFSR_MMARVALID_Msk) != 0U) ? SCB->MMFAR : SCB->BFAR; + } + // a failed stacking leaves no frame to read + fault_pc = ((SCB->CFSR & (SCB_CFSR_MSTKERR_Msk | SCB_CFSR_STKERR_Msk)) == 0U) ? frame[6] : 0U; + NVIC_SystemReset(); +} + +__attribute__((used)) static void nmi_record(const uint32_t *frame) { + fault_record(NMI_COOKIE, frame); +} + +__attribute__((used)) static void hardfault_record(const uint32_t *frame) { + fault_record(HARDFAULT_COOKIE, frame); +} + +// naked to read the frame before anything is pushed +__attribute__((naked)) void NMI_Handler(void) { + // cppcheck-suppress misra-c2012-17.3 ; inline asm + __asm volatile ("mrs r0, msp\n b nmi_record"); +} + +__attribute__((naked)) void HardFault_Handler(void) { + // cppcheck-suppress misra-c2012-17.3 ; inline asm + __asm volatile ("mrs r0, msp\n b hardfault_record"); +} + +#else void NMI_Handler(void) { fault_cookie = NMI_COOKIE; NVIC_SystemReset(); @@ -79,3 +115,5 @@ void HardFault_Handler(void) { fault_cookie = HARDFAULT_COOKIE; NVIC_SystemReset(); } + +#endif diff --git a/panda/board/health.h b/panda/board/health.h index 018ef2a06f..c9819c13d3 100644 --- a/panda/board/health.h +++ b/panda/board/health.h @@ -9,6 +9,7 @@ #define HEALTH_FLAG_SOM_RESET_TRIGGERED (1U << 6) #define HEALTH_FLAG_NMI_RESET (1U << 7) #define HEALTH_FLAG_HARDFAULT_RESET ((uint16_t)1U << 8) +#define HEALTH_FLAG_HEARTBEAT_DISABLED ((uint16_t)1U << 9) #define AOL_SAFETY_PROTOCOL_MAGIC 0x314C4F41U // "AOL1", little endian #define AOL_SAFETY_PROTOCOL_VERSION 1U diff --git a/panda/board/main.c b/panda/board/main.c index 0546033072..fe90cea99f 100644 --- a/panda/board/main.c +++ b/panda/board/main.c @@ -29,12 +29,21 @@ #include "board/can_comms.h" #include "board/main_comms.h" +#ifdef STM32H7 +#include "board/drivers/self_test.h" +#endif // ****************************** safety mode ****************************** // this is the only way to leave silent mode void set_safety_mode(uint16_t mode, uint16_t param) { uint16_t mode_copy = mode; + if ((faults & PERMANENT_FAULTS) != 0U) { + mode_copy = SAFETY_SILENT; + } + #ifdef STM32H7 + mpu_safety_open(); + #endif int err = set_safety_hooks(mode_copy, param); if (err == -1) { print("Error: safety set mode failed. Falling back to SILENT\n"); @@ -43,6 +52,9 @@ void set_safety_mode(uint16_t mode, uint16_t param) { // TERMINAL ERROR: we can't continue if SILENT safety mode isn't succesfully set assert_fatal(err == 0, "Error: Failed setting SILENT mode. Hanging\n"); } + #ifdef STM32H7 + mpu_safety_close(); + #endif safety_tx_blocked = 0; safety_rx_invalid = 0; @@ -83,6 +95,11 @@ void set_safety_mode(uint16_t mode, uint16_t param) { can_init_all(); } +// SILENT clears controls_allowed and opens the relay +void fault_safe_state(void) { + set_safety_mode(SAFETY_SILENT, 0U); +} + bool is_car_safety_mode(uint16_t mode) { return (mode != SAFETY_SILENT) && (mode != SAFETY_NOOUTPUT) && @@ -109,7 +126,9 @@ static void __attribute__ ((noinline)) enable_fpu(void) { // called at 8Hz static void tick_handler(void) { - if (TICK_TIMER->SR != 0U) { + if ((TICK_TIMER->SR & TIM_SR_UIF) != 0U) { + // cleared first, a flag cleared on the way out can retrigger the vector + TICK_TIMER->SR = 0; static uint32_t siren_countdown = 0; // siren plays while countdown > 0 static uint32_t controls_allowed_countdown = 0; static uint8_t prev_harness_status = HARNESS_STATUS_NC; @@ -169,6 +188,11 @@ static void tick_handler(void) { // decimated to 1Hz if (loop_counter == 0U) { + static timer_check_t tick_timer_check = {0}; + uint32_t tick_dt = 0U; + if (timer_check(&tick_timer_check, &tick_dt)) { + fault_occurred_value(FAULT_TIMER, tick_dt); + } // set green LED to be controls allowed led_set(LED_GREEN, controls_allowed); @@ -203,7 +227,13 @@ static void tick_handler(void) { if (controls_allowed && !heartbeat_engaged) { heartbeat_engaged_mismatches += 1U; if (heartbeat_engaged_mismatches >= 3U) { + #ifdef STM32H7 + mpu_safety_open(); + #endif controls_allowed = false; + #ifdef STM32H7 + mpu_safety_close(); + #endif } } else { heartbeat_engaged_mismatches = 0U; @@ -248,6 +278,14 @@ static void tick_handler(void) { // check registers check_registers(); + #ifdef STM32H7 + clock_check(); + #endif + interrupt_timer_check(); + nvic_check(); + #ifdef STM32H7 + check_option_bytes(); + #endif // set ignition_can to false after 2s of no CAN seen if (ignition_can_cnt > 2U) { @@ -273,19 +311,39 @@ static void tick_handler(void) { #endif // synchronous safety check + #ifdef STM32H7 + mpu_safety_open(); + #endif safety_tick(); + #ifdef STM32H7 + mpu_safety_close(); + #endif + #ifdef STM32H7 + mpu_check(); + #endif } + #ifdef STM32H7 + + self_test_tick(); + + #endif + loop_counter++; loop_counter %= 8U; + } else { + // TIM12 shares the vector with the TIM8 break, which has no interrupts enabled + fault_occurred_value(FAULT_UNUSED_INTERRUPT_HANDLED, __get_IPSR()); } - TICK_TIMER->SR = 0; } int main(void) { // Init interrupt table init_interrupts(true); + // register_map is zeroed with .bss after early_initialization + register_set(&(SCB->VTOR), (uint32_t)&g_pfnVectors, SCB_VTOR_TBLOFF_Msk); + // shouldn't have interrupts here, but just in case disable_interrupts(); @@ -296,6 +354,9 @@ int main(void) { // init early devices clock_init(); peripherals_init(); + #ifdef STM32H7 + clock_check_init(); + #endif detect_board_type(); led_init(); // red+green leds enabled until succesful USB/SPI init, as a debug indicator @@ -331,6 +392,12 @@ int main(void) { fan_init(); } + #ifdef STM32H7 + + mpu_init(); + + #endif + // init to SILENT and can silent set_safety_mode(SAFETY_SILENT, 0U); @@ -342,8 +409,15 @@ int main(void) { // 8Hz timer REGISTER_INTERRUPT(TICK_TIMER_IRQ, tick_handler, 10U, FAULT_INTERRUPT_RATE_TICK) + interrupts[TICK_TIMER_IRQ].min_call_rate = 4U; tick_timer_init(); + #ifdef STM32H7 + + self_test_init(); + + #endif + // enable USB (right before interrupts or enum can fail!) usb_init(); @@ -358,11 +432,22 @@ int main(void) { if (fault_cookie == NMI_COOKIE) { reset_flags |= HEALTH_FLAG_NMI_RESET; +#ifdef STM32H7 + print("NMI PC 0x"); puth(fault_pc); print("\n"); +#endif } if (fault_cookie == HARDFAULT_COOKIE) { reset_flags |= HEALTH_FLAG_HARDFAULT_RESET; +#ifdef STM32H7 + print("HardFault CFSR 0x"); puth(fault_cfsr); print(" address 0x"); puth(fault_addr); print(" PC 0x"); puth(fault_pc); print("\n"); +#endif } fault_cookie = 0U; + // cleared so the next boot shows only its own reset +#ifdef STM32H7 + print("RSR 0x"); puth(RCC->RSR); print("\n"); + RCC->RSR |= RCC_RSR_RMVF; +#endif print("**** INTERRUPTS ON ****\n"); enable_interrupts(); @@ -375,6 +460,11 @@ int main(void) { } #endif if (!power_save_enabled) { + // ADC_SM_3, takes milliseconds so not in an interrupt + #ifdef STM32H7 + adc_check(current_board->avdd_mV); + #endif + // useful for debugging, fade breaks = panda is overloaded for (uint32_t fade = 0U; fade < MAX_LED_FADE; fade += 1U) { led_set(LED_RED, true); diff --git a/panda/board/main_comms.h b/panda/board/main_comms.h index 7962636924..a36ad09ab6 100644 --- a/panda/board/main_comms.h +++ b/panda/board/main_comms.h @@ -14,6 +14,9 @@ extern int _app_start[0xc000]; // Only first 3 sectors of size 0x4000 are used // Prototypes void set_safety_mode(uint16_t mode, uint16_t param); +#if defined(ALLOW_DEBUG) && defined(PANDA_FAULT_INJECTION) +void self_test_inject(uint8_t test); +#endif bool is_car_safety_mode(uint16_t mode); static int get_health_pkt(void *dat) { @@ -32,6 +35,7 @@ static int get_health_pkt(void *dat) { health->flags_pkt |= heartbeat_lost ? HEALTH_FLAG_HEARTBEAT_LOST : 0U; health->flags_pkt |= safety_rx_checks_invalid ? HEALTH_FLAG_SAFETY_RX_CHECKS_INVALID : 0U; health->flags_pkt |= bootkick_reset_triggered ? HEALTH_FLAG_SOM_RESET_TRIGGERED : 0U; + health->flags_pkt |= heartbeat_disabled ? HEALTH_FLAG_HEARTBEAT_DISABLED : 0U; health->flags_pkt |= reset_flags; health->safety_tx_blocked_pkt = safety_tx_blocked; @@ -74,6 +78,14 @@ void comms_endpoint2_write(const uint8_t *data, uint32_t len) { UNUSED(len); } +#if defined(ALLOW_DEBUG) && defined(STM32H7) && defined(PANDA_FAULT_INJECTION) +static void __attribute__((noinline)) stack_guard_hit(void) { + volatile uint32_t frame[4]; + frame[0] = 0U; + UNUSED(frame); +} +#endif + int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { unsigned int resp_len = 0; uint32_t time; @@ -301,8 +313,89 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { break; // **** 0xe8: set can-fd auto swithing mode case 0xe8: - bus_config[req->param1].canfd_auto = req->param2 > 0U; + if (req->param1 < PANDA_CAN_CNT) { + bus_config[req->param1].canfd_auto = req->param2 > 0U; + } break; + #if defined(ALLOW_DEBUG) && defined(PANDA_FAULT_INJECTION) + // **** 0xea: inject fault, for testing + case 0xea: + if (req->param1 < 32U) { + fault_occurred(1UL << req->param1); + } + break; + // **** 0xeb: inject HardFault, for testing + case 0xeb: + __builtin_trap(); + break; + // **** 0xec: inject NMI, for testing + case 0xec: + SCB->ICSR = SCB_ICSR_NMIPENDSET_Msk; + break; + #ifdef STM32H7 + // **** 0xee: write controls_allowed outside safety, for testing + case 0xee: + controls_allowed = !controls_allowed; + break; + // **** 0xf0: run the stack into its MPU guard, for testing + case 0xf0: + __set_MSP((uint32_t)&_stack_guard + 32U); + stack_guard_hit(); + break; + // **** 0xed: inject self-test failure, for testing + case 0xed: + self_test_inject((uint8_t)req->param1); + break; + // **** 0xef: break an interrupt or the clock check, for testing + case 0xef: + if (req->param1 == 0U) { + nvic_disable(TICK_TIMER_IRQ); + } else if (req->param1 == 1U) { + nvic_disable(INTERRUPT_TIMER_IRQ); + } else if (req->param1 == 2U) { + // the tick handler runs without its flag + NVIC_SetPendingIRQ(TICK_TIMER_IRQ); + } else if (req->param1 == 3U) { + // halves the counts per LSI capture + register_set(&(TIM16->PSC), 1U, 0xFFFFU); + } else if (req->param1 == 4U) { + // an unused IRQ, outside nvic_enable + NVIC_EnableIRQ(WWDG_IRQn); + } else if (req->param1 == 5U) { + NVIC_SetPriority(WWDG_IRQn, 1U); + } else if (req->param1 == 6U) { + // the interrupt timer handler runs without its flag + NVIC_SetPendingIRQ(INTERRUPT_TIMER_IRQ); + } else { + } + break; + // **** 0xe9: break a read-back or cross-check, for testing + case 0xe9: + if (req->param1 == 0U) { + relay_readback_inject = true; + } else if (req->param1 == 1U) { + register_set(&(INTERRUPT_TIMER->PSC), (INTERRUPT_TIMER->PSC * 23U) / 20U, 0xFFFFU); + } else if (req->param1 == 2U) { + ADC3_COMMON->CCR &= ~ADC_CCR_VREFEN; + } else if (req->param1 == 3U) { + register_set(&(TICK_TIMER->PSC), (TICK_TIMER->PSC * 23U) / 20U, 0xFFFFU); + } else if (req->param1 == 4U) { + // raw writes, outside the register map + FDCAN1->IE ^= FDCAN_IE_TCFE; + } else if (req->param1 == 5U) { + RCC->APB1LENR |= RCC_APB1LENR_TIM13EN; + } else if (req->param1 == 6U) { + harness_adc2_offset_mV = 500U; + } else if (req->param1 == 7U) { + // a third MPU region, on unused FMC space + MPU->RNR = MPU_REGION_STACK_GUARD + 1U; + MPU->RBAR = 0x60000000U; + MPU->RASR = mpu_rasr(ARM_MPU_AP_FULL, ARM_MPU_REGION_SIZE_32B); + } else { + } + break; + #endif + #endif // **** 0xf1: Clear CAN ring buffer. case 0xf1: if (req->param1 == 0xFFFFU) { diff --git a/panda/board/stm32f4/llbxcan.h b/panda/board/stm32f4/llbxcan.h index 82794be629..c139f75d02 100644 --- a/panda/board/stm32f4/llbxcan.h +++ b/panda/board/stm32f4/llbxcan.h @@ -59,34 +59,34 @@ bool llcan_set_speed(CAN_TypeDef *CANx, uint32_t speed, bool loopback, bool sile void llcan_irq_disable(const CAN_TypeDef *CANx) { if (CANx == CAN1) { - NVIC_DisableIRQ(CAN1_TX_IRQn); - NVIC_DisableIRQ(CAN1_RX0_IRQn); - NVIC_DisableIRQ(CAN1_SCE_IRQn); + nvic_disable(CAN1_TX_IRQn); + nvic_disable(CAN1_RX0_IRQn); + nvic_disable(CAN1_SCE_IRQn); } else if (CANx == CAN2) { - NVIC_DisableIRQ(CAN2_TX_IRQn); - NVIC_DisableIRQ(CAN2_RX0_IRQn); - NVIC_DisableIRQ(CAN2_SCE_IRQn); + nvic_disable(CAN2_TX_IRQn); + nvic_disable(CAN2_RX0_IRQn); + nvic_disable(CAN2_SCE_IRQn); } else if (CANx == CAN3) { - NVIC_DisableIRQ(CAN3_TX_IRQn); - NVIC_DisableIRQ(CAN3_RX0_IRQn); - NVIC_DisableIRQ(CAN3_SCE_IRQn); + nvic_disable(CAN3_TX_IRQn); + nvic_disable(CAN3_RX0_IRQn); + nvic_disable(CAN3_SCE_IRQn); } else { } } void llcan_irq_enable(const CAN_TypeDef *CANx) { if (CANx == CAN1) { - NVIC_EnableIRQ(CAN1_TX_IRQn); - NVIC_EnableIRQ(CAN1_RX0_IRQn); - NVIC_EnableIRQ(CAN1_SCE_IRQn); + nvic_enable(CAN1_TX_IRQn); + nvic_enable(CAN1_RX0_IRQn); + nvic_enable(CAN1_SCE_IRQn); } else if (CANx == CAN2) { - NVIC_EnableIRQ(CAN2_TX_IRQn); - NVIC_EnableIRQ(CAN2_RX0_IRQn); - NVIC_EnableIRQ(CAN2_SCE_IRQn); + nvic_enable(CAN2_TX_IRQn); + nvic_enable(CAN2_RX0_IRQn); + nvic_enable(CAN2_SCE_IRQn); } else if (CANx == CAN3) { - NVIC_EnableIRQ(CAN3_TX_IRQn); - NVIC_EnableIRQ(CAN3_RX0_IRQn); - NVIC_EnableIRQ(CAN3_SCE_IRQn); + nvic_enable(CAN3_TX_IRQn); + nvic_enable(CAN3_RX0_IRQn); + nvic_enable(CAN3_SCE_IRQn); } else { } } diff --git a/panda/board/stm32f4/llfan.h b/panda/board/stm32f4/llfan.h index 9586166121..5ac9ed3b9b 100644 --- a/panda/board/stm32f4/llfan.h +++ b/panda/board/stm32f4/llfan.h @@ -19,5 +19,5 @@ void llfan_init(void) { register_set_bits(&(EXTI->IMR), (1U << 2)); register_set_bits(&(EXTI->RTSR), (1U << 2)); register_set_bits(&(EXTI->FTSR), (1U << 2)); - NVIC_EnableIRQ(EXTI2_IRQn); + nvic_enable(EXTI2_IRQn); } diff --git a/panda/board/stm32f4/llusb.h b/panda/board/stm32f4/llusb.h index 3d3b35e588..bcb093bc4a 100644 --- a/panda/board/stm32f4/llusb.h +++ b/panda/board/stm32f4/llusb.h @@ -3,11 +3,11 @@ USB_OTG_GlobalTypeDef *USBx = USB_OTG_FS; static void OTG_FS_IRQ_Handler(void) { - NVIC_DisableIRQ(OTG_FS_IRQn); + nvic_disable(OTG_FS_IRQn); //__disable_irq(); usb_irqhandler(); //__enable_irq(); - NVIC_EnableIRQ(OTG_FS_IRQn); + nvic_enable(OTG_FS_IRQn); } void usb_init(void) { @@ -76,5 +76,5 @@ void usb_init(void) { USBx_DEVICE->DCTL = 0; // enable the IRQ - NVIC_EnableIRQ(OTG_FS_IRQn); + nvic_enable(OTG_FS_IRQn); } diff --git a/panda/board/stm32h7/clock.h b/panda/board/stm32h7/clock.h index 0e6d4795ab..80eedfe87f 100644 --- a/panda/board/stm32h7/clock.h +++ b/panda/board/stm32h7/clock.h @@ -120,3 +120,39 @@ void clock_init(void) { //Enable Vdd33usb supply level detector register_set_bits(&(PWR->CR3), PWR_CR3_USB33DEN); } + +// CLK_SM_3: TIM16 captures every 8th edge of the 32kHz LSI on the HSE derived timer clock +#define LSI_COUNTS_NOMINAL (APB2_TIMER_FREQ * 250U) + +void clock_check_init(void) { + register_set_bits(&(RCC->CSR), RCC_CSR_LSION); + register_set(&(TIM16->TISEL), 1U, TIM_TISEL_TI1SEL); + register_set(&(TIM16->CCMR1), TIM_CCMR1_CC1S_0 | TIM_CCMR1_IC1PSC_1 | TIM_CCMR1_IC1PSC_0, 0xFFU); + register_set(&(TIM16->CCER), TIM_CCER_CC1E, 0xFU); + register_set(&(TIM16->PSC), 0U, 0xFFFFU); + register_set(&(TIM16->ARR), 0xFFFFU, 0xFFFFU); + register_set(&(TIM16->CR1), TIM_CR1_CEN, 0x3FU); +} + +// waits up to 1ms for the next capture, an LSI/8 period is 250us +static bool lsi_capture(uint16_t *count) { + uint32_t ts = microsecond_timer_get(); + bool captured = false; + while (!captured && (get_ts_elapsed(microsecond_timer_get(), ts) < 1000U)) { + captured = (TIM16->SR & TIM_SR_CC1IF) != 0U; + } + *count = (uint16_t)TIM16->CCR1; + return captured; +} + +// called at 1Hz, LSI isn't accurate so the window only catches gross errors +void clock_check(void) { + // reading CCR1 clears CC1IF + uint16_t start = (uint16_t)TIM16->CCR1; + uint16_t end = 0U; + bool captured = lsi_capture(&end); + uint32_t counts = (uint16_t)(end - start); + if (!captured || (counts <= ((LSI_COUNTS_NOMINAL * 3U) / 4U)) || (counts >= ((LSI_COUNTS_NOMINAL * 5U) / 4U))) { + fault_occurred_value(FAULT_CLOCK, captured ? counts : 0U); + } +} diff --git a/panda/board/stm32h7/interrupt_handlers.h b/panda/board/stm32h7/interrupt_handlers.h index 47abfbfca6..15a44969f2 100644 --- a/panda/board/stm32h7/interrupt_handlers.h +++ b/panda/board/stm32h7/interrupt_handlers.h @@ -28,7 +28,7 @@ void FDCAN1_IT1_IRQHandler(void) {handle_interrupt(FDCAN1_IT1_IRQn);} void FDCAN2_IT1_IRQHandler(void) {handle_interrupt(FDCAN2_IT1_IRQn);} void EXTI9_5_IRQHandler(void) {handle_interrupt(EXTI9_5_IRQn);} void TIM1_BRK_IRQHandler(void) {handle_interrupt(TIM1_BRK_IRQn);} -void TIM1_UP_TIM10_IRQHandler(void) {handle_interrupt(TIM1_UP_TIM10_IRQn);} +void TIM1_UP_IRQHandler(void) {handle_interrupt(TIM1_UP_IRQn);} void TIM1_TRG_COM_IRQHandler(void) {handle_interrupt(TIM1_TRG_COM_IRQn);} void TIM1_CC_IRQHandler(void) {handle_interrupt(TIM1_CC_IRQn);} void TIM2_IRQHandler(void) {handle_interrupt(TIM2_IRQn);} @@ -119,7 +119,7 @@ void BDMA_Channel4_IRQHandler(void) {handle_interrupt(BDMA_Channel4_IRQn);} void BDMA_Channel5_IRQHandler(void) {handle_interrupt(BDMA_Channel5_IRQn);} void BDMA_Channel6_IRQHandler(void) {handle_interrupt(BDMA_Channel6_IRQn);} void BDMA_Channel7_IRQHandler(void) {handle_interrupt(BDMA_Channel7_IRQn);} -void COMP_IRQHandler(void) {handle_interrupt(COMP_IRQn);} +void COMP1_IRQHandler(void) {handle_interrupt(COMP_IRQn);} void LPTIM2_IRQHandler(void) {handle_interrupt(LPTIM2_IRQn);} void LPTIM3_IRQHandler(void) {handle_interrupt(LPTIM3_IRQn);} void LPTIM4_IRQHandler(void) {handle_interrupt(LPTIM4_IRQn);} diff --git a/panda/board/stm32h7/lladc.h b/panda/board/stm32h7/lladc.h index 563d10356a..f0c7a84797 100644 --- a/panda/board/stm32h7/lladc.h +++ b/panda/board/stm32h7/lladc.h @@ -42,30 +42,49 @@ uint16_t adc_get_raw(const adc_signal_t *signal) { // start conversion signal->adc->CR |= ADC_CR_ADSTART; - while (!(signal->adc->ISR & ADC_ISR_EOC)); - - uint16_t res = signal->adc->DR; - - while (!(signal->adc->ISR & ADC_ISR_EOS)); - signal->adc->ISR |= ADC_ISR_EOS; + // a stuck ADC reads 0 + uint32_t timeout = 1000000U; + while (((signal->adc->ISR & ADC_ISR_EOS) == 0U) && (timeout > 0U)) { + timeout--; + } + uint16_t res = (timeout > 0U) ? (uint16_t)signal->adc->DR : 0U; + signal->adc->ISR = ADC_ISR_EOC | ADC_ISR_EOS; return res; } +static uint32_t adc_measure_avdd_mV(void) { + uint16_t raw_vrefint = adc_get_raw(&(adc_signal_t){.adc = ADC2, .channel = 17U, .sample_time = SAMPLETIME_810_CYCLES, .oversampling = OVERSAMPLING_256}); + return (raw_vrefint != 0U) ? ((uint32_t) *VREFINT_CAL_ADDR * 16U * 3300U / raw_vrefint) : 0U; +} + static void adc_calibrate_vdda(void) { // ADC2 used for calibration adc_init(ADC2); // enable VREFINT channel - ADC3_COMMON->CCR |= ADC_CCR_VREFEN; - SYSCFG->ADC2ALT |= SYSCFG_ADC2ALT_ADC2_ROUT1; + register_set_bits(&(ADC3_COMMON->CCR), ADC_CCR_VREFEN); + register_set_bits(&(SYSCFG->ADC2ALT), SYSCFG_ADC2ALT_ADC2_ROUT1); - // measure VREFINT and derive AVDD - uint16_t raw_vrefint = adc_get_raw(&(adc_signal_t){.adc = ADC2, .channel = 17U, .sample_time = SAMPLETIME_810_CYCLES, .oversampling = OVERSAMPLING_256}); - adc_avdd_mV = (uint32_t) *VREFINT_CAL_ADDR * 16U * 3300U / raw_vrefint; + adc_avdd_mV = adc_measure_avdd_mV(); print(" AVDD: 0x"); puth(adc_avdd_mV); print(" mV\n"); } +// the main loop is using ADC2 +static volatile bool adc2_busy = false; + +// ADC_SM_2, ADC_SM_3: AVDD from VREFINT has to match the board +void adc_check(uint32_t expected_avdd_mV) { + adc2_busy = true; + uint32_t avdd_mV = adc_measure_avdd_mV(); + adc2_busy = false; + if ((avdd_mV <= ((expected_avdd_mV * 9U) / 10U)) || (avdd_mV >= ((expected_avdd_mV * 11U) / 10U))) { + ENTER_CRITICAL(); + fault_occurred_value(FAULT_ADC, avdd_mV); + EXIT_CRITICAL(); + } +} + uint16_t adc_get_mV(const adc_signal_t *signal) { uint16_t ret = 0; diff --git a/panda/board/stm32h7/llfan.h b/panda/board/stm32h7/llfan.h index e968d67f11..c87e6e7888 100644 --- a/panda/board/stm32h7/llfan.h +++ b/panda/board/stm32h7/llfan.h @@ -21,5 +21,5 @@ void llfan_init(void) { register_set_bits(&(EXTI->IMR1), (1U << 2)); register_set_bits(&(EXTI->RTSR1), (1U << 2)); register_set_bits(&(EXTI->FTSR1), (1U << 2)); - NVIC_EnableIRQ(EXTI2_IRQn); + nvic_enable(EXTI2_IRQn); } diff --git a/panda/board/stm32h7/llfdcan.h b/panda/board/stm32h7/llfdcan.h index f09691070d..38967df97a 100644 --- a/panda/board/stm32h7/llfdcan.h +++ b/panda/board/stm32h7/llfdcan.h @@ -28,6 +28,16 @@ static bool fdcan_request_init(FDCAN_GlobalTypeDef *FDCANx) { return ret; } +// CAN_SM_0 +static void fdcan_track_config(FDCAN_GlobalTypeDef *FDCANx) { + volatile uint32_t *regs[] = {&(FDCANx->NBTP), &(FDCANx->DBTP), &(FDCANx->IE), &(FDCANx->ILS), &(FDCANx->ILE), &(FDCANx->GFC), + &(FDCANx->SIDFC), &(FDCANx->XIDFC), &(FDCANx->RXF0C), &(FDCANx->RXESC), &(FDCANx->TXBC), &(FDCANx->TXESC)}; + for (uint8_t i = 0U; i < (sizeof(regs) / sizeof(regs[0])); i++) { + register_set(regs[i], *regs[i], 0xFFFFFFFFU); + } + register_set(&(FDCANx->CCCR), FDCANx->CCCR, FDCAN_CCCR_MON | FDCAN_CCCR_DAR | FDCAN_CCCR_TEST | FDCAN_CCCR_FDOE | FDCAN_CCCR_BRSE | FDCAN_CCCR_NISO); +} + static bool fdcan_exit_init(FDCAN_GlobalTypeDef *FDCANx) { bool ret = true; @@ -108,6 +118,7 @@ bool llcan_set_speed(FDCAN_GlobalTypeDef *FDCANx, uint32_t speed, uint32_t data_ if (silent) { FDCANx->CCCR |= FDCAN_CCCR_MON; } + fdcan_track_config(FDCANx); ret = fdcan_exit_init(FDCANx); if (!ret) { print(CAN_NAME_FROM_CANIF(FDCANx)); print(" set_speed timed out! (2)\n"); @@ -120,28 +131,28 @@ bool llcan_set_speed(FDCAN_GlobalTypeDef *FDCANx, uint32_t speed, uint32_t data_ void llcan_irq_disable(const FDCAN_GlobalTypeDef *FDCANx) { if (FDCANx == FDCAN1) { - NVIC_DisableIRQ(FDCAN1_IT0_IRQn); - NVIC_DisableIRQ(FDCAN1_IT1_IRQn); + nvic_disable(FDCAN1_IT0_IRQn); + nvic_disable(FDCAN1_IT1_IRQn); } else if (FDCANx == FDCAN2) { - NVIC_DisableIRQ(FDCAN2_IT0_IRQn); - NVIC_DisableIRQ(FDCAN2_IT1_IRQn); + nvic_disable(FDCAN2_IT0_IRQn); + nvic_disable(FDCAN2_IT1_IRQn); } else if (FDCANx == FDCAN3) { - NVIC_DisableIRQ(FDCAN3_IT0_IRQn); - NVIC_DisableIRQ(FDCAN3_IT1_IRQn); + nvic_disable(FDCAN3_IT0_IRQn); + nvic_disable(FDCAN3_IT1_IRQn); } else { } } void llcan_irq_enable(const FDCAN_GlobalTypeDef *FDCANx) { if (FDCANx == FDCAN1) { - NVIC_EnableIRQ(FDCAN1_IT0_IRQn); - NVIC_EnableIRQ(FDCAN1_IT1_IRQn); + nvic_enable(FDCAN1_IT0_IRQn); + nvic_enable(FDCAN1_IT1_IRQn); } else if (FDCANx == FDCAN2) { - NVIC_EnableIRQ(FDCAN2_IT0_IRQn); - NVIC_EnableIRQ(FDCAN2_IT1_IRQn); + nvic_enable(FDCAN2_IT0_IRQn); + nvic_enable(FDCAN2_IT1_IRQn); } else if (FDCANx == FDCAN3) { - NVIC_EnableIRQ(FDCAN3_IT0_IRQn); - NVIC_EnableIRQ(FDCAN3_IT1_IRQn); + nvic_enable(FDCAN3_IT0_IRQn); + nvic_enable(FDCAN3_IT1_IRQn); } else { } } @@ -207,6 +218,7 @@ bool llcan_init(FDCAN_GlobalTypeDef *FDCANx) { FDCANx->ILS |= FDCAN_ILS_TFEL; FDCANx->IE |= FDCAN_IE_TFEE; // Tx FIFO empty + fdcan_track_config(FDCANx); ret = fdcan_exit_init(FDCANx); if(!ret) { print(CAN_NAME_FROM_CANIF(FDCANx)); print(" llcan_init timed out (2)!\n"); diff --git a/panda/board/stm32h7/llflash.h b/panda/board/stm32h7/llflash.h index 434a130699..40b772e8f7 100644 --- a/panda/board/stm32h7/llflash.h +++ b/panda/board/stm32h7/llflash.h @@ -30,6 +30,6 @@ void flash_write_word(void *prog_ptr, uint32_t data) { void flush_write_buffer(void) { if (FLASH->SR1 & FLASH_SR_WBNE) { FLASH->CR1 |= FLASH_CR_FW; - while (FLASH->SR1 & FLASH_CR_FW); + while (FLASH->SR1 & FLASH_SR_QW); } } diff --git a/panda/board/stm32h7/lli2c.h b/panda/board/stm32h7/lli2c.h index b4a7998849..16b3ce5ecd 100644 --- a/panda/board/stm32h7/lli2c.h +++ b/panda/board/stm32h7/lli2c.h @@ -24,11 +24,7 @@ static bool i2c_write_reg(I2C_TypeDef *I2C, uint8_t addr, uint8_t reg, uint8_t v // Setup transfer and send START + addr for (uint32_t i = 0U; i < I2C_RETRY_COUNT; i++) { - register_clear_bits(&I2C->CR2, I2C_CR2_ADD10); - I2C->CR2 = ((uint32_t)addr << 1U) & I2C_CR2_SADD_Msk; - register_clear_bits(&I2C->CR2, I2C_CR2_RD_WRN); - register_set_bits(&I2C->CR2, I2C_CR2_AUTOEND); - I2C->CR2 |= 2UL << I2C_CR2_NBYTES_Pos; + I2C->CR2 = (((uint32_t)addr << 1U) & I2C_CR2_SADD_Msk) | I2C_CR2_AUTOEND | (2UL << I2C_CR2_NBYTES_Pos); I2C->CR2 |= I2C_CR2_START; if(!i2c_status_wait(&I2C->CR2, I2C_CR2_START, 0U)) { @@ -37,7 +33,7 @@ static bool i2c_write_reg(I2C_TypeDef *I2C, uint8_t addr, uint8_t reg, uint8_t v // check if we lost arbitration if ((I2C->ISR & I2C_ISR_ARLO) != 0U) { - register_set_bits(&I2C->ICR, I2C_ICR_ARLOCF); + I2C->ICR = I2C_ICR_ARLOCF; } else { ret = true; break; @@ -70,11 +66,7 @@ static bool i2c_read_reg(I2C_TypeDef *I2C, uint8_t addr, uint8_t reg, uint8_t *v // Setup transfer and send START + addr for (uint32_t i = 0U; i < I2C_RETRY_COUNT; i++) { - register_clear_bits(&I2C->CR2, I2C_CR2_ADD10); - I2C->CR2 = ((uint32_t)addr << 1U) & I2C_CR2_SADD_Msk; - register_clear_bits(&I2C->CR2, I2C_CR2_RD_WRN); - register_clear_bits(&I2C->CR2, I2C_CR2_AUTOEND); - I2C->CR2 |= 1UL << I2C_CR2_NBYTES_Pos; + I2C->CR2 = (((uint32_t)addr << 1U) & I2C_CR2_SADD_Msk) | (1UL << I2C_CR2_NBYTES_Pos); I2C->CR2 |= I2C_CR2_START; if(!i2c_status_wait(&I2C->CR2, I2C_CR2_START, 0U)) { @@ -83,7 +75,7 @@ static bool i2c_read_reg(I2C_TypeDef *I2C, uint8_t addr, uint8_t reg, uint8_t *v // check if we lost arbitration if ((I2C->ISR & I2C_ISR_ARLO) != 0U) { - register_set_bits(&I2C->ICR, I2C_ICR_ARLOCF); + I2C->ICR = I2C_ICR_ARLOCF; } else { ret = true; break; @@ -110,7 +102,7 @@ static bool i2c_read_reg(I2C_TypeDef *I2C, uint8_t addr, uint8_t reg, uint8_t *v // check if we lost arbitration if ((I2C->ISR & I2C_ISR_ARLO) != 0U) { - register_set_bits(&I2C->ICR, I2C_ICR_ARLOCF); + I2C->ICR = I2C_ICR_ARLOCF; ret = false; goto end; } diff --git a/panda/board/stm32h7/llmpu.h b/panda/board/stm32h7/llmpu.h new file mode 100644 index 0000000000..361fd164fc --- /dev/null +++ b/panda/board/stm32h7/llmpu.h @@ -0,0 +1,103 @@ +#pragma once + +// linker symbols, see stm32h7x5_flash.ld +extern uint32_t _ssafety[16]; +extern uint32_t _stack_guard; + +#define MPU_REGION_SAFETY 0U +#define MPU_REGION_STACK_GUARD 1U + +// read only once FAULT_MPU is permanent, until then a write from outside the safety code is caught in mpu_safety_open() +#define MPU_SAFETY_CLOSED (((PERMANENT_FAULTS & FAULT_MPU) != 0U) ? ARM_MPU_AP_PRO : ARM_MPU_AP_PRIV) + +static uint8_t mpu_safety_depth = 0U; +static uint32_t mpu_safety_copy[16]; +static bool mpu_safety_changed = false; +static uint32_t mpu_safety_changed_addr = 0U; + +// normal memory, non-cacheable, never executed +static uint32_t mpu_rasr(uint32_t access, uint32_t size) { + return MPU_RASR_XN_Msk | (access << MPU_RASR_AP_Pos) | (1UL << MPU_RASR_TEX_Pos) | (size << MPU_RASR_SIZE_Pos) | MPU_RASR_ENABLE_Msk; +} + +static uint32_t mpu_safety_rasr(uint32_t access) { + return mpu_rasr(access, ARM_MPU_REGION_SIZE_64B); +} + +static uint32_t mpu_stack_guard_rasr(void) { + return mpu_rasr(ARM_MPU_AP_NONE, ARM_MPU_REGION_SIZE_512B); +} + +static void mpu_set_safety_access(uint32_t access) { + __DMB(); + MPU->RNR = MPU_REGION_SAFETY; + MPU->RASR = mpu_safety_rasr(access); + __DSB(); + __ISB(); +} + +// only safety code writes the safety state, interrupts stay off while it's writable +void mpu_safety_open(void) { + ENTER_CRITICAL(); + if (mpu_safety_depth == 0U) { + // body firmware runs without the MPU + if ((MPU->CTRL & MPU_CTRL_ENABLE_Msk) != 0U) { + for (uint8_t i = 0U; i < 16U; i++) { + if (!mpu_safety_changed && (_ssafety[i] != mpu_safety_copy[i])) { + mpu_safety_changed = true; + mpu_safety_changed_addr = (uint32_t)&_ssafety[i]; + } + } + } + mpu_set_safety_access(ARM_MPU_AP_PRIV); + } + mpu_safety_depth += 1U; +} + +void mpu_safety_close(void) { + mpu_safety_depth -= 1U; + if (mpu_safety_depth == 0U) { + mpu_set_safety_access(MPU_SAFETY_CLOSED); + (void)memcpy(mpu_safety_copy, _ssafety, sizeof(mpu_safety_copy)); + } + EXIT_CRITICAL(); +} + +void mpu_init(void) { + MPU->RNR = MPU_REGION_STACK_GUARD; + MPU->RBAR = (uint32_t)&_stack_guard; + MPU->RASR = mpu_stack_guard_rasr(); + + MPU->RNR = MPU_REGION_SAFETY; + MPU->RBAR = (uint32_t)&_ssafety; + MPU->RASR = mpu_safety_rasr(MPU_SAFETY_CLOSED); + (void)memcpy(mpu_safety_copy, _ssafety, sizeof(mpu_safety_copy)); + + // privileged default map for everything else, a violation escalates to HardFault + register_set(&(MPU->CTRL), MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk, 0x7U); + __DSB(); + __ISB(); +} + +static bool mpu_region_ok(uint32_t region, uint32_t base, uint32_t rasr) { + MPU->RNR = region; + return ((MPU->RBAR & MPU_RBAR_ADDR_Msk) == base) && (MPU->RASR == rasr); +} + +// RBAR and RASR are banked by RNR, so register_set can't read them back +void mpu_check(void) { + ENTER_CRITICAL(); + uint32_t bad = mpu_region_ok(MPU_REGION_STACK_GUARD, (uint32_t)&_stack_guard, mpu_stack_guard_rasr()) ? 0U : (1UL << MPU_REGION_STACK_GUARD); + bad |= mpu_region_ok(MPU_REGION_SAFETY, (uint32_t)&_ssafety, mpu_safety_rasr(MPU_SAFETY_CLOSED)) ? 0U : (1UL << MPU_REGION_SAFETY); + for (uint32_t region = MPU_REGION_STACK_GUARD + 1U; region < ((MPU->TYPE & MPU_TYPE_DREGION_Msk) >> MPU_TYPE_DREGION_Pos); region++) { + MPU->RNR = region; + bad |= ((MPU->RASR & MPU_RASR_ENABLE_Msk) == 0U) ? 0U : (1UL << region); + } + EXIT_CRITICAL(); + if (bad != 0U) { + fault_occurred_value(FAULT_REGISTER_DIVERGENT, bad); + } + if (mpu_safety_changed) { + fault_occurred_value(FAULT_MPU, mpu_safety_changed_addr); + } +} diff --git a/panda/board/stm32h7/llspi.h b/panda/board/stm32h7/llspi.h index 650c62ccab..6b5b00964e 100644 --- a/panda/board/stm32h7/llspi.h +++ b/panda/board/stm32h7/llspi.h @@ -21,6 +21,10 @@ void llspi_queue(const uint8_t *tx, uint16_t tx_len) { (void)dat; } SPI4->IFCR = (0x1FFU << 3U); + // DMA_SM_4: a transfer error of the last transfer, checked before the clear + if ((DMA2->LISR & (DMA_LISR_TEIF2 | DMA_LISR_TEIF3)) != 0U) { + fault_occurred_value(FAULT_DMA, DMA2->LISR); + } DMA2->LIFCR = (0x3DUL << 16U) | (0x3DUL << 22U); // streams 2 + 3 // setup buffers @@ -44,16 +48,24 @@ uint16_t llspi_rx_count(void) { return SPI_BUF_SIZE - DMA2_Stream2->NDTR; } +#define EXTI15_10_LINES 0xFC00U + // CS rising edge, the end of a transfer static void EXTI15_10_IRQ_Handler(void) { if ((EXTI->PR1 & (1UL << SPI_CS_PIN)) != 0U) { - // clear first, so an edge during processing fires again + // clear first, so an edge during processing fires again, read back so the clear lands before the return EXTI->PR1 = (1UL << SPI_CS_PIN); + (void)EXTI->PR1; // if we're late and the next transfer already started, its own rising edge handles it if ((GPIOE->IDR & (1UL << SPI_CS_PIN)) != 0U) { spi_cs_rising(); } } + // no other line on this vector has a trigger enabled + if ((EXTI->PR1 & (EXTI15_10_LINES & ~(1UL << SPI_CS_PIN))) != 0U) { + EXTI->PR1 = EXTI15_10_LINES & ~(1UL << SPI_CS_PIN); + fault_occurred_value(FAULT_UNUSED_INTERRUPT_HANDLED, __get_IPSR()); + } } void llspi_init(void) { @@ -81,5 +93,5 @@ void llspi_init(void) { register_set_bits(&(EXTI->RTSR1), (1UL << SPI_CS_PIN)); register_set_bits(&(EXTI->IMR1), (1UL << SPI_CS_PIN)); EXTI->PR1 = (1UL << SPI_CS_PIN); - NVIC_EnableIRQ(EXTI15_10_IRQn); + nvic_enable(EXTI15_10_IRQn); } diff --git a/panda/board/stm32h7/llusb.h b/panda/board/stm32h7/llusb.h index 3a48ff0da5..fbc79d1f8c 100644 --- a/panda/board/stm32h7/llusb.h +++ b/panda/board/stm32h7/llusb.h @@ -5,9 +5,9 @@ USB_OTG_GlobalTypeDef *USBx = USB_OTG_HS; static void OTG_HS_IRQ_Handler(void) { - NVIC_DisableIRQ(OTG_HS_IRQn); + nvic_disable(OTG_HS_IRQn); usb_irqhandler(); - NVIC_EnableIRQ(OTG_HS_IRQn); + nvic_enable(OTG_HS_IRQn); } void usb_init(void) { @@ -77,5 +77,5 @@ void usb_init(void) { USBx_DEVICE->DCTL &= ~(USB_OTG_DCTL_SDIS); // enable the IRQ - NVIC_EnableIRQ(OTG_HS_IRQn); + nvic_enable(OTG_HS_IRQn); } diff --git a/panda/board/stm32h7/peripherals.h b/panda/board/stm32h7/peripherals.h index 4ad0930632..b0eab86200 100644 --- a/panda/board/stm32h7/peripherals.h +++ b/panda/board/stm32h7/peripherals.h @@ -65,6 +65,27 @@ void flasher_peripherals_init(void) { } #endif +// FFI_SM_0, FFI_SM_1: hold everything without a clock enable in reset, RST and EN bits share positions +// all RST bits per stm32h725xx.h, except CPURST and the H73x only CRYP, HASH, OTFDEC +static void hold_unused_in_reset(void) { + static const struct { volatile uint32_t *enr; volatile uint32_t *rstr; uint32_t rst_bits; } regs[] = { + {&(RCC->AHB1ENR), &(RCC->AHB1RSTR), 0x02008023U}, + {&(RCC->AHB2ENR), &(RCC->AHB2RSTR), 0x00030241U}, + {&(RCC->AHB3ENR), &(RCC->AHB3RSTR), 0x00295011U}, + {&(RCC->AHB4ENR), &(RCC->AHB4RSTR), 0x032806FFU}, + {&(RCC->APB1LENR), &(RCC->APB1LRSTR), 0xEAFFC3FFU}, + {&(RCC->APB1HENR), &(RCC->APB1HRSTR), 0x03000136U}, + {&(RCC->APB2ENR), &(RCC->APB2RSTR), 0x405730F3U}, + {&(RCC->APB3ENR), &(RCC->APB3RSTR), 0x00000008U}, + {&(RCC->APB4ENR), &(RCC->APB4RSTR), 0x0420DEAAU}, + }; + for (uint8_t i = 0U; i < (sizeof(regs) / sizeof(regs[0])); i++) { + uint32_t en = *regs[i].enr; + register_set(regs[i].rstr, regs[i].rst_bits & ~en, 0xFFFFFFFFU); + register_set(regs[i].enr, en, 0xFFFFFFFFU); + } +} + // Peripheral initialization void peripherals_init(void) { // enable GPIO(A,B,C,D,E,F,G,H) @@ -110,11 +131,22 @@ void peripherals_init(void) { RCC->APB1LENR |= RCC_APB1LENR_TIM6EN; // interrupt timer RCC->APB1LENR |= RCC_APB1LENR_TIM7EN; // DMA trigger timer RCC->APB2ENR |= RCC_APB2ENR_TIM8EN; // tick timer + RCC->APB2ENR |= RCC_APB2ENR_TIM16EN; // clock check RCC->APB1LENR |= RCC_APB1LENR_TIM12EN; // slow loop RCC->APB1LENR |= RCC_APB1LENR_TIM5EN; // sound trigger timer + hold_unused_in_reset(); } void enable_interrupt_timer(void) { register_set_bits(&(RCC->APB1LENR), RCC_APB1LENR_TIM6EN); // Enable interrupt timer peripheral } + +// FLASH_SM_3: nothing unlocks the option bytes, and they boot from 0x08000000 when BOOT0 is low +void check_option_bytes(void) { + uint32_t optlock = FLASH->OPTCR & FLASH_OPTCR_OPTLOCK; + uint32_t boot = FLASH->BOOT_CUR & FLASH_BOOT_ADD0; + if ((optlock == 0U) || (boot != 0x0800U)) { + fault_occurred_value(FAULT_REGISTER_DIVERGENT, (boot << 16) | optlock); + } +} diff --git a/panda/board/stm32h7/self_test.s b/panda/board/stm32h7/self_test.s new file mode 100644 index 0000000000..b519c61a22 --- /dev/null +++ b/panda/board/stm32h7/self_test.s @@ -0,0 +1,377 @@ + .syntax unified + .cpu cortex-m7 + .thumb + +/* keep in sync with board/drivers/self_test.h */ + .equ SELF_TEST_PASS, 0x5AFEC0DE + +/* r0 holds the expected value */ +.macro expect reg, val, fail + ldr r0, =\val + cmp \reg, r0 + bne \fail +.endm + + .section .text.cpu_test + .global cpu_test + .type cpu_test, %function + +/* UM2331 CPU_SM_0, walking bit (IEC 61508-7 A.3.2) and a fixed ALU, flags, branch and load/store sequence + returns SELF_TEST_PASS, anything else is a fault */ +cpu_test: + push {r4-r11, lr} + mrs r1, primask + mrs r2, psp + push {r1, r2} + cpsid i + + /* every register its own value, written before any is read back, so coupling between registers shows up */ + .irp pattern, 0x55555555, 0xAAAAAAAA + ldr r0, =\pattern + .irp reg, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, lr + add r0, r0, #0x01010101 + mov \reg, r0 + .endr + ldr r0, =\pattern + .irp reg, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, lr + add r0, r0, #0x01010101 + cmp \reg, r0 + bne .Lcpu_fail + .endr + ldr r0, =\pattern + ldr r1, =\pattern + cmp r0, r1 + bne .Lcpu_fail + .endr + + /* walking one, against r0 */ + .irp reg, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, lr + movs r0, #1 + mov \reg, #1 +1: + cmp \reg, r0 + bne .Lcpu_fail + lsl \reg, \reg, #1 + lsls r0, r0, #1 + bne 1b + .endr + + /* stack pointers, MSP is word aligned and only out of use for the two instructions in between */ + .irp pattern, 0x55555554, 0xAAAAAAA8 + ldr r1, =\pattern + msr psp, r1 + mrs r2, psp + cmp r2, r1 + bne .Lcpu_fail + mov r3, sp + msr msp, r1 + mrs r2, msp + msr msp, r3 + cmp r2, r1 + bne .Lcpu_fail + .endr + + /* APSR NZCVQ and GE */ + .irp flags, 0xA80A0000, 0x50050000 + ldr r1, =\flags + msr APSR_nzcvqg, r1 + mrs r2, APSR + ldr r3, =0xF80F0000 + and r2, r2, r3 + cmp r2, r1 + bne .Lcpu_fail + .endr + + /* ALU, expected values computed off target */ + ldr r4, =0x12345678 + ldr r5, =0x9ABCDEF0 + mul r1, r4, r5 + expect r1, 0x242D2080, .Lcpu_fail + umull r1, r2, r4, r5 + expect r1, 0x242D2080, .Lcpu_fail + expect r2, 0x0B00EA4E, .Lcpu_fail + smull r1, r2, r4, r5 + expect r2, 0xF8CC93D6, .Lcpu_fail + mla r1, r4, r5, r4 + expect r1, 0x366176F8, .Lcpu_fail + udiv r1, r5, r4 + expect r1, 0x00000008, .Lcpu_fail + sdiv r1, r5, r4 + expect r1, 0xFFFFFFFB, .Lcpu_fail + ror r1, r4, #8 + expect r1, 0x78123456, .Lcpu_fail + asr r1, r5, #4 + expect r1, 0xF9ABCDEF, .Lcpu_fail + lsr r1, r5, #4 + expect r1, 0x09ABCDEF, .Lcpu_fail + lsl r1, r4, #4 + expect r1, 0x23456780, .Lcpu_fail + eor r1, r4, r5 + expect r1, 0x88888888, .Lcpu_fail + and r1, r4, r5 + expect r1, 0x12345670, .Lcpu_fail + orr r1, r4, r5 + expect r1, 0x9ABCDEF8, .Lcpu_fail + bic r1, r4, r5 + expect r1, 0x00000008, .Lcpu_fail + mvn r1, r4 + expect r1, 0xEDCBA987, .Lcpu_fail + clz r1, r4 + expect r1, 0x00000003, .Lcpu_fail + rbit r1, r4 + expect r1, 0x1E6A2C48, .Lcpu_fail + rev r1, r4 + expect r1, 0x78563412, .Lcpu_fail + + /* flags and conditional branches */ + ldr r1, =0x7FFFFFFF + adds r1, r1, #1 + bvc .Lcpu_fail + bpl .Lcpu_fail + bcs .Lcpu_fail + beq .Lcpu_fail + adds r1, r1, r1 + bne .Lcpu_fail + bcc .Lcpu_fail + bvc .Lcpu_fail + bmi .Lcpu_fail + movs r1, #0 + subs r1, r1, #1 + bcs .Lcpu_fail + bpl .Lcpu_fail + bvs .Lcpu_fail + cmp r4, r5 + ite lo + movlo r1, #1 + movhs r1, #2 + expect r1, 1, .Lcpu_fail + cmp r4, r5 + ite gt + movgt r1, #1 + movle r1, #2 + expect r1, 1, .Lcpu_fail + + /* load and store widths */ + sub sp, sp, #16 + str r4, [sp] + strh r5, [sp, #4] + strb r4, [sp, #6] + strd r4, r5, [sp, #8] + ldr r1, [sp] + ldrh r2, [sp, #4] + ldrb r3, [sp, #6] + ldrd r6, r7, [sp, #8] + add sp, sp, #16 + expect r1, 0x12345678, .Lcpu_fail + expect r2, 0x0000DEF0, .Lcpu_fail + expect r3, 0x00000078, .Lcpu_fail + cmp r6, r4 + bne .Lcpu_fail + cmp r7, r5 + bne .Lcpu_fail + + ldr r0, =SELF_TEST_PASS + b .Lcpu_done +.Lcpu_fail: + movs r0, #0 +.Lcpu_done: + pop {r1, r2} + msr psp, r2 + msr primask, r1 + pop {r4-r11, pc} + .ltorg + .size cpu_test, .-cpu_test + + .fpu fpv5-d16 + .section .text.fpu_test + .global fpu_test + .type fpu_test, %function + +/* UM2331 CPU_SM_0 for the FPU: every register its own value, FPSCR flags and control bits, arithmetic + returns SELF_TEST_PASS, anything else is a fault */ +fpu_test: + vpush {s16-s31} + vmrs r3, fpscr + + .irp pattern, 0x55555555, 0xAAAAAAAA + ldr r0, =\pattern + .irp reg, s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, s16, s17, s18, s19, s20, s21, s22, s23, s24, s25, s26, s27, s28, s29, s30, s31 + add r0, r0, #0x01010101 + vmov \reg, r0 + .endr + ldr r0, =\pattern + .irp reg, s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, s16, s17, s18, s19, s20, s21, s22, s23, s24, s25, s26, s27, s28, s29, s30, s31 + add r0, r0, #0x01010101 + vmov r1, \reg + cmp r1, r0 + bne .Lfpu_fail + .endr + .endr + + /* NZCV and AHP, DN, FZ, RMode */ + .irp bits, 0xA6800000, 0x51400000 + ldr r1, =\bits + vmsr fpscr, r1 + vmrs r2, fpscr + ldr r0, =0xF7C00000 + and r2, r2, r0 + cmp r2, r1 + bne .Lfpu_fail + .endr + + /* exact single precision results, round to nearest */ + vmsr fpscr, r3 + ldr r0, =0x3FC00000 + vmov s0, r0 + ldr r0, =0x40100000 + vmov s1, r0 + vadd.f32 s2, s0, s1 + vmov r1, s2 + expect r1, 0x40700000, .Lfpu_fail + vsub.f32 s2, s1, s0 + vmov r1, s2 + expect r1, 0x3F400000, .Lfpu_fail + vmul.f32 s2, s0, s1 + vmov r1, s2 + expect r1, 0x40580000, .Lfpu_fail + vdiv.f32 s3, s2, s0 + vmov r1, s3 + expect r1, 0x40100000, .Lfpu_fail + vsqrt.f32 s3, s1 + vmov r1, s3 + expect r1, 0x3FC00000, .Lfpu_fail + vmov.f32 s3, s0 + vfma.f32 s3, s0, s1 + vmov r1, s3 + expect r1, 0x409C0000, .Lfpu_fail + vcmp.f32 s0, s1 + vmrs APSR_nzcv, fpscr + bge .Lfpu_fail + + ldr r0, =SELF_TEST_PASS + b .Lfpu_done +.Lfpu_fail: + movs r0, #0 +.Lfpu_done: + vmsr fpscr, r3 + vpop {s16-s31} + bx lr + .ltorg + .size fpu_test, .-fpu_test + + .section .text.ram_test_startup + .global ram_test_startup + .type ram_test_startup, %function + +/* UM2331 RAM_SM_0/8, march c- over all RAM before the C runtime, without a stack since the stack is tested too + the first element writes 64 bits, which also sets up the ECC of AXI SRAM and ITCM (64-bit ECC words, RM0468 3.2) + leaves the RAM zeroed and SELF_TEST_PASS in self_test_cookie if every word passed */ +ram_test_startup: + /* SRAM1/2 clocks are off after reset (RM0468 RCC_AHB2ENR), read back before the first access (RM0468 8.5.10) */ + ldr r0, =0x580244DC + ldr r1, [r0] + orr r1, r1, #0x60000000 + str r1, [r0] + ldr r1, [r0] + dsb + + /* the reset cookies are in SRAM4 */ + ldr r0, =enter_bootloader_mode + ldr r10, [r0] + ldr r0, =fault_cookie + ldr r11, [r0] + ldr r0, =fault_cfsr + ldr r7, [r0] + ldr r0, =fault_addr + ldr r12, [r0] + ldr r0, =fault_pc + ldr r4, [r0] + + ldr r9, =SELF_TEST_PASS + ldr r8, =ram_test_regions +.Lram_region: + ldr r0, [r8], #4 + ldr r1, [r8], #4 + cmp r0, r1 + beq .Lram_done + .irp bg, 0x55555555, 0x00000000 + ldr r2, =\bg + mvn r3, r2 + mov r5, r0 +1: + strd r2, r2, [r5], #8 + cmp r5, r1 + blo 1b + mov r5, r0 +2: + ldr r6, [r5] + cmp r6, r2 + it ne + movne r9, #0 + str r3, [r5], #4 + cmp r5, r1 + blo 2b + mov r5, r0 +3: + ldr r6, [r5] + cmp r6, r3 + it ne + movne r9, #0 + str r2, [r5], #4 + cmp r5, r1 + blo 3b + mov r5, r1 +4: + ldr r6, [r5, #-4]! + cmp r6, r2 + it ne + movne r9, #0 + str r3, [r5] + cmp r5, r0 + bhi 4b + mov r5, r1 +5: + ldr r6, [r5, #-4]! + cmp r6, r3 + it ne + movne r9, #0 + str r2, [r5] + cmp r5, r0 + bhi 5b + mov r5, r0 +6: + ldr r6, [r5], #4 + cmp r6, r2 + it ne + movne r9, #0 + cmp r5, r1 + blo 6b + .endr + b .Lram_region + +.Lram_done: + ldr r0, =enter_bootloader_mode + str r10, [r0] + ldr r0, =fault_cookie + str r11, [r0] + ldr r0, =fault_cfsr + str r7, [r0] + ldr r0, =fault_addr + str r12, [r0] + ldr r0, =fault_pc + str r4, [r0] + ldr r0, =self_test_cookie + str r9, [r0] + bx lr + .ltorg + .size ram_test_startup, .-ram_test_startup + + .section .rodata.ram_test_regions + .align 2 +ram_test_regions: + .word _dtcmram_start, _dtcmram_end + .word _axisram_start, _axisram_end + .word _itcmram_start, _itcmram_end + .word _sram12_start, _sram12_end + .word _sram4_start, _sram4_end + .word 0, 0 diff --git a/panda/board/stm32h7/sound.h b/panda/board/stm32h7/sound.h index d34b0c4ff3..18938f11be 100644 --- a/panda/board/stm32h7/sound.h +++ b/panda/board/stm32h7/sound.h @@ -229,6 +229,6 @@ void sound_init(void) { // enable all initted blocks register_set_bits(&SAI4_Block_A->CR1, SAI_xCR1_SAIEN); register_set_bits(&SAI4_Block_B->CR1, SAI_xCR1_SAIEN); - NVIC_EnableIRQ(BDMA_Channel0_IRQn); - NVIC_EnableIRQ(DMA1_Stream0_IRQn); + nvic_enable(BDMA_Channel0_IRQn); + nvic_enable(DMA1_Stream0_IRQn); } diff --git a/panda/board/stm32h7/startup_stm32h7x5xx.s b/panda/board/stm32h7/startup_stm32h7x5xx.s index 77849b9148..4e28fb8cbc 100644 --- a/panda/board/stm32h7/startup_stm32h7x5xx.s +++ b/panda/board/stm32h7/startup_stm32h7x5xx.s @@ -61,6 +61,7 @@ defined in linker script */ Reset_Handler: ldr sp, =_estack /* set stack pointer */ bl __initialize_hardware_early + bl ram_test_startup /* Copy the data segment initializers from flash to SRAM */ ldr r0, =_sdata @@ -103,15 +104,22 @@ LoopFillZerobss: /** * @brief This is the code that gets called when the processor receives an - * unexpected interrupt. This simply enters an infinite loop, preserving - * the system state for examination by a debugger. + * unexpected interrupt. It resets through the HardFault handler, so the + * reset shows up in health. * @param None * @retval None */ +/* the app and body firmware link the real one from self_test.s, the bootstub doesn't test RAM */ + .section .text.ram_test_none,"ax",%progbits +ram_test_none: + bx lr + .size ram_test_none, .-ram_test_none + .weak ram_test_startup + .thumb_set ram_test_startup, ram_test_none + .section .text.Default_Handler,"ax",%progbits Default_Handler: -Infinite_Loop: - b Infinite_Loop + b HardFault_Handler .size Default_Handler, .-Default_Handler /****************************************************************************** * diff --git a/panda/board/stm32h7/stm32h7_config.h b/panda/board/stm32h7/stm32h7_config.h index 95abc6bc5f..6da06d8938 100644 --- a/panda/board/stm32h7/stm32h7_config.h +++ b/panda/board/stm32h7/stm32h7_config.h @@ -69,6 +69,7 @@ separate IRQs for RX and TX. #if !defined(BOOTSTUB) #include "board/drivers/debug.h" + #include "board/stm32h7/llmpu.h" #endif #ifdef PANDA_BODY diff --git a/panda/board/stm32h7/stm32h7x5_flash.ld b/panda/board/stm32h7/stm32h7x5_flash.ld index ae8800e7de..98392fc9ac 100644 --- a/panda/board/stm32h7/stm32h7x5_flash.ld +++ b/panda/board/stm32h7/stm32h7x5_flash.ld @@ -55,8 +55,22 @@ ENTRY(Reset_Handler) /* Highest address of the user mode stack */ enter_bootloader_mode = 0x38001FFC; fault_cookie = 0x38003FFC; +self_test_cookie = 0x38003FF8; +fault_cfsr = 0x38003FF4; +fault_addr = 0x38003FF0; +fault_pc = 0x38003FEC; _estack = 0x20020000; /* end of RAM */ +_dtcmram_start = ORIGIN(DTCMRAM); +_dtcmram_end = ORIGIN(DTCMRAM) + LENGTH(DTCMRAM); +_sram12_start = ORIGIN(SRAM12); +_sram12_end = ORIGIN(SRAM12) + LENGTH(SRAM12); +_sram4_start = ORIGIN(SRAM4); +_sram4_end = ORIGIN(SRAM4) + LENGTH(SRAM4); +_axisram_start = ORIGIN(AXISRAM); +_axisram_end = ORIGIN(AXISRAM) + LENGTH(AXISRAM); +_itcmram_start = ORIGIN(ITCMRAM); +_itcmram_end = ORIGIN(ITCMRAM) + LENGTH(ITCMRAM); _app_start = 0x08020000; /* Reserve Sector 0(128K) for bootloader */ /* Generate a link error if heap and stack don't fit into RAM */ @@ -152,6 +166,18 @@ SECTIONS { . = ALIGN(4); _sdata = .; /* create a global symbol at data start */ + + /* safety state, one MPU region, see board/stm32h7/llmpu.h */ + . = ALIGN(64); + _ssafety = .; + *(.data.controls_allowed .bss.controls_allowed) + *(.data.current_safety_mode .bss.current_safety_mode) + *(.data.current_safety_param .bss.current_safety_param) + *(.data.current_hooks .bss.current_hooks) + *(.data.current_safety_config .bss.current_safety_config) + . = ALIGN(64); + _esafety = .; + *(.data) /* .data sections */ *(.data*) /* .data* sections */ @@ -182,11 +208,21 @@ SECTIONS . = ALIGN(8); PROVIDE ( end = . ); PROVIDE ( _end = . ); + . = ALIGN(512); + _stack_guard = .; + . = . + 512; + _stack_guard_end = .; . = . + _Min_Heap_Size; . = . + _Min_Stack_Size; . = ALIGN(8); } >DTCMRAM + ASSERT((_esafety - _ssafety) <= 64, "safety state doesn't fit its MPU region") + ASSERT(DEFINED(controls_allowed) ? (controls_allowed >= _ssafety && controls_allowed < _esafety) : !DEFINED(safety_tx_hook), "controls_allowed not in the MPU region") + ASSERT(DEFINED(current_safety_mode) ? (current_safety_mode >= _ssafety && current_safety_mode < _esafety) : !DEFINED(safety_tx_hook), "current_safety_mode not in the MPU region") + ASSERT(DEFINED(current_safety_param) ? (current_safety_param >= _ssafety && current_safety_param < _esafety) : !DEFINED(safety_tx_hook), "current_safety_param not in the MPU region") + ASSERT(DEFINED(current_safety_config) ? (current_safety_config >= _ssafety && current_safety_config < _esafety) : !DEFINED(safety_tx_hook), "current_safety_config not in the MPU region") + .itcmram (NOLOAD) : { . = ALIGN(4); diff --git a/panda/board/sys/faults.h b/panda/board/sys/faults.h index c96bcc199f..0e167bbf94 100644 --- a/panda/board/sys/faults.h +++ b/panda/board/sys/faults.h @@ -10,14 +10,33 @@ void fault_occurred(uint32_t fault) { if ((PERMANENT_FAULTS & fault) != 0U) { print("Permanent fault occurred: 0x"); puth(fault); print("\n"); fault_status = FAULT_STATUS_PERMANENT; + fault_safe_state(); } else { print("Temporary fault occurred: 0x"); puth(fault); print("\n"); - fault_status = FAULT_STATUS_TEMPORARY; + fault_status = (fault_status == FAULT_STATUS_PERMANENT) ? FAULT_STATUS_PERMANENT : FAULT_STATUS_TEMPORARY; } } faults |= fault; } +#define FAULT_SITES 32U + +// printed once per call site +void fault_occurred_at(uint32_t fault, uint32_t value, const char *site) { + static const char *sites[FAULT_SITES]; + static uint8_t site_cnt = 0U; + bool seen = false; + for (uint8_t i = 0U; i < site_cnt; i++) { + seen = seen || (sites[i] == site); + } + if (!seen && (site_cnt < FAULT_SITES)) { + sites[site_cnt] = site; + site_cnt++; + print("Fault 0x"); puth(fault); print(" at "); print(site); print(" value 0x"); puth(value); print("\n"); + } + fault_occurred(fault); +} + void fault_recovered(uint32_t fault) { if ((PERMANENT_FAULTS & fault) == 0U) { faults &= ~fault; diff --git a/panda/board/sys/power_saving.h b/panda/board/sys/power_saving.h index 9fb33f0cb7..eabea6c12d 100644 --- a/panda/board/sys/power_saving.h +++ b/panda/board/sys/power_saving.h @@ -62,7 +62,7 @@ void set_power_save_state(bool enable) { #ifdef STM32H7 static void enter_stop_mode(void) { // the SPI CS interrupt shares an IRQ with the FDCAN3 RX wakeup line, and doesn't clear its pending bit - NVIC_DisableIRQ(EXTI15_10_IRQn); + nvic_disable(EXTI15_10_IRQn); // set all GPIO to analog mode to reduce power, analog mode also disables pull resistors register_set(&(GPIOA->MODER), 0xFFFFFFFFU, 0xFFFFFFFFU); @@ -90,7 +90,7 @@ static void enter_stop_mode(void) { register_clear_bits(&(DTS->CFGR1), DTS_CFGR1_TS1_START); register_clear_bits(&(DTS->CFGR1), DTS_CFGR1_TS1_EN); register_set(&(DTS->CFGR1), 0U, (DTS_CFGR1_TS1_SMP_TIME_Msk | DTS_CFGR1_REFCLK_SEL_Msk | DTS_CFGR1_Q_MEAS_OPT_Msk | DTS_CFGR1_HSREF_CLK_DIV_Msk | DTS_CFGR1_TS1_INTRIG_SEL_Msk)); - RCC->APB4ENR &= ~(RCC_APB4ENR_DTSEN); + register_clear_bits(&(RCC->APB4ENR), RCC_APB4ENR_DTSEN); // disable HSI48: 48 MHz USB clock register_clear_bits(&(RCC->CR), RCC_CR_HSI48ON); diff --git a/panda/board/sys/sys.h b/panda/board/sys/sys.h index 19e5f38f7a..ec2a901c55 100644 --- a/panda/board/sys/sys.h +++ b/panda/board/sys/sys.h @@ -36,24 +36,42 @@ extern uint8_t global_critical_depth; #define FAULT_INTERRUPT_RATE_TACH (1UL << 5) #define FAULT_INTERRUPT_RATE_INTERRUPTS (1UL << 7) #define FAULT_INTERRUPT_RATE_SPI_DMA (1UL << 8) +#define FAULT_SELF_TEST_CPU (1UL << 9) +#define FAULT_SELF_TEST_RAM (1UL << 10) +#define FAULT_SELF_TEST_FLASH (1UL << 11) +#define FAULT_ECC (1UL << 12) +#define FAULT_OUTPUT_READBACK (1UL << 13) +#define FAULT_DMA (1UL << 14) #define FAULT_INTERRUPT_RATE_USB (1UL << 15) +#define FAULT_CLOCK (1UL << 16) +#define FAULT_INTERRUPT_MISSING (1UL << 17) #define FAULT_REGISTER_DIVERGENT (1UL << 18) #define FAULT_INTERRUPT_RATE_CLOCK_SOURCE (1UL << 20) #define FAULT_INTERRUPT_RATE_TICK (1UL << 21) #define FAULT_INTERRUPT_RATE_EXTI (1UL << 22) #define FAULT_INTERRUPT_RATE_SPI (1UL << 23) +#define FAULT_ADC (1UL << 24) #define FAULT_SIREN_MALFUNCTION (1UL << 25) #define FAULT_HEARTBEAT_LOOP_WATCHDOG (1UL << 26) #define FAULT_INTERRUPT_RATE_SOUND_DMA (1UL << 27) +#define FAULT_MPU (1UL << 28) +#define FAULT_TIMER (1UL << 29) -// Permanent faults +// permanent faults put panda in the safe state until reset +// TODO: enable faults one by one once their fleet rate is checked #define PERMANENT_FAULTS 0U extern uint8_t fault_status; extern uint32_t faults; void fault_occurred(uint32_t fault); +void fault_occurred_at(uint32_t fault, uint32_t value, const char *site); +// a bit can be raised from several places, so each logs where it was raised +#define FAULT_STR(x) #x +#define FAULT_XSTR(x) FAULT_STR(x) +#define fault_occurred_value(fault, value) fault_occurred_at((fault), (value), __FILE__ ":" FAULT_XSTR(__LINE__)) void fault_recovered(uint32_t fault); +void fault_safe_state(void); // ******************** power_saving ******************** diff --git a/panda/python/__init__.py b/panda/python/__init__.py index ec5f5d4382..5277e128fe 100644 --- a/panda/python/__init__.py +++ b/panda/python/__init__.py @@ -1,6 +1,7 @@ # python library to interface with panda import os import re +import math import sys import time import usb1 @@ -17,7 +18,7 @@ from opendbc.car.structs import CarParams from .base import BaseHandle from .constants import BASEDIR, FW_PATH, McuType, compute_version_hash from .dfu import PandaDFU -from .spi import PandaSpiHandle, PandaSpiException, PandaProtocolMismatch +from .spi import PandaSpiHandle, PandaSpiException, PandaSpiNackResponse, PandaProtocolMismatch from .usb import PandaUsbHandle from .utils import logger @@ -149,6 +150,7 @@ class Panda: HEALTH_FLAG_SOM_RESET_TRIGGERED = 1 << 6 HEALTH_FLAG_NMI_RESET = 1 << 7 HEALTH_FLAG_HARDFAULT_RESET = 1 << 8 + HEALTH_FLAG_HEARTBEAT_DISABLED = 1 << 9 H7_DEVICES = [HW_TYPE_RED_PANDA, HW_TYPE_TRES, HW_TYPE_CUATRO, HW_TYPE_BODY] F4_DEVICES = [HW_TYPE_DOS] @@ -556,6 +558,7 @@ class Panda: "som_reset_triggered": bool(flags & self.HEALTH_FLAG_SOM_RESET_TRIGGERED), "nmi_reset": bool(flags & self.HEALTH_FLAG_NMI_RESET), "hardfault_reset": bool(flags & self.HEALTH_FLAG_HARDFAULT_RESET), + "heartbeat_disabled": bool(flags & self.HEALTH_FLAG_HEARTBEAT_DISABLED), "sound_output_level": a[19], "temperature": a[20] - 40.0, } @@ -690,6 +693,46 @@ class Panda: def enter_stop_mode(self): self._handle.controlWrite(Panda.REQUEST_OUT, 0xb5, 0, 0, b'', expect_disconnect=True) + def _require_fault_injection(self, *, h7=False): + if self.get_type() == Panda.HW_TYPE_BODY: + raise NotImplementedError("Body firmware does not implement these diagnostics") + if h7 and self.get_mcu_type() != McuType.H7: + raise NotImplementedError("This diagnostic requires an H7 Panda") + if not self.get_version().endswith("-FAULTTEST"): + raise RuntimeError("Fault injection requires a dedicated bench firmware build") + + def inject_fault(self, fault_bit): + self._require_fault_injection() + self._handle.controlWrite(Panda.REQUEST_OUT, 0xea, fault_bit, 0, b'') + + def inject_hardfault(self): + self._require_fault_injection() + self._handle.controlWrite(Panda.REQUEST_OUT, 0xeb, 0, 0, b'', expect_disconnect=True) + + def inject_interrupt_fault(self, test): + self._require_fault_injection(h7=True) + self._handle.controlWrite(Panda.REQUEST_OUT, 0xef, test, 0, b'') + + def inject_nmi(self): + self._require_fault_injection() + self._handle.controlWrite(Panda.REQUEST_OUT, 0xec, 0, 0, b'', expect_disconnect=True) + + def inject_mpu_fault(self): + self._require_fault_injection(h7=True) + self._handle.controlWrite(Panda.REQUEST_OUT, 0xee, 0, 0, b'', expect_disconnect=True) + + def inject_stack_overflow(self): + self._require_fault_injection(h7=True) + self._handle.controlWrite(Panda.REQUEST_OUT, 0xf0, 0, 0, b'', expect_disconnect=True) + + def inject_self_test_failure(self, test): + self._require_fault_injection(h7=True) + self._handle.controlWrite(Panda.REQUEST_OUT, 0xed, test, 0, b'') + + def inject_readback_fault(self, test): + self._require_fault_injection(h7=True) + self._handle.controlWrite(Panda.REQUEST_OUT, 0xe9, test, 0, b'') + def set_safety_mode(self, mode=CarParams.SafetyModel.silent, param=0): self._handle.controlWrite(Panda.REQUEST_OUT, 0xdc, mode, param, b'') @@ -722,16 +765,27 @@ class Panda: # libusb will try to send it again, with a max timeout. # Timeout is in ms. If set to 0, the timeout is infinite. CAN_SEND_TIMEOUT_MS = 10 + # over SPI, a NACK comes back right away. this is how long we wait before trying that chunk again + CAN_SEND_RETRY_S = 1e-3 def can_reset_communications(self): self._handle.controlWrite(Panda.REQUEST_OUT, 0xc0, 0, 0, b'') @ensure_can_packet_version def can_send_many(self, arr, *, fd=False, timeout=CAN_SEND_TIMEOUT_MS): + deadline = time.monotonic() + (timeout * 1e-3 if timeout != 0 else math.inf) snds = pack_can_buffer(arr, chunk=True, fd=fd) for tx in snds: while len(tx) > 0: - bs = self._handle.bulkWrite(3, tx, timeout=timeout) + try: + bs = self._handle.bulkWrite(3, tx, timeout=timeout) + except PandaSpiNackResponse: + # the panda's CAN TX buffers are full. try this chunk again if there's time for it, + # so a timeout of one retry period or less means a single attempt + if time.monotonic() + self.CAN_SEND_RETRY_S > deadline: + raise + time.sleep(self.CAN_SEND_RETRY_S) + continue tx = tx[bs:] def can_send(self, addr, dat, bus, *, fd=False, timeout=CAN_SEND_TIMEOUT_MS): diff --git a/panda/python/spi.py b/panda/python/spi.py index 6c87584180..9be3511cd2 100644 --- a/panda/python/spi.py +++ b/panda/python/spi.py @@ -207,35 +207,19 @@ class PandaSpiHandle(BaseHandle): # timeout of 0 means forever deadline = time.monotonic() + (max(MIN_TIMEOUT_MS, timeout) * 1e-3 if timeout != 0 else math.inf) - nack = None - nack_count = 0 # random, so it's never the same as another request the panda just handled, even from another client. # it stays the same when we send the request again, so the panda never runs it twice req_id = int.from_bytes(os.urandom(4), "little") while True: - try: - with self.dev.acquire() as spi: - dat = self._transfer_spidev(spi, endpoint, req_id, data, deadline, max_rx_len, expect_disconnect) - if dat is not None: - return dat - # the panda went quiet. let go of the bus for a bit, someone else might need it - logger.warning("SPI: panda isn't responding") - time.sleep(0.01) - except PandaSpiNackResponse as e: - # the panda NACKs while its CAN TX buffers are full, don't busy wait on it - if time.monotonic() > deadline: - raise - # a new id, since the panda would answer the same id with the same NACK - req_id = int.from_bytes(os.urandom(4), "little") - nack = e - nack_count += 1 - if nack_count > 3: - time.sleep(min(max(nack_count * 10, 200), 2000) * 1e-6) - except PandaSpiMissingAck: - # only raised once the deadline passed. if the panda was refusing, that's why - if nack is not None: - raise nack from None - raise + # a NACK is the panda's answer (unknown endpoint, or its CAN TX buffers are full), it raises right away. + # whether to try again is the caller's call. + with self.dev.acquire() as spi: + dat = self._transfer_spidev(spi, endpoint, req_id, data, deadline, max_rx_len, expect_disconnect) + if dat is not None: + return dat + # the panda went quiet. let go of the bus for a bit, someone else might need it + logger.warning("SPI: panda isn't responding") + time.sleep(0.01) def get_info(self) -> bytes: # serial, HW type, bootstub diff --git a/panda/tests/libpanda/panda.c b/panda/tests/libpanda/panda.c index 826463d4eb..13ff0d24cb 100644 --- a/panda/tests/libpanda/panda.c +++ b/panda/tests/libpanda/panda.c @@ -9,6 +9,9 @@ void process_can(uint8_t can_number) { } typedef struct harness_configuration harness_configuration; void can_tx_comms_resume_usb(void) { }; void can_tx_comms_resume_spi(void) { }; +void fault_safe_state(void) { } +void mpu_safety_open(void) { } +void mpu_safety_close(void) { } #include "health.h" #include "sys/faults.h" diff --git a/panda/tests/unit/test_diagnostic_requests.py b/panda/tests/unit/test_diagnostic_requests.py new file mode 100644 index 0000000000..7c5488f5fe --- /dev/null +++ b/panda/tests/unit/test_diagnostic_requests.py @@ -0,0 +1,86 @@ +from unittest.mock import Mock + +import pytest + +from panda import Panda, McuType + + +@pytest.mark.parametrize('mcu', (McuType.F4, McuType.H7)) +@pytest.mark.parametrize('version', ('DEV-source-DEBUG', 'DEV-source-DEBUG-FAULTTEST')) +@pytest.mark.parametrize('method,arguments,request,h7', ( + ('inject_fault', (2,), 0xea, False), + ('inject_hardfault', (), 0xeb, False), + ('inject_nmi', (), 0xec, False), + ('inject_interrupt_fault', (2,), 0xef, True), + ('inject_mpu_fault', (), 0xee, True), + ('inject_stack_overflow', (), 0xf0, True), + ('inject_self_test_failure', (2,), 0xed, True), + ('inject_readback_fault', (2,), 0xe9, True), +)) +def test_diagnostic_requests_require_dedicated_bench_firmware(mcu, version, method, arguments, request, h7): + panda = object.__new__(Panda) + panda._handle = Mock() + panda.get_mcu_type = lambda: mcu + panda.get_type = lambda: Panda.HW_TYPE_DOS if mcu == McuType.F4 else Panda.HW_TYPE_TRES + panda.get_version = lambda: version + if h7 and mcu == McuType.F4: + with pytest.raises(NotImplementedError): + getattr(panda, method)(*arguments) + panda._handle.controlWrite.assert_not_called() + elif not version.endswith('-FAULTTEST'): + with pytest.raises(RuntimeError): + getattr(panda, method)(*arguments) + panda._handle.controlWrite.assert_not_called() + else: + getattr(panda, method)(*arguments) + assert request != 0xf5 + panda._handle.controlWrite.assert_called_once() + assert panda._handle.controlWrite.call_args.args[1] == request + + +def test_body_does_not_claim_fault_injection_capability(): + panda = object.__new__(Panda) + panda._handle = Mock() + panda.get_type = lambda: Panda.HW_TYPE_BODY + panda.get_mcu_type = lambda: McuType.H7 + panda.get_version = lambda: 'DEV-source-DEBUG-FAULTTEST' + with pytest.raises(NotImplementedError): + panda.inject_fault(1) + panda._handle.controlWrite.assert_not_called() + + +@pytest.mark.parametrize('release,injection,expected', ( + (False, '0', 'DEBUG'), + (False, '1', 'DEBUG-FAULTTEST'), + (True, '0', 'RELEASE'), + (True, '1', None), + (False, 'yes', None), +)) +def test_build_requires_explicit_debug_fault_injection(monkeypatch, release, injection, expected): + import ast + from pathlib import Path + from SCons.Script import File + + root = Path(__file__).resolve().parents[2] + source = root / 'SConscript' + tree = ast.parse(source.read_text()) + prefix = [] + for node in tree.body: + if isinstance(node, ast.FunctionDef) and node.name == 'objcopy': + break + prefix.append(node) + if release: + monkeypatch.setenv('RELEASE', '1') + else: + monkeypatch.delenv('RELEASE', raising=False) + monkeypatch.setenv('CERT', str(root / 'board/crypto/certs/debug')) + monkeypatch.setenv('PANDA_FAULT_INJECTION', injection) + namespace = {'File': File} + code = compile(ast.Module(body=prefix, type_ignores=[]), str(source), 'exec') + if expected is None: + with pytest.raises(ValueError): + exec(code, namespace) + else: + exec(code, namespace) + assert namespace['BUILD_TYPE'] == expected + assert ('-DPANDA_FAULT_INJECTION' in namespace['common_flags']) == expected.endswith('-FAULTTEST') diff --git a/tools/ci/run_predeploy.py b/tools/ci/run_predeploy.py index 15f34bfd7b..200c3f13b4 100755 --- a/tools/ci/run_predeploy.py +++ b/tools/ci/run_predeploy.py @@ -37,7 +37,7 @@ def commands(output, cache, download=False): '--cache', str(cache), '--output', str(output / f'recorded-{variant}'), *(['--download'] if download else [])]) for variant in ('debug', 'release')] native = [(name, [str(ROOT / 'openpilot/selfdrive/pandad/tests' / name)]) - for name in ('test_panda_usb', 'test_pandad_canprotocol', 'test_aol_protocol', 'test_aol_wire')] + for name in ('test_panda_usb', 'test_pandad_canprotocol', 'test_aol_protocol', 'test_aol_wire', 'test_panda_faults')] if platform.system() == 'Linux': native.extend((name, [str(ROOT / 'openpilot/selfdrive/pandad/tests' / name)]) for name in ('test_producer_timing', 'test_producer_sink')) diff --git a/tools/ci/tests/test_predeploy.py b/tools/ci/tests/test_predeploy.py index f67cf7ea50..c5a83bd62a 100644 --- a/tools/ci/tests/test_predeploy.py +++ b/tools/ci/tests/test_predeploy.py @@ -92,7 +92,7 @@ class TestPredeploy(unittest.TestCase): plan = commands(Path('/results'), Path('/cache')) self.assertEqual(plan['build'][0][1][1:], ['-m', 'SCons', '-j4']) self.assertEqual([name for name, _ in plan['native']], [ - 'test_panda_usb', 'test_pandad_canprotocol', 'test_aol_protocol', 'test_aol_wire', + 'test_panda_usb', 'test_pandad_canprotocol', 'test_aol_protocol', 'test_aol_wire', 'test_panda_faults', *(['test_producer_timing', 'test_producer_sink'] if platform.system() == 'Linux' else []), 'map-ipc']) self.assertIn('-race', plan['native'][-1][1]) diff --git a/upstream-sync.json b/upstream-sync.json index 15af00765e..0a55dcf768 100644 --- a/upstream-sync.json +++ b/upstream-sync.json @@ -22,8 +22,8 @@ { "path": "panda", "url": "https://github.com/commaai/panda.git", - "commit": "a59195163c32a3a375ea6c86715dc08fd0984c1b", - "tree": "207668f2df34a2ca503de012208c394ad01b2c6b", + "commit": "f38f504ffac4a6d42bb4c20897adcf5b68093151", + "tree": "6d6101f8f3700088062c247a9509f8b5ac8d23c7", "exclude": [] }, {