I'm working on project, where some interrupt service has to be handled in assembler. The handler function is called from interrupt vector wrapper. The handler body is written in assembler and it receives single (pointer) parameter in specific register.
The code target is MSP430 and it has to compile with both MSP430-gcc and TI compiler. I already have working solution for MSP430-gcc and it looks like this:
static void __attribute__((naked)) _shared_vector_handler(Timer_driver_t *driver) {
__asm__(
" MOVX.W %c[iv_register_offset](R12),R14 ; \n"
" ADD @R14,PC ; \n"
" RETA ; \n"
" JMP CCIFG_1_HND ; Vector 2 \n"
" JMP CCIFG_2_HND ; Vector 4 \n"
" JMP CCIFG_3_HND ; Vector 6 \n"
" JMP CCIFG_4_HND ; Vector 8 \n"
" JMP CCIFG_5_HND ; Vector 10 \n"
" JMP CCIFG_6_HND ; Vector 12 \n"
"TIFG_HND: \n"
" MOVX.A %c[overflow_handle_offset](R12),R14 ; \n"
" MOVX.A %c[handler_param_offset](R14),R12 ; \n"
" MOVX.A %c[handler_offset](R14),R14 ; \n"
" CALLA R14 ; \n"
" RETA ; \n"
"CCIFG_1_HND: \n"
" MOVX.A %c[ccr1_handle_offset](R12),R14 ; \n"
" MOVX.A %c[handler_param_offset](R14),R12 ; \n"
" MOVX.A %c[handler_offset](R14),R14 ; \n"
" CALLA R14 ; \n"
" RETA ; \n"
"CCIFG_2_HND: \n"
" MOVX.A %c[ccr2_handle_offset](R12),R14 ; \n"
" MOVX.A %c[handler_param_offset](R14),R12 ; \n"
" MOVX.A %c[handler_offset](R14),R14 ; \n"
" CALLA R14 ; \n"
" RETA ; \n"
"CCIFG_3_HND: \n"
" MOVX.A %c[ccr3_handle_offset](R12),R14 ; \n"
" MOVX.A %c[handler_param_offset](R14),R12 ; \n"
" MOVX.A %c[handler_offset](R14),R14 ; \n"
" CALLA R14 ; \n"
" RETA ; \n"
"CCIFG_4_HND: \n"
" MOVX.A %c[ccr4_handle_offset](R12),R14 ; \n"
" MOVX.A %c[handler_param_offset](R14),R12 ; \n"
" MOVX.A %c[handler_offset](R14),R14 ; \n"
" CALLA R14 ; \n"
" RETA ; \n"
"CCIFG_5_HND: \n"
" MOVX.A %c[ccr5_handle_offset](R12),R14 ; \n"
" MOVX.A %c[handler_param_offset](R14),R12 ; \n"
" MOVX.A %c[handler_offset](R14),R14 ; \n"
" CALLA R14 ; \n"
" RETA ; \n"
"CCIFG_6_HND: \n"
" MOVX.A %c[ccr6_handle_offset](R12),R14 ; \n"
" MOVX.A %c[handler_param_offset](R14),R12 ; \n"
" MOVX.A %c[handler_offset](R14),R14 ; \n"
" CALLA R14 ; \n" ::
[iv_register_offset] "i" (offsetof(Timer_driver_t, _IV_register)),
[overflow_handle_offset] "i" (offsetof(Timer_driver_t, _overflow_handle)),
[ccr1_handle_offset] "i" (offsetof(Timer_driver_t, _CCR1_handle)),
[ccr2_handle_offset] "i" (offsetof(Timer_driver_t, _CCR2_handle)),
[ccr3_handle_offset] "i" (offsetof(Timer_driver_t, _CCR3_handle)),
[ccr4_handle_offset] "i" (offsetof(Timer_driver_t, _CCR4_handle)),
[ccr5_handle_offset] "i" (offsetof(Timer_driver_t, _CCR5_handle)),
[ccr6_handle_offset] "i" (offsetof(Timer_driver_t, _CCR6_handle)),
[handler_offset] "i" (offsetof(Timer_channel_handle_t, _handler)),
[handler_param_offset] "i" (offsetof(Timer_channel_handle_t, _handler_param)) :
);
}
Translated to English: the driver structure contains address of IV register on some specific offset. Content on that address is added to PC, so jump to specific label (depending on which interrupt flag is set) occurs. This is recommended usage as described by TI in user's guide, page 653. All labels do the same: they take pointer to some handle from driver structure from specific offset. The handle has again on some specific offset function pointer (interrupt service handler) and pointer to some parameter, that shall be passed to handler. The structures in short:
typedef struct Timer_driver {
// enable dispose(Timer_driver_t *)
Disposable_t _disposable;
// base of HW timer registers, (address of corresponding TxCTL register)
uint16_t _CTL_register;
...
// interrupt vector register
uint16_t _IV_register;
// stored mode control
uint8_t _mode;
// amount of CCRn registers
uint8_t _available_handles_cnt;
// main (CCR0) handle
Timer_channel_handle_t *_CCR0_handle;
// up to six (CCRn) handles sharing one interrupt vector
Timer_channel_handle_t *_CCR1_handle;
Timer_channel_handle_t *_CCR2_handle;
...
}
and
struct Timer_channel_handle {
// vector wrapper, enable dispose(Timer_channel_handle_t *)
Vector_handle_t vector;
// HW timer driver reference
Timer_driver_t *_driver;
// capture / compare control register
uint16_t _CCTLn_register;
// capture / compare register
uint16_t _CCRn_register;
// vector interrupt service handler
void (*_handler)(void *);
// vector interrupt service handler parameter
void *_handler_param;
...
}
Now the problem.
- offsets are not known until compile time
- I can't pass to assembler some offsetof(s, m)
- offsets depend on memory model used (size of pointers 16bit or 32bit)
- offsets depend on size of first member of both structures and this size depends on preprocessor definitions (1 pointer or 4 pointers)
- offsets cannot be precomputed, because each compiler adds some alignment and padding to the first member structure
- the first member must be first member (reordening is not allowed)
- TI compiler does not support passing compile-time vars to inline assembly code
The goal:
- support both compilers
- do not duplicate code, do not hardcode offsets
- if possible, avoid extracting whole handler to asm file and including headers via .cdecls (or #include in case of gcc). Both compilers handle including of C headers in a lot different way, structure offsets are also defined in a lot different way and some non-trivial restructuring of headers would be required, which I believe is near impossible.
When I compile this with TI compiler I get following error:
"../module/driver/src/timer.c", line 274: error #18: expected a ")"
"../module/driver/src/timer.c", line 285: warning #12-D: parsing restarts here after previous syntax error
1 error detected in the compilation of "../module/driver/src/timer.c".
gmake: *** [module/driver/src/timer.obj] Error 1
My build is handled by CMake and I can think of one solution - just to pregenerate those offsets to some header file, that shall be included in the driver. The way how to do that is described here. But if possible I'd like to also avoid this one step, since it needs to compile in Code Composer Studio, that does not run cmake.
So how do I create CMake target to pregenerate those offsets? Or any other ideas?