📄 (ldd) ch15-外围总线概览(转载).htm
字号:
<DIV align=center><A href="mailto:joyfire@sina.com"><FONT
color=#ffffff>联系</FONT></A></DIV></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE>
<TABLE borderColor=#666666 cellPadding=2 width="90%" align=center border=2>
<TBODY>
<TR>
<TD bgColor=#000000>
<P align=center><A href="http://joyfire.net/lsdp/index.htm"><FONT
color=#ffffff size=2>目录页</FONT></A> | <A
href="http://joyfire.net/lsdp/18.htm"><FONT color=#ffffff
size=2>上一页</FONT></A> | <A href="http://joyfire.net/lsdp/20.htm"><FONT
color=#ffffff size=2>下一页</FONT></A></P>
<P align=center><FONT face=黑体 color=#ffffff size=6>(LDD) Ch15-外围总线概览(转载)
</FONT></P><SPAN style="LINE-HEIGHT: 1; LETTER-SPACING: 0pt"><FONT
color=#ffffff size=3>
<P>发信人: Altmayer (alt), 信区: GNULinux<BR>标 题: (LDD) Ch15-外围总线概览(转载)<BR>发信站: 饮水思源 (2001年12月13日08:58:01 星期四), 站内信件<BR> <BR>【 以下文字转载自 <FONT
color=#00ff00>UNIXpost </FONT>讨论区 】<BR>【 原文由<FONT
color=#00ff00> altmayer.bbs@bbs.nju.edu.cn,</FONT> 所发表 】<BR> <BR>【 以下文字转载自 <FONT
color=#00ff00>altmayer </FONT>的信箱 】<BR> <BR> <BR>第十五章 外围总线概览<BR> <BR> <BR> <BR>在第八章“硬件管理”中,我们介绍了最低级的硬件控制,本章提供一个较高级的总线<BR>体系结构的概览。总线由电气接口和编程接口组成。在这一章,我打算介绍编程接口。<BR> <BR>本章覆盖了几种总线体系结构。不过,基本重点是访问PCI外围的核心功能,因为近来,<BR>PCI总线是最常用的外围总线,也是核心支持最好的总线。<BR> <BR> <BR> <BR>PCI接口<BR></P></FONT><FONT
color=#ffffff size=3>
<P>PCI接口<BR> <BR> <BR> <BR>尽管很多计算机用户认为PCI(外围部件互连,Peripheral Component Interconnect)<BR>是布局电气线路的一种方法,但实际上,它是一组完全的规范,定义了计算机的不同部<BR>分是如何交互的。<BR> <BR>PCI规范覆盖了与计算机接口相关的绝大多数方面。我不打算在这里全部介绍,在本节中<BR>,我主要关心一个PCI驱动程序是如何找到它的硬件,并获得对它的访问的。在第二章“<BR>构造和运行模块”的“自动和手工配置”一节,及在第九章“中断处理”的“自动检测<BR>中断号”一节中讨论过的探测技术同样可以应用于PCI设备,但规范还提供了探测的另外<BR>办法。<BR> <BR>PCI结构被设计来替代ISA标准,由三个主要目标:在计算机和其外围之间传送数据时有<BR>更高的性能,尽可能地做到平台无关性,使在系统中增减外围设备得到简化。<BR> <BR>PCI通过使用比ISA高的时钟频率来获得更高的性能;它的时钟运行在25或33MHZ(实际时<BR>钟是系统时钟的几分之一的整数倍),而且马上就会游66MHZ的扩展。另外,它被装配在<BR>32位的数据总线上,64位的扩展正在规范中。平台无关性一直是计算机总线的一个设计<BR>目标,这是PCI的尤其重要的一个特征,因为PC世界一直以来总是被处理器特定的标准所<BR>主宰。<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR>不过对驱动程序作者来说,最要紧的是对接口板自动检测的支持。PCI设备是无跳线的(<BR>与大多数ISA外围不同),并且在引导时被自动配置。因此,设备驱动程序必须能访问设<BR>备上的配置信息来完成初始化。这些情形都不需要任何探测。<BR> <BR> <BR> <BR>PCI寻址<BR> <BR>每个外围由一个总线号、一个设备号、和一个功能号确定。虽然PCI规范允许一个系统最<BR>多拥有256条总线,但PC只有一条。每条总线最多带32个设备,但每个设备可以是最多个<BR>功能的多功能板(如一个音频设备带一个CD-ROM驱动器)。每个功能可以由一个16位的<BR>键或两个8位的键确定。Linux核心采用后一种方法。<BR> <BR>每个外围板子的硬件电路回答与三个地址空间相关的询问:内存位置,I/O端口,和配置<BR>寄存器。前两个地址空间由PCI总线上的所有设备共享(也就是说,当你访问一个内存位<BR>置,所有的设备都将同时看到这个总线周期)。而配置空间则利用“地理寻址”,每个<BR>槽有一个配置事务的私用使能线,PCI控制器一次访问一个板子,不会有地址冲突。考虑<BR>到驱动程序,内存和I/O是以通常的inb,memcpy等来访问。而配置事务则通过调用特定的<BR>核心函数访问配置寄存器来完成。至于中断,每个PCI设备有4个中断管脚,它们到处理<BR>器中断线的路由是主板的任务;PCI中断可以设计为共享的,这样即使是一个有限中断线<BR>的处理器也能带很多PCI接口板。<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR>PCI总线的I/O空间使用32位的地址总线(这样就是4GB的I/O端口),而内存空间则可以<BR>用32位或64位地址访问。地址对每个设备来说应该是唯一的,但也有可能有两个设备错<BR>误地映射到同一个地址,使得哪个都不能被访问。一个好消息是接口卡提供的每个内存<BR>和I/O地址区段都可以通过配置事务重映射。这就是设备可以在引导时被初始化从而避免<BR>地址冲突的机制这些区段当前映射到的地址可以从配置空间读出,因此Linux驱动程序可<BR>以不通过探测就访问其设备。一旦配置寄存器被读出,驱动程序就可以安全的访问它的<BR>硬件。<BR> <BR>PCI配置空间由每个设备函数256个字节构成,配置寄存器的布局是标准化的。配置空间<BR>有四个字节含有一个唯一的函数ID,因此驱动程序可以通过在外围查找特定的ID来B确定<BR>它的设备*。总之,每个设备板子被地理寻址以取得它的配置寄存器;这个信息可以用来<BR>确定这个板子或采取进一步动作。<BR> <BR>从前面的描述,应该清楚PCI接口标准比ISA的主要创新是配置地址空间。因此,除了通<BR>常的驱动程序代码外,PCI驱动程序还需要访问配置空间的能力。<BR> <BR>在本章的其余部分,我将使用单词“设备”来指一个设备功能,因为多功能板上的每个<BR>功能均是一个独立的实体。当我提到一个设备,我是指元组“总线号,设备号,功能号<BR>”。如前所述,每个元组在Linux中由两个8位数字表示。<BR> <BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR>引导时<BR> <BR>让我们看一下PCI是如何工作的,从系统引导开始,因为那时设备被配置。<BR> <BR>当PCI设备被加电时,硬件关闭。或者说,设备只对配置事务响应。加电时,设备没有映<BR>射到计算机地址空间的内存和I/O端口;所有其它的设备特定的特征,象中断线,也都被<BR>关闭。<BR> <BR>幸运的是,每个PCI母板都装有懂得PCI 的固件,根据平台的不同被称做BIOS、NVRAM、<BR>或PROM。固件提供对设备配置地址空间的访问,即使处理器的指令集不提供这样的能力<BR>。<BR> <BR>在系统引导时,固件对每个PCI外围执行配置事务,从而为它提供的任何地址区段分配一<BR>个安全的地方。到设备驱动程序访问设备时,它的内存和I/O区段已经被映射到处理器的<BR>地址空间。驱动程序可以改变这个缺省的分配,但它通常并不这样做,除非有一些设备<BR>相关的原因要求这样。<BR> <BR>在Linux中,用户可以通过读/proc/pci来查看PCI 设备,这是个文本文件,系统中每个P<BR>CI板子有一项。下面是/proc/pci中一项的例子:<BR> <BR>(代码344)<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR>/proc/pci中每一项是一个设备的设备无关特征的概述,如它的配置寄存器所描述的。例<BR>如,上面这一项告诉我们这个设备有板上内存,已被映射到地址0xf1000000。一些古怪<BR>的细节的含义以后在我介绍过配置寄存器后将会清楚。<BR> <BR> <BR> <BR>检测设备<BR> <BR>如前面提到的,配置空间的布局是设备无关的。在这一节,我们将看看用来确定外围的<BR>配置寄存器。<BR> <BR>PCI设备有一个256字节的地址空间。前64个字节是标准化的,而其余的则是设备相关的<BR>。图15-1显示了设备无关配置空间的布局。<BR> <BR>如图所示,有些PCI的配置寄存器是要求的,而有些则是可选的。每个PCI设备必须在必<BR>要寄存器中包含有意义的值,而可选寄存器的内容则以来与实际外围的能力。可选域并<BR>不使用,除非必要域的内容表明它们是有效的。这样,必要域断言了板子的能力,包括<BR>其它域可用与否。<BR> <BR>有意思的是注意到PCI寄存器总是小印地安字节顺序的。尽管标准要设计为体系结构无关<BR>的,PCI的设计者有时还是显示出对PC环境的偏见。驱动程序的作者应该留神字节顺序,<BR>特别是访问多字节的配置寄存器时;在PC上工作的代码可能在别的平台上就不行。Linux<BR></P></FONT><FONT
color=#ffffff size=3>
<P>特别是访问多字节的配置寄存器时;在PC上工作的代码可能在别的平台上就不行。Linux<BR>的开发者已经注意了字节排序问题(见下一节“访问配置空间”),但这个问题还是要<BR>牢记在心。不幸的是,标准函数ntohs和ntohl都不能用,因为网络字节顺序与PCI顺序相<BR>反;在Linux2.0中没有标准函数将PCI字节顺序转换为主机字节顺序,每个用单个字节构<BR>成多字节值的驱动程序都应该特别小心地正确处理印地安字节序。核心版本2.1.10引入<BR>了几个函数来处理这些字节顺序问题,它们在第十七章“最近的发展”中“转换函数”<BR>一节介绍。<BR> <BR>(图15-1:标准化的PCI配置寄存器)<BR> <BR>描述所有的配置项超出了本书的范围。通常,与设备一起发布的技术文档会描述它支持<BR>的寄存器。我们感兴趣的是驱动程序如何找到它的设备,以及它如何访问设备的配置空<BR>间。<BR> <BR>三个PCI寄存器确定一个设备:销售商,设备ID,和类。每个PCI外围把它自己的值放入<BR>这些只读寄存器,驱动程序可以用它们来查找设备。让我们更仔细地看看这些寄存器:<BR> <BR>销售商<BR> <BR>这个16位的寄存器确定硬件的生产商。例如,每个Intel的设备都会标上同样的销售商号<BR>,8086 hex(是个随即值?)。这样的号码有一个全球的注册,生产商必须申请一个唯<BR>一的号。<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR>设备ID<BR> <BR>这是另一个16位寄存器,由生产商选择;不需要有官方的注册。这个ID通常与销售商ID<BR>成对出现,形成一个硬件设备的唯一的32位标志符。我将用单词“签名”来指销售商/设<BR>备ID对。一个设备驱动程序经常以来于签名来确定它的设备;驱动程序的作者从硬件文<BR>档中知道要寻找什么值。<BR> <BR>类<BR> <BR>每个外围设备都属于一个类。类寄存器是个16位的值,它的高八位确定 “基类”(或组<BR>)。例如,“以太网”和“令牌环”是属于“网络”组的两类,而“串行”和“并行”<BR>类属于“通信”组。有些驱动程序可以支持几种类似的设备,它们虽然有不同的签名,<BR>却属于同一类;这些驱动程序可以依赖于类寄存器来确定它们的外围,如以后所示。<BR> <BR> <BR> <BR>下面的头文件,宏,以及函数都将被PCI驱动程序用来寻找它的硬件设备:<BR> <BR>#include <linux/config.h><BR> <BR>驱动程序需要知道是否PCI函数在核心是可用的。通过包含这个头文件,驱动程序获得了<BR>对CONFIG_宏的访问,包括CONFIG_PCI(将在下面介绍)。从1.3.73以来,这个头文件包含<BR></P></FONT><FONT
color=#ffffff size=3>
<P>对CONFIG_宏的访问,包括CONFIG_PCI(将在下面介绍)。从1.3.73以来,这个头文件包含<BR>在<linux/fs.h>中;如果想向后兼容,你必须把它显式地包含。<BR> <BR>CONFIG_PCI<BR> <BR>如果核心包括对PCI BIOS调用的支持,那么这个宏被定义。并不是每个计算机都有PCI总<BR>线,所以核心的开发者应该把 PCI的支持做成编译时选项,从而在无PCI的计算机上运行<BR>Linux时节省内存。如果CONFIG_PCI没有定义,那么这个列表中其它的函数都不可用,驱<BR>动程序应使用预编译的条件语句将PCI支持全都排除在外,以避免加载时的“未定义符号<BR>”错。<BR> <BR>#include <linux/bios32.h><BR> <BR>这个头文件声明了本节介绍的所有的原型,因此一定要被包含。这个头文件还定义了函<BR>数返回的错误代码的符号值。它在1.2和2.0之间没有改变,因此没有可移植性问题。<BR> <BR>int pcibios_present(void)<BR> <BR>由于PCI相关的函数在无PCI的计算机上毫无意义,pcibios_present函数就是告诉驱动程<BR>序计算机是否支持PCI;如果BIOS懂得PCI,它返回一个为真布尔值。即使CONFIG_PCI被<BR>定义了,PCI功能仍是一个运行时选项。因此,你在调用下面介绍的函数之前要检查一下<BR>pcibios_present,保证计算机支持PCI。<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR>#include <linux/pci.h><BR> <BR>这个头文件定义了下面函数使用的所有数值的符号名。并不是所有的设备ID都在这个文<BR>件中列出了,但你在为你的ID,销售商,类定义宏之前,最好还是看看这个文件。注意<BR>这个文件一直在变大,因为不断有新设备的符号定义被加入。<BR> <BR>int pcibios_find_device(unsigned short vendor, unsigned short id, unsigned<BR>short index,<BR> <BR> unsigned char *bus, unsigned char *function);<BR> <BR>如果CONFIG_PCI被定义了,并且pcibios_present也是真,这个函数被用来从BIOS请求关<BR>于设备的信息。销售商/ID对确定设备。index用来支持具有同样的销售商/ID对的几个设<BR>备,下面将会解释。对这个函数的调用返回设备在总线上的位置以及函数指针。返回代<BR>码为0表示成功,非0表示失败。<BR> <BR>int pcibios_find_class(unsigned int class_code, unsigned short index,<BR> <BR> unsigned char *bus, unsigned char *function);<BR> <BR>这个函数和上一个类似,但它寻找属于特定类的设备。参数class_code传递的形式为:1<BR>6位的类寄存器左移八位,这与BIOS接口使用类寄存器的方式有关。这次还是,返回代码<BR></P></FONT><FONT
color=#ffffff size=3>
<P>6位的类寄存器左移八位,这与BIOS接口使用类寄存器的方式有关。这次还是,返回代码<BR>为0表示成功,非0表示有错。<BR> <BR>char *pcibios_strerror(int error);<BR> <BR>这个函数用来翻译一个PCI错误代码(象pcibios_find_device返回的)为一个字符串。<BR>你也许在查找函数返回的即不是PCIBIOS_SUCCESSFUL(0),也不是PCIBIOS_DEVICE_NOT_F<BR>OUND时(这是当所有的设备都被找过以后所期望返回的错误代码),希望打印一条错误<BR>信息。<BR> <BR> <BR> <BR>下面的代码是驱动程序在加载时检测设备所使用的典型代码。如上面所提到的,这个查<BR>找可以基于签名或者设备类。不管是哪种情况,驱动程序不许存储bus和function值,它<BR>们在后面确定设备时要用到。function的前五位确定设备,后三位确定函数。<BR> <BR>下面的代码中,每个设备特定的符号加前缀jail_(另一个指令列表),大写或小写依赖<BR>于符号的种类。<BR> <BR>如果驱动程序可以依赖于唯一的销售商/ID对,下面的循环可以用来初始化驱动程序:<BR> <BR>(代码347)<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR>(代码348)<BR> <BR>如果这个代码段只处理由JAIL_VENDOR和JAIL_ID确定的一类PCI设备,那么它是正确的。<BR> <BR> <BR>不过,很多驱动程序非常灵活,能够同时处理PCI和ISA板子。在这种情况下,驱动程序<BR>仅在没有检测到PCI板子或CONFIG_PCIBIOS没有定义时才探测ISA设备。<BR> <BR>使用pcibios_find_class要求jail_init_dev完成比例子中要多的工作。只要它找到了一<BR>个属于指定类的设备,这个函数就成功返回,但驱动程序还要确认其签名也是被支持的<BR>。这个任务通过一系列的条件语句完成,结果是抛弃很多不期望的设备。<BR> <BR>有些PCI外围包含通用目的的PCI接口芯片和设备特定的电路。所有使用同样接口芯片的<BR>外围板子都有同样的签名,驱动程序必须进行额外的探测以保证它在处理正确的外围设<BR>备。因此,有时象jail_init_dev之类的函数必须准备好做一些设备特定的额外的检测,<BR>以抛弃那些可能有正确签名的设备。<BR> <BR> <BR> <BR>访问配置空间<BR> <BR>在驱动程序检测到设备后,它通常要对三个地址空间读或写:内存、端口和配置。特别<BR></P></FONT><FONT
color=#ffffff size=3>
<P>在驱动程序检测到设备后,它通常要对三个地址空间读或写:内存、端口和配置。特别<BR>地,访问配置空间对驱动程序来说极为重要,以呢这是它发现设备被映射到内存和I/O空<BR>间什么地方的唯一的办法。<BR> <BR>由于微处理器无法直接访问配置空间,计算机销售商必须提供一个办法来完成它。准确<BR>的实现因此是销售商相关的,与我们这里的讨论无关。幸运的是,这个事务的软件接口<BR>(下面描述)是标准化的,驱动程序或Linux核心都不需要知道它的细节。<BR> <BR>至于驱动程序,配置空间可以通过8位、16位、32位的数据传送来访问。相关函数的原型<BR>在<linux/bios32.h>:<BR> <BR>int pcibios_read_config_byte(unsigned char bus, unsigned char function,<BR> <BR> unsigned char where, unsigned char *ptr);<BR> <BR>int pcibios_read_config_word(unsigned char bus, unsigned char function,<BR> <BR> unsigned char where, unsigned char *ptr);<BR> <BR>int pcibios_read_config_dword(unsigned char bus, unsigned char function,<BR> <BR> unsigned char where, unsigned char *ptr);<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> 从由bus和function确定的设备的配置空间读取1,2,4个字节。参数where是从配置<BR>空间开始处的字节偏移。 从配置空间取出的值通过ptr返回,这些函数的返回值是错误<BR>代码。字和双字函数将刚从小印地安字节序读出的值转换为处理器本身的字节序,因此<BR>你并不需要处理字节序。<BR> <BR>int pcibios_write_config_byte(unsigned char bus, unsigned char function,<BR> <BR> unsigned char where, unsigned char val);<BR> <BR>int pcibios_write_config_word(unsigned char bus, unsigned char function,<BR> <BR> unsigned char where, unsigned short val);<BR> <BR>int pcibios_write_config_dword(unsigned char bus, unsigned char function,<BR> <BR> unsigned char where, unsigned int val);<BR> <BR> 向配置空间里写1,2,4个字节。设备仍由bus和function确定,要写的值由val传递<BR>。字和双字函数在向外围设备写之前将数值转换为小印地安字节序。<BR> <BR>访问配置变量的最好办法是使用在<linux/pci.h>中定义的符号名。例如,下面的两行程<BR>序通过给pcibios_read_config_byte的where传递符号名来获取一个设备的修正ID。<BR></P></FONT><FONT
color=#ffffff size=3>
<P>序通过给pcibios_read_config_byte的where传递符号名来获取一个设备的修正ID。<BR> <BR>Unsigned char jail_get_revision(unsigned char bus, unsigned char fn)<BR> <BR>{<BR> <BR> unsigned char *revision;<BR> <BR> <BR> <BR> pcibios_read_config_byte(bus,fn, PCI_REVISION_ID,&revision);<BR> <BR> return revision;<BR> <BR>}<BR> <BR>当访问多字节值时,程序远一定要记住字节序的问题。<BR> <BR> <BR> <BR>看看一个配置快照<BR> <BR>如果你向浏览你系统上PCI设备的配置空间,你可以编译并加载模块pci/pcidata.c,它<BR></P></FONT><FONT
color=#ffffff size=3>
<P>如果你向浏览你系统上PCI设备的配置空间,你可以编译并加载模块pci/pcidata.c,它<BR>在O’Reilly FTP站点上提供的源文件中。<BR> <BR>这个模块生成一个动态的/proc/pcidata文件,包含有你的PCI设备配置空间的二进制快<BR>照。这个快照在文件每次被读时更新。/proc/pcidata的大小被限制为PAGE_SIZE字节(这<BR>是动态/proc文件的限制,在第四章“调试技术”中“使用/proc文件系统”一节介绍过)<BR>。这样,它只列出前PAGESIZE/256个设备的配置内存,意味着16或32个设备(也许对你<BR>的系统已经够了)。我选择把/proc/pcidata作成二进制文件,而不是象其它/proc文件<BR>那样是文本的,就是因为这个大小限制。<BR> <BR>pcidata的另一个限制是它只扫描系统的第一条PCI总线。如果你的系统有到其它PCI总线<BR>的桥,pcidata将忽略它们。<BR> <BR>在/proc/pcidata中设备出现的顺序与/proc/pci中相反。这是因为/proc/pci读的是一个<BR>从头部生长的链表,而/proc/pcidata则是一个简单的查找循环,它按照取到的顺序将所<BR>有的东西输出。<BR> <BR>例如,我的抓图器在/proc/pcidata的第二个出现,(目前)有下面的配置寄存器:<BR> <BR>morgana% dd bs=256 skip=1 count=1 if=/proc/pcidata | od –Ax –t x1<BR> <BR>1+0 records in<BR> <BR></P></FONT><FONT
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -