/* * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * This module contains the core dump logic which writes the core dump to SPI flash. It operates * under a very limited set of constraints: * 1.) It can NOT use most FreeRTOS functions * 2.) It can NOT use the regular flash driver (because that uses FreeRTOS mutexes) * * There is a separate module, core_dump_protocol.c which implements the session endpoint logic for * fetching the core dump over bluetooth. That module is free to use FreeRTOS, regular flash * driver, etc. */ #include "kernel/core_dump.h" #include "kernel/core_dump_private.h" #include "console/dbgserial.h" #include "kernel/logging_private.h" #include "kernel/pulse_logging.h" #include "drivers/flash.h" #include "drivers/mpu.h" #include "drivers/spi.h" #include "drivers/watchdog.h" #include "drivers/rtc.h" #include "flash_region/flash_region.h" #include "kernel/pbl_malloc.h" #include "mfg/mfg_serials.h" #include "pebbleos/chip_id.h" #include "services/common/comm_session/session.h" #include "system/bootbits.h" #include "system/passert.h" #include "system/reset.h" #include "system/logging.h" #include "system/version.h" #include "util/attributes.h" #include "util/build_id.h" #include "util/math.h" #include "util/net.h" #include "util/size.h" #include "util/string.h" #define STM32F2_COMPATIBLE #define STM32F4_COMPATIBLE #define STM32F7_COMPATIBLE #include #include "FreeRTOS.h" /* FreeRTOS Kernel Prototypes/Constants. */ #include "task.h" /* FreeRTOS Task Prototypes/Constants. */ #include #include #include //! Evaluates to 1 iff execution will use the process stack when returning from //! the exception. #define RETURNS_TO_PSP(exc_return) ((exc_return & 0x4) == 0x4) //! This symbol and its contents are provided by the linker script, see the //! .note.gnu.build-id section in src/fw/stm32f2xx_flash_fw.ld extern const ElfExternalNote TINTIN_BUILD_ID; extern const uint32_t __CCM_RAM_size__[]; extern const uint32_t __DTCM_RAM_size__[]; void cd_flash_init(void); uint32_t cd_flash_write_bytes(const void* buffer_ptr, uint32_t start_addr, uint32_t buffer_size); void cd_flash_erase_region(uint32_t start_addr, uint32_t total_bytes); void cd_flash_read_bytes(void* buffer_ptr, uint32_t start_addr, uint32_t buffer_size); // ---------------------------------------------------------------------------------------- // Private globals static uint32_t s_flash_addr; // next address in flash to write to // Saved registers before we trigger our interrupt: [r0-r12, sp, lr, pc, xpsr] static ALIGN(4) CoreDumpSavedRegisters s_saved_registers; static uint32_t s_time_stamp; static bool s_core_dump_initiated = false; static bool s_core_dump_is_forced = false; static bool s_test_force_bus_fault = false; // Used for unit testing static bool s_test_force_inf_loop = false; // Used for unit testing static bool s_test_force_assert = false; // Used for unit testing // List of memory regions to include in the core dump typedef struct { void* start; uint32_t length; bool word_reads_only; // Some peripherals can only be read 32 bits at a // time, or you BusFault (maybe). Set this to true // for memory regions where reads smaller than 32 // bits will fail. The start pointer must also be // word-aligned. } MemoryRegion; // Memory regions to dump static const MemoryRegion MEMORY_REGIONS_DUMP[] = { #if MICRO_FAMILY_STM32F2 { .start = (void *)SRAM_BASE, .length = COREDUMP_RAM_SIZE }, #else { .start = (void *)SRAM1_BASE, .length = COREDUMP_RAM_SIZE }, #endif #if PLATFORM_SNOWY || PLATFORM_SPALDING { .start = (void *)CCMDATARAM_BASE, .length = (uint32_t)__CCM_RAM_size__ }, #endif #if MICRO_FAMILY_STM32F7 { .start = (void *)RAMDTCM_BASE, .length = (uint32_t)__DTCM_RAM_size__ }, #endif { .start = (void *)RCC, .length = sizeof(*RCC) }, { .start = (void *)&NVIC->ISER, .length = sizeof(NVIC->ISER) }, // Enabled interrupts { .start = (void *)&NVIC->ISPR, .length = sizeof(NVIC->ISPR) }, // Pending interrupts { .start = (void *)&NVIC->IABR, .length = sizeof(NVIC->IABR) }, // Active interrupts { .start = (void *)&NVIC->IP, .length = sizeof(NVIC->IP) }, // Interrupt priorities { .start = (void *)RTC, .length = sizeof(*RTC) }, { .start = (void*)DMA1_BASE, .length = 0xD0, .word_reads_only = true }, { .start = (void*)DMA2_BASE, .length = 0xD0, .word_reads_only = true }, }; static struct { RCC_TypeDef rcc; SPI_TypeDef spi1; } s_stash_data; // ------------------------------------------------------------------------------------------------- // Flash driver dual-API. static bool s_use_cd_flash_driver = true; static uint32_t prv_flash_write_bytes(const void* buffer_ptr, uint32_t start_addr, uint32_t buffer_size) { if (s_use_cd_flash_driver) { return cd_flash_write_bytes(buffer_ptr, start_addr, buffer_size); } else { flash_write_bytes(buffer_ptr, start_addr, buffer_size); return buffer_size; } } static void prv_flash_erase_region(uint32_t start_addr, uint32_t total_bytes) { if (s_use_cd_flash_driver) { cd_flash_erase_region(start_addr, total_bytes); } else { uint32_t end = start_addr + total_bytes; flash_region_erase_optimal_range_no_watchdog(start_addr, start_addr, end, end); } } static void prv_flash_read_bytes(void* buffer_ptr, uint32_t start_addr, uint32_t buffer_size) { if (s_use_cd_flash_driver) { cd_flash_read_bytes(buffer_ptr, start_addr, buffer_size); } else { flash_read_bytes(buffer_ptr, start_addr, buffer_size); } } // ------------------------------------------------------------------------------------------------- // NOTE: We are explicitly avoiding use of vsniprintf and cohorts to reduce our stack // requirements static void prv_debug_str(const char* msg) { kernel_pbl_log_from_fault_handler(__FILE_NAME__, 0, msg); } // ------------------------------------------------------------------------------------------------- // NOTE: We are explicitly avoiding use of vsniprintf and cohorts to reduce our stack // requirements static void prv_debug_str_str(const char* msg, const char* s) { #if PULSE_EVERYWHERE void *ctx = pulse_logging_log_sync_begin(LOG_LEVEL_ALWAYS, __FILE_NAME__, 0); pulse_logging_log_sync_append(ctx, msg); pulse_logging_log_sync_append(ctx, s); pulse_logging_log_sync_send(ctx); #else int max_length = 256; while (*msg && max_length--) { dbgserial_putchar(*msg); ++msg; } dbgserial_putstr(s); #endif } // ------------------------------------------------------------------------------------------------- // NOTE: We are explicitly avoiding use of vsniprintf and cohorts to reduce our stack // requirements static void prv_debug_str_int(const char* msg, uint32_t i) { char buffer[12]; itoa(i, buffer, sizeof(buffer)); #if PULSE_EVERYWHERE void *ctx = pulse_logging_log_sync_begin(LOG_LEVEL_ALWAYS, __FILE_NAME__, 0); pulse_logging_log_sync_append(ctx, msg); pulse_logging_log_sync_append(ctx, buffer); pulse_logging_log_sync_send(ctx); #else int max_length = 256; while (*msg && max_length--) { dbgserial_putchar(*msg); ++msg; } dbgserial_putstr(buffer); #endif } static NORETURN prv_reset(void) { dbgserial_flush(); system_hard_reset(); } // ----------------------------------------------------------------------------------------- void coredump_assert(int line) { prv_debug_str_int("CD: assert - line ", line); boot_bit_set(BOOT_BIT_SOFTWARE_FAILURE_OCCURRED); prv_reset(); } // ------------------------------------------------------------------------------------------------- // Stash the flash status registers and peripheral clock state before the flash // driver messes with them. static void prv_stash_regions(void) { memcpy(&s_stash_data.rcc, RCC, sizeof(RCC_TypeDef)); memcpy(&s_stash_data.spi1, SPI1, sizeof(SPI_TypeDef)); } // ----------------------------------------------------------------------------------------------- // Return the start address of the flash region containing the core dump image. We write the core image to // different regions in flash to avoid premature burnout of any particular region. // @param[in] new If true, then return a pointer to a region where a new image can be stored. // If false, then return the region containing the most recent stored image or // CORE_DUMP_FLASH_INVALID_ADDR if no image has been written. // @return flash base address to use static uint32_t prv_flash_start_address(bool new) { CoreDumpFlashHeader flash_hdr; CoreDumpFlashRegionHeader region_hdr; uint32_t base_address; unsigned int i; // ---------------------------------------------------------------------------------- // First, see if the flash header has been put in place prv_flash_read_bytes(&flash_hdr, CORE_DUMP_FLASH_START, sizeof(flash_hdr)); if (flash_hdr.magic != CORE_DUMP_FLASH_HDR_MAGIC) { prv_flash_erase_region(CORE_DUMP_FLASH_START, SUBSECTOR_SIZE_BYTES); flash_hdr = (CoreDumpFlashHeader) { .magic = CORE_DUMP_FLASH_HDR_MAGIC, .unformatted = CORE_DUMP_ALL_UNFORMATTED, }; prv_flash_write_bytes(&flash_hdr, CORE_DUMP_FLASH_START, sizeof(flash_hdr)); } // If asking for an existing region and no regions have been formatted yet, return not found if (!new && flash_hdr.unformatted == CORE_DUMP_ALL_UNFORMATTED) { return CORE_DUMP_FLASH_INVALID_ADDR; } // ---------------------------------------------------------------------------------- // Find which region was most recently used (highest last_used value). uint32_t max_last_used = 0; int last_used_idx = -1; for (i=0; i max_last_used) { max_last_used = region_hdr.last_used; last_used_idx = i; } } // If simply trying to find most recently used image, return that now. if (!new) { if (max_last_used > 0) { CD_ASSERTN(last_used_idx >= 0); return core_dump_get_slot_address(last_used_idx); } else { return CORE_DUMP_FLASH_INVALID_ADDR; } } // ---------------------------------------------------------------------------------- // We need to write a new image. Find which region to put it in. // If no regions yet, pick one at random unsigned int start_idx; if (max_last_used == 0) { start_idx = s_time_stamp % CORE_DUMP_MAX_IMAGES; } else { // Else, put it into the next region start_idx = (last_used_idx + 1) % CORE_DUMP_MAX_IMAGES; } // Erase the new region and write out the region header base_address = core_dump_get_slot_address(start_idx); CD_ASSERTN(base_address + CORE_DUMP_MAX_SIZE <= CORE_DUMP_FLASH_END); prv_flash_erase_region(base_address, CORE_DUMP_MAX_SIZE); region_hdr = (CoreDumpFlashRegionHeader) { .magic = CORE_DUMP_FLASH_HDR_MAGIC, .last_used = max_last_used + 1, .unread = true, }; prv_flash_write_bytes(®ion_hdr, base_address, sizeof(region_hdr)); // Clear the unformatted bit in the flash region header flash_hdr.unformatted &= ~(1 << start_idx); prv_flash_write_bytes(&flash_hdr, CORE_DUMP_FLASH_START, sizeof(flash_hdr)); return base_address; } // ------------------------------------------------------------------------------------------------- // This callback gets called by FreeRTOS for each task during the call to vTaskListWalk. static void prvTaskInfoCallback( const xPORT_TASK_INFO * const task_info, void * data) { CoreDumpChunkHeader chunk_hdr; CoreDumpThreadInfo packed_info; void *current_task_id = (void *)xTaskGetCurrentTaskHandle(); prv_debug_str_str("CD: Th info ", task_info->pcName); // Unit testing various types of fault? if (s_test_force_bus_fault) { typedef void (*KaboomCallback)(void); KaboomCallback kaboom = 0; kaboom(); } if (s_test_force_inf_loop) { while (true) ; } if (s_test_force_assert) { PBL_ASSERTN(false); } // Create the packed chunk header strncpy ((char *)packed_info.name, task_info->pcName, CORE_DUMP_THREAD_NAME_SIZE); packed_info.id = (uint32_t)task_info->taskHandle; packed_info.running = (current_task_id == task_info->taskHandle); for (int i = 0; i < portCANONICAL_REG_COUNT; i++) { // registers [r0-r12, sp, lr, pc, sr] packed_info.registers[i] = task_info->registers[i]; } // If this is the current task, adjust the registers based on whether or not we were handling // an exception at the time core_dump_reset() was called. if (packed_info.running) { if (!RETURNS_TO_PSP(s_saved_registers.core_reg[portCANONICAL_REG_INDEX_LR])) { // The core dump handler got invoked from another exception, therefore the // running task was interrupted by an exception. // Get R0-R3, R12, R14, PC, xpsr for the task off the process stack used // by the task. // The information for this task is going to be incorrect: the values of // R4-R11 will be completely bogus. The only way to recover them is to // properly unwind the full exception stack in a debugger with unwind // information available. Unfortunately mainline GDB is unable to unwind // across the MSP/PSP split stack so this incomplete hack is required to // get useable information. for (int i = 0; i < portCANONICAL_REG_COUNT; i++) { // Clear out all of the bogus values in the info packed_info.registers[i] = 0xa5a5a5a5; } uint32_t *sp = (uint32_t *)s_saved_registers.extra_reg.psp; packed_info.registers[portCANONICAL_REG_INDEX_R0] = sp[0]; packed_info.registers[portCANONICAL_REG_INDEX_R1] = sp[1]; packed_info.registers[portCANONICAL_REG_INDEX_R2] = sp[2]; packed_info.registers[portCANONICAL_REG_INDEX_R3] = sp[3]; packed_info.registers[portCANONICAL_REG_INDEX_R12] = sp[4]; packed_info.registers[portCANONICAL_REG_INDEX_LR] = sp[5]; packed_info.registers[portCANONICAL_REG_INDEX_PC] = sp[6]; packed_info.registers[portCANONICAL_REG_INDEX_XPSR] = sp[7]; // Pop the exception stack frame, taking stack alignment into account. // The 10th bit of the pushed xPSR indicates whether an alignment word was // inserted into the stack frame during exception entry in order to make // sp 8-byte aligned. // Note that this is going to be wrong if the floating-point registers // were stacked. The only way to know for sure whether the FP regs were // pushed during exception entry requires unwinding the ISR stack to // determine the EXC_RETURN value of the bottom-most ISR. if (sp[7] & 0x200) { packed_info.registers[portCANONICAL_REG_INDEX_SP] = (uint32_t)(&sp[9]); } else { packed_info.registers[portCANONICAL_REG_INDEX_SP] = (uint32_t)(&sp[8]); } } else { // If current task called core_dump_reset directly, then jam in the // registers we saved at the beginning. for (int i = 0; i < portCANONICAL_REG_COUNT; i++) { // registers [r0-r12, msp, lr, pc, psr] packed_info.registers[i] = s_saved_registers.core_reg[i]; } // Set sp to the saved psp so that GDB can unwind the task's stack. packed_info.registers[portCANONICAL_REG_INDEX_SP] = s_saved_registers.extra_reg.psp; } } // Write out this thread info chunk_hdr.key = CORE_DUMP_CHUNK_KEY_THREAD; chunk_hdr.size = sizeof(packed_info); s_flash_addr += prv_flash_write_bytes(&chunk_hdr, s_flash_addr, sizeof(chunk_hdr)); s_flash_addr += prv_flash_write_bytes(&packed_info, s_flash_addr, chunk_hdr.size); } static void prv_write_memory_regions(const MemoryRegion *regions, unsigned int count, uint32_t flash_base) { CoreDumpChunkHeader chunk_hdr; chunk_hdr.key = CORE_DUMP_CHUNK_KEY_MEMORY; for (unsigned int i = 0; i < count; i++) { chunk_hdr.size = regions[i].length + sizeof(CoreDumpMemoryHeader); CD_ASSERTN(s_flash_addr + chunk_hdr.size - flash_base < CORE_DUMP_MAX_SIZE); s_flash_addr += prv_flash_write_bytes(&chunk_hdr, s_flash_addr, sizeof(chunk_hdr)); CoreDumpMemoryHeader mem_hdr; mem_hdr.start = (uint32_t)regions[i].start; s_flash_addr += prv_flash_write_bytes(&mem_hdr, s_flash_addr, sizeof(mem_hdr)); if (regions[i].word_reads_only) { // Copy the memory into a temporary buffer before writing it to flash so // that we can be sure that the memory is only being accessed by word. uint32_t temp; for (uint32_t offset = 0; offset < regions[i].length; offset += sizeof(temp)) { temp = *(volatile uint32_t*)((char *)regions[i].start + offset); s_flash_addr += prv_flash_write_bytes(&temp, s_flash_addr, sizeof(temp)); watchdog_feed(); } } else { uint32_t bytes_remaining = regions[i].length; for (uint32_t offset = 0; offset < regions[i].length; offset += SECTOR_SIZE_BYTES) { uint32_t bytes_to_write = MIN(bytes_remaining, SECTOR_SIZE_BYTES); s_flash_addr += prv_flash_write_bytes( (void *) ((uint32_t)regions[i].start + offset), s_flash_addr, bytes_to_write); bytes_remaining -= bytes_to_write; watchdog_feed(); } } } } // Write the Core Dump Image Header // Returns number of bytes written @ flash_addr static uint32_t prv_write_image_header(uint32_t flash_addr, uint8_t core_number, const ElfExternalNote *build_id, uint32_t timestamp) { CoreDumpImageHeader hdr = { .magic = CORE_DUMP_MAGIC, .core_number = core_number, .version = CORE_DUMP_VERSION, .time_stamp = timestamp, }; strncpy((char *)hdr.serial_number, mfg_get_serial_number(), sizeof(hdr.serial_number)); hdr.serial_number[sizeof(hdr.serial_number)-1] = 0; version_copy_build_id_hex_string((char *)hdr.build_id, sizeof(hdr.build_id), build_id); hdr.build_id[sizeof(hdr.build_id)-1] = 0; return prv_flash_write_bytes(&hdr, flash_addr, sizeof(hdr)); } // ================================================================================================= // Public interface // ----------------------------------------------------------------------------------------------- // Trigger a core dump NORETURN core_dump_reset(bool is_forced) { // Big problem if we re-enter here - it likely means we encountered an // exception during the core dump if (s_core_dump_initiated) { prv_debug_str("CD: re-entered"); prv_reset(); } s_core_dump_initiated = true; s_core_dump_is_forced = is_forced; if (is_forced) { RebootReason reason = { RebootReasonCode_ForcedCoreDump, 0}; reboot_reason_set(&reason); } // Pend the Non-Maskable Interrupt, as the NMI handler performs the core dump. SCB->ICSR = SCB_ICSR_NMIPENDSET_Msk; __DSB(); __ISB(); // Shouldn't get here RebootReason reason = { RebootReasonCode_CoreDumpEntryFailed, 0 }; reboot_reason_set(&reason); prv_reset(); } void __attribute__((naked)) NMI_Handler(void) { // Save the processor state at the moment the NMI exception was entered to a // struct of type CoreDumpSavedRegisters. // // Save the processor state which is not automatically stacked during // exception entry before any C code can clobber it. __asm volatile ( " ldr r0, =%[s_saved_registers]\n" " stmia r0!, {r4-r11} \n" " str sp, [r0, #4]! \n" // sp, skipping r12 " str lr, [r0, #4]! \n" // lr " mrs r1, xpsr \n" " mrs r2, msp \n" " mrs r3, psp \n" " adds r0, #8 \n" // skip pc " stmia r0!, {r1-r3} \n" // xpsr, msp, psp " b core_dump_handler_c \n" : : [s_saved_registers] "i" (&s_saved_registers.core_reg[portCANONICAL_REG_INDEX_R4]) : "r0", "r1", "r2", "r3", "cc" ); } EXTERNALLY_VISIBLE void core_dump_handler_c(void) { // Locate the stack pointer where the processor state was stacked before the // NMI handler was executed so that the saved state can be copied into // s_saved_registers. uint32_t *process_sp = (uint32_t *)( RETURNS_TO_PSP(s_saved_registers.core_reg[portCANONICAL_REG_INDEX_LR])? s_saved_registers.extra_reg.psp : s_saved_registers.extra_reg.msp); s_saved_registers.core_reg[portCANONICAL_REG_INDEX_R0] = process_sp[0]; s_saved_registers.core_reg[portCANONICAL_REG_INDEX_R1] = process_sp[1]; s_saved_registers.core_reg[portCANONICAL_REG_INDEX_R2] = process_sp[2]; s_saved_registers.core_reg[portCANONICAL_REG_INDEX_R3] = process_sp[3]; // Replace the r12 saved earlier with the real value. s_saved_registers.core_reg[portCANONICAL_REG_INDEX_R12] = process_sp[4]; // Make it look like the processor had halted at the start of this function. s_saved_registers.core_reg[portCANONICAL_REG_INDEX_PC] = (uint32_t)NMI_Handler; // Save the special registers that the C compiler won't clobber. s_saved_registers.extra_reg.primask = __get_PRIMASK(); s_saved_registers.extra_reg.basepri = __get_BASEPRI(); s_saved_registers.extra_reg.faultmask = __get_FAULTMASK(); s_saved_registers.extra_reg.control = __get_CONTROL(); // if we coredump after new fw has been installed but before we reboot, the // FW image will be overwritten with a coredump. Clear the boot bits so we // don't try and load the resources which would result in us dropping to PRF if (boot_bit_test(BOOT_BIT_NEW_FW_AVAILABLE)) { boot_bit_clear(BOOT_BIT_NEW_FW_AVAILABLE); boot_bit_clear(BOOT_BIT_NEW_SYSTEM_RESOURCES_AVAILABLE); } // Normally a reboot reason would be set before initiating a core dump. In // case this isn't true, set a default reason so that we know the reboot was // because of a core dump. RebootReason reason; reboot_reason_get(&reason); if (reason.code == RebootReasonCode_Unknown) { reason = (RebootReason) { RebootReasonCode_CoreDump, 0 }; reboot_reason_set(&reason); } prv_debug_str("Starting core dump"); // Save the current time now because rtc_get_ticks() disables and then re-enables interrupts s_time_stamp = rtc_get_time(); prv_debug_str("CD: starting"); // Feed the watchdog so that we don't get watchdog reset in the middle of dumping the core watchdog_feed(); prv_stash_regions(); // Init the flash and SPI bus s_use_cd_flash_driver = true; cd_flash_init(); // If there is a fairly recent unread core image already present, don't replace it. Once it is read through // the get_bytes_protocol_msg_callback(), the unread flag gets cleared out. uint32_t flash_base; flash_base = prv_flash_start_address(false /*new*/); if (!s_core_dump_is_forced && flash_base != CORE_DUMP_FLASH_INVALID_ADDR) { CoreDumpFlashRegionHeader region_hdr; CoreDumpImageHeader image_hdr; prv_debug_str_int("CD: Checking: ", flash_base); prv_flash_read_bytes(®ion_hdr, flash_base, sizeof(region_hdr)); prv_flash_read_bytes(&image_hdr, flash_base + sizeof(region_hdr), sizeof(image_hdr)); if ((image_hdr.magic == CORE_DUMP_MAGIC) && region_hdr.unread && ((s_time_stamp - image_hdr.time_stamp) < CORE_DUMP_MIN_AGE_SECONDS)) { prv_debug_str("CD: Still fresh"); #ifndef IS_BIGBOARD prv_reset(); #else prv_debug_str("CD: BigBoard, forcing dump"); #endif } } // Get flash address to save new image to. This method also pre-erases the region for us. flash_base = prv_flash_start_address(true /*new*/); prv_debug_str_int("CD: Saving to: ", flash_base); // --------------------------------------------------------------------------------------- // Dump RAM and thread info into flash. We store data in flash using the following format: // // CoreDumpImageHeader image_header // includes magic signature, version, time stamp, serial number // // and build id. // // uint32_t chunk_key // CORE_DUMP_CHUNK_KEY_MEMORY, CORE_DUMP_CHUNK_KEY_THREAD, etc. // uint32_t chunk_size // # of bytes of data that follow // uint8_t chunk[chunk_size] // data for the above chunk // // uint32_t chunk_key // uint32_t chunk_size // uint8_t chunk[chunk_size] // ... // uint32_t 0xFFFFFFFF // terminates list // // For threads, we store a CoreDumpThreadInfo structure as the "chunk": // chunk_key = 'THRD' // chunk[] = { uint8_t name[16]; // includes null termination // uint32_t id; // thread id // uint8_t running; // true if this thread is running // uint32_t registers[17]; // thread registers [r0-r12, sp, lr, pc, xpsr] // } // // Start at the core dump image header s_flash_addr = flash_base + sizeof(CoreDumpFlashRegionHeader); // Write out the core dump header ----------------------------------- s_flash_addr += prv_write_image_header(s_flash_addr, CORE_ID_MAIN_MCU, &TINTIN_BUILD_ID, s_time_stamp); // Write out the memory chunks ---------------------------------------- prv_write_memory_regions(MEMORY_REGIONS_DUMP, ARRAY_LENGTH(MEMORY_REGIONS_DUMP), flash_base); // Write out the extra registers chunk -------------------------------------------- CoreDumpChunkHeader chunk_hdr; chunk_hdr.key = CORE_DUMP_CHUNK_KEY_EXTRA_REG; chunk_hdr.size = sizeof(CoreDumpExtraRegInfo); CD_ASSERTN(s_flash_addr + chunk_hdr.size - flash_base < CORE_DUMP_MAX_SIZE); s_flash_addr += prv_flash_write_bytes(&chunk_hdr, s_flash_addr, sizeof(chunk_hdr)); s_flash_addr += prv_flash_write_bytes(&s_saved_registers.extra_reg, s_flash_addr, chunk_hdr.size); // Write out each of the thread chunks ---------------------------------- // Note that we leave the threads for last just in case we encounter corrupted FreeRTOS structures. // In that case, the core dump will at least contain the RAM and registers info and perhaps some of the // threads. The format of the binary core dump is streamable and is read until we reach a chunk key // of 0xFFFFFFFF (what gets placed into flash after an erase). vTaskListWalk(prvTaskInfoCallback, NULL); // If we core dumped from an ISR, we make up a special "ISR" thread to hold the registers if (!RETURNS_TO_PSP(s_saved_registers.core_reg[portCANONICAL_REG_INDEX_LR])) { // Another exception invoked the core dump handler xPORT_TASK_INFO task_info; task_info.pcName = "ISR"; task_info.taskHandle = (void *)1; for (int i = 0; i < portCANONICAL_REG_COUNT; i++) { // registers [r0-r12, sp, lr, pc, sr] task_info.registers[i] = s_saved_registers.core_reg[i]; } prvTaskInfoCallback(&task_info, NULL); } // Write out chunk terminator chunk_hdr.key = CORE_DUMP_CHUNK_KEY_TERMINATOR; chunk_hdr.size = 0; s_flash_addr += prv_flash_write_bytes(&chunk_hdr, s_flash_addr, sizeof(chunk_hdr)); // Reset! prv_debug_str("CD: completed"); prv_reset(); } // ----------------------------------------------------- // Warning: these functions use the normal flash driver status_t core_dump_size(uint32_t flash_base, uint32_t *size) { CoreDumpChunkHeader chunk_hdr; uint32_t core_dump_base = flash_base + sizeof(CoreDumpFlashRegionHeader); uint32_t current_offset = sizeof(CoreDumpImageHeader); while (true) { flash_read_bytes((uint8_t *)&chunk_hdr, core_dump_base + current_offset, sizeof(chunk_hdr)); if (chunk_hdr.key == CORE_DUMP_CHUNK_KEY_TERMINATOR) { current_offset += sizeof(chunk_hdr); break; } else if (chunk_hdr.key == CORE_DUMP_CHUNK_KEY_RAM || chunk_hdr.key == CORE_DUMP_CHUNK_KEY_THREAD || chunk_hdr.key == CORE_DUMP_CHUNK_KEY_EXTRA_REG || chunk_hdr.key == CORE_DUMP_CHUNK_KEY_MEMORY) { current_offset += sizeof(chunk_hdr) + chunk_hdr.size; } else { return E_INTERNAL; } // Totally bogus size? if (current_offset > CORE_DUMP_MAX_SIZE) { return E_INTERNAL; } } *size = current_offset; return S_SUCCESS; } void core_dump_mark_read(uint32_t flash_base) { CoreDumpFlashRegionHeader region_hdr; flash_read_bytes((uint8_t *)®ion_hdr, flash_base, sizeof(region_hdr)); region_hdr.unread = 0; flash_write_bytes((uint8_t *)®ion_hdr, flash_base, sizeof(region_hdr)); } bool core_dump_is_unread_available(uint32_t flash_base) { if (flash_base != CORE_DUMP_FLASH_INVALID_ADDR) { // a coredump is on flash CoreDumpFlashRegionHeader region_hdr; CoreDumpImageHeader image_hdr; flash_read_bytes((uint8_t *)®ion_hdr, flash_base, sizeof(region_hdr)); flash_read_bytes((uint8_t *)&image_hdr, flash_base + sizeof(region_hdr), sizeof(image_hdr)); return ((image_hdr.magic == CORE_DUMP_MAGIC) && (region_hdr.unread != 0)); } return (false); } uint32_t core_dump_get_slot_address(unsigned int i) { return (CORE_DUMP_FLASH_START + SUBSECTOR_SIZE_BYTES + i * CORE_DUMP_MAX_SIZE); } // BLE API - reserve core dump slot in flash bool core_dump_reserve_ble_slot(uint32_t *flash_base, uint32_t *max_size, ElfExternalNote *build_id) { bool status = true; uint32_t flash_addr, flash_addr_base; // Use the standard flash driver s_use_cd_flash_driver = false; flash_addr_base = prv_flash_start_address(true /*new*/); if (flash_addr_base == CORE_DUMP_FLASH_INVALID_ADDR) { status = false; goto cleanup; } flash_addr = flash_addr_base + sizeof(CoreDumpFlashRegionHeader); flash_addr += prv_write_image_header(flash_addr, CORE_ID_BLE, build_id, rtc_get_time()); *flash_base = flash_addr; *max_size = CORE_DUMP_MAX_SIZE - (flash_addr - flash_addr_base); cleanup: s_use_cd_flash_driver = true; return status; } // -------------------------------------------------------------------------------------------------- // Used by unit tests in to cause fw/apps/demo_apps/test_core_dump_app to encounter a bus fault during the core dump void core_dump_test_force_bus_fault(void) { s_test_force_bus_fault = true; } void core_dump_test_force_inf_loop(void) { s_test_force_inf_loop = true; } void core_dump_test_force_assert(void) { s_test_force_assert = true; }