一、LinuxFramebuffer的软件需求
1.针对SoC的LCD控制寄存器进行编程,以支持不同的LCD屏,以使该SOC的应用场景最大化。这是硬件平台相关的需求。其对应Linux源码路径arch\arm\mach-s5pv210\XXX210-lcds.c中的实现内容。
2.给用户提供一个进程空间映射到实际的显示物理内存的接口(mmap),以使应用在一次拷贝的情况下即可以将图像资源显示到LCD屏幕上。这需要SoC的MMU(内存管理单元)、Linux操作系统的内存管理、SoC的SDRAM(内存)三者支持。由于内存管理作为操作系统的公共组成部分,给其他子系统提供了独立的接口。所以我们可以认为framebuffer通过调用内存管理接口来实现映射的接口属于非平台相关的需求。
3.Framebuffer支持32个显示缓存。其在内部进行了抽象,即其向上层应用统一抽象为一个字符主设备,而不同的显示缓存即视为不同的字符从设备。对显示从设备的管理属于Framebuffer内部框架的功能。
4.Linux设备驱动支持多种类型的设备驱动,除了Framebuffer,还包括input、USB、watchdog、MTD等等,这种不同类型的设备一般都表述为不同的主设备。管理不同的主设备是由Linux设备驱动框架来完成的。另外,由于Linux把设备也认为是一个文件,因此设备驱动之上还架设了一层虚拟文件系统(VFS),因此,实际上应用层是跟framebuffer对应的VFS接口进行交互的。如下图:
对于驱动开发人员来说,其实只需要针对具体的硬件平台SoC和具体的LCD(焊接连接到该SOC的引脚上)来进行第一部分的寄存器编程(红色部分)。而第二、三、四部分内容(绿色部分)已经被抽象并实现在Linux driver发布源码中了,LCD驱动开发人员只需要理解framebuffer内部的框架和接口使用即可。其实,对于其他的设备驱动,包括按键、触摸屏、SD卡、usb等等,Linuxdriver都已经实现了大部分非平台相关的需求任务了,开发人员同样是只需要理解所属驱动的内部框架和接口使用即可。
接下来,我们就详细分析Linux Framebuffer如何实现支持第二和第三点需求的。其对应driver\video\fbmem.c等实现内容。先分析第三点驱动框架,再分析映射接口。
二、Linux Framebuffer的内部驱动框架
Linux Framebuffer的驱动框架主要涉及以下文件:
(1)drivers/video/fbmem.c。主要任务:1、创建graphics类、注册FB的字符设备驱动(主设备号是29)、提供register_framebuffer接口给具体framebuffer驱动编写着来注册fb设备的。本文件相对于fb来说,地位和作用和misc.c文件相对于杂散类设备来说一样的,结构和分析方法也是类似的。
(2)drivers/video/fbsys.c。这个文件是fbmem.c引出来的,处理fb在/sys/class/graphics/fb0目录下的一些属性文件的。
(3)drivers/video/modedb.c。这个文件是fbmem.c引出来的,管理显示模式(譬如VGA、720P等就是显示模式)的
(4)drivers/video/fb_notify.c。是fbmem.c引出来的
1. 初始化
–driver\video\fbmem.c
static int __init fbmem_init(void)
{
proc_create("fb", 0, NULL, &fb_proc_fops); //向proc文件系统报告驱动状态和参数
if (register_chrdev(FB_MAJOR,"fb",&fb_fops)) //注册字符设备驱动,主设备号是29
printk("unable to get major %d for fb devs\n", FB_MAJOR);
//创建sys/class/graphics设备类,配合mdev生成设备文件
fb_class = class_create(THIS_MODULE, "graphics");
return 0;
}
Framebuffer作为一个子系统,在fbmem_init中通过register_chrdev接口向系统注册一个主设备号位29的字符设备驱动。通过class_create创建graphics设备类,配合mdev机制生成供用户访问的设备文件(位于/dev目录)。设备文件和mdev机制详见《 Linux设备文件的创建和mdev》。
Framebuffer设备驱动的接口集fb_fops的定义为:
static const struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read,
.write = fb_write, //二次拷贝
.unlocked_ioctl = fb_ioctl,
.mmap = fb_mmap, //一次拷贝
.open = fb_open,
.release = fb_release,
}
在linux设备驱动中,所有的显示缓存设备均由framebuffer子系统内部管理,即linux设备驱动框架只认识一个主设备号为29的framebuffer设备。应用层所有针对显示缓存(最多32个)的访问均会推送给fb_fops进行进一步分发操作。
2.register_framebuffer
单个显示缓存视为一个framebuffer从设备,其在驱动加载初始化时需要通过register_framebuffer接口向framebuffer子系统注册自己。这样,当应用层要访问该从设备时,才能通过framebuffer子系统进行操作管理分发。我们跟踪一下:
int register_framebuffer(struct fb_info *fb_info)
{
int i;
struct fb_event event;
struct fb_videomode mode;
num_registered_fb++;
for (i = 0 ; i < FB_MAX; i++)
if (!registered_fb[i])
break;
fb_info->node = i;
mutex_init(&fb_info->lock);
mutex_init(&fb_info->mm_lock);
//在sys/class/grapics下创建fbx设备,用于设备文件的创建
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
fb_var_to_videomode(&mode, &fb_info->var);
fb_add_videomode(&mode, &fb_info->modelist);
registered_fb[i] = fb_info;
return 0;
}
每个从设备都需要传递一个fb_info的数据结构指针,其即代表单个显示缓存设备。从中可以看到fb_info最终会存储到全局数组struct fb_info*registered_fb[FB_MAX]中,FB_MAX是32,从这里我们也可以看出,framebuffer最多支持32个从设备。另外,每个从设备注册还会在/sys/class/graphics/设备类中创建一个设备,最终由mdev在/dev/目录中生成对应的设备文件。假设M个从设备调用register_framebuffer接口,即会在/dev中生成M个设备文件,如/dev/fb0、/dev/fb1、/dev/fb2等等。这M个设备的主设备号都是29,从设备则是0、1、2等等。
3.fb_info结构体
fb_info结构体代表单个显示缓存从设备,在调用register_framebuffer接口之前,必须要初始化其中的重要数据成员。其定义如下:
struct fb_info {
int node;
int flags;
struct mutex lock; /* Lock for open/release/ioctl funcs */
struct mutex mm_lock; /* Lock for fb_mmap and smem_* fields */
struct fb_var_screeninfo var; /* lcd可变参数结构体 */
struct fb_fix_screeninfo fix; /* lcd固定参数结构体 */
struct fb_monspecs monspecs; /* Current Monitor specs */
struct work_struct queue; /* 帧缓存事件队列 */
struct fb_pixmap pixmap; /* Image hardware mapper */
struct fb_pixmap sprite; /* Cursor hardware mapper */
struct fb_cmap cmap; /* Current cmap */
struct list_head modelist; /* mode list */
struct fb_videomode *mode; /* 显示模式 */
struct fb_ops *fbops; /* lcd底层硬件操作接口 */
struct device *device; /* 设备驱动模型 */
struct device *dev; /* This is this fb device */
int class_flag; /* private sysfs flags */
char __iomem *screen_base; /* 显示内存虚拟基地址 */
unsigned long screen_size; /* 显示内存大小 */
void *pseudo_palette; /* Fake palette of 16 colors */
}
其中,fb_var_screeninfo 和 fb_fix_screeninfo 两个结构体跟LCD硬件属性相关,fb_var_screeninfo 代表可修改的LCD显示参数,如分辨率和像素比特数;fb_fix_screeninfo 代表不可修改的LCD属性参数,如显示内存的物理地址和长度等。另外一个非常重要的成员是fb_ops,其是LCD底层硬件操作接口集。
fb_ops硬件操作接口集包含很多接口,如设置可变参数fb_set_par、设置颜色寄存器fb_setcolreg、清屏接口fb_blank、画位图接口fb_imageblit、内存映射fb_mmap等等。
fb_info结构体在调用register_framebuffer之前完成初始化。一般来说,LCD设备属于平台设备,其初始化是在平台设备驱动的probe接口完成。而LCD设备所涉及的硬件初始化(第一部分需求)则在平台设备初始化中完成。有关平台设备驱动的分析,笔者在另写一篇文章阐述。
4.fb_open接口
当一个framebuffer设备完成初始化时,其对应的fb_info结构体会在全局数组registered_fb中记录,并且位于跟从设备号相等的位置上;而且,在/dev目录下也会生成一个/dev/fbx的设备文件。以下分析假设是访问第一个framebuffer设备:
对于应用层open(“/dev/fb0”,…)访问该framebuffer设备来说,vfs先通过设备名(/dev/fb0)获得该设备的主设备(29)和从设备号(0)。而linux设备驱动框架则通过主设备29找到该设备对应的设备驱动接口集fb_fops。Linux驱动框架的分析过程请看《Linux字符设备驱动剖析》。接着linux设备驱动框架会调用fb_fops的fb_open,我们来跟踪一下:
static int fb_open(struct inode *inode, struct file *file)
__acquires(&info->lock)
__releases(&info->lock)
{
int fbidx = iminor(inode); //获得次设备号
struct fb_info *info; //代表一个framebuffer的全局数据结构
int res = 0;
if (fbidx >= FB_MAX)
return -ENODEV;
info = registered_fb[fbidx]; //找到对应的fb_info
file->private_data = info;
if (info->fbops->fb_open) { //调用info->fbops->fb_open
res = info->fbops->fb_open(info,1);
if (res)
module_put(info->fbops->owner);
}
return res;
}
这个接口很简单,即是次设备号在全局数组registered_fb中找出对应的fb_info数据结构,将其设置到file指针的私有数据中,便于之后用户访问调用其他接口如mmap、ioctl等能够直接找到对应的fb_info。最后也会调用fb_info->fb_ops->fb_open,不过这个接口一般没干啥,赋值为NULL即可。
所以,framebuffer子系统内部驱动框架即负责通过从设备号找到对应的从设备(具体LCD操作接口所在的fb_info)。
三、mmap映射
framebuffer驱动最重要的功能就是给用户提供一个进程空间映射到实际的显示物理内存的接口(mmap)。当用户图像数据buffer和内核虚拟地址空间buffer对应的都是同一块物理内存时,资源数据拷贝到用户图像数据buffer时,即是直接拷贝到显示物理内存了。
应用层mmap映射接口会经历以下层次调用:
1.sys_mmap
sys_mmap是虚拟文件系统层的映射实现,其会在用户进程虚拟空间(0到3G)申请一块虚拟内存,最终会调用到framebuffer子系统的fb_mmap,并向其传递vm_area_struct结构体指针,该结构体已经包括进程用户空间的内存地址信息。
Vfs虚拟文件系统是linux系统的重要组成部分,这里不再展开,以后再分析。
2.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;
mutex_lock(&info->mm_lock);
if (fb->fb_mmap) {
int res;
res = fb->fb_mmap(info, vma);
mutex_unlock(&info->mm_lock);
return res;
}
……
return 0;
}
该接口也是找到具体的从设备对应的LCD操作接口集,并调用fb_mmap接口。
四、接口使用
如下,操作是不是很简单?不过,大家要记得,framebuffer只负责位图显示,而很多图像都是有编码格式的,如JPG等等,需要先解码,再送给framebuffer。
int main(void)
{
int fd = -1;
struct fb_fix_screeninfo finfo = {0};
struct fb_var_screeninfo vinfo = {0};
// 第1步:打开设备
fd = open(/dev/fb0, O_RDWR);
// 第2步:获取设备的硬件信息
ioctl(fd, FBIOGET_FSCREENINFO, &finfo);
printf("smem_start = 0x%x, smem_len = %u.\n", finfo.smem_start, finfo.smem_len);
ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);
printf("xres = %u, yres = %u.\n", vinfo.xres, vinfo.yres);
printf("xres_virtual = %u, yres_virtual = %u.\n", vinfo.xres_virtual, vinfo.yres_virtual);
printf("bpp = %u.\n", vinfo.bits_per_pixel);
// 第3步:进行mmap
unsigned long len = vinfo.xres_virtual * vinfo.yres_virtual * vinfo.bits_per_pixel / 8;
printf("len = %ld\n", len);
pfb = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
printf("pfb = %p.\n", pfb);
// 第4步:进行lcd相关的操作
close(fd);
return 0;
}
写了这么多,我发现,再写一篇如何进行LCD驱动开发的文章,实现framebuffer的第一部分需求,以串起以前对驱动框架的分析,会更加清晰地掌握LCD驱动和framebuffer子系统的来龙去脉。这个可以安排在平台设备驱动的分析之后进行,因为大部分的驱动都是平台设备驱动,应该先讲平台设备驱动的框架,再回到LCD驱动开发,包括LCD平台设备相关的部分(GPIO资源、中断资源配置等)和LCD驱动部分。
原文:http://blog.csdn.net/yueqian_scut/article/details/50429113