PCI(Peripheral Component Interconnect)外围设备互联的简称,是在桌面及更大型的计算机上普遍使用的外设总线。(驱动程序移植)
PCI总线具有三个非常显著的优点:
1、在计算机和外设间传输数据时具有更好的性能
2、能够尽量独立于具体的平台
3、可以方便地实现即插即用
体系结构:
从结构上,PCI总线是一种不依附于某个具体处理器的局部总线,它是在CPU和原来的系统总线之间的一级总线,具体由一个桥接电路实现对这一层的管理,并实现上下之间的接口以协调数据的传送。
系统的各个部分通过PCI总线和PCI-PCI桥连接在一起。CPU和RAM通过PCI桥连接到PCI总线0(即主PCI总线),而具有PCI接口的显卡直接连接到主PCI总线上。PCI-PCI桥是一个特殊的PCI设备,它负责将PCI总线0和PCI总线1连接在一起。图中连接到PCI1号总线上的是SCSI卡和以太网卡。为了兼容旧的ISA总线标准,PCI总线还可以通过PCI-ISA桥来连接ISA总线,从而支持以前的ISA设备,图中ISA总线上连接一个多功能I/O控制器,用于控制键盘、鼠标和软驱等。
PCI设备寻址:(深度优先遍历)
每个PCI设备由一个总线号、一个设备号和一个功能号确定。PCI规范允许一个系统最多拥有256条总线,每条总线最多带32个设备,但每个设备可以是最多8个功能的多功能板(如一个音频设备带一个CD-ROM驱动器)
/proc/iomem描述了系统中所有的设备I/O在内存地址空间上的映射。
dc400000-dc40ffff : 0000:02:01.0
dc400000-dc40ffff是它所映射的内存空间地址
0000:02:01.0是PCI外设的地址,它以冒号和逗号分隔开为4个部分(域16位+总线编号8位+设备号5位+功能号3位):0000表示域,02表示一个总线号,01表示设备号,0表示功能号。由此描述为2号总线上的1号设备上的0号功能
因为PCI规范允许单个系统拥有最多256条总线,所以总线编号是8位。每个总线上可支持32个设备,所以设备号是5位,而每个设备上最多可有8种功能,所以功能号是3位。
使用lspci -xx命令查看系统中的PCI设备。
启动时间:
为见到 PCI 如何工作的, 我们从系统启动开始, 因为那是设备被配置的时候.
当一个PCI设备上电时, 硬件保持非激活. 换句话说, 设备只响应配置交易. 在上电时, 设备没有内存并且没有 I/O 端口被映射在计算机的地址空间; 每个其他的设备特定的特性, 例如中断报告, 也被关闭.
幸运的是, 每个 PCI 主板都装配有识别 PCI 固件, 称为 BIOS, NVRAM, 或者 PROM, 依赖平台. 这个固件提供对设备配置地址空间的存取, 通过读和写 PCI 控制器中的寄存器.
在系统启动时, 固件(或者 Linux 内核, 如果配置成这样)和每个 PCI 外设进行配置交易, 为了分配一个安全的位置给每个它提供的地址区. 在驱动存取设备的时候, 它的内存和I/O区已经被映射到处理器的地址空间. 驱动可改变这个缺省的分配, 但是它从不需要这样做.
如同被建议的一样, 用户可查看 PCI 设备列表和设备的配置寄存器, 通过读 /proc/bus/pcidevices 和 /proc/bus/pci/*/*. 前者是一个带有(16进制)设备信息的文本文件, 并且后者是二进制文件来报告每个设备的每个配置寄存器的一个快照, 每个设备一个文件. 在 sysfs 目录树中的单个的 PCI 设备目录可在 /sys/bus/pci/devices 中找到. 一个 PCI 设备目录包含许多不同的文件:
$ 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
文件 config 是一个二进制文件, 它允许原始的 PCI 配置信息从设备中读出(就象由 /proc/bus/pci/*/* 提供的一样). 文件 verndor, subsytem_device, subsystem_vernder, 和 class 都指的是这个 PCI 设备的特定值( 所有的 PCI 设备都提供这个信息). 文件 irq 显示分配给这个 PCI 设备的当前的 IRQ, 并且文件 resource 显示这个设备分配的当前内存资源.
配置寄存器:
每个PCI设备都有一组固定格式的寄存器,即配置寄存器,配置寄存器由Linux内核中的PCI初始化代码与驱动程序共同使用。内核启动时负责对配置寄存器进行初始化,包括设置中断号以及I/O基址等。
配置空间:
00H-01H Vendor ID 制造商标识
02H-03H Device ID 设备标识
04H-05H command 命令寄存器
06H-07H status 状态寄存器
08H Revision ID ID版本识别号寄存器
09H-0bH Class Code 分类代码寄存器
0cH Cache Line Size CACHE 行长度寄存器
0dH Latency Timer 主设备延迟时间寄存器
0eH header Type 头标类型寄存器
0fH Built-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 DeviceID 子设备标识
30H-33H Expasion ROM BaseAddress 扩展ROM基地址
34H-3bH 保留
3cH Interrupt Line 中断线寄存器
3dH Interrupt Pin 中断引脚寄存器
3eH Min_Gnt 最小授权寄存器
2fH Max_Lat 最大延迟寄存器
厂商标识(Vendor id)
用来标识PCI设备生产厂家的数值。Intel的厂商标识为0x8086,全球厂商标识由PCISpecial Interest Group来分配
设备标识(Device id)
用来标识设备的数值。Digital 21141快速以太设备的设备标识为0x0009
基地址寄存器(Base AddressRegister)记录此设备使用的I/O与内存空间的位置
中断连线(Interrupt Line)
记录此设备使用的中断号
中断引脚(Interrupt Pin) //查看该设备是否支持中断?
记录此PCI设备使用的引脚号(A、B、C、D)
Ø PCI 驱动程序设计
1、在Linux内核中,PCI驱动使用structpci_driver结构来描述:
structpci_driver{
……………….
const structpci_device_id *id_table;
int(*probe)(structpci_dev *dev, const struct pci_device_id *id)
void (*remove)(structpci_dev *dev);
/*Device removed (NULL ifnot a hot-plug capable driver)*/
…………………
}
2、注册PCI驱动,使用如下函数:
pci_register_driver(structpci_driver *drv)
3、使能设备:
在PCI驱动使用PCI设备的任何资源(I/O区或者中断)之前,驱动必须调用如下函数来使能设备:
int pci_enable_device(structpci_dev *dev)
4、存取I/O和内存空间
一个 PCI 设备实现直至 6 个 I/O 地址区. 每个区由要么内存要么 I/O 区组成. 大部分设备实现它们的 I/O 寄存器在内存区中, 因为通常它是一个完善的方法(如同在" I/O 端口和 I/O 内存"一节中解释的, 在第 9 章). 但是, 不像正常的内存, I/O 寄存器不应当被 CPU 缓存, 因为每次存取都可能有边际效果. 作为内存区来实现 I/O 寄存器的 PCI 设备, 通过设置一个在它的配置寄存器的"内存可预取"位来标志出这个不同.如果这个内存区被标识为可预取的, CPU 可缓存它的内容并且对它做所有类型的优化. 非可预取的内存存取, 另一方面, 不能被优化因为每次存取可能有边际效果, 就象 I/O 端口. 映射它们的寄存器到一个内存地址范围的外设声明这个范围是非可预取的, 而象在 PCI 板的视频内存的一些是可预取的. 在本节, 我们使用词语"区"来指代一个通用的 I/O 地址空间, 这个空间要么是内存映射的, 要么是端口映射的.
一个接口板报告它的区的大小和当前位置, 使用配置寄存器- 6 个 32 位寄存器, 在图12-2中显示的, 它们的符号名是 PCI_ADDRESS_0 到 PCI_BASE_ADDRESS_5. 因为 PCI 定义的 I/O 空间是 32-位空间, 使用同样的配置接口给内存和 I/O是有意义的. 如果设备使用 64-位地址总线, 它可以在 64-位内存空间声明各个区, 使用 2 个连续的 PCI_BASE_ADDRESS 寄存器给每个区, 低位在前. 对一个设备可能提供 32-位 和 64-位区.
内核中, PCI 设备的 I/O 区已被集成到通用的资源管理中. 由于这个原因, 你不必存取配置变量来知道你的设备映射到内存或者 I/O 空间什么地方. 首选的用来获得区信息的接口包括下列函数:
-
unsigned long pci_resource_start(struct pci_dev *dev, int bar);
-
这个函数返回第一个地址(内存地址或者 I/O 端口号), 和 6 个 PCI I/O 区中的一个相关联的. 这个区通过整数 bar (the base address register), 范围从 0-5 (包含).
unsigned long pci_resource_end(struct pci_dev *dev, int bar);
-
这个函数返回最后一个地址, I/O 区号 bar 的一部分. 注意这是最后一个可用地址, 不是这个区后的第一个地址.
unsigned long pci_resource_flags(struct pci_dev *dev, int bar);
-
这个函数返回和这个资源相关联的标识.
资源标识用来定义单个资源的一些特性. 对于和 PCI I/O 区相关联的 PCI资源, 这个信息从基地址寄存器中抽取出来, 但是可来自其他地方, 对于没有和 PCI 设备关联的资源.
所有的资源标志都定义在 <linux/ioport.h>; 最重要的是:
-
IORESOURCE_IO
IORESOURCE_MEM
-
如果被关联的 I/O 区存在, 一个并且只有一个这样的标志被设置.
IORESOURCE_PREFETCH
IORESOURCE_READONLY
-
这些标志告诉是否一个内存区是可预取的并且/或者写保护的. 后一个标志对 PCI 资源从不设置.
通过使用 pci_resource_ 函数, 一个设备驱动可完全忽略底层的 PCI 寄存器, 因为系统已经使用它们来构造资源信息
762 <4>[ 1.514397] sdhci_pci_probe_slot:pci_resource_start:ffa50000.
763 <4>[ 1.514537] sdhci_pci_probe_slot:pci_resource_end:ffa500ff.
764 <4>[ 1.514759] sdhci_pci_probe_slot:pci_resource_len:100.
765 <4>[ 1.514883] sdhci_pci_probe_slot:pci_resource_flags:40210.
766 <4>[ 1.515121] sdhci_pci_probe_slot:the irq is 27.
767 <4>[ 1.515376] sdhci_pci_probe_slot: host->ioaddr:f526e000.
5、中断:
中断号存放于配置寄存器PCI_INTERRUPT_LINE中,驱动不必去检查它,因为PCI_INTERRUPT_LINE中找到的值保证是正确的。如果设备不支持中断,寄存器PCI_INTERRUPT_PIN中的值是0,否则它是非0的值。但因为驱动开发者通常知道设备是否是支持中断,所以常常不需要访问PCI_INTERRUPT_PIN.
6. 存取配置空间
在驱动已探测到设备后, 它常常需要读或写 3 个地址空间: 内存, 端口, 和配置. 特别地, 存取配置空间对驱动是至关重要的, 因为这是唯一的找到设备被映射到内存和 I/O 空间的位置的方法.
因为微处理器无法直接存取配置空间, 计算机供应商不得不提供一个方法来完成它. 为存取配置空间, CPU 必须写和读 PCI 控制器中的寄存器, 但是确切的实现是依赖于供应商的, 并且和这个讨论无关, 因为 Linux提供了一个标准接口来存取配置空间.
对于驱动, 配置空间可通过8-位, 16-位, 或者 32-位数据传输来存取. 相关的函数原型定义于 <linux/pci.h>:
-
int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val);
int pci_read_config_word(struct pci_dev *dev, int where, u16 *val);
int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val);
int pci_write_config_byte(struct pci_dev *dev, int where, u8 val);
int pci_write_config_word(struct pci_dev *dev, int where, u16 val);
int pci_write_config_dword(struct pci_dev *dev, int where, u32 val);
MODULEDEVICETABLE 宏
这个 pci_device_id 结构需要被输出到用户空间, 来允许热插拔和模块加载系统知道什么模块使用什么硬件设备. 宏 MODULE_DEVICE_TABLE 完成这个. 例如:
MODULE_DEVICE_TABLE(pci, i810_ids);
这个语句创建一个局部变量称为 __mod_pci_device_table, 它指向 struct pci_device_id 的列表. 稍后在内核建立过程中, depmod 程序在所有的模块中寻找 __mod_pci_device_table. 如果找到这个符号, 它将数据拉出模块并且添加到文件 /lib/modules/KERNEL_VERSION/modules.pcimap. 在 depmod 完成后, 所有的被内核中的模块支持的 PCI 设备被列出, 带有它们的模块名子, 在那个文件中. 当内核告知热插拔系统有新的 PCI 设备已找到, 热插拔系统使用 moudles.pcimap 文件来找到正确的驱动来加载.