打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
I/O端口和I/O内存节选

9.2.1 I/O端口

1. I/O端口和I/O内存

 

设备驱动程序要直接访问外设或其接口卡上的物理电路,这部分通常都是以寄存器的形式出现。外设寄存器也称为I/O端口, 通常包括:控制寄存器、状态寄存器和数据寄存器三大类。

系统设计者为了对I/O编程提供统一的方法,每个设备的I/O 端口都被组织成如图9.3所示的一组专用寄存器。CPU把要发给设备的命令写入控制寄存器,并从状态寄存器中读出表示设备内部状态的值。CPU还可以通过读取输入寄存器的内容从设备取得数据,也可以通过向输出寄存器中写入字节而把数据输出到设备。

 

 

 

 

 

 


9.3 专用I/O端口

根据访问外设寄存器的不同方式,可以把CPU分成两大类。一类CPU(如M68KPower PC等)把这些寄存器看作内存的一部分,寄存器参与内存统一编址,访问寄存器只需使用一般的内存指令进行,即这类CPU不需要有专门用于设备I/O的指 令。这就是所谓的I/O内存方式。另一类CPU(如X86)将外设的寄存器看成一个独立的地址空间,这样访问内存的指令就不能用来访问这些寄存器,因此要为对外设寄存器的读/写设置专用指令,如INOUT指令。这就是所谓的I/O端口方式

然而,用于I/O指令的地址空间相对来说是很小的。事实上,现在x86I/O地址空间已经非常拥挤。但是,随着计算机技术的发展,这种只能对外设中的几个寄存器进行操作的方式,已经无法满足实际需要了。而实际上,需求在不断发生变化,例 如,在PC上可以插上一块图形卡,有2MB的存储空间,甚至可能还带有ROM,其中装有可执行代码。自从PCI总线出现后,无论CPU的设计采用I/O 端口方式,还是I/O内存方式,都必须将外设卡上的存储器映射到内存空间,实际上是采用了虚存空间的手段,这样的映射是通过ioremap()来建立的(参见)。

2.  访问I/O端口
  inoutinsouts汇编语言指令都可以访问I/O端口。内核中包含了以下辅助函数来简化这种访问:

inb( )inw( )inl( ),分别从I/O端口读取124个连续字节。后缀“b”“w”“l”分别代表一个字节(8位)、一个字(16位)以及一个长整型(32位)。

inb_p( )inw_p( )inl_p( )分别从I/O端口读取124个连续字节,然后执行一条哑元(dummy,即空指令)指令使CPU暂停。

outb( )outw( )outl( )分别向一个I/O端口写入124个连续字节。

outb_p( )outw_p( )outl_p( )分别向一个I/O端口写入124个连续字节,然后执行一条哑元指令使CPU暂停。

insb( )insw( )insl( )分别从I/O端口读入以124个字节为一组的连续字节序列。字节序列的长度由该函数的参数给出。

outsb( )outsw( )outsl( )分别向I/O端口写入以124个字节为一组的连续字节序列。

 

虽然访问I/O端口非常简单,但是检测哪些I/O端口已经分配给I/O设备可能就不这么简单了,对基于ISA总线的系统来说更是如此。通常,I/O设备驱动程序为了探测硬件设备,需要盲目地向某一I/O端口写入数据;但是,如果其他硬件设备已经使用这个端口,那么系统就会崩溃。为了防止这种情况的发生,内核必须使用资源来记录分配给每个硬件设备的I/O端口。

资源表示某个实体的一部分,这部分被互斥地分配给设备驱动程序。在这里,资源表示I/O端口地址的一个范围。每个资源对应的信息存放在resource数据结构中:

struct resource {

   resource_size_tstart;
         resource_size_t end;
         const char *name;
         unsigned long flags;
         struct resource *parent, *sibling,*child;
};

其字段如表1所示。所有的同种资源都插入到一个树型数据结构(父亲、兄弟和孩子)中;例如,表示I/O端口地址范围的所有资源都包括在一个根节点为ioport_resource的树中。

1 resource数据结构中的字段

类型

字段

描述

const char *

name

资源拥有者的名字

unsigned long

start

资源范围的开始

unsigned long

end

资源范围的结束

unsigned long

flags

各种标志

struct resource *

parent

指向资源树中父亲的指针

struct resource *

sibling

指向资源树中兄弟的指针

struct resource *

child

指向资源树中第一个孩子的指针


节点的孩子被收集在一个链表中,其第一个元素由child指向。sibling字段指向链表中的下一个节点。

为什么使用树?例如,考虑一下IDE硬盘接口所使用的I/O端口地址-比如说从0xf000 0xf00f。那么,start字段为0xf000end字段为0xf00f的这样一个资源包含在树中,控制器的常规名字存放在name字段中。但是,IDE设备驱动程序需要记住另外的信息,也就是IDE链主盘使用0xf000 0xf007的子范围,从盘使用0xf008 0xf00f的子范围。为了做到这点,设备驱动程序把两个子范围对应的孩子插入到从0xf000 0xf00f的整个范围对应的资源下。一般来说,树中的每个节点肯定相当于父节点对应范围的一个子范围。I/O端口资源树(ioport_resource)的根节点跨越了整个I/O地址空间(从端口065535)。

任何设备驱动程序都可以使用下面三个函数,传递给它们的参数为资源树的根节点和要插入的新资源数据结构的地址:

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文件中获得。
3.I/O端口映射到内存空间-访问I/O端口的另一种方式
映射函数的原型为:
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);
4. 访问I/O内存
  Linux
内核也提供了一组函数申请和释放某一范围的I/O内存:
  struct resource *requset_mem_region(unsigned long start, unsignedlong len,char *name);
   
这个函数从内核申请len个内存地址(在3G~4G之间的虚地址),而这里的startI/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 longflags);
其中三个参数的含义为:
phys_addr
:与requset_mem_region函数中参数start相同的I/O物理地址;
size
:要映射的空间的大小;
flags
:要映射的IO空间的和权限有关的标志;

功能: 将一个I/O地址空间映射到内核的虚拟地址空间上(通过release_mem_region()申请到的)

为什么要申请虚拟内存然后才进行映射?

 源于:http://wenku.baidu.com/view/c527b8abd1f34693daef3ecf.html

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
IO端口和IO内存的区别及分别使用的函数接口
与硬件通讯---IO端口
Linux内核开发之内存与I/O访问(二)
linux中的IO端口映射和IO内存映射
《Linux设备驱动程序》(十五)硬件通信IO设备读写寄存器的过程
Linux设备驱动之I/O端口与I/O内存
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服