DRM框架(vkms)分析(9)----drm驱动创建fbdevice分析(以rockchip_drm_drv为例)

本文主要介绍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 = %d\n", 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 mmap\n");
        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[i]);
                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[i]);
 
                // 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;   
}

关于fbdev双缓冲可以参考:

Linux 驱动开发 / fbdev 双缓存 / 快速入门_嵌入式Hacker-CSDN博客

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值