Linux 帧缓冲子系统详解:LCD介绍framebuffer驱动框架LCD驱动源码分析
Posted 正在起飞的蜗牛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux 帧缓冲子系统详解:LCD介绍framebuffer驱动框架LCD驱动源码分析相关的知识,希望对你有一定的参考价值。
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, Linux Framebuffer 驱动框架之一概念介绍及LCD硬件原理
Linux驱动开发: FrameBuffe(LCD)驱动开发