内联汇编中的结构偏移量

时间:2018-11-06 23:09:39

标签: c assembly cmake cross-compiling msp430

我正在研究项目,其中一些中断服务必须在汇编器中处理。从中断向量包装器调用处理程序函数。处理程序主体是用汇编器编写的,它在特定的寄存器中接收单个(指针)参数。

代码目标是MSP430,并且必须同时使用MSP430-gcc和TI编译器进行编译。我已经有适用于MSP430-gcc的工作解决方案,它看起来像这样:

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)) :
);
}

翻译成英文:驱动程序结构包含一些特定偏移量的IV寄存器的地址。该地址上的内容已添加到PC,因此会跳到特定标签(取决于设置了哪个中断标志)。推荐使用此方法,如TI在user's guide,第653页中所述。所有标签的作用相同:它们从特定偏移量获取驱动程序结构中某些句柄的指针。该句柄再次具有一些特定的偏移量函数指针(中断服务处理程序)和指向某些参数的指针,这些参数将传递给处理程序。简短的结构:

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;
...
}

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;
...
}

现在是问题所在。

  • 偏移直到编译时才知道
  • 我无法将一些offsetof(s,m)传递给汇编器
  • 偏移量取决于所使用的内存模型(指针大小为16位或32位)
  • 偏移量取决于两个结构的第一个成员的大小,并且此大小取决于预处理器的定义(1个指针或4个指针)
  • 偏移量无法预先计算,因为每个编译器都会在第一个成员结构上添加一些对齐方式和填充
  • 第一位成员必须是第一位成员(不允许重新排序)
  • TI编译器不支持将编译时变量传递给内联汇编代码

目标:

  • 同时支持两个编译器
  • 不重复代码,不对偏移量进行硬编码
  • 如果可能,请避免将整个处理程序提取到asm文件中,并避免通过.cdecls(或在gcc情况下为#include)包含标头。两种编译器都以不同的方式处理C头文件,结构偏移量也以许多不同的方式定义,并且将需要对头文件进行一些不平凡的重构,我相信这几乎是不可能的。

当我使用TI编译器进行编译时,出现以下错误:

"../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

我的构建由CMake处理,我可以想到一种解决方案-只是将这些偏移量预先生成到某些头文件中,该文件应包含在驱动程序中。 here说明了该方法。但是如果可能的话,我也想避免这一步,因为它需要在Code Composer Studio中进行编译,因此不会运行cmake。

那么,如何创建CMake目标以预生成这些偏移量?还是其他想法?

2 个答案:

答案 0 :(得分:2)

感谢大家,特别感谢@CL。我一直认为必须在汇编器中执行此操作的原因有很多,我只需要以某种方式获得这些偏移即可。解决方案很简单:

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);
}

翻译成汇编程序(TI编译器,内存模型很大):

        _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    

原始汇编器需要7条指令来执行处理程序,但是add-IV-to-PC中断了管道。这里我们有13条指令,因此效率几乎相等。

顺便说一句,实际的提交是here

答案 1 :(得分:0)

对于在C预处理程序运行时以数字形式可用的常量,您可以使用7.8宏对它们进行内联并与inline-asm字符串(例如#define)进行连接,但这不会为asm("blah blah " stringify(MYCONST) "\nblah blah");工作,这要求编译器适当地将其评估为数字。


FIXME:这在交叉编译时不那么容易。您必须解析编译器生成的asm,或从offsetof 中转储静态数据,这两种方法都可以对此方法进行较小的修改,但是有点丑陋。如果在非交叉编译用例中有用,我将在此处保留此答案。


但是,由于您标记了此.o,因此您拥有一个可以处理一系列依赖关系的构建系统。 您可以编写一个程序,使用一些简单的cmake语句,使用offsetof来创建.h并创建具有这样内容的printf

// already stringified to simplify
// if you want them as numeric literals, leave out the double quotes and use a STR() macro
#define OFFSET_Timer_driver_t__CCR1_handle  "12"
#define OFFSET_Timer_driver_t__CCR2_handle  "16"
...

然后您可以#include "struct_offsets.h"在需要它的文件中,并将其用于内联汇编,例如

asm("insn " OFFSET_Timer_driver_t__CCR1_handle "\n\t"
    "insn blah, blah \n\t"
    "insn foo " OFFSET_Timer_driver_t__CCR2_handle "\n\t"
   );

或者,因为您在使用纯asm而不是裸函数。

使用CMake构建依赖项,以便需要更改的所有struct_offsets.h文件都可以重新构建。