162306a36Sopenharmony_ci.. SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci.. include:: ../disclaimer-zh_CN.rst 362306a36Sopenharmony_ci 462306a36Sopenharmony_ci:Original: Documentation/PCI/pci.rst 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci:翻译: 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci 司延腾 Yanteng Si <siyanteng@loongson.cn> 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci:校译: 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci.. _cn_PCI_pci.rst: 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci=================== 1762306a36Sopenharmony_ci如何写Linux PCI驱动 1862306a36Sopenharmony_ci=================== 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci:作者: - Martin Mares <mj@ucw.cz> 2162306a36Sopenharmony_ci - Grant Grundler <grundler@parisc-linux.org> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ciPCI的世界是巨大的,而且充满了(大多数是不愉快的)惊喜。由于每个CPU架构实现了不同 2462306a36Sopenharmony_ci的芯片组,并且PCI设备有不同的要求(呃,“特性”),结果是Linux内核中的PCI支持并不 2562306a36Sopenharmony_ci像人们希望的那样简单。这篇短文试图向所有潜在的驱动程序作者介绍PCI设备驱动程序的 2662306a36Sopenharmony_ciLinux APIs。 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci更完整的资源是Jonathan Corbet、Alessandro Rubini和Greg Kroah-Hartman的 2962306a36Sopenharmony_ci《Linux设备驱动程序》第三版。LDD3可以免费获得(在知识共享许可下),网址是: 3062306a36Sopenharmony_cihttps://lwn.net/Kernel/LDD3/。 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci然而,请记住,所有的文档都会受到“维护不及时”的影响。如果事情没有按照这里描述的那 3562306a36Sopenharmony_ci样进行,请参考源代码。 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci请将有关Linux PCI API的问题/评论/补丁发送到“Linux PCI” 3862306a36Sopenharmony_ci<linux-pci@atrey.karlin.mff.cuni.cz> 邮件列表。 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ciPCI驱动的结构体 4262306a36Sopenharmony_ci=============== 4362306a36Sopenharmony_ciPCI驱动通过pci_register_driver()在系统中“发现”PCI设备。实际上,它是反过来的。 4462306a36Sopenharmony_ci当PCI通用代码发现一个新设备时,具有匹配“描述”的驱动程序将被通知。下面是这方面的细 4562306a36Sopenharmony_ci节。 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_cipci_register_driver()将大部分探测设备的工作留给了PCI层,并支持设备的在线插入/移 4862306a36Sopenharmony_ci除[从而在一个驱动中支持可热插拔的PCI、CardBus和Express-Card]。 pci_register_driver() 4962306a36Sopenharmony_ci调用需要传入一个函数指针表,从而决定了驱动的高层结构体。 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci一旦驱动探测到一个PCI设备并取得了所有权,驱动通常需要执行以下初始化: 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci - 启用设备 5462306a36Sopenharmony_ci - 请求MMIO/IOP资源 5562306a36Sopenharmony_ci - 设置DMA掩码大小(对于流式和一致的DMA) 5662306a36Sopenharmony_ci - 分配和初始化共享控制数据(pci_allocate_coherent()) 5762306a36Sopenharmony_ci - 访问设备配置空间(如果需要) 5862306a36Sopenharmony_ci - 注册IRQ处理程序(request_irq()) 5962306a36Sopenharmony_ci - 初始化非PCI(即芯片的LAN/SCSI/等部分) 6062306a36Sopenharmony_ci - 启用DMA/处理引擎 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci当使用完设备后,也许需要卸载模块,驱动需要采取以下步骤: 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci - 禁用设备产生的IRQ 6562306a36Sopenharmony_ci - 释放IRQ(free_irq()) 6662306a36Sopenharmony_ci - 停止所有DMA活动 6762306a36Sopenharmony_ci - 释放DMA缓冲区(包括一致性和数据流式) 6862306a36Sopenharmony_ci - 从其他子系统(例如scsi或netdev)上取消注册 6962306a36Sopenharmony_ci - 释放MMIO/IOP资源 7062306a36Sopenharmony_ci - 禁用设备 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci这些主题中的大部分都在下面的章节中有所涉及。其余的内容请参考LDD3或<linux/pci.h> 。 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci如果没有配置PCI子系统(没有设置 ``CONFIG_PCI`` ),下面描述的大多数PCI函数被定 7562306a36Sopenharmony_ci义为内联函数,要么完全为空,要么只是返回一个适当的错误代码,以避免在驱动程序中出现 7662306a36Sopenharmony_ci大量的 ``ifdef`` 。 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci调用pci_register_driver() 8062306a36Sopenharmony_ci========================= 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ciPCI设备驱动程序在初始化过程中调用 ``pci_register_driver()`` ,并提供一个指向 8362306a36Sopenharmony_ci描述驱动程序的结构体的指针( ``struct pci_driver`` ): 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci该API在以下内核代码中: 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ciinclude/linux/pci.h 8862306a36Sopenharmony_cipci_driver 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ciID表是一个由 ``struct pci_device_id`` 结构体成员组成的数组,以一个全零的成员 9162306a36Sopenharmony_ci结束。一般来说,带有静态常数的定义是首选。 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci该API在以下内核代码中: 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ciinclude/linux/mod_devicetable.h 9662306a36Sopenharmony_cipci_device_id 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci大多数驱动程序只需要 ``PCI_DEVICE()`` 或 ``PCI_DEVICE_CLASS()`` 来设置一个 9962306a36Sopenharmony_cipci_device_id表。 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci新的 ``PCI ID`` 可以在运行时被添加到设备驱动的 ``pci_ids`` 表中,如下所示:: 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci echo "vendor device subvendor subdevice class class_mask driver_data" > \ 10462306a36Sopenharmony_ci /sys/bus/pci/drivers/{driver}/new_id 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci所有字段都以十六进制值传递(没有前置0x)。供应商和设备字段是强制性的,其他字段是可 10762306a36Sopenharmony_ci选的。用户只需要传递必要的可选字段: 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci - subvendor和subdevice字段默认为PCI_ANY_ID (FFFFFFF)。 11062306a36Sopenharmony_ci - class和classmask字段默认为0 11162306a36Sopenharmony_ci - driver_data默认为0UL。 11262306a36Sopenharmony_ci - override_only字段默认为0。 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci请注意, ``driver_data`` 必须与驱动程序中定义的任何一个 ``pci_device_id`` 条 11562306a36Sopenharmony_ci目所使用的值相匹配。如果所有的 ``pci_device_id`` 成员都有一个非零的driver_data 11662306a36Sopenharmony_ci值,这使得driver_data字段是强制性的。 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci一旦添加,驱动程序探测程序将被调用,以探测其(新更新的) ``pci_ids`` 列表中列出的 11962306a36Sopenharmony_ci任何无人认领的PCI设备。 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci当驱动退出时,它只是调用 ``pci_unregister_driver()`` ,PCI层会自动调用驱动处理 12262306a36Sopenharmony_ci的所有设备的移除钩子。 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci驱动程序功能/数据的“属性” 12662306a36Sopenharmony_ci------------------------- 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci请在适当的地方标记初始化和清理函数(相应的宏在<linux/init.h>中定义): 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci ====== ============================================== 13162306a36Sopenharmony_ci __init 初始化代码。在驱动程序初始化后被抛弃。 13262306a36Sopenharmony_ci __exit 退出代码。对于非模块化的驱动程序来说是忽略的。 13362306a36Sopenharmony_ci ====== ============================================== 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci关于何时/何地使用上述属性的提示: 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci - module_init()/module_exit()函数(以及所有仅由这些函数调用的初始化函数)应该被标记 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci - 为__init/__exit。 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci - 不要标记pci_driver结构体。 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci - 如果你不确定应该使用哪种标记,请不要标记一个函数。不标记函数比标记错误的函数更好。 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci如何手动搜索PCI设备 14762306a36Sopenharmony_ci=================== 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ciPCI驱动最好有一个非常好的理由不使用 ``pci_register_driver()`` 接口来搜索PCI设备。 15062306a36Sopenharmony_ciPCI设备被多个驱动程序控制的主要原因是一个PCI设备实现了几个不同的HW服务。例如,组合的 15162306a36Sopenharmony_ci串行/并行端口/软盘控制器。 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci可以使用以下结构体进行手动搜索: 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci通过供应商和设备ID进行搜索:: 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci struct pci_dev *dev = NULL; 15862306a36Sopenharmony_ci while (dev = pci_get_device(VENDOR_ID, DEVICE_ID, dev)) 15962306a36Sopenharmony_ci configure_device(dev); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci按类别ID搜索(以类似的方式迭代):: 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci pci_get_class(CLASS_ID, dev) 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci通过供应商/设备和子系统供应商/设备ID进行搜索:: 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci pci_get_subsys(VENDOR_ID,DEVICE_ID, SUBSYS_VENDOR_ID, SUBSYS_DEVICE_ID, dev). 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci你可以使用常数 ``PCI_ANY_ID`` 作为 ``VENDOR_ID`` 或 ``DEVICE_ID`` 的通 17062306a36Sopenharmony_ci配符替代。例如,这允许搜索来自一个特定供应商的任何设备。 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci这些函数是热拔插安全的。它们会增加它们所返回的 ``pci_dev`` 的参考计数。你最终 17362306a36Sopenharmony_ci必须通过调用 ``pci_dev_put()`` 来减少这些设备上的参考计数(可能在模块卸载时)。 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci设备初始化步骤 17762306a36Sopenharmony_ci============== 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci正如介绍中所指出的,大多数PCI驱动需要以下步骤进行设备初始化: 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci - 启用设备 18262306a36Sopenharmony_ci - 请求MMIO/IOP资源 18362306a36Sopenharmony_ci - 设置DMA掩码大小(对于流式和一致的DMA) 18462306a36Sopenharmony_ci - 分配和初始化共享控制数据(pci_allocate_coherent()) 18562306a36Sopenharmony_ci - 访问设备配置空间(如果需要) 18662306a36Sopenharmony_ci - 注册IRQ处理程序(request_irq()) 18762306a36Sopenharmony_ci - 初始化non-PCI(即芯片的LAN/SCSI/等部分) 18862306a36Sopenharmony_ci - 启用DMA/处理引擎 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci驱动程序可以在任何时候访问PCI配置空间寄存器。(嗯,几乎如此。当运行BIST时,配置 19162306a36Sopenharmony_ci空间可以消失......但这只会导致PCI总线主控中止,读取配置将返回垃圾值)。) 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci启用PCI设备 19562306a36Sopenharmony_ci----------- 19662306a36Sopenharmony_ci在接触任何设备寄存器之前,驱动程序需要通过调用 ``pci_enable_device()`` 启用 19762306a36Sopenharmony_ciPCI设备。这将: 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci - 唤醒处于暂停状态的设备。 20062306a36Sopenharmony_ci - 分配设备的I/O和内存区域(如果BIOS没有这样做)。 20162306a36Sopenharmony_ci - 分配一个IRQ(如果BIOS没有)。 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci.. note:: 20462306a36Sopenharmony_ci pci_enable_device() 可能失败,检查返回值。 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci.. warning:: 20762306a36Sopenharmony_ci OS BUG:在启用这些资源之前,我们没有检查资源分配情况。如果我们在调用 20862306a36Sopenharmony_ci 之前调用pci_request_resources(),这个顺序会更合理。目前,当两个设备被分配 20962306a36Sopenharmony_ci 了相同的范围时,设备驱动无法检测到这个错误。这不是一个常见的问题,不太可能很快 21062306a36Sopenharmony_ci 得到修复。 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci 这个问题之前已经讨论过了,但从2.6.19开始没有改变: 21362306a36Sopenharmony_ci https://lore.kernel.org/r/20060302180025.GC28895@flint.arm.linux.org.uk/ 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_cipci_set_master()将通过设置PCI_COMMAND寄存器中的总线主控位来启用DMA。 21762306a36Sopenharmony_ci``pci_clear_master()`` 将通过清除总线主控位来禁用DMA,它还修复了延迟计时器的 21862306a36Sopenharmony_ci值,如果它被BIOS设置成假的。 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci如果PCI设备可以使用 ``PCI Memory-Write-Invalidate`` 事务,请调用 ``pci_set_mwi()`` 。 22162306a36Sopenharmony_ci这将启用 ``Mem-Wr-Inval`` 的 ``PCI_COMMAND`` 位,也确保缓存行大小寄存器被正确设置。检 22262306a36Sopenharmony_ci查 ``pci_set_mwi()`` 的返回值,因为不是所有的架构或芯片组都支持 ``Memory-Write-Invalidate`` 。 22362306a36Sopenharmony_ci另外,如果 ``Mem-Wr-Inval`` 是好的,但不是必须的,可以调用 ``pci_try_set_mwi()`` ,让 22462306a36Sopenharmony_ci系统尽最大努力来启用 ``Mem-Wr-Inval`` 。 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci请求MMIO/IOP资源 22862306a36Sopenharmony_ci---------------- 22962306a36Sopenharmony_ci内存(MMIO)和I/O端口地址不应该直接从PCI设备配置空间中读取。使用 ``pci_dev`` 结构体 23062306a36Sopenharmony_ci中的值,因为PCI “总线地址”可能已经被arch/chip-set特定的内核支持重新映射为“主机物理” 23162306a36Sopenharmony_ci地址。 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci参见io_mapping函数,了解如何访问设备寄存器或设备内存。 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci设备驱动需要调用 ``pci_request_region()`` 来确认没有其他设备已经在使用相同的地址 23662306a36Sopenharmony_ci资源。反之,驱动应该在调用 ``pci_disable_device()`` 之后调用 ``pci_release_region()`` 。 23762306a36Sopenharmony_ci这个想法是为了防止两个设备在同一地址范围内发生冲突。 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci.. tip:: 24062306a36Sopenharmony_ci 见上面的操作系统BUG注释。目前(2.6.19),驱动程序只能在调用pci_enable_device() 24162306a36Sopenharmony_ci 后确定MMIO和IO端口资源的可用性。 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci``pci_request_region()`` 的通用风格是 ``request_mem_region()`` (用于MMIO 24462306a36Sopenharmony_ci范围)和 ``request_region()`` (用于IO端口范围)。对于那些不被 "正常 "PCI BAR描 24562306a36Sopenharmony_ci述的地址资源,使用这些方法。 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci也请看下面的 ``pci_request_selected_regions()`` 。 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci设置DMA掩码大小 25162306a36Sopenharmony_ci--------------- 25262306a36Sopenharmony_ci.. note:: 25362306a36Sopenharmony_ci 如果下面有什么不明白的地方,请参考使用通用设备的动态DMA映射。本节只是提醒大家, 25462306a36Sopenharmony_ci 驱动程序需要说明设备的DMA功能,并不是DMA接口的权威来源。 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci虽然所有的驱动程序都应该明确指出PCI总线主控的DMA功能(如32位或64位),但对于流式 25762306a36Sopenharmony_ci数据来说,具有超过32位总线主站功能的设备需要驱动程序通过调用带有适当参数的 25862306a36Sopenharmony_ci``dma_set_mask()`` 来“注册”这种功能。一般来说,在系统RAM高于4G物理地址的情 25962306a36Sopenharmony_ci况下,这允许更有效的DMA。 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci所有PCI-X和PCIe兼容设备的驱动程序必须调用 ``dma_set_mask()`` ,因为它们 26262306a36Sopenharmony_ci是64位DMA设备。 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci同样,如果设备可以通过调用 ``dma_set_coherent_mask()`` 直接寻址到 26562306a36Sopenharmony_ci4G物理地址以上的系统RAM中的“一致性内存”,那么驱动程序也必须“注册”这种功能。同 26662306a36Sopenharmony_ci样,这包括所有PCI-X和PCIe兼容设备的驱动程序。许多64位“PCI”设备(在PCI-X之前) 26762306a36Sopenharmony_ci和一些PCI-X设备对有效载荷(“流式”)数据具有64位DMA功能,但对控制(“一致性”)数 26862306a36Sopenharmony_ci据则没有。 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci设置共享控制数据 27262306a36Sopenharmony_ci---------------- 27362306a36Sopenharmony_ci一旦DMA掩码设置完毕,驱动程序就可以分配“一致的”(又称共享的)内存。参见使用通 27462306a36Sopenharmony_ci用设备的动态DMA映射,了解DMA API的完整描述。本节只是提醒大家,需要在设备上启 27562306a36Sopenharmony_ci用DMA之前完成。 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci初始化设备寄存器 27962306a36Sopenharmony_ci---------------- 28062306a36Sopenharmony_ci一些驱动程序需要对特定的“功能”字段进行编程,或对其他“供应商专用”寄存器进行初始 28162306a36Sopenharmony_ci化或重置。例如,清除挂起的中断。 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci注册IRQ处理函数 28562306a36Sopenharmony_ci--------------- 28662306a36Sopenharmony_ci虽然调用 ``request_irq()`` 是这里描述的最后一步,但这往往只是初始化设备的另 28762306a36Sopenharmony_ci一个中间步骤。这一步通常可以推迟到设备被打开使用时进行。 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci所有IRQ线的中断处理程序都应该用 ``IRQF_SHARED`` 注册,并使用devid将IRQ映射 29062306a36Sopenharmony_ci到设备(记住,所有的PCI IRQ线都可以共享)。 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci``request_irq()`` 将把一个中断处理程序和设备句柄与一个中断号联系起来。历史上, 29362306a36Sopenharmony_ci中断号码代表从PCI设备到中断控制器的IRQ线。在MSI和MSI-X中(更多内容见下文),中 29462306a36Sopenharmony_ci断号是CPU的一个“向量”。 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci``request_irq()`` 也启用中断。在注册中断处理程序之前,请确保设备是静止的,并且 29762306a36Sopenharmony_ci没有任何中断等待。 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ciMSI和MSI-X是PCI功能。两者都是“消息信号中断”,通过向本地APIC的DMA写入来向CPU发 30062306a36Sopenharmony_ci送中断。MSI和MSI-X的根本区别在于如何分配多个“向量”。MSI需要连续的向量块,而 30162306a36Sopenharmony_ciMSI-X可以分配几个单独的向量。 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci在调用 ``request_irq()`` 之前,可以通过调用 ``pci_alloc_irq_vectors()`` 30462306a36Sopenharmony_ci的PCI_IRQ_MSI和/或PCI_IRQ_MSIX标志来启用MSI功能。这将导致PCI支持将CPU向量数 30562306a36Sopenharmony_ci据编程到PCI设备功能寄存器中。许多架构、芯片组或BIOS不支持MSI或MSI-X,调用 30662306a36Sopenharmony_ci``pci_alloc_irq_vectors`` 时只使用PCI_IRQ_MSI和PCI_IRQ_MSIX标志会失败, 30762306a36Sopenharmony_ci所以尽量也要指定 ``PCI_IRQ_LEGACY`` 。 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci对MSI/MSI-X和传统INTx有不同中断处理程序的驱动程序应该在调用 31062306a36Sopenharmony_ci``pci_alloc_irq_vectors`` 后根据 ``pci_dev``结构体中的 ``msi_enabled`` 31162306a36Sopenharmony_ci和 ``msix_enabled`` 标志选择正确的处理程序。 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci使用MSI有(至少)两个真正好的理由: 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci1) 根据定义,MSI是一个排他性的中断向量。这意味着中断处理程序不需要验证其设备是 31662306a36Sopenharmony_ci 否引起了中断。 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci2) MSI避免了DMA/IRQ竞争条件。到主机内存的DMA被保证在MSI交付时对主机CPU是可 31962306a36Sopenharmony_ci 见的。这对数据一致性和避 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci3) 免控制数据过期都很重要。这个保证允许驱动程序省略MMIO读取,以刷新DMA流。 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci参见drivers/infiniband/hw/mthca/或drivers/net/tg3.c了解MSI/MSI-X的使 32462306a36Sopenharmony_ci用实例。 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ciPCI设备关闭 32862306a36Sopenharmony_ci=========== 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci当一个PCI设备驱动程序被卸载时,需要执行以下大部分步骤: 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci - 禁用设备产生的IRQ 33362306a36Sopenharmony_ci - 释放IRQ(free_irq()) 33462306a36Sopenharmony_ci - 停止所有DMA活动 33562306a36Sopenharmony_ci - 释放DMA缓冲区(包括流式和一致的) 33662306a36Sopenharmony_ci - 从其他子系统(例如scsi或netdev)上取消注册 33762306a36Sopenharmony_ci - 禁用设备对MMIO/IO端口地址的响应 33862306a36Sopenharmony_ci - 释放MMIO/IO端口资源 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci停止设备上的IRQ 34262306a36Sopenharmony_ci--------------- 34362306a36Sopenharmony_ci如何做到这一点是针对芯片/设备的。如果不这样做,如果(也只有在)IRQ与另一个设备 34462306a36Sopenharmony_ci共享,就会出现“尖叫中断”的可能性。 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci当共享的IRQ处理程序被“解钩”时,使用同一IRQ线的其余设备仍然需要启用该IRQ。因此, 34762306a36Sopenharmony_ci如果“脱钩”的设备断言IRQ线,假设它是其余设备中的一个断言IRQ线,系统将作出反应。 34862306a36Sopenharmony_ci由于其他设备都不会处理这个IRQ,系统将“挂起”,直到它决定这个IRQ不会被处理并屏蔽 34962306a36Sopenharmony_ci这个IRQ(100,000次之后)。一旦共享的IRQ被屏蔽,其余设备将停止正常工作。这不是 35062306a36Sopenharmony_ci一个好事情。 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci这是使用MSI或MSI-X的另一个原因,如果它可用的话。MSI和MSI-X被定义为独占中断, 35362306a36Sopenharmony_ci因此不容易受到“尖叫中断”问题的影响。 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci释放IRQ 35662306a36Sopenharmony_ci------- 35762306a36Sopenharmony_ci一旦设备被静止(不再有IRQ),就可以调用free_irq()。这个函数将在任何待处理 35862306a36Sopenharmony_ci的IRQ被处理后返回控制,从该IRQ上“解钩”驱动程序的IRQ处理程序,最后如果没有人 35962306a36Sopenharmony_ci使用该IRQ,则释放它。 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci停止所有DMA活动 36362306a36Sopenharmony_ci--------------- 36462306a36Sopenharmony_ci在试图取消分配DMA控制数据之前,停止所有的DMA操作是非常重要的。如果不这样做, 36562306a36Sopenharmony_ci可能会导致内存损坏、挂起,在某些芯片组上还会导致硬崩溃。 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci在停止IRQ后停止DMA可以避免IRQ处理程序可能重新启动DMA引擎的竞争。 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci虽然这个步骤听起来很明显,也很琐碎,但过去有几个“成熟”的驱动程序没有做好这个 37062306a36Sopenharmony_ci步骤。 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci释放DMA缓冲区 37462306a36Sopenharmony_ci------------- 37562306a36Sopenharmony_ci一旦DMA被停止,首先要清理流式DMA。即取消数据缓冲区的映射,如果有的话,将缓 37662306a36Sopenharmony_ci冲区返回给“上游”所有者。 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci然后清理包含控制数据的“一致的”缓冲区。 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_ci关于取消映射接口的细节,请参见Documentation/core-api/dma-api.rst。 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci从其他子系统取消注册 38462306a36Sopenharmony_ci-------------------- 38562306a36Sopenharmony_ci大多数低级别的PCI设备驱动程序支持其他一些子系统,如USB、ALSA、SCSI、NetDev、 38662306a36Sopenharmony_ciInfiniband等。请确保你的驱动程序没有从其他子系统中丢失资源。如果发生这种情况, 38762306a36Sopenharmony_ci典型的症状是当子系统试图调用已经卸载的驱动程序时,会出现Oops(恐慌)。 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci禁止设备对MMIO/IO端口地址做出响应 39162306a36Sopenharmony_ci--------------------------------- 39262306a36Sopenharmony_ciio_unmap() MMIO或IO端口资源,然后调用pci_disable_device()。 39362306a36Sopenharmony_ci这与pci_enable_device()对称相反。 39462306a36Sopenharmony_ci在调用pci_disable_device()后不要访问设备寄存器。 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci释放MMIO/IO端口资源 39862306a36Sopenharmony_ci------------------- 39962306a36Sopenharmony_ci调用pci_release_region()来标记MMIO或IO端口范围为可用。 40062306a36Sopenharmony_ci如果不这样做,通常会导致无法重新加载驱动程序。 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci如何访问PCI配置空间 40662306a36Sopenharmony_ci=================== 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci你可以使用 `pci_(read|write)_config_(byte|word|dword)` 来访问由 40962306a36Sopenharmony_ci`struct pci_dev *` 表示的设备的配置空间。所有这些函数在成功时返回0,或者返回一个 41062306a36Sopenharmony_ci错误代码( `PCIBIOS_...` ),这个错误代码可以通过pcibios_strerror翻译成文本字 41162306a36Sopenharmony_ci符串。大多数驱动程序希望对有效的PCI设备的访问不会失败。 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci如果你没有可用的pci_dev结构体,你可以调用 41462306a36Sopenharmony_ci`pci_bus_(read|write)_config_(byte|word|dword)` 来访问一个给定的设备和该总 41562306a36Sopenharmony_ci线上的功能。 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci如果你访问配置头的标准部分的字段,请使用<linux/pci.h>中声明的位置和位的符号名称。 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_ci如果你需要访问扩展的PCI功能寄存器,只要为特定的功能调用pci_find_capability(), 42062306a36Sopenharmony_ci它就会为你找到相应的寄存器块。 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci其它有趣的函数 42462306a36Sopenharmony_ci============== 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_ci============================= ================================================= 42762306a36Sopenharmony_cipci_get_domain_bus_and_slot() 找到与给定的域、总线和槽以及编号相对应的pci_dev。 42862306a36Sopenharmony_ci 如果找到该设备,它的引用计数就会增加。 42962306a36Sopenharmony_cipci_set_power_state() 设置PCI电源管理状态(0=D0 ... 3=D3 43062306a36Sopenharmony_cipci_find_capability() 在设备的功能列表中找到指定的功能 43162306a36Sopenharmony_cipci_resource_start() 返回一个给定的PCI区域的总线起始地址 43262306a36Sopenharmony_cipci_resource_end() 返回给定PCI区域的总线末端地址 43362306a36Sopenharmony_cipci_resource_len() 返回一个PCI区域的字节长度 43462306a36Sopenharmony_cipci_set_drvdata() 为一个pci_dev设置私有驱动数据指针 43562306a36Sopenharmony_cipci_get_drvdata() 返回一个pci_dev的私有驱动数据指针 43662306a36Sopenharmony_cipci_set_mwi() 启用设备内存写无效 43762306a36Sopenharmony_cipci_clear_mwi() 关闭设备内存写无效 43862306a36Sopenharmony_ci============================= ================================================= 43962306a36Sopenharmony_ci 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci杂项提示 44262306a36Sopenharmony_ci======== 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_ci当向用户显示PCI设备名称时(例如,当驱动程序想告诉用户它找到了什么卡时),请使 44562306a36Sopenharmony_ci用pci_name(pci_dev)。 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_ci始终通过对pci_dev结构体的指针来引用PCI设备。所有的PCI层函数都使用这个标识, 44862306a36Sopenharmony_ci它是唯一合理的标识。除了非常特殊的目的,不要使用总线/插槽/功能号————在有多个 44962306a36Sopenharmony_ci主总线的系统上,它们的语义可能相当复杂。 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci不要试图在你的驱动程序中开启快速寻址周期写入功能。总线上的所有设备都需要有这样 45262306a36Sopenharmony_ci的功能,所以这需要由平台和通用代码来处理,而不是由单个驱动程序来处理。 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci供应商和设备标识 45662306a36Sopenharmony_ci================ 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci不要在include/linux/pci_ids.h中添加新的设备或供应商ID,除非它们是在多个驱 45962306a36Sopenharmony_ci动程序中共享。如果有需要的话,你可以在你的驱动程序中添加私有定义,或者直接使用 46062306a36Sopenharmony_ci普通的十六进制常量。 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ci设备ID是任意的十六进制数字(厂商控制),通常只在一个地方使用,即pci_device_id 46362306a36Sopenharmony_ci表。 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci请务必提交新的供应商/设备ID到https://pci-ids.ucw.cz/。在 46662306a36Sopenharmony_cihttps://github.com/pciutils/pciids,有一个pci.ids文件的镜像。 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci 46962306a36Sopenharmony_ci过时的函数 47062306a36Sopenharmony_ci========== 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_ci当你试图将一个旧的驱动程序移植到新的PCI接口时,你可能会遇到几个函数。它们不再存 47362306a36Sopenharmony_ci在于内核中,因为它们与热插拔或PCI域或具有健全的锁不兼容。 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci================= =================================== 47662306a36Sopenharmony_cipci_find_device() 被pci_get_device()取代 47762306a36Sopenharmony_cipci_find_subsys() 被pci_get_subsys()取代 47862306a36Sopenharmony_cipci_find_slot() 被pci_get_domain_bus_and_slot()取代 47962306a36Sopenharmony_cipci_get_slot() 被pci_get_domain_bus_and_slot()取代 48062306a36Sopenharmony_ci================= =================================== 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci另一种方法是传统的PCI设备驱动,即走PCI设备列表。这仍然是可能的,但不鼓励这样做。 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ciMMIO空间和“写通知” 48662306a36Sopenharmony_ci================== 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_ci将驱动程序从使用I/O端口空间转换为使用MMIO空间,通常需要一些额外的改变。具体来说, 48962306a36Sopenharmony_ci需要处理“写通知”。许多驱动程序(如tg3,acenic,sym53c8xx_2)已经做了这个。I/O 49062306a36Sopenharmony_ci端口空间保证写事务在CPU继续之前到达PCI设备。对MMIO空间的写入允许CPU在事务到达PCI 49162306a36Sopenharmony_ci设备之前继续。HW weenies称这为“写通知”,因为在事务到达目的地之前,写的完成被“通知” 49262306a36Sopenharmony_ci给CPU。 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ci因此,对时间敏感的代码应该添加readl(),CPU在做其他工作之前应该等待。经典的“位脉冲” 49562306a36Sopenharmony_ci序列对I/O端口空间很有效:: 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_ci for (i = 8; --i; val >>= 1) { 49862306a36Sopenharmony_ci outb(val & 1, ioport_reg); /* 置位 */ 49962306a36Sopenharmony_ci udelay(10); 50062306a36Sopenharmony_ci } 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_ci对MMIO空间来说,同样的顺序应该是:: 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ci for (i = 8; --i; val >>= 1) { 50562306a36Sopenharmony_ci writeb(val & 1, mmio_reg); /* 置位 */ 50662306a36Sopenharmony_ci readb(safe_mmio_reg); /* 刷新写通知 */ 50762306a36Sopenharmony_ci udelay(10); 50862306a36Sopenharmony_ci } 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci重要的是, ``safe_mmio_reg`` 不能有任何干扰设备正确操作的副作用。 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci另一种需要注意的情况是在重置PCI设备时。使用PCI配置空间读数来刷新writeel()。如果预期 51362306a36Sopenharmony_ciPCI设备不响应readl(),这将在所有平台上优雅地处理PCI主控器的中止。大多数x86平台将允许 51462306a36Sopenharmony_ciMMIO读取主控中止(又称“软失败”),并返回垃圾(例如~0)。但许多RISC平台会崩溃(又称“硬失败”)。 515