exynos 4412 Framebuffer驱动详解


 本文参考了http://blog.chinaunix.net/uid-28328633-id-3565345.html
 文中牵扯到一些android fence的知识。这里不做赘述。 请参考相关文章:
 http://blog.csdn.net/ear5cm/article/details/45093807
 http://blog.csdn.net/fuyajun01/article/details/44261495
 也牵扯到一些ION,iommu,dma_buf的知识,请参考
 http://blog.chinaunix.net/uid-27411029-id-3642988.html
 http://blog.csdn.net/crazyjiang/article/details/7927260
 http://blog.csdn.net/zirconsdu/article/details/8965962
 
 
 

1,帧缓冲设备在Linux中也可以看做是一个完整的子系统,

大体由fbmem.c和xxxfb.c(对应我们的s3cfb.c) 组成。向上给应用程序提供完善的设备文件

操作接口(即对FrameBuffer设备进行read、 write、 ioctl等操作),接口在Linux提供的fbmem.c文件中实现;向下提供了硬件操作的接口,只是这些接口Linux并没有提供实现,因为这要根据具体的LCD控制器硬件进行设置,所以这就是我们要做的事情了(即s3c-fb.c部分的实现)。

exynos 4412的驱动代码里,framebuffer主要代码在driver/video/,文件名是s3c-fb.c 

exynos 4412显示控制器可以控制0~5个windows,代码中分给它们分别编号win0, win1,win2......

这里一个window就对应一个独立的驱动控制个体, 每个framebuffer有自己的一个FBI(fb_info)结构,
显示控制器对应的抽象结构是s3c_fb结构体, window对应的抽象结构是s3c_fb_win结构体。


2. 数据结构及接口函数
从帧缓冲设备驱动程序结构看,该驱动主要跟fb_info结构体有关,该结构体记录了帧缓冲设备的全部信息,包括设备的设置参数、状态以及对底层硬件操作的函数指针。
在Linux中,每一个帧缓冲设备都必须对应一个fb_info, fb_info在/linux/fb.h中的定义如下: (只列出重要的一些)
struct fb_info {
int node;
int flags;
struct fb_var_screeninfo var;/*LCD可变参数结构体*/
struct fb_fix_screeninfo fix;/*LCD固定参数结构体*/
struct fb_monspecs monspecs; /*LCD显示器标准*/
struct work_struct queue; /*帧缓冲事件队列*/
struct fb_pixmap pixmap; /*图像硬件 mapper*/
struct fb_pixmap sprite; /*光标硬件 mapper*/
struct fb_cmap cmap; /*当前的颜色表*/
struct fb_videomode *mode; /*当前的显示模式*/
#ifdef CONFIG_FB_BACKLIGHT
struct backlight_device *bl_dev;/*对应的背光设备*/
struct mutex bl_curve_mutex;
u8 bl_curve[FB_BACKLIGHT_LEVELS];/*背光调整*/
#endif
#ifdef CONFIG_FB_DEFERRED_IO
struct delayed_work deferred_work;
struct fb_deferred_io *fbdefio;
#endif
struct fb_ops *fbops; /*对底层硬件操作的函数指针*/
struct device *device;
struct device *dev; /*fb设备*/
int class_flag;
#ifdef CONFIG_FB_TILEBLITTING
struct fb_tile_ops *tileops; /*图块Blitting*/
#endif
char __iomem *screen_base; /*虚拟基地址*/
unsigned long screen_size; /*LCD IO映射的虚拟内存大小*/
void *pseudo_palette; /*伪 16色颜色表*/
#define FBINFO_STATE_RUNNING 0
#define FBINFO_STATE_SUSPENDED 1
u32 state; /*LCD的挂起或恢复状态*/
void *fbcon_par;
void *par;
};


其中,比较重要的成员有struct fb_var_screeninfo var、 struct fb_fix_screeninfo fix和
struct fb_ops*fbops,他们也都是结构体。
fb_var_screeninfo结构体主要记录用户可以修改的控制器的参数,比如屏幕的分辨率
和每个像素的比特数等,该结构体定义如下:
struct fb_var_screeninfo {
__u32 xres; /*可见屏幕一行有多少个像素点*/
__u32 yres; /*可见屏幕一列有多少个像素点*/
__u32 xres_virtual; /*虚拟屏幕一行有多少个像素点*/
__u32 yres_virtual; /*虚拟屏幕一列有多少个像素点*/
__u32 xoffset; /*虚拟到可见屏幕之间的行偏移*/
__u32 yoffset; /*虚拟到可见屏幕之间的列偏移*/
__u32 bits_per_pixel; /*每个像素的位数即 BPP*/
__u32 grayscale; /*非 0时,指的是灰度*/
struct fb_bitfield red; /*fb缓存的 R位域*/
struct fb_bitfield green; /*fb缓存的 G位域*/
struct fb_bitfield blue; /*fb缓存的B位域*/
struct fb_bitfield transp; /*透明度*/
__u32 nonstd; /* != 0 非标准像素格式*/
__u32 activate;
__u32 height; /*高度*/
__u32 width; /*宽度*/
__u32 accel_flags;
/*定时:除了 pixclock本身外,其他的都以像素时钟为单位*/
__u32 pixclock; /*像素时钟(皮秒)*/
__u32 left_margin; /*行切换,从同步到绘图之间的延迟*/
__u32 right_margin; /*行切换,从绘图到同步之间的延迟*/
__u32 upper_margin; /*帧切换,从同步到绘图之间的延迟*/
__u32 lower_margin; /*帧切换,从绘图到同步之间的延迟*/
__u32 hsync_len; /*水平同步的长度*/
__u32 vsync_len; /*垂直同步的长度*/
__u32 sync;
__u32 vmode;
__u32 rotate;
__u32 reserved[5]; /*保留*/
};


而fb_fix_screeninfo结构体又主要记录用户不可以修改的控制器的参数,比如屏幕缓
冲区的物理地址和长度等,该结构体的定义如下:
struct fb_fix_screeninfo {
char id[16]; /*字符串形式的标示符 */
unsigned long smem_start; /*fb缓存的开始位置 */
__u32 smem_len; /*fb缓存的长度 */
__u32 type; /*看FB_TYPE_* */
__u32 type_aux; /*分界*/
__u32 visual; /*看FB_VISUAL_* */
__u16 xpanstep; /*如果没有硬件panning就赋值为0 */
__u16 ypanstep; /*如果没有硬件panning就赋值为0 */
__u16 ywrapstep; /*如果没有硬件ywrap就赋值为0 */
__u32 line_length; /*一行的字节数 */
unsigned long mmio_start; /*内存映射IO的开始位置*/
__u32 mmio_len; /*内存映射IO的长度*/
__u32 accel;
__u16 reserved[3]; /*保留*/
};


fb_ops结构体是对底层硬件操作的函数指针,该结构体中定义了对硬件的操作有:(这里只列出了常用的操作)
struct fb_ops {
struct module *owner;
//检查可变参数并进行设置
int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
//根据设置的值进行更新,使之有效
int (*fb_set_par)(struct fb_info *info);
//设置颜色寄存器
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp, struct fb_info *info);
//显示空白
int (*fb_blank)(int blank, struct fb_info *info);
//矩形填充
void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
//复制数据
void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
//图形填充
void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);
};



3. LCD参数配置
在 exynos 4412中,LCD控制器被集成在芯片的内部作为一个相对独立的单元,所以 Linux把它看做是一个平台设备,故在内核代码/arch/arm/plat-samsung/devs.c中定义有 LCD相关的平台设备及资源,代码如下:
/* LCD Controller */
//LCD控制器的资源信息
static struct resource s5p_fimd0_resource[] = {
	[0] = DEFINE_RES_MEM(S5P_PA_FIMD0, SZ_32K),
	[1] = DEFINE_RES_IRQ(IRQ_FIMD0_VSYNC),
	[2] = DEFINE_RES_IRQ(IRQ_FIMD0_FIFO),
	[3] = DEFINE_RES_IRQ(IRQ_FIMD0_SYSTEM),
};


struct platform_device s5p_device_fimd0 = {
	.name		= "s5p-fb",
	.id		= 0,
	.num_resources	= ARRAY_SIZE(s5p_fimd0_resource),
	.resource	= s5p_fimd0_resource,
	.dev		= {
		.dma_mask		= &samsung_device_dma_mask,
		.coherent_dma_mask	= DMA_BIT_MASK(32),
	},
};

除此之外, Linux还在/arch/arm/mach-exynos/include/mach/s3cfb.h中为LCD平台设备定义了一个s3cfb_lcd结构体,该结构体主要是记录LCD的硬件参数信息(比如LCD的屏幕尺寸、 屏幕信息、 可变的屏幕参数、 LCD配置寄存器等),这样在写驱动的时候就直接使用这个结构体。下面,我们来看一下内核是如果使用这个结构体的。在/arch/arm/mach-exynos/tiny4412-lcds.c中定义有:
/* LCDdriver info */
//LCD硬件的配置信息,这些参数要根据具体的LCD屏进行设置
static struct s3cfb_lcd wvga_s70 = {
	.width = 800,
	.height = 480,
	.p_width = 155,
	.p_height = 93,
	.bpp = 24,
	.freq = 63,
	.timing = {
		.h_fp = 80,
		.h_bp = 36,
		.h_sw = 10,
		.v_fp = 22,
		.v_fpe = 1,
		.v_bp = 15,
		.v_bpe = 1,
		.v_sw = 8,
	},
	.polarity = {
		.rise_vclk = 1,
		.inv_hsync = 1,
		.inv_vsync = 1,
		.inv_vden = 0,
	},
};


4. Framebuffer设备注册
S3c-fb.c中的s3c_fb_probe设备探测,是驱动注册的主要函数,
/*定义一个结构体用来维护驱动程序中各函数中用到的变量先别看结构体要定义这些成员,到各函数使用的地方就明白了*/

static int __devinit s3c_fb_probe(struct platform_device *pdev)
{


	platid = platform_get_device_id(pdev);
	fbdrv = (struct s3c_fb_driverdata *)platid->driver_data;
	pd = pdev->dev.platform_data;
	sfb = devm_kzalloc(dev, sizeof(struct s3c_fb), GFP_KERNEL);  //设置FB描述信息结构体
	sfb->dev = dev;
	sfb->pdata = pd;
	sfb->variant = fbdrv->variant;
	spin_lock_init(&sfb->slock);
#if defined(CONFIG_FB_ION_EXYNOS)
	mutex_init(&sfb->output_lock);
	INIT_LIST_HEAD(&sfb->update_regs_list);
	mutex_init(&sfb->update_regs_list_lock);
	init_kthread_worker(&sfb->update_regs_worker);
	sfb->update_regs_thread = kthread_run(kthread_worker_fn,
			&sfb->update_regs_worker, "s3c-fb");
	init_kthread_work(&sfb->update_regs_work, s3c_fb_update_regs_handler);  //将s3c_fb_update_regs_handler绑定到内核线程中。周期性调用。
	sfb->timeline = sw_sync_timeline_create("s3c-fb");  //创建timeline,提供给releasefence使用
	sfb->timeline_max = 1;
	/* XXX need to cleanup on errors */
#endif
	sfb->bus_clk = clk_get(dev, "lcd");  //为外部LCD 提供时钟
	clk_enable(sfb->bus_clk);
	if (!sfb->variant.has_clksel) {
		sfb->lcd_clk = clk_get(dev, "sclk_fimd");  //为FIMD模块提供时钟
		clk_enable(sfb->lcd_clk);
	}
	pm_runtime_enable(sfb->dev);
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);  //得到地址,中断号信息
	sfb->regs = devm_request_and_ioremap(dev, res);
	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);  //得到地址,中断号信息
	sfb->irq_no = res->start;
	ret = devm_request_irq(dev, sfb->irq_no, s3c_fb_irq,  //设置中断
			  0, "s3c_fb", sfb);
	platform_set_drvdata(pdev, sfb);
	/* setup gpio and output polarity controls */
	pd->setup_gpio();    //gpio设置
	writel(pd->vidcon1, sfb->regs + VIDCON1);    //设置VIDCON配置器的初始值


#if defined(CONFIG_FB_ION_EXYNOS)
	sfb->fb_ion_client = ion_client_create(exynos_ion_dev,"fimd");  //创建ion客户端
#endif


	s3c_fb_set_rgb_timing(sfb); //设置rgb lcd总线接口的时序
	/* we have the register setup, start allocating framebuffers */
	init_waitqueue_head(&sfb->vsync_info.wait);
	for (win = 0; win < fbdrv->variant.nr_windows; win++) {
		if (!pd->win[win])
			continue;
		ret = s3c_fb_probe_win(sfb, win, fbdrv->win[win],   //设置每个window
				       &sfb->windows[win]);
	}


#if defined(CONFIG_FB_ION_EXYNOS)
	sfb->vsync_info.thread = kthread_run(s3c_fb_wait_for_vsync_thread,	sfb, "s3c-fb-vsync"); //启动vsync的处理线程
#endif
	platform_set_drvdata(pdev, sfb);
	return 0;
}


5,window的设置
static int __devinit s3c_fb_probe_win(struct s3c_fb *sfb, unsigned int win_no,
		struct s3c_fb_win_variant *variant,
		struct s3c_fb_win **res)
{
	struct fb_var_screeninfo *var;
	struct fb_videomode initmode;
	struct s3c_fb_pd_win *windata;
	struct s3c_fb_win *win;
	struct fb_info *fbinfo;
	int ret;


	fbinfo = framebuffer_alloc(sizeof(struct s3c_fb_win) +   palette_size * sizeof(u32), sfb->dev);
	windata = sfb->pdata->win[win_no];
	initmode = *sfb->pdata->vtiming;


	win = fbinfo->par;
	*res = win;
	var = &fbinfo->var;
	win->variant = *variant;
	win->fbinfo = fbinfo;
	win->parent = sfb;
	win->windata = windata;
	win->index = win_no;    //设置各种win的信息。结构体指来指去。数据来回赋值。没啥好讲的。


	ret = s3c_fb_alloc_memory(sfb, win); //为每个win分配buffer


	/* setup the initial video mode from the window */
	initmode.xres = windata->xres;//各种赋值
	initmode.yres = windata->yres;//各种赋值
	fb_videomode_to_var(&fbinfo->var, &initmode);//各种赋值


	fbinfo->var.width	= windata->width;//各种赋值
	fbinfo->var.height	= windata->height;//各种赋值
	fbinfo->fix.type	= FB_TYPE_PACKED_PIXELS;//各种赋值
	fbinfo->fix.accel	= FB_ACCEL_NONE;//各种赋值
	fbinfo->var.activate	= FB_ACTIVATE_NOW;//各种赋值
	fbinfo->var.vmode	= FB_VMODE_NONINTERLACED;//各种赋值
	fbinfo->var.bits_per_pixel = windata->default_bpp;   //各种赋值
	fbinfo->fbops		= &s3c_fb_ops;//各种赋值
	fbinfo->flags		= FBINFO_FLAG_DEFAULT;//各种赋值


	/* prepare to actually start the framebuffer */
	ret = s3c_fb_check_var(&fbinfo->var, fbinfo);   //根据硬件能力来检查和计算各种参数


	s3c_fb_set_par(fbinfo);   //将参数设置给每个win的硬件寄存器
	return 0;
}


6,framebuffer 内存的管理与使用
下面的函数为每个window,也就是每个fb分配内存
static int __devinit s3c_fb_alloc_memory(struct s3c_fb *sfb, struct s3c_fb_win *win)
{
	struct s3c_fb_pd_win *windata = win->windata;
	unsigned int real_size, virt_size, size;
	struct fb_info *fbi = win->fbinfo;
	dma_addr_t map_dma;
#if defined(CONFIG_FB_ION_EXYNOS)
	struct ion_handle *handle;
	int fd;
	int ret;
	struct file *file;
#endif
	real_size = windata->xres * windata->yres;
	virt_size = windata->virtual_x * windata->virtual_y;
	size = (real_size > virt_size) ? real_size : virt_size;
	size *= (windata->max_bpp > 16) ? 32 : windata->max_bpp;
	size /= 8;
	fbi->fix.smem_len = size;     //计算所需要的内存size
	size = PAGE_ALIGN(size);
#if defined(CONFIG_FB_ION_EXYNOS) 
	handle = ion_alloc(sfb->fb_ion_client, (size_t)size, 0,	ION_HEAP_SYSTEM_MASK, 0);   //申请一块vmalloc的内存
	fd = ion_share_dma_buf(sfb->fb_ion_client, handle);  //设置这块内存为共享的方式
	ret = s3c_fb_map_ion_handle(sfb, &win->dma_buf_data, handle, fd); //通过iommu,将这块内存映射给FIMD,使其看到的是虚拟地址,并且是连续内存,便于DMA
	map_dma = win->dma_buf_data.dma_addr; 
#else
	fbi->screen_base = dma_alloc_writecombine(sfb->dev, size,	&map_dma, GFP_KERNEL);   //如果是非ION的内存管理方式,直接用dma的API申请大片连续内存
	memset(fbi->screen_base, 0x0, size);
#endif
	fbi->fix.smem_start = map_dma;//申请的内存基地址赋值。该地址值最重要送给寄存器


	return 0;
}



上面这段代码最核心的是内存申请这几行函数。
通过编译宏CONFIG_FB_ION_EXYNOS来控制内存的申请方式。
如果CONFIG_FB_ION_EXYNOS不生效,说明是在普通linux环境下。直接用dma申请大片连续内存即可
如果CONFIG_FB_ION_EXYNOS生效,说明是在android的内核环境下。其内存分配采用的方式就大有门道了。
这里采用的是ION_HEAP_SYSTEM_MASK来申请内存。这种内存是vmalloc的方式申请的。虚拟地址上连续,但是物理地址可能不连续。
但是我们知道,DMA对内存的要求是要连续的。那不连续的内存怎么用呢?
这里就用到了IOMMU。
IOMMU是近年来ARM新引入的一个硬件模块。和CPU使用的MMU功能一样。
目的是让FIMD这些外设使用的地址是一个虚拟地址。虚拟地址经过IOMMU的转换,才是实际的内存物理地址。
这样,即使不连续的物理地址内存,经过IOMMU映射之后,FIMD看到的就成了连续的地址了。就可以将数据从memory DMA到LCD上了。
好,我们来具体看一下IOMMU是如何映射的
static unsigned int s3c_fb_map_ion_handle(struct s3c_fb *sfb,
		struct s3c_dma_buf_data *dma, struct ion_handle *ion_handle,
		int fd)
{
	dma->dma_buf = dma_buf_get(fd);  //获得buffer信息
	dma->attachment = dma_buf_attach(dma->dma_buf, sfb->dev);  //将buffer和framebuffer设备绑定
	dma->sg_table = dma_buf_map_attachment(dma->attachment,	DMA_BIDIRECTIONAL);  //获得这块buffer的散列表。
	dma->dma_addr = iovmm_map(&s5p_device_fimd0.dev, dma->sg_table->sgl, 0,	dma->dma_buf->size); //用散列表中的物理地址,在s5p_device_fimd0设备的IOMMU上做映射
	return dma->dma_buf->size;
}


我们前面讲过,ION_HEAP_SYSTEM_MASK这种方式申请的内存。虚拟地址上连续,但是物理地址可能不连续。
dma->sg_table->sgl散列表里面带的信息就包括了着每一块分散的小内存的物理地址信息。
在iovmm_map函数里面就可以把这些物理地址用映射的方式连起来。
dma_addr_t iovmm_map(struct device *dev, struct scatterlist *sg, off_t offset,size_t size)
{
	start = (dma_addr_t)gen_pool_alloc(vmm->vmm_pool, size);  //从vmm_poll里面分配一块内存空间。注意,是内存空间,不是内存。只是一个空间而已。没有实际的可用内存与之对应
	addr = start;
	do {
		phys_addr_t phys;
		size_t len;
		phys = sg_phys(sg);
		len = sg_dma_len(sg);
		/* if back to back sg entries are contiguous consolidate them */
		while (sg_next(sg) &&
		       sg_phys(sg) + sg_dma_len(sg) == sg_phys(sg_next(sg))) {
			len += sg_dma_len(sg_next(sg));
			sg = sg_next(sg);
		}
		if (offset_in_page(phys)) {
			len += offset_in_page(phys);
			phys = round_down(phys, PAGE_SIZE);
		}
		len = PAGE_ALIGN(len);
		if (len > (size - mapped_size))
			len = size - mapped_size;
		ret = iommu_map(vmm->domain, addr, phys, len, 0);  //对每块内存逐条映射
		if (ret)
			break;
		addr += len;
		mapped_size += len;
	} while ((sg = sg_next(sg)) && (mapped_size < size));  //对散列表里的每块内存逐条遍历


	region->start = start + start_off;
	region->size = size;
	INIT_LIST_HEAD(®ion->node);
	spin_lock(&vmm->lock);
	list_add(®ion->node, &vmm->regions_list);
	spin_unlock(&vmm->lock);
	return region->start;  //返回FIMD看到的经过IOMMU映射后的连续内存块的首地址。  这个首地址对FIMD来说是个虚拟地址。DMA操作就是按照这个地址来的。对于CPU来说可以理解成一个物理地址。这个“物理地址“要设置给FIMD的寄存器。
}

可以看到,是从vmm->vmm_pool这里面申请了一片内存空间。然后用iommu_map把物理地址映射到这块空间上。
映射的时候是逐条遍历每个内存块。把不连续的拼成了一个连续的。
vmm->vmm_pool这块内存空间池,是在 drivers\iommu\Exynos-iovmm.c里面的exynos_init_iovmm函数中创建并且初始化的。被固化到了#define IOVA_START 0xC0000000这个地址空间上。
我们可以看到,在” iommu_map(vmm->domain, addr, phys, len, 0) “, 物理地址和期望的FIMD看到的虚拟地址映射的前提是 vmm->domain。
由此可以猜测,一定在某个地方,把这个vmm->domain和FIMD设备做了绑定。这样才能iommu_map的实际生效对象编程硬件FIMD模块。
直接揭晓答案吧,就是在这里做的绑定:
int iovmm_activate(struct device *dev)
{
	struct exynos_iovmm *vmm = exynos_get_iovmm(dev);
	return iommu_attach_device(vmm->domain, dev);
}

到这里,我们就介绍完了,显示内存是如何申请的,以及如何告诉给FIMD的。
要注意的是,这里要为每个window申请内存。

罗嗦了这么多,为什么不在一开始使用ION_HEAP_TYPE_CARVEOUT或者ION_HEAP_TYPE_SYSTEM_CONTIG来申请内存呢?这样直接就是连续的。
我百思不得其解。

7,fence的使用与控制
我们知道,android中为了保证显存数据的准确性,使用了fence机制,来保证消费者和生产者的同步。
下面我们来看一下fb驱动中是如何处理fence的。
当SurfaceFlinger hwc通过S3CFB_WIN_CONFIG ioctl把所有layer的信息送进fb driver时
static int s3c_fb_ioctl(struct fb_info *info, unsigned int cmd,  
            unsigned long arg)  
{  
    ...  
    case S3CFB_WIN_CONFIG:  
        if (copy_from_user(&p.win_data,  
                   (struct s3c_fb_win_config_data __user *)arg,  
                   sizeof(p.win_data))) {  
            ret = -EFAULT;  
            break;  
        }  
          ret = s3c_fb_set_win_config(sfb, &p.win_data);  
          if (copy_to_user((struct s3c_fb_win_config_data __user *)arg,  
                 &p.win_data,  
                 sizeof(p.user_ion_client))) {  
            ret = -EFAULT;  
            break;  
        }  
         break;  
    ...  
}  


static int s3c_fb_set_win_config(struct s3c_fb *sfb,struct s3c_fb_win_config_data *win_data)
{
	fd = get_unused_fd();
	mutex_lock(&sfb->output_lock);
	regs = kzalloc(sizeof(struct s3c_reg_data), GFP_KERNEL);
	for (i = 0; i < sfb->variant.nr_windows && !ret; i++) {  //逐个window计算并设置参数
		struct s3c_fb_win_config *config = &win_config[i];
		struct s3c_fb_win *win = sfb->windows[i];
		bool enabled = 0;
		u32 color_map = WINxMAP_MAP | WINxMAP_MAP_COLOUR(0);
		switch (config->state) {
		case S3C_FB_WIN_STATE_BUFFER:
			ret = s3c_fb_set_win_buffer(sfb, win, config, regs); 
			//逐个window计算并设置参数,函数中把每个window对应的acquirefence设置给每个window, 各种信息都记录在regs里面
			break;
		}
	}
		mutex_lock(&sfb->update_regs_list_lock);
		sfb->timeline_max++;
		pt = sw_sync_pt_create(sfb->timeline, sfb->timeline_max);
		fence = sync_fence_create("display", pt);   //所有window都配置完成后,创建retirefence。 通知user space的app
		sync_fence_install(fence, fd);
		win_data->fence = fd;


		list_add_tail(®s->list, &sfb->update_regs_list);   //regs挂入队列
		mutex_unlock(&sfb->update_regs_list_lock);
		queue_kthread_work(&sfb->update_regs_worker,&sfb->update_regs_work);  
		//触发update_regs_worker线程中的钩子函数s3c_fb_update_regs_handler,进行最实质的显示操作
	mutex_unlock(&sfb->output_lock);
	return ret;
}

重点看一下s3c_fb_set_win_buffer函数,其中有这么一句代码:
dma_buf_data.fence = sync_fence_fdget(win_config->fence_fd);
这里就是把每个surfaceflinger 的layer对应的fence赋值给了每个window对应的结构体。

再看一下s3c_fb_update_regs_handler,该函数在update_regs_worker线程中被触发,

static void s3c_fb_update_regs_handler(struct kthread_work *work)
{
	list_for_each_entry_safe(data, next, &saved_list, list) {  //依次遍历之前挂入队列的regs任务
		s3c_fb_update_regs(sfb, data);
		list_del(&data->list);
		kfree(data);
	}
}


static void s3c_fb_update_regs(struct s3c_fb *sfb, struct s3c_reg_data *regs)
{
	for (i = 0; i < sfb->variant.nr_windows; i++) {
		old_dma_bufs[i] = sfb->windows[i]->dma_buf_data;
		if (regs->dma_buf_data[i].fence) {
			s3c_fd_fence_wait(sfb, regs->dma_buf_data[i].fence);  //等待每个window,也就是surfaceflinger中的买个layer对应的acquirefence触发。
		}
	}
	do {
		__s3c_fb_update_regs(sfb, regs);  //实质的设置寄存器。进行实质的显示
		s3c_fb_wait_for_vsync(sfb, 0);   //等待VSYNC,确保每次设置寄存器,都能和VSYNC同步
		wait_for_vsync = false;
		for (i = 0; i < sfb->variant.nr_windows; i++) {
			u32 new_start = regs->vidw_buf_start[i];
			u32 shadow_start = readl(sfb->regs + SHD_VIDW_BUF_START(i));
			if (unlikely(new_start != shadow_start)) {     //确保所有window实际显示的buffer地址和设置下去的地址一致。实际上就是保证设置给FIMD的显示地址已经正常显示
				wait_for_vsync = true;   
				break;
			}
		}
	} while (wait_for_vsync && count--);


	sw_sync_timeline_inc(sfb->timeline, 1);  //显示完毕,更新时间轴,从而触发已经等待在userspace端的 retire fence


}

总结一下,

1,APP通过s3c_fb_set_win_config设置acquire fence下来,
2,s3c_fb_set_win_config里面把参数整理整理。设置给各个window的结构体。然后构造一个retirefence告诉APP。
3,触发执行内核线程的钩子函数s3c_fb_update_regs_handler
4,s3c_fb_update_regs_handler被触发后,里面等待五个window的acquire fence全部被触发后,将刚才整理好的参数设置给寄存器。
5,等待VSYNC事件,并且等待所有设置下去的参数实际生效,数据已经被显示到屏幕
6,显存数据已经被显示了,所以可以sw_sync_timeline_inc,从而触发retire fence,通知生产者APP。

8,VSYNC中断的使用
VSYNC中断是FB驱动中的节拍器。负责触发每一帧的控制。
s3c_fb_enable_irq函数使能中断。
s3c_fb_irq响应并且处理中断。
static irqreturn_t s3c_fb_irq(int irq, void *dev_id)
{
	spin_lock(&sfb->slock);
	irq_sts_reg = readl(regs + VIDINTCON1);
	if (irq_sts_reg & VIDINTCON1_INT_FRAME) {
		/* VSYNC interrupt, accept it */      //检查中断号
		writel(VIDINTCON1_INT_FRAME, regs + VIDINTCON1);
		sfb->vsync_info.count++;
		wake_up_interruptible(&sfb->vsync_info.wait);    //触发等待线程
	}
	spin_unlock(&sfb->slock);
	return IRQ_HANDLED;
}


static int s3c_fb_wait_for_vsync(struct s3c_fb *sfb, u32 crtc)
{
	count = sfb->vsync_info.count;
#if defined(CONFIG_FB_ION_EXYNOS)
	s3c_fb_activate_vsync(sfb);
#endif
	ret = wait_event_interruptible_timeout(sfb->vsync_info.wait,
			count != sfb->vsync_info.count,    //等待被触发
			msecs_to_jiffies(VSYNC_TIMEOUT_MSEC));
#if defined(CONFIG_FB_ION_EXYNOS)
	s3c_fb_deactivate_vsync(sfb);
#endif
	return 0;
}


9,整体流程总结 

exynos 4412 Framebuffer驱动详解_第1张图片


由此图可以看出,实质是通过s3c_fb_set_win_config 函数来触发s3c_fb_updates_regs_handler函数,其中会进行等待acquire fence被触发。

被触发之后,就可以设置硬件寄存器进行实质的显示。

显示之后,等待VSYNC中断触发,来实现帧同步。

帧同步之后,表示一帧正常显示完毕,buffer不再使用,此时可以触发retire fence来通知生产者该buffer可用。

其中各种显示参数设置与获取,内存mmap,等操作,可以由图中的IOCTL云框中的函数进行单独的设置。



你可能感兴趣的:(linux,驱动开发)