記憶體管理

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

int xe_ggtt_init_early(struct xe_ggtt *ggtt)

早期 GGTT 初始化

引數

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_node

bool invalidate

如果節點需要在刪除時失效

int xe_ggtt_init(struct xe_ggtt *ggtt)

常規的非早期 GGTT 初始化

引數

struct xe_ggtt *ggtt

要初始化的 xe_ggtt

返回

成功時返回 0,失敗時返回負錯誤程式碼。

int xe_ggtt_node_insert_balloon(struct xe_ggtt_node *node, u64 start, u64 end)

阻止分配指定的 GGTT 地址

引數

struct xe_ggtt_node *node

用於儲存保留 GGTT 節點的 xe_ggtt_node

u64 start

保留區域的起始 GGTT 地址

u64 end

保留區域的結束 GGTT 地址

描述

使用 xe_ggtt_node_remove_balloon() 釋放保留的 GGTT 節點。

返回

成功時返回 0,失敗時返回負錯誤程式碼。

void xe_ggtt_node_remove_balloon(struct xe_ggtt_node *node)

釋放保留的 GGTT 區域

引數

struct xe_ggtt_node *node

帶有保留 GGTT 區域的 xe_ggtt_node

描述

有關詳細資訊,請參見 xe_ggtt_node_insert_balloon()

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_node

u32 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_node

u32 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 中分配

引數

const struct xe_ggtt_node *node

要檢查的 xe_ggtt_node

返回

如果已分配則為 True,否則為 False。

void xe_ggtt_map_bo(struct xe_ggtt *ggtt, struct xe_bo *bo)

將 BO 對映到 GGTT 中

引數

struct xe_ggtt *ggtt

節點將被對映到的 xe_ggtt

struct 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 *ggtt

bo 將被插入到的 xe_ggtt

struct xe_bo *bo

要插入的 xe_bo

u64 start

插入的起始地址

u64 end

插入範圍的結束地址

返回

成功時返回 0,失敗時返回負錯誤程式碼。

int xe_ggtt_insert_bo(struct xe_ggtt *ggtt, struct xe_bo *bo)

將 BO 插入到 GGTT 中

引數

struct xe_ggtt *ggtt

bo 將被插入到的 xe_ggtt

struct xe_bo *bo

要插入的 xe_bo

返回

成功時返回 0,失敗時返回負錯誤程式碼。

void xe_ggtt_remove_bo(struct xe_ggtt *ggtt, struct xe_bo *bo)

從 GGTT 中移除 BO

引數

struct xe_ggtt *ggtt

節點將被移除的 xe_ggtt

struct xe_bo *bo

要移除的 xe_bo

u64 xe_ggtt_largest_hole(struct xe_ggtt *ggtt, u64 alignment, u64 *spare)

最大的 GGTT 空洞

引數

struct xe_ggtt *ggtt

要檢查的 xe_ggtt

u64 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_node

u16 vfid

VF 識別符號

描述

此函式由 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_ggtt

struct 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_ggtt

u64 alignment

最小對齊

struct drm_printer *p

drm_printer

描述

列印可用的 GGTT 範圍,並返回總可用大小。

返回

總可用大小。

頁表構建

下面,我們使用“頁表”一詞來表示頁目錄(包含指向較低級別頁目錄或頁表的指標)以及僅包含指向記憶體頁面的頁表條目的第 0 級頁表。

當在一個已存在的頁表樹中插入地址範圍時,通常會有一組與其它地址範圍共享的頁表,以及一組此地址範圍私有的頁表。每層最多可以有兩個共享頁表,並且這些頁表不能立即更新,因為這些頁表的條目可能仍在被 GPU 用於其它對映。因此,當將條目插入到這些頁表中時,我們改為透過將插入資料新增到 struct xe_vm_pgtable_update 結構體中來暫存這些插入。然後,在單獨的提交步驟中新增此資料(CPU 的子樹和 GPU 的頁表條目)。CPU 資料在 vm 鎖、物件鎖以及 userptr 的讀取模式下的 notifier 鎖下提交。GPU 非同步資料由 GPU 或 CPU 在滿足相關依賴性後提交。對於非共享頁表(實際上,對於暫存時不存在的共享頁表),我們直接新增資料,而無需特殊的更新結構。頁表樹的這個私有部分將與 vm 頁表樹斷開連線,直到資料在提交階段提交到 vm 樹的共享頁表。