外设互联标准(或称个人电脑接口,Personal Computer Interface),实际应用中简称PCI(Peripheral Component Interconnect),是一种连接电子计算机主板和外部设备的总线标准。一般PCI设备可分为两种形式:直接布放在主板上的集成电路,在PCI规范中称作”平面设备“,另一种是安装在插槽中的扩展卡。
PCI bus常见于现代的个人计算机中,已经取代ISA和VESA局部总线,成为标准扩展总线。PCI总线最终将被PCI Express或者更先进的技术取代。
PCI是并行基于总线控制,所有设备共同分享单向32bit/64bit并行总线(半双工)。如果有多个PCI设备共用总线,他们将共享总的传输速率。
PCI标准于1993年7月被Intel发明,每个接口最多连接1个设备,可以工作在33MHz和66MHz(工作时电压33MHz为5V,66MHz为3V),2004年被PCI Express替代。
PCI插槽可以插很多类型的卡,包括网卡、声卡、调制解调器(内置Modem)、电视卡、磁盘控制器(RAID卡)、视频采集卡、IDE接口卡、IEEE1394卡、USB卡和串行等,原本也可以插显卡,但很快PCI的带宽不足以支持显卡的性能。PCI插槽通过插不同的卡几乎可以实现所有的外接功能。后来显卡使用AGP插槽,现在已被PCI Express插槽取代。
PCI接口分32bit和64bit两种。早期的PCI(PCI2.1标准)工作在32bit、33.33MHz、5V下,最大传输速度133MB/s(33.33MHz * 32bit / 8bit/byte = 133MB/s),后来又出现了PCI2.2 2.3等标准。现在PCI有32bit和64bit两种,32bit的一般用在PC上,64bit的一般应用于服务器上,64bit的要比32bit的长一些。32bit和64bit都有5v和3.3v电压两种,5v电压的是PCI2.1标准工作在33MHz,3.3v电压的是PCI2.2标准工作在66MHz的时钟频率上。频率或者位宽增加都会增加传输速率,实现也是通过这两个指标来实现的。
在PC上,64位PCI还没有成为主流。原因在于制造64位和66MHzPCI主板的难度很大。首先,使用64位PCI插槽需要64位南桥芯片组支持,该南桥控制器必须可以正确处理64位的数据。Intel和AMD都有64位的南桥可提供给主板厂商,但是价格很高;其次是因为66MzPCI槽对主板配套元件要求极高,且需要特殊的布线设计。这就是66MHzPCI技术一直停留在服务器领域的原因。
PCI有几种不同的接口样图:现在生产的多为通用模式的以防插错。还有64bit统一比32bit的宽出右边缺口的部分。
PCI 32bit的网卡都可以查到PCI 64bit插槽上使用。3.3v的插到3.3v的上,5v的插到5v的上。
有一些PCI网卡同时支持32位和64位标准的兼容网卡,这类网卡相比前面介绍的纯64位PCI网卡来说,在外观上也有一个明显的区别,那就是它又多了一个缺口,有3个缺口(下图右边第三个图)。
PCI-X是传统PCI总线的改版,有更高的带宽。PCI-X插槽类型基本于64bit的PCI插槽相同。
PCI-X于1998年被IBM、HP和Compaq发明,64bit位宽,传输方式并发,2004年被新出的PCI Express替代。PCI-X多用于服务器上,不过也是昙花一现。
PCI Express是INTEL提出的新一代的总线接口,PCI Express采用了目前业内流行的点对点串行连接,比起PCI以及更早期的计算机总线的共享并行架构,每个设备都有自己的专用连接,不需要向整个总线请求带宽,而且可以把数据传输率提高到一个很高的频率,达到PCI所不能提供的高带宽。相对于传统PCI总线在单一时间周期内只能实现单向传输,PCI Express的双单工连接能提供更高的传输速率和质量。PCI-E插槽是可以向下兼容的,比如PCI-E 16X插槽可以插8X、4X、1X的卡。现在的服务器一般都会提供多个8X、4X的接口,已取代以前的PCI-X接口。
PCI是总线结构,而PCIe是点对点结构。一个典型的PCIe系统框图如下:
一个典型的结构是一个root port
和一个endpoint
直接组成一个点对点连接对,而Switch
可以同时连接几个endpoint
。一个root port
和一个endpoint
对就需要一个单独的PCI bus
。而PCI是在同一个总线上的设备共享同一个bus number
。过去主板上的PCI插槽都公用一个PCI bus
,而现在的PCIe插槽却连在芯片组不同的root port
上。
Linux PCI设备驱动实际包括Linux PCI设备驱动和设备本身驱动两部分。PCI(Periheral Component Interconnect)有三种地址空间:PCI I/O空间、PCI内存地址空间和PCI配置空间。其中,PCI I/O空间和PCI内存地址空间由设备驱动程序使用,而PCI配置空间由Linux PCI初始化代码使用,用于配置PCI设备,比如中断号以及I/O或内存基地址。
Linux内核主要就做了对PCI设备的枚举和配置;在Linux内核初始化时完成的。
对于PCI总线,有一个叫做PCI桥的设备用来将父总线与子总线连接。作为一种特殊的PCI设备,PCI桥主要包括以下三种:
从Host/PCI桥开始进行探测和扫描,逐个“枚举”连接在第一条PCI总线上的所有设备并记录在案。如果其中的某个设备是PCI-PCI桥,则又进一步再探测和扫描连在这个桥上的次级PCI总线。就这样递归下去,直到穷尽系统中的所有PCI设备。其结果,是在内存中建立起一棵代表着这些PCI总线和设备的PCI树。
每个PCI设备(包括PCI桥设备)都由一个pci_dev结构体来表示,而每条PCI总线则由pci_bus结构来表示。
PCI设备中一般都带有一些RAM和ROM 空间,通常的控制/状态寄存器和数据寄存器也往往以RAM区间的形式出现,而这些区间的地址在设备内部一般都是从0开始编址的,那么当总线上挂接了多个设备时,对这些空间的访问就会产生冲突。所以,这些地址都要先映射到系统总线上,再进一步映射到内核的虚拟地址空间。
配置就是通过对PCI配置空间的寄存器进行操作从而完成地址的映射。
pci这块代码在两个地方,一个是driver/pci
另一个是arch/×××/pci
中。
pci系统中入口函数处理subsys_initcall
之外,还有arch_initcall
,postcore_initcall
等等。
pci系统的初始化工作有内核来完成,在drivers/pci/probe.c
文件中,调用postcore_initcall(pcibus_class_init)
;函数,在sys/class/
下创建一个pci_bus
目录。
drivers/pci/pci-driver.c
文件,postcore_initcall(pci_driver_init)
; 注册pci总线,并在/sys/bus/
下创建了一个pci
目录
arch/xxx/pci/init.c
文件中
arch_initcall(pci_arch_init)
;体系架构相关,对于64 bit x86来说使用CONFIG_PCI_DIRECT
的方式进行访问PCI配置空间。在内核编译时候可以指定。
drivers/pci/slot.c
文件中,subsys_initcall(pci_slot_init)
; 创建/sys/bus/slots
文件。
1)struct pci_host_bridge
主桥数据结构,用来描述连接CPU和PCIE设备的主桥,该结构有Root bus0
成员,它也是一个设备,需要注册。
2)struct pci_dev
该结构体用来描述PCI设备,包括EP和pci to pci 桥
等设备.
Bus
:用来关联该设备在哪条bus下,
device
,version
: 枚举出来的设备配置空间的重要东西都会保存该数据结构。
3)struct pci_bus
该结构体用来描述PCI的总线。
struct list_head devices
: 设备链表用于关联该bus下的所有设备。
children
: PCI桥可以使当前总线得到扩展,当前总线上有几个PCI桥,那么当前总线就会拥有几个子总线,子总线会连接到父总线的children链表中。
ops
: 当前总线访问总线上设备配置空间的 read
、write
方法。
4)struct pci_slot
用来描述bus下的物理插槽
5)struct pci_bus_type
这个是pci
的总线模型,和之前的pci_bus
是两码事, pci_driver
和pci_dev
就是通过该总线关联起来.
match
: 通过pci_dev
对应的字段 vendor
、subvendor
、device
、subdevice
来匹 配对应的pci_driver
所以大致的流程是内核启动之后,通过PCI_BUS
之间的关系枚举出来所有设备,并为每一个设备创建一个PCI_DEV
的数据结构,将配置空间的信息填入PCI_DEV
之后,注册到总线PCI_BUS_TYPE.
然后我们编写的pci_driver
中,填写了此驱动适配的设备号版本号等,在一个pci_device_id
的数据结构中,同样也注册到pci_bus_type
的总线中,如果信息一致则执行驱动probe
函数。
在 Linux 系统中,PCI 总线用 pci_bus
来描述,这个结构体记录了本 PCI 总线的信息以及本PCI 总线的父总线、子总线、桥设备信息等。
./include/linux/pci.h
struct pci_bus {
/* 链表元素node:对于PCI根总线而言,其pci_bus结构通过node成员链接到本节一开始所述的根总线链表中,根总线链表
的表头由一个list_head类型的全局变量pci_root_buses所描述。而对于非根pci总线,其pci_bus结构通过node成员链接
到其父总线的子总线链表children中*/
struct list_head node;
/*parent指针:指向该pci总线的父总线,即pci桥所在的那条总线*/
struct pci_bus *parent;
/*children指针:描述了这条PCI总线的子总线链表的表头。这条PCI总线的所有子总线都通过上述的node链表元素链接成一
条子总线链表,而该链表的表头就由父总线的children指针所描述*/
struct list_head children;
/* list of devices on this bus */
struct list_head devices;
/* devices链表头:描述了这条PCI总线的逻辑设备链表的表头。除了链接在全局PCI设备链表中之外,每一个PCI逻辑设备也
通过其 pci_dev结构中的bus_list成员链入其所在PCI总线的局部设备链表中,而这个局部的总线设备链表的表头就由
pci_bus结构中的 devices成员所描述*/
struct pci_dev *self;
/* 资源指针数组:指向应路由到这条pci总线的地址空间资源,通常是指向对应桥设备的pci_dev结构中的资源数组resource[10:7]*/
struct resource *resource[PCI_BUS_NUM_RESOURCES];
/* 指针ops:指向一个pci_ops结构,表示这条pci总线所使用的配置空间访问函数*/
struct pci_ops *ops;
/* 无类型指针sysdata:指向系统特定的扩展数据*/
void *sysdata;
/* 指针procdir:指向该PCI总线在/proc文件系统中对应的目录项*/
struct proc_dir_entry *procdir;
/* number:这条PCI总线的总线编号(bus number),取值范围0-255 */
unsigned char number;
/* primary:表示引出这条PCI总线的“桥设备的主总线”(也即桥设备所在的PCI总线)编号,取值范围0-255*/
unsigned char primary;/* secondary:表示引出这条PCI总线的桥设备的次总线号,因此secondary成员总是等于number成员的值。取值范围0-255*/
unsigned char secondary;
/* subordinate:这条PCI总线的下属PCI总线(Subordinate pci bus)的总线编号最大值,它应该等于引出这条PCI总线的桥设备的subordinate值*/
unsigned char subordinate;
/* name[48]:这条PCI总线的名字字符串*/
char name[48];
unsigned short bridge_ctl;
unsigned short pad2;
struct device *bridge;
struct device class_dev;
struct bin_attribute *legacy_io;
struct bin_attribute *legacy_mem;
};
假定一个如图 所示的 PCI 总线系统,根总线 0 上有一个 PCI 桥,它引出子总线 Bus 1,Bus 1 上又有一个 PCI 桥引出 Bus 2。
在上图中,Bus 0 总线的 pci_bus
结构体中的 number
、primary
、secondary
都应该为 0,因为它是通过 Host/PCI
桥引出的根总线;Bus 1 总线的 pci_bus
结构体中的 number
和 secondary
都为 1,但是它的 primary
应该为 0;Bus 2 总线的 pci_bus
结构体中的 number
和 secondary
都应该为 2,而其 primary
则应该等于 1。这 3 条总线的 subordinate
值都应该等于 2。
系统中当前存在的所有根总线都通过其 pci_bus
结构体中的 node
成员链接成一条全局的根总线链表,其表头由 list
类型的全局变量 pci_root_buses
来描述。而根总线下面的所有下级总线则都通过其 pci_bus
结构体中的 node
成员链接到其父总线的 children
链表中。这样,通过这两种 PCI总线链表,Linux 内核就将所有的 pci_bus
结构体以一种倒置树的方式组织起来。假定对于如图 所示的多根 PCI 总线体系结构:
它所对应的总线链表结构将如图所示。
Linux 系统中,所有种类的 PCI 设备都可以用 pci_dev
结构体来描述,由于一个 PCI 接口卡上可能包含多个功能模块,每个功能被当作一个独立的逻辑设备,因此,每一个 PCI 功能,即PCI 逻辑设备都惟一地对应一个 pci_dev
设备描述符:
struct pci_dev {
/* 总线设备链表元素bus_list:每一个pci_dev结构除了链接到全局设备链表中外,还会通过这个成员连接到
其所属PCI总线的设备链表中。每一条PCI总线都维护一条它自己的设备链表视图,以便描述所有连接在该
PCI总线上的设备,其表头由PCI总线的pci_bus结构中的 devices成员所描述t*/
struct list_head bus_list;
/* 总线指针bus:指向这个PCI设备所在的PCI总线的pci_bus结构。因此,对于桥设备而言,bus指针将指向
桥设备的主总线(primary bus),也即指向桥设备所在的PCI总线*/
struct pci_bus *bus; /* 这个 PCI 设备所在的 PCI 总线的 pci_bus 结构 */
/* 指针subordinate:指向这个PCI设备所桥接的下级总线。这个指针成员仅对桥设备才有意义,而对于一般
的非桥PCI设备而言,该指针成员总是为NULL*/
struct pci_bus *subordinate; /* 指向这个 PCI 设备所桥接的下级总线 */
/* 无类型指针sysdata:指向一片特定于系统的扩展数据*/
void *sysdata; /* 指向一片特定于系统的扩展数据 */
/* 指针procent:指向该PCI设备在/proc文件系统中对应的目录项*/
struct proc_dir_entry *procent; /* 该 PCI 设备在/proc/bus/pci 中对应的目录项 */
struct pci_slot *slot; /* 设备位于的物理插槽 */
/* devfn:这个PCI设备的设备功能号,也成为PCI逻辑设备号(0-255)。其中bit[7:3]是物理设备号(取值
范围0-31),bit[2:0]是功能号(取值范围0-7)。 */
unsigned int devfn; /* 这个 PCI 设备的设备功能号 */
/* vendor:这是一个16无符号整数,表示PCI设备的厂商ID*/
unsigned short vendor;
/*device:这是一个16无符号整数,表示PCI设备的设备ID */
unsigned short device;
/* subsystem_vendor:这是一个16无符号整数,表示PCI设备的子系统厂商ID*/
unsigned short subsystem_vendor;
/* subsystem_device:这是一个16无符号整数,表示PCI设备的子系统设备ID。*/
unsigned short subsystem_device;
/* class:32位的无符号整数,表示该PCI设备的类别,其中,bit[7:0]为编程接口,bit[15:8]为子类
别代码,bit [23:16]为基类别代码,bit[31:24]无意义。显然,class成员的低3字节刚好对应与PCI配
置空间中的类代码*/
unsigned int class;
/* hdr_type:8位符号整数,表示PCI配置空间头部的类型。其中,bit[7]=1表示这是一个多功能设备,
bit[7]=0表示这是一个单功能设备。Bit[6:0]则表示PCI配置空间头部的布局类型,值00h表示这是一
个一般PCI设备的配置空间头部,值01h表示这是一个PCI-to-PCI桥的配置空间头部,值02h表示CardBus桥
的配置空间头部*/
u8 hdr_type;
/* rom_base_reg:8位无符号整数,表示PCI配置空间中的ROM基地址寄存器在PCI配置空间中的位置。
ROM基地址寄存器在不同类型的PCI配置空间头部的位置是不一样的,对于type 0的配置空间布局,ROM基
地址寄存器的起始位置是30h,而对于PCI-to-PCI桥所用的type 1配置空间布局,ROM基地址寄存器的起始
位置是38h*/
u8 rom_base_reg;
/* 指针driver:指向这个PCI设备所对应的驱动程序定义的pci_driver结构。每一个pci设备驱动程序都必须定
义它自己的pci_driver结构来描述它自己。*/
struct pci_driver *driver;
/*dma_mask:用于DMA的总线地址掩码,一般来说,这个成员的值是0xffffffff。数据类型dma_addr_t定义在
include/asm/types.h中,在x86平台上,dma_addr_t类型就是u32类型*/
u64 dma_mask;
/* 当前操作状态 */
pci_power_t current_state;
/* 通用的设备接口*/
struct device dev;
/* 无符号的整数irq:表示这个PCI设备通过哪根IRQ输入线产生中断,一般为0-15之间的某个值 */
unsigned int irq;
/*表示该设备可能用到的资源,包括:I/O断口区域、设备内存地址区域以及扩展ROM地址区域。*/
struct resource resource[DEVICE_COUNT_RESOURCE];
/* 配置空间的大小 */
int cfg_size;
/* 透明 PCI 桥 */
unsigned int transparent:1;
/* 多功能设备*/
unsigned int multifunction:1;
/* 设备是主设备*/
unsigned int is_busmaster:1;
/* 设备不使用msi*/
unsigned int no_msi:1;
/* 配置空间访问形式用块的形式 */
unsigned int block_ucfg_access:1;
/* 在挂起时保存配置空间*/
u32 saved_config_space[16];
/* sysfs ROM入口的属性描述*/
struct bin_attribute *rom_attr;
/* 能显示rom 属性*/
int rom_attr_enabled;
/* 资源的sysfs文件*/
struct bin_attribute *res_attr[DEVICE_COUNT_RESOURCE];
};
PS:
同一条总线上的设备通过struct list_head bus_list组成这条总线的设备链表;
表头则是pci_bus结构体中的struct list_head devices成员。
对于PCI配置空间的讲解专门放到单独一篇文章中进行说明。
不管是哪一种类型的配置空间头部,其前 16 个字节的格式都是相同的,./include/uapi/linux/pci_regs.h
文件中定义了 PCI 配置空间头部。
/*
* Under PCI, each device has 256 bytes of configuration address space,
* of which the first 64 bytes are standardized as follows:
*/
#define PCI_STD_HEADER_SIZEOF 64
#define PCI_VENDOR_ID 0x00 /* 16 bits *//* 16 位厂商 ID */
#define PCI_DEVICE_ID 0x02 /* 16 bits *//* 16 位设备 ID */
/* PCI 命令寄存器 */
#define PCI_COMMAND 0x04 /* 16 bits */
#define PCI_COMMAND_IO 0x1 /* Enable response in I/O space *//* 使能设备响应对 I/O 空间的访问 */
#define PCI_COMMAND_MEMORY 0x2 /* Enable response in Memory space *//* 使能设备响应对存储空间的访问 */
#define PCI_COMMAND_MASTER 0x4 /* Enable bus mastering *//* 使能总线主模式 */
#define PCI_COMMAND_SPECIAL 0x8 /* Enable response to special cycles *//* 使能设备响应特殊周期 */
#define PCI_COMMAND_INVALIDATE 0x10 /* Use memory write and invalidate */ /*使用 PCI 内存写无效事务 */
#define PCI_COMMAND_VGA_PALETTE 0x20 /* Enable palette snooping *//* 使能 VGA 调色板侦测 */
#define PCI_COMMAND_PARITY 0x40 /* Enable parity checking *//* 使能奇偶校验 */
#define PCI_COMMAND_WAIT 0x80 /* Enable address/data stepping *//* 使能地址/数据步进 */
#define PCI_COMMAND_SERR 0x100 /* Enable SERR *//* 使能 SERR */
#define PCI_COMMAND_FAST_BACK 0x200 /* Enable back-to-back writes *//* 使能背靠背写 */
#define PCI_COMMAND_INTX_DISABLE 0x400 /* INTx Emulation Disable */ /* 禁止中断竞争*/
/* PCI 状态寄存器 */
#define PCI_STATUS 0x06 /* 16 bits */
#define PCI_STATUS_INTERRUPT 0x08 /* Interrupt status */
#define PCI_STATUS_CAP_LIST 0x10 /* Support Capability List *//* 支持的能力列表 */
#define PCI_STATUS_66MHZ 0x20 /* Support 66 MHz PCI 2.1 bus *//* 支持 PCI 2.1 66MHz */
#define PCI_STATUS_UDF 0x40 /* Support User Definable Features [obsolete] *//* 支持用户定义的特征 */
#define PCI_STATUS_FAST_BACK 0x80 /* Accept fast-back to back *//* 快速背靠背操作 */
#define PCI_STATUS_PARITY 0x100 /* Detected parity error *//* 侦测到奇偶校验错 */
#define PCI_STATUS_DEVSEL_MASK 0x600 /* DEVSEL timing *//* DEVSEL 定时 */
#define PCI_STATUS_DEVSEL_FAST 0x000
#define PCI_STATUS_DEVSEL_MEDIUM 0x200
#define PCI_STATUS_DEVSEL_SLOW 0x400
#define PCI_STATUS_SIG_TARGET_ABORT 0x800 /* Set on target abort */ /* 目标设备异常 */
#define PCI_STATUS_REC_TARGET_ABORT 0x1000 /* Master ack of " */ /* 主设备确认 */
#define PCI_STATUS_REC_MASTER_ABORT 0x2000 /* Set on master abort *//* 主设备异常 */
#define PCI_STATUS_SIG_SYSTEM_ERROR 0x4000 /* Set when we drive SERR */ /* 驱动了 SERR */
#define PCI_STATUS_DETECTED_PARITY 0x8000 /* Set on parity error */ /* 奇偶校验错 */
/* 类代码寄存器和修订版本寄存器 */
#define PCI_CLASS_REVISION 0x08 /* High 24 bits are class, low 8 revision *//* 高 24 位为类码,低 8 位为修订版本 */
#define PCI_REVISION_ID 0x08 /* Revision ID *//* 修订号 */
#define PCI_CLASS_PROG 0x09 /* Reg. Level Programming Interface *//* 编程接口 */
#define PCI_CLASS_DEVICE 0x0a /* Device class *//* 设备类 */
#define PCI_CACHE_LINE_SIZE 0x0c /* 8 bits */
#define PCI_LATENCY_TIMER 0x0d /* 8 bits */
/* PCI 头类型 */
#define PCI_HEADER_TYPE 0x0e /* 8 bits *//* 8 位头类型 */
#define PCI_HEADER_TYPE_NORMAL 0
#define PCI_HEADER_TYPE_BRIDGE 1
#define PCI_HEADER_TYPE_CARDBUS 2
/* 表示配置空间头部中的 Built-In Self-Test 寄存器在配置空间中的字节地址索引 */
#define PCI_BIST 0x0f /* 8 bits */
#define PCI_BIST_CODE_MASK 0x0f /* Return result *//* 完成代码 */
#define PCI_BIST_START 0x40 /* 1 to start BIST, 2 secs or less *//* 用于启动 BIST*/
#define PCI_BIST_CAPABLE 0x80 /* 1 if BIST capable *//* 设备是否支持 BIST? */
紧接着前 16 个字节的寄存器为基地址寄存器 0~基地址寄存器 5,其中,PCI_BASE_ADDRESS_2~5
仅对标准 PCI 设备的 0 类型配置空间头部有意义,而 PCI_BASE_ADDRESS_0~1
则适用于0 类型和 1 类型配置空间头部。
基地址寄存器中的 bit[0]
的值决定了这个基地址寄存器所指定的地址范围是在 I/O 空间还是在内存映射空间内进行译码。当基地址寄存器所指定的地址范围位于内存映射空间中时,其 bit[2∶1]
表示内存地址的类型,bit[3]
表示内存范围是否为可预取(Prefetchable)的内存。
1 类型配置空间头部适用于 PCI-PCI
桥设备,其基地址寄存器 0 与基地址寄存器 1 可以用来指定桥设备本身可能要用到的地址范围,而后 40 个字节(0x18
~0x39
)则被用来配置桥设备的主、次编号以及地址过滤窗口等信息。
pci_bus
结构体中的 pci_ops
类型成员指针 ops
指向该 PCI 总线所使用的配置空间访问操作的具体实现。
struct pci_ops {
int (*add_bus)(struct pci_bus *bus);
void (*remove_bus)(struct pci_bus *bus);
void __iomem *(*map_bus)(struct pci_bus *bus, unsigned int devfn, int where);
int (*read)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val);/* 读配置空间 */
int (*write)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val); /* 写配置空间 */
};
read()
和 write()
成员函数中的 size
表示访问的是字节、2 字节还是 4 字节,对于 write()
而言,val
是要写入的值;对于 read()
而言,val
是要返回的读取到的值的指针。通过 bus
参数的成员以及devfn
可以定位相应 PCI 总线上相应 PCI 逻辑设备的配置空间。在 Linux 设备驱动中,可用如下一组函数来访问配置空间:
int pci_bus_read_config_byte(struct pci_bus *bus, unsigned int devfn, int where, u8 *val);
int pci_bus_read_config_word(struct pci_bus *bus, unsigned int devfn, int where, u16 *val);
int pci_bus_read_config_dword(struct pci_bus *bus, unsigned int devfn, int where, u32 *val);
int pci_bus_write_config_byte(struct pci_bus *bus, unsigned int devfn, int where, u8 val);
int pci_bus_write_config_word(struct pci_bus *bus, unsigned int devf, int where, u16 val);
int pci_bus_write_config_dword(struct pci_bus *bus, unsigned int devfn, int where, u32 val);
————————————————
版权声明:本文为CSDN博主「xdtp」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/tpmamba/article/details/111389986
上述函数只是对如下函数进行调用:
读字节 */
int pci_bus_read_config_word (struct pci_bus *bus, unsigned int devfn, int where, u16 *val); /* 读字 */
int pci_bus_read_config_dword (struct pci_bus *bus, unsigned int devfn, int where, u32 *val); /* 读双字 */
int pci_bus_write_config_byte (struct pci_bus *bus, unsigned int devfn, int where, u8 val); /* 写字节 */
int pci_bus_write_config_word (struct pci_bus *bus, unsigned int devfn, int where, u16 val); /* 写字 */
int pci_bus_write_config_dword (struct pci_bus *bus, unsigned int devfn, int where, u32 val); /* 写双字 */
查看/proc
和/sysfs
文件系统下的PCI总线、设备和驱动
tree /proc/bus/pci
tree /sys/bus/pci
uos@uos-PC:~$ tree /proc/bus/pci
/proc/bus/pci
├── 0000:00
│ ├── 00.0
│ ├── 04.0
│ ├── 04.1
│ ├── 05.0
│ ├── 05.1
│ ├── 07.0
│ ├── 08.0
│ ├── 08.1
│ ├── 08.2
│ ├── 0a.0
│ ├── 0b.0
│ ├── 0d.0
│ ├── 0f.0
│ ├── 13.0
│ ├── 16.0
│ └── 17.0
├── 0000:01
│ └── 00.0
├── 0000:02
│ └── 00.0
├── 0000:03
│ └── 00.0
├── 0000:04
│ ├── 00.0
│ └── 00.1
├── 0000:05
│ └── 00.0
└── devices
6 directories, 23 files
uos@uos-PC:~$ tree /sys/bus/pci
/sys/bus/pci
├── devices
│ ├── 0000:00:00.0 -> ../../../devices/pci0000:00/0000:00:00.0
│ ├── 0000:00:04.0 -> ../../../devices/pci0000:00/0000:00:04.0
│ ├── 0000:00:04.1 -> ../../../devices/pci0000:00/0000:00:04.1
│ ├── 0000:00:05.0 -> ../../../devices/pci0000:00/0000:00:05.0
│ ├── 0000:00:05.1 -> ../../../devices/pci0000:00/0000:00:05.1
│ ├── 0000:00:07.0 -> ../../../devices/pci0000:00/0000:00:07.0
│ ├── 0000:00:08.0 -> ../../../devices/pci0000:00/0000:00:08.0
│ ├── 0000:00:08.1 -> ../../../devices/pci0000:00/0000:00:08.1
│ ├── 0000:00:08.2 -> ../../../devices/pci0000:00/0000:00:08.2
│ ├── 0000:00:0a.0 -> ../../../devices/pci0000:00/0000:00:0a.0
│ ├── 0000:00:0b.0 -> ../../../devices/pci0000:00/0000:00:0b.0
│ ├── 0000:00:0d.0 -> ../../../devices/pci0000:00/0000:00:0d.0
│ ├── 0000:00:0f.0 -> ../../../devices/pci0000:00/0000:00:0f.0
│ ├── 0000:00:13.0 -> ../../../devices/pci0000:00/0000:00:13.0
│ ├── 0000:00:16.0 -> ../../../devices/pci0000:00/0000:00:16.0
│ ├── 0000:00:17.0 -> ../../../devices/pci0000:00/0000:00:17.0
│ ├── 0000:01:00.0 -> ../../../devices/pci0000:00/0000:00:0a.0/0000:01:00.0
│ ├── 0000:02:00.0 -> ../../../devices/pci0000:00/0000:00:0b.0/0000:02:00.0
│ ├── 0000:03:00.0 -> ../../../devices/pci0000:00/0000:00:0d.0/0000:03:00.0
│ ├── 0000:04:00.0 -> ../../../devices/pci0000:00/0000:00:0f.0/0000:04:00.0
│ ├── 0000:04:00.1 -> ../../../devices/pci0000:00/0000:00:0f.0/0000:04:00.1
│ └── 0000:05:00.0 -> ../../../devices/pci0000:00/0000:00:13.0/0000:05:00.0
├── drivers
│ ├── ahci
│ │ ├── 0000:00:08.0 -> ../../../../devices/pci0000:00/0000:00:08.0
│ │ ├── 0000:00:08.1 -> ../../../../devices/pci0000:00/0000:00:08.1
│ │ ├── 0000:00:08.2 -> ../../../../devices/pci0000:00/0000:00:08.2
│ │ ├── 0000:03:00.0 -> ../../../../devices/pci0000:00/0000:00:0d.0/0000:03:00.0
│ │ ├── bind
│ │ ├── module -> ../../../../module/ahci
│ │ ├── new_id
│ │ ├── remove_id
│ │ ├── uevent
│ │ └── unbind
│ ├── amdgpu
│ │ ├── 0000:04:00.0 -> ../../../../devices/pci0000:00/0000:00:0f.0/0000:04:00.0
│ │ ├── bind
│ │ ├── module -> ../../../../module/amdgpu
│ │ ├── new_id
│ │ ├── remove_id
│ │ ├── uevent
│ │ └── unbind
│ ├── ast
│ │ ├── bind
│ │ ├── module -> ../../../../module/ast
│ │ ├── new_id
│ │ ├── remove_id
│ │ ├── uevent
│ │ └── unbind
│ ├── cavium_ptp
│ │ ├── bind
│ │ ├── new_id
│ │ ├── remove_id
│ │ ├── uevent
│ │ └── unbind
│ ├── ehci-pci
│ │ ├── 0000:00:04.1 -> ../../../../devices/pci0000:00/0000:00:04.1
│ │ ├── 0000:00:05.1 -> ../../../../devices/pci0000:00/0000:00:05.1
│ │ ├── bind
│ │ ├── module -> ../../../../module/ehci_pci
│ │ ├── new_id
│ │ ├── remove_id
│ │ ├── uevent
│ │ └── unbind
│ ├── loongson-audio
│ │ ├── 0000:00:07.0 -> ../../../../devices/pci0000:00/0000:00:07.0
│ │ ├── bind
│ │ ├── module -> ../../../../module/snd_hda_loongson
│ │ ├── new_id
│ │ ├── remove_id
│ │ ├── uevent
│ │ └── unbind
│ ├── ls-spi-pci
│ │ ├── 0000:00:16.0 -> ../../../../devices/pci0000:00/0000:00:16.0
│ │ ├── bind
│ │ ├── new_id
│ │ ├── remove_id
│ │ ├── uevent
│ │ └── unbind
│ ├── megaraid_legacy
│ │ ├── bind
│ │ ├── module -> ../../../../module/megaraid
│ │ ├── new_id
│ │ ├── remove_id
│ │ ├── uevent
│ │ └── unbind
│ ├── megaraid_sas
│ │ ├── bind
│ │ ├── dbg_lvl
│ │ ├── module -> ../../../../module/megaraid_sas
│ │ ├── new_id
│ │ ├── release_date
│ │ ├── remove_id
│ │ ├── support_device_change
│ │ ├── support_nvme_encapsulation
│ │ ├── support_poll_for_event
│ │ ├── uevent
│ │ ├── unbind
│ │ └── version
│ ├── mpt3sas
│ │ ├── bind
│ │ ├── module -> ../../../../module/mpt3sas
│ │ ├── new_id
│ │ ├── remove_id
│ │ ├── uevent
│ │ └── unbind
│ ├── mvumi
│ │ ├── bind
│ │ ├── new_id
│ │ ├── remove_id
│ │ ├── uevent
│ │ └── unbind
│ ├── nvme
│ │ ├── 0000:05:00.0 -> ../../../../devices/pci0000:00/0000:00:13.0/0000:05:00.0
│ │ ├── bind
│ │ ├── module -> ../../../../module/nvme
│ │ ├── new_id
│ │ ├── remove_id
│ │ ├── uevent
│ │ └── unbind
│ ├── ohci-pci
│ │ ├── 0000:00:04.0 -> ../../../../devices/pci0000:00/0000:00:04.0
│ │ ├── 0000:00:05.0 -> ../../../../devices/pci0000:00/0000:00:05.0
│ │ ├── bind
│ │ ├── module -> ../../../../module/ohci_pci
│ │ ├── new_id
│ │ ├── remove_id
│ │ ├── uevent
│ │ └── unbind
│ ├── pcieport
│ │ ├── 0000:00:0a.0 -> ../../../../devices/pci0000:00/0000:00:0a.0
│ │ ├── 0000:00:0b.0 -> ../../../../devices/pci0000:00/0000:00:0b.0
│ │ ├── 0000:00:0d.0 -> ../../../../devices/pci0000:00/0000:00:0d.0
│ │ ├── 0000:00:0f.0 -> ../../../../devices/pci0000:00/0000:00:0f.0
│ │ ├── 0000:00:13.0 -> ../../../../devices/pci0000:00/0000:00:13.0
│ │ ├── bind
│ │ ├── new_id
│ │ ├── remove_id
│ │ ├── uevent
│ │ └── unbind
│ ├── r8168
│ │ ├── 0000:02:00.0 -> ../../../../devices/pci0000:00/0000:00:0b.0/0000:02:00.0
│ │ ├── bind
│ │ ├── module -> ../../../../module/r8168
│ │ ├── new_id
│ │ ├── remove_id
│ │ ├── uevent
│ │ └── unbind
│ ├── radeon
│ │ ├── bind
│ │ ├── module -> ../../../../module/radeon
│ │ ├── new_id
│ │ ├── remove_id
│ │ ├── uevent
│ │ └── unbind
│ ├── radeonfb
│ │ ├── bind
│ │ ├── new_id
│ │ ├── remove_id
│ │ ├── uevent
│ │ └── unbind
│ ├── serial
│ │ ├── bind
│ │ ├── new_id
│ │ ├── remove_id
│ │ ├── uevent
│ │ └── unbind
│ ├── shpchp
│ │ ├── bind
│ │ ├── module -> ../../../../module/shpchp
│ │ ├── new_id
│ │ ├── remove_id
│ │ ├── uevent
│ │ └── unbind
│ ├── smifb
│ │ ├── bind
│ │ ├── module -> ../../../../module/smifb
│ │ ├── new_id
│ │ ├── remove_id
│ │ ├── uevent
│ │ └── unbind
│ ├── snd_hda_intel
│ │ ├── 0000:04:00.1 -> ../../../../devices/pci0000:00/0000:00:0f.0/0000:04:00.1
│ │ ├── bind
│ │ ├── module -> ../../../../module/snd_hda_intel
│ │ ├── new_id
│ │ ├── remove_id
│ │ ├── uevent
│ │ └── unbind
│ ├── stmmaceth
│ │ ├── bind
│ │ ├── new_id
│ │ ├── remove_id
│ │ ├── uevent
│ │ └── unbind
│ ├── tsi721
│ │ ├── bind
│ │ ├── module -> ../../../../module/tsi721_mport
│ │ ├── new_id
│ │ ├── remove_id
│ │ ├── uevent
│ │ └── unbind
│ └── xhci_hcd
│ ├── 0000:01:00.0 -> ../../../../devices/pci0000:00/0000:00:0a.0/0000:01:00.0
│ ├── bind
│ ├── module -> ../../../../module/xhci_pci
│ ├── new_id
│ ├── remove_id
│ ├── uevent
│ └── unbind
├── drivers_autoprobe
├── drivers_probe
├── rescan
├── resource_alignment
├── slots
└── uevent
86 directories, 131 files
内核中定义了一组专门针对 PCI 设备的 DMA 操作接口,这些 API 的原型和作用与通用 DMA API 非常相似,主要包括如下。
int pci_set_dma_mask(struct pci_dev *dev, u64 mask);
void *pci_alloc_consistent(struct pci_dev *pdev, size_t size, dma_addr_t *dma_handle);
void pci_free_consistent(struct pci_dev *hwdev, size_t size, void *vaddr, dma_addr_t dma_handle);
dma_addr_t pci_map_single(struct pci_dev *pdev, void *ptr, size_t size, int direction);
int pci_map_sg(struct pci_dev *pdev,struct scatterlist *sgl,int num_entries, int direction);
void pci_unmap_single(struct pci_dev *pdev, dma_addr_t dma_addr, size_t size, int direction);
void pci_unmap_sg(struct pci_dev *pdev, struct scatterlist *sg, int nents, int direction);
这些 API 的用法与dma_alloc_consistent()
、dma_map_single()
、dma_map_sg()
相似,只是以 pci_
开头的 API 用于 PCI 设备驱动。
除了 DMA API
外,在 PCI 设备驱动中其他常用的函数(或宏)如下所示。
获取 I/O 或内存资源。
#define pci_resource_start(dev,bar) ((dev)!resource[(bar)].start)
#define pci_resource_end(dev,bar) ((dev)!resource[(bar)].end)
#define pci_resource_flags(dev,bar) ((dev)!resource[(bar)].flags)
#define pci_resource_len(dev,bar) \
((pci_resource_start((dev),(bar)) == 0 && \
pci_resource_end((dev),(bar)) == \
pci_resource_start((dev),(bar))) ? 0 : \
\
(pci_resource_end((dev),(bar)) - \
pci_resource_start((dev),(bar)) + 1))
申请/释放 I/O 或内存资源。
int pci_request_regions(struct pci_dev *pdev, const char *res_name);
void pci_release_regions(struct pci_dev *pdev);
获取/设置驱动私有数据。
void *pci_get_drvdata (struct pci_dev *pdev);
void pci_set_drvdata (struct pci_dev *pdev, void *data);
使能/禁止 PCI 设备。
int pci_enable_device(struct pci_dev *dev);
void pci_disable_device(struct pci_dev *dev);
设置为总线主 DMA。
void pci_set_master(struct pci_dev *dev);
寻找指定总线指定槽位的 PCI 设备。
struct pci_dev *pci_find_slot (unsigned int bus, unsigned int devfn);
设置 PCI 能量管理状态(0=D0 … 3=D3)。
int pci_set_power_state(struct pci_dev *dev, pci_power_t state);
在设备的能力表中找出指定的能力。
int pci_find_capability (struct pci_dev *dev, int cap);
启用设备内存写无效事务。
int pci_set_mwi(struct pci_dev *dev);
禁用设备内存写无效事务。
void pci_clear_mwi(struct pci_dev *dev);
从本质上讲 PCI 只是一种总线,具体的 PCI 设备可以是字符设备、网络设备、USB 主机控制器等,因此,一个通过 PCI 总线与系统连接的设备的驱动至少包含以下两部分内容。
PCI 驱动只是为了辅助设备本身的驱动,它不是目的,而是手段。例如,对于通过 PCI 总线与系统连接的字符设备,则驱动中除了要实现 PCI 驱动部分外,其主体仍然是设备作为字符设备本身的驱动,即实现 file_operations
成员函数并注册 cdev
。分析 Linux 内核可知,在/drivers/block/
、/drivers/atm/
、 /drivers/char/
、 /drivers/i2c/
、 /drivers/ieee1394/
、 /drivers/media/
、 /drivers/mtd/
、/drivers/net/
、/drivers/serial/
、/drivers/video/
、/sound/
等目录中均广泛分布着 PCI 设备驱动。
在 Linux 内核中,用 pci_driver
结构体来定义 PCI 驱动,该结构体中包含了 PCI 设备的探测/移除、挂起/恢复等函数。pci_driver
和 platform_driver
、i2c_driver
、usb_driver
的地位非常相似,都是起到挂接总线的作用。
struct pci_driver {
struct list_head node;
const char *name;
const struct pci_device_id *id_table; /* Must be non-NULL for probe to be called */ /*不能为 NULL,以便 probe 函数调用*/
/* 新设备添加 */
int (*probe)(struct pci_dev *dev, const struct pci_device_id *id); /* New device inserted */
void (*remove)(struct pci_dev *dev); /* Device removed (NULL if not a hot-plug capable driver) */ /* 设备移出 */
int (*suspend)(struct pci_dev *dev, pm_message_t state); /* Device suspended *//* 设备挂起 */
int (*suspend_late)(struct pci_dev *dev, pm_message_t state);
int (*resume_early)(struct pci_dev *dev);
int (*resume) (struct pci_dev *dev); /* Device woken up */ /* 设备唤醒 */
void (*shutdown) (struct pci_dev *dev);
int (*sriov_configure) (struct pci_dev *dev, int num_vfs); /* On PF */
const struct pci_error_handlers *err_handler;
const struct attribute_group **groups;
struct device_driver driver;
struct pci_dynids dynids;
};
#define to_pci_driver(drv) container_of(drv, struct pci_driver, driver)
注册/注销PCI驱动
int pci_register_driver(struct pci_driver *drv);
void pci_unregister_driver(struct pci_driver *drv);
使能/禁止PCI设备
int pci_enable_device(struct pci_dev *dev);
void pci_disable_device(struct pci_dev *dev);
申请/释放I/O或内存资源
int pci_request_regions(struct pci_dev *dev, const char *res_name);
void pci_release_regions(struct pci_dev *dev);
获取I/O或内存资源
#define pci_resource_start(dev, bar) ((dev)->resource[(bar)].start)
#define pci_resource_end(dev, bar) ((dev)->resource[(bar)].end)
#define pci_resource_flags(dev, bar) ((dev)->resource[(bar)].flags)
#define pci_resource_len(dev,bar) \
((pci_resource_start((dev), (bar)) == 0 && \
pci_resource_end((dev), (bar)) == \
pci_resource_start((dev), (bar))) ? 0 : \
\
(pci_resource_end((dev), (bar)) - \
pci_resource_start((dev), (bar)) + 1))
设置/获取驱动私有数据
void pci_set_drvdata(struct pci_dev *pdev, void *data);
void *pci_get_drvdata(struct pci_dev *pdev);
设置/清除为总线主DMA
void pci_set_master(struct pci_dev *dev);
void pci_clear_master(struct pci_dev *dev);
在设备的能力表中找出指定的capability
int pci_find_capability(struct pci_dev *dev, int cap);
设置/清除内存写无效事务
int pci_set_mwi(struct pci_dev *dev);
void pci_clear_mwi(struct pci_dev *dev);
pci_driver的probe函数
func: 完成PCI设备的初始化及设备本身(字符、tty、网络等)的驱动的注册。
pci_device_id结构体
struct pci_device_id {
__u32 vendor, device; /* Vendor and device ID or PCI_ANY_ID*/
__u32 subvendor, subdevice; /* Subsystem ID's or PCI_ANY_ID */
__u32 class, class_mask; /* (class,subclass,prog-if) triplet */
kernel_ulong_t driver_data; /* Data private to the driver */
};
#define PCI_DEVICE(vend,dev) \
.vendor = (vend), .device = (dev), \
.subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID
pci_driver
的 probe()
函数要完成 PCI 设备的初始化及其设备本身身份(字符、TTY、网络等)驱动的注册。当 Linux 内核启动并完成对所有 PCI 设备进行扫描、登录和分配资源等初始化操作的同时,会建立起系统中所有 PCI 设备的拓扑结构,probe()
函数将负责硬件的探测工作并保存配置信息。
static int xxx_probe(struct pci_dev *pci_dev, const struct pci_device_id *id)
{
/*pci设备相关结构的初始化*/
/*初始化设备本身(字符、tty、网络等)*/
/*注册设备本身*/
}
static void xxx_remove(struct pci_dev *pci_dev)
{
/*注销设备本身*/
/*PCI设备相关结构的释放*/
}
static struct pci_driver xxx_pci_driver = {
.name = DRV_NAME,
.id_table = pci_tbl,
.probe = xxx_probe,
.remove = xxx_remove,
...
};
xxx_init
pci_register_driver(&xxx_pci_driver);
xxx_exit
pci_unregister_driver(&xxx_pci_driver);
pci总线就像工作单位、PCI设备需要向“工作单位”注册并接收其管理(probe/remove/resume/...
);
但PCI设备又本身可能是个保安、工程师、经理等,故有其自己的主体工作(字符/tty/网络等驱动程序)。
************************************************
NVIDIA nForce媒体访问控制器的以太网驱动程序
(drivers/net/ethernet/nvidia/forcedeth.c)
************************************************
static const struct net_device_ops nv_netdev_ops = {
.ndo_open = nv_open,
.ndo_stop = nv_close,
.ndo_get_stats64 = nv_get_stats64,
.ndo_start_xmit = nv_start_xmit,
...
...
};
static int nv_probe(struct pci_dev *pci_dev, const struct pci_device_id *id)
{
struct net_device *dev;
struct fe_priv *np;
unsigned long addr;
u8 __iomem *base;
...
dev = alloc_etherdev(sizeof(struct fe_priv)); /*分配etherdev设备*/
...
np = netdev_priv(dev);
np->dev = dev;
np->pci_dev = pci_dev;
...
SET_NETDEV_DEV(dev, &pci_dev->dev);
...
err = pci_enable_device(pci_dev); /*使能PCI设备*/
...
pci_set_master(pci_dev); /*设置为总线主DMA*/
err = pci_request_regions(pci_dev, DRV_NAME); /*申请PCI I/O和内存资源*/
...
addr = 0;
for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) {
if (pci_resource_flags(pci_dev, i) & IORESOURCE_MEM &&
pci_resource_len(pci_dev, i) >= np->register_size) {
addr = pci_resource_start(pci_dev, i); /*获取I/O或内存基址*/
break;
}
}
...
...
np->base = ioremap(addr, np->register_size); /*ioremap I/O区域*/
...
...
if (!nv_optimized(np))
dev->netdev_ops = &nv_netdev_ops; /*指定net_device_ops结构体*/
else
dev->netdev_ops = &nv_netdev_ops_optimized;
netif_napi_add(dev, &np->napi, nv_napi_poll, RX_WORK_PER_LOOP);
dev->ethtool_ops = &ops;
dev->watchdog_timeo = NV_WATCHDOG_TIMEO;
pci_set_drvdata(pci_dev, dev); /*获取驱动私有数据*/
...
...
err = register_netdev(dev); /*注册网络设备*/
...
...
return 0;
...
}
static void nv_remove(struct pci_dev *pci_dev)
{
struct net_device *dev = pci_get_drvdata(pci_dev);
unregister_netdev(dev); /*注销网络设备*/
nv_restore_mac_addr(pci_dev);
/* restore any phy related changes */
nv_restore_phy(dev);
nv_mgmt_release_sema(dev);
/* free all structures */
free_rings(dev);
iounmap(get_hwbase(dev));
pci_release_regions(pci_dev); /*释放I/O或内存资源*/
pci_disable_device(pci_dev); /*禁止PCI设备*/
free_netdev(dev); /*释放网络设备*/
}
static const struct pci_device_id pci_tbl[] = {
{ /* nForce Ethernet Controller */
PCI_DEVICE(0x10DE, 0x01C3), /*宏定义见附录*/
.driver_data = DEV_NEED_TIMERIRQ|DEV_NEED_LINKTIMER,
},
{ /* nForce2 Ethernet Controller */
PCI_DEVICE(0x10DE, 0x0066),
.driver_data = DEV_NEED_TIMERIRQ|DEV_NEED_LINKTIMER,
},
...
...
{0,},
};
static struct pci_driver forcedeth_pci_driver = {
.name = DRV_NAME,
.id_table = pci_tbl,
.probe = nv_probe,
.remove = nv_remove,
...
};
module_pci_driver(forcedeth_pci_driver); /*该函数实际已经包含了pci_register_driver和pci_register_driver*/
MODULE_DEVICE_TABLE(pci, pci_tbl); /*使用该宏将pci_tbl导出到用户空间*/