目录
12 PCI接口
12.1 PCI接口
12.1.1 PCI寻址
12.1.2 启动时间
12.1.3 配置寄存器和初始化
12.2 数据结构
12.2.1 pci_dev
12.2.2 pci_device_id
12.2.3 pci_driver
12.3 注册PCI驱动程序
12.3.1 pci_register_driver
12.3.2 pci_unregister_driver
12.3.3 驱动注册引导过程
12.4 设备初始化
12.4.1 pci_enable_device
12.4 访问I/O和内存空间
12.4.1 I/O内存
12.4.2 request_region
12.4.3 pci_request_region
12.4.4 pci_resource_start
12.4.5 pci_resource_length
12.4.6 pci_resource_flags
12.4.7 pci_resource_end
12.4.8 映射 ioremap
12.6 卸载模块
附录
边际效应
pci_dev成员清单
PCI:外围设备互联
12.1 PCI接口
12.1.1 PCI寻址
PCI外设:域,总线号,设备号,功能号标识
PCI域:占用多达256个总线,,每个总线占用32个设备,每个设备最多8个功能,可被16位地址或key标识
借助数据结构pci_dev在设备上操作
lspci
查询设备列表:lspci
开头的xx:yy:zxx:表示总线号,一个PCI域能容纳256个总线YY:是PCI设备编号,每个总线可以支持32个PCI设备Z:表示总线功能,每个PCI设备可以容纳8个PCI功能
PCI系统布局(lspci -t)
PCI系统的整体布局为树形,其中每个总线连接到上一级总线,直到树根的0号总线。
桥:连接两个总线的特殊PCI外设
简图:
介绍:
Host/PCI桥:用于连接CPU与PCI根总线,在PC中,内存控制器也通常被集成到Host/PCI桥设备芯片,Host/PCI桥通常被称 为“北桥芯片组”。PCI/ISA桥:用于连接旧的ISA总线。PCI/ISA桥也被称为“南桥芯片组”。PCI-to-PCI桥:用于连接PCI主总线与次总线。
显示硬件地址时:2个值:8位总线号 + 8 位 设备和功能号
3个值:(bus (8:256)+ device(5:32) + function(3:8))
4个值:(domain + bus + device +function)
表示:常用16进制显示
PCI设备包括3个寻址空间:配置空间、IO端口和设备内存。
PCI配置空间访问(共为256字节):
制造商标识:由PCI组织分配给厂家。 设备标识:按产品分类给本卡编号。 分类码:本卡功能分类码。 申请存储器空间:PCI卡内有存储器或以存储器编址的寄存器和I/O空间,为使驱动程序和应用程序能访问它们。配置空间 的基地址寄存器用于此目的。 申请I/O空间:配置空间基地址寄存器用来进行系统I/O空间申请。 中断资源申请:向系统申请中断资源。
12.1.2 启动时间
PCI设备上电时,硬件保持非激活
系统启动时:固件和PCI外设进行配置交易
(1)分配一个安全的的位置给每个它提供的地址区
(2)在驱动存取设备时,内存和IO区已经被映射到处理器的地址空间
12.1.3 配置寄存器和初始化
PCI设备至少有一个256字节 地址空间。前64字节是标准的,而剩下的是依赖设备的。(1排16字节,共4排)
PCI设备必须包含有意义的值在被要求的寄存器中。
被要求的字段声称版的功能,包括其他的字段是否可用。
PCI寄存器为小端模式(低对低,高对高)
常用:只读寄存器:verdorID ,deviceID,和class
驱动借助它们来查找设备。
subsystem verdorID和subsystem deviceID 被供应商设置来进一步区分类似的设备。
寄存器细节
verdorID:16位寄存器,标识硬件制造商 ,例如:intel 0x8086
deviceID:16位寄存器,和供应商ID,组成唯一的32位标识符。设备驱动依靠签名来标识它的设备。可在硬件手册中找到对于目标设备要寻找的值。
class:类寄存器,16位:高8位标识“基类”;例如“ethernet和token ring 是2个类,都属于network群
一些驱动可支持类似的设备,每个设备都有一个不同的签名,但是都属于同样的类。驱动依赖类寄存器标识他们的外设。
pci驱动可告知内核它支持的设备
struct pci_device_id用来定义一个驱动支持的不同类型PCI设备的列表
结构体成员
struct pci_device_id {__u32 vendor, device; /* 厂商和设备ID如果驱动可以处理任意供应商或者设备ID:PCI_ANY_ID*/__u32 subvendor, subdevice; /* 子供应商和子设备ID支持任意类型子系统: PCI_ANY_ID*/__u32 class, class_mask; /* 类、子类 */kernel_ulong_t driver_data;/* 驱动私有数据:不匹配设备,用来持有信息 */};
帮助宏定义:
PCI_DEVICE(vendor,device)
PCI_DEVICE(vendor,device)创建一个struct pci_device_id ,只匹配特定的供应商和设备ID,子供应商和子设备成员为PCI_ANY_ID
12.2 数据结构
-
struct pci_dev :描述PCI设备
-
struct pci_device_id :PCI设备标识符
-
struct pci_driver :定义PCI层和设备驱动程序之间的接口。主要由函数指针组成。
12.2.1 pci_dev
这个设备由内核使用,以引用一个PCI设备,如同网络设备都会被分派net_device实例一样。
12.2.2 pci_device_id
ID表是一个以全零结尾的struct pci_device_id条目数组。通常首选使用静态const定义
(1)成员清单
struct pci_device_id 结构体用于定义该驱动程序支持的不同类型的PCI设备列表。该结构体包含下列字段
<mod_devicetable.h>struct pci_device_id {/* Vendor and device ID or PCI_ANY_ID 指定设备的PCI厂商和设备ID。* 如果驱动程序可以处理任何厂商或者设备ID,这些字段应该使用值PCI_ANY_ID */__u32 vendor, device; /* Subsystem ID's or PCI_ANY_ID 指定设备的PCI子系统厂商和子系统设备ID。* 如果驱动程序可以处理任何类型的子系统ID,这些字段应该使用值PCI_ANY_ID*/__u32 subvendor, subdevice; /* (class,subclass,prog-if) triplet 指定驱动支持一种PCI类(class)设备 。* PCI规范中描述了不同类的PCI设备。* 如果驱动程序可以处理任何类型的子系统ID,这些字段应该使用值PCI_ANY_ID*/__u32 class, class_mask; /* Data private to the driver * 不是用来和设备相匹配的,而是用来保存PCI驱动程序用于区分不同设备的信息*/kernel_ulong_t driver_data; };
实例:
(2)MODULE_DEVICE_TABLE
struct pci_device_id结构体需要被导出到用户空间,使热拔插和模块装载系统知道什么模块针对什么硬件设备。
宏MODULE_DEVICE_TABLE完成这个工作。
/* 该语句创建一个名为__mod_pci_device_table 的局部变量,指向struct pci_device_table数组。* 在稍后的内核构建过程中,depmod程序在所有的模块中搜索符号__mod_pci_device_table。* 如果找到该符号,它把数据从该模块中抽出,添加到文件/lib/modules/KERNEL_VERSION/modules.pcimap中。* 当depmod结束后,内核模块支持的所有PCI设备,连同它们的模块名都在该文件中被列出。当内核告知热插拔系统一个新的PCI设备已* 经被发现时,热插拔系统使用modules.pcimap文件来寻找要装载的恰当的驱动程序*/
12.2.3 pci_driver
下面列出了该结构体中PCI驱动程序必须注意的字段:
(1)name
/* 驱动程序的名字。在内核的所有PCI驱动程序中它必须是唯一的,通常被设置为和驱动程序的模块名相同。当驱动程序运行在内核时,它会出现在sysfs的/sys/bus/pci/drivers/下面 */const char *name;
实例:
查询结果:
(2)id_table
/* ID向量,内核用于把一些设备关联到此驱动程序,指向struct pci_device_id表 */ const struct pci_device_id *id_table;
实例:
(3)probe
/* 指针,指向PCI驱动程序中的探测函数* 当PCI层发现它正在搜寻驱动程序的设备ID与前面id_table匹配,就会调用此函数* 此函数应该开启硬件、分配net_device结构,初始化并注册新设备,分配正确工作所需的所有数据结构(如传输或接收时所用的缓冲区)* 成功确认pci_dev 则返回0,失败返回负值*/ int (*probe) (struct pci_dev *dev, const struct pci_device_id *id); /* New device inserted */
探测方式有两种:
-
静态:给定设备PCI ID,内核就能根据id_table向量查询出正确的PCI驱动程序
-
动态:根据用户手动配置的ID,动态指系统管理员可以新增ID的能力
(4)remove
/* 指针,指向移除函数*当pci_dev被从系统移除,或从内核中卸载PCI驱动程序时,调用,probe配对函数* 释放已分配的I/O端口,I/O内存,为设备除名,释放net_device以及其他由设备驱动程序在probe函数内分配的辅助数据结构*/ void (*remove) (struct pci_dev *dev); /* Device removed (NULL if not a hot-plug capable driver) */
(5)suspend
/* 可选,指向挂起函数,挂起状态用state传递* 主要停止设备出口队列,使得该设备无法再传输*/ int (*suspend) (struct pci_dev *dev, pm_message_t state); /* Device suspended */
(6)resume
/* 可选,指向恢复函数 ,重启出口队列*/ int (*resume) (struct pci_dev *dev); /* Device woken up */
只有当内核支持电源管理时,才会对suspend和resume做初始化。
实例:
结构体成员清单
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 */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); /* PF pdev */const struct pci_error_handlers *err_handler;struct device_driver driver;struct pci_dynids dynids; };
12.3 注册PCI驱动程序
每个设备驱动程序都会把一个pci_device_id实例的向量注册到内核,这个实例向量列出了其所能处理的设备ID。
PCI设备驱动程序分别用pci_register_driver和pci_unregister_driver向内核注册和除名。
12.3.1 pci_register_driver
pci_register_driver函数需要一个pci_driver数据结构作为参数,借助pci_driver的id_table向量,内核知道该驱动程序可以处理哪些设备。
以struct pci_driver为参数,pci_register_driver()就是把某个设备下的driver程序放入struct kset drivers结构体中。
在总线对应的数据结构bus_type中,struct kset drivers和struct kset devices。
在写驱动程序的时候,要想驱动程序能工作,首先就要把驱动程序对应的driver程序(struct pci_driver结构)放进struct kset driver结构体中,然后把设备的信息放入struct kset device中。
/* register a new pci driver;Adds the driver structure to the list of registered drivers.*/ static inline int pci_register_driver(struct pci_driver *drv) { return 0; }
12.3.2 pci_unregister_driver
从内核注销pci_driver
void pci_unregister_driver(struct pci_driver *drv) {driver_unregister(&drv->driver);pci_free_dynids(drv); }
12.3.3 驱动注册引导过程
当驱动程序A被加载时,会调用pci_register_driver并提供其pci_driver实参而与PCI层注册。pci_driver结构内含有一个此驱动程序能驱动的PCI设备ID的向量。接着,PCI层使用该表去查看在已侦测的PCI设备列表中与哪些设备匹配。于是,就会建立该驱动程序的设备列表,如图(b)所示。
对于每个匹配的设备而言,PCI层会调用相匹配的驱动程序中的pci_driver结构中所提供的probe函数。probe函数会建立并注册相关联的网络设备。
就此而言,设备dev3需要另一个驱动程序B。当驱动程序B在内核注册后,dev3就会被分派给驱动程序B。
12.4 设备初始化
pci_register_driver()调用需要传入一个函数指针表,从而指示驱动程序的更高一级结构体。一旦驱动程序知道了一个PCI设备并获得了所有权,驱动程序通常需要执行以下初始化:
-
启用设备
-
请求MMIO / IOP资源
-
设置DMA掩码大小(用于一致性DMA和流式DMA)
-
分配和初始化共享控制数据(pci_allocate_coherent())
-
访问设备配置空间(如果需要)
-
注册IRQ处理程序(request_irq())
-
初始化non-PCI(即LAN/SCSI/等芯片部分)
-
启用DMA /处理引擎
12.4.1 pci_enable_device
/* 初始化设备。使能IO位和memory,如果设备处于挂起状态,则唤醒。该函数可能执行失败* Initialize device before it's used by a driver. Ask low-level code* to enable I/O and memory. Wake up the device if it was suspended.* Beware, this function can fail.*/ int pci_enable_device(struct pci_dev *dev) {return pci_enable_device_flags(dev, IORESOURCE_MEM | IORESOURCE_IO); }
12.4 访问I/O和内存空间
PCI设备包括 3 个寻址空间:
-
设备内存:映射缓冲,存放数据,不是所有卡都有可寻址的内存区域
-
I/O 端口:
-
配置空间
PCI有6个I/O或内存区域。每个区域可以是内存也可以是I/O地址。但是不同于常规内存,I/O寄存器具有边际效应(side effect)。I/O寄存器不应该由CPU缓存。
对于PCI设备,在内存区域实现I/O寄存器,可以通过“配置寄存器”设置内存属性。
-
非可预取的:内存的访问不能被优化,比如I/O端口
-
可预取的: IORESOURCE_PREFETCH
12.4.1 I/O内存
I/O端口:当一个寄存器或内存位于I/O空间时,称其为I/O端口
使用I/O端口:
-
申请/分配
-
访问:多数硬件都会把8位,16位和32位端口区分开。因此,C语言程序中必须调用不同的函数来访问大小不同的端口
-
释放
I/O内存:当一个寄存器或内存位于内存空间时,称其为I/O内存
使用I/O内存:
-
申请
-
映射
-
访问
-
释放
根据计算机平台和所使用的总线的不同,I/O内存不一定是通过页表访问的。如果访问是经由页表进行的,内核必须首先安排物理地址使其对设备驱动程序可见(这通常意味着在进行任何I/O之前必须先调用(ioremap))。如果访问无需页表,那么I/O内存区域就非常类似I/O端口,可以使用适当形式的函数读写它们。
12.4.2 request_region
-
内核提供的接口函数
/* 申请起始于first的n个端口,参数name是设备名称* 返回值:成功:非NULL;失败:NULL */ struct resource *request_region(unsigned long first, unsigned long n, const char *name);void release_region(unsigned long start, unsigned long n);// 卸载
-
PCI内置接口函数
PCI设备的I/O区域已经被集成到通用资源管理。因此我们无需访问配置变量来了解设备的被映射到内存或I/O空间的何处。但需要调用pci_request_region()来验证没有设备已经在使用相同的地址资源。
12.4.3 pci_request_region
/* Reserve PCI I/O and memory resource* pdev:要预留的设备* bar:要预留的总线* res_name :设备名称*/ int pci_request_region(struct pci_dev *pdev, int bar, const char *res_name);
12.4.4 pci_resource_start
// 从配置区相应寄存器得到I/O区域的基址 unsigned long pci_resource_start(struct pci_dev *dev, int bar);// Bar值的范围为0-5
12.4.5 pci_resource_length
// 从配置区相应寄存器得到I/O区域的内存区域长度: unsigned long pci_resource_length(struct pci_dev *dev, int bar);
12.4.6 pci_resource_flags
// 返回该资源相关联的标志 unsigned long pci_resource_flags(struct pci_dev *dev, int bar);
12.4.7 pci_resource_end
// 返回第bar个区域的尾地址 // 是最后一个可用的地址,而不是下一个区域的首地址 unsigned long pci_resource_end(struct pci_dev *dev, int bar);// Bar值的范围为0-5
细节差异:SI检索的是宏,书中是函数
12.4.8 映射 ioremap
<!--详见第8章vmalloc及其辅助函数-->
分配I/O内存之后,我们还需要保证该I/O内存对于内核而言是可访问的,必须首先建立映射。映射的建立有ioremap完成。
ioremap:将一个IO地址空间映射到内核的虚拟地址空间上去。
返回值:虚拟地址,用来访问指定的物理内存区域
应用:映射PCI缓冲区地址到内核空间。
iounmap:释放虚拟地址
注意:
-
由ioremap返回的地址不应当做指向内存的指针直接访问,而应该使用其他I/O函数
-
会修改页表
12.6 卸载模块
当使用设备完成时,可能需要卸载模块,驱动程序需要采取以下步骤:
-
禁止设备产生irq
-
释放IRQ
-
停止所有DMA活动
-
释放DMA缓存区(包括流式DMA和一致性DMA)
-
从其他子系统注销(例如scsi或netdev)
-
释放MMIO/ IOP资源
-
禁用该设备
附录
边际效应
I/O寄存器具有边际效应(side effect)
边际效应:读取某个地址时可能导致该地址内容发生变化。比如很多设备的中断状态寄存器主要一读取,便自动清零
常规内存操作:内存写操作的唯一结果就是在指定位置存储一个数据;内存读操作仅仅是返回指定位置最后一次写入的数据。
CPU优化/编译器优化:编译器能够缓存数据值到CPU寄存器而不写回内存,并且即使数据值已经存储到内存,读和写操作都能够在缓冲内存中进行,而不是直接接触物理RAM。此外,指令重编排可能在编译器级别或者在硬件级别发生。很多情况下,如果一个指令以不同于在程序文本中出现的顺序来执行(例如,为避免在RISC流水线中的互锁),它能够执行得更快
对于传统的内存(单处理器系统)来说,这些优化是透明和有益的。驱动直接存取I/O寄存器主要目的是能提高CPU性能。然后,这些优化对正确的I/O操作可能是致命的。处理器无法预见这种情形,一些其他的操作(在一个独立处理器上运行,或者发生在一个I/O控制器的事情)依赖内存存取的顺序。编译器或CPU可能只尽力胜过你并且重编排你请求的操作,结果可能是奇怪的错误而且非常难以调试。因此,一个驱动程序必须确保没有进行缓冲并且在存取寄存器时没有发生读或写的重编排。
pci_dev成员清单
struct pci_dev {struct list_head bus_list; /* node in per-bus list */struct pci_bus *bus; /* bus this device is on */struct pci_bus *subordinate; /* bus this device bridges to */void *sysdata; /* hook for sys-specific extension */struct proc_dir_entry *procent; /* device entry in /proc/bus/pci */struct pci_slot *slot; /* Physical slot this device is in */unsigned int devfn; /* encoded device & function index */unsigned short vendor;unsigned short device;unsigned short subsystem_vendor;unsigned short subsystem_device;unsigned int class; /* 3 bytes: (base,sub,prog-if) */u8 revision; /* PCI revision, low byte of class word */u8 hdr_type; /* PCI header type (`multi' flag masked out) */u8 pcie_cap; /* PCI-E capability offset */u8 msi_cap; /* MSI capability offset */u8 msix_cap; /* MSI-X capability offset */u8 pcie_mpss:3; /* PCI-E Max Payload Size Supported */u8 rom_base_reg; /* which config register controls the ROM */u8 pin; /* which interrupt pin this device uses */u16 pcie_flags_reg; /* cached PCI-E Capabilities Register */struct pci_driver *driver; /* which driver has allocated this device */u64 dma_mask; /* Mask of the bits of bus address thisdevice implements. Normally this is0xffffffff. You only need to changethis if your device has broken DMAor supports 64-bit transfers. */struct device_dma_parameters dma_parms;pci_power_t current_state; /* Current operating state. In ACPI-speak,this is D0-D3, D0 being fully functional,and D3 being off. */u8 pm_cap; /* PM capability offset */unsigned int pme_support:5; /* Bitmask of states from which PME#can be generated */unsigned int pme_interrupt:1;unsigned int pme_poll:1; /* Poll device's PME status bit */unsigned int d1_support:1; /* Low power state D1 is supported */unsigned int d2_support:1; /* Low power state D2 is supported */unsigned int no_d1d2:1; /* D1 and D2 are forbidden */unsigned int no_d3cold:1; /* D3cold is forbidden */unsigned int d3cold_allowed:1; /* D3cold is allowed by user */unsigned int mmio_always_on:1; /* disallow turning off io/memdecoding during bar sizing */unsigned int wakeup_prepared:1;unsigned int runtime_d3cold:1; /* whether go through runtimeD3cold, not set for devicespowered on/off by thecorresponding bridge */unsigned int d3_delay; /* D3->D0 transition time in ms */unsigned int d3cold_delay; /* D3cold->D0 transition time in ms */#ifdef CONFIG_PCIEASPMstruct pcie_link_state *link_state; /* ASPM link state. */ #endifpci_channel_state_t error_state; /* current connectivity state */struct device dev; /* Generic device interface */int cfg_size; /* Size of configuration space *//** Instead of touching interrupt line and base address registers* directly, use the values stored here. They might be different!*/unsigned int irq;struct resource resource[DEVICE_COUNT_RESOURCE]; /* I/O and memory regions + expansion ROMs */bool match_driver; /* Skip attaching driver *//* These fields are used by common fixups */unsigned int transparent:1; /* Transparent PCI bridge */unsigned int multifunction:1;/* Part of multi-function device *//* keep track of device state */unsigned int is_added:1;unsigned int is_busmaster:1; /* device is busmaster */unsigned int no_msi:1; /* device may not use msi */unsigned int no_64bit_msi:1; /* device may only use 32-bit MSIs */unsigned int block_cfg_access:1; /* config space access is blocked */unsigned int broken_parity_status:1; /* Device generates false positive parity */unsigned int irq_reroute_variant:2; /* device needs IRQ rerouting variant */unsigned int msi_enabled:1;unsigned int msix_enabled:1;unsigned int ari_enabled:1; /* ARI forwarding */unsigned int is_managed:1;unsigned int is_pcie:1; /* Obsolete. Will be removed.Use pci_is_pcie() instead */unsigned int needs_freset:1; /* Dev requires fundamental reset */unsigned int state_saved:1;unsigned int is_physfn:1;unsigned int is_virtfn:1;unsigned int reset_fn:1;unsigned int is_hotplug_bridge:1;unsigned int __aer_firmware_first_valid:1;unsigned int __aer_firmware_first:1;unsigned int broken_intx_masking:1;unsigned int io_window_1k:1; /* Intel P2P bridge 1K I/O windows */pci_dev_flags_t dev_flags;atomic_t enable_cnt; /* pci_enable_device has been called */u32 saved_config_space[16]; /* config space saved at suspend time */struct hlist_head saved_cap_space;struct bin_attribute *rom_attr; /* attribute descriptor for sysfs ROM entry */int rom_attr_enabled; /* has display of the rom attribute been enabled? */struct bin_attribute *res_attr[DEVICE_COUNT_RESOURCE]; /* sysfs file for resources */struct bin_attribute *res_attr_wc[DEVICE_COUNT_RESOURCE]; /* sysfs file for WC mapping of resources */ #ifdef CONFIG_PCI_MSIstruct list_head msi_list;const struct attribute_group **msi_irq_groups; #endifstruct pci_vpd *vpd; #ifdef CONFIG_PCI_ATSunion {struct pci_sriov *sriov; /* SR-IOV capability related */struct pci_dev *physfn; /* the PF this VF is associated with */};struct pci_ats *ats; /* Address Translation Service */ #endifphys_addr_t rom; /* Physical address of ROM if it's not from the BAR */size_t romlen; /* Length of ROM if it's not from the BAR */ };