点击上方蓝字【囧囧妹】一起学习,一起成长!
一、PCIE驱动结构
PCI 驱动程序通过 pci_register_driver()“发现”系统中的 PCI 设备。当 PCI 通用代码发现新设备时,将通知具有匹配“描述”的驱动程序。
pci_register_driver() 将大部分设备探测留给 PCI 层,并支持插入/移除设备,因此在单个驱动程序中支持热插拔 PCI、CardBus 和 Express-Card。pci_register_driver() 调用需要传入函数指针表。
一旦驱动程序知道了一个 PCI 设备并取得了句柄操作权,驱动程序通常需要执行以下初始化:
启用设备
请求 MMIO/IOP 资源
设置 DMA 掩码大小(对于一致性DMA和流式DMA)
分配和初始化共享控制数据 (pci_allocate_coherent())
访问设备配置空间(如果需要)
注册 IRQ 处理程序 (
request_irq()
)初始化非PCI(即芯片的LAN/SCSI/etc部分)
启用 DMA/处理引擎
使用完设备后,需要卸载模块,驱动程序需要执行以下步骤:
禁止设备生成 IRQ
释放 IRQ (
free_irq()
)停止所有 DMA 活动
释放 DMA 缓冲区(流式和一致性)
从其他子系统(例如 scsi 或 netdev)注销
释放 MMIO/IOP 资源
禁用设备
如果未配置 PCI 子系统(未设置 CONFIG_PCI),则下面描述的大多数 PCI 函数被定义为内联函数,要么完全为空,要么仅返回适当的错误代码,以避免驱动程序中出现大量 ifdef。
二,pci_register_driver() 调用
PCI 设备驱动程序pci_register_driver()在初始化期间调用一个指向描述驱动程序的结构的指针(struct pci_driver):
struct pci_driver pci结构定义
struct pci_driver {struct list_head node;const char *name;const struct pci_device_id *id_table;int (*probe)(struct pci_dev *dev, const struct pci_device_id *id);void (*remove)(struct pci_dev *dev);int (*suspend)(struct pci_dev *dev, pm_message_t state);int (*resume)(struct pci_dev *dev);void (*shutdown)(struct pci_dev *dev);int (*sriov_configure)(struct pci_dev *dev, int num_vfs);int (*sriov_set_msix_vec_count)(struct pci_dev *vf, int msix_vec_count);u32 (*sriov_get_vf_total_msix)(struct pci_dev *pf);const struct pci_error_handlers *err_handler;const struct attribute_group **groups;const struct attribute_group **dev_groups;struct device_driver driver;struct pci_dynids dynids;bool driver_managed_dma;};
node: 驱动程序结构列表。
name: 驱动程序名称。
id_table: 指向驱动程序的设备 ID 表的指针。大多数驱动程序应使用 MODULE_DEVICE_TABLE(pci,…) 导出此表。
probe: 对于与 ID 表匹配且尚未由其他驱动程序“拥有”的所有 PCI 设备,将调用此探测函数(在对现有设备执行 pci_register_driver() 期间或稍后如果插入新设备)。对于 ID 表中的条目与设备匹配的每个设备,此函数都会传递一个“struct pci_dev *”。当驱动程序选择“拥有”设备时,探测函数返回零,否则返回错误代码(负数)。探测函数总是从进程上下文中调用,所以它可以休眠。
remove: 每当删除此驱动程序处理的设备时(在取消注册驱动程序期间或手动将其从热插拔插槽中拉出时),都会调用 remove() 函数。remove 函数总是从进程上下文中调用,所以它可以休眠。
suspend: 将设备置于低功耗状态。
resume: 从低功耗状态唤醒设备。需要参考PCI电源管理。
shutdown: 连接到 reboot_notifier_list (kernel/sys.c)。旨在停止任何空闲的 DMA 操作。对于启用 LAN 唤醒 (NIC) 或在重新启动前更改设备的电源状态很有用。例如驱动程序/net/e100.c。
sriov_configure:可选的驱动程序回调,以允许通过 sysfs “sriov_numvfs”文件配置要启用的 VF 数量。
sriov_set_msix_vec_count: PF 驱动程序回调以更改 VF 上的 MSI-X 向量的数量。通过 sysfs “sriov_vf_msix_count” 触发。这将更改 VF 消息控制寄存器中的 MSI-X 表大小。
sriov_get_vf_total_msix: PF 驱动程序回调以获取可用于分发到 VF 的 MSI-X 向量的总数。
groups: Sysfs 属性组。
dev_groups: 绑定到驱动程序后将创建的附加到设备的属性。
driver: 驱动程序模型结构。
dynids: 动态添加的设备 ID 列表。
driver_managed_dma: 设备驱动程序不使用内核 DMA API 进行 DMA。对于大多数设备驱动程序,只要所有 DMA 都通过内核 DMA API 处理,就不需要关心这个标志。对于一些特殊的驱动程序,例如 VFIO 驱动程序,它们知道如何自己管理 DMA 并设置此标志,以便 IOMMU 层允许它们设置和管理自己的 I/O 地址空间。
struct pci_device_id //PCI 设备 ID 结构
定义
struct pci_device_id {__u32 vendor, device;__u32 subvendor, subdevice;__u32 class, class_mask;kernel_ulong_t driver_data;__u32 override_only;};
成员描述:
vendor:要匹配的供应商 ID(或 PCI_ANY_ID)
device:要匹配的设备 ID(或 PCI_ANY_ID)
subvendor:要匹配的子系统供应商 ID(或 PCI_ANY_ID)
subdevice:要匹配的子系统设备 ID(或 PCI_ANY_ID)
class:要匹配的设备类、子类和“接口”。请参阅 PCI 本地总线规范的附录 D 或 include/linux/pci_ids.h 以获取完整的类列表。大多数驱动程序不需要指定 class/class_mask,因为 vendor/device 通常就足够了。
class_mask:限制比较类字段的哪些子字段。有关用法示例,请参见 drivers/scsi/sym53c8xx_2/。
driver_data:驱动程序私有的数据。大多数驱动程序不需要使用 driver_data 字段。最佳实践是将 driver_data 用作等效设备类型的静态列表的索引,而不是将其用作指针。
override_only:仅当 dev->driver_override 是此驱动程序时匹配。
大多数驱动程序只需要PCI_DEVICE()
或PCI_DEVICE_CLASS()
设置一个 pci_device_id 表。
新的 PCI ID 可以在运行时添加到设备驱动程序 pci_ids 表中,如下所示:
echo "vendor device subvendor subdevice class class_mask driver_data" > \/sys/bus/pci/drivers/{driver}/new_id
subvendor 和 subdevice 字段默认为 PCI_ANY_ID (FFFFFFFF)
class 和 classmask 字段默认为 0
driver_data 默认为 0UL。
override_only 字段默认为 0。
请注意,driver_data 必须与驱动程序中定义的任何 pci_device_id 条目使用的值匹配。如果所有 pci_device_id 条目都具有非零 driver_data 值,这将使 driver_data 字段成为必需字段。
添加后,将为在其(新更新的)pci_ids 列表中列出的任何无人认领的 PCI 设备调用驱动程序探测例程。
当驱动程序退出时,它只是调用pci_unregister_driver()并且 PCI 层自动为驱动程序处理的所有设备调用删除钩子。
三,驱动程序功能/数据的“属性”
__init | 初始化代码。驱动程序初始化后不再调用。 |
__exit | 退出代码。对于非模块化驱动程序被忽略。 |




