linux lcd设备驱动剖析三

上一节文章中详细地剖析了probe函数,但是从始至终都没有看到打开读写文件接口的操作函数,只看到了下面这个操作结构体

static struct fb_ops s3c2410fb_ops = {
	.owner			= THIS_MODULE,
	.fb_check_var	= s3c2410fb_check_var,
	.fb_set_par		= s3c2410fb_set_par,
	.fb_blank		= s3c2410fb_blank,
	.fb_setcolreg	= s3c2410fb_setcolreg,
	.fb_fillrect	= cfb_fillrect,
	.fb_copyarea	= cfb_copyarea,
	.fb_imageblit	= cfb_imageblit,
};
这并不是我们想要的打开读写操作函数。上一节文章链接: http://blog.csdn.net/lwj103862095/article/details/18189765
问:那到底帧缓冲设备的文件操作结构体在哪里呢?

答:在drivers/vedio/fbmem.c文件里。

从入口函数开始看:

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注册了一个主设备号为29的字符设备,并创了graphics类(图形类)。

字符设备有一个关键的成员是文件操作结构体

static const struct file_operations fb_fops = {
	.owner 			= THIS_MODULE,
	.read 			= fb_read,
	.write 			= fb_write,
	.unlocked_ioctl = fb_ioctl,
	.mmap 			= fb_mmap,
	.open 			= fb_open,
	.release 		= fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
	.get_unmapped_area = get_fb_unmapped_area,
#endif
};
理所当然的,我们应当首先看的函数是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;

	if (fbidx >= FB_MAX)			/* 次设备号有没有大于规定的最大值32 */
		return -ENODEV;				/* 没有这样的设备 */
	info = registered_fb[fbidx];	/* 使用次设备号得到fb_info结构体 */
	if (!info)
		request_module("fb%d", fbidx);

	/* 再次使用次设备号得到fb_info结构体 */
	info = registered_fb[fbidx];
	if (!info)
		return -ENODEV;
	
	mutex_lock(&info->lock);		/* 获取mutex */

	/* 获取模块使用计数module,成功返回非NULL */
	if (!try_module_get(info->fbops->owner)) {
		res = -ENODEV;
		goto out;
	}

	/* 从registered_fb[]数组项里找到一个fb_info结构体保存到
	 * struct file结构中的私有信息指针赋值给它呢是为了以后调用
	 * read、write、ioctl等系统调用时找到这个struct fb_info结构
	 */
	file->private_data = info;	

	/* registered_fb[]数组项里有没有默认的fb_open函数,如果有就使用它 */
	if (info->fbops->fb_open) {
		res = info->fbops->fb_open(info,1);	

		/* 有默认的fb_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);	/* 释放mutex */
	return res;
}
发现fb_open函数是围绕fb_info来实现的,而 fb_info设置为registered_fb[fbidx]

问:registered_fb[fbidx]结构体数组是在哪里被设置?

答:register_framebuffer函数里设置registered_fb

/* register_framebuffer()函数的主要工作是设置fb_info结构体的一些成员 */
int
register_framebuffer(struct fb_info *fb_info)
{
	int i;
	struct fb_event event;
	struct fb_videomode mode;

	/* num_registered_fb代表注册帧缓冲设备的个数 */
	if (num_registered_fb == FB_MAX)
		return -ENXIO;

	if (fb_check_foreignness(fb_info))
		return -ENOSYS;

	num_registered_fb++;

	/* 当registered_fb[]项都为NULL,就会break,找到一个空的次设备号 */
	for (i = 0 ; i < FB_MAX; i++)
		if (!registered_fb[i])	
			break;
	fb_info->node = i;
	mutex_init(&fb_info->lock);		/* 初始化mutex */

	/* 因为在init加载函数里只创建了类,这里在类下面创建设备 */
	fb_info->dev = device_create(fb_class, fb_info->device,
				     MKDEV(FB_MAJOR, i), NULL, "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);	/* 对struct fb_info做一些初始化 */


	/* 初始化fb_info->pixmap结构体成员 */
	if (fb_info->pixmap.addr == NULL) {
		fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL); /* 8K大小 */
		if (fb_info->pixmap.addr) {
			fb_info->pixmap.size = FBPIXMAPSIZE;	/* 8K */
			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);	/* 初始化modelist链表 */

	fb_var_to_videomode(&mode, &fb_info->var);
	fb_add_videomode(&mode, &fb_info->modelist);

	/* registered_fb[]数组项在这里被设置 */
	registered_fb[i] = fb_info;

	event.info = fb_info;

	
	if (!lock_fb_info(fb_info)) /* 上锁 */
		return -ENODEV;
	fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
	unlock_fb_info(fb_info);	/* 解锁 */
	return 0;
}
fb_read函数源码分析

static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
	unsigned long p = *ppos;
	/* 通过file结构体的成员得到inode节点 */
	struct inode *inode = file->f_path.dentry->d_inode;

	/* 获取次设备号 */
	int fbidx = iminor(inode);
	/* 以次设备号为下标找到一项fb_info结构体 */
	struct fb_info *info = registered_fb[fbidx];
	
	u32 *buffer, *dst;
	u32 __iomem *src;
	int c, i, cnt = 0, err = 0;
	unsigned long total_size;

	if (!info || ! info->screen_base) /* screen_base是虚拟(显存)基地址 */
		return -ENODEV;

	if (info->state != FBINFO_STATE_RUNNING)
		return -EPERM;		/* 禁止操作 */

	/* 如果registered_fb[]项里面提供了fb_read()函数,就调用下面的函数 */
	if (info->fbops->fb_read)
		return info->fbops->fb_read(info, buf, count, ppos);

	/* 没有默认的读函数就从下面的screen_base里读数据 */
	total_size = info->screen_size;  /* x*y*4,x,y分别为屏幕分辨率 */

	if (total_size == 0)
		total_size = info->fix.smem_len;	/* fb缓冲区的长度 */

	if (p >= total_size)			/* 调整读的偏移位置 */
		return 0;

	if (count >= total_size)
		count = total_size;  		/* 一次性最多读多少个字节 */

	if (count + p > total_size)
		count = total_size - p;		/* 调整读的位置及能读多少字节 */

	/* 分配内存,最大分配4K的大小 */
	buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
			 GFP_KERNEL);		
	if (!buffer)
		return -ENOMEM;

	src = (u32 __iomem *) (info->screen_base + p);  /* 源虚拟基地址 */

	/* 如果registered_fb[]项里面提供了fb_sync()函数,就调用下面的函数 */
	if (info->fbops->fb_sync)		
		info->fbops->fb_sync(info);

	while (count) {
		/* 读多少计数变量,单位为byte */
		c  = (count > PAGE_SIZE) ? PAGE_SIZE : count; 
		
		/* buffer是指向刚分配内存的首地址的指针 */
		dst = buffer;	/* dst指针指向buffer */

		/* 先除以4,因为每次读4个字节 */
		for (i = c >> 2; i--; )			
			*dst++ = fb_readl(src++);	/* 拷贝源虚拟机基地址的数据到目标地址 */

		/* 判断是否以字节为单位来读取 */
		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;
		}

		/* 从内核刚申请内存的地址buffer拷贝c长度的数据到用户空间的buf里去 */
		if (copy_to_user(buf, buffer, c)) {
			err = -EFAULT;	/* 成功拷贝,则err返回值为0 */
			break;
		}
		*ppos += c;		/* 调整偏移位置 */
		buf += c;		/* 调整用户的buf */
		cnt += c;
		/* count变量减去已经读取的c数量,用于判断while(count)是否为真*/
		count -= c;		
	}

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

	return (err) ? err : cnt;	/* err = 0时,返回被拷贝成功的数量cnt */
}
fb_write函数源码分析

static ssize_t
fb_write(struct file *file, const 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];
	u32 *buffer, *src;
	u32 __iomem *dst;
	int c, i, 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_write)
		return info->fbops->fb_write(info, buf, count, ppos);
	
	total_size = info->screen_size;

	if (total_size == 0)
		total_size = info->fix.smem_len;

	if (p > total_size)
		return -EFBIG;

	if (count > total_size) {
		err = -EFBIG;
		count = total_size;
	}

	if (count + p > total_size) {
		if (!err)
			err = -ENOSPC;

		count = total_size - p;
	}

	buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
			 GFP_KERNEL);
	if (!buffer)
		return -ENOMEM;

	dst = (u32 __iomem *) (info->screen_base + p);	/* 源虚拟基地址 */

	if (info->fbops->fb_sync)
		info->fbops->fb_sync(info);

	while (count) {
		c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
		src = buffer;	/* buffer为指向刚申请的内存的指针 */


		/* 从用户空间的buf地址里拷贝c长度的数据到src内存里,成功时返回0 */
		if (copy_from_user(src, buf, c)) {
			err = -EFAULT;
			break;
		}

		for (i = c >> 2; i--; )			/* 以4字节为单位拷贝数据 */
			fb_writel(*src++, dst++);	/*     *dst++ = *src++     */

		if (c & 3) {					/* 以字节为单位拷贝数据 */
			u8 *src8 = (u8 *) src;
			u8 __iomem *dst8 = (u8 __iomem *) dst;

			for (i = c & 3; i--; )
				fb_writeb(*src8++, dst8++);

			dst = (u32 __iomem *) dst8;
		}

		*ppos += c;
		buf += c;
		cnt += c;
		count -= c;
	}

	kfree(buffer);

	return (cnt) ? cnt : err;
}
fb_ioctl函数源码分析

static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct inode *inode = file->f_path.dentry->d_inode;
	int fbidx = iminor(inode);
	struct fb_info *info = registered_fb[fbidx];

	/* 这个才是真正的fb_ioctl驱动函数 */
	return do_fb_ioctl(info, cmd, arg);	
}
do_fb_ioctl函数根据cmd来设置各种命令,这里仅举例说明:

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;
fb_mmap源码分析:

/* 这里分配的显存是在内核空间分配的,用户空间并不能直接访问,
 * 所以需要用到这里的mmap函数,直接将这段内存空间映射到
 * 用户空间去,用户空间就能访问这段内存空间了。
 */
static int
fb_mmap(struct file *file, struct vm_area_struct * vma)
__acquires(&info->lock)
__releases(&info->lock)
{
	int fbidx = iminor(file->f_path.dentry->d_inode);
	struct fb_info *info = registered_fb[fbidx]; /* 通过次设备号找到fb_info结构体 */
	struct fb_ops *fb = info->fbops;
	unsigned long off;
	unsigned long start;
	u32 len;

	if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
		return -EINVAL;
	off = vma->vm_pgoff << PAGE_SHIFT;
	if (!fb)
		return -ENODEV;

	/* 如果registered_fb[]里有默认的fb_mmap就使用它 */
	if (fb->fb_mmap) {
		int res;
		mutex_lock(&info->lock);
		res = fb->fb_mmap(info, vma);
		mutex_unlock(&info->lock);
		return res;
	}

	mutex_lock(&info->lock);

	/* 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->lock);
			return -EINVAL;
		}
		start = info->fix.mmio_start;
		len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len);
	}
	mutex_unlock(&info->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;
	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;
}
问:怎么写LCD驱动程序?
1. 分配一个fb_info结构体: framebuffer_alloc
2. 设置
3. 注册: register_framebuffer
4. 硬件相关的操作

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值