/* Reflow Oven Controller * * Copyright (C) 2020 Mario Hüttel * * This file is part of the Reflow Oven Controller Project. * * The reflow oven controller is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * The Reflow Oven Control Firmware is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with the reflow oven controller project. * If not, see . */ /** * @addtogroup safety-controller * @{ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** * @brief Macro that checks if a given @ref error_flag is persistent */ #define check_flag_persistent(flag) ((flag)->persistence && (flag)->persistence->persistence) /** * @brief Macro that retrieves the flag weight of a given @ref error_flag * * If no flag weight table is present, the flag is evaluated as SAFETY_FLAG_CONFIG_WEIGHT_PANIC */ #define get_flag_weight(flag) ((flag)->weight ? ((flag)->weight->weight) : SAFETY_FLAG_CONFIG_WEIGHT_PANIC) /** * @brief Safety controller internal structure implementing a safety flag weight. */ struct safety_weight { /** @brief Dummy value. This seeds the CRC */ uint32_t start_dummy; /** @brief The safety flag's weight */ enum config_weight weight; /** @brief The enum value of the flag this weight corresponds to */ enum safety_flag flag; /** @brief the flag, this weight corresponds to */ volatile struct error_flag *flag_ptr; /** @brief Dummy value. This seeds the CRC */ uint32_t end_dummy; }; /** * @brief Safety controller internal struct implementing a flag persistence entry */ struct safety_persistence { /** @brief Dummy value. This seeds the CRC */ uint32_t start_dummy; /** @brief Corresponding flag is persistent and cannot be cleared */ bool persistence; /** @brief Corresponding safety flag's enum value */ enum safety_flag flag; /** @brief Corresponding safety error flag */ volatile struct error_flag *flag_ptr; /** @brief Dummy value. This seeds the CRC */ uint32_t end_dummy; }; /** * @brief Safety controller internal struct implementing an error flag */ struct error_flag { /** @brief Name of the error flag */ const char *name; /** @brief Enum value of this safety flag */ enum safety_flag flag; /** @brief The flag's state. True is errorneous. */ bool error_state; /** @brief Not the flag's state. This always has to be inverted to @ref error_flag::error_state */ bool error_state_inv; /** @brief Persistence entry of this flag */ volatile struct safety_persistence *persistence; /** @brief Weight entry of this flag */ volatile struct safety_weight *weight; /** @brief Key needed to remove this safety flag. If key == 0, no key is set and * the flag can be cleared by all code */ uint32_t key; }; struct timing_mon { const char *name; enum timing_monitor monitor; enum safety_flag associated_flag; uint64_t min_delta; uint64_t max_delta; uint64_t last; uint64_t calculated_delta; bool enabled; }; struct analog_mon { const char *name; enum analog_value_monitor monitor; enum safety_flag associated_flag; float min; float max; float value; bool valid; uint64_t timestamp; }; struct overtemp_config { uint32_t crc_dummy_seed; float overtemp_deg_celsius; float overtemp_equiv_resistance; uint32_t crc; }; struct flash_crcs { uint32_t start_magic; uint32_t crc_section_text; uint32_t crc_section_data; uint32_t crc_section_ccm_data; uint32_t crc_section_vectors; uint32_t end_magic; }; struct crc_monitor_register { const volatile void *reg_addr; uint32_t mask; uint8_t size; }; #define CRC_MON_REGISTER_ENTRY(_addr, _mask, _size) {.reg_addr = &(_addr), .mask = (_mask), .size = (_size)} struct crc_mon { /** * @brief Array of registers to monitor. Terminated by NULL sentinel! */ const struct crc_monitor_register *registers; const enum crc_monitor monitor; const uint32_t pw; const enum safety_flag flag_to_set; uint32_t expected_crc; uint32_t expected_crc_inv; uint32_t last_crc; bool active; }; /** * @brief All safety error flags. */ static volatile struct error_flag IN_SECTION(.ccm.data) flags[] = { ERR_FLAG_ENTRY(ERR_FLAG_MEAS_ADC_OFF), ERR_FLAG_ENTRY(ERR_FLAG_MEAS_ADC_WATCHDOG), ERR_FLAG_ENTRY(ERR_FLAG_MEAS_ADC_UNSTABLE), ERR_FLAG_ENTRY(ERR_FLAG_MEAS_ADC_OVERFLOW), ERR_FLAG_ENTRY(ERR_FLAG_TIMING_MEAS_ADC), ERR_FLAG_ENTRY(ERR_FLAG_TIMING_PID), ERR_FLAG_ENTRY(ERR_FLAG_AMON_UC_TEMP), ERR_FLAG_ENTRY(ERR_FLAG_AMON_VREF), ERR_FLAG_ENTRY(ERR_FLAG_STACK), ERR_FLAG_ENTRY(ERR_FLAG_SAFETY_ADC), ERR_FLAG_ENTRY(ERR_FLAG_SYSTICK), ERR_FLAG_ENTRY(ERR_FLAG_WTCHDG_FIRED), ERR_FLAG_ENTRY(ERR_FLAG_UNCAL), ERR_FLAG_ENTRY(ERR_FLAG_DEBUG), ERR_FLAG_ENTRY(ERR_FLAG_TIMING_MAIN_LOOP), ERR_FLAG_ENTRY(ERR_FLAG_SAFETY_MEM_CORRUPT), ERR_FLAG_ENTRY(ERR_FLAG_SAFETY_TAB_CORRUPT), ERR_FLAG_ENTRY(ERR_FLAG_AMON_SUPPLY_VOLT), ERR_FLAG_ENTRY(ERR_FLAG_OVERTEMP), ERR_FLAG_ENTRY(ERR_FLAG_FLASH_CRC_CODE), ERR_FLAG_ENTRY(ERR_FLAG_FLASH_CRC_DATA), ERR_FLAG_ENTRY(ERR_FLAG_CFG_CRC_MEAS_ADC), ERR_FLAG_ENTRY(ERR_FLAG_CFG_CRC_SAFETY_ADC), }; /** * @brief All timing monitors */ static volatile struct timing_mon IN_SECTION(.ccm.data) timings[] = { TIM_MON_ENTRY(ERR_TIMING_PID, 2, 5000, ERR_FLAG_TIMING_PID), TIM_MON_ENTRY(ERR_TIMING_MEAS_ADC, 0, 50, ERR_FLAG_TIMING_MEAS_ADC), TIM_MON_ENTRY(ERR_TIMING_SAFETY_ADC, 10, SAFETY_CONTROLLER_ADC_DELAY_MS + 1000, ERR_FLAG_SAFETY_ADC), TIM_MON_ENTRY(ERR_TIMING_MAIN_LOOP, 0, 1000, ERR_FLAG_TIMING_MAIN_LOOP), }; /** * @brief All analog value monitors */ static volatile struct analog_mon IN_SECTION(.ccm.data) analog_mons[] = { ANA_MON_ENTRY(ERR_AMON_VREF, SAFETY_ADC_VREF_MVOLT - SAFETY_ADC_VREF_TOL_MVOLT, SAFETY_ADC_VREF_MVOLT + SAFETY_ADC_VREF_TOL_MVOLT, ERR_FLAG_AMON_VREF), ANA_MON_ENTRY(ERR_AMON_UC_TEMP, SAFETY_ADC_TEMP_LOW_LIM, SAFETY_ADC_TEMP_HIGH_LIM, ERR_FLAG_AMON_UC_TEMP), ANA_MON_ENTRY(ERR_AMON_SUPPLY_VOLT, SAFETY_ADC_SUPPLY_MVOLT - SAFETY_ADC_SUPPLY_TOL_MVOLT, SAFETY_ADC_SUPPLY_MVOLT + SAFETY_ADC_SUPPLY_TOL_MVOLT, ERR_FLAG_AMON_SUPPLY_VOLT), }; /** * @brief The default flag weights, that are loaded on boot. */ static const struct safety_weight default_flag_weights[] = { SAFETY_CONFIG_DEFAULT_WEIGHTS }; /** * @brief The default flag persistencies, that are loaded on boot. */ static const struct safety_persistence default_flag_persistencies[] = {SAFETY_CONFIG_DEFAULT_PERSIST}; /** * @brief The working copy of the flag persistence table. It is protected by the @ref flag_persistencies_crc * @note This is stored in CCM RAM */ static volatile struct safety_persistence IN_SECTION(.ccm.bss) flag_persistencies[COUNT_OF(default_flag_persistencies)]; /** * @brief The CRC of the flag weight table @ref flag_persistencies. * * The CRC is calculated using the internal CRC module of the STM32F407 controller. * See the refernece manual for the polynomial. * * @note This is stored in CCM RAM. */ static uint32_t IN_SECTION(.ccm.bss) flag_persistencies_crc; /** * @brief The working copy of the flag weight table. It is protected by the @ref flag_weight_crc. * @note This is stored in CCM RAM */ static volatile struct safety_weight IN_SECTION(.ccm.bss) flag_weights[COUNT_OF(default_flag_weights)]; /** * @brief The CRC of the flag weight table @ref flag_weights. * * The CRC is calculated using the internal CRC module of the STM32F407 controller. * See the refernece manual for the polynomial. * * @note This is stored in CCM RAM. */ static uint32_t IN_SECTION(.ccm.bss) flag_weight_crc; /** * @brief Configuration struct containing the overtemperature flag configuration */ static struct overtemp_config IN_SECTION(.ccm.bss) safety_controller_overtemp_config; static const struct crc_monitor_register meas_adc_crc_regs[] = { CRC_MON_REGISTER_ENTRY(ADC_PT1000_PERIPH->CR1, 0xFFFFFFFF, 4), CRC_MON_REGISTER_ENTRY(ADC_PT1000_PERIPH->CR2, ADC_CR2_ADON | ADC_CR2_CONT | ADC_CR2_ALIGN | ADC_CR2_DMA | ADC_CR2_DDS | ADC_CR2_EOCS | ADC_CR2_EXTEN | ADC_CR2_EXTSEL, 4), CRC_MON_REGISTER_ENTRY(ADC_PT1000_PERIPH->SMPR1, 0xFFFFFFFF, 4), CRC_MON_REGISTER_ENTRY(ADC_PT1000_PERIPH->SMPR2, 0xFFFFFFFF, 4), CRC_MON_REGISTER_ENTRY(ADC_PT1000_PERIPH->HTR, ADC_HTR_HT, 4), CRC_MON_REGISTER_ENTRY(ADC_PT1000_PERIPH->LTR, ADC_LTR_LT, 4), CRC_MON_REGISTER_ENTRY(ADC_PT1000_PERIPH->SQR1, ADC_SQR1_L | ADC_SQR1_SQ16 | ADC_SQR1_SQ15 | ADC_SQR1_SQ14 | ADC_SQR1_SQ13, 4), CRC_MON_REGISTER_ENTRY(ADC_PT1000_PERIPH->SQR2, ADC_SQR2_SQ12 | ADC_SQR2_SQ11 | ADC_SQR2_SQ10 | ADC_SQR2_SQ9 | ADC_SQR2_SQ8 | ADC_SQR2_SQ7, 4), CRC_MON_REGISTER_ENTRY(ADC_PT1000_PERIPH->SQR3, ADC_SQR3_SQ6 | ADC_SQR3_SQ5 | ADC_SQR3_SQ4 | ADC_SQR3_SQ3 | ADC_SQR3_SQ2 | ADC_SQR3_SQ1, 4), {NULL, 0, 0} }; static const struct crc_monitor_register safety_adc_crc_regs[] = { CRC_MON_REGISTER_ENTRY(SAFETY_ADC_ADC_PERIPHERAL->CR1, 0xFFFFFFFF, 4), CRC_MON_REGISTER_ENTRY(SAFETY_ADC_ADC_PERIPHERAL->CR2, ADC_CR2_ADON | ADC_CR2_CONT | ADC_CR2_ALIGN | ADC_CR2_DMA | ADC_CR2_DDS | ADC_CR2_EOCS | ADC_CR2_EXTEN | ADC_CR2_EXTSEL, 4), CRC_MON_REGISTER_ENTRY(SAFETY_ADC_ADC_PERIPHERAL->SMPR1, 0xFFFFFFFF, 4), CRC_MON_REGISTER_ENTRY(SAFETY_ADC_ADC_PERIPHERAL->SMPR2, 0xFFFFFFFF, 4), CRC_MON_REGISTER_ENTRY(SAFETY_ADC_ADC_PERIPHERAL->SQR1, ADC_SQR1_L | ADC_SQR1_SQ16 | ADC_SQR1_SQ15 | ADC_SQR1_SQ14 | ADC_SQR1_SQ13, 4), CRC_MON_REGISTER_ENTRY(SAFETY_ADC_ADC_PERIPHERAL->SQR2, ADC_SQR2_SQ12 | ADC_SQR2_SQ11 | ADC_SQR2_SQ10 | ADC_SQR2_SQ9 | ADC_SQR2_SQ8 | ADC_SQR2_SQ7, 4), CRC_MON_REGISTER_ENTRY(SAFETY_ADC_ADC_PERIPHERAL->SQR3, ADC_SQR3_SQ6 | ADC_SQR3_SQ5 | ADC_SQR3_SQ4 | ADC_SQR3_SQ3 | ADC_SQR3_SQ2 | ADC_SQR3_SQ1, 4), {NULL, 0, 0} }; static struct crc_mon IN_SECTION(.ccm.data) crc_monitors[] = { { .registers = meas_adc_crc_regs, .monitor = ERR_CRC_MON_MEAS_ADC, .pw = SAFETY_CRC_MON_MEAS_ADC_PW, .flag_to_set = ERR_FLAG_CFG_CRC_MEAS_ADC, .expected_crc = 0UL, .expected_crc_inv = ~0UL, .last_crc = 0UL, .active = false, }, { .registers = safety_adc_crc_regs, .monitor = ERR_CRC_MON_SAFETY_ADC, .pw = SAFETY_CRC_MON_SAFETY_ADC_PW, .flag_to_set = ERR_FLAG_CFG_CRC_SAFETY_ADC, .expected_crc = 0UL, .expected_crc_inv = ~0UL, .last_crc = 0UL, .active = false, }, }; /** * @brief Configure the overtemperature flag's settings * @param over_temperature Temperature to set the limit to. */ static void set_overtemp_config(float over_temperature) { int result; float resistance; result = temp_converter_convert_temp_to_resistance(over_temperature, &resistance); /* An error in this function is really bad... */ if (result < -1) panic_mode(); crc_unit_reset(); safety_controller_overtemp_config.crc_dummy_seed = 0xA4F5C7E6UL; safety_controller_overtemp_config.overtemp_deg_celsius = over_temperature; safety_controller_overtemp_config.overtemp_equiv_resistance = resistance; crc_unit_input_array((const uint32_t *)&safety_controller_overtemp_config, wordsize_of(struct overtemp_config) - 1); safety_controller_overtemp_config.crc = crc_unit_get_crc(); } /** * @brief Check the overtemperature config structure's CRC * @return true if check failed. false if CRC check successful */ static bool over_temperature_config_check(void) { if (safety_controller_overtemp_config.crc_dummy_seed != 0xA4F5C7E6UL) return true; crc_unit_reset(); crc_unit_input_array((const uint32_t *)&safety_controller_overtemp_config, wordsize_of(struct overtemp_config) - 1); if (crc_unit_get_crc() != safety_controller_overtemp_config.crc) return true; return false; } /** * @brief Convert a flag enum to the flag number. * * Flag numbers are used by the error memory to store flags. * This function will fail and return 0xFF if multiple flags are ORed and * passed as flag parameter. * * @param flag Flag enum * @return Flag number or 0xFF in case of an error */ static uint8_t flag_enum_to_flag_no(enum safety_flag flag) { uint32_t flag_mask; uint8_t i; if (!is_power_of_two(flag)) return 0xFF; flag_mask = (uint32_t)flag; for (i = 0; i < 32; i++) { if ((flag_mask >> i) & 0x1U) break; } return i; } /** * @brief Convert a safety flag's number to its enum value. * * Flag numbers are used by the error memory to store flags. * * @param no The flag number. * @return Flag enum */ static enum safety_flag flag_no_to_flag_enum(uint8_t no) { if (no >= COUNT_OF(flags)) return ERR_FLAG_NO_FLAG; return (1U << no); } /** * @brief Check the CRC chacksum of the flag weight table * @return 0 if CRC is valid, else -1; */ static int flag_weight_table_crc_check(void) { /* Check the flag weight table */ crc_unit_reset(); crc_unit_input_array((uint32_t *)flag_weights, wordsize_of(flag_weights)); if (crc_unit_get_crc() != flag_weight_crc) return -1; return 0; } /** * @brief Check the CRC chacksum of the flag persistence table * @return 0 if CRC is valid, else -1. */ static int flag_persistence_table_crc_check(void) { crc_unit_reset(); crc_unit_input_array((uint32_t *)flag_persistencies, wordsize_of(flag_persistencies)); if (crc_unit_get_crc() != flag_persistencies_crc) return -1; return 0; } /** * @brief Find the error flag structure for a given safety_flag enum * * Only one flag can be given at a time. Giving multiple flags by ORing them * together, will not math any flag at all. * * @param flag Enum defining the flag. * @return NULL in case nothing matched. Pointer otherwise. */ static volatile struct error_flag *find_error_flag(enum safety_flag flag) { uint32_t i; volatile struct error_flag *ret = NULL; for (i = 0; i < COUNT_OF(flags); i++) { if (flags[i].flag == flag) ret = &flags[i]; } return ret; } static int crc_monitor_calculate_crc(const struct crc_monitor_register *registers, uint32_t *crc_out) { const struct crc_monitor_register *reg; uint32_t i; uint32_t reg_val; if (!registers || !crc_out) return -1000; crc_unit_reset(); for (i = 0; registers[i].reg_addr != NULL; i++) { reg = ®isters[i]; switch (reg->size) { case 1: reg_val = *((volatile uint8_t *)reg->reg_addr); break; case 2: reg_val = *((volatile uint16_t *)reg->reg_addr); break; case 4: /* FALLTHRU */ default: reg_val = *((volatile uint32_t *)reg->reg_addr); break; } reg_val &= reg->mask; crc_unit_input(reg_val); } *crc_out = crc_unit_get_crc(); return 0; } static int safety_controller_check_crc_monitors(void) { uint32_t i; struct crc_mon *mon; uint32_t crc; for (i = 0; i < COUNT_OF(crc_monitors); i++) { mon = &crc_monitors[i]; if (!mon->active) continue; if (crc_monitor_calculate_crc(mon->registers, &crc)) return -1; if (mon->expected_crc != crc || ~mon->expected_crc_inv != crc) safety_controller_report_error(mon->flag_to_set); mon->last_crc = crc; } return 0; } /** * @brief This function copies the safety weigths from flash ro RAM and computes the CRC */ static void init_safety_flag_weight_table_from_default(void) { uint32_t index; volatile struct safety_weight *current_weight; /* Copy the table */ memcpy((void *)flag_weights, default_flag_weights, sizeof(flag_weights)); /* Fill in the flag pointers */ for (index = 0; index < COUNT_OF(flag_weights); index++) { current_weight = &flag_weights[index]; current_weight->flag_ptr = find_error_flag(current_weight->flag); if (current_weight->flag_ptr) current_weight->flag_ptr->weight = current_weight; } crc_unit_reset(); crc_unit_input_array((uint32_t *)flag_weights, wordsize_of(flag_weights)); flag_weight_crc = crc_unit_get_crc(); } /** * @brief Initialize the default persistence settings of all safety flags. * * This function copies the default persistence settings of the safety flags defined in * @ref SAFETY_CONFIG_DEFAULT_PERSIST and computes the protection CRC over the settings. */ static void init_safety_flag_persistencies_from_default(void) { uint32_t index; volatile struct safety_persistence *current_persistence; /* Copy values */ memcpy((void *)flag_persistencies, default_flag_persistencies, sizeof(flag_persistencies)); /* Fill in flag pointers */ for (index = 0; index < COUNT_OF(flag_persistencies); index++) { current_persistence = &flag_persistencies[index]; current_persistence->flag_ptr = find_error_flag(current_persistence->flag); if (current_persistence->flag_ptr) current_persistence->flag_ptr->persistence = current_persistence; } crc_unit_reset(); crc_unit_input_array((uint32_t *)flag_persistencies, wordsize_of(flag_persistencies)); flag_persistencies_crc = crc_unit_get_crc(); } /** * @brief Apply the config overrrides stored in the safety memory. * * The config overrides are read from the safety memory and applied. * The config overrides can override the following things: * * 1) Safety Weights (See @ref config_weight) * 2) Flag Persistence */ static void apply_config_overrides(void) { uint32_t count; uint32_t idx; struct config_override override; int res; enum safety_flag flag_enum; volatile struct error_flag *flag; res = safety_memory_get_config_override_count(&count); if (res) return; for (idx = 0; idx < count; idx++) { res = safety_memory_get_config_override(idx, &override); if (res) continue; switch (override.type) { case SAFETY_MEMORY_CONFIG_OVERRIDE_WEIGHT: flag_enum = flag_no_to_flag_enum(override.entry.weight_override.flag); flag = find_error_flag(flag_enum); if (flag && flag->weight) flag->weight->weight = override.entry.weight_override.weight; break; case SAFETY_MEMORY_CONFIG_OVERRIDE_PERSISTENCE: flag_enum = flag_no_to_flag_enum(override.entry.persistence_override.flag); flag = find_error_flag(flag_enum); if (flag && flag->persistence) flag->persistence->persistence = override.entry.persistence_override.persistence; break; default: continue; } } /* Patch new CRCs */ crc_unit_reset(); crc_unit_input_array((uint32_t *)flag_persistencies, wordsize_of(flag_persistencies)); flag_persistencies_crc = crc_unit_get_crc(); crc_unit_reset(); crc_unit_input_array((uint32_t *)flag_weights, wordsize_of(flag_weights)); flag_weight_crc = crc_unit_get_crc(); } /** * @brief Get the error state of a flag. * * This function takes inbto account that the error_flag::error_state and * error_flag::error_state_inv fileds must never be the same value. In case they are, * the flag is treated as errorneous. * @param flag Flag to check * @return The error state */ static bool error_flag_get_status(const volatile struct error_flag *flag) { if (!flag) return true; if (flag->error_state == flag->error_state_inv) return true; else return flag->error_state; } /** * @brief Find a analog value monitor structure by its enum number * @param mon Enum representing the analog monitor * @return NULL incase nothing is found, else pointer to structure. */ static volatile struct analog_mon *find_analog_mon(enum analog_value_monitor mon) { uint32_t i; volatile struct analog_mon *ret = NULL; for (i = 0; i < COUNT_OF(analog_mons); i++) { if (analog_mons[i].monitor == mon) ret = &analog_mons[i]; } return ret; } /** * @brief Find a timing monitor structure by its enum number * @param mon Enum representing the timing monitor * @return NULL incase nothing is found, else pointer to structure. */ static volatile struct timing_mon *find_timing_mon(enum timing_monitor mon) { uint32_t i; volatile struct timing_mon *ret = NULL; for (i = 0; i < COUNT_OF(timings); i++) { if (timings[i].monitor == mon) ret = &timings[i]; } return ret; } /** * @brief Check the active timing monitors and set the appropriate flags in case of an error. */ static void safety_controller_process_active_timing_mons(void) { uint32_t i; volatile struct timing_mon *current_mon; uint64_t last; for (i = 0; i < COUNT_OF(timings); i++) { current_mon = &timings[i]; if (current_mon->enabled) { __disable_irq(); last = current_mon->last; __enable_irq(); if (systick_ticks_have_passed(last, current_mon->max_delta)) safety_controller_report_error(current_mon->associated_flag); } } } /** * @brief safety_controller_process_monitor_checks * Process the analog and timing monitors and set the relevant flags in case of a monitor outside its limits. * Furthermore, the PT1000 resistance is checked for overtemperature * * The checking of the analog monitors will only be armed after a startup delay of 1000 ms to * allow the values to stabilize. */ static void safety_controller_process_monitor_checks(void) { static bool startup_completed = false; struct analog_monitor_info amon_info; uint32_t idx; uint32_t analog_mon_count; float pt1000_val = 1000000.0f; if (!startup_completed && systick_get_global_tick() >= 1000) startup_completed = true; if (startup_completed) { analog_mon_count = safety_controller_get_analog_monitor_count(); for (idx = 0; idx < analog_mon_count; idx++) if (safety_controller_get_analog_mon_by_index(idx, &amon_info)) { panic_mode(); if (amon_info.status != ANALOG_MONITOR_OK) safety_controller_report_error(amon_info.associated_flag); } } adc_pt1000_get_current_resistance(&pt1000_val); if (pt1000_val > safety_controller_overtemp_config.overtemp_equiv_resistance) safety_controller_report_error(ERR_FLAG_OVERTEMP); (void)safety_controller_check_crc_monitors(); safety_controller_process_active_timing_mons(); } /** * @brief Internal function for setting an error flag * * Multiple flags can be ored together to set them in one go. * The provided key will be set on all of the flags in order to prevent them from being reset by * unauthorized code. If nop key shall be used, set key to zero. * * @param flag Enum of the flags to set. This can be an ORed value of multiple error flags. * @param key The kex to set on the flag. * @param prevent_error_mem_enty Prevent the flag from being stored in the error memory. * @return 0 if successful. */ static int report_error(enum safety_flag flag, uint32_t key, bool prevent_error_mem_enty) { uint32_t i; int ret = -1; bool old_state; int res; struct error_memory_entry err_mem_entry; for (i = 0; i < COUNT_OF(flags); i++) { if (flags[i].flag & flag) { old_state = flags[i].error_state; flags[i].error_state = true; flags[i].error_state_inv = !flags[i].error_state; flags[i].key = key; if ((check_flag_persistent(&flags[i]) && !old_state && !prevent_error_mem_enty) || get_flag_weight(&flags[i]) == SAFETY_FLAG_CONFIG_WEIGHT_PANIC) { err_mem_entry.counter = 1; err_mem_entry.flag_num = flag_enum_to_flag_no(flags[i].flag); err_mem_entry.type = SAFETY_MEMORY_ERR_ENTRY_FLAG; res = safety_memory_insert_error_entry(&err_mem_entry); if (res) ret = -12; } else { ret = 0; } flag &= ~flags[i].flag; if (!flag) break; } } return ret; } int safety_controller_report_error(enum safety_flag flag) { return safety_controller_report_error_with_key(flag, 0x0UL); } int safety_controller_report_error_with_key(enum safety_flag flag, uint32_t key) { return report_error(flag, key, false); } void safety_controller_report_timing(enum timing_monitor monitor) { volatile struct timing_mon *tim; uint64_t timestamp; timestamp = systick_get_global_tick(); tim = find_timing_mon(monitor); if (tim) { if (tim->enabled) { if (!systick_ticks_have_passed(tim->last, tim->min_delta) && tim->min_delta > 0U) safety_controller_report_error(tim->associated_flag); } tim->calculated_delta = timestamp - tim->last; tim->last = timestamp; tim->enabled = true; } } void safety_controller_report_analog_value(enum analog_value_monitor monitor, float value) { volatile struct analog_mon *ana; /* Return if not a power of two */ if (!is_power_of_two(monitor)) return; ana = find_analog_mon(monitor); if (ana) { ana->valid = true; ana->value = value; ana->timestamp = systick_get_global_tick(); } } /** * @brief Return the flags, which are set in the error memory * @param flags Flags read from error memory * @return 0 if ok, != 0 if error */ static int get_safety_flags_from_error_mem(enum safety_flag *flags) { uint32_t count; uint32_t idx; int res; enum safety_flag return_flags = 0; struct error_memory_entry entry; if (!flags) return -1001; res = safety_memory_get_error_entry_count(&count); if (res) return -1; for (idx = 0; idx < count; idx++) { res = safety_memory_get_error_entry(idx, &entry); if (entry.type == SAFETY_MEMORY_ERR_ENTRY_FLAG) return_flags |= flag_no_to_flag_enum(entry.flag_num); } *flags = return_flags; return 0; } /** * @brief Initialize the GPIOs for the external hardware watchdog. * * The external harware watchdog has to be periodically reset or it will reset hte controller. * Because debugging is not possible, when the watchdog is active, it is only activated, if the application is * compiled in release mode. Any interruption of the main programm will then trigger the internal and/or * the external watchdog. * * @note When enabled, execute the @ref external_watchdog_toggle function to reset the external watchdog. */ static void safety_controller_init_external_watchdog(void) { rcc_manager_enable_clock(&RCC->AHB1ENR, BITMASK_TO_BITNO(SAFETY_EXT_WATCHDOG_RCC_MASK)); SAFETY_EXT_WATCHDOG_PORT->MODER &= MODER_DELETE(SAFETY_EXT_WATCHDOG_PIN); #ifndef DEBUGBUILD SAFETY_EXT_WATCHDOG_PORT->MODER |= OUTPUT(SAFETY_EXT_WATCHDOG_PIN); SAFETY_EXT_WATCHDOG_PORT->ODR |= (1< 1000) safety_controller_report_error(ERR_FLAG_SYSTICK); } else { same_systick_cnt = 0UL; } last_systick = systick; } /** * @brief Handle weightet flags. * * This functions loops over all error flags and checks the weights. If a flag * is set, the appropriate action defined by the flag weight is executed. * @note If no flag weigth is present for a given error flag, it is treated as the most critical category * (@ref SAFETY_FLAG_CONFIG_WEIGHT_PANIC) * * @returns Worst config weight set */ static enum config_weight safety_controller_handle_weighted_flags(void) { uint32_t flag_index; volatile struct error_flag *current_flag; enum config_weight flag_weigth; enum config_weight worst = SAFETY_FLAG_CONFIG_WEIGHT_NONE; for (flag_index = 0u; flag_index < COUNT_OF(flags); flag_index++) { current_flag = &flags[flag_index]; /* Continue if this flag is not set */ if (!error_flag_get_status(current_flag)) continue; flag_weigth = get_flag_weight(current_flag); /* Override the worst flag weigt set, if it is worse than the previous ones */ if (flag_weigth > worst) worst = flag_weigth; switch (flag_weigth) { case SAFETY_FLAG_CONFIG_WEIGHT_NONE: break; case SAFETY_FLAG_CONFIG_WEIGHT_PID: oven_pid_abort(); break; case SAFETY_FLAG_CONFIG_WEIGHT_PANIC: /* EXPECTED FALLTHRU */ default: oven_pid_abort(); panic_mode(); break; } } return worst; } #ifndef DEBUGBUILD static void external_watchdog_toggle(void) { SAFETY_EXT_WATCHDOG_PORT->ODR ^= (1< 30) { external_watchdog_toggle(); watchdog_counter = 0UL; } } #endif return worst_weight_set; } int safety_controller_enable_timing_mon(enum timing_monitor monitor, bool enable) { volatile struct timing_mon *tim; if (enable) { safety_controller_report_timing(monitor); } else { tim = find_timing_mon(monitor); if (!tim) return -1; tim->enabled = false; } return 0; } enum analog_monitor_status safety_controller_get_analog_mon_value(enum analog_value_monitor monitor, float *value) { volatile struct analog_mon *mon; int ret = ANALOG_MONITOR_ERROR; if (!is_power_of_two(monitor)) goto go_out; if (!value) goto go_out; mon = find_analog_mon(monitor); if (mon) { if (!mon->valid) { ret = ANALOG_MONITOR_INACTIVE; goto go_out; } *value = mon->value; if (mon->value < mon->min) ret = ANALOG_MONITOR_UNDER; else if (mon->value > mon->max) ret = ANALOG_MONITOR_OVER; else ret = ANALOG_MONITOR_OK; } go_out: return ret; } int safety_controller_get_flag(enum safety_flag flag, bool *status, bool try_ack) { volatile struct error_flag *found_flag; int ret = -1; if (!status) return -1002; if (!is_power_of_two(flag)) return -1001; found_flag = find_error_flag(flag); if (found_flag) { *status = error_flag_get_status(found_flag); if (try_ack && !check_flag_persistent(found_flag)) { /* Flag is generally non persistent * If key is set, this function cannot remove the flag */ if (found_flag->key == 0UL) { found_flag->error_state = false; found_flag->error_state_inv = !found_flag->error_state; } } } return ret; } int safety_controller_ack_flag(enum safety_flag flag) { return safety_controller_ack_flag_with_key(flag, 0UL); } int safety_controller_ack_flag_with_key(enum safety_flag flag, uint32_t key) { int ret = -1; volatile struct error_flag *found_flag; if (!is_power_of_two(flag)) return -1001; found_flag = find_error_flag(flag); if (found_flag) { if (!check_flag_persistent(found_flag) && (found_flag->key == key || !found_flag->key)) { found_flag->error_state = false; found_flag->error_state_inv = true; ret = 0; } else { ret = -2; } } return ret; } bool safety_controller_get_flags_by_mask(enum safety_flag mask) { uint32_t i; bool ret = false; for (i = 0; i < COUNT_OF(flags); i++) { if ((flags[i].flag & mask) && error_flag_get_status(&flags[i])) { ret = true; break; } } return ret; } uint32_t safety_controller_get_flag_count(void) { return COUNT_OF(flags); } uint32_t safety_controller_get_analog_monitor_count(void) { return COUNT_OF(analog_mons); } uint32_t safety_controller_get_timing_monitor_count(void) { return COUNT_OF(timings); } int safety_controller_get_analog_mon_name_by_index(uint32_t index, char *buffer, size_t buffsize) { if (index >= COUNT_OF(analog_mons)) return -1; if (buffsize == 0 || !buffer) return -1000; strncpy(buffer, analog_mons[index].name, buffsize); buffer[buffsize - 1] = 0; return 0; } int safety_controller_get_flag_name_by_index(uint32_t index, char *buffer, size_t buffsize) { if (index >= COUNT_OF(flags)) return -1; if (buffsize == 0 || !buffer) return -1000; strncpy(buffer, flags[index].name, buffsize); buffer[buffsize - 1] = 0; return 0; } int safety_controller_get_timing_mon_name_by_index(uint32_t index, char *buffer, size_t buffsize) { if (index >= COUNT_OF(timings)) return -1; if (buffsize == 0 || !buffer) return -1000; strncpy(buffer, timings[index].name, buffsize); buffer[buffsize - 1] = 0; return 0; } int safety_controller_get_flag_by_index(uint32_t index, bool *status, enum safety_flag *flag_enum) { int ret = -1; if (!status && !flag_enum) return -1000; if (index < COUNT_OF(flags)) { if (status) *status = error_flag_get_status(&flags[index]); if (flag_enum) *flag_enum = flags[index].flag; ret = 0; } return ret; } int safety_controller_get_analog_mon_by_index(uint32_t index, struct analog_monitor_info *info) { volatile struct analog_mon *mon; if (!info) return -1002; if (index >= COUNT_OF(analog_mons)) { info->status = ANALOG_MONITOR_ERROR; return -1001; } mon = &analog_mons[index]; info->max = mon->max; info->min = mon->min; info->value = mon->value; info->timestamp = mon->timestamp; info->associated_flag = mon->associated_flag; if (!mon->valid) { info->status = ANALOG_MONITOR_INACTIVE; } else { if (mon->value > mon->max) info->status = ANALOG_MONITOR_OVER; else if (mon->value < mon->min) info->status = ANALOG_MONITOR_UNDER; else info->status = ANALOG_MONITOR_OK; } return 0; } int safety_controller_get_timing_mon_by_index(uint32_t index, struct timing_monitor_info *info) { volatile struct timing_mon *mon; if (!info) return -1002; if (index >= COUNT_OF(timings)) return -1001; mon = &timings[index]; info->max = mon->max_delta; info->min = mon->min_delta; info->enabled = mon->enabled; info->last_run = mon->last; info->delta = mon->calculated_delta; return 0; } int safety_controller_set_overtemp_limit(float over_temperature) { set_overtemp_config(over_temperature); return 0; } float safety_controller_get_overtemp_limit(void) { return safety_controller_overtemp_config.overtemp_deg_celsius; } extern const uint32_t __ld_vectors_start; extern const uint32_t __ld_vectors_end; extern const uint32_t __ld_text_start; extern const uint32_t __ld_text_end; extern const uint32_t __ld_sdata_ccm; extern const uint32_t __ld_edata_ccm; extern const uint32_t __ld_load_ccm_data; extern const uint32_t __ld_sdata; extern const uint32_t __ld_edata; extern const uint32_t __ld_load_data; int safety_controller_trigger_flash_crc_check(void) { /* This structs needs to be volatile!! * This prevents the compiler form optimizing out the reads to the crcs which will be patched in later by * a separate python script! */ static volatile const struct flash_crcs IN_SECTION(.flashcrc) crcs_in_flash = { .start_magic = 0xA8BE53F9UL, .crc_section_ccm_data = 0UL, .crc_section_text = 0UL, .crc_section_data = 0UL, .crc_section_vectors = 0UL, .end_magic = 0xFFA582FFUL, }; int ret = -1; uint32_t len; uint32_t crc; /* Perform CRC check over vector table */ len = (uint32_t)((void *)&__ld_vectors_end - (void *)&__ld_vectors_start); if (len % 4) { safety_controller_report_error(ERR_FLAG_FLASH_CRC_CODE); } else { len /= 4; crc_unit_reset(); crc_unit_input_array(&__ld_vectors_start, len); crc = crc_unit_get_crc(); if (crc != crcs_in_flash.crc_section_vectors) safety_controller_report_error(ERR_FLAG_FLASH_CRC_CODE); } /* Perform CRC check over text section */ len = (uint32_t)((void *)&__ld_text_end - (void *)&__ld_text_start); if (len % 4) { safety_controller_report_error(ERR_FLAG_FLASH_CRC_CODE); } else { len /= 4; crc_unit_reset(); crc_unit_input_array(&__ld_text_start, len); crc = crc_unit_get_crc(); if (crc != crcs_in_flash.crc_section_text) safety_controller_report_error(ERR_FLAG_FLASH_CRC_CODE); } /* Perform CRC check over data section */ len = (uint32_t)((void *)&__ld_edata - (void *)&__ld_sdata); if (len % 4) { safety_controller_report_error(ERR_FLAG_FLASH_CRC_DATA); } else { len /= 4; crc_unit_reset(); crc_unit_input_array(&__ld_load_data, len); crc = crc_unit_get_crc(); if (crc != crcs_in_flash.crc_section_data) safety_controller_report_error(ERR_FLAG_FLASH_CRC_DATA); } /* Perform CRC check over ccm data section */ len = (uint32_t)((void *)&__ld_edata_ccm - (void *)&__ld_sdata_ccm); if (len % 4) { safety_controller_report_error(ERR_FLAG_FLASH_CRC_DATA); } else { len /= 4; crc_unit_reset(); crc_unit_input_array(&__ld_load_ccm_data, len); crc = crc_unit_get_crc(); if (crc != crcs_in_flash.crc_section_ccm_data) safety_controller_report_error(ERR_FLAG_FLASH_CRC_DATA); } ret = 0; return ret; } int safety_controller_set_crc_monitor(enum crc_monitor mon, uint32_t password) { uint32_t i; struct crc_mon *monitor; uint32_t crc; for (i = 0; i < COUNT_OF(crc_monitors); i++) { monitor = &crc_monitors[i]; if (monitor->monitor != mon) continue; monitor->active = true; if (password != monitor->pw) return -1002; crc = 0; (void)crc_monitor_calculate_crc(monitor->registers, &crc); monitor->expected_crc = crc; monitor->expected_crc_inv = ~crc; monitor->last_crc = crc; return 0; } return -1001; } /** @} */