本文主要介绍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;//fbdevstruct 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_objectrockchip_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_helperinfo->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_inithelper = &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数目创建modesetclient->modesets[i++].crtc = crtc

(3)fb_helper->fbdev的注册

注册逻辑如下:

rockchip_drm_fbdev_initdrm_fb_helper_preparedrm_fb_helper_initdrm_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和显示模式modedrm_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->xmodeset->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->fbdrm_setup_crtcs_fb(fb_helper)drm_client_for_each_modeset(modeset, client) {//这里将创建的显存fb赋给所有的modesetmodeset->fb = fb_helper->fb;}注册fb_infoinfo = 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的所有modesetdrm_client_for_each_modeset(mode_set, client){   //找到mode_set绑定的crtc的primary planestruct drm_plane* primary = mode_set->crtc->primaryret = derm_atomic_helper_set_config(mode_set, state)//将显存mode_set->fb赋值给plane_statedrm_atomic_set_fb_for_plane(primary_state, set->fb)}//通过atomic modeset 提交显存fb到crtc的primary planeret = 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_ioctlcase 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_commitret = drm_client_modeset_commit(&fb_helper->client)drm_client_modeset_commit_locked//调用drm_client_modeset_commit_atomicdrm_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_ioctlstatic 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找到helperstruct 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/ypan_set(fb_helper, var->xoffset, var->yoffset);modeset->x = x; //var->xoffsetmodeset->y = y; // var->yoffset//更新plane的参数x,ydrm_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_ioctlswitch(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. 使能多 buffervar.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. 通知驱动切换 buffervar.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博客

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

  1. linux tap 源码分析,tun/tap 驱动源码分析

    此驱动运行时可设置tun模式和tap模式,tun模式能取到IP数据包,无法获得ARP数据,而tap模式取到的是以太包,可以得到链路层以上的一切数据包. 由于项目需要使用tun驱动,而又不想不求甚解,从 ...

  2. Linux图形显示DRM框架学习

    一.Direct Rendering Manager(DRM)是linux内核子系统,负责与显卡交互.DRM提供一组API,用户空间程序可以使用该API将命令和数据发送到GPU或者专用图形处理硬件(如 ...

  3. Android DRM框架与基础知识

    Android DRM框架与基础知识 Android DRM框架 DRM框架的目的:能让安卓设备可以播放更多的内容,不同的内容和硬件设备可能使用的是不同的内容版权保护机制或者没有版权管理机制,但是安卓 ...

  4. DRM框架(vkms)分析(4)----encoder初始化

    本文 主要分析encoder的初始化和配置,drm_encoder结构体如下: /*** struct drm_encoder - central DRM encoder structure* @de ...

  5. Linux内核4.14版本——drm框架分析(1)——drm简介

    目录 1. DRM简介(Direct Rendering Manager) 1.1 DRM发展历史 1.2 DRM架构对比FB架构优势 1.3 DRM图形显示框架 1.4 DRM图形显示框架涉及元素 ...

  6. Android DRM框架分析

    Android DRM框架分析 1. DRM框架 2.DRM架构 3.DRM插件 4. 实现 5.DRM插件详情 6.MediaDrm 7.MediaCrypto 8.参考链接 1. DRM框架 An ...

  7. 《DRM 专栏》| 彻底入门 DRM 驱动

    https://cloud.tencent.com/developer/article/2021477 前面的 DRM 应用程序系列文章中,我们学习了如何使用 libdrm 接口编写 DRM 应用程序 ...

  8. uboot drm框架

    uboot drm框架 drm概念 drm显示通路 CRTC: 显示控制器, 在 rockchip 平台是 SOC 内部 VOP(部分文档也称为 LCDC)模块的抽象: Plane: 图层, 在 ro ...

  9. hisi3516dv300芯片基于hwmon驱动框架的温度获取驱动源码分析

    1.内核hwmon驱动框架 参考博客:<内核hwmon驱动框架详解以及海思芯片温度驱动分析>: 2.驱动实现的效果 /sys/devices/virtual/hwmon/hwmon0 # ...

最新文章

  1. Dell服务器的 Idrac调试口的配置方式
  2. golang 反射 获取 设置 结构体 字段值
  3. window环境编译在linux环境运行的golang程序
  4. 【论文推荐】张笑钦团队 | 综述:基于深度学习的视觉跟踪方法进展
  5. 密码学 / 什么是数字签名
  6. 尹成python爬虫百度云_Python爬虫实战:抓取并保存百度云资源
  7. 详解TCP协议三次握手四次挥手
  8. 关于现代计算机的知识,从资本经济到知识经济:现代计算机的知识革命
  9. 计算机口令管理,农村信用社联合社计算机账户与口令管理办法
  10. python的copy模块是哪个模块_每周一个 Python 模块 | copy
  11. php中函数的类型提示和文件读取功能
  12. TQJson序列和还原clientdataset.data
  13. JS实现,页面显示数据加载,显示加载效果,加载完成显示数据
  14. 云南昆明寺庙方丈还俗完婚 迎娶26岁女老板(图)
  15. 无线量子通信/无线量子通讯,5G下一代物联网的创新研究
  16. PMP 项目管理(12)项目采购管理 思维导图 解读
  17. (E4)ENVI-met运行结果处理
  18. 学习SEO有别的味道
  19. 生产者-消费者-管程法(java代码示例)
  20. 【二层网络和三层网络是什么?有什么区别?】

热门文章

  1. MySQL --- 多表查询 - 七种JOINS实现、集合运算、多表查询练习
  2. antv x6踩坑记录二
  3. 利用opencv+QT打开并显示图片
  4. 计算机行业各种职业技能树
  5. 华为p10多屏互动在哪_华为p10如何投屏到电视?手机还有这些使用技巧
  6. 数电课程设计——电子钟
  7. TXT文件编码格式解析
  8. java代码审计_Java代码审计入门篇
  9. YOLOv3目标检测算法——通俗易懂的解析
  10. 使用IDEA如何对Java项目进行打包