每种外设都通过读写寄存器进行控制,大部分外设都有几个寄存器,不管在内存地址空间还是在I/O地址空间,这些寄存器的访问地址是连续的。
在硬件层,内存区域和IO区域没有概念上的区别:他们都通过向地址总线和控制总线发送电平信号进行访问,再通过数据总线读写数据。
尽管硬件寄存器和内存非常相似,但程序员在访问IO寄存器的时候必须注意避免由于CPU或编译器不恰当地优化而改变预期的IO动作。
IO寄存器和RAM的主要是区别是IO操作具有副作用,而内存操作没有:内存写操作的唯一结果是在指定位置储存一个值;内存读操作则仅仅返回指定位置最后一次写入的数值,
编译器能够将数据缓存在IO寄存器中而不写入内存,即使存储数据,读写操作也都能在高速缓存中进行而不访问物理RAM。无论在编译器一级还是硬件一级,指令的重新排序都有可能发生:一个指令序列如果以不同于程序文本的序列运行往往能执行的更快。
处理器无法预料某些其他进程是否会依赖于内存访问的顺序。编译器或CPU可能会自作聪明地重新排序所要求的操作,结果会发生奇怪的错误,并且很难调试。因此,驱动程序必须确保不使用高速缓存,并且在访问寄存器时不发生读或写指令的重新排序。
由硬件自身缓存引起的问题很好解决:只要把底层硬件设置成在访问IO区域时(不管是内存还是端口)时禁止硬件缓存即可。
由编译器或硬件重新排序引起的问题的解决办法是:对硬件必须以特定顺序执行的操作之间设置内存屏障。Linux提供以下函数。
#include <linux/kernel.h>
void barrier(void *)
IO端口分配
在尚未取得对这些端口的独占访问之前,我们不应该对这些端口进行操作,内核为我们提供了一个注册用的接口,它允许驱动程序声明自己需要操作的端口。该接口的核心函数是request_region:#include <linux/kernel.h> struct resource *request_region(unsigned long first , unsigned long n, const char *name)
这个函数告诉内核,我们要使用起始于first的n个端口,参数name是设备的名字,如果分配不成功,则返回NULL值。如果我们不再使用某组IO端口,则应该使用下面的函数将这么端口返回给系统。void release_region(unsigned long start ,unsigned long n)
操作IO端口
当驱动程序请求了需要使用的IO端口范围后,必须读取或写入这么端口。为此,大多数硬件会把8位、16位和32位的端口区分开。Linux内核头文件中(<asm/io.h>)定义了如下一些访问IO端口的内联函数。unsigned inb(unsigned port) void outb(unsigned char byte, unsigned port)
字节(8位)读写端口。16位为short,32位为longword。以上的IO操作都是一次传输一个数据,作为补充,有些处理器实现了一次传输一个数据序列的特殊指令,序列中的数据单位可以是字节、字或双字。这些指令称为串操作指令。使用IO内存
和设备通信的另一种主要机制是通过使用映射到内存的寄存器或设备内存。这两种都称为IO内存,因为寄存器和内存的区别对软件是透明的。
IO内存仅仅是类似RAM的一块区域,在那里处理器可以通过总线访问设备。这种内存有很多用途,比如存放视频数据或以太网数据包,也可以用来实现类是IO端口的设备寄存器。
在使用之前,必须首先分配IO内存区域,用于分配内存区域的接口如下所示:
#include <linux/ioport.h>
struct resource *request_mem_region(unsigned long start , unsigned long len.char *name)
该函数从start开始分配len字节长的内存区域,如果失败,返回NULL。
当不再使用已分配的内存区域时,使用下面的接口释放:
void release_mem_region(unsigned long start ,unsigned long len)
分配内存并不是访问这些内存之前的唯一步骤,我们还必须确保该IO内存内核是可访问的。获取IO内存并不仅仅意味着可引用相应的指针,在许多系统上,IO内存根本不能通过这样的方式;在很多系统中,IO内存不能通过这样的方式直接访问。因此,我们首先必须得建立映射,映射的建立由ioremap()函数完成。
#include <asm/io.h>
void *ioremap(unsigned long phys_addr,unsigned long size)
在某些平台上,我们可以将ioremap()返回的值直接当作指针引用。但是这种不具有可移植性。访问IO内存的正确方法是通过一组专用于此目的的函数。
以8位为例,16、32位都是一样的
unsigned ioread8(void *addr);
其中addr是ioremap()的返回值;返回值则是从给定IO内存读取到的值
void iowrite8(u8 value, void *addr);
void iowrite8_rep(void *addr, void *buf, unsigned long count);
从给定的buf向给定的addr读取或写入count个值