Chinaunix首页 | 论坛 | 博客
  • 博客访问: 70752
  • 博文数量: 6
  • 博客积分: 175
  • 博客等级: 入伍新兵
  • 技术积分: 99
  • 用 户 组: 普通用户
  • 注册时间: 2011-05-30 23:28
文章分类
文章存档

2012年(2)

2011年(4)

我的朋友

分类: LINUX

2012-02-16 10:21:17

参考资料:

  • Linux Kernel Source
作者:agan
联系邮箱 beswipe@yahoo.com.cn
racer.blog.chinaunix.net

转载须注明出处!

在编写设备驱动的时候,为什么有些设备地址可以直接拿来使用,而有些需要先映射才可使用。以下是我Track代码的一些经验。

1. 内存映射的几种情况。

第一种情况
----------

在平台的Linux启动过程中,平台底层源码会初始化一个IOTABLE。
通常的调用堆栈如下: 
machine_XXX.map_io()-> 
iotable_init() -> 
arch/arm/mm/mmu.c:alloc_init_section()
这里会把一组平台相关的物理地址(比如总线地址、设备基地址)映射到一组固定虚拟地址上,这组虚拟地址在整个内核空间可见,且从来不会被UNMAP。这个过程一般在平台初始化过程中被调用。平台厂商一般出厂前都会定义好自己的iotable,因此很多时候访问设备并不需要用户自己去做内存映射。

第二种情况
----------
驱动通过ioremap_XXX()(定义位于"arch/arm/include/asm/io.h")把物理地址映射成为虚拟地址。通过阅读代码'arch/arm/mm/ioremap.c:__arm_ioremap_pfn()',发现其流程大致如下:首先通过'get_vm_area(size, VM_IOREMAP)'获取一个空闲的虚拟内存区域,然后通过remap_area_section()把物理地址映射到这片内存区域。 对比mmu.c:alloc_init_section()ioremap.c:remap_area_sections(),发现他们的工作内容基本一致,唯一的区别就是每次ioremap返回的虚拟地址,是随机不固定的。

第三种情况
----------

在NOMMU的情况下,物理地址就是虚拟地址,"arm/arm/mm/nommu.c:__arm_ioremap()",此函数直接返回物理地址。

2. Samsung 6410 Kernel (linux-2.6.28), 映射连接在Xm0CSn1上的DM9000网卡基地址。
mach-smdk6410.c:smdk6410_map_io(smdk6410_iodesc) ->
plat-s3c64xx/cpu.c:s3c64xx_init_io():

iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc));
iotable_init(mach_desc, size);

其中,s3c_iodesc数组包含了6410平台基本上所有模块的基地址映射,mach_desc则对应DMP9000的定义:

 struct map_desc smdk6410_iodesc[] = {
.virtual = (u32)S3C64XX_VA_DM9000,
.pfn = __phys_to_pfn(S3C64XX_PA_DM9000),
.length = S3C64XX_SZ_DM9000,
.type = MT_DEVICE,
},
};

后续的DM9000驱动如何使用此虚拟地址,请看:

plat-s3c64xx/devs.c:
dm9000_resources_cs1

net/dm9000_con201.c:
if (pdev->num_resources == 2) {
base = pdev->resource[0].start;
if (!request_mem_region(base, 4, ndev->name)) {
ret = -EBUSY;
goto out;
}

ndev->base_addr = base;
ndev->irq = pdev->resource[1].start;
db->io_addr = (void __iomem *)base;
db->io_data = (void __iomem *)(base + DM9000_CMD);
}

可以看到,在iotable_init后,驱动再无映射同一物理地址。顺便提一下,此平台并没有提供自己的ioremap函数。它一直在使用arm平台的默认映射函数,其定义位于'arch/arm/include/asm/io.h'。

3. OMAP3530 Kernel 'linux-omap-pm-2.6.38' 的ioremap函数实现。

查看源代码‘arch/arm/mach-omap2/io.c’:

iotable_init(omap34xx_io_desc, ARRAY_SIZE(omap34xx_io_desc));
// Map buses and devices base physical address:

.virtual = L3_34XX_VIRT,
.pfn = __phys_to_pfn(L3_34XX_PHYS),

.virtual = ...
.pfn = __phys_to_pfn(L4_34XX_PHYS),
.pfn = __phys_to_pfn(OMAP34XX_GPMC_PHYS),
.pfn = __phys_to_pfn(OMAP343X_SMS_PHYS),
.pfn = __phys_to_pfn(OMAP343X_SDRC_PHYS),
.pfn = __phys_to_pfn(L4_PER_34XX_PHYS),
.pfn = __phys_to_pfn(L4_EMU_34XX_PHYS),
.pfn = __phys_to_pfn(ZOOM_UART_BASE),

这里基本上映射了omap3530片內所有模块的设备基地址。后续ioremap的调用堆栈如下:

arch/arm/include/asm/io.h:250:#define ioremap_nocache(cookie,size) __arch_ioremap((cookie), (size), MT_DEVICE)
arch/arm/plat-omap/include/plat/io.h:298:#define __arch_ioremap omap_ioremap
arch/arm/plat-omap/io.c: void __iomem *omap_ioremap(unsigned long p, size_t size, unsigned int type)
if (BETWEEN(p, L3_34XX_PHYS, L3_34XX_SIZE))
return XLATE(p, L3_34XX_PHYS, L3_34XX_VIRT);
// #define XLATE(p,pst,vst) ((void __iomem *)((p) - (pst) + (vst)))
由此,此平台实现了自己的ioremap函数,它首先会检查需要映射的物理地址是否已经映射,如此则返回在iotable_init中已经映射了的虚拟地址。此举至少能节约系统的虚拟地址空间。

本文是作者原创,如有错误请指正!
阅读(11087) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~