帧缓冲
(
framebuffer
)
是
Linux
系统为显示设备提供的一个接口,它将显示缓冲区抽象,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。帧缓冲设备为标准字符设备,主设备号为29,对应于
/dev/fbn
设备文件。
见: drivers\video\ fbmem.c 中的 module_init(fbmem_init); 就可知道 fbmem_init 是其字符设备加载函数,具体定义如下
其中fb_fops定义如下
同时,fb_open()定义如下
当用户使用系统调用打开设备节点时,内核就会调用 fb_open() 函数,同时帧缓冲又会调用具体设备的fbops中的fb_open()函数。
fb_read() 和 fb_write() 定义如下
当用户使用系统调用读写设备节点时,内核就会调用 fb_read() 和 fb_write() 函数,同时帧缓冲又会调用具体设备的fbops中的fb_read()和fb_write()函数。
fb_ioctl()函数定义如下
其中,也会尝试去调用 具体设备的 fbops 中的 相关函数
大家可以参考下面的结构图,它说明了 Linux 帧缓冲设备驱动的主要结构,帧缓冲设备提供给用户空间的file_operations结构体由fbmem.c中的 fb_fops实例提供,并向下提供了相应的接口,让驱动开发者挂接具体的设备驱动。
见: drivers\video\ fbmem.c 中的 module_init(fbmem_init); 就可知道 fbmem_init 是其字符设备加载函数,具体定义如下
点击(此处)折叠或打开
- static int __init
- fbmem_init(void)
- {
- create_proc_read_entry("fb", 0, NULL, fbmem_read_proc, NULL);
-
- if (register_chrdev(FB_MAJOR,"fb",&fb_fops)) //可知其file_operations操作集为fb_fops //#define FB_MAJOR 29
- 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;
- }
其中fb_fops定义如下
点击(此处)折叠或打开
- static const struct file_operations fb_fops = {
- .owner = THIS_MODULE,
- .read = fb_read,
- .write = fb_write,
- .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
- };
同时,fb_open()定义如下
点击(此处)折叠或打开
- static int
- fb_open(struct inode *inode, struct file *file)
- {
- int fbidx = iminor(inode); //获得设备的次设备号,在register_framebuffer中,次设备号被定为缓冲设备在registered_fb数组中的索引值
- struct fb_info *info;
- int res = 0;
-
- if (fbidx >= FB_MAX) //如果fbidx大于最大设备数目,则返回错误
- return -ENODEV;
- #ifdef CONFIG_KMOD
- if (!(info = registered_fb[fbidx]))
- try_to_load(fbidx);
- #endif /* CONFIG_KMOD */
- if (!(info = registered_fb[fbidx])) //如果通过fbidx无法在registered_fb中,索引到对象的fb_info结构体实例,则返回错误
- return -ENODEV;
- if (!try_module_get(info->fbops->owner))
- return -ENODEV;
- file->private_data = info; //将得到的fb_info结构体实例,赋给file->private_data,方便后续的操作,可以通过此方法获取此值
- if (info->fbops->fb_open) { //如果对应的缓冲设备有open函数,则调用。
- res = info->fbops->fb_open(info,1);
- if (res)
- module_put(info->fbops->owner);
- }
- return res;
- }
当用户使用系统调用打开设备节点时,内核就会调用 fb_open() 函数,同时帧缓冲又会调用具体设备的fbops中的fb_open()函数。
fb_read() 和 fb_write() 定义如下
点击(此处)折叠或打开
- static ssize_t
- fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
- {
- unsigned long p = *ppos;
- struct inode *inode = file->f_path.dentry->d_inode;
- int fbidx = iminor(inode);
- struct fb_info *info = registered_fb[fbidx]; //通过fbidx索引registered_fb得到设备的fb_info结构体实例
- u32 *buffer, *dst;
- u32 __iomem *src;
- int c, i, cnt = 0, err = 0;
- unsigned long total_size;
-
- if (!info || ! info->screen_base) //若info不存在 或 DMA缓冲区的虚拟地址不存在,则出错返回
- return -ENODEV;
-
- if (info->state != FBINFO_STATE_RUNNING)
- return -EPERM;
-
- if (info->fbops->fb_read) //若设备fb_ops操作集中有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; //total_size为0的话,取 info->fix.smem_len
-
- if (p >= total_size) //若 读取偏移量 > total_size,则出错返回。
- return 0;
-
- if (count >= total_size) //若 读取数目 > total_size,则修正为total_size
- count = total_size;
-
- if (count + p > total_size) //若 读取数目 + 偏移量 > total_size,则 读取数目 修正为total_size - 偏移量
- count = total_size - p;
-
- buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
- GFP_KERNEL); //动态申请内存,用于缓冲数据,最大申请PAGE_SIZE大小,后面会分段传输
- if (!buffer)
- return -ENOMEM;
-
- src = (u32 __iomem *) (info->screen_base + p); //DMA缓冲区的首地址 + 偏移量 设为源地址
-
- if (info->fbops->fb_sync) //若设备fb_ops操作集中有fb_sync函数,则调用之
- info->fbops->fb_sync(info);
-
- while (count) {
- c = (count > PAGE_SIZE) ? PAGE_SIZE : count; //缓冲大小为PAGE_SIZE,若count较大,则分段。
- dst = buffer; //目的地址为缓冲区首地址
- for (i = c >> 2; i--; ) //每次读取4字节
- *dst++ = fb_readl(src++);
- if (c & 3) { //对于最后小于4字节的数据,进行特别操作,每次传输1字节
- u8 *dst8 = (u8 *) dst;
- u8 __iomem *src8 = (u8 __iomem *) src;
-
- for (i = c & 3; i--;)
- *dst8++ = fb_readb(src8++);
-
- src = (u32 __iomem *) src8;
- }
-
- if (copy_to_user(buf, buffer, c)) { //将内核缓冲区中的数据传输给用户层
- err = -EFAULT;
- break;
- }
- *ppos += c; //?
- buf += c; //用户层接收区域需要后移
- cnt += c; //已传输数据数目需要增加
- count -= c; //读取的数目需要减少
- }
-
- kfree(buffer); //传输完毕后,释放内核缓冲区
-
- return (err) ? err : cnt;
- }
-
- static ssize_t
- fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
- {
- unsigned long p = *ppos;
- struct inode *inode = file->f_path.dentry->d_inode;
- int fbidx = iminor(inode);
- struct fb_info *info = registered_fb[fbidx]; //通过fbidx索引registered_fb得到设备的fb_info结构体实例
- u32 *buffer, *src;
- u32 __iomem *dst;
- int c, i, cnt = 0, err = 0;
- unsigned long total_size;
-
- if (!info || !info->screen_base) //若info不存在 或 DMA缓冲区的虚拟地址不存在,则出错返回
- return -ENODEV;
-
- if (info->state != FBINFO_STATE_RUNNING)
- return -EPERM;
-
- if (info->fbops->fb_write) //若设备fb_ops操作集中有fb_write函数,则调用之
- return info->fbops->fb_write(info, buf, count, ppos);
-
- total_size = info->screen_size;
-
- if (total_size == 0)
- total_size = info->fix.smem_len; //total_size为0的话,取 info->fix.smem_len
-
- if (p > total_size) //若 写入偏移量 > total_size,则出错返回。
- return -EFBIG;
-
- if (count > total_size) { //若 写入数目 > total_size,则修正为total_size
- err = -EFBIG;
- count = total_size;
- }
-
- if (count + p > total_size) { //若 读取数目 + 偏移量 > total_size,则 写入数目 修正为 total_size - 偏移量
- if (!err)
- err = -ENOSPC;
-
- count = total_size - p;
- }
-
- buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
- GFP_KERNEL); //动态申请内存,用于缓冲数据,最大申请PAGE_SIZE大小,后面会分段传输
- if (!buffer)
- return -ENOMEM;
-
- dst = (u32 __iomem *) (info->screen_base + p); //DMA缓冲区的首地址 + 偏移量 设为目的地址
-
- if (info->fbops->fb_sync) //若设备fb_ops操作集中有fb_sync函数,则调用之
- info->fbops->fb_sync(info);
-
- while (count) { //同读取操作类似,不详述
- c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
- src = buffer;
-
- if (copy_from_user(src, buf, c)) {
- err = -EFAULT;
- break;
- }
-
- for (i = c >> 2; i--; )
- fb_writel(*src++, dst++);
-
- if (c & 3) {
- u8 *src8 = (u8 *) src;
- u8 __iomem *dst8 = (u8 __iomem *) dst;
-
- for (i = c & 3; i--; )
- fb_writeb(*src8++, dst8++);
-
- dst = (u32 __iomem *) dst8;
- }
-
- *ppos += c;
- buf += c;
- cnt += c;
- count -= c;
- }
-
- kfree(buffer);
-
- return (cnt) ? cnt : err;
- }
当用户使用系统调用读写设备节点时,内核就会调用 fb_read() 和 fb_write() 函数,同时帧缓冲又会调用具体设备的fbops中的fb_read()和fb_write()函数。
fb_ioctl()函数定义如下
点击(此处)折叠或打开
- static int
- fb_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
- unsigned long arg)
- {
- int fbidx = iminor(inode);
- struct fb_info *info = registered_fb[fbidx]; //通过fbidx索引registered_fb得到设备的fb_info结构体实例
- struct fb_ops *fb = info->fbops; //得到设备的fb_ops操作集
- struct fb_var_screeninfo var;
- struct fb_fix_screeninfo fix;
- struct fb_con2fbmap con2fb;
- struct fb_cmap_user cmap;
- struct fb_event event;
- void __user *argp = (void __user *)arg;
- int i;
-
- if (!fb) //如果设备没有自定义fb_ops操作集,则返回出错
- return -ENODEV;
- switch (cmd) { //根据cmd命令,执行相应的操作
- case FBIOGET_VSCREENINFO: //获取fb_var_screeninfo(可变参数),直接将info->var传输给用户层
- return copy_to_user(argp, &info->var,
- sizeof(var)) ? -EFAULT : 0;
- case FBIOPUT_VSCREENINFO: //设置fb_var_screeninfo(可变参数)
- if (copy_from_user(&var, argp, sizeof(var))) //通过argp,将用户层传递过来的参数保存在var变量中
- return -EFAULT;
- acquire_console_sem(); //lock the console system for exclusive use
- info->flags |= FBINFO_MISC_USEREVENT;
- i = fb_set_var(info, &var); //调用fb_set_var,进行设定,FBINFO_MISC_USEREVENT用于指定需要设定参数
- info->flags &= ~FBINFO_MISC_USEREVENT;
- release_console_sem(); //unlock the console system
- if (i) return i; //若fb_set_var出错,则将错误码返回
- if (copy_to_user(argp, &var, sizeof(var))) //若没出错,则通过argp地址将var参数返回
- return -EFAULT;
- return 0;
- case FBIOGET_FSCREENINFO: //获取fb_fix_screeninfo(可变参数),直接将info->fix传输给用户层
- return copy_to_user(argp, &info->fix,
- sizeof(fix)) ? -EFAULT : 0;
- case FBIOPUTCMAP: //设置用户提供的颜色表
- if (copy_from_user(&cmap, argp, sizeof(cmap))) //通过argp,将用户设定的调色板参数保存在cmap中
- return -EFAULT;
- return (fb_set_user_cmap(&cmap, info)); //调用fb_set_user_cmap,设置调色表
- case FBIOGETCMAP: //查询用户提供的颜色表
- if (copy_from_user(&cmap, argp, sizeof(cmap)))
- return -EFAULT;
- return fb_cmap_to_user(&info->cmap, &cmap);
- case FBIOPAN_DISPLAY:
- if (copy_from_user(&var, argp, sizeof(var)))
- return -EFAULT;
- acquire_console_sem();
- i = fb_pan_display(info, &var);
- release_console_sem();
- if (i)
- return i;
- if (copy_to_user(argp, &var, sizeof(var)))
- return -EFAULT;
- return 0;
- case FBIO_CURSOR:
- return -EINVAL;
- case FBIOGET_CON2FBMAP:
- if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
- return -EFAULT;
- if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
- return -EINVAL;
- con2fb.framebuffer = -1;
- event.info = info;
- event.data = &con2fb;
- fb_notifier_call_chain(FB_EVENT_GET_CONSOLE_MAP, &event);
- return copy_to_user(argp, &con2fb,
- sizeof(con2fb)) ? -EFAULT : 0;
- case FBIOPUT_CON2FBMAP:
- if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
- return - EFAULT;
- if (con2fb.console < 0 || con2fb.console > MAX_NR_CONSOLES)
- return -EINVAL;
- if (con2fb.framebuffer < 0 || con2fb.framebuffer >= FB_MAX)
- return -EINVAL;
- #ifdef CONFIG_KMOD
- if (!registered_fb[con2fb.framebuffer])
- try_to_load(con2fb.framebuffer);
- #endif /* CONFIG_KMOD */
- if (!registered_fb[con2fb.framebuffer])
- return -EINVAL;
- event.info = info;
- event.data = &con2fb;
- return fb_notifier_call_chain(FB_EVENT_SET_CONSOLE_MAP,
- &event);
- case FBIOBLANK:
- acquire_console_sem();
- info->flags |= FBINFO_MISC_USEREVENT;
- i = fb_blank(info, arg);
- info->flags &= ~FBINFO_MISC_USEREVENT;
- release_console_sem();
- return i;
- default:
- if (fb->fb_ioctl == NULL)
- return -EINVAL;
- return fb->fb_ioctl(info, cmd, arg);
- }
- }
其中,也会尝试去调用 具体设备的 fbops 中的 相关函数
大家可以参考下面的结构图,它说明了 Linux 帧缓冲设备驱动的主要结构,帧缓冲设备提供给用户空间的file_operations结构体由fbmem.c中的 fb_fops实例提供,并向下提供了相应的接口,让驱动开发者挂接具体的设备驱动。
来源:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=11366591&id=3864559