文章目录
fbmem驱动框架分析
1、与misc驱动框架类比
1.1 my_first_cdev的记录
从/proc/devices中可以看到test设备,但是不能够看到节点,这是之前分析字符设备时就遇到过的问题,注册了字符设备后需要自己创建节点,下图是在3月份自己写第一个字符设备驱动VirtualDisk时所用到的一个应用程序,当时备注了通过mknod自己创建节点的过程。
后来在misc驱动框架下写字符设备驱动VirtualDisk时,就可以自动创建设备节点了。
1.2、关于misc驱动框架的总结
2、fbmem驱动框架
首先分析它的入口函数。
fbmem_init(void)
{
proc_create("fb", 0, NULL, &fb_proc_fops);//创建proc中的fb,并提供操作接口
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;
}
入口函数比较简单,关键的一句就是注册字符设备,提供操作接口,其中提供操作接口十分重要。
root@am335x-evm:/proc# cat devices
Character devices:
···
29 fb
···
可以看到这是与1.1小节遇到的问题类似,在devices里可以看到fb设备,但是没有相应的节点,当不使用驱动框架注册设备时,是不会自动创建节点的。
2.1提供注册函数
/*register_framebuffer - registers a frame buffer device
* @fb_info: frame buffer info structure
* Registers a frame buffer device @fb_info.
* Returns negative errno on error, or zero for success. */
int
register_framebuffer(struct fb_info *fb_info)
{
int ret;
mutex_lock(®istration_lock);
ret = do_register_framebuffer(fb_info);
mutex_unlock(®istration_lock);
return ret;
}
/*unregister_framebuffer - releases a frame buffer device
* @fb_info: frame buffer info structure
* Unregisters a frame buffer device @fb_info.
* Returns negative errno on error, or zero for success.
* This function will also notify the framebuffer console
* to release the driver.
* This is meant to be called within a driver's module_exit()
* function. If this is called outside module_exit(), ensure
* that the driver implements fb_open() and fb_release() to
* check that no processes are using the device.*/
int
unregister_framebuffer(struct fb_info *fb_info)
{
int ret;
mutex_lock(®istration_lock);
ret = do_unregister_framebuffer(fb_info);
mutex_unlock(®istration_lock);
return ret;
}
这两个接口是framebuffer总线给出的两个接口,通过这里的接口注册framebuffer设备,就会出现/dev/fb0节点,然后对fb0节点就可以进行操作,其中fbmem.c中提供了通用的操作函数。
2.2、操作函数分析
我们追踪一下他的操作函数都有哪些。
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
.llseek = default_llseek,
};
2.2.1、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;
/*
在本函数中有一个贯穿函数的变量,info,info变量的结构体里边存着许多的信息,有设备、操作函数等信息,具体fb_info的使用情况在函数中继续分析。
*/
int res = 0;
info = get_fb_info(fbidx);//通过副设备号获取info结构体
if (!info) {
request_module("fb%d", fbidx);
info = get_fb_info(fbidx);
if (!info)
return -ENODEV;
}
if (IS_ERR(info))
return PTR_ERR(info);
mutex_lock(&info->lock);
if (!try_module_get(info->fbops->owner)) {
res = -ENODEV;
goto out;
}
file->private_data = info;//将info结构体作为file的私有信息
if (info->fbops->fb_open) {
res = info->fbops->fb_open(info,1);//如果info存在open函数,则调用其open函数
/*此处做了一个实验,为info创建一个fb_open,观察发生的现象,可以看到以下打印信息。
[ 3.673068] [KERNEL_DEBUG:] fb_open
[ 3.677050] [KERNEL_DEBUG:] nfk_open
该实验为info增加了一个open函数,实验证明,open函数还是可以重定义的。
*/
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);
if (res)
put_fb_info(info);
return res;
}
open函数的主要作用就是通过inode节点找到驱动程序,然后将驱动程序中的ops赋值给file结构体,就可以实现对file文件的操作。
struct fb_info {
atomic_t count;
int node;
int flags;
···
struct fb_ops *fbops;
struct device *device; /* This is the parent */
struct device *dev; /* This is this fb device */
int class_flag; /* private sysfs flags */
···
};
open函数究竟做了什么
分析完fbmem.c的open函数的调用关系之后,就要进一步的分析open函数到底做了什么事情。
open函数打开了一个文件,这个文件是内核空间的一个文件结构体,open函数要做的就是将这个结构体进行填充,将该文件的操作方法赋值给文件结构体的fops。
open函数中的这一行就可以看到面向对象编程的痕迹。
file->private_data = info;//将info结构体作为file的私有信息
file是一个对象,其中的私有数据就是info结构体内的数据。
进一步思考
在2.2.1小节,实验可以看到open函数可以重定义,那open函数又是如何被第一次调用的?
下图表述了open第一次的调用情况,首先系统调用了sys_open,然后通过设备特点选择了chrdev_open函数。
static int chrdev_open(struct inode *inode, struct file *filp)
{
struct cdev *p;
struct cdev *new = NULL;
int ret = 0;
···
filp->f_op = fops_get(p->ops);//此处为重点,通过inode节点中的设备号找到ops赋值给filp
if (!filp->f_op)
goto out_cdev_put;
if (filp->f_op->open) {
ret = filp->f_op->open(inode,filp);//如果filp有open则继续执行filp的open函数
if (ret)
goto out_cdev_put;
}
···
}
这里会发现这种不断嵌套open函数是一脉相承的,刚才在分析fbmem.c中的open函数时,就发现执行完赋值之后,会根据赋值的file进一步的执行新的ops->open。
2.2.2 fb_read函数
open完成之后,就可以根据file当中被赋值的ops选择read函数,read函数直接执行file里的ops当中的read函数,然后在read函数中找到file当中的info,进一步调用info当中read函数。
static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
struct fb_info *info = file_fb_info(file);
u8 *buffer, *dst;
u8 __iomem *src;//IO存储空间
int c, cnt = 0, err = 0;
unsigned long total_size;
printd("fb_read\n");//此处测试cat /dev/fb0得出结果,证明确实调用了此处函数。
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);//判断info是否存在fbops的read操作函数
total_size = info->screen_size;
if (total_size == 0)
total_size = info->fix.smem_len;//framebuffer大小
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);//为buffer申请内存
if (!buffer)
return -ENOMEM;
src = (u8 __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;
fb_memcpy_fromfb(dst, src, c);//将数据从framebuffer中拷贝出来
dst += c;
src += c;
if (copy_to_user(buf, buffer, c)) {//将数据拷贝至用户空间
err = -EFAULT;
break;
}
*ppos += c;
buf += c;
cnt += c;
count -= c;
}
kfree(buffer);
return (err) ? err : cnt;
}
以上函数中,我们先分析它的传入参数,首先传入了一个非常关键的变量,*file变量是由内核创建。
目前了解到的是file参数是由内核创建,创建出来之后给该函数使用,file通过chrdev_open和fb_open都已经进行了赋值。这里可以拿来直接用了。
struct fb_info *info = file_fb_info(file);
可以看到是这么使用的,将file通过file_fb_info进行了一个操作,进一步查看是如何进行操作的。
/* We hold a reference to the fb_info in file->private_data,
* but if the current registered fb has changed, we don't
* actually want to use it.
* So look up the fb_info using the inode minor number,
* and just verify it against the reference we have.*/
static struct fb_info *file_fb_info(struct file *file)
{
struct inode *inode = file->f_path.dentry->d_inode;
int fbidx = iminor(inode);
struct fb_info *info = registered_fb[fbidx];
if (info != file->private_data)
info = NULL;
return info;
}
此处是进行一个判断,看当前我们使用的file->private_data是否已经不再适用,通过inode重新寻找被注册的fb当中的fb_info结构体。而不是直接使用file->private_data当中的info。
2.2.3、fb_write函数
fb_write函数与fb_read函数大体类似。此处不再重复分析。
2.2.4、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)) /* 如果info->fbops不为空,则上锁,成功返回1 */
return -ENODEV;
var = info->var;/* 可变参数变量的设置 */
unlock_fb_info(info);/* 解锁 */
/* 从内核空间的var地址拷贝var大小的数据到用户空间的argp地址里去 */
ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;
/* 成功返回0 */
break;
···
}
return ret;
}
ioctl函数是执行命令的函数,ioctl函数非常长,可以执行的命令也非常多,这里暂时不做分析,在后期用到这些命令时再进行分析。
2.2.5、 fb_mmap函数
这里分配的显存是在内核空间分配的,用户空间并不能直接访问, 所以需要用到这里的mmap函数,直接将这段内存空间映射到用户空间去,用户空间就能访问这段内存空间了。
static int
fb_mmap(struct file *file, struct vm_area_struct * vma)
{
struct fb_info *info = file_fb_info(file);
struct fb_ops *fb;
unsigned long off;
unsigned long start;
u32 len;
if (!info)
return -ENODEV;
if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
return -EINVAL;
off = vma->vm_pgoff << PAGE_SHIFT;
fb = info->fbops;
if (!fb)
return -ENODEV;
mutex_lock(&info->mm_lock);
if (fb->fb_mmap) {
int res;
res = fb->fb_mmap(info, vma);
mutex_unlock(&info->mm_lock);
return res;
}
/* frame buffer memory */
start = info->fix.smem_start; /* fb缓冲内存的开始位置(物理地址) */
len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len);
if (off >= len) { /* 偏移值大于len长度 */
/* memory mapped io */ /* 内存映射的IO */
off -= len;
if (info->var.accel_flags) {
mutex_unlock(&info->mm_lock);
return -EINVAL;
}
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 += 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;
vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
fb_pgprotect(file, vma, off);
/* io_remap_pfn_range正式映射物理内存到用户空间虚拟地址 */
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;
}
3、结合psplash应用分析
psplash应用是典型的C语言编程。与QT应用不同,QT应用使用的C++编程,并且大量的程序都是被封装过的,通过分析C语言应用编程可以充分了解驱动的使用。
当前仅分析了framebuffer总线驱动,具体还需要分析LCD驱动,才能够结合实际的硬件进行分析。在此处仅简单分析psplash调用的总线驱动当中的函数,下一步将结合LCD驱动再进行分析。
int
main (int argc, char** argv)
{
char *tmpdir;
int pipe_fd, i = 0, angle = 0, ret = 0;
PSplashFB *fb;
bool disable_console_switch = FALSE;
signal(SIGHUP, psplash_exit);
signal(SIGINT, psplash_exit);
signal(SIGQUIT, psplash_exit);
···
if ((fb = psplash_fb_new(angle)) == NULL) {//此处为重点
ret = -1;
goto fb_fail;
}
···
psplash_main (fb, pipe_fd, 0);//执行的循环
···
return ret;
}
下面分析重点函数psplash_fb_new();
psplash_fb_new (int angle)
{
struct fb_var_screeninfo fb_var;
struct fb_fix_screeninfo fb_fix;
int off;
char *fbdev;
PSplashFB *fb = NULL;
fbdev = getenv("FBDEV");
if (fbdev == NULL)
fbdev = "/dev/fb0";
if ((fb = malloc (sizeof(PSplashFB))) == NULL){
perror ("Error no memory");
goto fail;
}
memset (fb, 0, sizeof(PSplashFB));
fb->fd = -1;
if ((fb->fd = open (fbdev, O_RDWR)) < 0)//首先调用open函数,
{
perror ("Error opening /dev/fb0");
goto fail;
}
if (ioctl (fb->fd, FBIOGET_VSCREENINFO, &fb_var) == -1)//ioctl函数的命令调用
{
perror ("Error getting variable framebuffer info");
goto fail;
}
```
if (ioctl (fb->fd, FBIOGET_VSCREENINFO, &fb_var) == -1)//ioctl函数的命令调用
{
perror ("Error getting variable framebuffer info (2)");
goto fail;
}
/* NB: It looks like the fbdev concept of fixed vs variable screen info is
* broken. The line_length is part of the fixed info but it can be changed
* if you set a new pixel format. */
if (ioctl (fb->fd, FBIOGET_FSCREENINFO, &fb_fix) == -1)//ioctl函数的命令调用
{
perror ("Error getting fixed framebuffer info");
goto fail;
}
fb->real_width = fb->width = fb_var.xres;
fb->real_height = fb->height = fb_var.yres;
fb->bpp = fb_var.bits_per_pixel;
fb->stride = fb_fix.line_length;
fb->type = fb_fix.type;
fb->visual = fb_fix.visual;
fb->red_offset = fb_var.red.offset;
fb->red_length = fb_var.red.length;
fb->green_offset = fb_var.green.offset;
fb->green_length = fb_var.green.length;
fb->blue_offset = fb_var.blue.offset;
fb->blue_length = fb_var.blue.length;
//通过ioctl函数获取到的数值赋值情况
if (fb->red_offset == 11 && fb->red_length == 5 &&
fb->green_offset == 5 && fb->green_length == 6 &&
fb->blue_offset == 0 && fb->blue_length == 5) {
fb->rgbmode = RGB565;
} else if (fb->red_offset == 0 && fb->red_length == 5 &&
fb->green_offset == 5 && fb->green_length == 6 &&
fb->blue_offset == 11 && fb->blue_length == 5) {
fb->rgbmode = BGR565;
} else if (fb->red_offset == 16 && fb->red_length == 8 &&
fb->green_offset == 8 && fb->green_length == 8 &&
fb->blue_offset == 0 && fb->blue_length == 8) {
fb->rgbmode = RGB888;
} else if (fb->red_offset == 0 && fb->red_length == 8 &&
fb->green_offset == 8 && fb->green_length == 8 &&
fb->blue_offset == 16 && fb->blue_length == 8) {
fb->rgbmode = BGR888;
} else {
fb->rgbmode = GENERIC;
}
```
/*调用mmap函数*/
fb->base = (char *) mmap ((caddr_t) NULL,
/*fb_fix.smem_len */
fb->stride * fb->height,
PROT_READ|PROT_WRITE,
MAP_SHARED,
fb->fd, 0);
```
off = (unsigned long) fb_fix.smem_start % (unsigned long) getpagesize();
fb->data = fb->base + off;
```
```
return fb;
```
}
可以看到在framebuffer驱动总线上给出的接口,在psplash一开始的时候就被调用了,相当于在初始化数据的阶段将这些接口调用,然后对用户空间的结构体进行赋值。
初始化完成之后,就可以对framebuffer内存进行写入了,主要是在mmap函数执行之后进行。
下面代码是关于一个像素点的绘制,所有绘制最终都要落脚到像素点的绘制,像素点绘制最终落脚到对framebuffer内存的写入。
static inline void
psplash_fb_plot_pixel (PSplashFB *fb,
int x,
int y,
uint8 red,
uint8 green,
uint8 blue)
{
int off;
if (x < 0 || x > fb->width-1 || y < 0 || y > fb->height-1)
return;
switch (fb->angle)
{
case 270:
off = OFFSET (fb, fb->height - y - 1, x);
break;
···//关于屏幕旋转的操作
default:
off = OFFSET (fb, x, y);
break;
}
//根据fb->bpp绘制相应的像素点。byts per pixel
if (fb->rgbmode == RGB565 || fb->rgbmode == RGB888) {
···
}
else {
switch (fb->bpp)
{
case 32:
*(volatile uint32_t *) (fb->data + off)
= ((red >> (8 - fb->red_length)) << fb->red_offset)
| ((green >> (8 - fb->green_length)) << fb->green_offset)
| ((blue >> (8 - fb->blue_length)) << fb->blue_offset);
break;
case 16:
*(volatile uint16_t *) (fb->data + off)
= ((red >> (8 - fb->red_length)) << fb->red_offset)
| ((green >> (8 - fb->green_length)) << fb->green_offset)
| ((blue >> (8 - fb->blue_length)) << fb->blue_offset);
break;
default:
/* depth not supported yet */
break;
}
}
}