Linux内核LCD驱动程序框架
1. 框架说明
Linux 内核中的 LCD 驱动程序与字符设备驱动程序的整体框架是差不多的,包括如下:
- 设置主设备号;
- 完成 file_operations 结构体的创建和填充;
- register_chrdev 注册成一个字符设备;
- 完成入口函数和出口函数的编写;
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 驱动也是基于总线设备模型实现的,并且是基于平台设备实现的。
了解平台设备模型的我们知道。当有平台设备匹配时,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 的完成的工作如下:
- 分配一个 fb_info 结构体;
- 设备 fb_info,填充相关的信息;
- 调用 register_framebuffer 进行注册;
- 其余硬件相关的操作;
既然 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 驱动程序又是基于平台设备模型的,因此硬件层也进行的分层操作。