[Linux Kernel] PCI BUS (1) 官方文档 pci.rst

How To Write Linux PCI Drivers

   PCI的世界是巨大的,充满(大多是不愉快的)惊喜。 由于每种CPU体系结构实现不同的芯片组,并且PCI设备具有不同的要求(erm,“功能”),因此结果是Linux内核对PCI的支持并不像希望的那么简单。 本简短的文章试图向所有潜在的驱动程序作者介绍用于PCI设备驱动程序的Linux API。

   更完整的资源是Jonathan Corbet,Alessandro Rubini和Greg Kroah-Hartman撰写的第三版“ Linux设备驱动程序”。 LDD3可从以下站点免费获得(根据知识共享许可):http://lwn.net/Kernel/LDD3/.

   但是,请记住,所有文档都受“bit rot”的影响。 如果无法按此处所述操作,请参考源代码。

Structure of PCI drivers

   PCI驱动程序通过 pci_register_driver()“发现”系统中的PCI设备。 实际上,这是另一回事。 当PCI通用代码发现新设备时,将通知具有匹配“描述”的驱动程序。 详细信息如下。

   pci_register_driver()将大多数设备探测留给PCI层,并支持设备的在线插入/移除(因此在单个驱动程序中支持热插拔PCI,CardBus和Express-Card)。 调用pci_register_driver()要求传递函数指针表,从而决定了驱动程序的高级结构。

一旦驱动程序发现了PCI设备并获得了所有权,驱动程序通常需要执行以下初始化:

  • Enable the device
  • Request MMIO/IOP resources
  • Set the DMA mask size (for both coherent and streaming DMA)
  • Allocate and initialize shared control data (pci_allocate_coherent())
  • Access device configuration space (if needed)
  • Register IRQ handler (request_irq())
  • Initialize non-PCI (i.e. LAN/SCSI/etc parts of the chip)
  • Enable DMA/processing engines

使用完设备后,也许需要卸载模块,驱动程序需要采取以下步骤:

  • Disable the device from generating IRQs
  • Release the IRQ (free_irq())
  • Stop all DMA activity
  • Release DMA buffers (both streaming and coherent)
  • Unregister from other subsystems (e.g. scsi or netdev)
  • Release MMIO/IOP resources
  • Disable the device

以下各节将介绍其中大多数主题。 其余的请看LDD3或<linux / pci.h>。

如果未配置PCI子系统(未设置CONFIG_PCI),则以下描述的大多数PCI函数都被定义为内联函数,这些函数要么完全为空,要么只是返回适当的错误代码,以避免驱动程序中出现许多ifdef。

pci_register_driver() call

   PCI设备驱动程序在初始化期间使用指向描述该驱动程序的结构的指针(“ struct pci_driver”)调用“ pci_register_driver()”:

… kernel-doc:: include/linux/pci.h
:functions: pci_driver

   ID table是一个以 NULL 结尾的struct pci_device_id的数组。 通常首选带有 static const 的定义。

… kernel-doc:: include/linux/mod_devicetable.h
:functions: pci_device_id

   大多数驱动程序只需要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 and subdevice fields default to PCI_ANY_ID (FFFFFFFF)
  • class and classmask fields default to 0
  • driver_data defaults to 0UL.

   请注意,driver_data必须与驱动程序中定义的任何pci_device_id条目使用的值匹配。 如果所有pci_device_id条目都具有非零的driver_data值,则这将driver_data字段为必填字段。

   添加后,将为在其(新更新的)pci_ids列表中列出的所有无人认领的PCI设备调用驱动程序探测例程。

   驱动程序退出时,它仅调用pci_unregister_driver(),PCI层将自动为该驱动程序处理的所有设备调用remove挂钩。

“Attributes” for driver functions/data

请在适当的地方标记初始化和清除功能(相应的宏在<linux / init.h>中定义):

======    =================================================
__init    Initialization code. Thrown away after the driver initializes.
__exit    Exit code. Ignored for non-modular drivers.
======    =================================================

有关何时/何处使用上述属性的提示:

  • The module_init()/module_exit() functions (and all initialization functions called only from these) should be marked __init/__exit.

  • Do not mark the struct pci_driver.

  • Do NOT mark a function if you are not sure which mark to use. Better to not mark the function than mark the function wrong.

How to find PCI devices manually

   PCI驱动程序应该有一个很好的理由不使用pci_register_driver()接口来搜索PCI设备。 PCI设备受多个驱动程序控制的主要原因是,一个PCI设备实现了几种不同的硬件服务。 例如。 组合的串行/并行端口/软盘控制器。

可以使用以下结构执行手动搜索:

  1. 按供应商和设备ID搜索:
struct pci_dev *dev = NULL;
while (dev = pci_get_device(VENDOR_ID, DEVICE_ID, dev))
	configure_device(dev);
  1. 按Class ID搜索(以类似方式重复):
pci_get_class(CLASS_ID, dev)
  1. 通过供应商/设备和子系统供应商/设备ID进行搜索:
pci_get_subsys(VENDOR_ID,DEVICE_ID, SUBSYS_VENDOR_ID, SUBSYS_DEVICE_ID, dev)

您可以将常量PCI_ANY_ID用作VENDOR_ID或DEVICE_ID的通配符替换。 例如,这允许搜索来自特定供应商的任何设备。

这些功能是热插拔安全的。 它们增加返回的pci_dev上的引用计数。 您最终必须(可能在模块卸载时)通过调用pci_dev_put()来减少这些设备上的引用计数。

Device Initialization Steps

如简介中所述,大多数PCI驱动程序需要以下步骤来进行设备初始化:

  • Enable the device
  • Request MMIO/IOP resources
  • Set the DMA mask size (for both coherent and streaming DMA)
  • Allocate and initialize shared control data (pci_allocate_coherent())
  • Access device configuration space (if needed)
  • Register IRQ handler (request_irq())
  • Initialize non-PCI (i.e. LAN/SCSI/etc parts of the chip)
  • Enable DMA/processing engines.

   驱动程序可以随时访问PCI配置空间寄存器。 (好吧,几乎。运行BIST时,配置空间可能会消失……但这只会导致PCI总线主设备中止,而配置读取将返回垃圾)。

Enable the PCI device

在触摸任何设备寄存器之前,驱动程序需要通过调用pci_enable_device()来启用PCI设备。 这将:

  • wake up the device if it was in suspended state,
  • allocate I/O and memory regions of the device (if BIOS did not),
  • allocate an IRQ (if BIOS did not).

注意:
  pci_enable_device()可能会失败! 检查返回值。

警告:
  操作系统错误:启用这些资源之前,我们不会检查资源分配。 如果我们调用该序列,将更有意义在调用pci_enable_device()之前先调用pci_request_resources()。 当前,当两个设备分配了相同的范围时,设备驱动程序无法检测到该错误。 这不是一个普遍的问题,不太可能很快得到解决。

之前已经讨论过,但是从2.6.19开始没有更改:http://lkml.org/lkml/2006/3/2/194

   pci_set_master()将通过设置PCI_COMMAND寄存器中的总线主控位来启用DMA。 如果BIOS将其设置为虚假值,它还可以修复延迟计时器值。 pci_clear_master()将通过清除总线主控位来禁用DMA。

   如果PCI设备可以使用PCI Memory-Write-Invalidate事务,请调用pci_set_mwi()。 这将使能Mem-Wr-Inval的PCI_COMMAND位,并确保正确设置了缓存行大小寄存器。 检查pci_set_mwi()的返回值,因为并非所有架构或芯片组都可能支持Memory-Write-Invalidate。 或者,如果Mem-Wr-Inval很好但不是必需的,则调用pci_try_set_mwi()以使系统尽最大努力启用Mem-Wr-Inval。

Request MMIO/IOP resources

   不应直接从PCI设备配置空间读取内存(MMIO)和I / O端口地址。 使用pci_dev结构中的值,因为特定于架构/芯片组的内核支持可能已将PCI“总线地址”重新映射为“主机物理”地址。

   有关如何访问设备寄存器或设备内存的信息,请参见Documentation / io-mapping.txt

   设备驱动程序需要调用pci_request_region()以确认没有其他设备已经在使用相同的地址资源。 相反,驱动程序应在调用pci_disable_device()之后再调用pci_release_region()。

这样做的目的是防止两个设备在同一地址范围内发生冲突。

提示:
  请参阅上面的OS BUG评论。 当前(2.6.19),驱动程序只能在调用pci_enable_device()之后确定MMIO和IO端口资源的可用性。

   pci_request_region()的通用类型是request_mem_region()(用于MMIO范围)和request_region()(用于IO端口范围)。 将它们用于“普通” PCI BAR未描述的地址资源。

另请参见下面的pci_request_selected_regions()。

Set the DMA mask size

注意:
  如果看完没有任何感觉,请参阅Documentation / DMA-API.txt。 本节仅提醒您,驱动程序需要指示设备的DMA功能,而不是DMA接口的权威来源。

   尽管所有驱动程序都应明确指示PCI总线主控器的DMA功能(例如32或64位),但具有超过32位总线主控器用于流数据的设备需要驱动程序通过使用以下命令调用pci_set_dma_mask()来“注册”该功能适当的参数。 通常,这允许系统RAM在4G物理地址以上的系统上使用更有效的DMA。

所有PCI-X和PCIe兼容设备的驱动程序必须调用 pci_set_dma_mask() ,因为它们是64位DMA设备。

   同样,如果设备可以通过调用 pci_set_consistent_dma_mask() 直接在4G物理地址以上的系统RAM中直接寻址“一致内存”,则驱动程序也必须“注册”此功能。

   同样,它包括所有PCI-X和PCIe兼容设备的驱动程序。 许多64位“ PCI”设备(在PCI-X之前)和某些PCI-X设备都是64位DMA,能够处理有效载荷(“流”)数据,但不能控制(“一致”)数据。

Setup shared control data

   设置DMA掩码后,驱动程序可以分配“一致”(又称为共享)内存。 有关DMA API的完整说明,请参见Documentation / DMA-API.txt。 本节仅提醒您在设备上启用DMA之前需要完成此操作。

Initialize device registers

   一些驱动程序可能需要特定的“capability”字段,或是“vendor specific”的寄存器进行 initialized or reset。例如清除中断 pending 位。

Register IRQ handler

   虽然调用request_irq()是此处描述的最后一步,但这通常只是初始化设备的另一个中间步骤。 通常可以将此步骤推迟到打开设备使用之前。

   IRQ线路的所有中断处理程序都应向IRQF_SHARED注册,并使用该分隔符将IRQ映射到设备(请记住,所有PCI IRQ线路都可以共享)。

   request_irq()会将中断处理程序和设备句柄与中断号关联。 从历史上看,中断号代表从PCI设备到中断控制器的IRQ线。 对于MSI和MSI-X(在下文中有更多介绍),中断号是一个CPU“vector”。

   request_irq()也启用中断。 在注册中断处理程序之前,请确保设备已停顿并且没有任何待处理的中断。

   MSI和MSI-X是PCI功能。 两者都是“消息信号中断”,它们通过对本地APIC的DMA写操作将中断传递给CPU。 MSI和MSI-X之间的根本区别是如何分配多个“vectors”。 MSI需要vectors的连续块,而MSI-X可以分配几个独立的(不连续)向量。

   可以通过在调用request_irq()之前使用具有PCI_IRQ_MSI和/或PCI_IRQ_MSIX标志的pci_alloc_irq_vectors()来启用MSI功能。 这导致PCI支持将CPU向量数据编程到PCI设备功能寄存器中。 许多体系结构,芯片组或BIOS不支持MSI或MSI-X,仅使用PCI_IRQ_MSI和PCI_IRQ_MSIX标志调用pci_alloc_irq_vectors将会失败,因此请尝试始终也指定PCI_IRQ_LEGACY。

   对于MSI / MSI-X和旧版INTx具有不同中断处理程序的驱动程序,应在调用pci_alloc_irq_vectors之后基于pci_dev结构中的msi_enabled和msix_enabled标志选择正确的驱动程序。

使用MSI至少有两个非常好的理由:

  • 根据定义,MSI是专用中断向量。 这意味着中断处理程序不必验证引起中断的设备。
  • MSI避免了DMA / IRQ竞争条件。 交付MSI时,可以确保对主机内存的DMA对主机CPU是可见的。 这对于数据一致性和避免过时的控制数据都很重要。 此保证允许驱动程序省略MMIO读取以刷新DMA流。

有关MSI / MSI-X用法的示例,请参阅drivers / infiniband / hw / mthca /或drivers / net / tg3.c。

PCI device shutdown

卸载PCI设备驱动程序时,需要执行以下大多数步骤:

  • Disable the device from generating IRQs
  • Release the IRQ (free_irq())
  • Stop all DMA activity
  • Release DMA buffers (both streaming and consistent)
  • Unregister from other subsystems (e.g. scsi or netdev)
  • Disable device from responding to MMIO/IO Port addresses
  • Release MMIO/IO Port resource(s)

Stop IRQs on the device

   如何执行此操作是特定于芯片/设备的。 如果未完成,则在(且仅当)IRQ与其他设备共享时,才可能出现“尖叫中断”。

   When the shared IRQ handler is “unhooked”, the remaining devices using the same IRQ line will still need the IRQ enabled. Thus if the “unhooked” device asserts IRQ line, the system will respond assuming it was one of the remaining devices asserted the IRQ line. Since none of the other devices will handle the IRQ, the system will “hang” until it decides the IRQ isn’t going to get handled and masks the IRQ (100,000 iterations later). Once the shared IRQ is masked, the remaining devices will stop functioning properly. Not a nice situation.(简单来说就是没有 driver 来应答中断,系统会相应10W次中断后关掉这个中断)

   这是使用MSI或MSI-X(如果可用)的另一个原因。 MSI和MSI-X被定义为互斥中断,因此不易受到“尖叫中断”问题的影响。

Release the IRQ

   Once the device is quiesced (no more IRQs), one can call free_irq(). This function will return control once any pending IRQs are handled, “unhook” the drivers IRQ handler from that IRQ, and finally release the IRQ if no one else is using it.

设备停顿后(不再有IRQ),可以调用free_irq()。 一旦处理了任何未决的IRQ,此函数将返回控制,从该IRQ中“解钩”驱动程序IRQ处理程序,如果没有其他人正在使用它,则最终释放IRQ。

Stop all DMA activity

   在尝试取消分配DMA控制数据之前,停止所有DMA操作非常重要。 否则可能会导致内存损坏,挂起,并在某些芯片组上造成严重崩溃。

   在停止IRQ之后停止DMA可以避免IRQ处理程序可能重新启动DMA引擎的竞赛。

   尽管这一步骤听起来很明显且微不足道,但一些“成熟”的驱动程序过去并没有正确执行此步骤。

Release DMA buffers

   Once DMA is stopped, clean up streaming DMA first. I.e. unmap data buffers and return buffers to “upstream” owners if there is one.DMA停止后,请先清除流DMA。 即 取消映射数据缓冲区,并将缓冲区返回给“上游”所有者(如果有)。

   Then clean up “consistent” buffers which contain the control data. 然后清除包含控制数据的“一致”缓冲区。

See Documentation/DMA-API.txt for details on unmapping interfaces.

Unregister from other subsystems

   大多数低级PCI设备驱动程序都支持其他一些子系统,例如USB,ALSA,SCSI,NetDev,Infiniband等。请确保您的驱动程序不会从该其他子系统上丢失资源。 如果发生这种情况,那么当子系统尝试调用已卸载的驱动程序时,通常的症状是“糟糕”。

Disable Device from responding to MMIO/IO Port addresses

  • io_unmap() MMIO or IO Port resources and then call pci_disable_device().
  • This is the symmetric opposite of pci_enable_device().
  • Do not access device registers after calling pci_disable_device().

Release MMIO/IO Port Resource(s)

   调用pci_release_region()以将MMIO或IO端口范围标记为可用。
否则,通常会导致无法重新加载驱动程序。

How to access PCI config space

   您可以使用pci_(read | write)_config_(byte | word | dword)来访问由struct pci_dev *表示的设备的配置空间。 所有这些函数均在成功时返回 0 或错误代码(PCIBIOS_...),可通过 pcibios_strerror 将其转换为文本字符串。 大多数驱动程序期望对有效PCI设备的访问不会失败。

   如果没有可用的struct pci_dev结构,则可以调用pci_bus_(read | write)_config_(byte | word | dword)来访问该总线上的给定设备和功能。

   If you access fields in the standard portion of the config header, please use symbolic names of locations and bits declared in <linux/pci.h>.

   如果您需要访问扩展PCI功能寄存器,只需为特定功能调用pci_find_capability(),它将为您找到相应的寄存器块。

Other interesting functions

FunctionsDescription
pci_get_domain_bus_and_slot()查找与给定域,总线,插槽和编号相对应的pci_dev。 如果找到该设备,则其引用计数会增加
pci_set_power_state()设置 PCI 电源管理状态(0 = D0 … 3 = D3)
pci_find_capability()在设备的功能列表中找到指定的功能。
pci_resource_start()返回给定PCI区域的总线起始地址
pci_resource_end()返回给定PCI区域的总线末端地址
pci_resource_len()返回PCI区域的字节长度
pci_set_drvdata()设置pci_dev的私有驱动程序数据指针 (private driver data)
pci_get_drvdata()返回pci_dev的私有驱动程序数据指针 (见上)
pci_set_mwi()Enable Memory-Write-Invalidate transactions.
pci_clear_mwi()Disable Memory-Write-Invalidate transactions.

Miscellaneous hints

   向用户显示PCI设备名称时(例如,当驱动程序要告诉用户找到了哪个卡时),请使用pci_name(pci_dev)。

   始终通过指向pci_dev结构的指针来引用PCI设备。 所有PCI层功能都使用此标识,这是唯一合理的标识。 除非出于特殊目的,否则请勿使用 bus/slot/function numbers - 在具有多个主总线的系统上,其语义可能非常复杂。

   不要尝试在驱动程序中打开“Fast Back to Back writes”。 总线上的所有设备都必须能够执行此操作,因此这需要平台和通用代码(而不是单个驱动程序)来处理。

Vendor and device identifications

   除非在多个驱动程序之间共享它们,否则不要将新设备或供应商ID添加到include / linux / pci_ids.h。 您可以在驱动程序中添加私有定义(如果有帮助的话),或者仅使用普通十六进制常量。

   设备ID是任意十六进制数字(由供应商控制),通常仅在单个位置pci_device_id表中使用。

Please DO submit new vendor/device IDs to http://pci-ids.ucw.cz/.
There are mirrors of the pci.ids file at http://pciids.sourceforge.net/
and https://github.com/pciutils/pciids
.

Obsolete functions

   There are several functions which you might come across when trying to port an old driver to the new PCI interface. They are no longer present in the kernel as they aren’t compatible with hotplug or PCI domains or having sane locking.

ObsoleteSuperseded
pci_find_device()pci_get_device()
pci_find_subsys()Superseded by pci_get_subsys()
pci_find_slot()pci_get_domain_bus_and_slot()
pci_get_slot()pci_get_domain_bus_and_slot()

The alternative is the traditional PCI device driver that walks PCI device lists. This is still possible but discouraged.

MMIO Space and “Write Posting”

Converting a driver from using I/O Port space to using MMIO space often requires some additional changes. Specifically, “write posting” needs to be handled. Many drivers (e.g. tg3, acenic, sym53c8xx_2) already do this. I/O Port space guarantees write transactions reach the PCI device before the CPU can continue. Writes to MMIO space allow the CPU to continue before the transaction reaches the PCI device. HW weenies call this “Write Posting” because the write completion is “posted” to the CPU before the transaction has reached its destination.

这里的描述好像和 spec 有点不同,描述的应该就是 spec 上的 Memory Write 是 Posted。

Thus, timing sensitive code should add readl() where the CPU is expected to wait before doing other work. The classic “bit banging” sequence works fine for I/O Port space:

for (i = 8; --i; val >>= 1) {
	outb(val & 1, ioport_reg);      /* write bit */
	udelay(10);
}

时序敏感的 code 需要添加 readl() 来保证正确性。
The same sequence for MMIO space should be:

for (i = 8; --i; val >>= 1) {
	writeb(val & 1, mmio_reg);      /* write bit */
	readb(safe_mmio_reg);           /* flush posted write */
	udelay(10);
}

It is important that “safe_mmio_reg” not have any side effects that interferes with the correct operation of the device.

Another case to watch out for is when resetting a PCI device. Use PCI Configuration space reads to flush the writel(). This will gracefully handle the PCI master abort on all platforms if the PCI device is expected to not respond to a readl(). Most x86 platforms will allow MMIO reads to master abort (a.k.a. “Soft Fail”) and return garbage (e.g. ~0). But many RISC platforms will crash (a.k.a.“Hard Fail”).

参考

  • linux-5.4.37/Documentation/PCI/pci.rst
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Traditional multi-drop, parallel bus technology is approaching its practical performance limits. It is clear that balancing system performance requires I/O bandwidth to scale with processing and application demands. There is an industry mandate to re-engineer I/O connectivity within cost constraints. PCI Express comprehends the many I/O requirements presented across the spectrum of computing and communications platforms, and rolls them into a common scalable and extensible I/O industry specification. Alongside these increasing performance demands, the enterprise server and communications markets have the need for improved reliability, security, and quality of service guarantees. This specification will therefore be applicable to multiple market segments. Technology advances in high-speed, point-to-point interconnects enable us to break away from the bandwidth limitations of multi-drop, parallel buses. The PCI Express basic physical layer consists of a differential transmit pair and a differential receive pair. Dual simplex data on these point-to-point connections is self-clocked and its bandwidth increases linearly with interconnect width and frequency. PCI Express takes an additional step of including a message space within its bus protocol that is used to implement legacy “side- band” signals. This further reduction of signal pins produces a very low pin count connection for components and adapters. The PCI Express Transaction, Data Link, and Physical Layers are optimized for chip-to-chip and board-to-board interconnect applications. An inherent limitation of today’s PCI-based platforms is the lack of support for isochronous data delivery, an attribute that is especially important to streaming media applications. To enable these emerging applications, PCI Express adds a virtual channel mechanism. In addition to use for support of isochronous traffic, the virtual channel mechanism provides an infrastructure for future extensions in supporting new applications. By adhering to the PCI Software Model, today’s applications are easily migrated even as emerging applications are enabled.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值