1. 如何編寫 Linux PCI 驅動程式¶
- 作者:
Martin Mares <mj@ucw.cz>
Grant Grundler <grundler@parisc-linux.org>
PCI 的世界浩瀚且充滿(大多是不愉快的)驚喜。由於每個 CPU 架構都實現不同的晶片組,並且 PCI 裝置具有不同的要求(呃,“特性”),因此 Linux 核心中的 PCI 支援不像人們希望的那樣簡單。這篇短文試圖向所有潛在的驅動程式作者介紹 Linux PCI 裝置驅動程式的 API。
更完整的資源是 Jonathan Corbet、Alessandro Rubini 和 Greg Kroah-Hartman 編寫的第三版“Linux Device Drivers”。LDD3 可從以下網址免費獲得(在 Creative Commons 許可下):https://lwn.net/Kernel/LDD3/。
但是,請記住,所有文件都容易“位腐爛”。如果事情沒有像此處描述的那樣工作,請參考原始碼。
請將有關 Linux PCI API 的問題/評論/補丁傳送到“Linux PCI”<linux-pci@atrey.karlin.mff.cuni.cz> 郵件列表。
1.1. PCI 驅動程式的結構¶
PCI 驅動程式透過 pci_register_driver() “發現” 系統中的 PCI 裝置。實際上,情況正好相反。當 PCI 通用程式碼發現新裝置時,將通知具有匹配“描述”的驅動程式。有關此的詳細資訊如下。
pci_register_driver() 將裝置的大部分探測工作留給 PCI 層,並支援裝置的線上插入/移除 [因此在單個驅動程式中支援熱插拔 PCI、CardBus 和 Express-Card]。pci_register_driver() 呼叫需要傳入一個函式指標表,因此決定了驅動程式的高階結構。
一旦驅動程式知道 PCI 裝置並獲得所有權,驅動程式通常需要執行以下初始化
啟用裝置
請求 MMIO/IOP 資源
設定 DMA 掩碼大小(對於相干 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 函式大部分定義為完全為空的行內函數,或者僅返回適當的錯誤程式碼,以避免驅動程式中出現大量 ifdefs。
1.2. pci_register_driver() 呼叫¶
PCI 裝置驅動程式在其初始化期間呼叫 pci_register_driver(),其中包含指向描述驅動程式的結構的指標 (struct pci_driver)
-
struct pci_driver¶
PCI 驅動程式結構
定義:
struct pci_driver {
const char *name;
const struct pci_device_id *id_table;
int (*probe)(struct pci_dev *dev, const struct pci_device_id *id);
void (*remove)(struct pci_dev *dev);
int (*suspend)(struct pci_dev *dev, pm_message_t state);
int (*resume)(struct pci_dev *dev);
void (*shutdown)(struct pci_dev *dev);
int (*sriov_configure)(struct pci_dev *dev, int num_vfs);
int (*sriov_set_msix_vec_count)(struct pci_dev *vf, int msix_vec_count);
u32 (*sriov_get_vf_total_msix)(struct pci_dev *pf);
const struct pci_error_handlers *err_handler;
const struct attribute_group **groups;
const struct attribute_group **dev_groups;
struct device_driver driver;
struct pci_dynids dynids;
bool driver_managed_dma;
};
成員
name驅動程式名稱。
id_table指向驅動程式感興趣的裝置 ID 表的指標。大多數驅動程式應該使用 MODULE_DEVICE_TABLE(pci,...) 匯出此表。
probe對於所有匹配 ID 表且尚未被其他驅動程式“擁有”的 PCI 裝置(在執行 pci_register_driver() 期間用於已存在的裝置,或者在稍後插入新裝置時),將呼叫此探測函式。對於 ID 表中條目與裝置匹配的每個裝置,此函式會傳遞一個 “struct pci_dev *”。當驅動程式選擇獲取裝置的“所有權”時,探測函式返回零,否則返回錯誤程式碼(負數)。始終從程序上下文中呼叫探測函式,因此它可以休眠。
remove每當從此驅動程式處理的裝置中移除裝置時(在取消註冊驅動程式期間或手動從熱插拔插槽中拔出時),都會呼叫 remove() 函式。始終從程序上下文中呼叫 remove 函式,因此它可以休眠。
suspend將裝置置於低功耗狀態。
resume將裝置從低功耗狀態喚醒。(請參閱 PCI 電源管理,瞭解有關 PCI 電源管理和相關函式的描述。)
shutdown掛鉤到 reboot_notifier_list (kernel/sys.c)。旨在停止任何空閒的 DMA 操作。在重新啟動之前,用於啟用區域網喚醒 (NIC) 或更改裝置的電源狀態。例如 drivers/net/e100.c。
sriov_configure可選的驅動程式回撥,允許透過 sysfs “sriov_numvfs” 檔案配置要啟用的 VF 的數量。
sriov_set_msix_vec_countPF 驅動程式回撥以更改 VF 上的 MSI-X 向量數量。透過 sysfs “sriov_vf_msix_count” 觸發。這將更改 VF 訊息控制暫存器中的 MSI-X 表大小。
sriov_get_vf_total_msixPF 驅動程式回撥以獲取可用於分配給 VF 的 MSI-X 向量總數。
err_handler請參閱 PCI 錯誤恢復
groupsSysfs 屬性組。
dev_groups附加到裝置的屬性,一旦它繫結到驅動程式就會建立。
driver驅動程式模型結構。
dynids動態新增的裝置 ID 列表。
driver_managed_dma裝置驅動程式不使用核心 DMA API 進行 DMA。對於大多數裝置驅動程式,只要所有 DMA 都透過核心 DMA API 處理,就不需要關心此標誌。對於某些特殊裝置,例如 VFIO 驅動程式,它們知道如何自行管理 DMA,並設定此標誌,以便 IOMMU 層允許它們設定和管理自己的 I/O 地址空間。
ID 表是一個 struct pci_device_id 條目的陣列,以全零條目結尾。通常首選使用 static const 定義。
-
struct pci_device_id¶
PCI 裝置 ID 結構
定義:
struct pci_device_id {
__u32 vendor, device;
__u32 subvendor, subdevice;
__u32 class, class_mask;
kernel_ulong_t driver_data;
__u32 override_only;
};
成員
vendor要匹配的供應商 ID(或 PCI_ANY_ID)
device要匹配的裝置 ID(或 PCI_ANY_ID)
subvendor要匹配的子系統供應商 ID(或 PCI_ANY_ID)
subdevice要匹配的子系統裝置 ID(或 PCI_ANY_ID)
class要匹配的裝置類、子類和“介面”。有關類的完整列表,請參閱 PCI Local Bus Spec 的附錄 D 或 include/linux/pci_ids.h。大多數驅動程式不需要指定類/class_mask,因為通常供應商/裝置就足夠了。
class_mask限制要比較的類欄位的子欄位。有關用法示例,請參閱 drivers/scsi/sym53c8xx_2/。
driver_data驅動程式私有的資料。大多數驅動程式不需要使用 driver_data 欄位。最佳實踐是使用 driver_data 作為靜態等效裝置型別列表的索引,而不是將其用作指標。
override_only僅當 dev->driver_override 是此驅動程式時才匹配。
大多數驅動程式只需要 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)。供應商和裝置欄位是必需的,其他欄位是可選的。使用者只需要傳遞儘可能多的可選欄位
子供應商和子裝置欄位預設為 PCI_ANY_ID (FFFFFFFF)
類和 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 層會自動為驅動程式處理的所有裝置呼叫 remove 鉤子。
1.2.1. 驅動程式函式/資料的“屬性”¶
請適當地標記初始化和清理函式(相應的宏在 <linux/init.h> 中定義)
__init
初始化程式碼。在驅動程式初始化後丟棄。
__exit
退出程式碼。對於非模組化驅動程式,將忽略。
- 有關何時/何地使用上述屬性的提示
module_init()/module_exit()函式(以及 _僅_ 從這些函式呼叫的所有初始化函式)應標記為 __init/__exit。不要標記
struct pci_driver。如果您不確定使用哪個標記,請不要標記函式。最好不要標記函式,也不要錯誤地標記函式。
1.3. 如何手動查詢 PCI 裝置¶
PCI 驅動程式應該有充分的理由不使用 pci_register_driver() 介面來搜尋 PCI 裝置。PCI 裝置由多個驅動程式控制的主要原因是,一個 PCI 裝置實現了幾種不同的硬體服務。例如,組合的序列/並行埠/軟盤控制器。
可以使用以下構造執行手動搜尋
按供應商和裝置 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() 來減少這些裝置的引用計數。
1.4. 裝置初始化步驟¶
如簡介中所述,大多數 PCI 驅動程式都需要以下步驟進行裝置初始化
啟用裝置
請求 MMIO/IOP 資源
設定 DMA 掩碼大小(對於相干 DMA 和流式 DMA)
分配和初始化共享控制資料 (pci_allocate_coherent())
訪問裝置配置空間(如果需要)
註冊 IRQ 處理程式 (
request_irq())初始化非 PCI 部分(即晶片的 LAN/SCSI/等部分)
啟用 DMA/處理引擎。
驅動程式可以隨時訪問 PCI 配置空間暫存器。(嗯,幾乎。當執行 BIST 時,配置空間可能會消失……但這隻會導致 PCI 匯流排主控中止,並且配置讀取將返回垃圾)。
1.4.1. 啟用 PCI 裝置¶
在接觸任何裝置暫存器之前,驅動程式需要透過呼叫 pci_enable_device() 來啟用 PCI 裝置。這將
喚醒裝置(如果它處於掛起狀態),
分配裝置的 I/O 和記憶體區域(如果 BIOS 沒有這樣做),
分配 IRQ(如果 BIOS 沒有這樣做)。
注意
pci_enable_device() 可能會失敗!檢查返回值。
警告
作業系統錯誤:我們不會在啟用這些資源之前檢查資源分配。如果我們先呼叫 pci_request_resources(),然後再呼叫 pci_enable_device(),則該序列更有意義。目前,當兩個裝置已分配到同一範圍時,裝置驅動程式無法檢測到該錯誤。這不是一個常見問題,並且不太可能很快修復。
在 2.6.19 中,之前已經討論過這個問題,但沒有改變:https://lore.kernel.org/r/20060302180025.GC28895@flint.arm.linux.org.uk/
pci_set_master() 將透過設定 PCI_COMMAND 暫存器中的匯流排主控位來啟用 DMA。如果延遲計時器值由 BIOS 設定為錯誤的值,它還會修復該值。pci_clear_master() 將透過清除匯流排主控位來停用 DMA。
如果 PCI 裝置可以使用 PCI 記憶體寫入無效事務,請呼叫 pci_set_mwi()。這將啟用 Mem-Wr-Inval 的 PCI_COMMAND 位,並確保正確設定快取行大小暫存器。檢查 pci_set_mwi() 的返回值,因為並非所有架構或晶片組都支援記憶體寫入無效。或者,如果 Mem-Wr-Inval 很好,但不是必需的,請呼叫 pci_try_set_mwi(),以便系統盡最大努力啟用 Mem-Wr-Inval。
1.4.2. 請求 MMIO/IOP 資源¶
不應直接從 PCI 裝置配置空間讀取記憶體 (MMIO) 和 I/O 埠地址。使用 pci_dev 結構中的值,因為 PCI “匯流排地址”可能已被 arch/晶片組特定的核心支援重新對映到“主機物理”地址。
有關如何訪問裝置暫存器或裝置記憶體,請參閱 io_mapping 函式。
裝置驅動程式需要呼叫 pci_request_region() 以驗證沒有其他裝置已經在使用相同的地址資源。相反,驅動程式應該在呼叫 pci_disable_device() 之後呼叫 pci_release_region()。這樣做是為了防止兩個裝置在同一地址範圍內發生衝突。
提示
請參閱上面的作業系統錯誤註釋。目前 (2.6.19),驅動程式只能在呼叫 pci_enable_device() _之後_ 確定 MMIO 和 IO 埠資源可用性。
pci_request_region() 的通用形式是 request_mem_region()(用於 MMIO 範圍)和 request_region()(用於 IO 埠範圍)。對於未由“正常” PCI BAR 描述的地址資源,請使用這些資源。
另請參閱下面的 pci_request_selected_regions()。
1.4.3. 設定 DMA 掩碼大小¶
注意
如果以下任何內容沒有意義,請參閱 使用通用裝置進行動態 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() 直接定址系統 RAM 中高於 4G 物理地址的“相干記憶體”,則驅動程式也必須“註冊”此功能。同樣,這包括所有 PCI-X 和 PCIe 相容裝置的驅動程式。許多 64 位“PCI”裝置(在 PCI-X 之前)和一些 PCI-X 裝置都可以進行 64 位 DMA,用於有效負載(“流式”)資料,但不能用於控制(“相干”)資料。
1.4.5. 初始化裝置暫存器¶
一些驅動程式將需要程式設計特定的“功能”欄位或其他“特定於供應商”的暫存器初始化或重置。例如,清除掛起的 中斷。
1.4.6. 註冊 IRQ 處理程式¶
雖然呼叫 request_irq() 是此處描述的最後一步,但這通常只是初始化裝置的另一箇中間步驟。通常可以將此步驟推遲到裝置開啟以供使用時。
應該使用 IRQF_SHARED 註冊 IRQ 線的所有中斷處理程式,並使用 devid 將 IRQ 對映到裝置(請記住,所有 PCI IRQ 線都可以共享)。
request_irq() 會將中斷處理程式和裝置控制代碼與中斷號關聯。從歷史上看,中斷號表示從 PCI 裝置執行到中斷控制器的 IRQ 線。使用 MSI 和 MSI-X(更多內容如下),中斷號是一個 CPU“向量”。
request_irq() 還會啟用中斷。在註冊中斷處理程式之前,請確保裝置已靜止且沒有掛起的中斷。
MSI 和 MSI-X 是 PCI 功能。兩者都是“訊息訊號中斷”,透過 DMA 寫入本地 APIC 將中斷傳遞到 CPU。MSI 和 MSI-X 之間的根本區別在於如何分配多個“向量”。MSI 需要連續的向量塊,而 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_INTX。
對於 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。
1.5. PCI 裝置關機¶
當 PCI 裝置驅動程式正在解除安裝時,需要執行以下大部分步驟
停用裝置生成 IRQ
釋放 IRQ (
free_irq())停止所有 DMA 活動
釋放 DMA 緩衝區(流式和相干)
從其他子系統登出(例如 scsi 或 netdev)
停用裝置響應 MMIO/IO 埠地址
釋放 MMIO/IO 埠資源
1.5.1. 停止裝置上的 IRQ¶
如何執行此操作特定於晶片/裝置。如果不這樣做,如果 IRQ 與另一個裝置共享,則會開啟“尖叫中斷”的可能性(並且只有在這種情況下)。
當共享 IRQ 處理程式“取消掛鉤”時,使用同一 IRQ 線的剩餘裝置仍然需要啟用 IRQ。因此,如果“取消掛鉤”的裝置斷言 IRQ 線,系統將響應,假設它是剩餘裝置之一斷言了 IRQ 線。由於其他裝置都不會處理 IRQ,因此係統將“掛起”,直到它確定 IRQ 不會被處理並遮蔽 IRQ(100,000 次迭代後)。一旦遮蔽了共享 IRQ,剩餘裝置將停止正常工作。這不是一個好的情況。
這是另一個在可用的情況下使用 MSI 或 MSI-X 的原因。MSI 和 MSI-X 被定義為獨佔中斷,因此不易受到“尖叫中斷”問題的影響。
1.5.2. 釋放 IRQ¶
一旦裝置靜止(沒有更多 IRQ),就可以呼叫 free_irq()。此函式將在處理任何掛起的 IRQ 後返回控制權,“取消掛鉤”驅動程式的 IRQ 處理程式,如果沒有人使用它,最終會釋放 IRQ。
1.5.3. 停止所有 DMA 活動¶
在嘗試取消分配 DMA 控制資料之前,停止所有 DMA 操作非常重要。如果不這樣做,可能會導致記憶體損壞、掛起,並在某些晶片組上導致硬崩潰。
在停止 IRQ 後停止 DMA 可以避免 IRQ 處理程式可能重新啟動 DMA 引擎的競爭。
雖然此步驟聽起來很明顯且微不足道,但過去幾個“成熟”的驅動程式並沒有正確完成此步驟。
1.5.4. 釋放 DMA 緩衝區¶
一旦 DMA 停止,首先清理流式 DMA。即,取消對映資料緩衝區並將緩衝區返回給“上游”所有者(如果存在)。
然後清理包含控制資料的“相干”緩衝區。
有關取消對映介面的詳細資訊,請參閱 使用通用裝置進行動態 DMA 對映。
1.5.5. 從其他子系統登出¶
大多數低階 PCI 裝置驅動程式都支援其他子系統,如 USB、ALSA、SCSI、NetDev、Infiniband 等。請確保您的驅動程式沒有丟失來自該其他子系統的資源。如果發生這種情況,典型的症狀是當子系統嘗試呼叫已解除安裝的驅動程式時出現 Oops(panic)。
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 埠範圍標記為可用。如果不這樣做,通常會導致無法重新載入驅動程式。
1.6. 如何訪問 PCI 配置空間¶
您可以使用 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) 來訪問該總線上的給定裝置和功能。
如果您訪問配置標頭的標準部分中的欄位,請使用 <linux/pci.h> 中宣告的位置和位的符號名稱。
如果您需要訪問擴充套件 PCI 功能暫存器,只需呼叫 pci_find_capability() 以獲取特定功能,它將為您找到相應的暫存器塊。
1.7. 其他有趣的函式¶
查詢與給定域、匯流排和插槽號對應的 pci_dev。如果找到該裝置,則其引用計數會增加。 |
|
設定 PCI 電源管理狀態 (0=D0 ... 3=D3) |
|
在裝置的功能列表中查詢指定的功能。 |
|
pci_resource_start() |
返回給定 PCI 區域的匯流排起始地址 |
pci_resource_end() |
返回給定 PCI 區域的匯流排結束地址 |
pci_resource_len() |
返回 PCI 區域的位元組長度 |
pci_set_drvdata() |
為 pci_dev 設定私有驅動程式資料指標 |
pci_get_drvdata() |
返回 pci_dev 的私有驅動程式資料指標 |
啟用記憶體寫入無效事務。 |
|
停用記憶體寫入無效事務。 |
1.8. 其他提示¶
當向用戶顯示 PCI 裝置名稱時(例如,當驅動程式想要告訴使用者它發現了什麼卡時),請使用 pci_name(pci_dev)。
始終透過指向 pci_dev 結構的指標來引用 PCI 裝置。所有 PCI 層函式都使用此標識,這是唯一合理的標識。不要使用匯流排/插槽/功能號,除非用於非常特殊的用途——在具有多個主匯流排的系統上,它們的語義可能非常複雜。
不要嘗試在驅動程式中啟用快速背靠背寫入。總線上的所有裝置都需要能夠執行此操作,因此這需要由平臺和通用程式碼處理,而不是由單個驅動程式處理。
1.9. 供應商和裝置標識¶
除非新的裝置或供應商 ID 在多個驅動程式之間共享,否則不要將它們新增到 include/linux/pci_ids.h 中。您可以在驅動程式中新增私有定義(如果它們有用),或者只使用純十六進位制常量。
裝置 ID 是任意十六進位制數(供應商控制),通常只在 pci_device_id 表中的單個位置使用。
請將新的供應商/裝置 ID 提交到 https://pci-ids.ucw.cz/。pci.ids 檔案的映象位於 https://github.com/pciutils/pciids。
1.10. 已過時的函式¶
當嘗試將舊驅動程式移植到新的 PCI 介面時,您可能會遇到幾個函式。它們不再存在於核心中,因為它們與熱插拔或 PCI 域或具有健全的鎖定不相容。
pci_find_device() |
已被 |
pci_find_subsys() |
已被 |
pci_find_slot() |
|
另一種替代方法是傳統的PCI裝置驅動程式,它遍歷PCI裝置列表。這仍然是可能的,但不建議使用。
1.11. MMIO空間和“寫入後發”¶
將驅動程式從使用I/O埠空間轉換為使用MMIO空間通常需要一些額外的更改。具體來說,需要處理“寫入後發”。許多驅動程式(例如tg3,acenic,sym53c8xx_2)已經這樣做了。I/O埠空間保證寫入事務在CPU繼續之前到達PCI裝置。寫入MMIO空間允許CPU在事務到達PCI裝置之前繼續。“硬體專家”稱之為“寫入後發”,因為寫入完成在事務到達目的地之前已“釋出”到CPU。
因此,對時間敏感的程式碼應新增readl(),以便CPU在執行其他工作之前等待。經典的“位操作”序列對於I/O埠空間來說工作正常
for (i = 8; --i; val >>= 1) {
outb(val & 1, ioport_reg); /* write bit */
udelay(10);
}
對於MMIO空間,相同的序列應該是
for (i = 8; --i; val >>= 1) {
writeb(val & 1, mmio_reg); /* write bit */
readb(safe_mmio_reg); /* flush posted write */
udelay(10);
}
重要的是,“safe_mmio_reg”不應具有任何干擾裝置正確執行的副作用。
另一個需要注意的情況是重置PCI裝置時。使用PCI配置空間讀取來重新整理writel()。如果PCI裝置預計不會響應readl(),這將優雅地處理所有平臺上的PCI主裝置中止。大多數x86平臺將允許MMIO讀取主裝置中止(也稱為“軟故障”)並返回垃圾(例如〜0)。但是許多RISC平臺將會崩潰(也稱為“硬故障”)。