一、如何編寫Linux PCI驅動程序


PCI的世界是廣闊的,充滿了(大部分令人不快的)驚喜。由於每個CPU體系結構實現不同的芯片集,並且PCI設備有不同的需求(“特性”),因此Linux內核中的PCI支持並不像人們希望的那么簡單。這篇簡短的文章介紹用於PCI設備驅動程序的Linux APIs。

一個更完整的資源是由Jonathan Corbet、Alessandro Rubini和Greg Kroah-Hartman編寫的“Linux設備驅動程序”的第三版。LDD3可以從https://lwn.net/Kernel/LDD3/免費獲得(根據知識共享許可協議)。

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())
  • 初始化non-PCI(即LAN/SCSI/等芯片部分)
  • 啟用DMA /處理引擎

當使用設備完成時,可能需要卸載模塊,驅動程序需要采取以下步驟:

  • 禁止設備產生irq
  • 釋放IRQ (free_irq())
  • 停止所有DMA活動
  • 釋放DMA緩沖區(包括流式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

 定義

struct pci_driver {
  struct list_head        node;
  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;
};

成員

node

  驅動結構鏈表節點。

name

  驅動名稱

id_table

  驅動程序感興趣的設備id表的指針。大多數驅動程序應該使用MODULE_DEVICE_TABLE(pci,…)導出這個表。

probe

  對於所有匹配ID表且還沒有被其他驅動“擁有”的PCI設備,這個探測函數會被調用(在pci_register_driver()對已經存在的設備執行期間,如果插入了新設備,則執行該方法)。對於ID表中條目與設備匹配的每個設備,這個函數被傳遞一個“struct pci_dev *”。當驅動程序選擇獲得設備的“所有權”時,探針函數返回0,否則返回一個錯誤代碼(負數)。探測函數總是從進程上下文中被調用,因此它可以休眠。

remove

  每當這個驅動程序處理的設備被移除時(在驅動程序注銷或手動從熱插拔槽中拔出時),remove()函數就會被調用。remove函數總是從進程上下文中被調用,因此它可以休眠。

suspend

  將設備置於低功耗狀態。

resume

  喚醒低功率狀態下的設備。

shutdown

  連接到reboot_notifier_list (kernel/sys.c)。意在停止任何空閑的DMA操作。用於啟用lan上喚醒(NIC)或在重新啟動之前改變設備的電源狀態。如drivers/net/e100.c。

sriov_configure

  可選驅動回調,允許配置的VFs數量,通過sysfs “sriov_numvfs”文件。

sriov_set_msix_vec_count

  PF驅動回調來改變VF上MSI-X向量的數量。通過sysfs " sriov_vf_msix_count "觸發。這將改變VF Message Control寄存器中的MSI-X表大小。

sriov_get_vf_total_msix

  PF驅動回調以獲得用於分配到VFs的MSI-X向量的總數。

err_handler

  錯誤處理結構

groups

  Sysfs屬性組。

dev_groups

  附加到設備上的屬性,一旦設備綁定到驅動程序,就會創建這些屬性。

driver

  驅動程序模型結構。

dynids

  動態添加的設備id列表。

 

ID表是一個以全零結尾的struct pci_device_id條目數組。通常首選使用靜態const的定義。

struct pci_device_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或包含/linux/pci_ids.h以獲得完整的類列表。大多數驅動程序不需要指定class/class_mask,因為vendor/device通常就足夠了。

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)。vendor和device字段是必須的,其他字段是可選的。用戶只需要通過必要的可選字段:

  • subvendor和subdevice字段默認為PCI_ANY_ID (FFFFFFFF)
  • 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層自動調用驅動程序處理的所有設備的remove回調。

1.2.1 驅動函數/數據的“屬性”

請在適當的地方標記初始化和清理函數(相應的宏在<linux/init.h>中定義):

__init 初始化代碼。在驅動程序初始化后丟棄。
__exit 退出代碼。對於非模塊化驅動程序忽略。

關於何時/何處使用上述屬性的提示:

  • module_init()/module_exit()函數(以及所有從這些函數中調用_only_的初始化函數)應該標記為__init/__exit。
  • 不要標記結構體pci_driver。
  • 如果不確定要使用哪個標記,則不要標記函數。與其把函數標記錯,不如不標記函數。

1.3 如何手動查找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);

按class 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())
  • 初始化non-PCI(即LAN/SCSI/等芯片部分)
  • 啟用DMA /處理引擎

驅動程序可以在任何時候訪問PCI配置空間寄存器。(好吧,幾乎。當運行BIST時,配置空間可以消失…但這只會導致PCI總線Master中止和配置讀取將返回垃圾)。

1.4.1 啟用PCI設備

在接觸任何設備寄存器之前,驅動程序需要通過調用pci_enable_device()來啟用PCI設備。這將:

  • 如果設備處於掛起狀態,請喚醒它
  • 分配設備的I/O和內存區域(如果BIOS沒有做)
  • 分配一個IRQ(如果BIOS沒有做)

pci_enable_device()可能會失敗!檢查返回值。

操作系統BUG:在啟用資源之前,我們不會檢查資源分配。如果在調用pci_enable_device()之前調用pci_request_resources(),這個序列會更有意義。目前,當兩個設備被分配了相同的范圍時,設備驅動程序無法檢測到這個bug。這不是一個常見的問題,不太可能很快得到解決。

之前已經討論過這個問題,但在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() 將通過清除總線master bit位禁用DMA。

如果PCI設備可以使用PCI Memory-Write-Invalidate事務,則調用pci_set_mwi()。這將為Mem-Wr-Inval啟用PCI_COMMAND位,並確保正確設置了緩存行大小寄存器。檢查pci_set_mwi()的返回值,因為不是所有的架構或芯片集都支持Memory-Write-Invalidate。另外,如果使用memw - inval很好,但不是必需的,則調用pci_try_set_mwi()讓系統盡最大努力啟用memw - inval。

1.4.2 請求MMIO / IOP資源

內存(MMIO)和I/O端口地址不應該直接從PCI設備配置空間讀取。使用pci_dev結構中的值,因為PCI“總線地址”可能已經被特定於arch/芯片集的內核支持重新映射到“主機物理”地址。

關於如何訪問設備寄存器或設備內存,請參見io_mapping函數。

設備驅動程序需要調用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所沒有描述的地址資源。

int pci_request_region(struct pci_dev *pdev, int bar, const char *res_name)

  預留PCI I/O和內存資源

參數

struct pci_dev *pdev
  需要預留資源的PCI設備
int bar
  BAR待定
const  char *res_name
  要與資源關聯的名稱

描述

將與PCI設備pdev BAR關聯的PCI區域標記為由所有者res_name保留的。不要訪問PCI區域內的任何地址,除非這個調用成功返回。

成功返回0,錯誤返回EBUSY。當出現故障時,還會打印一條警告消息。
 

還請參閱下面的pci_request_selected_regions()。

int pci_request_selected_regions(struct pci_dev *pdev, int bars, const char *res_name)

預留所選PCI I/O和內存資源

參數

struct pci_dev *pdev
  需要預留資源的PCI設備
int bars
  請求的bar的位掩碼
const char *res_name
  要與資源關聯的名稱

1.4.3 設置DMA掩碼大小

如果下面的內容沒有意義,請參考使用通用設備的動態DMA映射。本節只是提醒您,驅動程序需要指示設備的DMA功能,並不是DMA接口的權威來源。

雖然所有驅動程序都應該顯式地指明PCI總線主設備的DMA能力(例如32位或64位),但對於流數據具有32位以上總線主設備能力的設備需要驅動程序通過調用pci_set_dma_mask()與適當的參數來“注冊”該能力。一般來說,在系統RAM大於4G _physical_地址的系統上,這允許更有效的DMA。

所有PCI-X和PCIe兼容設備的驅動程序必須調用pci_set_dma_mask(),因為它們是64位DMA設備。

類似地,如果設備可以通過調用 pci_set_consistent_dma_mask() 在系統RAM中直接尋址4G物理地址以上的“一致內存”,驅動程序也必須“注冊”這個能力。同樣,這包括所有PCI-X和PCIe兼容設備的驅動程序。許多64位“PCI”設備(在PCI- x之前)和一些PCI- x設備都是64位DMA,能夠有效負載(“流”)數據,但不能控制(“一致”)數據。

1.4.4 設置共享控制數據

一旦設置了DMA掩碼,驅動程序就可以分配“一致的”(也就是共享的)內存。有關DMA api的完整描述,請參閱使用通用設備的動態DMA映射。本節只是提醒您,在設備上啟用DMA之前需要進行此操作。

1.4.5 初始化設備寄存器

一些驅動程序將需要特定的“能力”字段編程或其他“供應商特定的”寄存器初始化或重置。例如,清除懸而未決的中斷。

1.4.6 注冊IRQ處理程序

雖然調用request_irq()是這里描述的最后一步,但這通常只是初始化設備的另一個中間步驟。這個步驟通常可以延遲到設備打開使用。

所有IRQ線路的中斷處理程序都應該注冊到IRQF_SHARED,並使用devid將IRQs映射到設備(記住所有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 可以分配幾個單獨的向量塊。

MSI功能可以通過在調用request_irq()之前使用 PCI_IRQ_MSI 和/或 PCI_IRQ_MSIX 標志調用 pci_alloc_irq_vectors() 來啟用。這導致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有(至少)兩個很好的理由:

  1. 根據定義,MSI是一個互斥中斷向量。這意味着中斷處理程序不必驗證引起中斷的設備。
  2. MSI避免了DMA/IRQ競爭條件。當MSI被交付時,主機內存的DMA保證對主機CPU是可見的。這對於數據一致性和避免陳舊的控制數據都很重要。這種保證允許驅動程序省略MMIO讀取以刷新DMA流。

請參閱drivers/infiniband/hw/mthca/或drivers/net/tg3.c以了解MSI/MSI- x的用法。

1.5  PCI設備關閉

在卸載PCI設備驅動程序時,需要執行以下大部分步驟:

  • 禁止設備產生irq
  • 釋放IRQ (free_irq())
  • 停止所有DMA活動
  • 釋放DMA緩沖區(包括流式DMA和一致性DMA)
  • 從其他子系統注銷(例如scsi或netdev)
  • 禁止設備響應MMIO/IO端口地址
  • 釋放MMIO/IO端口資源

1.5.1 停止設備上的IRQs

如何做到這一點是芯片/設備特定的。如果沒有這樣做,當(且僅當)IRQ與另一個設備共享時,就有可能出現“screaming interrupt”。

當共享IRQ處理程序被“解除鈎”時,使用相同IRQ線路的其余設備仍然需要啟用IRQ。因此,如果“斷開”的設備斷言了IRQ線路,系統將響應假設它是斷言了IRQ線路的其余設備之一。由於沒有其他設備將處理IRQ,系統將“掛起”,直到它決定IRQ不會被處理並掩蓋IRQ(100,000次迭代后)。一旦共享IRQ被屏蔽,其余的設備將停止正常工作。情況不妙。

這是另一個使用MSI或MSI-X的原因。MSI和MSI-X被定義為排他中斷,因此不容易受到“screaming interrupt”問題的影響。

1.5.2 釋放IRQ

一旦設備處於靜止狀態(不再有irq),就可以調用free_irq()。這個函數將在任何待處理的IRQ被處理后返回控制,從那個IRQ“解除”驅動程序的IRQ處理程序,如果沒有其他人在使用它,最后釋放這個IRQ。

1.5.3 停止所有DMA活動

在試圖釋放DMA控制數據之前,停止所有DMA操作是非常重要的。如果不這樣做,可能會導致內存損壞、掛起,並在某些芯片集上導致硬崩潰。

在停止IRQ之后停止DMA可以避免IRQ處理程序可能重新啟動DMA引擎的競爭。

雖然這一步聽起來很明顯也很瑣碎,但是一些“成熟的”驅動程序在過去並沒有完成這一步。

1.5.4 釋放DMA緩沖區

一旦DMA停止,首先清理流式DMA。例如,unmap數據緩沖區和返回緩沖區的“上游”所有者,如果有一個。

然后清理包含控制數據的“一致”緩沖區。

有關取消映射接口的詳細信息,請參閱使用通用設備的動態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 *表示的設備的配置空間。所有這些函數在成功或錯誤代碼(PCIBIOS_…)時返回0,該錯誤代碼可以通過pcibios_strerror轉換為文本字符串。大多數驅動程序都希望對有效PCI設備的訪問不會失敗。

如果你沒有一個結構體pci_dev可用,你可以調用pci_bus_(read|write)_config_(byte|word|dword)來訪問總線上的給定設備和函數。

如果您訪問配置頭的標准部分的字段,請使用<linux/pci.h>中聲明的位置和位的符號名。

如果您需要訪問Extended PCI Capability寄存器,只需為特定的功能調用 pci_find_capability(),它將為您找到相應的寄存器塊。

1.7 其他有關的函數

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()

使能Memory-Write-Invalidate事務

pci_clear_mwi()

禁用Memory-Write-Invalidate事務

1.8 各種提示

當向用戶顯示PCI設備名稱時(例如,當驅動程序想告訴用戶它找到了什么卡時),請使用pci_name(pci_dev)。

總是通過指向pci_dev結構的指針來引用PCI設備。所有PCI層函數都使用這個標識,而且它是唯一合理的標識。不要使用總線/插槽/函數號,除非是非常特殊的用途——在有多個主總線的系統上,它們的語義可能相當復雜。

不要試圖打開快速背靠背寫在你的驅動程序。總線上的所有設備都需要能夠做到這一點,所以這需要由平台和通用代碼來處理,而不是單個驅動程序。

1.9 供應商和設備標識

不要添加新的設備或廠商id來包含/linux/pci_ids.h,除非它們在多個驅動程序之間共享。如果有用的話,你可以在驅動中添加私有定義,或者使用普通的十六進制常量。

設備id是任意的十六進制數字(供應商控制),通常只在pci_device_id表中使用。

請提交新的供應商/設備IDs到https://pci-ids.ucw.cz/。這是一個pci.ids文件的鏡像在 https://github.com/pciutils/pciids。

1.10 已停用的函數

在嘗試將舊驅動程序移植到新的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設備列表。這仍然是可能的,但令人沮喪。

1.11 MMIO空間與“Write Posting”

將驅動程序從使用I/O端口空間轉換為使用MMIO空間通常需要一些額外的更改。具體來說,“write posting”需要處理。許多驅動程序(如tg3, acenic, sym53c8xx_2)已經這樣做了。I/O端口空間保證在CPU繼續之前寫事務到達PCI設備。對MMIO空間的寫操作允許CPU在事務到達PCI設備之前繼續。HW weenies稱之為“Write Posting”,因為在事務到達目的地之前,寫完成被“post”到CPU。

因此,對時間敏感的代碼應該添加readl(),以便CPU在執行其他工作之前等待。經典的“bit banging”序列適用於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 Configuration空間讀取來刷新writel()。如果PCI設備不響應readl(),這將優雅地處理所有平台上的PCI Master中止。大多數x86平台將允許MMIO讀取master中止(也稱為“Soft Fail”)並返回垃圾(例如~0)。但是許多RISC平台會崩潰(也就是“Hard Fail”)。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM