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 *pdev

VGA 卡的 pci 裝置,如果為 NULL,則表示系統預設裝置

unsigned int rsrc

要獲取和鎖定的資源的位掩碼

描述

vga_get 的快捷方式,interruptible 設定為 true。

成功後,再次使用 vga_put() 釋放 VGA 資源。

int vga_get_uninterruptible(struct pci_dev *pdev, unsigned int rsrc)

vga_get() 的快捷方式

引數

struct pci_dev *pdev

VGA 卡的 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 *pdev

PCI 裝置。

描述

如果 pdev 是預設的 VGA 裝置,則取消繫結和登出 vgacon。GPU 驅動程式可以在初始化時呼叫它,以確保 vgacon 完成的 VGA 暫存器訪問不會干擾裝置。

int vga_get(struct pci_dev *pdev, unsigned int rsrc, int interruptible)

獲取並鎖定 VGA 資源

引數

struct pci_dev *pdev

VGA 卡的 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 *pdev

VGA 卡的 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 *pdev

VGA 卡的 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 *pdev

VGA 客戶端的 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 樹。

  1. https://cgit.freedesktop.org/xorg/xserver/commit/?id=4b42448a2388d40f257774fbffdccaea87bd0347

  2. https://lists.freedesktop.org/archives/xorg/2005-March/006663.html

  3. https://lists.freedesktop.org/archives/xorg/2005-March/006745.html

  4. https://lists.freedesktop.org/archives/xorg/2007-October/029507.html