您的位置:首页 > 运维架构 > Linux

linux lcd设备驱动剖析三

2014-01-12 21:44 477 查看
上一节文章中详细地剖析了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,
};

这并不是我们想要的打开读写操作函数。上一节文章链接:/article/8095429.html

问:那到底帧缓冲设备的文件操作结构体在哪里呢?

答:在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. 硬件相关的操作
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: