FrameBuffer代码分析
+--------+
| app |
+--------+
| vfs |
+--------+
| fbmem |
+--------+
| driver |
+--------+
先看drivers/video/fbmem.c
一般作为builtin编入内核,看下模块入口
//-------------------------------------start
#ifdef MODULE
module_init(fbmem_init);
...
#else
subsys_initcall(fbmem_init);
#endif
//--------------------------------------end
从而可知入口是fbmem_init函数
//--------------------------------------start
static int __init
fbmem_init(void)
{
//创建/proc/fb文件,提供给用户查询驱动部分信息的接口
proc_create("fb", 0, NULL, &fb_proc_fops);
//注册一个字符设备驱动,占用主设备号 FB_MAJOR,应用程序对所有fb驱动的操作就在这里的fb_fops
if (
register_chrdev(FB_MAJOR,"fb",&fb_fops))
printk("unable to get major %d for fb devs\n", FB_MAJOR);
//创建一个设备类目录/sys/class/graphics
fb_class = class_create(THIS_MODULE, "graphics");
...
return 0;
}
//--------------------------------------end
我们来看下fb_fops的定义
//--------------------------------------start
static const struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read,
.write = fb_write,
.unlocked_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
};
//--------------------------------------end
针对我们应用经常进行的操作,主要的重点是open/close/read/write/mmap/ioctl,首先我们看open操作
//--------------------------------------start
static int
fb_open(struct inode *inode, struct file *file)
__acquires(&info->lock)
__releases(&info->lock)
{
//获取次设备号,作为全局数组registered_fb的下表
int fbidx = iminor(inode);
struct fb_info *info;
int res = 0;
if (fbidx >= FB_MAX)
return -ENODEV;
info = registered_fb[fbidx];
//如果没有加载驱动,请求加载驱动
if (!info)
request_module("fb%d", fbidx);
info = registered_fb[fbidx];
...
mutex_lock(&info->lock);
...
file->private_data = info;
if (info->fbops->fb_open) {
res =
info->fbops->fb_open(info,1);
...
}
...
}
//--------------------------------------end
从代码看出,真实的操作是推迟到具体的设备驱动来实现,而找到正确的设备驱动是通过次设备号作为下标在registered_fb的全局数组中找到跟具体设备驱动相关的数据(sturct fb_info,这个在后面描述),然后调用对应的fb_open函数来进行打开操作
可以看出来fbmem是将vfs的操作转换城具体驱动的实现,从而达到给上层应用对所有fb的一致操作接口,但又能使用具体不同的设备。这个目的是通过registered_fb数组和fb_ops这2个数据来达到的。
通过其他的几个文件操作实现代码也可以验证这一点。
//--------------------------------------start
fb_mmap(struct file *file, struct vm_area_struct * vma)
__acquires(&info->lock)
__releases(&info->lock)
{
int fbidx = iminor(file->f_path.dentry->d_inode);
struct fb_info *info = registered_fb[fbidx];
struct fb_ops *fb = info->fbops;
...
if (fb->fb_mmap) {
int res;
mutex_lock(&info->lock);
res =
fb->fb_mmap(info, vma);
mutex_unlock(&info->lock);
return res;
}
...
}
//--------------------------------------end
read操作
//--------------------------------------start
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];
...
if (info->fbops->fb_read)
return info->fbops->fb_read(info, buf, count, ppos);
//如果没有对应的fb_write函数,则通过info中的screen_base来直接进行内存操作
}
//--------------------------------------end
write操作
//--------------------------------------start
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];
...
if (info->fbops->fb_write)
return info->fbops->fb_write(info, buf, count, ppos);
//如果没有对应的fb_write函数,则通过info中的screen_base来直接进行内存操作
}
//--------------------------------------end
ioctl操作 fb_ioctl--->do_fb_ioctl
//--------------------------------------start
//--------------------------------------end
至于registered_fb数组中的值从哪来的,就牵扯到下面要说的具体设备驱动,这里以iMx515的framebuffer驱动为例
看下驱动模块入口
//--------------------------------------start
module_init(mxcfb_init);
static struct platform_driver mxcfb_driver = {
.driver = {
.name = MXCFB_NAME,
},
.probe = mxcfb_probe,
.remove = mxcfb_remove,
.suspend = mxcfb_suspend,
.resume = mxcfb_resume,
};
int __init mxcfb_init(void)
{
...
ret = platform_driver_register(&mxcfb_driver);
}
//--------------------------------------end
这里注册platform_driver_register函数,如果找到匹配的设备,在iMx515中匹配设备的声明在
//--------------------------------------start
static struct mxc_fb_platform_data fb_data[] = {
{
.interface_pix_fmt = IPU_PIX_FMT_RGB666,
},
{
.interface_pix_fmt = IPU_PIX_FMT_YUV444,
},
};
static struct platform_device mxc_fb_device[] = {
{
.name = "mxc_sdc_fb",
.id = 0,
.dev = {
.release = mxc_nop_release,
.coherent_dma_mask = 0xFFFFFFFF,
.platform_data = &fb_data[0],
},
},
{
.name = "mxc_sdc_fb",
.id = 1,
.dev = {
.release = mxc_nop_release,
.coherent_dma_mask = 0xFFFFFFFF,
.platform_data = &fb_data[1],
},
},
{
.name = "mxc_sdc_fb",
.id = 2,
.dev = {
.release = mxc_nop_release,
.coherent_dma_mask = 0xFFFFFFFF,
},
},
};
//--------------------------------------end
则进入probe函数
//--------------------------------------start
//驱动具体实现的文件操作函数
static struct fb_ops mxcfb_ops = {
.owner = THIS_MODULE,
.fb_set_par = mxcfb_set_par,
.fb_check_var = mxcfb_check_var,
.fb_setcolreg = mxcfb_setcolreg,
.fb_pan_display = mxcfb_pan_display,
.fb_ioctl = mxcfb_ioctl,
.fb_mmap = mxcfb_mmap,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
.fb_blank = mxcfb_blank,
};
static int mxcfb_probe(struct platform_device *pdev)
{
struct fb_info *fbi;
struct mxcfb_info *mxcfbi;
struct mxc_fb_platform_data *plat_data = pdev->dev.platform_data;
struct resource *res;
int ret = 0;
/*
* Initialize FB structures
*/
//通过framebuffer_alloc来分配空间,给info的fb_ops的参数为mxcfb_ops
fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops);
if (!fbi) {
ret = -ENOMEM;
goto err0;
}
mxcfbi = (struct mxcfb_info *)fbi->par;
//第一次进这里
if (!g_dp_in_use) {
mxcfbi->ipu_ch_irq = IPU_IRQ_BG_SYNC_EOF;
mxcfbi->ipu_ch = MEM_BG_SYNC;
mxcfbi->blank = FB_BLANK_UNBLANK;
} else {
mxcfbi->ipu_ch_irq = IPU_IRQ_DC_SYNC_EOF;
mxcfbi->ipu_ch = MEM_DC_SYNC;
mxcfbi->blank = FB_BLANK_POWERDOWN;
}
//fb的索引号
mxcfbi->ipu_di = pdev->id;
if (pdev->id == 0) {
ipu_disp_set_global_alpha(mxcfbi->ipu_ch, true, 0x80);
ipu_disp_set_color_key(mxcfbi->ipu_ch, false, 0);
strcpy(fbi->fix.id, "DISP3 BG");
if (!g_dp_in_use)
if (ipu_request_irq(IPU_IRQ_BG_ALPHA_SYNC_EOF,
mxcfb_alpha_irq_handler, 0,
MXCFB_NAME, fbi) != 0) {
dev_err(&pdev->dev, "Error registering BG "
"alpha irq handler.\n");
ret = -EBUSY;
goto err1;
}
g_dp_in_use = true;
} else if (pdev->id == 1) {
strcpy(fbi->fix.id, "DISP3 BG - DI1");
if (!g_dp_in_use)
if (ipu_request_irq(IPU_IRQ_BG_ALPHA_SYNC_EOF,
mxcfb_alpha_irq_handler, 0,
MXCFB_NAME, fbi) != 0) {
dev_err(&pdev->dev, "Error registering BG "
"alpha irq handler.\n");
ret = -EBUSY;
goto err1;
}
g_dp_in_use = true;
} else if (pdev->id == 2) { /* Overlay */
mxcfbi->ipu_ch_irq = IPU_IRQ_FG_SYNC_EOF;
mxcfbi->ipu_ch = MEM_FG_SYNC;
mxcfbi->ipu_di = -1;
mxcfbi->overlay = true;
mxcfbi->blank = FB_BLANK_POWERDOWN;
strcpy(fbi->fix.id, "DISP3 FG");
if (ipu_request_irq(IPU_IRQ_FG_ALPHA_SYNC_EOF,
mxcfb_alpha_irq_handler, 0,
MXCFB_NAME, fbi) != 0) {
dev_err(&pdev->dev, "Error registering FG alpha irq "
"handler.\n");
ret = -EBUSY;
goto err1;
}
}
//在驱动内部保存fb_info的消息,并对应pdev->id的索引
mxcfb_info[pdev->id] = fbi;
//申请相应的中断
if (ipu_request_irq(mxcfbi->ipu_ch_irq, mxcfb_irq_handler, 0,
MXCFB_NAME, fbi) != 0) {
dev_err(&pdev->dev, "Error registering BG irq handler.\n");
ret = -EBUSY;
goto err1;
}
ipu_disable_irq(mxcfbi->ipu_ch_irq);
//映射显存空间,但从上面的代码块可以看出没有IORESOURCE_MEM资源,显存地址由后面的代码分配
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res) {
fbi->fix.smem_len = res->end - res->start + 1;
fbi->fix.smem_start = res->start;
fbi->screen_base = ioremap(fbi->fix.smem_start, fbi->fix.smem_len);
}
/* Need dummy values until real panel is configured */
fbi->var.xres = 240;
fbi->var.yres = 320;
//具体的LCD相关参数在下面3个步骤里发现
//1. 从上面那个代码块的定义可知,plat_data->mode_str为空
if (!fb_mode && plat_data && plat_data->mode_str)
fb_find_mode(&fbi->var, fbi, plat_data->mode_str, NULL, 0, NULL,
default_bpp);
//2. fb_mode由内核命令行参数传入video=mxcfb:1024x768M-16@60
if (fb_mode)
fb_find_mode(&fbi->var, fbi, fb_mode, NULL, 0, NULL,
default_bpp);
//3. 进入这里
if (plat_data) {
mxcfbi->ipu_di_pix_fmt = plat_data->interface_pix_fmt;
//但下面的plat_data->mode不满足
if (!fb_mode && plat_data->mode)
fb_videomode_to_var(&fbi->var, plat_data->mode);
}
/* Default Y virtual size is 2x panel size */
fbi->var.yres_virtual = fbi->var.yres * 2;
mxcfb_check_var(&fbi->var, fbi);
//通过上面的查找和检测过程 fb_info的var部分的值被确定下
/* Default Y virtual size is 2x panel size */
fbi->var.yres_virtual = fbi->var.yres * 2;
mxcfb_set_fix(fbi);
//这里分配显存空间,见后面的实现代码块
/* alocate fb first */
if (!res)
if (mxcfb_map_video_memory(fbi) < 0)
return -ENOMEM;
//执行到这里fb_info的fix部分成员值被确定下来
//这里将具体驱动里的fb_info注册到fb_mem中的registered_fb数组中去
ret = register_framebuffer(fbi);
if (ret < 0)
goto err2;
platform_set_drvdata(pdev, fbi);
ret = device_create_file(fbi->dev, &dev_attr_fsl_disp_property);
if (ret)
dev_err(&pdev->dev, "Error %d on creating file\n", ret);
return 0;
err2:
ipu_free_irq(mxcfbi->ipu_ch_irq, fbi);
err1:
fb_dealloc_cmap(&fbi->cmap);
framebuffer_release(fbi);
err0:
return ret;
}
//--------------------------------------end
上面的显存物理地址分配由如下函数实现
//--------------------------------------start
static int mxcfb_map_video_memory(struct fb_info *fbi)
{
if (fbi->fix.smem_len < fbi->var.yres_virtual * fbi->fix.line_length)
fbi->fix.smem_len = fbi->var.yres_virtual *
fbi->fix.line_length;
fbi->screen_base = dma_alloc_writecombine(fbi->device,
fbi->fix.smem_len,
(dma_addr_t *)&fbi->fix.smem_start,
GFP_DMA);
...
fbi->screen_size = fbi->fix.smem_len;
/* Clear the screen */
memset((char *)fbi->screen_base, 0, fbi->fix.smem_len);
return 0;
}
//--------------------------------------end
在fb video mode的查找中,对模式的指定字符串格式如下:
x[M][R][-][@][i][m] or [-][@]
例子:1024x768MR-8@60m - Reduced blank with margins at 60Hz
在上面函数的最后部分的register_framebuffer函数即将具体驱动里面的fb_info注册到fb_mem框架中去,这样对具体硬件的操作就有了者落点
//---------------------------------------start
int
register_framebuffer(struct fb_info *fb_info)
{
int i;
struct fb_event event;
struct fb_videomode mode;
//判断注册数组是否已满
if (num_registered_fb == FB_MAX)
return -ENXIO;
if (fb_check_foreignness(fb_info))
return -ENOSYS;
//增加已注册计数
num_registered_fb++;
//从注册数组中寻找一个空格
for (i = 0 ; i < FB_MAX; i++)
if (!registered_fb[i])
break;
//将空格索引保存在fb_info中,在下面作为设备驱动的次设备号
fb_info->node = i;
mutex_init(&fb_info->lock);
//创建设备节点/dev/fbn n即为索引值
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
if (IS_ERR(fb_info->dev)) {
/* Not fatal */
printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
fb_info->dev = NULL;
} else
fb_init_device(fb_info); //创建设备文件属性文件,使得用户从sys文件系统能查询或控制对应的驱动
//下面应该是硬件部分,还不清楚,有待再看下????
if (fb_info->pixmap.addr == NULL) {
fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
if (fb_info->pixmap.addr) {
fb_info->pixmap.size = FBPIXMAPSIZE;
fb_info->pixmap.buf_align = 1;
fb_info->pixmap.scan_align = 1;
fb_info->pixmap.access_align = 32;
fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
}
}
fb_info->pixmap.offset = 0;
if (!fb_info->pixmap.blit_x)
fb_info->pixmap.blit_x = ~(u32)0;
if (!fb_info->pixmap.blit_y)
fb_info->pixmap.blit_y = ~(u32)0;
if (!fb_info->modelist.prev || !fb_info->modelist.next)
INIT_LIST_HEAD(&fb_info->modelist);
fb_var_to_videomode(&mode, &fb_info->var);
fb_add_videomode(&mode, &fb_info->modelist);
//将fb_info放在前面寻找到的数组空格位置上,到这里,fbmem框架就能根据操作的
//设备得到次设备号从而在这个数组里得到具体驱动的相应操作实现
registered_fb[i] = fb_info;
//下面这2句是给关注framebuffer驱动事件的部分发送通知消息
event.info = fb_info;
fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
return 0;
}
//---------------------------------------end
到这里,设备驱动的注册过程就完成了,fbmem框架能够实现具体硬件相关的操作了
接写来描述具体驱动中,应用程序常见的ioctl操作在底层是怎么实现的。