[ZLG-ARM] Linux设备驱动程序学习-与硬件通信

[复制链接]
1820|1
 楼主| reeper 发表于 2009-4-9 14:42 | 显示全部楼层 |阅读模式
在学习有关I/O总线的内容时,最好先看看相关的知识:从PC总线到ARM的内部总线&nbsp;<br /><br />I/O&nbsp;端口和&nbsp;I/O&nbsp;内存<br />每种外设都是通过读写寄存器来进行控制。&nbsp;<br /><br />在硬件层,内存区和&nbsp;I/O&nbsp;区域没有概念上的区别:&nbsp;它们都是通过向在地址总线和控制总线发出电平信号来进行访问,再通过数据总线读写数据。<br /><br />因为外设要与IO总线匹配,而大部分流行的&nbsp;I/O&nbsp;总线是基于个人计算机模型(主要是&nbsp;x86&nbsp;家族:它为读和写&nbsp;I/O&nbsp;端口提供了独立的线路和特殊的&nbsp;CPU&nbsp;指令),所以即便那些没有单独I/O&nbsp;端口地址空间的处理器,在访问外设时也要模拟成读写IO端口。这一功能通常由外围芯片组(PC&nbsp;中的南北桥)或&nbsp;CPU&nbsp;中的附加电路实现(嵌入式中的方法)&nbsp;。<br /><br />Linux&nbsp;在所有的计算机平台上实现了&nbsp;I/O&nbsp;端口。但不是所有的设备都将寄存器映射到&nbsp;I/O&nbsp;端口。虽然ISA设备普遍使用&nbsp;I/O&nbsp;端口,但大部分&nbsp;PCI&nbsp;设备则把寄存器映射到某个内存地址区,这种&nbsp;I/O&nbsp;内存方法通常是首选的。因为它无需使用特殊的处理器指令,CPU&nbsp;核访问内存更有效率,且编译器在访问内存时在寄存器分配和寻址模式的选择上有更多自由。&nbsp;<br /><br /><br />--------------------------------------------------------------------------------<br /><br /><br />I/O&nbsp;寄存器和常规内存&nbsp;<br />在进入这部分学习的时候,首先要理解一个概念:side&nbsp;effect,书中译为边际效应,第二版译为副作用。我觉得不管它是怎么被翻译的,都不可能精准表达原作者的意思,所以我个人认为记住side&nbsp;effect就好。下面来讲讲side&nbsp;effect的含义。我先贴出两个网上已有的两种说法(在这里谢谢两位高人的分享):&nbsp;第一种说法:<br /><br />3.&nbsp;side&nbsp;effect(译为边际效应或副作用):是指读取某个地址时可能导致该地址内容发生变化,比如,有些设备的中断状态寄存器只要一读取,便自动清零。I/O寄存器的操作具有side&nbsp;effect,因此,不能对其操作不能使用cpu缓存。<br /><br />原文网址:http://qinbh.blog.sohu.com/62733495.html<br /><br />第二种说法:<br /><br />说一下我的理解:I/O端口与实际外部设备相关联,通过访问I/O端口控制外部设备,“边际效应”是指控制设备(读取或写入)生效,访问I/O口的主要目的就是边际效应,不像访问普通的内存,只是在一个位置存储或读取一个数值,没有别的含义了。我是基于ARM平台理解的,在《linux设备驱动程序》第二版中的说法是“副作用”,不是“边际效应”。<br /><br />原文网址:http://linux.chinaunix.net/bbs/viewthread.php?tid=890636&page=1#pid6312646<br /><br /><br /><br />结合以上两种说法和自己看《Linux设备驱动程序(第3版)》的理解,我个人认为可以这样解释:<br /><br />side&nbsp;effect&nbsp;是指:访问I/O寄存器时,不仅仅会像访问普通内存一样影响存储单元的值,更重要的是它可能改变CPU的I/O端口电平、输出时序或CPU对I/O端口电平的反应等等,从而实现CPU的控制功能。CPU在电路中的意义就是实现其side&nbsp;effect&nbsp;。&nbsp;<br /><br /><br />I/O&nbsp;寄存器和&nbsp;RAM&nbsp;的主要不同就是&nbsp;I/O&nbsp;寄存器操作有side&nbsp;effect,&nbsp;而内存操作没有。<br /><br />因为存储单元的访问速度对&nbsp;CPU&nbsp;性能至关重要,编译器会对源代码进行优化,主要是:&nbsp;使用高速缓存保存数值&nbsp;和&nbsp;重新编排读/写指令顺序。但对I/O&nbsp;寄存器操作来说,这些优化可能造成致命错误。因此,驱动程序必须确保在操作I/O&nbsp;寄存器时,不使用高速缓存,且不能重新编排读/写指令顺序。<br /><br />解决方法:<br /><br />硬件缓存问题:只要把底层硬件配置(自动地或者通过&nbsp;Linux&nbsp;初始化代码)成当访问&nbsp;I/O&nbsp;区域时(不管内存还是端口)禁止硬件缓存即可。&nbsp;<br /><br />硬件指令重新排序问题:在硬件(或其他处理器)必须以一个特定顺序执行的操作之间设置内存屏障(memory&nbsp;barrier)。<br /><br />Linux&nbsp;提供以下宏来解决所有可能的排序问题:<br /><br />#include&nbsp;<br />void&nbsp;barrier(void)&nbsp;/*告知编译器插入一个内存屏障但是对硬件没有影响。编译后的代码会将当前CPU&nbsp;寄存器中所有修改过的数值保存到内存中,&nbsp;并当需要时重新读取它们。可阻止在屏障前后的编译器优化,但硬件能完成自己的重新排序。其实&nbsp;中并没有这个函数,因为它是在kernel.h包含的头文件compiler.h中定义的*/<br />#include&nbsp;<br />#&nbsp;define&nbsp;barrier()&nbsp;__memory_barrier()<br /><br />#include&nbsp;<br />void&nbsp;rmb(void);&nbsp;/*保证任何出现于屏障前的读在执行任何后续的读之前完成*/<br />void&nbsp;wmb(void);&nbsp;/*保证任何出现于屏障前的写在执行任何后续的写之前完成*/<br />void&nbsp;mb(void);&nbsp;/*保证任何出现于屏障前的读写操作在执行任何后续的读写操作之前完成*/<br />void&nbsp;read_barrier_depends(void);&nbsp;/*一种特殊的、弱些的读屏障形式。rmb&nbsp;阻止屏障前后的所有读指令的重新排序,read_barrier_depends&nbsp;只阻止依赖于其他读指令返回的数据的读指令的重新排序。区别微小,&nbsp;且不在所有体系中存在。除非你确切地理解它们的差别,&nbsp;并确信完整的读屏障会增加系统开销,否则应当始终使用&nbsp;rmb。*/<br />/*以上指令是barrier的超集*/<br /><br /><br />void&nbsp;smp_rmb(void);&nbsp;<br />void&nbsp;smp_read_barrier_depends(void);&nbsp;<br />void&nbsp;smp_wmb(void);&nbsp;<br />void&nbsp;smp_mb(void);&nbsp;<br />/*仅当内核为&nbsp;SMP&nbsp;系统编译时插入硬件屏障;&nbsp;否则,&nbsp;它们都扩展为一个简单的屏障调用。*/<br /><br /><br />典型的应用:<br /><br />writel(dev-&gtregisters.addr,&nbsp;io_destination_address);<br />writel(dev-&gtregisters.size,&nbsp;io_size);<br />writel(dev-&gtregisters.operation,&nbsp;DEV_READ);<br />wmb();/*类似一条分界线,上面的写操作必然会在下面的写操作前完成,但是上面的三个写操作的排序无法保证*/<br />writel(dev-&gtregisters.control,&nbsp;DEV_GO);<br /><br /><br />内存屏障影响性能,所以应当只在确实需要它们的地方使用。不同的类型对性能的影响也不同,因此要尽可能地使用需要的特定类型。值得注意的是大部分处理同步的内核原语,例如自旋锁和atomic_t,也可作为内存屏障使用。<br /><br />某些体系允许赋值和内存屏障组合,以提高效率。它们定义如下:&nbsp;#define&nbsp;set_mb(var,&nbsp;value)&nbsp;do&nbsp;{var&nbsp;=&nbsp;value;&nbsp;mb();}&nbsp;while&nbsp;0<br />/*以下宏定义在ARM体系中不存在*/<br />#define&nbsp;set_wmb(var,&nbsp;value)&nbsp;do&nbsp;{var&nbsp;=&nbsp;value;&nbsp;wmb();}&nbsp;while&nbsp;0<br />#define&nbsp;set_rmb(var,&nbsp;value)&nbsp;do&nbsp;{var&nbsp;=&nbsp;value;&nbsp;rmb();}&nbsp;while&nbsp;0<br /><br /><br /><br />使用do...while&nbsp;结构来构造宏是标准&nbsp;C&nbsp;的惯用方法,它保证了扩展后的宏可在所有上下文环境中被作为一个正常的&nbsp;C&nbsp;语句执行。&nbsp;<br /><br /><br />--------------------------------------------------------------------------------<br /><br /><br />使用&nbsp;I/O&nbsp;端口<br /><br />I/O&nbsp;端口是驱动用来和许多设备之间的通讯方式。<br />I/O&nbsp;端口分配<br />在尚未取得端口的独占访问前,不应对端口进行操作。内核提供了一个注册用的接口,允许驱动程序声明它需要的端口:<br />#include&nbsp;<br />struct&nbsp;resource&nbsp;*request_region(unsigned&nbsp;long&nbsp;first,&nbsp;unsigned&nbsp;long&nbsp;n,&nbsp;const&nbsp;char&nbsp;*name);/*告诉内核:要使用从&nbsp;first&nbsp;开始的&nbsp;n&nbsp;个端口,name&nbsp;参数为设备名。若分配成功返回非&nbsp;NULL,否则将无法使用需要的端口。*/&nbsp;<br />/*所有的的端口分配显示在&nbsp;/proc/ioports&nbsp;中。若不能分配到需要的端口,则可以到这里看看谁先用了。*/<br /><br />/*当用完&nbsp;I/O&nbsp;端口集(可能在模块卸载时),&nbsp;应当将它们返回给系统*/<br />void&nbsp;release_region(unsigned&nbsp;long&nbsp;start,&nbsp;unsigned&nbsp;long&nbsp;n);&nbsp;<br /><br />int&nbsp;check_region(unsigned&nbsp;long&nbsp;first,&nbsp;unsigned&nbsp;long&nbsp;n);&nbsp;<br />/*检查一个给定的&nbsp;I/O&nbsp;端口集是否可用,若不可用,&nbsp;返回值是一个负错误码。不推荐使用*/<br /><br /><br />操作&nbsp;I/O&nbsp;端口<br /><br />在驱动程序注册I/O&nbsp;端口后,就可以读/写这些端口。大部分硬件会把8、16和32位端口区分开,不能像访问系统内存那样混淆使用。驱动必须调用不同的函数来存取不同大小的端口。<br /><br />只支持内存映射的&nbsp;I/O&nbsp;寄存器的计算机体系通过重新映射I/O端口到内存地址来伪装端口I/O。为了提高移植性,内核向驱动隐藏了这些细节。Linux&nbsp;内核头文件(体系依赖的头文件&nbsp;)&nbsp;定义了下列内联函数(有的体系是宏,有的不存在)来访问&nbsp;I/O&nbsp;端口:<br /><br />unsigned&nbsp;inb(unsigned&nbsp;port);&nbsp;<br />void&nbsp;outb(unsigned&nbsp;char&nbsp;byte,&nbsp;unsigned&nbsp;port);&nbsp;<br />/*读/写字节端口(&nbsp;8&nbsp;位宽&nbsp;)。port&nbsp;参数某些平台定义为&nbsp;unsigned&nbsp;long&nbsp;,有些为&nbsp;unsigned&nbsp;short&nbsp;。&nbsp;inb&nbsp;的返回类型也体系而不同。*/<br /><br />unsigned&nbsp;inw(unsigned&nbsp;port);&nbsp;<br />void&nbsp;outw(unsigned&nbsp;short&nbsp;word,&nbsp;unsigned&nbsp;port);&nbsp;<br />/*访问&nbsp;16位&nbsp;端口(&nbsp;一个字宽&nbsp;)*/<br /><br />unsigned&nbsp;inl(unsigned&nbsp;port);&nbsp;<br />void&nbsp;outl(unsigned&nbsp;longword,&nbsp;unsigned&nbsp;port);&nbsp;<br />/*访问&nbsp;32位&nbsp;端口。&nbsp;longword&nbsp;声明有的平台为&nbsp;unsigned&nbsp;long&nbsp;,有的为&nbsp;unsigned&nbsp;int。*/<br /><br /><br />在用户空间访问&nbsp;I/O&nbsp;端口<br /><br />以上函数主要提供给设备驱动使用,但它们也可在用户空间使用,至少在&nbsp;PC上可以。&nbsp;GNU&nbsp;C&nbsp;库在&nbsp;中定义了它们。如果在用户空间代码中使用必须满足以下条件:<br /><br />(1)程序必须使用&nbsp;-O&nbsp;选项编译来强制扩展内联函数。<br /><br />(2)必须用ioperm&nbsp;和&nbsp;iopl&nbsp;系统调用(#include&nbsp;)&nbsp;来获得对端口&nbsp;I/O&nbsp;操作的权限。ioperm&nbsp;为获取单独端口操作权限,而&nbsp;iopl&nbsp;为整个&nbsp;I/O&nbsp;空间的操作权限。&nbsp;(x86&nbsp;特有的)<br /><br />(3)程序以&nbsp;root&nbsp;来调用&nbsp;ioperm&nbsp;和&nbsp;iopl,或是其父进程必须以&nbsp;root&nbsp;获得端口操作权限。(x86&nbsp;特有的)<br /><br />若平台没有&nbsp;ioperm&nbsp;和&nbsp;iopl&nbsp;系统调用,用户空间可以仍然通过使用&nbsp;/dev/prot&nbsp;设备文件访问&nbsp;I/O&nbsp;端口。注意:这个文件的定义是体系相关的,并且I/O&nbsp;端口必须先被注册。<br /><br />串操作<br /><br />除了一次传输一个数据的I/O操作,一些处理器实现了一次传输一个数据序列的特殊指令,序列中的数据单位可以是字节、字或双字,这是所谓的串操作指令。它们完成任务比一个&nbsp;C&nbsp;语言循环更快。下列宏定义实现了串I/O,它们有的通过单个机器指令实现;但如果目标处理器没有进行串&nbsp;I/O&nbsp;的指令,则通过执行一个紧凑的循环实现。&nbsp;有的体系的原型如下:<br /><br />void&nbsp;insb(unsigned&nbsp;port,&nbsp;void&nbsp;*addr,&nbsp;unsigned&nbsp;long&nbsp;count);&nbsp;<br />void&nbsp;outsb(unsigned&nbsp;port,&nbsp;void&nbsp;*addr,&nbsp;unsigned&nbsp;long&nbsp;count);&nbsp;<br /><br />void&nbsp;insw(unsigned&nbsp;port,&nbsp;void&nbsp;*addr,&nbsp;unsigned&nbsp;long&nbsp;count);&nbsp;<br />void&nbsp;outsw(unsigned&nbsp;port,&nbsp;void&nbsp;*addr,&nbsp;unsigned&nbsp;long&nbsp;count);&nbsp;<br /><br />void&nbsp;insl(unsigned&nbsp;port,&nbsp;void&nbsp;*addr,&nbsp;unsigned&nbsp;long&nbsp;count);&nbsp;<br />void&nbsp;outsl(unsigned&nbsp;port,&nbsp;void&nbsp;*addr,&nbsp;unsigned&nbsp;long&nbsp;count);&nbsp;<br /><br /><br />使用时注意:&nbsp;它们直接将字节流从端口中读取或写入。当端口和主机系统有不同的字节序时,会导致不可预期的结果。&nbsp;使用&nbsp;inw&nbsp;读取端口应在必要时自行转换字节序,以匹配主机字节序。<br /><br />暂停式&nbsp;I/O<br /><br />为了匹配低速外设的速度,有时若&nbsp;I/O&nbsp;指令后面还紧跟着另一个类似的I/O指令,就必须在&nbsp;I/O&nbsp;指令后面插入一个小延时。在这种情况下,可以使用暂停式的I/O函数代替通常的I/O函数,它们的名字以&nbsp;_p&nbsp;结尾,如&nbsp;inb_p、outb_p等等。&nbsp;这些函数定义被大部分体系支持,尽管它们常常被扩展为与非暂停式I/O&nbsp;同样的代码。因为如果体系使用一个合理的现代外设总线,就没有必要额外暂停。细节可参考平台的&nbsp;asm&nbsp;子目录的&nbsp;io.h&nbsp;文件。以下是includeasm-armio.h中的宏定义:<br /><br />#define&nbsp;outb_p(val,port)&nbsp;&nbsp;&nbsp;&nbsp;outb((val),(port))<br />#define&nbsp;outw_p(val,port)&nbsp;&nbsp;&nbsp;&nbsp;outw((val),(port))<br />#define&nbsp;outl_p(val,port)&nbsp;&nbsp;&nbsp;&nbsp;outl((val),(port))<br />#define&nbsp;inb_p(port)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;inb((port))<br />#define&nbsp;inw_p(port)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;inw((port))<br />#define&nbsp;inl_p(port)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;inl((port))<br /><br />#define&nbsp;outsb_p(port,from,len)&nbsp;&nbsp;&nbsp;&nbsp;outsb(port,from,len)<br />#define&nbsp;outsw_p(port,from,len)&nbsp;&nbsp;&nbsp;&nbsp;outsw(port,from,len)<br />#define&nbsp;outsl_p(port,from,len)&nbsp;&nbsp;&nbsp;&nbsp;outsl(port,from,len)<br />#define&nbsp;insb_p(port,to,len)&nbsp;&nbsp;&nbsp;&nbsp;insb(port,to,len)<br />#define&nbsp;insw_p(port,to,len)&nbsp;&nbsp;&nbsp;&nbsp;insw(port,to,len)<br />#define&nbsp;insl_p(port,to,len)&nbsp;&nbsp;&nbsp;&nbsp;insl(port,to,len)<br /><br /><br />由此可见,由于ARM使用内部总线,就没有必要额外暂停,所以暂停式的I/O函数被扩展为与非暂停式I/O&nbsp;同样的代码。<br /><br />平台相关性<br /><br />由于自身的特性,I/O&nbsp;指令与处理器密切相关的,非常难以隐藏系统间的不同。所以大部分的关于端口&nbsp;I/O&nbsp;的源码是平台依赖的。以下是x86和ARM所使用函数的总结:<br /><br />IA-32&nbsp;(x86)&nbsp;<br />x86_64&nbsp;<br />这个体系支持所有的以上描述的函数,端口号是&nbsp;unsigned&nbsp;short&nbsp;类型。&nbsp;<br />ARM&nbsp;<br />端口映射到内存,支持所有函数。串操作&nbsp;用C语言实现。端口是&nbsp;unsigned&nbsp;int&nbsp;类型。&nbsp;<br />--------------------------------------------------------------------------------<br />使用&nbsp;I/O&nbsp;内存&nbsp;<br />除了&nbsp;x86上普遍使用的I/O&nbsp;端口外,和设备通讯另一种主要机制是通过使用映射到内存的寄存器或设备内存,统称为&nbsp;I/O&nbsp;内存。因为寄存器和内存之间的区别对软件是透明的。I/O&nbsp;内存仅仅是类似&nbsp;RAM&nbsp;的一个区域,处理器通过总线访问这个区域,以实现设备的访问。<br /><br />根据平台和总线的不同,I/O&nbsp;内存可以就是否通过页表访问分类。若通过页表访问,内核必须首先安排物理地址使其对设备驱动程序可见,在进行任何&nbsp;I/O&nbsp;之前必须调用&nbsp;ioremap。若不通过页表,I/O&nbsp;内存区域就类似I/O&nbsp;端口,可以使用适当形式的函数访问它们。因为“side&nbsp;effect”的影响,不管是否需要&nbsp;ioremap&nbsp;,都不鼓励直接使用&nbsp;I/O&nbsp;内存的指针。而使用专用的&nbsp;I/O&nbsp;内存操作函数,不仅在所有平台上是安全,而且对直接使用指针操作&nbsp;I/O&nbsp;内存的情况进行了优化。<br /><br />I/O&nbsp;内存分配和映射<br /><br />I/O&nbsp;内存区域使用前必须先分配,函数接口在&nbsp;定义:<br /><br />struct&nbsp;resource&nbsp;*request_mem_region(unsigned&nbsp;long&nbsp;start,&nbsp;unsigned&nbsp;long&nbsp;len,&nbsp;char&nbsp;*name);/*&nbsp;从&nbsp;start&nbsp;开始,分配一个&nbsp;len&nbsp;字节的内存区域。成功返回一个非NULL指针,否则返回NULL。所有的&nbsp;I/O&nbsp;内存分配情况都&nbsp;/proc/iomem&nbsp;中列出。*/<br /><br />/*I/O内存区域在不再需要时应当释放*/<br />void&nbsp;release_mem_region(unsigned&nbsp;long&nbsp;start,&nbsp;unsigned&nbsp;long&nbsp;len);&nbsp;<br /><br />/*一个旧的检查&nbsp;I/O&nbsp;内存区可用性的函数,不推荐使用*/<br />int&nbsp;check_mem_region(unsigned&nbsp;long&nbsp;start,&nbsp;unsigned&nbsp;long&nbsp;len);&nbsp;<br /><br /><br />然后必须设置一个映射,由&nbsp;ioremap&nbsp;函数实现,此函数专门用来为I/O&nbsp;内存区域分配虚拟地址。经过ioremap&nbsp;之后,设备驱动即可访问任意的&nbsp;I/O&nbsp;内存地址。注意:ioremap&nbsp;返回的地址不应当直接引用;应使用内核提供的&nbsp;accessor&nbsp;函数。以下为函数定义:&nbsp;#include&nbsp;<br />void&nbsp;*ioremap(unsigned&nbsp;long&nbsp;phys_addr,&nbsp;unsigned&nbsp;long&nbsp;size);<br />void&nbsp;*ioremap_nocache(unsigned&nbsp;long&nbsp;phys_addr,&nbsp;unsigned&nbsp;long&nbsp;size);/*如果控制寄存器也在该区域,应使用的非缓存版本,以实现side&nbsp;effect。*/<br />void&nbsp;iounmap(void&nbsp;*&nbsp;addr);<br /><br /><br /><br />访问I/O&nbsp;内存<br /><br />访问I/O&nbsp;内存的正确方式是通过一系列专用于此目的的函数(在&nbsp;中定义的):&nbsp;/*I/O&nbsp;内存读函数*/<br />unsigned&nbsp;int&nbsp;ioread8(void&nbsp;*addr);<br />unsigned&nbsp;int&nbsp;ioread16(void&nbsp;*addr);<br />unsigned&nbsp;int&nbsp;ioread32(void&nbsp;*addr);<br />/*addr&nbsp;是从&nbsp;ioremap&nbsp;获得的地址(可能包含一个整型偏移量),&nbsp;返回值是从给定&nbsp;I/O&nbsp;内存读取的值*/<br /><br />/*对应的I/O&nbsp;内存写函数*/<br />void&nbsp;iowrite8(u8&nbsp;value,&nbsp;void&nbsp;*addr);<br />void&nbsp;iowrite16(u16&nbsp;value,&nbsp;void&nbsp;*addr);<br />void&nbsp;iowrite32(u32&nbsp;value,&nbsp;void&nbsp;*addr);<br /><br />/*读和写一系列值到一个给定的&nbsp;I/O&nbsp;内存地址,从给定的&nbsp;buf&nbsp;读或写&nbsp;count&nbsp;个值到给定的&nbsp;addr&nbsp;*/<br />void&nbsp;ioread8_rep(void&nbsp;*addr,&nbsp;void&nbsp;*buf,&nbsp;unsigned&nbsp;long&nbsp;count);<br />void&nbsp;ioread16_rep(void&nbsp;*addr,&nbsp;void&nbsp;*buf,&nbsp;unsigned&nbsp;long&nbsp;count);<br />void&nbsp;ioread32_rep(void&nbsp;*addr,&nbsp;void&nbsp;*buf,&nbsp;unsigned&nbsp;long&nbsp;count);<br />void&nbsp;iowrite8_rep(void&nbsp;*addr,&nbsp;const&nbsp;void&nbsp;*buf,&nbsp;unsigned&nbsp;long&nbsp;count);<br />void&nbsp;iowrite16_rep(void&nbsp;*addr,&nbsp;const&nbsp;void&nbsp;*buf,&nbsp;unsigned&nbsp;long&nbsp;count);<br />void&nbsp;iowrite32_rep(void&nbsp;*addr,&nbsp;const&nbsp;void&nbsp;*buf,&nbsp;unsigned&nbsp;long&nbsp;count);<br /><br />/*需要操作一块&nbsp;I/O&nbsp;地址,使用一下函数*/<br />void&nbsp;memset_io(void&nbsp;*addr,&nbsp;u8&nbsp;value,&nbsp;unsigned&nbsp;int&nbsp;count);<br />void&nbsp;memcpy_fromio(void&nbsp;*dest,&nbsp;void&nbsp;*source,&nbsp;unsigned&nbsp;int&nbsp;count);<br />void&nbsp;memcpy_toio(void&nbsp;*dest,&nbsp;void&nbsp;*source,&nbsp;unsigned&nbsp;int&nbsp;count);<br /><br />/*旧函数接口,仍可工作,&nbsp;但不推荐。*/<br />unsigned&nbsp;readb(address);<br />unsigned&nbsp;readw(address);<br />unsigned&nbsp;readl(address);&nbsp;<br />void&nbsp;writeb(unsigned&nbsp;value,&nbsp;address);<br />void&nbsp;writew(unsigned&nbsp;value,&nbsp;address);<br />void&nbsp;writel(unsigned&nbsp;value,&nbsp;address);&nbsp;<br /><br /><br /><br />像&nbsp;I/O&nbsp;内存一样使用端口<br />一些硬件有一个有趣的特性:一些版本使用&nbsp;I/O&nbsp;端口,而其他的使用&nbsp;I/O&nbsp;内存。为了统一编程接口,使驱动程序易于编写,2.6&nbsp;内核提供了一个ioport_map函数:<br /><br />void&nbsp;*ioport_map(unsigned&nbsp;long&nbsp;port,&nbsp;unsigned&nbsp;int&nbsp;count);/*重映射&nbsp;count&nbsp;个I/O&nbsp;端口,使其看起来像&nbsp;I/O&nbsp;内存。,此后,驱动程序可以在返回的地址上使用&nbsp;ioread8&nbsp;和同类函数。其在编程时消除了I/O&nbsp;端口和I/O&nbsp;内存的区别。<br /><br />/*这个映射应当在它不再被使用时撤销:*/<br />void&nbsp;ioport_unmap(void&nbsp;*addr);&nbsp;<br /><br />/*注意:I/O&nbsp;端口仍然必须在重映射前使用&nbsp;request_region&nbsp;分配I/O&nbsp;端口。ARM9不支持这两个函数!*/<br /><br /><br /><br /><br />--------------------------------------------------------------------------------<br /><br />上面是基于《Linux设备驱动程序(第3版)》的介绍,以下分析&nbsp;ARM9的s3c2440A的linux驱动接口。<br />ARM9的linux驱动接口<br />s3c24x0处理器是使用I/O内存的,也就是说:他们的外设接口是通过读写相应的寄存器实现的,这些寄存器和内存是使用单一的地址空间,并使用和读写内存一样的指令。所以推荐使用I/O内存的相关指令。<br /><br />但这并不表示I/O端口的指令在s3c24x0中不可用。但是只要你注意其源码,你就会发现:其实I/O端口的指令只是一个外壳,内部还是使用和I/O内存一样的代码。以下列出一些:<br /><br />I/O端口&nbsp;#define&nbsp;outb(v,p)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;__raw_writeb(v,__io(p))<br />#define&nbsp;outw(v,p)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;__raw_writew((__force&nbsp;__u16)&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cpu_to_le16(v),__io(p))<br />#define&nbsp;outl(v,p)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;__raw_writel((__force&nbsp;__u32)&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cpu_to_le32(v),__io(p))<br /><br />#define&nbsp;inb(p)&nbsp;&nbsp;&nbsp;&nbsp;({&nbsp;__u8&nbsp;__v&nbsp;=&nbsp;__raw_readb(__io(p));&nbsp;__v;&nbsp;})<br />#define&nbsp;inw(p)&nbsp;&nbsp;&nbsp;&nbsp;({&nbsp;__u16&nbsp;__v&nbsp;=&nbsp;le16_to_cpu((__force&nbsp;__le16)&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;__raw_readw(__io(p)));&nbsp;__v;&nbsp;})<br />#define&nbsp;inl(p)&nbsp;&nbsp;&nbsp;&nbsp;({&nbsp;__u32&nbsp;__v&nbsp;=&nbsp;le32_to_cpu((__force&nbsp;__le32)&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;__raw_readl(__io(p)));&nbsp;__v;&nbsp;})<br /><br /><br /><br />I/O内存&nbsp;#define&nbsp;ioread8(p)&nbsp;&nbsp;&nbsp;&nbsp;({&nbsp;unsigned&nbsp;int&nbsp;__v&nbsp;=&nbsp;__raw_readb(p);&nbsp;__v;&nbsp;})<br />#define&nbsp;ioread16(p)&nbsp;&nbsp;&nbsp;&nbsp;({&nbsp;unsigned&nbsp;int&nbsp;__v&nbsp;=&nbsp;le16_to_cpu(__raw_readw(p));&nbsp;__v;&nbsp;})<br />#define&nbsp;ioread32(p)&nbsp;&nbsp;&nbsp;&nbsp;({&nbsp;unsigned&nbsp;int&nbsp;__v&nbsp;=&nbsp;le32_to_cpu(__raw_readl(p));&nbsp;__v;&nbsp;})<br /><br />#define&nbsp;iowrite8(v,p)&nbsp;&nbsp;&nbsp;&nbsp;__raw_writeb(v,&nbsp;p)<br />#define&nbsp;iowrite16(v,p)&nbsp;&nbsp;&nbsp;&nbsp;__raw_writew(cpu_to_le16(v),&nbsp;p)<br />#define&nbsp;iowrite32(v,p)&nbsp;&nbsp;&nbsp;&nbsp;__raw_writel(cpu_to_le32(v),&nbsp;p)<br /><br /><br /><br />我对I/O端口的指令和I/O内存的指令都写了相应的驱动程序,都通过了测试。在这里值得注意的有4点:<br /><br />(1)所有的读写指令所赋的地址必须都是虚拟地址,你有两种选择:使用内核已经定义好的地址,如&nbsp;S3C2440_GPJCON等等,这些都是内核定义好的虚拟地址,有兴趣的可以看源码。还有一种方法就是使用自己用ioremap映射的虚拟地址。绝对不能使用实际的物理地址,否则会因为内核无法处理地址而出现oops。<br /><br />(2)在使用I/O指令时,可以不使用request_region和request_mem_region,而直接使用outb、ioread等指令。因为request的功能只是告诉内核端口被谁占用了,如再次request,内核会制止。<br /><br />(3)在使用I/O指令时,所赋的地址数据有时必须通过强制类型转换为&nbsp;unsigned&nbsp;long&nbsp;,不然会有警告(具体原因请看Linux设备驱动程序学习(7)-内核的数据类型)&nbsp;。虽然你的程序可能也可以使用,但是最好还是不要有警告为妙。<br /><br />(4)在includeasm-armarch-s3c2410hardware.h中定义了很多io口的操作函数,有需要可以在驱动中直接使用,很方便。<br /><br />实验源码:<br /><br />IO_port.tar.gz<br /><br />IO_port_test.tar.gz<br /><br />IO_mem.tar.gz<br /><br />IO_mem_test.tar.gz<br /><br />两个模块都实现了阻塞型独享设备的访问控制,并通知内核不支持llseek。具体的测试在IO_port中。<br /><br />测试现象如下:<br /><br />[Tekkaman2440@SBC2440V4]#cd&nbsp;/lib/modules/<br />[Tekkaman2440@SBC2440V4]#insmod&nbsp;IO_port.ko<br />[Tekkaman2440@SBC2440V4]#insmod&nbsp;IO_mem.ko<br />[Tekkaman2440@SBC2440V4]#cat&nbsp;/proc/devices<br />Character&nbsp;devices:<br />&nbsp;&nbsp;1&nbsp;mem<br />&nbsp;&nbsp;2&nbsp;pty<br />&nbsp;&nbsp;3&nbsp;ttyp<br />&nbsp;&nbsp;4&nbsp;/dev/vc/0<br />&nbsp;&nbsp;4&nbsp;tty<br />&nbsp;&nbsp;4&nbsp;ttyS<br />&nbsp;&nbsp;5&nbsp;/dev/tty<br />&nbsp;&nbsp;5&nbsp;/dev/console<br />&nbsp;&nbsp;5&nbsp;/dev/ptmx<br />&nbsp;&nbsp;7&nbsp;vcs<br />10&nbsp;misc<br />13&nbsp;input<br />14&nbsp;sound<br />81&nbsp;video4linux<br />89&nbsp;i2c<br />90&nbsp;mtd<br />116&nbsp;alsa<br />128&nbsp;ptm<br />136&nbsp;pts<br />153&nbsp;spi<br />180&nbsp;usb<br />189&nbsp;usb_device<br />204&nbsp;s3c2410_serial<br />251&nbsp;IO_mem<br />252&nbsp;IO_port<br />253&nbsp;usb_endpoint<br />254&nbsp;rtc<br /><br />Block&nbsp;devices:<br />&nbsp;&nbsp;1&nbsp;ramdisk<br />256&nbsp;rfd<br />&nbsp;&nbsp;7&nbsp;loop<br />31&nbsp;mtdblock<br />93&nbsp;nftl<br />96&nbsp;inftl<br />179&nbsp;mmc<br />[Tekkaman2440@SBC2440V4]#mknod&nbsp;-m&nbsp;666&nbsp;/dev/IO_port&nbsp;c&nbsp;252&nbsp;0<br />[Tekkaman2440@SBC2440V4]#mknod&nbsp;-m&nbsp;666&nbsp;/dev/IO_mem&nbsp;c&nbsp;251&nbsp;0<br />[Tekkaman2440@SBC2440V4]#cd&nbsp;/tmp/&nbsp;<br />[Tekkaman2440@SBC2440V4]#./IO_mem_test<br />io_addr&nbsp;:&nbsp;c485e0d0<br />IO_mem:&nbsp;the&nbsp;module&nbsp;can&nbsp;not&nbsp;lseek!<br />please&nbsp;input&nbsp;the&nbsp;command&nbsp;:1<br />IO_mem:&nbsp;ioctl&nbsp;1&nbsp;ok!<br />please&nbsp;input&nbsp;the&nbsp;command&nbsp;:8<br />IO_mem:&nbsp;ioctl&nbsp;STATUS&nbsp;ok!current_status=0X1<br />please&nbsp;input&nbsp;the&nbsp;command&nbsp;:3<br />IO_mem:&nbsp;ioctl&nbsp;3&nbsp;ok!<br />please&nbsp;input&nbsp;the&nbsp;command&nbsp;:q<br />[Tekkaman2440@SBC2440V4]#./IO_porttest_sleep&nbsp;&<br />[Tekkaman2440@SBC2440V4]#./IO_porttest_sleep&nbsp;&<br />[Tekkaman2440@SBC2440V4]#./IO_porttest_sleep&nbsp;&<br />[Tekkaman2440@SBC2440V4]#./IO_port_test<br />IO_port:&nbsp;the&nbsp;module&nbsp;can&nbsp;not&nbsp;lseek!<br />please&nbsp;input&nbsp;the&nbsp;command&nbsp;:1<br />IO_port:&nbsp;ioctl&nbsp;1&nbsp;ok!<br />please&nbsp;input&nbsp;the&nbsp;command&nbsp;:8<br />IO_port:&nbsp;ioctl&nbsp;STATUS&nbsp;ok!current_status=0X1<br />please&nbsp;input&nbsp;the&nbsp;command&nbsp;:3<br />IO_port:&nbsp;ioctl&nbsp;3&nbsp;ok!<br />please&nbsp;input&nbsp;the&nbsp;command&nbsp;:8<br />IO_port:&nbsp;ioctl&nbsp;STATUS&nbsp;ok!&nbsp;current_status=0X3<br />please&nbsp;input&nbsp;the&nbsp;command&nbsp;:q<br />[1]&nbsp;Done&nbsp;./IO_porttest_sleep<br />[Tekkaman2440@SBC2440V4]#ps<br />&nbsp;&nbsp;PID&nbsp;Uid&nbsp;VSZ&nbsp;Stat&nbsp;Command<br />&nbsp;&nbsp;&nbsp;&nbsp;1&nbsp;root&nbsp;1744&nbsp;S&nbsp;init<br />&nbsp;&nbsp;&nbsp;&nbsp;2&nbsp;root&nbsp;SW&lt&nbsp;[kthreadd]<br />&nbsp;&nbsp;&nbsp;&nbsp;3&nbsp;root&nbsp;SWN&nbsp;[ksoftirqd/0]<br />&nbsp;&nbsp;&nbsp;&nbsp;4&nbsp;root&nbsp;SW&lt&nbsp;[watchdog/0]<br />&nbsp;&nbsp;&nbsp;&nbsp;5&nbsp;root&nbsp;SW&lt&nbsp;[events/0]<br />&nbsp;&nbsp;&nbsp;&nbsp;6&nbsp;root&nbsp;SW&lt&nbsp;[khelper]<br />&nbsp;&nbsp;&nbsp;61&nbsp;root&nbsp;SW&lt&nbsp;[kblockd/0]<br />&nbsp;&nbsp;&nbsp;62&nbsp;root&nbsp;SW&lt&nbsp;[ksuspend_usbd]<br />&nbsp;&nbsp;&nbsp;65&nbsp;root&nbsp;SW&lt&nbsp;[khubd]<br />&nbsp;&nbsp;&nbsp;67&nbsp;root&nbsp;SW&lt&nbsp;[kseriod]<br />&nbsp;&nbsp;&nbsp;79&nbsp;root&nbsp;SW&nbsp;[pdflush]<br />&nbsp;&nbsp;&nbsp;80&nbsp;root&nbsp;SW&nbsp;[pdflush]<br />&nbsp;&nbsp;&nbsp;81&nbsp;root&nbsp;SW&lt&nbsp;[kswapd0]<br />&nbsp;&nbsp;&nbsp;82&nbsp;root&nbsp;SW&lt&nbsp;[aio/0]<br />&nbsp;&nbsp;709&nbsp;root&nbsp;SW&lt&nbsp;[mtdblockd]<br />&nbsp;&nbsp;710&nbsp;root&nbsp;SW&lt&nbsp;[nftld]<br />&nbsp;&nbsp;711&nbsp;root&nbsp;SW&lt&nbsp;[inftld]<br />&nbsp;&nbsp;712&nbsp;root&nbsp;SW&lt&nbsp;[rfdd]<br />&nbsp;&nbsp;746&nbsp;root&nbsp;SW&lt&nbsp;[kpsmoused]<br />&nbsp;&nbsp;755&nbsp;root&nbsp;SW&lt&nbsp;[kmmcd]<br />&nbsp;&nbsp;773&nbsp;root&nbsp;SW&lt&nbsp;[rpciod/0]<br />&nbsp;&nbsp;782&nbsp;root&nbsp;1752&nbsp;S&nbsp;-sh<br />&nbsp;&nbsp;783&nbsp;root&nbsp;1744&nbsp;S&nbsp;init<br />&nbsp;&nbsp;785&nbsp;root&nbsp;1744&nbsp;S&nbsp;init<br />&nbsp;&nbsp;787&nbsp;root&nbsp;1744&nbsp;S&nbsp;init<br />&nbsp;&nbsp;790&nbsp;root&nbsp;1744&nbsp;S&nbsp;init<br />&nbsp;&nbsp;843&nbsp;root&nbsp;1336&nbsp;S&nbsp;./IO_porttest_sleep<br />&nbsp;&nbsp;844&nbsp;root&nbsp;1336&nbsp;S&nbsp;./IO_porttest_sleep<br />&nbsp;&nbsp;846&nbsp;root&nbsp;1744&nbsp;R&nbsp;ps<br />[Tekkaman2440@SBC2440V4]#ps<br />&nbsp;&nbsp;PID&nbsp;Uid&nbsp;VSZ&nbsp;Stat&nbsp;Command<br />&nbsp;&nbsp;&nbsp;&nbsp;1&nbsp;root&nbsp;1744&nbsp;S&nbsp;init<br />&nbsp;&nbsp;&nbsp;&nbsp;2&nbsp;root&nbsp;SW&lt&nbsp;[kthreadd]<br />&nbsp;&nbsp;&nbsp;&nbsp;3&nbsp;root&nbsp;SWN&nbsp;[ksoftirqd/0]<br />&nbsp;&nbsp;&nbsp;&nbsp;4&nbsp;root&nbsp;SW&lt&nbsp;[watchdog/0]<br />&nbsp;&nbsp;&nbsp;&nbsp;5&nbsp;root&nbsp;SW&lt&nbsp;[events/0]<br />&nbsp;&nbsp;&nbsp;&nbsp;6&nbsp;root&nbsp;SW&lt&nbsp;[khelper]<br />&nbsp;&nbsp;&nbsp;61&nbsp;root&nbsp;SW&lt&nbsp;[kblockd/0]<br />&nbsp;&nbsp;&nbsp;62&nbsp;root&nbsp;SW&lt&nbsp;[ksuspend_usbd]<br />&nbsp;&nbsp;&nbsp;65&nbsp;root&nbsp;SW&lt&nbsp;[khubd]<br />&nbsp;&nbsp;&nbsp;67&nbsp;root&nbsp;SW&lt&nbsp;[kseriod]<br />&nbsp;&nbsp;&nbsp;79&nbsp;root&nbsp;SW&nbsp;[pdflush]<br />&nbsp;&nbsp;&nbsp;80&nbsp;root&nbsp;SW&nbsp;[pdflush]<br />&nbsp;&nbsp;&nbsp;81&nbsp;root&nbsp;SW&lt&nbsp;[kswapd0]<br />&nbsp;&nbsp;&nbsp;82&nbsp;root&nbsp;SW&lt&nbsp;[aio/0]<br />&nbsp;&nbsp;709&nbsp;root&nbsp;SW&lt&nbsp;[mtdblockd]<br />&nbsp;&nbsp;710&nbsp;root&nbsp;SW&lt&nbsp;[nftld]<br />&nbsp;&nbsp;711&nbsp;root&nbsp;SW&lt&nbsp;[inftld]<br />&nbsp;&nbsp;712&nbsp;root&nbsp;SW&lt&nbsp;[rfdd]<br />&nbsp;&nbsp;746&nbsp;root&nbsp;SW&lt&nbsp;[kpsmoused]<br />&nbsp;&nbsp;755&nbsp;root&nbsp;SW&lt&nbsp;[kmmcd]<br />&nbsp;&nbsp;773&nbsp;root&nbsp;SW&lt&nbsp;[rpciod/0]<br />&nbsp;&nbsp;782&nbsp;root&nbsp;1752&nbsp;S&nbsp;-sh<br />&nbsp;&nbsp;783&nbsp;root&nbsp;1744&nbsp;S&nbsp;init<br />&nbsp;&nbsp;785&nbsp;root&nbsp;1744&nbsp;S&nbsp;init<br />&nbsp;&nbsp;787&nbsp;root&nbsp;1744&nbsp;S&nbsp;init<br />&nbsp;&nbsp;790&nbsp;root&nbsp;1744&nbsp;S&nbsp;init<br />&nbsp;&nbsp;847&nbsp;root&nbsp;1744&nbsp;R&nbsp;ps<br />[3]&nbsp;+&nbsp;Done&nbsp;./IO_porttest_sleep<br />[2]&nbsp;+&nbsp;Done&nbsp;./IO_porttest_sleep<br />&nbsp;<br /> &nbsp;&nbsp;<br />
zcying 发表于 2009-4-9 14:44 | 显示全部楼层

学习了。谢谢

  
您需要登录后才可以回帖 登录 | 注册

本版积分规则

139

主题

185

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部