一、I/O端口
端口(port)是接口电路中能被CPU直接拜访的寄存器的地址。简直每一种外设都是经过读写设备上的寄存器来进行的。CPU经过这些地址即端口向接口电路中的寄存器发送指令,读取状况和传送数据。外设寄存器也称为“I/O端口”,一般包含:操控寄存器、状况寄存器和数据寄存器三大类,并且一个外设的寄存器一般被接连地编址。
二、IO内存
例如,在PC上能够插上一块图形卡,有2MB的存储空间,乃至或许还带有ROM,其间装有可履行代码。
三、IO端口和IO内存的差异及联络
这两者怎么差异就涉及到硬件常识,X86体系中,具有两个地址空间:IO空间和内存空间,而RISC指令体系的CPU(如ARM、PowerPC等)一般只完结一个物理地址空间,即内存空间。
内存空间:内存地址寻址规模,32位操作体系内存空间为2的32次幂,即4G。
IO空间:X86特有的一个空间,与内存空间互相独立的地址空间,32位X86有64K的IO空间。
IO端口:当寄存器或内存坐落IO空间时,称为IO端口。一般寄存器也俗称I/O端口,或许说I/O ports,这个I/O端口能够被映射在Memory Space,也能够被映射在I/O Space。
IO内存:当寄存器或内存坐落内存空间时,称为IO内存。
四、外设IO端口物理地址的编址办法
CPU对外设IO端口物理地址的编址办法有两种:一种是I/O映射办法(I/O-mapped),另一种是内存映射办法(Memory-mapped)。而详细选用哪一种则取决于CPU的体系结构。
1、共同编址
RISC指令体系的CPU(如,PowerPC、m68k、ARM等)一般只完结一个物理地址空间(RAM)。在这种状况下,外设I/O端口的物理地址就被映射到CPU的单一物理地址空间中,而成为内存的一部分。此刻,CPU能够象拜访一个内存单元那样拜访外设I/O端口,而不需求建立专门的外设I/O指令。
共同编址也称为“I/O内存”办法,外设寄存器坐落“内存空间”(许多外设有自己的内存、缓冲区,外设的寄存器和内存总称“I/O空间”)。
2、独立编址
而别的一些体系结构的CPU(典型地如X86)则为外设专门完结了一个单独地地址空间,称为“I/O地址空间”或许“I/O端口空间”。这是一个与CPU地RAM物理地址空间不同的地址空间,一切外设的I/O端口均在这一空间中进行编址。CPU经过建立专门的I/O指令(如X86的IN和OUT指令)来拜访这一空间中的地址单元(也即I/O端口)。与RAM物理地址空间比较,I/O地址空间一般都比较小,如x86 CPU的I/O空间就只有64KB(0-0xffff)。这是“I/O映射办法”的一个首要缺陷。
独立编址也称为“I/O端口”办法,外设寄存器坐落“I/O(地址)空间”。
3、优缺陷
独立编址首要长处是:
1)、I/O端口地址不占用存储器空间;运用专门的I/O指令对端口进行操作,I/O指令短,履行速度快。
2)、并且由于专门I/O指令与存储器拜访指令有显着的差异,使程序中I/O操作和存储器操作层次明晰,程序的可读性强。
3)、一起,由于运用专门的I/O指令拜访端口,并且I/O端口地址和存储器地址是分隔的,故I/O端口地址和存储器地址能够堆叠,而不会彼此混杂。
4)、译码电路比较简略(由于I/0端口的地址空间一般较小,所用地址线也就较少)。
其缺陷是:只能用专门的I/0指令,拜访端口的办法不如拜访存储器的办法多。
共同编址长处:
1)、由于对I/O设备的拜访是运用拜访存储器的指令,所以指令类型多,功用完全,这不只使拜访I/O端口可完结输入/输出操作,并且还可对端口内容进行算术逻辑运算,移位等等;
2)、别的,能给端口有较大的编址空间,这对大型操控体系和数据通信体系是很有意义的。
这种办法的缺陷是端口占用了存储器的地址空间,使存储器容量减小,别的指令长度比专门I/O指令要长,因而履行速度较慢。
终究选用哪一种取决于体系的总体设计。在一个体系中也能够一起运用两种办法,条件是首要要支撑I/O独立编址。Intel的x86微处理器都支撑I/O 独立编址,由于它们的指令体系中都有I/O指令,并设置了能够差异I/O拜访和存储器拜访的操控信号引脚。而一些微处理器或单片机,为了削减引脚,然后减 少芯片占用面积,不支撑I/O独立编址,只能选用存储器共同编址。
五、Linux下拜访IO端口
关于某一既定的体系,它要么是独立编址、要么是共同编址,详细选用哪一种则取决于CPU的体系结构。 如,PowerPC、m68k等选用共同编址,而X86等则选用独立编址,存在IO空间的概念。现在,大多数嵌入式微操控器如ARM、PowerPC等并不供给I/O空间,仅有内存空间,可直接用地址、指针拜访。但关于Linux内核而言,它或许用于不同的CPU,所以它有必要都要考虑这两种办法,所以它选用一种新的办法,将根据I/O映射办法的或内存映射办法的I/O端口通称为“I/O区域”(I/O region),不管你选用哪种办法,都要先请求IO区域:request_resource(),完毕时开释它:release_resource()。
IO region是一种IO资源,因而它能够用resource结构类型来描绘。
拜访IO端口有2种途径:I/O映射办法(I/O-mapped)、内存映射办法(Memory-mapped)。前一种途径不映射到内存空间,直接运用 intb()/outb()之类的函数来读写IO端口;后一种MMIO是先把IO端口映射到IO内存(“内存空间”),再运用拜访IO内存的函数来拜访 IO端口。
1、I/O映射办法
直接运用IO端口操作函数:在设备翻开或驱动模块被加载时请求IO端口区域,之后运用inb(),outb()等进行端口拜访,最终在设备封闭或驱动被卸载时开释IO端口规模。
in、out、ins和outs汇编语言指令都能够拜访I/O端口。内核中包含了以下辅佐函数来简化这种拜访:
inb( )、inw( )、inl( )
别离从I/O端口读取1、2或4个接连字节。后缀“b”、“w”、“l”别离代表一个字节(8位)、一个字(16位)以及一个长整型(32位)。
inb_p( )、inw_p( )、inl_p( )
别离从I/O端口读取1、2或4个接连字节,然后履行一条“哑元(dummy,即空指令)”指令使CPU暂停。
outb( )、outw( )、outl( )
别离向一个I/O端口写入1、2或4个接连字节。
outb_p( )、outw_p( )、outl_p( )
别离向一个I/O端口写入1、2或4个接连字节,然后履行一条“哑元”指令使CPU暂停。
insb( )、insw( )、insl( )
别离从I/O端口读入以1、2或4个字节为一组的接连字节序列。字节序列的长度由该函数的参数给出。
outsb( )、outsw( )、outsl( )
别离向I/O端口写入以1、2或4个字节为一组的接连字节序列。
流程如下:
虽然拜访I/O端口十分简略,可是检测哪些I/O端口现已分配给I/O设备或许就不这么简略了,对根据ISA总线的体系来说更是如此。一般,I/O设备驱动程序为了勘探硬件设备,需求盲目地向某一I/O端口写入数据;可是,假如其他硬件设备现已运用这个端口,那么体系就会溃散。为了避免这种状况的产生,内核有必要运用“资源”来记载分配给每个硬件设备的I/O端口。资源表明某个实体的一部分,这部分被互斥地分配给设备驱动程序。在这儿,资源表明I/O端口地址的一个规模。每个资源对应的信息存放在resource数据结构中:
print?
- structresource{
- resource_size_tstart;//资源规模的开端
- resource_size_tend;//资源规模的完毕
- constchar*name;//资源具有者的姓名
- unsignedlongflags;//各种标志
- structresource*parent,*sibling,*child;//指向资源树中父亲,兄弟和孩子的指针
- };
一切的同种资源都刺进到一个树型数据结构(父亲、兄弟和孩子)中;例如,表明I/O端口地址规模的一切资源都包含在一个根节点为ioport_resource的树中。节点的孩子被搜集在一个链表中,其第一个元素由child指向。sibling字段指向链表中的下一个节点。
为什么运用树?例如,考虑一下IDE硬盘接口所运用的I/O端口地址-比如说从0xf000 到 0xf00f。那么,start字段为0xf000 且end 字段为0xf00f的这样一个资源包含在树中,操控器的惯例姓名存放在name字段中。可是,IDE设备驱动程序需求记住别的的信息,也便是IDE链主盘运用0xf000 到0xf007的子规模,从盘运用0xf008 到0xf00f的子规模。为了做到这点,设备驱动程序把两个子规模对应的孩子刺进到从0xf000 到0xf00f的整个规模对应的资源下。一般来说,树中的每个节点必定相当于父节点对应规模的一个子规模。I/O端口资源树(ioport_resource)的根节点跨过了整个I/O地址空间(从端口0到65535)。
任何设备驱动程序都能够运用下面三个函数,传递给它们的参数为资源树的根节点和要刺进的新资源数据结构的地址:
request_resource( ) //把一个给定规模分配给一个I/O设备。
allocate_resource( ) //在资源树中寻觅一个给定巨细和摆放办法的可用规模;若存在,将这个规模分配给一个I/O设备(首要由PCI设备驱动程序运用,能够运用恣意的端口号和主板上的内存地址对其进行装备)。
release_resource( ) //开释曾经分配给I/O设备的给定规模。
内核也为以上函数界说了一些应用于I/O端口的方便函数:request_region( )分配I/O端口的给定规模,release_region( )开释曾经分配给I/O端口的规模。当时分配给I/O设备的一切I/O地址的树都能够从/proc/ioports文件中取得。
2、内存映射办法
将IO端口映射为内存进行拜访,在设备翻开或驱动模块被加载时,请求IO端口区域并运用ioport_map()映射到内存,之后运用IO内存的函数进行端口拜访,最终,在设备封闭或驱动模块被卸载时开释IO端口并开释映射。
映射函数的原型为:
void *ioport_map(unsigned long port, unsigned int count);
经过这个函数,能够把port开端的count个接连的I/O端口重映射为一段“内存空间”。然后就能够在其回来的地址上像拜访I/O内存相同拜访这些I/O端口。但请留意,在进行映射前,还有必要经过request_region( )分配I/O端口。
当不再需求这种映射时,需求调用下面的函数来吊销:
void ioport_unmap(void *addr);
在设备的物理地址被映射到虚拟地址之后,虽然能够直接经过指针拜访这些地址,可是宜运用Linux内核的如下一组函数来完结拜访I/O内存:·读I/O内存
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
与上述函数对应的较早版别的函数为(这些函数在Linux 2.6中依然被支撑):
unsigned readb(address);
unsigned readw(address);
unsigned readl(address);
·写I/O内存
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
与上述函数对应的较早版别的函数为(这些函数在Linux 2.6中依然被支撑):
void writeb(unsigned value, address);
void writew(unsigned value, address);
void writel(unsigned value, address);
流程如下:
六、Linux下拜访IO内存
IO内存的拜访办法是:首要调用request_mem_region()请求资源,接着将寄存器地址经过ioremap()映射到内核空间的虚拟地址,之后就能够Linux设备拜访编程接口拜访这些寄存器了,拜访完结后,运用ioremap()对请求的虚拟地址进行开释,并开释release_mem_region()请求的IO内存资源。
struct resource *requset_mem_region(unsigned long start, unsigned long len,char *name);
这个函数从内核请求len个内存地址(在3G~4G之间的虚地址),而这儿的start为I/O物理地址,name为设备的称号。留意,。假如分配成功,则回来非NULL,不然,回来NULL。
别的,能够经过/proc/iomem检查体系给各种设备的内存规模。
要开释所请求的I/O内存,应当运用release_mem_region()函数:
void release_mem_region(unsigned long start, unsigned long len)
请求一组I/O内存后, 调用ioremap()函数:
void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);
其间三个参数的意义为:
phys_addr:与requset_mem_region函数中参数start相同的I/O物理地址;
size:要映射的空间的巨细;
flags:要映射的IO空间的和权限有关的标志;
功用:将一个I/O地址空间映射到内核的虚拟地址空间上(经过release_mem_region()请求到的)
流程如下:
六、ioremap和ioport_map
下面详细看一下ioport_map和ioport_umap的源码:
print?
- void__iomem*ioport_map(unsignedlongport,unsignedintnr)
- {
- if(port>PIO_MASK)
- returnNULL;
- return(void__iomem*)(unsignedlong)(port+PIO_OFFSET);
- }
- voidioport_unmap(void__iomem*addr)
- {
- /*Nothingtodo*/
- }
ioport_map只是是将port加上PIO_OFFSET(64k),而ioport_unmap则什么都不做。这样portio的64k空间就被映射到虚拟地址的64k~128k之间,而ioremap回来的虚拟地址则必定在3G之上。ioport_map函数的意图是企图供给与ioremap共同的虚拟地址空间。剖析ioport_map()的源代码可发现,所谓的映射到内存空间行为实际上是给开发人员制作的一个“假象”,并没有映射到内核虚拟地址,只是是为了让工程师可运用共同的I/O内存拜访接口ioread8/iowrite8(……)拜访I/O端口。
最终来看一下ioread8的源码,其完结也便是对虚拟地址进行了判别,以差异IO端口和IO内存,然后别离运用inb/outb和readb/writeb来读写。
print?
- unsignedintfastcallioread8(void__iomem*addr)
- {
- IO_COND(addr,returninb(port),returnreadb(addr));
- }
- #defineVERIFY_PIO(port)BUG_ON((port&~PIO_MASK)!=PIO_OFFSET)
- #defineIO_COND(addr,is_pio,is_mmio)do{\
- unsignedlongport=(unsignedlong__force)addr;\
- if(port
- VERIFY_PIO(port);\
- port&=PIO_MASK;\
- is_pio;\
- }else{\
- is_mmio;\
- }\
- }while(0)
- 打开:
- unsignedintfastcallioread8(void__iomem*addr)
- {
- unsignedlongport=(unsignedlong__force)addr;
- if(port<0x40000UL){
- BUG_ON((port&~PIO_MASK)!=PIO_OFFSET);
- port&=PIO_MASK;
- returninb(port);
- }else{
- returnreadb(addr);
- }
- }
七、总结
外设IO寄存器地址独立编址的CPU,这时应该称外设IO寄存器为IO端口,拜访IO寄存器可经过ioport_map将其映射到虚拟地址空间,但实际上这是给开发人员制作的一个“假象”,并没有映射到内核虚拟地址,只是是为了能够运用和IO内存相同的接口拜访IO寄存器;也能够直接运用in/out指令拜访IO寄存器。
例如:Intel x86渠道一般运用了名为内存映射(MMIO)的技能,该技能是PCI标准的一部分,IO设备端口被映射到内存空间,映射后,CPU拜访IO端口就好像访 问内存相同。
外设IO寄存器地址共同编址的CPU,这时应该称外设IO寄存器为IO内存,拜访IO寄存器可经过ioremap将其映射到虚拟地址空间,然后再运用read/write接口拜访。