Linux系统LCD驱动架构分析
转载地址:http://blog.****.net/u012452561/article/details/50352732
我们一起来看一个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结构体
- struct fb_info {
- atomic_t count;
- int node;
- 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 */
- struct fb_fix_screeninfo fix; /* Current fix */
- 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 */
- struct device *dev; /* This is this fb 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 */
- unsigned long screen_size; /* Amount of ioremapped VRAM or 0 */
- 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 similar 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;
- };
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)入口函数
- static int __init fbmem_init(void)
- {
- proc_create("fb", 0, NULL, &fb_proc_fops);
- if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
- printk("unable to get major %d for fb devs\n", FB_MAJOR);
- 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;
- }
虽然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
- static const struct file_operations fb_fops = {
- .owner = THIS_MODULE,
- .read = fb_read,
- .write = fb_write,
- .unlocked_ioctl = fb_ioctl,
- #ifdef CONFIG_COMPAT
- .compat_ioctl = fb_compat_ioctl,
- #endif
- .mmap = fb_mmap,
- .open = fb_open,
- .release = fb_release,
- #ifdef HAVE_ARCH_FB_UNMAPPED_AREA
- .get_unmapped_area = get_fb_unmapped_area,
- #endif
- #ifdef CONFIG_FB_DEFERRED_IO
- .fsync = fb_deferred_io_fsync,
- #endif
- .llseek = default_llseek,
- };
果然看到了read、write等非常熟悉的老朋友。假如应用程序发出read命令,则会触发fb_read回调函数。点进去看一下。
代码清单 input.c中的fb_read() 函数
- static ssize_t
- fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
- {
- unsigned long p = *ppos;
- struct fb_info *info = file_fb_info(file);
- u8 *buffer, *dst;
- u8 __iomem *src;
- int c, cnt = 0, err = 0;
- unsigned long total_size;
- if (!info || ! info->screen_base)
- return -ENODEV;
- if (info->state != FBINFO_STATE_RUNNING)
- return -EPERM;
- if (info->fbops->fb_read)
- return info->fbops->fb_read(info, buf, count, ppos);
- total_size = info->screen_size;
- if (total_size == 0)
- total_size = info->fix.smem_len;
- if (p >= total_size)
- return 0;
- if (count >= total_size)
- count = total_size;
- if (count + p > total_size)
- count = total_size - p;
- buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
- GFP_KERNEL);
- if (!buffer)
- return -ENOMEM;
- src = (u8 __iomem *) (info->screen_base + p);
- if (info->fbops->fb_sync)
- info->fbops->fb_sync(info);
- while (count) {
- c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
- dst = buffer;
- fb_memcpy_fromfb(dst, src, c);
- dst += c;
- src += c;
- if (copy_to_user(buf, buffer, c)) {
- err = -EFAULT;
- break;
- }
- *ppos += c;
- buf += c;
- cnt += c;
- count -= c;
- }
- kfree(buffer);
- return (err) ? err : cnt;
- }
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()
- static struct fb_info *file_fb_info(struct file *file)
- {
- struct inode *inode = file->f_path.dentry->d_inode;
- int fbidx = iminor(inode);
- struct fb_info *info = registered_fb[fbidx];
- if (info != file->private_data)
- info = NULL;
- return info;
- }
原来这个fb_info结构体是一个叫registered_fb[]数组中的一个元素,这个数组从字面上解释叫“已经注册的fb”,猜也能猜到这个数组肯定是在注册fb_info的时候赋值的。这是不是跟输入子系统中那个input_table[]数组比较类似呢。不同之处是input_table[]是在注册纯软件驱动的input_handler时赋值,而registered_fb[]是在注册fb_info这个硬件相关的结构体时赋值的。查找一个registered_fb[]数组的赋值位置。
代码清单 do_register_framebuffer()函数
- static int do_register_framebuffer(struct fb_info *fb_info)
- {
- ... ...
- fb_info->dev = device_create(fb_class, fb_info->device,
- MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
- ... ...
- registered_fb[i] = fb_info;
- ... ...
- }
在do_register_framebuffer中有两个重大发现:
1). 调用了device_craate()函数,创建了我们非常熟悉的字符型设备驱动的设备文件节点。
2). 找到了registered_fb[]的赋值位置。继续搜索do_register_framebuffer的调用位置。
代码清单 register_framebuffer()
- int register_framebuffer(struct fb_info *fb_info)
- {
- int ret;
- mutex_lock(®istration_lock);
- ret = do_register_framebuffer(fb_info);
- mutex_unlock(®istration_lock);
- return ret;
- }
原来是这个叫做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驱动”的文章贴出来。