紧接上一博文 ,实现一个最基本功能的fb设备驱动,需实现如下步聚:
1) 从内存里分配出禁用数据缓存功能的缓冲区,用于作显存.
2) 动态分配struct fb_info对象空间, 每个fb_info对象表示一个fb设备.
3) 初始化fb_info对象里的fb_var_screeninfo成员里的分辨率, 位色,每个像素的rgb位域等信息.
4) 初始化fb_info对象里的fb_fix_screeninfo成员里的显存物理地址,line_length等成员信息.
5) 初始化fb_info对象里的fbops成员指针,此指针不可以为NULL, 必须指向一个struct fb_ops对象的地址.
如果指向的fb_ops对象里功能函数没有实现,则会调用fbmem.c里实现功能函数.
struct fb_ops {
...
int (*fb_open)(struct fb_info *info, int user); //打开/dev/fb*设备文件时触发调用
int (*fb_release)(struct fb_info *info, int user); //关闭/dev/fb*设备文件时触发调用
...
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);
...
int (*fb_ioctl)(struct fb_info *info, unsigned int cmd, //对设备文件ioctl时触发
unsigned long arg);
...
};
6) 初始化fb_info对象里的screen_base成员,记录显存的虚拟地址. 最后注册fb设备
fbi->screen_base = v_addr; // 显示缓冲区的虚拟地址
fbi->screen_size = X*Y*4; //显存大小
register_framebuffer(fbi); //注册fb设备
//
framebuffer设备驱动的主要功能及入口在内核源码里的”drivers/video/fbmem.c”里
过程分析:
46 struct fb_info *registered_fb[FB_MAX] __read_mostly; //此全局fb_info指针数组用于记录所有的fb_info对象的地址. FB_MAX的值是32
47 int num_registered_fb __read_mostly; //此全局变量记录已注册的fb_info对象的个数
1802 #ifdef MODULE //如fbmem.c编成模块,则此宏成立
1803 module_init(fbmem_init);
..
1815 #else //编进内核镜像,则这里成立. 总之fbmem_init函数会在内核初始化后被调用的.
1816 subsys_initcall(fbmem_init);
1817 #endif
1786 static int __init
1787 fbmem_init(void)
1788 {
1789 proc_create("fb", 0, NULL, &fb_proc_fops); //会创建"/proc/fb"属性文件
1790
1791 if (register_chrdev(FB_MAJOR,"fb",&fb_fops)) //实现字符设备驱动接口,当操作所有的fb设备文件时都会先使用fb_fops文件操作对象里的功能函数
...
1794 fb_class = class_create(THIS_MODULE, "graphics"); //创建"/sys/class/graphics"子目录
...
1799 return 0;
1800 }
//注意看哦, fb_fops文件操作对象里所有文件操作的功能函数全部实现了哦,这也就是为什么我们前面的fb设备驱动里什么文件操作函数都没有实现也可以正常工作的原因.
1461 static const struct file_operations fb_fops = {
1462 .owner = THIS_MODULE,
1463 .read = fb_read,
1464 .write = fb_write,
1465 .unlocked_ioctl = fb_ioctl,
1466 #ifdef CONFIG_COMPAT
1467 .compat_ioctl = fb_compat_ioctl,
1468 #endif
1469 .mmap = fb_mmap,
1470 .open = fb_open,
1471 .release = fb_release,
1472 #ifdef HAVE_ARCH_FB_UNMAPPED_AREA
1473 .get_unmapped_area = get_fb_unmapped_area,
1474 #endif
1475 #ifdef CONFIG_FB_DEFERRED_IO
1476 .fsync = fb_deferred_io_fsync,
1477 #endif
1478 .llseek = default_llseek,
1479 };
//注册fb_info对象时的代码过程
1714 int
1715 register_framebuffer(struct fb_info *fb_info)
1716 {
...
1720 ret = do_register_framebuffer(fb_info);
...
1723 return ret;
1724 }
1574 static int do_register_framebuffer(struct fb_info *fb_info)
1575 {
1576 int i;
...
1586 if (num_registered_fb == FB_MAX) //如果已有32个fb设备了,则失败返回。因全局数组registered_fb元素个数只有32个.
1587 return -ENXIO;
1588
1589 num_registered_fb++; //fb设备的计数加1
1590 for (i = 0 ; i < FB_MAX; i++) //顺序找出registered_fb数组里最前面的空的指针
1591 if (!registered_fb[i])
1592 break;
1593 fb_info->node = i; // i表示registered_fb数组里的下标,也就是需要注册的fb设备的编号
...
//创建出fb设备文件,注意设备文件的次设备号为fb_info对象在registered_fb数组里的下标
1598 fb_info->dev = device_create(fb_class, fb_info->device,
1599 MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
...
1630 registered_fb[i] = fb_info; //注册的fb_info对象的地址存放在registered_fb数组里了,这就是注册.
//注意通过创建的设备文件的次设备号,就可以在registered_fb数组里找到fb_info对象的地址了
...
1639 return 0;
1640 }
当应用程序打开/dev/fb*设备文件时,先触发调用fb_ops里的fb_open
1404 static int
1405 fb_open(struct inode *inode, struct file *file)
1406 __acquires(&info->lock)
1407 __releases(&info->lock)
1408 {
1409 int fbidx = iminor(inode); //获取设备文件的次设备号,也就获取出fb_info对象在registered_fb数组里的下标
1410 struct fb_info *info;
1411 int res = 0;
1412
1413 info = get_fb_info(fbidx); //根据次设备号,获取到相应的fb_info对象的地址
...
1428 file->private_data = info;
1429 if (info->fbops->fb_open) { //如果fb_info对象的fbops成员有实现fb_open函数
1430 res = info->fbops->fb_open(info,1); //则会在这里被调用(这是用C语言实现C++的多态功能)
...
1433 }
1442 return res;
1443 }
当应用程序读设备文件时,也就是读显存里的内容时, 触发fb_ops里的fb_read
739 static ssize_t
740 fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
741 {
742 unsigned long p = *ppos; //文件描述符的偏移
743 struct fb_info *info = file_fb_info(file); //通过文件描述符对象获取到次设备号,再根据次设备获取到fb_info对象的地址
744 u8 *buffer, *dst;
745 u8 __iomem *src;
746 int c, cnt = 0, err = 0;
747 unsigned long total_size;
...
755 if (info->fbops->fb_read) //如果fb_info对象里的fbops有实现fb_read功能函数,则直接调用
756 return info->fbops->fb_read(info, buf, count, ppos);
757 //如果fb_info对象里的fbopsi没有实现fb_read函数,则使用下面的代码(这是用C语言实现C++的多态功能)
758 total_size = info->screen_size; //记录显存的大小
...
772 buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
773 GFP_KERNEL); //动态分配出一个缓冲区
...
777 src = (u8 __iomem *) (info->screen_base + p); //src指针指向显存的虚拟地址加上文件描述符的偏移
...
782 while (count) {
783 c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
784 dst = buffer;
785 fb_memcpy_fromfb(dst, src, c);//先把显存里的内容复制到buffer指向的缓冲区里
786 dst += c;
787 src += c;
788
789 if (copy_to_user(buf, buffer, c)) { //再把buffer指向的缓冲里内容复制到用户进程的缓冲区里
790 err = -EFAULT;
791 break;
792 }
793 *ppos += c;
794 buf += c;
795 cnt += c;
796 count -= c;
797 }
798
799 kfree(buffer);
800
801 return (err) ? err : cnt;
802 }
其它函数基本也是这套路,根据设备文件的次设备号在registered_fb数组里找到对应的fb_info对象,
然后判断fb_info对象里的fbops有没有实现相应的功能函数,如有实现,则直接调用,如没有实现,则用里面写好的功能代码来代替.