Chinaunix首页 | 论坛 | 博客

分类: LINUX

2011-09-20 00:12:43

i/o方式:
每块接口卡(外设)都通过某种途径告诉系统:卡上有几个存储区间及I/O地址空间,每个区间多大,以及搁在在卡上的局部地址,都从0算起。因为区间 不与总线直接相连,在加电之处,总线上还访问不到这些区间,所以不会冲突。
系统知道了有多少外部设备,各自的存储区间以后,可以为这些区间分配“物理地址”。此“物理地址”实际上是一种逻辑地址,称为“总线地址”,因为这是cpu在总线上看到的地址。
所谓为外设分配地址就是分配其总线地址,并为之建立映射。
这就引出了PCI总线。
每个PCI设备或者接口卡上都有许多用于地址配置的寄存器,初始化时,通过这些寄存器来配置该设备的总线地址。完成配置之后,CPU就可以访问该设备的各项资源,就好像访问内存一样的了。
怎样完成配置呢?
PCI标准规定每个设备配置寄存器组最多可以有256字节连续空间,开头64空间的用途和格式是标准的,称为配置寄存器组的头部。头部分两种:0为一般的pci设备,1型表示PCI桥。起开头16个字节用途和格式固定:包括设备的种类,设备的性质,制造商。
在linux下通过cat/proc/pci可以查看所有pci设备的信息,这些来自寄存器。
为了节省空间,让所有设备的配置寄存器组都是用相同的地址,由所在总线的pci桥在访问时加上其他条件来区分具体读写。在i386结构中总线设计者保留了8个字节用于此目的.0xcf8-0xcff这8个字节构成2个32位寄存器。一个是地址寄存器,一个是数据寄存器。
当访问某个设备的某个配置寄存器时,cpu先往地址寄存器中写入目标地址,然后通过数据寄存器写入数据。写入的目标地址包括总线号,设备号,功能号,配置寄存器地址的综合地址。
主总线号固定为0,其余的由cpu枚举时分配。设备号为pci接口卡(插槽位置)。
.......
典型地,如X86处理器为外设专门实现了一个单独的地址空间,称为"I/O地址空间"或者"I/O端口空间",CPU通过专门的I/O指令(如X86的IN和OUT指令)来访问这一空间中的地址单元。

二、内存映射方式(Memory-mapped)
一般来说,在系统运行时,外设的I/O内存资源的物理地址是已知的,由硬件的设计决定。但是CPU通常并没有为这些已知的外设I/O内存资源的物 理地址预定义虚拟地址范围,驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内(通过页表),然后才能根据映射所得 到的核心虚地址范围,通过访内指令访问这些I/O内存资源。Linux在io.h头文件中声明了函数ioremap(),用来将I/O内存资源的物理地址 映射到核心虚地址空间(3GB-4GB)中,原型如下:

void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);

  iounmap函数用于取消ioremap()所做的映射,原型如下:

void iounmap(void * addr);

  这两个函数都是实现在mm/ioremap.c文件中。

  在将I/O内存资源的物理地址映射成核心虚地址后,理论上讲我们就可以象读写RAM那样直接读写I/O内存资源了。为了保证驱动程序的跨平台的可移植性,我们应该使用Linux中特定的函数来访问I/O内存资源,而不应该通过指向核心虚地址的指针来访问。如在x86平台上,读写I/O的函数如下所示:

#define readb(addr) (*(volatile unsigned char *) __io_virt(addr))
#define readw(addr) (*(volatile unsigned short *) __io_virt(addr))
#define readl(addr) (*(volatile unsigned int *) __io_virt(addr))

#define writeb(b,addr) (*(volatile unsigned char *) __io_virt(addr) = (b))
#define writew(b,addr) (*(volatile unsigned short *) __io_virt(addr) = (b))
#define writel(b,addr) (*(volatile unsigned int *) __io_virt(addr) = (b))

#define memset_io(a,b,c) memset(__io_virt(a),(b),(c))
#define memcpy_fromio(a,b,c) memcpy((a),__io_virt(b),(c))
#define memcpy_toio(a,b,c) memcpy(__io_virt(a),(b),(c))

  最后,我们要特别强调驱动程序中mmap函数的实现方法。用mmap映射一个设备,意味着使用户空间的一段地址关联到设备内存上,这使得只要程序在分配的地址范围内进行读取或者写入,实际上就是对设备的访问。

使用mmap最典型的例子是显示卡的驱动,将显存空间直接从内核映射到用户空间将可提供显存的读写效率。

      (在内核驱动程序的初始化阶段,通过ioremap()将物理地址映射到内核虚拟空间;在驱动程序的mmap系统调用中,使用remap_page_range()将该块ROM映射到用户虚拟空间。这样内核空间和用户空间都能访问这段被映射后的虚拟地址。)


阅读(1414) | 评论(0) | 转发(0) |
0

上一篇:Tasklet机制

下一篇:linux 0.11内核初始化篇

给主人留下些什么吧!~~