瑞芯微Rockchip开发者社区
直播中

张华

7年用户 1425经验值
私信 关注
[经验]

DRM框架里的fbdev兼容逻辑介绍

一 framebuffer框架简单介绍
framebuffer框架下fbdev的注册主要三步步:
(1)创建fbdev操作函数,以rockchip为例:
static const struct fb_ops rockchip_drm_fbdev_ops = {
.owner = THIS_MODULE,
DRM_FB_HELPER_DEFAULT_OPS,
.fb_mmap = rockchip_fbdev_mmap,
.fb_fillrect = drm_fb_helper_cfb_fillrect,
.fb_copyarea = drm_fb_helper_cfb_copyarea,
.fb_imageblit = drm_fb_helper_cfb_imageblit,
};
(2)填充struct fb_info* fbi实例的各个参数,其中就包括赋值操作函数
fbi->fbops = &rockchip_drm_fbdev_ops
(3)注册fb设备
register_framebuffer(fbi)
二 DRM注册接口
drm驱动中注册fbdev, 主要是选择主要有以下几个方面:
1)创建显存drm_framebuffer 实例fb
2)填充到fb_info中 并注册fbdev
3)将fb绑定到对应的crtc plane
经过上述操作,就可以通过fbdev的FBIOGET_FSCREENINFO/FBIOGET_VSCREEENINFO、mmap/FBIOPAN_DISPLAY/FBIO_WAITFORVSYNC等操作显存, 而fb/crtc/plane/fb_info之间是通过drm_fb_helper来关联的,其结构体如下:
struct drm_fb_helper {
    //client用来关联crtc/plane/mode_set等参数
struct drm_client_dev client;
struct drm_client_buffer *buffer;
    //fb显存
struct drm_framebuffer *fb;
struct drm_device *dev;
    //fb显存的创建hook,以及部分fb_info fbdev成员的填充,该hook由各drm驱动创建
const struct drm_fb_helper_funcs *funcs;
    //fbdev
struct fb_info *fbdev;
    // ....
};
(1)fb_helper->fb的创建
helper->fb是显存drm_framebuffer实例,其创建主要是通过调用drm_fb_helper_funcs实例中的fb_probe hook,如下:
static const struct drm_fb_helper_funcs rockchip_drm_fb_helper_funcs = {
.fb_probe = rockchip_drm_fbdev_create,
};
static int rockchip_drm_fbdev_create(struct drm_fb_helper *helper,
     struct drm_fb_helper_surface_size *sizes)
{
struct rockchip_drm_private *private = to_drm_private(helper);
struct drm_mode_fb_cmd2 mode_cmd = { 0 };
struct drm_device *dev = helper->dev;
;
struct drm_framebuffer *fb;
unsigned int bytes_per_pixel;
unsigned long offset;
    //根据size创建drm_gem_obj对象
    struct rockchip_gem_object *rk_obj rk_obj;
rk_obj = rockchip_gem_create_object(dev, size, true);
               rok_obj = rockchip_gem_alloc_object
               rockchip_gem_alloc_buf
    //drm_gem_obj对象                                                         
private->fbdev_bo = &rk_obj->base;
    //创建fb_info对象并保存到fb_helper->fbdev中
    struct fb_info *fbi;
fbi = drm_fb_helper_alloc_fbi(helper);
    //利用private->fbdev_bo(drm_gem_obj对象)创建drm_frame_buffer对象并保存到helper->fb中
helper->fb = rockchip_drm_framebuffer_init(dev, &mode_cmd,
   private->fbdev_bo);
    //初始化fbdev的操作函数
fbi->fbops = &rockchip_drm_fbdev_ops;
    //填充fb_info的参数
fb = helper->fb;
drm_fb_helper_fill_info(fbi, helper, sizes);
        drm_fb_helper_fill_fix //填充固定参数
        drm_fb_helper_fill_var //填充可变参数
        //将fb_helper保存到par中,fbdev操作函数(rockchip_drm_fbdev_ops)会根据
        //jnfo->par找到fb_helper
        info->par = fb_helper;
    //继续填充fb_info的参数
offset = fbi->var.xoffset * bytes_per_pixel;
offset += fbi->var.yoffset * fb->pitches[0];
fbi->screen_base = rk_obj->kvaddr + offset;
fbi->screen_size = rk_obj->base.size;
fbi->fix.smem_len = rk_obj->base.size;
}
(2)fb_helper->client的初始化
fb_helper->client是drm_client_dev对象, 其成员modesets(drm_mode_set类型)关联了fb/crtc/connector/displaymode等
rockchip_drm_fbdev_init
    helper = &private->fbdev_helper
    /*将rockchip_drm_fb_helper_funcs保存到helper->funcs
     *后续会调用其fb_probe,用来创建fb
     */
    drm_fb_helper_prepare(dev, helper, &rockchip_drm_fb_helper_funcs)
        helper->funcs = funcs; //rockchip_drm_fb_helper_funcs
    //初始化drm_fb_helper对象
    drm_fb_helper_init(dev, helper)
        /*创建drm_client_dev对象fb_helper->client,这个对象很重要,
         *其成员modesets(drm_mode_set类型)
         *关联fb/crtc/connector/displaymode等
         */
        drm_client_init(dev, &fb_helper->client)
            drm_client_modeset_create(client)
                drm_for_each_crtc(crtc, dev)
                    根据drm设备的crtc数目创建modeset
                    client->modesets[i++].crtc = crtc
(3)fb_helper->fbdev的注册
注册逻辑如下:
rockchip_drm_fbdev_init
    drm_fb_helper_prepare
    drm_fb_helper_init
    drm_fb_helper_initial_config(helper, ...)
        __drm_fb_helper_initial_config_and_unlock(fb_helper, bpp_sel)
            //
            drm_client_modeset_probe(&fb_helper->client, width, height)
                //根据connector count申请crtcs/modes/offsets/enabled等变量
                //....
                //找到status为connected的connectors以及其连接的crtc和显示模式mode
                drm_client_firmware_config(...,connectors, crtcs, modes, offsets, enabled)
                //初始化client->modesets[]
                for(i = 0; i < connector_count;i++)
                {
                    /*从client->modesets[]中找到匹配的crtc
                     * client->modesets[].crtc在drm_client_modeset_create中初始化
                     * 其位于drm_fb_helper_init接口中调用
                     */
                    drm_mode_set* modeset = drm_client_find_modeset(client, crtc)
                    //赋值modesets的mode/connectors/x/y等成员      
                    modeset->mode = drm_mode_dupicate(dev, mode)
                    modeset->connectors[modeset->num_connectors++] = connector;
                    modeset->x = offset->x
                    modeset->y = offset->y
                }
            //创建单显存fb,所以这里应该不支持双缓冲
            ret = drm_fb_helper_single_fb_probe(fb_helper, bpp_sel);
                     /*根据connector/plane调整size,最后调用
                      *fb_probe(即 rockchip_drm_fbdev_create)创建显存
                       */
                     ret = (*fb_helper->funcs->fb_probe)(fb_helper, &sizes);
            //设置显存fb到modeset->fb
            drm_setup_crtcs_fb(fb_helper)
                drm_client_for_each_modeset(modeset, client)
                {
                    //这里将创建的显存fb赋给所有的modeset
                    modeset->fb = fb_helper->fb;
                }
            注册fb_info
            info = fb_helper->fbdev;
            ret = register_framebuffer(info);      
从上边的逻辑drm_fb_helper_initial_config触发所有的操作:
drm_client_modeset_probe探测所有的modesets
drm_fb_helper_single_fb_probe从所有的modesets中确定合适的fb大小并调用fb_probe创建显存fb
drm_setup_crtcs_fb将创建的显存fb保存到client所有的modeset->fb中
最后调用register_framebuffer注册fbdev
(4)显存fb绑定到crtc(plane)
我们注意到drm_setup_crtcs_fb接口只是将显存fb保存的client下所有的modeset->fb中。那么显存fb是如何绑定到crtc中的呢?
其实显存fb(modeset->fb)通过drm_client_modeset_commit_atomic接口绑定到crtc上的,如下:
总结:显存fb通过atomic modeset绑定到所有crtc的primary plane(这里是否是所有crtc? 存疑)
drm_client_modeset_commit_atomic
    //遍历client的所有modeset
    drm_client_for_each_modeset(mode_set, client)
    {   
        //找到mode_set绑定的crtc的primary plane
        struct drm_plane* primary = mode_set->crtc->primary
        ret = derm_atomic_helper_set_config(mode_set, state)
                //将显存mode_set->fb赋值给plane_state
                drm_atomic_set_fb_for_plane(primary_state, set->fb)
    }
    //通过atomic modeset 提交显存fb到crtc的primary plane
    ret = drm_atomic_commit(state)
drm_client_modeset_commit_atomic是何时被调用的呢?梳理代码发现, 在fbdev进行FBIOGET_VSCREENINFO时被调用,逻辑如下:
//用户态
fd_fb = open("/dev/fb0")
ioctl(fd_fb, FBIOPUT_VSCREENINFO, &var)
//内核态
do_fb_ioctl
    case FBIOPUT_VSCREENINFO:
        ret = fb_set_var(info, &var)
                ret = info->fbos->fb_set_par(info)
                //drm_fb_helper_set_par
                    __drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper, force)
                        //调用drm_client_modeset_commit
                        ret = drm_client_modeset_commit(&fb_helper->client)
                                drm_client_modeset_commit_locked
                                    //调用drm_client_modeset_commit_atomic
                                    drm_client_modeset_commit_atomic(client, ...)
需要注意的是,drm_client_modeset_commit_atomic接口其他地方也会调用,待梳理。
三 fbdev操作
fbdev操作按顺序来主要有FBIOGET_FSCREENINFO/FBIOGET_VSCREENINFO/mmap/FBIO_PUT_VSCREENINFO/
FBIOPAN_DISPLAY/FBIO_WAITFORVSYNC, 对应的操作函数如下:
#define DRM_FB_HELPER_DEFAULT_OPS
.fb_check_var = drm_fb_helper_check_var,
.fb_set_par = drm_fb_helper_set_par,
.fb_setcmap = drm_fb_helper_setcmap,
.fb_blank = drm_fb_helper_blank,
.fb_pan_display = drm_fb_helper_pan_display,
.fb_debug_enter = drm_fb_helper_debug_enter,
.fb_debug_leave = drm_fb_helper_debug_leave,
.fb_ioctl = drm_fb_helper_ioctl
static const struct fb_ops rockchip_drm_fbdev_ops = {
.owner = THIS_MODULE,
DRM_FB_HELPER_DEFAULT_OPS,
.fb_mmap = rockchip_fbdev_mmap,
.fb_fillrect = drm_fb_helper_cfb_fillrect,
.fb_copyarea = drm_fb_helper_cfb_copyarea,
.fb_imageblit = drm_fb_helper_cfb_imageblit,
};
(1) FBIOGET_FSCREENINFO
获取固定参数,比较简单不做介绍
(2)FBIOGET_VSCREENINFO
获取可变参数,比较简单不做介绍
(3)mmap
映射显存fb虚拟地址,供用户空间使用,内核态对应接口fb_mmap
static int rockchip_fbdev_mmap(struct fb_info *info,
       struct vm_area_struct *vma)
{
//根据info->par找到helper
    struct drm_fb_helper *helper = info->par;
struct rockchip_drm_private *private = to_drm_private(helper);
    //fbdev_bo就是显存fb对应的drm_gem_obj实例
rockchip_gem_mmap_buf(private->fbdev_bo, vma);
        //映射操作
        drm_gem_mmap_obj
}
(4)FBIO_PUT_VSCREENINFO
设置可变参数,其用法之一就是设置多显存buffer, 用户态设置方法如下:
ioctl(fd, FBIOGET_FSCREENINFO, &fix);
ioctl(fd, FBIOGET_VSCREENINFO, &var);
//获取当前显存buffer个数
curr_num = fix.smem_len/screen_size;
//设置双缓冲
var.yres_virtual = 2 * var.yes
ioctl(fd, FBIOPUT_VSCREENINFO, &var);
内核态对应接口fb_set_par ,这个接口我们在前文已经分析过,他会触发drm_client_modeset_commit_atomic接口绑定fb到plane, 当我们没有分析双缓冲的逻辑,这个后续补充
(5)FBIOPAN_DISPLAY
双缓冲时,切换缓冲使用,对用内核态接口fb_pan_display
//用户态
var.yoffset = buf_idx*var.yes
ioctl(fd, FBIOPAN_DISPLAY, &var)
//内核态
drm_fb_helper_pan_display(fb_var_screeninfo*var, fb_info* info)
    pan_display_atomic(var, info)
        //设置modeset->x/y
        pan_set(fb_helper, var->xoffset, var->yoffset);
            modeset->x = x; //var->xoffset
            modeset->y = y; // var->yoffset
        //更新plane的参数x,y
        drm_client_modeset_commit_locked(&fb_helper->client);
从(4)(5)可以看出,所谓的双缓冲,在内核态实际上申请一块大的显存(2倍的单缓冲大小),然后通过var.yoffset(内核态对应modeset->y)来切换plan的显示区域
(6)FBIO_WAITFORVSYNC
等待vsync,内核态对应接口drm_fb_helper_ioctl:
drm_fb_helper_ioctl
    switch(cmd)
    {
        case FBIO_WAITFORVSYNC:
            //仅支持第一个crtc的vsync等待
            crtc = fb_helper->client.modeset[0].crtc;
            drm_crtc_wait_one_vblank(crtc)
    }
(7)测试程序
static int fd_fb;
static struct fb_fix_screeninfo fix;    /* Current fix */
static struct fb_var_screeninfo var;    /* Current var */
static int screen_size;
static unsigned char *fb_base;
static unsigned int line_width;
static unsigned int pixel_width;
int main(int argc, char **argv)
{
    int i;
    int ret;
    int buffer_num;
    int buf_idx = 1;
    char *buf_next;
    unsigned int colors[] = {0x00FF0000, 0x0000FF00, 0x000000FF, 0, 0x00FFFFFF};  /* 0x00RRGGBB */
    struct timespec time;
    ...
    fd_fb = open("/dev/fb0", O_RDWR);
    ioctl(fd_fb, FBIOGET_FSCREENINFO, &fix);
    ioctl(fd_fb, FBIOGET_VSCREENINFO, &var);
    line_width  = var.xres * var.bits_per_pixel / 8;
    pixel_width = var.bits_per_pixel / 8;
    screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
    // 1. 获得 buffer 个数
    buffer_num = fix.smem_len / screen_size;
    printf("buffer_num = %dn", buffer_num);
    fb_base = (unsigned char *)mmap(NULL , fix.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
    if (fb_base == (unsigned char *)-1) {
        printf("can't mmapn");
        return -1;
    }
    if ((argv[1][0] == 's') || (buffer_num == 1)) {
        printf("single buffer:n");
        while (1) {
            for (i = 0; i < sizeof(colors)/sizeof(colors[0]); i++) {
                lcd_draw_screen(fb_base, colors);
                nanosleep(&time, NULL);
            }
        }
    } else {
        printf("double buffer:n");
        // 2. 使能多 buffer
        var.yres_virtual = buffer_num * var.yres;
        ioctl(fd_fb, FBIOPUT_VSCREENINFO, &var);
        while (1) {
            for (i = 0; i < sizeof(colors)/sizeof(colors[0]); i++) {
                // 3. 更新 buffer 里的数据
                buf_next =  fb_base + buf_idx * screen_size;
                lcd_draw_screen(buf_next, colors);
                // 4. 通知驱动切换 buffer
                var.yoffset = buf_idx * var.yres;
                ret = ioctl(fd_fb, FBIOPAN_DISPLAY, &var);
                if (ret < 0) {
                    perror("ioctl() / FBIOPAN_DISPLAY");
                }
                // 5. 等待帧同步完成
                ret = 0;
                ioctl(fd_fb, FBIO_WAITFORVSYNC, &ret);
                if (ret < 0) {
                    perror("ioctl() / FBIO_WAITFORVSYNC");
                }
                buf_idx = !buf_idx;
                nanosleep(&time, NULL);
            }
        }
    }
    munmap(fb_base , screen_size);
    close(fd_fb);
    return 0;   
}

原作者:HugoVus

更多回帖

×
20
完善资料,
赚取积分