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

浙公网安备 33010602011771号