在讀者學習本章以及後續LCD相關章節以前,最好擁有LCD裸機基礎,能夠參考:LCD編程。html
在內核中,表示LCD使用的是framebuffer(幀緩衝,簡寫爲fb),其內容對應於屏幕上的界面顯示。修改framebuffer中的內容,即修改屏幕上的內容。操做framebuffer能夠直接在LCD上觀察到效果。node
framebuffer本質上是一段內存,或稱做顯存。編程
在內核中,LCD對應的參數使用struct fb_info存儲,對應的行爲使用struct fb_ops存儲。緩存
在如下章節,我會分別討論fb_info和fb_ops。app
1、fb_info
以前說過fb_info定義的是屬性,其結構體定義以下:框架
struct fb_info { ... struct fb_var_screeninfo var; /* LCD可變參數,如屏幕一行像素點個數xres,一列像素點個數yres,每像素點所佔位數等 */ struct fb_fix_screeninfo fix; /* LCD固定參數,記錄用戶不能修改的顯示控制器的參數,如屏幕緩存區物理地址smem_start,id,type等 */ ... struct backlight_device *bl_dev; /* 背光設備 */ ... struct fb_ops *fbops; /* LCD操做函數 */ struct device *device; /* This is the parent */ struct device *dev; /* This is this fb device */ ... char __iomem *screen_base; /* 顯存虛擬地址 */ unsigned long screen_size; /* 屏幕大小*每一個像素的字節數 */ void *pseudo_palette; /* Fake palette of 16 colors */ ... };
其中,咱們須要關注的有var、fix、screen_base和pseudo_paletteide
var結構體定義以下:函數
struct fb_var_screeninfo { __u32 xres; /* LCD物理分辨率 */ __u32 yres; __u32 xres_virtual; /* LCD虛擬分辨率 */ __u32 yres_virtual; __u32 xoffset; /* 虛擬和物理分辨率的偏移值 */ __u32 yoffset; __u32 bits_per_pixel; /* 每個像素佔多少bit */ __u32 grayscale; /* 灰度值,0 = color,1 = grayscale, */ /* >1 = FOURCC */ struct fb_bitfield red; /* bitfield in fb mem if true color, */ struct fb_bitfield green; /* else only length is significant */ struct fb_bitfield blue; ... __u32 activate; /* see FB_ACTIVATE_* */ ... /* Timing指的是LCD上下的黑框的寬度等參數,通常不用設置 */ __u32 pixclock; /* pixel clock in ps (pico seconds) */ __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 colorspace; /* colorspace for FOURCC-based modes */ __u32 reserved[4]; /* Reserved for future compatibility */ };
其中須要咱們瞭解的有:學習
1. bits_per_pixel是LCD邏輯中的BPP,通常有24BPP、16BPP和8BPP。BPP的數值越大,顯存所需空間越大,給處理器帶來的負擔也就越重;BPP的數值在8位如下時,所能表達的顏色又太少,不可以知足用戶特定的需求。爲解決這個問題,就須要採起調色板,也就是pseudo_palette。ui
2. fb_bitfield結構體用於設置紅色、綠色和藍色在BPP中的位置和長度。好比16BPP,格式爲565,則格式示例代碼以下:
fbinfo->var.red.offset = 11; fbinfo->var.red.length = 5; // fbinfo->var.red.msb_right = ; /* 1: 右邊爲高位 */ fbinfo->var.green.offset = 5; fbinfo->var.green.length = 6; // fbinfo->var.green.msb_right = ; fbinfo->var.blue.offset = 0; fbinfo->var.blue.length = 5; // fbinfo->var.blue.msb_right = ;
3. FB_ACTIVATE宏定義以下:
#define FB_ACTIVATE_NOW 0 /* 當即設置值,通常選用此選項 */ #define FB_ACTIVATE_NXTOPEN 1 /* 下次打開時激活 */ #define FB_ACTIVATE_TEST 2 /* 不設置 */ #define FB_ACTIVATE_MASK 15 /* values */ #define FB_ACTIVATE_VBL 16 /* 在下一次設置值時激活 */ #define FB_CHANGE_CMAP_VBL 32 /* change colormap on vbl */ #define FB_ACTIVATE_ALL 64 /* change all VCs on this fb */ #define FB_ACTIVATE_FORCE 128 /* force apply even when no change*/ #define FB_ACTIVATE_INV_MODE 256 /* invalidate videomode */
fix結構體定義以下:
struct fb_fix_screeninfo { char id[16]; /* 屏幕名字,自行設置 */ unsigned long smem_start; /* 屏幕緩存區物理地址 */ __u32 smem_len; /* 屏幕緩存區長度 */ __u32 type; /* see FB_TYPE_* */ __u32 type_aux; /* 輔助類型,通常設置爲0 */ __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; /* 一行的字節數 */ unsigned long mmio_start; /* 寄存器的起始物理地址,通常不須要設置 */ __u32 mmio_len; /* 寄存器的長度,通常不須要設置 */ __u32 accel; /* Indicate to driver which */ /* specific chip/card we have */ __u16 reserved[3]; /* Reserved for future compatibility */ };
其中,FB_TYPE宏定義以下:
#define FB_TYPE_PACKED_PIXELS 0 /* 像素填充,通常選用此選項 */ #define FB_TYPE_PLANES 1 /* 非交錯planes */ #define FB_TYPE_INTERLEAVED_PLANES 2 /* 交錯planes */ #define FB_TYPE_TEXT 3 /*文本/屬性 */ #define FB_TYPE_VGA_PLANES 4 /* EGA/VGA planes */
FB_VISUAL宏定義以下:
#define FB_VISUAL_MONO01 0 /* 二值圖像,只有黑白 1=Black 0=White */ #define FB_VISUAL_MONO10 1 /* 二值圖像,只有黑白 1=White 0=Black */ #define FB_VISUAL_TRUECOLOR 2 /* 真彩色,通常選用此選項 */ #define FB_VISUAL_PSEUDOCOLOR 3 /* Pseudo color (like atari) */ #define FB_VISUAL_DIRECTCOLOR 4 /* Direct color */ #define FB_VISUAL_STATIC_PSEUDOCOLOR 5 /* Pseudo color readonly */
pseudo_palette,又稱調色板,它能夠在低位BPP的條件下,在有限的像素值與RGB顏色之間創建擁有對應關係的線性表。好比從全部的16BPP的顏色中抽取必定數量的顏色編制索引。當須要使用某種彩色時,不須要對這種顏色的RGB份量進行描述,只須要引用它的索引號,就能夠選取本身須要的顏色。索引號的長度遠遠小於RGB份量的編碼長度,所以在彩色顯示的同時,也減輕了系統的負擔。
若須要調色板,咱們須要在LCD操做函數中添加以下代碼:
1 /* 代碼來源於drivers/video/samsung/s3cfb_ops.c */ 2 3 inline unsigned int __chan_to_field(unsigned int chan, struct fb_bitfield bf) 4 { 5 chan &= 0xffff; 6 chan >>= 16 - bf.length; 7 8 return chan << bf.offset; 9 } 10 11 int s3cfb_setcolreg(unsigned int regno, unsigned int red, 12 unsigned int green, unsigned int blue, 13 unsigned int transp, struct fb_info *fb) 14 { 15 unsigned int *pal = (unsigned int *)fb->pseudo_palette; 16 unsigned int val = 0; 17 18 if (regno < 16) { 19 /* fake palette of 16 colors */ 20 val |= __chan_to_field(red, fb->var.red); 21 val |= __chan_to_field(green, fb->var.green); 22 val |= __chan_to_field(blue, fb->var.blue); 23 val |= __chan_to_field(transp, fb->var.transp); 24 pal[regno] = val; 25 } 26 27 return 0; 28 }
2、fb_ops
以前說過fb_ops定義的是行爲,其結構體定義以下:


struct fb_ops { /* open/release and usage marking */ struct module *owner; int (*fb_open)(struct fb_info *info, int user); int (*fb_release)(struct fb_info *info, int user); /* For framebuffers with strange non linear layouts or that do not * work with normal memory mapped access */ ssize_t (*fb_read)(struct fb_info *info, char __user *buf, size_t count, loff_t *ppos); ssize_t (*fb_write)(struct fb_info *info, const char __user *buf, size_t count, loff_t *ppos); /* checks var and eventually tweaks it to something supported, * DO NOT MODIFY PAR */ int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info); /* set the video mode according to info->var */ int (*fb_set_par)(struct fb_info *info); /* set color register */ int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *info); /* set color registers in batch */ int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info); /* blank display */ int (*fb_blank)(int blank, struct fb_info *info); /* pan display */ int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info); /* Draws a rectangle */ void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect); /* Copy data from area to another */ void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region); /* Draws a image to the display */ void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image); /* Draws cursor */ int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor); /* Rotates the display */ void (*fb_rotate)(struct fb_info *info, int angle); /* wait for blit idle, optional */ int (*fb_sync)(struct fb_info *info); /* perform fb specific ioctl (optional) */ int (*fb_ioctl)(struct fb_info *info, unsigned int cmd, unsigned long arg); /* Handle 32bit compat ioctl (optional) */ int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd, unsigned long arg); /* perform fb specific mmap */ int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma); /* get capability given var */ void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps, struct fb_var_screeninfo *var); /* teardown any resources to do with this framebuffer */ void (*fb_destroy)(struct fb_info *info); /* called at KDB enter and leave time to prepare the console */ int (*fb_debug_enter)(struct fb_info *info); int (*fb_debug_leave)(struct fb_info *info); };
此結構體中函數咱們只須要根據實際狀況編寫部分函數便可。好比以前的調色板代碼應該設置爲fb_setcolreg函數指針:.fb_setcolreg = s3cfb_setcolreg,
3、framebuffer驅動調用流程
在應用程序使用LCD以前,內核主要須要作如下工做:
1. 初始化framebuffer框架,這個在drivers/video/fbmem.c中實現
2. 註冊LCD設備,也就是註冊fb_info
接下來應用程序須要操做LCD,會調用內核函數:
3. 應用程序open(),調用fb_open()
4. 應用程序write()、mmap()等,調用fb_write()、fb_mmap()等
5. 應用程序close(),調用fb_release()
1. 初始化framebuffer框架
1 static int __init 2 fbmem_init(void) 3 { 4 /* 1. 在proc文件系統中建立fb相關操做接口 */ 5 proc_create("fb", 0, NULL, &fb_proc_fops); 6 7 /* 2. 註冊fb字符驅動 */ 8 if (register_chrdev(FB_MAJOR,"fb",&fb_fops)) 9 printk("unable to get major %d for fb devs\n", FB_MAJOR); 10 11 /* 3. 建立graphics類 */ 12 fb_class = class_create(THIS_MODULE, "graphics"); 13 if (IS_ERR(fb_class)) { 14 printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class)); 15 fb_class = NULL; 16 } 17 return 0; 18 }
由代碼可知,fb的主設備號是代碼中定好的,區分各個LCD設備依靠的是次設備號。
在框架搭建完成以後,咱們就須要註冊本身寫的驅動中的fb_info結構體
2. 註冊fb_info結構體
1 int 2 register_framebuffer(struct fb_info *fb_info) 3 { 4 int ret; 5 6 mutex_lock(®istration_lock); 7 ret = do_register_framebuffer(fb_info); 8 mutex_unlock(®istration_lock); 9 10 return ret; 11 }
1 static int do_register_framebuffer(struct fb_info *fb_info) 2 { 3 ... 4 /* 1. 判斷要註冊設備的顯存和已有設備的顯存是否衝突 */ 5 do_remove_conflicting_framebuffers(fb_info->apertures, fb_info->fix.id, 6 fb_is_primary_device(fb_info)); 7 8 /* 2. FB_MAX = 32,最多支持32個LCD設備 */ 9 if (num_registered_fb == FB_MAX) 10 return -ENXIO; 11 12 ... 13 14 /* 3. 建立設備fb0/1/2... */ 15 fb_info->dev = device_create(fb_class, fb_info->device, 16 MKDEV(FB_MAJOR, i), NULL, "fb%d", i); 17 ... 18 19 /* 4. 若驅動沒有實現fb_info中pixmap,內核使用默認參數 */ 20 if (fb_info->pixmap.addr == NULL) { 21 fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL); 22 if (fb_info->pixmap.addr) { 23 fb_info->pixmap.size = FBPIXMAPSIZE; 24 fb_info->pixmap.buf_align = 1; 25 fb_info->pixmap.scan_align = 1; 26 fb_info->pixmap.access_align = 32; 27 fb_info->pixmap.flags = FB_PIXMAP_DEFAULT; 28 } 29 } 30 fb_info->pixmap.offset = 0; 31 32 ... 33 34 /* 5. 使用fbinfo中參數初始化mode */ 35 fb_var_to_videomode(&mode, &fb_info->var); 36 fb_add_videomode(&mode, &fb_info->modelist); 37 registered_fb[i] = fb_info; 38 39 event.info = fb_info; 40 if (!lock_fb_info(fb_info)) 41 return -ENODEV; 42 /* 6. 通知fb註冊成功 */ 43 fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event); 44 unlock_fb_info(fb_info); 45 return 0; 46 }
3. 應用程序open(),調用fb_open()函數
在初始化framebuffer框架的fbmem_init()函數中register_chrdev(FB_MAJOR,"fb",&fb_fops)的fb_fops定義了fb_open()函數。


1 static int fb_open(struct inode *inode, struct file *file) 2 __acquires(&info->lock) 3 __releases(&info->lock) 4 { 5 /* 1. 根據次設備號獲取fb_info */ 6 int fbidx = iminor(inode); 7 struct fb_info *info; 8 int res = 0; 9 10 info = get_fb_info(fbidx); 11 12 ... 13 14 mutex_lock(&info->lock); 15 if (!try_module_get(info->fbops->owner)) { 16 res = -ENODEV; 17 goto out; 18 } 19 file->private_data = info; 20 21 /* 2. 若驅動程序中定義了fb_open(),則優先調用 */ 22 if (info->fbops->fb_open) { 23 res = info->fbops->fb_open(info,1); 24 if (res) 25 module_put(info->fbops->owner); 26 } 27 #ifdef CONFIG_FB_DEFERRED_IO 28 if (info->fbdefio) 29 fb_deferred_io_open(info, inode, file); 30 #endif 31 out: 32 mutex_unlock(&info->lock); 33 if (res) 34 put_fb_info(info); 35 return res; 36 }
fb_open()函數所作的有私有化數據和調用驅動程序中fb_ops的fb_open()函數。
4. 應用程序write(),調用fb_write()函數
看過LED和KEY驅動程序的讀者能夠發現write()和read()函數實現差異不大,在此以fb中經常使用的write()函數爲例分析。


1 static ssize_t fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) 2 { 3 unsigned long p = *ppos; /* 偏移量 */ 4 struct fb_info *info = file_fb_info(file); 5 u8 *buffer, *src; 6 u8 __iomem *dst; 7 int c, cnt = 0, err = 0; 8 unsigned long total_size; 9 10 ... 11 12 /* 若驅動程序中定義了fb_write(),則優先調用 */ 13 if (info->fbops->fb_write) 14 return info->fbops->fb_write(info, buf, count, ppos); 15 16 total_size = info->screen_size; 17 18 if (total_size == 0) 19 total_size = info->fix.smem_len; 20 21 if (p > total_size) 22 return -EFBIG; 23 24 if (count > total_size) { 25 err = -EFBIG; 26 count = total_size; 27 } 28 29 if (count + p > total_size) { 30 if (!err) 31 err = -ENOSPC; 32 33 count = total_size - p; 34 } 35 36 buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count, 37 GFP_KERNEL); 38 if (!buffer) 39 return -ENOMEM; 40 41 dst = (u8 __iomem *) (info->screen_base + p); 42 43 /* 若驅動程序中定義了fb_sync(),則優先調用 */ 44 if (info->fbops->fb_sync) 45 info->fbops->fb_sync(info); 46 47 /* 使用copy_from_user()將數據從用戶空間拷貝到內核空間 */ 48 while (count) { 49 c = (count > PAGE_SIZE) ? PAGE_SIZE : count; 50 src = buffer; 51 52 if (copy_from_user(src, buf, c)) { 53 err = -EFAULT; 54 break; 55 } 56 57 fb_memcpy_tofb(dst, src, c); 58 dst += c; 59 src += c; 60 *ppos += c; 61 buf += c; 62 cnt += c; 63 count -= c; 64 } 65 66 kfree(buffer); 67 68 return (cnt) ? cnt : err; 69 }
咱們能夠發現fb_write()函數默認提供的寫操做一樣使用了copy_from_user()拷貝數據。除此以外,它還使用fb_memcpy_tofb()函數把數據寫到顯存。也就是執行兩次拷貝操做。
以前咱們使用copy_from_user()拷貝數據是由於咱們的數據量較小,通常只有幾字節。可是fb顯存通常爲幾百KB,copy_from_user()拷貝數據極有可能致使畫面卡頓,致使效率下降。
解決此問題的方式就是使用mmap()函數。
應用程序mmap()函數使用方法能夠參考:第七章:進程環境中6、存儲空間的分配mmap()函數。
內核使用struct task_struct來表示某個進程,該結構體包含一些進程狀態、調度信息等成員,並使用結構體鏈表來管理全部進程。咱們須要關注進程描述符中內存描述符:struct mm_struct。
struct mm_struct中struct vm_area_struct用來表示一個獨立的虛擬內存區域,該結構體包含映射地址、大小、結束地址等成員,並使用結構體鏈表來管理全部虛擬內存區域。
由此咱們能夠推出:mmap()把設備地址映射到進程虛擬地址(ioremap()把設備地址映射到內核虛擬空間)。指針指向以下圖:
mmap()函數首先分配一個struct vm_area_struct放到進程的地址空間,以後實現文件地址和虛擬地址區域的映射關係。
此時映射關係有了,但內存中沒有數據,進程訪問內存會引起引起缺頁異常,最終內核會發起請求調頁過程,它先在交換緩存空間中尋找須要訪問的內存頁,若是沒有則調用nopage()函數把所缺的頁從磁盤裝入到主存中。在這以後進程即可以正常訪問數據。
這樣作的好處是映射過程並無拷貝數據,只須要從磁盤到用戶主存的一次拷貝數據過程。而write()函數須要從磁盤到頁緩存再到用戶主存的兩次拷貝數據過程。
分析完mmap()後,咱們來查看fb_mmap()函數


1 static int fb_mmap(struct file *file, struct vm_area_struct * vma) 2 { 3 struct fb_info *info = file_fb_info(file); 4 struct fb_ops *fb; 5 unsigned long off; 6 unsigned long start; 7 u32 len; 8 9 if (!info) 10 return -ENODEV; 11 if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) 12 return -EINVAL; 13 off = vma->vm_pgoff << PAGE_SHIFT; 14 fb = info->fbops; 15 if (!fb) 16 return -ENODEV; 17 mutex_lock(&info->mm_lock); 18 if (fb->fb_mmap) { 19 int res; 20 res = fb->fb_mmap(info, vma); /* 若驅動程序中定義了fb_mmap(),則優先調用 */ 21 mutex_unlock(&info->mm_lock); 22 return res; 23 } 24 25 /* frame buffer memory */ 26 start = info->fix.smem_start; /* 顯存起始地址 */ 27 len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len); /* 顯存大小*/ 28 if (off >= len) { 29 /* memory mapped io */ 30 off -= len; 31 if (info->var.accel_flags) { 32 mutex_unlock(&info->mm_lock); 33 return -EINVAL; 34 } 35 start = info->fix.mmio_start; 36 len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len); 37 } 38 mutex_unlock(&info->mm_lock); 39 start &= PAGE_MASK; 40 if ((vma->vm_end - vma->vm_start + off) > len) 41 return -EINVAL; 42 off += start; 43 vma->vm_pgoff = off >> PAGE_SHIFT; 44 /* This is an IO map - tell maydump to skip this VMA */ 45 vma->vm_flags |= VM_IO | VM_RESERVED; 46 vma->vm_page_prot = vm_get_page_prot(vma->vm_flags); 47 fb_pgprotect(file, vma, off); 48 49 /* 映射頁I/O,vma爲用戶分配的空間 */ 50 if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT, 51 vma->vm_end - vma->vm_start, vma->vm_page_prot)) 52 return -EAGAIN; 53 return 0; 54 }
接下來簡單舉例在應用程序中使用mmap()函數並把LCD顯示器的背景刷成藍色。此代碼讀者暫時不須要會修改,熟悉便可。我將在接下來的LCD章節中對平臺驅動框架對代碼中參數進行分析。


1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <fcntl.h> 4 #include <unistd.h> 5 #include <string.h> 6 #include <sys/mman.h> 7 8 /* Usage: 9 * ./a.out <fb0|fb1|fb2|...> 10 */ 11 int main(int argc, char **argv) 12 { 13 if (argc != 2) { 14 printf("Usage:\n"); 15 printf("%s <fb0|fb1|fb2|...>\n", argv[0]); 16 return -1; 17 } 18 19 char path[48] = "/dev/"; 20 strcat(path, argv[1]); 21 22 int fd = open(path, O_RDWR); 23 if (fd < 0) 24 perror("open"), exit(-1); 25 26 /* 1280*800*4 27 * 1280: xres,x方向分辨率 28 * 800:yres,y方向分辨率 29 * 4:個人內核中默認LCD爲24BPP,查手冊能夠肯定24BPP佔32位,也就是4字節 30 */ 31 unsigned int *memory = (unsigned int *)mmap(NULL, 1280*800*4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 32 if (memory == (unsigned int *)-1) { 33 close(fd); 34 perror("mmap"); 35 exit(-1); 36 } 37 38 close(fd); 39 40 /* 把屏幕刷成藍色 */ 41 int i; 42 for (i = 0; i < (1280*800); ++i) { 43 memory[i] = 0x000000ff; 44 } 45 46 /* 寫回磁盤文件中 */ 47 msync(memory, 1280*800*4, MS_SYNC); 48 49 return 0; 50 }
5. 應用程序close(),調用fb_release()
在此僅給出調用過程:
fb_release(struct inode *inode, struct file *file) if (info->fbops->fb_release) info->fbops->fb_release(info,1); -> put_fb_info(info); if (fb_info->fbops->fb_destroy) fb_info->fbops->fb_destroy(fb_info);