每种外设都是通过读写寄存器进行控制。外设寄存器也被称为I/O端口,通常包括:控制寄存器、状态寄存器和数据寄存器三大类,不管是在内存地址还是在I/O地址空间,这些寄存器的访问地址都是连续的。在硬件层,内存区域和I/O区域没有概念上的区别,它们都是通过向地址总线和控制总线发送电平信号进行访问,再通过数据总线读写数据。
控制的方式分为I/O端口和I/O内存,它们的定义说明如下:
I/O端口:当一个寄存器或内存位于I/O空间时,称其为I/O端口。
I/O内存:当一个寄存器或内存位于内存空间时,称其为I/O内存。
I/O端口
注册端口:
在尚未取得对一个I/O端口的独占访问时,我们不能对这些端口操作。因此内核给我们提供了一个注册用的接口,定义在<linux/ioport.h>:
struct resource *request_region(unsigned long first,unsigned long n,const char *name);
first:I/O端口的起始地址。
n:占用端口数量。
name:设备的名称。
如果分配失败返回NULL,如果分配成功,返回非NULL,可以在/proc/ioports文件中看到注册的io口。
释放端口:
void release_region(unsigned long start,unsigned long n);
检查给定的I/O端口集是否可用:
int check_region(unsigned long first,unsigned long n);
如果端口不可用,返回一个负值错误码。不推荐使用该函数,因为它的返回值不能确保分配是否能够成功。
操作I/O端口:
当所需的I/O端口范围申请成功之后,需要操作端口。大多数硬件会被8位、16位和32位端口区分开来。它们之间不能混用。在头文件<asm/io.h>中定义了如下操作函数:
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);
8位宽度的读写端口。port参数在一些平台上被定义为unsigned long,而在另外一些平台上被定义为unsigned short。不同平台的inb返回值类型也不同。
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);
16位宽度的读写端口。
unsigned inl(unsigned port);
void outl(unsigned longword, unsigned port);
32位宽度的读写端口。longword参数根据平台不同被定义为unsigned long类型或者unsigned int类型。
注意,这里没有定义64位的I/O操作,即使在64位的体系架构上,端口地址空间也只使用32位的数据通路。
I/O端口的串操作:
上述的I/O操作都是一次传输一个数据,作为补充,有些处理器上实现了一次传输一个数据序列的特殊指令,序列中的数据单位可以是字节、字或双字。这些指令称为串操作指令,具体如下:
void insb(unsigned port, void *addr, unsigned long count);
void outsb(unsigned port, void *addr, unsigned long count);
从内存地址addr开始连续读/写count数目的字节,只对单一端口port读取或写入数据。
void insw(unsigned port, void *addr, unsigned long count);
void outsw(unsigned port, void *addr, unsigned long count);
对一个16位端口读写16位数据。
void insl(unsigned port, void *addr, unsigned long count
void outsl(unsigned port, void *addr, unsigned long count);
对一个32位端口读写32位数据,即使在64位的体系结构上,端口地址也只使用最低32位的数据通路。
平台相关性:
在ARM架构上,端口映射到内存,支持所有函数;串操作用C语言实现。端口类型是unsigned int。
I/O内存
和设备通讯还有另一种主要机制是通过使用映射到内存的寄存器或设备内存,这两种都称为I/O内存。
I/O内存的分配和映射:
在使用之前,必须首先分配I/O内存区域。用于分配的接口定义在<linux/ioport.h>中:
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
该函数从start开始分配len字节长的区域。如果成功,返回非NULL指针,所有的I/O内存分配情况可从**/proc/iomem**中看到。
当内存不再使用的时候,使用下面的接口释放:
void release_mem_region(unsigned long start, unsigned long len);
一旦装备了 ioremap (和 iounmap), 一个设备驱动可以存取任何 I/O 内存地址,不管是否它是直接映射到虚拟地址空间,定义在<asm/io.h>,原型如下:
void *ioremap(unsigned long phys_addr, unsigned long size);
phys_addr:是要映射的物理地址。
size:是要映射的长度,单位是字节。
void *ioremap_nocache(unsigned long phys_addr, unsigned long size);
void iounmap(void * addr);
操作I/O内存
从I/O内存中读取:
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
addr 是从 ioremap 获得的地址(可能包含一个整型偏移量), 返回值是从给定 I/O 内存读取的值
对应的I/O 内存写函数
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
读和写一系列值到一个给定的 I/O 内存地址,从给定的 buf 读或写 count 个值到给定的 addr
void ioread8_rep(void *addr, void *buf, unsigned long count);
void ioread16_rep(void *addr, void *buf, unsigned long count);
void ioread32_rep(void *addr, void *buf, unsigned long count);
void iowrite8_rep(void *addr, const void *buf, unsigned long count);
void iowrite16_rep(void *addr, const void *buf, unsigned long count);
void iowrite32_rep(void *addr, const void *buf, unsigned long count);
需要操作一块 I/O 地址,使用一下函数
void memset_io(void *addr, u8 value, unsigned int count);
void memcpy_fromio(void *dest, void *source, unsigned int count);
void memcpy_toio(void *dest, void *source, unsigned int count);
旧函数接口,仍可工作, 但不推荐。
unsigned readb(address);
unsigned readw(address);
unsigned readl(address);
void writeb(unsigned value, address);
void writew(unsigned value, address);
void writel(unsigned value, address);