Linux内核LCD驱动分析(一)

LCD驱动其实也是字符型驱动的一种,只不过是结构上没有那么明显,细细的看下来其实还是可以看出来的。
在Linux内核中,LCD驱动又叫FrameBuffer驱动,即帧缓冲子系统,缓冲区中的内容对应于屏幕上的内容,可以将其简单理解为屏幕上显示内容对应的缓存,修改Framebuffer中的内容,就可以改变屏幕上的显示。在内核代码中,其源码是drivers/video/fbdev/core/fbmem.c

static int __init
fbmem_init(void)
{
	int ret;

	if (!proc_create("fb", 0, NULL, &fb_proc_fops))
		return -ENOMEM;

	ret = register_chrdev(FB_MAJOR, "fb", &fb_fops);
	if (ret) {
		printk("unable to get major %d for fb devs\n", FB_MAJOR);
		goto err_chrdev;
	}

	fb_class = class_create(THIS_MODULE, "graphics");
	if (IS_ERR(fb_class)) {
		ret = PTR_ERR(fb_class);
		pr_warn("Unable to create fb class; errno = %d\n", ret);
		fb_class = NULL;
		goto err_class;
	}

	fb_console_init();

	return 0;

err_class:
	unregister_chrdev(FB_MAJOR, "fb");
err_chrdev:
	remove_proc_entry("fb", NULL);
	return ret;
}

从驱动的入口函数中可以看出,LCD驱动始终离不开字符型驱动的框架,驱动会调用proc_create创建一个proc虚拟文件,用于报告驱动状态和参数,以方便和内核交互。然后就会使用register_chrdev注册一个字符设备,可见,使用register_chrdev注册这个方式在新的内核中还是有用到的。由FB_MAJOR可知,帧缓冲设备在内核中的主设备号是29。然后使用class_create接口创建了一个名字叫做graphics的类,即在内核中可以看到有/sys/class/graphics这个目录。但是并没有看到它在这个类下面创建设备。新的内核还会调用fb_console_init函数,从名字上看出,它是初始化一个帧缓冲控制台的,这里暂且不用理会。
关键在于,这个字符设备的file_operations结构体,也就是fb_fops。

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,
#if defined(HAVE_ARCH_FB_UNMAPPED_AREA) || \
	(defined(CONFIG_FB_PROVIDE_GET_FB_UNMAPPED_AREA) && \
	 !defined(CONFIG_MMU))
	.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
	.fsync =	fb_deferred_io_fsync,
#endif
	.llseek =	default_llseek,
};

当应用程序使用系统调用接口去操作/dev/fbX这个节点的时候,就会导致这个结构体对应的函数被调用。例如,当应用程序使用open函数打开/dev/fbX设备的时候,这个结构体中的fb_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;
	int res = 0;

	info = get_fb_info(fbidx);
	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;
	if (info->fbops->fb_open) {
		res = info->fbops->fb_open(info,1);
		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;
}

函数一开始就从inode节点获取该设备的次设备号,也就是/dev/fbX中对应的X是多少。然后调用get_fb_info函数,根据次设备号获得一个结构体指针。然后将获得到的结构体指针赋值给file->private_data = info;,这样就能在其他的系统调用接口拿到这个结构体了。然后就是,判断一下这个结构体下有没有fbops->fb_open这个成员,有的话就调用它,没有的话就算了。
好像也没有干什么事,关键的是get_fb_info这个函数,为什么传入一个次设备号就能得到一个结构体指针呢?我们看一下这个函数的实现。

static struct fb_info *get_fb_info(unsigned int idx)
{
	struct fb_info *fb_info;

	if (idx >= FB_MAX)
		return ERR_PTR(-ENODEV);

	mutex_lock(&registration_lock);
	fb_info = registered_fb[idx];
	if (fb_info)
		atomic_inc(&fb_info->count);
	mutex_unlock(&registration_lock);

	return fb_info;
}

这个函数简直可怕,直接了当,从registered_fb数组中拿到一项,返回就可以了。那问题来了,这个数组是谁来填充的呢,难道一开始就有?
我在内核中搜索一下这个数组。发现只有do_register_framebuffer这个函数会使用registered_fb[i] = fb_info对这个数组赋值。那就顺藤摸瓜,看看谁在调用这个函数。继续搜索一下do_register_framebuffer这个函数。是的,你会发现只有register_framebuffer这个函数在调用它。这个是一个EXPORT_SYMBOL(register_framebuffer);函数,估计会有很多的驱动调用这个函数注册fb_info, 然后将fb_info一个个的添加到刚刚的那个数组中去。为了验证这个猜想,我搜索了一下这个函数的调用者,真的有好多,各种各样的LCD驱动程序都调用了这个接口注册fb_info结构体。
我们先不去理会这些驱动的实现,我们回到刚刚的fbmem.c中,当应用程序使用open这个系统调用的时候,会调用的逻辑我们已经知道,其实就是找到某一个驱动注册的fb_info结构体,然后调用fb_info结构体提供的open函数。那应用程序通过read这个函数读的时候,会是怎么样的实现呢,我们先分析一下这个读的过程。
当应用程序使用read接口读/dev/fbX这个设备的时候,会导致fb_read这个函数被调用,下面是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;
}

函数一开始也是通过file_fb_info这个函数获得一个结构体,传入的参数是file指针。看看这个函数的实现。

static struct fb_info *file_fb_info(struct file *file)
{
	struct inode *inode = file_inode(file);
	int fbidx = iminor(inode);
	struct fb_info *info = registered_fb[fbidx];

	if (info != file->private_data)
		info = NULL;
	return info;
}

一开始我以为是从file指针的private_data直接返回这个fb_info结构体,因为在open的时候已经对这个private_data成员变量赋值了。但是函数并不是从file指针中获取的fb_info结构体, 而是从registered_fb数组中获取的,其实无论是从数组获取还是从file指针获取,这个结构体应该都是一样的,为了保险起见,内核还是做了一下判断,如果这两个东西不一样,就返回一个空,这样相当于对registered_fb数组中的数据进行了一次验证。
继续回到fb_read函数,从数组中根据次设备号获取到一个fb_info结构体之后,函数中会对这个结构的数据参数进行各种判断,从这些判断中我们可以知道,某一个驱动注册fb_info结构体的时候,必须设置某些参数,否者就会导致这个函数不能进行下去,比如说info->screen_base这个就不能为空。函数中还判断了fb_info结构体是否存在fbops->fb_read成员变量,如存在,直接调用fbops->fb_read函数,然后直接返回这个函数的返回值。如果不存在,就使用kmalloc自己分配一块内存,然后从info->screen_base这个地方开始复制,复制info->screen_size这个这么大的范围,复制到刚刚自己分配的内存中,然后将刚刚分配的那块内存返回给用户空间。换句话说,当应用程序使用读接口的时候,fb_mem会去找到某一个驱动的fb_info结构体,判断一下这个结构体有没有提供读函数,如果有,就使用fb_info中的读函数,如果没有,就根据fb_info中指定的参数,自己拷贝数据返回给用户。
然后你会发现,当用户执行write的过程也是一样的,判断一下是否有提供write函数,有就执行,没有就自己帮你写。
很显然,fb_mem这个驱动仅仅是做一些共性的操作,相当于对上层提供了一个标准的接口,具体的差异,需要某一个驱动自己提供读写函数,对于应用程序来讲,其实就屏蔽了这些具体的差异,因为对于它们来说,读写都是一样的,无论你是什么样的LCD。这就有点像面向对象编程中的重写,可以有效的减少代码的重复性,有很方便的实现了重定义。
既然到处都是fb_info结构体,那这个fb_info一定是驱动的核心部分, 这个结构体的定义在include/linux/fb.h中。

struct fb_info {
	atomic_t count;         //打开计数器
	int node;               //文件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
	union {
		char __iomem *screen_base;	/* Virtual address 显存的起始地址,虚拟地址*/
		char *screen_buffer;
	};
	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;

	bool skip_vt_switch; /* no VT switch on suspend/resume required */
};

这个结构体中,其中fb_var_screeninfo/fb_fix_screeninfo对具体的硬件进行详细的描述。

struct fb_var_screeninfo {
	__u32 xres;			/* visible resolution		绝对的可视区域,就是真正硬件分辨率 */
	__u32 yres;
	__u32 xres_virtual;		/* virtual resolution		虚拟的分辨率 */
	__u32 yres_virtual;
	__u32 xoffset;			/* offset from virtual to visible 虚拟分辨率和可视分辨率之间的行偏移 */
	__u32 yoffset;			/* resolution			*/

	__u32 bits_per_pixel;		/* guess what			每个像素的bit数 */
	__u32 grayscale;		/* 0 = color, 1 = grayscale,	灰度 */
					/* >1 = FOURCC			*/
	struct fb_bitfield red;		/* bitfield in fb mem if true color, RGB中红绿蓝占的位域 */
	struct fb_bitfield green;	/* else only length is significant */
	struct fb_bitfield blue;
	struct fb_bitfield transp;	/* transparency			透明度 */	

	__u32 nonstd;			/* != 0 Non standard pixel format */

	__u32 activate;			/* see FB_ACTIVATE_*		*/

	__u32 height;			/* height of picture in mm    */
	__u32 width;			/* width of picture in mm     */

	__u32 accel_flags;		/* (OBSOLETE) see fb_info.flags */

	/* Timing: All values in pixclocks, except pixclock (of course) 显示时序*/
	__u32 pixclock;			/* pixel clock in ps (pico seconds) */
	__u32 left_margin;		/* time from sync to picture	*/
	__u32 right_margin;		/* time from picture to sync	*/
	__u32 upper_margin;		/* time from sync to picture	*/
	__u32 lower_margin;
	__u32 hsync_len;		/* length of horizontal sync	*/
	__u32 vsync_len;		/* length of vertical sync	*/
	__u32 sync;			/* see FB_SYNC_*		*/
	__u32 vmode;			/* see FB_VMODE_*		*/
	__u32 rotate;			/* angle we rotate counter clockwise */
	__u32 colorspace;		/* colorspace for FOURCC-based modes */
	__u32 reserved[4];		/* Reserved for future compatibility */
};
struct fb_fix_screeninfo {
	char id[16];			/* identification string eg "TT Builtin" 字符串形式的标示符 */
	unsigned long smem_start;	/* Start of frame buffer mem 显存的开始地址,这里是物理地址 */
					/* (physical address) */
	__u32 smem_len;			/* Length of frame buffer mem 显存的大小 */
	__u32 type;			/* see FB_TYPE_*		显示器的类型 */
	__u32 type_aux;			/* Interleave for interleaved Planes */
	__u32 visual;			/* see FB_VISUAL_*		*/ 
	__u16 xpanstep;			/* zero if no hardware panning  */
	__u16 ypanstep;			/* zero if no hardware panning  */
	__u16 ywrapstep;		/* zero if no hardware ywrap    */
	__u32 line_length;		/* length of a line in bytes    */
	unsigned long mmio_start;	/* Start of Memory Mapped I/O   内存映射IO的开始位置*/
					/* (physical address) */
	__u32 mmio_len;			/* Length of Memory Mapped I/O  内存映射IO的长度 */
	__u32 accel;			/* Indicate to driver which	*/
					/*  specific chip/card we have	*/
	__u16 capabilities;		/* see FB_CAP_*			*/
	__u16 reserved[2];		/* Reserved for future compatibility */
};

不同的显示屏就要根据自己的性能设备这些参数,然后注册这个结构体。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

mr_xiaogui

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

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

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

打赏作者

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

抵扣说明:

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

余额充值