概述
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 devsn", 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 = %dn", 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(®istration_lock);
fb_info = registered_fb[idx];
if (fb_info)
atomic_inc(&fb_info->count);
mutex_unlock(®istration_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 */
};
不同的显示屏就要根据自己的性能设备这些参数,然后注册这个结构体。
最后
以上就是犹豫书本为你收集整理的Linux内核LCD驱动分析(一)的全部内容,希望文章能够帮你解决Linux内核LCD驱动分析(一)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复