Linux系统LCD驱动架构分析

来源:互联网 发布:知豆电动汽车怎么样 编辑:程序博客网 时间:2024/06/14 00:45

我们一起来看一个Linux系统中最重要的输出系统的驱动架构---LCD驱动。在Linux中,LCD驱动采用了帧缓冲(FrameBuffer)技术,所以LCD驱动又叫FrameBuffer驱动。在很多地方,这两种叫法是通用的。本文章的核心内容就是搞清楚FrameBuffer的程序架构,弄明白内核已经帮我们做了多少工作,我们自己又该做哪些工作。需要跟大家剧透一点信息,FrameBuffer驱动架构和input子系统驱动架构的实现思路上面有很多共性,在学习的时候对比着学效果更佳。如果您电脑上已经安装了SourceInsignt软件,并且也有现成的Linux内核源码的话,那请打开软件边操作边学吧,效果更佳哦!

        如图所示是FrameBuffer驱动的总体架构。用户空间的应用程序通过FrameBuffer设备文件(/dev/graphics/fbX)与FrameBuffer进行交互,而直接参与交互的是fbmem.c文件,从图中可以看出fbmem.c提供了fb_read(), fb_write()等回调函数。fbmem.c是内核帮我们完成的framebuffer驱动的核心文件,类似于平台设备驱动总线的platform_driver,它为上层应用程序提供回调函数接口,为下层硬件相关的设备驱动程序提供注册通道。xxxfb.c文件则是与硬件相关的设备驱动程序,类似与平台设备总线的platform_device,该文件由驱动工程师完成,其最主要的工作就是初始化fb_info结构体并进行注册。

 

 

介绍该架构中最重要的一个结构体------fb_info结构体。

代码清单 fb_info结构体

[cpp] view plain copy
  1. struct fb_info {  
  2.     atomic_t count;  
  3.     int node;  
  4.     int flags;  
  5.     struct mutex lock;      /* Lock for open/release/ioctl funcs */  
  6.     struct mutex mm_lock;       /* Lock for fb_mmap and smem_* fields */  
  7.     struct fb_var_screeninfo var;   /* Current var */  
  8.     struct fb_fix_screeninfo fix;   /* Current fix */  
  9.     struct fb_monspecs monspecs;    /* Current Monitor specs */  
  10.     struct work_struct queue;   /* Framebuffer event queue */  
  11.     struct fb_pixmap pixmap;    /* Image hardware mapper */  
  12.     struct fb_pixmap sprite;    /* Cursor hardware mapper */  
  13.     struct fb_cmap cmap;        /* Current cmap */  
  14.     struct list_head modelist;      /* mode list */  
  15.     struct fb_videomode *mode;  /* current mode */  
  16.   
  17. #ifdef CONFIG_FB_BACKLIGHT  
  18.     /* assigned backlight device */  
  19.     /* set before framebuffer registration,  
  20.        remove after unregister */  
  21.     struct backlight_device *bl_dev;  
  22.   
  23.     /* Backlight level curve */  
  24.     struct mutex bl_curve_mutex;      
  25.     u8 bl_curve[FB_BACKLIGHT_LEVELS];  
  26. #endif  
  27. #ifdef CONFIG_FB_DEFERRED_IO  
  28.     struct delayed_work deferred_work;  
  29.     struct fb_deferred_io *fbdefio;  
  30. #endif  
  31.   
  32.     struct fb_ops *fbops;  
  33.     struct device *device;      /* This is the parent */  
  34.     struct device *dev;     /* This is this fb device */  
  35.     int class_flag;                    /* private sysfs flags */  
  36. #ifdef CONFIG_FB_TILEBLITTING  
  37.     struct fb_tile_ops *tileops;    /* Tile Blitting */  
  38. #endif  
  39.     char __iomem *screen_base;  /* Virtual address */  
  40.     unsigned long screen_size;  /* Amount of ioremapped VRAM or 0 */   
  41.     void *pseudo_palette;       /* Fake palette of 16 colors */   
  42. #define FBINFO_STATE_RUNNING    0  
  43. #define FBINFO_STATE_SUSPENDED  1  
  44.     u32 state;          /* Hardware state i.e suspend */  
  45.     void *fbcon_par;                /* fbcon use-only private area */  
  46.     /* From here on everything is device dependent */  
  47.     void *par;  
  48.     /* we need the PCI or similar aperture base/size not 
  49.        smem_start/size as smem_start may just be an object 
  50.        allocated inside the aperture so may not actually overlap */  
  51.     struct apertures_struct {  
  52.         unsigned int count;  
  53.         struct aperture {  
  54.             resource_size_t base;  
  55.             resource_size_t size;  
  56.         } ranges[0];  
  57.     } *apertures;  
  58. };  


fb_info结构体比较复杂,但是我们不需要了解每一个成员变量的功能和使用方法。在这里面有3个最主要的结构体,分别是fb_var_screeninfo,fb_fix_screeninfo和fb_ops。其中,fb_var_screeninfo结构体记录了用户可以修改的显示参数,比如屏幕分辨率,屏幕颜色位域等;fb_fix_screeninfo结构体则记录了用户不可修改的参数,比如屏幕缓冲区的物理地址和缓存的长度等。fb_ops结构体记录了对底层硬件操作的函数指针,也就是充当了file_operations结构体的角色,如果fb_info结构体对象定义了fb_ops,那应用程序发出的read命令最终访问到的将是fb_info->fb_ops->fb_read函数,如果fb_info结构体对象没有定义fb_ops则read命令访问的是input.c中定义的fb_read函数。这些知识当然都是由内核告诉我们的,下文分析内核源码的时候大家就明白了。

         如果您了解input子系统的话知道input子系统有一个核心文件input.c,它为应用程序提供访问接口。Framebuffer架构则以fbmem.c为核心,也是应用程序访问的接口。现在以应用程序发出read命令访问设备节点为线索分析以下源代码。首先利用SourceInsight软件定位到fbmem.c的入口函数。

 


代码清单 fbmem_init(void)入口函数

[cpp] view plain copy
  1. static int __init fbmem_init(void)  
  2. {  
  3.     proc_create("fb", 0, NULL, &fb_proc_fops);  
  4.   
  5.     if (register_chrdev(FB_MAJOR,"fb",&fb_fops))  
  6.         printk("unable to get major %d for fb devs\n", FB_MAJOR);  
  7.   
  8.     fb_class = class_create(THIS_MODULE, "graphics");  
  9.     if (IS_ERR(fb_class)) {  
  10.         printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));  
  11.         fb_class = NULL;  
  12.     }  
  13.     return 0;  
  14. }  


虽然fbmem_init入口函数代码不长,但是结合我们之前所学的知识分析的话,这里面信息量很大。分几点说明

1) 首先看到一句非常熟悉的register_chrdev(FB_MAJOR,”fb”,&fb_fops)注册函数,这就证明了FrameBuffer驱动也是一个字符型设备驱动。主设备号FB_MAJOR = 29。

2) 接着看到了class_create()创建类,然而并没有创建设备文件。可以大胆的做如下猜测:fbmem.c是framebuffer架构的纯软件部分,是内核帮我们提取出来的。我们自己编写LCD驱动程序的时候只需要编写与硬件息息相关的设备驱动程序即可,然后将某一个代表硬件的结构体注册进内核,与此同时内核才会创建与该硬件相关的设备文件节点。那是不是这样的呢?肯定是,不是就见鬼了,不信就接着往下分析。

3) register_chrdev()函数中第三个参数&fb_fops代表file_operations结构体,是字符设备驱动的核心部分,这里面肯定会提供read、write等回调函数。或者像input子系统那样虽然只提供了一个open函数,但是在open函数里面又进一步的访问到了特定handler的fops。定位到fb_fops看一下。


代码清单  fb_ops

[cpp] view plain copy
  1. static const struct file_operations fb_fops = {  
  2.     .owner =    THIS_MODULE,  
  3.     .read =     fb_read,  
  4.     .write =    fb_write,  
  5.     .unlocked_ioctl = fb_ioctl,  
  6. #ifdef CONFIG_COMPAT  
  7.     .compat_ioctl = fb_compat_ioctl,  
  8. #endif  
  9.     .mmap =     fb_mmap,  
  10.     .open =     fb_open,  
  11.     .release =  fb_release,  
  12. #ifdef HAVE_ARCH_FB_UNMAPPED_AREA  
  13.     .get_unmapped_area = get_fb_unmapped_area,  
  14. #endif  
  15. #ifdef CONFIG_FB_DEFERRED_IO  
  16.     .fsync =    fb_deferred_io_fsync,  
  17. #endif  
  18.     .llseek =   default_llseek,  
  19. };  


果然看到了read、write等非常熟悉的老朋友。假如应用程序发出read命令,则会触发fb_read回调函数。点进去看一下。


代码清单 input.c中的fb_read() 函数

[cpp] view plain copy
  1. static ssize_t  
  2. fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)  
  3. {  
  4.     unsigned long p = *ppos;  
  5.     struct fb_info *info = file_fb_info(file);  
  6.     u8 *buffer, *dst;  
  7.     u8 __iomem *src;  
  8.     int c, cnt = 0, err = 0;  
  9.     unsigned long total_size;  
  10.   
  11.     if (!info || ! info->screen_base)  
  12.         return -ENODEV;  
  13.   
  14.     if (info->state != FBINFO_STATE_RUNNING)  
  15.         return -EPERM;  
  16.   
  17.     if (info->fbops->fb_read)  
  18.         return info->fbops->fb_read(info, buf, count, ppos);  
  19.       
  20.     total_size = info->screen_size;  
  21.   
  22.     if (total_size == 0)  
  23.         total_size = info->fix.smem_len;  
  24.   
  25.     if (p >= total_size)  
  26.         return 0;  
  27.   
  28.     if (count >= total_size)  
  29.         count = total_size;  
  30.   
  31.     if (count + p > total_size)  
  32.         count = total_size - p;  
  33.   
  34.     buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,  
  35.              GFP_KERNEL);  
  36.     if (!buffer)  
  37.         return -ENOMEM;  
  38.   
  39.     src = (u8 __iomem *) (info->screen_base + p);  
  40.   
  41.     if (info->fbops->fb_sync)  
  42.         info->fbops->fb_sync(info);  
  43.   
  44.     while (count) {  
  45.         c  = (count > PAGE_SIZE) ? PAGE_SIZE : count;  
  46.         dst = buffer;  
  47.         fb_memcpy_fromfb(dst, src, c);  
  48.         dst += c;  
  49.         src += c;  
  50.   
  51.         if (copy_to_user(buf, buffer, c)) {  
  52.             err = -EFAULT;  
  53.             break;  
  54.         }  
  55.         *ppos += c;  
  56.         buf += c;  
  57.         cnt += c;  
  58.         count -= c;  
  59.     }  
  60.   
  61.     kfree(buffer);  
  62.   
  63.     return (err) ? err : cnt;  
  64. }  


fb_read函数的思路是这样子的。首先通过file_fb_info(file)函数获得了一个fb_info结构体,该结构体代表了一款特定的LCD硬件。如果fb_info结构体中定义了fops成员变量,并且fops中定义了read成员函数,则调用这个read函数。否则继续执行fb_read中的copy_to_user()。可以看出来,fbmem.c在某种程度上起到了中转站的作用。目前的线索指向了file_fb_info(file)函数,它到底是如何返回的fb_info结构体呢?追踪进去看一下。


代码清单 file_fb_info()

[cpp] view plain copy
  1. static struct fb_info *file_fb_info(struct file *file)  
  2. {  
  3.     struct inode *inode = file->f_path.dentry->d_inode;  
  4.     int fbidx = iminor(inode);  
  5.     struct fb_info *info = registered_fb[fbidx];  
  6.   
  7.     if (info != file->private_data)  
  8.         info = NULL;  
  9.     return info;  
  10. }  


原来这个fb_info结构体是一个叫registered_fb[]数组中的一个元素,这个数组从字面上解释叫“已经注册的fb”,猜也能猜到这个数组肯定是在注册fb_info的时候赋值的。这是不是跟输入子系统中那个input_table[]数组比较类似呢。不同之处是input_table[]是在注册纯软件驱动的input_handler时赋值,而registered_fb[]是在注册fb_info这个硬件相关的结构体时赋值的。查找一个registered_fb[]数组的赋值位置。


代码清单 do_register_framebuffer()函数

[cpp] view plain copy
  1. static int do_register_framebuffer(struct fb_info *fb_info)  
  2. {  
  3.     ... ...  
  4. fb_info->dev = device_create(fb_class, fb_info->device,  
  5.                      MKDEV(FB_MAJOR, i), NULL, "fb%d", i);  
  6. ... ...  
  7.     registered_fb[i] = fb_info;  
  8.     ... ...  
  9. }   

在do_register_framebuffer中有两个重大发现:

1). 调用了device_craate()函数,创建了我们非常熟悉的字符型设备驱动的设备文件节点

2). 找到了registered_fb[]的赋值位置。继续搜索do_register_framebuffer的调用位置。

  

代码清单 register_framebuffer()

[html] view plain copy
  1. int register_framebuffer(struct fb_info *fb_info)  
  2. {  
  3.     int ret;  
  4.   
  5.     mutex_lock(&registration_lock);  
  6.     ret = do_register_framebuffer(fb_info);  
  7.     mutex_unlock(&registration_lock);  
  8.   
  9.     return ret;  
  10. }  


原来是这个叫做register_framebuffer的函数调用了do_register_framebuffer。这个register_framebuffer和我们学过的register_chrdev长得好像,估计它就是用来注册fb_info结构体的核心函数了。全文搜索一下register_framebuffer的调用位置,会发现好多与硬件相关的驱动程序中都调用了register_framebuffer来注册fb_info结构体。

 


分析到这,framebuffer驱动的架构层次也就出来了。驱动工程师在编写LCD驱动的时候最重要的是初始化一个fb_info结构体。当设置好fb_info结构体之后,使用register_framebuffer(struct fb_info *)函数进行注册,这时内核就会把该fb_info放到registered_fb[]数组中,并创建一个设备文件节点。当应用程序发出read等请求访问设备文件节点的时候,input.c中的read回调函数会首先根据次设备号将fb_info从registered_fb[]中取出,然后看看fb_info中是否设置了fops成员变量如设置,则调用fops中的read回调函数,否则继续执行input.c中的read回调函数。

        那我们该如何编写一个LCD驱动的设备驱动程序呢?其实从上文的源码分析中我们应该已经知道具体该做什么了。今天就先到这吧,等我有时间了,或者大家迫切需要这方面知识了,我再把“如何一步一步编写LCD驱动”的文章贴出来。

原创粉丝点击