1 前言
LCD是Liquid Crystal Display的简称,也就是经常所说的液晶显示器,LCD能够支持彩色图像的显示和视频和播放,是一种非常重要的输出设备,对于LCD的驱动一般就是涉及framebuffer的编程,形象点说,framebuffer驱动负责把我们输入的内容转成送往LCD的信号,那LCD的驱动负责把信号转成LCD上显示的内容,两者是相互合作的关系.
1.1FrameBuffer的概念
FrameBuffer又叫帧缓冲,是LINUX为操作显示设备提供的一个用户接口,用户应用程序可以通过FrameBuffer透明地访问不同类型的显示设备,从这方面来说,FrameBuffe是硬件设备显示缓冲区抽象,屏蔽图像硬件的底层差异,LINUX抽象出FrameBuffer这个帧缓冲区可以供用户应用直接读写,通过更改FrameBuffer中的内容,就可以立刻显示在LCD显示屏上,除此之外,FrameBuffer是一个标准的字符设备,
主设备号为29,对应于/dev/fb%d设备文件,FrameBuffer设备也是一种普通的内存设备,可以直接对其读写,比如执行"cp /dev/fb0 file.png"对屏幕进行抓屏
1.2FrameBuffer与应用程序的交互
在Linux中,FrameBuffer是一种能够提取图形的硬件设备,是用户进入图像界面的很好接口,它是显存抽象后的一种设备,它允许上层应用程序在直接进行读写操作,这种操作是抽象的,统一的,用户不比关心物理显存的位置,换页机制等具体细节,对于用户程序而言,它和/dev下面的其他设备没有什么区别,用户可以把FrameBuffer看成一块内存,我们想显示图片时只需要映射这块内存,向里面写入数据即可,后续的显示工作由LCD控制完成即可
1.3LCD显示原理
简单地讲,FrameBuffer驱动的功能就是分配一块内存作为显存,然后对LCD控制器的寄存器做一些设置,LCD显示器会不断从显存中获得数据,并将其显示在LCD显示器上,LCD显示器可以显示显存中的一个区域或者整个区域,具体点讲,通过FrameBuffer,应用程序用mmap()把分配在物理空间的显存映射到应用程序虚拟地址空间,应用程序只需要将显示的数据写入到这个内存空间,然后LCD控制器会自动将这个内存空间(显存)中的数据显示在LCD显示屏上,在linux内核中framebuffer驱动的核心在fbmem.c文件中实现,我们下面来分析这个过程
2 Fbmem.c分析:
#define FB_MAJOR 29
static int __init
fbmem_init(void)
{
create_proc_read_entry("fb", 0, NULL, fbmem_read_proc, NULL);
if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
printk("unable to get major %d for fb devs\n", FB_MAJOR);
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;
}
#ifdef MODULE
module_init(fbmem_init);
可以看出在入口函数中注册了一个名为"fb"的字符设备和注册一个file_operations结构体"fb_fops"与之关联,注册的主设备号为29,还创建一个类,因为“fbmem.c”是通用的文件,故并不能直接使用这file_operations 结构的.read 等函数,这里的fbmem.c没有在设备类下创建设备,只有真正有硬件设备时才有必要在这个类下去创建设备,至于在哪里创建设备我们后面会提到,我们看到fb_fops的open函数
static int
fb_open(struct inode *inode, struct file *file)
{
int fbidx = iminor(inode);
struct fb_info *info;
int res = 0;
if (fbidx >= FB_MAX)
return -ENODEV;
#ifdef CONFIG_KMOD
if (!(info = registered_fb[fbidx]))
try_to_load(fbidx);
#endif /* CONFIG_KMOD */
if (!(info = registered_fb[fbidx]))
return -ENODEV;
if (!try_module_get(info->fbops->owner))
return -ENODEV;
file->private_data = info;
if (info->fbops->fb_open) {
res = info->fbops->fb_open(info,1);
if (res)
module_put(info->fbops->owner);
}
return res;
}
首先通过iminor(inode)得到这个设备节点的"次设备号"(这里假设为0),然后定义一个类型为struct fb_info*类型的结构体,从这个registered_fb[]数组里得到“以次设备号为0为下标”的一项赋值给这个info,若这个 info=registered_fd[0] 的 “fbops”有“fb_open”函数时就调用这个fb_open打开设备,至于registered_fb[]数组中的项是从哪里来的我们后面会说到,我们先来看看fb_fops的read函数
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];
u32 *buffer, *dst;
u32 __iomem *src;
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;
if (info->fbops->fb_read)
return info->fbops->fb_read(info, buf, count, ppos);
total_size = info->screen_size//设置屏幕大小
if (total_size == 0)
total_size = info->fix.smem_len;
if (p >= total_size)
return 0;
if (count >= total_size)
count = total_size;
if (count + p > total_size)
count = total_size - p;
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
GFP_KERNEL);//分配一个缓冲区
if (!buffer)
return -ENOMEM;
src = (u32 __iomem *) (info->screen_base + p);//screen_base是指显存的基地址,这里是等于显存的基地址加上某个偏移量
if (info->fbops->fb_sync)
info->fbops->fb_sync(info);
while (count) {
c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
dst = buffer;
for (i = c >> 2; i--; )
*dst++ = fb_readl(src++);//读源(从显存基地址+P 偏移)那里读到一个数据放到目标“*dst++”里.dst 是 buffer,buffer是 kmalloc()上面分配的空间.
if