/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "common.h" #include "console.h" #include "cpu.h" #include "host_command.h" #include "panic.h" #include "panic-internal.h" #include "printf.h" #include "system.h" #include "task.h" #include "timer.h" #include "uart.h" #include "util.h" #include "watchdog.h" /* Whether bus fault is ignored */ static int bus_fault_ignored; /* Panic data goes at the end of RAM. */ static struct panic_data * const pdata_ptr = PANIC_DATA_PTR; /* Preceded by stack, rounded down to nearest 64-bit-aligned boundary */ static const uint32_t pstack_addr = (CONFIG_RAM_BASE + CONFIG_RAM_SIZE - sizeof(struct panic_data)) & ~7; /** * Print the name and value of a register * * This is a convenient helper function for displaying a register value. * It shows the register name in a 3 character field, followed by a colon. * The register value is regs[index], and this is shown in hex. If regs is * NULL, then we display spaces instead. * * After displaying the value, either a space or \n is displayed depending * on the register number, so that (assuming the caller passes all 16 * registers in sequence) we put 4 values per line like this * * r0 :0000000b r1 :00000047 r2 :60000000 r3 :200012b5 * r4 :00000000 r5 :08004e64 r6 :08004e1c r7 :200012a8 * r8 :08004e64 r9 :00000002 r10:00000000 r11:00000000 * r12:0000003f sp :200009a0 lr :0800270d pc :0800351a * * @param regnum Register number to display (0-15) * @param regs Pointer to array holding the registers, or NULL * @param index Index into array where the register value is present */ static void print_reg(int regnum, const uint32_t *regs, int index) { static const char regname[] = "r10r11r12sp lr pc "; static char rname[3] = "r "; const char *name; rname[1] = '0' + regnum; name = regnum < 10 ? rname : ®name[(regnum - 10) * 3]; panic_printf("%c%c%c:", name[0], name[1], name[2]); if (regs) panic_printf("%08x", regs[index]); else panic_puts(" "); panic_puts((regnum & 3) == 3 ? "\n" : " "); } /* * Returns non-zero if the exception frame was created on the main stack, or * zero if it's on the process stack. * * See B1.5.8 "Exception return behavior" of ARM DDI 0403D for details. */ static int32_t is_frame_in_handler_stack(const uint32_t exc_return) { return (exc_return & 0xf) == 1 || (exc_return & 0xf) == 9; } #ifdef CONFIG_DEBUG_EXCEPTIONS /* Names for each of the bits in the cfs register, starting at bit 0 */ static const char * const cfsr_name[32] = { /* MMFSR */ [0] = "Instruction access violation", [1] = "Data access violation", [3] = "Unstack from exception violation", [4] = "Stack from exception violation", /* BFSR */ [8] = "Instruction bus error", [9] = "Precise data bus error", [10] = "Imprecise data bus error", [11] = "Unstack from exception bus fault", [12] = "Stack from exception bus fault", /* UFSR */ [16] = "Undefined instructions", [17] = "Invalid state", [18] = "Invalid PC", [19] = "No coprocessor", [24] = "Unaligned", [25] = "Divide by 0", }; /* Names for the first 5 bits in the DFSR */ static const char * const dfsr_name[] = { "Halt request", "Breakpoint", "Data watchpoint/trace", "Vector catch", "External debug request", }; /** * Helper function to display a separator after the previous item * * If items have been displayed already, we display a comma separator. * In any case, the count of items displayed is incremeneted. * * @param count Number of items displayed so far (0 for none) */ static void do_separate(int *count) { if (*count) panic_puts(", "); (*count)++; } /** * Show a textual representaton of the fault registers * * A list of detected faults is shown, with no trailing newline. * * @param cfsr Value of Configurable Fault Status * @param hfsr Value of Hard Fault Status * @param dfsr Value of Debug Fault Status */ static void show_fault(uint32_t cfsr, uint32_t hfsr, uint32_t dfsr) { unsigned int upto; int count = 0; for (upto = 0; upto < 32; upto++) { if ((cfsr & (1 << upto)) && cfsr_name[upto]) { do_separate(&count); panic_puts(cfsr_name[upto]); } } if (hfsr & CPU_NVIC_HFSR_DEBUGEVT) { do_separate(&count); panic_puts("Debug event"); } if (hfsr & CPU_NVIC_HFSR_FORCED) { do_separate(&count); panic_puts("Forced hard fault"); } if (hfsr & CPU_NVIC_HFSR_VECTTBL) { do_separate(&count); panic_puts("Vector table bus fault"); } for (upto = 0; upto < 5; upto++) { if ((dfsr & (1 << upto))) { do_separate(&count); panic_puts(dfsr_name[upto]); } } } /* * Returns the size of the exception frame. * * See B1.5.7 "Stack alignment on exception entry" of ARM DDI 0403D for details. * In short, the exception frame size can be either 0x20, 0x24, 0x68, or 0x6c * depending on FPU context and padding for 8-byte alignment. */ static uint32_t get_exception_frame_size(const struct panic_data *pdata) { uint32_t frame_size = 0; /* base exception frame */ frame_size += 8 * sizeof(uint32_t); /* CPU uses xPSR[9] to indicate whether it padded the stack for * alignment or not. */ if (pdata->cm.frame[7] & (1 << 9)) frame_size += sizeof(uint32_t); #ifdef CONFIG_FPU /* CPU uses EXC_RETURN[4] to indicate whether it stored extended * frame for FPU or not. */ if (!(pdata->cm.regs[11] & (1 << 4))) frame_size += 18 * sizeof(uint32_t); #endif return frame_size; } /* * Returns the position of the process stack before the exception frame. * It computes the size of the exception frame and adds it to psp. * If the exception happened in the exception context, it returns psp as is. */ static uint32_t get_process_stack_position(const struct panic_data *pdata) { uint32_t psp = pdata->cm.regs[0]; if (!is_frame_in_handler_stack(pdata->cm.regs[11])) psp += get_exception_frame_size(pdata); return psp; } /* * Show extra information that might be useful to understand a panic() * * We show fault register information, including the fault address registers * if valid. */ static void panic_show_extra(const struct panic_data *pdata) { show_fault(pdata->cm.cfsr, pdata->cm.hfsr, pdata->cm.dfsr); if (pdata->cm.cfsr & CPU_NVIC_CFSR_BFARVALID) panic_printf(", bfar = %x", pdata->cm.bfar); if (pdata->cm.cfsr & CPU_NVIC_CFSR_MFARVALID) panic_printf(", mfar = %x", pdata->cm.mfar); panic_printf("\ncfsr = %x, ", pdata->cm.cfsr); panic_printf("shcsr = %x, ", pdata->cm.shcsr); panic_printf("hfsr = %x, ", pdata->cm.hfsr); panic_printf("dfsr = %x, ", pdata->cm.dfsr); panic_printf("ipsr = %x\n", pdata->cm.regs[1]); } /* * Prints process stack contents stored above the exception frame. */ static void panic_show_process_stack(const struct panic_data *pdata) { panic_printf("\n=========== Process Stack Contents ==========="); if (pdata->flags & PANIC_DATA_FLAG_FRAME_VALID) { uint32_t psp = get_process_stack_position(pdata); int i; for (i = 0; i < 16; i++) { if (psp + sizeof(uint32_t) > CONFIG_RAM_BASE + CONFIG_RAM_SIZE) break; if (i % 4 == 0) panic_printf("\n%08x:", psp); panic_printf(" %08x", *(uint32_t *)psp); psp += sizeof(uint32_t); } } else { panic_printf("\nBad psp"); } } #endif /* CONFIG_DEBUG_EXCEPTIONS */ /* * Print panic data */ void panic_data_print(const struct panic_data *pdata) { const uint32_t *lregs = pdata->cm.regs; const uint32_t *sregs = NULL; const int32_t in_handler = is_frame_in_handler_stack(pdata->cm.regs[11]); int i; if (pdata->flags & PANIC_DATA_FLAG_FRAME_VALID) sregs = pdata->cm.frame; if (((pdata->cm.hfsr >> 26) & 0xf) == HFSR_FLAG_WATCHDOG) { panic_printf("\n### WATCHDOG PC=%08x / LR=%08x / task=%d\n", lregs[4], lregs[1], (pdata->cm.hfsr >> 2) & 0xff); } else if (((pdata->cm.hfsr >> 26) & 0xf) == HFSR_FLAG_ASSERT) { panic_printf("\nASSERTION FAILURE in %s() at %s:%d\n", lregs[3], lregs[4], (pdata->cm.hfsr >> 10) & 0xffff); } else { panic_printf("\n=== %s EXCEPTION: %02x ====== xPSR: %08x ===\n", in_handler ? "HANDLER" : "PROCESS", lregs[1] & 0xff, sregs ? sregs[7] : -1); } for (i = 0; i < 4; i++) print_reg(i, sregs, i); for (i = 4; i < 10; i++) print_reg(i, lregs, i - 1); print_reg(10, lregs, 9); print_reg(11, lregs, 10); print_reg(12, sregs, 4); print_reg(13, lregs, in_handler ? 2 : 0); print_reg(14, sregs, 5); print_reg(15, sregs, 6); #ifdef CONFIG_DEBUG_EXCEPTIONS panic_show_extra(pdata); #endif } void __keep report_panic(void) { struct panic_data *pdata = pdata_ptr; uint32_t sp; pdata->magic = PANIC_DATA_MAGIC; pdata->struct_size = sizeof(*pdata); pdata->struct_version = 2; pdata->arch = PANIC_ARCH_CORTEX_M; pdata->flags = 0; pdata->reserved = 0; /* Choose the right sp (psp or msp) based on EXC_RETURN value */ sp = is_frame_in_handler_stack(pdata->cm.regs[11]) ? pdata->cm.regs[2] : pdata->cm.regs[0]; /* If stack is valid, copy exception frame to pdata */ if ((sp & 3) == 0 && sp >= CONFIG_RAM_BASE && sp <= CONFIG_RAM_BASE + CONFIG_RAM_SIZE - 8 * sizeof(uint32_t)) { const uint32_t *sregs = (const uint32_t *)sp; int i; /* Skip r0-r3 and r12 registers if necessary */ for (i = CORTEX_PANIC_FRAME_REGISTER_R0; i <= CORTEX_PANIC_FRAME_REGISTER_R12; i++) if (IS_ENABLED(CONFIG_PANIC_STRIP_GPR)) pdata->cm.frame[i] = 0; else pdata->cm.frame[i] = sregs[i]; for (i = CORTEX_PANIC_FRAME_REGISTER_LR; i < NUM_CORTEX_PANIC_FRAME_REGISTERS; i++) pdata->cm.frame[i] = sregs[i]; pdata->flags |= PANIC_DATA_FLAG_FRAME_VALID; } /* Save extra information */ pdata->cm.cfsr = CPU_NVIC_CFSR; pdata->cm.bfar = CPU_NVIC_BFAR; pdata->cm.mfar = CPU_NVIC_MFAR; pdata->cm.shcsr = CPU_NVIC_SHCSR; pdata->cm.hfsr = CPU_NVIC_HFSR; pdata->cm.dfsr = CPU_NVIC_DFSR; /* Store LR & PC in cm.regs to make them survive a PMIC reset. */ if (pdata->flags & PANIC_DATA_FLAG_FRAME_VALID) { pdata->cm.regs[3] = pdata->cm.frame[5]; /* LR */ pdata->cm.regs[4] = pdata->cm.frame[6]; /* PC */ } #ifdef CONFIG_UART_PAD_SWITCH uart_reset_default_pad_panic(); #endif panic_data_print(pdata); #ifdef CONFIG_DEBUG_EXCEPTIONS panic_show_process_stack(pdata); /* * TODO(crosbug.com/p/23760): Dump main stack contents as well if the * exception happened in a handler's context. */ #endif panic_reboot(); } /** * Default exception handler, which reports a panic. * * Declare this as a naked call so we can extract raw LR and IPSR values. */ void exception_panic(void) { /* Save registers and branch directly to panic handler */ asm volatile( "mov r0, %[pregs]\n" "mrs r1, psp\n" "mrs r2, ipsr\n" "mov r3, sp\n" #ifdef CONFIG_PANIC_STRIP_GPR /* * Check if we are in exception. This is similar to * in_interrupt_context(). Exception bits are 9 LSB, so * we can perform left shift for 23 bits and check if result * is 0 (lsls instruction is setting appropriate flags). */ "lsls r6, r2, #23\n" /* * If this is software panic (shift result == 0) then register * r4 and r5 contain additional info about panic. * Clear r6-r11 always and r4, r5 only if this is exception * panic. To clear r4 and r5, 'movne' conditional instruction * is used. It works only when flags contain information that * result was != 0. Itt is pseudo instruction which is used * to make sure we are using correct conditional instructions. */ "itt ne\n" "movne r4, #0\n" "movne r5, #0\n" "mov r6, #0\n" "mov r7, #0\n" "mov r8, #0\n" "mov r9, #0\n" "mov r10, #0\n" "mov r11, #0\n" #endif "stmia r0, {r1-r11, lr}\n" "mov sp, %[pstack]\n" "bl report_panic\n" : : [pregs] "r" (pdata_ptr->cm.regs), [pstack] "r" (pstack_addr) : /* Constraints protecting these from being clobbered. * Gcc should be using r0 & r12 for pregs and pstack. */ "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "cc", "memory" ); } #ifdef CONFIG_SOFTWARE_PANIC void software_panic(uint32_t reason, uint32_t info) { __asm__("mov " STRINGIFY(SOFTWARE_PANIC_INFO_REG) ", %0\n" "mov " STRINGIFY(SOFTWARE_PANIC_REASON_REG) ", %1\n" "bl exception_panic\n" : : "r"(info), "r"(reason)); __builtin_unreachable(); } void panic_set_reason(uint32_t reason, uint32_t info, uint8_t exception) { uint32_t *lregs = pdata_ptr->cm.regs; /* Setup panic data structure */ memset(pdata_ptr, 0, sizeof(*pdata_ptr)); pdata_ptr->magic = PANIC_DATA_MAGIC; pdata_ptr->struct_size = sizeof(*pdata_ptr); pdata_ptr->struct_version = 2; pdata_ptr->arch = PANIC_ARCH_CORTEX_M; /* Log panic cause */ lregs[1] = exception; lregs[3] = reason; lregs[4] = info; } void panic_get_reason(uint32_t *reason, uint32_t *info, uint8_t *exception) { uint32_t *lregs = pdata_ptr->cm.regs; if (pdata_ptr->magic == PANIC_DATA_MAGIC && pdata_ptr->struct_version == 2) { *exception = lregs[1]; *reason = lregs[3]; *info = lregs[4]; } else { *exception = *reason = *info = 0; } } #endif void panic_assert(const char *func, const char *file, uint16_t line) { struct panic_data *pdata = pdata_ptr; pdata->magic = PANIC_DATA_MAGIC; pdata->struct_size = sizeof(*pdata); pdata->struct_version = 2; pdata->arch = PANIC_ARCH_CORTEX_M; pdata->flags = 0; pdata->reserved = 0; /* * Panic data is cleared by PMIC reset on Nami. HFSR is used because * it's saved to BBRAM by the released RO (before PMIC reset). Bit * assignments are as follows: * * ([31:30] Used for DEBUGEVT and FORCED) * [29:26] Flags * [25:10] Line # * [9:2] Task # * ([1:0] Used for VECTTBLE and reserved) */ pdata->cm.hfsr = HFSR_FLAG_ASSERT << 26 | line << 10 | (task_get_current() & 0xff) << 2; pdata->cm.regs[3] = (uint32_t)func; pdata->cm.regs[4] = (uint32_t)file; panic_reboot(); } void bus_fault_handler(void) { if (!bus_fault_ignored) exception_panic(); } void ignore_bus_fault(int ignored) { /* * Flash code might call this before cpu_init(), * ensure that the bus faults really go through our handler. */ CPU_NVIC_SHCSR |= CPU_NVIC_SHCSR_BUSFAULTENA; bus_fault_ignored = ignored; }