📄 ch12.html
字号:
00a0
$ tree /sys/bus/pci/devices/
/sys/bus/pci/devices/
|-- 0000:00:00.0 -> ../../../devices/pci0000:00/0000:00:00.0
|-- 0000:00:00.1 -> ../../../devices/pci0000:00/0000:00:00.1
|-- 0000:00:00.2 -> ../../../devices/pci0000:00/0000:00:00.2
|-- 0000:00:02.0 -> ../../../devices/pci0000:00/0000:00:02.0
|-- 0000:00:04.0 -> ../../../devices/pci0000:00/0000:00:04.0
|-- 0000:00:06.0 -> ../../../devices/pci0000:00/0000:00:06.0
|-- 0000:00:07.0 -> ../../../devices/pci0000:00/0000:00:07.0
|-- 0000:00:09.0 -> ../../../devices/pci0000:00/0000:00:09.0
|-- 0000:00:09.1 -> ../../../devices/pci0000:00/0000:00:09.1
|-- 0000:00:09.2 -> ../../../devices/pci0000:00/0000:00:09.2
|-- 0000:00:0c.0 -> ../../../devices/pci0000:00/0000:00:0c.0
|-- 0000:00:0f.0 -> ../../../devices/pci0000:00/0000:00:0f.0
|-- 0000:00:10.0 -> ../../../devices/pci0000:00/0000:00:10.0
|-- 0000:00:12.0 -> ../../../devices/pci0000:00/0000:00:12.0
|-- 0000:00:13.0 -> ../../../devices/pci0000:00/0000:00:13.0
`-- 0000:00:14.0 -> ../../../devices/pci0000:00/0000:00:14.0
</pre>
<p>所有的 3 个设备列表都以相同顺序排列, 因为 lspci 使用 /proc 文件作为它的信息源. 拿 VGA 视频控制器作一个例子, 0x00a 意思是 0000:00:14.0 当划分为域(16位), 总线(8位), 设备(5位)和功能(3位).</p>
<p>每个外设板的硬件电路回应查询, 固定在 3 个地址空间: 内存位置, I/O 端口, 和配置寄存器. 前 2 个地址空间由所有在同一个 PCI 总线上的设备共享(即, 当你存取一个内存位置, 在那个 PCI 总线上的所有的设备在同一时间都看到总线周期). 配置空间, 另外的, 采用地理式寻址. 配置只一次一个插槽地查询地址, 因此它们从不冲突.</p>
<p>至于驱动, 内存和 I/O 区用通常的方法, 通过 inb, readb, 等等来存取. 另一方面, 配置传输通过调用特殊的内核函数来存取配置寄存器. 考虑到中断, 每个 PCI 插槽有 4 个中断脚, 并且每个设备功能可以使用它们中的一个, 不必关心这些引脚如何连入 CPU. 这样的连接是计算机平台的责任并且是在 PCI 总线之外实现的. 因为 PCI 规范要求中断线是可共享的, 即便一个处理器有有限的 IRQ 线数, 例如 x86, 可以驻有许多 PCI 接口板( 每个有 4 个中断脚).</p>
<p>PCI 总线的 I/O 空间使用一个 32-位地址总线( 产生了 4 GB 的 I/O 端口), 而内存空间可使用 32-位或者 64-位地址存取. 64-位地址在大部分近期的平台上可用. 假设地址对每个设备是唯一的, 但是软件可能错误地配置 2 个设备到同样的地址, 使得不可能存取任何一个. 但是这个问题不会产生, 除非一个驱动想玩弄不应当触动的寄存器. 好消息是每个由接口板提供的内存和 I/O 地址区可被重新映射, 通过配置交易. 那是, 在系统启动时固件初始化 PCI 硬件, 映射每个区到不同地址来避免冲突.<sup>[<a name="id469764" href="#ftn.id469764">41</a>]</sup>这些区当前被映射到的地址可从配置空间读出, 因此 Linux 驱动可存取它的设备而不用探测. 在读取了配置寄存器后, 驱动可安全地存取它的硬件.</p>
<p>PCI 配置空间为每个设备包含 256 字节(除了 PCI Express 设备, , 它每个功能有 4 KB 地配置空间), 并且配置寄存器的排布是标准化的. 配置空间的 4 个字节含有一个唯一的功能 ID, 因此驱动可标识它的设备, 通过查找那个设备的特定的 ID.<sup>[<a name="id469852" href="#ftn.id469852">42</a>]</sup> 总之, 每个设备板被地理式寻址来获取它的配置寄存器; 这些寄存器中的信息可接着被用来进行正常的 I/O 存取, 不必进一步的地理式寻址. </p>
<p>从这个描述应当清楚, PCI 接口标准对比 ISA 主要的创新是配置地址空间. 因此, 除了通常的驱动代码, 一个 PCI 驱动需要存取配置空间的能力, 为了从冒险的探测任务中解放自己.</p>
<p>本章的剩余部分, 我们使用词语设备来指一个设备功能, 因为在多功能板的每个功能如同一个独立的实体. 当我们引用一个设备, 我们的意思是"域号, 总线号, 设备号, 和功能号"的组合.</p>
</div>
<div class="sect2" lang="zh-cn">
<div class="titlepage"><div><div><h3 class="title">
<a name="BootTime.sect2"></a>12.1.2. 启动时间</h3></div></div></div>
<p>为见到 PCI 如何工作的, 我们从系统启动开始, 因为那是设备被配置的时候.</p>
<p>当一个PCI设备上电时, 硬件保持非激活. 换句话说, 设备只响应配置交易. 在上电时, 设备没有内存并且没有 I/O 端口被映射在计算机的地址空间; 每个其他的设备特定的特性, 例如中断报告, 也被关闭.</p>
<p>幸运的是, 每个 PCI 主板都装配有识别 PCI 固件, 称为 BIOS, NVRAM, 或者 PROM, 依赖平台. 这个固件提供对设备配置地址空间的存取, 通过读和写 PCI 控制器中的寄存器.</p>
<p>在系统启动时, 固件(或者 Linux 内核, 如果配置成这样)和每个 PCI 外设进行配置交易, 为了分配一个安全的位置给每个它提供的地址区. 在驱动存取设备的时候, 它的内存和I/O区已经被映射到处理器的地址空间. 驱动可改变这个缺省的分配, 但是它从不需要这样做.</p>
<p>如同被建议的一样, 用户可查看 PCI 设备列表和设备的配置寄存器, 通过读 /proc/bus/pcidevices 和 /proc/bus/pci/*/*. 前者是一个带有(16进制)设备信息的文本文件, 并且后者是二进制文件来报告每个设备的每个配置寄存器的一个快照, 每个设备一个文件. 在 sysfs 目录树中的单个的 PCI 设备目录可在 /sys/bus/pci/devices 中找到. 一个 PCI 设备目录包含许多不同的文件:</p>
<pre class="screen">
$ tree /sys/bus/pci/devices/0000:00:10.0
/sys/bus/pci/devices/0000:00:10.0
|-- class
|-- config
|-- detach_state
|-- device
|-- irq
|-- power
| `-- state
|-- resource
|-- subsystem_device
|-- subsystem_vendor
`-- vendor
</pre>
<p>文件 config 是一个二进制文件, 它允许原始的 PCI 配置信息从设备中读出(就象由 /proc/bus/pci/*/* 提供的一样). 文件 verndor, subsytem_device, subsystem_vernder, 和 class 都指的是这个 PCI 设备的特定值( 所有的 PCI 设备都提供这个信息). 文件 irq 显示分配给这个 PCI 设备的当前的 IRQ, 并且文件 resource 显示这个设备分配的当前内存资源.</p>
</div>
<div class="sect2" lang="zh-cn">
<div class="titlepage"><div><div><h3 class="title">
<a name="ConfigurationRegistersandInitialization.sect2"></a>12.1.3. 配置寄存器和初始化</h3></div></div></div>
<p>本节, 我们看 PCI 设备包含的配置寄存器. 所有的 PCI 设备都有至少一个 256-字节 地址空间. 前 64 字节是标准的, 而剩下的是依赖设备的. 图 <a href="ch12.html#ldd3-12-2.fig" title="图 12.2. 标准 PCI 配置寄存器">标准 PCI 配置寄存器</a>显示了设备独立的配置空间的排布.</p>
<div class="figure">
<a name="ldd3-12-2.fig"></a><p class="title"><b>图 12.2. 标准 PCI 配置寄存器</b></p>
<div><img src="images/ldd3-12-2.png" alt="标准 PCI 配置寄存器"></div>
</div>
<p>如图所示, 一些 PCI 配置寄存器是要求的, 一些是可选的. 每个 PCI 设备必须包含有意义的值在被要求的寄存器中, 而可选寄存器的内容依赖外设的实际功能. 可选的字段不被使用, 除非被要求的字段的内容指出它们是有效的. 因此, 被要求的字段声称板的功能, 包括其他的字段是否可用.</p>
<p>注意 PCI 寄存器一直是小端模式. 尽管标准被设计为独立于体系, PCI 设计者有时露出一些倾向 PC 环境. 驱动编写者应当小心处理字节序, 当存取多字节配置寄存器时; 在 PC 上使用的代码可能在其他平台上不工作. Linux 开发者已经处理了这个字节序问题(见下一节, "存取配置空间"), 但是这个问题必须记住. 如果你曾需要转换数据从主机序到 PCI 序, 或者反之, 你可求助在 <asm/byteorder.h> 中定义的函数, 在第 11 章介绍, 知道 PCI 字节序是小端.</p>
<p>描述所有的配置项超出了本书的范围. 常常地, 随每个设备发布的技术文档描述被支持的寄存器. 我们感兴趣的是一个驱动如何能知道它的设备以及它如何能存取设备的配置空间.</p>
<p>3 个或者 4 个 PCI 寄存器标识一个设备: verdorID, deviceID, 和 class 是 3 个常常用到的. 每个 PCI 制造商分配正确的值给这些只读寄存器, 并且驱动可使用它们来查找设备. 另外, 字段 subsystem verdorID 和 subsystem deviceID 有时被供应商设置来进一步区分类似的设备.</p>
<p>我们看这些寄存器的细节:</p>
<div class="variablelist"><dl>
<dt><span class="term"><span>vendorID </span></span></dt>
<dd><p>这个 16-位 寄存器标识一个硬件制造商. 例如, 每个 Intel 设备都标有相同的供应商号, 0x8086. 这样的号有一个全球的注册, 由 PCI 特别利益体所维护, 并且供应商必须申请有一个唯一的分配给它们的号.</p></dd>
<dt><span class="term"><span>deviceID </span></span></dt>
<dd><p>这是另一个 16-位 寄存器, 由供应商选择; 对于这个设备 ID 没有要求官方的注册. 这个 ID 常常和 供应商 ID 成对出现来组成一个唯一的 32-位 标识符给一个硬件设备. 我们使用词语"签名"来指代供应商和设备 ID 对. 一个设备驱动常常依靠签名来标识它的设备; 你可在硬件手册中找到对于目标设备要寻找的值.</p></dd>
<dt><span class="term"><span>class </span></span></dt>
<dd><p>每个外设都属于一个类. 类寄存器是一个 16-位 值, 它的高 8 位标识"基类"(或者群). 例如, "ethernet"和"token ring"是 2 个类都属于"network"群, 而"serial"和"parallel"属于"communication"群. 一些驱动可支持几个类似的设备, 每个都有一个不同的签名但是都属于同样的类; 这些驱动可依赖类寄存器标识它们的外设, 就象后面所示.</p></dd>
<dt><span class="term"><span>subsystem vendorID</span></span></dt>
<dd></dd>
<dt><span class="term"><span>subsystem deviceID </span></span></dt>
<dd><p>这些字段可用来进一步标识一个设备. 如果芯片对于本地总线是一个通用接口芯片, 它常常被用在几个完全不同的地方, 并且驱动必须标识出它在与之通话的实际设备. 子系统标志用作此目的.</p></dd>
</dl></div>
<p>使用这些不同的标识符, 一个 PCI 驱动可告知内核它支持什么类型的设备. struct pci_device_id 结构被用来定义一个驱动支持的不同类型 PCI 设备的列表. 这个结构包含不同的成员:</p>
<div class="variablelist"><dl>
<dt><span class="term"><span>__u32 vendor;</span></span></dt>
<dd></dd>
<dt><span class="term"><span>__u32 device;</span></span></dt>
<dd><p>这些指定一个设备的 PCI 供应商和设备 ID. 如果驱动可处理任何供应商或者设备 ID, 值 PCI_ANY_ID 应当用作这些成员上.</p></dd>
<dt><span class="term"><span>__u32 subvendor;</span></span></dt>
<dd></dd>
<dt><span class="term"><span>__u32 subdevice;</span></span></dt>
<dd><p>这些指定一个设备的 PCI 子系统供应商和子系统设备 ID. 如果驱动可处理任何类型的子系统 ID, 值 PCI_ANY_ID 应当用作这些成员上.</p></dd>
<dt><span class="term"><span>__u32 class;</span></span></dt>
<dd></dd>
<dt><span class="term"><span>__u32 class_mask;</span></span></dt>
<dd><p>这 2 个值允许驱动来指定它支持一类 PCI 类设备. 不同的 PCI 设备类( 一个 VAG 控制器是一个例子 )在 PCI 规范里被描述. 如果一个驱动可处理任何子系统 ID, 值 PCI_ANY_ID 应当用作这些字段.</p></dd>
<dt><span class="term"><span>kernel_ulong_t driver_data;</span></span></dt>
<dd><p>这个值不用来匹配一个设备, 但是用来持有信息, PCI 驱动可用来区分不同的设备, 如果它想这样.</p></dd>
</dl></div>
<p>有 2 个帮助宏定义应当被用来初始化一个 struct pci_device_id 结构:</p>
<div class="variablelist"><dl>
<dt><span class="term"><span>PCI_DEVICE(vendor, device)</span></span></dt>
<dd><p>这个创建一个 struct pci_device_id , 它只匹配特定的供应商和设备 ID. 这个宏设置这个结构的子供应商和子设备成员为 PCI_ANY_ID.</p></dd>
<dt><span class="term"><span>PCI_DEVICE_CLASS(device_class, device_class_mask)</span></span></dt>
<dd><p>这个创建一个 struct pci_device_id, 它匹配一个特定的 PCI 类.</p></dd>
</dl></div>
<p>一个使用这些宏来定义一个驱动支持的设备类型的例子, 在下面的内核文件中可找到:</p>
<pre class="programlisting">
drivers/usb/host/ehci-hcd.c:
static const struct pci_device_id pci_ids[] = { {
/* handle any USB 2.0 EHCI controller */
PCI_DEVICE_CLASS(((PCI_CLASS_SERIAL_USB << 8) | 0x20), ~0),
.driver_data = (unsigned long) &ehci_driver,
},
{ /* end: all zeroes */ }
};
drivers/i2c/busses/i2c-i810.c:
static struct pci_device_id i810_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810_IG1) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810_IG3) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810E_IG) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82815_CGC) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82845G_IG) },
{ 0, },
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -