在移植内核时,发现一个驱动使用数组十分巧妙。
一般地,操作CPU某一外设寄存,不是直接使用完整的地址,而是通过相对地址来访问。比如,访问定时器,首先参考手册定义好定时器基地址,然后再定义寄存器(如控制寄存器、读数据寄存器等)对于定时器基地址的偏移地址。在使用时,一般都是使用偏移地址的,这样十分方便、快捷。如果有七、八个定时器,只需要定义好一个基地址数组、一个偏移地址数组,通过不同的序号就能访问不同的地址。
本文的例子是从驱动中抽象出来的。一个芯片平台系列中,对于特定外设,大部分的地址是相同的,但还是有个别不同,为了适应不同的芯片,代码也要做些处理。比如,这个系列中有十个寄存器,但另一个系统只有五个,其中大部分是共用的,为了共用代码,把所有的寄存器用枚举类型对一个数组进行访问,从而得到实际的偏移地址。
这里的数组赋值形式是我之前没见过。 下面的例子中foo_regs数组,如果转化成实际值,变成:
1
| static const u32 foo_regs[] = { [0] = 0x00, [3] = 0x48, [2] = 0x3c,};
|
这种赋值的好处在于,可以根据不同的情况使用不同的下标、不同的值。比如,同样是控制寄存器FOO_CTRL_REG,有的平台是0x3c,有的是0x3f,这样就可以通过不同的数组区别开来,而对外的访问接口,可以做到统一。
完整示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| #include <stdio.h> #include <stdlib.h> #include <string.h>
#define u32 unsigned int
enum { FOO_ID_REG = 0, FOO_SYS_STAT_REG, FOO_CTRL_REG, FOO_COUNTER_REG, };
static const u32 foo_regs[] = { [FOO_ID_REG] = 0x00, [FOO_COUNTER_REG] = 0x48, [FOO_CTRL_REG] = 0x3c, };
static const u32 bar_regs[] = { [FOO_ID_REG] = 0x00, [FOO_CTRL_REG] = 0x3f, [FOO_COUNTER_REG] = 0x4f, };
#define FOO_START (1 << 0) #define FOO_STOP (1 << 1)
static u32 foo_read_reg(unsigned int timer, u32 reg) { u32 tmp = 0;
tmp = (timer + (foo_regs[reg] & 0xff)); printf("%x: %xn", reg, tmp); }
static u32 foo_write_reg(unsigned int timer, u32 reg, u32 value) { u32 tmp = 0;
tmp = (timer + (bar_regs[reg] & 0xff)); printf("%x: %xn", reg, tmp); }
int main(void) { foo_read_reg(0x80084000, FOO_COUNTER_REG); foo_write_reg(0x80084000, FOO_COUNTER_REG, FOO_START);
return 0; }
|
运行结果:
从这里学习到一个数组的赋值的方法,即实际赋值可以按不同的顺序(只要是合法的下标),而不是仅限于从0~N这样的顺序去赋值。
2019.4.9中午的PS:
本文所述的仅是为了不一一写出数据索引而使用的一个方法,如果索引非常大,则本文方法不合适。
李迟记于2014年2月28日