单片机
返回首页

Linux系统对IO端口和IO内存的管理

2016-03-02 来源:eefocus

一、I/O端口

      端口(port)是接口电路中能被CPU直接访问的寄存器的地址。几乎每一种外设都是通过读写设备上的寄存器来进行的。CPU通过这些地址即端口向接口电路中的寄存器发送命令,读取状态和传送数据。外设寄存器也称为“I/O端口”,通常包括:控制寄存器、状态寄存器和数据寄存器三大类,而且一个外设的寄存器通常被连续地编址。

view plain copy
 
 print?

  1. struct resource {  
  2.          resource_size_t start;// 资源范围的开始  
  3.          resource_size_t end;// 资源范围的结束  
  4.          const char *name; //资源拥有者的名字  
  5.          unsigned long flags;// 各种标志  
  6.          struct resource *parent, *sibling, *child;// 指向资源树中父亲,兄弟和孩子的指针  
  7. };  


          所有的同种资源都插入到一个树型数据结构(父亲、兄弟和孩子)中;例如,表示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?
  1. void __iomem *ioport_map(unsigned long port, unsigned int nr)  
  2. {  
  3.     if (port > PIO_MASK)  
  4.         return NULL;  
  5.     return (void __iomem *) (unsigned long) (port + PIO_OFFSET);  
  6. }  
  7.   
  8. void ioport_unmap(void __iomem *addr)  
  9. {  
  10.     /* Nothing to do */  
  11. }  

          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来读写。

[plain] view plain copy
 
 print?
  1. unsigned int fastcall ioread8(void __iomem *addr)  
  2. {  
  3.     IO_COND(addr, return inb(port), return readb(addr));  
  4. }  
  5.   
  6. #define VERIFY_PIO(port) BUG_ON((port & ~PIO_MASK) != PIO_OFFSET)  
  7. #define IO_COND(addr, is_pio, is_mmio) do { \  
  8.     unsigned long port = (unsigned long __force)addr; \  
  9.         if (port < PIO_RESERVED) { \  
  10.             VERIFY_PIO(port); \  
  11.             port &= PIO_MASK; \  
  12.             is_pio; \  
  13.         } else { \  
  14.             is_mmio; \  
  15.         } \  
  16. } while (0)  
  17.   
  18. 展开:  
  19. unsigned int fastcall ioread8(void __iomem *addr)  
  20. {  
  21.     unsigned long port = (unsigned long __force)addr;  
  22.     if( port < 0x40000UL ) {  
  23.         BUG_ON( (port & ~PIO_MASK) != PIO_OFFSET );  
  24.         port &= PIO_MASK;  
  25.         return inb(port);  
  26.     }else{  
  27.         return readb(addr);  
  28.     }  
  29. }  

七、总结

         外设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接口访问。

进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

精选电路图
  • 短波AM发射器电路设计图

  • 开关电源的基本组成及工作原理

  • 用NE555制作定时器

  • RS-485基础知识:处理空闲总线条件的两种常见方法

  • 如何构建一个触摸传感器电路

  • 基于TDA2003的简单低功耗汽车立体声放大器电路

    相关电子头条文章