Chinaunix首页 | 论坛 | 博客
  • 博客访问: 844324
  • 博文数量: 133
  • 博客积分: 7117
  • 博客等级: 少将
  • 技术积分: 1846
  • 用 户 组: 普通用户
  • 注册时间: 2008-08-16 21:30
文章分类

全部博文(133)

文章存档

2012年(1)

2011年(4)

2010年(2)

2009年(57)

2008年(69)

分类: LINUX

2008-11-28 10:23:16

io port memory IO端口和IO内存

使用I/O端口
    I/O端口有点类似内存位置:可以用和访问内存芯片相同的电信号对它进行读写
。但这两者实际上并不一样;端口操作是直接对外设进行的,和内存相比更不灵活。而
且,有8位的端口,也有16位的端口和32位的端口,不能相互混淆。
 
 
   因此,C语言程序必须调用不同的函数来访问大小不同的端口。Linux内核头文件
中(就在与体系结构相关的头文件中)定义了如下一些内联函数。
 
    使用unsigned而不进一步指定类型信息的话,那是一个与体系结构相关的定义,此时
就不必关心它的准确特性。这些函数基本是可移植的,因为编译器在赋值时会自动进行强
制类型转换(cast)-类型被强制转换成unsigned类型防止了编译时出现的警告信息。只要
程序员赋值时注意避免溢出,这种强制类型转换就不会丢失信息。
 
unsigned inb(unsigned port);
 
void outb(unsigned char byte, unsigned port);
 
按字节(8位宽度)读写端口。port参数在一些平台上定义为unsigned long,而在另一些
平台上定义为unsigned short。不同平台上inb返回值的类型也不相同。
 
 
 
unsigned inw(unsigned port);
 
void outw(unsigned short word, unsigned port);
 
这些函数用于访问16位端口(“字宽度”);Linux的M68k版本不提供,因为该处理器只支
持字节宽度的I/O操作,不支持字宽度或更大宽度的操作。
 
 
unsigned inl(unsigned port);
 
void outl(unsigned doubleword, unsigned port);
 
这些函数用于访问32位端口。doubleword参数根据不同平台定义成unsigned long类型或
unsigned int类型。
 
       除了每次只能传输一个数据单位的in和out操作,绝大多数处理器还提供了能传
输多个字节,字或long类型数据的特殊指令。这些指令就是所谓的“串指令”。
 

       注意这里没有定义64位的I/O操作。即使在64位的体系结构上,I/O端口也只使用
32位的数据通路。
 
 
       上面这些函数主要是提供给设备驱动程序使用的,但它们也可以在用户空间使用
(预处理定义和内联声明没有用#ifdef __KERNEL__保护)。但是如果要在用户空间代码中
使用inb及其相关函数,必须满足下面这些条件:
 
 
  1编译该程序时必须带-O选项来强制内联函数的展开。

  2必须用ioperm或iopl来获取对端口进行I/O操作的权限。ioperm用来获取对指定端口的
操作权限,而iopl用来获取对整个I/O空间的操作权限。这两个函数都是Intel平台提供的。
 
  3必须以root身份运行该程序才能调用ioperm。或者,该程序的某个祖先已经以root身份
获取了对端口操作的权限。
 
       <>示例程序misc-progs/inp.c和misc-progs/outp.c是在用
户空间通过命令行读写8位端口的一个小工具。我已经在我的PC上成功运行过。但由于缺少
ioperm函数的原因,它们不能在其它平台上运行。如果你想冒险,可以将它们设置上SUID位
,那么不用显式地获取特权就可以使用硬件了。
 
平台相关性
       如果你考虑移植问题,你会发现I/O指令是所有计算机指令中与处理器最密切相
关的部分。因此,大部分与I/O端口有关的源代码都与平台相关

       Linux系统,尽管是可移植的,但处理器的特性不是完全透明的。大部分硬件驱
动程序在平台间是不可移植的,而且在同一模块中驱动程序所涵盖的平台一般不超过两
到三种。
 
       回头看看前面的函数列表,你可以看到一处不兼容的地方,数据类型,参数类型
根据各平台体系结构上的不同要相应地使用不同的数据类型。例如,port参数在x86平台
(处理器只支持64KB字节的I/O空间)上定义为unsigned short,但在Alpha平台上定义为u
nsigned long。Alpha平台上端口是和内存在同一地址空间内的一些特定区域,它在设计
上就不存在I/O地址空间,它的端口是被当作为不能被高速缓冲的内存区域。
 
       I/O数据类型是核心中一个仍需要整理的部分,尽管现在能正常工作。对这些不
够明确的类型最好的解决方法是定义一个与体系结构有关的port_t数据类型而对数据项
则使用u8,u16和u32这些数据类型。
 


X86
 
       该体系结构支持本章提到的所有函数。


Alpha
 
    支持前面所有函数,但不同的Alpha平台上端口I/O操作的实现也有不同。串操作是用C语
言实现的,在文件arch/alpha/lib/io.c中定义。但遗憾的是,2.0系列的核心在2.0.29
版之前还只开放word和long类型数据的串操作;因此,模块中无法使用insb和outsb函数
。在2.0.30和2.1.3版中这个问题已经改正过来了。
       值得提及的是,Alpha处理器并不为端口提供不同的地址空间,虽然AXP机器一般
带有ISA和PCI插槽,而且这两种总线都为内存和I/O操作提供了不同的信号线。利用特别
的接口芯片将指定的内存地址引用转换成对I/O端口的访问,基于Alpha的PC可以实现一
个与Intel系列兼容的I/O抽象层。
 
 
Sparc
 
Sparc不提供特殊的I/O指令。I/O空间是通过内存映射获得,在页表中设置了标志。在头
文件中inb和其它函数都被定义为空函数来避免第一次把驱动程序移植到Sparc体系结构
上时编译器为此报告错误。
 
 
 
M68k
 
只支持inb,outb和它们相应的暂停式版本。68000上没有定义串操作,也没定义readb,
writeb和相关函数。
 
 
 
Mips
 
支持前面所有函数。但串操作是用汇编语言写的紧凑循环(tight loop)实现的,因为Mip
s处理器不提供机器一级的串I/O操作。
 
 
PowerPC
 
       除了串I/O操作,其它函数都能支持。
 
 
       一些平台-特别是在i386上-当处理器和总线间数据得传输太快是会带来问题。
问题是源于相对ISA总线处理器的时钟频率太快了,当设备卡太慢时,这个问题就容易暴
露出来;解决该问题的方法是,如果后面又跟着一条I/O指令,就在该条I/O指令后添加
一小段延迟。如果你的设备会丢失数据,或者你担心它会丢失数据,你可以用暂停式的I
/O操作取代通常的I/O操作。暂停式I/O函数很象前面列出的那些I/O函数,但它们的名字
都以_p结尾;例如inb_p,outb_p等等。对所有支持的体系结构,如果定义了不暂停的I/
O函数,那么也会定义相应的暂停式的I/O函数。
新的I/O内存接口由下面这些函数组成:
 
unsigned readb(address);
 
unsigned readw(address);
 
unsigned readl(address);
 
这些宏用于从I/O内存中取得8位,16位和32位的数据值。1.2版的Linux不提供。使用宏
的优点是对参数的类型不作要求;参数address在使用前会被强制类型转换,因为这个值
“不清楚是整数还是指针,但我们两者都能接受”(见 asm-alpha/io.h)。读和写函数都
不会检查address参数的合法性,因为使用它就是为了能和使用指针一样快(我们已经知
道有时它们实际上就是被扩展成指针操作)。
 
 
unsigned writeb(unsigned value, address);
 
unsigned writew(unsigned value, address);
 
unsigned writel(unsigned value, address);
 
       和前面的函数类似的,这些函数(宏)用于写8位,16位和32位的数据项。
memset_io(address,value,count);
 
当你要对I/O调用memset进行操作时,这个函数可以满足你的需要,并且也保留了memset
原来的语义。
 
memcpy_fromio(dest, source, nbytes);
 
memcpy_toio(dest, source, nbytes);
 
这些函数用于成块传输I/O内存的数据,和memcpy_tofs的功能有些相似。它们是和上面
这些函数一起引入Linux中的,1.2版的Linux不提供。与示例代码一起发布的sysdep.h头
文件修正了函数的版本相关性问题,为1.2版以后的所有内核提供了这些函数的定义。
 
       和I/O端口函数一样的,这些函数在能支持的体系结构间的移植性现在也很有限
。一些平台根本不提供这些函数;一些平台上它们是被扩展为指针操作的宏,而在另一

。一些平台根本不提供这些函数;一些平台上它们是被扩展为指针操作的宏,而在另一
些平台上它们则是真正的函数。


在驱动程序编写过程中,很少会注意到IO Port和IO Mem的区别。虽然使用一些不符合规范的代码可以达到最终目的,这是极其不推荐使用的。

结合下图,我们彻底讲述IO端口和IO内存以及内存之间的关系。主存16M字节的SDRAM,外设是个视频采集卡,上面有16M字节的SDRAM作为缓冲区。

1.         CPU是i386架构的情况

在i386系列的处理中,内存和外部IO是独立编址,也是独立寻址的。MEM的内存空间是32位可以寻址到4G,IO空间是16位可以寻址到64K。

在 Linux内核中,访问外设上的IO Port必须通过IO Port的寻址方式。而访问IO Mem就比较罗嗦,外部MEM不能和主存一样访问,虽然大小上不相上下,可是外部MEM是没有在系统中注册的。访问外部IO MEM必须通过remap映射到内核的MEM空间后才能访问。

为了达到接口的同一性,内核提供了IO Port到IO Mem的映射函数。映射后IO Port就可以看作是IO Mem,按照IO Mem的访问方式即可。

2.         CPU是ARM 或PPC架构的情况

在这一类的嵌入式处理器中,IO Port的寻址方式是采用内存映射,也就是IO bus就是Mem bus。系统的寻址能力如果是32位,IO Port+Mem(包括IO Mem)可以达到4G。

访 问这类IO Port时,我们也可以用IO Port专用寻址方式。至于在对IO Port寻址时,内核是具体如何完成的,这个在内核移植时就已经完成。在这种架构的处理器中,仍然保持对IO Port的支持,完全是i386架构遗留下来的问题,在此不多讨论。而访问IO Mem的方式和i386一致。

 

注 意:linux内核给我提供了完全对IO Port和IO Mem的支持,然而具体去看看driver目录下的驱动程序,很少按照这个规范去组织IO Port和IO Mem资源。对这二者访问最关键问题就是地址的定位,在C语言中,使用volatile 就可以实现。很多的代码访问IO Port中的寄存器时,就使用volatile关键字,虽然功能可以实现,我们还是不推荐使用。就像最简单的延时莫过于while,可是在多任务的系统中 是坚决避免的!
阅读(1248) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~