/************************************************************************************html
*本文爲我的學習記錄,若有錯誤,歡迎指正。node
* https://www.cnblogs.com/deng-tao/p/6075709.html數組
* https://blog.csdn.net/ultraman_hs/article/details/54985963
數據結構
* http://www.javashuo.com/article/p-drlyjgxx-bm.html
app
* https://blog.csdn.net/qq_28992301/article/details/52727050框架
************************************************************************************/異步
(1)什麼是framebuffer?ide
framebuffer,幀緩衝設備(簡稱fb)是linux內核中虛擬出的一個設備,屬於字符設備;它的主設備號爲FB_MAJOR = 29,次設備號用來區份內核中不一樣的framebuffer。Linux內核中最多支持32個framebuffer,設備文件位於/dev/fb*。 函數
(2)framebuffer的做用
framebuffer的主要功能是嚮應用層提供一個統一標準接口的顯示設備。 它將顯示設備的硬件結構抽象爲一系列的數據結構,應用程序經過framebuffer的讀寫直接對顯存進行操做。用戶能夠將framebuffer當作是顯存的一個映像,將其映射到進程空間後,就能夠直接進行讀寫操做,寫操做會直接反映在屏幕上。
對於現代LCD,有一種「多屏疊加」的機制,即一個LCD設備能夠有多個獨立虛擬屏幕,以達到畫面疊加的效果。因此fb與LCD不是一對一的關係,在常見的狀況下,一個LCD對應了fb0~fb4。
(1)struct fb_info
Linux內核中使用fb_info結構體變量來描述一個framebuffer,在調用register_framebuffer接口註冊framebuffer以前,必需要初始化其中的重要數據成員。
struct fb_info中成員衆多,咱們須要着重關注如下成員:
fb_var_screeninfo:表明可修改的LCD顯示參數,如分辨率和像素比特數等;
fb_fix_screeninfo:表明不可修改的LCD屬性參數,如顯示內存的物理地址和長度等;
fb_ops:LCD底層硬件操做接口集。
struct fb_info { int node; //用來表示該fb設備的次設備號 int flags; //一個標誌位 struct mutex lock; /* Lock for open/release/ioctl funcs */ struct mutex mm_lock; /* Lock for fb_mmap and smem_* fields */ struct fb_var_screeninfo var; /* Current var */ // fb的可變參數 struct fb_fix_screeninfo fix; /* Current fix */ // fb的不可變參數 struct fb_monspecs monspecs; /* Current Monitor specs */ struct work_struct queue; /* Framebuffer event queue */ struct fb_pixmap pixmap; /* Image hardware mapper */ struct fb_pixmap sprite; /* Cursor hardware mapper */ struct fb_cmap cmap; /* Current cmap */ struct list_head modelist; /* mode list */ struct fb_videomode *mode; /* current mode */ #ifdef CONFIG_FB_BACKLIGHT /* assigned backlight device */ /* set before framebuffer registration, remove after unregister */ struct backlight_device *bl_dev; /* Backlight level curve */ struct mutex bl_curve_mutex; u8 bl_curve[FB_BACKLIGHT_LEVELS]; #endif #ifdef CONFIG_FB_DEFERRED_IO struct delayed_work deferred_work; struct fb_deferred_io *fbdefio; #endif struct fb_ops *fbops; // 該設備對應的操做方法 struct device *device; /* This is the parent */ //fb設備的父設備 struct device *dev; /* This is this fb device */ //本設備的device int class_flag; /* private sysfs flags */ #ifdef CONFIG_FB_TILEBLITTING struct fb_tile_ops *tileops; /* Tile Blitting */ #endif char __iomem *screen_base; /* Virtual address */ //LCD的顯存地址(虛擬地址) unsigned long screen_size; /* Amount of ioremapped VRAM or 0 */// LCD顯存的字節大小 void *pseudo_palette; /* Fake palette of 16 colors */ #define FBINFO_STATE_RUNNING 0 #define FBINFO_STATE_SUSPENDED 1 u32 state; /* Hardware state i.e suspend */ void *fbcon_par; /* fbcon use-only private area */ /* From here on everything is device dependent */ void *par; /* we need the PCI or similiar aperture base/size not smem_start/size as smem_start may just be an object allocated inside the aperture so may not actually overlap */ struct apertures_struct { unsigned int count; struct aperture { resource_size_t base; resource_size_t size; } ranges[0]; } *apertures; };
(2)struct fb_var_screeninfo
Linux內核中使用struct fb_var_screeninfo來描述可修改的LCD顯示參數,如分辨率和像素比特數等。
struct fb_var_screeninfo { __u32 xres; // 水平分辨率 __u32 yres; // 垂直分辨率 __u32 xres_virtual; // 虛擬水平分辨率 __u32 yres_virtual; // 虛擬垂直分辨率 __u32 xoffset; // 當前顯存水平偏移量 __u32 yoffset; // 當前顯存垂直偏移量 __u32 bits_per_pixel; // 像素深度 __u32 grayscale; /* != 0 Graylevels instead of colors */ struct fb_bitfield red; /* bitfield in fb mem if true color, */ struct fb_bitfield green; /* else only length is significant */ struct fb_bitfield blue; struct fb_bitfield transp; /* transparency*/ __u32 nonstd; /* != 0 Non standard pixel format */ __u32 activate; /* see FB_ACTIVATE_* */ __u32 height; // LCD的物理高度mm __u32 width; // LCD的物理寬度mm __u32 accel_flags; /* (OBSOLETE) see fb_info.flags */ /* Timing: All values in pixclocks, except pixclock (of course) */ __u32 pixclock; /* pixel clock in ps (pico seconds) */ // 像素時鐘 //下面這六個就是LCD的時序參數 __u32 left_margin; /* time from sync to picture */ __u32 right_margin; /* time from picture to sync */ __u32 upper_margin; /* time from sync to picture */ __u32 lower_margin; __u32 hsync_len; /* length of horizontal sync */ __u32 vsync_len; /* length of vertical sync */ __u32 sync; /* see FB_SYNC_* */ __u32 vmode; /* see FB_VMODE_* */ __u32 rotate; /* angle we rotate counter clockwise */ __u32 reserved[5]; /* Reserved for future compatibility */ };
(3)struct fb_fix_screeninfo
Linux內核中使用struct fb_fix_screeninfo來描述不可修改的LCD屬性參數,如顯示內存的物理地址和長度等。
struct fb_fix_screeninfo { char id[16]; /* identification string eg "TT Builtin" */ unsigned long smem_start; // LCD顯存的起始地址(物理地址) /* (physical address) */ __u32 smem_len; /* Length of frame buffer mem */// LCD顯存的字節大小 __u32 type; /* see FB_TYPE_**/ __u32 type_aux; /* Interleave for interleaved Planes */ __u32 visual; /* see FB_VISUAL_* */ __u16 xpanstep; /* zero if no hardware panning */ __u16 ypanstep; /* zero if no hardware panning */ __u16 ywrapstep; /* zero if no hardware ywrap */ __u32 line_length; /* length of a line in bytes */// LCD一行的長度 (以字節爲單位) unsigned long mmio_start; /* Start of Memory Mapped I/O */ /* (physical address) */ __u32 mmio_len; /* Length of Memory Mapped I/O */ __u32 accel; /* Indicate to driver which */ /* specific chip/card we have */ __u16 reserved[3]; /* Reserved for future compatibility */ };
Linux Framebuffer的驅動框架主要涉及如下文件:
1)drivers/video/fbmem.c:主要任務是建立graphics類、註冊FB的字符設備驅動(主設備號是29)、提供register_framebuffer接口給具體framebuffer驅動編寫着來註冊fb設備的;
2)drivers/video/fbsys.c:由是fbmem.c引出來的,處理fb在/sys/class/graphics/fb0目錄下的一些屬性文件的;
3)drivers/video/modedb.c:由是fbmem.c引出來的,管理顯示模式(譬如VGA、720P等就是顯示模式);
4)drivers/video/fb_notify.c:是f由bmem.c引出來的。
(1)framebuffer的初始化
Linux內核中將framebuffer的驅動框架實現爲模塊的形式。framebuffer的初始化函數fbmem_init()被module_init或subsys_initcall修飾,即fbmem_init()會在framebuffer驅動被加載時由Linux內核調度運行。
fbmem_init()函數的主要工做是經過register_chrdev接口向內核註冊一個主設備號位29的字符設備。經過class_create在/sys/class下建立graphics設備類,配合mdev機制生成供用戶訪問的設備文件(位於/dev目錄)。
static int __init fbmem_init(void) { proc_create("fb", 0, NULL, &fb_proc_fops); //向proc文件系統報告驅動狀態和參數 if (register_chrdev(FB_MAJOR,"fb",&fb_fops))//註冊字符設備驅動,主設備號是29 printk("unable to get major %d for fb devs\n", FB_MAJOR); //建立sys/class/graphics設備類,配合mdev生成設備文件 fb_class = class_create(THIS_MODULE, "graphics"); if (IS_ERR(fb_class)) { printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class)); fb_class = NULL; } return 0; } #ifdef MODULE module_init(fbmem_init); ... ... #else subsys_initcall(fbmem_init); #endif
(2)fb_fops
在framebuffer的初始化函數fbmem_init()中,調用register_chrdev(FB_MAJOR,"fb",&fb_fops)向內核註冊一個主設備號位FB_MAJOR = 29的字符設備,其中fb_fops爲fb設備的操做集。
static const struct file_operations fb_fops = { .owner = THIS_MODULE, .read = fb_read, .write = fb_write, .unlocked_ioctl = fb_ioctl, .mmap = fb_mmap, .open = fb_open, .release = fb_release, ... ... };
咱們着重分析一下fb_fops->fb_open。
假設在註冊fb設備過程當中生成/dev/input/fb0設備文件,咱們來跟蹤打開這個設備的過程。
Open(「/dev/input/fb0」)
1)vfs_open打開該設備文件,讀出文件的inode內容,獲得該設備的主設備號和次設備號;
2)chardev_open 字符設備驅動框架的open根據主設備號獲得framebuffer的fb_fops操做集;
3)進入到fb_fops->open, 即fb_open()。從fb_open()函數中能夠看出,最終調用的是fb0設備下的fb_ops來對fb0設備進行訪問,即fb_info->fb_ops->fb_open。
static int fb_open(struct inode *inode, struct file *file) __acquires(&info->lock) __releases(&info->lock) { int fbidx = iminor(inode); //得到次設備號 struct fb_info *info; //表明一個framebuffer的全局數據結構 int res = 0; if (fbidx >= FB_MAX) return -ENODEV; info = registered_fb[fbidx]; //找到對應的fb_info file->private_data = info; //將info存入file的私有數據中,便於用戶調用其餘接口(mmap、ioctl等)可以找到相應的info if (info->fbops->fb_open) //調用info->fbops->fb_open
{
res = info->fbops->fb_open(info,1); if (res) module_put(info->fbops->owner); }
... ...
return res; }
(3)framebuffer設備註冊/註銷接口
framebuffer驅動加載初始化時須要經過register_framebuffer接口向framebuffer子系統註冊本身。
數組struct fb_info *registered_fb[FB_MAX],是fb驅動框架維護的一個用來管理記錄fb設備的數組,裏面的元素就是 fb_info 指針,一個fb_info就表明一個fb設備。由此可知,Linux內核最多支持FB_MAX = 32個fb設備。
int register_framebuffer(struct fb_info *fb_info) { int i; struct fb_event event; struct fb_videomode mode; if (num_registered_fb == FB_MAX)//若是當前註冊的fb設備的數量已經滿了,則不能在進行註冊了 最大值32 return -ENXIO; ... ... //找到一個最小的沒有被使用的次設備號 num_registered_fb++; for (i = 0 ; i < FB_MAX; i++) if (!registered_fb[i]) break; fb_info->node = i; //將次設備號存放在 fb_info->node 中 mutex_init(&fb_info->lock); mutex_init(&fb_info->mm_lock); //建立framebuffer設備 fb_info->dev = device_create(fb_class, fb_info->device, MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
... ...
//初始化framebuffer設備信息,建立fb的設備屬性文件 fb_init_device(fb_info);
//pixmp相關設置 if (fb_info->pixmap.addr == NULL)
{ fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL); if (fb_info->pixmap.addr)
{ fb_info->pixmap.size = FBPIXMAPSIZE; fb_info->pixmap.buf_align = 1; fb_info->pixmap.scan_align = 1; fb_info->pixmap.access_align = 32; fb_info->pixmap.flags = FB_PIXMAP_DEFAULT; } } ... ... fb_var_to_videomode(&mode, &fb_info->var); //從fb_info結構體中獲取顯示模式存放在mode變量中 fb_add_videomode(&mode, &fb_info->modelist);//添加顯示模式 registered_fb[i] = fb_info; //將fb_info 結構體存放到 registered_fb數組中 ... ... fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event); //異步通知: 通知那些正在等待fb註冊事件發生的進程 ... ... return 0; }
相應的fb設備的註銷接口爲unregister_framebuffer()函數。
/* *功能:註銷fb設備 *參數:struct fb_info *fb_info:須要註銷的fb設備 */ int unregister_framebuffer(struct fb_info *fb_info);
(4)mmap映射
framebuffer驅動最重要的功能就是給用戶提供一個進程空間映射到實際的顯示物理內存的接口(fb_fops->mmap)。
應用層mmap映射接口會經歷如下層次調用:
1)sys_mmap(),虛擬文件系統層(VFS)的映射實現;
2)fb_ops.mmap = fb_mmap,此處的fb_fops爲framebuffer驅動框架的操做集;從fb_mmap()函數中能夠看出,最終調用的是fb設備的fb_ops中的fb_mmap,即fb_info->fb_fops->fb_mmap。
static int fb_mmap(struct file *file, struct vm_area_struct * vma) { int fbidx = iminor(file->f_path.dentry->d_inode); //獲取fb的子設備號 struct fb_info *info = registered_fb[fbidx]; //獲取相應的fb_info struct fb_ops *fb = info->fbops; //fb設備的操做集 ... ... if (fb->fb_mmap) { int res; res = fb->fb_mmap(info, vma); //調用fb設備操做集下的fb_mmap mutex_unlock(&info->mm_lock); return res; } ... ... return 0; }
在應用層中,用戶能夠將fb設備當作是顯存的一個映像,將其映射到進程空間後,就能夠直接進行讀寫操做,寫操做會直接反映在屏幕上。
在應用程序中,操做/dev/fbn的通常步驟以下:
1)打開/dev/fbn設備文件;
2)用ioctl()操做取得當前顯示屏幕的參數,如屏幕分辨率、每一個像素點的比特數。根據屏幕參數可計算屏幕緩衝區的大小;
3)用mmap()函數,將屏幕緩衝區映射到用戶空間;
4)映射後就能夠直接讀/寫屏幕緩衝區,進行繪圖和圖片顯示;。
5)使用完幀緩衝設備後須要將其釋放;
6)關閉文件。
應用示例:
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <linux/fb.h> #include <sys/ioctl.h> #include <sys/mman.h> #define FBDEVICE "/dev/fb0" #define WHITE 0xffffffff #define BLACK 0x00000000 void draw_back(unsigned int *pfb, unsigned int width, unsigned int height, unsigned int color) { unsigned int x, y; for (y=0; y<height; y++) { for (x=0; x<width; x++) { *(pfb + y * width + x) = color; } } } int main(void) { int fd = -1; unsigned int *pfb = NULL; unsigned int width; unsigned int height; struct fb_fix_screeninfo finfo = {0}; struct fb_var_screeninfo vinfo = {0}; /************************第1步:打開設備**************************/ fd = open(FBDEVICE, O_RDWR); if (fd < 0) { perror("open"); return -1; } printf("open %s success.\n", FBDEVICE); /*******************第2步:獲取設備的硬件信息********************/ ioctl(fd, FBIOGET_FSCREENINFO, &finfo);//獲取LCD的固定屬性參數 /* *finfo.smem_start:LCD顯存的起始地址 *finfo.smem_len:LCD顯存的字節大小 */ printf("smem_start = 0x%x, smem_len = %u.\n", finfo.smem_start, finfo.smem_len); ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);//獲取LCD的可變參數 /* *vinfo.xres:水平分辨率 *vinfo.yres:垂直分辨率 *vinfo.xres_virtual:虛擬水平分辨率 *vinfo.yres_virtual:虛擬垂直分辨率 *vinfo.bits_per_pixel:像素深度 */ printf("xres = %u, yres = %u.\n", vinfo.xres, vinfo.yres); printf("xres_virtual = %u, yres_virtual = %u.\n", vinfo.xres_virtual, vinfo.yres_virtual); printf("bpp = %u.\n", vinfo.bits_per_pixel); width = vinfo.xres; height = vinfo.yres; /*************************第3步:進行mmap***********************/ //計算LCD顯存大小(單位:字節) unsigned long len = vinfo.xres_virtual * vinfo.yres_virtual * vinfo.bits_per_pixel / 8; printf("len = %ld\n", len); pfb = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); printf("pfb = %p.\n", pfb); /********************第4步:進行lcd相關的操做********************/ draw_back(pfb, width, height, RED); /***********************第五步:釋放顯存************************/ munmap(pfb, len); /***********************第六步:關閉文件************************/ close(fd); return 0; }
5. LCD驅動程序分析