緩衝區共享與同步 (dma-buf)

dma-buf 子系統提供了跨多個裝置驅動程式和子系統共享硬體(DMA)訪問的緩衝區的框架,以及同步非同步硬體訪問的框架。

例如,DRM 子系統廣泛使用它來在程序、上下文、同一程序中的庫 API 之間交換緩衝區,以及與其他子系統(如 V4L2)交換緩衝區。

本文件描述了核心子系統如何使用 dma-buf 提供的三個主要原語以及如何與之互動

  • dma-buf,表示 sg_table,並作為檔案描述符暴露給使用者空間,以允許在程序、子系統、裝置等之間傳遞;

  • dma-fence,提供了一種機制來指示非同步硬體操作何時完成;以及

  • dma-resv,它為特定的 dma-buf 管理一組 dma-fences,允許對工作進行隱式(核心排序的)同步,以保留相干訪問的錯覺

使用者空間 API 原則和使用

有關如何為 dma-buf 使用設計子系統的 API 的更多詳細資訊,請參閱 交換畫素緩衝區

共享 DMA 緩衝區

本文件用作裝置驅動程式編寫者關於 dma-buf 緩衝區共享 API 是什麼,如何使用它來匯出和使用共享緩衝區的指南。

任何希望成為 DMA 緩衝區共享一部分的裝置驅動程式都可以作為緩衝區的“匯出者”或緩衝區的“使用者”或“匯入者”來實現。

假設驅動程式 A 想要使用驅動程式 B 建立的緩衝區,那麼我們稱 B 為匯出者,A 為緩衝區使用者/匯入者。

匯出者

  • struct dma_buf_ops 中實現和管理緩衝區操作,

  • 允許其他使用者使用 dma_buf 共享 API 共享緩衝區,

  • 管理緩衝區分配的詳細資訊,封裝在 struct dma_buf 中,

  • 決定此分配發生的實際後備儲存,

  • 並負責所有(共享)使用者的 scatterlist 的任何遷移。

緩衝區使用者

  • 是緩衝區的(許多)共享使用者之一。

  • 不需要擔心如何分配緩衝區或在何處分配。

  • 並且需要一種機制來訪問構成此緩衝區的記憶體中的 scatterlist,該 scatterlist 對映到其自身的地址空間中,以便它可以訪問同一塊記憶體區域。 此介面由 struct dma_buf_attachment 提供。

dma-buf 緩衝區共享框架的任何匯出者或使用者都必須在其各自的 Kconfig 中具有“select DMA_SHARED_BUFFER”。

使用者空間介面說明

通常,DMA 緩衝區檔案描述符對於使用者空間而言只是一個不透明的物件,因此暴露的通用介面非常小。 但是,有幾件事需要考慮

  • 自核心 3.12 以來,dma-buf FD 支援 llseek 系統呼叫,但僅支援 offset=0 和 whence=SEEK_END|SEEK_SET。 支援 SEEK_SET 以允許通常的大小發現模式 size = SEEK_END(0); SEEK_SET(0)。 每個其他 llseek 操作都將報告 -EINVAL。

    如果 dma-buf FD 不支援 llseek,則核心將在所有情況下報告 -ESPIPE。 使用者空間可以使用它來檢測是否支援使用 llseek 發現 dma-buf 大小。

  • 為了避免在 exec 上出現 fd 洩漏,必須在檔案描述符上設定 FD_CLOEXEC 標誌。 這不僅是資源洩漏,而且是潛在的安全漏洞。 它可能會透過洩漏的 fd 授予新執行的應用程式訪問許可權,訪問它本來不應該被允許訪問的緩衝區。

    透過單獨的 fcntl() 呼叫(而不是在建立 fd 時以原子方式執行)執行此操作的問題是,這在多執行緒應用程式中本質上是競爭的 [3]。 當庫程式碼開啟/建立檔案描述符時,問題會變得更糟,因為應用程式甚至可能不知道 fd 的存在。

    為了避免此問題,使用者空間必須有一種方法來請求在建立 dma-buf fd 時設定 O_CLOEXEC 標誌。 因此,匯出驅動程式提供的任何用於建立 dmabuf fd 的 API 必須提供一種方法,使使用者空間可以控制傳遞給 dma_buf_fd() 的 O_CLOEXEC 標誌的設定。

  • 還支援記憶體對映 DMA 緩衝區的內容。 有關完整詳細資訊,請參見下面關於 CPU 訪問 DMA 緩衝區物件 的討論。

  • DMA 緩衝區 FD 也是可輪詢的,有關詳細資訊,請參見下面的 隱式 Fence 輪詢支援

  • DMA 緩衝區 FD 還支援一些 dma-buf 特定的 ioctl,有關詳細資訊,請參見下面的 DMA 緩衝區 ioctl

基本操作和裝置 DMA 訪問

對於裝置 DMA 訪問共享 DMA 緩衝區,通常的操作順序非常簡單

  1. 匯出者使用 DEFINE_DMA_BUF_EXPORT_INFO() 定義他的匯出者例項,並呼叫 dma_buf_export() 將私有緩衝區物件包裝到 dma_buf 中。 然後,它透過呼叫 dma_buf_fd()dma_buf 作為檔案描述符匯出到使用者空間。

  2. 使用者空間將此檔案描述符傳遞給它希望此緩衝區與其共享的所有驅動程式:首先,使用 dma_buf_get() 將檔案描述符轉換為 dma_buf。 然後,使用 dma_buf_attach() 將緩衝區附加到裝置。

    到此階段,匯出者仍然可以自由遷移或重新分配後備儲存。

  3. 一旦緩衝區附加到所有裝置,使用者空間就可以啟動對共享緩衝區的 DMA 訪問。 在核心中,這是透過呼叫 dma_buf_map_attachment()dma_buf_unmap_attachment() 完成的。

  4. 一旦驅動程式使用完共享緩衝區,它需要呼叫 dma_buf_detach()(在清理任何對映之後),然後透過呼叫 dma_buf_put() 來釋放透過 dma_buf_get() 獲取的引用。

對於匯出者應實現的詳細語義,請參見 dma_buf_ops

CPU 訪問 DMA 緩衝區物件

支援 CPU 訪問 dma 緩衝區物件的原因有很多

  • 核心中的回退操作,例如,當裝置透過 USB 連線並且核心需要在傳送資料之前先對資料進行混洗時。 快取一致性由使用對 dma_buf_begin_cpu_access()dma_buf_end_cpu_access() 訪問的呼叫將任何事務括起來來處理。

    由於大多數核心內部 dma-buf 訪問都需要整個緩衝區,因此引入了 vmap 介面。 請注意,在非常舊的 32 位體系結構上,vmalloc 空間可能有限,並導致 vmap 呼叫失敗。

    介面

    void *dma_buf_vmap(struct dma_buf *dmabuf, struct iosys_map *map)
    void dma_buf_vunmap(struct dma_buf *dmabuf, struct iosys_map *map)
    

    如果匯出器中沒有 vmap 支援,或者 vmalloc 空間不足,則 vmap 呼叫可能會失敗。 請注意,dma-buf 層保留所有 vmap 訪問的引用計數,並且僅當不存在任何 vmapping 時才呼叫到匯出器的 vmap 函式中,並且僅取消對映一次。 透過獲取 dma_buf.lock 互斥鎖來提供防止併發 vmap/vunmap 呼叫的保護。

  • 為了在匯入程式端與現有的使用者空間介面完全相容,這些介面可能已經支援 mmap'ing 緩衝區。 這在許多處理管道(例如,將軟體渲染的影像饋送到硬體管道、縮圖建立、快照等)中是必需的。 此外,Android 的 ION 框架已經支援這一點,並且對於 DMA 緩衝區檔案描述符替換 ION 緩衝區,需要 mmap 支援。

    沒有特殊的介面,使用者空間只需在 dma-buf fd 上呼叫 mmap 即可。 但是,與 CPU 訪問一樣,需要將實際訪問括起來,這由 ioctl (DMA_BUF_IOCTL_SYNC) 處理。 請注意,DMA_BUF_IOCTL_SYNC 可能會因 -EAGAIN 或 -EINTR 而失敗,在這種情況下,必須重新啟動。

    某些系統可能需要某種型別的快取一致性管理,例如,當 CPU 和 GPU 域同時透過 dma-buf 訪問時。 為了解決此問題,存在 begin/end 一致性標記,這些標記直接轉發到現有的 dma-buf 裝置驅動程式 vfunc 掛鉤。 使用者空間可以透過 DMA_BUF_IOCTL_SYNC ioctl 來使用這些標記。 序列的使用方式如下

    • mmap dma-buf fd

    • 對於 CPU 中的每個繪圖/上傳週期 1. SYNC_START ioctl,2. 讀/寫 mmap 區域 3. SYNC_END ioctl。 可以根據需要經常重複此操作(新資料被 GPU 或掃描輸出裝置消耗)

    • 一旦不再需要緩衝區,就取消對映

    為了正確性和最佳效能,始終需要在訪問對映的地址之前和之後分別使用 SYNC_START 和 SYNC_END。 使用者空間不能依賴於相干訪問,即使在某些系統中,無需呼叫這些 ioctl 也可以正常工作。

  • 並作為使用者空間處理管道中的 CPU 回退。

    與核心 cpu 訪問的動機類似,匯入子系統的給定使用者空間程式碼可以使用與本機緩衝區物件相同的介面來處理匯入的 dma-buf 緩衝區物件,這一點再次非常重要。 這對於 drm 尤其重要,因為當代 OpenGL、X 和其他驅動程式的使用者空間部分非常龐大,並且重做它們以使用不同的方式來 mmap 緩衝區具有相當大的侵入性。

    當前 dma-buf 介面中的假設是,重定向初始 mmap 是所有需要的。 對一些現有子系統的調查表明,似乎沒有驅動程式在做任何有害的事情,例如與裝置上未完成的非同步處理同步或在出現故障時分配特殊資源。 因此,希望這足夠好,因為新增介面來攔截頁面錯誤並允許 pte 射擊會大大增加複雜性。

    介面

    int dma_buf_mmap(struct dma_buf *, struct vm_area_struct *, unsigned long);
    

    如果匯入子系統僅提供專用的 mmap 呼叫來在使用者空間中設定對映,則使用 dma_buf.file 呼叫 do_mmap 也將同樣實現 dma-buf 物件。

隱式 Fence 輪詢支援

為了支援跨裝置和跨驅動程式同步緩衝區訪問,可以將隱式 fence(在核心中內部表示為 struct dma_fence)附加到 dma_bufdma_resv 結構中提供了相關的膠水和一些相關的東西。

使用者空間可以使用 poll() 和相關的系統呼叫來查詢這些隱式跟蹤的 fence 的狀態

  • 檢查 EPOLLIN,即讀取訪問,可用於查詢最新寫入或獨佔 fence 的狀態。

  • 檢查 EPOLLOUT,即寫入訪問,可用於查詢所有附加的 fence(共享的和獨佔的)的狀態。

請注意,這僅表示相應 fence 的完成,即 DMA 傳輸已完成。 在 CPU 訪問可以開始之前,仍然需要進行快取重新整理和任何其他必要的準備工作。

作為 poll() 的替代方法,可以使用 dma_buf_sync_file_export 將 DMA 緩衝區上的一組 fence 匯出為 sync_file

DMA-BUF 統計資訊

/sys/kernel/debug/dma_buf/bufinfo 提供了系統中每個 DMA-BUF 的概述。 但是,由於 debugfs 在生產中安裝是不安全的,因此可以使用 procfs 和 sysfs 來收集生產系統上的 DMA-BUF 統計資訊。

procfs 中的 /proc/<pid>/fdinfo/<fd> 檔案可用於收集有關 DMA-BUF fd 的資訊。 有關該介面的詳細文件,請參見 /proc 檔案系統

不幸的是,現有的 procfs 介面只能提供有關程序擁有 fd 或已將緩衝區 mmapped 到其地址空間中的 DMA-BUF 的資訊。 這就需要建立 DMA-BUF sysfs 統計資訊介面,以在生產系統上提供每個緩衝區的資訊。

啟用 CONFIG_DMABUF_SYSFS_STATS 時,/sys/kernel/dmabuf/buffers 處的介面會公開有關每個 DMA-BUF 的資訊。

該介面公開了以下統計資訊

  • /sys/kernel/dmabuf/buffers/<inode_number>/exporter_name

  • /sys/kernel/dmabuf/buffers/<inode_number>/size

介面中的資訊也可用於匯出每個匯出者的統計資訊。 可以收集介面中的資料,以瞭解錯誤情況或其他重要事件,從而提供 DMA-BUF 使用情況的快照。 也可以透過遙測定期收集它以監視各種指標。

有關該介面的詳細文件,請參見 ABI 檔案測試/sysfs-kernel-dmabuf-buffers

DMA 緩衝區 ioctl

struct dma_buf_sync

與 CPU 訪問同步。

定義:

struct dma_buf_sync {
    __u64 flags;
};

成員

flags

訪問標誌集

DMA_BUF_SYNC_START

指示對映訪問會話的開始。

DMA_BUF_SYNC_END

指示對映訪問會話的結束。

DMA_BUF_SYNC_READ

指示對映的 DMA 緩衝區將由客戶端透過 CPU 對映讀取。

DMA_BUF_SYNC_WRITE

指示對映的 DMA 緩衝區將由客戶端透過 CPU 對映寫入。

DMA_BUF_SYNC_RW

DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE 的別名。

描述

當透過 mmap 從 CPU 訪問 DMA 緩衝區時,無法始終保證 CPU 可見對映與底層記憶體之間的一致性。 為了管理一致性,必須使用 DMA_BUF_IOCTL_SYNC 將任何 CPU 訪問括起來,以使核心有機會在需要時對記憶體進行混洗。

在訪問對映之前,客戶端必須使用 DMA_BUF_SYNC_START 和適當的讀/寫標誌呼叫 DMA_BUF_IOCTL_SYNC。 訪問完成後,客戶端應使用 DMA_BUF_SYNC_END 和相同的讀/寫標誌呼叫 DMA_BUF_IOCTL_SYNC。

透過 DMA_BUF_IOCTL_SYNC 提供的同步僅提供快取一致性。 它不會阻止其他程序或裝置同時訪問記憶體。 如果需要與 GPU 或其他裝置驅動程式同步,則客戶端有責任等待緩衝區準備好讀取或寫入,然後再使用 DMA_BUF_SYNC_START 呼叫此 ioctl。 同樣,客戶端必須確保在以 DMA_BUF_SYNC_END 呼叫此 ioctl 之後,不會將後續工作提交到 GPU 或其他裝置驅動程式?

如果客戶端與之互動的驅動程式或 API 使用隱式同步,則可以透過 poll() 在 DMA 緩衝區檔案描述符上完成等待先前工作完成的操作。 如果驅動程式或 API 需要顯式同步,則客戶端可能必須等待 sync_file 或 DMA 緩衝區 API 範圍之外的其他同步原語。

struct dma_buf_export_sync_file

從 dma-buf 獲取 sync_file

定義:

struct dma_buf_export_sync_file {
    __u32 flags;
    __s32 fd;
};

成員

flags

讀/寫標誌

必須是 DMA_BUF_SYNC_READ、DMA_BUF_SYNC_WRITE 或兩者。

如果設定了 DMA_BUF_SYNC_READ 並且未設定 DMA_BUF_SYNC_WRITE,則返回的同步檔案會等待 dma-buf 的任何寫入者完成。 等待返回的同步檔案等同於使用 POLLIN 進行 poll()。

如果設定了 DMA_BUF_SYNC_WRITE,則返回的同步檔案會等待 dma-buf 的任何使用者(讀取或寫入)完成。 等待返回的同步檔案等同於使用 POLLOUT 進行 poll()。 如果同時設定了 DMA_BUF_SYNC_WRITE 和 DMA_BUF_SYNC_READ,則等同於僅設定 DMA_BUF_SYNC_WRITE。

fd

返回的同步檔案描述符

描述

使用者空間可以執行 DMA_BUF_IOCTL_EXPORT_SYNC_FILE 以將 dma-buf 檔案描述符上的當前 fence 集檢索為 sync_file。 透過 poll() 或其他驅動程式特定機制進行的 CPU 等待通常會等待開始等待時 dma-buf 上的任何 fence。 這類似,只是它獲取 dma-buf 上當前 fence 的快照以供以後等待,而不是立即等待。 這對於現代圖形 API(如 Vulkan)非常有用,這些 API 假定使用顯式同步模型,但仍然需要與 dma-buf 互操作。

預期的使用模式如下

  1. 透過 DMA_BUF_IOCTL_EXPORT_SYNC_FILE 匯出 sync_file,其標誌對應於預期的 GPU 使用情況。

  2. 提交使用 dma-buf 的渲染工作。 該工作應在渲染之前等待匯出的同步檔案,並在完成時生成另一個同步檔案。

  3. 透過 DMA_BUF_IOCTL_IMPORT_SYNC_FILE 將渲染完成的同步檔案匯入到 dma-buf 中,其標誌對應於 GPU 使用情況。

與透過 GPU 核心驅動程式的 exec ioctl 進行隱式同步不同,以上不是單個原子操作。 如果使用者空間想要確保透過這些 fence 進行排序,則使用者空間有責任使用鎖或其他機制來確保在上述步驟 1 和 3 之間沒有其他上下文新增 fence 或提交工作。

struct dma_buf_import_sync_file

將 sync_file 插入到 dma-buf 中

定義:

struct dma_buf_import_sync_file {
    __u32 flags;
    __s32 fd;
};

成員

flags

讀/寫標誌

必須是 DMA_BUF_SYNC_READ、DMA_BUF_SYNC_WRITE 或兩者。

如果設定了 DMA_BUF_SYNC_READ 並且未設定 DMA_BUF_SYNC_WRITE,則將 sync_file 作為只讀 fence 插入。 任何後續的隱式同步寫入此 dma-buf 都將等待此 fence,但讀取不會。

如果設定了 DMA_BUF_SYNC_WRITE,則將 sync_file 作為寫入 fence 插入。 所有後續的隱式同步訪問此 dma-buf 都將等待此 fence。

fd

同步檔案描述符

描述

使用者空間可以執行 DMA_BUF_IOCTL_IMPORT_SYNC_FILE 以將 sync_file 插入到 dma-buf 中,以便與其他 dma-buf 使用者進行隱式同步。 這允許使用顯式同步 API(如 Vulkan)的客戶端與期望隱式同步的 dma-buf 使用者(如 OpenGL 或大多數媒體驅動程式/影片)進行互操作。

DMA-BUF 鎖定約定

為了避免 dma-buf 匯出者和匯入者之間的死鎖情況,所有 dma-buf API 使用者都必須遵循通用的 dma-buf 鎖定約定。

匯入者的約定

  1. 匯入者在呼叫以下函式時必須持有 dma-buf 預留鎖

  2. 匯入者在呼叫以下函式時不得持有 dma-buf 預留鎖

匯出器約定

  1. 這些 dma_buf_ops 回撥函式在未鎖定的 dma-buf 預留區被呼叫,匯出器可以獲取鎖

  2. 這些 dma_buf_ops 回撥函式在鎖定的 dma-buf 預留區被呼叫,匯出器無法獲取鎖

  3. 當呼叫這些函式時,匯出器必須持有 dma-buf 預留鎖

核心函式和結構體參考

struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info)

建立一個新的 dma_buf,並將一個匿名檔案與此緩衝區相關聯,以便它可以被匯出。 此外,將分配器特定的資料和操作連線到緩衝區。 另外,為匯出器提供一個名稱字串; 在除錯中很有用。

引數

const struct dma_buf_export_info *exp_info

[in] 儲存由匯出器提供的所有匯出相關資訊。 有關更多詳細資訊,請參見 struct dma_buf_export_info

描述

成功時,返回一個新建立的 struct dma_buf 物件,該物件包裝提供的私有資料和 struct dma_buf_ops 的操作。 如果缺少操作或分配 struct dma_buf 時發生錯誤,將返回負錯誤。

在大多數情況下,建立 exp_info 的最簡單方法是透過 DEFINE_DMA_BUF_EXPORT_INFO 宏。

int dma_buf_fd(struct dma_buf *dmabuf, int flags)

返回給定 struct dma_buf 的檔案描述符

引數

struct dma_buf *dmabuf

[in] 指向需要 fd 的 dma_buf。

int flags

[in] 傳遞給 fd 的標誌

描述

成功時,返回關聯的“fd”。 否則,返回錯誤。

struct dma_buf *dma_buf_get(int fd)

返回與 fd 相關的 struct dma_buf

引數

int fd

[in] 與要返回的 struct dma_buf 關聯的 fd

描述

成功時,返回與 fd 關聯的 struct dma_buf; 使用 fget 完成的檔案引用計數來增加引用計數。 否則返回 ERR_PTR。

void dma_buf_put(struct dma_buf *dmabuf)

減少緩衝區的引用計數

引數

struct dma_buf *dmabuf

[in] 要減少引用計數的緩衝區

描述

使用 fput() 隱式完成的檔案引用計數。

如果由於此呼叫的結果,引用計數變為 0,則將呼叫與此 fd 相關的“釋放”檔案操作。 它進而呼叫 dma_buf_ops.release vfunc,並釋放匯出時為 dmabuf 分配的記憶體。

struct dma_buf_attachment *dma_buf_dynamic_attach(struct dma_buf *dmabuf, struct device *dev, const struct dma_buf_attach_ops *importer_ops, void *importer_priv)

將裝置新增到 dma_buf 的附件列表

引數

struct dma_buf *dmabuf

[in] 要附加裝置的緩衝區。

struct device *dev

[in] 要附加的裝置。

const struct dma_buf_attach_ops *importer_ops

[in] 附件的匯入器操作

void *importer_priv

[in] 附件的匯入器私有指標

描述

返回此附件的 struct dma_buf_attachment 指標。 必須透過呼叫 dma_buf_detach() 來清除附件。

(可選)此呼叫 dma_buf_ops.attach 以允許裝置特定的附加功能。

成功時,指向新建立的 dma_buf_attachment 的指標,如果失敗,則為包裝為指標的負錯誤程式碼。

請注意,如果 dmabuf 的後備儲存位於 dev 無法訪問的位置,並且無法移動到更合適的位置,則此操作可能會失敗。 這用錯誤程式碼 -EBUSY 指示。

struct dma_buf_attachment *dma_buf_attach(struct dma_buf *dmabuf, struct device *dev)

dma_buf_dynamic_attach 的包裝器

引數

struct dma_buf *dmabuf

[in] 要附加裝置的緩衝區。

struct device *dev

[in] 要附加的裝置。

描述

包裝器呼叫 dma_buf_dynamic_attach() 以用於仍使用靜態對映的驅動程式。

void dma_buf_detach(struct dma_buf *dmabuf, struct dma_buf_attachment *attach)

從 dmabuf 的附件列表中刪除給定的附件

引數

struct dma_buf *dmabuf

[in] 要從中分離的緩衝區。

struct dma_buf_attachment *attach

[in] 要分離的附件; 在此呼叫之後被釋放。

描述

清理透過呼叫 dma_buf_attach() 獲得的裝置附件。

(可選)此呼叫 dma_buf_ops.detach 用於裝置特定的分離。

int dma_buf_pin(struct dma_buf_attachment *attach)

鎖定 DMA-buf

引數

struct dma_buf_attachment *attach

[in] 應該被鎖定的附件

描述

只有動態匯入器(使用 dma_buf_dynamic_attach() 設定 attach 的匯入器)可以呼叫此函式,並且只能用於有限的用例,例如掃描輸出,而不能用於臨時鎖定操作。 不允許使用者空間透過此介面鎖定任意數量的緩衝區。

必須透過呼叫 dma_buf_unpin() 來解鎖緩衝區。

返回

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

void dma_buf_unpin(struct dma_buf_attachment *attach)

取消鎖定 DMA-buf

引數

struct dma_buf_attachment *attach

[in] 應該取消鎖定的附件

描述

這將取消鎖定由 dma_buf_pin() 鎖定的緩衝區,並允許匯出器再次移動 attach 的任何對映,並透過 dma_buf_attach_ops.move_notify 通知匯入器。

struct sg_table *dma_buf_map_attachment(struct dma_buf_attachment *attach, enum dma_data_direction direction)

返回附件的散列表; 對映到 _device_ 地址空間。 是 dma_buf_ops 的 map_dma_buf() 的包裝器。

引數

struct dma_buf_attachment *attach

[in] 要返回其散列表的附件

enum dma_data_direction direction

[in] DMA 傳輸的方向

描述

返回包含要返回的散列表的 sg_table; 錯誤時返回 ERR_PTR。 如果被訊號中斷,可能會返回 -EINTR。

成功後,返回的散列表中的 DMA 地址和長度將與 PAGE_SIZE 對齊。

必須使用 dma_buf_unmap_attachment() 取消對映。 請注意,只要存在對映,底層後備儲存就會被鎖定,因此使用者/匯入器不應在不適當的時間段內保留對映。

重要說明: 動態匯入器必須首先等待附加到 DMA-BUF 的 struct dma_resv 的獨佔柵欄。

struct sg_table *dma_buf_map_attachment_unlocked(struct dma_buf_attachment *attach, enum dma_data_direction direction)

返回附件的散列表; 對映到 _device_ 地址空間。 是 dma_buf_ops 的 map_dma_buf() 的包裝器。

引數

struct dma_buf_attachment *attach

[in] 要返回其散列表的附件

enum dma_data_direction direction

[in] DMA 傳輸的方向

描述

dma_buf_map_attachment() 的非鎖定變體。

void dma_buf_unmap_attachment(struct dma_buf_attachment *attach, struct sg_table *sg_table, enum dma_data_direction direction)

取消對映並減少緩衝區的使用計數;可能會釋放關聯的散列表。 是 dma_buf_ops 的 unmap_dma_buf() 的包裝器。

引數

struct dma_buf_attachment *attach

[in] 要從中取消對映緩衝區的附件

struct sg_table *sg_table

[in] 要取消對映的緩衝區的散列表資訊

enum dma_data_direction direction

[in] DMA 傳輸的方向

描述

這將取消對映由 dma_buf_map_attachment() 獲得的 attached 的 DMA 對映。

void dma_buf_unmap_attachment_unlocked(struct dma_buf_attachment *attach, struct sg_table *sg_table, enum dma_data_direction direction)

取消對映並減少緩衝區的使用計數;可能會釋放關聯的散列表。 是 dma_buf_ops 的 unmap_dma_buf() 的包裝器。

引數

struct dma_buf_attachment *attach

[in] 要從中取消對映緩衝區的附件

struct sg_table *sg_table

[in] 要取消對映的緩衝區的散列表資訊

enum dma_data_direction direction

[in] DMA 傳輸的方向

描述

dma_buf_unmap_attachment() 的非鎖定變體。

void dma_buf_move_notify(struct dma_buf *dmabuf)

通知附件 DMA-buf 正在移動

引數

struct dma_buf *dmabuf

[in] 正在移動的緩衝區

描述

通知所有附件需要銷燬並重新建立所有對映。

int dma_buf_begin_cpu_access(struct dma_buf *dmabuf, enum dma_data_direction direction)

在核心上下文中從 CPU 訪問 dma_buf 之前必須呼叫此函式。 呼叫 begin_cpu_access 以允許匯出器特定的準備。 僅保證指定範圍內指定訪問方向的一致性。

引數

struct dma_buf *dmabuf

[in] 要準備 CPU 訪問的緩衝區。

enum dma_data_direction direction

[in] 訪問方向。

描述

CPU 訪問完成後,呼叫者應呼叫 dma_buf_end_cpu_access()。 只有當 CPU 訪問被這兩個呼叫括起來時,才能保證與其他 DMA 訪問一致。

此函式還將等待透過 dma_buf.resv 中的隱式同步跟蹤的任何 DMA 事務。 對於具有顯式同步的 DMA 事務,此函式僅確保快取一致性,呼叫者必須自行確保與此類 DMA 事務的同步。

可以返回負錯誤值,成功時返回 0。

int dma_buf_end_cpu_access(struct dma_buf *dmabuf, enum dma_data_direction direction)

在核心上下文中從 CPU 訪問 dma_buf 之後必須呼叫此函式。 呼叫 end_cpu_access 以允許匯出器特定的操作。 僅保證指定範圍內指定訪問方向的一致性。

引數

struct dma_buf *dmabuf

[in] 要完成 CPU 訪問的緩衝區。

enum dma_data_direction direction

[in] 訪問方向。

描述

此函式終止以 dma_buf_begin_cpu_access() 開始的 CPU 訪問。

可以返回負錯誤值,成功時返回 0。

int dma_buf_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma, unsigned long pgoff)

使用給定的 vma 設定使用者空間 mmap

引數

struct dma_buf *dmabuf

[in] 應該支援 vma 的緩衝區

struct vm_area_struct *vma

[in] mmap 的 vma

unsigned long pgoff

[in] 此 mmap 應在 dma-buf 緩衝區中開始的頁偏移量。

描述

此函式調整傳入的 vma,使其指向 dma_buf 操作的檔案。 它還調整起始 pgoff 並對 vma 的大小進行邊界檢查。 然後它呼叫匯出器的 mmap 函式來設定對映。

可以返回負錯誤值,成功時返回 0。

int dma_buf_vmap(struct dma_buf *dmabuf, struct iosys_map *map)

在核心地址空間中為緩衝區物件建立虛擬對映。 與 vmap 及其朋友的限制相同。

引數

struct dma_buf *dmabuf

[in] 要 vmap 的緩衝區

struct iosys_map *map

[out] 返回 vmap 指標

描述

由於缺少虛擬對映地址空間,此呼叫可能會失敗。 這些呼叫在驅動程式中是可選的。 它們的預期用途是在核心空間中線性對映物件以用於高使用率物件。

為了確保一致性,使用者必須在透過此對映執行的任何 CPU 訪問前後呼叫 dma_buf_begin_cpu_access()dma_buf_end_cpu_access()

成功時返回 0,否則返回負 errno 程式碼。

int dma_buf_vmap_unlocked(struct dma_buf *dmabuf, struct iosys_map *map)

在核心地址空間中為緩衝區物件建立虛擬對映。 與 vmap 及其朋友的限制相同。

引數

struct dma_buf *dmabuf

[in] 要 vmap 的緩衝區

struct iosys_map *map

[out] 返回 vmap 指標

描述

dma_buf_vmap() 的非鎖定版本

成功時返回 0,否則返回負 errno 程式碼。

void dma_buf_vunmap(struct dma_buf *dmabuf, struct iosys_map *map)

取消對映由 dma_buf_vmap 獲取的 vmap。

引數

struct dma_buf *dmabuf

[in] 要取消對映的緩衝區

struct iosys_map *map

[in] 要取消對映的 vmap 指標

void dma_buf_vunmap_unlocked(struct dma_buf *dmabuf, struct iosys_map *map)

取消對映由 dma_buf_vmap 獲取的 vmap。

引數

struct dma_buf *dmabuf

[in] 要取消對映的緩衝區

struct iosys_map *map

[in] 要取消對映的 vmap 指標

struct dma_buf_ops

struct dma_buf 上可能的操作

定義:

struct dma_buf_ops {
    int (*attach)(struct dma_buf *, struct dma_buf_attachment *);
    void (*detach)(struct dma_buf *, struct dma_buf_attachment *);
    int (*pin)(struct dma_buf_attachment *attach);
    void (*unpin)(struct dma_buf_attachment *attach);
    struct sg_table * (*map_dma_buf)(struct dma_buf_attachment *, enum dma_data_direction);
    void (*unmap_dma_buf)(struct dma_buf_attachment *,struct sg_table *, enum dma_data_direction);
    void (*release)(struct dma_buf *);
    int (*begin_cpu_access)(struct dma_buf *, enum dma_data_direction);
    int (*end_cpu_access)(struct dma_buf *, enum dma_data_direction);
    int (*mmap)(struct dma_buf *, struct vm_area_struct *vma);
    int (*vmap)(struct dma_buf *dmabuf, struct iosys_map *map);
    void (*vunmap)(struct dma_buf *dmabuf, struct iosys_map *map);
};

成員

attach

dma_buf_attach() 呼叫此函式以確保給定的 dma_buf_attachment.dev 可以訪問提供的 dma_buf。 支援 VRAM 或特定於裝置的 carvedout 區域等特殊位置的緩衝區物件的匯出器應檢查緩衝區是否可以移動到系統記憶體(或由提供的裝置直接訪問),否則需要使 attach 操作失敗。

匯出器通常還應檢查當前分配是否滿足新裝置的 DMA 約束。 如果不是這種情況,並且無法移動分配,它也應使 attach 操作失敗。

任何匯出器私有 housekeeping 資料都可以儲存在 dma_buf_attachment.priv 指標中。

此回撥是可選的。

返回值

成功時返回 0,失敗時返回負錯誤程式碼。 它可能會返回 -EBUSY,以表明後備儲存已分配且與請求裝置的要求不相容。

detach

dma_buf_detach() 呼叫此函式以釋放 dma_buf_attachment。 提供此函式是為了讓匯出器可以清理 dma_buf_attachment 的任何 housekeeping。

此回撥是可選的。

pin

dma_buf_pin() 呼叫此函式,並讓匯出器知道 DMA-buf 不能再移動。 理想情況下,匯出器應 pin 緩衝區,以便所有裝置通常都可以訪問它。

此函式使用鎖定的 dmabuf.resv 物件呼叫,並且與 cache_sgt_mapping 互斥。

對於非動態匯入器,將從 dma_buf_attach() 自動呼叫此函式。

請注意,與非動態匯出器在其 map_dma_buf 回撥中類似,驅動程式必須保證記憶體可用且在此函式返回時已清除任何舊資料。 以流水線方式進行內部緩衝區移動的驅動程式必須等待所有移動和清除完成。

返回值

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

unpin

dma_buf_unpin() 呼叫此函式,並讓匯出器知道 DMA-buf 可以再次移動。

此函式使用鎖定的 dmabuf->resv 物件呼叫,並且與 cache_sgt_mapping 互斥。

此回撥是可選的。

map_dma_buf

dma_buf_map_attachment() 呼叫此函式,用於將共享的 dma_buf 對映到裝置地址空間,並且是強制性的。 只有在成功呼叫 attach 後才能呼叫它。

此呼叫可能會休眠,例如,當後備儲存首先需要分配或移動到適合所有當前連線裝置的位置時。

請注意,此函式所需的任何特定緩衝區屬性都應新增到 device_dma_parameters,該引數可透過來自 dma_buf_attachmentdevice.dma_params 訪問。 attach 回撥還應檢查這些約束。

如果這是第一次呼叫此函式,則匯出器現在可以選擇掃描此緩衝區的附件列表,整理連線裝置的需求,併為緩衝區選擇合適的後備儲存。

根據 enum dma_data_direction,可能存在多個使用者同時訪問(對於讀取,可能),或者匯出器可能希望向緩衝區使用者提供的任何其他型別的共享。

當 dynamic_mapping 標誌為 true 時,始終使用鎖定的 dmabuf->resv 物件呼叫此函式。

請注意,對於非動態匯出器,驅動程式必須保證記憶體可用且在此函式返回時已清除任何舊資料。 以流水線方式進行內部緩衝區移動的驅動程式必須等待所有移動和清除完成。 動態匯出器不需要遵循此規則:對於非動態匯入器,緩衝區已透過 pin 進行 pin,這具有相同的要求。 動態匯入器 otoh 需要遵守 dma_resv 柵欄。

返回值

DMA 緩衝區的後備儲存的 sg_table 散點列表,已經對映到連線了提供的 dma_buf_attachmentdevice 的裝置地址空間中。 散點列表中的地址和長度是 PAGE_SIZE 對齊的。

失敗時,返回包裝到指標中的負錯誤值。 當在阻塞時收到訊號時,也可能返回 -EINTR。

請注意,匯出器不應嘗試快取散點列表,也不應為多個呼叫返回同一個列表。 快取由 DMA-BUF 程式碼(對於非動態匯入器)或匯入器完成。 散點列表的所有權已轉移給呼叫者,並由 unmap_dma_buf 返回。

unmap_dma_buf

dma_buf_unmap_attachment() 呼叫此函式,應取消對映並釋放在 map_dma_buf 中分配的 sg_table,並且是強制性的。 對於靜態 dma_buf 處理,如果這是 DMA 緩衝區的最後一個對映,這也可能取消 pin 後備儲存。

release

在最後一個 dma_buf_put 之後呼叫,以釋放 dma_buf,並且是強制性的。

begin_cpu_access

dma_buf_begin_cpu_access() 呼叫此函式,並允許匯出器確保記憶體實際上與 CPU 訪問一致。 匯出器還需要確保 CPU 訪問對於訪問方向是一致的。 匯出器可以使用該方向來最佳化快取重新整理,即具有不同方向(讀取而不是寫入)的訪問可能會返回陳舊甚至偽造的資料(例如,當匯出器需要將資料複製到臨時儲存時)。

請注意,這既透過 DMA_BUF_IOCTL_SYNC IOCTL 命令為透過 mmap 建立的使用者空間對映呼叫,也為透過 vmap 建立的核心對映呼叫。

此回撥是可選的。

返回值

成功時返回 0,失敗時返回負錯誤程式碼。 例如,當無法分配後備儲存時,可能會失敗。 當呼叫被中斷並且需要重新啟動時,也可以返回 -ERESTARTSYS 或 -EINTR。

end_cpu_access

當匯入器完成訪問 CPU 時,從 dma_buf_end_cpu_access() 呼叫此函式。 匯出器可以使用它來重新整理快取並撤消在 begin_cpu_access 中完成的任何其他操作。

此回撥是可選的。

返回值

成功時返回 0,失敗時返回負錯誤程式碼。 當呼叫被中斷並且需要重新啟動時,可以返回 -ERESTARTSYS 或 -EINTR。

mmap

此回撥由 dma_buf_mmap() 函式使用

請注意,對映需要不一致,期望使用者空間使用 DMA_BUF_IOCTL_SYNC 介面來括起 CPU 訪問。

由於 dma-buf 緩衝區在其生命週期內具有不變的大小,因此 dma-buf 核心會檢查 vma 是否太大並拒絕此類對映。 因此,匯出器不需要重複此檢查。 驅動程式不需要自己檢查此情況。

如果匯出器需要手動重新整理快取,因此需要為 mmap 支援偽造一致性,則它需要能夠刪除指向後備儲存的所有 pte。 現在 linux mm 需要與儲存在 vma->vm_file 中的 struct file 相關聯的 struct address_space,以便使用函式 unmap_mapping_range 執行此操作。 但是 dma_buf 框架僅使用 anon_file struct file 支援每個 dma_buf fd,即所有 dma_buf 共享同一個檔案。

因此,匯出器需要透過設定 vma->vm_file 並在 dma_buf mmap 回撥中調整 vma->vm_pgoff 來設定他們自己的檔案(和 address_space)關聯。 在 gem 驅動程式的特定情況下,匯出器可以使用 gem 已經提供的 shmem 檔案(並設定 vm_pgoff = 0)。 然後,匯出器可以透過取消對映與其自己的檔案關聯的 struct address_space 的相應範圍來刪除 pte。

此回撥是可選的。

返回值

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

vmap

[可選] 在核心地址空間中為緩衝區建立虛擬對映。 與 vmap 及其朋友的限制相同。

vunmap

[可選] 從緩衝區取消對映 vmap

struct dma_buf

共享緩衝區物件

定義:

struct dma_buf {
    size_t size;
    struct file *file;
    struct list_head attachments;
    const struct dma_buf_ops *ops;
    unsigned vmapping_counter;
    struct iosys_map vmap_ptr;
    const char *exp_name;
    const char *name;
    spinlock_t name_lock;
    struct module *owner;
    struct list_head list_node;
    void *priv;
    struct dma_resv *resv;
    wait_queue_head_t poll;
    struct dma_buf_poll_cb_t {
        struct dma_fence_cb cb;
        wait_queue_head_t *poll;
        __poll_t active;
    } cb_in, cb_out;
#ifdef CONFIG_DMABUF_SYSFS_STATS;
    struct dma_buf_sysfs_entry {
        struct kobject kobj;
        struct dma_buf *dmabuf;
    } *sysfs_entry;
#endif;
};

成員

size

緩衝區的大小; 在緩衝區的生命週期內是不變的。

file

用於跨共享緩衝區的檔案指標,以及用於引用計數。 請參閱 dma_buf_get()dma_buf_put()

attachments

dma_buf_attachment 的列表,表示所有連線的裝置,受 dma_resvresv 保護。

ops

與此緩衝區物件關聯的 dma_buf_ops。

vmapping_counter

在內部用於對 dma_buf_vmap() 返回的 vmap 進行引用計數。 受 lock 保護。

vmap_ptr

如果 vmapping_counter > 0,則為當前 vmap 指標。 受 lock 保護。

exp_name

匯出器的名稱; 用於除錯。 不得為 NULL

name

使用者空間提供的名稱。 預設值為 NULL。 如果不為 NULL,則長度不能超過 DMA_BUF_NAME_LEN,包括 NIL 字元。 用於會計和除錯。 讀/寫訪問受 name_lock 保護

請參閱 IOCTL DMA_BUF_SET_NAME 或 DMA_BUF_SET_NAME_A/B

name_lock

spinlock 用於保護名稱訪問以進行讀取訪問。

owner

指向匯出器模組的指標; 當匯出器是核心模組時用於引用計數。

list_node

用於 dma_buf 會計和除錯的節點。

priv

此緩衝區物件的匯出器特定私有資料。

resv

與此 dma-buf 關聯的預留物件。

隱式同步規則

支援隱式緩衝區訪問同步的驅動程式(例如 隱式 Fence 輪詢支援 中所公開的)必須遵循以下規則。

  • 對於使用者空間 API 認為的任何讀取訪問,驅動程式必須透過帶有 DMA_RESV_USAGE_READ 標誌的 dma_resv_add_fence() 新增一個讀取 fence。 這在很大程度上取決於 API 和視窗系統。

  • 類似地,對於使用者空間 API 認為的任何寫入訪問,驅動程式必須透過帶有 DMA_RESV_USAGE_WRITE 標誌的 dma_resv_add_fence() 新增一個寫入 fence。

  • 驅動程式可以始終只新增一個寫入 fence,因為它只會導致不必要的同步,但不會導致正確性問題。

  • 某些驅動程式只公開一個同步的使用者空間 API,而沒有跨驅動程式的流水線。 這些驅動程式不會為它們的訪問設定任何 fence。 v4l 就是一個例子。

  • 驅動程式在檢索 fence 作為隱式同步的依賴項時,應使用 dma_resv_usage_rw()

動態匯入器規則

動態匯入器(參見 dma_buf_attachment_is_dynamic())在如何設定 fence 方面有額外的約束。

  • 動態匯入器必須遵守寫入 fence,並在允許透過裝置訪問緩衝區底層儲存之前等待它們發出訊號。

  • 對於它們無法立即從其 dma_buf_attach_ops.move_notify 回撥中停用的任何訪問,動態匯入器應設定 fence。

重要提示

所有驅動程式和記憶體管理相關函式都必須遵守 struct dma_resv 規則,特別是更新和遵守 fence 的規則。 有關更多說明,請參見 enum dma_resv_usage

poll

用於使用者空間 poll 支援

cb_in

用於使用者空間 poll 支援

cb_out

用於使用者空間 poll 支援

sysfs_entry

用於在 sysfs 中公開有關此緩衝區的資訊。另請參見 DMA-BUF 統計資訊,瞭解此功能啟用的 uapi。

描述

這表示一個共享緩衝區,透過呼叫 dma_buf_export() 建立。 使用者空間表示是一個普通的檔案描述符,可以透過呼叫 dma_buf_fd() 建立。

共享 dma 緩衝區使用 dma_buf_put()get_dma_buf() 進行引用計數。

裝置 DMA 訪問由單獨的 struct dma_buf_attachment 處理。

struct dma_buf_attach_ops

附件的匯入器操作

定義:

struct dma_buf_attach_ops {
    bool allow_peer2peer;
    void (*move_notify)(struct dma_buf_attachment *attach);
};

成員

allow_peer2peer

如果設定為 true,則匯入器必須能夠處理沒有 struct pages 的對等資源。

move_notify

[可選] DMA-buf 正在移動的通知

如果提供了此回撥,則框架可以避免在對映存在時固定後備儲存。

呼叫此回撥時,會持有與 dma_buf 關聯的預留物件的鎖,並且還必須持有此鎖來呼叫對映函式。 這確保了不會與正在進行的移動操作同時建立任何對映。

對映保持有效,並且不受此回撥的直接影響。 但是,DMA-buf 現在可能位於不同的物理位置,因此應儘快銷燬所有對映並重新建立。

在此回撥返回後可以建立新的對映,並且將指向 DMA-buf 的新位置。

描述

由匯入器實現的附件操作。

struct dma_buf_attachment

儲存裝置-緩衝區附件資料

定義:

struct dma_buf_attachment {
    struct dma_buf *dmabuf;
    struct device *dev;
    struct list_head node;
    bool peer2peer;
    const struct dma_buf_attach_ops *importer_ops;
    void *importer_priv;
    void *priv;
};

成員

dmabuf

此附件的緩衝區。

dev

連線到緩衝區的裝置。

node

dma_buf_attachment 的列表,受 dmabuf 的 dma_resv 鎖保護。

peer2peer

如果匯入器可以處理沒有 pages 的對等資源,則為 true。

importer_ops

此附件的匯入器操作;如果提供了 dma_buf_map/unmap_attachment(),則必須持有 dma_resv 鎖來呼叫。

importer_priv

匯入器特定的附件資料。

priv

匯出器特定的附件資料。

描述

此結構儲存 dma_buf 緩衝區與其使用者裝置之間的附件資訊。 該列表包含每個連線到緩衝區的裝置的一個附件結構。

透過呼叫 dma_buf_attach() 建立附件,並透過呼叫 dma_buf_detach() 再次釋放附件。 啟動傳輸所需的 DMA 對映本身由 dma_buf_map_attachment() 建立,並透過呼叫 dma_buf_unmap_attachment() 再次釋放。

struct dma_buf_export_info

儲存匯出 dma_buf 所需的資訊

定義:

struct dma_buf_export_info {
    const char *exp_name;
    struct module *owner;
    const struct dma_buf_ops *ops;
    size_t size;
    int flags;
    struct dma_resv *resv;
    void *priv;
};

成員

exp_name

匯出器的名稱 - 對除錯很有用。

owner

指向匯出器模組的指標 - 用於 refcounting 核心模組

ops

將分配器定義的 dma buf ops 附加到新緩衝區

size

緩衝區的大小 - 在緩衝區的生命週期內是不變的

flags

檔案的模式標誌

resv

預留物件,NULL 表示分配預設物件

priv

將分配器的私有資料附加到此緩衝區

描述

此結構儲存匯出緩衝區所需的資訊。 僅與 dma_buf_export() 一起使用。

DEFINE_DMA_BUF_EXPORT_INFO

DEFINE_DMA_BUF_EXPORT_INFO (name)

匯出器的輔助宏

引數

name

匯出資訊名稱

描述

DEFINE_DMA_BUF_EXPORT_INFO 宏定義了 struct dma_buf_export_info,將其清零並預先填充 exp_name。

void get_dma_buf(struct dma_buf *dmabuf)

get_file 的便捷包裝器。

引數

struct dma_buf *dmabuf

[in] 指向 dma_buf 的指標

描述

遞增 dma-buf 上的引用計數,對於需要在核心端建立對 dmabuf 的額外引用的驅動程式來說,這是必需的。 例如,一個匯出器需要保留一個 dmabuf 指標,以便後續匯出不會建立一個新的 dmabuf。

bool dma_buf_is_dynamic(struct dma_buf *dmabuf)

檢查 DMA-buf 是否使用動態對映。

引數

struct dma_buf *dmabuf

要檢查的 DMA-buf

描述

如果 DMA-buf 匯出器希望在呼叫對映/取消映射回調時持有 dma_resv 鎖,則返回 true;如果它不希望持有該鎖,則返回 false。

預留物件

預留物件提供了一種機制來管理與資源關聯的 dma_fence 物件的容器。 一個預留物件可以附加任意數量的 fence。 每個 fence 都帶有一個使用引數,該引數確定 fence 所代表的操作如何使用該資源。 RCU 機制用於保護對 fence 的讀取訪問,防止鎖定的寫入端更新。

有關更多詳細資訊,請參見 struct dma_resv

void dma_resv_init(struct dma_resv *obj)

初始化預留物件

引數

struct dma_resv *obj

預留物件

void dma_resv_fini(struct dma_resv *obj)

銷燬預留物件

引數

struct dma_resv *obj

預留物件

int dma_resv_reserve_fences(struct dma_resv *obj, unsigned int num_fences)

為 dma_resv 物件新增 fence 保留空間。

引數

struct dma_resv *obj

預留物件

unsigned int num_fences

我們要新增的 fence 的數量

描述

應在 dma_resv_add_fence() 之前呼叫。 必須透過 dma_resv_lock() 鎖定 obj 來呼叫。

請注意,如果在呼叫 dma_resv_add_fence() 之前 obj 在任何時候被解鎖,則需要重新預留預分配的插槽。 當 CONFIG_DEBUG_MUTEXES 啟用時,會驗證這一點。

返回值:成功時為零,或 -errno

void dma_resv_reset_max_fences(struct dma_resv *obj)

重置 fence 以進行除錯

引數

struct dma_resv *obj

要重置的 dma_resv 物件

描述

重置預先預留的 fence 插槽的數量,以測試驅動程式是否使用 dma_resv_reserve_fences() 執行正確的插槽分配。 另請參見 dma_resv_list.max_fences

void dma_resv_add_fence(struct dma_resv *obj, struct dma_fence *fence, enum dma_resv_usage usage)

將 fence 新增到 dma_resv obj

引數

struct dma_resv *obj

預留物件

struct dma_fence *fence

要新增的 fence

enum dma_resv_usage usage

如何使用 fence,請參見 enum dma_resv_usage

描述

將 fence 新增到一個插槽,必須使用 dma_resv_lock() 鎖定 obj,並且已呼叫 dma_resv_reserve_fences()

有關語義的討論,另請參見 dma_resv.fence

void dma_resv_replace_fences(struct dma_resv *obj, uint64_t context, struct dma_fence *replacement, enum dma_resv_usage usage)

替換 dma_resv obj 中的 fence

引數

struct dma_resv *obj

預留物件

uint64_t context

要替換的 fence 的上下文

struct dma_fence *replacement

要改用的新 fence

enum dma_resv_usage usage

如何使用新 fence,請參見 enum dma_resv_usage

描述

將具有指定上下文的 fence 替換為新的 fence。 僅當原始 fence 所代表的操作在新的 fence 完成時不再可以訪問 dma_resv 物件所代表的資源時才有效。

使用此方法的一個示例是用頁表更新 fence 替換搶佔 fence,這會使資源無法訪問。

struct dma_fence *dma_resv_iter_first_unlocked(struct dma_resv_iter *cursor)

未鎖定的 dma_resv obj 中的第一個 fence。

引數

struct dma_resv_iter *cursor

帶有當前位置的游標

描述

後續 fence 使用 dma_resv_iter_next_unlocked() 進行迭代。

請注意,迭代器可以重新啟動。 累積統計資訊或類似資訊的程式碼需要使用 dma_resv_iter_is_restarted() 檢查這一點。 因此,請儘可能使用鎖定的 dma_resv_iter_first()

返回來自未鎖定的 dma_resv obj 的第一個 fence。

struct dma_fence *dma_resv_iter_next_unlocked(struct dma_resv_iter *cursor)

未鎖定的 dma_resv obj 中的下一個 fence。

引數

struct dma_resv_iter *cursor

帶有當前位置的游標

描述

請注意,迭代器可以重新啟動。 累積統計資訊或類似資訊的程式碼需要使用 dma_resv_iter_is_restarted() 檢查這一點。 因此,請儘可能使用鎖定的 dma_resv_iter_next()

返回來自未鎖定的 dma_resv obj 的下一個 fence。

struct dma_fence *dma_resv_iter_first(struct dma_resv_iter *cursor)

從已鎖定的 dma_resv 物件獲取第一個 fence。

引數

struct dma_resv_iter *cursor

用於記錄當前位置的游標。

描述

後續 fence 使用 dma_resv_iter_next_unlocked() 進行迭代。

返回 dma_resv 物件中的第一個 fence,同時持有 dma_resv.lock

struct dma_fence *dma_resv_iter_next(struct dma_resv_iter *cursor)

從已鎖定的 dma_resv 物件獲取下一個 fence。

引數

struct dma_resv_iter *cursor

用於記錄當前位置的游標。

描述

返回 dma_resv 物件中的下一個 fence,同時持有 dma_resv.lock

int dma_resv_copy_fences(struct dma_resv *dst, struct dma_resv *src)

將所有 fence 從 src 複製到 dst。

引數

struct dma_resv *dst

目標 reservation 物件。

struct dma_resv *src

源 reservation 物件。

描述

將所有 fence 從 src 複製到 dst。必須持有 dst-lock。

int dma_resv_get_fences(struct dma_resv *obj, enum dma_resv_usage usage, unsigned int *num_fences, struct dma_fence ***fences)

獲取物件的 fences,無需持有 update side lock。

引數

struct dma_resv *obj

預留物件

enum dma_resv_usage usage

控制包含哪些 fences,請參閱 enum dma_resv_usage

unsigned int *num_fences

返回的 fence 數量。

struct dma_fence ***fences

返回的 fence 指標陣列 (陣列將 krealloc’d 到所需大小,並且必須由呼叫者釋放)

描述

從 reservation 物件檢索所有 fence。返回零或 -ENOMEM。

int dma_resv_get_singleton(struct dma_resv *obj, enum dma_resv_usage usage, struct dma_fence **fence)

獲取表示所有 fences 的單個 fence。

引數

struct dma_resv *obj

預留物件

enum dma_resv_usage usage

控制包含哪些 fences,請參閱 enum dma_resv_usage

struct dma_fence **fence

生成的 fence。

描述

獲取表示 resv 物件中所有 fences 的單個 fence。成功時返回 0,失敗時返回負錯誤值。

警告:將 fence 添加回 resv 物件時,不能像這樣使用它,因為在完成 dma_fence_array 時,這可能會導致堆疊損壞。

成功時返回 0,失敗時返回負錯誤值。

long dma_resv_wait_timeout(struct dma_resv *obj, enum dma_resv_usage usage, bool intr, unsigned long timeout)

等待 reservation 物件的 fences。

引數

struct dma_resv *obj

預留物件

enum dma_resv_usage usage

控制包含哪些 fences,請參閱 enum dma_resv_usage

bool intr

如果為 true,則執行可中斷的等待。

unsigned long timeout

jiffies 中的超時值,或零以立即返回。

描述

呼叫者不需要持有特定的鎖,但可能已經持有 dma_resv_lock()。如果被中斷,返回 -ERESTARTSYS;如果等待超時,返回 0;成功時返回大於零的值。

void dma_resv_set_deadline(struct dma_resv *obj, enum dma_resv_usage usage, ktime_t deadline)

在 reservation 物件的 fences 上設定截止時間。

引數

struct dma_resv *obj

預留物件

enum dma_resv_usage usage

控制包含哪些 fences,請參閱 enum dma_resv_usage

ktime_t deadline

請求的截止時間 (MONOTONIC)。

描述

可以在不持有 dma_resv 鎖的情況下呼叫。在所有由 **usage** 過濾的 fences 上設定 **deadline**。

bool dma_resv_test_signaled(struct dma_resv *obj, enum dma_resv_usage usage)

測試 reservation 物件的 fences 是否已發出訊號。

引數

struct dma_resv *obj

預留物件

enum dma_resv_usage usage

控制包含哪些 fences,請參閱 enum dma_resv_usage

描述

呼叫者不需要持有特定的鎖,但可能已經持有 dma_resv_lock()

返回值

如果所有 fences 都已發出訊號,則為 True,否則為 false。

void dma_resv_describe(struct dma_resv *obj, struct seq_file *seq)

將 resv 物件的描述轉儲到 seq_file 中。

引數

struct dma_resv *obj

預留物件

struct seq_file *seq

要將描述轉儲到的 seq_file。

描述

將 dma_resv 物件中的 fences 的文字描述轉儲到 seq_file 中。

enum dma_resv_usage

dma_resv 物件中的 fences 的使用方式。

常量

DMA_RESV_USAGE_KERNEL

僅用於核心記憶體管理。

這應該僅用於使用 DMA 硬體引擎複製或清除記憶體,以用於核心記憶體管理的情況。

驅動程式在訪問受 dma_resv 物件保護的資源之前*始終*必須等待這些 fences。唯一的例外是已知資源透過先前固定它而被鎖定到位的情況。

DMA_RESV_USAGE_WRITE

隱式寫入同步。

這應該僅用於新增隱式寫入依賴項的使用者空間命令提交。

DMA_RESV_USAGE_READ

隱式讀取同步。

這應該僅用於新增隱式讀取依賴項的使用者空間命令提交。

DMA_RESV_USAGE_BOOKKEEP

沒有隱式同步。

這應該由不希望參與任何隱式同步的提交使用。

最常見的情況是搶佔 fences、頁表更新、TLB 重新整理以及顯式同步的使用者提交。

顯式同步的使用者提交可以根據需要使用 dma_buf_import_sync_file() 提升到 DMA_RESV_USAGE_READ 或 DMA_RESV_USAGE_WRITE,以便在初始新增 fence 後,隱式同步變得必要。

描述

此列舉描述了 dma_resv 物件的不同用例,並控制查詢時返回哪些 fences。

一個重要的事實是,順序為 KERNEL<WRITE<READ<BOOKKEEP,當 dma_resv 物件被要求提供一個用例的 fences 時,也會返回較低用例的 fences。

例如,當請求 WRITE fences 時,也會返回 KERNEL fences。類似地,當請求 READ fences 時,也會返回 WRITE 和 KERNEL fences。

已經使用的 fences 可以透過再次新增此用法的 fence 來提升,例如,具有 DMA_RESV_USAGE_BOOKKEEP 的 fence 可以變為 DMA_RESV_USAGE_READ。但是,fences 永遠無法降級,例如,具有 DMA_RESV_USAGE_WRITE 的 fence 無法變為 DMA_RESV_USAGE_READ。

enum dma_resv_usage dma_resv_usage_rw(bool write)

隱式同步的輔助函式。

引數

bool write

如果我們要建立新的隱式同步寫入,則為 true。

描述

這返回用於寫入或讀取訪問的隱式同步用法,請參閱 enum dma_resv_usagedma_buf.resv

struct dma_resv

一個 reservation 物件,用於管理緩衝區的 fences。

定義:

struct dma_resv {
    struct ww_mutex lock;
    struct dma_resv_list __rcu *fences;
};

成員

lock

更新側鎖。不要直接使用,而是使用包裝器函式,例如 dma_resv_lock()dma_resv_unlock()

使用 reservation 物件動態管理記憶體的驅動程式也使用此鎖來保護緩衝區物件狀態,例如放置、分配策略或整個命令提交。

fences

已新增到 dma_resv 物件的 fences 陣列。

透過呼叫 dma_resv_add_fence() 新增新的 fence。由於這通常需要在命令提交中無法返回的點之後完成,因此它不能失敗,因此需要透過呼叫 dma_resv_reserve_fences() 保留足夠的 slots。

描述

這是 dma_fence 物件的容器,需要處理多個用例。

一種用途是同步對 struct dma_buf 的跨驅動程式訪問,無論是用於動態緩衝區管理,還是僅處理使用者空間中緩衝區不同使用者之間的隱式同步。有關更深入的討論,請參閱 dma_buf.resv

另一個主要用途是在基於緩衝區的記憶體管理器中管理驅動程式內的訪問和鎖定。struct ttm_buffer_object 是這裡的規範示例,因為這是 reservation 物件最初的來源。但在驅動程式中的使用正在擴充套件,並且一些驅動程式還使用相同的方案管理 struct drm_gem_object

struct dma_resv_iter

dma_resv fences 中的當前位置。

定義:

struct dma_resv_iter {
    struct dma_resv *obj;
    enum dma_resv_usage usage;
    struct dma_fence *fence;
    enum dma_resv_usage fence_usage;
    unsigned int index;
    struct dma_resv_list *fences;
    unsigned int num_fences;
    bool is_restarted;
};

成員

obj

我們要迭代的 dma_resv 物件。

usage

返回具有此用法或更低用法的 fences。

fence

當前處理的 fence。

fence_usage

當前 fence 的用法。

index

共享 fences 的索引。

fences

共享 fences;私有,*必須* 不取消引用。

num_fences

fences 的數量。

is_restarted

如果這是第一個返回的 fence,則為 true。

描述

不要在驅動程式中直接觸控此物件,而是使用訪問器函式。

重要提示

當使用無鎖迭代器(例如 dma_resv_iter_next_unlocked()dma_resv_for_each_fence_unlocked())時,請注意迭代器可以重新啟動。使用 dma_resv_iter_is_restarted() 檢查是否正在累積統計資訊或類似資訊。

void dma_resv_iter_begin(struct dma_resv_iter *cursor, struct dma_resv *obj, enum dma_resv_usage usage)

初始化 dma_resv_iter 物件。

引數

struct dma_resv_iter *cursor

要初始化的 dma_resv_iter 物件。

struct dma_resv *obj

我們要迭代的 dma_resv 物件。

enum dma_resv_usage usage

控制包含哪些 fences,請參閱 enum dma_resv_usage

void dma_resv_iter_end(struct dma_resv_iter *cursor)

清理 dma_resv_iter 物件。

引數

struct dma_resv_iter *cursor

應該清理的 dma_resv_iter 物件。

描述

確保正確刪除對游標中 fence 的引用。

enum dma_resv_usage dma_resv_iter_usage(struct dma_resv_iter *cursor)

返回當前 fence 的 usage。

引數

struct dma_resv_iter *cursor

當前位置的游標。

描述

返回當前處理的 fence 的 usage。

bool dma_resv_iter_is_restarted(struct dma_resv_iter *cursor)

測試這是否是重啟後的第一個 fence。

引數

struct dma_resv_iter *cursor

帶有當前位置的游標

描述

如果這是重啟後迭代中的第一個 fence,則返回 true。

dma_resv_for_each_fence_unlocked

dma_resv_for_each_fence_unlocked (cursor, fence)

未鎖定的 fence 迭代器。

引數

游標。

一個 struct dma_resv_iter 指標。

fence

當前的 fence。

描述

在不持有 struct dma_resv 物件的 dma_resv.lock 鎖的情況下,使用 RCU 迭代 fences。 游標需要使用 dma_resv_iter_begin() 初始化,並使用 dma_resv_iter_end() 清理。 在迭代器內部,持有對 dma_fence 的引用,並釋放 RCU 鎖。

請注意,當修改 cursorstruct dma_resv 時,迭代器可以重新啟動。 積累統計資訊或類似資訊的程式碼需要使用 dma_resv_iter_is_restarted() 檢查這一點。 因此,儘可能優先選擇鎖迭代器 dma_resv_for_each_fence()

dma_resv_for_each_fence

dma_resv_for_each_fence (cursor, obj, usage, fence)

fence 迭代器。

引數

游標。

一個 struct dma_resv_iter 指標。

obj

一個 dma_resv 物件指標。

usage

控制返回哪些 fences。

fence

當前的 fence。

描述

在持有 struct dma_resv 物件的 dma_resv.lock 鎖的情況下,迭代 fences。 all_fences 控制是否也返回共享的 fences。 游標初始化是迭代器的一部分,並且 fence 只要鎖被持有就保持有效,因此不會對 fence 進行額外的引用。

int dma_resv_lock(struct dma_resv *obj, struct ww_acquire_ctx *ctx)

鎖定 reservation 物件。

引數

struct dma_resv *obj

預留物件

struct ww_acquire_ctx *ctx

鎖定上下文。

描述

鎖定 reservation 物件以進行獨佔訪問和修改。 請注意,該鎖僅針對其他寫入者,讀取者將與 RCU 下的寫入者併發執行。 seqlock 用於在讀者與作者重疊時通知讀者。

由於 reservation 物件可能被多個參與者以未定義的順序鎖定,因此傳遞一個 #ww_acquire_ctx,以便在檢測到迴圈時進行回退。 請參閱 ww_mutex_lock() 和 ww_acquire_init()。 reservation 物件可以透過傳遞 NULL 作為 ctx 來鎖定自身。

當返回 -EDEADLK 指示出現死鎖情況時,必須解鎖 ctx 持有的所有鎖,然後在 obj 上呼叫 dma_resv_lock_slow()

透過呼叫 dma_resv_unlock() 解鎖。

另請參閱 dma_resv_lock_interruptible() 的可中斷變體。

int dma_resv_lock_interruptible(struct dma_resv *obj, struct ww_acquire_ctx *ctx)

鎖定 reservation 物件。

引數

struct dma_resv *obj

預留物件

struct ww_acquire_ctx *ctx

鎖定上下文。

描述

以可中斷的方式鎖定 reservation 物件以進行獨佔訪問和修改。 請注意,該鎖僅針對其他寫入者,讀取者將與 RCU 下的寫入者併發執行。 seqlock 用於在讀者與作者重疊時通知讀者。

由於 reservation 物件可能被多個參與者以未定義的順序鎖定,因此傳遞一個 #ww_acquire_ctx,以便在檢測到迴圈時進行回退。 請參閱 ww_mutex_lock() 和 ww_acquire_init()。 reservation 物件可以透過傳遞 NULL 作為 ctx 來鎖定自身。

當返回 -EDEADLK 指示出現死鎖情況時,必須解鎖 ctx 持有的所有鎖,然後在 obj 上呼叫 dma_resv_lock_slow_interruptible()

透過呼叫 dma_resv_unlock() 解鎖。

void dma_resv_lock_slow(struct dma_resv *obj, struct ww_acquire_ctx *ctx)

slowpath 鎖定 reservation 物件。

引數

struct dma_resv *obj

預留物件

struct ww_acquire_ctx *ctx

鎖定上下文。

描述

在死鎖情況後獲取 reservation 物件。 此函式將休眠直到鎖變為可用。 另請參閱 dma_resv_lock()

另請參閱 dma_resv_lock_slow_interruptible() 的可中斷變體。

int dma_resv_lock_slow_interruptible(struct dma_resv *obj, struct ww_acquire_ctx *ctx)

slowpath 鎖定 reservation 物件,可中斷。

引數

struct dma_resv *obj

預留物件

struct ww_acquire_ctx *ctx

鎖定上下文。

描述

在死鎖情況後以可中斷的方式獲取 reservation 物件。 此函式將休眠直到鎖變為可用。 另請參閱 dma_resv_lock_interruptible()

bool dma_resv_trylock(struct dma_resv *obj)

嘗試鎖定 reservation 物件。

引數

struct dma_resv *obj

預留物件

描述

嘗試鎖定 reservation 物件以進行獨佔訪問和修改。 請注意,該鎖僅針對其他寫入者,讀取者將與 RCU 下的寫入者併發執行。 seqlock 用於在讀者與作者重疊時通知讀者。

另請注意,由於未提供上下文,因此無法進行死鎖保護,對於 trylock 而言也不需要死鎖保護。

如果獲取了鎖,則返回 true,否則返回 false。

bool dma_resv_is_locked(struct dma_resv *obj)

reservation 物件是否已鎖定。

引數

struct dma_resv *obj

預留物件

描述

如果互斥鎖已鎖定,則返回 true,如果未鎖定,則返回 false。

struct ww_acquire_ctx *dma_resv_locking_ctx(struct dma_resv *obj)

返回用於鎖定物件的上下文。

引數

struct dma_resv *obj

預留物件

描述

返回用於鎖定 reservation 物件的上下文,如果未使用上下文或物件根本未鎖定,則返回 NULL。

警告:此介面非常糟糕,但 TTM 需要它,因為它沒有在一些非常長的呼叫鏈中傳遞 struct ww_acquire_ctx。 其他人都只是用它來檢查他們是否持有 reservation。

void dma_resv_unlock(struct dma_resv *obj)

解鎖 reservation 物件。

引數

struct dma_resv *obj

預留物件

描述

在獨佔訪問之後解鎖 reservation 物件。

DMA Fences

DMA fences,由 struct dma_fence 表示,是用於 DMA 操作的核心內部同步原語,例如 GPU 渲染、影片編碼/解碼或在螢幕上顯示緩衝區。

使用 dma_fence_init() 初始化 fence,並使用 dma_fence_signal() 完成 fence。 Fences 與上下文相關聯,該上下文透過 dma_fence_context_alloc() 分配,並且同一上下文上的所有 fences 都完全排序。

由於 fences 的目的是促進跨裝置和跨應用程式同步,因此有多種使用方式。

  • 單個 fences 可以作為 sync_file 公開,作為使用者空間的 file descriptor 訪問,透過呼叫 sync_file_create() 建立。 這被稱為顯式 fencing,因為使用者空間傳遞顯式同步點。

  • 一些子系統也有它們自己的顯式 fencing 原語,例如 drm_syncobj。 與 sync_file 相比,drm_syncobj 允許更新底層 fence。

  • 還有隱式 fencing,其中同步點作為共享 dma_buf 例項的一部分隱式傳遞。 這些隱式 fences 儲存在 struct dma_resv 中,透過 dma_buf.resv 指標。

DMA Fence 跨驅動程式約定

由於 dma_fence 提供跨驅動程式約定,因此所有驅動程式必須遵循相同的規則。

  • Fences 必須在合理的時間內完成。 代表使用者空間提交的 kernels 和 shaders 的 fences,可能會永遠執行,必須由超時和 gpu hang recovery 程式碼支援。 至少該程式碼必須阻止進一步的命令提交併強制完成所有正在執行的 fences,例如,當驅動程式或硬體不支援 gpu 重置,或者如果由於某種原因 gpu 重置失敗時。 理想情況下,驅動程式支援 gpu recovery,它隻影響有問題的使用者空間上下文,而不影響其他使用者空間提交。

  • 驅動程式可能對在合理時間內完成意味著什麼有不同的想法。 一些 hang recovery 程式碼使用固定的超時,另一些則混合使用觀察前進進度和越來越嚴格的超時。 驅動程式不應試圖猜測來自其他驅動程式的 fences 的超時處理。

  • 為了確保 dma_fence_wait() 不會死鎖與其他鎖,驅動程式應使用 dma_fence_begin_signalling()dma_fence_end_signalling() 註釋所有需要到達 dma_fence_signal() 的程式碼,該程式碼完成 fences。

  • 驅動程式允許在持有 dma_resv_lock() 時呼叫 dma_fence_wait()。 這意味著 fence 完成所需的任何程式碼都無法獲取 dma_resv 鎖。 請注意,這也會引入圍繞 dma_resv_lock()dma_resv_unlock() 的整個已建立的鎖定層次結構。

  • 驅動程式允許從他們的 shrinker 回撥中呼叫 dma_fence_wait()。 這意味著 fence 完成所需的任何程式碼都無法使用 GFP_KERNEL 分配記憶體。

  • 驅動程式允許從他們的 mmu_notifier 分別是 mmu_interval_notifier 回撥中呼叫 dma_fence_wait()。 這意味著 fence 完成所需的任何程式碼都無法使用 GFP_NOFS 或 GFP_NOIO 分配記憶體。 只允許 GFP_ATOMIC,這可能會失敗。

請注意,只有 GPU 驅動程式有合理的理由同時需要 mmu_interval_notifiershrinker 回撥,同時還必須使用 dma_fence 跟蹤非同步計算工作。 drivers/gpu 之外的任何驅動程式都不應在這種情況下呼叫 dma_fence_wait()

DMA Fence Signaling 註釋

對於一些原因,透過程式碼審查和測試來證明圍繞 dma_fence 的所有核心程式碼的正確性是很棘手的。

  • 這是一個跨驅動程式約定,因此所有驅動程式必須遵循相同的鎖定巢狀順序規則,各種函式的呼叫上下文以及對核心內介面有意義的任何其他內容。 但也不可能在一臺機器中測試所有驅動程式,因此不可能對所有組合進行暴力 N vs. N 測試。 即使只是限制可能的組合也是不可行的。

  • 涉及大量的驅動程式程式碼。 對於渲染驅動程式,在釋出 fences 之後,命令提交的尾部、排程程式程式碼、中斷和處理 job 完成的 workers 以及超時、gpu 重置和 gpu hang recovery 程式碼。 此外,為了與核心 mm 整合,我們有 mmu_notifier,分別是 mmu_interval_notifiershrinker。 對於 modesetting 驅動程式,在釋出原子 modeset 的 fences 和相應的 vblank 完成之間,有 commit 尾部函式,包括任何中斷處理和相關的 workers。 審計所有驅動程式上的所有這些程式碼是不可行的。

  • 由於涉及許多其他子系統以及由此引入的鎖定層次結構,因此驅動程式特定的差異幾乎沒有迴旋餘地。 dma_fence 透過 dma_resvdma_resv_lock()dma_resv_unlock() 與幾乎所有核心記憶體處理(透過頁面錯誤處理程式)進行互動。另一方面,它還透過 mmu_notifiershrinker 與所有分配點進行互動。

此外,lockdep 不處理跨釋出依賴關係,這意味著 dma_fence_wait()dma_fence_signal() 之間的任何死鎖都無法在執行時透過一些快速測試來捕獲。最簡單的例子是一個執行緒等待 dma_fence,同時持有一個鎖

lock(A);
dma_fence_wait(B);
unlock(A);

而另一個執行緒被阻止嘗試獲取相同的鎖,這阻止了它發出前一個執行緒被阻止等待的 fence 訊號

lock(A);
unlock(A);
dma_fence_signal(B);

透過手動註釋所有與發出 dma_fence 訊號相關的程式碼,我們可以教導 lockdep 關於這些依賴關係,這也有助於驗證難題,因為現在 lockdep 可以為我們檢查所有規則

cookie = dma_fence_begin_signalling();
lock(A);
unlock(A);
dma_fence_signal(B);
dma_fence_end_signalling(cookie);

對於使用 dma_fence_begin_signalling()dma_fence_end_signalling() 來註釋關鍵部分,需要遵守以下規則

  • 必須註釋完成 dma_fence 所需的所有程式碼,從 fence 可供其他執行緒訪問的點到呼叫 dma_fence_signal() 的點。未註釋的程式碼可能包含死鎖問題,並且由於非常嚴格的規則和許多極端情況,僅透過審查或正常的壓力測試無法捕獲這些問題。

  • struct dma_resv 值得特別注意,因為讀取器僅受 rcu 保護。這意味著信令關鍵部分從安裝新 fence 時開始,甚至在呼叫 dma_resv_unlock() 之前。

  • 唯一的例外是快速路徑和機會主義信令程式碼,它們呼叫 dma_fence_signal() 純粹是為了最佳化,但不需要保證 dma_fence 的完成。通常的例子是等待 IOCTL 呼叫 dma_fence_signal(),而強制完成路徑透過硬體中斷和可能的工作完成程式。

  • 為了幫助程式碼的可組合性,只要總體鎖定層次結構一致,註釋可以自由巢狀。註釋也可以在中斷和程序上下文中工作。由於實現細節,這需要呼叫者將來自 dma_fence_begin_signalling() 的不透明 cookie 傳遞給 dma_fence_end_signalling()

  • 針對跨驅動程式合同的驗證是透過在啟動時使用相關層次結構來啟動 lockdep 來實現的。這意味著即使僅使用單個裝置進行測試也足以驗證驅動程式,至少就 dma_fence_wait()dma_fence_signal() 之間的死鎖而言。

DMA Fence 截止期限提示

在一個理想的世界中,有可能充分流水線化工作負載,以至於基於利用率的裝置頻率調節器可以達到滿足用例要求的最低頻率,從而最大限度地降低功耗。但在現實世界中,許多工作負載都與這種理想背道而馳。例如,但不限於

  • 在裝置和 CPU 之間來回切換的工作負載,CPU 交替等待裝置,裝置等待 CPU。這可能導致 devfreq 和 cpufreq 在各自的域中看到空閒時間,並導致降低頻率。

  • 與基於時間的週期性截止期限互動的工作負載,例如雙緩衝 GPU 渲染與 vblank 同步的頁面翻轉。在這種情況下,錯過 vblank 截止期限會導致 GPU 空閒時間增加(因為它必須等待額外的 vblank 週期),從而向 GPU 的 devfreq 傳送訊號以降低頻率,而實際上需要的是相反的。

為此,可以透過 dma_fence_set_deadline(或透過面向使用者空間的 ioctl,如 sync_set_deadline)在 dma_fence 上設定截止期限提示。截止期限提示提供了一種方式,讓等待的驅動程式或使用者空間向信令驅動程式傳達適當的緊迫感。

截止期限提示以絕對 ktime 給出(面向使用者空間的 API 為 CLOCK_MONOTONIC)。該時間可以是未來的某個時間點(例如頁面翻轉的基於 vblank 的截止期限,或合成器的合成週期的開始),也可以是當前時間以指示立即截止期限提示(即,在發出此 fence 訊號之前,無法取得進展)。

可以在給定的 fence 上設定多個截止期限,甚至可以並行設定。請參閱 dma_fence_ops.set_deadline 的文件。

截止期限提示只是一個提示。建立 fence 的驅動程式可能會透過提高頻率、做出不同的排程選擇等來做出反應。或者什麼都不做。

DMA Fences 函式參考

struct dma_fence *dma_fence_get_stub(void)

返回已發出訊號的 fence

引數

void

沒有引數

描述

返回已發出訊號的存根 fence。該 fence 的時間戳對應於啟動後首次呼叫此函式的時間。

struct dma_fence *dma_fence_allocate_private_stub(ktime_t timestamp)

返回私有的、已發出訊號的 fence

引數

ktime_t timestamp

fence 發出訊號的時間戳

描述

返回新分配和已發出訊號的存根 fence。

u64 dma_fence_context_alloc(unsigned num)

分配 fence 上下文陣列

引數

unsigned num

要分配的上下文數量

描述

此函式將返回分配的 fence 上下文數量的第一個索引。fence 上下文用於透過將上下文傳遞給 dma_fence_init(),將 dma_fence.context 設定為唯一數字。

bool dma_fence_begin_signalling(void)

開始關鍵的 DMA fence 信令部分

引數

void

沒有引數

描述

驅動程式應使用它來註釋最終需要透過呼叫 dma_fence_signal() 來完成 dma_fence 的任何程式碼部分的開頭。

這些關鍵部分的結尾用 dma_fence_end_signalling() 註釋。

實現所需的不透明 cookie,需要傳遞給 dma_fence_end_signalling()

void dma_fence_end_signalling(bool cookie)

結束關鍵的 DMA fence 信令部分

引數

bool cookie

來自 dma_fence_begin_signalling() 的不透明 cookie

描述

關閉由 dma_fence_begin_signalling() 開啟的關鍵部分註釋。

int dma_fence_signal_timestamp_locked(struct dma_fence *fence, ktime_t timestamp)

發出 fence 完成訊號

引數

struct dma_fence *fence

要發出訊號的 fence

ktime_t timestamp

核心 CLOCK_MONOTONIC 時域中的 fence 訊號時間戳

描述

發出 fence 上軟體回撥的完成訊號,這將解除 dma_fence_wait() 呼叫的阻止,並執行使用 dma_fence_add_callback() 新增的所有回撥。可以多次呼叫,但由於 fence 只能從未發出訊號的狀態變為已發出訊號的狀態,而不能返回,因此它只會在第一次生效。將提供的時間戳設定為 fence 訊號時間戳。

dma_fence_signal_timestamp() 不同,必須在持有 dma_fence.lock 的情況下呼叫此函式。

成功時返回 0,如果已發出 fence 訊號,則返回負錯誤值。

int dma_fence_signal_timestamp(struct dma_fence *fence, ktime_t timestamp)

發出 fence 完成訊號

引數

struct dma_fence *fence

要發出訊號的 fence

ktime_t timestamp

核心 CLOCK_MONOTONIC 時域中的 fence 訊號時間戳

描述

發出 fence 上軟體回撥的完成訊號,這將解除 dma_fence_wait() 呼叫的阻止,並執行使用 dma_fence_add_callback() 新增的所有回撥。可以多次呼叫,但由於 fence 只能從未發出訊號的狀態變為已發出訊號的狀態,而不能返回,因此它只會在第一次生效。將提供的時間戳設定為 fence 訊號時間戳。

成功時返回 0,如果已發出 fence 訊號,則返回負錯誤值。

int dma_fence_signal_locked(struct dma_fence *fence)

發出 fence 完成訊號

引數

struct dma_fence *fence

要發出訊號的 fence

描述

發出 fence 上軟體回撥的完成訊號,這將解除 dma_fence_wait() 呼叫的阻止,並執行使用 dma_fence_add_callback() 新增的所有回撥。可以多次呼叫,但由於 fence 只能從未發出訊號的狀態變為已發出訊號的狀態,而不能返回,因此它只會在第一次生效。

dma_fence_signal() 不同,必須在持有 dma_fence.lock 的情況下呼叫此函式。

成功時返回 0,如果已發出 fence 訊號,則返回負錯誤值。

int dma_fence_signal(struct dma_fence *fence)

發出 fence 完成訊號

引數

struct dma_fence *fence

要發出訊號的 fence

描述

發出 fence 上軟體回撥的完成訊號,這將解除 dma_fence_wait() 呼叫的阻止,並執行使用 dma_fence_add_callback() 新增的所有回撥。可以多次呼叫,但由於 fence 只能從未發出訊號的狀態變為已發出訊號的狀態,而不能返回,因此它只會在第一次生效。

成功時返回 0,如果已發出 fence 訊號,則返回負錯誤值。

signed long dma_fence_wait_timeout(struct dma_fence *fence, bool intr, signed long timeout)

睡眠,直到 fence 發出訊號或直到超時時間過去

引數

struct dma_fence *fence

要等待的 fence

bool intr

如果為 true,則進行可中斷的等待

signed long timeout

jiffies 中的超時值,或 MAX_SCHEDULE_TIMEOUT

描述

如果被中斷,則返回 -ERESTARTSYS;如果等待超時,則返回 0;如果成功,則返回剩餘的 jiffies 超時時間。其他錯誤值可能會在自定義實現中返回。

對此 fence 執行同步等待。假定呼叫者直接或間接地(預留和提交之間的 buf-mgr)持有對 fence 的引用,否則 fence 可能會在返回之前被釋放,從而導致未定義的行為。

另請參閱 dma_fence_wait()dma_fence_wait_any_timeout()

void dma_fence_release(struct kref *kref)

fence 的預設釋放函式

引數

struct kref *kref

dma_fence.recfount

描述

這是 dma_fence 的預設釋放函式。驅動程式不應直接呼叫此函式,而應呼叫 dma_fence_put()

void dma_fence_free(struct dma_fence *fence)

dma_fence 的預設釋放函式。

引數

struct dma_fence *fence

要釋放的 fence

描述

這是 dma_fence_ops.release 的預設實現。它在 fence 上呼叫 kfree_rcu()

void dma_fence_enable_sw_signaling(struct dma_fence *fence)

啟用 fence 上的信令

引數

struct dma_fence *fence

要啟用的 fence

描述

這將請求啟用 sw 信令,以便儘快完成 fence。這在內部呼叫 dma_fence_ops.enable_signaling

int dma_fence_add_callback(struct dma_fence *fence, struct dma_fence_cb *cb, dma_fence_func_t func)

新增要在發出 fence 訊號時呼叫的回撥

引數

struct dma_fence *fence

要等待的 fence

struct dma_fence_cb *cb

要註冊的回撥函式

dma_fence_func_t func

要呼叫的函式

描述

向 fence 新增一個軟體回撥。呼叫者應保持對 fence 的引用。

cb 將由 dma_fence_add_callback() 初始化,呼叫者無需進行初始化。可以向一個 fence 註冊任意數量的回撥,但一個回撥一次只能註冊到一個 fence。

如果 fence 已經發出訊號,此函式將返回 -ENOENT(並且 *不* 呼叫回撥)。

請注意,回撥可以從原子上下文或 irq 上下文中呼叫。

成功時返回 0,如果 fence 已經發出訊號則返回 -ENOENT,如果出錯則返回 -EINVAL。

int dma_fence_get_status(struct dma_fence *fence)

返回完成時的狀態

引數

struct dma_fence *fence

要查詢的 dma_fence

描述

這封裝了 dma_fence_get_status_locked() 以在已發出訊號的 fence 上返回錯誤狀態條件。有關更多詳細資訊,請參見 dma_fence_get_status_locked()

如果 fence 尚未發出訊號,則返回 0;如果 fence 已發出訊號且沒有錯誤條件,則返回 1;如果 fence 已在 err 中完成,則返回負錯誤程式碼。

bool dma_fence_remove_callback(struct dma_fence *fence, struct dma_fence_cb *cb)

從信令列表中刪除回撥

引數

struct dma_fence *fence

要等待的 fence

struct dma_fence_cb *cb

要刪除的回撥

描述

從 fence 中刪除先前排隊的回撥。如果成功刪除回撥,則此函式返回 true;如果 fence 已經發出訊號,則返回 false。

警告:只有在你真正知道自己在做什麼的情況下,才應該取消回撥,因為很容易發生死鎖和競爭條件。因此,它應該只在硬體鎖定恢復時進行,並且持有對 fence 的引用。

如果 cb 之前未使用 dma_fence_add_callback() 新增到 fence,則行為未定義。

signed long dma_fence_default_wait(struct dma_fence *fence, bool intr, signed long timeout)

預設睡眠,直到 fence 發出訊號或直到超時時間過去

引數

struct dma_fence *fence

要等待的 fence

bool intr

如果為 true,則進行可中斷的等待

signed long timeout

jiffies 中的超時值,或 MAX_SCHEDULE_TIMEOUT

描述

如果被中斷,則返回 -ERESTARTSYS;如果等待超時,則返回 0;如果成功,則返回 jiffies 中剩餘的超時時間。如果超時時間為零,則如果 fence 已經發出訊號,則返回值 1,以便與其他採用 jiffies 超時的函式保持一致。

signed long dma_fence_wait_any_timeout(struct dma_fence **fences, uint32_t count, bool intr, signed long timeout, uint32_t *idx)

睡眠,直到任何 fence 發出訊號或直到超時時間過去

引數

struct dma_fence **fences

要等待的 fence 陣列

uint32_t count

要等待的 fence 數量

bool intr

如果為 true,則進行可中斷的等待

signed long timeout

jiffies 中的超時值,或 MAX_SCHEDULE_TIMEOUT

uint32_t *idx

用於儲存第一個發出訊號的 fence 索引,僅在正返回時有意義

描述

如果在自定義 fence 等待實現上,則返回 -EINVAL;如果被中斷,則返回 -ERESTARTSYS;如果等待超時,則返回 0;如果成功,則返回 jiffies 中剩餘的超時時間。

同步等待陣列中第一個發出訊號的 fence。呼叫者需要持有對陣列中所有 fence 的引用,否則 fence 可能會在返回之前被釋放,從而導致未定義的行為。

另請參見 dma_fence_wait()dma_fence_wait_timeout()

void dma_fence_set_deadline(struct dma_fence *fence, ktime_t deadline)

設定所需的 fence-wait 截止日期提示

引數

struct dma_fence *fence

要等待的 fence

ktime_t deadline

等待者希望 fence 發出訊號的時間

描述

向 fence 傳送者提供有關即將到來的截止日期(例如 vblank)的提示,到那時,等待者希望 fence 發出訊號。 這旨在向 fence 傳送者提供反饋,以幫助進行電源管理決策,例如,如果定期 vblank 截止日期即將到來但 fence 尚未發出訊號,則提高 GPU 頻率。

void dma_fence_describe(struct dma_fence *fence, struct seq_file *seq)

將 fence 描述轉儲到 seq_file 中

引數

struct dma_fence *fence

要描述的 fence

struct seq_file *seq

要將文字描述放入的 seq_file

描述

將 fence 及其狀態的文字描述轉儲到 seq_file 中。

void dma_fence_init(struct dma_fence *fence, const struct dma_fence_ops *ops, spinlock_t *lock, u64 context, u64 seqno)

初始化自定義 fence。

引數

struct dma_fence *fence

要初始化的 fence

const struct dma_fence_ops *ops

用於對此 fence 執行操作的 dma_fence_ops

spinlock_t *lock

用於鎖定此 fence 的 irqsafe 自旋鎖

u64 context

執行此 fence 的執行上下文

u64 seqno

此上下文的線性遞增序列號

描述

初始化已分配的 fence,呼叫者無需在此 fence 提交後保留其引用計數,但如果呼叫了 dma_fence_ops.enable_signaling,則需要再次保留引用計數。

context 和 seqno 用於在 fence 之間輕鬆比較,從而允許透過簡單地使用 dma_fence_later() 來檢查哪個 fence 更晚。

struct dma_fence

軟體同步原語

定義:

struct dma_fence {
    spinlock_t *lock;
    const struct dma_fence_ops *ops;
    union {
        struct list_head cb_list;
        ktime_t timestamp;
        struct rcu_head rcu;
    };
    u64 context;
    u64 seqno;
    unsigned long flags;
    struct kref refcount;
    int error;
};

成員

lock

用於鎖定的 spin_lock_irqsave

ops

與此 fence 關聯的 dma_fence_ops

{unnamed_union}

匿名

cb_list

要呼叫的所有回撥的列表

timestamp

fence 發出訊號時的時間戳。

rcu

用於使用 kfree_rcu 釋放 fence

context

此 fence 所屬的執行上下文,由 dma_fence_context_alloc() 返回

seqno

此 fence 在執行上下文中的序列號,可以進行比較以確定哪個 fence 將在稍後發出訊號。

flags

下面定義的 DMA_FENCE_FLAG_* 的掩碼

refcount

此 fence 的引用計數

error

可選,僅當 < 0 時有效,必須在呼叫 dma_fence_signal 之前設定,表示 fence 已完成並出現錯誤。

描述

必須使用適當的原子操作 (bit_*) 來操作和讀取 flags 成員,因此大多數情況下不需要獲取自旋鎖。

DMA_FENCE_FLAG_SIGNALED_BIT - fence 已經發出訊號 DMA_FENCE_FLAG_TIMESTAMP_BIT - 記錄了 fence 信令的時間戳 DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT - 可能已呼叫 enable_signaling DMA_FENCE_FLAG_USER_BITS - 未使用位的開始,可以由 fence 的實現者用於自己的目的。 不同的 fence 實現者可以使用不同的方式,因此不要依賴它。

由於使用了原子位操作,因此不能保證這種情況。 特別是,如果設定了該位,但在設定此位之前立即呼叫了 dma_fence_signal,則它將能夠在呼叫 enable_signaling 之前設定 DMA_FENCE_FLAG_SIGNALED_BIT。 在設定 DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT 之後新增對 DMA_FENCE_FLAG_SIGNALED_BIT 的檢查會關閉此競爭,並確保在呼叫 dma_fence_signal 之後,任何 enable_signaling 呼叫都已完成或根本未呼叫。

struct dma_fence_cb

dma_fence_add_callback() 的回撥

定義:

struct dma_fence_cb {
    struct list_head node;
    dma_fence_func_t func;
};

成員

node

dma_fence_add_callback() 使用以將此結構附加到 fence::cb_list

func

要呼叫的 dma_fence_func_t

描述

此結構將由 dma_fence_add_callback() 初始化,可以透過將 dma_fence_cb 嵌入到另一個結構中來傳遞其他資料。

struct dma_fence_ops

為 fence 實現的操作

定義:

struct dma_fence_ops {
    bool use_64bit_seqno;
    const char * (*get_driver_name)(struct dma_fence *fence);
    const char * (*get_timeline_name)(struct dma_fence *fence);
    bool (*enable_signaling)(struct dma_fence *fence);
    bool (*signaled)(struct dma_fence *fence);
    signed long (*wait)(struct dma_fence *fence, bool intr, signed long timeout);
    void (*release)(struct dma_fence *fence);
    void (*set_deadline)(struct dma_fence *fence, ktime_t deadline);
};

成員

use_64bit_seqno

如果此 dma_fence 實現使用 64 位 seqno,則為 True,否則為 false。

get_driver_name

返回驅動程式名稱。這是一個回撥,允許驅動程式在執行時計算名稱,而無需為每個 fence 永久儲存名稱,或構建某種快取。

此回撥是必需的。

get_timeline_name

返回此 fence 所屬的上下文的名稱。這是一個回撥,允許驅動程式在執行時計算名稱,而無需為每個 fence 永久儲存名稱,或構建某種快取。

此回撥是必需的。

enable_signaling

啟用 fence 的軟體信令。

對於具有 hw->hw 信令功能的 fence 實現,它們可以實現此操作以啟用必要的 interrupts,或將命令插入到 cmdstream 等中,以避免在僅需要 hw->hw 同步的常見情況下執行這些代價高昂的操作。 這在第一個 dma_fence_wait()dma_fence_add_callback() 路徑中呼叫,以讓 fence 實現知道有另一個驅動程式正在等待訊號(即 hw->sw 情況)。

這在停用 irq 的情況下呼叫,因此只有停用 IRQ 的自旋鎖才能在此回撥之外的程式碼中使用。

返回值 false 表示 fence 已經透過,或者發生了某些故障,導致無法啟用信令。 True 表示成功啟用。

可以在 enable_signaling 中設定 dma_fence.error,但僅在返回 false 時。

由於許多實現即使在呼叫 enable_signaling 之前也可以呼叫 dma_fence_signal(),因此存在一個競爭視窗,其中 dma_fence_signal() 可能會導致最終的 fence 引用被釋放,並且其記憶體被釋放。 為了避免這種情況,此回撥的實現應使用 dma_fence_get() 獲取自己的引用,以便在發出 fence 訊號時(例如,透過中斷處理程式)釋放。

此回撥是可選的。 如果不存在此回撥,則驅動程式必須始終啟用信令。

signaled

窺視 fence 是否發出訊號,作為例如 dma_fence_wait()dma_fence_add_callback() 的快速路徑最佳化。 請注意,除了指示為已發出訊號的 fence 必須始終從此回撥返回 true 之外,此回撥不需要做出任何保證。 即使 fence 已經完成,此回撥也可能返回 false,在這種情況下,資訊尚未在系統中傳播。 另請參見 dma_fence_is_signaled()

如果返回 true,則可以設定 dma_fence.error

此回撥是可選的。

wait

自定義等待實現,如果未設定,則預設為 dma_fence_default_wait()

已棄用,不應由新的實現使用。 僅由需要對其硬體重置過程進行特殊處理的現有實現使用。

如果 wait 是 intr = true 並且 wait 被中斷,則必須返回 -ERESTARTSYS;如果 fence 已發出訊號,則返回剩餘的 jiffies;如果等待超時,則返回 0。 也可以在自定義實現上返回其他錯誤值,這些值應被視為 fence 已發出訊號。 例如,硬體鎖定可能會以這種方式報告。

release

在銷燬 fence 時呼叫以釋放其他資源。 可以從 irq 上下文中呼叫。 此回撥是可選的。 如果為 NULL,則改為呼叫 dma_fence_free() 作為預設實現。

set_deadline

回撥以允許 fence 等待者通知 fence 信令者即將到來的截止日期,例如 vblank,到那時,等待者希望 fence 發出訊號。 這旨在向 fence 信令者提供反饋,以幫助進行電源管理決策,例如提高 GPU 頻率。

這在沒有持有 dma_fence.lock 的情況下呼叫,它可以多次呼叫並且可以從任何上下文中呼叫。 如果 callee 有一些狀態要管理,則鎖定由 callee 決定。 如果設定了多個截止日期,則期望跟蹤最早的截止日期。 如果截止日期早於當前時間,則應將其解釋為立即截止日期。

此回撥是可選的。

void dma_fence_put(struct dma_fence *fence)

減少 fence 的引用計數

引數

struct dma_fence *fence

要減少引用計數的 fence

struct dma_fence *dma_fence_get(struct dma_fence *fence)

增加 fence 的引用計數

引數

struct dma_fence *fence

要增加引用計數的 fence

描述

返回相同的 fence,引用計數增加 1。

struct dma_fence *dma_fence_get_rcu(struct dma_fence *fence)

使用 rcu 讀取鎖從 dma_resv_list 獲取 fence

引數

struct dma_fence *fence

要增加引用計數的 fence

描述

如果無法獲得引用計數,則函式返回 NULL 或 fence。

struct dma_fence *dma_fence_get_rcu_safe(struct dma_fence __rcu **fencep)

獲取對 RCU 跟蹤的 fence 的引用

引數

struct dma_fence __rcu **fencep

指向要增加引用計數的 fence 的指標

描述

如果無法獲得引用計數,則函式返回 NULL;否則返回 fence。此函式處理獲取對可能在 RCU 寬限期內重新分配的 fence 的引用(例如使用 SLAB_TYPESAFE_BY_RCU),只要呼叫者在指向 fence 的指標上使用 RCU 即可。

另一種機制是使用 seqlock 來保護一組 fence,例如 struct dma_resv 使用的機制。使用 seqlock 時,必須在獲取對 fence 的引用之前獲取 seqlock 並在之後進行檢查(如此處所示)。

呼叫者需要持有 RCU 讀取鎖。

bool dma_fence_is_signaled_locked(struct dma_fence *fence)

返回一個指示 fence 是否已發出訊號的標誌。

引數

struct dma_fence *fence

要檢查的 fence

描述

如果 fence 已經發出訊號,則返回 true,否則返回 false。由於此函式不啟用訊號,因此如果在呼叫 dma_fence_add_callback()dma_fence_wait()dma_fence_enable_sw_signaling() 之前未呼叫它,則不保證它永遠返回 true。

此函式需要持有 dma_fence.lock

另請參見 dma_fence_is_signaled()

bool dma_fence_is_signaled(struct dma_fence *fence)

返回一個指示 fence 是否已發出訊號的標誌。

引數

struct dma_fence *fence

要檢查的 fence

描述

如果 fence 已經發出訊號,則返回 true,否則返回 false。由於此函式不啟用訊號,因此如果在呼叫 dma_fence_add_callback()dma_fence_wait()dma_fence_enable_sw_signaling() 之前未呼叫它,則不保證它永遠返回 true。

建議 seqno fence 在操作完成時呼叫 dma_fence_signal,這樣可以透過在呼叫特定於硬體的等待指令之前檢查此函式的返回值來防止釋出時間和使用時間之間的環繞問題。

另請參見 dma_fence_is_signaled_locked()

bool __dma_fence_is_later(u64 f1, u64 f2, const struct dma_fence_ops *ops)

如果 f1 在時間上晚於 f2,則返回 true

引數

u64 f1

第一個 fence 的 seqno

u64 f2

來自同一上下文的第二個 fence 的 seqno

const struct dma_fence_ops *ops

與 seqno 關聯的 dma_fence_ops

描述

如果 f1 在時間上晚於 f2,則返回 true。由於 seqno 在上下文之間不通用,因此兩個 fence 必須來自同一上下文。

bool dma_fence_is_later(struct dma_fence *f1, struct dma_fence *f2)

如果 f1 在時間上晚於 f2,則返回 true

引數

struct dma_fence *f1

來自同一上下文的第一個 fence

struct dma_fence *f2

來自同一上下文的第二個 fence

描述

如果 f1 在時間上晚於 f2,則返回 true。由於 seqno 不會在上下文之間重複使用,因此兩個 fence 必須來自同一上下文。

bool dma_fence_is_later_or_same(struct dma_fence *f1, struct dma_fence *f2)

如果 f1 晚於或等於 f2,則返回 true

引數

struct dma_fence *f1

來自同一上下文的第一個 fence

struct dma_fence *f2

來自同一上下文的第二個 fence

描述

如果 f1 在時間上晚於 f2 或與 f2 相同,則返回 true。由於 seqno 不會在上下文之間重複使用,因此兩個 fence 必須來自同一上下文。

struct dma_fence *dma_fence_later(struct dma_fence *f1, struct dma_fence *f2)

返回時間上較晚的 fence

引數

struct dma_fence *f1

來自同一上下文的第一個 fence

struct dma_fence *f2

來自同一上下文的第二個 fence

描述

如果兩個 fence 都已發出訊號,則返回 NULL;否則返回將最後發出訊號的 fence。由於 seqno 不會在上下文之間重複使用,因此兩個 fence 必須來自同一上下文。

int dma_fence_get_status_locked(struct dma_fence *fence)

返回完成時的狀態

引數

struct dma_fence *fence

要查詢的 dma_fence

描述

驅動程式可以在發出 fence 訊號之前提供一個可選的錯誤狀態條件(以指示 fence 的完成是由於錯誤而不是成功)。只有在 fence 已發出訊號時,狀態條件的值才有效,dma_fence_get_status_locked() 首先檢查訊號狀態,然後報告錯誤狀態。

如果 fence 尚未發出訊號,則返回 0;如果 fence 已發出訊號且沒有錯誤條件,則返回 1;如果 fence 已在 err 中完成,則返回負錯誤程式碼。

void dma_fence_set_error(struct dma_fence *fence, int error)

在 fence 上標記錯誤條件

引數

struct dma_fence *fence

dma_fence

int error

要儲存的錯誤

描述

驅動程式可以在發出 fence 訊號之前提供一個可選的錯誤狀態條件,以指示 fence 的完成是由於錯誤而不是成功。這必須在發出訊號之前設定(以便在喚醒訊號回撥上的任何等待者之前,該值是可見的)。此幫助程式的存在是為了幫助捕獲 #dma_fence.error 的錯誤設定。

驅動程式應使用的錯誤程式碼示例

  • -ENODATA 此操作未生成任何資料,沒有其他操作受到影響。

  • -ECANCELED 來自同一上下文的所有操作都已取消。

  • -ETIME 操作導致超時,並可能導致裝置重置。

ktime_t dma_fence_timestamp(struct dma_fence *fence)

獲取 fence 的完成時間戳的幫助程式

引數

struct dma_fence *fence

從中獲取時間戳的 fence。

描述

在發出 fence 訊號後,時間戳會使用訊號時間進行更新,但設定時間戳可能會與等待訊號的任務發生競爭。此幫助程式會忙等待,直到出現正確的時間戳。

signed long dma_fence_wait(struct dma_fence *fence, bool intr)

睡眠直到發出 fence 訊號

引數

struct dma_fence *fence

要等待的 fence

bool intr

如果為 true,則進行可中斷的等待

描述

如果被訊號中斷,此函式將返回 -ERESTARTSYS;如果 fence 已發出訊號,則返回 0。自定義實現可能會返回其他錯誤值。

對此 fence 執行同步等待。假定呼叫者直接或間接持有對 fence 的引用,否則 fence 可能會在返回之前被釋放,從而導致未定義的行為。

另請參見 dma_fence_wait_timeout()dma_fence_wait_any_timeout()

bool dma_fence_is_array(struct dma_fence *fence)

檢查 fence 是否來自陣列子類

引數

struct dma_fence *fence

要測試的 fence

描述

如果是 dma_fence_array,則返回 true,否則返回 false。

bool dma_fence_is_chain(struct dma_fence *fence)

檢查 fence 是否來自鏈子類

引數

struct dma_fence *fence

要測試的 fence

描述

如果是 dma_fence_chain,則返回 true,否則返回 false。

bool dma_fence_is_container(struct dma_fence *fence)

檢查 fence 是否是其他 fence 的容器

引數

struct dma_fence *fence

要測試的 fence

描述

如果此 fence 是其他 fence 的容器,則返回 true,否則返回 false。這很重要,因為我們不能構建大型 fence 結構,否則在對這些 fence 進行操作時會遇到遞迴。

DMA Fence 陣列

struct dma_fence_array *dma_fence_array_alloc(int num_fences)

分配一個自定義 fence 陣列

引數

int num_fences

[in] 要新增到陣列中的 fence 的數量

描述

成功時返回 dma fence 陣列,失敗時返回 NULL

void dma_fence_array_init(struct dma_fence_array *array, int num_fences, struct dma_fence **fences, u64 context, unsigned seqno, bool signal_on_any)

初始化一個自定義 fence 陣列

引數

struct dma_fence_array *array

[in] 要武裝的 dma fence 陣列

int num_fences

[in] 要新增到陣列中的 fence 的數量

struct dma_fence **fences

[in] 包含 fence 的陣列

u64 context

[in] 要使用的 fence 上下文

unsigned seqno

[in] 要使用的序列號

bool signal_on_any

[in] 如果陣列中的任何 fence 發出訊號,則發出訊號

描述

實現沒有分配的 dma_fence_array_create。可用於在回收或 dma fence 訊號的路徑中初始化預先分配的 dma fence 陣列。

struct dma_fence_array *dma_fence_array_create(int num_fences, struct dma_fence **fences, u64 context, unsigned seqno, bool signal_on_any)

建立一個自定義 fence 陣列

引數

int num_fences

[in] 要新增到陣列中的 fence 的數量

struct dma_fence **fences

[in] 包含 fence 的陣列

u64 context

[in] 要使用的 fence 上下文

unsigned seqno

[in] 要使用的序列號

bool signal_on_any

[in] 如果陣列中的任何 fence 發出訊號,則發出訊號

描述

分配一個 dma_fence_array 物件,並使用 dma_fence_init() 初始化基本 fence。如果發生錯誤,則返回 NULL。

呼叫者應使用 num_fences 大小分配 fences 陣列,並使用要新增到物件的 fence 填充它。此陣列的所有權將被獲取,並且在釋放時,會對每個 fence 使用 dma_fence_put()

如果 signal_on_any 為 true,則如果陣列中的任何 fence 發出訊號,則 fence 陣列會發出訊號;否則,當陣列中的所有 fence 發出訊號時,它會發出訊號。

bool dma_fence_match_context(struct dma_fence *fence, u64 context)

檢查所有 fence 是否來自給定的上下文

引數

struct dma_fence *fence

[in] fence 或 fence 陣列

u64 context

[in] 用於檢查所有 fence 的 fence 上下文

描述

根據給定的上下文檢查提供的 fence,或者對於 fence 陣列,檢查陣列中的所有 fence。如果任何 fence 來自不同的上下文,則返回 false。

struct dma_fence_array_cb

用於 fence 陣列的回撥幫助程式

定義:

struct dma_fence_array_cb {
    struct dma_fence_cb cb;
    struct dma_fence_array *array;
};

成員

cb

用於訊號的 fence 回撥結構

陣列

指向父 fence 陣列物件的引用

struct dma_fence_array

用於表示 fence 陣列的 fence

定義:

struct dma_fence_array {
    struct dma_fence base;
    spinlock_t lock;
    unsigned num_fences;
    atomic_t num_pending;
    struct dma_fence **fences;
    struct irq_work work;
    struct dma_fence_array_cb callbacks[] ;
};

成員

基類

fence 基類

lock

用於 fence 處理的自旋鎖

num_fences

陣列中 fence 的數量

num_pending

陣列中仍在掛起的 fence 數量

fences

fence 的陣列

工作項

內部 irq_work 函式

回撥

回撥助手陣列

struct dma_fence_array *to_dma_fence_array(struct dma_fence *fence)

將 fence 強制轉換為 dma_fence_array

引數

struct dma_fence *fence

要強制轉換為 dma_fence_array 的 fence

描述

如果 fence 不是 dma_fence_array,則返回 NULL;否則返回 dma_fence_array。

dma_fence_array_for_each

dma_fence_array_for_each (fence, index, head)

迭代陣列中的所有 fence

引數

fence

當前的 fence

index

陣列中的索引

head

潛在的 dma_fence_array 物件

描述

測試 array 是否為 dma_fence_array 物件,如果是,則迭代陣列中的所有 fence。 如果不是,則只迭代 array 自身中的 fence。

有關深度迭代器,請參見 dma_fence_unwrap_for_each()

DMA Fence 鏈

struct dma_fence *dma_fence_chain_walk(struct dma_fence *fence)

鏈式遍歷函式

引數

struct dma_fence *fence

當前鏈節點

描述

遍歷鏈到下一個節點。如果到達鏈的末尾,則返回下一個 fence 或 NULL。垃圾回收已發出訊號的鏈節點。

int dma_fence_chain_find_seqno(struct dma_fence **pfence, uint64_t seqno)

按 seqno 查詢 fence 鏈節點

引數

struct dma_fence **pfence

指向鏈節點開始位置的指標

uint64_t seqno

要搜尋的序列號

描述

將 fence 指標提前到將發出此序列號訊號的鏈節點。 如果沒有提供序列號,則這是一個空操作。

如果 fence 不是鏈節點或序列號尚未前進到足夠遠,則返回 EINVAL。

void dma_fence_chain_init(struct dma_fence_chain *chain, struct dma_fence *prev, struct dma_fence *fence, uint64_t seqno)

初始化 fence 鏈

引數

struct dma_fence_chain *chain

要初始化的鏈節點

struct dma_fence *prev

先前的 fence

struct dma_fence *fence

當前的 fence。

uint64_t seqno

用於 fence 鏈的序列號

描述

初始化一個新的鏈節點,並啟動一個新的鏈或將該節點新增到先前 fence 的現有鏈中。

struct dma_fence_chain

用於表示 fence 鏈節點的 fence

定義:

struct dma_fence_chain {
    struct dma_fence base;
    struct dma_fence __rcu *prev;
    u64 prev_seqno;
    struct dma_fence *fence;
    union {
        struct dma_fence_cb cb;
        struct irq_work work;
    };
    spinlock_t lock;
};

成員

基類

fence 基類

prev

鏈的先前 fence

prev_seqno

垃圾回收前的原始先前 seqno

fence

封裝的 fence

{unnamed_union}

匿名

cb

用於發出訊號的回撥

這用於新增用於發出 fence 鏈完成訊號的回撥。 永遠不要與 irq work 同時使用。

工作項

用於發出訊號的 irq work 專案

允許我們添加回調而不遇到鎖反轉的 Irq work 結構。 永遠不要與回撥同時使用。

lock

用於 fence 處理的自旋鎖

struct dma_fence_chain *to_dma_fence_chain(struct dma_fence *fence)

將 fence 強制轉換為 dma_fence_chain

引數

struct dma_fence *fence

要強制轉換為 dma_fence_array 的 fence

描述

如果 fence 不是 dma_fence_chain,則返回 NULL;否則返回 dma_fence_chain。

struct dma_fence *dma_fence_chain_contained(struct dma_fence *fence)

返回包含的 fence

引數

struct dma_fence *fence

要測試的 fence

描述

如果 fence 是 dma_fence_chain,則該函式返回包含在鏈物件內的 fence,否則返回 fence 本身。

dma_fence_chain_alloc

dma_fence_chain_alloc ()

描述

返回一個新的 struct dma_fence_chain 物件,如果失敗則返回 NULL。

這個專門的分配器必須是一個宏,以便其分配可以單獨計算(具有單獨的 alloc_tag)。 型別轉換是有意強制執行型別安全的。

void dma_fence_chain_free(struct dma_fence_chain *chain)

引數

struct dma_fence_chain *chain

要釋放的鏈節點

描述

釋放已分配但未使用的 struct dma_fence_chain 物件。 這不需要 RCU 寬限期,因為 fence 從未初始化或釋出。 在呼叫 dma_fence_chain_init() 之後,必須透過呼叫 dma_fence_put() 來釋放 fence,而不是透過此函式。

dma_fence_chain_for_each

dma_fence_chain_for_each (iter, head)

迭代鏈中的所有 fence

引數

iter

當前的 fence

head

起點

描述

迭代鏈中的所有 fence。 我們在迴圈內保持對當前 fence 的引用,該引用在退出時必須刪除。

有關深度迭代器,請參見 dma_fence_unwrap_for_each()

DMA Fence 解包

struct dma_fence_unwrap

容器結構中的遊標

定義:

struct dma_fence_unwrap {
    struct dma_fence *chain;
    struct dma_fence *array;
    unsigned int index;
};

成員

chain

潛在的 dma_fence_chain,但也可以是其他 fence

陣列

潛在的 dma_fence_array,但也可以是其他 fence

index

如果 array 確實是一個 dma_fence_array,則最後返回的索引

描述

應與 dma_fence_unwrap_for_each() 迭代器宏一起使用。

dma_fence_unwrap_for_each

dma_fence_unwrap_for_each (fence, cursor, head)

迭代容器中的所有 fence

引數

fence

當前的 fence

游標。

容器內的當前位置

head

迭代器的起點

描述

解包 dma_fence_chain 和 dma_fence_array 容器,並深入到其中的所有潛在 fence 中。 如果 head 只是一個普通的 fence,則只返回該 fence。

dma_fence_unwrap_merge

dma_fence_unwrap_merge (...)

解包併合並 fence

引數

...

可變引數

描述

作為引數給出的所有 fence 都被解包並作為平面 dma_fence_array 合併回一起。 如果需要將多個容器合併在一起,則很有用。

實現為一個宏,用於在堆疊上分配必要的陣列,並將堆疊幀大小計算到呼叫方。

如果記憶體分配失敗,則返回 NULL;否則返回表示所有給定 fence 的 dma_fence 物件。

DMA Fence 同步檔案

struct sync_file *sync_file_create(struct dma_fence *fence)

建立同步檔案

引數

struct dma_fence *fence

要新增到 sync_fence 的 fence

描述

建立一個包含 fence 的 sync_file。 如果成功,此函式會為新建立的 sync_file 獲取 fence 的附加引用。 可以使用 fput(sync_file->file) 釋放 sync_file。 返回 sync_file,如果出錯則返回 NULL。

struct dma_fence *sync_file_get_fence(int fd)

獲取與 sync_file fd 相關的 fence

引數

int fd

從中獲取 fence 的 sync_file fd

描述

確保 fd 引用一個有效的 sync_file,並返回一個表示 sync_file 中所有 fence 的 fence。 如果出錯,則返回 NULL。

struct sync_file

要匯出到使用者空間的同步檔案

定義:

struct sync_file {
    struct file             *file;
    char user_name[32];
#ifdef CONFIG_DEBUG_FS;
    struct list_head        sync_file_list;
#endif;
    wait_queue_head_t wq;
    unsigned long           flags;
    struct dma_fence        *fence;
    struct dma_fence_cb cb;
};

成員

file

表示此 fence 的檔案

user_name

使用者空間提供的同步檔案的名稱,用於合併的 fence。 否則透過驅動程式回撥生成(在這種情況下,整個陣列為 0)。

sync_file_list

全域性檔案列表中的成員資格

wq

用於 fence 訊號的等待佇列

flags

同步檔案的標誌

fence

帶有 sync_file 中的 fence 的 fence

cb

fence 回撥資訊

描述

標誌:POLL_ENABLED:使用者空間當前是否正在 poll()

DMA Fence 同步檔案 uABI

struct sync_merge_data

SYNC_IOC_MERGE:合併兩個 fence

定義:

struct sync_merge_data {
    char name[32];
    __s32 fd2;
    __s32 fence;
    __u32 flags;
    __u32 pad;
};

成員

name

新 fence 的名稱

fd2

第二個 fence 的檔案描述符

fence

將新 fence 的 fd 返回給使用者空間

flags

merge_data 標誌

pad

用於 64 位對齊的填充,應始終為零

描述

建立一個新 fence,其中包含呼叫 fd 和 sync_merge_data.fd2 中的 sync_pts 的副本。 在 sync_merge_data.fence 中返回新 fence 的 fd

struct sync_fence_info

詳細的 fence 資訊

定義:

struct sync_fence_info {
    char obj_name[32];
    char driver_name[32];
    __s32 status;
    __u32 flags;
    __u64 timestamp_ns;
};

成員

obj_name

父 sync_timeline 的名稱

driver_name

實現父物件的驅動程式的名稱

status

fence 的狀態 0:active 1:signaled <0:error

flags

fence_info 標誌

timestamp_ns

狀態更改的時間戳(以納秒為單位)

struct sync_file_info

SYNC_IOC_FILE_INFO:獲取有關 sync_file 的詳細資訊

定義:

struct sync_file_info {
    char name[32];
    __s32 status;
    __u32 flags;
    __u32 num_fences;
    __u32 pad;
    __u64 sync_fence_info;
};

成員

name

fence 的名稱

status

fence 的狀態。 1: signaled 0:active <0:error

flags

sync_file_info 標誌

num_fences

sync_file 中的 fence 數量

pad

用於 64 位對齊的填充,應始終為零

sync_fence_info

指向 struct sync_fence_info 陣列的指標,其中包含 sync_file 中的所有 fence

描述

採用 struct sync_file_info。 如果 num_fences 為 0,則該欄位將更新為實際的 fence 數量。 如果 num_fences > 0,則系統將使用 sync_fence_info 上提供的指標來返回最多 num_fences 個 struct sync_fence_info,其中包含詳細的 fence 資訊。

struct sync_set_deadline

SYNC_IOC_SET_DEADLINE - 在 fence 上設定截止時間提示

定義:

struct sync_set_deadline {
    __u64 deadline_ns;
    __u64 pad;
};

成員

deadline_ns

截止時間的絕對時間

pad

必須為零

描述

允許使用者空間在 fence 上設定截止時間,請參見 dma_fence_set_deadline

截止時間的時間基準為 CLOCK_MONOTONIC(與 vblank 相同)。 例如

clock_gettime(CLOCK_MONOTONIC, t); deadline_ns = (t.tv_sec * 1000000000L) + t.tv_nsec + ns_until_deadline

無限 DMA Fence

在不同的時間,已經提出了 struct dma_fence,它具有無限的時間,直到 dma_fence_wait() 完成。 示例包括

  • Future fence,在 HWC1 中用於發出訊號,表明緩衝區不再被顯示器使用,並且使用使緩衝區可見的螢幕更新建立。 此 fence 完成的時間完全由使用者空間控制。

  • 代理 fence,用於處理尚未設定 fence 的 &drm_syncobj。 用於非同步延遲命令提交。

  • 使用者空間 fence 或 gpu futex,命令緩衝區內的細粒度鎖定,使用者空間使用它來進行跨引擎或與 CPU 的同步,然後將其作為 DMA fence 匯入,以便整合到現有的 winsys 協議中。

  • 長時間執行的計算命令緩衝區,同時仍然使用傳統的批處理結束 DMA fence 進行記憶體管理,而不是在重新安排計算作業時重新附加的上下文搶佔 DMA fence。

所有這些方案的共同點是使用者空間控制這些 fence 的依賴關係,並控制它們何時觸發。 將無限 fence 與正常的核心 DMA fence 混合使用不起作用,即使包含回退超時以防止惡意使用者空間也是如此

  • 只有核心知道所有 DMA fence 依賴關係,使用者空間不知道由於記憶體管理或排程程式決策而注入的依賴關係。

  • 只有使用者空間知道無限 fence 中的所有依賴關係以及它們何時準確完成,核心沒有可見性。

此外,核心必須能夠為記憶體管理需求阻止使用者空間命令提交,這意味著我們必須支援依賴於 DMA fence 的無限 fence。 如果核心還支援核心中的無限 fence,就像上述任何提案一樣,則存在死鎖的可能性。

Indefinite Fencing Dependency Cycle

無限 Fencing 依賴迴圈

這意味著核心可能會意外地透過使用者空間不知道的記憶體管理依賴關係建立死鎖,從而隨機掛起工作負載,直到超時生效。從使用者空間的角度來看,工作負載不包含死鎖。在這種混合 fencing 架構中,沒有一個實體瞭解所有依賴關係。因此,無法從核心內部阻止此類死鎖。

避免依賴迴圈的唯一方法是不允許核心中的無限 fence。這意味著

  • 沒有未來的 fence、代理 fence 或匯入為 DMA fence 的使用者空間 fence,無論是否設定超時。

  • 沒有 DMA fence 用於標記命令提交的批處理緩衝區結束,而使用者空間允許使用使用者空間 fencing 或長時間執行的計算工作負載。 這也意味著在這些情況下,不允許共享緩衝區的隱式 fencing。

可恢復的硬體頁面錯誤的影響

現代硬體支援可恢復的頁面錯誤,這對 DMA fence 產生了許多影響。

首先,待處理的頁面錯誤顯然會阻止加速器上正在執行的工作,並且通常需要記憶體分配來解決該錯誤。但是,不允許記憶體分配阻止 DMA fence 的完成,這意味著任何使用可恢復頁面錯誤的工作負載都不能使用 DMA fence 進行同步。必須改用由使用者空間控制的同步 fence。

在 GPU 上,這帶來了一個問題,因為 Linux 上當前的桌面合成器協議依賴於 DMA fence,這意味著如果沒有構建在使用者空間 fence 之上的全新使用者空間堆疊,它們就無法從可恢復的頁面錯誤中受益。具體來說,這意味著不可能進行隱式同步。唯一的例外是頁面錯誤僅用作遷移提示,而從不用作按需填充記憶體請求。目前,這意味著 GPU 上的可恢復頁面錯誤僅限於純計算工作負載。

此外,GPU 通常在 3D 渲染和計算端之間共享資源,例如計算單元或命令提交引擎。如果帶有 DMA fence 的 3D 作業和使用可恢復頁面錯誤的計算工作負載都處於掛起狀態,則它們可能會死鎖

  • 3D 工作負載可能需要等待計算作業完成並首先釋放硬體資源。

  • 計算工作負載可能被卡在頁面錯誤中,因為記憶體分配正在等待 3D 工作負載的 DMA fence 完成。

有幾種方法可以避免此問題,其中驅動程式需要確保

  • 計算工作負載始終可以被搶佔,即使頁面錯誤處於掛起狀態且尚未修復。並非所有硬體都支援此功能。

  • DMA fence 工作負載和需要頁面錯誤處理的工作負載具有獨立的硬體資源,以保證向前進展。這可以透過例如透過專用引擎和 DMA fence 工作負載的最小計算單元預留來實現。

  • 可以透過僅在 DMA fence 工作負載處於活動狀態時才為其預留硬體資源來進一步改進預留方法。這必須涵蓋從 DMA fence 對其他執行緒可見到透過 dma_fence_signal() 完成 fence 的時間。

  • 作為最後的手段,如果硬體不提供有用的預留機制,則在需要 DMA fence 的作業或需要頁面錯誤處理的作業之間切換時,必須從 GPU 中清除所有工作負載:這意味著在可以將帶有頁面錯誤處理的計算作業插入排程程式佇列之前,必須完成所有 DMA fence。反之亦然,在 DMA fence 可以在系統的任何位置可見之前,必須搶佔所有計算工作負載,以保證清除所有掛起的 GPU 頁面錯誤。

  • 一個相當理論的選擇是在分配記憶體以修復硬體頁面錯誤時,解開這些依賴關係,無論是透過單獨的記憶體塊還是執行時跟蹤所有 DMA fence 的完整依賴關係圖。這對核心產生了非常廣泛的影響,因為在 CPU 端解決頁面的問題本身可能涉及頁面錯誤。將硬體頁面錯誤處理的影響限制在特定驅動程式中,更可行且更可靠。

請注意,在獨立硬體(如複製引擎或其他 GPU)上執行的工作負載沒有任何影響。這允許我們即使在解決硬體頁面錯誤時,也可以在核心中內部使用 DMA fence,例如,透過使用複製引擎來清除或複製解決頁面錯誤所需的記憶體。

在某些方面,這個頁面錯誤問題是 無限 DMA Fence 討論的一個特殊情況:允許來自計算工作負載的無限 fence 依賴於 DMA fence,但反之則不然。甚至頁面錯誤問題也不是什麼新鮮事,因為使用者空間中的一些其他 CPU 執行緒可能會遇到頁面錯誤,這會阻止使用者空間 fence - 在 GPU 上支援頁面錯誤並沒有帶來任何根本性的新東西。