DRM应用及驱动浅析
drm相关函数原型:https://blog.csdn.net/weixin_42645653/article/details/115486627
参考何小龙老师的博客:https://blog.csdn.net/hexiaolong2009/article/details/83720940
1.DRM简介
DRM 是linux图形显示子系统,主要分为三个部分:
- libdrm:for userspace,提供各种API供用户空间使用,主要是对IOCTL函数的封装;
- KMS:kernel mode setting,主要作用是设置参数和控制显示;
- GEM:graphic execution manager,主要做内存管理
在驱动侧主要工作在和KMS和GEM上,这两个模块又包含以下元素:
- KMS:
- crtc:对显示buffer进行扫描,并产生时序信号的硬件模块,通常指Display Controller;
- encoder:负责将CRTC输出的timing时序转换成外部设备所需要的信号的模块,如HDMI转换器或DSI Controller;
- connector:连接物理显示设备的连接器,如HDMI、DisplayPort、DSI总线,通常和Encoder驱动绑定在一起;
- plane:硬件图层,有的Display硬件支持多层合成显示,但所有的Display Controller至少要有1个plane;
- framebuffer:Framebuffer,单个图层的显示内容,唯一一个和硬件无关的基本元素;
- vblank:软件和硬件的同步机制,RGB时序中的垂直消影区,软件通常使用硬件VSYNC来实现;
- property:任何你想设置的参数,都可以做成property,是DRM驱动中最灵活、最方便的Mode setting机制;
- GEM:
- dumb:只支持连续物理内存,基于kernel中通用CMA API实现,多用于小分辨率简单场景;
- prime:连续、非连续物理内存都支持,基于DMA-BUF机制,可以实现buffer共享,多用于大内存复杂场景;
- fence:buffer同步机制,基于内核dma_fence机制实现,用于防止显示内容出现异步问题;
什么是plane?
RM中的Plane指的是Display Controller中用于多层合成的单个硬件图层模块,属于硬件层面。plane是连接fb与crtc的纽带,是内存的搬运工。
上图:以framebuffer为图源,利用drmModeSetPlane函数设置plane。
当 SRC 与 CRTC 的 X/Y 不相等时,则实现了平移的效果;
当 SRC 与 CRTC 的 W/H 不相等时,则实现了缩放的效果;
当 SRC 与 FrameBuffer 的 W/H 不相等时,则实现了裁剪的效果;
- 一个高级plane通常具有如下功能
功能 说明 crop 裁剪 如上图 scaling 缩放 放大或者缩小 rotation 旋转 90 180 270 X/Y镜像 z-order z-顺序 调整当前plane在所有图层中的顺序 blending 合成 pixel alpha/global alpha format 颜色格式,ARGB888 XRGB888 YUV240等 - plane的类型
类型 说明 cursor 光标图层 一般用于pc系统,用于显示鼠标 overlay 叠加图层 通常用于YUV格式的视频图层 primary 主要图层 通常用于仅支持RGB格式的简单图层 DRM框架规定 任何一个CRTC,必须要有一个primary plane
legacy版本的简单plane显示代码流程如下
定义一个buffer_obj:
1
2
3
4
5
6
7
8
9
10
11struct buffer_object {
uint32_t width;
uint32_t height;
uint32_t pitch;
uint32_t handle;
uint32_t size;
uint8_t *vaddr; //物理内存映射后的地址
uint32_t fb_id;
};
struct buffer_object buf;打开显示设备
int fd = open("/dev/dri/card0", O_RDWR);
获取显示资源,包括crtc、encoder和connector信息
1
2
3
4
5
6struct drmModeRes *res = drmModeGetResources(fd);
/*
获取crtc id和connector id,res中包含各个显示元素信息,且crtc和connector可能不止一个
*/
uint32_t conn_id = res->connectors[0];
uint32_t crtc_id = res->crtcs[0];获取plane有关设备
1
2
3
4//必须设置DRM_CLIENT_CAP_UNIVERSAL_PLANES,如果不设置,drmModeGetPlaneResources只会返回verlay plane,如果设置则会返回所有的支持的plane资源
drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
struct drmModePlaneRes *plane_res = drmModeGetPlaneResources(fd); //获取plane资源列表
uint32_t plane_id = plane_res->planes[0];通过conn_id获取connector,connector中包含显示设备的信息,用显示设备的信息初始化buf
1
2
3struct srmModeConnector *conn = drmModeGetConnector(fd, conn_id);
buf.width = conn->modes[0].hdisplay;
buf.height = conn->modes[0].vdisplay;根据buf创建framebuffer
- (1)根据buf创建一个dumb buf
1
2
3
4
5struct drm_mode_create_dumb create;
create.width = buf.width;
create.height = buf.height;
create.bpp = 32; //bpp-bits per pixel
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create); //为create创建dumb 内存,同时creat得到初始化 - (2)创建一个framebuffer
1
2
3
4
5//将create初始化后的值反赋给buf
buf.pitch = create.pitch;
buf.size = create.size;
buf.handle = create.handle;
drmModeAddFB(fd, buf.width, buf.height, 24, 32, buf.pitch, buf.handle, &buf.fb_id); - (3)将dumb buffer映射到userspace
1
2
3
4struct drm_mode_map_dumb map;
map.handle = create.handle;
drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map); //映射一个用于CPU访问的dumb buffer
buf.vaddr = mmap(0, create.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, map.offset); //将dumb buffer映射到用户空间
- (1)根据buf创建一个dumb buf
通过映射的内存地址画framebuffer
1
memset(buf.vaddr, 0xff, buf.size); //画白色
设置crtc,实现显示framebuffer
1
drmModeSetCrtc(fd, crtc_id, buf.fb_id, 0, 0, &conn_id, 1, &conn->modes[0]);
从framebuffer(100,150)位置crop一个320x320的plane放到crtc上,显示plane
1
drmModeSetPlane(fd, plane_id, crtc_id, buf.fb_id, 0,50, 50, 320, 320, 100 << 16, 150 << 16, 320 << 16, 320 << 16);
drmModeSetPlane函数原型为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16int drmModeSetPlane ( int fd, uint32_t plane_id, uint32_t crtc_id, uint32_t fb_id, uint32_t flags, uint32_t crtc_x, uint32_t crtc_y, uint32_t crtc_w, uint32_t crtc_h, uint32_t src_x, uint32_t src_y, uint32_t src_w, uint32_t src_h )
/******
fd:打开的DRM设备的文件描述符。
plane_id:平面需要修改的平面ID。
crtc_id:CRTC该平面所在CRTC的ID。
fb_id:Framebuffer在平面上显示的Framebuffer ID,或-1表示保持Framebuffer不变。
flags:控制函数行为的标志。目前不支持外部使用标志。
crtc_x:从有源显示区域左侧显示平面的偏移量
crtc_y:从有源显示区域顶部到显示平面的偏移量。
crtc_w:显示输出矩形的宽度。
crtc_h:显示输出矩形的高度
src_x:从源framebuffer左侧的剪辑偏移量(Q16.16固定点)。
src_y:来自源framebuffer顶部的剪辑偏移量(Q16.16固定点)。
src_w:源矩形的宽度(Q16.16固定点)。
src_h:源矩形的高度(Q16.16固定点)。
***/完成显示后释放资源
1
2
3
4
5modeset_destroy_fb(fd, &buf);
drmModeFreeConnector(conn);
drmModeFreePlaneResources(plane_res);
drmModeFreeResources(res);
close(fd);
property:
- 把legacy接口传入的参数做成一个个独立的全局属性,通过设置这些属性参数,可以完成对显示参数的设置;
- property由name、id、value三部分组成。其中id为该property在DRM框架中唯一的标识符;
- property机制的好处:
- 减少上层应用接口的维护工作量;添加新功能时无需增加新的函数和IOCTL,只需要在底层中新增一个property,然后在应用程序中获取/操作property即可;
- 增加了参数设置的灵活性;一次IOCTL可以同时设置多个property,减少了usersapce与kernel的切换次数。
常用的property
- crtc相关
name description ACTIVE crtc当前的使能状态,一般用于CRTC上下电 MODE_ID crtc当前使用的display mode id,通过该id可以找到具体的display mode参数 OUT_FENCE_PTR 输出fence指针,指向当前正在显示的buffer所对应的fence id,该fence由DRM驱动创建,供上层应用使用,用来表示当前buffer crtc是否还在占用 - plane相关
name description type plane的类型,CURSOR、PRIMARY或者OVERLAY FB_ID 与当前plane绑定的framebuffer object ID IN_FENCE_FD 与当前plane相关联的input fence fd,由buffer的生产者创建,供DRM底层驱动使用,用来标识当前传下来的buffer是否可以开始访问 CRTC_ID 当前plane所关联的CRTC object ID,与CONNECTOR中的CRTC_ID属性是同一个property SRC_X 当前framebuffer crop区域的起始偏移x坐标 SRC_Y 当前framebuffer crop区域的起始偏移y坐标 SRC_W 当前framebuffer crop区域的宽度 SRC_H 当前framebuffer crop区域的高度 CRTC_X 屏幕显示区域的起始偏移x坐标 CRTC_Y 屏幕显示区域的起始偏移y坐标 CRTC_W 屏幕显示区域的宽度 CRTC_H 屏幕显示区域的高度 IN_FORMATS 用于标识特殊的颜色存储格式,如AFBC、IFBC存储格式,该属性为只读 rotation 当前图层的旋转角度 zposition 当前图层在所有图层中的Z轴顺序 alpha 当前图层的global alpha(非pixel alpha),用于多层合成 pixel blend mode 当前图层的合成方式,如Pre-multiplied/Coverage等 - connector相关
name description EDID Extended Display Identification Data,标识显示器的参数信息,是一种VESA标准数据格式 DPMS Display Power Management Signaling,用于控制显示器的电源状态,如休眠唤醒。也是一种VESA标准 link-status 用于标识当前connector的连接状态,如Good/Bad CRTC_ID 当前connector所连接的CRTC object ID,与PLANE中CRTC_ID属性是同一个property PATH DisplayPort专用的属性,主要用于Multi-Stream Transport (MST) 功能,即多路显示应用场景 TILE 用于标识当前connector是否应用于多屏拼接场景,如平时随处可见的多屏拼接显示的广告大屏幕 property的类型
- DRM_MODE_PROP_RANGE:range property会report它们可接受的最大值和最小值范围(unsigned value),KMS内核验证app设定的值是否符合这个范围。范围属性由drm_property_create_range()函数创建;
- DRM_MODE_PROP_SIGNED_RANGE:range property会report它们可接受的最大值和最小值范围(signed value),KMS内核验证app设定的值是否符合这个范围。范围属性由drm_property_create_signed_range()函数创建;
- DRM_MODE_PROP_ENUM:Enumerated properties(枚举属性)限定一个范围,从0到属性定义的值减1,每个值有一个字符串符号,应用程序可以通过检索值-名称列表,使用数值来获取或者设置属性实例的值。枚举属性由drm_property_create_enum()函数创建;
- DRM_MODE_PROP_BITMASK:Bitmask properties(位掩码属性),其实就是将所有枚举值范围限定在0-63的枚举值属性,bitmask property的值将属性定义的一个或者多个枚举位组合表示,位掩码属性由drm_property_create_bitmask()函数创建;
- DRM_MODE_PROP_OBJECT:object property与modeset_object联系,在atomic中广泛使用,通过链接&drm_frmaebuffer和&drm_plane,链接&drm_plane和&drm_crtc,链接&drm_crtc和&drm_connector创建display_pipeline,Object property由drm_property_create_object()函数创建;
- DRM_MODE_PROP_BLOB:blob property存储一段没有格式限制的二进制blob,它的值表示它链接的blob obj的id,blob property由drm_property_create()函数使用DRM_MODE_PROP_BLOB类型参数创建,带blob数据的blob obj由drm_property_create_blob()创建;
- DRM_MODE_PROP_ATOMIC:为atomic模式的属性设置,这些属性不会暴露给legacy 用户空间;
- DRM_MODE_PROP_IMMUTABLE:为用户空间无法改变其值的属性设置,kernel可以改变这些属性的值,通过drm_object_property_set_value()函数;
利用libdrm提供的atomic接口,通过name来获取property,通过id操作property,通过value修改property
第1-6步与legacy plane应用程序中步骤相同
创建buffer
获取资源,ctrc、connector、encoder
获取plane资源
获取connector并且根据其值初始化buffer
根据buffer创建framebuffer
通过映射的内存地址画framebuffer
告知DRM驱动该应用程序支持atomic操作
1
drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1);
获取connector properties
1
2
3struct drmModeObjectProperties *props = drmModeObjectGetProperties(fd, conn_id, DRM_MODE_OBJECT_CONNECTOR);
uint32_t property_crtc_id = get_property_id(fd, props, "CRTC_ID"); //与connector关联的crtc_id
drmModeFreeObjectProperties(props); //释放props获取crtc properties
1
2
3
4props = drmModeObjectGetProperties(fd, crtc_id, DRM_MODE_OBJECT_CRTC);
uint32_t property_active = get_property_id(fd, props, "ACTIVE"); //获取crtc active属性
uint32_t property_mode_id = get_property_id(fd, props, "MODE_ID"); //获取crtc使用的display mode id
drmModeFreeObjectProperties(props); //释放props创建blob property储存现在的模式,并保存blob id
1
2uint32_t blob_id;
drmModeCreatePropertyBlob(fd, &conn->modes[0], sizeof(conn->modes[0]), &blob_id);mode setting 替代从前的drmModeSetCrtc代码
1
2
3
4
5
6
7struct drmModeAtomicReq *req;
req = drmModeAtomicAlloc(); //创建atomic请求对象
drmModeAtomicAddProperty(req, crtc_id, property_active, 1); //设置crtc的property_active值
drmModeAtomicAddProperty(req, crtc_id, property_mode_id, blob_id); //设置crtc的property_mode_id值
drmModeAtomicAddProperty(req, conn_id, property_crtc_id, crtc_id); //设置connector的property_crtc_id值
drmModeAtomicCommit(fd, req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL); //提交请求
drmModeAtomicFree(req); //释放请求对象从framebuffer(100,150)位置crop一个320x320的plane放到crtc上,显示plane
1
drmModeSetPlane(fd, plane_id, crtc_id, buf.fb_id, 0,50, 50, 320, 320, 100 << 16, 150 << 16, 320 << 16, 320 << 16);
释放资源
1
2
3
4
5modeset_destroy_fb(fd, &buf);
drmModeFreeConnector(conn);
drmModeFreePlaneResources(plane_res);
drmModeFreeResources(res);
close(fd);
- 第12步仍然使用的是legacy接口drmModeSetPlane(),现在使用atomic接口替换其功能
- 12.1 获取plane属性
1
2
3
4
5
6
7
8
9
10
11
12props = drmModeObjectGetProperties(fd, plane_id, DRM_MODE_OBJECT_PLANE);
uint32_t property_crtc_id = get_property_id(fd, props, "CRTC_ID");
uint32_t property_fb_id = get_property_id(fd, props, "FB_ID");
uint32_t property_crtc_x = get_property_id(fd, props, "CRTC_X");
uint32_t property_crtc_y = get_property_id(fd, props, "CRTC_Y");
uint32_t property_crtc_w = get_property_id(fd, props, "CRTC_W");
uint32_t property_crtc_h = get_property_id(fd, props, "CRTC_H");
uint32_t property_src_x = get_property_id(fd, props, "SRC_X");
uint32_t property_src_y = get_property_id(fd, props, "SRC_Y");
uint32_t property_src_w = get_property_id(fd, props, "SRC_W");
uint32_t property_src_h = get_property_id(fd, props, "SRC_H");
drmModeFreeObjectProperties(props); - 12.2 atomic update plane
1
2
3
4
5
6
7
8
9
10
11
12
13req = drmModeAtomicAlloc();
drmModeAtomicAddProperty(req, plane_id, property_crtc_id, crtc_id);
drmModeAtomicAddProperty(req, plane_id, property_fb_id, buf.fb_id);
drmModeAtomicAddProperty(req, plane_id, property_crtc_x, 50);
drmModeAtomicAddProperty(req, plane_id, property_crtc_y, 50);
drmModeAtomicAddProperty(req, plane_id, property_crtc_w, 320);
drmModeAtomicAddProperty(req, plane_id, property_crtc_h, 320);
drmModeAtomicAddProperty(req, plane_id, property_src_x, 0);
drmModeAtomicAddProperty(req, plane_id, property_src_y, 0);
drmModeAtomicAddProperty(req, plane_id, property_src_w, 320 << 16);
drmModeAtomicAddProperty(req, plane_id, property_src_h, 320 << 16);
drmModeAtomicCommit(fd, req, 0, NULL);
drmModeAtomicFree(req);
- 12.1 获取plane属性
DRM框架核心:
- 上图蓝色部分则是对物理硬件的抽象,黄色部分则是对软件的抽象;
- 虚线以上的为 drm_mode_object,虚线以下为 drm_gem_object;
各个objects之间的关系:
- plane连接fb和crtc
- encoder连接crtc和connector
最简单的KMS驱动代码分析(legacy)
1 |
|
- 定义drm设备对象和drm_mode_objects(crtc plane encoder connector);
- 创建驱动核心drm_driver,设置其相关information和features;
- 完成driver相关的fops,使用DEFINE_DRM_GEM_CMA_FOPS(drm_driver_fops);
- 完成mode_config_funcs;
- 完成各个object的drm_xxx_funcs和drm_xxx_helper_funcs
- drm_dec_init
- drm_mode_config_init,完成设备的modeconfig设置
- 用drm_xxx_funcs创建各个drm_mode_objects,利用drm_xxx_helper_add绑定各个drm_xxx_helper_funcs
- drm_dev_register
以上为legacy接口驱动,且为最简单的驱动实现,各个drm_xxx_funcs和drm_xxx_helper_funcs无法更精简
drm_xxx_funcs 是 drm ioctl 操作的最终入口,但是对于大多数 SoC 厂商来说,它们的 drm_xxx_funcs 操作流程基本相同,只是在寄存器配置上存在差异,因此开发者们将那些 common 的操作流程做成了 helper 函数,而将那些厂商差异化的代码放到了 drm_xxx_helper_funcs 中去,由 SoC 厂商自己实现。
- legacy接口重点
- xxx_funcs必须有,xxx_helper_funcs可以没有
- drm_xxx_init必须有,drm_xxx_helper_add()取决于是否有xxx_helper_funcs
- 只有当xxx_funcs采用DRM标准的helper函数实现时才有可能需要定义xxx_helper_funcs接口
- xxx_funcs.destroy()接口必须实现
最简单的KMS驱动代码分析(atomic)
1 |
|
- 与legacy框架相似,区别主要在于
- 在drm_mode_Config中添加了atomic_check等操作函数
- 在driver中添加了DRIVER_ATOMIC feature
- 各个drm_mode_objects的funcs和helper funcs改为了atomic接口
- 在创建完各个drm_mode_objests后使用drm_mode_config_reset
- atomic重点在于
- driver.feature添加DRIVER_ATOMIC标志
- drm_mode_config_funcs.atomic_commit()接口是atomic操作的主要入口函数,必须实现
- atomic操作依赖vsync,即vblank,因此需要hrtimer提供软件中断信号,驱动初始化时调用drm_vblank_init(),定时器回调函数中调用drm_handle_vblank
- 在各个drm_mode_objs初始化完成后必须调用drm_mode_congfig_reset()来动态创建各个pipeline的软件状态,即drm_xxx_state
- atomic的xxx_funcs必须实现以下接口,它们主要用于维护drm_xxx_state数据结构
- reset()
- atomic_duplicate_state()
- atomic_destroy_state()
- drm_plane_helper_funcs.atomic_update()必须实现
前面的代码只实现了KMS相关驱动的框架,还需要为驱动添加GEM的功能
最简单的GEM驱动
1 |
|
- driver.feature = DRIVER_GEM,表明驱动支持GEM操作
- driver.dumb_create : 分配dumb buffer 的或调接口,主要完成三件事
- 创建gem object
- 分配gem handle
- 分配物理buffer
- fops中有mmap函数,通常用drm_gem_cma_mmap()实现
- gem_vm_ops:主要为mmap服务,必须实现
其实driver中还有dumb_map_offset和dumb_destroy两个接口,分别对应各自的ioctl函数,如果驱动没有实现这两个回调接口, 那么 DRM 框架会使用默认的 drm_gem_dumb_map_offset() 和 drm_gem_dumb_destroy() 代替。