framebuffer驱动详解

1.framebuffer介绍

1、什么是framebuffer
(1)裸机中如何操作LCD
(2)OS下操作LCD的难点
显存放在内核中还是应用中是个问题,之前讲的应用和内核之间传递数据用的是copy_from_usr,copy_to_usr,只能满足小流量的传输,显示图片并以24帧每秒播放不适合用此方法。我们用的方法是mmap,在内核空间申请一段内存作为显存,然后把这段内存的物理地址映射到应用的地址空间来。相当于应用中也有一块内存,但是这两块内存的虚拟地址不一样。但是对应同一块物理地址。
(3)framebuffer帧缓冲(简称fb)是linux内核中虚拟出的一个设备,是由lcd的硬件设备和所需要的软件设施抽象出来的(显卡、显卡驱动),这个体系综合出来构成了frambuffer体系。
(4)framebuffer向应用层提供一个统一标准接口的显示设备
(5)从驱动来看,fb是一个典型的字符设备,而且创建了一个类/sys/class/graphics
链接文件:
fb0 -> …/…/devices/pci0000:00/0000:00:0f.0/graphics/fb0
fbcon -> …/…/devices/virtual/graphics/fbcon
(6)显存本质上是一段内存,裸机里用内存不用申请,只要自己清楚就好,但是应用要使用显存需要向内核报备申请。
2、framebuffer的使用
(1)设备文件 /dev/fb0
(2)获取设备信息 #include (将来不知道会放在多大的led设备上显示,这些信息都在fb.h文件中,应用层是不知道设备信息的,只有驱动知道,应用通过接口函数获取设备的硬件信息,这里有个特殊的头文件,一般写应用层时不会包含内核源码的头文件,这里应用包含了#include 头文件,这个头文件来自内核,但是我们要在应用层包含它,所以在交叉编译工具链制作时也将它丢进去了
(3)mmap做映射:内核和应用对应不同的虚拟内存,但是对应同一块物理内存。
(4)填充framebuffer

2.framebuffer应用编程实践

#include 
#include 
#include 
#include 
#include 
#include 
#include 

// 宏定义
#define FBDEVICE	"/dev/fb0"

// 旧开发板
//#define WIDTH		800	
//#define HEIGHT		480
// 新开发板
#define WIDTH		1024	
#define HEIGHT		600

#define WHITE		0xffffffff			// test ok
#define BLACK		0x00000000
#define RED			0xffff0000
#define GREEN		0xff00ff00			// test ok
#define BLUE		0xff0000ff			

#define GREENP		0x0000ff00			// 一样,说明前2个ff透明位不起作用
// 函数声明
void draw_back(unsigned int width, unsigned int height, unsigned int color);
void draw_line(unsigned int color);
// 全局变量
unsigned int *pfb = NULL;
int main(void)
{
	int fd = -1, ret = -1;
	
	
	struct fb_fix_screeninfo finfo = {0};
	struct fb_var_screeninfo vinfo = {0};
	
	// 第1步:打开设备
	fd = open(FBDEVICE, O_RDWR);
	if (fd < 0)
	{
		perror("open");
		return -1;
	}
	printf("open %s success.\n", FBDEVICE);
	
	// 第2步:获取设备的硬件信息
	ret = ioctl(fd, FBIOGET_FSCREENINFO, &finfo);
	if (ret < 0)
	{
		perror("ioctl");
		return -1;
	}
	printf("smem_start = 0x%x, smem_len = %u.\n", finfo.smem_start, finfo.smem_len);
	
	ret = ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);
	if (ret < 0)
	{
		perror("ioctl");
		return -1;
	}
	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);
	
	// 修改驱动中屏幕的分辨率
	vinfo.xres = 1024;
	vinfo.yres = 600;
	vinfo.xres_virtual = 1024;
	vinfo.yres_virtual = 1200;
	ret = ioctl(fd, FBIOPUT_VSCREENINFO, &vinfo);
	if (ret < 0)
	{
		perror("ioctl");
		return -1;
	}
	
	// 再次读出来检验一下
	ret = ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);
	if (ret < 0)
	{
		perror("ioctl");
		return -1;
	}
	printf("修改过之后的参数:\n");
	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);
	if (NULL == pfb)
	{
		perror("mmap");
		return -1;
	}
	printf("pfb = %p.\n", pfb);
	
	draw_back(WIDTH, HEIGHT, WHITE);
	draw_line(RED);
	

	close(fd);
	
	return 0;
}
void draw_back(unsigned int width, unsigned int height, unsigned int color)
{
	unsigned int x, y;
	
	for (y=0; y<height; y++)
	{
		for (x=0; x<width; x++)
		{
			*(pfb + y * WIDTH + x) = color;
		}
	}
}
void draw_line(unsigned int color)
{
	unsigned int x, y;
	
	for (x=50; x<600; x++)
	{
		*(pfb + 200 * WIDTH + x) = color;
	}
}

1、打开设备
2、获取设备信息
(1)不可变信息FSCREENINFO,使用ioctl的FBIOGET_FSCREENINFO名(比如屏幕大小)
(2)可变信息VSCREENINFO,使用ioctl的FBIOGET_VSCREENINFO名(比如屏幕分辨率)
(3)九鼎的fb的大小比屏幕的大小大一倍,fd为10241200,屏幕大小为1024600,这种机制叫双缓冲结构(乒乓结构)。
3、mmap做映射
做完了mmap后fb在当前进程中就已经就绪了,随时可以去读写LCD显示器了。
4、fb显示之刷背景
5、设置分辨率
(1)实验失败,实验结果是只能修改虚拟分辨率,不能修改可视分辨率。原因要去驱动里找。
老师的驱动中的屏幕大小设置应该是800480的,而开发板是1024600的,希望通过ioctl传FBIOPUT_VSCREENINFO命令将驱动中的设置改成1024600,从而让开发板显示完整的画面,老师设置失败,得出结论:不能通过应用层通过ioctl来修改驱动的设置。而我的驱动原本就是设为1024600的(因为打印出来的长宽信息是1024600),我通过ioctl将其改为800480,我做的实际情况是可以修改,但是修改后会有小错误,应该和驱动源码有关,这里修改了导致其他地方受牵连了。
(2)正确的做法是在驱动中去修改参数,然后重新编译运行,才能解决。
6、写字、画线、图片显示等

3.framebuffer驱动框架总览

1、驱动框架部分
(1)drivers/video/fbmem.c。
1、创建graphics类、注册FB的字符设备驱动
2、提供register_framebuffer接口给具体framebuffer驱动编写着来注册fb设备的。(本文件相对于fb来说,地位和作用和misc.c文件相对于杂散类设备来说一样的,结构和分析方法也是类似的。)
(2)drivers/video/fbsys.c。这个文件是处理fb在/sys目录下的一些属性文件的。
(3)drivers/video/modedb.c。这个文件是管理显示模式(譬如VGA、720P等就是显示模式)
(4)drivers/video/fb_notify.c。(反向唤醒,当新加了一个fb,某个链表下的设备就会都被通知一遍)
2、驱动部分
(1)drivers/video/samsung/s3cfb.c,驱动主体
(2)drivers/video/samsung/s3cfb_fimd6x.c,里面有很多LCD硬件操作的函数
(2)arch/arm/mach-s5pv210/mach-x210.c,负责提供platform_device的
(3)arch/arm/plat-s5p/devs.c,为platform_device提供一些硬件描述信息的
3、如何分析
(1)经验
(2)分析menuconfig、Makefile、Kconfig等
menuconfig关于frambuffer的S3C Framebuffer support被选上了,查看help,FB_S3C [=y],在drivers/video下的Makefile中有以下选项

obj-$(CONFIG_FB_S3C)		  += samsung/
#	samsung/这个目录被编译了,进入samsung/目录的Makefile,以下两个是被编译的
obj-y				+= s3cfb.o
obj-$(CONFIG_ARCH_S5PV210)	+= s3cfb_fimd6x.o

然后下面的配置项是lcd的型号,我们在menuconfig中查找有关lcd型号的相关配置信息,发现选了Select LCD Type (EK070TN93)。然而Makefile没有用这个配置项,在Kconfig中有这一项配置项,在menuconfig这样配置的话对Makefile是没有影响的,相当于没有配置。
(3)内核编译后检查编译结果中的.o文件
上面的分析结果有点怪异,为什么给了配置选项却没有用,我们可以根据.o文件分析,在/drivers/video/samsung下确实没有Makefile中支持的lcd型号被编译为.o文件。所以我们的分析结果是对的。

4.framebuffer驱动框架分析

1、fbmem_init函数(三大函数:proc_create、register_chrdev、class_create)
(1)#ifdef MODULE
条件编译,如果定义了MODULE,那就会被编译成模块,如果没定义,就会被编译进内核,.c文件中没有定义MODULE,所以会被编译进内核。
(2)第一大函数:proc_create(“fb”, 0, NULL, &fb_proc_fops);
在proc下创建名为fb的文件,分析fb_proc_fops结构体,可得我们可以读fb:cat fb。
fb_proc_fops和fb在proc文件系统中的表现。得知我们cat fb时执行的调用路径:

static const struct file_operations fb_proc_fops = {
	.open		= proc_fb_open,
};
static int proc_fb_open(struct inode *inode, struct file *file)
{
	return seq_open(file, &proc_fb_seq_ops);
}
static const struct seq_operations proc_fb_seq_ops = {
	.show	= fb_seq_show,
};
static int fb_seq_show(struct seq_file *m, void *v)
{
	int i = *(loff_t *)v;
	struct fb_info *fi = registered_fb[i];

	if (fi)
		seq_printf(m, "%d %s\n", fi->node, fi->fix.id);
	return 0;
}

(3)register_chrdev注册fb设备
(4)class_create创建graphics类
(5)fbmem_exit的对应
2、fb_fops
(1)read/write/mmap/ioctl
分析fb_read函数
最后执行return info->fbops->fb_read(info, buf, count, ppos);就是从struct fb_info来的。
三星提供的read函数在s3cfb.c文件中,(但是没有找到read函数,所以使用的是fb默认的file_operation->read,fb支持的函数在struct fb_ops s3cfb_ops结构体中,s3cfb.c的680行)
(2)registered_fb和num_registered_fb
(3)struct fb_info
inode设备文件节点(主设备号次设备号之类的,在硬盘里面存着的那一份文件)
int fbidx = iminor(inode);获得次设备号
struct fb_info *info = registered_fb[fbidx];用次设备号作为下标访问数组,就能得到一个struct fb_info类型的指针。
fb管理设备使用数组,有32个:struct fb_info *registered_fb[FB_MAX] __read_mostly;
被注册了几个:int num_registered_fb __read_mostly;
一个超大的结构体,用来描述一个fb的结构体,用于生产一个fb设备指针数组:

struct fb_info {	
	struct fb_ops *fbops;
};

7.framebuffer驱动框架分析2
7.1、register_framebuffer
(1)fb驱动框架开放给驱动编写着的注册接口
(2)fb_check_foreignness
(3)remove_conflicting_framebuffers
(4)device_create
(5)fb_init_device(出自fbsys.c初始化sys目录下面相关的内容)
dev_set_drvdata(fb_info->dev, fb_info);(出自base/dd.c,内核提供的底层驱动框架支持,不需要动)将fb_info暂存到device *指针中去,到时候被dev_get_drvdata又取出来fb_info,(这个函数会被属性的一些show和store方法调用)这两个函数是一对。
7.2、fb在sysfs中的接口
(1)device_attrs
static struct device_attribute device_attrs[]定义了fb的一些属性
device_create_file接收 struct device *dev, const struct device_attribute *attr参数,在sys目录下创建文件(可在sys中查看,cat、echo):device_create_file(fb_info->dev, &device_attrs[i]);
(2)dev_set_drvdata和dev_get_drvdata

8.framebuffer驱动框架分析3
8.1、fb的mode
(1)什么是mode
modedb.c定义了显示模式数组,列出了一些显示模式static const struct fb_videomode modedb[],但是这里只是列了一部分,可以根据自己的显示器参数对数组继续添加。
(2) fb_var_to_videomode(&mode, &fb_info->var)
从fb_var_screeninfo结构体中获取一些信息得到显示模式。
(3) fb_add_videomode(&mode, &fb_info->modelist)
把mode添加到modelist里面,首先要判断是否已经有相同的mode:fb_mode_is_equal,不相同才将其添加进来:list_add(&modelist->list, head)。
8.2、注册登记该fb设备
(1)registered_fb[i] = fb_info;
event.info = fb_info
(2)结合fb_read等函数中对fb_info的使用
(3)关键点:数据如何封装、数据由谁准备由谁消费、数据如何传递
8.3、fb_notifier_call_chain

9.framebuffer驱动分析1

9.1、s3cfb.c
(1)注意目录结构的组织
(2)s3cfb_driver
9.2、s3c_device_fb
(1)mach-x210.c中,被使用
(2)plat- s5p/devs.c中
(3)resource的定义和作用
static struct resource s3cfb_resource[]
跟IO操作有关的内存,其实就是寄存器(IO与内存统一编址)

{ [0] = {
		.start = S5P_PA_LCD,		//寄存器基地址,物理地址
		.end   = S5P_PA_LCD + S5P_SZ_LCD - 1,	//定义了1M大小(寄存器没有用完这一段空间)
		.flags = IORESOURCE_MEM,
	},

分配物理地址就是为了驱动在动态映射(绝大多数驱动都采用动态映射为的是使驱动更加灵活,驱动和数据松耦合,驱动要用的时候动态分配)形成虚拟地址去访问设备。

10.framebuffer驱动分析1
10.1、probe函数分析
(1)struct s3c_platform_fb 这个结构体是fb的platform_data结构体,这个结构体变量就是fb设备的私有数据,这个数据在platform_device.device.platform_data中存储。在mach文件中去准备并填充这些数据,在probe函数中通过传参的platform_device指针取出来。
(2)struct s3cfb_global 这个结构体主要作用是在驱动部分的2个文件(s3cfb.c和s3cfb_fimd6x.c)的函数中做数据传递用的。
(3)struct resource
(4)regulator:整流器,电源管理。在不同的模式下,对LCD进行不同的电压电流的方式供电。fbdev->regulator = regulator_get(&pdev->dev, “pd”);
10.2、platform_data的传递过程
(1)to_fb_plat
(2)s3cfb_set_platdata
(3)smdkc110_machine_init
(4) struct platform_device s3c_device_fb定义在devs.c的405行,这里没有给出platform_data,会在其他地方将platform_data继续填充进来:void __init s3cfb_set_platdata(struct s3c_platform_fb *pd)(427行)这个函数会在smdkc110_machine_init中被调用执行。而且是被宏条件编译的,我们的LCD型号是EK070TN93。
#ifdef CONFIG_FB_S3C_LTE480WV
s3cfb_set_platdata(<e480wv_fb_data);
#endif
#ifdef CONFIG_FB_S3C_EK070TN93
smdkv210_backlight_off();
s3cfb_set_platdata(&ek070tn93_fb_data); //就是platform_data结构体
#endif

11.framebuffer驱动分析2(很深,可自己分析)
11.1、struct s3cfb_lcd
11.2、pdata->cfg_gpio
11.3、pdata->clk_on
11.4、resource的处理
(1)platform_device中提供resource结构体数组
(2)probe中platform_get_resource取出resource并且按FLAG分头处理
(3)使用 request_mem_region、ioremap动态分配内存

12.framebuffer驱动分析3
12.1、一些硬件操作
(1)s3cfb_set_vsync_interrupt
(2)s3cfb_set_global_interrupt
12.2、s3cfb_init_global
完成量同步机制completion(百度自学)
12.3、向框架注册该fb设备
(1)s3cfb_alloc_framebuffer
(2)s3cfb_register_framebuffer

13.framebuffer驱动分析4
13.1、一些硬件操作
(1)s3cfb_set_clock
(2)s3cfb_set_window
(3)s3cfb_display_on
13.2、驱动中处理中断
(1)platform_get_irq
(2)request_irq
13.3、logo显示
(1)调用路径:

fb_prepare_logo
fb_logo.logo = fb_find_logo(depth);	// fb_find_logo采用了后覆盖式设计,前面定义的会被后面的覆盖掉,所以送后往前看,九鼎添加了一个
// lqm added
#ifdef CONFIG_LOGO_X210_CLUT224
/* x210 android logo */
logo = &logo_x210_clut224;	//所以最终是这个起了作用
#endif
// end added.
fb_show_logo
	fb_show_logo_line
		fb_do_show_logo
info->fbops->fb_imageblit(info, image);	// fb_imageblit真正干活的函数

(2)logo集中放在/drivers/video/logo文件夹下
13.4、backlight点亮

14.应用层为何不能设置分辨率
14.1、问题描述
(1)第4节时试图在应用层设置分辨率失败了,原因何在?
(2)定位问题:肯定是驱动的事儿
(3)进一步驱动中定位:ioctl部分的事儿
14.2、fb的ioctl部分
(1)fb是典型的字符设备驱动
fb_ops包含了两个关于ioctl的函数:

	.unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = fb_compat_ioctl,
#endif

搜索.config文件中有没有CONFIG_COMPAT,没有,说明ioctl用的是上面这个函数
(2)ioctl分为2部分,在驱动框架部分和驱动部分各有一半
(3)一路追踪找问题

fbmem.c
	fbmem_init
		register_chrdev
			fb_fops
				fb_ioctl
					do_fb_ioctl
						fb_set_var
							ret = info->fbops->fb_check_var(var, info);(s3cfb.c中)

15.折腾内核的启动logo
15.1、让logo显示在屏幕中央
x=(屏幕宽-图片宽)/2
fb_show_logo_line的471行
image.dx =(info->var.xres - logo->width) / 2;
image.dy = (info->var.yres - logo->height) / 2;
15.2、自定义内核启动logo
找一张png格式的图片,然后通过以下命令创建ppm格式的文件,前后两端的文件名可以修改。(需要安装netpbm工具包)
pngtopnm logo.png | ppmquant -fs 224 | pnmtoplainpnm > logo_linux_clut224.ppm

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