來自核心文件/Documentation/pci/MSI-HOWTO.txt

1. 什麼是MSI

MSI全稱Message Signaled Interrupt
當設備向一個特殊地址寫入時,會向CPU產生一個中斷,即也MSI中斷。
MSI
能力最初在PCI 2.2里定義,在PCI 3.0里被強化,使得每個中斷都可以單獨控制。
PCI 3.0
還引入了MSI-X能力,相比MSI,每個設備可以支持更多的中斷,并且可以獨立配置。

設備可以同時支持MSIMSI-X,但一時刻只能致能其中一種。

2. 為什麼使用MSI

與傳統引腳中斷相比,有三個方面的優勢。
基於引腳的PCI中斷經常在幾個設備間共享,核心必須呼叫與該中斷相關的每一個中斷處理函數,降低了效率。MSI不是共享的,所以不存在這個問題。
當設備向記憶體寫入數據,然后發起引腳中斷時,有可能在CPU收到中斷時,數據還未到達記憶體(在PCI-PCI橋后的設備更有可能如此)。為了保證數據已達 記憶體,中斷處理程序必須輪詢產生該中斷的設備的一個暫存器,PCI事務保序規則會確保所有數據到達記憶體后,暫存器才會返回值。
使用MSI時,產生中斷的寫不能越過數據寫,因而避免了這個問題。當中斷產生時,驅動可以確信所有數據已經到達記憶體。

PCI的每個功能設備只支持一個基於引腳的中斷,驅動常常需要查詢設備來確定發生的事件,降低了中斷處理的效率。通過MSI,一個設備可以支持多個中斷,這樣可以為不同的使用不同的中斷。比如:

     1. 給不常發生的事件(如錯誤)指定獨立的中斷,這樣驅動可以正常中斷路徑進行更有效的處理。

     2. 給網卡的每個報文隊列或者存儲控制器的每個端口分配中斷。

3. 如何使用MSI

PCI設備初始化為使用基於引腳的中斷,設備驅動需要將設備配置為使用MSIMSI-X。不是所有的設備可以完整支持MSI,下面有些API就直接返回失敗,因此仍然使用基於引腳的中斷。

3.1 核心提供MSI支持

核心需要配置CONFIG_PCI_MSI以支持MSI或者MSI-X,該配置是否可以配置受架搆和其它一些配置的影響。

3.2 使用MSI

大部分工作在PCI層的驅動里完成,只需要請求PCI層為設備設置MSI能力即可。

3.2.1 pci_enable_msi

int pci_enable_msi(struct pci_dev *dev)
這個呼叫只給設備分配一個中斷,不管設備支持多少個MSI中斷。設備會從基於引腳中斷模式切換為MSI模式。dev->irq會賦予一個新的代表MSI的編號。

在驅動需要在呼叫request_irq()之前呼叫這個接口。因為打開MSI中斷的同時會禁用引腳中斷IRQ,驅動因此不會收到舊的中斷。

3.2.2 pci_enable_msi_block

int pci_enable_msi_block(struct pci_dev *dev, int count)
這個接口是pci_enable_msi的變種,它可以請求多個MSI中斷。MSI規范要求中斷必須以2的冪分配,最多為2^5(32)
如果函數返回0,則表示成功分配了不少於驅動請求的中斷數量(可能會大於請求數量)。該函數打開設備的MSI,并將中斷組的最小編號賦給dev->irq,該設備的中斷范圍為dev->irqdev->irq + count - 1
如果函數返回負值,表示設備無法提供更多的中斷,驅動不應試圖再去請求。如果返回的是正值,這個值會小於count,表示目前最分配的最大中斷數量。對這於兩種情況,函數都不會更新irq值,設備也不會切換到MSI模式。

設備驅動必須要正確處理上述第二種情況。在中斷數量不夠的情況下,有的設備尚可工作,驅動就應再次呼叫pci_enable_msi_block()。不過由於一些限制,第二次呼叫未必也可以成功。

3.2.3 pci_disable_msi

void pci_disable_msi(struct pci_dev *dev)
該函數的功能是撤消pci_enable_msi()pci_enable_msi_block()的工作。它恢復dev->irq為引腳中斷號,釋放此前分配的MSI。中斷號之后有可能分配給其它設備,因此驅動不應保留dev->irq的值。

驅動必須呼叫free_irq()以釋放之前request_irq()分配的中斷號。否則會產生BUG_ON(),設備將繼續保持MSI致能,并泄露中斷向量。

3.3 使用MSI-X

MSI-X能力比MSI更為靈活,它支持2048個中斷,每個中斷都可以單獨控制。要支持這種能力,驅動必須使用結搆struct msix_entry



  1. struct msix_entry { 

  2.      u16 vector;     /* kernel uses to write alloc vector */ 

  3.      u16 entry;     /* driver uses to specify entry */ 



  1. struct msix_entry {  

  2.      u16 vector;     /* kernel uses to write alloc vector */  

  3.      u16 entry;     /* driver uses to specify entry */  

  4. }  

這個定義允許設備以分散的方式使用中斷(比如使用中斷31027,只需分配兩個陣列元素)。驅動負責填充陣列各元素的entry部分,以使核心為其分配中斷。不能給多個entry賦予相同的編號。

3.3.1 pci_enable_msix

int pci_enable_msix(struct pci_dev *dev, struct msix_entry *entries, int nvec)
該函數向PCI子系統請求分配nvecMSI中斷。入參entries指向結搆,元素個數不少於nvec。函數返回0表示呼叫成功,設備被切換至 MSI-X中斷模式,陣列元素中的vector字段也被填充為中斷號。驅動接下來為每個需要使用的vector呼叫request_irq()
如果函數返回負值,則表示出錯,驅動不應再從該設備申請分配MSI-X中斷。如果返回的是正值,則表示最大可以分配的中斷數。
該函數與pci_enable_msi()不同之處在於它不修改dev->irq,一旦MSI-X致能后,dev->irq這個中斷號不會再產生中斷。驅動需要記錄已分配的MSI-X中斷號,以保證后續的資源釋放。
一般來說,驅動在設備初始化時呼叫一次該函數。
由於種種原因,核心有可能無法提供驅動所要求的中斷數量,一個好的驅動應該能夠處理可變數量的MSI-X中斷。下面是一個例子:



  1. static int foo_driver_enable_msix(struct foo_adapter *adapter, int nvec) 

  2.      while (nvec >= FOO_DRIVER_MINIMUM_NVEC) 

  3.     

  4.           rc = pci_enable_msix(adapter->pdev, adapter->msix_entries, nvec); 

  5.           if (rc > 0) 

  6.                nvec = rc; 

  7.           else 

  8.                return rc; 

  9.     

  10.      return -ENOSPC; 

  11.  



  1. static int foo_driver_enable_msix(struct foo_adapter *adapter, int nvec)  

  2. {  

  3.      while (nvec >= FOO_DRIVER_MINIMUM_NVEC)  

  4.      {  

  5.           rc = pci_enable_msix(adapter->pdev, adapter->msix_entries, nvec);  

  6.           if (rc > 0)  

  7.                nvec = rc;  

  8.           else  

  9.                return rc;  

  10.      }  

  11.      return -ENOSPC;  

  12.   

  13. }  

3.3.2 pci_disable_msix

void pci_disable_msix(struct pci_dev *dev)
這個函數的作用是撤銷pci_enable_msix()的工作,它釋放之前分配的MSI中斷。同樣,釋放的中斷號后續可能會分配給其它設備,驅動不應再記錄使用這些中斷號。

在呼叫這個函數之前,驅動必須呼叫free_irq()以釋放request_irq()分配的中斷號,否則會產生BUG_ON,設備將維持在MSI致能的狀態,并泄漏中斷向量。

3.3.3 MSI-X

MSI-X能力指定了一個BARBAR內偏移量用於訪問MSI-X表,這個地址由PCI子系統映射,驅動不應該直接訪問。如果驅動想屏蔽或者開啟一個中斷,應該呼叫disable_irq()/enable_irq()

3.4 處理同時支持MSIMSI-X能力的設

如果設備同時支持MSIMSI-X,則可以運行在MSI模式或者MSI-X模式下,但不能同時運行,這是PCI規則的要求,因此PCI層也進行了 限制。在MSI-X致能的情況下呼叫pci_enable_msi()或者在MSI致能的情況下呼叫pci_enable_msix()將產生錯誤。如果 設備驅動在運行時希望在MSIMSI-X之間切換,它必須先停止設備,然后將其切換為引腳中斷模式,然后再通過pci_enable_msi()pci_enable_msix()進入MSIMSI-X模式。這種操作并不常見,在開發過程中用於調試/測試。

3.5 使用MSI的考慮

3.5.1 選擇MSI-XMSI

如果設備同時支持MSI-XMSI能力,應優先考慮使用MSI-XMSI-X支持1~2048間任意數量的中斷,而MSI只支持32個中斷(并 且必須是2的冪)。MSI中斷必須是連續分配的,系統不能像MSI-X那樣分配這麼多的中斷向量。在某些平台上,MSI中斷只能發送給一個CPU 組,MSI-X中斷可以發給不同的CPU

3.5.2 spinlock

多數驅動為每個設備定義了一個spinlock,在中斷處理函數中取鎖,對於引腳中斷或者單個MSI中斷,不需要禁用中斷(Linux保證同一個中斷不會 重入)。如果設備使用了多個中斷,驅動在持鎖期間必須禁用中斷,否則在設備產生另一個中斷時,驅動會遞歸取鎖從而產生死鎖。
有兩個解決方法,一個是使用spin_lock_irqsave()spin_lock_irq(),另一個是在request_irq()呼叫時指定IRQF_DISABLED,核心會在禁用中斷的環境下完成整個中斷處理過程。

如果MSI中斷處理程序不在整個過程中持鎖,使用第一種方法是最好的。如果想避免在中斷禁用/致能狀態間切換,則選擇第二種方法。

3.6 如何得知設備的MSI/MSI-X已經致能

使用lspci -v。有些設備會顯示"MSI""Message Signaled Interrupts"或者"MSI-X"能力,致能的會在前面顯示+,禁用的會顯示"-"

4. MSI quirks

一些PCI晶片或設備不支持MSIPCI子系統提供了三種方法禁用MSI
     1.
全域禁用
     2.
禁用特定橋之下的所有設備

     3. 禁用某個設備

4.1 全域禁用

有些host晶片不能正確支持MSI,如果廠家在ACPI FADT表中明確了,Linux會自動禁用MSI。有些單板沒有在這個表包含這樣的信息,需要自己檢測,這些都列在drivers/pci /quriks.c中的quirk_disable_all_msi()中了。

如果單板在MSI支持上有問題,可以在核心命令參數里加上pci=nomsi以禁用所有設備的MSI

4.2 禁用特定橋之下的所有設備

有些PCI橋不能在總線間正確地傳遞MSI,這種情況必須禁用該橋之下所有設備的MSI
有些橋允許通過配置空間的某些位來致能MSI。在可能的情況下,Linux會盡量打開host晶片的MSI支持。如果某個橋片Linux并不識別,而你確定它可以使用MSI,可以通過下面的命令打開MSI支持。
     echo 1 > /sys/bus/pci/devices/$bridge/msi_bus
$bridge
是橋的PCI地址(比如0000:00:0e.0)。

要禁用MSI,使用echo 0即可。

4.3 禁用某個設備

如果某些設備已知在MSI實現上有問題,一般是在設備驅動里處理,如果有必要,也可以在quirk里處理。

4.4 設備MSI被禁用的原因查找

除上述情況外,還有很多原因會導致一個設備的MSI沒有致能,第一步應該仔細檢查dmesg,看MSI有沒有致能,還要檢查CONFIG_PCI_MSI配置有沒有打開。
通過lspci -t可以查看設備之上的橋,讀取/sys/bus/pci/devices/*/msi_busMSI是否致能了。
檢查設備驅動是否支持MSI,比如是否呼叫了pci_enable_msi(), pci_enable_msix()或者pci_enable_msi_block()等等。

 

 

原文:

http://blog.csdn.net/wbcuc/article/details/8940249

arrow
arrow
    全站熱搜

    立你斯 發表在 痞客邦 留言(0) 人氣()