设备驱动程序是软件概念和硬件电路之间的一个抽象层。
一、io端口和io内存
外设通过读写寄存器进行控制。
不管是在内存地址空间还是在io地址空间,寄存器的访问地址都是连续的。
在硬件层,内存区域和io区域没有概念上的区别,都是通过向地址总线和控制总线发送电平信号进行访问,再通过数据总线读写数据。
二、io寄存器和常规内存
io寄存器和RAM的主要区别就是io操作具有边际效应。
由编译器和硬件重新排序引起的问题的解决办法是,对硬件必须以特定顺序执行的操作之间设置内存屏障。
<linux/kernel.h>
void barrier(void); #插入内存屏障
<asm/system.h>
void rmb(void); #读内存屏障,保证屏障之前的读操作一定会在后来的读操作执行之前完成
void read_barrier_depends(void); #特殊的、弱一点的读屏障
void wmb(void); #保证写操作不会乱序
void mb(void); #保证读写操作不会乱序
大多数处理同步的内核原语,如自旋锁、atomic_t操作,也能作为内存屏障使用。
三、使用io端口
io端口是驱动程序与许多设备之间的通信方式。
io端口分配
注册需要操作的端口:
<linux/ioport.h>
struct resource *request_region(unsigned long first, unsigned long n, const char *name); #使用从first开始的n个端口,name为设备名称
释放io端口:
void release_region(unsigned long start, unsigned long n);
检查io端口集是否可用:
int check_region(unsigned long first, unsigned long n);
操作io端口
大多数的硬件会把8位、16位和32位的端口区分开来。
unsigned inb(unsigned port);
void outb(unsigned char type, unsigned port);
字节(8位宽度)读写端口。
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);
访问16位端口(字宽度)。
unsigned inl(unsigned port);
void outl(unsigned longWord, unsigned port);
访问32位端口。
没有定义64位的io操作,是因为即使在64位的体系架构上,端口地址空间也只使用最大32位的数据通路。
在用户空间访问io端口
ioperm系统调用:获取对单个端口的操作权限。
iopl系统调用:获取对整个io空间的操作权限。
如果平台没有ioperm和iopl系统调用,用户空间仍然可以使用/dev/port设备文件访问io端口。
串操作
以上的io操作是一次传输一个数据。有些处理器上实现了一次传输一个数据序列的特殊指令,序列的数据单位可以为字节、字和双字。串io可以使用一条机器指令实现,在没有指令的平台,可以使用紧凑循环实现。
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位数据。
串io端口操作函数,直接将字节流从端口读取或写入,当端口和主机系统具有不同的字节序时,将导致不可预期的结果。
暂停式io
如果设备有丢失数据的情况,或为了防止出现丢失数据的情况,可以使用暂停式io函数代替通常的io函数。
平台相关性
io指令是与处理器密切相关的。
四、使用io内存
和设备通信的另一种主要机制是通过使用映射到内存的寄存器或设备内存,称为io内存。
io内存分配和映射
<linux/ioport.h>
分配io内存区域:
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name); #从start开始分配len字节长的内存区域
释放io内存区域:
void release_mem_region(unsigned long start, unsigned long len);
检查io内存区域是否可用:
int check_mem_region(unsigned long start, unsigned long len); #避免使用
设备驱动程序访问io内存地址
调用ioremap之后,设备驱动程序即可访问任意的io内存地址了。有ioremap返回的地址不应直接使用,应该使用内核提供的accessor函数。
<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);
访问io内存
在某些平台上,可以将ioremap返回的值当做指针使用,这种做法不具有可移植性,应减少使用。
<asm/io.h>
读取io内存:
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
写入io内存:
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *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);
从给定的buf向给定的addr读取或写入count个值。
以上函数为在指定addr执行io操作,以下函数为在一块io内存上执行操作。
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);
像io内存一样使用端口
void *ioport_map(unsigned long port, unsigned int count);
重新映射count个io端口,使其看起来像io内存,可在函数返回的地址上使用访问io内存函数
撤销映射:
void ioport_unmap(void *addr);