.. 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_LEGACY`` 。 对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å¹³å°ä¼šå´©æºƒï¼ˆåˆç§°â€œç¡¬å¤±è´¥â€ï¼‰ã€‚