注册 登录  
 加关注
查看详情
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

刺马的博客

 
 
 

日志

 
 

基于Linux平台PCI设备驱动程序设计(一)  

2009-10-25 16:48:26|  分类: LInux |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
第一章 Linux设备管理概述
  1.1 设备分类
  在Linux系统中,对设备的管理有其自身的特点:对所有的硬件设备进行了抽象,使得计算机用户对硬件设备的操作与对文件的操作十分相似,可以通过与操作文件完全一样的标准系统调用来打开、关闭、读和写设备。
  Linux将所有的硬件设备被归结为三类:
  字符设备:
   字符设备指无需缓冲就可以直接读写的设备。用户可以像访问文件一样访问字符设备,字符设备驱动程序负责实现这些访问操作。驱动程序通常会实现 open,close,read和write系统调用。键盘和鼠标就是字符设备的典型例子。通过文件系统节点可以访问字符设备,例如/dev/tty1和 /dev/lp1。字符设备和普通文件系统间的显著区别是:普通文件允许在其上来回读写,而大多数字符设备仅仅是数据通道,只能顺序读写。当然,也存在这 样的字符设备,看起来像是个数据区,可以来回读取其中的数据。
  块设备:
  块设备是文件系统的宿主,如硬盘、光驱、软驱等。在大多数 Unix系统中,只能将块设备看作多个块进行访问,一个块通常是1KB字节数据。Linux允许用户像字符设备那样读取块设备--允许一次传输任意数目的 字节。块设备和字符设备只在内核内部的管理上有所区别,因此也就是在内核/驱动程序间的软件接口上有所区别。就像字符设备一样,每个块设备也通过文件系统 节点来读写数据,它们之间的不同对用户来说是透明的。块设备驱动程序和内核的接口与字符设备驱动程序的接口是一样的,它也是通过一个传统的面向块的接口与 内核通信,但这个接口对用户来说是不可见的。
  网络设备:
  网络设备与字符设备和块设备最大的不同之处就在于网络设备没有对应的设备文件。网卡是最典型的一个例子。
  网卡把向外发送的数据写入通往远程计算机系统的一条通信线路上,把从远程系统中接受到的报文装入内核内存。在Unix系统中,计算机为每个网卡分配一个不同的符号名,例如:eth0,eth1等。然而这个名字并没有对应的设备文件,也没有对应的索引节点。
   由于没有使用文件系统,所以系统管理员必须建立设备名和网络地址之间的联系。因此,应用程序和网络接口之间的数据通信不是基于标准的有关文件的 read()、write()等系统调用,而是基于socket()、bind()、listen()、accept()、connect()系统调用, 这些系统调用对网络地址进行操作。
  1.2 主设备号和次设备号
  传统方式的设备管理中,除了设备类型(字符设备或块设备)以外,内 核还需要一对称作主、次设备号的参数来唯一标识一个设备。主设备号标识设备对应的驱动程序,内核利用主设备号将设备与对应的驱动程序对应起来。次设备号只 由设备驱动程序使用,内核的其他部分不使用它。一个驱动程序可以控制若干个设备,次设备号可将使用同一驱动程序的不同设备区分开来。
  所有设备 在适当的目录(通常在/dev目录下)下必须有相应的特殊文件,这样字符设备和块设备都可以通过文件操作完成系统调用了。设备文件是特殊文件,这一点可以 通过命令“ls -l”输出的第一列中的“c”标明,它说明它们是字符节点;块设备的第一列是“b”。在执行了“ls -l”命令之后,在设备文件条目的最新修改日期前可看到两个数(用逗号分隔),这个位置通常显示常规文件的长度。这两个数就是相应设备的主设备号和次设备 号。下面列表显示了我使用的系统上的一些设备,它们的主设备号分别是10、14、29、41、68,而次设备号是3、4、20、7、0、1、2。

第二章 PCI总线简介
  2.1 Linux对PCI总线的支持
  PC机发展至今,出现了许多的总线标准:PCI、ISA、MCA、 EISA、VLB、Sbus等都是当今PC市场上能找得到的总线标准。其中PCI和ISA是PC世界最常用的外设接口,但由于ISA在设计上已经相当陈旧 了,现在有PCI将全面替代ISA的趋势,因此,PCI总线已成为当今最常用的外设总线,也是Linux内核支持最好的总线。
  2.2 PCI总线概览
  PCI是一组完全的规范,它定义了计算机的不同部分是如何交互的。PCI规范覆盖了与计算机接口相关的绝大多数方面,本文主要讨论一个PCI驱动程序是如何找到它的硬件,并获得对它的访问的。
  PCI外设由一个总线号、一个设备号、和一个功能号确定。
   PCI设备有三种地址空间,分别是:内存空间、I/O共享空间和配置空间。前两个地址空间由PCI总线上的所有设备共享,而配置空间则是设备私有的。每 个PCI插槽有一个配置事务的私用使能线,PCI控制器一次只能访问一个外设,不会有地址冲突。所有这些空间对于CPU来说都是可以访问的。在初始化阶 段,外设的配置信息被Linux内核中的初始化程序从配置空间读出。这样,一旦配置寄存器被读出,不需通过探测,驱动程序就可以访问它的硬件了。 54com.cn
  2.3 PCI配置寄存器
  2.3.1 配置寄存器布局
  PCI配置寄存器的布局是标准化的,由256字节的地址空间组成,其中前64个字节是标准化的,其余的则与具体设备相关。
  下表显示了与设备无关配置空间的布局 。
  配置地址偏移 寄存器英文名 寄存器中文名
  00H—01H Vendor ID 制造商标识
  02H—03H Device ID 设备标识
  04H—05H Command 命令寄存器
  06H—07H Status 状态寄存器
  08H Revision ID 版本识别号寄存器
  09H—0bH Class Code 分类代码寄存器
  0cH Cache Line Size CACHE行长度寄存器
  0dH Latency Timer 主设备延迟时间寄存器
  0eH Header Type 头标类型寄存器
  0fH Bulit-in-teset Register 内含自测试寄存器
  10H—13H Base Address Register 0 基地址寄存器0
  14H—17H Base Address Register 1 基地址寄存器1
  18H—1bH Base Address Register 2 基地址寄存器2
  1cH—19H Base Address Register 3 基地址寄存器3
  20H—23H Base Address Register 4 基地址寄存器4
  24H—27H Base Address Register 5 基地址寄存器5
  28H—2bH Cardbus CIS Pointer 设备总线CIS指针寄存器
  2cH—2dH Subsystem Vendor ID 子设备制造商标识
  2eH—2fH Subsystem Device ID 子设备标识
  30H—33H Expasion ROM Base Address 扩展ROM基地址
  34H—3bH ——— 保留
  3cH Interrupt Line 中断线寄存器
  3dH Interrupt Pin 中断引脚寄存器
  3eH Min_Gnt 最小授权寄存器
  3fH Max_Lat 最大延迟寄存器
   讨论所有的配置项显然超出了本文的讨论范围,通常,与设备一起发布的技术资料会详细描述它支持的寄存器。我们只对那些有助于驱动程序找到设备的配置项感 兴趣,它们是:VendorID、DeviceID和ClassCode。每个PCI外设都会把自己的值放入这些只读寄存器,驱动程序可以用它们来查找设 备。
  下面的头文件,宏,以及函数都将被PCI驱动程序用来寻找它的硬件设备:
  #include
   驱动程序需要知道PCI函数在核心是否是可用的。通过包含这个头文件,驱动程序获得了对CONFIG_宏的访问,包括CONFIG_PCI。
  CONFIG_PCI
   如果核心支持对PCI BIOS的调用,那么这个宏被定义。并不是每台计算机都有PCI总线,所以核心的开发者应该把 PCI的支持做成编译时的可选项,从而在无PCI的计算机上运行Linux时节省内存。如果CONFIG_PCI没有定义,那么这个列表中其它的函数都不 可用,驱动程序应使用预编译的条件语句将针对PCI的语句全都排除在外。
  #include
  这个头文件声明了以下介绍的函数。这个头文件还定义了函数返回的错误代码的符号值。
  int pcibios_present(void)
   由于与PCI相关的函数在无PCI总线的计算机上是毫无意义的,pcibios_present函数就是告诉驱动程序计算机是否支持PCI;如果支持, 它返回一个为真布尔值。在调用下面介绍的函数之前最好先检查一下pcibios_present,以保证计算机支持PCI。
  #include
  这个头文件定义了下面函数使用的所有数值的符号名。并不是所有的设备ID都在这个文件中列出了,但这个文件内容一直在增加,因为不断有新设备的符号定义被加入。
  int pcibios_find_device(unsigned short vendor,
  unsigned short id,
  unsigned short index,
  unsigned char *bus,
  unsigned char *function);
   如果CONFIG_PCI被定义了,并且pcibios_present也是真,这个函数被用来从BIOS请求相关设备的信息。vendor / id对用来确定设备。index用来支持具有同样的vendor / id对的几个设备。对这个函数的调用返回设备在总线上的位置以及函数指针。返回代码为0表示成功,非0表示失败。
  int pcibios_find_class(unsigned int class_code,
  unsigned short index,
  unsigned char *bus,
  unsigned char *function);
  这个函数和上一个类似,但它寻找属于特定类的设备。返回代码为0表示成功,非0表示有错。
  char *pcibios_strerror(int error)
  这个函数用来将一个PCI错误代码翻译为字符串。
  2.3.2 访问配置寄存器
  当驱动程序检测到设备后,就可以对内存、I/O和配置空间进行读或写了。特别地,访问配置空间对于驱动程序来说极为重要,因为这是它发现设备被映射到内存和I/O空间某地方的唯一方法。
  驱动程序或Linux内核可以使用以下的软件接口,以8位、16位、32位的数据来访问配置空间。这些函数都是标准化的,相关原型在中:
  int pcibios_read_config_byte( unsigned char bus,
  unsigned char function,
  unsigned char where,
  unsigned char *ptr);
  int pcibios_read_config_word(unsigned char bus,
  unsigned char function,
  unsigned char where,
  unsigned char *ptr);
  int pcibios_read_config_dword(unsigned char bus,
  unsigned char function,
  unsigned char where,
  unsigned char *ptr); 中国网管论坛bbs.bitsCN.com
  它们分别从由bus和function确定的设备的配置空间读取1,2,4个字节。参数where是从配置空间开始处的字节偏移, 从配置空间取出的值通过ptr返回。如果出错,这些函数的返回值是错误代码。
  int pcibios_write_config_byte( unsigned char bus,
  unsigned char function,
  unsigned char where,
  unsigned char val);
  int pcibios_ write_config_word(unsigned char bus,
  unsigned char function,
  unsigned char where,
  unsigned char val);
  int pcibios_ write_config_dword( unsigned char bus,
  unsigned char function,
  unsigned char where,
  unsigned char val);
  它们分别向配置空间里写1,2,4个字节。设备仍由bus和function确定,要写的值由val传递。
 2.4 访问I/O和内存空间
  配置项PCI_BASE_ADDRESS_0 到PCI_BASE_ADDRESS_5表示PCI外设的六个地址区段(这里的“区段”指一个PCI地址范围),每个区段可以由内存或I/O位置组成,或 者根本不存在。由于PC上的I/O空间已经相当拥挤,且有的处理器(如Alpha)自身没有I/O空间,因此大多数设备用一个内存区段代替它们的I/O 端口。
  PCI定义的I/O空间是一个32位的地址空间,如果设备使用64位的地址总线,那么它可以为每个区段用两个连续的PCI_BASE_ADDRESS寄存器在64位的内存空间来声明区段。
  然后我们就可以用前面讲到的函数来读写区段地址了, 例如:
  pcibios_read_config_dword(bus,fun, PCI_BASE_ADDRESS_0,&port);
  将PCI_BASE_ADDRESS_0代表的地址读到port中。
  pcibios_write_config_dword(bus,fun, PCI_BASE_ADDRESS_3,val);
  将val的值写到PCI_BASE_ADDRESS_3中。
  如何访问I/O端口呢?
  在已得到了I/O端口地址的前提下,可以使用Linux内核头文件中定义的函数访问I/O端口:
  unsigned char inb(unsigned short port);
  void outb(unsigned char byte,unsigned short port);
  按字节读写8位端口。
  unsigned short inw(unsigned short port);
  void outw(unsigned short word,unsigned short port);
  按字宽度读写16位端口。
  unsigned long inl(unsigned short port);
  void outl(unsigned long word,unsigned short port);
  按双字读写32位端口。
  注意,port参数在x86平台上定义为unsigned short,但在Alpha平台上定义为unsigned long。
     
  评论这张
 
阅读(247)| 评论(0)

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018