内存映射-IO空间-ioremap-iounremap

内存映射-IO空间-ioremap-iounremap

1 )关于 IO 与内存空间: 函数

X86 处理器中存在着 I/O 空间的概念, I/O 空间是相对于内存空间而言的,它经过特定的指令 in out 来访问。端口号标识了外设的寄存器地址。 Intel 语法的 in out 指令格式为: spa

IN 累加器 ,{ 端口号 │DX} .net

OUT{ 端口号 │DX}, 累加器 设计

目前,大多数嵌入式微控制器如 ARM PowerPC 等中并不提供 I/O 空间,而仅存在内存空间。内存空间能够直接经过地址、指针来访问,程序和程序运行中使用的变量和其余数据都存在于内存空间中。 指针

即使是在 X86 处理器中,虽然提供了 I/O 空间,若是由咱们本身设计电路板,外设仍然能够只挂接在内存空间。此时, CPU 能够像访问一个内存单元那样访问外设 I/O 端口,而不须要设立专门的 I/O 指令。所以,内存空间是必须的,而 I/O 空间是可选的。 blog

2 inb outb 接口

Linux 设备驱动中,宜使用 Linux 内核提供的函数来访问定位于 I/O 空间的端口,这些函数包括: 进程

· 读写字节端口( 8 位宽) 内存

unsignedinb(unsignedport); 开发

voidoutb(unsignedcharbyte,unsignedport);

· 读写字端口( 16 位宽)

unsignedinw(unsignedport);

voidoutw(unsignedshortword,unsignedport);

· 读写长字端口( 32 位宽)

unsignedinl(unsignedport);

voidoutl(unsignedlongword,unsignedport);

· 读写一串字节

voidinsb(unsignedport,void*addr,unsignedlongcount);

voidoutsb(unsignedport,void*addr,unsignedlongcount);

·insb() 从端口 port 开始读 count 个字节端口,并将读取结果写入 addr 指向的内存; outsb() addr 指向的内存的 count 个字节连续地写入 port 开始的端口。

· 读写一串字

voidinsw(unsignedport,void*addr,unsignedlongcount);

voidoutsw(unsignedport,void*addr,unsignedlongcount);

· 读写一串长字

voidinsl(unsignedport,void*addr,unsignedlongcount);

voidoutsl(unsignedport,void*addr,unsignedlongcount);

上述各函数中 I/O 端口号 port 的类型高度依赖于具体的硬件平台,所以,只是写出了 unsigned

3 readb writeb:

在设备的物理地址被映射到虚拟地址以后,尽管能够直接经过指针访问这些地址,可是工程师宜使用 Linux 内核的以下一组函数来完成设备内存映射的虚拟地址的读写,这些函数包括:

· I/O 内存

unsignedintioread8(void*addr);

unsignedintioread16(void*addr);

unsignedintioread32(void*addr);

与上述函数对应的较早版本的函数为(这些函数在 Linux2.6 中仍然被支持):

unsignedreadb(address);

unsignedreadw(address);

unsignedreadl(address);

· I/O 内存

voidiowrite8(u8value,void*addr);

voidiowrite16(u16value,void*addr);

voidiowrite32(u32value,void*addr);

与上述函数对应的较早版本的函数为(这些函数在 Linux2.6 中仍然被支持):

voidwriteb(unsignedvalue,address);

voidwritew(unsignedvalue,address);

voidwritel(unsignedvalue,address);

4 )把 I/O 端口映射到 内存空间 ”:

void*ioport_map(unsignedlongport,unsignedintcount);

经过这个函数,能够把 port 开始的 count 个连续的 I/O 端口重映射为一段 内存空间 。而后就能够在其返回的地址上像访问 I/O 内存同样访问这些 I/O 端口。当再也不须要这种映射时,须要调用下面的函数来撤消:

voidioport_unmap(void*addr);

实际上,分析 ioport_map() 的源代码可发现,所谓的映射到内存空间行为其实是给开发人员制造的一个 假象 ,并无映射到内核虚拟地址,仅仅是为了让工程师可以使用统一的 I/O 内存访问接口访问 I/O 端口。

 

11.2.7 I/O 空间的映射

不少硬件设备都有本身的内存,一般称之为 I/O 空间。例如,全部比较新的图形卡都有几 MB RAM ,称为显存,用它来存放要在屏幕上显示的屏幕影像。

1 .地址映射

根据设备和总线类型的不一样, PC 体系结构中的 I/O 空间能够在三个不一样的物理地址范围之间进行映射:

1 )对于链接到 ISA 总线上的大多数设备

I/O 空间一般被映射到从 0xa0000 0xfffff 的物理地址范围,这就在 640K 1MB 之间留出了一段空间,这就是所谓的

2 )对于使用 VESA 本地总线( VLB )的一些老设备

这是主要由图形卡使用的一条专用总线: I/O 空间被映射到从 0xe00000 0xffffff 的地址范围中,也就是 14MB 16MB 之间。由于这些设备使页表的初始化更加复杂,所以已经不生产这种设备。

3 )对于链接到 PCI 总线的设备

I/O 空间被映射到很大的物理地址区间,位于 RAM 物理地址的顶端。这种设备的处理比较简单。

2 .访问 I/O 空间

内核如何访问一个 I/O 空间单元?让咱们从 PC 体系结构开始入手,这个问题很容易就能够解决,以后咱们再进一步讨论其余体系结构。

不要忘了内核程序做用于虚拟地址,所以 I/O 空间单元必须表示成大于 PAGE_OFFSET 的地址。在后面的讨论中,咱们假设 PAGE_OFFSET 等于 0xc0000000 ,也就是说,内核虚拟地址是在第 4G

内核驱动程序必须把 I/O 空间单元的物理地址转换成内核空间的虚拟地址。在 PC 体系结构中,这能够简单地把 32 位的物理地址和 0xc0000000 常量进行或运算获得。例如,假设内核须要把物理地址为 0x000b0fe4 I/O 单元的值存放在 t1 中,把物理地址为 0xfc000000 I/O 单元的值存放在 t2 中,就可使用下面的表达式来完成这项功能:

 

t1=*((unsignedchar*)(0xc00b0fe4));

t2=*((unsignedchar*)(0xfc000000));

 

在第六章咱们已经介绍过 , 在初始化阶段 , 内核已经把可用的 RAM 物理地址映射到虚拟地址空间第 4G 的最初部分。所以,分页机制把出如今第一个语句中的虚拟地址 0xc00b0fe4 映射回到原来的 I/O 物理地址 0x000b0fe4 ,这正好落在从 640K 1MB 的这段 “ISA 中。这正是咱们所指望的。

可是,对于第二个语句来讲,这里有一个问题,由于其 I/O 物理地址超过了系统 RAM 的最大物理地址。所以,虚拟地址 0xfc000000 就不须要与物理地址 0xfc000000 相对应。在这种状况下,为了在内核页表中包括对这个 I/O 物理地址进行映射的虚拟地址,必须对页表进行修改:这能够经过调用 ioremap() 函数来实现。 ioremap() vmalloc() 函数相似,都调用 get_vm_area() 创建一个新的 vm_struct 描述符,其描述的虚拟地址区间为所请求 I/O 空间区的大小。而后, ioremap() 函数适当地更新全部进程的对应页表项。

所以,第二个语句的正确形式应该为:

 

io_mem=ioremap(0xfb000000,0x200000);

t2=*((unsignedchar*)(io_mem+0x100000));

 

第一条语句创建一个 2MB 的虚拟地址区间,从 0xfb000000 开始;第二条语句读取地址 0xfc000000 的内存单元。驱动程序之后要取消这种映射,就必须使用 iounmap() 函数。

 

如今让咱们考虑一下除 PC 以外的体系结构。在这种状况下,把 I/O 物理地址加上 0xc0000000 常量所获得的相应虚拟地址并不老是正确的。为了提升内核的可移植性, Linux 特地包含了下面这些宏来访问 I/O 空间:

readb,readw,readl

分别从一个 I/O 空间单元读取 1 2 或者 4 个字节

writeb,writew,writel

分别向一个 I/O 空间单元写入 1 2 或者 4 个字节

memcpy_fromio,memcpy_toio

把一个数据块从一个 I/O 空间单元拷贝到动态内存中,另外一个函数正好相反,把一个数据块从动态内存中拷贝到一个 I/O 空间单元

memset_io

用一个固定的值填充一个 I/O 空间区域

对于 0xfc000000I/O 单元的访问推荐使用这样的方法:

io_mem=ioremap(0xfb000000,0x200000);

t2=readb(io_mem+0x100000);

使用这些宏,就能够隐藏不一样平台访问 I/O 空间所用方法的差别。
相关文章
相关标签/搜索