Rockchip RK3399 - DRM gem基础知识
----------------------------------------------------------------------------------------------------------------------------
开发板 :NanoPC-T4
开发板
eMMC
:16GB
LPDDR3
:4GB
显示屏 :15.6
英寸HDMI
接口显示屏
u-boot
:2023.04
linux
:6.3
----------------------------------------------------------------------------------------------------------------------------
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
:
/**
* struct drm_gem_object - GEM buffer object
*
* This structure defines the generic parts for GEM buffer objects, which are
* mostly around handling mmap and userspace handles.
*
* Buffer objects are often abbreviated to BO.
*/
struct drm_gem_object {
/**
* @refcount:
*
* Reference count of this object
*
* Please use drm_gem_object_get() to acquire and drm_gem_object_put_locked()
* or drm_gem_object_put() to release a reference to a GEM
* buffer object.
*/
struct kref refcount;
/**
* @handle_count:
*
* This is the GEM file_priv handle count of this object.
*
* Each handle also holds a reference. Note that when the handle_count
* drops to 0 any global names (e.g. the id in the flink namespace) will
* be cleared.
*
* Protected by &drm_device.object_name_lock.
*/
unsigned handle_count;
/**
* @dev: DRM dev this object belongs to.
*/
struct drm_device *dev;
/**
* @filp:
*
* SHMEM file node used as backing storage for swappable buffer objects.
* GEM also supports driver private objects with driver-specific backing
* storage (contiguous DMA memory, special reserved blocks). In this
* case @filp is NULL.
*/
struct file *filp;
/**
* @vma_node:
*
* Mapping info for this object to support mmap. Drivers are supposed to
* allocate the mmap offset using drm_gem_create_mmap_offset(). The
* offset itself can be retrieved using drm_vma_node_offset_addr().
*
* Memory mapping itself is handled by drm_gem_mmap(), which also checks
* that userspace is allowed to access the object.
*/
struct drm_vma_offset_node vma_node;
/**
* @size:
*
* Size of the object, in bytes. Immutable over the object's
* lifetime.
*/
size_t size;
/**
* @name:
*
* Global name for this object, starts at 1. 0 means unnamed.
* Access is covered by &drm_device.object_name_lock. This is used by
* the GEM_FLINK and GEM_OPEN ioctls.
*/
int name;
/**
* @dma_buf:
*
* dma-buf associated with this GEM object.
*
* Pointer to the dma-buf associated with this gem object (either
* through importing or exporting). We break the resulting reference
* loop when the last gem handle for this object is released.
*
* Protected by &drm_device.object_name_lock.
*/
struct dma_buf *dma_buf;
/**
* @import_attach:
*
* dma-buf attachment backing this object.
*
* Any foreign dma_buf imported as a gem object has this set to the
* attachment point for the device. This is invariant over the lifetime
* of a gem object.
*
* The &drm_gem_object_funcs.free callback is responsible for
* cleaning up the dma_buf attachment and references acquired at import
* time.
*
* Note that the drm gem/prime core does not depend upon drivers setting
* this field any more. So for drivers where this doesn't make sense
* (e.g. virtual devices or a displaylink behind an usb bus) they can
* simply leave it as NULL.
*/
struct dma_buf_attachment *import_attach;
/**
* @resv:
*
* Pointer to reservation object associated with the this GEM object.
*
* Normally (@resv == &@_resv) except for imported GEM objects.
*/
struct dma_resv *resv;
/**
* @_resv:
*
* A reservation object for this GEM object.
*
* This is unused for imported GEM objects.
*/
struct dma_resv _resv;
/**
* @funcs:
*
* Optional GEM object functions. If this is set, it will be used instead of the
* corresponding &drm_driver GEM callbacks.
*
* New drivers should use this.
*
*/
const struct drm_gem_object_funcs *funcs;
/**
* @lru_node:
*
* List node in a &drm_gem_lru.
*/
struct list_head lru_node;
/**
* @lru:
*
* The current LRU list that the GEM object is on.
*/
struct drm_gem_lru *lru;
};
其中:
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
对象;
struct drm_gem_object_funcs {
void (*free)(struct drm_gem_object *obj);
int (*open)(struct drm_gem_object *obj, struct drm_file *file);
void (*close)(struct drm_gem_object *obj, struct drm_file *file);
void (*print_info)(struct drm_printer *p, unsigned int indent, const struct drm_gem_object *obj);
struct dma_buf *(*export)(struct drm_gem_object *obj, int flags);
int (*pin)(struct drm_gem_object *obj);
void (*unpin)(struct drm_gem_object *obj);
struct sg_table *(*get_sg_table)(struct drm_gem_object *obj);
int (*vmap)(struct drm_gem_object *obj, struct iosys_map *map);
void (*vunmap)(struct drm_gem_object *obj, struct iosys_map *map);
int (*mmap)(struct drm_gem_object *obj, struct vm_area_struct *vma);
int (*evict)(struct drm_gem_object *obj);
enum drm_gem_object_status (*status)(struct drm_gem_object *obj);
const struct vm_operations_struct *vm_ops;
};
其中:
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
:
/**
* drm_gem_object_init - initialize an allocated shmem-backed GEM object
* @dev: drm_device the object should be initialized for
* @obj: drm_gem_object to initialize
* @size: object size 对象的大小
*
* Initialize an already allocated GEM object of the specified size with
* shmfs backing store.
*/
int drm_gem_object_init(struct drm_device *dev,
struct drm_gem_object *obj, size_t )
{
struct file *filp;
drm_gem_private_object_init(dev, obj, size);
filp = shmem_file_setup("drm mm object", size, VM_NORESERVE);
if (IS_ERR(filp))
return PTR_ERR(filp);
obj->filp = filp;
return 0;
}
函数通过调用 drm_gem_private_object_init
进行对象初始化;
/**
* drm_gem_private_object_init - initialize an allocated private GEM object
* @dev: drm_device the object should be initialized for
* @obj: drm_gem_object to initialize
* @size: object size
*
* Initialize an already allocated GEM object of the specified size with
* no GEM provided backing store. Instead the caller is responsible for
* backing the object and handling it.
*/
void drm_gem_private_object_init(struct drm_device *dev,
struct drm_gem_object *obj, size_t size)
{
BUG_ON((size & (PAGE_SIZE - 1)) != 0);
// 初始化成员
obj->dev = dev;
obj->filp = NULL;
// 初始化对象引用计数为1
kref_init(&obj->refcount);
obj->handle_count = 0;
obj->size = size;
dma_resv_init(&obj->_resv);
if (!obj->resv)
obj->resv = &obj->_resv;
drm_vma_node_reset(&obj->vma_node);
INIT_LIST_HEAD(&obj->lru_node);
}
然后通过调用 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
:
/**
* drm_gem_handle_create_tail - internal functions to create a handle
* @file_priv: drm file-private structure to register the handle for
* @obj: object to register
* @handlep: pointer to return the created handle to the caller
*
* This expects the &drm_device.object_name_lock to be held already and will
* drop it before returning. Used to avoid races in establishing new handles
* when importing an object from either an flink name or a dma-buf.
*
* Handles must be release again through drm_gem_handle_delete(). This is done
* when userspace closes @file_priv for all attached handles, or through the
* GEM_CLOSE ioctl for individual handles.
*/
int
drm_gem_handle_create_tail(struct drm_file *file_priv,
struct drm_gem_object *obj,
u32 *handlep)
{
struct drm_device *dev = obj->dev;
u32 handle;
int ret;
WARN_ON(!mutex_is_locked(&dev->object_name_lock));
if (obj->handle_count++ == 0)
drm_gem_object_get(obj);
/*
* Get the user-visible handle using idr. Preload and perform
* allocation under our spinlock.
*/
idr_preload(GFP_KERNEL);
// 获取自旋锁
spin_lock(&file_priv->table_lock);
// 基于基数树分配一个唯一的id
ret = idr_alloc(&file_priv->object_idr, obj, 1, 0, GFP_NOWAIT);
// 释放自旋锁
spin_unlock(&file_priv->table_lock);
idr_preload_end();
mutex_unlock(&dev->object_name_lock);
if (ret < 0)
goto err_unref;
handle = ret;
ret = drm_vma_node_allow(&obj->vma_node, file_priv);
if (ret)
goto err_remove;
if (obj->funcs->open) {
// 回调open函数
ret = obj->funcs->open(obj, file_priv);
if (ret)
goto err_revoke;
}
*handlep = handle; // 写回
return 0;
err_revoke:
drm_vma_node_revoke(&obj->vma_node, file_priv);
err_remove:
spin_lock(&file_priv->table_lock);
idr_remove(&file_priv->object_idr, handle);
spin_unlock(&file_priv->table_lock);
err_unref:
drm_gem_object_handle_put_unlocked(obj);
return ret;
}
/**
* drm_gem_handle_create - create a gem handle for an object
* @file_priv: drm file-private structure to register the handle for
* @obj: object to register
* @handlep: pointer to return the created handle to the caller
*
* Create a handle for this object. This adds a handle reference to the object,
* which includes a regular reference count. Callers will likely want to
* dereference the object afterwards.
*
* Since this publishes @obj to userspace it must be fully set up by this point,
* drivers must call this last in their buffer object creation callbacks.
*/
int drm_gem_handle_create(struct drm_file *file_priv,
struct drm_gem_object *obj,
u32 *handlep)
{
mutex_lock(&obj->dev->object_name_lock);
return drm_gem_handle_create_tail(file_priv, obj, handlep);
}
可以由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
文件句柄进行映射;
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
中;
static const struct drm_driver rockchip_drm_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
.dumb_create = rockchip_gem_dumb_create,
......
};
其中dumb_create
配置为rockchip_gem_dumb_create
,该函数用于分配物理内存dumb buffer
,函数定义在drivers/gpu/drm/rockchip/rockchip_drm_gem.c
:
/*
* rockchip_gem_dumb_create - (struct drm_driver)->dumb_create callback
* function
*
* This aligns the pitch and size arguments to the minimum required. wrap
* this into your own function if you need bigger alignment.
*/
int rockchip_gem_dumb_create(struct drm_file *file_priv,
struct drm_device *dev,
struct drm_mode_create_dumb *args)
{
struct rockchip_gem_object *rk_obj;
// 宽度*每像素位数/8
int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
/*
* align to 64 bytes since Mali requires it.
*/
args->pitch = ALIGN(min_pitch, 64);
// 宽*高*每像素位数/8,即得到总大小(单位字节)
args->size = args->pitch * args->height;
rk_obj = rockchip_gem_create_with_handle(file_priv, dev, args->size,
&args->handle);
return PTR_ERR_OR_ZERO(rk_obj);
}
这里主要实现函数是rockchip_gem_create_with_handle
,定义如下;
/*
* rockchip_gem_create_with_handle - allocate an object with the given
* size and create a gem handle on it
*
* returns a struct rockchip_gem_object* on success or ERR_PTR values
* on failure.
*/
static struct rockchip_gem_object *
rockchip_gem_create_with_handle(struct drm_file *file_priv,
struct drm_device *drm, unsigned int size,
unsigned int *handle)
{
struct rockchip_gem_object *rk_obj;
struct drm_gem_object *obj;
bool is_framebuffer;
int ret;
is_framebuffer = drm->fb_helper && file_priv == drm->fb_helper->client.file;
rk_obj = rockchip_gem_create_object(drm, size, is_framebuffer);
if (IS_ERR(rk_obj))
return ERR_CAST(rk_obj);
// 获取GEM对象
obj = &rk_obj->base;
/*
* allocate a id of idr table where the obj is registered
* and handle has the id what user can see.
*/
ret = drm_gem_handle_create(file_priv, obj, handle);
if (ret)
goto err_handle_create;
/* drop reference from allocate - handle holds it now. */
drm_gem_object_put(obj);
return rk_obj;
err_handle_create:
rockchip_gem_free_object(obj);
return ERR_PTR(ret);
}
函数执行流程如下:
- 调用
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
函数调用栈:
rockchip_gem_create_with_handle(file_priv, dev, args->size,&args->handle)
// #1 创建并初始化Rockchip驱动自定义的GEM对象
rk_obj = rockchip_gem_create_object(drm, size, is_framebuffer)
// #1.1 创建Rockchip驱动自定义的GEM对象
rk_obj = rockchip_gem_alloc_object(drm, size)
// GEM对象初始化
drm_gem_object_init(drm, obj, size)
// #1.2 为Rockchip GEM对象分配DMA缓冲区,或者在IOMMU上分配内存
rockchip_gem_alloc_buf(rk_obj, alloc_kmap)
rockchip_gem_alloc_dma(rk_obj, alloc_kmap)
// 分配DMA缓冲区。分配的大小为obj->size字节,属性为rk_obj->dma_attrs
rk_obj->kvaddr = dma_alloc_attrs(drm->dev, obj->size,
&rk_obj->dma_addr, GFP_KERNEL,
rk_obj->dma_attrs);
// 获取GEM对象 struct drm_gem_object
obj = &rk_obj->base;
// #2 创建GEM对象handle
drm_gem_handle_create(file_priv, obj, handle)
// 3# 引用计数-1
drm_gem_object_put(obj)
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
;
struct rockchip_gem_object {
struct drm_gem_object base;
unsigned int flags;
void *kvaddr;
dma_addr_t dma_addr;
/* Used when IOMMU is disabled */
unsigned long dma_attrs;
/* Used when IOMMU is enabled */
struct drm_mm_node mm;
unsigned long num_pages;
struct page **pages;
struct sg_table *sgt;
size_t size;
};
其中:
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
对象;
static const struct drm_gem_object_funcs rockchip_gem_object_funcs = {
// 释放GEM对象及其相关资源的操作
.free = rockchip_gem_free_object,
.get_sg_table = rockchip_gem_prime_get_sg_table,
.vmap = rockchip_gem_prime_vmap,
.vunmap = rockchip_gem_prime_vunmap,
.mmap = rockchip_drm_gem_object_mmap,
.vm_ops = &drm_gem_dma_vm_ops,
};
static struct rockchip_gem_object *
rockchip_gem_alloc_object(struct drm_device *drm, unsigned int size)
{
struct rockchip_gem_object *rk_obj;
struct drm_gem_object *obj;
size = round_up(size, PAGE_SIZE);
rk_obj = kzalloc(sizeof(*rk_obj), GFP_KERNEL);
if (!rk_obj)
return ERR_PTR(-ENOMEM);
obj = &rk_obj->base;
obj->funcs = &rockchip_gem_object_funcs;
// 初始化gem object
drm_gem_object_init(drm, obj, size);
return rk_obj;
}
流程如此:
- 调用
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
上分配内存;
static int rockchip_gem_alloc_buf(struct rockchip_gem_object *rk_obj,
bool alloc_kmap)
{
struct drm_gem_object *obj = &rk_obj->base;
struct drm_device *drm = obj->dev;
// 获取drm驱动私有数据
struct rockchip_drm_private *private = drm->dev_private;
if (private->domain)
// 为Rockchip GEM对象在IOMMU上分配内存
return rockchip_gem_alloc_iommu(rk_obj, alloc_kmap);
else
// 为Rockchip GEM对象分配DMA缓冲区
return rockchip_gem_alloc_dma(rk_obj, alloc_kmap);
}
由于没有初始化private->domain
,因此会执行rockchip_gem_alloc_dma
;
static int rockchip_gem_alloc_iommu(struct rockchip_gem_object *rk_obj,
bool alloc_kmap)
{
int ret;
// 获取页面
ret = rockchip_gem_get_pages(rk_obj);
if (ret < 0)
return ret;
// 将GEM对象映射到IOMMU
ret = rockchip_gem_iommu_map(rk_obj);
if (ret < 0)
goto err_free;
if (alloc_kmap) {
// 将获取到的页面映射到内核虚拟地址空间中
rk_obj->kvaddr = vmap(rk_obj->pages, rk_obj->num_pages, VM_MAP,
pgprot_writecombine(PAGE_KERNEL));
if (!rk_obj->kvaddr) {
DRM_ERROR("failed to vmap() buffer\n");
ret = -ENOMEM;
goto err_unmap;
}
}
return 0;
err_unmap:
// 解除IOMMU映射
rockchip_gem_iommu_unmap(rk_obj);
err_free:
// 释放页面
rockchip_gem_put_pages(rk_obj);
return ret;
}
static int rockchip_gem_alloc_dma(struct rockchip_gem_object *rk_obj,
bool alloc_kmap)
{
struct drm_gem_object *obj = &rk_obj->base;
struct drm_device *drm = obj->dev;
rk_obj->dma_attrs = DMA_ATTR_WRITE_COMBINE;
if (!alloc_kmap)
rk_obj->dma_attrs |= DMA_ATTR_NO_KERNEL_MAPPING;
// 分配DMA缓冲区。分配的大小为obj->size字节,属性为rk_obj->dma_attrs
rk_obj->kvaddr = dma_alloc_attrs(drm->dev, obj->size,
&rk_obj->dma_addr, GFP_KERNEL,
rk_obj->dma_attrs);
if (!rk_obj->kvaddr) {
DRM_ERROR("failed to allocate %zu byte dma buffer", obj->size);
return -ENOMEM;
}
return 0;
}
3.2 framebuffer
创建
介绍完了gem object
的创建和初始化,我们知道drm framebuffer
的实现依赖于底层内存管理器比如GEM
、TTM
。
那么struct drm_frmebuffer
是怎么创建的呢?
这里我们需要回顾一下《Rockchip RK3399 - DRM
驱动程序》文章中介绍的rockchip_drm_bind
函数,在该函数执行中会进行模式配置的初始化,在jams及rockchip_drm_mode_config_init
中有如下一行代码:
dev->mode_config.funcs = &rockchip_drm_mode_config_funcs;
drm
设备模式设置mode_config
的回调函数funcs
被设置为rockchip_drm_mode_config_funcs
;
static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = {
.fb_create = rockchip_fb_create,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
};
fb_create
回调接口用于创建framebuffer object
,并绑定GEM
对象。
rockchip_fb_create
定义在drivers/gpu/drm/rockchip/rockchip_drm_fb.c
:
static const struct drm_framebuffer_funcs rockchip_drm_fb_funcs = {
.destroy = drm_gem_fb_destroy,
.create_handle = drm_gem_fb_create_handle,
.dirty = drm_atomic_helper_dirtyfb,
};
static struct drm_framebuffer *
rockchip_fb_create(struct drm_device *dev, struct drm_file *file,
const struct drm_mode_fb_cmd2 *mode_cmd)
{
struct drm_afbc_framebuffer *afbc_fb;
const struct drm_format_info *info;
int ret;
// 获取drm格式
info = drm_get_format_info(dev, mode_cmd);
if (!info)
return ERR_PTR(-ENOMEM);
// 动态分配内存,指向struct drm_afbc_framebuffer
afbc_fb = kzalloc(sizeof(*afbc_fb), GFP_KERNEL);
if (!afbc_fb)
return ERR_PTR(-ENOMEM);
// 初始化成员afbc_fb->base,struct drm_framebuffer类型;同时设置framebuffer的funcs为rockchip_drm_fb_funcs
ret = drm_gem_fb_init_with_funcs(dev, &afbc_fb->base, file, mode_cmd,
&rockchip_drm_fb_funcs);
if (ret) {
kfree(afbc_fb);
return ERR_PTR(ret);
}
// 如果支持afbc
if (drm_is_afbc(mode_cmd->modifier[0])) {
int ret, i;
ret = drm_gem_fb_afbc_init(dev, mode_cmd, afbc_fb);
if (ret) {
struct drm_gem_object **obj = afbc_fb->base.obj;
for (i = 0; i < info->num_planes; ++i)
drm_gem_object_put(obj[i]);
kfree(afbc_fb);
return ERR_PTR(ret);
}
}
// 返回framebuffer
return &afbc_fb->base;
}
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
操作结合;
/**
* drm_gem_fb_init_with_funcs() - Helper function for implementing
* &drm_mode_config_funcs.fb_create
* callback in cases when the driver
* allocates a subclass of
* struct drm_framebuffer
* @dev: DRM device
* @fb: framebuffer object
* @file: DRM file that holds the GEM handle(s) backing the framebuffer
* @mode_cmd: Metadata from the userspace framebuffer creation request
* @funcs: vtable to be used for the new framebuffer object
*
* This function can be used to set &drm_framebuffer_funcs for drivers that need
* custom framebuffer callbacks. Use drm_gem_fb_create() if you don't need to
* change &drm_framebuffer_funcs. The function does buffer size validation.
* The buffer size validation is for a general case, though, so users should
* pay attention to the checks being appropriate for them or, at least,
* non-conflicting.
*
* Returns:
* Zero or a negative error code.
*/
int drm_gem_fb_init_with_funcs(struct drm_device *dev,
struct drm_framebuffer *fb,
struct drm_file *file,
const struct drm_mode_fb_cmd2 *mode_cmd,
const struct drm_framebuffer_funcs *funcs)
{
const struct drm_format_info *info;
struct drm_gem_object *objs[DRM_FORMAT_MAX_PLANES];
unsigned int i;
int ret;
// 获取DRM格式信息
info = drm_get_format_info(dev, mode_cmd);
if (!info) {
drm_dbg_kms(dev, "Failed to get FB format info\n");
return -EINVAL;
}
// 遍历每一个color plane
for (i = 0; i < info->num_planes; i++) {
unsigned int width = mode_cmd->width / (i ? info->hsub : 1);
unsigned int height = mode_cmd->height / (i ? info->vsub : 1);
unsigned int min_size;
// 查找对应color plane的GEM对象
objs[i] = drm_gem_object_lookup(file, mode_cmd->handles[i]);
if (!objs[i]) {
drm_dbg_kms(dev, "Failed to lookup GEM object\n");
ret = -ENOENT;
goto err_gem_object_put;
}
min_size = (height - 1) * mode_cmd->pitches[i]
+ drm_format_info_min_pitch(info, i, width)
+ mode_cmd->offsets[i];
if (objs[i]->size < min_size) {
drm_dbg_kms(dev,
"GEM object size (%zu) smaller than minimum size (%u) for plane %d\n",
objs[i]->size, min_size, i);
drm_gem_object_put(objs[i]);
ret = -EINVAL;
goto err_gem_object_put;
}
}
// 初始化framebuffer对象
ret = drm_gem_fb_init(dev, fb, mode_cmd, objs, i, funcs);
if (ret)
goto err_gem_object_put;
return 0;
err_gem_object_put:
while (i > 0) {
--i;
drm_gem_object_put(objs[i]);
}
return ret;
}
函数主要流程如下:
- 通过
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
;
static int
drm_gem_fb_init(struct drm_device *dev,
struct drm_framebuffer *fb,
const struct drm_mode_fb_cmd2 *mode_cmd,
struct drm_gem_object **obj, unsigned int num_planes,
const struct drm_framebuffer_funcs *funcs)
{
unsigned int i;
int ret;
drm_helper_mode_fill_fb_struct(dev, fb, mode_cmd);
for (i = 0; i < num_planes; i++)
fb->obj[i] = obj[i];
ret = drm_framebuffer_init(dev, fb, funcs);
if (ret)
drm_err(dev, "Failed to init framebuffer: %d\n", ret);
return ret;
}
参考文章
[1] DRM
的GEM
[2] DRM
驱动mmap
详解:(二)CMA Helper