Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,139 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/PCI/acpi-info.rst
:翻译:
司延腾 Yanteng Si <siyanteng@loongson.cn>
:校译:
=====================
PCI主桥的ACPI注意事项
=====================
一般的规则是,ACPI命名空间应该描述操作系统可能使用的所有东西,除非有其他方法让操作系
统找到它[1, 2]。
例如,没有标准的硬件机制来枚举PCI主桥,所以ACPI命名空间必须描述每个主桥、访问它
下面的PCI配置空间的方法、主桥转发到PCI的地址空间窗口(使用_CRS)以及传统的INTx
中断的路由(使用_PRT)。
在主桥下面的PCI设备,通常不需要通过ACPI描述。操作系统可以通过标准的PCI枚举机制来
发现它们,使用配置访问来发现和识别设备,并读取和测量它们的BAR。然而,如果ACPI为它们
提供电源管理或热插拔功能,或者如果设备有由平台中断控制器连接的INTx中断,需要一个_PRT
来描述这些连接,这种情况下ACPI可以描述PCI设备。
ACPI资源描述是通过ACPI命名空间中设备的_CRS对象完成的[2]。_CRS就像一个通用的PCI BAR:
操作系统可以读取_CRS并找出正在消耗的资源,即使它没有该设备的驱动程序[3]。这一点很重要,
因为它意味着一个旧的操作系统可以正确地工作,即使是在操作系统不知道的新设备的系统上。新设
备可能什么都不做,但操作系统至少可以确保没有资源与它们冲突。
像MCFG、HPET、ECDT等静态表,不是保留地址空间的机制。静态表是在操作系统在启动初期且在它
能够解析ACPI命名空间之前需要知道的东西。如果定义了一个新的表,即使旧的操作系统忽略了这
个表,它也需要正常运行。_CRS允许这样做,因为它是通用的,可以被旧的操作系统解析;而静态表
则不允许。
如果操作系统要管理一个通过ACPI描述的不可发现的设备,该设备将有一个特定的_HID/_CID,以
告诉操作系统与之绑定的驱动程序,并且_CRS告诉操作系统和驱动程序该设备的寄存器在哪里。
PCI主桥是PNP0A03或PNP0A08设备。它们的_CRS应该描述它们所消耗的所有地址空间。这包括它
们转发到PCI总线上的所有窗口,以及不转发到PCI的主桥本身的寄存器。主桥的寄存器包括次要/下
级总线寄存器,决定了桥下面的总线范围,窗口寄存器描述了桥洞,等等。这些都是设备相关的,非
架构相关的东西,所以PNP0A03/PNP0A08驱动可以管理它们的唯一方法是通过_PRS/_CRS/_SRS,
它包含了特定于设备的细节。主桥寄存器也包括ECAM空间,因为它是由主桥消耗的。
ACPI定义了一个Consumer/Producer位来区分桥寄存器(“Consumer”下文译作消费者)和
桥洞(“Producer”下文译作生产者)[4, 5],但是早期的BIOS没有正确使用这个位。其结果
是,目前的ACPI规范只为扩展地址空间描述符定义了消费者/生产者;在旧的QWord/Word/Word地
址空间描述符中,该位应该被忽略。因此,操作系统必须假定所有的QWord/Word/Word描述符都是
窗口。
在增加扩展地址空间描述符之前,消费者/生产者的失败意味着没有办法描述PNP0A03/PNP0A08设
备本身的桥寄存器。解决办法是在PNP0C02捕捉器中描述桥寄存器(包括ECAM空间)[6]。
除了ECAM之外,桥寄存器空间反正是特定于设备的,所以通用的PNP0A03/PNP0A08驱动程
序(pci_root.c)没有必要了解它。
新的架构应该能够在PNP0A03设备中使用“消费者”扩展地址空间描述符,用于桥寄存器,包括
ECAM,尽管对[6]的严格解释可能禁止这样做。旧的x86和ia64内核假定所有的地址空间描述
符,包括“消费者”扩展地址空间的描述符,都是窗口,所以在这些架构上以这种方式描述桥寄
存器是不安全的。
PNP0C02“主板”设备基本上是万能的。除了“不要将这些资源用于其他用途”之外,没有其他的编
程模型。因此,PNP0C02 _CRS应该声明ACPI命名空间中(1)没有被_CRS声明的任何其他设备对
象的地址空间,(2)不应该被OS分配给其他东西。
除非有一个标准的固件接口用于配置访问,例如ia64 SAL接口[7],否则PCIe规范要求使用增强
型配置访问方法(ECAM)。主桥消耗ECAM内存地址空间并将内存访问转换为PCI配置访问。该规范
定义了ECAM地址空间的布局和功能;只有地址空间的基础是特定于设备的。ACPI操作系统从静态
MCFG表或PNP0A03设备中的_CBA方法中了解基础地址。
MCFG表必须描述非热插拔主桥的ECAM空间[8]。由于MCFG是一个静态表,不能通过热插拔更新,
PNP0A03设备中的_CBA方法描述了可热插拔主桥的ECAM空间[9]。请注意,对于MCFG和_CBA,
基址总是对应于总线0,即使桥器下面的总线范围(通过_CRS报告)不从0开始。
[1] ACPI 6.2, sec 6.1:
对于任何在非枚举类型的总线上的设备(例如,ISA总线),OSPM会枚举设备的标识符,ACPI
系统固件必须为每个设备提供一个_HID对象...以使OSPM能够做到这一点。
[2] ACPI 6.2, sec 3.7:
操作系统枚举主板设备时,只需通过读取ACPI命名空间来寻找具有硬件ID的设备。
ACPI枚举的每个设备都包括ACPI命名空间中ACPI定义的对象,该对象报告设备可能占用的硬
件资源[_PRS],报告设备当前使用的资源[_CRS]的对象,以及配置这些资源的对象[_SRS]。
这些信息被即插即用操作系统(OSPM)用来配置设备。
[3] ACPI 6.2, sec 6.2:
OSPM使用设备配置对象来配置通过ACPI列举的设备的硬件资源。设备配置对象提供了关于当前
和可能的资源需求的信息,共享资源之间的关系,以及配置硬件资源的方法。
当OSPM枚举一个设备时,它调用_PRS来确定该设备的资源需求。它也可以调用_CRS来找到该设
备的当前资源设置。利用这些信息,即插即用系统决定设备应该消耗什么资源,并通过调用设备
的_SRS控制方法来设置这些资源。
在ACPI中,设备可以消耗资源(例如,传统的键盘),提供资源(例如,一个专有的PCI桥),
或者两者都做。除非另有规定,设备的资源被假定为来自设备层次结构中设备上方最近的匹配资
源。
[4] ACPI 6.2, sec 6.4.3.5.1, 2, 3, 4:
QWord/DWord/Word 地址空间描述符 (.1, .2, .3)
常规标志: Bit [0] 被忽略。
扩展地址空间描述符 (.4)
常规标志: Bit [0] 消费者/生产者:
* 1 – 这个设备消费这个资源
* 0 – 该设备生产和消费该资源
[5] ACPI 6.2, sec 19.6.43:
ResourceUsage指定内存范围是由这个设备(ResourceConsumer)消费还是传递给子设备
(ResourceProducer)。如果没有指定,那么就假定是ResourceConsumer。
[6] PCI Firmware 3.2, sec 4.1.2:
如果操作系统不能原生的懂得保留MMCFG区域,MMCFG区域必须由固件保留。在MCFG表中或通
过_CBA方法(见第4.1.3节)报告的地址范围必须通过声明主板资源来保留。对于大多数系统,
主板资源将出现在ACPI命名空间的根部(在_SB下),在一个节点的_HID为EISAID(PNP0C0
2),在这种情况下的资源不应该要求在根PCI总线的_CRS。这些资源可以选择在Int15 E820
或EFIGetMemoryMap中作为保留内存返回,但必须始终通过ACPI作为主板资源报告。
[7] PCI Express 4.0, sec 7.2.2:
对于PC兼容的系统,或者没有实现允许访问配置空间的处理器架构特定固件接口标准的系统,需
要使用本节中定义的ECAM。
[8] PCI Firmware 3.2, sec 4.1.2:
MCFG表是一个ACPI表,用于沟通的基础地址对应的非热的可移动的PCI段组范围内的PCI段组在
启动时提供给操作系统。这对PC兼容系统来说是必需的。
MCFG表仅用于沟通在启动时系统可用的PCI段组对应的基址。
[9] PCI Firmware 3.2, sec 4.1.3:
_CBA (Memory mapped Configuration Base Address) 控制方法是一个可选的ACPI对
象,用于返回热插拔主桥的64位内存映射的配置基址。_CBA 返回的基址是与处理器相关的地址。
_CBA 控制方法被评估为一个整数。
这个控制方法出现在主桥对象下。当_CBA方法出现在一个活动的主桥对象下时,操作系统会评
估这个结构,以确定内存映射的配置基址,对应于_CRS方法中指定的总线编号范围的PCI段组。
一个包含_CBA方法的ACPI命名空间对象也必须包含一个相应的_SEG方法。
@@ -0,0 +1,34 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/PCI/index.rst
:翻译:
司延腾 Yanteng Si <siyanteng@loongson.cn>
:校译:
===================
Linux PCI总线子系统
===================
.. toctree::
:maxdepth: 2
:numbered:
pci
pciebus-howto
pci-iov-howto
msi-howto
sysfs-pci
acpi-info
Todolist:
* pci-error-recovery
* pcieaer-howto
* endpoint/index
* boot-interrupts
@@ -0,0 +1,244 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: <isonum.txt>
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/PCI/msi-howto.rst
:翻译:
司延腾 Yanteng Si <siyanteng@loongson.cn>
:校译:
===========
MSI驱动指南
===========
:作者: Tom L Nguyen; Martine Silbermann; Matthew Wilcox
:版权: 2003, 2008 Intel Corporation
关于本指南
==========
本指南介绍了消息标记中断(MSI)的基本知识,使用MSI相对于传统中断机制的优势,如何
改变你的驱动程序以使用MSI或MSI-X,以及在设备不支持MSI时可以尝试的一些基本诊断方法。
什么是MSI?
==========
信息信号中断是指从设备写到一个特殊的地址,导致CPU接收到一个中断。
MSI能力首次在PCI 2.2中规定,后来在PCI 3.0中得到增强,允许对每个中断进行单独屏蔽。
MSI-X功能也随着PCI 3.0被引入。它比MSI支持每个设备更多的中断,并允许独立配置中断。
设备可以同时支持MSI和MSI-X,但一次只能启用一个。
为什么用MSI?
============
有三个原因可以说明为什么使用MSI比传统的基于针脚的中断有优势。
基于针脚的PCI中断通常在几个设备之间共享。为了支持这一点,内核必须调用每个与中断相
关的中断处理程序,这导致了整个系统性能的降低。MSI从不共享,所以这个问题不会出现。
当一个设备将数据写入内存,然后引发一个基于引脚的中断时,有可能在所有的数据到达内存
之前,中断就已经到达了(这在PCI-PCI桥后面的设备中变得更有可能)。为了确保所有的数
据已经到达内存中,中断处理程序必须在引发中断的设备上读取一个寄存器。PCI事务排序规
则要求所有的数据在返回寄存器的值之前到达内存。使用MSI可以避免这个问题,因为中断产
生的写入不能通过数据写入,所以当中断发生时,驱动程序知道所有的数据已经到达内存中。
PCI设备每个功能只能支持一个基于引脚的中断。通常情况下,驱动程序必须查询设备以找出
发生了什么事件,这就减慢了对常见情况的中断处理。有了MSI,设备可以支持更多的中断,
允许每个中断被专门用于不同的目的。一种可能的设计是给不经常发生的情况(如错误)提供
自己的中断,这使得驱动程序可以更有效地处理正常的中断处理路径。其他可能的设计包括给
网卡的每个数据包队列或存储控制器的每个端口提供一个中断。
如何使用MSI
===========
PCI设备被初始化为使用基于引脚的中断。设备驱动程序必须将设备设置为使用MSI或MSI-X。
并非所有的机器都能正确地支持MSI,对于这些机器,下面描述的API将简单地失败,设备将
继续使用基于引脚的中断。
加入内核对MSI的支持
-------------------
为了支持MSI或MSI-X,内核在构建时必须启用CONFIG_PCI_MSI选项。这个选项只在某些架
构上可用,而且它可能取决于其他一些选项的设置。例如,在x86上,你必须同时启用X86_UP_APIC
或SMP,才能看到CONFIG_PCI_MSI选项。
使用MSI
-------
大部分沉重的工作是在PCI层为驱动程序完成的。驱动程序只需要请求PCI层为这个设备设置
MSI功能。
要自动使用MSI或MSI-X中断向量,请使用以下函数::
int pci_alloc_irq_vectors(struct pci_dev *dev, unsigned int min_vecs,
unsigned int max_vecs, unsigned int flags);
它为一个PCI设备分配最多至max_vecs的中断向量。它返回分配的向量数量或一个负的错误。
如果设备对最小数量的向量有要求,驱动程序可以传递一个min_vecs参数,设置为这个限制,
如果PCI核不能满足最小数量的向量,将返回-ENOSPC。
flags参数用来指定设备和驱动程序可以使用哪种类型的中断(PCI_IRQ_INTX, PCI_IRQ_MSI,
PCI_IRQ_MSIX)。一个方便的短语(PCI_IRQ_ALL_TYPES)也可以用来要求任何可能的中断类型。
如果PCI_IRQ_AFFINITY标志被设置,pci_alloc_irq_vectors()将把中断分散到可用的CPU上。
要获得传递给require_irq()和free_irq()的Linux IRQ号码和向量,请使用以下函数::
int pci_irq_vector(struct pci_dev *dev, unsigned int nr);
在删除设备之前,应使用以下功能释放任何已分配的资源::
void pci_free_irq_vectors(struct pci_dev *dev);
如果一个设备同时支持MSI-X和MSI功能,这个API将优先使用MSI-X,而不是MSI。MSI-X支
持1到2048之间的任何数量的中断。相比之下,MSI被限制为最多32个中断(而且必须是2的幂)。
此外,MSI中断向量必须连续分配,所以系统可能无法为MSI分配像MSI-X那样多的向量。在一
些平台上,MSI中断必须全部针对同一组CPU,而MSI-X中断可以全部针对不同的CPU。
如果一个设备既不支持MSI-X,也不支持MSI,它就会退回到一个传统的IRQ向量。
MSI或MSI-X中断的典型用法是分配尽可能多的向量,可能达到设备支持的极限。如果nvec大于
设备支持的数量,它将自动被限制在支持的限度内,所以没有必要事先查询支持的向量的数量。::
nvec = pci_alloc_irq_vectors(pdev, 1, nvec, PCI_IRQ_ALL_TYPES)
if (nvec < 0)
goto out_err;
如果一个驱动程序不能或不愿意处理可变数量的MSI中断,它可以要求一个特定数量的中断,将该
数量作为“min_vecs“和“max_vecs“参数传递给pci_alloc_irq_vectors()函数。::
ret = pci_alloc_irq_vectors(pdev, nvec, nvec, PCI_IRQ_ALL_TYPES);
if (ret < 0)
goto out_err;
上述请求类型的最臭名昭著的例子是为一个设备启用单一的MSI模式。它可以通过传递两个1作为
'min_vecs'和'max_vecs'来实现::
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES);
if (ret < 0)
goto out_err;
一些设备可能不支持使用传统的线路中断,在这种情况下,驱动程序可以指定只接受MSI或MSI-X。::
nvec = pci_alloc_irq_vectors(pdev, 1, nvec, PCI_IRQ_MSI | PCI_IRQ_MSIX);
if (nvec < 0)
goto out_err;
传统API
-----------
以下用于启用和禁用MSI或MSI-X中断的旧API不应该在新代码中使用::
pci_enable_msi() /* deprecated */
pci_disable_msi() /* deprecated */
pci_enable_msix_range() /* deprecated */
pci_enable_msix_exact() /* deprecated */
pci_disable_msix() /* deprecated */
此外,还有一些API来提供支持的MSI或MSI-X向量的数量:pci_msi_vec_count()和
pci_msix_vec_count()。一般来说,应该避免使用这些方法,而是让pci_alloc_irq_vectors()
来限制向量的数量。如果你对向量的数量有合法的特殊用例,我们可能要重新审视这个决定,
并增加一个pci_nr_irq_vectors()助手,透明地处理MSI和MSI-X。
使用MSI时需要考虑的因素
-----------------------
自旋锁
~~~~~~
大多数设备驱动程序都有一个每的自旋锁,在中断处理程序中被占用。对于基于引脚的中断
或单一的MSI,没有必要禁用中断(Linux保证同一中断不会被重新输入)。如果一个设备
使用多个中断,驱动程序必须在锁被持有的时候禁用中断。如果设备发出一个不同的中断,
驱动程序将死锁,试图递归地获取自旋锁。这种死锁可以通过使用spin_lock_irqsave()
或spin_lock_irq()来避免,它们可以禁用本地中断并获取锁(见《不可靠的锁定指南》)。
如何判断一个设备上是否启用了MSI/MSI-X
-------------------------------------
使用“lspci -v“(以root身份)可能会显示一些具有“MSI“、“Message Signalled Interrupts“
或“MSI-X“功能的设备。这些功能中的每一个都有一个“启用“标志,后面是“+“(启用)
或“-“(禁用)。
MSI特性
=======
众所周知,一些PCI芯片组或设备不支持MSI。PCI协议栈提供了三种禁用MSI的方法:
1. 全局的
2. 在一个特定的桥后面的所有设备上
3. 在单一设备上
全局禁用MSI
-----------
一些主控芯片组根本无法正确支持MSI。如果我们幸运的话,制造商知道这一点,并在
ACPI FADT表中指明了它。在这种情况下,Linux会自动禁用MSI。有些板卡在表中没
有包括这一信息,因此我们必须自己检测它们。完整的列表可以在drivers/pci/quirks.c
中的quirk_disable_all_msi()函数附近找到。
如果你有一块有MSI问题的板子,你可以在内核命令行中传递pci=nomsi来禁用所有设
备上的MSI。你最好把问题报告给linux-pci@vger.kernel.org,包括完整的
“lspci -v“,这样我们就可以把这些怪癖添加到内核中。
禁用桥下的MSI
-------------
一些PCI桥接器不能在总线之间正确地路由MSI。在这种情况下,必须在桥接器后面的所
有设备上禁用MSI。
一些桥接器允许你通过改变PCI配置空间的一些位来启用MSI(特别是Hypertransport
芯片组,如nVidia nForce和Serverworks HT2000)。与主机芯片组一样,Linux大
多知道它们,如果可以的话,会自动启用MSI。如果你有一个Linux不知道的网桥,你可以
用你知道的任何方法在配置空间中启用MSI,然后通过以下方式在该网桥上启用MSI::
echo 1 > /sys/bus/pci/devices/$bridge/msi_bus
其中$bridge是你所启用的桥的PCI地址(例如0000:00:0e.0)。
要禁用MSI,请回显0而不是1。改变这个值应该谨慎进行,因为它可能会破坏这个桥下面所
有设备的中断处理。
同样,请通知 linux-pci@vger.kernel.org 任何需要特殊处理的桥。
在单一设备上关闭MSIs
--------------------
众所周知,有些设备的MSI实现是有问题的。通常情况下,这是在单个设备驱动程序中处理的,
但偶尔也有必要用一个古怪的方法来处理。一些驱动程序有一个选项可以禁用MSI的使用。虽然
这对驱动程序的作者来说是一个方便的变通办法,但这不是一个好的做法,不应该被模仿。
寻找设备上MSI被禁用的原因
-------------------------
从以上三个部分,你可以看到有许多原因导致MSI没有在某个设备上被启用。你的第一步应该是
仔细检查你的dmesg以确定你的机器是否启用了MSI。你还应该检查你的.config以确定你已经
启用了CONFIG_PCI_MSI。
然后,“lspci -t“给出一个设备上面的网列表。读取 ``/sys/bus/pci/devices/*/msi_bus``
将告诉你MSI是否被启用(1)或禁用(0)。如果在任何属于PCI根和设备之间的桥的msi_bus
文件中发现0,说明MSI被禁用。
也需要检查设备驱动程序,看它是否支持MSI。例如,它可能包含对带有PCI_IRQ_MSI或
PCI_IRQ_MSIX标志的pci_alloc_irq_vectors()的调用。
MSI(-X) APIs设备驱动程序列表
============================
PCI/MSI子系统有一个专门的C文件,用于其导出的设备驱动程序APIs - `drivers/pci/msi/api.c` 。
以下是导出的函数:
该API在以下内核代码中:
drivers/pci/msi/api.c
@@ -0,0 +1,169 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: <isonum.txt>
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/PCI/pci-iov-howto.rst
:翻译:
司延腾 Yanteng Si <siyanteng@loongson.cn>
:校译:
.. _cn_pci-iov-howto:
==========================
PCI Express I/O 虚拟化指南
==========================
:版权: |copy| 2009 Intel Corporation
:作者: - Yu Zhao <yu.zhao@intel.com>
- Donald Dutile <ddutile@redhat.com>
概述
====
什么是SR-IOV
------------
单根I/O虚拟化(SR-IOV)是一种PCI Express扩展功能,它使一个物理设备显示为多个
虚拟设备。物理设备被称为物理功能(PF),而虚拟设备被称为虚拟功能(VF)。VF的分
配可以由PF通过封装在该功能中的寄存器动态控制。默认情况下,该功能未被启用,PF表
现为传统的PCIe设备。一旦开启,每个VF的PCI配置空间都可以通过自己的总线、设备和
功能编号(路由ID)来访问。而且每个VF也有PCI内存空间,用于映射其寄存器集。VF设
备驱动程序对寄存器集进行操作,这样它就可以发挥功能,并作为一个真正的现有PCI设备
出现。
使用指南
========
我怎样才能启用SR-IOV功能
------------------------
有多种方法可用于SR-IOV的启用。在第一种方法中,设备驱动(PF驱动)将通过SR-IOV
核心提供的API控制功能的启用和禁用。如果硬件具有SR-IOV能力,加载其PF驱动器将启
用它和与PF相关的所有VF。一些PF驱动需要设置一个模块参数,以确定要启用的VF的数量。
在第二种方法中,对sysfs文件sriov_numvfs的写入将启用和禁用与PCIe PF相关的VF。
这种方法实现了每个PF的VF启用/禁用值,而第一种方法则适用于同一设备的所有PF。此外,
PCI SRIOV核心支持确保启用/禁用操作是有效的,以减少同一检查在多个驱动程序中的重
复,例如,如果启用VF,检查numvfs == 0,确保numvfs <= totalvfs。
第二种方法是对新的/未来的VF设备的推荐方法。
我怎样才能使用虚拟功能
----------------------
在内核中,VF被视为热插拔的PCI设备,所以它们应该能够以与真正的PCI设备相同的方式
工作。VF需要的设备驱动与普通PCI设备的驱动相同。
开发者指南
==========
SR-IOV API
----------
用来开启SR-IOV功能:
(a) 对于第一种方法,在驱动程序中::
int pci_enable_sriov(struct pci_dev *dev, int nr_virtfn);
nr_virtfn'是要启用的VF的编号。
(b) 对于第二种方法,从sysfs::
echo 'nr_virtfn' > \
/sys/bus/pci/devices/<DOMAIN:BUS:DEVICE.FUNCTION>/sriov_numvfs
用来关闭SR-IOV功能:
(a) 对于第一种方法,在驱动程序中::
void pci_disable_sriov(struct pci_dev *dev);
(b) 对于第二种方法,从sysfs::
echo 0 > \
/sys/bus/pci/devices/<DOMAIN:BUS:DEVICE.FUNCTION>/sriov_numvfs
要想通过主机上的兼容驱动启用自动探测VF,在启用SR-IOV功能之前运行下面的命令。这
是默认的行为。
::
echo 1 > \
/sys/bus/pci/devices/<DOMAIN:BUS:DEVICE.FUNCTION>/sriov_drivers_autoprobe
要禁止主机上的兼容驱动自动探测VF,请在启用SR-IOV功能之前运行以下命令。更新这个
入口不会影响已经被探测的VF。
::
echo 0 > \
/sys/bus/pci/devices/<DOMAIN:BUS:DEVICE.FUNCTION>/sriov_drivers_autoprobe
用例
----
下面的代码演示了SR-IOV API的用法
::
static int dev_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
pci_enable_sriov(dev, NR_VIRTFN);
...
return 0;
}
static void dev_remove(struct pci_dev *dev)
{
pci_disable_sriov(dev);
...
}
static int dev_suspend(struct device *dev)
{
...
return 0;
}
static int dev_resume(struct device *dev)
{
...
return 0;
}
static void dev_shutdown(struct pci_dev *dev)
{
...
}
static int dev_sriov_configure(struct pci_dev *dev, int numvfs)
{
if (numvfs > 0) {
...
pci_enable_sriov(dev, numvfs);
...
return numvfs;
}
if (numvfs == 0) {
....
pci_disable_sriov(dev);
...
return 0;
}
}
static struct pci_driver dev_driver = {
.name = "SR-IOV Physical Function driver",
.id_table = dev_id_table,
.probe = dev_probe,
.remove = dev_remove,
.driver.pm = &dev_pm_ops
.shutdown = dev_shutdown,
.sriov_configure = dev_sriov_configure,
};
@@ -0,0 +1,514 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/PCI/pci.rst
:翻译:
司延腾 Yanteng Si <siyanteng@loongson.cn>
:校译:
.. _cn_PCI_pci.rst:
===================
如何写Linux PCI驱动
===================
:作者: - Martin Mares <mj@ucw.cz>
- Grant Grundler <grundler@parisc-linux.org>
PCI的世界是巨大的,而且充满了(大多数是不愉快的)惊喜。由于每个CPU架构实现了不同
的芯片组,并且PCI设备有不同的要求(呃,“特性”),结果是Linux内核中的PCI支持并不
像人们希望的那样简单。这篇短文试图向所有潜在的驱动程序作者介绍PCI设备驱动程序的
Linux APIs。
更完整的资源是Jonathan Corbet、Alessandro Rubini和Greg Kroah-Hartman的
《Linux设备驱动程序》第三版。LDD3可以免费获得(在知识共享许可下),网址是:
https://lwn.net/Kernel/LDD3/。
然而,请记住,所有的文档都会受到“维护不及时”的影响。如果事情没有按照这里描述的那
样进行,请参考源代码。
请将有关Linux PCI API的问题/评论/补丁发送到“Linux PCI”
<linux-pci@atrey.karlin.mff.cuni.cz> 邮件列表。
PCI驱动的结构体
===============
PCI驱动通过pci_register_driver()在系统中“发现”PCI设备。实际上,它是反过来的。
当PCI通用代码发现一个新设备时,具有匹配“描述”的驱动程序将被通知。下面是这方面的细
节。
pci_register_driver()将大部分探测设备的工作留给了PCI层,并支持设备的在线插入/移
除[从而在一个驱动中支持可热插拔的PCI、CardBus和Express-Card]。 pci_register_driver()
调用需要传入一个函数指针表,从而决定了驱动的高层结构体。
一旦驱动探测到一个PCI设备并取得了所有权,驱动通常需要执行以下初始化:
- 启用设备
- 请求MMIO/IOP资源
- 设置DMA掩码大小(对于流式和一致的DMA)
- 分配和初始化共享控制数据(pci_allocate_coherent())
- 访问设备配置空间(如果需要)
- 注册IRQ处理程序(request_irq())
- 初始化非PCI(即芯片的LAN/SCSI/等部分)
- 启用DMA/处理引擎
当使用完设备后,也许需要卸载模块,驱动需要采取以下步骤:
- 禁用设备产生的IRQ
- 释放IRQ(free_irq())
- 停止所有DMA活动
- 释放DMA缓冲区(包括一致性和数据流式)
- 从其他子系统(例如scsi或netdev)上取消注册
- 释放MMIO/IOP资源
- 禁用设备
这些主题中的大部分都在下面的章节中有所涉及。其余的内容请参考LDD3或<linux/pci.h> 。
如果没有配置PCI子系统(没有设置 ``CONFIG_PCI`` ),下面描述的大多数PCI函数被定
义为内联函数,要么完全为空,要么只是返回一个适当的错误代码,以避免在驱动程序中出现
大量的 ``ifdef`` 。
调用pci_register_driver()
=========================
PCI设备驱动程序在初始化过程中调用 ``pci_register_driver()`` ,并提供一个指向
描述驱动程序的结构体的指针( ``struct pci_driver`` ):
该API在以下内核代码中:
include/linux/pci.h
pci_driver
ID表是一个由 ``struct pci_device_id`` 结构体成员组成的数组,以一个全零的成员
结束。一般来说,带有静态常数的定义是首选。
该API在以下内核代码中:
include/linux/mod_devicetable.h
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和subdevice字段默认为PCI_ANY_ID (FFFFFFF)。
- 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 退出代码。对于非模块化的驱动程序来说是忽略的。
====== ==============================================
关于何时/何地使用上述属性的提示:
- module_init()/module_exit()函数(以及所有仅由这些函数调用的初始化函数)应该被标记
- 为__init/__exit。
- 不要标记pci_driver结构体。
- 如果你不确定应该使用哪种标记,请不要标记一个函数。不标记函数比标记错误的函数更好。
如何手动搜索PCI设备
===================
PCI驱动最好有一个非常好的理由不使用 ``pci_register_driver()`` 接口来搜索PCI设备。
PCI设备被多个驱动程序控制的主要原因是一个PCI设备实现了几个不同的HW服务。例如,组合的
串行/并行端口/软盘控制器。
可以使用以下结构体进行手动搜索:
通过供应商和设备ID进行搜索::
struct pci_dev *dev = NULL;
while (dev = pci_get_device(VENDOR_ID, DEVICE_ID, dev))
configure_device(dev);
按类别ID搜索(以类似的方式迭代)::
pci_get_class(CLASS_ID, dev)
通过供应商/设备和子系统供应商/设备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()`` 来减少这些设备上的参考计数(可能在模块卸载时)。
设备初始化步骤
==============
正如介绍中所指出的,大多数PCI驱动需要以下步骤进行设备初始化:
- 启用设备
- 请求MMIO/IOP资源
- 设置DMA掩码大小(对于流式和一致的DMA)
- 分配和初始化共享控制数据(pci_allocate_coherent())
- 访问设备配置空间(如果需要)
- 注册IRQ处理程序(request_irq())
- 初始化non-PCI(即芯片的LAN/SCSI/等部分)
- 启用DMA/处理引擎
驱动程序可以在任何时候访问PCI配置空间寄存器。(嗯,几乎如此。当运行BIST时,配置
空间可以消失......但这只会导致PCI总线主控中止,读取配置将返回垃圾值)。)
启用PCI设备
-----------
在接触任何设备寄存器之前,驱动程序需要通过调用 ``pci_enable_device()`` 启用
PCI设备。这将:
- 唤醒处于暂停状态的设备。
- 分配设备的I/O和内存区域(如果BIOS没有这样做)。
- 分配一个IRQ(如果BIOS没有)。
.. note::
pci_enable_device() 可能失败,检查返回值。
.. warning::
OS BUG:在启用这些资源之前,我们没有检查资源分配情况。如果我们在调用
之前调用pci_request_resources(),这个顺序会更合理。目前,当两个设备被分配
了相同的范围时,设备驱动无法检测到这个错误。这不是一个常见的问题,不太可能很快
得到修复。
这个问题之前已经讨论过了,但从2.6.19开始没有改变:
https://lore.kernel.org/r/20060302180025.GC28895@flint.arm.linux.org.uk/
pci_set_master()将通过设置PCI_COMMAND寄存器中的总线主控位来启用DMA。
``pci_clear_master()`` 将通过清除总线主控位来禁用DMA,它还修复了延迟计时器的
值,如果它被BIOS设置成假的。
如果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`` 。
请求MMIO/IOP资源
----------------
内存(MMIO)和I/O端口地址不应该直接从PCI设备配置空间中读取。使用 ``pci_dev`` 结构体
中的值,因为PCI “总线地址”可能已经被arch/chip-set特定的内核支持重新映射为“主机物理”
地址。
参见io_mapping函数,了解如何访问设备寄存器或设备内存。
设备驱动需要调用 ``pci_request_region()`` 来确认没有其他设备已经在使用相同的地址
资源。反之,驱动应该在调用 ``pci_disable_device()`` 之后调用 ``pci_release_region()`` 。
这个想法是为了防止两个设备在同一地址范围内发生冲突。
.. tip::
见上面的操作系统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()`` 。
设置DMA掩码大小
---------------
.. note::
如果下面有什么不明白的地方,请参考使用通用设备的动态DMA映射。本节只是提醒大家,
驱动程序需要说明设备的DMA功能,并不是DMA接口的权威来源。
虽然所有的驱动程序都应该明确指出PCI总线主控的DMA功能(如32位或64位),但对于流式
数据来说,具有超过32位总线主站功能的设备需要驱动程序通过调用带有适当参数的
``dma_set_mask()`` 来“注册”这种功能。一般来说,在系统RAM高于4G物理地址的情
况下,这允许更有效的DMA。
所有PCI-X和PCIe兼容设备的驱动程序必须调用 ``dma_set_mask()`` ,因为它们
是64位DMA设备。
同样,如果设备可以通过调用 ``dma_set_coherent_mask()`` 直接寻址到
4G物理地址以上的系统RAM中的“一致性内存”,那么驱动程序也必须“注册”这种功能。同
样,这包括所有PCI-X和PCIe兼容设备的驱动程序。许多64位“PCI”设备(在PCI-X之前)
和一些PCI-X设备对有效载荷(“流式”)数据具有64位DMA功能,但对控制(“一致性”)数
据则没有。
设置共享控制数据
----------------
一旦DMA掩码设置完毕,驱动程序就可以分配“一致的”(又称共享的)内存。参见使用通
用设备的动态DMA映射,了解DMA API的完整描述。本节只是提醒大家,需要在设备上启
用DMA之前完成。
初始化设备寄存器
----------------
一些驱动程序需要对特定的“功能”字段进行编程,或对其他“供应商专用”寄存器进行初始
化或重置。例如,清除挂起的中断。
注册IRQ处理函数
---------------
虽然调用 ``request_irq()`` 是这里描述的最后一步,但这往往只是初始化设备的另
一个中间步骤。这一步通常可以推迟到设备被打开使用时进行。
所有IRQ线的中断处理程序都应该用 ``IRQF_SHARED`` 注册,并使用devid将IRQ映射
到设备(记住,所有的PCI IRQ线都可以共享)。
``request_irq()`` 将把一个中断处理程序和设备句柄与一个中断号联系起来。历史上,
中断号码代表从PCI设备到中断控制器的IRQ线。在MSI和MSI-X中(更多内容见下文),中
断号是CPU的一个“向量”。
``request_irq()`` 也启用中断。在注册中断处理程序之前,请确保设备是静止的,并且
没有任何中断等待。
MSI和MSI-X是PCI功能。两者都是“消息信号中断”,通过向本地APIC的DMA写入来向CPU发
送中断。MSI和MSI-X的根本区别在于如何分配多个“向量”。MSI需要连续的向量块,而
MSI-X可以分配几个单独的向量。
在调用 ``request_irq()`` 之前,可以通过调用 ``pci_alloc_irq_vectors()``
的PCI_IRQ_MSI和/或PCI_IRQ_MSIX标志来启用MSI功能。这将导致PCI支持将CPU向量数
据编程到PCI设备功能寄存器中。许多架构、芯片组或BIOS不支持MSI或MSI-X,调用
``pci_alloc_irq_vectors`` 时只使用PCI_IRQ_MSI和PCI_IRQ_MSIX标志会失败,
所以尽量也要指定 ``PCI_IRQ_INTX`` 。
对MSI/MSI-X和传统INTx有不同中断处理程序的驱动程序应该在调用
``pci_alloc_irq_vectors`` 后根据 ``pci_dev``结构体中的 ``msi_enabled``
和 ``msix_enabled`` 标志选择正确的处理程序。
使用MSI有(至少)两个真正好的理由:
1) 根据定义,MSI是一个排他性的中断向量。这意味着中断处理程序不需要验证其设备是
否引起了中断。
2) MSI避免了DMA/IRQ竞争条件。到主机内存的DMA被保证在MSI交付时对主机CPU是可
见的。这对数据一致性和避
3) 免控制数据过期都很重要。这个保证允许驱动程序省略MMIO读取,以刷新DMA流。
参见drivers/infiniband/hw/mthca/或drivers/net/tg3.c了解MSI/MSI-X的使
用实例。
PCI设备关闭
===========
当一个PCI设备驱动程序被卸载时,需要执行以下大部分步骤:
- 禁用设备产生的IRQ
- 释放IRQ(free_irq())
- 停止所有DMA活动
- 释放DMA缓冲区(包括流式和一致的)
- 从其他子系统(例如scsi或netdev)上取消注册
- 禁用设备对MMIO/IO端口地址的响应
- 释放MMIO/IO端口资源
停止设备上的IRQ
---------------
如何做到这一点是针对芯片/设备的。如果不这样做,如果(也只有在)IRQ与另一个设备
共享,就会出现“尖叫中断”的可能性。
当共享的IRQ处理程序被“解钩”时,使用同一IRQ线的其余设备仍然需要启用该IRQ。因此,
如果“脱钩”的设备断言IRQ线,假设它是其余设备中的一个断言IRQ线,系统将作出反应。
由于其他设备都不会处理这个IRQ,系统将“挂起”,直到它决定这个IRQ不会被处理并屏蔽
这个IRQ(100,000次之后)。一旦共享的IRQ被屏蔽,其余设备将停止正常工作。这不是
一个好事情。
这是使用MSI或MSI-X的另一个原因,如果它可用的话。MSI和MSI-X被定义为独占中断,
因此不容易受到“尖叫中断”问题的影响。
释放IRQ
-------
一旦设备被静止(不再有IRQ),就可以调用free_irq()。这个函数将在任何待处理
的IRQ被处理后返回控制,从该IRQ上“解钩”驱动程序的IRQ处理程序,最后如果没有人
使用该IRQ,则释放它。
停止所有DMA活动
---------------
在试图取消分配DMA控制数据之前,停止所有的DMA操作是非常重要的。如果不这样做,
可能会导致内存损坏、挂起,在某些芯片组上还会导致硬崩溃。
在停止IRQ后停止DMA可以避免IRQ处理程序可能重新启动DMA引擎的竞争。
虽然这个步骤听起来很明显,也很琐碎,但过去有几个“成熟”的驱动程序没有做好这个
步骤。
释放DMA缓冲区
-------------
一旦DMA被停止,首先要清理流式DMA。即取消数据缓冲区的映射,如果有的话,将缓
冲区返回给“上游”所有者。
然后清理包含控制数据的“一致的”缓冲区。
关于取消映射接口的细节,请参见Documentation/core-api/dma-api.rst。
从其他子系统取消注册
--------------------
大多数低级别的PCI设备驱动程序支持其他一些子系统,如USB、ALSA、SCSI、NetDev、
Infiniband等。请确保你的驱动程序没有从其他子系统中丢失资源。如果发生这种情况,
典型的症状是当子系统试图调用已经卸载的驱动程序时,会出现Oops(恐慌)。
禁止设备对MMIO/IO端口地址做出响应
---------------------------------
io_unmap() MMIO或IO端口资源,然后调用pci_disable_device()。
这与pci_enable_device()对称相反。
在调用pci_disable_device()后不要访问设备寄存器。
释放MMIO/IO端口资源
-------------------
调用pci_release_region()来标记MMIO或IO端口范围为可用。
如果不这样做,通常会导致无法重新加载驱动程序。
如何访问PCI配置空间
===================
你可以使用 `pci_(read|write)_config_(byte|word|dword)` 来访问由
`struct pci_dev *` 表示的设备的配置空间。所有这些函数在成功时返回0,或者返回一个
错误代码( `PCIBIOS_...` ),这个错误代码可以通过pcibios_strerror翻译成文本字
符串。大多数驱动程序希望对有效的PCI设备的访问不会失败。
如果你没有可用的pci_dev结构体,你可以调用
`pci_bus_(read|write)_config_(byte|word|dword)` 来访问一个给定的设备和该总
线上的功能。
如果你访问配置头的标准部分的字段,请使用<linux/pci.h>中声明的位置和位的符号名称。
如果你需要访问扩展的PCI功能寄存器,只要为特定的功能调用pci_find_capability(),
它就会为你找到相应的寄存器块。
其它有趣的函数
==============
============================= =================================================
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设置私有驱动数据指针
pci_get_drvdata() 返回一个pci_dev的私有驱动数据指针
pci_set_mwi() 启用设备内存写无效
pci_clear_mwi() 关闭设备内存写无效
============================= =================================================
杂项提示
========
当向用户显示PCI设备名称时(例如,当驱动程序想告诉用户它找到了什么卡时),请使
用pci_name(pci_dev)。
始终通过对pci_dev结构体的指针来引用PCI设备。所有的PCI层函数都使用这个标识,
它是唯一合理的标识。除了非常特殊的目的,不要使用总线/插槽/功能号————在有多个
主总线的系统上,它们的语义可能相当复杂。
不要试图在你的驱动程序中开启快速寻址周期写入功能。总线上的所有设备都需要有这样
的功能,所以这需要由平台和通用代码来处理,而不是由单个驱动程序来处理。
供应商和设备标识
================
不要在include/linux/pci_ids.h中添加新的设备或供应商ID,除非它们是在多个驱
动程序中共享。如果有需要的话,你可以在你的驱动程序中添加私有定义,或者直接使用
普通的十六进制常量。
设备ID是任意的十六进制数字(厂商控制),通常只在一个地方使用,即pci_device_id
表。
请务必提交新的供应商/设备ID到https://pci-ids.ucw.cz/。在
https://github.com/pciutils/pciids,有一个pci.ids文件的镜像。
过时的函数
==========
当你试图将一个旧的驱动程序移植到新的PCI接口时,你可能会遇到几个函数。它们不再存
在于内核中,因为它们与热插拔或PCI域或具有健全的锁不兼容。
================= ===================================
pci_find_device() 被pci_get_device()取代
pci_find_subsys() 被pci_get_subsys()取代
pci_find_slot() 被pci_get_domain_bus_and_slot()取代
pci_get_slot() 被pci_get_domain_bus_and_slot()取代
================= ===================================
另一种方法是传统的PCI设备驱动,即走PCI设备列表。这仍然是可能的,但不鼓励这样做。
MMIO空间和“写通知”
==================
将驱动程序从使用I/O端口空间转换为使用MMIO空间,通常需要一些额外的改变。具体来说,
需要处理“写通知”。许多驱动程序(如tg3,acenic,sym53c8xx_2)已经做了这个。I/O
端口空间保证写事务在CPU继续之前到达PCI设备。对MMIO空间的写入允许CPU在事务到达PCI
设备之前继续。HW weenies称这为“写通知”,因为在事务到达目的地之前,写的完成被“通知”
给CPU。
因此,对时间敏感的代码应该添加readl(),CPU在做其他工作之前应该等待。经典的“位脉冲”
序列对I/O端口空间很有效::
for (i = 8; --i; val >>= 1) {
outb(val & 1, ioport_reg); /* 置位 */
udelay(10);
}
对MMIO空间来说,同样的顺序应该是::
for (i = 8; --i; val >>= 1) {
writeb(val & 1, mmio_reg); /* 置位 */
readb(safe_mmio_reg); /* 刷新写通知 */
udelay(10);
}
重要的是, ``safe_mmio_reg`` 不能有任何干扰设备正确操作的副作用。
另一种需要注意的情况是在重置PCI设备时。使用PCI配置空间读数来刷新writeel()。如果预期
PCI设备不响应readl(),这将在所有平台上优雅地处理PCI主控器的中止。大多数x86平台将允许
MMIO读取主控中止(又称“软失败”),并返回垃圾(例如~0)。但许多RISC平台会崩溃(又称“硬失败”)。
@@ -0,0 +1,192 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: <isonum.txt>
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/PCI/pciebus-howto.rst
:翻译:
司延腾 Yanteng Si <siyanteng@loongson.cn>
:校译:
.. _cn_pciebus-howto:
===========================
PCI Express端口总线驱动指南
===========================
:作者: Tom L Nguyen tom.l.nguyen@intel.com 11/03/2004
:版权: |copy| 2004 Intel Corporation
关于本指南
==========
本指南介绍了PCI Express端口总线驱动程序的基本知识,并提供了如何使服务驱
动程序在PCI Express端口总线驱动程序中注册/取消注册的介绍。
什么是PCI Express端口总线驱动程序
=================================
一个PCI Express端口是一个逻辑的PCI-PCI桥结构。有两种类型的PCI Express端
口:根端口和交换端口。根端口从PCI Express根综合体发起一个PCI Express链接,
交换端口将PCI Express链接连接到内部逻辑PCI总线。交换机端口,其二级总线代表
交换机的内部路由逻辑,被称为交换机的上行端口。交换机的下行端口是从交换机的内部
路由总线桥接到代表来自PCI Express交换机的下游PCI Express链接的总线。
一个PCI Express端口可以提供多达四个不同的功能,在本文中被称为服务,这取决于
其端口类型。PCI Express端口的服务包括本地热拔插支持(HP)、电源管理事件支持(PME)、
高级错误报告支持(AER)和虚拟通道支持(VC)。这些服务可以由一个复杂的驱动程序
处理,也可以单独分布并由相应的服务驱动程序处理。
为什么要使用PCI Express端口总线驱动程序?
=========================================
在现有的Linux内核中,Linux设备驱动模型允许一个物理设备只由一个驱动处理。
PCI Express端口是一个具有多个不同服务的PCI-PCI桥设备。为了保持一个干净和简
单的解决方案,每个服务都可以有自己的软件服务驱动。在这种情况下,几个服务驱动将
竞争一个PCI-PCI桥设备。例如,如果PCI Express根端口的本机热拔插服务驱动程序
首先被加载,它就会要求一个PCI-PCI桥根端口。因此,内核不会为该根端口加载其他服
务驱动。换句话说,使用当前的驱动模型,不可能让多个服务驱动同时加载并运行在
PCI-PCI桥设备上。
为了使多个服务驱动程序同时运行,需要有一个PCI Express端口总线驱动程序,它管
理所有填充的PCI Express端口,并根据需要将所有提供的服务请求分配给相应的服务
驱动程序。下面列出了使用PCI Express端口总线驱动程序的一些关键优势:
- 允许在一个PCI-PCI桥接端口设备上同时运行多个服务驱动。
- 允许以独立的分阶段方式实施服务驱动程序。
- 允许一个服务驱动程序在多个PCI-PCI桥接端口设备上运行。
- 管理和分配PCI-PCI桥接端口设备的资源给要求的服务驱动程序。
配置PCI Express端口总线驱动程序与服务驱动程序
=============================================
将PCI Express端口总线驱动支持纳入内核
-------------------------------------
包括PCI Express端口总线驱动程序取决于内核配置中是否包含PCI Express支持。当内核
中的PCI Express支持被启用时,内核将自动包含PCI Express端口总线驱动程序作为内核
驱动程序。
启用服务驱动支持
----------------
PCI设备驱动是基于Linux设备驱动模型实现的。所有的服务驱动都是PCI设备驱动。如上所述,
一旦内核加载了PCI Express端口总线驱动程序,就不可能再加载任何服务驱动程序。为了满
足PCI Express端口总线驱动程序模型,需要对现有的服务驱动程序进行一些最小的改变,其
对现有的服务驱动程序的功能没有影响。
服务驱动程序需要使用下面所示的两个API,将其服务注册到PCI Express端口总线驱动程
序中(见第5.2.1和5.2.2节)。在调用这些API之前,服务驱动程序必须初始化头文件
/include/linux/pcieport_if.h中的pcie_port_service_driver数据结构。如果不这
样做,将导致身份不匹配,从而使PCI Express端口总线驱动程序无法加载服务驱动程序。
pcie_port_service_register
~~~~~~~~~~~~~~~~~~~~~~~~~~
::
int pcie_port_service_register(struct pcie_port_service_driver *new)
这个API取代了Linux驱动模型的 pci_register_driver API。一个服务驱动应该总是在模
块启动时调用 pcie_port_service_register。请注意,在服务驱动被加载后,诸如
pci_enable_device(dev) 和 pci_set_master(dev) 的调用不再需要,因为这些调用由
PCI端口总线驱动执行。
pcie_port_service_unregister
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
::
void pcie_port_service_unregister(struct pcie_port_service_driver *new)
pcie_port_service_unregister取代了Linux驱动模型的pci_unregister_driver。当一
个模块退出时,它总是被服务驱动调用。
示例代码
~~~~~~~~
下面是服务驱动代码示例,用于初始化端口服务的驱动程序数据结构。
::
static struct pcie_port_service_id service_id[] = { {
.vendor = PCI_ANY_ID,
.device = PCI_ANY_ID,
.port_type = PCIE_RC_PORT,
.service_type = PCIE_PORT_SERVICE_AER,
}, { /* end: all zeroes */ }
};
static struct pcie_port_service_driver root_aerdrv = {
.name = (char *)device_name,
.id_table = service_id,
.probe = aerdrv_load,
.remove = aerdrv_unload,
.suspend = aerdrv_suspend,
.resume = aerdrv_resume,
};
下面是一个注册/取消注册服务驱动的示例代码。
::
static int __init aerdrv_service_init(void)
{
int retval = 0;
retval = pcie_port_service_register(&root_aerdrv);
if (!retval) {
/*
* FIX ME
*/
}
return retval;
}
static void __exit aerdrv_service_exit(void)
{
pcie_port_service_unregister(&root_aerdrv);
}
module_init(aerdrv_service_init);
module_exit(aerdrv_service_exit);
可能的资源冲突
==============
由于PCI-PCI桥接端口设备的所有服务驱动被允许同时运行,下面列出了一些可能的资源冲突和
建议的解决方案。
MSI 和 MSI-X 向量资源
---------------------
一旦设备上的MSI或MSI-X中断被启用,它就会一直保持这种模式,直到它们再次被禁用。由于同
一个PCI-PCI桥接端口的服务驱动程序共享同一个物理设备,如果一个单独的服务驱动程序启用或
禁用MSI/MSI-X模式,可能会导致不可预知的行为。
为了避免这种情况,所有的服务驱动程序都不允许在其设备上切换中断模式。PCI Express端口
总线驱动程序负责确定中断模式,这对服务驱动程序来说应该是透明的。服务驱动程序只需要知道
分配给结构体pcie_device的字段irq的向量IRQ,当PCI Express端口总线驱动程序探测每
个服务驱动程序时,它被传入。服务驱动应该使用(struct pcie_device*)dev->irq来调用
request_irq/free_irq。此外,中断模式被存储在struct pcie_device的interrupt_mode
字段中。
PCI内存/IO映射的区域
--------------------
PCI Express电源管理(PME)、高级错误报告(AER)、热插拔(HP)和虚拟通道(VC)的服务
驱动程序访问PCI Express端口的PCI配置空间。在所有情况下,访问的寄存器是相互独立的。这
个补丁假定所有的服务驱动程序都会表现良好,不会覆盖其他服务驱动程序的配置设置。
PCI配置寄存器
-------------
每个服务驱动都在自己的功能结构体上运行PCI配置操作,除了PCI Express功能结构体,其中根控制
寄存器和设备控制寄存器是在PME和AER之间共享。这个补丁假定所有的服务驱动都会表现良好,不会
覆盖其他服务驱动的配置设置。
@@ -0,0 +1,126 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/PCI/sysfs-pci.rst
:翻译:
司延腾 Yanteng Si <siyanteng@loongson.cn>
:校译:
========================
通过sysfs访问PCI设备资源
========================
sysfs,通常挂载在/sys,在支持它的平台上提供对PCI资源的访问。例如,一个特定的总线可能看起
来像这样::
/sys/devices/pci0000:17
|-- 0000:17:00.0
| |-- class
| |-- config
| |-- device
| |-- enable
| |-- irq
| |-- local_cpus
| |-- remove
| |-- resource
| |-- resource0
| |-- resource1
| |-- resource2
| |-- revision
| |-- rom
| |-- subsystem_device
| |-- subsystem_vendor
| `-- vendor
`-- ...
最上面的元素描述了PCI域和总线号码。在这种情况下,域号是0000,总线号是17(两个值都是十六进制)。
这个总线在0号插槽中包含一个单一功能的设备。为了方便起见,我们复制了域和总线的编号。在设备目录
下有几个文件,每个文件都有自己的功能。
=================== =====================================================
文件 功能
=================== =====================================================
class PCI级别 (ascii, ro)
config PCI配置空间 (binary, rw)
device PCI设备 (ascii, ro)
enable 设备是否被启用 (ascii, rw)
irq IRQ编号 (ascii, ro)
local_cpus 临近CPU掩码(cpumask, ro)
remove 从内核的列表中删除设备 (ascii, wo)
resource PCI资源主机地址 (ascii, ro)
resource0..N PCI资源N,如果存在的话 (binary, mmap, rw\ [1]_)
resource0_wc..N_wc PCI WC映射资源N,如果可预取的话 (binary, mmap)
revision PCI修订版 (ascii, ro)
rom PCI ROM资源,如果存在的话 (binary, ro)
subsystem_device PCI子系统设备 (ascii, ro)
subsystem_vendor PCI子系统供应商 (ascii, ro)
vendor PCI供应商 (ascii, ro)
=================== =====================================================
::
ro - 只读文件
rw - 文件是可读和可写的
wo - 只写文件
mmap - 文件是可移动的
ascii - 文件包含ascii文本
binary - 文件包含二进制数据
cpumask - 文件包含一个cpumask类型的
.. [1] rw 仅适用于 IORESOURCE_IO(I/O 端口)区域
只读文件是信息性的,对它们的写入将被忽略,但 "rom "文件除外。可写文件可以用来在设备上执
行操作(例如,改变配置空间,分离设备)。 mmapable文件可以通过偏移量为0的文件的mmap获得,
可以用来从用户空间进行实际的设备编程。注意,有些平台不支持某些资源的mmapping,所以一定要
检查任何尝试的mmap的返回值。其中最值得注意的是I/O端口资源,它也提供读/写访问。
enable "文件提供了一个计数器,表明设备已经被启用了多少次。如果'enable'文件目前返回'4',
而一个'1'被呼入它,它将返回'5'。向它呼入一个'0'会减少计数。不过,即使它返回到0,一些初始
化可能也不会被逆转。
rom "文件很特别,因为它提供了对设备ROM文件的只读访问,如果有的话。然而,它在默认情况下是
禁用的,所以应用程序应该在尝试读取调用之前将字符串 "1 "写入该文件以启用它,并在访问之后将
"0 "写入该文件以禁用它。请注意,设备必须被启用,才能成功返回数据。如果驱动没有被绑定到设备
上,可以使用上面提到的 "enable "文件将其启用。
remove "文件是用来移除PCI设备的,通过向该文件写入一个非零的整数。这并不涉及任何形式的热插
拔功能,例如关闭设备的电源。该设备被从内核的PCI设备列表中移除,它的sysfs目录被移除,并且该
设备将被从任何连接到它的驱动程序中移除。移除PCI根总线是不允许的。
通过sysfs访问原有资源
---------------------
如果底层平台支持的话,传统的I/O端口和ISA内存资源也会在sysfs中提供。它们位于PCI类的层次结构
中,例如::
/sys/class/pci_bus/0000:17/
|-- bridge -> ../../../devices/pci0000:17
|-- cpuaffinity
|-- legacy_io
`-- legacy_mem
legacy_io文件是一个读/写文件,可以被应用程序用来做传统的端口I/O。应用程序应该打开该文件,寻
找所需的端口(例如0x3e8),并进行1、2或4字节的读或写。legacy_mem文件应该被mmapped,其偏移
量与所需的内存偏移量相对应,例如0xa0000用于VGA帧缓冲器。然后,应用程序可以简单地解除引用返回
的指针(当然是在检查了错误之后)来访问遗留内存空间。
支持新平台上的PCI访问
---------------------
为了支持上述的PCI资源映射,Linux平台代码最好定义ARCH_GENERIC_PCI_MMAP_RESOURCE并使用该
功能的通用实现。为了支持通过/proc/bus/pci中的文件实现mmap()的历史接口,平台也可以设置
HAVE_PCI_MMAP。
另外,设置了 HAVE_PCI_MMAP 的平台可以提供他们自己的 pci_mmap_page_range() 实现,而不是定
义 ARCH_GENERIC_PCI_MMAP_RESOURCE。
支持PCI资源的写组合映射的平台必须定义arch_can_pci_mmap_wc(),当写组合被允许时,在运行时应
评估为非零。支持I/O资源映射的平台同样定义arch_can_pci_mmap_io()。
遗留资源由HAVE_PCI_LEGACY定义保护。希望支持遗留功能的平台应该定义它并提供 pci_legacy_read,
pci_legacy_write 和 pci_mmap_legacy_page_range 函数。