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);
判断I/O端口是否可用:
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,使用这些函数时需要注意以下问题:
  1. 代码必须用-O选项编译来展开内敛函数
  2. 通过ioperm和iopl系统调用来获取单独端口或者整个I/O地址空间的权限(两个函数是x86平台)
  3. 程序要调用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);
I/O存储器的分配情况可以从/proc/iomem获取。
使用完毕后,内存区域需要释放:
1 void release_mem_region(unsigned long start, unsigned long len);
若要判断I/O存储区域是否可用:
1 int check_mem_region(unsigned long start, unsigned long len);
当然,判断是否可用后还需要调用ioremap函数:
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);
这个函数将count指明的数量I/O端口重新映射,使之可像I/O存储器那样进行操作——可以对函数的返回值进行ioread8之类的操作。操作完成后,需要调用函数解除映射:
1 void *ioport_unmap(void *addr);
当然,在调用此映射函数之前,还需要调用request_region。
 
书中说ISA存储器位于640KB(0xA0000)和1MB(0x100000)之间,但是我在系统中的/proc/iomem没有发现。
 
 
 
志:每个端口都有一个地址,相应的也有一个寄存器;因此对于端口的读写其实是对寄存器的读写。每个端口的寄存器的每一位可能对应一个物理引脚的电平状态。
 
 
 
 
 
 
 
posted @ 2018-11-16 15:19  glob  阅读(223)  评论(0编辑  收藏  举报