Проблема компиляции структурных битовых полей в 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 больше, чем требуется.