一 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
|