读写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
#define memcpy_toio(a,b,c)memcpy(__io_virt(a),(b),(c�
上述定义中的宏__io_virt()仅仅检查虚地址addr是否是核心空间中的虚地址。具体的实现函数在arch/i386/lib/iodebug.c文件。
显然,在x86平台上访问I/O内存资源与访问系统主存RAM是无差别的。但是为了保证驱动程序的跨平台的可移植性,我们应该使用上面的函数来访问I/O内存资源,而不应该通过指向核心虚地址的指针来访问。
大多数平台都区分8位、16位和32位宽度的I/O端口的。Linux在include/asm/io.h头文件(对于i386平台就是include/asm-i386/io.h)中定义了一系列读写不同宽度I/O端口的宏函数。如下所示:
(1)读写8位宽的I/O端口
unsigned char inb(unsigned port);
void outb(unsigned char value,unsigned port);
其中,port参数指定I/O端口空间中的端口地址。在大多数平台上(如x86)它都是unsigned short类型的,其它的一些平台上则是unsigned int类型的。显然,端口地址的类型是由I/O端口空间的大小来决定的。
(2)读写16位宽的I/O端口
unsigned short inw(unsigned port);
void outw(unsigned short value,unsigned port);
(3)读写32位宽的I/O端口
unsigned int inl(unsigned port);
void outl(unsigned int value,unsigned port);
Linux同样在io.h文件中定义了字符串I/O读写函数:
(1)8位宽的字符串I/O操作
void insb(unsigned port,void * addr,unsigned long count);
void outsb(unsigned port ,void * addr,unsigned long count);
(2)16位宽的字符串I/O操作
void insw(unsigned port,void * addr,unsigned long count);
void outsw(unsigned port ,void * addr,unsigned long count);
(3)32位宽的字符串I/O操作
void insl(unsigned port,void * addr,unsigned long count);
void outsl(unsigned port ,void * addr,unsigned long count);
在一些平台上(典型地如X86),对于慢速外设来说,如果CPU读写其I/O端口的速度太快,可能会发生丢失数据。这就要在两次连续的I/O操作之间插入时延。Linux在io.h头文件中定义了带延迟的I/O读写函数,以XXX_p命名,如:inb_p()、outb_p()等。下面以out_p()为例进行分析。
将io.h中的宏定义__OUT(b,"b"char)展开后可得如下定义:
extern inline void outb(unsigned char value, unsigned short port) {
__asm__ __volatile__ ("outb %" "b " "0,%" "w" "1"
: : "a" (value), "Nd" (port));
}
extern inline void outb_p(unsigned char value, unsigned short port) {
__asm__ __volatile__ ("outb %" "b " "0,%" "w" "1"
__FULL_SLOW_DOWN_IO
: : "a" (value), "Nd" (port));
}
可以看出,outb_p()函数的实现中被插入了宏__FULL_SLOWN_DOWN_IO,以实现微小的延时。宏__FULL_SLOWN_DOWN_IO在头文件io.h中一开始就被定义:
#ifdef SLOW_IO_BY_JUMPING
#define __SLOW_DOWN_IO "
jmp 1f
1:jmp 1f
1:"
#else
#define __SLOW_DOWN_IO "
outb %%al,$0x80"
#endif
#ifdef REALLY_SLOW_IO
#define __FULL_SLOW_DOWN_IO __SLOW_DOWN_IO
__SLOW_DOWN_IO __SLOW_DOWN_IO __SLOW_DOWN_IO
#else
#define __FULL_SLOW_DOWN_IO __SLOW_DOWN_IO
#endif
显然,__FULL_SLOW_DOWN_IO就是一个或四个__SLOW_DOWN_IO(根据是否定义了宏REALLY_SLOW_IO来决定),而宏__SLOW_DOWN_IO则被定义成毫无意义的跳转语句或写端口0x80的操作(根据是否定义了宏SLOW_IO_BY_JUMPING来决定)。