设备驱动—I/O空间的管理

设备通常会提供一组寄存器来控制设备、读写设备以及获取设备的状态。这些寄存器就是控制寄存器、数据(输入/输出)寄存器和状态寄存器,它们可能位于I/O空间,也可能位于内存空间。当位于I/O空间时,通常被称为I/O端口,当位于内存空间时,对应的内存空间被称为I/O内存。

一、I/O端口和I/O内存

系统设计者为了对I/O编程提供统一的方法,每个设备的I/O端口都被组织成如图9.3所示的一组专用寄存器。

CPU把要发给设备的命令写入控制寄存器,并从状态寄存器中读出表示设备内部状态的值。CPU还可以通过读取输入寄存器的内容从设备获得数据,也可以通过向输出寄存器中写入字节把数据输出到设备。

一般来讲,一个外设的寄存器通常被连续地编址。CPU对外设I/O端口物理地址的编址方式有两种:一种是I/O端口,另一种是I/O内存。具体采用哪一种编址方式取决于CPU的体系结构。

有些体系结构的CPU(如PowerPC等)通常只实现一个物理地址空间(RAM)。这种情况下,外设I/O端口的物理地址被映射到CPU的单一物理地址空间中,成为内存的一部分。这时,CPU可以像访问一个内存单元那样来访问外设I/O端口,不需要设立专门的外设I/O指令。这就是所谓的I/O内存。

另外一些体系结构的CPU(典型地如x86)则为外设专门实现了一个单独的地址空间,称为I/O端口空间。这是一个与CPU的内存物理地址空间不同的地址空间,所有外设的I/O端口都在这一空间中进行编址。CPU通过设立专门的I/O指令(如x86的IN和OUT指令)来访问这一空间中的地址单元(即I/O端口),这就是所谓的I/O端口。与内存物理地址空间相比,I/O地址空间通常都比较小,如x86 CPU的I/O空间只有64KB(0~0xffff)。这是I/O端口的一个主要缺点。

二、I/O资源管理

Linux将基于I/O端口和I/O内存的映射方式统称为I/O区域(I/O Region)。首先进行分析Linux如何实现I/O资源抽象概念的。

1、Linux对I/O资源的描述

Linux设计一个通用的数据结构resource来描述各种I/O资源(如I/O端口、I/O内存、DMA和IRQ等)。该结构定义在include/linux/ioport.h文件中:

资源表示某个实体的一部分,这部分被互斥地分配给设备驱动程序。所有的同种资源都插入到一个树状结构(父亲、兄弟和孩子)中;结点的孩子被收集在一个链表中,其第一个元素由child指向,sibling成员指向链表中的下一个结点。

为什么使用树?例如,考虑一下IDE(电子集成驱动器)硬盘接口所使用的I/O端口地址—比如说从0xf000到0xf00f。那么,start成员为0xf000,end成员为0xf00f,控制器的名字存放在name成员中,这就是一棵资源树。但是,IDE设备驱动程序要记住另外的信息,比如IDE主盘使用0xf000到0xf007的子范围,从盘使用0xf008到0xf00f的子范围。为了做到这点,设备驱动程序把两个子范围对应的孩子插入到从0xf000到0xf00f的整个范围对应的资源下。

Linux在kernel/resource.c文件中定义全局变量ioport_resource和iomem_resource,分别描述基于I/O端口的整个I/O端口空间基于I/O内存的整个I/O内存资源空间,其定义如下。

其中,宏IO_SPACE_LIMIT表示整个I/O空间的大小,对于x86平台(与硬件体系结构有关),其定义在include/asm-i386/io.h

#define IO_SPACE_LIMIT 0xffff

任何设备驱动程序都可以使用如下三个函数申请、分配和释放资源,传递给它们的参数为资源树的根结点和要插入的新资源数据结构的地址。

当前分配给I/O设备的所有I/O地址的树都可以从/proc/ioports文件中查看。

2、管理I/O区域资源

I/O区域是一种I/O资源,因此它仍可以用resource结构体类型来描述。Linux在头文件include/linux/ioport.h中定义如下三个对I/O区域进行操作的接口函数。

3、管理I/O内存资源

对I/O内存空间的操作基于I/O区域的操作函数_xxx_region(),对I/O内存空间操作的接口函数定义在include/linux/ioport.h文件中。

4、管理I/O端口资源

采用I/O端口的x86处理器为外设实现了一个单独的地址空间,即I/O空间或称为I/O端口空间,其大小是64KB(0x0000~0xffff)。Linux在其支持的所有平台上都实现了I/O端口空间概念。

由于I/O空间非常小,因此即使外设总线有一个单独的I/O端口空间,也不是所有的外设都将其I/O端口(指寄存器)映射到I/O端口空间中。大多数PCI卡都通过内存映射方式将其I/O端口或外设内存映射到CPU的内存物理地址空间中。老式的ISA卡通常将其I/O端口映射到I/O端口空间中。

Linux是基于I/O区域概念来实现对I/O端口资源的管理的。对I/O端口空间的操作基于I/O区域的操作函数_xxx_region(),Linux在头文件include/linux/ioport.h中定义三个对I/O端口空间操作的接口函数。

三、访问I/O端口空间

驱动程序请求I/O端口空间中的端口资源后,它就可以通过CPU的I/O指令来读写I/O端口。在读写I/O端口时注意,大多数平台都区分为8位、16位和32位的端口。

inb(),outb(),inw(),outw(),inl(),outl()。

inb()的函数原型为:

unsigned char inb(unsigned port);

参数port指定I/O端口空间中的端口地址。在大多数平台上(如x86)它都是unsigned short类型的,其他一些平台上则是unsigned int类型的。端口地址的类型是由I/O端口空间的大小决定的。

某些CPU也支持对某个I/O端口进行连续的读写操作,也即对单个I/O端口读或写一系列字节、字或32位整数,这是所谓的串I/O指令。这种指令在速度上比用循环来实现同样的功能快的多。

insb(),outsb(),insw(),outsw(),insl(),outsl()。

在一些平台上(如x86),对于老式总线(ISA)上的慢速外设来说,如果CPU读写其I/O端口的速度太快,可能会发生丢失数据的现象。针对这个文件解决方法是在两次连续的I/O操作之间插入一段微小时延,以便等待慢速外设。这就是所谓的暂停I/O。

对于暂停I/O,Linux在io.h头文件中定义了其I/O读写函数,如:inb_p(),outb_p(),inw_p(),outw_p(),inl_p(),outl_p()。

四、访问I/O内存资源

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

void * ioremap (unsigned long offset, unsigned long size);

其中参数含义如下:

offset:I/O设备上的一块物理内存的起始地址

size:要映射空间的大小

ioremap ()需要建立新的页表,并返回一个特殊的虚拟地址,该地址可用来存取特定的物理地址范围。通过ioremap ()获得的虚拟地址被iounmap()函数释放,其原型如下:

void iounmap(void *addr);

在调用ioremap ()之前,首先要调用request_mem_region()函数申请资源,该函数的原型为:

struct resource * request_mem_region(unsigned long start, unsigned long len, char *name);

该函数从内核空间申请len个内存地址(在3~4GB之间的虚拟地址),start为I/O物理地址,name为设备的名称(注意,如果分配成功,则返回非空,否则,返回空)。

通过cat /proc/iomem查看系统给各种设备的内存范围。

将I/O内存的物理地址映射成内核虚拟地址后,理论上讲可以像读内存那样直接读I/O内存。但由于在某些平台上,对I/O内存系统内存有不同的访问处理,为了确保跨平台的兼容性,Linux实现一系列读写I/O内存的函数,这些函数在不同的平台上有不同的实现。但在x86平台上,读写I/O内存与读写内存无任何差别,相关函数如下:

readb(),readw()和readl():读I/O内存

writeb(),writew()和writel():写I/O内存

memset_io(),memcpy_fromio()和memcpy_toio():拷贝I/O内存

为保证驱动程序跨平台的可移植性,建议使用上面的函数来访问I/O内存。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值