framebuffer驱动分析

来源:互联网 发布:通达信股票软件编程 编辑:程序博客网 时间:2024/05/11 04:34

Linux-2.6.26

 

  其中涉及到的主要文件包括,

     fbdef_io.c mmap 机制的实现

     fb_notify.c  FB 中 notify 相关的

     fbmem.c   FB 注册注销相关方法和属性文件的相关操作

     fbsysfs.c  FB sys file system

     backlight.c  该文件在 /drivers/video/backlight 下,主要通过读写属性文件的方式,控制背光 .

当我们想编写一个 FB 设备驱动时,一个比较好的方法是注册 platform 设备,然后将 FB 设备的注册, IO 映射操作,硬件初始化等操作放在在 probe 中进行,从而整体上结构清晰。

 

一. fbmem.c

     关于平台设备驱动的在这里就不多说,只说其中的 probe 函数。 FB 设备最重要的就是填充 struct fb_info ,然后调用 register_framebuffer 注册 , (当然少不了调用 framebuffer_alloc ),至于另外的硬件初始化就看相关的文档了,说一个 IO 内存分配和映射的问题。用 request_mem_region 或者 request_region 后,调用 ioremap 函数进行映射,设备驱动程序即可访问任意的 IO 内存地址,但是 ioremap 地址不应该直接使用,而应该调用内核提供的 access 函数,这样能提高驱动的可移植性。包括 iowriteX,ioreadX 等一系列的函数。

接着来看, register_framebuffer 函数,

int register_framebuffer(struct fb_info *fb_info)

{

     // 每一个新注册的 fb_info ,都会分配一个下标“ i”, 对应的就是

registered_fb[i], 最多能注册的 FB 设备个数则为 FB_MAX, 若新注册 FB 则 number_register_fb++

     if (num_registered_fb == FB_MAX)

         return -ENXIO;

    

     num_registered_fb++;

     for (i = 0 ; i < FB_MAX; i++)

         if (!registered_fb[i])

              break;

fb_info->node = i;// 找到尚未被赋值的 register_fb ,其 i 赋值为 fb_info->node ,整个操作很重要,在整个 FB 相关的方法中,都通过这个找到相应的 fb_info  

// 调用设备模型中的 device_create ,将 device 添加入设备链表,写到这里想起这样的 OO 其实挺有意思的。

     fb_info->dev = device_create(fb_class, fb_info->device,

                        MKDEV(FB_MAJOR, i), "fb%d", i);

     if (IS_ERR(fb_info->dev)) {

     …

     } else

         fb_init_device(fb_info);// 初始化 fb 的属性文件

。。。

     registered_fb[i] = fb_info;

 

     event.info = fb_info;    // 这里就是调用 fb_notify 中的相关操作,发送消息

     fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);

     return 0;

}

     注册完 FB 后,在应用层空间调用 open 时,通过系统调用,首先调用 fbmem.c 中的 fb_open ,我们知道通过在 FB 系列设备中主设备号是相同的,要想找到不同的 FB 设备就需要通过次设备号,在 open 中就是这样完成该操作的。

     static int fb_open(struct inode *inode, struct file *file)

{

     int fbidx = iminor(inode);

     struct fb_info *info;

     int res = 0;

 

     if (fbidx >= FB_MAX)

         return -ENODEV;

     if (!(info = registered_fb[fbidx]))// 通过此设备号得到 fb_info 结构

         return -ENODEV;

     if (!try_module_get(info->fbops->owner))// 引用计数

         return -ENODEV;

     file->private_data = info; // 这里也是很多 open 函数常用的方法,将 info 作为 file 的数据,方便需要时获得。

     if (info->fbops->fb_open) {

         res = info->fbops->fb_open(info,1);// 若 FB 设备本身有 open 函数,则调用之,一般情况下, open 是不需要的

         if (res)

              module_put(info->fbops->owner);// 释放引用

     }

     return res;

}

至于 fb_read,fb_write,fb_ioctl,fb_close 类似,就不阐述。

     大家都知道之所以 framebuffer 设备驱动这么出众,主要原因还是它的 mmap 机制啊,至于 mmap 如何实现的我们在 fbdef_io 中说明。

 

二. Fbsysfs.c

     在 register_framebuffer 中调用的     fb_init_device(fb_info) ,就是初始化 fb 的属性文件,该文件其实很简单,取其中的一个属性文件来说明,

     __ATTR(bits_per_pixel, S_IRUGO|S_IWUSR, show_bpp, store_bpp),

     具体的宏的解析后的结果就不说了,之所以需要使用属性这件这种操作方式,是有好处的,一方面提供给用户空间直接操作驱动的机会,另一方面,在进行封装的驱动给上层提供接口的时候也可以通过这种方式增强可移植性,具体后面会解释。

     如果你要问,为何操作 bit_per_pixel 属性文件会调用 store_bpp ,可以看设备模型,里面说的很清楚。

     下面说说 store_bpp ,

static ssize_t store_bpp(struct device *device, struct device_attribute *attr, const char *buf, size_t count)

{

     struct fb_info *fb_info = dev_get_drvdata(device);

     struct fb_var_screeninfo var;

     char ** last = NULL;

     int err;

 

     var = fb_info->var;

 

     //simple string to unsigned long

     var.bits_per_pixel = simple_strtoul(buf, last, 0);

     if ((err = activate(fb_info, &var)))

         return err;

     return count;

}

该函数具体的在 activate 函数,我们在文件 fb_notify.c 中说明。

 

三. Fb_notify.c

在 register_framebuffer 中,有这个一个操作,

fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);

其实这是 FB 中 notify 机制。

查看该函数,不禁大惊失色,虽然这种封装确实挺不错。

int fb_notifier_call_chain(unsigned long val, void *v)

{

     return blocking_notifier_call_chain(&fb_notifier_list, val, v);

}

调用 fb_notifier_call_chain 函数,首先需要在 fb_notifer_list 中注册自己的 NB 块, NB 块就是 notifer_block ,调用

int fb_register_client(struct notifier_block *nb)

{

     return blocking_notifier_chain_register(&fb_notifier_list, nb);

}

得以实现,

那么哪里调用了 fb_register_client 呢?在 fbcon.c 和 backlight.c 中。

Fbcon.c 是 framebuffer 下的控制台显示的操作,如果在 KCONFIG 中选择了

Config_framebuffer_console 则该文件会被包含。

Backlight.c 是专门为 backlight 创建的。

像我们在 register_framebuffer 注册了之后,调用 fb_notifier_call_chain ,通知的 NB 块就是 fbcon_event_notifier

事实上,对于发送的 event , fb_notifier_list 中的每一个 NB 块都会调用自己的 notifier_call 函数尝试着进行处理,可惜的是落花有意,流水无情,也只有 fbcon_event_notifier 能处理它们了。

不过,其实 fb_notifier_list 中也只有两个 NB 块,一个是这里的 fbcon_event_notifier ,另一个则是 backlight.c 文件中提到的 bd->fb_notif

来看 fbcon.c 中的 fbcon_event_notifier 的 notifier_call 。

static struct notifier_block fbcon_event_notifier = {

     .notifier_call  = fbcon_event_notify,

};

将 fbcon_event_notify 中的一部分摘抄出来,就知道什么情况了,

switch(action) {

     case FB_EVENT_SUSPEND:

         fbcon_suspended(info);

         break;

     。。。

}

对的,不同的 event.Flag 不同的处理函数。

 

四. Fb_defio.c 和 mmap

     对于 FB 设备的 write , read, 我们知道它的用法了,看看 mmap 的实现吧。

     首先说明,如果你想用 fb 的 mmap 方法,一定要在 probe 中调用 fb_defered_io_init 函数,同时,需要定义自己的 fb_deferred_io 结构,并对其中的 delay 和 deferred_io 进行初始化。例如,

static struct fb_deferred_io hecubafb_defio = {

     .delay      = HZ,

     .deferred_io  = hecubafb_dpy_deferred_io,

};

在 fb_defferred_io_init 中,对 info->fbops->fb_mmap 进行了重定义,

将 fb_deffered_io_mmap 函数指针赋值给了 info->fbops->fb_mmap 。

同时,初始化一个工作任务,将其提交给 info->deferred_work 这个工作队列。

在   fb_deferred_io_mmap 中,初始化了 vma(virtual memory area), 比如如下所示,包括 vm_ops

vma->vm_ops = &fb_deferred_io_vm_ops;

              vma->vm_private_data = info;

当用户层试图通过 mmap 得到的地址(本身地址是个用户空间地址)进行读写操作时,由于映射的是高端内存,读写操作引起错误,此时会调用 vm_ops 中的 fb_deferred_io_fault ,该函数将调用fb_deffered_io_page 得到该地址空间所在的 page 结构,然后给 page 进行引用计数,该页结构在 vmf->page 中,其中 struct vm_fault *vmf.

接着对该页进行写操作,调用的是 fb_deffered_io_mkwrite ,通过遍历 fbdefio->pagelist ,找寻空页进行写操作。同时

schedule_delayed_work(&info->deferred_work, fbdefio->delay);

在 fbdefio->delay 的延迟后, info->deferred_work 工作运行,

而在这段延迟时间之内,实现用户层对映射的页的数据的读写,当页满时则出错,重复上述过程。

     info->deferred_work 指向的是 fb_deferred_io_work ,该函数对 fbdefio->pagelist 进行 page_mkclean 后,将写有数据的那些页挂载 handle_pagelist 链表上,然后回调 xxx_deferred_io 函数,此时通过遍历handle_pagelist 链表,将其上有数据的页写入 LCD 即可。以上就是 mmap 的详细过程。

      值得注意的是,在驱动中需要设置工作队列的 delay ,一般而言,根据 LCD 的映射空间的大小来决定,例如,

可以通过测试写 1/8 屏的时间 *8 ,来决定延时时间,这样就保证了在内存页不被上锁的时间内传输完数据。具体来说,在延迟的这段时间里面,能写的页数不变(假设 N 页),在延迟时间到来之后,物理页面将被锁定,用户层不能继续写数据到页上(如果用户层在 N ms 内未将需要写的数据写完),必须等待 deffered_io ,即 N 页的数据全写到 LCD 之后才重新开始用户层写数据。此时等待锁的时间将白白浪费,在全屏写操作的时候,所需要的写数据到页内的时间小于 delay ,则能避免浪费时间在等待上。

 

五. Backlight.c

        对于背光的控制,我一直觉得在 fbsysfs.c 中也能很好实现,不懂为什么需要重新创建一个类来控制。

     不过想想也有其合理性,对于背光的控制,如果可以通过读写属性文件的方式进行控制,这样在库文件和驱动文件之间就能做到很好的独立性,库文件只要实现支持对属性文件的读写操作即可,而驱动程序只要保证对相应的读写操作有对应的响应操作即可,代码的可移植性也变强。

     说说这个文件吧。

     首先,主要就是注册了一个类,然后就是一些属性文件,还是比较好懂的,说说在 fbnotify.c 中提到的内核消息机制吧,

     static int backlight_register_fb(struct backlight_device *bd)

{

     memset(&bd->fb_notif, 0, sizeof(bd->fb_notif));

     bd->fb_notif.notifier_call = fb_notifier_callback;

 

     return fb_register_client(&bd->fb_notif);

}

     从上面代码我们看到了 bd ( backlight_device )的初始化,并且将 bd->fb_notif 这个 notifier_block 加入了 fb_notifier_list 中,

     当有消息过来时,都会尝试调用该 LIST 中的 NB 的 notifier_call ,

     static int fb_notifier_callback(struct notifier_block *self,

                   unsigned long event, void *data)

{

     struct backlight_device *bd;

     struct fb_event *evdata = data;

 

     /* If we aren't interested in this event, skip it immediately ... */

     if (event != FB_EVENT_BLANK && event != FB_EVENT_CONBLANK)

         return 0;

 

     bd = container_of(self, struct backlight_device, fb_notif);

     mutex_lock(&bd->ops_lock); // 只允许有一个 bd->ops

     if (bd->ops)

         if (!bd->ops->check_fb ||

             bd->ops->check_fb(evdata->info)) {

              bd->props.fb_blank = *(int *)evdata->data;

              backlight_update_status(bd);

         }

     mutex_unlock(&bd->ops_lock);

     return 0;

}

     显然,如果是响应的 blank 的消息或者是 CONBLANK 的消息,则进行处理,否则直接放回,在 LIST 中进行下一个 notifier_call 的调用。

以上就是本人对 framebuffer 的粗浅分析,如果有什么地方存在问题,欢迎互相交流。

原创粉丝点击