帧缓冲设备的file_operations中的成员函数都在fbmem.c中实现,一般不需要驱动工程师修改。这里主要分析它的write、mmap和ioctl方法。
1 static ssize_t fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { 2 unsigned long p = *ppos; 3 struct fb_info *info = file_fb_info(file); // 获取相应的帧缓冲描述结构,fb_info结构 4 u8 *buffer, *src; 5 u8 __iomem *dst; 6 int c, cnt = 0, err = 0; 7 unsigned long total_size; 8 9 if (!info || !info->screen_base) 10 return -ENODEV; 11 12 if (info->state != FBINFO_STATE_RUNNING) 13 return -EPERM; 14 15 if (info->fbops->fb_write) // 如果fb_ops中定义了特定的写函数则调用之 16 return info->fbops->fb_write(info, buf, count, ppos); 17 18 /* 如果fb_ops中没有定义写函数则执行下面通用的写缓冲代码 */ 19 total_size = info->screen_size; 20 21 if (total_size == 0) 22 total_size = info->fix.smem_len; 23 24 if (p > total_size) // 写入位置超过帧缓冲大小则返回-EFBIG错误 25 return -EFBIG; 26 27 if (count > total_size) { 28 err = -EFBIG; 29 count = total_size; 30 } 31 32 if (count + p > total_size) { // 对写入大小进行调整 33 if (!err) 34 err = -ENOSPC; 35 36 count = total_size - p; 37 } 38 /* 分配用于临时存放显示缓冲区数据的buffer */ 39 buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count, 40 GFP_KERNEL); 41 if (!buffer) 42 return -ENOMEM; 43 /* 目的地址是帧缓冲虚拟地址加上偏移 */ 44 dst = (u8 __iomem *) (info->screen_base + p); 45 46 if (info->fbops->fb_sync) // 如果fb_ops中实现了同步方法则调用之 47 info->fbops->fb_sync(info); 48 49 while (count) { 50 /* 将预写数据一次读到buffer中,每次写数据不超过PAGE_SIZE大小 */ 51 c = (count > PAGE_SIZE) ? PAGE_SIZE : count; 52 src = buffer; 53 54 if (copy_from_user(src, buf, c)) { 55 err = -EFAULT; 56 break; 57 } 58 /* 将实际数据写入 */ 59 fb_memcpy_tofb(dst, src, c); 60 dst += c; 61 src += c; 62 *ppos += c; 63 buf += c; 64 cnt += c; 65 count -= c; 66 } 67 68 kfree(buffer); 69 70 return (cnt) ? cnt : err; 71 }
帧缓冲设备的mmap()操作函数比较重要,因为多数情况下访问帧缓冲设备不是通过其读写方法,而是通过mmap()系统调用将帧缓冲映射到用户空间直接访问。这样做不仅可以省去一次用户空间与内核空间的数据拷贝,而且操作起来更加方便快捷。
1 static int fb_mmap(struct file *file, struct vm_area_struct * vma) { 2 struct fb_info *info = file_fb_info(file); 3 struct fb_ops *fb; 4 unsigned long off; 5 unsigned long start; 6 u32 len; 7 8 if (!info) 9 return -ENODEV; 10 if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) 11 return -EINVAL; 12 off = vma->vm_pgoff << PAGE_SHIFT; // 取得此VMA在映射设备文件中的偏移 13 fb = info->fbops; 14 if (!fb) 15 return -ENODEV; 16 mutex_lock(&info->mm_lock); 17 if (fb->fb_mmap) { // 如果fb_ops中实现了mmap()方法则调用之 18 int res; 19 res = fb->fb_mmap(info, vma); 20 mutex_unlock(&info->mm_lock); 21 return res; 22 } 23 24 /* 如果fb_ops中没有实现mmap()方法则执行下面通用的映射代码 25 获取映射帧缓冲的物理起始地址和长度 */ 26 start = info->fix.smem_start; 27 len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len); 28 if (off >= len) { 29 /* 如果off大于帧缓冲长度,则认为映射的是内存映射IO */ 30 off -= len; 31 if (info->var.accel_flags) { 32 mutex_unlock(&info->mm_lock); 33 return -EINVAL; 34 } 35 /* 获取内存映射IO的物理起始地址和长度 */ 36 start = info->fix.mmio_start; 37 len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len); 38 } 39 mutex_unlock(&info->mm_lock); 40 start &= PAGE_MASK; // 保证页对齐 41 if ((vma->vm_end - vma->vm_start + off) > len) 42 return -EINVAL; 43 off += start; // 现在off表示的是映射设备内存实际的物理地址 44 vma->vm_pgoff = off >> PAGE_SHIFT; 45 /* This is an IO map - tell maydump to skip this VMA */ 46 vma->vm_flags |= VM_IO | VM_RESERVED; 47 vma->vm_page_prot = vm_get_page_prot(vma->vm_flags); 48 fb_pgprotect(file, vma, off); 49 /* 建立从物理页帧号为off>>PAGE_SHIFT的物理内存,到虚拟地址为vma->vm_start,大小为vma-> 50 vm_end - vma->vm_start,页保护标志为vma->vm_page_prot的映射 */ 51 if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT, 52 vma->vm_end - vma->vm_start, vma->vm_page_prot)) 53 return -EAGAIN; 54 return 0; 55 }
最后给出的是ioctl()方法的实现代码,我们需要关注的是ioctl()方法支持的命令,以及命令的实现中调用了哪些与驱动相关的接口。
static long do_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) { struct fb_ops *fb; struct fb_var_screeninfo var; struct fb_fix_screeninfo fix; struct fb_con2fbmap con2fb; struct fb_cmap cmap_from; struct fb_cmap_user cmap; struct fb_event event; void __user *argp = (void __user *)arg; long ret = 0; switch (cmd) { case FBIOGET_VSCREENINFO: if (!lock_fb_info(info)) return -ENODEV; var = info->var; unlock_fb_info(info); ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0; break; case FBIOPUT_VSCREENINFO: if (copy_from_user(&var, argp, sizeof(var))) return -EFAULT; console_lock(); if (!lock_fb_info(info)) { console_unlock(); return -ENODEV; } info->flags |= FBINFO_MISC_USEREVENT; ret = fb_set_var(info, &var); info->flags &= ~FBINFO_MISC_USEREVENT; unlock_fb_info(info); console_unlock(); if (!ret && copy_to_user(argp, &var, sizeof(var))) ret = -EFAULT; break; case FBIOGET_FSCREENINFO: if (!lock_fb_info(info)) return -ENODEV; fix = info->fix; unlock_fb_info(info); ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0; break; case FBIOPUTCMAP: if (copy_from_user(&cmap, argp, sizeof(cmap))) return -EFAULT; ret = fb_set_user_cmap(&cmap, info); break; case FBIOGETCMAP: if (copy_from_user(&cmap, argp, sizeof(cmap))) return -EFAULT; if (!lock_fb_info(info)) return -ENODEV; cmap_from = info->cmap; unlock_fb_info(info); ret = fb_cmap_to_user(&cmap_from, &cmap); break; case FBIOPAN_DISPLAY: if (copy_from_user(&var, argp, sizeof(var))) return -EFAULT; console_lock(); if (!lock_fb_info(info)) { console_unlock(); return -ENODEV; } ret = fb_pan_display(info, &var); unlock_fb_info(info); console_unlock(); if (ret == 0 && copy_to_user(argp, &var, sizeof(var))) return -EFAULT; break; case FBIO_CURSOR: ret = -EINVAL; break; 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.data = &con2fb; if (!lock_fb_info(info)) return -ENODEV; event.info = info; fb_notifier_call_chain(FB_EVENT_GET_CONSOLE_MAP, &event); unlock_fb_info(info); ret = copy_to_user(argp, &con2fb, sizeof(con2fb)) ? -EFAULT : 0; break; case FBIOPUT_CON2FBMAP: if (copy_from_user(&con2fb, argp, sizeof(con2fb))) return -EFAULT; if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES) return -EINVAL; if (con2fb.framebuffer < 0 || con2fb.framebuffer >= FB_MAX) return -EINVAL; if (!registered_fb[con2fb.framebuffer]) request_module("fb%d", con2fb.framebuffer); if (!registered_fb[con2fb.framebuffer]) { ret = -EINVAL; break; } event.data = &con2fb; console_lock(); if (!lock_fb_info(info)) { console_unlock(); return -ENODEV; } event.info = info; ret = fb_notifier_call_chain(FB_EVENT_SET_CONSOLE_MAP, &event); unlock_fb_info(info); console_unlock(); break; case FBIOBLANK: console_lock(); if (!lock_fb_info(info)) { console_unlock(); return -ENODEV; } info->flags |= FBINFO_MISC_USEREVENT; ret = fb_blank(info, arg); info->flags &= ~FBINFO_MISC_USEREVENT; unlock_fb_info(info); console_unlock(); break; default: if (!lock_fb_info(info)) return -ENODEV; fb = info->fbops; if (fb->fb_ioctl) ret = fb->fb_ioctl(info, cmd, arg); else ret = -ENOTTY; unlock_fb_info(info); } return ret; }
常用的命令有:
- FBIOGET_VSCREENINFO:获得可变的屏幕参数。
- FBIOPUT_VSCREENINFO:设置可变的屏幕参数。
- FBIOGET_FSCREENINFO:获得固定的屏幕参数设置,不能由用户设置。
- FBIOPUTCMAP:设置颜色表。
- FBIOGETCMAP:获得颜色表。