1、为什么需要映射
在内核启动过程中会开启MMU,建立虚拟映射表,以后内核使用的都是虚拟地址。但是我们查询数据手册得到I/O寄存器地址都是物理地址,于是需要将物理地址转换到虚拟地址,这样才能在内核空间去访问I/O寄存器。物理地址转换到虚拟地址就叫做映射,映射分为静态映射和动态映射。
2、动态映射和静态映射的比较
(1)建立映射的时机:静态映射是在内核启动过程中就建立好,并且在整个内核的生命周期都存在;动态映射是在需要的时候才进行,使用完后要解除映射; (2)占用资源:静态映射是在内核启动就建立,所以会一直占用地址资源;动态映射只在映射的时候才占用地址资源,解除映射后就不再占用地址资源; (3)访问速度:静态映射因为是一开始就映射好的,所以访问速度快;动态映射使用的时候要向申请再映射,所以访问速度稍慢一点; (4)一般会把常访问的I/O地址设置成静态映射,因为访问速度更快;但是不能把太多物理地址设置成静态映射,因为静态映射会一直占用资源;
3、静态映射
3.1、struct machine_desc结构体
每个开发板都对应一个struct machine_desc结构体,因为嵌入式设备都是高度定制的,结构体里是针对该开发板的描述。其中成员变量map_io是个函数指针,每个开发板的静态映射就是该函数指针对应的函数完成的。struct machine_desc结构体的详细介绍见博客:《内核启动过程中机器码的确定》。
3.2、静态映射的建立过程
start_kernel
setup_arch
paging_init
devicemaps_init
if (mdesc->map_io)
mdesc->map_io();
在devicemaps_init函数里去调用struct machine_desc结构体的map_io函数指针,也就是每个开发板特定的静态映射建立函数。从上面的代码还可以知道,静态映射不是必须的,在添加开发板对应的struct machine_desc结构体时,可以将map_io函数指针赋值为NULL,表示此开发板不需要静态映射。
3.3、struct map_desc结构体
{
unsigned long virtual;
unsigned long pfn;
unsigned long length;
unsigned int type;
};
struct map_desc结构体是用来描述如何建立静态映射的,需要指明物理地址、映射后的虚拟地址、要映射的长度、映射的资源类型;其中物理地址传的是页帧号,因为内核建立的虚拟映射是以叶为单位的。
3.4、以X210开发板的map_io函数为例
#define S3C_ADDR_BASE (0xFD000000)
#define S3C_ADDR(x) (S3C_ADDR_BASE + (x))
#define S5P_VA_CHIPID S3C_ADDR(0x00700000)
#define S5P_VA_GPIO S3C_ADDR(0x00500000)
static struct map_desc s5p_iodesc[] __initdata = {
{
.virtual = (unsigned long)S5P_VA_CHIPID,
.pfn = __phys_to_pfn(S5P_PA_CHIPID),
.length = SZ_4K,
.type = MT_DEVICE,
}, {
.virtual = (unsigned long)S3C_VA_SYS,
.pfn = __phys_to_pfn(S5P_PA_SYSCON),
.length = SZ_64K,
.type = MT_DEVICE,
}, {
.virtual = (unsigned long)S3C_VA_UART,
.pfn = __phys_to_pfn(S3C_PA_UART),
.length = SZ_4K,
.type = MT_DEVICE,
}, {
.virtual = (unsigned long)VA_VIC0,
.pfn = __phys_to_pfn(S5P_PA_VIC0),
.length = SZ_16K,
.type = MT_DEVICE,
}, {
.virtual = (unsigned long)VA_VIC1,
.pfn = __phys_to_pfn(S5P_PA_VIC1),
.length = SZ_16K,
.type = MT_DEVICE,
}, {
.virtual = (unsigned long)S3C_VA_TIMER,
.pfn = __phys_to_pfn(S5P_PA_TIMER),
.length = SZ_16K,
.type = MT_DEVICE,
}, {
.virtual = (unsigned long)S5P_VA_GPIO,
.pfn = __phys_to_pfn(S5P_PA_GPIO),
.length = SZ_4K,
.type = MT_DEVICE,
},
};
void __init iotable_init(struct map_desc *io_desc, int nr)
{
int i;
for (i = 0; i < nr; i++)
create_mapping(io_desc + i);
}
void __init s5p_init_io(struct map_desc *mach_desc,
int size, void __iomem *cpuid_addr)
{
unsigned long idcode;
iotable_init(s5p_iodesc, ARRAY_SIZE(s5p_iodesc));
if (mach_desc)
iotable_init(mach_desc, size);
idcode = __raw_readl(cpuid_addr);
s3c_init_cpu(idcode, cpu_ids, ARRAY_SIZE(cpu_ids));
}
static void __init smdkv210_map_io(void)
{
s5p_init_io(NULL, 0, S5P_VA_CHIPID);
s3c24xx_init_clocks(24000000);
s3c24xx_init_uarts(smdkv210_uartcfgs, ARRAY_SIZE(smdkv210_uartcfgs));
}
3.4.1、函数调用顺序:
smdkv210_map_io
s5p_init_io
iotable_init
create_mapping
3.4.2、函数调用说明
(1)X210开发板的map_io函数是smdkv210_map_io,这是在构建struct machine_desc结构体时指定的,见博客:《内核启动过程中机器码的确定》。 (2)s5p_iodesc是struct map_desc类型的数组,每个struct map_desc类型的元素代表要进行静态映射的虚拟地址、物理地址已经映射的长度; (3)create_mapping函数是真正构建静态映射的函数,将对s5p_iodesc数组里的每个元素进行解析,构建静态映射;
3.4.3、如何添加静态映射
{
.virtual = (unsigned long)S5P_VA_GPIO,
.pfn = __phys_to_pfn(S5P_PA_GPIO),
.length = SZ_4K,
.type = MT_DEVICE,
},
(1)在s5p_iodesc结构体数组中多添加一个struct map_desc 结构体成员,里面说明要映射的物理地址和虚拟地址; (2)以s5p_iodesc结构体数组的最后一个成员为例,就是将物理地址S5P_PA_GPIO(0xE0200000)映射到虚拟地址S5P_VA_GPIO(0xFD500000)。物理地址就是从数据手册里查到的地址,映射后就可以在操作系统中用虚拟地址去访问那些物理地址。
4、动态映射
4.1、涉及的函数
#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name), 0)
#define ioremap(cookie,size) __ioremap(cookie,size,0)
#define iounmap(cookie) __iounmap(cookie)
#define release_mem_region(start,n) __release_region(&iomem_resource, (start), (n))
4.2、函数的调用次序
(1)request_mem_region:向内核申请物理地址资源; (2)ioremap:将物理地址映射成虚拟地址; (3)iounmap:解除物理地址到虚拟地址的映射; (4)release_mem_region:通知内核释放物理地址;
4.3、示例代码
#define GPJ0CON_PA 0xe0200240
#define GPJ0DAT_PA 0xe0200244
unsigned int *pGPJ0CON;
unsigned int *pGPJ0DAT;
if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))
return -EINVAL;
if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON"))
return -EINVAL;
pGPJ0CON = ioremap(GPJ0CON_PA, 4);
pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
iounmap(pGPJ0CON);
iounmap(pGPJ0DAT);
release_mem_region(GPJ0CON_PA, 4);
release_mem_region(GPJ0DAT_PA, 4);
|