注:文章都是通过阅读各位前辈总结的资料 Android 11.0 && Linux(Kernel 4.19)Rockchip平台源码、加上自己的思考分析总结出来的,其中难免有理解不对的地方,欢迎大家批评指正。文章为个人学习、研究、欣赏之用,图文内容整理自互联网,如有侵权,请联系删除(◕‿◕),转载请注明出处(©Rockchip ©Android @Linux 版权所有),谢谢。
(==文章基于 Kernel-4.19==)&&(==文章基于 Android 11.0==)
【开发板 RockPi4bPlusV1.6 Android 11.0 && Linux(Kernel 4.19)源码链接】:(repo init -u https://github.com/radxa/manifests.git -b Android11_Radxa_rk11.1 -m rockchip-r-release.xml)
【开发板 RockPi4bPlusV1.6 Android 11.0 && Linux(Kernel 4.19)编译指南】
正是由于前人(各位大神)的分析和总结,帮助我节约了大量的时间和精力,特别感谢,由于不喜欢图片水印,去除了水印,敬请谅解!!!
本文转自Rockchip RK3399 - DRM gem基础知识 ,如有侵权,请联系删除。
开发板 :ROCK Pi 4B+
开发板eMMC
:32GB
LPDDR4
:4GB
显示屏 :7
英寸HDMI
接口显示屏u-boot
:2017.09
linux
:4.19
GEM
主要负责显示buffer
的分配和释放,linux
内核中使用struct drm_gem_object
表示GEM
对象,驱动一般需要用私有信息来扩展GEM
对象,因此struct drm_gem_object
都是嵌入在驱动自定义的GEM
结构体内的。gem object
的创建以及初始化步骤如下:
- 创建一个
GEM
对象,驱动为自定义GEM
对象申请内存; - 通过
drm_gem_object_init
来初始化嵌入在其中的struct drm_gem_object
; - 通过
drm_gem_handle_create
创建GEM
对象handle
; - 分配物理
buffer
; - 通过
mmap
将物理buffer
映射到用户空间;这样用户空间就可以直接访问了;
一、GEM
数据结构
1.1 struct drm_gem_object
linux
内核中使用struct drm_gem_object
表示GEM
对象,struct drm_gem_object
定义在include/drm/drm_gem.h
:
1 | /** |
其中:
refcount
:表示对象引用计数,用于对GEM
对象全生命周期的管理;handle_count
:表示该对象的句柄计数。每个句柄还持有一个引用,当handle_count
降为0时,任何全局名称(例如flink
命名空间中的id
)都将被清除;dev
:该对象所属的DRM
设备的指针;filp
:用作可互换的缓存对象的后备存储的共享内存文件节点;vma_node
:用于支持内存映射的对象的映射信息;size
:对象的大小,以字节为单位;name
:该对象的全局名称,从1开始。值为0表示无名称;dma_buf
:与此GEM
对象关联的dma-buf
;import_attach
:支持此对象的dma-buf
附件;resv
:与此GEM
对象关联的保留对象的指针;_resv
:此GEM
对象的保留对象;funcs
:可选的GEM
对象函数。如果设置了此字段,则将使用它而不是对应的drm_driver GEM
回调函数;lru_node
:在drm_gem_lru
中的列表节点;lru
:GEM
对象当前所在的LRU
列表;
1.2 struct drm_gem_object_funcs
struct drm_gem_object_funcs
定义了与GEM
对象相关的回调函数,用于管理和操作GEM
对象;
1 | struct drm_gem_object_funcs { |
其中:
free
:用于释放GEM
对象及其相关资源的操作,必须实现;open
:创建GEM handle
时(drm_gem_handle_create
),回调该函数,可选;close
:释放GEM handle
时(drm_gem_handle_delete
),回调该函数,可选;get_sg_table
:用于获取buffer
的Scatter-Gather
表(SG
表);vmap
:为buffer
获取一个虚拟地址,会被drm_gem_dmabuf_vmap helper
使用,可选;vunmap
: 释放由vmap
返回的虚拟地址,会被drm_gem_dmabuf_vunmap helper
使用,可选;mmap
:用于处理对GEM
对象的mmap
调用,并相应地设置vma
(vm_area_struct
)结构,可选;- 此回调函数被
drm_gem_mmap_obj
和drm_gem_prime_mmap
两者使用; - 当存在
mmap
时,不会使用vm_ops
,而是必须由mmap
回调函数设置vma
->vm_ops
;
- 此回调函数被
vm_ops
:与mmap
一起使用的虚拟内存区域操作结构体,它包含了对应于虚拟内存区域操作的函数指针;对于GEM
对象或其他需要通过mmap
系统调用映射到用户空间的内核对象,必须实现适当的vm_ops
结构体。
二、核心API
2.1 GEM
初始化
GEM
使用shmem
来申请匿名页内存,drm_gem_object_init
将会根据传入的size
创建一个指定大小的指定大小的shmfs
,并将这个shmfs file
保存到struct drm_gem_object
的filp
字段。
当图形硬件使用系统内存,这些内存就会作为对象的主存储直接使用,否则就会作为后备内存。
驱动负责调用shmem_read_mapping_page_gfp
做实际物理页面的申请,初始化GEM
对象时驱动可以决定申请页面,或者延迟到需要内存时再申请(需要内存时是指:用户态访问内存发生缺页中断,或是驱动需要启动DMA
用到这段内存)。
在某些情况下,匿名可分页内存分配并不理想,特别是当硬件要求物理连续的系统内存时,这在嵌入式设备中经常发生。在这种情况下,驱动程序可以通过调用drm_gem_private_object_init
来初始化GEM
对象,而不是使用drm_gem_object_init
,从而创建没有shmfs
支持的私有GEM
对象。
drm_gem_object_init
函数定义在drivers/gpu/drm/drm_gem.c
:
1 | /** |
函数通过调用 drm_gem_private_object_init
进行对象初始化;
1 | /** |
然后通过调用 shmem_file_setup
创建一个与该 GEM
对象关联的shmem
文件,并将文件指针赋值给 obj->filp
。
2.2 GEM
对象命名
用户空间与内核之间的通信使用本地句柄(handle
)、全局名称或者文件描述符来引用GEM
对象,所有这些都是32bit
数。
2.2.1 GEM handle
GEM handle
只在特定的DRM
文件中有效,应用程序通过驱动程序特定的ioctl
获取GEM
对象的handle
,并可以在其他标准或驱动程序特定的ioctl
中使用该handle
引用GEM
对象,关闭一个DRM
文件句柄会释放其中所有的GEM
,并解引用相关的GEM
对象。
对于GEM handle
,函数drm_gem_handle_create
用于创建GEM
对象的handle
,这个函数接收三个参数:
file_priv
:DRM file
的私有数据;obj
:GEM
对象;handlep
:将创建的handle
返回给调用者的指针handlep
;
drm_gem_handle_create
函数定义在drivers/gpu/drm/drm_gem.c
:
1 | /** |
可以由drm_gem_object_lookup
检索与handle
关联的GEM
对象。
当handle
不再需要时,驱动程序使用drm_gem_handle_delete
进行删除。释放句柄并不直接销毁或释放GEM
对象本身,它只是解除了句柄与GEM
对象之间的关联。
为了避免泄漏GEM
对象,驱动程序必须确保适当地丢弃自己所拥有的引用(比如在对象创建时获取的初始引用),而不需要特别考虑handle
。例如,在实现dumb_create
操作时,驱动程序必须在返回handle
之前丢弃对GEM
对象的初始引用。
2.2.2 GEM
名称
GEM
名称类似于句柄,但不仅限于DRM
文件。它们可以在进程之间传递,用于全局引用GEM
对象。应用程序需要通过ioctl的DRM_IOCTL_GEM_FLINK
和DRM_IOCTL_GEM_OPEN
来将句柄转换为名称,以及将名称转换为句柄。这个转换由DRM core
处理,不需要任何驱动程序特定的支持。
2.2.3 文件描述符
GEM
也支持通过PRIME dma-buf
文件描述符的缓存共享,基于GEM
的驱动必须使用提供的辅助函数来实现exoprting
和importing
。共享文件描述符比可以被猜测的全局名称更安全,因此是首选的缓存共享机制。通过GEM
全局名称进行缓存共享仅在传统用户态支持,更进一步的说,PRIME
由于其基于dma-buf
,还允许跨设备缓存共享。
2.3 GEM
对象的生命周期
所有的GEM
对象都由GEM Core
进行引用计数管理,在DRM
子系统中,可以通过调用函数drm_gem_object_get
和drm_gem_object_put
来获取和释放对GEM
对象的引用。
执行drm_gem_object_get
调用者必须持有struct drm_device
的struct_mutex
锁,而为了方便也提供了drm_gem_object_put_unlocked
也可以不持锁操作。
当最后一个对GEM
对象的引用释放时,GEM core
调用struct drm_gem_object_funcs
中的free
操作,这一操作对于使能了GEM
的驱动来说是必须,并且必须释放GEM
对象和所有相关资源。
在struct drm_gem_object_funcs
中,void (*free)(struct drm_gem_object *obj)
函数指针是用于释放GEM对象及其相关资源的操作。驱动程序需要实现这个函数,并在适当的时候调用drm_gem_object_release
释放与GEM
对象相关的资源。
2.4 GEM
对象内存映射
在linux kernel
驱动中,实现mmap
系统调用离不开两个关键步骤:
- 物理内存的分配;
- 建立物理内存到用户空间的映射关系。
这刚好对应了DRM
中的dumb_create
和mmap
操作,先说映射关系,在linux
驱动中建立映射关系的方法主要有如下两种:
- 一次性映射:在
mmap
回调函数中,一次性建立好整块内存的映射关系,通常以remap_pfn_range
为代表; Page Fault
:在mmap
先不建立映射关系,等上层触发缺页异常时,在fault
中断处理函数中建立映射关系,缺哪块补哪块,通常以vm_insert_page
为代表。
想再进一步学习mmap
系统调用的相关知识,推荐大家阅读《DRM
驱动mmap
详解:(一)预备知识》和《认真分析mmap
:是什么 为什么 怎么用》。
在DRM
中,GEM
更倾向于通过特定于驱动程序的ioctl
实现类似读/写的访问buffer
,而不是将buffer
映射到用户空间。然而,当需要对buffer
进行随机访问时(例如执行软件渲染),直接访问对象可能更有效率。
不能直接使用mmap
系统调用来映射GEM
对象,因为它们没有自己的文件句柄。目前有两种共存的方法将GEM
对象映射到用户空间:
(1) 第一种方法使用特定于驱动程序的ioctl
来执行映射操作,底层调用do_mmap
,这种方法经常被认为不可靠,在新的GEM
驱动程序中似乎不被鼓励使用,因此这里不会描述它;
(2) 使用mmap
系统调用对DRM
文件句柄进行映射;
1 | void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) |
DRM
通过传递一个虚拟偏移量来标识要映射的GEM
对象,该偏移量通过mmap
的offset
参数传递。在映射之前,GEM
对象必须与虚拟偏移量关联起来。为此,驱动程序必须在对象上调用drm_gem_create_mmap_offset
。
分配了虚拟偏移量值后,驱动程序必须以特定于驱动程序的方式将该值传递给应用程序,然后可以将其用作mmap
的offset
参数。
GEM
核心提供了一个辅助方法drm_gem_mmap
来处理对象映射。该方法可以直接设置为mmap
文件操作处理程序,它将根据偏移值查找GEM
对象,并将VMA
操作设置为struct drm_driver gem_vm_ops
字段。
请注意:drm_gem_mmap
不会将内存映射到用户空间,而是依赖于驱动程序提供的fault handler
来逐个映射页面。
三、Rockchip gem
驱动
这里我们介绍一下Rochchip DRM
驱动中与gem object
相关的实现,具体实现文件:
drivers/gpu/drm/rockchip/rockchip_drm_fb.c
;drivers/gpu/drm/rockchip/rockchip_drm_gem.c
;drivers/gpu/drm/rockchip/rockchip_drm_gem.h
;drivers/gpu/drm/rockchip/rockchip_drm_fb.h
;
3.1 gem object
创建及初始化
gem object
的创建以及初始化是由rockchip_gem_dumb_create
函数完成的,其定义在rockchip_drm_driver
中;
1 | static const struct drm_driver rockchip_drm_driver = { |
其中dumb_create
配置为rockchip_gem_dumb_create
,该函数用于分配物理内存dumb buffer
,函数定义在drivers/gpu/drm/rockchip/rockchip_drm_gem.c
:
1 | /* |
这里主要实现函数是rockchip_gem_create_with_handle
,定义如下;
1 | /* |
函数执行流程如下:
- 调用
rockchip_gem_create_object
创建并初始化Rockchip
驱动自定义的GEM
对象struct rockchip_gem_objec
;数据结构rockchip_gem_object
是Rockchip
驱动自定义的GEM
对象,内部包含struct drm_gem_object
,定义在drivers/gpu/drm/rockchip/rockchip_drm_gem.h
: - 调用
drm_gem_handle_create
创建GEM
对象handle
; - 调用
drm_gem_object_put
将GEM
对象的引用计数-1;
rockchip_gem_create_with_handle
函数调用栈:
1 | rockchip_gem_create_with_handle(file_priv, dev, args->size,&args->handle) |
rockchip_gem_alloc_dma
函数执行后会创建了一块内存放在了rockchip gem object
的对象里。
3.1.1 struct rockchip_gem_object
struct rockchip_gem_object
为Rockchip
驱动扩展的gem object
,定义在drivers/gpu/drm/rockchip/rockchip_drm_gem.h
;
1 | struct rockchip_gem_object { |
其中:
base
:这是内核定义的gem object
结构;kvaddr
:这个字段是一个指向虚拟地址的指针,它可能指向这个GEM
对象在用户空间中的虚拟地址;dma_addr
:这个字段是设备内存地址,它可能被用于直接内存访问(DMA
)操作;dma_attrs
:这个字段可能用于存储与DMA
操作相关的属性;num_pages
:这个字段表示这个GEM
对象占用的页面数量;pages
:这是一个指向struct page
的指针数组,它可能用于存储这个GEM
对象占用的所有页面的信息;size
:这个字段表示这个GEM
对象的大小;
3.1.2 rockchip_gem_alloc_object
rockchip_gem_alloc_object
用于创建Rockchip
驱动自定义的GEM
对象;
1 | static const struct drm_gem_object_funcs rockchip_gem_object_funcs = { |
流程如此:
- 调用
kzalloc
动态分配Rockchip
驱动自定义的GEM
对象struct rockchip_gem_object
; - 然后设定
GEM
对象funcs
为rockchip_gem_object_funcs
; - 最后调用
drm_gem_object_init
初始化GEM
对象,创建一个大小为size
的shmfs
,并将这个shmfs file
存放到struct drm_gem_object
的filp
字段。
3.1.3 rockchip_gem_alloc_buf
rockchip_gem_alloc_buf
函数用于为Rockchip GEM
对象分配DMA
缓冲区,或者在IOMMU
上分配内存;
1 | static int rockchip_gem_alloc_buf(struct rockchip_gem_object *rk_obj, |
由于没有初始化private->domain
,因此会执行rockchip_gem_alloc_dma
;
1 | static int rockchip_gem_alloc_iommu(struct rockchip_gem_object *rk_obj, |
3.2 framebuffer
创建
介绍完了gem object
的创建和初始化,我们知道drm framebuffer
的实现依赖于底层内存管理器比如GEM
、TTM
。
那么struct drm_frmebuffer
是怎么创建的呢?
这里我们需要回顾一下《Rockchip RK3399 - DRM
驱动程序》文章中介绍的rockchip_drm_bind
函数,在该函数执行中会进行模式配置的初始化,在jams及rockchip_drm_mode_config_init
中有如下一行代码:
1 | dev->mode_config.funcs = &rockchip_drm_mode_config_funcs; |
drm
设备模式设置mode_config
的回调函数funcs
被设置为rockchip_drm_mode_config_funcs
;
1 | static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = { |
fb_create
回调接口用于创建framebuffer object
,并绑定GEM
对象。
rockchip_fb_create
定义在drivers/gpu/drm/rockchip/rockchip_drm_fb.c
:
1 | static const struct drm_framebuffer_funcs rockchip_drm_fb_funcs = { |
3.2.1 drm_gem_fb_init_with_funcs
drm_gem_fb_init_with_funcs
是一个用于实现&drm_mode_config_funcs.fb_create
回调函数的辅助函数。它适用于那些初始化framebuffer
时同时提供自定义的framebuffer
操作集合的驱动程序,drm_gem_fb_init_with_funcs
定义在drivers/gpu/drm/drm_gem_framebuffer_helper.c:
函数参数说明:
dev
:DRM
设备结构体指针。fb
:framebuffer
对象指针。file
:保存了支持framebuffer
的GEM
句柄的DRM
文件指针;mode_cmd
:用户空间framebuffer
创建请求的元数据;funcs
:framebuffer
操作结合;
1 | /** |
函数主要流程如下:
- 通过
drm_get_format_info
函数获取DRM
格式信息,如果失败则返回错误; - 遍历每个
color plane
,计算出每个color plane
的宽度、高度和最小尺寸; - 使用
drm_gem_object_lookup
函数查找对应color plane
的GEM
对象,如果失败则返回错误; - 对比
GEM
对象的大小和最小尺寸,如果大小小于最小尺寸则返回错误; - 调用
drm_gem_fb_init
函数初始化framebuffer
对象。
3.2.2 drm_gem_fb_init
drm_gem_fb_init
函数用于初始化framebuffer
对象,函数定义在drivers/gpu/drm/drm_gem_framebuffer_helper.c
;
1 | static int |
参考文章
[1] DRM
的GEM