Search code examples
cgccinterruptstartupcortex-m

Understanding this C syntax for ARM Cortex-M interrupt vector table definition


I'm currently looking at some startup code for an ARM Cortex-M microcontroller written by someone else. The entire file can be found in this Github repo.

It does stuff like setting up the stack pointer and initalizing the .data and .bss sections appropriately using some for loops which are straightforward.

I'm struggling to understand the syntax used to define the interrupt vector table:

#define DUMMY __attribute__ ((weak, alias ("irq_handler_dummy")))

//-----------------------------------------------------------------------------
void irq_handler_reset(void);
DUMMY void irq_handler_nmi(void);
DUMMY void irq_handler_hard_fault(void);
// etc. 

extern int main(void);

extern void _stack_top(void);
// etc.

//-----------------------------------------------------------------------------
__attribute__ ((used, section(".vectors")))
void (* const vectors[])(void) =
{
  &_stack_top,                   // 0 - Initial Stack Pointer Value

  // Cortex-M0+ handlers
  irq_handler_reset,             // 1 - Reset
  irq_handler_nmi,               // 2 - NMI
  irq_handler_hard_fault,        // 3 - Hard Fault
  // etc.
};

The __attribute__ definition for GCC is clear and I've found the answer to what it does in the official documentation: GCC Function attributes.

I still have no idea how to parse and put in to words what this syntax means

void (* const vectors[])(void)

Can somebody help me understand what all that syntax unpacks to or represents?


Solution

  • A vector table is essentially just an array of ISR addresses. Which translated to C can be regarded as an array of function pointers. Creating the vector table as an array of function pointers is quite common.

    What makes Cortex M a special snowflake is that it loads the stack pointer from flash through hardware, rather than the programmer setting it up manually in run-time. The first item of the vector table contains the value of the initial stack pointer - it is actually not a function address. Therefore some manner of hack is necessary. The _stack_top will likely boil down to some stack address set in a linker script. Your code will never use this item directly, it's just there so that the stack gets set correctly at boot-up.

    Apart from that one, the rest are just normal function pointers to ISRs. Since an ISR takes no parameters and returns to value, the syntax of an ISR function pointer is:

    void (*name) (void)
    

    An array of such function pointers is declared as:

    void (*name [n]) (void)
    

    Where n can optionally be used to express array size.

    __attribute__ ((used, section(".vectors"))) is just to place the array at a specific address, in this case from 0 and upwards. You can check the linker script and you'll find .vectors there.

    We want this vector table to get loaded in flash ROM as read-only data. Therefore we want the pointers to be read-only, not what they point at. This is achieved by placing const on the right side of the * (same rule applies for normal object pointers too):

    void (*const vectors[])(void)
    

    We could have written this much more readable if a typedef had been used:

    typedef void isr_vector_t (void);
    ...
    
    isr_vector_t* const vectors[] = { ... };