Defining ARM interrupt vectors inside of functions... - Page 1
Forum section: electronics / general

author: westfw
author_rank: Super Contributor
timestamp: July 25, 2020, 01:27:14 am
content: On an ARM Cortex, you would normally define an interrupt handler by simply creating the function:Code:void SERCOM3_Handler() { // stuff}I would like to set up code so that the Handler is not defined UNLESS a function is actually called. (on SAMD, this would allow SERCOM peripherals to be initialized as UART, I2C, or SPI, without each trying to define a handler.)Now, the handlers are typically in FLASH, so they can't actually be modified at runtime. So I can't just assign a vector to a variable.But the main thought is to leave out defining the vector when the function is eliminated by linktime garbage collection. It's just a symbol value by then. Can I do something like:Code:I2C3_init() { static const voidFunction_t SERCOM3_Hanlder = i2C_intHandler; // stuff}(and then if C++, even use lambdas?)(I'm not sure that it's much better than having a macro at global scope. Just Curious...)

author: ataradov
author_rank: Super Contributor
timestamp: July 25, 2020, 02:45:08 am
content: No, this is impossible. You will still have one instance that is in the table. What if you initialize SPI and I2C on the same SERCOM at different times?The easiest way is to just define real vector table in RAM and leave only fault handlers in the flash. Or even just the SP and reset handler in the flash. Then drivers just have to go and update the table in RAM with whatever function they like.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: July 25, 2020, 02:46:22 am
content: HALs usually do it with a callback, yes. The interrupt stub is basicallyCode:void* SoftwareInterruptVectorTable[] = {Interrupt_Default,Interrupt_Default,Interrupt_Default,...Interrupt_Default}void Interrupt_Default(void) {return;}void Interrupt_Handler(void) {// base housekeeping operations here...// user call!*SoftwareInterruptVectorTable[HALGetInterruptNumber()]();return;}And your program sets an element in SoftwareInterruptVectorTable[] at runtime to hook it, or sets it back to Interrupt_Default to unhook it (or there's a call to unhook it, which is basically syntactic sugar for the same thing.)Or instead of a default do-nothing stub, it could also check the vector for a default value i.e. a null pointer, and simply not call anything in that case.Or sure, a lambda if you don't need to name the function. Makes it look very much like OOP on event-driven OSs -- install the callbacks, which can be inlined, and it's off to the races. (Maybe don't even have to worry about destructing these objects -- interrupts are persistent after all!)Note this is showing a sort of emulation method, the hypothetical HALGetInterruptNumber() returns an index to the table based on what caused the interrupt (which might be visible on the stack, or a register in the interrupt controller, or..). I'm just showing this as a possible strategy; it's not necessary on most platforms, which have an extensive hardware table already (AVR, ARM, etc.). In that case of course, the table entry can be selected by the individual handlers (i.e., SERCOM3 calls *SoftwareInterruptVectorTable(); ).There might still be a flag that enables a handler only when a given device is selected for use; it would be a lot of overhead after all, to have however many hundreds of unique interrupts linked into every project [built with the libraries]. It may default all interrupts to a stub do-nothing function, and only enable this functionality when a device has been selected. (This would probably be done in one of the code generation tools, e.g. ST Cube, so look in those outputs to see how and where it's set up.)Mind that these function calls add considerable overhead -- the call takes a lot of cycles itself, and all the registers saved take a lot of stack space. Doesn't matter on a lot of projects, but easy pick'ns if optimization calls for it.Tim

author: ataradov
author_rank: Super Contributor
timestamp: July 25, 2020, 02:48:32 am
content: If you already keep SoftwareInterruptVectorTable in the SARM, might as well just move the real vector table and use it directly. Way less overhead and confusion of layers of pointer calls.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: July 25, 2020, 02:50:21 am
content: Yes, on platforms where the IVT isn't hard fixed.Tim

author: westfw
author_rank: Super Contributor
timestamp: July 25, 2020, 08:10:48 am
content: The ARM RAM vector table has really annoying alignment requirements. :-(QuoteI mean to only have a single SERCOM usage left, by the time the linker is done garbage-collecting unused code.If the Source has:Code:i2C3_init()UART3_init()SPI3_initbut my main/... path only ever calls UART3 init, I want the UART ISRs. If I call two init functions, I'd get a "multiply defined symbol" or "attempt to redefine xxx" or similar from link, and that would be fine...

author: ataradov
author_rank: Super Contributor
timestamp: July 25, 2020, 08:18:35 am
content: Why they are annoying? Just place it first in the RAM and it will automatically correctly align.You are relying on linker to remove unused code, but the interrupt handlers are "used" through the vector table. So this abuse of a linker has its limits.

author: westfw
author_rank: Super Contributor
timestamp: July 27, 2020, 07:39:23 am
content: QuoteAh. Right. I was thinking that the weak symbol in the startup file would override a GC'ed symbol in the code, but if the code is redefining the symbol, that obviously wouldn't be true. Rats.

author: abyrvalg
author_rank: Frequent Contributor
timestamp: July 27, 2020, 08:09:32 am
content: If you don’t need runtime reconfiguration and only one of mutually exclusive SERCOM modes will be used, wouldn’t it be better to state it clear (i.e. with some #define and #ifdef...#endif around the mode-specific code) instead of relying on linker? Linker would not prevent you from things like calling init() of one mode, then send() of another mode, but #ifdef will.

author: ejeffrey
author_rank: Super Contributor
timestamp: July 27, 2020, 07:56:30 pm
content: Quote from: westfw on July 25, 2020, 01:27:14 amIt isn't clear what you are trying to accomplish. Are you trying to save flash space, allow dynamic dispatch, or some other goal?Anyway, if you want something to be left out by the linker there must be no references to it at all. This is generally accomplished with preprocessor macros and #ifdef sections. If you just want dynamic dispatch based on operation mode, you just need to put the dispatch table in SRAM.

author: hans
author_rank: Super Contributor
timestamp: July 27, 2020, 09:40:48 pm
content: My best guess is that the SERCOM has several operation modes, like I2C or SPI, and so switching between ISR handler depending on the context can be useful.In my view, you could do this 2 ways. You could define a general SERCOM interrupt, and then check some global variable or register to see if SPI/I2C/etc. needs to be handled, and then call the corresponding driver code. Like:Code:void SERCOM3_Handler() {switch(drvSercom3Mode) {case SPI: drvSercom3IsrSpi(); break;case I2C: drvSercom3IsrI2c(); break;default: break;}}This works, but a downside is that a big if/else or switch/case in an ISR can add some overhead, in particular for the last options of the list.. Moreover,the compiler will include ISR handlers for SPI/I2C at all times, because it may not be able to predict the value of drvSercom3Mode to be only SPI or only I2C. Since you probably hardwire peripherals on your PCBs, that's not particularly helpful.Therefore alternatively you could define a SERCOM interrupt that calls a handler that is referenced by a function pointer. For example:Code:typedef void (*ISRHandler)(void);ISRHandler sercom3Handler = Default_Handler;void drvSercom3InitSpi() {// etc.sercom3Handler = drvSercom3ISRSpi;// etc.}void drvSercom3ISRI2c() {// etc.sercom3Handler = drvSercom3ISRI2c;// etc.}void SERCOM3_Handler() {sercom3Handler();}Calling a void function pointer will probably only cost a few CPU cycles:Code:SERCOM3_Handler:ldr r3, .L16ldr r3, bx r3 @ indirect register sibling callThough, as mentioned, you could do 1 better and move the vector table to RAM and overwrite the function pointer there. Both a naive or RAM function pointer approach both have the benefit of potentially cleaning up unused ISR handlers from your output image. Personally I would a simple function pointer first if I didn't want to bother with remapping vector tables.In C++ you can also remap lambda's onto these void (*IsrHandler)(void) function pointer without many problems.. For example:https://godbolt.org/z/xKxKdKI left 1 conventional ISR routine in there from the previous examples, and the output assembly is identical to a lambda.