每种外设都通过读写寄存器进行控制。大部分外设都有几个寄存器,不管是在内存地址空间还是在I/O地址空间,这些寄存器的访问地址都是连续的。x86的处理器为I/O端口的读和写提供了独立的线路,并且使用特殊的CPU指令访问端口。但即使这样,也不是所有的设备都会把寄存器映射到I/O端口中。ISA设备普遍使用I/O端口,而大多数PCI设备则把寄存器映射到某个内存地址区段。
I/O端口是驱动程序与许多设备之间的通信方式,Linux的内核为我们提供了I/O端口分配的操作接口,但对PCI设备来讲,它的配置地址空间已经为其指定了I/O端口范围,不需要额外的分配操作。Linux内核
提供了如下一些访问I/O端口的内联函数:
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);
unsigned inl(unsigned port);
void outl(unsigned longword, unsigned port);
下面我们重点来看一下2.6内核引入的ioport_map函数:
void *ioport_map( unsigned long port, unsigned int count );
通过这个函数,可以把port开始的count个连续端口重映射为一段“内存空间”。然后就可以在其返回的地址上象访问I/O内存一样访问这几个I/O端口。当不需要这种映射时,需要调用下面的函数来撤消:
void iport_unmap(void *addr);
浏览2.6内核的源代码,我们不难发现,这种所谓的映射其实是“假”的。下面是这两个函数的实现,及一些相关数据:
#define PIO_OFFSET 0x10000UL
#define PIO_MASK 0x0ffffUL
#define PIO_RESERVED 0x40000UL
void __iomem *ioport_map(unsigned long port, unsigned int nr)
{
if (port > PIO_MASK)
return NULL;
return (void __iomem *) (unsigned long) (port + PIO_OFFSET);
}
void ioport_unmap(void __iomem *addr)
{
/* Nothing to do */
}
它只是简单地把I/O端口号加上PIO_OFFSET(64K),作为一个“假”的内存地址返回,而unmap则什么也不做。之所以这样做,是基于这样一个事实:真正的I/O内存地址经过映射成为虚拟地址后,由于是在内核空间,其值肯定大于3G。而port+PIO_OFFSET不会大于128K。所以,内核不会把这两种地址搞混。可以分别进行处理,下面看看ioread8函数的实现:
unsigned int fastcall ioread8(void __iomem *addr)
{
IO_COND(addr, return inb(port), return readb(addr));
}
#define VERIFY_PIO(port) BUG_ON((port & ~PIO_MASK) != PIO_OFFSET)
#define IO_COND(addr, is_pio, is_mmio) do { \
unsigned long port = (unsigned long __force)addr; \
if (port < PIO_RESERVED) { \
VERIFY_PIO(port); \
port &= PIO_MASK; \
is_pio; \
} else { \
is_mmio; \
} \
} while (0)
展开:
unsigned int fastcall ioread8(void __iomem *addr)
{
unsigned long port = (unsigned long __force)addr;
if( port < 0x40000UL ) {
BUG_ON( (port & ~PIO_MASK) != PIO_OFFSET );
port &= PIO_MASK;
return inb(port);
}else{
return readb(addr);
}
}
所以,除了提供一个统一的接口,它并没有在本质上改变什么。
除了I/O端口之外,和设备通信的另一种主要机制是通过使用映射到内存的寄存器或设备内存。这两种都称为I/O内存,因为寄存器和内存的差别对软件是透明的。
对于分配好的I/O内存,一般不鼓励直接使用指向I/O内存的指针进行访问,最好通过页表,用包装函数访问。要通过页表访问,那么需要对分配好的I/O内存进行映射,确保该I/O内存对内核而言是可访问的。完成I/O内存映射的函数是ioremap:
#include
void *ioremap(unsigned long phys_addr, unsigned long size);
它把一个总线内存地址映射到CPU空间。