Проблема компиляции структурных битовых полей в C

Используемая архитектура: Xtensa-LX6

У меня есть следующее определение:

      volatile typedef struct{
    union{
        struct{
            uint32_t FIELD1:16;
            uint32_t FIELD2:12;
            uint32_t FIELD:4;
        };
        uint32_t _;
    }REGISTER_1; //0x00 offset, 4bytes width
}registers_t;
static spi_t* HARDWARE = (registers_t *)0x3FF43000;

Тогда у меня есть строчка где-то в коде:

      SPI[2]->USER2.USR_COMMAND_VALUE = 0b10101u;

Эта строка компилируется xtensa gcc в следующий ассемблерный код (улучшенный):

      HARDWARE:
    .word   1073102848

THE_LINE_OF_CODE:
    .literal .LC120, HARDWARE ; .LC120 now holds the address of HARDWARE - 0x3FF43000
    .align  4                 ; assembler directive
    l32r    a2, .LC120        ; reg_a2 <= HARDWARE
    l32i.n  a2, a2, 0         ; reg_a2 <= memory_at(reg_a2) //reg_2a <= 0x3FF43000
    movi.n  a3, 0b10101       ; reg_a3 <= 0b10101
    memw                      ; wait for memory operations to finish
    s16i    a3, a2, 0         ; memory_at(reg_a2+0) <= a3 // *(0x3FF43000) = 0b10101

Как видите, ассемблер полностью проигнорировал остальные поля регистра, он просто предполагает, что будут записаны младшие 16 бит, и записывает туда 16-битное значение. Проблема в том, что регистры в архитектуре xtensa всегда имеют ширину 4 бита и выровнены таким образом. Таким образом, если мы запишем содержимое регистра в двоичной форме: REGISTER1: 0b----------------1111111111111111, часть '-' просто удаляется, не сохраняя своего значения. Реестр повреждается, и происходит лавина ошибок.

Этого можно избежать, вставив в объявление структуры:

          union{
        struct{
            uint32_t FIELD1:16;
            uint32_t FIELD2:12;
            uint32_t FIELD:4;
        }__attribute__((packed));
        uint32_t _;
    }REGISTER_1; //0x00 offset, 4bytes width

Теперь ассемблер знает, что мы ищем, и получившаяся сборка (украшена):

      HARDWARE:
    .word   1073102848

THE_LINE_OF_CODE:
    .literal .LC120, HARDWARE ; .LC120 now holds the address of HARDWARE - 0x3FF43000
    .literal .LC121, 0xFFFF0000 ; A bit mask for our portion
    .align  4                 ; assembler directive
    l32r    a2, .LC120        ; a2 <= HARDWARE
    l32i.n  a2, a2, 0         ; a2 <= memory_at(a2) //2a <= 0x3FF43000
    memw                      ; wait for memory operations to finish
    l32i.n  a4, a2, 0         ; a4 <= memory_at(a2 + 0 the struct offset)
    l32r    a3, .LC121        ; a3 <= 0xFFFF0000
    and a4, a4, a3            ; a4 <= a4 & a3 (zeroes the lower 16 bits we will be writing to, preserves rest)
    movi.n  a3, 0b10101       ; a3 <= 0b10101
    or  a3, a4, a3            ; a3 <= a4 | a3 (writes to the lower 16 bits)
    memw                      ; wait for memory operations to finish
    s32i.n  a3, a2, 0         ; memory_at(a2 + 0 the struct offset) <= a3

Задача решена! Отлично. Но..

НАСТОЯЩИЙ ВОПРОС НАЧИНАЕТСЯ ЗДЕСЬ:

В моем определении регистров может быть много структур:

      volatile typedef struct{
    union{
        struct{...}__attribute__((packed));
        uint32_t _;
    }REGISTER_1;
    union{
        struct{...};
        uint32_t _;
    }REGISTER_2;
    .
    .
    .
}registers_t;
static spi_t* HARDWARE = (registers_t *)0x3FF43000;

Как упаковать структуры так, чтобы выравнивание во всем typedef сохранялось, и каждая операция битового поля компилировалась правильно? Положив __attribute__((packed)) везде может работать, но нарушает выравнивание - в целом sizeof typedef больше, чем требуется.

0 ответов

Другие вопросы по тегам