分类: LINUX
2015-03-19 14:31:41
在内核访问设备之前,首先需要将设备所处的物理地址映射到虚拟地址空间。我们一般使用两种方式。一种是动态映射的方式,利用ioremap函数进行映射, iounmap进行释放来完成的;一种是静态映射的方式。内存静态映射分3个层次:
开发板的层次(如:声卡,网卡和开发板相关的部分)
最小系统的层次(系统必需的几个,如GPIO,IRQ,MEMCTRL,UART)
其他系统的层次(不影响开机的部分,如:usb,lcd,adc)
在setup_arch函数中会调用paging_init函数,调用形式如下:
paging_init(&meminfo, mdesc)
其中meminfo中存放内存信息,正如前面所讲到的,U-Boot tag列表通过在内存标记将内存信息传给内核,在start_kernel中调用setup_arch函数,setup_arch函数会调用setup_archparse_tag_mem32()函数对内存标记进行处理,根据内存标记定义内存的起始地址、长度,在全局变量meminfo中增加内存的描述信息。以后内核就可以通过meminfo结构了解开发板的内存信息。
mdesc就是前面lookup_machine_type函数返回的machine_dec的结构。对于s3c2410来说,这个结构在arch/arm/mach-s3c2410/mach-smdk2410.c中(对于s3c6410来说,这个结构在arch/arm/mach-s3c6410/mach-smdk6410.c中)。代码如下:
/*向系统注册一个machine_desc的对象*/
MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch
* to SMDK2410 */
/* Maintainer: Jonas Dietsche */
/* Maintainer: Samsung Electronics */
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C_SDRAM_PA + 0x100,
/*init_irq在start_kernel() --> init_IRQ() --> init_arch_irq() 被调用*/
.init_irq = s3c24xx_init_irq,
/*map_io 在setup_arch() --> paging_init() --> devicemaps_init()*/
.map_io = smdk2410_map_io,
/*.init_machine 在 arch/arm/kernel/setup.c 中被 customize_machine 调用,放在 arch_initcall() 段里面,会自动按顺序被调用 start_kernel,参考 init/main.c*/
.init_machine = smdk2410_init,
.timer = &s3c24xx_timer,
MACHINE_END
在mach-smdk2410.c中先通过MACHINE_START()定义了machine_desc的变量,其中注册了smdk2410_map_io(), s3c2410_init_irq(), smdk2410_init()这3个回调函数. 这3个回调函数会在系统起来的时候setup_arch()里面逐个调用来进行开发板虚实地址映射, 中断初始化, clock初始化,片上设备的注册等操作,后面会逐一讲到。
paging_init函数在arch/arm/mm/mmu.c中定义,在这个函数它会调用同文件中的函数devicemaps_init(),而在devicemaps_init()中会调用mdesc->map_io函数,对于s3c2410就是调用smdk2410_map_io,而smdk2410_map_io-> s3c24xx_init_io(smdk2410_iodesc, ARRAY_SIZE(smdk2410_iodesc)),s3c24xx_init_io函数在arch/arm/plat-s3c24xx/cpu.c中实现,代码如下:
void __init s3c24xx_init_io(struct map_desc *mach_desc, int size)
{
unsigned long idcode = 0x0;
/* initialise the io descriptors we need for initialisation */
iotable_init(mach_desc, size); //针开发板的层次
iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc));// 最小系统的层次
if (cpu_architecture() >= CPU_ARCH_ARMv5) {
idcode = s3c24xx_read_idcode_v5();
} else {
idcode = s3c24xx_read_idcode_v4();
}
arm_pm_restart = s3c24xx_pm_restart;
s3c_init_cpu(idcode, cpu_ids, ARRAY_SIZE(cpu_ids)); /*其他系统的层次*/
}
1、 最小系统层次的内存静态映射(与CPU相关)
iotable_init内核提供,定义如下:
/* Create the architecture specific mappings*/
void __init iotable_init(struct map_desc *io_desc, int nr)
{
int i;
for (i = 0; i nr; i++)
create_mapping(io_desc + i);
}
由上知道,smdk2410_map_io最终调用iotable_init建立映射表。
iotable_init函数的参数有两个:一个是map_desc类型的结构体,另一个是该结构体的数量nr。这里最关键的就是struct map_desc。map_desc结构体定义如下:
/* include/asm-arm/mach/map.h */
struct map_desc {
unsigned long virtual; /* 映射后的虚拟地址 */
unsigned long physical;/* I/O资源物理地址所在的页帧号*/
unsigned long length; /* I/O资源长度*/
unsigned int type; /* I/O资源类型 */
};
create_mapping( )函数就是通过map_desc提供的信息创建线性映射表的。
这样的话我们就知道了创建I/O映射表的大致流程为:只要定义相应I/O资源的map_desc结构体,并将该结构体传给iotable_init函数执行,就可以创建相应的I/O资源到内核虚拟地址空间的映射表了。
我们来看看s3c2410是怎么定义map_desc结构体的(即上面iotable_init()函数内的s3c_iodesc)。在arch/arm/mach-s3c2410/cpu.c结构体中:
/* minimal IO mapping */
static struct map_desc s3c_iodesc[] __initdata = {
IODESC_ENT(GPIO),
IODESC_ENT(IRQ),
IODESC_ENT(MEMCTRL),
IODESC_ENT(UART)
};
IODESC_ENT宏如下:
#define IODESC_ENT(x) { (unsigned long)S3C24XX_VA_##x, S3C2410_PA_##x, S3C24XX_SZ_##x, MT_DEVICE }
展开后等价于:
static struct map_desc s3c_iodesc[] __initdata = {
{
.virtual = S3C24XX_VA_GPIO,
.physical = S3C24XX_PA_GPIO,
.length = S3C24XX_SZ_GPIO,
.type = MT_DEVICE
},
……
};
这里,我们可以比较清晰看到GPIO被静态映射的过程。由于我们在前面的静态映射中已经做好了GPIO的映射,也就是我们写GPIO相关驱动的时候可以不需要映射直接。如下配置引脚的原因,例如s3c2410_gpio_cfgpin(xxx,xxx);
其实,s3c2410_gpio_cfgpin定义如下:
void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function)
{
void __iomem *base = S3C2410_GPIO_BASE(pin);
unsigned long mask;
unsigned long con;
unsigned long flags;
if (pin < S3C2410_GPIO_BANKB) {
mask = 1 << S3C2410_GPIO_OFFSET(pin);
} else {
mask = 3 << S3C2410_GPIO_OFFSET(pin)*2;
}
local_irq_save(flags);
con = __raw_readl(base + 0x00);
con &= ~mask;
con |= function;
__raw_writel(con, base + 0x00);
local_irq_restore(flags);
}
其中,比较关键的一个地方:
void __iomem *base = S3C2410_GPIO_BASE(pin);
这一行中,S3C2410_GPIO_BASE定义如下:
#define S3C2410_GPIO_BASE(pin) ((((pin) & ~31) >> 1) + S3C24XX_VA_GPIO)
至此,GPIO的静态映射就看得很明白了。
2 开发板的层次的静态映射
开发板的层次的静态映射和最小系统层次的静态映射相同,都是通过iotable_init函数来实现的。只是传递的参数值不相同。最小系统层次iotable_init()函数map_desc结构体参数为s3c_iodesc,定义在arch/arm/mach-s3c2410/cpu.c文件中。开发板的层次iotable_init()函数map_desc结构体参数为mach_desc,,定义在arch/arm/mach-s3c2410/mach_smdk2410.c文件中。定义如下:
static struct map_desc smdk2410_iodesc[] __initdata = {
/*自己添加*/
};
3 其他外设的静态映射(其他系统的层次)
在s3c24xx_init_io()函数中,除了iotable_init()以外,还会在最后调用s3c_init_cpu函数,该函数会调用cpu->map_io()函数。
而CPU的这个map_io在arch/arm/mach-s3c2410/cpu.c里面定义如下:
static struct cpu_table cpu_ids[] __initdata = {
{
.idcode = 0x32410000,
.idmask = 0xffffffff,
.map_io = s3c2410_map_io,
.init_clocks = s3c2410_init_clocks,
.init_uarts = s3c2410_init_uarts,
.init = s3c2410_init,
.name = name_s3c2410
},
...
}
s3c2410_map_io -> s3c24xx_init_io -> s3c2410_map_io
再查看s3c2410_map_io(),函数代码如下:
void __init s3c2410_map_io(void)
{
s3c24xx_gpiocfg_default.set_pull = s3c_gpio_setpull_1up;
s3c24xx_gpiocfg_default.get_pull = s3c_gpio_getpull_1up;
iotable_init(s3c2410_iodesc, ARRAY_SIZE(s3c2410_iodesc));//*其他系统的层次*/
}
接下来看结构s3c2410_iodesc [arch/arm/mach-s3c2410/s3c2410.c],代码如下,
/* Initial IO mappings */
static struct map_desc s3c2410_iodesc[] __initdata = {
IODESC_ENT(USBHOST),
IODESC_ENT(USBDEV),
IODESC_ENT(CLKPWR),
IODESC_ENT(LCD),
IODESC_ENT(TIMER),
IODESC_ENT(ADC),
IODESC_ENT(WATCHDOG),
};
赫然发现IODESC_ENT(TIMER)这一行,结合之前GPIO的类似分析,IODESC_ENT宏如下:
#define IODESC_ENT(x) { (unsigned long)S3C24XX_VA_##x, S3C2410_PA_##x, S3C24XX_SZ_##x, MT_DEVICE }
至此,TIMER, USBHOST,USBDEV,lcd,adc,watchdog等的静态映射都看得很明白了。
S3C24XX_VA_CLKPWR -->__phys_to_pfn(S3C24XX_PA_CLKPWR), size 为
S3C24XX_SZ_CLKPWR
S3C24XX_VA_LCD --> __phys_to_pfn(S3C24XX_PA_LCD), size 为 S3C24XX_SZ_LCD
S3C24XX_VA_TIMER --> __phys_to_pfn(S3C24XX_PA_ TIMER), size 为 S3C24XX_SZ_ TIMER
S3C24XX_VA_WATCHDOG --> __phys_to_pfn(S3C24XX_PA_ WATCHDOG), size 为 S3C24XX_SZ_ WATCHDOG
如果我们要加入我们自己的映射,可以在smdk2410_iodesc[]和s3c2410_iodesc[]添加。以后如果某个资源没有映射也可以在相应的驱动种用ioremap()来动态映射(很多驱动都是这么做的)
注:经静态映射后,在设备驱动中访问的I/O内存时,直接在map_desc中该段的虚拟地址上加上相对偏移即可,不需要使用ioremap。