pci设备初始化、使能、关闭的过程

一、PCI设备初始化步骤

  • 启用设备

  • 请求MMIO/IOP资源

  • 设置DMA掩码大小(对于流式和一致的DMA)

  • 分配和初始化共享控制数据(pci_allocate_coherent())

  • 访问设备配置空间(如果需要)

  • 注册IRQ处理程序(request_irq()

  • 初始化non-PCI(即芯片的LAN/SCSI/等部分)

  • 启用DMA/处理引擎

1、 启用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 。

2、 请求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() 。

3、 设置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功能,但对控制(“一致性”)数 据则没有。

4、 设置共享控制数据

一旦DMA掩码设置完毕,驱动程序就可以分配“一致的”(又称共享的)内存。参见使用通 用设备的动态DMA映射,了解DMA API的完整描述。本节只是提醒大家,需要在设备上启 用DMA之前完成。

5、 初始化设备寄存器

一些驱动程序需要对特定的“功能”字段进行编程,或对其他“供应商专用”寄存器进行初始 化或重置。例如,清除挂起的中断。

6、 注册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端口资源

1、停止设备上的IRQ

如何做到这一点是针对芯片/设备的。如果不这样做,如果(也只有在)IRQ与另一个设备 共享,就会出现“尖叫中断”的可能性。

当共享的IRQ处理程序被“解钩”时,使用同一IRQ线的其余设备仍然需要启用该IRQ。因此, 如果“脱钩”的设备断言IRQ线,假设它是其余设备中的一个断言IRQ线,系统将作出反应。 由于其他设备都不会处理这个IRQ,系统将“挂起”,直到它决定这个IRQ不会被处理并屏蔽 这个IRQ(100,000次之后)。一旦共享的IRQ被屏蔽,其余设备将停止正常工作。这不是 一个好事情。

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

2、 释放IRQ

一旦设备被静止(不再有IRQ),就可以调用free_irq()。这个函数将在任何待处理 的IRQ被处理后返回控制,从该IRQ上“解钩”驱动程序的IRQ处理程序,最后如果没有人 使用该IRQ,则释放它。

3、停止所有DMA活动

在试图取消分配DMA控制数据之前,停止所有的DMA操作是非常重要的。如果不这样做, 可能会导致内存损坏、挂起,在某些芯片组上还会导致硬崩溃。

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

虽然这个步骤听起来很明显,也很琐碎,但过去有几个“成熟”的驱动程序没有做好这个 步骤。

4、释放DMA缓冲区

一旦DMA被停止,首先要清理流式DMA。即取消数据缓冲区的映射,如果有的话,将缓 冲区返回给“上游”所有者。

然后清理包含控制数据的“一致的”缓冲区。

关于取消映射接口的细节,请参见Documentation/core-api/dma-api.rst。

5、 从其他子系统取消注册

大多数低级别的PCI设备驱动程序支持其他一些子系统,如USB、ALSA、SCSI、NetDev、 Infiniband等。请确保你的驱动程序没有从其他子系统中丢失资源。如果发生这种情况, 典型的症状是当子系统试图调用已经卸载的驱动程序时,会出现Oops(恐慌)。

1.5.6. 禁止设备对MMIO/IO端口地址做出响应

io_unmap() MMIO或IO端口资源,然后调用pci_disable_device()。 这与pci_enable_device()对称相反。 在调用pci_disable_device()后不要访问设备寄存器。

1.5.7. 释放MMIO/IO端口资源

调用pci_release_region()来标记MMIO或IO端口范围为可用。 如果不这样做,通常会导致无法重新加载驱动程序。

posted @ 2025-04-18 15:38  轻轻的吻  阅读(348)  评论(0)    收藏  举报