




已阅读5页,还剩6页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
I/O 端口和 I/O 内存 1、I/O端口和I/O内存每个外设都是通过读写其寄存器来控制的。通常一个设备有几个寄存器,它们位于内存地址空间或者I/O地址空间,并且地址是连续的。在硬件层上,内存区和I/O区域没有概念上的区别:它们都是通过在地址总线和控制总线上发出电信号来存取(即,读写信号),并且通过数据总线来读写数据。在一些CPU制造商在其芯片上实现了一个单地址空间(统一编址)的同时,其它的CPU制造商认为外设不同于内存,应该有一个独立的地址空间给外设(单独编址),其生产处理器(特别是x86家族)的I/O端口有自己的读写信号线和特殊的CPU指令来存取端口。因为外设要与外设总线相匹配,并且大部分流行的I/O总线都是以个人计算机(主要是x86家族)作为模型,所以即便那些没有单独地址空间给I/O端口的处理器,也必须在访问外设时模拟成读写端口。这通常通过外部芯片组(PC中的南北桥)或者在CPU核中附加额外电路来实现(基于嵌入式应用的处理器)。关于编址方式、I/O端口和I/O内存更详细内容见/u3/96613/showart_1926286.html由于同样的理由,Linux在所有计算机平台上都实现了I/O端口,甚至在那些单地址空间的CPU平台上(模拟I/O端口)。但并不是所有的设备都会将其寄存器映射到I/O端口。虽然ISA设备普遍使用I/O端口,但大部分PCI设备将寄存器映射到某个内存地址区。这种I/O内存方法通常是首选的,因为它无需使用特殊的处理器指令,CPU存取内存也更有效率,并且编译器在存取内存时在寄存器分配和寻址模式的选择上有更多自由。1.1、I/O寄存器和常规内存I/O寄存器和RAM的主要不同是I/O操作有边际效应(side effect),而内存操作没有:访问内存只是在内存某一位置存储数值。因为内存存取速度严重影响CPU的性能,编译器可能会对源码进行优化,主要是:使用高速缓存和重排读/写指令的顺序。对于传统内存(至少在单处理器系统)这些优化是透明有益的,但是对于I/O寄存器,这可能是致命错误,因为它们干扰了那些边际效应(驱动程序存取I/O寄存器就是为了获取边际效应)。因此,驱动程序必须确保在存取寄存器时,不能使用高速缓存并且不能重新编排读写指令的顺序。side effect 是指:访问I/O寄存器时,不仅仅会像访问普通内存一样影响存储单元的值,更重要的是它可能改变CPU的I/O端口电平、输出时序或CPU对I/O端口电平的反应等等,从而实现CPU的控制功能。CPU在电路中的意义就是实现其side effect 。举个例子,有些设备的中断状态寄存器只要一读取,便自动清零。硬件缓冲的问题是最易解决的:只要将底层硬件配置(或者自动地或者通过Linux初始化代码)为当存取I/O区时,禁止任何硬件缓冲(不管是I/O内存还是I/O端口)。编译器优化和硬件重编排读写指令顺序的解决方法是:在硬件或处理器必须以一个特定顺序执行的操作之间安放一个内存屏障(memory barrier)。Linux提供以下几个宏来实现这个功能:#include/* barrier(软件内存屏障)告知编译器插入一个内存屏障但是对硬件没有影响。编译后的代码将当前CPU寄存器所有修改过的值保存到内存,并且在需要时重新读取它们。barrier可阻止在屏障前后的编译器优化,但硬件能完成自己的重新排序 */voidbarrier(void)#include/* rmb(read memory barrier)保证任何出现于屏障前的读在执行任何后续读之前完成 */voidrmb(void);/* read_barrier_depends是一种特殊的、弱些的读屏障形式。rmb 阻止屏障前后的所有读指令的重新排序,read_barrier_depends 只阻止依赖于其他读指令返回的数据的读指令的重新排序。区别微小, 并且不是所有体系都支持。除非你确切地理解它们的差别, 并确信完整的读屏障会增加系统开销,否则应当始终使用 rmb。*/voidread_barrier_depends(void);/* wmb(write memory barrier)保证任何出现于屏障前的写在执行任何后续的写之前完成 */voidwmb(void);/* rmb(memory barrier)保证任何出现于屏障前的读写操作在执行任何后续的读写操作之前完成 */voidmb(void);/* 以上这些宏都是是barrier的超集,它们插入硬件内存屏障在编译的指令流中,并且它们的实际实现是平台相关的 */* 以下这些屏障宏仅当SMP系统时插入硬件屏障;否则它们只是简单被替换成barrier宏 */voidsmp_rmb(void);voidsmp_read_barrier_depends(void);voidsmp_wmb(void);voidsmp_mb(void);典型例子:writel(dev-registers.addr,io_destination_address);writel(dev-registers.size,io_size);writel(dev-registers.operation,DEV_READ);wmb();/* 在开始执行操作(最后一个写操作)之前,先将各相关控制寄存器设置好(前三个写操作)。其实wmb就是一条分界线,它保证其之前的三个写操作执行完了之后,才执行其后的写操作,但是前面三个写操作顺序则无法保证 */writel(dev-registers.control,DEV_GO);内存屏障影响系统性能,所以只能在确实需要它们的地方使用。不同类型的屏障也有不同的性能特性,因此,应当尽可能使用最合适的屏障类型。值得注意的是大部分处理同步的内核原语,例如自旋锁和atomic_t操作,也具有内存屏障的功能。还有就是有些外设总线(如PCI总线)有它们自己的缓冲问题;我们在后面章节遇到时讨论这些问题。某些体系允许将赋值和内存屏障组合在一起,以提高效率。它们如下定义:#defineset_mb(var,value)dovar=value;mb();while0#defineset_wmb(var,,value)dovar=value;wmb();while0#defineset_rmb(var,,value)dovar=value;rmb();while0在合适的地方,为了更快的完成任务,使用体系特定的(architecture-specific)指令来定义这些宏。注意,很少体系支持set_rmb。使用do.while结构来定义宏是标准C的惯用方法(在内核源码中非常常见),它使被扩展的宏可在所有上下文环境中作为一个正常的C语句被执行。2、使用I/O端口I/O端口是驱动用来和很多设备通讯的方法。2.1、分配I/O端口在驱动还没独占设备之前,不应对端口进行操作。内核提供了一个注册接口,以允许驱动声明其需要的端口:#include/* request_region告诉内核:要使用first开始的n个端口。参数name为设备名。如果分配成功返回值是非NULL;否则无法使用需要的端口(/proc/ioports包含了系统当前所有端口的分配信息,若request_region分配失败时,可以查看该文件,看谁先用了你要的端口) */structresource*request_region(unsignedlongfirst,unsignedlongn,constchar*name);/* 用完I/O端口后(可能在模块卸载时),应当调用release_region将I/O端口返还给系统。参数start和n应与之前传递给request_region一致 */voidrelease_region(unsignedlongstart,unsignedlongn);/* check_region用于检查一个给定的I/O端口集是否可用。如果给定的端口不可用,check_region返回一个错误码。不推荐使用该函数,因为即便它返回0(端口可用),它也不能保证后面的端口分配操作会成功,因为检查和后面的端口分配并不是一个原子操作。而request_region通过加锁来保证操作的原子性,因此是安全的 */intcheck_region(unsignedlongfirst,unsignedlongn);2.2、操作I/O端口 在驱动成功请求到I/O端口后,就可以读写这些端口了。大部分硬件会将8位、16位和32位端口区分开,无法像访问内存那样混淆使用。驱动程序必须调用不同的函数来访问不同大小的端口。如同前面所讲的,仅支持单地址空间的计算机体系通过将I/O端口地址重新映射到内存地址来伪装端口I/O。为了提高移植性,内核对驱动隐藏了这些细节。Linux内核头文件(体系依赖的头文件)定义了下列内联函数来存取I/O端口:/* inb/outb:读/写字节端口(8位宽)。有些体系将port参数定义为unsigned long;而有些平台则将它定义为unsigned short。inb的返回类型也是依赖体系的 */unsignedinb(unsignedport);voidoutb(unsignedcharbyte,unsignedport);/* inw/outw:读/写字端口(16位宽) */unsignedinw(unsignedport);voidoutw(unsignedshortword,unsignedport);/* inl/outl:读/写32位端口。longword也是依赖体系的,有的体系为unsigned long;而有的为unsigned int */unsignedinl(unsignedport);voidoutl(unsignedlongword,unsignedport);从现在开始,当我们使用unsigned没有进一步指定类型时,表示是一个依赖体系的定义。注意,没有64位的I/O端口操作函数。即便在64位体系中,端口地址空间使用一个32位(最大)的数据通路。2.3、从用户空间访问I/O端口2.2节介绍的函数主要是提供给驱动使用,但它们也可在用户空间使用,至少在PC机上可以。GNUC库在中定义它们。如果在用户空间使用这些函数,必须满足下列条件:1)、程序必须使用-O选项编译来强制扩展内联函数2)、必须使用ioperm和iopl系统调用(#include ) 来获得进行操作I/O端口的权限。ioperm为获取单个端口的操作许可,iopl为获取整个I/O空间许可。这2个函数都是x86特有的3)、程序必须以root来调用ioperm或者iopl,或者其父进程(祖先)必须以root获得的端口操作权限如果平台不支持ioperm和iopl系统调用,通过使用/dev/prot设备文件,用户空间仍然可以存取I/O端口。但是要注意的是,这个文件的定义也是依赖平台的。2.4、字串操作除了一次传递一个数据的I/O操作,某些处理器实现了一次传递一序列数据(单位可以是字节、字和双字)的特殊指令。这些所谓的字串指令,它们完成任务比一个C语言循环更快。下列宏定义实现字串操作,在某些体系上,它们通过使用单个机器指令实现;但如果目标处理器没有进行字串I/O指令,则通过执行一个紧凑的循环实现。字串函数的原型是:/* insb:从I/O端口port读取count个数据(单位字节)到以内存地址addr为开始的内存空间 */voidinsb(unsignedport,void*addr,unsignedlongcount);/* outsb:将内存地址addr开始的count个数据(单位字节)写到I/O端口port */voidoutsb(unsignedport,void*addr,unsignedlongcount);/* insw:从I/O端口port读取count个数据(单位字)到以内存地址addr为开始的内存空间 */voidinsw(unsignedport,void*addr,unsignedlongcount);/* outsw:将内存地址addr开始的count个数据(单位字)写到I/O端口port */voidoutsw(unsignedport,void*addr,unsignedlongcount);/* insl:从I/O端口port读取count个数据(单位双字)到以内存地址addr为开始的内存空间 */voidinsl(unsignedport,void*addr,unsignedlongcount);/* outsl:将内存地址addr开始的count个数据(单位双字)写到I/O端口port */voidoutsl(unsignedport,void*addr,unsignedlongcount);注意:使用字串函数时,它们直接将字节流从端口中读取或写入。当端口和主机系统有不同的字节序时,会导致不可预期的结果。 使用 inw 读取端口应在必要时自行转换字节序,以匹配主机字节序。2.5、暂停式I/O操作函数由于处理器的速率可能与外设(尤其是低速设备)的并不匹配,当处理器过快地传送数据到或自总线时,这时可能就会引起问题。解决方法是:如果在I/O指令后面紧跟着另一个相似的I/O指令,就必须插入一个小的延时。为此,Linux提供了暂停式I/O操作函数,这些函数的名子只是在非暂停式I/O操作函数(前面提到的那些I/O操作函数都是非暂停式的)名后加上_p,如inb_p、outb_p等。大部分体系都支持这些函数,尽管它们常常被扩展为与非暂停I/O同样的代码,因为如果体系使用一个合理的现代外设总线,没有必要额外暂停。以下是ARM体系暂停式I/O宏的定义:#defineoutb_p(val,port)outb(val),(port)#defineoutw_p(val,port)outw(val),(port)#defineoutl_p(val,port)outl(val),(port)#defineinb_p(port)inb(port)#defineinw_p(port)inw(port)#defineinl_p(port)inl(port)#defineoutsb_p(port,from,len)outsb(port,from,len)#defineoutsw_p(port,from,len)outsw(port,from,len)#defineoutsl_p(port,from,len)outsl(port,from,len)#defineinsb_p(port,to,len)insb(port,to,len)#defineinsw_p(port,to,len)insw(port,to,len)#defineinsl_p(port,to,len)insl(port,to,len)因为ARM使用内部总线,就没有必要额外暂停,所以暂停式的I/O函数被扩展为与非暂停式I/O 同样的代码。2.6、平台依赖性由于自身的特性,I/O指令高度依赖于处理器,非常难以隐藏各体系间的不同。因此,大部分的关于端口 I/O 的源码是平台依赖的。以下是x86和ARM所使用函数的总结:IA-32(x86)x86_64这个体系支持本章介绍的所有函数;port参数的类型为unsignedshort。ARM端口映射到内存,并且支持本章介绍的所有函数;port参数的类型为unsignedint;字串函数用C语言实现。3、使用I/O内存尽管I/O端口在x86世界中非常流行,但是用来和设备通讯的主要机制是通过内存映射的寄存器和设备内存,两者都称为I/O内存,因为寄存器和内存之间的区别对软件是透明的。I/O内存仅仅是一个类似于RAM的区域,处理器通过总线访问该区域,以实现对设备的访问。同样,读写这个区域是有边际效应。根据计算机体系和总线不同,I/O内存可分为可以或者不可以通过页表来存取。若通过页表存取,内核必须先重新编排物理地址,使其对驱动程序可见,这就意味着在进行任何I/O操作之前,你必须调用ioremap;如果不需要页表,I/O内存区域就类似于I/O端口,你可以直接使用适当的I/O函数读写它们。由于边际效应的缘故,不管是否需要ioremap,都不鼓励直接使用I/O内存指针,而应使用专门的I/O内存操作函数。这些I/O内存操作函数不仅在所有平台上是安全,而且对直接使用指针操作 I/O 内存的情况进行了优化。3.1、I/O内存分配和映射I/O内存区在使用前必须先分配。分配内存区的函数接口在定义中:/* request_mem_region分配一个开始于start,len字节的I/O内存区。分配成功,返回一个非NULL指针;否则返回NULL。系统当前所有I/O内存分配信息都在/proc/iomem文件中列出,你分配失败时,可以看看该文件,看谁先占用了该内存区 */structresource*request_mem_region(unsignedlongstart,unsignedlonglen,char*name);/* release_mem_region用于释放不再需要的I/O内存区 */voidrelease_mem_region(unsignedlongstart,unsignedlonglen);/* check_mem_region用于检查I/O内存区的可用性。同样,该函数不安全,不推荐使用 */intcheck_mem_region(unsignedlongstart,unsignedlonglen);在访问I/O内存之前,分配I/O内存并不是唯一要求的步骤,你还必须保证内核可存取该I/O内存。访问I/O内存并不只是简单解引用指针,在许多体系中,I/O内存无法以这种方式直接存取。因此,还必须通过ioremap函数(第8章第4节介绍过)设置一个映射。#include/* ioremap用于将I/O内存区映射到虚拟地址。参数phys_addr为要映射的I/O内存起始地址,参数size为要映射的I/O内存的大小,返回值为被映射到的虚拟地址 */void*ioremap(unsignedlongphys_addr,unsignedlongsize);/* ioremap_nocache为ioremap的无缓存版本。实际上,在大部分体系中,ioremap与ioremap_nocache的实现一样的,因为所有 I/O 内存都是在无缓存的内存地址空间中 */void*ioremap_nocache(unsignedlongphys_addr,unsignedlongsize);/* iounmap用于释放不再需要的映射 */voidiounmap(void*addr);经过ioremap(和iounmap)之后,设备驱动就可以存取任何I/O内存地址。注意,ioremap返回的地址不可以直接解引用;相反,应当使用内核提供的访问函数。3.2、访问I/O内存访问I/O内存的正确方式是通过一系列专门用于实现此目的的函数:#include/* I/O内存读函数。参数addr应当是从ioremap获得的地址(可能包含一个整型偏移); 返回值是从给定I/O内存读取到的值 */unsignedintioread8(void*addr);unsignedintioread16(void*addr);unsignedintioread32(void*addr);/* I/O内存写函数。参数addr同I/O内存读函数,参数value为要写的值 */voidiowrite8(u8 value,void*addr);voidiowrite16(u16 value,void*addr);voidiowrite32(u32 value,void*addr);/* 以下这些函数读和写一系列值到一个给定的 I/O 内存地址,从给定的buf读或写count个值到给定的addr。参数count表示要读写的数据个数,而不是字节大小 */voidioread8_rep(void*addr,void*buf,unsignedlongcount);voidioread16_rep(void*addr,void*buf,unsignedlongcount);voidioread32_rep(void*addr,void*buf,unsignedlongcount);voidiowrite8_rep(void*addr,constvoid*buf,unsignedlongcount);voidiowrite16_rep(void*addr,constvoid*buf,unsignedlongcount);voidiowrite32_rep(void*addr,,onstvoid*buf,,nsignedlongcount);/* 需要操作一块I/O 地址时,使用下列函数(这些函数的行为类似于它们的C库类似函数): */voidmemset_io(void*addr,u8 value,unsignedintcount);voidmemcpy_fromio(void*dest,void*source,unsignedintcount);voidmemcpy_toio(void*dest,void*source,unsignedintcount);/* 旧的I/O内存读写函数,不推荐使用 */unsignedreadb(address);unsignedreadw(address);unsignedreadl(address);voidwriteb(unsignedvalue,address);voidwritew(unsignedvalue,address);voidwritel(unsignedvalue,address);3.3、像I/O内存一样使用端口一些硬件有一个有趣的特性:有些版本使用I/O端口;而有些版本则使用I/O内存。不管是I/O端口还是I/O内存,处理器见到的设备寄存器都是相同的,只是访问方法不同。为了统一编程接口,使驱动程序易于编写,2.6 内核提供了一个ioport_map函数:/* ioport_map重新映射count个I/O端口,使它们看起来I/O内存。此后,驱动程序可以在ioport_map返回的地址上使用ioread8和同类函数。这样,就可以在编程时,消除了I/O 端口和I/O 内存的区别 */void*ioport_map(unsignedlongport,unsignedintcount);/* ioport_unmap用于释放不再需要的映射 */voidioport_unmap(void*addr);注意,I/O端口在重新映射前必须使用request_region分配分配所需的I/O端口。4、ARM体系的I/O操作接口s3c24x0处理器使用的是I/O内存,也就是说:s3c24x0处理器使用统一编址方式,I/O寄存器和内存使用的是单一地址空间,并且读写I/O寄存器和读写内存的指令是相同的。所以推荐使用I/O内存的相关指令和函数。但这并不表示I/O端口的指令在s3c24x0中不可用。如果你注意过s3c24x0关于I/O方面的内核源码,你就会发现:其实I/O端口的指令只是一个外壳,内部还是使用和I/O内存一样的代码。下面是ARM体系原始的I/O操作函数。其实后面I/O端口和I/O内存操作函数,只是对这些函数进行再封装。从这里也可以看出为什么我们不推荐直接使用I/O端口和I/O内存地址指针,而是要求使用专门的I/O操作函数专门的I/O操作函数会检查地址指针是否有效是否为IO地址(通过_iomem或_chk_io_ptr)#include/* Generic IO read/write. These perform native-endian accesses. Note* that some architectures will want to re-define _raw_read,writew.*/externvoid_raw_writesb(void_iomem*addr,constvoid*data,intbytelen);externvoid_raw_writesw(void_iomem*addr,constvoid*data,intwordlen);externvoid_raw_writesl(void_iomem*addr,constvoid*data,intlonglen);externvoid_raw_readsb(constvoid_iomem*addr,void*data,intbytelen);externvoid_raw_readsw(constvoid_iomem*addr,void*data,intwordlen);externvoid_raw_readsl(constvoid_iomem*addr,void*data,intlonglen);#define_raw_writeb(v,a)(_chk_io_ptr(a),*(volatileunsignedchar_force*)(a)=(v)#define_raw_writew(v,a)(_chk_io_ptr(a),*(volatileunsignedshort_force*)(a)=(v)#define_raw_writel(v,a)(_chk_io_ptr(a),*(volatileunsignedint_force*)(a)=(v)#define_raw_readb(a)(_chk_io_ptr(a),*(volatileunsignedchar_force*)(a)#define_raw_readw(a)(_chk_io_ptr(a),*(volatileunsignedshort_force*)(a)#define_raw_readl(a)(_chk_io_ptr(a),*(volatileunsignedint_force*)(a)关于_force和_iomem#include/* _force表示所定义的变量类型是可以做强制类型转换的 */#define_force _attribute_(force)/* _iomem是用来修饰一个变量的,这个变量必须是非解引用(no dereference)的,即这个变量地址必须是有效的,而且变量所在的地址空间必须是2,即设备地址映射空间。0表示normal space,即普通地址空间,对内核代码来说,当然就是内核空间地址了。1表示用户地址空间,2表示是设备地址映射空间 */#define_iomem _attribute_(noderef,address_space(2)I/O端口#include#defineoutb(v,p)_raw_writeb(v,_io(p)#defineoutw(v,p)_raw_writew(_force _u16)cpu_to_le16(v),_io(p)#defineoutl(v,p)_raw_writel(_force _u32)cpu_to_le32(v),_io(p)#defineinb(p)(_u8 _v=_raw_readb(_io(p);_v;)#defineinw(p)(_u16 _v=le16_to_cpu(_force _le16)_raw_readw(_io(p);_v;)#defineinl(p)(_u32 _v=le32_to_cpu(_force _le32)_raw_readl(_io(p);_v;)#defineoutsb(p,d,l)_raw_writesb(_io(p),d,l)#defineoutsw(p,d,l)_raw_writesw(_io(p),d,l)#defineoutsl(p,d,l)_raw_writesl(_io(p),d,l)#defineinsb(p,d,l)_raw_readsb(_io(p),d
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 农发行运城市万荣县2025秋招结构化面试经典题及参考答案
- 农发行保定市涞水县2025秋招笔试专业知识题专练及答案
- 农发行荆州市荆州区2025秋招英文面试题库及高分回答
- 国家能源邯郸市邯山区2025秋招笔试综合知识题专练及答案
- 常德安乡县中储粮2025秋招笔试性格测评题专练及答案
- 国家能源大同市灵丘县2025秋招写作案例分析万能模板可套用
- 分红协议书汇编15篇
- 2025年德州宁津县公开招聘省属公费师范毕业生(28人)模拟试卷附答案详解
- 2025年琼海市校园招聘教育类专业技术人才(西安站)模拟试卷及答案详解(典优)
- 医院医护人员工作总结
- 2025年淮南市大通区和寿县经开区公开招聘社区“两委”后备干部30名笔试备考题库及答案解析
- 2025云南红河红家众服经营管理有限公司社会招聘工作人员8人笔试参考题库附带答案详解
- 2025双11大促商家一站式指南
- 助理医师考试题库及答案
- 电梯管理安全试题库及答案解析
- 2.2 6、7的加减法(课件)数学青岛版一年级上册(新教材)
- DL-T 794-2024 火力发电厂锅炉化学清洗导则
- 消防战斗服穿戴培训课件
- 老年病人误吸预防及护理
- 生理学全套课件
- 购牛合同参考参考
评论
0/150
提交评论