linux设备驱动程序设计11.docx

上传人:b****5 文档编号:29016248 上传时间:2023-07-20 格式:DOCX 页数:19 大小:30.56KB
下载 相关 举报
linux设备驱动程序设计11.docx_第1页
第1页 / 共19页
linux设备驱动程序设计11.docx_第2页
第2页 / 共19页
linux设备驱动程序设计11.docx_第3页
第3页 / 共19页
linux设备驱动程序设计11.docx_第4页
第4页 / 共19页
linux设备驱动程序设计11.docx_第5页
第5页 / 共19页
点击查看更多>>
下载资源
资源描述

linux设备驱动程序设计11.docx

《linux设备驱动程序设计11.docx》由会员分享,可在线阅读,更多相关《linux设备驱动程序设计11.docx(19页珍藏版)》请在冰豆网上搜索。

linux设备驱动程序设计11.docx

linux设备驱动程序设计11

PCI接口

尽管很多计算机用户认为PCI(外围部件互连,PeripheralComponentInterconnect)是布局电气线路的一种方法,但实际上,它是一组完全的规范,定义了计算机的不同部分是如何交互的。

PCI规范覆盖了与计算机接口相关的绝大多数方面。

我不打算在这里全部介绍,在本节中,我主要关心一个PCI驱动程序是如何找到它的硬件,并获得对它的访问的。

在第二章“构造和运行模块”的“自动和手工配置”一节,及在第九章“中断处理”的“自动检测中断号”一节中讨论过的探测技术同样可以应用于PCI设备,但规范还提供了探测的另外办法。

PCI结构被设计来替代ISA标准,由三个主要目标:

在计算机和其外围之间传送数据时有更高的性能,尽可能地做到平台无关性,使在系统中增减外围设备得到简化。

PCI通过使用比ISA高的时钟频率来获得更高的性能;它的时钟运行在25或33MHZ(实际时钟是系统时钟的几分之一的整数倍),而且马上就会游66MHZ的扩展。

另外,它被装配在32位的数据总线上,64位的扩展正在规范中。

平台无关性一直是计算机总线的一个设计目标,这是PCI的尤其重要的一个特征,因为PC世界一直以来总是被处理器特定的标准所主宰。

不过对驱动程序作者来说,最要紧的是对接口板自动检测的支持。

PCI设备是无跳线的(与大多数ISA外围不同),并且在引导时被自动配置。

因此,设备驱动程序必须能访问设备上的配置信息来完成初始化。

这些情形都不需要任何探测。

PCI寻址

每个外围由一个总线号、一个设备号、和一个功能号确定。

虽然PCI规范允许一个系统最多拥有256条总线,但PC只有一条。

每条总线最多带32个设备,但每个设备可以是最多个功能的多功能板(如一个音频设备带一个CD-ROM驱动器)。

每个功能可以由一个16位的键或两个8位的键确定。

Linux核心采用后一种方法。

每个外围板子的硬件电路回答与三个地址空间相关的询问:

内存位置,I/O端口,和配置寄存器。

前两个地址空间由PCI总线上的所有设备共享(也就是说,当你访问一个内存位置,所有的设备都将同时看到这个总线周期)。

而配置空间则利用“地理寻址”,每个槽有一个配置事务的私用使能线,PCI控制器一次访问一个板子,不会有地址冲突。

考虑到驱动程序,内存和I/O是以通常的inb,memcpy等来访问。

而配置事务则通过调用特定的核心函数访问配置寄存器来完成。

至于中断,每个PCI设备有4个中断管脚,它们到处理器中断线的路由是主板的任务;PCI中断可以设计为共享的,这样即使是一个有限中断线的处理器也能带很多PCI接口板。

PCI总线的I/O空间使用32位的地址总线(这样就是4GB的I/O端口),而内存空间则可以用32位或64位地址访问。

地址对每个设备来说应该是唯一的,但也有可能有两个设备错误地映射到同一个地址,使得哪个都不能被访问。

一个好消息是接口卡提供的每个内存和I/O地址区段都可以通过配置事务重映射。

这就是设备可以在引导时被初始化从而避免地址冲突的机制这些区段当前映射到的地址可以从配置空间读出,因此Linux驱动程序可以不通过探测就访问其设备。

一旦配置寄存器被读出,驱动程序就可以安全的访问它的硬件。

PCI配置空间由每个设备函数256个字节构成,配置寄存器的布局是标准化的。

配置空间有四个字节含有一个唯一的函数ID,因此驱动程序可以通过在外围查找特定的ID来B确定它的设备。

总之,每个设备板子被地理寻址以取得它的配置寄存器;这个信息可以用来确定这个板子或采取进一步动作。

从前面的描述,应该清楚PCI接口标准比ISA的主要创新是配置地址空间。

因此,除了通常的驱动程序代码外,PCI驱动程序还需要访问配置空间的能力。

在本章的其余部分,我将使用单词“设备”来指一个设备功能,因为多功能板上的每个功能均是一个独立的实体。

当我提到一个设备,我是指元组“总线号,设备号,功能号”。

如前所述,每个元组在Linux中由两个8位数字表示。

引导时

让我们看一下PCI是如何工作的,从系统引导开始,因为那时设备被配置。

当PCI设备被加电时,硬件关闭。

或者说,设备只对配置事务响应。

加电时,设备没有映射到计算机地址空间的内存和I/O端口;所有其它的设备特定的特征,象中断线,也都被关闭。

幸运的是,每个PCI母板都装有懂得PCI的固件,根据平台的不同被称做BIOS、NVRAM、或PROM。

固件提供对设备配置地址空间的访问,即使处理器的指令集不提供这样的能力。

在系统引导时,固件对每个PCI外围执行配置事务,从而为它提供的任何地址区段分配一个安全的地方。

到设备驱动程序访问设备时,它的内存和I/O区段已经被映射到处理器的地址空间。

驱动程序可以改变这个缺省的分配,但它通常并不这样做,除非有一些设备相关的原因要求这样。

在Linux中,用户可以通过读/proc/pci来查看PCI设备,这是个文本文件,系统中每个PCI板子有一项。

下面是/proc/pci中一项的例子:

(代码344)

/proc/pci中每一项是一个设备的设备无关特征的概述,如它的配置寄存器所描述的。

例如,上面这一项告诉我们这个设备有板上内存,已被映射到地址0xf1000000。

一些古怪的细节的含义以后在我介绍过配置寄存器后将会清楚。

检测设备

如前面提到的,配置空间的布局是设备无关的。

在这一节,我们将看看用来确定外围的配置寄存器。

PCI设备有一个256字节的地址空间。

前64个字节是标准化的,而其余的则是设备相关的。

图15-1显示了设备无关配置空间的布局。

如图所示,有些PCI的配置寄存器是要求的,而有些则是可选的。

每个PCI设备必须在必要寄存器中包含有意义的值,而可选寄存器的内容则以来与实际外围的能力。

可选域并不使用,除非必要域的内容表明它们是有效的。

这样,必要域断言了板子的能力,包括其它域可用与否。

有意思的是注意到PCI寄存器总是小印地安字节顺序的。

尽管标准要设计为体系结构无关的,PCI的设计者有时还是显示出对PC环境的偏见。

驱动程序的作者应该留神字节顺序,特别是访问多字节的配置寄存器时;在PC上工作的代码可能在别的平台上就不行。

Linux的开发者已经注意了字节排序问题(见下一节“访问配置空间”),但这个问题还是要牢记在心。

不幸的是,标准函数ntohs和ntohl都不能用,因为网络字节顺序与PCI顺序相反;在Linux2.0中没有标准函数将PCI字节顺序转换为主机字节顺序,每个用单个字节构成多字节值的驱动程序都应该特别小心地正确处理印地安字节序。

核心版本2.1.10引入了几个函数来处理这些字节顺序问题,它们在第十七章“最近的发展”中“转换函数”一节介绍。

(图15-1:

标准化的PCI配置寄存器)

描述所有的配置项超出了本书的范围。

通常,与设备一起发布的技术文档会描述它支持的寄存器。

我们感兴趣的是驱动程序如何找到它的设备,以及它如何访问设备的配置空间。

三个PCI寄存器确定一个设备:

销售商,设备ID,和类。

每个PCI外围把它自己的值放入这些只读寄存器,驱动程序可以用它们来查找设备。

让我们更仔细地看看这些寄存器:

销售商

这个16位的寄存器确定硬件的生产商。

例如,每个Intel的设备都会标上同样的销售商号,8086hex(是个随即值?

)。

这样的号码有一个全球的注册,生产商必须申请一个唯一的号。

设备ID

这是另一个16位寄存器,由生产商选择;不需要有官方的注册。

这个ID通常与销售商ID成对出现,形成一个硬件设备的唯一的32位标志符。

我将用单词“签名”来指销售商/设备ID对。

一个设备驱动程序经常以来于签名来确定它的设备;驱动程序的作者从硬件文档中知道要寻找什么值。

每个外围设备都属于一个类。

类寄存器是个16位的值,它的高八位确定“基类”(或组)。

例如,“以太网”和“令牌环”是属于“网络”组的两类,而“串行”和“并行”类属于“通信”组。

有些驱动程序可以支持几种类似的设备,它们虽然有不同的签名,却属于同一类;这些驱动程序可以依赖于类寄存器来确定它们的外围,如以后所示。

下面的头文件,宏,以及函数都将被PCI驱动程序用来寻找它的硬件设备:

#include

驱动程序需要知道是否PCI函数在核心是可用的。

通过包含这个头文件,驱动程序获得了对CONFIG_宏的访问,包括CONFIG_PCI(将在下面介绍)。

从1.3.73以来,这个头文件包含在中;如果想向后兼容,你必须把它显式地包含。

CONFIG_PCI

如果核心包括对PCIBIOS调用的支持,那么这个宏被定义。

并不是每个计算机都有PCI总线,所以核心的开发者应该把PCI的支持做成编译时选项,从而在无PCI的计算机上运行Linux时节省内存。

如果CONFIG_PCI没有定义,那么这个列表中其它的函数都不可用,驱动程序应使用预编译的条件语句将PCI支持全都排除在外,以避免加载时的“未定义符号”错。

#include

这个头文件声明了本节介绍的所有的原型,因此一定要被包含。

这个头文件还定义了函数返回的错误代码的符号值。

它在1.2和2.0之间没有改变,因此没有可移植性问题。

intpcibios_present(void)

由于PCI相关的函数在无PCI的计算机上毫无意义,pcibios_present函数就是告诉驱动程序计算机是否支持PCI;如果BIOS懂得PCI,它返回一个为真布尔值。

即使CONFIG_PCI被定义了,PCI功能仍是一个运行时选项。

因此,你在调用下面介绍的函数之前要检查一下pcibios_present,保证计算机支持PCI。

#include

这个头文件定义了下面函数使用的所有数值的符号名。

并不是所有的设备ID都在这个文件中列出了,但你在为你的ID,销售商,类定义宏之前,最好还是看看这个文件。

注意这个文件一直在变大,因为不断有新设备的符号定义被加入。

intpcibios_find_device(unsignedshortvendor,unsignedshortid,unsignedshortindex,

unsignedchar*bus,unsignedchar*function);

如果CONFIG_PCI被定义了,并且pcibios_present也是真,这个函数被用来从BIOS请求关于设备的信息。

销售商/ID对确定设备。

index用来支持具有同样的销售商/ID对的几个设备,下面将会解释。

对这个函数的调用返回设备在总线上的位置以及函数指针。

返回代码为0表示成功,非0表示失败。

intpcibios_find_class(unsignedintclass_code,unsignedshortindex,

unsignedchar*bus,unsignedchar*function);

这个函数和上一个类似,但它寻找属于特定类的设备。

参数class_code传递的形式为:

16位的类寄存器左移八位,这与BIOS接口使用类寄存器的方式有关。

这次还是,返回代码为0表示成功,非0表示有错。

char*pcibios_strerror(interror);

这个函数用来翻译一个PCI错误代码(象pcibios_find_device返回的)为一个字符串。

你也许在查找函数返回的即不是PCIBIOS_SUCCESSFUL(0),也不是PCIBIOS_DEVICE_NOT_FOUND时(这是当所有的设备都被找过以后所期望返回的错误代码),希望打印一条错误信息。

下面的代码是驱动程序在加载时检测设备所使用的典型代码。

如上面所提到的,这个查找可以基于签名或者设备类。

不管是哪种情况,驱动程序不许存储bus和function值,它们在后面确定设备时要用到。

function的前五位确定设备,后三位确定函数。

下面的代码中,每个设备特定的符号加前缀jail_(另一个指令列表),大写或小写依赖于符号的种类。

如果驱动程序可以依赖于唯一的销售商/ID对,下面的循环可以用来初始化驱动程序:

(代码347)

(代码348)

如果这个代码段只处理由JAIL_VENDOR和JAIL_ID确定的一类PCI设备,那么它是正确的。

不过,很多驱动程序非常灵活,能够同时处理PCI和ISA板子。

在这种情况下,驱动程序仅在没有检测到PCI板子或CONFIG_PCIBIOS没有定义时才探测ISA设备。

使用pcibios_find_class要求jail_init_dev完成比例子中要多的工作。

只要它找到了一个属于指定类的设备,这个函数就成功返回,但驱动程序还要确认其签名也是被支持的。

这个任务通过一系列的条件语句完成,结果是抛弃很多不期望的设备。

有些PCI外围包含通用目的的PCI接口芯片和设备特定的电路。

所有使用同样接口芯片的外围板子都有同样的签名,驱动程序必须进行额外的探测以保证它在处理正确的外围设备。

因此,有时象jail_init_dev之类的函数必须准备好做一些设备特定的额外的检测,以抛弃那些可能有正确签名的设备。

访问配置空间

在驱动程序检测到设备后,它通常要对三个地址空间读或写:

内存、端口和配置。

特别地,访问配置空间对驱动程序来说极为重要,以呢这是它发现设备被映射到内存和I/O空间什么地方的唯一的办法。

由于微处理器无法直接访问配置空间,计算机销售商必须提供一个办法来完成它。

准确的实现因此是销售商相关的,与我们这里的讨论无关。

幸运的是,这个事务的软件接口(下面描述)是标准化的,驱动程序或Linux核心都不需要知道它的细节。

至于驱动程序,配置空间可以通过8位、16位、32位的数据传送来访问。

相关函数的原型在

intpcibios_read_config_byte(unsignedcharbus,unsignedcharfunction,

unsignedcharwhere,unsignedchar*ptr);

intpcibios_read_config_word(unsignedcharbus,unsignedcharfunction,

unsignedcharwhere,unsignedchar*ptr);

intpcibios_read_config_dword(unsignedcharbus,unsignedcharfunction,

unsignedcharwhere,unsignedchar*ptr);

从由bus和function确定的设备的配置空间读取1,2,4个字节。

参数where是从配置空间开始处的字节偏移。

从配置空间取出的值通过ptr返回,这些函数的返回值是错误代码。

字和双字函数将刚从小印地安字节序读出的值转换为处理器本身的字节序,因此你并不需要处理字节序。

intpcibios_write_config_byte(unsignedcharbus,unsignedcharfunction,

unsignedcharwhere,unsignedcharval);

intpcibios_write_config_word(unsignedcharbus,unsignedcharfunction,

unsignedcharwhere,unsignedshortval);

intpcibios_write_config_dword(unsignedcharbus,unsignedcharfunction,

unsignedcharwhere,unsignedintval);

向配置空间里写1,2,4个字节。

设备仍由bus和function确定,要写的值由val传递。

字和双字函数在向外围设备写之前将数值转换为小印地安字节序。

访问配置变量的最好办法是使用在中定义的符号名。

例如,下面的两行程序通过给pcibios_read_config_byte的where传递符号名来获取一个设备的修正ID。

Unsignedcharjail_get_revision(unsignedcharbus,unsignedcharfn)

{

unsignedchar*revision;

pcibios_read_config_byte(bus,fn,PCI_REVISION_ID,&revision);

returnrevision;

}

当访问多字节值时,程序远一定要记住字节序的问题。

看看一个配置快照

如果你向浏览你系统上PCI设备的配置空间,你可以编译并加载模块pci/pcidata.c,它在O’ReillyFTP站点上提供的源文件中。

这个模块生成一个动态的/proc/pcidata文件,包含有你的PCI设备配置空间的二进制快照。

这个快照在文件每次被读时更新。

/proc/pcidata的大小被限制为PAGE_SIZE字节(这是动态/proc文件的限制,在第四章“调试技术”中“使用/proc文件系统”一节介绍过)。

这样,它只列出前PAGESIZE/256个设备的配置内存,意味着16或32个设备(也许对你的系统已经够了)。

我选择把/proc/pcidata作成二进制文件,而不是象其它/proc文件那样是文本的,就是因为这个大小限制。

pcidata的另一个限制是它只扫描系统的第一条PCI总线。

如果你的系统有到其它PCI总线的桥,pcidata将忽略它们。

在/proc/pcidata中设备出现的顺序与/proc/pci中相反。

这是因为/proc/pci读的是一个从头部生长的链表,而/proc/pcidata则是一个简单的查找循环,它按照取到的顺序将所有的东西输出。

例如,我的抓图器在/proc/pcidata的第二个出现,(目前)有下面的配置寄存器:

morgana%ddbs=256skip=1count=1if=/proc/pcidata|od–Ax–tx1

1+0recordsin

1+0recordsout

00000086802312060000020000000400200000

000010000000f1000000000000000000000000

00002000000000000000000000000000000000

0000300000000000000000000000000a010000

00004000000000000000000000000000000000

如果你将上面的输出和图15-1比较,你就可以理解这些数字。

或者,你可以使用pcidump程序,在可以从FTP站点上找到,它将输出列表格式化并标号。

pcidump的代码并不值得在这儿列出,因为这个简单程序只是一个长表,外加十行扫描这个表的代码。

相反,让我们看看一些选择的输出行:

(代码351)

pcidata和pcidump,与grep配合使用,对调试驱动程序的初始化代码非常有用。

不过注意,pcidata.c模块是GPL的,因为我是从核心源码中取的PCI扫描循环。

这不应该对你作为一个驱动程序的作者有什么影响,因为我只是以一个支持工具的形式将这个模块包含在源文件中,而不是新驱动程序的可重用模版。

访问I/O和内存空间

一个PCI外围实现六个地址区段。

每个区段由内存或I/O位置组成,或者压根不存在。

大多数设备用一个内存区段代替它们的I/O端口,因为有些处理器(象Alpha)没有本身的I/O空间,还因为PC上的I/O空间都相当拥挤。

内存和I/O空间的结构化的不同通过实现一个“内存可预取”位来表达。

将其控制寄存器映射到内存地址范围的外围将这个范围声明为不可预取的,而PCI板子上的有些东西如视频内存是可预取的。

在本节中,只要讨论适用于内存或I/O,我就用单词“区段”来指一个PCI地址范围。

一个接口板子用配置寄存器(在图15-1中所示的6个32位寄存器,它们的符号名从PCI_BASE_ADDRESS_0到PCI_BASE_ADDRESS_5)报告它的区段的大小和当前位置。

由于PCI定义的I/O空间是一个32位的地址空间,因此用对内存和I/O适用同样的配置接口是可行的。

如果设备使用64位的地址总线,它可以为每个区段用两个连续的PCI_BASE_ADDRESS寄存器在64位的内存空间来声明区段。

因此有可能一个设备同时提供32位和64位的区段。

我不想在这儿讨论太多的细节,因为如果你打算写一个PCI驱动程序,你总会这个设备的硬件手册的。

特别地,我不打算使用寄存器的预取位或两个“类型”位,并且我将讨论限制在32位外围上。

不过,了解一下一般情况下是如何实现的,以及Linux驱动程序是如何处理PCI内存是很有趣的。

PCI规范要求每个被实现的区段百升微被映射到一个可配置地址上。

这意味着设备必须位它实现的每个区段装备一个可编程32位解码器,并且利用64位PCI扩展的板子必须有一个4位可编程解码器。

尽管在PC上没有64位PCI总线,一些Alpha工作站则有。

由于通常一个区段的字节数是2的幂,如32、64、4KB或2MB,所以可编程解码器的实际实现和使用都被简化了。

而且,将一个区段映射到一个未对齐的地址上意义也不大;1MB的区段自然在1M整数倍的地址处对齐,32字节的区段则在32的整数倍处。

PCI规范利用了这个对齐;它要求地址解码器需要且只需查看地址总线的高位,并且只有高位是可编程的。

这个约定也意味着任何区段的大小都必须是2的幂。

这样,重映射一个PCI区段可以通过在配置寄存器的高位设置一个合适的值来完成。

例如,一个1M的区段,有20位的地址空间,可以通过设置寄存器的高12位进行重映射;向寄存器写0x008xxxxx告诉板子对8MB-9MB的地址区间响应。

实际上,只有非常高的地址被用来映射PCI区段。

这种“部分解码”有几个额外的好处就是软件可以通过检查配置寄存器中非可编程位的数目来确定PCI区段的大小。

为了这个目的,PCI标准要求未使用的位必须总是读作0。

通过强制I/O区段的最小大小为8字节,内存区段为16字节,标准可以把一些额外的信息放入同一个PCI寄存器中:

“空间”位,表明区段是内存的还是I/O的;两个“类型”位;一个“预取”位,只是位内存定义的。

类型位在32位区段、64位区段、以及“必须映射在1M一下的32位区段”进行选择。

最后这个值用于那些仍然运行于一些PC上的过时软件。

检测一个PCI区段的大小可以通过使用几个定义在中的位掩码来简化:

是个内存区段时PCI_BASE_ADDRESS_SPACE被置位;PCI_BASE_ADDRESS_MEM_MASK为内存区段掩去配置位;PCI_BASE_ADDRESS_TO_MASK位I/O区段掩去这些位。

规范还要求地址区段必须按序分配,从PCI_BASE_ADDRESS_0到PCI_BASE_ADDRESS_5;这样一旦一个基地址未用(也就是被置未0),你就可以知道所有的后续地址都未用。

报告PCI区段当前位置和大小的典型代码如下:

(代码353)

(代码354#1)

这个代码是pciregion模块的一部分,与pcidata在同一个目录下发布;这个模块生成一个/pci/pciregions文件,用上面给出的代码产生数据。

当配置寄存器被修改时,中断报告被关闭,以防止驱动程序访问被映射到错误位置的区段。

使用cli而不是save_

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 求职职场 > 社交礼仪

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1