Linux系统LCD驱动架构分析

  我们一起来看一个Linux系统中最重要的输出系统的驱动架构---LCD驱动。在Linux中,LCD驱动采用了帧缓冲(FrameBuffer)技术,所以LCD驱动又叫FrameBuffer驱动。在很多地方,这两种叫法是通用的。本文章的核心内容就是搞清楚FrameBuffer的程序架构,弄明白内核已经帮我们做了多少工作,我们自己又该做哪些工作。需要跟大家剧透一点信息,FrameBuffer驱动架构和input子系统驱动架构的实现思路上面有很多共性,在学习的时候对比着学效果更佳。如果您电脑上已经安装了SourceInsignt软件,并且也有现成的Linux内核源码的话,那请打开软件边操作边学吧,效果更佳哦!

        如图所示是FrameBuffer驱动的总体架构。用户空间的应用程序通过FrameBuffer设备文件(/dev/graphics/fbX)与FrameBuffer进行交互,而直接参与交互的是fbmem.c文件,从图中可以看出fbmem.c提供了fb_read(), fb_write()等回调函数。fbmem.c是内核帮我们完成的framebuffer驱动的核心文件,类似于平台设备驱动总线的platform_driver,它为上层应用程序提供回调函数接口,为下层硬件相关的设备驱动程序提供注册通道。xxxfb.c文件则是与硬件相关的设备驱动程序,类似与平台设备总线的platform_device,该文件由驱动工程师完成,其最主要的工作就是初始化fb_info结构体并进行注册。

 

 

介绍该架构中最重要的一个结构体------fb_info结构体。

代码清单 fb_info结构体

struct fb_info {
	atomic_t count;
	int node;
	int flags;
	struct mutex lock;		/* Lock for open/release/ioctl funcs */
	struct mutex mm_lock;		/* Lock for fb_mmap and smem_* fields */
	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 */

#ifdef CONFIG_FB_BACKLIGHT
	/* assigned backlight device */
	/* set before framebuffer registration, 
	   remove after unregister */
	struct backlight_device *bl_dev;

	/* Backlight level curve */
	struct mutex bl_curve_mutex;	
	u8 bl_curve[FB_BACKLIGHT_LEVELS];
#endif
#ifdef CONFIG_FB_DEFERRED_IO
	struct delayed_work deferred_work;
	struct fb_deferred_io *fbdefio;
#endif

	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 */
#ifdef CONFIG_FB_TILEBLITTING
	struct fb_tile_ops *tileops;    /* Tile Blitting */
#endif
	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;
	/* we need the PCI or similar aperture base/size not
	   smem_start/size as smem_start may just be an object
	   allocated inside the aperture so may not actually overlap */
	struct apertures_struct {
		unsigned int count;
		struct aperture {
			resource_size_t base;
			resource_size_t size;
		} ranges[0];
	} *apertures;
};


fb_info结构体比较复杂,但是我们不需要了解每一个成员变量的功能和使用方法。在这里面有3个最主要的结构体,分别是fb_var_screeninfo,fb_fix_screeninfo和fb_ops。其中,fb_var_screeninfo结构体记录了用户可以修改的显示参数,比如屏幕分辨率,屏幕颜色位域等;fb_fix_screeninfo结构体则记录了用户不可修改的参数,比如屏幕缓冲区的物理地址和缓存的长度等。fb_ops结构体记录了对底层硬件操作的函数指针,也就是充当了file_operations结构体的角色,如果fb_info结构体对象定义了fb_ops,那应用程序发出的read命令最终访问到的将是fb_info->fb_ops->fb_read函数,如果fb_info结构体对象没有定义fb_ops则read命令访问的是input.c中定义的fb_read函数。这些知识当然都是由内核告诉我们的,下文分析内核源码的时候大家就明白了。

         如果您了解input子系统的话知道input子系统有一个核心文件input.c,它为应用程序提供访问接口。Framebuffer架构则以fbmem.c为核心,也是应用程序访问的接口。现在以应用程序发出read命令访问设备节点为线索分析以下源代码。首先利用SourceInsight软件定位到fbmem.c的入口函数。

 


代码清单 fbmem_init(void)入口函数

static int __init fbmem_init(void)
{
	proc_create("fb", 0, NULL, &fb_proc_fops);

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


虽然fbmem_init入口函数代码不长,但是结合我们之前所学的知识分析的话,这里面信息量很大。分几点说明

1) 首先看到一句非常熟悉的register_chrdev(FB_MAJOR,”fb”,&fb_fops)注册函数,这就证明了FrameBuffer驱动也是一个字符型设备驱动。主设备号FB_MAJOR = 29。

2) 接着看到了class_create()创建类,然而并没有创建设备文件。可以大胆的做如下猜测:fbmem.c是framebuffer架构的纯软件部分,是内核帮我们提取出来的。我们自己编写LCD驱动程序的时候只需要编写与硬件息息相关的设备驱动程序即可,然后将某一个代表硬件的结构体注册进内核,与此同时内核才会创建与该硬件相关的设备文件节点。那是不是这样的呢?肯定是,不是就见鬼了,不信就接着往下分析。

3) register_chrdev()函数中第三个参数&fb_fops代表file_operations结构体,是字符设备驱动的核心部分,这里面肯定会提供read、write等回调函数。或者像input子系统那样虽然只提供了一个open函数,但是在open函数里面又进一步的访问到了特定handler的fops。定位到fb_fops看一下。


代码清单  fb_ops

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,
};


果然看到了read、write等非常熟悉的老朋友。假如应用程序发出read命令,则会触发fb_read回调函数。点进去看一下。


代码清单 input.c中的fb_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;
	int c, cnt = 0, err = 0;
	unsigned long total_size;

	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);
	
	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 = (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);
		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;
}


fb_read函数的思路是这样子的。首先通过file_fb_info(file)函数获得了一个fb_info结构体,该结构体代表了一款特定的LCD硬件。如果fb_info结构体中定义了fops成员变量,并且fops中定义了read成员函数,则调用这个read函数。否则继续执行fb_read中的copy_to_user()。可以看出来,fbmem.c在某种程度上起到了中转站的作用。目前的线索指向了file_fb_info(file)函数,它到底是如何返回的fb_info结构体呢?追踪进去看一下。


代码清单 file_fb_info()

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


原来这个fb_info结构体是一个叫registered_fb[]数组中的一个元素,这个数组从字面上解释叫“已经注册的fb”,猜也能猜到这个数组肯定是在注册fb_info的时候赋值的。这是不是跟输入子系统中那个input_table[]数组比较类似呢。不同之处是input_table[]是在注册纯软件驱动的input_handler时赋值,而registered_fb[]是在注册fb_info这个硬件相关的结构体时赋值的。查找一个registered_fb[]数组的赋值位置。


代码清单 do_register_framebuffer()函数

static int do_register_framebuffer(struct fb_info *fb_info)
{
	... ...
fb_info->dev = device_create(fb_class, fb_info->device,
				     MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
... ...
	registered_fb[i] = fb_info;
 	... ...
} 

在do_register_framebuffer中有两个重大发现:

1). 调用了device_craate()函数,创建了我们非常熟悉的字符型设备驱动的设备文件节点

2). 找到了registered_fb[]的赋值位置。继续搜索do_register_framebuffer的调用位置。

 

代码清单 register_framebuffer()

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


原来是这个叫做register_framebuffer的函数调用了do_register_framebuffer。这个register_framebuffer和我们学过的register_chrdev长得好像,估计它就是用来注册fb_info结构体的核心函数了。全文搜索一下register_framebuffer的调用位置,会发现好多与硬件相关的驱动程序中都调用了register_framebuffer来注册fb_info结构体。

 


分析到这,framebuffer驱动的架构层次也就出来了。驱动工程师在编写LCD驱动的时候最重要的是初始化一个fb_info结构体。当设置好fb_info结构体之后,使用register_framebuffer(struct fb_info *)函数进行注册,这时内核就会把该fb_info放到registered_fb[]数组中,并创建一个设备文件节点。当应用程序发出read等请求访问设备文件节点的时候,input.c中的read回调函数会首先根据次设备号将fb_info从registered_fb[]中取出,然后看看fb_info中是否设置了fops成员变量如设置,则调用fops中的read回调函数,否则继续执行input.c中的read回调函数。

        那我们该如何编写一个LCD驱动的设备驱动程序呢?其实从上文的源码分析中我们应该已经知道具体该做什么了。今天就先到这吧,等我有时间了,或者大家迫切需要这方面知识了,我再把“如何一步一步编写LCD驱动”的文章贴出来。


  • 13
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值