記憶體管理¶
BO 管理¶
TTM 管理(放置、驅逐等)XE 中的所有 BO。
BO 建立¶
建立可供 GPU 使用的記憶體塊。建立時傳入放置規則(sysmem 或 vram 區域)。TTM 處理 BO 的放置,並可以觸發其他 BO 的驅逐,以便為新 BO 騰出空間。
核心 BO¶
核心 BO 作為驅動程式載入的一部分建立(例如,uC 韌體映象、GuC ADS 等),或者作為需要核心 BO 的使用者操作的一部分建立(例如,引擎狀態、頁表的記憶體等)。這些 BO 通常對映在 GGTT 中(除了頁表的記憶體之外的任何核心 BO 都在 GGTT 中),被鎖定(執行時無法移動或驅逐),具有 vmap(XE 可以透過 xe_map 層訪問記憶體)並且具有連續的物理記憶體。
以下是核心 BO 被鎖定和連續的原因的更多詳細資訊。
使用者 BO¶
使用者 BO 透過 DRM_IOCTL_XE_GEM_CREATE IOCTL 建立。建立後,可以 mmap BO(透過 DRM_IOCTL_XE_GEM_MMAP_OFFSET)以供使用者訪問,並且可以繫結 BO 以供 GPU 訪問(透過 DRM_IOCTL_XE_VM_BIND)。所有使用者 BO 都是可驅逐的,並且使用者 BO 永遠不會被 XE 鎖定。後備儲存的分配可以從建立時延遲到首次使用時,即 mmap、bind 或 pagefault。
私有 BO¶
私有 BO 是使用傳遞到 create IOCTL 的有效 VM 引數建立的使用者 BO。如果 BO 是私有的,則無法透過 prime FD 匯出,並且只能在繫結到的 VM 中為 BO 建立對映。最後,BO dma-resv 插槽/鎖指向 VM 的 dma-resv 插槽/鎖(VM 的所有私有 BO 共享通用的 dma-resv 插槽/鎖)。
外部 BO¶
外部 BO 是使用傳遞到 create IOCTL 的 NULL VM 引數建立的使用者 BO。外部 BO 可以透過 prime FD 與不同的 UMD/裝置共享,並且 BO 可以對映到多個 VM 中。外部 BO 具有自己唯一的 dma-resv 插槽/鎖。外部 BO 將位於具有 BO 對映的所有 VM 的陣列中。這允許 VM 查詢和鎖定 VM 中對映的所有外部 BO(如果需要)。
BO 放置¶
建立使用者 BO 時,會傳遞有效放置的掩碼,指示哪些記憶體區域被認為是有效的。
記憶體區域資訊可透過查詢 uAPI 獲得(TODO:新增連結)。
BO 驗證¶
BO 驗證 (ttm_bo_validate) 是指確保 BO 具有有效的放置。如果 BO 被交換到臨時儲存,則驗證呼叫將觸發移動回到有效的(GPU 可以訪問 BO 的位置)放置。BO 的驗證可能會驅逐其他 BO,以便為要驗證的 BO 騰出空間。
BO 驅逐 / 移動¶
所有驅逐(或者換句話說,將 BO 從一個記憶體位置移動到另一個記憶體位置)都透過 TTM 進行路由,並回調到 XE。
執行時驅逐¶
執行時驅逐是指在正常操作期間,TTM 決定需要移動 BO 的情況。通常,這是因為 TTM 需要為另一個 BO 騰出空間,並且被驅逐的 BO 是 LRU 列表中第一個未鎖定的 BO。
一個例子是隻能放置在 VRAM 中的新 BO,但 VRAM 中沒有空間。可能有多個具有 sysmem 和 VRAM 放置規則的 BO 當前駐留在 VRAM 中,TTM 將觸發移動這些 BO 中的一個(或多個),直到 VRAM 中有空間放置新的 BO。被驅逐的 BO 有效,但在再次使用 BO 之前仍然需要新的繫結(執行或計算模式重新繫結 worker)。
另一個例子是,TTM 找不到另一個具有有效放置的 BO 來驅逐。在這種情況下,TTM 會將一個(或多個)未鎖定的 BO 驅逐到臨時不可達(無效)的放置。被驅逐的 BO 無效,並且在下次使用之前需要移動到有效的放置並重新繫結。
在這兩種情況下,這些 BO 的移動都安排在 BO 的 dma-resv 插槽中的柵欄之後。
WW 鎖定嘗試確保如果 2 個 VM 使用 51% 的記憶體,則兩個 VM 都會取得進展。
執行時驅逐使用每個 GT 遷移引擎(TODO:連結到遷移引擎文件)來執行從一個位置到另一個位置的 GPU memcpy。
執行時驅逐後重新繫結¶
當 BO 被移動時,BO 的每個對映 (VMA) 都需要在再次使用 BO 之前重新繫結。當 BO 被移動時,每個 VMA 都會新增到其 VM 的已驅逐列表中。這是安全的,因為 VM 鎖定結構(TODO:連結到 VM 鎖定文件)。在下次使用 VM 時(執行或計算模式重新繫結 worker),會檢查已驅逐的 VMA 列表並觸發重新繫結。在發生錯誤的 VM 的情況下,重新繫結在頁面錯誤處理程式中完成。
VRAM 的掛起/恢復驅逐¶
在裝置掛起/恢復期間,VRAM 可能會失去電源,這意味著 VRAM 記憶體的內容會被清除。因此,為了儲存 VRAM 中存在的 BO 的內容,必須在掛起時將它們移動到 sysmem。
一個簡單的 TTM 呼叫 (ttm_resource_manager_evict_all) 可以將所有非鎖定(使用者)BO 移動到 sysmem。需要手動驅逐鎖定的外部 BO,使用一個簡單的迴圈 + xe_bo_evict 呼叫。核心 BO 會稍微複雜一些。
一些核心 BO 被 GT 遷移引擎用於執行移動,因此我們無法透過 GT 遷移引擎移動所有 BO。為了簡化起見,在掛起或恢復時,使用 TTM memcpy (CPU) 移動任何核心(鎖定)BO。
一些核心 BO 需要恢復到完全相同的物理位置。TTM 使這非常容易,但需要注意的是,記憶體必須是連續的。同樣為了簡化起見,我們強制所有核心(鎖定)BO 都是連續的,並恢復到相同的物理位置。
VRAM 中鎖定的外部 BO 在恢復時透過 GPU 恢復。
掛起/恢復後重新繫結¶
大多數核心 BO 具有 GGTT 對映,必須在恢復過程中恢復。所有使用者 BO 在驗證後在下次使用時重新繫結。
未來工作¶
精簡透過 TTM memcpy 在掛起/恢復時儲存/恢復的 BO 列表。我們真正需要透過 TTM memcpy 儲存/恢復的是 GuC 載入所需的記憶體以及 GT 遷移引擎執行所需的記憶體。
不要要求核心 BO 在物理記憶體中是連續的/在恢復時恢復到相同的物理地址。在所有可能性中,唯一需要恢復到相同物理地址的記憶體是用於頁表的記憶體。所有這些記憶體一次分配 1 頁,因此不需要連續要求。如果核心 BO 不是連續的,則需要在 vmap 程式碼上進行一些工作。
使一些核心 BO 可驅逐而不是鎖定。一個例子是引擎狀態,在所有可能性中,如果這些 BO 的 dma-slots 被正確使用而不是鎖定,我們可以根據需要安全地驅逐 + 重新繫結這些 BO。
某些核心 BO 不需要在恢復時恢復(例如,GuC ADS,因為它在恢復時重新填充),新增一個標誌來標記此類物件為不儲存/恢復。
GGTT¶
Xe GGTT 實現了對全域性虛擬地址空間的支援,該空間用於特權(即核心模式)程序可訪問的資源,並且不繫結到特定的使用者級程序。例如,圖形微控制器 (GuC) 和顯示引擎(如果存在)利用此全域性地址空間。
全域性 GTT (GGTT) 將全域性虛擬地址轉換為可由 HW 訪問的物理地址。GGTT 是一個扁平的單級表。
Xe 實現了 GGTT 的簡化版本,專門管理從 Write Once Protected Content Memory (WOPCM) 佈局到預定義的 GUC_GGTT_TOP 的特定範圍。這種方法避免了與 GuC(圖形微控制器)硬體限制相關的複雜性。GuC 地址空間在 GGTT 的兩端都受到限制,因為 GuC shim HW 將對這些地址的訪問重定向到其他 HW 區域,而不是透過 GGTT。在底部,GuC 無法訪問 WOPCM 大小以下的偏移量,而在頂部,限制固定為 GUC_GGTT_TOP。為了保持簡單,我們不檢查每個物件是否被 GuC 訪問,而是直接從分配器中排除這些區域。此外,為了簡化驅動程式載入,我們在此邏輯中使用最大 WOPCM 大小而不是程式設計的大小,因此我們不需要等到實際要程式設計的大小確定(這需要 FW 獲取)之後才能初始化 GGTT。這些簡化可能會浪費 GGTT 中的空間(取決於平臺,約為 20-25 MB),但我們可以忍受這一點。另一個好處是 GuC 引導 ROM 無法訪問 WOPCM 最大大小以下的任何內容,因此引導 ROM 需要訪問的任何內容(例如 RSA 金鑰)都需要放置在 GGTT 中 WOPCM 最大大小之上。在 WOPCM 最大大小之上啟動 GGTT 分配可以免費為我們提供正確的放置。
GGTT 內部 API¶
-
struct xe_ggtt¶
主 GGTT 結構
定義:
struct xe_ggtt {
struct xe_tile *tile;
u64 size;
#define XE_GGTT_FLAGS_64K BIT(0);
unsigned int flags;
struct xe_bo *scratch;
struct mutex lock;
u64 __iomem *gsm;
const struct xe_ggtt_pt_ops *pt_ops;
struct drm_mm mm;
unsigned int access_count;
struct workqueue_struct *wq;
};
成員
tile指向此 GGTT 所屬的 tile 的後向指標
size此 GGTT 的總大小
flags此 GGTT 的標誌 可接受的標誌:-
XE_GGTT_FLAGS_64K- 如果 PTE 大小為 64K。否則,常規大小為 4K。scratch用作暫存頁面的內部物件分配
lock用於保護 GGTT 資料的互斥鎖
gsm指向位於 GSM 中用於輕鬆 PTE 操作的轉換表實際位置的 iomem 指標
pt_ops每個平臺的頁表操作
mm用於管理各個 GGTT 分配的記憶體管理器
access_count計算 GGTT 寫入次數
wq用於處理節點刪除的專用無序工作佇列
描述
一般來說,每個 tile 可以包含其自己的全域性圖形轉換表 (GGTT) 例項。
-
struct xe_ggtt_node¶
GGTT 中的一個節點。
定義:
struct xe_ggtt_node {
struct xe_ggtt *ggtt;
struct drm_mm_node base;
struct work_struct delayed_removal_work;
bool invalidate_on_remove;
};
成員
ggtt指向將插入此區域的 xe_ggtt 的後向指標
base一個 drm_mm_node
delayed_removal_work延遲刪除的工作結構
invalidate_on_remove如果需要在刪除時失效
描述
此結構需要在使用 xe_ggtt_node_init()(僅一次)初始化後,才能進行任何節點插入、保留或“氣球化”。然後,它將由 xe_ggtt_node_remove() 或 xe_ggtt_node_deballoon() 最終確定。
-
struct xe_ggtt_pt_ops¶
GGTT 頁表操作 這可能會因平臺而異。
定義:
struct xe_ggtt_pt_ops {
u64 (*pte_encode_bo)(struct xe_bo *bo, u64 bo_offset, u16 pat_index);
void (*ggtt_set_pte)(struct xe_ggtt *ggtt, u64 addr, u64 pte);
};
成員
pte_encode_bo對給定 BO 的 PTE 地址進行編碼
ggtt_set_pte直接寫入 GGTT 的 PTE
引數
struct xe_ggtt *ggtt要初始化的
xe_ggtt
描述
它允許建立 GuC 可用的新對映。HW 引擎不可使用對映,因為它還沒有完成暫存和初始清除。這將在常規的非早期 GGTT 初始化中發生。
返回
成功時返回 0,失敗時返回負錯誤程式碼。
-
void xe_ggtt_node_remove(struct xe_ggtt_node *node, bool invalidate)¶
從 GGTT 中刪除
xe_ggtt_node
引數
struct xe_ggtt_node *node要刪除的
xe_ggtt_nodebool invalidate如果節點需要在刪除時失效
-
int xe_ggtt_node_insert_balloon(struct xe_ggtt_node *node, u64 start, u64 end)¶
阻止分配指定的 GGTT 地址
引數
struct xe_ggtt_node *node用於儲存保留 GGTT 節點的
xe_ggtt_nodeu64 start保留區域的起始 GGTT 地址
u64 end保留區域的結束 GGTT 地址
描述
使用 xe_ggtt_node_remove_balloon() 釋放保留的 GGTT 節點。
返回
成功時返回 0,失敗時返回負錯誤程式碼。
-
void xe_ggtt_node_remove_balloon(struct xe_ggtt_node *node)¶
釋放保留的 GGTT 區域
-
int xe_ggtt_node_insert_locked(struct xe_ggtt_node *node, u32 size, u32 align, u32 mm_flags)¶
鎖定的版本,用於將
xe_ggtt_node插入到 GGTT 中
引數
struct xe_ggtt_node *node要插入的
xe_ggtt_nodeu32 size節點的大小
u32 align節點的對齊約束
u32 mm_flags用於控制節點行為的標誌
描述
不能在沒有首先呼叫 xe_ggtt_init() 的情況下呼叫它一次。用於已經獲取 ggtt->lock 的情況。
返回
成功時返回 0,失敗時返回負錯誤程式碼。
-
int xe_ggtt_node_insert(struct xe_ggtt_node *node, u32 size, u32 align)¶
將
xe_ggtt_node插入到 GGTT 中
引數
struct xe_ggtt_node *node要插入的
xe_ggtt_nodeu32 size節點的大小
u32 align節點的對齊約束
描述
不能在沒有首先呼叫 xe_ggtt_init() 的情況下呼叫它一次。
返回
成功時返回 0,失敗時返回負錯誤程式碼。
-
struct xe_ggtt_node *xe_ggtt_node_init(struct xe_ggtt *ggtt)¶
初始化
xe_ggtt_node結構體
引數
struct xe_ggtt *ggtt新節點將要插入/保留的
xe_ggtt。
描述
此函式將分配 xe_ggtt_node 結構體並返回其指標。該結構體將在透過 xe_ggtt_node_remove() 或 xe_ggtt_node_remove_balloon() 移除節點後釋放。 分配了 xe_ggtt_node 結構體並不意味著該節點已在 GGTT 中分配。 只有 xe_ggtt_node_insert(), xe_ggtt_node_insert_locked(), xe_ggtt_node_insert_balloon() 才能確保節點插入或保留在 GGTT 中。
返回
成功時返回指向 xe_ggtt_node 結構體的指標。 否則返回 ERR_PTR。
-
void xe_ggtt_node_fini(struct xe_ggtt_node *node)¶
強制結束
xe_ggtt_node結構體
引數
struct xe_ggtt_node *node要釋放的
xe_ggtt_node
描述
如果 xe_ggtt_node_insert(), xe_ggtt_node_insert_locked(), 或 xe_ggtt_node_insert_balloon() 中的任何一個出現問題; 並且這個 **node** 不會被重用,那麼需要呼叫此函式來釋放 xe_ggtt_node 結構體
-
bool xe_ggtt_node_allocated(const struct xe_ggtt_node *node)¶
檢查節點是否已在 GGTT 中分配
引數
struct xe_ggtt *ggtt節點將被對映到的
xe_ggttstruct xe_bo *bo要對映的
xe_bo
-
int xe_ggtt_insert_bo_at(struct xe_ggtt *ggtt, struct xe_bo *bo, u64 start, u64 end)¶
在特定的 GGTT 空間插入 BO
引數
struct xe_ggtt *ggttbo 將被插入到的
xe_ggttstruct xe_bo *bo要插入的
xe_bou64 start插入的起始地址
u64 end插入範圍的結束地址
返回
成功時返回 0,失敗時返回負錯誤程式碼。
引數
struct xe_ggtt *ggtt節點將被移除的
xe_ggttstruct xe_bo *bo要移除的
xe_bo
引數
struct xe_ggtt *ggtt要檢查的
xe_ggttu64 alignment最小對齊
u64 *spare如果非 NULL:輸入:期望保留的記憶體大小 / 輸出:調整後的可能保留空間
返回
最大的連續 GGTT 區域的大小
-
void xe_ggtt_assign(const struct xe_ggtt_node *node, u16 vfid)¶
將 GGTT 區域分配給 VF
引數
const struct xe_ggtt_node *node要更新的
xe_ggtt_nodeu16 vfidVF 識別符號
描述
此函式由 PF 驅動程式使用,將 GGTT 區域分配給 VF。除了 PTE 的 VFID 位 11:2 之外,PRESENT 位 0 也被設定為,因為在某些平臺上 VF 也無法修改它。
-
int xe_ggtt_dump(struct xe_ggtt *ggtt, struct drm_printer *p)¶
轉儲 GGTT 以進行除錯
引數
struct xe_ggtt *ggtt要轉儲的
xe_ggttstruct drm_printer *p用於轉儲資訊的
drm_mm_printer輔助控制代碼
返回
成功時返回 0,失敗時返回負錯誤程式碼。
-
u64 xe_ggtt_print_holes(struct xe_ggtt *ggtt, u64 alignment, struct drm_printer *p)¶
列印空洞
引數
struct xe_ggtt *ggtt要檢查的
xe_ggttu64 alignment最小對齊
struct drm_printer *p
描述
列印可用的 GGTT 範圍,並返回總可用大小。
返回
總可用大小。
頁表構建¶
下面,我們使用“頁表”一詞來表示頁目錄(包含指向較低級別頁目錄或頁表的指標)以及僅包含指向記憶體頁面的頁表條目的第 0 級頁表。
當在一個已存在的頁表樹中插入地址範圍時,通常會有一組與其它地址範圍共享的頁表,以及一組此地址範圍私有的頁表。每層最多可以有兩個共享頁表,並且這些頁表不能立即更新,因為這些頁表的條目可能仍在被 GPU 用於其它對映。因此,當將條目插入到這些頁表中時,我們改為透過將插入資料新增到 struct xe_vm_pgtable_update 結構體中來暫存這些插入。然後,在單獨的提交步驟中新增此資料(CPU 的子樹和 GPU 的頁表條目)。CPU 資料在 vm 鎖、物件鎖以及 userptr 的讀取模式下的 notifier 鎖下提交。GPU 非同步資料由 GPU 或 CPU 在滿足相關依賴性後提交。對於非共享頁表(實際上,對於暫存時不存在的共享頁表),我們直接新增資料,而無需特殊的更新結構。頁表樹的這個私有部分將與 vm 頁表樹斷開連線,直到資料在提交階段提交到 vm 樹的共享頁表。