LDD-Communicating with Hardware
I/O Ports and I/O Memory
所有的外设都通过读写其寄存器来控制,一个外设在内存地址空间或者I/O地址空间有多个寄存器。
访问I/O寄存器的操作可能会有副作用——有些寄存器在访问后会清零,因此这些寄存器的值不能缓存在高速缓存中。内核提供了相关接口:
1 #include <linux/kernel.h> 2 void barrier(void); 3 // 此函数会插入一个内存界限,对于硬件没有影响;编译器不会将界限后的代码优化到界限前 4 5 #include <asm/system.h> 6 void rmb(void); 7 void read_barrier_depends(void); // rmb的弱版本,read_barrier_depends blocks only the reordering of reads that depend on data from other reads 8 void wmb(void); 9 void mb(void); 10 // 这些函数会在编译后的指令插入硬件内存界限 11 12 void smp_rmb(void); 13 void smp_read_barrier_depends(void); 14 void smp_wmb(void); 15 void smp_mb(void); 16 // 这些函数只有在SMP系统才会插入硬件内存界限,否则会转化为barrier
内存界限会影响性能,不同的函数会带来不同程度的性能影响,因此要使用尽可能明确的类型。在x86平台,由于 writes outside the processor are not reordered,wmb()什么也不做;然而读操作不会重新排序,所以mb()比wmb()慢。
Using I/O Ports
I/O端口是驱动和许多设备通讯的方式。内核提供的请求端口的接口:
1 #include <linux/ioport.h> 2 struct resource *request_region(unsigned long first, unsigned long n, const char *name);
n为端口数,起始数为first,name为设备名,成功时返回非NULL。端口的分配情况由/proc/ioports可得。
要释放所得的端口:
1 void release_region(unsigned long start, unsigned long n);
1 int check_region(unsigned long first, unsigned long n);
对于I/O端口的操作要指明大小,相关的函数定义在asm/io.h中:
1 usigned inb(unsigned port); 2 void outb(unsigned char byte, unsigned port); 3 unsigned inw(unsigned port); 4 void outw(unsigned short word, unsigned port); 5 unsigned inl(unsigned port); 6 void outl(unsigned longword, unsigned port);
上述函数也可在用户空间使用,GNU C库函数定义在sys/io.h,使用这些函数时需要注意以下问题:
-
代码必须用-O选项编译来展开内敛函数
-
通过ioperm和iopl系统调用来获取单独端口或者整个I/O地址空间的权限(两个函数是x86平台)
-
程序要调用ioperm或iopl必须要以root权限
如果计算机平台没有ioperm和iopl系统调用,用户空间可以通过/dev/port设备文件来访问I/O端口。
下列函数同样能和端口通信:
1 void insb(unsigned port, void *addr, unsigned long count); 2 void outsb(unsigned port, void *addr, unsigned long count); 3 void insw(unsigned port, void *addr, unsigned long count); 4 void outsw(unsigned port, void *addr, unsigned long count); 5 void insl(unsigned port, void *addr, unsigned long count); 6 void outsl(unsigned port, void *addr, unsigned long count);
上述函数会和端口直接以字节流通信,如果端口和主机的字节序不同,可能会有意外的结果。inw函数会交换两个字节,但是上述函数不会执行这些函数。
如果总线传输数据过快,会导致数据丢失。为此,内核定义了_p后缀的函数,是上述函数的暂停版本。
由于I/O操作和CPU如何与硬件交互数据紧密相关,很多I/O操作的源码都是平台相关的。有趣的是,x86平台支持一个64KB大小的I/O空间,其他的CPU都没有专门的I/O空间。早期的Alphas处理器没有操作一个或两个字节的相关指令,于是,Alphas将I/O端口映射到内存地址空间的一些特殊区域,然后对这些内存进行读写。
Using I/O Memory
除了x86处理器的I/O地址空间,和设备通信的主要方式是memory-mapped寄存器和设备存储器,二者对软件透明,都称为I/O存储器(I/O memory)。本章主要介绍ISA和PCI的存储器。
根据计算机的平台和总线类型,I/O存储器不一定能够通过页表访问。在访问页表之前,驱动程序一般先调用ioremap建立页表;如果不需要页表,I/O存储器和I/O端口的访问形式类似。
I/O存储器区域在使用前首先要分配,函数定义在linux/ioport.h:
1 struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
使用完毕后,内存区域需要释放:
1 void release_mem_region(unsigned long start, unsigned long len);
1 int check_mem_region(unsigned long start, unsigned long len);
1 #include <asm/io.h> 2 void *ioremap(unsigned long phys_addr, unsigned long size); 3 void *ioremap_nocache(unsigned long phys_addr, unsigned long size); 4 void iounmap(void * addr);
访问I/O存储器的接口定义在asm/io.h:
1 unsigned int ioread8(void *addr); 2 unsigned int ioread16(void *addr); 3 unsigned int ioread32(void *addr); 4 5 void iowrite8(u8 value, void *addr); 6 void iowrite16(u16 value, void *addr); 7 void iowrite32(u32 value, void *addr); 8 9 void ioread8_rep(void *addr, void *buf, unsigned long count); 10 void ioread16_rep(void *addr, void *buf, unsigned long count); 11 void ioread32_rep(void *addr, void *buf, unsigned long count); 12 void iowrite8_rep(void *addr, const void *buf, unsigned long count); 13 void iowrite16_rep(void *addr, const void *buf, unsigned long count); 14 void iowrite32_rep(void *addr, const void *buf, unsigned long count);
函数中的addr参数是ioremap函数的返回值。
若要操作I/O存储器的大块区域,可以通过下列函数:
1 void memset_io(void *addr, u8 value, unsigned int count); 2 void memcpy_fromio(void *dest, void *source, unsigned int count); 3 void memcpy_toio(void *dest, void *source, unsigned int count);
下列函数虽然也可对I/O存储器进行操作,但是不提倡再进行使用:
1 unsigned readb(address); 2 unsigned readw(address); 3 unsigned readl(address); 4 unsigned readq(address); // 64位 5 6 void writeb(unsigned value, address); 7 void writew(unsigned value, address); 8 void writel(unsigned value, address); 9 void writeq(unsigned value, address); // 64位
有些设备的某些版本使用I/O端口,有的版本却使用I/O存储器;为了屏蔽这种差异,2.6内核提供了函数:
1 void *ioport_map(unsigned long port, unsigned int count);
1 void *ioport_unmap(void *addr);
书中说ISA存储器位于640KB(0xA0000)和1MB(0x100000)之间,但是我在系统中的/proc/iomem没有发现。
志:每个端口都有一个地址,相应的也有一个寄存器;因此对于端口的读写其实是对寄存器的读写。每个端口的寄存器的每一位可能对应一个物理引脚的电平状态。
作者:glob
出处:http://www.cnblogs.com/adera/
欢迎访问我的个人博客:https://blog.globs.site/
本文版权归作者和博客园共有,转载请注明出处。