Linux内核LCD驱动程序框架

1. 框架说明

Linux 内核中的 LCD 驱动程序与字符设备驱动程序的整体框架是差不多的,包括如下:

  1. 设置主设备号;
  2. 完成 file_operations 结构体的创建和填充;
  3. register_chrdev 注册成一个字符设备;
  4. 完成入口函数和出口函数的编写;

2. LCD 驱动程序框架

LCD 驱动程序的源码在 drivers/video/fbmem.c

2.1 分析

我们先从 init 入口函数着手进行分析:

static int __init fbmem_init(void)
{
    /* ...省略... */
	if (register_chrdev(FB_MAJOR,"fb",&fb_fops))	/* 注册字符设备 */
		printk("unable to get major %d for fb devs\n", FB_MAJOR);
    /* ...省略... */
}

对于 LCD 的操作使用的是 fb_fops 这个结构体:

static const struct file_operations fb_fops = {
	.owner =	THIS_MODULE,
	.read =		fb_read,
	.write =	fb_write,
	.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
};

假设应用程序调用 open 函数来打开 /dev/fb0(设备来源后面会说明) 文件时,最终会调用 fb_open 函数。同理,如果调用了 read 函数,最终会调用 fb_read 函数,我们先分析这两个函数。

fd_open 函数,该函数的定义如下:

static int fb_open(struct inode *inode, struct file *file)
{
	int fbidx = iminor(inode);	/* 获取设备的次设备号 */
	struct fb_info *info;
	int res = 0;
	/* ...省略... */
	if (!(info = registered_fb[fbidx]))	/* 在全局变量registered_fb中获取设备的fd_info */
		return -ENODEV;
    
	/* ...省略... */
    
	file->private_data = info;
	if (info->fbops->fb_open) {	
        /* 如果fd_info自己实现了open函数,则调用fd_info的open函数 */
		res = info->fbops->fb_open(info,1);
	/* ...省略... */
}

fd_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];	/* 同样的操作,获取fb_info */
	/* ...省略... */
	if (info->fbops->fb_read)	/* fb_info的读函数 */
		return info->fbops->fb_read(info, buf, count, ppos);
	
	/* 否则根据fb_info中的信息,完成读操作 */
	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 */

	while (count) {
		c  = (count > PAGE_SIZE) ? PAGE_SIZE : count;
		dst = buffer;
		for (i = c >> 2; i--; )
			*dst++ = fb_readl(src++);	/* 进行将显存的内容读入到buffer中 */
		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)) {	/* 将buffer的内容拷贝到用户空间 */
			err = -EFAULT;
			break;
		}
		*ppos += c;
		buf += c;
		cnt += c;
		count -= c;
	}

	kfree(buffer);	/* 释放buffer */

	return (err) ? err : cnt;
}

2.2 程序框架

从上面的 fd_read 和 fd_open 函数,我们可以看到,无论是打开文件,还是读取操作,都需要用到 fb_info 这个结构体。

而这个结构体又从全局变量 registered_fb 中,根据次设备号来获取 struct fb_info *info = registered_fb[fbidx]

#define FB_MAX			32	/* sufficient for now */
extern struct fb_info *registered_fb[FB_MAX];

那么 registered_fb 中的 fb_info 是在哪里填充的?通过搜索我们可以知道,在 register_framebuffer 函数中往 registered_fb 填充了 fb_info 的信息。

int register_framebuffer(struct fb_info *fb_info)
{
	/* ...省略... */
	for (i = 0 ; i < FB_MAX; i++)
		if (!registered_fb[i])
			break;
	/* ...省略... */
	registered_fb[i] = fb_info;
	/* ...省略... */
}

自此,我们就可以得出整个驱动程序的框架大致如下(非完整):
在这里插入图片描述

3. 源码分析

3.1 fb_info 结构

我们需要看一下 fb_info 结构的定义:

struct fb_info {
	int node;
	int flags;
	struct fb_var_screeninfo var;	/* Current var 存储可变的信息 */
	struct fb_fix_screeninfo fix;	/* Current fix 存储固定的信息 */
	struct fb_monspecs monspecs;	/* Current Monitor specs */
	struct work_struct queue;		/* Framebuffer event queue */
	struct fb_pixmap pixmap;		/* Image hardware mapper */
	struct fb_pixmap sprite;		/* Cursor hardware mapper */
	struct fb_cmap cmap;			/* Current cmap */
	struct list_head modelist;      /* mode list */
	struct fb_videomode *mode;		/* current mode */

	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 */
	char __iomem *screen_base;	/* Virtual address */
	unsigned long screen_size;	/* Amount of ioremapped VRAM or 0 */ 
	void *pseudo_palette;		/* Fake palette of 16 colors */ 
#define FBINFO_STATE_RUNNING	0
#define FBINFO_STATE_SUSPENDED	1
	u32 state;			/* Hardware state i.e suspend */
	void *fbcon_par;                /* fbcon use-only private area */
	/* From here on everything is device dependent */
	void *par;	
};

3.2 register_framebuffer 分析

register_framebuffer 的函数代码如下:

int register_framebuffer(struct fb_info *fb_info)
{
	int i;
	struct fb_event event;
	struct fb_videomode mode;

	if (num_registered_fb == FB_MAX)/* 已经注册的fb个数超过最大限度FB_MAX */
		return -ENXIO;
	num_registered_fb++;				
	for (i = 0 ; i < FB_MAX; i++)	/* 分配一个次设备号 */
		if (!registered_fb[i])
			break;
	fb_info->node = i;
	
    /* 在类下面创建一个设备节点,最终会生成一个/dev/fbi的设备 */
	fb_info->dev = device_create(fb_class, fb_info->device,
				     MKDEV(FB_MAJOR, i), "fb%d", i);		
	if (IS_ERR(fb_info->dev)) {
		/* Not fatal */
		printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", 
               i, PTR_ERR(fb_info->dev));
		fb_info->dev = NULL;
	} else
		fb_init_device(fb_info); /* 初始化fb_info结构体 */

    /* 在虚拟内存空间,分配一块FBPIXMAPSIZE大小的显存空间 */
	if (fb_info->pixmap.addr == NULL) {	
		fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
		if (fb_info->pixmap.addr) {
			fb_info->pixmap.size = FBPIXMAPSIZE;
			fb_info->pixmap.buf_align = 1;
			fb_info->pixmap.scan_align = 1;
			fb_info->pixmap.access_align = 32;
			fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
		}
	}	
	fb_info->pixmap.offset = 0;

	if (!fb_info->pixmap.blit_x)
		fb_info->pixmap.blit_x = ~(u32)0;

	if (!fb_info->pixmap.blit_y)
		fb_info->pixmap.blit_y = ~(u32)0;

	if (!fb_info->modelist.prev || !fb_info->modelist.next)
		INIT_LIST_HEAD(&fb_info->modelist);

	fb_var_to_videomode(&mode, &fb_info->var);
	fb_add_videomode(&mode, &fb_info->modelist);
	registered_fb[i] = fb_info;	/* 将fb_info结构体填充到全局变量registered_fb里 */

	event.info = fb_info;
	fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
	return 0;
}

我们以内核中自带的 s3c2410fb.c 为例,说明 register_framebuffer 函数的使用。

在 s3c2410fb.c 文件中,我们看到了一个名为 s3c2410fb_driver 的 platform_driver 定义:

static struct platform_driver s3c2410fb_driver = {
	.probe		= s3c2410fb_probe,
	.remove		= s3c2410fb_remove,
	.suspend	= s3c2410fb_suspend,
	.resume		= s3c2410fb_resume,
	.driver		= {
		.name	= "s3c2410-lcd",
		.owner	= THIS_MODULE,
	},
};

也就是说,LCD 驱动也是基于总线设备模型实现的,并且是基于平台设备实现的。

总线设备模型参考:Linux内核总线系统 —— 通用总线和平台设备_Bin Watson的博客-CSDN博客

平台设备模式参考:jz2440_基于平台设备的LED驱动程序_Bin Watson的博客-CSDN博客

了解平台设备模型的我们知道。当有平台设备匹配时,driver 程序的 .probe 函数会被调用。

3.3 s3c2410fb_probe 函数

s3c2410fb_probe 的定义如下:

static int __init s3c2410fb_probe(struct platform_device *pdev)
{
    struct s3c2410fb_info *info;
    struct fb_info	   *fbinfo;
    /*...省略...*/
    /* 分配fb_info结构体,大小为:sizeof(s3c2410fb_info) + sizeof(fb_info) */
    fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);	
    if (!fbinfo) {
        return -ENOMEM;
    }

    info = fbinfo->par;
    info->fb = fbinfo;
    info->dev = &pdev->dev;
    
	platform_set_drvdata(pdev, fbinfo);

	dprintk("devinit\n");

	strcpy(fbinfo->fix.id, driver_name);	/* 填充固定信息 */

    /* 填充fbinfo结构体 */
	fbinfo->fix.type	    = FB_TYPE_PACKED_PIXELS;
	fbinfo->fix.type_aux	    = 0;
	fbinfo->fix.xpanstep	    = 0;
	fbinfo->fix.ypanstep	    = 0;
	fbinfo->fix.ywrapstep	    = 0;
	fbinfo->fix.accel	    = FB_ACCEL_NONE;

	fbinfo->var.nonstd	    = 0;
	fbinfo->var.activate	    = FB_ACTIVATE_NOW;
	fbinfo->var.height	    = mach_info->height;
	fbinfo->var.width	    = mach_info->width;
	fbinfo->var.accel_flags     = 0;
	fbinfo->var.vmode	    = FB_VMODE_NONINTERLACED;

	fbinfo->fbops		    = &s3c2410fb_ops;
	fbinfo->flags		    = FBINFO_FLAG_DEFAULT;
	fbinfo->pseudo_palette      = &info->pseudo_pal;

	fbinfo->var.xres	    = mach_info->xres.defval;
	fbinfo->var.xres_virtual    = mach_info->xres.defval;
	fbinfo->var.yres	    = mach_info->yres.defval;
	fbinfo->var.yres_virtual    = mach_info->yres.defval;
	fbinfo->var.bits_per_pixel  = mach_info->bpp.defval;

	fbinfo->var.upper_margin    = S3C2410_LCDCON2_GET_VBPD(mregs->lcdcon2) + 1;
	fbinfo->var.lower_margin    = S3C2410_LCDCON2_GET_VFPD(mregs->lcdcon2) + 1;
	fbinfo->var.vsync_len	    = S3C2410_LCDCON2_GET_VSPW(mregs->lcdcon2) + 1;

	fbinfo->var.left_margin	    = S3C2410_LCDCON3_GET_HFPD(mregs->lcdcon3) + 1;
	fbinfo->var.right_margin    = S3C2410_LCDCON3_GET_HBPD(mregs->lcdcon3) + 1;
	fbinfo->var.hsync_len	    = S3C2410_LCDCON4_GET_HSPW(mregs->lcdcon4) + 1;

	fbinfo->var.red.offset      = 11;
	fbinfo->var.green.offset    = 5;
	fbinfo->var.blue.offset     = 0;
	fbinfo->var.transp.offset   = 0;
	fbinfo->var.red.length      = 5;
	fbinfo->var.green.length    = 6;
	fbinfo->var.blue.length     = 5;
	fbinfo->var.transp.length   = 0;
	fbinfo->fix.smem_len        =	mach_info->xres.max *
					mach_info->yres.max *
					mach_info->bpp.max / 8;
    
    /*...省略...*/
	ret = register_framebuffer(fbinfo);	/* 注册 */
    
	if (ret < 0) {
		printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret);
		goto free_video_memory;
	}

	/* create device files */
	device_create_file(&pdev->dev, &dev_attr_debug);

	printk(KERN_INFO "fb%d: %s frame buffer device\n",
		fbinfo->node, fbinfo->fix.id);
	/*...省略...*/
}

通过上面的代码,我们可以知道 s3c2410fb_probe 的完成的工作如下:

  1. 分配一个 fb_info 结构体;
  2. 设备 fb_info,填充相关的信息;
  3. 调用 register_framebuffer 进行注册;
  4. 其余硬件相关的操作;

既然 s3c2410fb 是一个 driver 程序,那么为什么还需要在 driver 和应用程序之间引入一层 fbmem 呢?

答案:内核中的 LCD 驱动程序是有很多相同的代码,例如:应用程序可以通过 ioctl 来获取屏幕的参数,如分辨率大小等信息;而这些操作需要应用程序与驱动程序进行协商,提高一个所有 LCD 驱动程序统一接口,因此在 LCD driver 程序和应用程序之间又引入了一层 fbmem 层,由 fbmem 层提供一个统一的接口给应用程序调用。应用程序进而间接访问 LCD driver 驱动程序。

我们以 ioctl 函数为例。

3.4 fb_ioctl 函数

static int 
fb_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
	 unsigned long arg)
{
	int fbidx = iminor(inode);
	struct fb_info *info = registered_fb[fbidx];
	struct fb_ops *fb = info->fbops;
	struct fb_var_screeninfo var;
	struct fb_fix_screeninfo fix;
	struct fb_con2fbmap con2fb;
	struct fb_cmap_user cmap;
	struct fb_event event;
	void __user *argp = (void __user *)arg;
	int i;
	
	if (!fb)
		return -ENODEV;
	switch (cmd) {
	case FBIOGET_VSCREENINFO:
			/*...省略...*/
	case FBIOPUT_VSCREENINFO:
			/*...省略...*/
	case FBIOGET_FSCREENINFO:
			/*...省略...*/
	case FBIOPUTCMAP:
			/*...省略...*/
	case FBIOGETCMAP:
			/*...省略...*/
	case FBIOPAN_DISPLAY:
			/*...省略...*/
	case FBIO_CURSOR:
			/*...省略...*/
	case FBIOGET_CON2FBMAP:
			/*...省略...*/
	case FBIOPUT_CON2FBMAP:
			/*...省略...*/
	case FBIOBLANK:
			/*...省略...*/
	default:
		if (fb->fb_ioctl == NULL)
			return -EINVAL;
		return fb->fb_ioctl(info, cmd, arg);
	}
}

上面 ioctl 根据 cmd 的类型执行不同的操作。而这些代码在所有驱动程序中都是通用的。

4. 完整的框架

在这里插入图片描述
为了统一 LCD 驱动的实现,在 LCD driver 和 应用程序之间引入了一层 fbmem。

LCD driver 和 fbmem 层之间通过 fb_info 结构体联系在一起。下层的 driver 层需要填充 fb_info 结构体,上层 fbmem 也可以动态设置 fb_info 的信息。

而 LCD 驱动程序又是基于平台设备模型的,因此硬件层也进行的分层操作。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值