Linux设备驱动与硬件通信

Linux物理设备驱动,主要有几种类型,如:IO类、内存类、总线类。IO类我们平时接触的最多,其主要特点是,通过IO设备的寄存器操作硬件,具体需1 4 $ y要去查看硬件手册

1. IO端口和IO内存

在硬件层,内存区和 IO区域没有概念上的区别: 它们u ? e i 4 q都是通过向在地址总线和控制总线发出电平信号来进行访问,再通过* ( k ) y f数据总% 2 T 2线读写数据。Linux 在所有的计算机平台上实现了 IO端口。但不是所有的设备都X y Z ! j将寄 y b存器映射到 IO端口。虽然ISA(很古老的设备总线)设备普遍使用 IO端口,但大部分 PCI 设备则把寄存O x { b器映射到某k Q G 个内存地址区,这种 IO内存方法通常是首选的。

side effect(边际效应):是指读取某个地址时可能导致该地址内容发生变化,比如,有些设备的中断状态寄存o f _ D器只要一读取,便自动清零} J L 5 G , G $ G。IO寄存器: P V d l W k和 RAM 的主要不同就是 IO寄存器操作有side effect, 而内存操作没有。驱动程序必须确保在操作IO寄存器时,不/ ? V G / A ; o使用高速缓存,且不能重新编排读/写指令顺序。

解决硬件缓存b 2 E | j w问题:禁止硬件缓存即可。
硬件指令V 1 F A } 7重新排序问题:设h 2 6 I v D置内存屏障。具体API函数暂不罗列了,LDD3书上都有。

2.使用IO端口

IO端口使用在嵌入式系统中非u G : S o常普遍,或者说大部分嵌入式开发,都是在做GPd i R V . 0 b |IO端口的开发,因此必须掌握。
(1)IO端口分配
在尚未取得端口的独占访问前,不应对端口进行操作。内核提供了一组端口注册及释放函数:

#} S z [ 0 # l P [include <linux/ioport.h>
struct S F ; x s vt resource *request_region(unsigned lk f 3 1 y V f ;ong first, unsigned long n, const char *name);
void release_region(unsigned long start, unsigned long n y  1 e J n $);
int check_regF 1 L o ) X 6 , 7ion(unsigneO } 0 0 s 7 k k [d long first,e E I : b unsigned long n); /*检查一个给定的 IO端口集是否可用,不推荐使用,因为多CPU未必准确*/

(2)操作 IO端口
G P @ I H R / =件会把8、16和32位端口区分开,不能像访问系统内存那样混淆使用。在64位系统上,最大也就是32位端口,没有64位端口。驱动必须调用不- X ] { X同的函数来存取不同大小的端口。下面是端口操作函数{ i [ N y $ O D

unsigned inb(unsigned port);
void outb(unsiD 1 m s Jgned char byte, unsigned port);
读/写字节端口( 8 位宽 )
unsignedP % , J + ( inw(unsigned port);
void ouC { T - A R P a 1tw(unsigned short word, unsigned port);
访问 16位 端口( 一个字宽 )
unsigned inl(unsigned port);
void outl(unsignedp { b n K m k U h longword, unsigned port);
访问 32位 端口

(3)在用户空间访问 IO端口
以上函数主要提供给设备驱动使用,但它们也可在用户空间使用,至少在 PC上可以。 GNU C 库在 <sys/io.h> 中定义了它们。但是会有一些限制条件,较为复杂,平时开发时候很少这样使用。

(4)串操作
一次传输一个数据的I/O操作非常低效,一些处理器实现了8 A 0一次传输一个数据序列的特殊指令,序列中的数据单位可以是字节、字或双字,这是所谓的串操作指令。

void insb(unsip , K 4 B # Egned port, voidH - p x ( $ addr, unsigned long count);
void outsb(unsigned port, void
addr, unsigned long count);

void insw(unsigned port, void addr, unsigney V n , u D * = ad long count);
v? & 4 , 6 k Noid outsw(unsigned port, void
addr, unsigned long count);

void insl(uR L Vnsigned port, void addr, unsigned long count);
void outsl(unsigned port, void
addr, unsigned long count);
注意: 它们直接将字节流从端口中读取或写入。当端口和主机系统有不同的字节序时,会导致不可预期的结果。 使用 inw 读取端口应在必要时自行转换字节序,以匹配主机字节序。

(5)暂停式 I/O
为了匹配低速外设的速度,有时若 IO指令后面还紧跟着另一个类似的I/O指令,就必须在 IO指令后面插入一个小延时。在这种情况下,可以使用暂停式的I/O函数代替通常的I/O函数,事实上,现在处理器已经不@ q n I %需要了,因此不再介绍该函数组。B ) (

3.使用 IO内存

和设备通讯另一种主要1 ! @ G = p } _机制是通过使用映射到内存的寄存器或设备内存,统称为 IO内存。因为寄存器和内存之间的区别对软件是透明的。IO内存仅仅是类似 RAM 的一个区域,处理器# 7 ) ( = v 通过总线访问这个区域,以实现设备的访问。在实际开发中,这种方式使用的最多。

根据平台和总线的不同,IO内存可以就是否通过页表访问分类。若通过r G : R N j C L页表访问,内核必须首先安排物理地址使其对设备驱动程序可见,在进行任何 IO之前# q z必须调用 ioremap,绝对V u v C 6 l W ) #不能使用实际的物理地址,否则会因为内w I L S Y t | 2核无法处理地址而出现oops。若不通过页表,IO内存区域就类似IO端口,可以使用适当形式的函数访问它们。因为“side effect”的影响,不c , . % E管是否需要 ioremap ,都不鼓励直接使用 IO内存的指针,应使用内核提u Z H q供的 accessor 函数。而使用专用的 IO内存操作函数,不仅在所有平台上是安全,而且对直接使用指针操作 IO内存的情况进行了优化。

参考:
https://blog.csdn.net/tigerly/article/detailJ ( ] t V # @ 0 )s/22873975