“小涛哥,咱们说Linux设备驱动程序说了那么久,怎么从来不说实际设备呢,顶多就说了下内存,总感觉驱动程序是和设备分离的,怎么关联起来..”小王思索着。
“不错,这也正是这次讲课的内容,设备I/O端口与I/O内存的访问”我啊,禁不住拍拍她的头。
对于一块实际的设备而言,通常会提供一组寄存器来用于控制设备,读写设备和获取设备状态,也就是我们常说的控制寄存器,数据寄存器和状态寄存器。这些寄存器可能位于I/O空间(这时叫做I/O端口),也可能位于内存空间(对应的内存空间被成为I/O内存)。在Linux中提供了一系列的I/O端口和I/O内存操作的接口如下:
1)I/O端口操作:在Linux设备驱动中,应使用Linux内核提供的函数来访问定位于I/O空间的端口,包括一下几种:
*读写字节端口(8位宽)
unsigned inb(unsigned port);【读】 voi outb(unsigned char byte, unsigned port);【写】
*读写字端口(16位宽)
unsigned inw(unsigned port);【读】 voi outw(unsigned short word, unsigned port);【写】
*读写长字端口(32位宽)
unsigned inl(unsigned port);【读】 voi outl(unsigned longword, unsigned port);【写】
*读写一串字节
unsigned insb(unsigned port, void *addr, unsigned long count);【读】 voi outsb(unsigned port, void *addr, unsigned long count);【写】
*读写一串字unsigned insw(unsigned port, void *addr,unsigned long count);【读】 voi outsb(unsigned port, void *addr, unsigned long count);【写】
*读写一串长字
unsigned insl(unsigned port, void * addr, unsigned long count);【读】 voi outsb(unsigned port, void *addr, unsigned long count);【写】
说明:上述各函数中I/O端口port的类型长度依赖与具体的硬件平台,所以只是写出了unsigned
2)I/O内存:在内核中访问I/O内存之前,需首先使用ioremap()函数将设备所处的物理地址映射到虚拟地址。
*ioremap()原型:void *ioremap(unsigned long offset, unsigned long size);
它返回一个特殊的虚拟地址,该地址可用来存取特定的物理地址范围。用它返回的虚拟地址应该使用iounmap()函数释放。
*iounmap()原型:void iounmap(void *addr);
现在,有了物理地址锁映射出来的虚拟地址后,我们就可以通过c指针来直接访问这些地址,但Linux内核也提供了一组函数来完成这中虚拟地址的读写。如下
*读IO内存
unsigned int ioread8(void *addr); unsigned int ioread16(void *addr); unsigned int ioread32(void *addr); 与之对应的较早版本是:
unsigned readb(address); unsigned readw(address); unsigned readl(address); 这些在2.6的内核中依然可以使用。
*写IO内存
void iowrite8(u8 value,void *addr); void iowrite16(u16 value, void *addr); void iowrite32(u32 value, void *addr); 与之对应的较早版本是:
void writeb(unsigned value, address); void writew(unsigned value,address); void writel(unsigned value,address); 2.6的内核中依然可以使用。
*读一串IO内存 *写一串IO内存
void ioread8_rep(void *addr, void *buf, unsigned long count); void iowrite8_rep(void *addr, void *buf, unsigned long count);
void ioread16_rep(void *addr, void *buf, unsigned long count); void iowrite8_rep(void *addr, void *buf, unsigned long count);
void ioread32_rep(void *addr, void *buf, unsigned long count); void iowrite8_rep(void *addr, void *buf, unsigned long count);
*复制IO内存
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);
3)把IO端口映射到内存空间
void *ioport_map(unsigned long port, unsigned int count); 通过这个函数,可以把port开始的count个连续的IO端口重映射为一段“内存空间”。然后
就可以在其返回的地址上向访问IO内存一样访问这些端口,当不再需要这种映射时,调用:
void ioport_unmap(void *addr); 来撤销这种映射
4)IO端口申请
struct resource *request_region(unsigned long first, unsigned long n, const char *name);
这个函数向内核申请n个端口,这些端口从first开始,name参数为设备的名称,成功返回非NULL.一旦申请端口使用完成后,应当使用:
void release_region(unsigned long start, unsigned long n);
5)IO内存申请
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
这个函数向内核申请n个内存,这些地址从first开始,name为设备的名称,成功返回非NULL,一旦申请的内存使用完成后,应当使用:
void release_mem_region() ; 来释放归回给系统。需要说明的是这两个函数也不是必须的,但建议使用。
6)通过以上的基础,我们就可以归纳出设备驱动访问IO端口和IO内存的步骤。
一种方法是:直接使用IO端口操作函数:在设备打开或驱动模块被加载时申请IO端口区域,之后使用inb(),outb()等进行端口访问,最后在设备关闭或驱动被卸载时释放IO端口范围。流程如下:
另外一种途径是:将IO端口映射为内存进行访问,在设备打开或驱动模块被加载时,申请IO端口区域并使用ioport_map()映射到内存,之后使用IO内存的函数进行端口访问,最后,在设备关闭或驱动模块被卸载时释放IO端口并释放映射,流程如下:
上边是IO端口的访问方法,至于IO内存的访问方法是:首先调用request_mem_region()申请资源,接着将寄存器地址通过ioremap()映射到内核空间的虚拟地址,之后就可以Linux设备访问编程接口访问这些寄存器了,访问完成后,使用ioremap()对申请的虚拟地址进行释放,并释放release_mem_region()申请的IO内存资源。
流程如下:
“小王,感觉怎么样呢, 我是尽最大努力了哈”我说。
“可以,可以,感觉挺好,我觉得哈,你要是多画图,我就没问题,就像上边三个图一样”小王调皮的说。