Chinaunix首页 | 论坛 | 博客
  • 博客访问: 261620
  • 博文数量: 78
  • 博客积分: 1810
  • 博客等级: 上尉
  • 技术积分: 1039
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-20 11:11
文章存档

2012年(78)

我的朋友

分类:

2012-04-24 10:57:46

几乎每一种外设都是通过读写设备上的寄存器来进行的。外设寄存器也称为“I/O端口通常包括:控制寄存器、状态寄存器和数据寄存器三大类,而且一个外设的寄存器通常被连续地编址。CPU对外设IO端口物理地址的编址方式有两种:一种是I/O映射方式(I/Omapped),另一种是内存映射方式(Memorymapped)。而具体采用哪一种则取决于CPU的体系结构。

  有些体系结构的CPU(如,PowerPCm68k等)通常只实现一个物理地址空间(RAM)。在这种情况下,外设I/O端口的物理地址就被映射到CPU的单一物理地址空间中,而成为内存的一部分。此时,CPU可以象访问一个内存单元那样访问外设I/O端口,而不需要设立专门的外设I/O指令。这就是所谓的内存映射方式Memorymapped)。

  而另外一些体系结构的CPU(典型地如X86)则为外设专门实现了一个单独地地址空间,称为“I/O地址空间或者“I/O端口空间。这是一个与CPURAM物理地址空间不同的地址空间,所有外设的I/O端口均在这一空间中进行编址。CPU通过设立专门的I/O指令(如X86INOUT指令)来访问这一空间中的地址单元(也即I/O端口)。这就是所谓的“I/O映射方式I/Omapped)。与RAM物理地址空间相比,I/O地址空间通常都比较小,如x86 CPUI/O空间就只有64KB00xffff)。这是“I/O映射方式的一个主要缺点。

36 访问I/O内存资源

  尽管I/O端口空间曾一度在x86平台上被广泛使用,但是由于它非常小,因此大多数现代总线的设备都以内存映射方式(Memorymapped)来映射它的I/O端口(指I/O寄存器)和外设内存。基于内存映射方式的I/O端口(指I/O寄存器)和外设内存可以通称为“I/O内存资源(I/O Memory)。因为这两者在硬件实现上的差异对于软件来说是完全透明的,所以驱动程序开发人员可以将内存映射方式的I/O端口和外设内存统一看作是“I/O内存资源

  从前几节的阐述我们知道,I/O内存资源是在CPU的单一内存物理地址空间内进行编址的,也即它和系统RAM同处在一个物理地址空间内。因此通过CPU的访内指令就可以访问I/O内存资源。

一般来说,在系统运行时,外设的I/O内存资源的物理地址是已知的,这可以通过系统固件(如BIOS)在启动时分配得到,或者通过设备的硬连线(hardwired)得到,或者kernel在启动的过程中自动探测自动分配得到,比如PCI总线 。      

比如,PCI卡的I/O内存资源的物理地址就是在系统启动时由PCI BIOS分配并写到PCI卡的配置空间中的BAR中的。而ISA卡的I/O内存资源的物理地址则是通过设备硬连线映射到640KB1MB范围之内的。但是CPU通常并没有为这些已知的外设I/O内存资源的物理地址预定义虚拟地址范围,因为它们是在系统启动后才已知的(某种意义上讲是动态的),所以驱动程序并不能直接通过物理地址访问I/O内存资源(因为物理地址可能也是动态的),而必须将它们映射到核心虚地址空间内(通过页表),然后才能根据映射所得到的核心虚地址范围,通过访内指令访问这些I/O内存资源。 ??IO内存资源的定义,见上面:

  361 映射I/O内存资源

  Linuxio.h头文件中声明了函数ioremap(),用来将I/O内存资源的物理地址映射到核心虚地址空间(3GB4GB)中,如下:

 

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

void iounmap(void * addr);

  Iounmap()函数用于取消ioremap()所做的映射,参数addr是指向核心虚地址的指针。这两个函数都是实现在mm/ioremap.c文件中。具体实现可参考《情景分析》一书。

  362 读写I/O内存资源

  在将I/O内存资源的物理地址映射成核心虚地址后,理论上讲我们就可以象读写RAM那样直接读写I/O内存资源了。但是,由于在某些平台上,对I/O内存和系统内存有不同的访问处理,因此为了确保跨平台的兼容性,Linux实现了一系列读写I/O内存资源的函数,这些函数在不同的平台上有不同的实现。但在x86平台上,读写I/O内存与读写RAM无任何差别。如下所示(include/asm-i386/io.h):

 

#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))

  上述定义中的宏__io_virt()仅仅检查虚地址addr是否是核心空间中的虚地址。该宏在内核2.4.0中的实现是临时性的。具体的实现函数在arch/i386/lib/Iodebug.c文件。

  显然,在x86平台上访问I/O内存资源与访问系统主存RAM是无差别的。但是为了保证驱动程序的跨平台的可移植性,我们应该使用上面的函数来访问I/O内存资源,而不应该通过指向核心虚地址的指针来访问。

//通过学习这篇文章的结论:

掌握IO端口的定义,  IO内存的定义, 为什么驱动程序不能访问设备物理地址。

CPU不能直接访问 IO端口的物理地址,因为设备的物理地址,可能是动态的,所以驱动程序中,无法直接使用物理地址来访问IO端口(即是设备的寄存器)

设备的物理地址的确定:

“I/O内存资源(I/O Memory):

基于内存映射方式的I/O端口(指I/O寄存器)和外设内存可以通称为“I/O内存资源(I/O Memory)。因为这两者在硬件实现上的差异对于软件来说是完全透明的,所以驱动程序开发人员可以将内存映射方式的I/O端口和外设内存统一看作是“I/O内存资源

阅读(2737) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~