ImageVerifierCode 换一换
格式:DOCX , 页数:15 ,大小:24.30KB ,
资源ID:7544585      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/7544585.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(Linux设备驱动程序与硬件通信.docx)为本站会员(b****5)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

Linux设备驱动程序与硬件通信.docx

1、Linux设备驱动程序与硬件通信Linux设备驱动程序-与硬件通信I/O 端口和 I/O 内存每种外设都是通过读写寄存器来进行控制。 在硬件层,内存区和 I/O 区域没有概念上的区别: 它们都是通过向在地址总线和控制总线发出电平信号来进行访问,再通过数据总线读写数据。因为外设要与IO总线匹配,而大部分流行的 I/O 总线是基于个人计算机模型(主要是 x86 家族:它为读和写 I/O 端口提供了独立的线路和特殊的 CPU 指令),所以即便那些没有单独I/O 端口地址空间的处理器,在访问外设时也要模拟成读写IO端口。这一功能通常由外围芯片组(PC 中的南北桥)或 CPU 中的附加电路实现(嵌入式中

2、的方法) 。Linux 在所有的计算机平台上实现了 I/O 端口。但不是所有的设备都将寄存器映射到I/O 端口。虽然ISA设备普遍使用 I/O 端口,但大部分 PCI 设备则把寄存器映射到某个内存地址区,这种 I/O内存方法通常是首选的。因为它无需使用特殊的处理器指令,CPU核访问内存更有效率,且编译器在访问内存时在寄存器分配和寻址模式的选择上有更多自由。 I/O 寄存器和常规内存 在进入这部分学习的时候,首先要理解一个概念:sideeffect,书中译为边际效应,第二版译为副作用。我觉得不管它是怎么被翻译的,都不可能精准表达原作者的意思,所以我个人认为记住sideeffect就好。下面来讲讲

3、side effect的含义。我先贴出两个网上已有的两种说法(在这里谢谢两位高人的分享): 第一种说法:3. side effect(译为边际效应或副作用):是指读取某个地址时可能导致该地址内容发生变化,比如,有些设备的中断状态寄存器只要一读取,便自动清零。I/O寄存器的操作具有side effect,因此,不能对其操作不能使用cpu缓存。原文网址:第二种说法:说一下我的理解:I/O端口与实际外部设备相关联,通过访问I/O端口控制外部设备,“边际效应”是指控制设备(读取或写入)生效,访问I/O口的主要目的就是边际效应,不像访问普通的内存,只是在一个位置存储或读取一个数值,没有别的含义了。我是基

4、于ARM平台理解的,在linux设备驱动程序第二版中的说法是“副作用”,不是“边际效应”。原文网址:结合以上两种说法和自己看Linux设备驱动程序(第3版)的理解,我个人认为可以这样解释:side effect是指:访问I/O寄存器时,不仅仅会像访问普通内存一样影响存储单元的值,更重要的是它可能改变CPU的I/O端口电平、输出时序或CPU对I/O端口电平的反应等等,从而实现CPU的控制功能。CPU在电路中的意义就是实现其side effect 。I/O 寄存器和 RAM 的主要不同就是 I/O 寄存器操作有side effect, 而内存操作没有。因为存储单元的访问速度对 CPU 性能至关重要

5、,编译器会对源代码进行优化,主要是: 使用高速缓存保存数值 和 重新编排读/写指令顺序。但对I/O 寄存器操作来说,这些优化可能造成致命错误。因此,驱动程序必须确保在操作I/O 寄存器时,不使用高速缓存,且不能重新编排读/写指令顺序。解决方法:硬件缓存问题:只要把底层硬件配置(自动地或者通过 Linux 初始化代码)成当访问 I/O 区域时(不管内存还是端口)禁止硬件缓存即可。 硬件指令重新排序问题:在硬件(或其他处理器)必须以一个特定顺序执行的操作之间设置内存屏障(memory barrier)。Linux 提供以下宏来解决所有可能的排序问题:#include linux/kernel.h&

6、gt; void barrier(void) /*告知编译器插入一个内存屏障但是对硬件没有影响。编译后的代码会将当前CPU 寄存器中所有修改过的数值保存到内存中, 并当需要时重新读取它们。可阻止在屏障前后的编译器优化,但硬件能完成自己的重新排序。其实linux/kernel.h> 中并没有这个函数,因为它是在kernel.h包含的头文件compiler.h中定义的*/#include linux/compiler.h># define barrier() _memory_barrier()#include asm/system.h> void rmb(void); /*保证任

7、何出现于屏障前的读在执行任何后续的读之前完成*/void wmb(void); /*保证任何出现于屏障前的写在执行任何后续的写之前完成*/void mb(void); /*保证任何出现于屏障前的读写操作在执行任何后续的读写操作之前完成*/void read_barrier_depends(void); /*一种特殊的、弱些的读屏障形式。rmb 阻止屏障前后的所有读指令的重新排序,read_barrier_depends只阻止依赖于其他读指令返回的数据的读指令的重新排序。区别微小, 且不在所有体系中存在。除非你确切地理解它们的差别,并确信完整的读屏障会增加系统开销,否则应当始终使用 rmb。*/

8、*以上指令是barrier的超集*/void smp_rmb(void); void smp_read_barrier_depends(void); void smp_wmb(void); void smp_mb(void); /*仅当内核为 SMP 系统编译时插入硬件屏障; 否则, 它们都扩展为一个简单的屏障调用。*/典型的应用:writel(dev->registers.addr, io_destination_address);writel(dev->registers.size, io_size);writel(dev->registers.operation, DE

9、V_READ);wmb();/*类似一条分界线,上面的写操作必然会在下面的写操作前完成,但是上面的三个写操作的排序无法保证*/writel(dev->registers.control, DEV_GO);内存屏障影响性能,所以应当只在确实需要它们的地方使用。不同的类型对性能的影响也不同,因此要尽可能地使用需要的特定类型。值得注意的是大部分处理同步的内核原语,例如自旋锁和atomic_t,也可作为内存屏障使用。某些体系允许赋值和内存屏障组合,以提高效率。它们定义如下: #define set_mb(var, value) do var = value; mb(); while 0/*以下宏

10、定义在ARM体系中不存在*/#define set_wmb(var, value) do var = value; wmb(); while 0#define set_rmb(var, value) do var = value; rmb(); while 0使用do.while 结构来构造宏是标准 C 的惯用方法,它保证了扩展后的宏可在所有上下文环境中被作为一个正常的 C 语句执行。 使用 I/O 端口I/O 端口是驱动用来和许多设备之间的通讯方式。I/O 端口分配在尚未取得端口的独占访问前,不应对端口进行操作。内核提供了一个注册用的接口,允许驱动程序声明它需要的端口:#include li

11、nux/ioport.h>struct resource *request_region(unsigned long first, unsigned long n, const char *name);/*告诉内核:要使用从 first 开始的 n 个端口,name 参数为设备名。若分配成功返回非 NULL,否则将无法使用需要的端口。*/ /*所有的的端口分配显示在 /proc/ioports 中。若不能分配到需要的端口,则可以到这里看看谁先用了。*/*当用完 I/O 端口集(可能在模块卸载时), 应当将它们返回给系统*/void release_region(unsigned long

12、 start, unsigned long n); int check_region(unsigned long first, unsigned long n); /*检查一个给定的 I/O 端口集是否可用,若不可用, 返回值是一个负错误码。不推荐使用*/操作 I/O 端口在驱动程序注册I/O 端口后,就可以读/写这些端口。大部分硬件会把8、16和32位端口区分开,不能像访问系统内存那样混淆使用。驱动必须调用不同的函数来存取不同大小的端口。只支持内存映射的 I/O 寄存器的计算机体系通过重新映射I/O端口到内存地址来伪装端口I/O。为了提高移植性,内核向驱动隐藏了这些细节。Linux 内核头文

13、件(体系依赖的头文件 ) 定义了下列内联函数(有的体系是宏,有的不存在)来访问 I/O 端口:unsigned inb(unsigned port); void outb(unsigned char byte, unsigned port); /*读/写字节端口( 8 位宽 )。port 参数某些平台定义为 unsigned long ,有些为 unsigned short 。 inb 的返回类型也体系而不同。*/unsigned inw(unsigned port); void outw(unsigned short word, unsigned port); /*访问 16位 端口( 一个

14、字宽 )*/unsigned inl(unsigned port); void outl(unsigned longword, unsigned port); /*访问 32位 端口。 longword 声明有的平台为 unsigned long ,有的为 unsigned int。*/在用户空间访问 I/O 端口以上函数主要提供给设备驱动使用,但它们也可在用户空间使用,至少在 PC上可以。 GNU C 库在  中定义了它们。如果在用户空间代码中使用必须满足以下条件:(1)程序必须使用 -O 选项编译来强制扩展内联函数。(2)必须用ioperm 和 iopl 系统调用(#i

15、nclude ) 来获得对端口 I/O 操作的权限。ioperm 为获取单独端口操作权限,而 iopl 为整个 I/O 空间的操作权限。 (x86 特有的)(3)程序以 root 来调用 ioperm 和 iopl,或是其父进程必须以 root 获得端口操作权限。(x86 特有的)若平台没有 ioperm 和 iopl 系统调用,用户空间可以仍然通过使用 /dev/prot 设备文件访问 I/O 端口。注意:这个文件的定义是体系相关的,并且I/O 端口必须先被注册。串操作除了一次传输一个数据的I/O操作,一些处理器实现了一次传输一个数据序列的特殊指令,序列中的数据单位可以是字节、字或双字,这是

16、所谓的串操作指令。它们完成任务比一个 C 语言循环更快。下列宏定义实现了串I/O,它们有的通过单个机器指令实现;但如果目标处理器没有进行串 I/O的指令,则通过执行一个紧凑的循环实现。 有的体系的原型如下:void insb(unsigned port, void *addr, unsigned long count); void outsb(unsigned port, void *addr, unsigned long count); void insw(unsigned port, void *addr, unsigned long count); void outsw(unsigned

17、 port, void *addr, unsigned long count); void insl(unsigned port, void *addr, unsigned long count); void outsl(unsigned port, void *addr, unsigned long count); 使用时注意: 它们直接将字节流从端口中读取或写入。当端口和主机系统有不同的字节序时,会导致不可预期的结果。 使用 inw 读取端口应在必要时自行转换字节序,以匹配主机字节序。暂停式 I/O为了匹配低速外设的速度,有时若 I/O 指令后面还紧跟着另一个类似的I/O指令,就必须在 I

18、/O 指令后面插入一个小延时。在这种情况下,可以使用暂停式的I/O函数代替通常的I/O函数,它们的名字以 _p 结尾,如 inb_p、outb_p等等。这些函数定义被大部分体系支持,尽管它们常常被扩展为与非暂停式I/O同样的代码。因为如果体系使用一个合理的现代外设总线,就没有必要额外暂停。细节可参考平台的 asm 子目录的 io.h文件。以下是includeasm-armio.h中的宏定义:#define outb_p(val,port)    outb(val),(port)#define outw_p(val,port)    outw(val),(

19、port)#define outl_p(val,port)    outl(val),(port)#define inb_p(port)        inb(port)#define inw_p(port)        inw(port)#define inl_p(port)        inl(port)#define outsb_p(port,from,len)   

20、 outsb(port,from,len)#define outsw_p(port,from,len)    outsw(port,from,len)#define outsl_p(port,from,len)    outsl(port,from,len)#define insb_p(port,to,len)    insb(port,to,len)#define insw_p(port,to,len)    insw(port,to,len)#define insl_p(port,to,len)  &

21、nbsp; insl(port,to,len)由此可见,由于ARM使用内部总线,就没有必要额外暂停,所以暂停式的I/O函数被扩展为与非暂停式I/O 同样的代码。平台相关性由于自身的特性,I/O 指令与处理器密切相关的,非常难以隐藏系统间的不同。所以大部分的关于端口 I/O 的源码是平台依赖的。以下是x86和ARM所使用函数的总结:IA-32 (x86) x86_64 这个体系支持所有的以上描述的函数,端口号是 unsigned short 类型。 ARM 端口映射到内存,支持所有函数。串操作 用C语言实现。端口是 unsigned int 类型。 使用 I/O 内存 除了 x86上普遍使用的I

22、/O端口外,和设备通讯另一种主要机制是通过使用映射到内存的寄存器或设备内存,统称为 I/O 内存。因为寄存器和内存之间的区别对软件是透明的。I/O内存仅仅是类似 RAM 的一个区域,处理器通过总线访问这个区域,以实现设备的访问。根据平台和总线的不同,I/O内存可以就是否通过页表访问分类。若通过页表访问,内核必须首先安排物理地址使其对设备驱动程序可见,在进行任何 I/O 之前必须调用ioremap。若不通过页表,I/O 内存区域就类似I/O 端口,可以使用适当形式的函数访问它们。因为“side effect”的影响,不管是否需要 ioremap ,都不鼓励直接使用 I/O 内存的指针。而使用专用

23、的 I/O 内存操作函数,不仅在所有平台上是安全,而且对直接使用指针操作 I/O 内存的情况进行了优化。I/O 内存分配和映射I/O 内存区域使用前必须先分配,函数接口在 定义:struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);/* 从 start 开始,分配一个 len 字节的内存区域。成功返回一个非NULL指针,否则返回NULL。所有的 I/O 内存分配情况都 /proc/iomem 中列出。*/*I/O内存区域在不再需要时应当释放*/void release_me

24、m_region(unsigned long start, unsigned long len); /*一个旧的检查 I/O 内存区可用性的函数,不推荐使用*/int check_mem_region(unsigned long start, unsigned long len); 然后必须设置一个映射,由 ioremap 函数实现,此函数专门用来为I/O内存区域分配虚拟地址。经过ioremap 之后,设备驱动即可访问任意的 I/O 内存地址。注意:ioremap返回的地址不应当直接引用;应使用内核提供的 accessor 函数。以下为函数定义: #include asm/io.h>vo

25、id *ioremap(unsigned long phys_addr, unsigned long size);void *ioremap_nocache(unsigned long phys_addr, unsigned long size);/*如果控制寄存器也在该区域,应使用的非缓存版本,以实现side effect。*/void iounmap(void * addr);访问I/O 内存访问I/O 内存的正确方式是通过一系列专用于此目的的函数(在 中定义的): /*I/O 内存读函数*/unsigned int ioread8(void *addr);unsigned int ior

26、ead16(void *addr);unsigned int ioread32(void *addr);/*addr 是从 ioremap 获得的地址(可能包含一个整型偏移量), 返回值是从给定 I/O 内存读取的值*/*对应的I/O 内存写函数*/void iowrite8(u8 value, void *addr);void iowrite16(u16 value, void *addr);void iowrite32(u32 value, void *addr);/*读和写一系列值到一个给定的 I/O 内存地址,从给定的 buf 读或写 count 个值到给定的 addr */void

27、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, unsi

28、gned long count);void iowrite32_rep(void *addr, const void *buf, unsigned long count);/*需要操作一块 I/O 地址,使用一下函数*/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);/*旧函数

29、接口,仍可工作, 但不推荐。*/unsigned readb(address);unsigned readw(address);unsigned readl(address); void writeb(unsigned value, address);void writew(unsigned value, address);void writel(unsigned value, address); 像 I/O 内存一样使用端口一些硬件有一个有趣的特性:一些版本使用 I/O 端口,而其他的使用 I/O 内存。为了统一编程接口,使驱动程序易于编写,2.6 内核提供了一个ioport_map函数:v

30、oid *ioport_map(unsigned long port, unsigned int count);/*重映射 count 个I/O 端口,使其看起来像 I/O 内存。,此后,驱动程序可以在返回的地址上使用 ioread8 和同类函数。其在编程时消除了I/O 端口和I/O 内存的区别。/*这个映射应当在它不再被使用时撤销:*/void ioport_unmap(void *addr); /*注意:I/O 端口仍然必须在重映射前使用 request_region 分配I/O 端口。ARM9不支持这两个函数!*/上面是基于Linux设备驱动程序(第3版)的介绍,以下分析 ARM9的s3

31、c2440A的linux驱动接口。ARM9的linux驱动接口s3c24x0处理器是使用I/O内存的,也就是说:他们的外设接口是通过读写相应的寄存器实现的,这些寄存器和内存是使用单一的地址空间,并使用和读写内存一样的指令。所以推荐使用I/O内存的相关指令。但这并不表示I/O端口的指令在s3c24x0中不可用。但是只要你注意其源码,你就会发现:其实I/O端口的指令只是一个外壳,内部还是使用和I/O内存一样的代码。以下列出一些:I/O端口 #define outb(v,p)        _raw_writeb(v,_io(p)#define outw(v,p)        _raw_writew(_force _u16)     

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1