SPRAD28 October 2022 AM2431 , AM2432 , AM2434 , AM2631 , AM2631-Q1 , AM2632 , AM2632-Q1 , AM2634 , AM2634-Q1 , AM26C31 , AM26C31-EP , AM26C31C , AM26C31I , AM26C31M , AM26C32 , AM26C32-EP , AM26C32C , AM26C32M , AM26LS31 , AM26LS31M , AM26LS32A , AM26LS32AC , AM26LS32AM , AM26LS33A , AM26LS33A-SP , AM26LS33AM , AM26LV31 , AM26LV31E , AM26LV31E-EP , AM26LV32 , AM26LV32E , AM26LV32E-EP , AM26S10 , AM2732 , AM2732-Q1
It is very useful to fetch core registers whenever an abort handler occurs. By analyzing the core registers, you can better understand what caused the abort handler. Below is an example of fetching the relevant registers whenever a data abort occurs. For this example, the "empty" project was used from the SDK.
By looking at the vector table that is located at HwiP_armv7r_vectors_nortos_asm.s file, you can see that all data aborts go to HwiP_data_abort_handler. This handler is a C function that is defined in HwiP_armv7r_handlers_nortos.c file.
In our SDK, abort handlers are written in C code. For every C function, the compiler creates prologue and epilogue sequences that manipulate some of the core registers (Stack Pointer register). Thus, if you fetch the Stack Pointer inside the C function, a wrong value will be stored. To avoid that, you must use the "naked" attribute and write a naked function in assembly. This attribute tells the compiler that the function is an embedded assembly function, and then prologue and epilogue sequences is not generated for that function by the compiler, and the Stack Pointer will point to the right value.
Inside HwiP_armv7r_handlers_nortos.c, a few modifications are done:
volatile uint32_t lrVal, pcVal, spVal;
__attribute__((naked)) void HwiP_data_abort_handler(void)
{
//When data abort occurs, the processor first halts here
/* Store Core Registers */
__asm volatile ( "mov %0, lr" : "=r" ( lrVal ) );
__asm volatile ( "mov %0, pc" : "=r" ( pcVal ) );
__asm volatile ( "mov %0, sp" : "=r" ( spVal ) );
/* Call the C version of the abort handler */
__asm ("B HwiP_data_abort_handler_c");
}
void __attribute__((interrupt("ABORT"), section(".text.hwi"))) HwiP_data_abort_handler_c(uint32_t *pMSP)
{
printf("\nLR(r14): %x, PC:%x, SP:%x\n" lrVal, pcVal, spVal);
volatile uint32_t loop = 1;
while(loop)
;
}
Inside
the naked function, assembly instructions were used to store the core registers.
Then, the C version that was created of the abort handler was called. Inside the C
version, the core registers can be printf or do other handling depends on the
use-case.
In order to test our code, a data fault inside empty_main() was generated by forcing the processor to write to an undefined memory region (writing 0x12 to pMem variable):
void empty_main(void *args)
{
volatile uint32_t lrVal, pcVal, spVal;
/* Open drivers to open the UART driver for console */
Drivers_open();
Board_driversOpen();
volatile uint32_t* pMem;
pMem = 0xFFFFFFFFF;
*pMem = 0x12;
while(1);
Board_driversClose();
Drivers_close();
}
Once this code was compiled and executed, a data fault is generated and makes the processor to first go through our naked function, store the core registers, call the C function and print the registers to the console.