在移植内核时,发现一个驱动使用数组十分巧妙。
一般地,操作CPU某一外设寄存,不是直接使用完整的地址,而是通过相对地址来访问。比如,访问定时器,首先参考手册定义好定时器基地址,然后再定义寄存器(如控制寄存器、读数据寄存器等)对于定时器基地址的偏移地址。在使用时,一般都是使用偏移地址的,这样十分方便、快捷。如果有七、八个定时器,只需要定义好一个基地址数组、一个偏移地址数组,通过不同的序号就能访问不同的地址。
本文的例子是从驱动中抽象出来的。一个芯片平台系列中,对于特定外设,大部分的地址是相同的,但还是有个别不同,为了适应不同的芯片,代码也要做些处理。比如,这个系列中有十个寄存器,但另一个系统只有五个,其中大部分是共用的,为了共用代码,把所有的寄存器用枚举类型对一个数组进行访问,从而得到实际的偏移地址。
这里的数组赋值形式是我之前没见过,下面的例子中foo_regs数组,如果转化成实际值,变成:
static const u32 foo_regs[] = { [0] = 0x00, [3] = 0x48, [2] = 0x3c,};
这种赋值的好处在于,可以根据不同的情况使用不同的下标、不同的值。比如,同样是控制寄存器FOO_CTRL_REG,有的平台是0x3c,有的是0x3f,这样就可以通过不同的数组区别开来,而对外的访问接口,可以做到统一。
完整示例代码如下:
#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: %x\n", 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: %x\n", reg, tmp);
}
int main(void)
{
foo_read_reg(0x80084000, FOO_COUNTER_REG);
foo_write_reg(0x80084000, FOO_COUNTER_REG, FOO_START);
return 0;
}
运行结果:
3: 80084048
3: 8008404f
从这里学习到一个数组的赋值的方法,即实际赋值可以按不同的顺序(只要是合法的下标),而不是仅限于从0~N这样的顺序去赋值。