fbmem驱动框架分析

fbmem驱动框架分析

1、与misc驱动框架类比

1.1 my_first_cdev的记录

从/proc/devices中可以看到test设备,但是不能够看到节点,这是之前分析字符设备时就遇到过的问题,注册了字符设备后需要自己创建节点,下图是在3月份自己写第一个字符设备驱动VirtualDisk时所用到的一个应用程序,当时备注了通过mknod自己创建节点的过程。

image-20200511150559775

后来在misc驱动框架下写字符设备驱动VirtualDisk时,就可以自动创建设备节点了。

image-20200511150825447

1.2、关于misc驱动框架的总结

image-20200511154338033

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(&registration_lock);
	ret = do_register_framebuffer(fb_info);
	mutex_unlock(&registration_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(&registration_lock);
	ret = do_unregister_framebuffer(fb_info);
	mutex_unlock(&registration_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函数。

image-20200511161837893
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;
      }   
  }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塔通天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值