转载:linux drm原理及应用
参考链接dma:https://blog.csdn.net/abc3240660/article/details/81942190#t6Linux DRM KMS 驱动简介:https://blog.csdn.net/zhuyong006/article/details/80942777DRM(Direct Rendering Manager)学习简介:https://blog.csdn.net/
参考链接
dma:https://blog.csdn.net/abc3240660/article/details/81942190#t6
Linux DRM KMS 驱动简介:https://blog.csdn.net/zhuyong006/article/details/80942777
DRM(Direct Rendering Manager)学习简介:https://blog.csdn.net/hexiaolong2009/article/details/83720940
应用场景
linux drm(Direct Rendering Manager)设计之初是作为一套display数据传输流程,用于将camera采集的视频数据抛给display显示。
drm驱动与应用程序之间封装了一个中间层libdrm,应用程序可以通过调用libdrm提供的接口实现对drm驱动的操作,相对简单。
主要模块
drm系统主要分为三个模块:libdrm,GEM,KMS。
libdrm
libdrm运行在用户空间,是应用程序与内核之间交互的桥梁,其功能主要是填充内核需要的结构并通过ioctl调用传入内核,内核填充后再返回给应用空间。
GEM
GEM(Graphic Execution Manager)主要负责buffer的操作。
KMS
KMS(Kernel Mode Setting)主要负责相关参数的设置(包括分辨率、刷新率、电源状态(休眠唤醒)等)和显示画面的切换(显示buffer的切换,多图层的合成方式,以及每个图层的显示位置)。
基本元素
KMS
-
- CRTC:对显示buffer进行扫描,并产生时序信号的硬件模块,通常指Display Controller;
a) DPMS (Display Power Manage System) 电源状态管理 (crtc_funcs->dpms)
b) 将 Framebuffer 转换成标准的 LCDC Timing ,其实就是一帧图像刷新的过程(crtc_funs->mode_set)
c) 帧切换,即在 VBlank 消影期间,切换 Framebuffer(crtc_funcs->page_flip)
-
- Encoder :负责将CRTC输出的timing时序转换成外部设备所需要
a) DPMS (Display Power Manage System) 电源状态管理 (encoder_funcs->dpms)
b) 将 VOP 输出的 lcdc Timing 打包转化为对应接口时序 HDMI TMDS / … (encoder_funcs->mode_set)
encoder是crtc和connector的连接者
-
- CONNECTOR:连接物理显示设备的连接器,如HDMI、DisplayPort、DSI总线,通常和Encoder 驱动绑定在一起,与display联系最为密切;
a) 获取和上报display热拔插(Hotplug)状态
b) 读取并解析屏 (Panel) 的 EDID 信息
-
- PLANE:硬件图层,有的Display硬件支持多层合成显示,但所有的Display Controller至少 要有1个plane;
a) plane是ctrc和framebuffer的连接者
b) 每个crtc至少要有一个plane
c) DRM中的Plane和我们常说的YUV/YCbCr图形格式中的plane完全是两个不同的概念。YUV图形格式中的plane指的是图 像数据在内存中的排列形式,一般Y通道占一段连续的内存块,UV通道占另一段连续的内存块,我们称之为YUV-2plane (也叫YUV 2平面),属于软件层面。而DRM中的Plane指的是Display Controller中用于多层合成的单个硬件图层模块, 属于硬件层面
d) 随着软件技术的不断更新,对硬件的性能要求越来越高,在满足功能正常使用的前提下,对功耗的要求也越来越苛刻。本来GPU可以 处理所有图形任务,但是由于它运行时的功耗实在太高,设计者们决定将一部分简单的任务交给Display Controller去处理(比如合 成),而让GPU专注于绘图(即渲染)这一主要任务,减轻GPU的负担,从而达到降低功耗提升性能的目的。于是,Plane(硬件图 层单元)就诞生了
-
- FB:Framebuffer即单个图层的显示内容,唯一一个和硬件无关的基本元素
a) buffer就是一块分配的内存,和硬件没有关系,一般都是通过DMA_BUF机制将该buffer和camera的数据buf关联起来,以达到数据快速转移过来的目的
-
- property:任何你想设置的参数,都可以做成property,是DRM驱动中最灵活、最方便的Mo de setting机制
a) Atomic机制的引入,可以,减少上层应用接口的维护工作量。当开发者有新的功能需要添加时,无需增加新的函数名和IOCTL,只需在底层驱动中新增 一个property,然后在自己的应用程序中获取/操作该property的值即可
b) Property的结构简单概括主要由3部分组成:name、id 和 value。其中id为该property在DRM框架中全局唯一的标识符
c) 增强了参数设置的灵活性。一次IOCTL可以同时设置多个property,减少了userspace与kernel space切换的次数,同时最 大限度的满足了不同硬件对于参数设置的要求,提高了软件效率
GEM
-
- DUMB:只支持连续物理内存的buffer类型,基于kernel中通用CMA API实现,多用于小分辨率简单场景
-
- PRIME:连续、非连续物理内存都支持,基于DMA-BUF机制,可以实现buffer共享,多用 于大内存复杂场景
-
- fence:buffer同步机制,基于内核dma_fence机制实现,用于防止显示内容出现异步问题
模块行为
以 HDMI Monitor 显示的过程为例,实例解析下 CRTC / Encoder / Connector 的行为
-
- 首先 HDMI 驱动检测到电视 Plugin 信号,读出电视的 EDID 信号,获取电视的分辨率信息 (DRM Connector)
-
- Userspace 将需要显示的数据填充在 framebuffer 里面,然后通过 libdrm 接口通知 VOP 设备开始显示
-
- 接着 VOP 驱动将 framebuffer 里面的数据转换成标准的 LCDC Timing 时序 (DRM CRTC)
-
- 同时 HDMI 驱动将 HDMI 硬件模块的 LCDC 时序配置与 VOP 输出时序一致,准备将输入的 LCDC Timing 转化为电视识别的 HDMI TMDS 信号 (DRM Encoder)
drm应用程序调用流程
-
- 初始化
open(/dev/dri/card0) //open drm device
--> drmSetClientCap(DRM_CLIENT_CAP_UNIVERSAL_PLANES)
--> drmSetClientCap(DRM_CLIENT_CAP_ATOMIC)
--> drmModeGetResources()
--> drmModeGetConnector() //found connector DSI
--> drmModeObjectGetProperties() //found connector dpms prop
--> drmModeGetEncoder() //found encoder DSI
--> drmModeGetCrtc() //found crtc that connect to DSI
--> drmGetPlaneByType(DRM_PLANE_TYPE_PRIMARY) //get PRIMARY/OVERLAY type drm plane info
--> drmModeObjectGetProperties(plane_id,DRM_MODE_OBJECT_PLANE) //get drm plane properties
--> drmModeGetProperty() //get drm info like crtc_id,fb_id and so on,they be used for commit
获取crtc,encoder,connector,plane的先后顺序为connector,encoder,crtc,plane,每个前者的结构中都有后者的id号,发现connector后可以用过其结构下的encoder_id找到与自己连接的encoder模块,同理直到找到从plane到connector的连接通路。
-
- 创建dumb空间并通过mmap映射到应用层
drmIoctl(DRM_IOCTL_MODE_CREATE_DUMB) //创建dumb buffer(只支持连续物理内存,基于kernel中通用CMA API实现)
--> drmIoctl(DRM_IOCTL_MODE_MAP_DUMB) //获取dumb buffer的映射偏移值
--> mmap() //通过mmap映射内核空间到应用层
--> drmPrimeHandleToFD(fd,handle,0,&fd2) //handle已和fd绑定,在此将fd2与handle绑定,即fd2同fd相同
--> drmModeAddFB2() //添加framebuffer
-
- 将相机数据传入前面mmap映射的地址中
可以将相机数据从相机buf拷贝到mmap空间,但该方式效率太低,对于高帧率的应用场景可以使用DMA_BUF机制将相机数据buf和mmap空间建立连接,使数据通过dma通道直达mmap空间。
-
- 提交数据
drmModeAtomicAlloc //申请Atomic结构
--> drmModeAtomicAddProperty() //将前面获取的crtc_id,fb_id等参数都传入申请的Atomic结构中
--> drmModeAtomicCommit() //提交数据到display
-
- 资源释放
drmModeRmFB(fb_id) //删除drmModeAddFB2()添加的framebuffer,不然会造成shmem泄露
--> munmap() //释放mmap映射的内存
--> close() //关闭打开的drm句柄
以上便为应用程序调用libdrm的流程,其中的3和4在循环中,1和2只需要调用一次,但在某些场景中需要释放2中申请的资源,但1中初始化的不能释放;
该场景为:单一plane,crtc等,需要多次打开和关闭display,且系统中有多个功能块在使用drm中的一个或多个模块(比如视频编码用到encoder模块),这样,若将1中初始化的资源释放掉,再次获取时可能会被其他进程占用导致无法获取,这种场景下就需要保留1中资源,但多次申请/是否2中资源。
但这样会引入一个问题:调用drmModeRmFB函数释放fb,但若释放了正在用于提交的fb,内核会将crtc关闭,导致vop数据传输通路断裂,而且没有framebuffer时Encoder也会被disable,此时再打开vop也无法获取Encoder,致使vop不通;虽然内核宏CONFIG_DRM_FBDEV_EMULATION使能时,在所有的drmfd关闭后fbdev会重新配置crtc,但由于其他进程也打开了drm,因此crtc不会再次被设置,除非重启程序。
解决这一问题的方法是:打开vop时调用drmModeSetCrtc函数手动设置crtc,即使无法找到Encoder,该函数也会使用一个默认的Encoder将crtc,encoder,connector三者建立连接,从而打通vop通路。
也就是说在以上的应用场景下即使内核默认使能了宏CONFIG_DRM_FBDEV_EMULATION,也需要调用到drmModeSetCrtc()函数,非以上场景可不用调用(对于Atomic模式来说)。
libdrm与内核的交互
-
- 创建dumb buffer
该操作使用idr机制将资源与一个id号关联起来,由此可以使用一个整数标识整个资源,非常简洁,但是一个整数能够记录表达的信息太有限了,所以整数 ID 的背后常常都有一个结构体与之对应;
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &alloc_arg); //struct drm_mode_create_dumb alloc_arg,该函数会填充该结构下的用于标识资源的id号handle
-->ioctl(fd,DRM_IOCTL_MODE_CREATE_DUMB,alloc_arg) //通过ioctl与内核交互
内核:
drm_mode_create_dumb_ioctl()
-->rockchip_gem_dumb_create(struct drm_mode_create_dumb *args)
-->rockchip_gem_create_with_handle(&args->handle)
-->rockchip_gem_create_object() //创建struct drm_gem_object结构指针*obj
-->drm_gem_handle_create(obj,handle) //申请一个id(使用idr机制)
-->drm_gem_handle_create_tail(handle)
-->int ret = idr_alloc(&dev->mode_config.crtc_idr, ...) //申请一个id并将该id与obj指针关联,该函数返回申请到的id值,(注意此处的参数crtc_idr)
/*
handle = ret; //至此,已将应用空间传入的args->handle填充
*/
-->drm_vma_node_allow(&obj->vma_node, file_priv->filp);
-->gem_open_object(obj)
至此,alloc_arg.handle, alloc_arg.pitch和alloc_arg.size都被填充
-
- 创建用于mmap的offset
前面创建了dumb buffer,在将其映射到应用空间时需要offset(映射点偏移量)参数;
struct drm_mode_map_dumb mmap_arg;
mmap_arg.handle = alloc_arg.handle; //将create_dumb返回的handle赋值给mmap_arg结构
drmIoctl(DRM_IOCTL_MODE_MAP_DUMB)
内核:
drm_mode_mmap_dumb_ioctl()
-->rockchip_gem_dumb_map_offset(args->handle, &args->offset) //传入mmap_arg结构的handle和offset
-->obj = drm_gem_object_lookup(handle) //struct drm_gem_object *obj
-->idr_find() //通过id号查找对应的结构
/*
struct drm_gem_object *obj;
obj = idr_find(&filp->object_idr, handle); //获取create_bumb时和该handle绑定的obj指针
*/
-->drm_gem_create_mmap_offset(obj) //为一个object创建一个虚假的mmap偏移量
//GEM内存映射通过将伪造的mmap偏移返回给用户空间来工作,该偏移可以在后续的mmap调用中使用,然后,DRM核心代码根据偏移量查找对象并设置各种内存映射结构.
//DRM通过通过mmap offset参数传递的伪偏移量来标识要映射的GEM对象.因此,在映射之前,GEM对象必须与假偏移量关联.为,驱动程序必须在对象上调用drm_gem_create_mmap_offset
-->*offset = drm_vma_node_offset_addr(&obj->vma_node); //填充应用传入的mmap_arg结构的offset
//offset值会根据调用DRM_IOCTL_MODE_MAP_DUMB的次数持续增大,返回后用于mmap的偏移参数,即保证了mmap多次时映射的buffer偏移不会重合
至此,mmap_arg.offset被填充
-
- mmap映射bumd buffer到应用空间
map = mmap(0, alloc_arg.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mmap_arg.offset);
-
- 申请dma_buf与fd并将二者关联
将前面被内核填充的alloc_arg.handle作为参数传给内核,内核使用该值寻找和其绑定的drm_gem_object结构,在其中寻找是否以有dma_buf被export,若有则不再申请,若无则申请.
drmPrimeHandleToFD(fd, alloc_arg.handle, 0, &buffer->dmabuf_fd) //dmabuf_fd:待填充的fd
/*
struct drm_prime_handle args;
args.fd = -1;
args.handle = handle;
args.flags = flags;
*/
-->drmIoctl(fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &args) //将handle,dmabuf_fd,flags传入内核
内核:
drm_prime_handle_to_fd_ioctl()
-->drm_gem_prime_handle_to_fd(struct drm_device *dev,struct drm_file *file_priv, uint32_t handle,uint32_t flags,int *prime_fd)
-->obj = drm_gem_object_lookup(dev, file_priv, handle) //获取与handle绑定的drm_gem_object结构
-->dmabuf = drm_prime_lookup_buf_by_handle(&file_priv->prime, handle); //查找是否有与handle绑定的被export的dma_buf
/*
if(dmabuf) get_dma_buf(dmabuf); //若存在,则获取和该dma_buf绑定的file结构
*/
/*
if (obj->import_attach) { //若已有import的dma_buf,重新export该dma_buf
dmabuf = obj->import_attach->dmabuf; //获取该dma_buf
get_dma_buf(dmabuf);
}
*/
/*
if (obj->dma_buf) { //判断obj结构中是否已申请dma_buf
get_dma_buf(obj->dma_buf);
dmabuf = obj->dma_buf;
}
*/
-->dmabuf = export_and_register_object(dev, obj, flags); //前三者都不满足,则申请dma_buf,并注册到该obj中
-->dmabuf = dev->driver->gem_prime_export(dev, obj, flags); //drm_gem_prime_export()
/*
struct dma_buf_export_info exp_info = {
.exp_name = KBUILD_MODNAME,
.owner = dev->driver->fops->owner,
.ops = &drm_gem_prime_dmabuf_ops, //dma_buf_ops操作集
.size = obj->size,
.flags = flags,
.priv = obj,
};
*/
-->dma_buf_export(&exp_info) //传入exp_info结构
/*
dmabuf = kzalloc(alloc_size, GFP_KERNEL); //申请dma_buf
dmabuf->priv = exp_info->priv; //填充
dmabuf->ops = exp_info->ops;
dmabuf->size = exp_info->size;
dmabuf->exp_name = exp_info->exp_name;
dmabuf->owner = exp_info->owner;
reservation_object_init(exp_info->resv);
file = anon_inode_getfile("dmabuf", &dma_buf_fops, dmabuf,exp_info->flags); //创建与该dma_buf关联的file结构
dmabuf->file = file;
return dmabuf; //返回生成的dma_buf
*/
/*
obj->dma_buf = dmabuf; //加入obj结构
*/
-->get_dma_buf(obj->dma_buf);
-->drm_gem_object_reference(obj); //获取新的内核引用
-->drm_prime_add_buf_handle(&file_priv->prime,dmabuf, handle); //将申请的dma_buf信息加入drm_prime_file_private->head链表
-->*prime_fd = dma_buf_fd(dmabuf, flags) //生成和dma_buf管理的fd,详见下方附录
在此获取到的dmafd会传到v4l2插件,如此v4l2插件便可使用该fd找到申请的dma_buf,完成相机数据的输入;
-
- 为指定的crtc创建framebuffer并获取创建的id(fb仅仅为显存描述信息如宽,高,bpp等,并不是开辟存储图片的内存)
DRM_IOCTL(fd, DRM_IOCTL_MODE_ADDFB2, &f)
/*
struct drm_mode_fb_cmd2 f;
f.width = width;
f.height = height;
f.pixel_format = pixel_format;
f.flags = flags;
memcpy(f.handles, bo_handles, 4 * sizeof(bo_handles[0])); //DRM_IOCTL_MODE_CREATE_DUMB时填充,用于查找之前创建的drm_gem_object结构
memcpy(f.pitches, pitches, 4 * sizeof(pitches[0])); //DRM_IOCTL_MODE_CREATE_DUMB时填充
memcpy(f.offsets, offsets, 4 * sizeof(offsets[0])); //0
*/
内核:
drm_mode_addfb2()
-->struct drm_framebuffer *fb = internal_framebuffer_create(..., r)
/*
struct drm_framebuffer *fb;
struct drm_mode_fb_cmd2 *r = data; //用户空间传入的参数
fb = internal_framebuffer_create(dev, r, file_priv);
*/
-->rockchip_user_fb_create(..., r) //.fb_create()
/*
fb = dev->mode_config.funcs->fb_create(dev, file_priv, r); //应用传入的参数
static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = {
.fb_create = rockchip_user_fb_create,
...
};
*/
-->struct drm_gem_object *obj = drm_gem_object_lookup(r->handles[i]) //循环:通过传入的handles找出对应的drm_gem_object结构
-->struct drm_framebuffer *fb = rockchip_fb_alloc(r, objs) //objs:循环得到的所有的drm_gem_object结构数组
-->drm_helper_mode_fill_fb_struct(..., &rockchip_fb->fb, r) //用r的参数填充rockchip_fb->fb结构
/*
struct rockchip_drm_fb *rockchip_fb;
*/
-->drm_framebuffer_init(..., &rockchip_fb->fb,&rockchip_drm_fb_funcs) //创建fb id并加入fb链表
/*
static const struct drm_framebuffer_funcs rockchip_drm_fb_funcs = {
.destroy = rockchip_drm_fb_destroy,
.create_handle = rockchip_drm_fb_create_handle,
};
*/
-->drm_mode_object_get(..., fb, DRM_MODE_OBJECT_FB) //创建fb id
-->r->fb_id = fb->base.id //将创建的fd id复制给用户空间传入的结构下
结构解析
-
- _drmModeEncoder
typedef struct _drmModeEncoder {
uint32_t encoder_id;
uint32_t encoder_type;
uint32_t crtc_id;
uint32_t possible_crtcs; //connector下连接若干encoder,每个encoder支持若干crtc,possible_crtcs的某一位为1代表相应次序(不是id)的crtc可用。
uint32_t possible_clones;
}
- 2 ._drmModePlane, plane在crtc和framebuffer之间,连接二者
typedef struct _drmModePlane {
uint32_t count_formats;
uint32_t *formats;
uint32_t plane_id;
uint32_t crtc_id; //连接的crtc
uint32_t fb_id; //连接的framebuffer
uint32_t crtc_x, crtc_y;
uint32_t x, y;
uint32_t possible_crtcs;
uint32_t gamma_size;
}
其中crtc_id和fb_id都不是指针,由此可以看出一个plane只能对应一个fb_id和一个crtc_id.
附录
dma-buf机制
-
- 概念 :
dma_buf是内核中一个独立的子系统,提供了一个让不同设备、子系统之间进行共享缓存的统一框架,这里说的缓存通常是指通过DMA方式访问的和硬件交互的内存。 比如,来自摄像头采集的通过pciv驱动传输的内存、gpu内部管理的内存等等.一开始,dma_buf机制在内核中的主要运用场景是支持GPU驱动中的prime机制,但是作为内核中的通用模块,它的适用范围很广.
-
- dma_buf子系统包含三个主要组成
- dma-buf对象,它代表的后端是一个sg_table,它暴露给应用层的接口是一个文件描述符,通过传递描述符达到了交互访问dma-buf对象,进而最终达成了 共享访问sg_table的目的.
2. fence对象, which provides a mechanism to signal when one device as finished access.
3. reservation对象, 它负责管理缓存的分享和互斥访问.
-
- 架构 :
DMA_BUF框架下主要有两个角色对象,一个是exporter,相当于是buffer的生产者,相对应的是importer或者是user,即buffer的消费使用者.
exporter职责:
- 实现struct amd_buf_ops中的buffer管理回调函数
4. 允许使用者通过amd_buf的sharing APIS来共享导出的buffer
5. 通过struct dma_buf结构体管理buffer的分配、包装等细节工作
6. 决策buffer的实际后端内存的来源
7. 管理好scatterlist的迁移工作
importer职责:
- 是共享buffer的使用者之一
8. 无需关心所用buffer是哪里以及如何产生的
9. 通过struct dma_buf_attachment结构体访问用于构建buffer的scatterlist,并且提供将buffer映射到自己地址空间的机制
-
- 使用流程
a) Exporter驱动申请或者引用已经导入的待共享访问的内存
b) Exporter驱动调用dma_buf_export()创建dma_buf对象,同时将自定义的struct dma_buf_ops方法集和步骤1中的内存挂载到dma_buf对象中
c) Exporter驱动调用dma_buf_fd()将步骤2中创建的dam_buf对象关联到全局可见的文件描述符fd,同时通过ioctl方法将fd传递给应用层
d). 应用层将fd传递给importer驱动程序
e) importer驱动通过调用dma_buf_get(fd)获取dma_buf对象
f) importer驱动调用dma_buf_attach()和dma_buf_map_attachment()获取共享缓存的信息
-
- 代码示例
a) exporter申请dma bufferSS
drm_gem_prime_export()
/*
struct dma_buf_export_info exp_info = {
.exp_name = KBUILD_MODNAME,
.owner = dev->driver->fops->owner,
.ops = &drm_gem_prime_dmabuf_ops,
.size = obj->size,
.flags = flags,
.priv = obj,
};
*/
-->dma_buf_export(&exp_info)
/*
struct dma_buf *dmabuf;
struct file *file;
dmabuf = kzalloc(alloc_size, GFP_KERNEL);
dmabuf->priv = exp_info->priv;
dmabuf->ops = exp_info->ops;
dmabuf->size = exp_info->size;
dmabuf->exp_name = exp_info->exp_name;
dmabuf->owner = exp_info->owner;
file = anon_inode_getfile("dmabuf", &dma_buf_fops, dmabuf,exp_info->flags) //创建file文件对象实例,以及匿名 inode节点和dentry 等数据结构
//dmabuf通过anon_inode_getfile()函数挂载到了file对象上的 priv指针上,而dma_buf_fops回调函数集挂载在file对象上的ops上,最后dma_buf_fops函数集中的回调函数实现都会通过file->priv拿到dma_buf对象, 然后直接调用dma_buf中的ops方法。这样的函数重载实现是file作为驱动程序接口功能实现的常规操作
dmabuf->file = file; //将生成的file结构与dmabuf关联
*/
b) 将创建的dma_buf与文件描述符fd关联
int fd = dma_buf_fd(dmabuf, flags);
/*
fd = get_unused_fd_flags(flags); //获取一个未使用的数字作为fd返回
fd_install(fd, dmabuf->file); //完成fd与file结构的关联,如此,通过fd就能访问file结构的资源
*/
ps:
1.file结构:当进程打开一个文件时,内核就会为该进程分配一个file结构,表示打开的文件在进程的上下文,然后应用程序会通过一个int类型的文件描述符来访问这个结构,实际上内核的进程里面维护一个file结构的数组,而文件描述符就是相应的file结构在数组中的下标
2.参考:https://blog.csdn.net/abc3240660/article/details/81942190#t6
3.转载地址:https://blog.csdn.net/user_jiang/article/details/105178965
更多推荐
所有评论(0)