HALs usually do it with a callback, yes. The interrupt stub is basically
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[SERCOM3_INTERRUPT_VECT_IDX](); ).
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