Linux Framebuffer驅動剖析之二—驅動框架、介面實現和使用
本文繼上一篇文章《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驅動部分。
這真的是嵌入式企鵝圈2015年最後一篇原創了:-), 在此,祝福大家在新的一年工作學習順利、身體健康、萬事如意!
更多嵌入式Linux和物聯網IOT原創技術分享敬請關注微信公眾號:嵌入式企鵝圈