Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Struct offsets in inline assembly

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?

like image 809
mutant-industries Avatar asked Dec 11 '25 14:12

mutant-industries


1 Answers

Thanks to everyone, special thanks to @CL. I've been stuck with the thought that this has to be done in assembler for so many reasons and that I only need to get those offsets somehow. The solution is simple:

static void _shared_vector_handler(Timer_driver_t *driver) {
    uint16_t interrupt_source;
    Timer_channel_handle_t *handle;

    if ( ! (interrupt_source = hw_register_16(driver->_IV_register))) {
        return;
    }

    handle = *((Timer_channel_handle_t **) 
        (((uintptr_t)(&driver->_CCR0_handle)) + (interrupt_source * _POINTER_SIZE_ / 2)));

    (*handle->_handler)(handle->_handler_param);
}

translated to assembler (TI compiler, memory model large):

        _shared_vector_handler():
011ef6:   4C1F 0008           MOV.W   0x0008(R12),R15
011efa:   4F2F                MOV.W   @R15,R15
011efc:   930F                TST.W   R15
011efe:   240D                JEQ     (0x1f1a)
231         (*handle->_handler)(handle->_handler_param);
011f00:   F03F 3FFF           AND.W   #0x3fff,R15
011f04:   025F                RLAM.W  #1,R15
011f06:   4F0F                MOV.W   R15,R15
011f08:   00AC 000C           ADDA    #0x0000c,R12
011f0c:   0FEC                ADDA    R15,R12
011f0e:   0C0F                MOVA    @R12,R15
011f10:   0F3C 003E           MOVA    0x003e(R15),R12
011f14:   00AF 003A           ADDA    #0x0003a,R15
011f18:   0F00                BRA     @R15
        $C$L12:
011f1a:   0110                RETA    

The original assembler takes 7 instructions to execute the handler, but the add-IV-to-PC breaks the pipeline. Here we have 13 instructions, therefore the effeciency is almost equal.

BTW the actual commit is here.

like image 101
mutant-industries Avatar answered Dec 13 '25 05:12

mutant-industries



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!