本文繼上一篇文章《Linux Framebuffer驅動剖析之一—軟件需求》,深入分析LinuxFramebuffer子系統的驅動框架、接口實現和使用。
一、LinuxFramebuffer的軟件需求
上一篇文章詳細闡述了LinuxFramebuffer的軟件需求(請先理解第一篇文章再來閱讀本篇文章),總結如下:
1. 針對SOC的LCD控制寄存器進行編程,以支持不同的LCD屏,以使該SOC的應用場景最大化。這是硬件平台相關的需求。其對應Linux源碼路徑arch\arm\mach-s5pv210\XXX210-lcds.c中的實現內容。
2. 給用戶提供一個進程空間映射到實際的顯示物理內存的接口(mmap),以使應用在一次拷貝的情況下即可以將圖像資源顯示到LCD屏幕上。這需要SOC的MMU(內存管理單元)、Linux操作系統的內存管理、SOC的SDRAM(內存)三者支持。由於內存管理作為操作系統的公共組成部分,給其他子系統提供了獨立的接口。所以我們可以認為framebuffer通過調用內存管理接口來實現映射的接口屬於非平台相關的需求。
3.Framebuffer支持32個顯示緩存。其在內部進行了抽象,即其向上層應用統一抽象為一個字符主設備,而不同的顯示緩存即視為不同的字符從設備。對顯示從設備的管理屬於Framebuffer內部框架的功能。
4. Linux設備驅動支持多種類型的設備驅動,除了Framebuffer,還包括input、USB、watchdog、MTD等等,這種不同類型的設備一般都表述為不同的主設備。管理不同的主設備是由Linux設備驅動框架來完成的。另外,由於Linux把設備也認為是一個文件,因此設備驅動之上還架設了一層虛擬文件系統(VFS),因此,實際上,應用層是跟framebuffer對應的VFS接口進行交互的。如下圖:
對於驅動開發人員來說,其實只需要針對具體的硬件平台SOC和具體的LCD(焊接連接到該SOC的引腳上)來進行第一部分的寄存器編程(紅色部分)。而第二、三、四部分內容(綠色部分)已經被抽象並實現在Linux driver發布源碼中了,LCD驅動開發人員只需要理解framebuffer內部的框架和接口使用即可。其實,對於其他的設備驅動,包括按鍵、觸摸屏、SD卡、usb等等,Linuxdriver都已經實現了大部分非平台相關的需求任務了,開發人員同樣是只需要理解所屬驅動的內部框架和接口使用即可。
接下來,我們就詳細分析LinuxFramebuffer如何實現支持第二和第三點需求的。其對應driver\video\fbmem.c等實現內容。先分析第三點驅動框架,再分析映射接口。
二、LinuxFramebuffer的內部驅動框架
1. 初始化
--driver\video\fbmem.c
Framebuffer作為一個子系統,在fbmem_init中通過register_chrdev接口向系統注冊一個主設備號位29的字符設備驅動。通過class_create創建graphics設備類,配合mdev機制生成供用戶訪問的設備文件(位於/dev目錄)。設備文件和mdev機制詳見《 Linux設備文件的創建和mdev》。
Framebuffer設備驅動的接口集fb_fops的定義為:
在linux設備驅動中,所有的顯示緩存設備均由framebuffer子系統內部管理,即linux設備驅動框架只認識一個主設備號為29的framebuffer設備。應用層所有針對顯示緩存(最多32個)的訪問均會推送給fb_fops進行進一步分發操作。
2. register_framebuffer
單個顯示緩存視為一個framebuffer從設備,其在驅動加載初始化時需要通過register_framebuffer接口向framebuffer子系統注冊自己。這樣,當應用層要訪問該從設備時,才能通過framebuffer子系統進行操作管理分發。我們跟蹤一下:
每個從設備都需要傳遞一個fb_info的數據結構指針,其即代表單個顯示緩存設備。從中,可以看到fb_info最終會存儲到全局數組struct fb_info*registered_fb[FB_MAX]中,FB_MAX是32,從這裡我們也可以看出,framebuffer最多支持32個從設備。另外,每個從設備注冊還會在/sys/class/graphics/設備類中創建一個設備,最終由mdev在/dev/目錄中生成對應的設備文件。假設M個從設備調用register_framebuffer接口,即會在/dev中生成M個設備文件,如/dev/fb0、/dev/fb1、/dev/fb2等等。這M個設備的主設備號都是29,從設備則是0、1、2等等。
3. fb_info結構體
fb_info結構體代表單個顯示緩存從設備,在調用register_framebuffer接口之前,必須要初始化其中的重要數據成員。其定義如下:
其中,fb_var_screeninfo和fb_fix_screeninfo兩個結構體跟LCD硬件屬性相關,fb_var_screeninfo代表可修改的LCD顯示參數,如分辨率和像素比特數;fb_fix_screeninfo代表不可修改的LCD屬性參數,如顯示內存的物理地址和長度等。另外一個非常重要的成員是fb_ops,其是LCD底層硬件操作接口集。
fb_ops硬件操作接口集包含很多接口,如設置可變參數fb_set_par、設置顏色寄存器fb_setcolreg、清屏接口fb_blank、畫位圖接口fb_imageblit、內存映射fb_mmap等等。
fb_info結構體在調用register_framebuffer之前完成初始化。一般來說,LCD設備屬於平台設備,其初始化是在平台設備驅動的probe接口完成。而LCD設備所涉及的硬件初始化(第一部分需求)則在平台設備初始化中完成。有關平台設備驅動的分析,筆者會另寫一篇文章闡述。
4. fb_open接口
當一個framebuffer設備完成初始化時,其對應的fb_info結構體會在全局數組registered_fb中記錄,並且位於跟從設備號相等的位置上;而且,在/dev目錄下也會生成一個/dev/fbx的設備文件。以下分析假設是訪問第一個framebuffer設備:
對於應用層open(“/dev/fb0”,…)訪問該framebuffer設備來說,vfs先通過設備名(/dev/fb0)獲得該設備的主設備(29)和從設備號(0)。而linux設備驅動框架則通過主設備29找到該設備對應的設備驅動接口集fb_fops。Linux驅動框架的分析過程請看《Linux字符設備驅動剖析》。接著linux設備驅動框架會調用fb_fops的fb_open,我們來跟蹤一下:
這個接口很簡單,即是次設備號在全局數組registered_fb中找出對應的fb_info數據結構,將其設置到file指針的私有數據中,便於之後用戶訪問調用其他接口如mmap、ioctl等能夠直接找到對應的fb_info。最後也會調用fb_info->fb_ops->fb_open,不過這個接口一般沒干啥,賦值為NULL即可。
所以,framebuffer子系統內部驅動框架即負責通過從設備號找到對應的從設備(具體LCD操作接口所在的fb_info)。
5. 驅動框架總結
經過上面的分析,這張圖應該很容易理解了。
三、mmap映射
framebuffer驅動最重要的功能就是給用戶提供一個進程空間映射到實際的顯示物理內存的接口(mmap)。當用戶圖像數據buffer和內核虛擬地址空間buffer對應的都是同一塊物理內存時,資源數據拷貝到用戶圖像數據buffer時,即是直接拷貝到顯示物理內存了。
應用層mmap映射接口會經歷以下層次調用:
1. sys_mmap
sys_mmap是虛擬文件系統層的映射實現,其會在用戶進程虛擬空間(0到3G)申請一塊虛擬內存,最終會調用到framebuffer子系統的fb_mmap,並向其傳遞vm_area_struct結構體指針,該結構體已經包括進程用戶空間的內存地址信息。
Vfs虛擬文件系統是linux系統的重要組成部分,這裡不再展開,以後再分析。
2. fb_mmap
來跟蹤一下:
該接口也是找到具體的從設備對應的LCD操作接口集,並調用fb_mmap接口。
我們選一個具體的LCD的接口實現看看:
remap_pfn_range接口即是建立進程地址空間(0到3G)到實際的顯示物理內存的映射。其中,lcd_mem是fb_info結構體初始化使用kzmalloc申請的,其代表內核態(3G到4G)的虛擬內存首地址。而virt_to_phys即是將虛擬地址轉化為物理地址,更新vm_area_struct的數據成員,而其最終會影響進程的頁表設置,進而影響MMU的設置。
當該接口完成之後,最終向應用層返回進程空間的內存首地址(0到3G)。這樣,應用層即可以直接訪問這塊內存,進行讀寫操作。其直接通過MMU來訪問實際的物理地址,而不需要再經過驅動的管理。
四、接口使用
如下,操作是不是很簡單?不過,大家要記得,framebuffer只負責位圖顯示,而很多圖像都是有編碼格式的,如JPG等等,需要先解碼,再送給framebuffer。
寫了這麼多,我發現,再寫一篇如何進行LCD驅動開發的文章,實現framebuffer的第一部分需求,以串起以前對驅動框架的分析,會更加清晰地掌握LCD驅動和framebuffer子系統的來龍去脈。這個可以安排在平台設備驅動的分析之後進行,因為大部分的驅動都是平台設備驅動,應該先講平台設備驅動的框架,再回到LCD驅動開發,包括LCD平台設備相關的部分(GPIO資源、中斷資源配置等)和LCD驅動部分。