注:文章都是通过阅读各位前辈总结的资料 Android 11.0 && Linux(Kernel 4.19)Rockchip平台源码、加上自己的思考分析总结出来的,其中难免有理解不对的地方,欢迎大家批评指正。文章为个人学习、研究、欣赏之用,图文内容整理自互联网,如有侵权,请联系删除(◕‿◕),转载请注明出处(©Rockchip ©Android @Linux 版权所有),谢谢。

(==文章基于 Kernel-4.19==)&&(==文章基于 Android 11.0==)

【zhoujinjian.com博客原图链接】

【开发板 RockPi4bPlusV1.6】

【开发板 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+开发板
eMMC32GB
LPDDR44GB
显示屏 :7英寸HDMI接口显示屏
u-boot2017.09
linux4.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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/**
* 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中的列表节点;
  • lruGEM对象当前所在的LRU列表;

1.2 struct drm_gem_object_funcs

struct drm_gem_object_funcs定义了与GEM对象相关的回调函数,用于管理和操作GEM对象;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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:用于获取bufferScatter-Gather表(SG表);
  • vmap:为buffer获取一个虚拟地址,会被drm_gem_dmabuf_vmap helper使用,可选;
  • vunmap: 释放由vmap返回的虚拟地址,会被drm_gem_dmabuf_vunmap helper使用,可选;
  • mmap:用于处理对GEM对象的mmap调用,并相应地设置vmavm_area_struct)结构,可选;
    • 此回调函数被drm_gem_mmap_objdrm_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_objectfilp字段。

当图形硬件使用系统内存,这些内存就会作为对象的主存储直接使用,否则就会作为后备内存。

驱动负责调用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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 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 进行对象初始化;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 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_privDRM file的私有数据;
  • objGEM对象;
  • handlep:将创建的handle返回给调用者的指针handlep

drm_gem_handle_create函数定义在drivers/gpu/drm/drm_gem.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/**
* 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_FLINKDRM_IOCTL_GEM_OPEN来将句柄转换为名称,以及将名称转换为句柄。这个转换由DRM core处理,不需要任何驱动程序特定的支持。

2.2.3 文件描述符

GEM也支持通过PRIME dma-buf文件描述符的缓存共享,基于GEM的驱动必须使用提供的辅助函数来实现exoprtingimporting。共享文件描述符比可以被猜测的全局名称更安全,因此是首选的缓存共享机制。通过GEM全局名称进行缓存共享仅在传统用户态支持,更进一步的说,PRIME由于其基于dma-buf,还允许跨设备缓存共享。

2.3 GEM对象的生命周期

所有的GEM对象都由GEM Core进行引用计数管理,在DRM子系统中,可以通过调用函数drm_gem_object_getdrm_gem_object_put来获取和释放对GEM对象的引用。

执行drm_gem_object_get调用者必须持有struct drm_devicestruct_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_createmmap操作,先说映射关系,在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对象,该偏移量通过mmapoffset参数传递。在映射之前,GEM对象必须与虚拟偏移量关联起来。为此,驱动程序必须在对象上调用drm_gem_create_mmap_offset

分配了虚拟偏移量值后,驱动程序必须以特定于驱动程序的方式将该值传递给应用程序,然后可以将其用作mmapoffset参数。

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
2
3
4
5
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*
* 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,定义如下;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*
* 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_objectRockchip驱动自定义的GEM对象,内部包含struct drm_gem_object,定义在drivers/gpu/drm/rockchip/rockchip_drm_gem.h
  • 调用drm_gem_handle_create创建GEM对象handle
  • 调用drm_gem_object_putGEM对象的引用计数-1;

rockchip_gem_create_with_handle函数调用栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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_objectRockchip驱动扩展的gem object,定义在drivers/gpu/drm/rockchip/rockchip_drm_gem.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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对象;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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对象funcsrockchip_gem_object_funcs
  • 最后调用drm_gem_object_init初始化GEM对象,创建一个大小为sizeshmfs,并将这个shmfs file存放到struct drm_gem_objectfilp字段。
3.1.3 rockchip_gem_alloc_buf

rockchip_gem_alloc_buf函数用于为Rockchip GEM对象分配DMA缓冲区,或者在IOMMU上分配内存;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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的实现依赖于底层内存管理器比如GEMTTM

那么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
2
3
4
5
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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:

函数参数说明:

  • devDRM设备结构体指针。
  • fbframebuffer对象指针。
  • file:保存了支持framebufferGEM句柄的DRM文件指针;
  • mode_cmd:用户空间framebuffer创建请求的元数据;
  • funcsframebuffer操作结合;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/**
* 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 planeGEM对象,如果失败则返回错误;
  • 对比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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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] DRMGEM

[2] DRM 驱动mmap详解:(二)CMA Helper

[3] linux kernel DRM Internals