struct fb_fix_screeninfo {
char id[ 16 ]; /* 设备名 */
unsigned long smem_start; /* frame buffer 缓冲区起始地址(物理地址) */
__u32 smem_len; /* 缓冲区长度 */
__u32 type; /* 设备类型,例如TFT或STN */ ……
__u32 visual; /* 色彩类型,真彩色、假彩色或单色 */
……
__u32 line_length; /* 屏幕上每行的字节数 */
unsigned long mmio_start; /* IO映射区起始地址(物理地址) */
__u32 mmio_len; /* IO 映射区长度 */
__u32 accel; /* 指出使用的加速卡是哪些特定的芯片 */
__u16 reserved[ 3 ]; /* 系统保留 */ };
struct fb_var_screeninfo {
__u32 xres; /* visible resolution 可见分辨率 */
__u32 yres;
__u32 xres_virtual; /* virtual resolution 虚拟分辨率 */
__u32 yres_virtual;
__u32 xoffset; /* 从虚拟分辨率到可见分辨率的偏移量 */
__u32 yoffset;
__u32 bits_per_pixel; /* 像素深度 */
__u32 grayscale; /* 灰度级 */
struct fb_bitfield red;
struct fb_bitfield green;
struct fb_bitfield blue;
struct fb_bitfield transp; /* 透明度 */
__u32 nonstd; /* 非标准像素格式 */
……
__u32 pixclock; /* 像素时钟,单位是皮秒 */
__u32 left_margin; /* 左侧边缘区 */
__u32 right_margin; /* 右侧边缘区 */
__u32 upper_margin; /* 顶部边缘区 */
__u32 lower_margin;
__u32 hsync_len; /* 水平扫描边缘区 */
__u32 vsync_len; /* 垂直扫描边缘区 */ ……. };
struct fb_info { int node; /* 设备节点 */ int flags; struct fb_var_screeninfo var; /* 当前可变参数 */ struct fb_fix_screeninfo fix; /* 当前固定参数 */ struct fb_monspecs monspecs; /* 当前监视器特征 */ struct work_struct queue; /* 帧缓冲事件队列 */ struct fb_pixmap pixmap; /* 图象硬件映射变量 */ struct fb_pixmap sprite; /* 光标硬件映射变量 */ struct fb_cmap cmap; /* 当前颜色映射变量 */ struct list_head modelist; /* 模式列表 */ struct fb_videomode * mode; /* 当前模式 */ ...... struct fb_ops * fbops; /* 该指针指向驱动函数集 */ …… struct device * dev; /* 代表此帧缓冲设备 */ …… char __iomem * screen_base; /* IO映射基址(虚地址) */ unsigned long screen_size; /* Amount of ioremapped VRAM or 0 */ void * pseudo_palette; /* 调色板内存地址 */ …… };
int ( * fb_check_var)( struct fb_var_screeninfo * var, struct fb_info * info);
int ( * fb_set_par)( struct fb_info * info);
此处所指的函数就可以对显示参数作出实质性的修改了。
4 Frambuffer驱动实现框架
这里我们不再重复大家已经比较熟悉的驱动程序注册和注销两个过程。我们以fbmem.c中的几个重要函数为对象,解释一下在注册之后到注销之前帧缓冲驱动的大致步骤。之所以选择fbmem.c中的函数,还是因为这个文件中的函数具有一定的代表性,读者在了解它们的大致结构之后,就可以举一反三,来分析和理解其它具体显卡的驱动程序了。
这几个函数分别是fb_mmap,fb_set_var和 fb_ioctl。
Fb_mmap顾名思义其任务是完成设备到系统内存(虚拟地址)之间的映射。
static int fb_mmap(struct file *file, struct vm_area_struct * vma)
{
int fbidx = iminor(file->f_path.dentry->d_inode); struct fb_info *info = registered_fb[fbidx]; struct fb_ops *fb = info->fbops;
unsigned long off;
unsigned long start;
u32 len;
if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
return -EINVAL;
off = vma->vm_pgoff << PAGE_SHIFT;
if (!fb)
return -ENODEV;
if (fb->fb_mmap) { //意思是:如果驱动程序自带了mmap函数,那就用它自己的
int res;
lock_kernel(); //上大内核锁
res = fb->fb_mmap(info, vma); //调用驱动程序自己的mmap函数
unlock_kernel();
return res;
} lock_kernel(); start = info->fix.smem_start; //注意,这里指向了设备的物理地址 len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len); //调整对齐后长度 if (off >= len) { /* 对应于I/O端口统一编址的情况 */ off -= len; if (info->var.accel_flags) { unlock_kernel(); return -EINVAL; } start = info->fix.mmio_start;//当I/O端口统一编址时就使用端口的物理地址 len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len); } unlock_kernel(); start &= PAGE_MASK; if ((vma->vm_end - vma->vm_start + off) > len) //判断虚拟内存长度是否超越实际长度 return -EINVAL; off += start; vma->vm_pgoff = off >> PAGE_SHIFT; /* 本内存页作为IO之用,已经保留 */ vma->vm_flags |= VM_IO | VM_RESERVED; fb_pgprotect(file, vma, off); //对帧缓冲的内存页进行标识,不要挪作它用 if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot)) //实际的映射操作 return -EAGAIN; return 0; }
这里的映射行为发生在驱动程序初始化阶段。请注意,这些行为是否被囊括在一个名
为mmap的函数中并不是问题的关键,我们在开发中真正应该关心的是映射所需要的前提
条件如设备物理地址的提取,映射长度的确认以及实际的映射操作。只要在驱动程序的初
始化中完成了上述动作,那就算是成功了。因此,不少显卡的驱动程序里是找不到mmap
这个函数的,但它们一样工作得很好,原因就是它们已经完成了实际的映射操作。
下面我们看看fb_set_var函数。它主要完成了显示模式、可变参数的设置。
Fb_set_var这样的函数在不同的显示驱动中的具体名称也不一样,但基本上
的功能都是完成对于模式和可变参数的控制。某些系统的驱动里fb_set_var
是不含fb_check_var这样的函数的。
int fb_set_var( struct fb_info * info, struct fb_var_screeninfo * var) { int flags = info -> flags; int ret = 0 ; ……. if ( ! info -> fbops -> fb_check_var) { * var = info -> var; goto done; } ret = info -> fbops -> fb_check_var(var, info); // 在这里对设备的诸多参数进行检查 if (ret) goto done; if ((var -> activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) { struct fb_videomode mode; …… info -> var = * var; if (info -> fbops -> fb_set_par) // 如果驱动程序自带fb_set_par函数就使用它 info -> fbops -> fb_set_par(info); // 这个函数设置例如控制寄存器等可变参数 fb_pan_display(info, & info -> var); // 硬件虚拟显示 fb_set_cmap( & info -> cmap, info); // 调色板设置 fb_var_to_videomode( & mode, & info -> var); // 把可变参数转为显示模式参数 ...... } } } done: return ret; }
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]; // 设备 struct fb_ops * fb = info -> fbops; // 设备函数指针 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) return - ENODEV; switch (cmd) { case FBIOGET_VSCREENINFO: return copy_to_user(argp, & info -> var, sizeof (var)) ? - EFAULT : 0 ; // 这里是读取可变参数 case FBIOPUT_VSCREENINFO: if (copy_from_user( & var, argp, sizeof (var))) return - EFAULT; // 尝试是否可以设置参数? acquire_console_sem(); // 控制台上锁 info -> flags |= FBINFO_MISC_USEREVENT; i = fb_set_var(info, & var); // 设置可变参数 info -> flags &= ~ FBINFO_MISC_USEREVENT; release_console_sem(); // 解锁 if (i) return i; if (copy_to_user(argp, & var, sizeof (var))) return - EFAULT; return 0 ; case FBIOGET_FSCREENINFO: return copy_to_user(argp, & info -> fix, // 读取固定参数 sizeof (fix)) ? - EFAULT : 0 ; case FBIOPUTCMAP: // 设置调色板参数 if (copy_from_user( & cmap, argp, sizeof (cmap))) return - EFAULT; return (fb_set_user_cmap( & cmap, info)); …… 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) // 如果存在自定义的fb_ioctl就使用它 return - EINVAL; return fb -> fb_ioctl(info, cmd, arg); } }