您的位置:首页 > 运维架构 > Linux

Linux-2.6.25内核帧缓冲设备驱动分析

2012-02-07 15:05 197 查看
帧缓冲(framebuffer)设备应用于linux显示技术方面。因为linux的显示平台已经全部基于framebuffer,所以目前在linux环境下开发图形化界面、游戏、影视软件等可视化应用时都必须用到帧缓冲技术,而现在随着消费逐渐娱乐化的大趋势,可视化应用已经在产品开发中越来越重要,因此,对于帧缓冲技术的理解和掌握就非常重要了。

1Frambuffer介绍

帧缓冲在linux体系中居于上层应用和底层显示设备之间,如下图所示。它的设计意图是对上层应用屏蔽掉低层不同硬件的操作细节:对于不同厂家不同类型的显示硬件,由于各自厂商在技术上扬长避短的需要,所以在具体的细节比如寄存器数量和种类的设计上就会存在相当大的差异,而且对于各个寄存器的初始化的定义和引脚信号的定义也不近相同,这样即便是两种性能相近的产品,例如夏普3.5寸LCD模块与三星的3.5寸模块其操作细节也截然不同。如果让擅长于开发图形界面的开发人员费尽心思去琢磨属于硬件范畴的液晶模块寄存器写入时序问题,无疑是很大的浪费。因此,需要在上层开发和底层设备之间加入一个中间层以节省开发人员的时间和精力。



2Frambuffer显示原理

帧缓冲类似一个蓄水池,存放来自用户进程的数据,然后把这些数据再输入显示设备中。对于用户而言,帧缓冲就是内存中的一块区域,可以读、写、映射。只要在初始化阶段把显示设备映射到用户进程空间,可以理解为将屏幕中的每一点和帧缓冲的每一点一一对应起来。这样接下来就可以对这块内存区域填充任何已经定义的像素以及颜色,而屏幕也就可以根据刚才写入的像素及颜色呈现出五彩缤纷的画面。

32.6.25内核Frambuffer相关的数据结构

相关的数据结构从运行环境可以分为用户空间和内核空间两类。在用户空间内使用的数据结构主要有fb_fix_screeninfo和fb_var_screeninfo。在内核空间使用的主要数据结构为fb_info。

首先介绍fb_fix_screeninfo,该数据结构定义了一些系统运行期间不能改变的信息,例如设备名,屏幕的像素数量,缓冲区的首址和长度等。这类信息一般通过ioctl函数获得。下面列出了fb_fix_screeninfo的主要内容:

C/C++code
structfb_fix_screeninfo{


charid[16];/*设备名*/


unsignedlongsmem_start;/*framebuffer缓冲区起始地址(物理地址)*/


__u32smem_len;/*缓冲区长度*/


__u32type;/*设备类型,例如TFT或STN*/
……


__u32visual;/*色彩类型,真彩色、假彩色或单色*/


……


__u32line_length;/*屏幕上每行的字节数*/


unsignedlongmmio_start;/*IO映射区起始地址(物理地址)*/


__u32mmio_len;/*IO映射区长度*/


__u32accel;/*指出使用的加速卡是哪些特定的芯片*/


__u16reserved[3];/*系统保留*/
};




相对应的,fb_var_screeninfo定义了一些在系统运行期间可以改变的信息。例如像素深度、灰度级、颜色格式、时序,屏幕边缘空白区等。下表中列出了fb_var_screeninfo的主要内容:
C/C++code
structfb_var_screeninfo{


__u32xres;/*visibleresolution可见分辨率*/


__u32yres;


__u32xres_virtual;/*virtualresolution虚拟分辨率*/


__u32yres_virtual;


__u32xoffset;/*从虚拟分辨率到可见分辨率的偏移量*/


__u32yoffset;




__u32bits_per_pixel;/*像素深度*/


__u32grayscale;/*灰度级*/




structfb_bitfieldred;


structfb_bitfieldgreen;


structfb_bitfieldblue;


structfb_bitfieldtransp;/*透明度*/




__u32nonstd;/*非标准像素格式*/


……


__u32pixclock;/*像素时钟,单位是皮秒*/


__u32left_margin;/*左侧边缘区*/


__u32right_margin;/*右侧边缘区*/


__u32upper_margin;/*顶部边缘区*/


__u32lower_margin;


__u32hsync_len;/*水平扫描边缘区*/


__u32vsync_len;/*垂直扫描边缘区*/
…….
};


下图标出了各种边缘区在整个屏幕上的位置。



内核级Fb_info

C/C++code
structfb_info{
intnode;/*设备节点*/
intflags;
structfb_var_screeninfovar;/*当前可变参数*/
structfb_fix_screeninfofix;/*当前固定参数*/
structfb_monspecsmonspecs;/*当前监视器特征*/
structwork_structqueue;/*帧缓冲事件队列*/
structfb_pixmappixmap;/*图象硬件映射变量*/
structfb_pixmapsprite;/*光标硬件映射变量*/
structfb_cmapcmap;/*当前颜色映射变量*/
structlist_headmodelist;/*模式列表*/
structfb_videomode*mode;/*当前模式*/
......
structfb_ops*fbops;/*该指针指向驱动函数集*/
……
structdevice*dev;/*代表此帧缓冲设备*/
……
char__iomem*screen_base;/*IO映射基址(虚地址)*/
unsignedlongscreen_size;/*AmountofioremappedVRAMor0*/
void*pseudo_palette;/*调色板内存地址*/
……
};


fb_info是显示驱动工作的主要载体,它定义了当前显示驱动和控制台有关的全部信息。显示驱动的实现形式就是先初始化fb_info各项,用来设置LCD控制器。以后大部分工作是:应用层通过ioctl系统调用fb_ops中的函数,来获得或修改fix、var变量中值,再写到寄存器中;或修改调色板等操作。如果在应用层中将要显示的两帧图像使用的调色板不同,就由fb_ops中的函数实现,后者获得调色板在内存的地址,修改其中的像素值来实现。

Fb_ops里面的函数指针很多,这些函数指针所指向的一般是各个硬件显卡自带的底层驱动函数,如果读者要自己开发一种专门的显卡,在fb_ops这个结构里必然要用指针把这种显卡专用的那些函数列出来。我们这里只简单介绍其中两个函数指针:

C/C++code
int(*fb_check_var)(structfb_var_screeninfo*var,structfb_info*info);


此指针指向的函数是用来检查前面提到的可变显示参数的,例如像素深度、边缘区宽度或深度等。注意这里的不会修改参数。

C/C++code
int(*fb_set_par)(structfb_info*info);


此处所指的函数就可以对显示参数作出实质性的修改了。

4Frambuffer驱动实现框架

这里我们不再重复大家已经比较熟悉的驱动程序注册和注销两个过程。我们以fbmem.c中的几个重要函数为对象,解释一下在注册之后到注销之前帧缓冲驱动的大致步骤。之所以选择fbmem.c中的函数,还是因为这个文件中的函数具有一定的代表性,读者在了解它们的大致结构之后,就可以举一反三,来分析和理解其它具体显卡的驱动程序了。

这几个函数分别是fb_mmap,fb_set_var和fb_ioctl。

Fb_mmap顾名思义其任务是完成设备到系统内存(虚拟地址)之间的映射。

C/C++code
staticint
fb_mmap(structfile*file,structvm_area_struct*vma)


{


	intfbidx=iminor(file->f_path.dentry->d_inode);
structfb_info*info=registered_fb[fbidx];
structfb_ops*fb=info->fbops;


	unsignedlongoff;


	unsignedlongstart;


	u32len;




	if(vma->vm_pgoff>(~0UL>>PAGE_SHIFT))


		return-EINVAL;


	off=vma->vm_pgoff<<PAGE_SHIFT;


	if(!fb)


		return-ENODEV;


	if(fb->fb_mmap){//意思是:如果驱动程序自带了mmap函数,那就用它自己的


		intres;


		lock_kernel();//上大内核锁


		res=fb->fb_mmap(info,vma);//调用驱动程序自己的mmap函数


unlock_kernel();


		returnres;


	}

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;
return0;
}




这里的映射行为发生在驱动程序初始化阶段。请注意,这些行为是否被囊括在一个名

为mmap的函数中并不是问题的关键,我们在开发中真正应该关心的是映射所需要的前提

条件如设备物理地址的提取,映射长度的确认以及实际的映射操作。只要在驱动程序的初

始化中完成了上述动作,那就算是成功了。因此,不少显卡的驱动程序里是找不到mmap

这个函数的,但它们一样工作得很好,原因就是它们已经完成了实际的映射操作。

下面我们看看fb_set_var函数。它主要完成了显示模式、可变参数的设置。

Fb_set_var这样的函数在不同的显示驱动中的具体名称也不一样,但基本上

的功能都是完成对于模式和可变参数的控制。某些系统的驱动里fb_set_var

是不含fb_check_var这样的函数的。

C/C++code
int
fb_set_var(structfb_info*info,structfb_var_screeninfo*var)
{
intflags=info->flags;
intret=0;

…….
if(!info->fbops->fb_check_var){
*var=info->var;
gotodone;
}

ret=info->fbops->fb_check_var(var,info);//在这里对设备的诸多参数进行检查

if(ret)
gotodone;

if((var->activate&FB_ACTIVATE_MASK)==FB_ACTIVATE_NOW){
structfb_videomodemode;

……
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:
returnret;
}


Fb_ioctl函数汇集了很多的功能,包括从内核读取显示设备参数(可变的和固定的都有),设置参数(就是调用上面提到的fb_set_var函数)。这些功能一般没有什么分类方面的限制,开发人员可以把各种自己实现的功能都一古脑放进fb_ioctl中。而且,开发人员也完全可以抛开系统提供的这个fb_ioctl转而实现自己的fb_ioctl。

C/C++code
staticint
fb_ioctl(structinode*inode,structfile*file,unsignedintcmd,
unsignedlongarg)
{
intfbidx=iminor(inode);
structfb_info*info=registered_fb[fbidx];//设备
structfb_ops*fb=info->fbops;//设备函数指针
structfb_var_screeninfovar;//可变参数
structfb_fix_screeninfofix;//固定参数
structfb_con2fbmapcon2fb;
structfb_cmap_usercmap;//调色板
structfb_eventevent;
void__user*argp=(void__user*)arg;
inti;

if(!fb)
return-ENODEV;
switch(cmd){
caseFBIOGET_VSCREENINFO:
returncopy_to_user(argp,&info->var,
sizeof(var))?-EFAULT:0;//这里是读取可变参数
caseFBIOPUT_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)returni;
if(copy_to_user(argp,&var,sizeof(var)))
return-EFAULT;
return0;
caseFBIOGET_FSCREENINFO:
returncopy_to_user(argp,&info->fix,//读取固定参数
sizeof(fix))?-EFAULT:0;
caseFBIOPUTCMAP://设置调色板参数
if(copy_from_user(&cmap,argp,sizeof(cmap)))
return-EFAULT;
return(fb_set_user_cmap(&cmap,info));

……
caseFBIOBLANK:
acquire_console_sem();
info->flags|=FBINFO_MISC_USEREVENT;
i=fb_blank(info,arg);//关闭显示器
info->flags&=~FBINFO_MISC_USEREVENT;
release_console_sem();
returni;
default:
if(fb->fb_ioctl==NULL)//如果存在自定义的fb_ioctl就使用它
return-EINVAL;
returnfb->fb_ioctl(info,cmd,arg);
}
}



从上面的叙述过程中可以看到,对于帧缓冲驱动程序,尽管不同的显示设备可能有不同的特定功能,但是在向内核注册了驱动程序后运行的共同点都是要先进行物理设备与虚拟内存之间的映射(fb_mmap);在操作设备的过程中,fb_set_var由于可以对于设备的运行参数进行控制因而尤为重要。类似的设备参数读写函数还有fb_check_var等,这些函数一般会被包含在fb_ioctl函数中并被调用。而上述参数的操作对象就是我们在上一节中已经介绍的fb_info,fb_fix_screeninfo,fb_var_screeninfo
等数据结构。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: