在上一小節《Linux GUI加速(1)_GUI系統概述》中,我們從應用層到kernel層大致分析了linux中的圖形界面的構成,並在最後給出了kernel中DRM+KMS的軟體顯示框架以及accelerate logic+framebuffer+displayport的硬體結構。在這一子篇會將這兩塊內容詳細展開。

本篇主要以Xilinx的xc7z010 的SOPC(zybo的開發板)為硬體平台,在以下幾方面介紹:

  1. 以zynq 7000的邏輯資源(PL)搭建CRTC/Encoder/Connector硬體模塊,以HDMI輸出介面為例,介紹各個模塊的介面特性(Framebuffer對應著物理的DDR部分);
  2. 會先給出DRM+KMS驅動框架下的主要模塊,並針對上述硬體子模塊分析對應的內核驅動部分;

DRI在片上系統的硬體構成

在各類SOC上,CRTC+Endode+Connector一般是集成在一個外設模塊掛在系統匯流排上,以ARM為例,CRTC/Endoder等需要配置的外設模塊,配置介面掛在APB匯流排,數據介面直接在AHB匯流排上,實現和Framebuffer的高速通信。

我們按照connector-->encoder-->crtc-->framebuffer的順序倒過來介紹吧。

Connector

Connector其實就是和顯示器連接的物理介面,常見的有VGA/HDMI/DVI/DP等。以HDMI為例,HDMI的介面信號主要由以下幾組信號組成:

  1. 1組TMDS clock:差分時鐘用於同步信號驅動;
  2. 3組TMDS data:查分數據傳輸視頻信號;
  3. 1組I2C:用於EDID的獲取;
  4. 1組音頻匯流排;

(註:EDID全稱是Extended Display Identification Data(擴展顯示標識數據),目的是讓視頻信號輸出設備輸出前獲取到存儲在顯示器內部的相關參數,如支持的解析度、幀率、圖像格式:RGB等,因此,整個輸出的控制參數是由以下幾個部分綜合決定的:

  1. 通過connector讀出的顯示器支持的參數;
  2. 內核靜態配置或devicetree傳入的參數;
  3. 用戶空間輸入的參數)

HDMI類型的connector的任務就是輸出顯示器解碼晶元所需的信號時序(主要是TMDS clock以及TMDS data)。

Encoder

Encoder比較好理解,在此處其實就是將一定格式的圖像信號(如RGB、YUV等)編碼成connector需要輸出的信號。以HDMI為例,幀/行同步/顯示內容都是通過TMDS data的串列匯流排輸出的,那麼並行的時序按照HDMI的標準編碼為串列順序則是Encoder的任務;

在本片中的XC7Z010 SOPC中Encoder+Connector如下:

CRTC

CRTC的任務是從Framebuffer中讀出待顯示的圖像,並按照相應的格式輸出給Encoder(本處的CRTC功能受限,相關格式配置只能通過配置硬體IP參數來改變,而不能通過內核)。在本例中,CRTC的硬體構成如下:

  1. AXI Video Direct Memory Access IP,通過AXI4匯流排獲取DDR中Framebuffer數據,轉為video-stream流格式的圖像信息;
  2. Video Timing Controller IP,根據內核相應配置輸出與視頻流匹配的幀、行同步信號;
  3. AXI4-Stream to Video Out IP,將串列視頻流轉化為指定格式(IP配置)、指定解析度(內核配置)的時序;

如下圖:

Planes

Plane其實就是圖層,實際輸出的圖像往往由多個圖層疊加而成(想像一下photoshop的過程),比如主圖層,顯示游標的圖層,其中有些圖層由硬體加速模塊生成,本例中不涉及,因此所有plane的相關操作都由軟體實現,不涉及到任何硬體結構。

Framebuffer

Framebuffer對應著存儲空間中的圖像數據,此處對應硬體為DDR。

麻雀雖小,五臟俱全,次常式中的顯示框架非常簡單,但也包含了Framebuffer、CRTC、Planes、Encoder、Connector5個組件,片內硬體結構如下:

(PS:Dynamic Clock Generator生成顯示子系統中各組件所需的驅動時鐘,由Linux中的common clock framework統一管理)

DRI在Linux Kernel內的軟體構成

按照DRI中幾個組件分別介紹。

Framebuffer

我們知道Framebuffer是存儲待顯示圖像信息的空間,因此,Framebuffer相關驅動中也就是對內存的操作,也就涉及到下面兩個部分:

  • 對內存的管理(如GEM,for Graphics Execution Manager)
  • 內存中數據的更顯方式(如DMA等)

對於第一點,GEM主要完成的事情是:

  • 對圖像內存(顯存)的空間開闢、釋放;
  • 不同硬體對同一顯存資源訪問下的管理;

在Linux Kernel下的默認實現方式是CMA(Contiguous Memory Allocator)實現的,內核中對應代碼是:

drivers/gpu/drm/drm_fb_cma_helper.c

這裡稍微提一下CMA,CMA是個好東西,不僅在顯存管理中有應用,在所有軟硬體協 同處理中同樣起這重要的作用。在一般的硬體(片內硬體加速模塊)加速方案中,一般 實現方式如下:

    • linux kernel通過device-tree傳入CMA配置所需的配置參數供CMA開闢相應的物理內存 空間(不被Cache到),並且相應的信息(物理空間首地址,size等)通過/dev/device 映射到用戶空間;
    • DMA可以將加速模塊預處理後的數據,根據已知參數(開闢的物理地址),傳遞到共享空間中供軟體訪問;

struct drm_framebuffer{

[...]

const struct drm_format_info *format;

[...]

}

  • format用於描述內存空間的組成,使用FOURCC(four-character code)制式,結構體中的pitch、offset 4個元素分別用於計算FOURCC4個圖層中的內存長寬參數;(width和height用於描述顯示的長寬參數);
  • DRI中支持下面這三種格式圖像:
    • RGB:R/G/B分別存於不同的圖層中;
    • YUV:不同壓縮格式下的YUV存儲方式不一;
    • C8:通過存儲一塊映射到RGB的映射表來實現圖像信息存儲;

framebuffer中在不同格式下所需要處理的圖層的數量不一,具體的顯存處理、格式解析主要在下列源碼錶中:

(顯存管理)

drivers/gpu/drm/drm_framebuffer.c

drivers/gpu/drm/drm_gem.c

drivers/gpu/drm/drm_gem_cma_helper.c

drivers/gpu/drm/drm_fb_cma_helper.c

drivers/gpu/drm/drm_fb_framebuffer_helper.c

drivers/gpu/drm/drm_fb_fourcc.c

drivers/gpu/drm/drm_fb_cma_helper.c

drivers/gpu/drm/drm_fb_cma_helper.c

drivers/gpu/drm/drm_fb_cma_helper.c

(顯存更新驅動介面)

drivers/gpu/drm/ati_pcigart.c

drivers/gpu/drm/ati_agpsupport.c

CRTC

CRTC雖然字面上意思為陰極射線顯像管控制器,但CRT在普通顯示設備中早已被淘汰,DRI中CRTC主要承擔的作用:

  • 配置適合顯示器的解析度(kernel)並輸出相應時序(hardware logic);
  • 掃描framebuffer送顯到一個或多個顯示設備中;
  • 更新framebuffer;

上述功能的主要通過struct drm_crtc_funcs和struct drm_crtc_helper_funcs這兩個描述符實現:

drm_crtc_funcs中的兩個重要句柄set_config和page_flip,其中,

set_config主要任務是:

    • 更新待送顯的framebuffer中數據;
    • 根據(之前說的EDID讀出支持的配置,device-tree對內核的配置以及用戶空間的配置)參數配置軟體參數並控制相應的寄存器(在本例中的VDMA及VTC的行列像素值等寄存器);
    • 將Encoder和connector的信息送給CRTC模塊。

page_flip解決的問題很簡單:

    • 必須得保證CRTC在讀取framebuffer的時候,framebuffer里的幀不會被修改而產生竄幀的情況,採用的解決方式也是軟硬體異構處理時常見的乒乓緩存的方式。

當page_flip完成後,會通過event通知用戶層準備好下一幀的數據;

Planes

不涉及到GPU的話,planes沒有那麼複雜,主要是負責:

    • 主圖層;
    • 移動游標圖層;
    • 覆蓋圖層;

的創建、更新、銷毀,其中圖層的更新(多個圖層的疊加),通過struct中的drm_plane_funcs來實現。

比較形象的例子如下:

Connector & Encoder

由於Connector和Encoder是SOC與外設直接打交道的地方,因此對應著不同SOC也是驅動適配修改最頻繁的地方。DRI中這兩塊通過適配struct drm_connector_helper_funcs和struct drm_encoder_helper_funcs來實現。

Linux Kernel中的DRM+KMS中涉及到的點太多,由於能力和時間問題,沒能遍歷一遍,只能根據自己認為的重點討論一邊,但有些點我認為比較重要的,但沒能深入了解,比如:

  • 硬體graphic加速和DRM的交互邏輯
  • CRTC的Atomic刷新機制的具體實現方式

等,如果大家能夠交流一下自己的理解,那再好不過了。


推薦閱讀:
相关文章