如果我们的系统要用GUI(图形界面接口),这时LCD设备驱动程序就应该编写成frambuffer接口,而不是像之前那样只编写操作底层的LCD控制器接口。
什么是frambuffer设备?
frambuffer设备层是对图像设备的一种抽象,它代表了视频硬件的帧缓存,使得应用程序通过定义好的接口就可以访问硬件。所以应用程序不需要考虑底层的(寄存器级)的操作。应用程序对设备文件的访问一般在/dev目录,如 /dev/fb*。
内核中的frambuffer在drivers/video/fbmem.c(fb: frame buffer)
1.我们进入fbmem.c找到它的入口函数:
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)) //(1)创建字符设备
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;
}
(1)创建字符设备"fb", FB_MAJOR=29,主设备号为29,我们cat /proc/devices 也能找到这个字符设备:
和我们之前的驱动程序一样,但是没有使用创建设备节点,为什么?
因为需要注册了LCD驱动后,才会有设备节点,所以这里的代码没有 ,后面会分析哪里有。
2.我们来看看注册的file_operations结构体fb_fops的.open函数和.read函数,应用层是如何打开驱动、读取驱动数据
2.1 fb_open函数如下:
static int fb_open(struct inode *inode, struct file *file)
{
int fbidx = iminor(inode); //获取设备节点的次设备号
struct fb_info *info; //定义fb_info结构体
int res = 0;
... ...
if (!(info = registered_fb[fbidx])) //(1) info= registered_fb[fbidx],获取此设备号的lcd驱动信息
try_to_load(fbidx);
... ...
if (info->fbops->fb_open) {
res = info->fbops->fb_open(info,1); //调用registered_fb[fbidx]->fbops->fb_open
if (res)
module_put(info->fbops->owner);
}
return res;
}
(1) registered_fb[fbidx] 这个数组也是fb_info结构体,其中fbidx等于次设备号id,显然这个数组就是保存我们各个lcd驱动的信息
2.2 fb_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]; //获取次设备号的lcd驱动的信息
u32 *buffer, *dst;
u32 __iomem *src;
int c, i, cnt = 0, err = 0;
unsigned long total_size;
... ...
if (info->fbops->fb_read)
return info->fbops->fb_read(info, buf, count, ppos);
total_size = info->screen_size; //获取屏幕长度
... ...
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,GFP_KERNEL); //分配缓冲区
if (!buffer)
return -ENOMEM;
src = (u32 __iomem *) (info->screen_base + p); //获取显存物理基地址
if (info->fbops->fb_sync)
info->fbops->fb_sync(info);
while (count) {
c = (count > PAGE_SIZE) ? PAGE_SIZE : count; //获取页地址
dst = buffer;
/*因为src是32位,一个src等于4个字节,所以页地址c >> 2*/
for (i = c >> 2; i--; )
*dst++ = fb_readl(src++); //读取显存每个像素点数据,放到dst地址上
if (c & 3) {
u8 *dst8 = (u8 *) dst;
u8 __iomem *src8 = (u8 __iomem *) src;
for (i = c & 3; i--;)
*dst8++ = fb_readb(src8++);
src = (u32 __iomem *) src8;
}
if (copy_to_user(buf, buffer, c)) { //上传数据,长度等于页地址大小
err = -EFAULT;
break;
}
*ppos += c;
buf += c;
cnt += c;
count -= c;
}
kfree(buffer);
return (err) ? err : cnt;
}
从.open和.write函数中可以发现,都依赖于fb_info帧缓冲信息结构体,它从registered_fb[fbidx]数组中得到,这个数组保存我们各个lcd驱动的信息
3.我们来找找这个数组在哪里被注册,位于register_framebuffer():
int register_framebuffer(struct fb_info *fb_info)
{
... ...
for (i = 0 ; i < FB_MAX; i++) //查找空的数组