10、LCD的framebuffer设备驱动

 

在读者学习本章以及后续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); };
View Code

此结构体中函数咱们只须要根据实际状况编写部分函数便可。好比以前的调色板代码应该设置为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(&registration_lock);  7     ret = do_register_framebuffer(fb_info);  8     mutex_unlock(&registration_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 }
View Code

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 }
View Code

咱们能够发现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 }
View Code

 

接下来简单举例在应用程序中使用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 }
View Code

 

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);

 

 

下一章  11、三星平台framebuffer驱动

相关文章
相关标签/搜索