VGA 仲裁器¶
圖形裝置透過 I/O 或記憶體空間中的範圍進行訪問。雖然大多數現代裝置允許重新定位這些範圍,但一些在 PCI 上實現的“傳統” VGA 裝置通常具有與其在 ISA 上相同的“硬解碼”地址。有關更多詳細資訊,請參閱“PCI 匯流排繫結到 IEEE Std 1275-1994 標準啟動(初始化配置)韌體修訂版 2.1”第 7 節,傳統裝置。
當同一臺機器上存在多個傳統裝置時,X 伺服器 [0] 中的資源訪問控制 (RAC) 模組用於傳統的 VGA 仲裁任務(以及其他匯流排管理任務)。但是,當這些設備嘗試被不同的使用者空間客戶端訪問時(例如,並行執行兩個伺服器),就會出現問題。它們的地址分配衝突。此外,理想情況下,作為使用者空間應用程式,控制匯流排資源不是 X 伺服器的角色。因此,需要 X 伺服器之外的仲裁方案來控制這些資源的共享。本文件介紹了為 Linux 核心實現的 VGA 仲裁器的操作。
vgaarb 核心/使用者空間 ABI¶
vgaarb 是 Linux 核心的一個模組。首次載入時,它會掃描所有 PCI 裝置,並將 VGA 裝置新增到仲裁中。然後,仲裁器啟用/停用 VGA 傳統指令在不同裝置上的解碼。不想/不需要使用仲裁器的裝置可以透過呼叫 vga_set_legacy_decoding() 顯式地告訴它。
核心向客戶端匯出一個字元裝置介面 (/dev/vga_arbiter),它具有以下語義
- 開啟
開啟仲裁器的使用者例項。預設情況下,它連線到系統的預設 VGA 裝置。
- 關閉
關閉使用者例項。釋放使用者進行的鎖定。
- 讀取
返回一個字串,指示目標的狀態,例如
“<card_ID>,decodes=<io_state>,owns=<io_state>,locks=<io_state> (ic,mc)”
IO 狀態字串的形式為 {io,mem,io+mem,none},mc 和 ic 分別是記憶體和 io 鎖定計數(僅用於除錯/診斷)。“decodes”表示卡當前解碼的內容,“owns”表示當前在其上啟用的內容,“locks”表示此卡鎖定的內容。如果卡被拔出,我們會得到 “invalid” 作為 card_ID,並且對於任何命令都會返回 -ENODEV 錯誤,直到目標設定為新卡。
- 寫入
向仲裁器寫入命令。命令列表
- target <card_ID>
切換目標到卡 <card_ID> (見下文)
- lock <io_state>
獲取目標上的鎖 (“none” 是一個無效的 io_state)
- trylock <io_state>
非阻塞地獲取目標上的鎖 (如果不成功,則返回 EBUSY)
- unlock <io_state>
釋放目標上的鎖
- unlock all
釋放此使用者持有的目標上的所有鎖(尚未實現)
- decodes <io_state>
設定卡的傳統解碼屬性
- poll
如果任何卡上發生變化(不僅僅是目標),則觸發事件
card_ID 的形式為 “PCI:domain:bus:dev.fn”。它可以設定為 “default” 以返回到系統預設卡(待辦事項:尚未實現)。目前,僅支援 PCI 作為字首,但使用者空間 API 在將來可能會支援其他匯流排型別,即使當前的核心實現不支援。
關於鎖的說明
驅動程式跟蹤哪個使用者在哪個卡上擁有哪些鎖。它支援像核心一樣的堆疊。這使實現變得有點複雜,但使仲裁器對使用者空間問題更具容忍性,並能夠在程序死亡的所有情況下正確清理。目前,對於仲裁器的給定使用者(檔案描述符例項),最多可以同時從使用者空間為 16 張卡發出鎖。
對於熱插拔裝置,有一個鉤子 - pci_notify() - 用於通知它們在系統中被新增/刪除,並自動在仲裁器中新增/刪除。
DRM、vgacon 或其他驅動程式想要使用它時,仲裁器還有一個核心 API。
核心介面¶
-
int vga_get_interruptible(struct pci_dev *pdev, unsigned int rsrc)¶
引數
struct pci_dev *pdevVGA 卡的 pci 裝置,如果為 NULL,則表示系統預設裝置
unsigned int rsrc要獲取和鎖定的資源的位掩碼
描述
vga_get 的快捷方式,interruptible 設定為 true。
成功後,再次使用 vga_put() 釋放 VGA 資源。
引數
struct pci_dev *pdevVGA 卡的 pci 裝置,如果為 NULL,則表示系統預設裝置
unsigned int rsrc要獲取和鎖定的資源的位掩碼
描述
vga_get 的快捷方式,interruptible 設定為 false。
成功後,再次使用 vga_put() 釋放 VGA 資源。
-
struct pci_dev *vga_default_device(void)¶
返回預設的 VGA 裝置,用於 vgacon
引數
void無引數
描述
這可以由平臺定義。預設實現相當愚蠢,可能只能在單個 VGA 卡設定和/或 x86 平臺上正常工作。
如果您的 VGA 預設裝置不是 PCI,則必須在此處返回 NULL。在這種情況下,我假設它不會與任何 PCI 卡衝突。如果這不是真的,我將必須定義兩個 arch 鉤子來啟用/停用 VGA 預設裝置(如果可能)。這對於真正的 _ISA_ VGA 卡,以及 PCI 卡來說可能是一個問題。我現在不知道如何處理該卡。它們的 IO 可以完全停用嗎?如果不能,那麼我認為這是一個具有適當的 arch 鉤子告訴我們它的問題,所以我們基本上不允許任何人成功呼叫 vga_get()。
-
int vga_remove_vgacon(struct pci_dev *pdev)¶
停用 VGA 控制檯
引數
struct pci_dev *pdevPCI 裝置。
描述
如果 pdev 是預設的 VGA 裝置,則取消繫結和登出 vgacon。GPU 驅動程式可以在初始化時呼叫它,以確保 vgacon 完成的 VGA 暫存器訪問不會干擾裝置。
-
int vga_get(struct pci_dev *pdev, unsigned int rsrc, int interruptible)¶
獲取並鎖定 VGA 資源
引數
struct pci_dev *pdevVGA 卡的 PCI 裝置,如果為 NULL,則表示系統預設裝置
unsigned int rsrc要獲取和鎖定的資源的位掩碼
int interruptible阻塞應該被訊號中斷嗎?
描述
獲取給定卡的 VGA 資源,並將這些資源標記為已鎖定。如果請求的資源是“normal”(而不是傳統)資源,則仲裁器將首先檢查該卡是否正在為該型別的資源進行傳統解碼。如果是,則鎖被“轉換”為傳統資源鎖。
仲裁器將首先查詢可能衝突的所有 VGA 卡,並停用它們的 IO 和/或記憶體訪問,包括在 P2P 網橋上的 VGA 轉發(如果需要),以便可以使用請求的資源。然後,該卡被標記為鎖定這些資源,並且在該卡上啟用 IO 和/或記憶體訪問(包括在父 P2P 網橋上的 VGA 轉發(如果有))。
如果某些衝突卡已經鎖定了一個或多個所需資源(或者在不同的匯流排段上的任何資源,因為 P2P 網橋不區分 VGA 記憶體和 IO),則此函式將阻塞。您可以指示此阻塞是否應被訊號(對於使用者空間介面)中斷。
不得在中斷時間或原子上下文中呼叫。如果卡已經擁有資源,則該函式成功。支援巢狀呼叫(維護每個資源的計數器)
成功後,再次使用 vga_put() 釋放 VGA 資源。
成功返回 0,失敗返回負錯誤程式碼。
-
void vga_put(struct pci_dev *pdev, unsigned int rsrc)¶
釋放傳統 VGA 資源的鎖
引數
struct pci_dev *pdevVGA 卡的 PCI 裝置,如果為 NULL,則表示系統預設裝置
unsigned int rsrc要釋放的資源的位掩碼
描述
釋放先前由 vga_get() 或 vga_tryget() 鎖定的資源。資源不會立即停用,因此隨後對同一卡上的 vga_get() 將立即成功。資源有一個計數器,因此只有當計數器達到 0 時才會釋放鎖。
-
void vga_set_legacy_decoding(struct pci_dev *pdev, unsigned int decodes)¶
引數
struct pci_dev *pdevVGA 卡的 PCI 裝置
unsigned int decodes卡解碼的傳統區域的位掩碼
描述
向仲裁器指示該卡是否解碼傳統 VGA IO、傳統 VGA 記憶體、兩者或都不解碼。所有卡預設為兩者,卡驅動程式(例如 fbdev)應告知仲裁器它是否已停用傳統解碼,因此可以將該卡排除在仲裁過程之外(並且可以隨時安全地接收中斷)。
-
int vga_client_register(struct pci_dev *pdev, unsigned int (*set_decode)(struct pci_dev *pdev, bool decode))¶
註冊或登出 VGA 仲裁客戶端
引數
struct pci_dev *pdevVGA 客戶端的 PCI 裝置
unsigned int (*set_decode)(struct pci_dev *pdev, bool decode)VGA 解碼更改回調
描述
客戶端有兩種回撥機制可以使用。
set_decode 回撥:如果客戶端可以停用其 GPU VGA 資源,它將收到此回撥以設定編碼/解碼狀態。
理由:我們不能無條件地停用 VGA 解碼資源,因為某些單 GPU 筆記型電腦似乎需要 ACPI 或 BIOS 訪問 VGA 暫存器來控制背光燈等。希望較新的多 GPU 筆記型電腦可以執行更合理的操作,並且桌上型電腦不會有任何特殊的 ACPI。當用戶空間首次使用 VGA 仲裁時,驅動程式將收到回撥,因為一些較舊的 X 伺服器存在問題。
不檢查是否已為 pdev 註冊了客戶端。
要登出,請呼叫 vga_client_unregister()。
返回值
成功返回 0,失敗返回 -ENODEV
libpciaccess¶
為了使用 vga 仲裁器字元裝置,在 libpciaccess 庫中實現了一個 API。一個欄位已新增到 struct pci_device(系統上的每個裝置)
/* the type of resource decoded by the device */
int vgaarb_rsrc;
除此之外,在 pci_system 中添加了
int vgaarb_fd;
int vga_count;
struct pci_device *vga_target;
struct pci_device *vga_default_dev;
vga_count 用於跟蹤有多少卡正在仲裁,因此,例如,如果只有一張卡,那麼它可以完全避開仲裁。
以下函式獲取給定卡的 VGA 資源,並將這些資源標記為已鎖定。如果請求的資源是“normal”(而不是傳統)資源,則仲裁器將首先檢查該卡是否正在為該型別的資源進行傳統解碼。如果是,則鎖被“轉換”為傳統資源鎖。仲裁器將首先查詢可能衝突的所有 VGA 卡,並停用它們的 IO 和/或記憶體訪問,包括在 P2P 網橋上的 VGA 轉發(如果需要),以便可以使用請求的資源。然後,該卡被標記為鎖定這些資源,並且在該卡上啟用 IO 和/或記憶體訪問(包括在父 P2P 網橋上的 VGA 轉發(如果有))。在 vga_arb_lock() 的情況下,如果某些衝突卡已經鎖定了一個或多個所需資源(或者在不同的匯流排段上的任何資源,因為 P2P 網橋不區分 VGA 記憶體和 IO),則該函式將阻塞。如果卡已經擁有資源,則該函式成功。vga_arb_trylock() 將返回 (-EBUSY) 而不是阻塞。支援巢狀呼叫(維護每個資源的計數器)。
設定此客戶端的目標裝置。
int pci_device_vgaarb_set_target (struct pci_device *dev);
例如,在 x86 中,如果同一總線上的兩個裝置想要鎖定不同的資源,則兩者都會成功(鎖定)。如果裝置位於不同的總線上,並且嘗試鎖定不同的資源,則只有第一個嘗試者才會成功。
int pci_device_vgaarb_lock (void);
int pci_device_vgaarb_trylock (void);
解鎖裝置的資源。
int pci_device_vgaarb_unlock (void);
向仲裁器指示該卡是否解碼傳統 VGA IO、傳統 VGA 記憶體、兩者或都不解碼。所有卡預設為兩者,卡驅動程式(例如 fbdev)應告知仲裁器它是否已停用傳統解碼,因此可以將該卡排除在仲裁過程之外(並且可以隨時安全地接收中斷)。
int pci_device_vgaarb_decodes (int new_vgaarb_rsrc);
連線到仲裁器裝置,分配 struct
int pci_device_vgaarb_init (void);
關閉連線
void pci_device_vgaarb_fini (void);
xf86VGAArbiter (X 伺服器實現)¶
X 伺服器基本上包裝了以某種方式觸控 VGA 暫存器的所有函式。
參考文獻¶
Benjamin Herrenschmidt (IBM?) 在 2005 年與 Xorg 社群討論此類設計時開始了這項工作 [1, 2]。在 2007 年底,Paulo Zanoni 和 Tiago Vignatti(均來自 C3SL/巴拉那聯邦大學)繼續了他的工作,增強了核心程式碼以適應作為核心模組,並且還完成了使用者空間端的實現 [3]。現在(2009 年),Tiago Vignatti 和 Dave Airlie 最終將這項工作成形,並排隊到 Jesse Barnes 的 PCI 樹。