出来工作两年,随着工作的深入,一直很少有时间写博客,并且随着工作各种零碎的调试,有时候很容易忘记自己所学的一些东西,近期就像用写博客来巩固自己刚入门时的一些基础知识以及往后学到的一些知识。
linux系统lcd驱动也称framebuff驱动,驱动框架如下图:
应用程序通过打开设备节点dev/graphics/fbX,然后通过字符设备访问的方式访问lcd驱动。fbmem.c便是向系统注册一个字符设备,并且提供的了字符设备的操作接口,fb_read,fb_write,fb_ioctl等等。如下看代码:
代码路径:
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;
}
return 0;
err_class:
unregister_chrdev(FB_MAJOR, "fb");
err_chrdev:
remove_proc_entry("fb", NULL);
return ret;
}
#ifdef MODULE
module_init(fbmem_init);
static void __exit
fbmem_exit(void)
{
remove_proc_entry("fb", NULL);
class_destroy(fb_class);
unregister_chrdev(FB_MAJOR, "fb");
}
module_exit(fbmem_exit);
从上面看出fbmem_init,fbmem_exit等就是标准的内核初始化函数,其中fbmem_init中的register_chrdev是字符设备设备注册的接口,说明我们的fbmem是标准的字符设备驱动,并且设备主设备号是FB_MAJOR(该值宏定义为29),既然是字符设备驱动都会有文件操作集合(这是我们第一次学习字符设备便接触的),这里文件操作集合便是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,
#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
.llseek = default_llseek,
};
当用户空间访问字符设备驱动的时候,调用write的时候会有如下调用过程:
write()-->fb_write()
那么我们就从write接口的访问角度来看整个系统的调用流程,如下是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 fb_info *info = file_fb_info(file);
u8 *buffer, *src;
u8 __iomem *dst;
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;
//主要的i调用info->fbops->fb_write
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 = (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;
src = buffer;
if (copy_from_user(src, buf, c)) {
err = -EFAULT;
break;
}
fb_memcpy_tofb(dst, src, c);
dst += c;
src += c;
*ppos += c;
buf += c;
cnt += c;
count -= c;
}
kfree(buffer);
return (cnt) ? cnt : err;
}
从上面的代码看出fb_write主要是调用info->fbops->fb_write进行对底下底层硬件的操作,在该函数开头info的定义
struct fb_info *info = 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结合得到inode,然后通过inode获取到次设备号,这是因为我们的lcd可以有多个驱动加载,但主设备号都是29,但是次设备号可以有多个。然后根据次设备号从registered_fb数组中获取到info。随之而来的疑问,registered_fb在哪里初始化?
int
register_framebuffer(struct fb_info *fb_info)
{
int ret;
mutex_lock(®istration_lock);
ret = do_register_framebuffer(fb_info);
mutex_unlock(®istration_lock);
return ret;
}
EXPORT_SYMBOL(register_framebuffer);
static bool lockless_register_fb;
module_param_named_unsafe(lockless_register_fb, lockless_register_fb, bool, 0400);
MODULE_PARM_DESC(lockless_register_fb,
"Lockless framebuffer registration for debugging [default=off]");
static int do_register_framebuffer(struct fb_info *fb_info)
{
...
//判断是否超过系统支持的最多fb设备
if (num_registered_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/graphics/fbX设备节点*/
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);
......
fb_var_to_videomode(&mode, &fb_info->var);
fb_add_videomode(&mode, &fb_info->modelist);
//赋值registered_fb
registered_fb[i] = fb_info;
......
return 0;
}
从上面看出registered_fb是个fb_info类型的全局变量数组,然后对外提供register_framebuffer–>do_register_framebuffer接口将已经初始化好的fb_info对象注册到registered_fb中,registered_fb就是储存了多个已经注册好的fb_info对象。fb_info对象的的声明:
struct fb_info {
atomic_t count;
int 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_info是底层lcd硬件与内核framebuffer字符设备的桥梁,这是内核设备的一种解耦方法吧,这样子驱动工程师在移植或者调试的时候只需要定义一个fb_info对象并初始化fb_info的信息,然后再通过register_framebuffer将fb_info注册系统便可。