Linux系统对IO端口和IO内存的管理
2016-03-02 来源:eefocus
一、I/O端口
端口(port)是接口电路中能被CPU直接访问的寄存器的地址。几乎每一种外设都是通过读写设备上的寄存器来进行的。CPU通过这些地址即端口向接口电路中的寄存器发送命令,读取状态和传送数据。外设寄存器也称为“I/O端口”,通常包括:控制寄存器、状态寄存器和数据寄存器三大类,而且一个外设的寄存器通常被连续地编址。
view plain copy
print?
- struct resource {
- resource_size_t start;// 资源范围的开始
- resource_size_t end;// 资源范围的结束
- const char *name; //资源拥有者的名字
- unsigned long flags;// 各种标志
- struct resource *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文件中获得。
view plain copy
print?
- void __iomem *ioport_map(unsigned long port, unsigned int nr)
- {
- if (port > PIO_MASK)
- return NULL;
- return (void __iomem *) (unsigned long) (port + PIO_OFFSET);
- }
-
- void ioport_unmap(void __iomem *addr)
- {
- /* Nothing to do */
- }
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来读写。
- unsigned int fastcall ioread8(void __iomem *addr)
- {
- IO_COND(addr, return inb(port), return readb(addr));
- }
- #define VERIFY_PIO(port) BUG_ON((port & ~PIO_MASK) != PIO_OFFSET)
- #define IO_COND(addr, is_pio, is_mmio) do { \
- unsigned long port = (unsigned long __force)addr; \
- if (port < PIO_RESERVED) { \
- VERIFY_PIO(port); \
- port &= PIO_MASK; \
- is_pio; \
- } else { \
- is_mmio; \
- } \
- } while (0)
- 展开:
- unsigned int fastcall ioread8(void __iomem *addr)
- {
- unsigned long port = (unsigned long __force)addr;
- if( port < 0x40000UL ) {
- BUG_ON( (port & ~PIO_MASK) != PIO_OFFSET );
- port &= PIO_MASK;
- return inb(port);
- }else{
- return readb(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接口访问。
进入单片机查看更多内容>>- 面向对象之编写驱动程序--中断(linux系统、s3c6410开发板)
- 在嵌入式Linux系统(OK6410)中移植Boa 服务器
- Linux系统当app发起socket传输直至网卡驱动,是怎样从socket访问到net_device的
- mini2440开发板Linux系统自动挂载U盘与SD卡失败的解决方法
- Linux系统移植开发篇1:系统移植前说明及源码编译
- linux驱动程序之电源管理之新版linux系统设备架构中关于电源管理方式的变更
- 俄罗斯公布全新国产PC电脑:自研4核ARM处理器+魔改Linux系统加持
- 基于S3C2440A芯片Linux系统实现MJPEG网络摄像机的搭建
- 基于ARM处理器S3C2440和Linux系统的I2C触摸屏
- 嵌入式Linux系统下的视频服务器解析方案