暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

pcie(三)- Linux PCI 驱动程序

囧囧妹 2022-08-09
1028

点击上方蓝字【囧囧妹】一起学习,一起成长!

一、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 地址空间。

    ID 表是一个以全零条目结尾的条目数组。使用 static const 的定义通常是首选。struct pci_device_id
    • 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
        所有字段都作为十六进制值传入(没有前导 0x)。供应商和设备字段是必需的,其他是可选的。用户只需根据需要传递尽可能多的可选字段:
        • 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 层自动为驱动程序处理的所有设备调用删除钩子。


        三,驱动程序功能/数据的“属性”

        驱动程序中要实现初始化和清理函数(对应的宏在<linux/init.h>中定义):

        __init

        初始化代码。驱动程序初始化后不再调用。

        __exit

        退出代码。对于非模块化驱动程序被忽略。



        觉得不错,点击“分享”,“赞”,“在看”传播给更多热爱嵌入式的小伙伴吧!
        文章转载自囧囧妹,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

        评论