I/O端口和I/O内存

来源:互联网 发布:云计算实训 编辑:程序博客网 时间:2024/04/27 17:06

I/O端口和I/O内存

每个外设都是通过读写它的寄存器来控制的。通常,通过内存地址空间或I/O地址空间进行访问。在硬件层面上,I/O区域与内存区域(DRAM)在概念上没有区别,它们都是通过在地址总线和控制总线上触发电信号来进行读写操作。根据处理器的不同,有些处理如X86拥有独立的外设地址空间,以区别普通的内存地址空间。针对I/O端口,会提供特殊的CPU访问指令。而有些处理器则使用统一的地址空间。由于大部分I/O总线是基于个人电脑设计的,即使那些单独I/O端口地址空间的处理器,在访问外部设备时,必须通过一个外部芯片或在CPU核上增加一个额外的电路来虚构读写I/O端口。

同理,基于相同的原因,Linux针对所支持的平台,都实现了I/O端口的概念。另外,对于拥有独立I/O端口地址空间的外设,并非所有的设备将它们的寄存器映射到I/O端口。与ISA设备不一样,对于PCI设备,大多数PCI设备将它们的寄存器映射到一段内存区域。通常这种映射方式更好,因为访问内存效率更高,且不需要特殊的CPU指令。

         这样一来,访问外设的寄存器与访问内存变得一样,形式上都是对某段内存地址的访问。不同之处在于,普通的内存访问是没有副作用,所有编译器可以对一些访问操作做优化,如Cache值,改变访问顺序等优化操作,而I/O映射的内存访问是有副作用的,编译器的一些优化操作会导致一些意想不到的副作用。因此,在Linux中,提供了如下函数来告诉编译器禁止对某段I/O内存的访问操作进行优化:


#include <linux/kernel.h>void barrier(void)/**This “software” memory barrier requests the compiler to consider all memoryvolatile across this instruction.*/#include <asm/system.h>void rmb(void);void read_barrier_depends(void);void wmb(void);void mb(void);/**Hardware memory barriers. They request the CPU (and the compiler) to checkpointall memory reads, writes, or both across this instruction.*/

使用示例:

writel(dev->registers.addr, io_destination_address);writel(dev->registers.size, io_size);writel(dev->registers.operation, DEV_READ);wmb( );writel(dev->registers.control, DEV_GO);

在上述例子中,writel(dev->registers.control, DEV_GO);必须发生在前面三个指令之后。wmb()函数保证了它们执行的先后顺序。


I/O端口

I/O端口是驱动与设备进行通信的手段,它拥有独立的地址空间。

下面考察下相关的操作函数:

在访问I/O端口前,首先要确保我们对该端口拥有唯一访问权,因此,在访问端口前,我们通过如下函数向系统提供访问端口的要求:


#include <linux/ioport.h>struct resource *request_region(unsigned long first, unsigned long n,const char *name);

上述函数告诉内存我们要使用n个端口,端口号从first开始。name是设备的名称。如果返回NULL,则表明当前不同使用所请求的端口。可以通过查看/proc/ioports的内容了解当前哪些端口被占用了。当然,对应端口请求函数,访问端口结束后,可以通过如下函数释放端口。


void release_region(unsigned long start, unsigned long n);

一旦通过request_region取得到端口的访问权后,就可以通过如下一系列函数对端口进行操作了:


unsigned inb(unsigned port);void outb(unsigned char byte, unsigned port);/**Read or write byte ports (eight bits wide). The port argument is defined asunsigned long for some platforms and unsigned short for others. The returntype of inb is also different across architectures.*/unsigned inw(unsigned port);void outw(unsigned short word, unsigned port);/**These functions access 16-bit ports (one word wide); they are not available whencompiling for the S390 platform, which supports only byte I/O.*/unsigned inl(unsigned port);void outl(unsigned longword, unsigned port);/**These functions access 32-bit ports. longword is declared as either unsigned longor unsigned int, according to the platform. Like word I/O, “long” I/O is notavailable on S390.*/

I/O内存

         尽量在X86使用I/O端口很多,但是,与设备进行通信的主要机制是通过内存映射的寄存器和设备内存,两者都称为I/O内存,对软件来说,是一样的。

         I/O内存就是一段类似RAM的内存,处理器通通过总线能够访问到它们。I/O内存可以存放数据,也可以映射寄存器,使其行为上与I/O端口类似(读写有副作用,需要使用内存屏障)。

         I/O内存的访问方式依赖于具体的硬件系统体系,I/O内存可以通过页表访问,也可以通过其他方式访问。如果通过页表访问,则首先需要为I/O内存映射一块驱动可见的物理地址,通常,在执行I/O操作前,需要调用ioremap函数。如果不需要通过页表访问,那么I/O内存就类似I/O端口,可以通过适当的包装函数直接进行读写操作。

         下面考察下I/O内存的相关操作:

         跟I/O端口一样,在使用I/O内存时,必须产生确保I/O内存可用,通过如下函数来请求I/O内存的访问:


struct resource *request_mem_region(unsigned long start, unsigned long len,char *name);

该函数将分配一个包含len字节的内存区域,从start地址开始。当该段内存区域不再需要时,通过如下函数释放:


void release_mem_region(unsigned long start, unsigned long len);

之后,为了使设备驱动能够访问I/O内存地址,必须将申请到的I/O内存地址通过ioremap映射。ioremap的函数原型如下:


#include <asm/io.h>void *ioremap(unsigned long phys_addr, unsigned long size);void *ioremap_nocache(unsigned long phys_addr, unsigned long size);void iounmap(void * addr);

通过ioremap映射后得到的地址不能通过指针的反引用直接使用,而是必须使用如下一些接口函数:


unsigned int ioread8(void *addr);unsigned int ioread16(void *addr);unsigned int ioread32(void *addr);void iowrite8(u8 value, void *addr);void iowrite16(u16 value, void *addr);void iowrite32(u32 value, void *addr);

将I/O端口按照I/O内存来访问

         有些硬件的一些版本使用I/O端口的方式访问寄存器,而一些版本则使用I/O内存的方式访问,为了统一,可以将I/O端口映射为I/O内存的方式进行访问,为了实现这个目的,可以使用如下函数:

 

void *ioport_map(unsigned long port, unsigned int count);

该函数将从port地址开始的cont个I/O端口重新映射,使它们看上去像I/O内存。之后,就可以使用ioread8及类似的函数访问端口了。需要注意的是,再使用该函数之前,仍然需要首先调用request_region函数。解除映射使用如下函数:


void ioport_unmap(void *addr);





0 0
原创粉丝点击