Framebuffer 驱动学习总结(二)---- Framebuffer模块初始化

---恢复内容开始---

Framebuffer模块初始化过程:--drivervideofbmem.c

1、  初始化Framebuffer

FrameBuffer驱动是以模块的形式注册到系统中,在模块初始化时,创建FrameBuffer对应的设备文件及proc文件,并注册FrameBuffer设备操作接口函数fb_fops。

static int __init  fbmem_init(void)
{
    proc_create("fb", 0, NULL, &fb_proc_fops);///向 proc 文件系统报告驱动状态和参数
    if (register_chrdev(FB_MAJOR,"fb",&fb_fops))///注册字符设备驱动,主设备号是29 
        printk("unable to get major %d for fb devs
", FB_MAJOR);
    fb_class = class_create(THIS_MODULE, "graphics");///创建 /sys/class/graphics 设备类,配合 mdev生成设备文件
    if (IS_ERR(fb_class)) {
        printk(KERN_WARNING "Unable to create fb class; errno = %ld
", PTR_ERR(fb_class));
        fb_class = NULL;
    }
    return 0;
}

Framebuffer作为一个子系统,在fbmem_init中通过register_chrdev接口向系统注册一个主设备号位29的字符设备驱动。通过class_create创建graphics设备类,配合mdev机制生成供用户访问的设备文件(位于/dev目录)。

2、 Framebuffer设备驱动的接口集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,
};

在linux设备驱动中,所有的显示缓存设备均由framebuffer子系统内部管理,即linux设备驱动框架只认识一个主设备号为29的framebuffer设备。应用层所有针对显示缓存(最多32个)的访问均会推送给fb_fops进行进一步分发操作。

3、 注册Framebuffer

linux 提供了register_framebuffer()和unregister_framebuffer()函数分别作为注册和注销帧缓冲设备,这两个函数都接受fb_info指针为参数,原型为:

int register_framebuffer(struct fb_info  *fb_info);

int Unregister_framebuffer(struct fb_info  *fb_info);

对于register_framebuffer()而言,如果注册的帧缓冲设备超过了FB_MAX(目前定义为32),则返回为-ENXIO,注册成功则返回为0。

int register_framebuffer(struct fb_info *fb_info)
{
    int ret;

    mutex_lock(&registration_lock);
    ret = do_register_framebuffer(fb_info);
    mutex_unlock(&registration_lock);

    return ret;
}
EXPORT_SYMBOL(register_framebuffer);// 在内核的启动过程会被调用,以便执行注册帧缓冲区硬件设备的操作
static int do_register_framebuffer(struct fb_info *fb_info)
{
    int i, ret;
    struct fb_event event;
    struct fb_videomode mode;

    if (fb_check_foreignness(fb_info))
        return -ENOSYS;

    ret = do_remove_conflicting_framebuffers(fb_info->apertures,
                         fb_info->fix.id,
                         fb_is_primary_device(fb_info));
    if (ret)
        return ret;

    if (num_registered_fb == FB_MAX)//如果注册的帧缓冲设备超过了FB_MAX(目前定义为32),则返回为-ENXIO,注册成功则返回为0。
        return -ENXIO;

    num_registered_fb++;///已经注册了的帧缓冲区硬件设备个数
    for (i = 0 ; i < FB_MAX; i++)
        if (!registered_fb[i])///registered_fb[i]保存所有已经注册了的帧缓冲区硬件设备
            break;
    fb_info->node = i;
    atomic_set(&fb_info->count, 1);
    mutex_init(&fb_info->lock);
    mutex_init(&fb_info->mm_lock);

    fb_info->dev = device_create(fb_class, fb_info->device,///在/sys/grapics/下创建 fbx设备,用于设备文件的创建
                     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
", i, PTR_ERR(fb_info->dev));
        fb_info->dev = NULL;
    } else
        fb_init_device(fb_info);

    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);

    if (fb_info->skip_vt_switch)
        pm_vt_switch_required(fb_info->dev, false);
    else
        pm_vt_switch_required(fb_info->dev, true);

    fb_var_to_videomode(&mode, &fb_info->var);
    fb_add_videomode(&mode, &fb_info->modelist);
    registered_fb[i] = fb_info;

    event.info = fb_info;
    console_lock();
    if (!lock_fb_info(fb_info)) {
        console_unlock();
        return -ENODEV;
    }

    fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);///通知帧缓冲区控制台,有一个新的帧缓冲区设备被注册到内核中来了
    unlock_fb_info(fb_info);
    console_unlock();
    return 0;
}

每个从设备都需要传递一个fb_info的数据结构指针,其即代表单个显示缓存设备。从中,可以看到fb_info最终会存储到全局数组struct fb_info*registered_fb[FB_MAX]中,FB_MAX是32,从这里我们也可以看出,framebuffer最多支持32个从设备。另外,每个从设备注册还会在/sys/class/graphics/设备类中创建一个设备,最终由mdev在/dev/目录中生成对应的设备文件。假设M个从设备调用register_framebuffer接口,即会在/dev中生成M个设备文件,如/dev/fb0、/dev/fb1、/dev/fb2等等。这M个设备的主设备号都是29,从设备则是0、1、2等等。

     每一个被注册的帧缓冲区硬件设备在/dev/graphics目录下都有一个对应的设备文件fb<minor>,其中,<minor>表示一个从设备号。例如,第一个被注册的帧缓冲区硬件设备在/dev/graphics目录下都有一个对应的设备文件fb0。用户空间的应用程序通过这个设备文件就可以操作帧缓冲区硬件设备了,即将要显示的画面渲染到帧缓冲区硬件设备上去。

 帧缓冲区控制台在内核中对应的驱动程序模块为fbcon:  (driversvideoconsoleFbcon.c)

初始化:

static struct notifier_block fbcon_event_notifier = {
    .notifier_call    = fbcon_event_notify,
};
......
static int __init fb_console_init(void)//帧缓冲区控制台初始化
{
    int i;

    console_lock();
    fb_register_client(&fbcon_event_notifier);//调用fb_register_client来监听帧缓冲区硬件设备的注册事件,fbcon_event_notifier--->实现
    fbcon_device = device_create(fb_class, NULL, MKDEV(0, 0), NULL,
                     "fbcon");

    if (IS_ERR(fbcon_device)) {
        printk(KERN_WARNING "Unable to create device "
               "for fbcon; errno = %ld
",
               PTR_ERR(fbcon_device));
        fbcon_device = NULL;
    } else
        fbcon_init_device();

    for (i = 0; i < MAX_NR_CONSOLES; i++)
        con2fb_map[i] = -1;

    console_unlock();
    fbcon_start();
    return 0;
}

这个函数除了会调用函数device_create来创建一个类别为graphics的设备fbcon之外,还会调用函数fb_register_client来监听帧缓冲区硬件设备的注册事件,这是由函数fbcon_event_notify来实现的,如下所示:

static int fbcon_event_notify(struct notifier_block *self,
                  unsigned long action, void *data)
{
    struct fb_event *event = data;
    struct fb_info *info = event->info;
    struct fb_videomode *mode;
    struct fb_con2fbmap *con2fb;
    struct fb_blit_caps *caps;
    int idx, ret = 0;

    switch(action) {
        
        ......

    case FB_EVENT_FB_REGISTERED:
        ret = fbcon_fb_registered(info);///---> 帧缓冲区硬件设备的注册事件最终是由函数fbcon_fb_registered来处理的
        break;

        .......
    }
done:
    return ret;
}        

帧缓冲区硬件设备的注册事件最终是由函数fbcon_fb_registered来处理的,它的实现如下所示:

static int fbcon_fb_registered(struct fb_info *info)
{
    int ret = 0, i, idx;

    idx = info->node;
    fbcon_select_primary(info);///检查当前注册的帧缓冲区硬件设备是否是一个主帧缓冲区硬件设备,如果是的话,那么就将它的信息记录下来

    if (info_idx == -1) {///如果是的话,那么就将它的信息记录下来
        for (i = first_fb_vc; i <= last_fb_vc; i++) {
            if (con2fb_map_boot[i] == idx) {
                info_idx = idx;
                break;
            }
        }

        if (info_idx != -1)
            ret = do_fbcon_takeover(1);
    } else {
        for (i = first_fb_vc; i <= last_fb_vc; i++) {
            if (con2fb_map_boot[i] == idx)
                set_con2fb_map(i, idx, 0);
        }
    }

    return ret;
}

 函数fbcon_select_primary用来检查当前注册的帧缓冲区硬件设备是否是一个主帧缓冲区硬件设备。如果是的话,那么就将它的信息记录下来。这个函数只有当指定了CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY编译选项时才有效,否则的话,它是一个空函数。

在Linux内核中,每一个控制台和每一个帧缓冲区硬件设备都有一个从0开始的编号,它们的初始对应关系保存在全局数组con2fb_map_boot中。控制台和帧缓冲区硬件设备的初始对应关系是可以通过设置内核启动参数来初始化的。在模块fbcon中,还有另外一个全局数组con2fb_map,也是用来映射控制台和帧缓冲区硬件设备的对应关系,不过它映射的是控制台和帧缓冲区硬件设备的实际对应关系。

---恢复内容结束---

原文地址:https://www.cnblogs.com/EaIE099/p/Andy.html