【背景】
我们在上一篇已经完成了S32K146的watchdog超时复位的功能验证(https://forum.eepw.com.cn/thread/391634/1),实际开发中我们不仅需要在有程序异常的时候能够复位同时也需要调查异常看门狗复位的原因,S32K146芯片支持看门狗中断,使能中断INIT信号会delay 128 个bus cycle在进行复位。
我们可以在触发中断后这128 个bus cycle 内保存异常信息至内存,在IAR 环境中使用.noinit section来保存数据,重启后在段信息不会丢失,我们可以重启后将这些信息打印或者保存至日志系统进行后续调查。
【功能实现】
我们可以在watchdog 的ISR 函数中获取当前的任务栈SP,然后根据ARM-CORTEXM 系列的一场硬件压栈的规则,从中获取到进入到看门狗异常前的寄存器栈帧信息,对应ARM-CORTEXM异常寄存器压栈是由硬件自动完成的其寄存器压栈的layou如下。
我们只要获取到进入异常前的SP指针既可以从SP中获取到发生异常时的寄存器栈帧信息,按照上述的理论实现代码如下:
/******************************************************************************************************** * Private Type Declarations * *******************************************************************************************************/ typedef struct exception_stack_frame { uint32_t r0; uint32_t r1; uint32_t r2; uint32_t r3; uint32_t r12; uint32_t lr; uint32_t pc; uint32_t psr; }exception_stack_frame_t; /******************************************************************************************************** * Private Variable Definitions * *******************************************************************************************************/ exception_stack_frame_t wdg_reset_frame_t section(".noinit"); void wdg_reset_fault_c (unsigned int * wdgfault_args) { wdg_reset_frame_t.r0 = ((uint32_t) wdgfault_args[0]); wdg_reset_frame_t.r1 = ((uint32_t) wdgfault_args[1]); wdg_reset_frame_t.r2 = ((uint32_t) wdgfault_args[2]); wdg_reset_frame_t.r3 = ((uint32_t) wdgfault_args[3]); wdg_reset_frame_t.r12 = ((uint32_t) wdgfault_args[4]); wdg_reset_frame_t.lr = ((uint32_t) wdgfault_args[5]); wdg_reset_frame_t.pc = ((uint32_t) wdgfault_args[6]); wdg_reset_frame_t.psr = ((uint32_t) wdgfault_args[7]); while(1); } /** * @brief This function handles Wdgreset. */ __attribute__((naked)) void WDOG_EWM_IRQHandler(void) { __asm volatile( "tst lr, #4 \n" "ite eq \n" "mrseq r0, msp \n" "mrsne r0, psp \n" "b wdg_reset_fault_c \n"); }
以上的代码已经保存了看门狗异常的寄存器栈帧至wdg_reset_frame_t变量中保存,该变量被存放在.noinit section中,该变量在重启后内容不会丢失。我们在重启后获取重启的reason 为看门狗复位时将wdg_reset_frame_t 的内容打印输出便于我们debug 看门狗重启前的代码现场,在启动处添加此逻辑代码,代码如下。
/******************************************************************************************************** * Global Function Declarations * *******************************************************************************************************/ void show_wdg_register_frame(void) { LOG_E("R0 = 0X%08X",wdg_reset_frame_t.r0); LOG_E("R1 = 0X%08X",wdg_reset_frame_t.r1); LOG_E("R2 = 0X%08X",wdg_reset_frame_t.r2); LOG_E("R3 = 0X%08X",wdg_reset_frame_t.r3); LOG_E("R12 = 0X%08X",wdg_reset_frame_t.r12); LOG_E("LR = 0X%08X",wdg_reset_frame_t.lr); LOG_E("PC = 0X%08X",wdg_reset_frame_t.pc); LOG_E("PSR = 0X%08X",wdg_reset_frame_t.psr); } int show_boot_reason(void) { LOG_I("boot reason 0x%08x",RCM->SRS); if(RCM->SRS & (0X01 << 5)) { show_wdg_register_frame(); } return 1; } INIT_BOARD_EXPORT_LEVEL1(show_boot_reason);
【功能验证】
添加如下测试代码,在初始化看门狗后,停止喂狗并卡死在whie(1) 代码处。
unsigned int wdg_test(char argc,char *argv[]) { status_t ret; /* Init wdg */ ret = WDOG_DRV_Init(WDOG_CONFIG_1_INST, &WDOG_Cfg0); if(ret != STATUS_SUCCESS) { LOG_E("Wdg init failed %x",ret); } /* Install IRQ handlers for WDOG and SysTick interrupts */ INT_SYS_InstallHandler(WDOG_EWM_IRQn, WDOG_EWM_IRQHandler, (isr_t *)0); /* Enable Watchdog IRQ */ INT_SYS_EnableIRQ(WDOG_EWM_IRQn); uint8_t count = 8; while(count--) { vTaskDelay(1000); /* Reset Watchdog counter */ WDOG_DRV_Trigger(WDOG_CONFIG_1_INST); LOG_I("feed dog"); } while(1); return 0; } LTSH_FUNCTION_EXPORT(wdg_test,"image crc32 check");
S32K146 芯片RCM 模块可以读取到本次重启触发的原因。
运行结果如下:从中可以看出RCM_SRS = 0x20 ,bit5 = 1 对应重启原因为看门狗复位。
从重启后打印的日志的PC/LR 地址信息产看对应的反汇编对应的代码如下:
0xD878 对应的代码为while(1)的死循环的代码
LR 对应的0x7c6d 对应返回地址为串口发送函数的地址,因为卡死前最后一次调用的函数为LOG 打印的宏接口对应的底层的uart 发送函数和实际情况也能匹配上。
至此我们已经完成使用看门狗中断保持异常寄存器帧的功能开发。