1.I/O端口和I/O内存
每种外设都通过读写寄存器进行控制,不管是在内存地址空间,还是I/O地址空间
在硬件层,内存区域和IO区域没有概念的区别:它们都是通过地址总线和控制总线发送电平信号(比如读写信号)进行访问,在通过数据总线读写数据。
I/O寄存器和常规内存
1.在访问I/O寄存器的时候必须之一避免由于cpu或编译器不恰当的优化而改变预期的I/O动作。驱动程序必须确保不使用高速缓存,并且在访问寄存器时不发生读写指令的重新排序。
2.硬件自身缓存的解决办法:把底层硬件配置成在访问I/O区域(不管是内存还是端口)时禁止硬件缓存
3.解决编译器自作聪明的优化和硬件重新排序的方法:
对硬件必须以特定顺序执行的操作之间设置内存屏障,Linux中提供了4个宏来解决所有肯的排序问题:
#include <linux/kernel.h>
void barrier(void);
该函数通知编译器插入一个内存屏障,但对硬件没有影响。
编译后的代码会把当前cpu寄存器的中所有修改过的数值保存到内存中,
需要的时候在重新读取出来。
对barrier的调用可避免在屏障前后的编译器优化,但硬件能完成自己的重新排序。
#include <asm/system.h>
void rmb(void);
void read_barrier_depenfs(void);
void wmb(void);
void mb(void);
rmb(读内存屏障)保证了屏障之前的读操作一定会在后来的读操作执行之前完成。
wmb保证写不会乱序,mb保证读写都不会乱序。
read_barrier_depends较为特殊,仅仅阻止某些读取操作的重新排序,
除非开发者能够正确理解read_barrier_depends和rmb,否则就应该始终使用rmb。
smp系统的屏障
void smp_rmb(void);
void smp_read_barriers_depends(void);
void smp_wmb(void);
void smp_wb(void);
上述屏障宏版本也插入硬件屏障,但仅仅在内核针对SMP系统编译时有效。
设备驱动使用内存屏障的典型代码
write_(dev->registers.addr,io_destianation_address);
write_(dev->registers.size,io_size);
write_(dev->registers.operation,DEV_READ);
wmb();
write_(dev->registers.control,DEV_GO);
内核空间使用I/O端口
I/O端口分配
声明自己需要操作的端口
#include <linux/ioport.h>
struct resource *request_region(unsigned long first,unsigned long n,
const char *name);
使用起始于first的n个端口,name是设备的名称。
查看分配的端口
cat proc/ioports
释放端口
void release_region(unsigned long start,unsigned long n);
检查端口是否可用
void check_region(unsigned long first,unsigned long n);
如果端口不可用,返回值是负的错误码,但是不赞成使用该函数。因为其操作不是原子的操作,返回值并不能代表什么,推荐使用request_region();
操作I/O端口
unsigned inb(unsigned port);
void outb(unsigned chat byte,unsigned port);
字节(8位宽度)读写端口。port参数在一些平台上被定义为unsigned long,
而其它平台被定义为unsigned short 。不同平台的inb返回值类型也不相同。
ungiened inw(unsigned port);
void outw(unsigned short word,unsigned port);
访问16位端口
unsigned inl(unsigned port)
void outl(unsigned long word,unsigend port)
访问32位端口
在用户空间访问I/O端口
GNU的c库在 <sys/io.h>中定义了这些函数
如果要在代码中使用inb及其相关函数,必须满足一些条件。
串操作
之前的I/O操作都是一次传输一个数据,下面的指令一次传输一个数据序列(序列中的数据单位可以是字节、字、双字)。
void insb(unsigned port,void * addr,unsigned long count);
void outsb(unsigned port,void * addr,unsigned long count);
从内存地址addr开始连续读/写count数目的字节,只针对单一端口。
(8位)
void intsw(unsigned port,void *addr,unsigned long count);
void intsw(unsigned port,void *addr,unsigned long count);
对一个16位端口读/写32位数据
void insl(unsigned port,void *addr,unsigned long count);
void outsl(unsigned port,void *addr,unsigned long count);
对一个32位端口读/写32位数据
使用I/O内存
分配内存
#include <linux/ioprot.h>
struct resource *request_mem_region(unsigned long start.unsigned long len,
char *name);
从start开始分配len字节长的内存区域。
可以通过cat proc/iomem查看
映射内存
分配IO内存之后,必须先建立映射才能访问。
#include <asm/io.h>
void *ioremap(unsigned long phts_addr,unsigned long size);
void *ioremap_nocache(unsigned long phys_addr,unsigned long size);
void *iounmap(void * addr);
访问IO内存
虽然可以将ioremap返回值直接当作指针使用,但是不推荐。
访问IO内存的正确方法是通过以下专用函数。
1.读取
unsigned int ioread8(void *addr);
unsigned int ioread16(void * addr);
unsigned int ioread32(void *addr);
这里的addr应该是ioremap获得的地址
2.写入
void iowrite8(u8 value,void *addr);
void iowrite16(u16 value,void *addr);
void iowrite32(u32 value,void *addr);
3.读写
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);
释放内存
void release_mem_region(unsigned long start,unsigned long len);
检测I/O内存区域是否可用
int check_mem_region(unsigned long start,unsigned long len);
和check_region一样,该函数不安全,避免使用。
老的IO内存函数
这些函数不执行类型检测,安全性差,不推荐使用!
unsigned readb(address);
unsigned readw(address);
unsigned readl(address);
unsigned writeb(unsigned value,address);
unsigned writew(unsigned value,address);
unsigned writel(unsigned value,address);
像IO内存一样使用端口
void *ioport_map(unsigned long port,unsigned int count);
该函数重新映射count个IO端口,使其看起来像IO内存,这样,驱动程序可在该函数返回的的地址上使用ioread8机器同类函数,不必理会IO端口和IO内存之前的区别。
void ioport_unmap(void * addr);
这些函数是的IO端口看起来像是IO内存,但需要注意的是,在重新银蛇之前,必须通过request_region来分配这些IO端口。
网友评论