Структурные смещения во встроенной сборке - PullRequest
0 голосов
/ 07 ноября 2018

Я работаю над проектом, где некоторая служба прерываний должна обрабатываться на ассемблере. Функция обработчика вызывается из обертки вектора прерывания. Тело обработчика записывается на ассемблере и получает единственный (указатель) параметр в конкретном регистре.

Целью кода является 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 для некоторого определенного смещения. Содержимое по этому адресу добавляется в ПК, поэтому происходит переход к определенной метке (в зависимости от того, какой флаг прерывания установлен). Это рекомендуемое использование, как описано TI в руководстве пользователя , стр. 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;
...
}
1010 * * и
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;
...
}

Теперь проблема.

  • смещения не известны до времени компиляции
  • Я не могу передать ассемблеру какое-либо смещение (s, m)
  • смещения зависят от используемой модели памяти (размер указателей 16 бит или 32 бита)
  • смещения зависят от размера первого члена обеих структур, и этот размер зависит от определений препроцессора (1 указатель или 4 указателя)
  • смещения не могут быть предварительно вычислены, потому что каждый компилятор добавляет некоторое выравнивание и отступ к структуре первого члена
  • первый участник должен быть первым (изменение порядка не допускается)
  • Компилятор TI не поддерживает передачу переменных времени компиляции во встроенный код сборки

Цель:

  • поддержка обоих компиляторов
  • не дублировать код, не исправлять смещения
  • если возможно, избегайте извлечения всего обработчика в файл asm и включая заголовки через .cdecls (или #include в случае gcc). Оба компилятора обрабатывают в том числе заголовки 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, и я могу придумать одно решение - просто сгенерировать эти смещения в некоторый заголовочный файл, который должен быть включен в драйвер. Способ как это сделать описан здесь . Но, если возможно, я бы также хотел избежать этого шага, так как он должен компилироваться в Code Composer Studio, который не запускает cmake.

Итак, как мне создать цель CMake для создания этих смещений? Или есть другие идеи?

Ответы [ 2 ]

0 голосов
/ 09 ноября 2018

Спасибо всем, особая благодарность @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 инструкций для выполнения обработчика, но добавление IV к ПК нарушает конвейер. Здесь у нас 13 инструкций, поэтому эффективность практически равна.

Кстати, фактический коммит здесь .

0 голосов
/ 07 ноября 2018

Для констант, которые доступны в числовой форме при запуске препроцессора C, вы можете использовать макросы #define, чтобы привести их в строку и объединить со строкой inline-asm, например, asm("blah blah " stringify(MYCONST) "\nblah blah");, но это не будет работать для offsetof, что требует правильного компилятора для вычисления его числа.


FIXME: это не будет так легко работать при кросс-компиляции. Вам нужно будет проанализировать сгенерированный компилятором asm или вывести статические данные из .o. Оба эти варианта возможны как незначительные модификации этого метода, но довольно уродливы. Я оставлю этот ответ здесь на тот случай, если он будет полезен для случаев, когда не требуется кросс-компиляция.


Однако, поскольку вы пометили это cmake, у вас есть система сборки, которая может обрабатывать цепочку зависимостей. Вы можете написать программу, которая использует от offsetof до create a .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"
   );

Или используйте чистый ассм вместо голой функции, поскольку вы.

Используйте зависимости сборки CMake, чтобы любые файлы, для которых требуется struct_offsets.h, перестраивались при его изменении.

...