您的位置:首页 > 其它

[TZ]内存与IO访问(4)-IO内存静态映射

2015-12-10 10:46 411 查看
转载请注明原文地址:http://blog.csdn.net/ts_dchs/article/details/50246543

1 流程分析

(本节以s5pv210(ARM A8) of Linux 3.0.26为例)

Linux提供外设IO内存物理地址到Linux虚拟地址的静态映射。静态映射是指通过map_desc结构体静态创建I/O资源映射表。

struct map_desc
结构体包含虚拟地址,设备物理地址。常用于寄存器资源映射,这样静态映射之后在编写内核代码或驱动时就不需要再ioremap。系统启动时创建好静态映射,程序直接通过映射后的虚拟地址去访问它们。

先说启动时做初始化的工作。内核提供了一个重要的结构体[code]struct machine_desc
 ,内核通过machine_desc结构体来控制系统体系架构相关部分的初始化。为了初始化工作,包括map_io, init_irq, init_machine以及phys_io , timer成员等。

这里的map_io成员即内核提供给用户的创建外设I/O资源到内核虚拟地址静态映射表的接口函数。map_io成员函数会在系统初始化过程中Start_kernel -> setup_arch() –> paging_init() –> devicemaps_init()中被调用。结构体通过[code]MACHINE_START
宏初始化。这部分根据平台不同设计。

[code]//LinuxSrc/arch/arm/mach-s5pv210/mach-smdkv210.c
MACHINE_START(SMDKV210, "SMDKV210")
/* Maintainer: Kukjin Kim <kgene.kim@samsung.com> */
.boot_params    = S5P_PA_SDRAM + 0x100,
.init_irq       = s5pv210_init_irq,
.map_io         = smdkv210_map_io,
.init_machine   = smdkv210_machine_init,
.timer          = &s5p_timer,
MACHINE_END


在上面的代码中,
map_io
初始化为
smdkv210_map_io
code。如下:

//LinuxSrc/arch/arm/mach-s5pv210/mach-smdkv210.c
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));
s5p_set_timer_source(S5P_PWM2, S5P_PWM4);
}


在这个函数中包含我们自己定义的创建静态I/O映射表的函数,在移植的时候可能需要我们自己实现。

上代码中
s5p_init_io(NULL, 0, S5P_VA_CHIPID);
函数中代码如下:

[code]//LinuxSrc/arch/arm/plat-s5p/cpu.c
void __init s5p_init_io(struct map_desc *mach_desc,
int size, void __iomem *cpuid_addr)
{
unsigned long idcode;
/* initialize the io descriptors we need for initialization */
iotable_init(s5p_iodesc, ARRAY_SIZE(s5p_iodesc));/* register our io-tables */
if (mach_desc)
iotable_init(mach_desc, size);

idcode = __raw_readl(cpuid_addr);
s3c_init_cpu(idcode, cpu_ids, ARRAY_SIZE(cpu_ids));
}


其中的
iotale_init()
就是最终建立页映射的函数。代码如下:

[code]//LiuxSrc/arch/arm/mm/mmu.c
void __init iotable_init(struct map_desc *io_desc, int nr)
{
int i;
for (i = 0; i < nr; i++)
create_mapping(io_desc + i);
}//__


其中
create_mapping
函数就是通过多个[code]map_desc
提供的信息创建线性映射表的。

这个结构体内容的来源在LinuxSrc/arch/arm/mach-s5pv210/cpu.c中。

static struct map_desc s5pv210_iodesc[] __initdata
。形如:

[code]//LinuxSrc/arch/arm/mach-s5pv210/cpu.c
//这个数组描述了每个映射的信息
static struct map_desc s5pv210_iodesc[] __initdata = {
{
.virtual        = (unsigned long)S5P_VA_SYSTIMER,
.pfn            = __phys_to_pfn(S5PV210_PA_SYSTIMER),
.length         = SZ_4K,
.type           = MT_DEVICE,
}, {
.virtual        = (unsigned long)S5P_VA_GPIO,
//LinuxSrc/arch/arm/plat-s5p/include/plat/map-s5p.h 对用到的虚拟地址做了定义
//这个值即该I/O资源映射后的内核虚拟地址,创建映射表成功后,便可以在内核或驱动中直接通过该虚拟地址访问这个I/O资源。
//#define S5P_VA_GPIO             S3C_ADDR(0x02200000)
.pfn            = __phys_to_pfn(S5PV210_PA_GPIO),
//LinuxSrc/arch/arm/mach-s5pv210/include/mach/map.h 对用到的物理地址做了定义
//#define S5PV210_PA_GPIO                 0xE0200000
.length         = SZ_4K,
.type           = MT_DEVICE,
}, {
.virtual        = (unsigned long)VA_VIC0,
.pfn            = __phys_to_pfn(S5PV210_PA_VIC0),
.length         = SZ_16K,
.type           = MT_DEVICE,
},
...
}


其中的函数:

#define __phys_to_pfn(paddr)    ((unsigned long)((paddr) >> PAGE_SHIFT))


通过物理地址右移(除以)页大小,得到物理地址页号。

注意有时有的平台IO映射被分为多各部分,也就是说有多个
map_desc[]


有的平台通过宏填写
map_desc[]
。如s3c2410在结构体数组中有
IODESC_ENT(LCD),


这个查看源码,在得到结构体需要的虚拟地址VA时,使用了
S3C_ADDR
的宏:

//LinuxSrc/arch/arm/plat-samsung/include/plat/map-base.h
#define S3C_ADDR_BASE   0xF6000000

#ifndef __ASSEMBLY__
#define S3C_ADDR(x)     ((void __iomem __force *)S3C_ADDR_BASE + (x))
#else
#define S3C_ADDR(x)     (S3C_ADDR_BASE + (x))
#endif


如对于上面例子GPIO的静态映射:

//LinuxSrc/arch/arm/plat-s5p/include/plat/map-s5p.h
#define S5P_VA_GPIO             S3C_ADDR(0x02200000)


最终的虚拟地址的位置是虚基址加上OFFSET(0x02200000)

Source Code Src

http://lxr.oss.org.cn

2 一个简单例子 移植时来实现自己的静态映射

首先知道要进行静态映射的四个参数。物理地址是硬件定义好的,虚拟地址的使用需要避开冲突。

源码中
S3C_ADDR_BASE
是0xF6000000,注释中也提到:

Fit all our registers in at 0xF6000000 upwards, trying to use as little of the VA space as possible so vmalloc and friends have a better chance of getting memory.

这表示相距4G,保留了160M的映射空间吗?(之后有描述)

同时也说明这种映射的结果是在内核空间。

在声明map的文件中做映射准备,创建map_des,添加到数组,以便系统启动时遍历数组可以完成这个映射的建立。

定义宏XXX_SRAM_BASE物理地址0x30000000

static struct map_desc xxx_iodesc[] __initdata = {
...
{
.virtual = XXX_VA,
.pfn = __phys_to_pfn(XXX_PA),
.length = SZ_4K,
.type = MT_DEVICE
},
};


写Module直接测试设备(用户空间的应用程序无法访问设备)

在Module中直接访问地址(使用了一个网上的例子,原理类似)IO_ADDRESS宏的功能与S3C_ADDR_BASE类似。

//init函数中申请IO内存 -  request_mem_region
struct resource * ret = request_mem_region(SRAM_BASE, SRAM_SIZE, "SRAM Region");//检查内存可用,声明占有
test()
//下面是test()的内容
char str[] = "Hello/n";
void * sram_p;
sram_p = (void *)IO_ADDRESS (XXX_SRAM_BASE);
memcpy(sram_p, str, sizeof(str));
printk(sram_p);
printk("/n");
//exit中 - release_mem_region


或者自己写驱动程序让应用程序间接访问内核空间内存。

附加一些内存布局的内容来探索这部分映射的虚拟地址究竟在哪里:

首先是在VMALLOC_END之后,VMALLOC区域用于
vmalloc()
/
ioremap()


//LinuxSrc/arch/arm/include/asm/highmem.h
#define VMALLOC_END     0xF6000000UL


这恰好就是之前
S3C_ADDR_BASE
的来源。那么有多大的空间可以做映射呢?

在Menory文档中描述:

//LinuxSrc/Documentation/arm/memory.txt
VMALLOC_END     feffffff        Free for platform use, recommended.
VMALLOC_END must be aligned to a 2MB
boundary.


0xfeffffff - 0xf6000000 = 9*2^24 B = 144MB 这段空间可以用作mapping

notification

source: 《Linux设备驱动开发详解》(第二版),内容为读书笔记和网络资料,有些资料原始来源不详,分享为了方便自己和他人查阅。如有侵权请及时告知,对于带来的不便非常抱歉。转载请注明来源。个人所学有限,若有错误和不足还请不吝赐教,我会及时更正。Terrence Zhou.

http://blog.csdn.net/ts_dchs

reference

[1] 静态地址映射,http://www.cnblogs.com/qiaoge/archive/2012/04/23/2467052.html

[2] (最好教材)源码,http://lxr.oss.org.cn
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  arm 内存