在编写设备驱动程序的时候,如果要想把设备内存映射到用户空间,那需要我们实现mmap,通过看ldd3上面的介绍,对实现mmap有了一点了解.书上介绍主要是利用
int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr,unsigned long pfn, unsigned long size,pgprot_t prot) 函数或者
int io_remap_page_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long phys_addr, unsigned long size, pgprot_t prot)函数来实现,它们负责建立新的页表.这两个函数的区别是第一个函数是在参数pfn指向实际系统RAM的时候使用,而第二个函数是在phys_addr指向I/O内存的时候使用.对于ARM平台来说,系统内存和端口应该是统一编址的,所以两个函数是等价的.
还有另外一个函数就是nopage函数,是VMA结构体中可以填充的一个函数,在虚拟页没有所对应的物理页的时候,会调用此函数,来分配一个物理页给虚拟页.
int remap_page_range(unsigned long from, unsigned long phys_addr, unsigned long size, pgprot_t prot)
其中from是映射开始的虚拟地址。这个函数为虚拟地址空间from和from+size之间的范围构造页表;
phys_addr是虚拟地址应该映射到的物理地址;
size是被映射区域的大小;prot是保护标志。
remap_page_range的处理过程是对from到form+size之间的每一个页面,查找它所在的页目录和页表(必要时建立页表),清除页表项旧的内容,重新填写它的物理地址与保护域。
remap_page_range可以对多个连续的物理页面进行处理。<<Linux设备驱动程序>>指出,remap_page_range只能给予对保留的页和物理内存之上的物理地址的访问,当对非保留的页使用remap_page_range时,缺省的nopage处理控制映射被访问的虚地址处的零页。所以在分配内存后,就要对所分配的内存置保留位,它是通过函数mem_map_reserve实现的,它就是对相应物理页面置
PG_reserved标志位。
remap_pfn_range 通过你的页帧号来建立页表, 并映射到用户空间!
一般情况是你的驱动分配一块内存,然后在驱动的mmap中使用这块内存的物理地址转成页帧号, 再调用remap_pfn_range!
假设1你是通过kmalloc(),get_free_pages()等分配的,这种内存页是不能通过remap_pfn_range()映射出去的,要对每个页面调用SetPageReserverd()标记为"保留"才可以,virt_to_phys()函数只是得到其物理地址,remap_pfn_range()中的第三个参数是要求物理页便的"帧"号,即pfn,所以你的phys还要">> PAGE_SHIFT"操作
假设2你是通过vmalloc分配得来的,同上,不同的是要用vmalloc_to_pfn
3,用kmalloc,get_free_pages,vmalloc分配的物理内存页面最好还是不要用remap_pfn_page方法,建议使用VMA的nopage方法
4,对于这样的设备内存,最好对调用pgprot_nocached(vma->vm_page_prot)后传给remap_pfn_range,防止处理器缓存
--------------------------------------------------------
linux中对IO寄存器访问的方法:
static void __iomem *wdt_base;
18 wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
419 if (wdt_mem == NULL) {
420 dev_err(dev, "no memory resource specified\n");
421 return -ENOENT;
422 }
423
424 size = resource_size(wdt_mem);
425 if (!request_mem_region(wdt_mem->start, size, pdev->name)) {
426 dev_err(dev, "failed to get memory region\n");
427 return -EBUSY;
428 }
429
430 wdt_base = ioremap(wdt_mem->start, size);
431 if (wdt_base == NULL) {
432 dev_err(dev, "failed to ioremap() region\n");
433 ret = -EINVAL;
434 goto err_req;
435 }
可以参考 drivers/watchdog/s3c2410_wdt.c 在probe函数里
----------------------------
当用户空间进程调用
mmap
,将设备内存映射到它的地址空间时,系统通过创建一个表示该映射的
VMA
(虚拟内存区)作为响应。支持
mmap
的驱动程序需要帮助进程完成
VMA
的初始化。
该结构中的重要成员:
unsigned long vm_start;
unsigned long vm_end;
该
VMA
所覆盖的虚拟地址范围。
struct file * vm_file;
指向与该区域相关联的
file
结构体指针。
unsigned long vm_pgoff;
以页为单位,
文件中该区域的偏移量。
当映射一个文件或设备时,
它是该区域中被映射的第一页在文
件中的位置。
unsigned long vm_flags;
描述该区域的一套标志。驱动程序最感兴趣的标志是
VM_IO
和
VM_RESERVED
。
VM_IO
将
VMA
设置成一个内存映射
IO
区域。
VM_IO
会阻止系统将该区域包含在进程的核心转存中。
VM_RESERVED
告诉内存管理系统不要将该
VMA
交换出去;大多数设备映射中都设置该标志。
struct vm_operations_struct * vm_ops;
内核能调用的一套函数,用来对该内存区进行操作。它的存在表示内存区域是一个内核“对象”。
一个驱动程序只映射与其外围设备相关的一小段地址,
而不是映射全部地址的例子。
为了向用户空间
只映射部分内存的需要,
驱动程序只需要使用偏移量即可。
下面的代码揭示了驱动程序如何对起始于
物理地址
simple_region_start
(页对齐)
,
大小为
simple_region_size
字节的区域进行映射的工作过程:
(注:“外围设备”
即是设备文件。
vm_pgoff;
为要映射区域在“外围设备”文件中的偏移量(以
页为单位),
“外围设备”文件即是指外围设备的一段内存。这个偏移量即使相对于这段内存开始的
偏移量)
unsigned long off = vma->vm_pgoff << PAGE_SHIFT; //
以页为单位转为以字节为单位
unsigned long physical = simple_region_start + off; //
计算相对于文件开始的偏移
unsigned long vsize = vma->vm_end - vma->vm_start; //
要映射的区域大小
unsigned long psize = simple_region_size - off; //
偏移之后文件的剩余空间
if (vsize > psize) //
是否偏移之后文件的剩余空间不足以满足要映射的空间大小
return -EINVAL; /*
跨度过大
*/
remap_pfn_range(vma, vma_>vm_start, physical, vsize, vma->vm_page_prot);
(注:我认为
physical
应向右移
PAGE_SHIFT
,因为函数此处为页帧号)
关于函数
remap_pfn_range
和
io_remap_page_range
:
建立新页来映射物理地址的工作由
remap_pfn_range
和
io_remap_page_range
来处理
,
它们有下面的
原型
:
int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long
pfn, unsigned long size, pgprot_t prot);
int io_remap_page_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned l
ong phys_addr, unsigned long size, pgprot_t prot);
由这个函数返回的值常常是
0
或者一个负的错误值
.
让我们看看这些函数参数的确切含义
:
vma
页范围被映射到的虚拟内存区
virt_addr
重新映射应当开始的用户虚拟地址
.
这个函数建立页表为这个虚拟地址范围
从
virt_addr
到
virt_addr_size.
pfn
页帧号
,
对应虚拟地址应当被映射的物理地址
.
这个页帧号简单地是物理地址右
移
PAGE_SHIFT
位
.
对大部分使用
,
VMA
结构的
vm_paoff
成员正好包含你需要的值
.
这个函数影响
物理地址从
(pfn<<PAGE_SHIFT)
到
(pfn<<PAGE_SHIFT)+size.
size
正在被重新映射的区的大小
,
以字节
.
prot
给新
VMA
要求的
"protection".
驱动可
(
并且应当
)
使用在
vma->vm_page_prot
中找到的值
.