Framebuffer、DRM、EXA和Mesa簡介
1. Framebuffer
Framebuffer驅動提供基本的顯示,framebuffer驅動操作的硬體就是一個顯示控制器和幀快取(一片位於系統主存或者顯示卡視訊記憶體)。Framebuffer驅動向應用程式提供/dev/fbx的裝置介面,應用程式通過讀寫這個裝置節點實現對顯示控制器和幀快取。
下面這個程式顯示了應用程式操作操作framebuffer節點的過程。執行這個程式,將在螢幕上方顯示一個正方形(這裡省略了錯誤檢查程式碼)。
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <fcntl.h>
4 #include <sys/mman.h>
5 #include <sys/ioctl.h>
6 #include <Linux/fb.h>
7
8 int main ()
9 {
10 int fd;
11 struct fb_var_screeninfo vinfo;
12 struct fb_fix_screeninfo finfo;
13 size_t screensize = 0;
14 int location;
15 char *fbp = NULL, *ptr;
16 int x, y, x0, y0;
17 int i,j;
18 int ret;
19
20 fd = open("/dev/fb0", O_RDWR);
21 if (fd < 0){
22 fprintf(stderr, "error open fb0\n");
23 return -1;
24 }
25 ret= ioctl(fd, FBIOGET_FSCREENINFO, &finfo ) ;
26 if (ret < 0) {
27 fprintf(stderr, "get fixed screen info error\n");
28 return -1;
29 }
30 ret = ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);
31 if (ret < 0) {
32 fprintf(stderr, "get variable screen info error\n");
33 return -1;
34 }
35 ret = ioctl(fd, FBIOPAN_DISPLAY,&vinfo);
36 if (ret < 0) {
37 fprintf(stderr, "pan display failed\n");
38 return -1;
39 }
40 screensize=vinfo.xres * vinfo.yres * vinfo.bits_per_pixel /8 ;
41 fbp = (char *)mmap(NULL, screensize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
42 if (fbp == MAP_FAILED) {
43 fprintf(stderr, "mapped error\n");
44 return -1;
45 }
46 x0 = 200;
47 y0 = 200;
48 ptr = fbp + y0 * finfo.line_length + x0 * vinfo.bits_per_pixel / 8;
49 for( i = 0; i < 100; i++){
50 char* tmp_ptr = ptr;
51 for(j = 0; j < 100; j++){
52 *tmp_ptr++ = 0;
53 *tmp_ptr++ = 255;
54 *tmp_ptr++ = 0;
55 *tmp_ptr++ = 0;
56 }
57 ptr += finfo.line_length;
58 }
59 munmap(fbp,screensize);
60 close(fd);
61 return 0;
62 }
應用程式對framebuffer的操作主要是通過ioctl和mmap完成的。mmap將視訊記憶體對映到使用者空間。25行和30行分別獲取當前framebuffer驅動的“固定引數”和“可變引數”,這兩個引數包含了當前顯示控制其的一些資訊,可變引數主要是當前解析度資訊,固定引數主要是當前視訊記憶體的地址。35行FBIOPAN_DISPLAY通常用於雙快取,但是這裡使用還有其它意義,在後面討論drm的framebuffer的時候還會具體討論。41行將視訊記憶體映射出來,51-59行操作這片視訊記憶體,往上繪製一個左上方座標為(200,200),邊長為100的正方形。
應用程式能夠使用這些ioctl是因為核心提供了相應的介面,Linux核心裝置驅動相關的書籍都會討論framebuffer驅動,這裡不討論如何寫一個framebuffer驅動。
2. DRM驅動
前一篇博文的圖4核心裡面是drm驅動,注意到和圖3的區別,單獨的FB driver已經沒有了,而是被集合到了drm驅動裡面。在一些只要求進行進本顯示的嵌入式系統上,依然會使用單獨的framebuffer驅動,而對於核心中有3D加速的AMD、intel等驅動,核心裡面和顯示有關的功能已經集合到了drm驅動裡面。在AMD/intel顯示卡+Xorg+3D這樣配置的開源Linux系統上,Xorg並不使用,但是系統中仍然有/dev/fb0這樣的裝置節點,如果我們在桌面環境下“cat xxx >/dev/fb0”,系統不會有變化。然而如果將xorg.conf的Driver修改成“fbdev”,重啟Xorg,在執行操作,能夠看到有變化(關於Xorg.conf可以參考"man xorg.conf",或者檢視這個頁面)。或者切換到控制檯終端“Ctrl+Alt+Fn”,然後運行同樣的命令,就能夠看到螢幕上的內容發生了變化,在這篇博文的第一節的程式有一個FBIOPAN_DISPLAY呼叫,這個呼叫會使得顯示卡的Crtc指向的視訊記憶體地址發生變化而將當前的顯示區域切換到fb0裝置節點對應的區域,因此上面的程式執行即使在X桌面環境下執行,也能夠看到螢幕發生了變化。這提示我們在核外X驅動正常執行的情況下,核外X驅動並不使用framebuffer驅動(實際上drm驅動註冊的frambuffer裝置只是給核心使用),在X啟動執行後,不使用framebuffer 管理的那片記憶體的內容作為顯示輸出,而是使用了另外一片記憶體。
當前的Linux系統上核心的顯示卡驅動稱為drm驅動,在通常的linux核心發行版上,我們使用lsmod命令檢視核心模組,能夠看到類似下面的資訊:
radeon 933054 3
ttm 45600 1 radeon
drm_kms_helper 22468 1 radeon
drm 162230 5 radeon,ttm,drm_kms_helper
i2c_algo_bit 5055 2 i2c_gpio,radeon
機器使用的是AMD的radeon顯示卡,上面顯示了當前系統核心和顯示卡驅動相關的模組,drm模組是核心drm驅動的基礎架構,所有drm顯示卡驅動都會載入這個核心模組,ttm是ttm核心管理機制,drm_kms_helper是核心模式的基礎框架程式碼,i2c_algo_bit是顯示卡上操作i2c裝置使用的模組,顯示卡上的i2c裝置主要包括了connector,encoder以及pll時鐘晶片。Drm核心驅動的程式碼在核心原始碼目錄drivers/gpu/drm下面。載入了drm驅動後,在/dev目錄下面會生成如下裝置節點:
/dev/char/226:0 -> ../dri/card0
/dev/char/226:64 -> ../dri/controlD64
/dev/dri/card0
/dev/dri/controlD64
其中/dev/dri/card0是操作gpu的介面,傳送命令等操作都是通過對這個裝置節點進行的。/dev/dri/controlD64是kms相關的裝置節點。
通常核外的驅動程式使用ioctl呼叫同核心進行互動,linux系統上核外對drm的ioctl進行了一層封裝,即libdrm,應用程式通過libdrm呼叫操作硬體,關於drm的呼叫,這裡有一些比較好的示例程式碼 https://github.com/dvdhrm/docs,這份程式碼主要呼叫libdrm進行模式設定,有詳細的註釋。
Linux下的圖形驅動的主要部分是核外部分,核外部分包括了xorg exa驅動以及mesa 3d 驅動,exa是傳統的2D加速框架,mesa 3d驅動則是針對3D驅動的硬體加速。由於歷史原因,在Fedora 16以及更早和稍後的系統上,即使現在硬體上不包含單獨的2D部件2D功能是由3D部件實現的,但是2D驅動和3D仍然是分離的。
3. EXA驅動
早期的2D加速驅動使用的是XAA、KAA架構,但是隨著composite擴充套件的加入,新的exa框架產生了,EXA刪除掉了原來2D驅動中的三角形繪製、線繪製等一些現在沒什麼用處的功能,取而代之的是三個加速功能:矩形填充(Solid)、拷屏操作(Copy/Blt)和混合操作(Composite)。
“XFree86 server 4.x Design (DRAFT)”文詳細介紹xorg驅動需要提供的介面,EXA驅動通過擴充套件的形式新增並編譯到xorg驅動中。在後續的blog文章裡面將介紹exa驅動介面,並使用radeon的exa驅動來描述顯示卡驅動程式設計過程。
4. 3D驅動
在linux環境中,我們可以通過glxinfo命令插卡3D硬體圖形加速是否可用。比如在radeon顯示卡上glxinfo的輸出包含以下內容:
OpenGL renderer string: Gallium 0.4 on AMD CEDAR
這裡顯示使用AMD CEDAR核心的顯示卡進行opengl 3d加速,如果硬體加速不可用,則應當是“vmware on llvmpipe”這類字眼。
開源的OpenGL實現是mesa,mesa向上提供OpenGL介面,下層通過硬體的mesa驅動和硬體互動。GLX是X協議的擴充套件,用於OpenGL和X的互動(GLX規範)。在mesa原始碼包中包含了大量的opengl示例程式,包括OpenGL紅寶書中的示例程式碼、呼叫GLUT或者GLX介面的程式碼、呼叫EGL的程式碼等。
圖1
圖1顯示了一個3D應用程式執行的過程,OpenGL繪圖程式命令被使用者空間的mesa驅動翻譯成對應GPU的繪圖命令放入命令緩衝區中,其他的如頂點資訊/紋理資訊/索引資訊放入到相應緩衝中,mesa驅動為每個應用程式儲存了當前的繪圖狀態,當發生3D程式切換,當前狀態被儲存下來,當下次排程該程式執行的時候,先恢復該程式的繪圖狀態到硬體上,然後繼續執行命令快取中的命令。當用戶空間呼叫傳送命令到核心的時候,核心驅動對硬體進行程式設計從該程式的命令緩衝區中取命令開始執行。這個過程是在dri框架下實現的,這裡的所有繪製過程並不請求X,OpenGL直接將命令傳送給硬體。GLX在初始化視窗,申請buffer和切換buffer的時候才會和X互動。
其他參考資料: