Linux 帧缓冲子系统详解:LCD介绍、framebuffer驱动框架、LCD驱动源码分析

2023-11-16

1、LCD显示屏基础知识介绍

请看博客:《嵌入式开发(S5PV210)——LCD显示器》

2、内核帧缓冲子系统

2.1、功能介绍

(1)帧缓冲( framebuffer)是 Linux 为显示设备提供的一个接口,它把显示设备描述成一个缓冲
区,允许应用程序通过帧缓冲定义好的接口访问这些图形设备,从而不用关心具体的硬件细节。
帧缓冲从本质上讲是图形设备的硬件抽象。
(2)对开发者而言,帧缓冲是一块显示缓存,向显示缓存写入特定格式的数据就意味着向屏幕输出内容。通过不断地向帧缓冲中写入数据,显示控制器自动从帧缓冲中取数据井显示出来。
(3)帧缓冲设备对应的设备文件为/dev/fb*,linux最多支持32个帧缓冲设备,分别为/dev/fb0到/dev/fb31,而/dev/fb是当前默认的帧缓冲设备,通常指向/dev/fb0;
(4)帧缓冲设备的主设备号是29,次设备号是0—31;

2.2、层次结构

在这里插入图片描述

(1)层次结构大致分为帧缓冲设备层和控制器驱动层;
(2)帧缓冲设备层:在 drivers/video/fbmem.c 中实现,属于内核的帧缓冲驱动架构部分,向上给应用提供统一的设备文件操作接口,向下调用硬件操作接口;
(3)控制器驱动层:这是负责操作具体硬件的,移植LCD驱动主要就是移植控制器驱动层,控制器驱动层被帧缓冲设备层调用,完成硬件的具体操作;
(4)控制器驱动一般被命名为 xxxfb.c,“ xxx ”根据具体控制器的不同而不同 。 例如, S3C2440LCD 控制器对应的驱动是 s3c2410fb.c;

2.3、帧缓冲子系统工作的大致流程

(1)首先构建fb_info结构体,里面包含LCD硬件信息、操作方法、软件配置等信息;
(2)通过register_framebuffer()函数去注册构建的fb_info结构体到帧缓冲驱动框架中,驱动框架会自动创建/dev/fbx设备节点;
(3)应用程序通过open、ioctl等函数接口去操作设备节点,帧缓冲驱动框架会调用fb_info结构体中注册的open、ioctl函数接口去完成具体的硬件操作;
(4)应用只需要将要显示的图像写入到帧缓冲区,硬件会自动刷新到屏幕进行显示;

3、应用操作LCD显示器

参考博客:《应用程序操作LCD源码分析》

4、帧缓冲子系统的加载和卸载

4.1、加载和卸载的源码

//如果定义了MODULE宏就表示要将帧缓冲子系统单独编译成ko文件
#ifdef MODULE

module_init(fbmem_init);
static void __exit fbmem_exit(void)
{
	remove_proc_entry("fb", NULL);
	class_destroy(fb_class);
	unregister_chrdev(FB_MAJOR, "fb");
}

module_exit(fbmem_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Framebuffer base");

#else

subsys_initcall(fbmem_init);

#endif

(1)可以将帧缓冲子系统单独编译成ko文件或者直接编译进内核,由是否定义MODULE宏决定;
(2)只有当帧缓冲子系统单独编译成ko文件时才会有卸载函数;
(3)加载函数fbmem_init()和卸载函数fbmem_exit()被宏定义赋予不同的段属性,效果就是函数被内核自动调用,详情参考博客:《内核加载驱动机制详解(module_init & module_exit)》

4.2、帧缓冲子系统的加载

#define FB_MAJOR		29   /* /dev/fb* framebuffers */

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

static int __init fbmem_init(void)
{
	proc_create("fb", 0, NULL, &fb_proc_fops);

	//注册主设备号是29的设备,名字是fb,操作方法在fb_fops定义
	if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
		printk("unable to get major %d for fb devs\n", FB_MAJOR);

	//创建名字是graphics的类
	fb_class = class_create(THIS_MODULE, "graphics");
	if (IS_ERR(fb_class)) {
		printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
		fb_class = NULL;
	}
	return 0;
}

(1)注册主设备号是29、名字是fb、操作方法在fb_fops定义的设备,可以在/proc/device文件中看到已经注册的fb设备;
(2)后面注册的帧缓冲设备都是现在注册的fb设备的次设备,共享主设备号29,次设备号不同;并且帧缓冲设备还共享fb_fops里定义的操作方法;
(3)在proc文件系统中创建/proc/fb文件,里面记录注册的帧缓冲设备;

4.3、帧缓冲子系统的卸载

static void __exit fbmem_exit(void)
{
	remove_proc_entry("fb", NULL);
	class_destroy(fb_class);
	unregister_chrdev(FB_MAJOR, "fb");
}

module_exit(fbmem_exit);

(1)只有单独成ko文件才有卸载函数;
(2)删除/proc/fb文件;
(3)销毁"graphics"类;
(4)注销掉fb设备;

5、注册、注销帧缓冲设备接口分析

5.1、struct fb_info结构体

在这里插入图片描述

变量名 解释
node framebuffer设备的节点数,也就是次设备号
lock 在open/release/ioctl操作中使用的
var framebuffer设备的可变参数
fix framebuffer设备的固定参数
screen base 虚拟内存基地址
screen_size ioremapped的虚拟内存大小
par 帧缓冲设备的私有数据
fbops 帧缓冲设备的操作方法,和硬件相关
pixmap 作用是将用于显示的硬件无关数据转换为设备需要的格式。 简单地说,pixmap 就是一个缓冲区,可以将图像数据在这个缓冲区中处理后再送入帧缓冲中显示。

struct fb_info结构体在内核中用来表示帧缓冲设备,是对帧缓冲设备的统一抽象的表达,没个帧缓冲设备在内核中就是一个struct fb_info结构体。结构体里记录了帧缓冲设备的所有信息,包括硬件设备的操作方法等;

5.2.1、struct fb_var_screeninfo结构体

在这里插入图片描述

(1)struct fb_var_screeninfo结构体属于struct fb_info结构体的一部分,是用来描述帧缓冲设备的可变参数,也就是能被应用层修改的参数;
(2)bits_per_pixel:简称bpp,代表每个像素占多少位,有8bit、16bit、24bit、32bit;像素占的位数越多所能表现出的颜色越丰富,但是图片所占的内存也越大;

5.2.2、可视屏幕和虚拟屏幕之间的关系

在这里插入图片描述

(1)可视屏幕:就是我们能在LCD显示屏幕上看到的图像的分辨率,这是硬件相关的,。比如:LCD屏幕的分辨率是800x480,那可视屏幕的最大分辨率就是800x480;
(2)虚拟屏幕:我们在内核中开辟的帧缓冲区的大小。比如:屏幕分辨率是800x480,但是我们可以将帧缓冲区开辟成1920x1080,在刷新屏幕时可以直接将1080p的图像一次性刷新到帧缓冲区中;
(3)虚拟屏到可视屏的偏移量:虚拟屏大小是超过可视屏幕的大小,偏移量决定了可视屏显示虚拟屏的哪一个部分;
(4)总结:通过改变虚拟屏到可视屏的偏移量,可以将虚拟屏的不同部分图像显示到可视屏中,而不需要每次都刷新帧缓冲区;

5.3、struct fb_fix_screeninfo结构体

在这里插入图片描述

变量名 说明
type 图像数据在帧缓冲区中存放的方式
visual 屏幕支持的图像的色彩模式,比如黑白模式、真彩色模式等
smem_start 帧缓冲区的起始地址(物理地址)
smem_size 帧缓冲区的大小

struct fb_fix_screeninfo结构体记录的硬件相关的信息,一般都是固定的,只能读不能改;

5.4、struct fb_ops结构体

struct fb_ops {
	/* open/release and usage marking */
	struct module *owner;
	int (*fb_open)(struct fb_info *info, int user);
	int (*fb_release)(struct fb_info *info, int user);

	/* For framebuffers with strange non linear layouts or that do not
	 * work with normal memory mapped access
	 */
	ssize_t (*fb_read)(struct fb_info *info, char __user *buf,
			   size_t count, loff_t *ppos);
	ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,
			    size_t count, loff_t *ppos);
	/* Draws a rectangle */
	void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
	/* Copy data from area to another */
	void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
	/* Draws a image to the display */
	void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);

	/* perform fb specific ioctl (optional) */
	int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
			unsigned long arg);
	/* perform fb specific mmap */
	int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
	······
};

(1)struct fb_ops结构体是帧缓冲设备的硬件操作方法,和具体硬件相关,驱动代码最主要的功能就是去实现这些操作硬件的代码;
(2)每个函数指针代表不同的功能,但是驱动代码不需要每个函数指针的操作方法都要去实现上面列出的是常用的帧缓冲设备的操作方法;

5.5、registered_fb全局变量

//fbmem.c
#define FB_MAX			32	/* sufficient for now */
struct fb_info *registered_fb[FB_MAX] __read_mostly;

(1)registered_fb是一个struct fb_info结构体指针数组,总共有32个成员变量,每个成员变量都是struct fb_info结构体指针;
(2)将来注册帧缓冲设备就是把struct fb_info结构体注册到register_fb数组里,在数组中的下标就是次设备号;
(3)因为register_fb数组只有32个成员,所以内核最多支持32个帧缓冲设备;

5.6、famebuffer_alloc( )函数

struct fb_info *framebuffer_alloc(size_t size, struct device *dev)
{
//计算私有数据起始地址需要补齐的字节数
#define BYTES_PER_LONG (BITS_PER_LONG/8)
#define PADDING (BYTES_PER_LONG - (sizeof(struct fb_info) % BYTES_PER_LONG))
	
	int fb_info_size = sizeof(struct fb_info);
	struct fb_info *info;
	char *p;

	//
	if (size)
		fb_info_size += PADDING;

	//申请内存
	p = kzalloc(fb_info_size + size, GFP_KERNEL);

	if (!p)
		return NULL;

	info = (struct fb_info *) p;

	//将申请的私有数据的地址赋值给info->par
	if (size)
		info->par = p + fb_info_size;

	//设备的父节点
	info->device = dev;

#ifdef CONFIG_FB_BACKLIGHT
	mutex_init(&info->bl_curve_mutex);
#endif

	return info;
#undef PADDING
#undef BYTES_PER_LONG
}

(1)famebuffer_alloc( )函数是用来申请一个struct fb_info结构体的,传参的size是设备私有数据的大小,dev是设备的父设备;
(2)申请sizeof(struct fb_info) + PADDING + size大小的空间分配给fb_info结构体类型的指针info,加上PADDING 字节是为了后面的设备私有数据保持BYTES_PER_LONG字节对齐;
(3)将fb_info结构体后面size大小且BYTES_PER_LONG 字节的设备私有数据地址赋值info->par;
(4)将传入的参数dev赋值给info->device,作为父设备;
(5)返回创建好的struct fb_into结构体指针info;

5.7、注册函数:register_framebuffer( )

//帧缓冲设备的主设备号
#define FB_MAJOR		29   /* /dev/fb* framebuffers */

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;

	//判断 fb_ info->flags 标志中关于控制器大小端的设置是否正确
	if (fb_check_foreignness(fb_info))
		return -ENOSYS;

	remove_conflicting_framebuffers(fb_info->apertures, fb_info->fix.id,
					 fb_is_primary_device(fb_info));

	//在registered_fb数组中找一个空闲的变量
	num_registered_fb++;
	for (i = 0 ; i < FB_MAX; i++)
		if (!registered_fb[i])
			break;

	//将申请到的变量在数组中的下标赋值给fb_info->node
	fb_info->node = i;
		
	mutex_init(&fb_info->lock);
	mutex_init(&fb_info->mm_lock);

	//创建帧缓冲设备
	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);

	//初始化fb_info->pixmap,该变量的作用是将用于显示的硬件无关数据转换为设备需要的格式
	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;

	//初始化显示模式链表 fb_ info->modelist
	if (!fb_info->modelist.prev || !fb_info->modelist.next)
		INIT_LIST_HEAD(&fb_info->modelist);

	//根据fb_info->var设置一个 mode
	fb_var_to_videomode(&mode, &fb_info->var);

	//将该mode添加到fb_info->modelist中
	fb_add_videomode(&mode, &fb_info->modelist);

	//将fb_info注册到registered_fb结构体中
	registered_fb[i] = fb_info;

	event.info = fb_info;
	if (!lock_fb_info(fb_info))
		return -ENODEV;

	//〕通知发生了FB_EVENT_FB_REGISTERED事件(帧缓冲设备注册事件)
	fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
	unlock_fb_info(fb_info);
	return 0;
}

(1)检查内核中帧缓冲设备的注册是否已经达到上限,如果没有达到上限就在registered_fb数组中找一个空闲的变量,将空闲变量在数组中的下标赋值给fb_info->node,再将记录了内核已经注册的帧缓冲设备数量的变量num_registered_fb加一;
(2)调用fb_check_foreignness()函数,判断 fb_ info->flags 标志中关于控制器大小端的设置是否正确;
(3)调用 device_createO 创建设备对象并返回给fb_ info->dev ,该调用还会导致用户空间创建设备号为MKDEV(FB_MAJOR, i),名称为” fb%d”( %d 由 i 决定)的设备文件。 如果device_create()返回成功,则还会调用 fb_init_device()在 sysfs 中创建更多相关的属性文件 ;
(4)判断 fb_info->pixmap.addr 是否为空,如果是,则为图像硬件时才器分配FBPIXMAPSIZE
宏指定大小的空间,默认是 8192 字节,并将 fb_info->pixmap 的主要成员设置为默认值;
(5)调用时INIT_LIST_HEAD()初始化显示模式链表 fb_ info->modelist 。 调用 fb_var_to_
videomode() 根据 fb_info->var设置一个 mode ,并调用 fb_add_videomode()将该 mode添加到fb_info->modelist 中;
(6)将fb_info注册到registered_fb结构体中
(7)调用 fb_notifier_call_chain( )通知发生了 FB_EVENT_FB_REGISTERED (帧缓冲设备
注册)事件,这会引起通知链fb_notifier_list上的所有通知器回调函数得到调用;

5.8、注销函数:unregister_framebuffer( )

int unregister_framebuffer(struct fb_info *fb_info)
{
	struct fb_event event;
	int i, ret = 0;

	//检查传入的fb_info是否已经注册过
	i = fb_info->node;
	if (!registered_fb[i]) {
		ret = -EINVAL;
		goto done;
	}


	if (!lock_fb_info(fb_info))
		return -ENODEV;
	event.info = fb_info;

	//通知发生FB_EVENT_FB_UNBIND事件,绑定了该帧缓冲设备的都解绑
	ret = fb_notifier_call_chain(FB_EVENT_FB_UNBIND, &event);
	unlock_fb_info(fb_info);

	if (ret) {
		ret = -EINVAL;
		goto done;
	}

	//释放掉申请的fb_info->pixmap.addr
	if (fb_info->pixmap.addr &&
	    (fb_info->pixmap.flags & FB_PIXMAP_DEFAULT))
		kfree(fb_info->pixmap.addr);

	//销毁fb_info->modelist模式链表
	fb_destroy_modelist(&fb_info->modelist);

	//将占用的registered_fb数组中的变量置为NULL,表示空闲
	registered_fb[i]=NULL;

	//内核中注册的帧缓冲设备数量减一
	num_registered_fb--;

	//销毁点帧缓冲设备的属性文件
	fb_cleanup_device(fb_info);

	//销毁掉帧缓冲设备
	device_destroy(fb_class, MKDEV(FB_MAJOR, i));
	event.info = fb_info;

	//通知发生了FB_EVENT_FB_UNREGISTERED事件,表示该帧缓冲设备已经被注销掉
	fb_notifier_call_chain(FB_EVENT_FB_UNREGISTERED, &event);

	//如果fb_info结构体中有销毁函数就调用销毁函数
	/* this may free fb info */
	if (fb_info->fbops->fb_destroy)
		fb_info->fbops->fb_destroy(fb_info);
done:
	return ret;
}

卸载函数就是注册函数的逆过程,都是资源的释放和销毁,详细过程看代码注释;

6、帧缓冲设备驱动框架操作接口分析

6.1、操作接口功能介绍

(1)帧缓冲设备驱动框架操作接口就是在加载驱动框架时指定的struct file_operations fb_fops结构体中的操作方法;
(2)struct file_operations fb_fops结构体中的操作函数属于框架部分,并不和具体的硬件相关,在进行一些处理后最后都是调用struct fb_info结构体中fbops定义的操作方法;

6.2、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;
	int res = 0;

	//判断次设备号是否在合法范围
	if (fbidx >= FB_MAX)
		return -ENODEV;

	//根据次设备号找到对应的struct fb_info结构体指针
	info = registered_fb[fbidx];
	if (!info)
		//如果数组下标fbidx的变量是NULL,手动加载帧缓冲设备
		request_module("fb%d", fbidx);

	//再次从registered_fb数组中获取对应的struct fb_info结构体指针
	info = registered_fb[fbidx];
	if (!info)
		return -ENODEV;
	mutex_lock(&info->lock);
	if (!try_module_get(info->fbops->owner)) {
		res = -ENODEV;
		goto out;
	}

	//将struct fb_info结构体指针保存到struct file结构体的私有数据指针中,后续的接口会用到
	file->private_data = info;

	//调用帧缓冲设备驱动的fb_open函数
	if (info->fbops->fb_open) {
		res = info->fbops->fb_open(info,1);
		if (res)
			module_put(info->fbops->owner);
	}
#ifdef CONFIG_FB_DEFERRED_IO
	if (info->fbdefio)
		fb_deferred_io_open(info, inode, file);
#endif
out:
	mutex_unlock(&info->lock);
	return res;
}

应用层用open函数打开设备节点时,底层就是调用帧缓冲子系统的fb_open()函数;
(1)从设备节点的struct inode结构体中获取到次设备号,再根据次设备号去registered_fb数组中找到对应的struct fb_info结构体指针;
(2)将struct fb_info结构体指针保存到struct file结构体的private_data 私有数据指针中,在后续的操作函数中会用到;
(3)调用struct fb_info结构体指针中保存的真正的open函数(info->fbops->fb_open);

6.3、fb_write()函数

static ssize_t fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
	//显示的偏移量
	unsigned long p = *ppos;

	//获取到设备节点的struct inode结构体
	struct inode *inode = file->f_path.dentry->d_inode;

	//从inode节点中获取次设备号
	int fbidx = iminor(inode);

	//以次设备号为下标在registered_fb数组中获取到对应的struct fb_info结构体指针
	struct fb_info *info = registered_fb[fbidx];
	
	u32 *buffer, *src;
	u32 __iomem *dst;
	int c, i, cnt = 0, err = 0;
	unsigned long total_size;

	if (!info || !info->screen_base)
		return -ENODEV;

	if (info->state != FBINFO_STATE_RUNNING)
		return -EPERM;

	//如果帧缓冲设备驱动中的struct fb_ops中有定义写帧缓冲的方法就执行
	if (info->fbops->fb_write)
		return info->fbops->fb_write(info, buf, count, ppos);

	/*************执行通用的写帧缓冲的方法*************/

	//虚拟内存的大小
	total_size = info->screen_size;

	if (total_size == 0)
		total_size = info->fix.smem_len;

	if (p > total_size)
		return -EFBIG;

	if (count > total_size) {
		err = -EFBIG;
		count = total_size;
	}

	//检查偏移量加上写入数据的大小是否超过虚拟内存的大小
	if (count + p > total_size) {
		if (!err)
			err = -ENOSPC;

		count = total_size - p;
	}

	buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
			 GFP_KERNEL);
	if (!buffer)
		return -ENOMEM;

	//得到要写入帧缓冲区的起始地址:帧缓冲虚拟起始地址加上偏移量
	dst = (u32 __iomem *) (info->screen_base + p);

	//对于某些帧缓冲设备来说,必须等待它完成之前的显示处理操作,
	//才能继续向帧缓冲中送入显示数据,该方t法用于该过程的同步
	if (info->fbops->fb_sync)
		info->fbops->fb_sync(info);

	//向帧缓冲写入count个字节数据,如果写入的数据超过一个页的大小,则分多次写入
	while (count) {

		//将预写数据依次读到buffer中,每次写数据不超过PAGE_SIZE大小
		c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
		src = buffer;

		//把数据从用户空间拷贝到内核空间
		if (copy_from_user(src, buf, c)) {
			err = -EFAULT;
			break;
		}

		//将数据写入到目标帧缓冲区地址
		for (i = c >> 2; i--; )
			fb_writel(*src++, dst++);

		if (c & 3) {
			u8 *src8 = (u8 *) src;
			u8 __iomem *dst8 = (u8 __iomem *) dst;

			for (i = c & 3; i--; )
				fb_writeb(*src8++, dst8++);

			dst = (u32 __iomem *) dst8;
		}

		*ppos += c;
		buf += c;
		cnt += c;
		count -= c;
	}

	kfree(buffer);

	return (cnt) ? cnt : err;
}

6.4、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 结构体
	struct fb_info *info = registered_fb[fbidx];

	//得到驱动的fbops操作方法
	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);

	//如果fb_ops中实现了mmap方法,则调用之
	if (fb->fb_mmap) {
		int res;
		res = fb->fb_mmap(info, vma);
		mutex_unlock(&info->mm_lock);
		return res;
	}

	/*******下面是通用的mmap方法********/
	
	/* 获取映射帧缓冲的物理起始地址和长度 */
	start = info->fix.smem_start;
	len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len);
	if (off >= len) {
		/* 如果off大于帧缓冲长度.则认为映射的是内存映射IO */
		off -= len;
		if (info->var.accel_flags) {
			mutex_unlock(&info->mm_lock);
			return -EINVAL;
		}

		//获取内存映射IO的物理起始地址和长度
		start = info->fix.mmio_start;
		len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len);
	}
	mutex_unlock(&info->mm_lock);

	//保证页对齐
	start &= PAGE_MASK;
	
	if ((vma->vm_end - vma->vm_start + off) > len)
		return -EINVAL;

	//现在off表示映射设备内存实际的物理地址
	off += start;
	vma->vm_pgoff = off >> PAGE_SHIFT;
	/* This is an IO map - tell maydump to skip this VMA */
	vma->vm_flags |= VM_IO | VM_RESERVED;

	//置页保护标识
	fb_pgprotect(file, vma, off);

	//建立从物理页帧号为 off》PAGE SH 工FT的物理内存,到虚拟地址为 vma->vm start 、
	//大小为 vma->vm_end - vma->vm_start 、页保护标志为 vma->vm_page_prot的映射
	if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
			     vma->vm_end - vma->vm_start, vma->vm_page_prot))
		return -EAGAIN;
	return 0;
}

应用层调用mmap函数进行内存映射,驱动层就是调用fb_mmap函数;

6.5、fb_ioctl()函数

static long do_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
{
	struct fb_ops *fb;
	struct fb_var_screeninfo var;
	struct fb_fix_screeninfo fix;
	struct fb_con2fbmap con2fb;
	struct fb_cmap cmap_from;
	struct fb_cmap_user cmap;
	struct fb_event event;
	void __user *argp = (void __user *)arg;
	long ret = 0;

	switch (cmd) {
	case FBIOGET_VSCREENINFO:
		if (!lock_fb_info(info))
			return -ENODEV;
		var = info->var;
		unlock_fb_info(info);

		ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;
		break;
	case FBIOPUT_VSCREENINFO:
		if (copy_from_user(&var, argp, sizeof(var)))
			return -EFAULT;
		if (!lock_fb_info(info))
			return -ENODEV;
		acquire_console_sem();
		info->flags |= FBINFO_MISC_USEREVENT;
		ret = fb_set_var(info, &var);
		info->flags &= ~FBINFO_MISC_USEREVENT;
		release_console_sem();
		unlock_fb_info(info);
		if (!ret && copy_to_user(argp, &var, sizeof(var)))
			ret = -EFAULT;
		break;
	case FBIOGET_FSCREENINFO:
		if (!lock_fb_info(info))
			return -ENODEV;
		fix = info->fix;
		unlock_fb_info(info);

		ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0;
		break;
	case FBIOPUTCMAP:
		if (copy_from_user(&cmap, argp, sizeof(cmap)))
			return -EFAULT;
		ret = fb_set_user_cmap(&cmap, info);
		break;
	case FBIOGETCMAP:
		if (copy_from_user(&cmap, argp, sizeof(cmap)))
			return -EFAULT;
		if (!lock_fb_info(info))
			return -ENODEV;
		cmap_from = info->cmap;
		unlock_fb_info(info);
		ret = fb_cmap_to_user(&cmap_from, &cmap);
		break;
	case FBIOPAN_DISPLAY:
		if (copy_from_user(&var, argp, sizeof(var)))
			return -EFAULT;
		if (!lock_fb_info(info))
			return -ENODEV;
		acquire_console_sem();
		ret = fb_pan_display(info, &var);
		release_console_sem();
		unlock_fb_info(info);
		if (ret == 0 && copy_to_user(argp, &var, sizeof(var)))
			return -EFAULT;
		break;
	case FBIO_CURSOR:
		ret = -EINVAL;
		break;
	case FBIOGET_CON2FBMAP:
		if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
			return -EFAULT;
		if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
			return -EINVAL;
		con2fb.framebuffer = -1;
		event.data = &con2fb;
		if (!lock_fb_info(info))
			return -ENODEV;
		event.info = info;
		fb_notifier_call_chain(FB_EVENT_GET_CONSOLE_MAP, &event);
		unlock_fb_info(info);
		ret = copy_to_user(argp, &con2fb, sizeof(con2fb)) ? -EFAULT : 0;
		break;
	case FBIOPUT_CON2FBMAP:
		if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
			return -EFAULT;
		if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
			return -EINVAL;
		if (con2fb.framebuffer < 0 || con2fb.framebuffer >= FB_MAX)
			return -EINVAL;
		if (!registered_fb[con2fb.framebuffer])
			request_module("fb%d", con2fb.framebuffer);
		if (!registered_fb[con2fb.framebuffer]) {
			ret = -EINVAL;
			break;
		}
		event.data = &con2fb;
		if (!lock_fb_info(info))
			return -ENODEV;
		event.info = info;
		ret = fb_notifier_call_chain(FB_EVENT_SET_CONSOLE_MAP, &event);
		unlock_fb_info(info);
		break;
	case FBIOBLANK:
		if (!lock_fb_info(info))
			return -ENODEV;
		acquire_console_sem();
		info->flags |= FBINFO_MISC_USEREVENT;
		ret = fb_blank(info, arg);
		info->flags &= ~FBINFO_MISC_USEREVENT;
		release_console_sem();
		unlock_fb_info(info);
		break;
	default:
		if (!lock_fb_info(info))
			return -ENODEV;
		fb = info->fbops;
		if (fb->fb_ioctl)
			ret = fb->fb_ioctl(info, cmd, arg);
		else
			ret = -ENOTTY;
		unlock_fb_info(info);
	}
	return ret;
}

static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct inode *inode = file->f_path.dentry->d_inode;
	int fbidx = iminor(inode);
	struct fb_info *info = registered_fb[fbidx];

	return do_fb_ioctl(info, cmd, arg);
}
宏定义 功能说明
FBIOGET_VSCREENINFO 获取屏幕可变参数
FBIOPUT_VSCREENINFO 设置屏幕可变参数
FBIOGET_FSCREENINFO 获取屏幕固定参数
FBIOPUTCMAP 设置颜色表
FBIOGETCMAP 获取颜色表
FBIOPAN_DISPLAY 动视窗显示
FBIO_CURSOR 光标设置,目前不支持
FBIOGET_CON2FBMAP 获取指定帧缓冲控制台对应的帧缓冲设备
FBIOPUT_CON2FBMAP 置指定的帧缓冲控制台对应的帧缓冲设备
FBIOBLANK 显示空白

应用层对设备节点调用ioctl函数,底层就是调用fb_ioctl函数,不同的命令对应不同的功能;

7、LCD驱动源码分析

《LCD驱动源码分析(s3cfb.c)》

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux 帧缓冲子系统详解:LCD介绍、framebuffer驱动框架、LCD驱动源码分析 的相关文章

  • Linux 上的 RTLD_LOCAL 和dynamic_cast

    我们有一个由应用程序中的一些共享库构成的插件 我们需要在应用程序运行时更新它 出于性能原因 我们在卸载旧插件之前加载并开始使用新插件 并且只有当所有线程都使用旧插件完成后 我们才卸载它 由于新插件和旧插件的库具有相同的符号 我们dlopen
  • 让 TeXstudio 在 linux mint 中工作:找不到文件“url.sty”。

    刚刚切换到 Linux Mint 以前的顽固 Windows 用户 我在尝试安装 TeXstudio 时遇到一些问题 Sudo apt get install texstudio 给了我一个正确的安装 至少 我是这么认为的 但是当我尝试构建
  • linux命令中括号的用途是什么[重复]

    这个问题在这里已经有答案了 我在 Linux 终端中运行以下命令 谁能告诉我 Linux 终端中括号和以下命令的用途是什么 echo GET HTTP 1 0 echo 主机 www google com echo 数控 www googl
  • Linux中使用管道进行进程间通信

    我已经编写了在 linux 中写入数字以进行管道传输的代码 如下所示 但显示错误 任何人都可以帮助我解决这个问题 基本上该程序的问题陈述如下 一个程序将打开一个管道 向管道写入一个数字 其他程序将打开同一管道 读取数字并打印它们 关闭两个管
  • 使用 inotify 的正确方法是什么?

    我想使用inotifyLinux 上的机制 我希望我的应用程序知道文件何时aaa被改变了 您能给我提供一个如何做到这一点的示例吗 文档 来自监视文件系统活动 inotify https developer ibm com tutorials
  • 如何将后台作业的输出分配给 bash 变量?

    我想在 bash 中运行后台作业并将其结果分配给一个变量 我不喜欢使用临时文件 并且希望同时运行多个类似的后台任务 root root var echo hello world root root echo var hello world
  • 使用 gcc 理解共享库

    我试图理解 C 中共享库的以下行为 机器一 cat one c include
  • 如何设置Java线程的CPU核心亲和力?

    我搜索了以前关于类似主题的帖子 但找不到合适的答案 因此提出这个问题 非常感谢您帮助回答 我知道在 Linux 中通过任务集命令设置进程与特定 CPU 核心的关联性 但我想设置 Java 线程与特定 cpu 核心的亲和力 以便属于同一进程的
  • C++:Linux平台上的线程同步场景

    我正在为 Linux 平台实现多线程 C 程序 其中我需要类似于 WaitForMultipleObjects 的功能 在搜索解决方案时 我发现有一些文章描述了如何在 Linux 中实现 WaitForMultipleObjects 功能
  • 无需 root 访问权限即可安装 zsh? [关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 有可能 以及如何 我确实需要在几台具有 ssh 访问权限 但没有 root 访问权限 的远程计算机上使用此功能 下载 zsh wget O zsh t
  • 计算 TCP 重传次数

    我想知道在LINUX中是否有一种方法可以计算一个流中发生的TCP重传的次数 无论是在客户端还是服务器端 好像netstat s解决了我的目的
  • 完整的 C++ i18n gettext()“hello world”示例

    我正在寻找完整的 i18ngettext 你好世界的例子 我已经开始了一个基于的脚本使用 GNU gettext 的本机语言支持教程 https web archive org web 20130330233819 http oriya s
  • 比较linux中的两个未排序列表,列出第二个文件中的唯一项

    我有 2 个包含号码列表 电话号码 的文件 我正在寻找一种列出第二个文件中第一个文件中不存在的数字的方法 我尝试过各种方法 comm getting some weird sorting errors fgrep v x f second
  • 运行 shell 命令并将输出发送到文件?

    我需要能够通过 php 脚本修改我的 openvpn 身份验证文件 我已将我的 http 用户设置为免通 sudoer 因为这台机器仅在我的家庭网络中可用 我目前有以下命令 echo shell exec sudo echo usernam
  • 用于时间线数据的类似 gnuplot 的程序

    我正在寻找一个类似 gnuplot用于在时间轴中绘制数据图表的程序 类似 gnuplot 在 Linux 上运行 命令行功能 GUI 对我帮助不大 可编写脚本的语法 输出为 jpg png svg 或 gif 输出应该是这样的 set5 s
  • 如何在linux中使用iptables将http和https流量转发到透明代理[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 这个问题似乎不是关于主要由程序员使用的特定编程问题 软件算法或软件工具 help on topic 如果您认为该问题与主题相关另一个 St
  • php_network_getaddresses: getaddrinfo 失败: 名称或服务未知 (0) 连接失败..!

    我正在使用 php 邮件程序功能 但出现以下错误 如何修复它 2016 01 22 06 15 48 SMTP 错误 无法连接到服务器 php network getaddresses getaddrinfo失败 名称或服务未知 0 连接失
  • 用于获取特定用户 ID 和进程数的 Bash 脚本

    我需要 bash 脚本来计算特定用户或所有用户的进程 我们可以输入 0 1 或更多参数 例如 myScript sh root deamon 应该像这样执行 root 92 deamon 8 2 users has total proces
  • 为什么 call_usermodehelper 大多数时候都会失败?

    从内核模块中 我尝试使用 call usermodehelper 函数来执行可执行文件 sha1 该可执行文件将文件作为参数并将文件的 SHA1 哈希和写入另一个文件 名为输出 可执行文件完美运行 int result 1 name hom
  • 查找并删除超过 x 天的文件或文件夹

    我想删除超过 7 天的文件和文件夹 所以我尝试了 17 07 14 email protected cdn cgi l email protection find tmp mindepth 1 maxdepth 1 ctime 7 exec

随机推荐