linux内核函数 ioremap()的原理及意义

940阅读 0评论2016-02-24 xitry
分类:LINUX

操作系统:ubuntu10.04



1,ioremap


void * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
参数:
phys_addr
:要映射的起始的IO地址;

size
:要映射的空间的大小;

flags
:要映射的IO空间的和权限有关的标志;

功能: 将一个IO地址空间映射到内核的虚拟地址空间上去,便于访问

实现:对要映射的IO地址空间进行判断,PCI/ISA地址不需要重新映射,也不允许用户将IO地址空间映射到正在使用的RAM中,最后申请一 个 vm_area_struct结构,调用remap_area_pages填写页表,若填写过程不成功则释放申请的vm_area_struct空 间;

意义:
比如isa设备和pci设备,或者是fb,硬件的跳线或者是物理连接方式决定了硬件上的内存影射到的cpu物理地址。
在内核访问这些地址必须分配给这段内存以虚拟地址,这正是__ioremap的意义所在 ,需要注意的是,物理内存已经"存在",无需alloc page给这段地址了.



为了使软件访问I/O内存,必须为设备分配虚拟地址.这就是ioremap的工作.这个函数专门用来为I/O内存区域分配虚拟地址(空间).对于直接映射的I/O地址ioremap不做任何事情(uClinux中是这么实现的??)
有了ioremap(iounmap),设备就可以访问任何I/O内存空间,不论它是否直接映射到虚拟地址空间.但是,这些地址永远不能直接使用(指物理地址),而要用readb这种函数.

根据计算机平台和所使用总线的不同,I/O 内存可能是,也可能不是通过页表访问的,通过页表访问的是统一编址(PowerPC),否则是独立编址(Intel)。如果访问是经由页表进行的,内核必须首先安排物理地址使其对设备驱动 程序可见(这通常意味着在进行任何 I/O 之前必须先调用 ioremap)。如果访问无需页表,那么 I/O 内存区域就很象 I/O 端口,可以使 用适当形式的函数读写它们。

不管访问 I/O 内存时是否需要调用 ioremap,都不鼓励直接使用指向 I/O 内存的指针。尽管(在“I/O 端口和 I/O 内存介绍过)I/O 内存在硬件一级是象普通 RAM 一样寻址的,但在“I/O 寄存器和常规内存中描述过的那些需要额外小心的情况中已经建议不要使用普 通指针。相反,使用包装的函数访问 I/O 内存,一方面在所有平台上都是安全的,另一方面,在可以直接对指针指向的内存区域执行操作的时候,该函数 是经过优化的


X86体系下的,CPU的物理地址和PCI总线地址共用一个空间。linux内核将3G-4G的虚拟地址固定映射到了物理地址的01G的地方。但是如果外围设备上的地址高于1G,例如某块PCI卡分配到了一个高于1G的地址,就需要调用ioremap来重新建立该物理地址(总线地址)和虚拟地址之间的映射。这个映射过程是这样的:在ioremap.c文件的__ioremap函数中首先对将来映射的物理地址进行检查,也就是不能重新映射640K-1M地址(由于历史的原因,物理地址640k1M空间被保留给了显卡),普通的 ram地也不能重新被映射。之后调用get_vm_area获得可用的虚拟地址,然后根这虚拟地址和欲映射的物理地址修改页表,之后内核就可以用这个虚拟地址来访问映射的物理地址了。

 

2,ioremap

offset: 物理空间(I/O设备上的一块物理内存)的起始地址
size:   物理空间的大小

给一段物理地址(起始地址offset)建立页表(地址映射)
--------------------------------------------------
static inline void __iomem * ioremap(unsigned long offset, unsigned long size)
{  
    return __ioremap(offset, size, 0);
}   



    Remap an arbitrary physical address space into the kernel virtual address space. Needed when the kernel wants to access high addresses directly.
--------------------------------------------------
void __iomem * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags){
    void __iomem * addr;
    struct vm_struct * area;
    unsigned long offset, last_addr;
    last_addr = phys_addr + size - 1;
    if (!size || last_addr < phys_addr)
        return NULL;
    if (phys_addr >= ISA_START_ADDRESS && last_addr < ISA_END_ADDRESS)
        return (void __iomem *) phys_to_virt(phys_addr);

如果需要映射的空间的起始地址phys_addr为于ZONE_NORMAL区,通过virt_to_page()进行映射
|------------------------------------------------------------------------------|
|   if (phys_addr <= virt_to_phys(high_memory - 1))                            |
|    {                                                                         |
|       char *t_addr, *t_end;                                                  |
|       struct page *page;                                                     |
|       t_addr = __va(phys_addr);                                              |
|       t_end = t_addr + (size - 1);                                           |
|       for(page = virt_to_page(t_addr); page <= virt_to_page(t_end); page++) -|
|           if(!PageReserved(page))                                            |
|               return NULL;                                                   |
|   }                                                                          |
|------------------------------------------------------------------------------|
    offset = phys_addr & ~PAGE_MASK;
    phys_addr &= PAGE_MASK;
    size = PAGE_ALIGN(last_addr+1) - phys_addr;

通过“非连续存储器区进行映射
get_vm_area()创建类型为vm_struct的新描述符
get_vm_area()首先调用kmalloc()为新描述符获得一个存储区;然后扫描类型为struct vm_struct的描述符表,查找一个可用线性地址空间(至少包含size+4096个地址)。
|----------------------------------------------------------|
|   area = get_vm_area(size, VM_IOREMAP | (flags << 20)); -|
|   if (!area)                                             |
|       return NULL;                                       |
|----------------------------------------------------------|



3,参考文件
1,


上一篇:深入分析request_irq的dev_id参数作用
下一篇:深入理解DebugInfo