天天看點

翻譯:Mainline Explicit Fencing

譯者注

dma-fence 作為 kernel 中 buffer 共享同步機制,已經成為 DRM 驅動架構必不可少的基礎元件。了解 dma-fence 的背景知識,有助于後期學習 DRM 中 fence 相關的驅動開發。

本文翻譯自 Gustavo Padovan 于 2016 年 9 月發表在 Collabora 官網的三篇文章,Gustavo 本人也是核心中 dma-fence 的送出作者。通過本文,我們可以了解到 dma-fence 最初是如何演變而來的,以及它是如何在 graphics pipeline 中起作用的。

Mainline Explicit Fencing - Part 1

當我們談到 Linux kernel 中的 buffer 共享同步機制時,通常有兩種:

Implicit(隐式) Fence

Explicit(顯式) Fence

。它們的主要差別在于:kernel 是否會将同步資訊共享給使用者空間。是以它要麼是隐式的(不提供任何 fence 資訊給使用者空間);要麼是顯式的(提供所有 fence 資訊給使用者空間)。

fence 同步機制可以確定 buffer 在共享過程中,驅動程式或使用者空間不會對一個正在被寫入的 buffer 進行讀操作,或對一個仍在被其他子產品使用的 buffer 進行寫操作。fence 確定了這些操作能夠有序進行,即隻會在 buffer 不被使用時才進行讀寫操作。例如,當一個 GPU 的 job 被塞進隊列時,該 job 中的 buffer 會被關聯上一個 fence,其他驅動程式可以借助該 fence 來進行同步。在收到該 fence 的信号(signal)之前,這些驅動程式不會對該 buffer 進行任何操作,fence signal 表明該 buffer 現在可以被正常使用了。同樣地,為了讓 GPU 驅動能等待 buffer 從顯示屏中切換出來,我們可以給顯示驅動采用相同的 fence 機制,以便 GPU 能再次使用這些 buffer 進行渲染。

fence 是該機制的核心元素,每當向 kernel 發送 buffer 相關的請求時,該 buffer 都會附帶一個 fence。使用者空間或其他驅動程式可以使用 fence 來等待硬體工作完成。是以,一旦工作完成,該 fence 就會被 signal,等待該 fence 的子產品也就可以繼續對該 buffer 進行它們想要的操作了。

雖然 Implicit Fence 在 buffer 同步方面起了很大作用,但在某些情況下,整個桌面的合成可能會被卡住。想象一下下面的合成過程:有 A、B、C 三個 buffer 需要處理,A 和 B 被 GPU 拿去同時做渲染,而 C 将作為 A 與 B 合成的結果拿去顯示。但隻有在 A、B 這兩個 buffer 都渲染完成時才會通知 compositor 做合成,是以如果 B 渲染的時間太長,将導緻整個桌面的合成因為需要等待 B 而被 block 住,于是 C 就無法及時的顯示出來。

翻譯:Mainline Explicit Fencing

圖 1:Compositor 采用 Implicit Fence 機制同時處理兩塊 buffer,如果 buffer B 渲染耗時太長,則會導緻桌面被當機

而如果采用 Explicit Fence 機制,compositor 會為每個 buffer 關聯上一個 fence,這樣就可以在每個 buffer 渲染完成時收到通知。是以,如果 A 渲染的很快而 B 需要很長時間才能渲染完成,那麼 compositor 可以做出決定不等待 B,而是繼續使用舊的 buffer B 與 A 一起做合成,接着顯示 C。是以 compositor 可以根據 fence 資訊來做出更加明智的決定,以此來避免螢幕當機的發生。

到目前為止,Linux 核心隻有 Implicit Fence 的通用 API,雖然有些驅動程式已經實作了 Explicit Fence,但它們的 API 都是與裝置強相關的。Android 目前已經有了一套自己的同步實作機制,即

Android Sync Framework

—— 我會在下一篇文章中對它進行介紹。

Explicit Fence 是一種 消費者-生産者 模型,在一條 GPU 渲染 + 掃描上屏顯示的 pipeline 中,它将在 kernel 驅動程式之間進行同步,是以當向 GPU(生産者)送出一個新的渲染 job 時,使用者空間将獲得一個與本次送出 buffer 相關聯的 fence。也就是說使用者空間不需要一直等待 job 完成而被阻塞在那裡,當 job 完成時會自動向該 fence 發送一個信号。因為使用者空間不再需要一直等在那裡,且有了 fence 的加持,它就可以立即進行系統調用,告訴 Display 硬體(消費者)去掃描還未渲染完的 buffer。采用 Explicit Fence,核心空間會被告需要等到 fence signal 之後才能開始 buffer 掃描上屏的處理。

當使用者空間向 kernel 空間送出一個 buffer 用于 Display 顯示時,使用者空間同時會收到一個新建立的 fence。當該 buffer 不再需要被顯示時,它所對應的 fence 就會被 signal,于是這塊 buffer 就可以被另一個渲染 job 拿去重新使用了。一旦戶空間拿到該 fence,它可以無需等待就向 GPU 送出一個新的渲染 job。GPU 驅動則在核心空間等待該 buffer 顯示完成,一旦 fence 發出信号,buffer 的渲染工作就可以立即啟動。

翻譯:Mainline Explicit Fencing

圖 2:fence 總是穿梭于 userspace 和 pipeline 中下一個子產品之間,黃色箭頭表示使用者空間裡的 fence

最後不得不提的是,Explicit Fence 大大改善了圖形 pipeline 的 debug 能力。在使用者空間通路 fence 可以更加清楚的知道底層 pipeline 目前正在發生的事情。以前,由于 Implicit Fence 沒有可通路的資訊,是以很難弄清楚 pipeline 上到底發生了什麼,而且每個 vendor 廠商都試圖實作他們自己的 Implicit Fence 機制,調試起來難道很大。現在,有了标準的 Expicit Fence,我們就可以更容易的建立起 debug 和 trace 架構,以便能夠跟蹤任何系統上的顯示問題。

下一篇文章将描述 Android Sync Framework,之後還将描述在 mainline 上添加 Explicit Fence 支援所作的工作。

Mainline Explicit Fencing - Part 2

在第一篇文章中,我們讨論了 Linux 核心 Explicit Fence 的基本概念。作為本系列的第二篇文章,我們将介紹 Android Sync Framework,這是 Linux 核心中首個(out-of-tree)Explicit Fence 的具體實作。

Sync Framework 是 Android 在 AOSP 中的 Explicit Fence 實作方案,它使用檔案描述符 fd 在 userspace 和 kernel 之間、以及程序之間傳遞 fence 資訊。

在 Sync Framework 中,一切都從建立一個

Sync Timeline

開始,Sync Timeline 是為每個驅動程式上下文建立的一個結構體,用來表示一個單調遞增的計數器。Sync Timeline 将保證同一時間軸上不同 fence 之間的執行順序,驅動程式上下文可以是不同的 GPU ring,或是硬體上不同的 Display。

翻譯:Mainline Explicit Fencing

圖 1:Sync Timeline

然後我們需要有

Sync Point

(sync_pt),Android 給 fence 起的名字,它們代表 Sync Timeline 上某個特定的值。Sync Point 剛被建立的時候會被初始化為

Active

狀态,當它發出信号時(比如與它關聯的 job 執行結束了),它将轉換成

Signaled

狀态,并通知 Sync Timeline 的計數器更新為最後一次被 signal 的 Sync Point 的值。

翻譯:Mainline Explicit Fencing

圖 2:Sync Point

要想将 Sync Point 導出給使用者空間,或從使用者空間導入到核心,需要借助

Sync Fence

結構體。Sync Fence 本質上是一個 linux file,我們使用 Sync Fence 來存儲 Sync Point 的資訊。要想将它導出給使用者空間,需要給 Sync Fence file 關聯上一個 unused 的檔案描述符(fd),然後驅動程式就可以通過該 fd 來傳遞 Sync Point 的資訊了。

翻譯:Mainline Explicit Fencing

圖 3:Sync Fence

Sync Fence 通常是在 Sync Point 被建立之後才建立的,然後經過使用者空間最終在 pipeline 子產品之間傳遞,直到驅動程式等待 Sync Fence 發出信号為止。隻有當 Sync Fence 内部所有的 Sync Point 都被 signal 時,Sync Fence 自己才會被 signal。

Android Sync Framework 最重要的一個功能就是能夠将兩個 Sync Fence merge(合并)到一個新的 Sync Fence 中,它包含來自兩個 Sync Fence 的所有 Sync Point。隻要你的資源允許,它可以包含無數多個 Sync Point。merge 後的 Sync Fence 隻會在其内部所有的 Sync Point 都發出信号後才會被 signal。

翻譯:Mainline Explicit Fencing

圖 4:Merge 後的 Sync Fence

說起使用者空間 API,Sync Framework 實作了三個 ioctl 調用。第一個是等待 sync_fence 被 signal,第二個是将兩個 sync_fence merge 為第三個新的 sync_fence,最後一個 API 是用來擷取 sync_fence 及其所有 sync_point 資訊的。

當我們通過系統調用,請求 kernel 對一塊 buffer 進行渲染或顯示時,Sync Fence 的 fd 将通過該系統調用傳遞給 kernel,或從 kernel 傳回給使用者空間。

本篇的主要目的為了簡要概括一下 Sync Framework,因為我們将在下一篇文章中看到這些概念。同時我将會在下一篇講述為了讓 mainline kernel 支援 Explicit Fence,我們都做了哪些工作。如果你想了解更多關于 Sync Framework 的詳細資訊,請點選 這裡 和 這裡 檢視。

Mainline Explicit Fencing - Part 3

在前兩篇文章中,我們讨論了 Explicit Fence 對于 graphics pipeline 大體上都起了什麼作用,以及為了将 Android Sync Framework upstream 我們都做了哪些事情。現在,作為在本系列的最後一篇文章,我将介紹在 DRM 和圖形棧的其他方面應該如何來實作 Explicit Fence。

DRM 實作 Explicit Fence 需要建立在兩個 kernel 基礎架構之上:

struct dma_fence

—— 該結構體表示 fence,和

struct sync_file

—— 用來提供與使用者空間共享的檔案描述符(如前幾篇文章中所讨論的)。在使用 fence 時,Display 底層驅動需要等待該 fence 發出信号後,才能在螢幕上顯示該 buffer。在 Explicit Fence 的實作中,fence 從使用者空間發送給核心空間,Display 底層驅動同樣會給使用者空間傳回一個新的 fence,該 fence 被封裝在 sync_file 結構體中。當該 buffer 被顯示到螢幕上時,它所對應的 fence 就會被 signal。同樣的,在渲染側也采用相同的等待流程。

必須使用 Atomic Modesetting,我們不打算支援 legacy API。DRM 要等待的 fence 需要通過每個 DRM Plane 的

IN_FENCE_FD

property 來傳遞,也就是說每個 Plane 将收到一個 sync_file fd,每個 fd 将包含一個或多個 dma_fence。請記住,在 DRM 中一個 Plane 直接對應一個 framebuffer,是以也可以說一個 framebuffer 對應一個 sync_file。

另一方面,對于 kernel 建立的 fence,需要借助于

OUT_FENCE PTR

這個 property 來傳回給使用者空間。這是一個 DRM CRTC 的 property,因為 CRTC 上的所有 buffer 都是在同一時刻被掃描出去的,是以我們隻為每個 CRTC 建立一個 dma_fence。kernel 通過将 fd 的值寫入

OUT_FENCE PTR

property 所指向的使用者空間記憶體裡,這樣就能将這個 fence 傳回給使用者空間了。請注意,與 Android 不同的是,Mainline 中的 fence 在被 signal 時,意味着上一個 buffer(即已經從螢幕上移除的 buffer)可以被再次使用了。而在 Android 上,當信号被觸發時,意味着目前正在顯示的 buffer 被釋放了。不過 Android 的開發人員已經重新修改了 SurfaceFlinger,以支援 Mainline 上 Explicit Fence 的使用規則!

不過,以上僅僅隻是一方面,要想讓 Explicit Fence 機制在整個 graphics pipeline 中運作起來,我們還需要在渲染側對它添加支援。由于每個渲染驅動程式都有自己的 userspace API,是以我們需要為每個渲染驅動添加 Explicit Fence 支援。freedreno 驅動程式已經可以支援 mainline 上的 Explicit Fence,而 i915 和 virtio_gpu 驅動也正在添加對該 fence 的支援。

在使用者空間這一端,Mesa 已經添加了對

EGL_ANDROID_native_fence_sync

擴充接口的支援,這樣就可以在 Android 平台上使用 Explicit Fence 了。libdrm 的頭檔案也已經合入了對 sync_file 的 ioctl 封裝。在 Android 平台上,libsync 現在既可以支援舊的 Android Sync API,又可以支援新的 Mainline Sync File API。最後,在 drm_hwcomposer 上,使用 Atomic Modesetting 和 Explicit Fence 的 patch 也都已經有了,隻不過它們暫時還沒有被 upstream。

IGT(intel-gpu-tools) 中也已經添加對 Sync File 和 fence 的 Atomic API 測試 case。

全文完。

原文連接配接

  1. Mainline Explicit Fencing - Part 1
  2. Mainline Explicit Fencing - Part 2
  3. Mainline Explicit Fencing - Part 3

擴充閱讀:

  1. Mainline Explicit Fencing - X.Org
  2. Bringing Android explicit fencing to the mainline