linux drm驱动系列

    drm 学习记录, 基础知识部分请参考何小龙的drm专题, 该系列博客非常详细的介绍了drm 的整个框架. 博主也是通过该系列文章入门, 只是有很多地方不是很理解, 于是参考内核源码进行一些补充. 文章内容引用会该专题部分内容.

一、 drm device

    drm 子系统的核心数据结构, linux 中的每个 drm 子系统都由一个 drm_device 统一进行描述. drm_device 的核心功能就是提供用户空间和内核中的 kms 进行交互. 他的本质是一个主设备号为 DRM_MAJOR 226的字符设备, 每一个 drm_minor 都是一个次设备. 总共有三种次设备 primarycontrolrender.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct drm_device {
struct list_head legacy_dev_list;
int if_version;

struct kref ref; /* 设备引用计数 */
struct device *dev; /* 表示这是一个设备 */
struct drm_driver *driver; /* 对应的 drm_driver */
void *dev_private; /* 私有数据 */
struct drm_minor *control; /* drm_dev 下的 control 设备,对应的 driver type DRM_MINOR_CONTROL */
struct drm_minor *primary; /* drm_dev 下的 primary 设备,对应的 driver type DRM_MINOR_PRIMARY */
struct drm_minor *render; /* drm_dev 下的 render 设备,对应的 driver type DRM_MINOR_RENDER */
bool registered; // 设备是否已经注册
......
};

    通过 drm_minor 用来描述一个 drm_dev 下的三种不同可以实例化的设备, 每个 drm 子系统至少有一个 DRM_MINOR_PRIMARY 设备

  • DRM_MINOR_PRIMARY 对应主设备
  • DRM_MINOR_CONTROL 对应控制设备
  • DRM_MINOR_RENDER 对应管理设备
1
2
3
4
5
6
7
8
9
10
11
struct drm_minor {
int index; // 次设备号, 用来标识该设备. 它由 drm_minors_idr 维护.
int type; // 表示该次设备的设备类型, DRM_MINOR_PRIMARY, DRM_MINOR_CONTROL, DRM_MINOR_RENDER
struct device *kdev; // 设备模型中的设备结构
struct drm_device *dev; // 指向所属的 drm 设备

struct dentry *debugfs_root; // 指向调试文件系统

struct list_head debugfs_list; // 用于将次设备的 DebugFS 目录添加到全局列表中的链表头。
struct mutex debugfs_lock; // 互斥锁
};

    内核提供了两个接口 drm_dev_initdrm_dev_register来创建和注册 drm_dev.

1
static struct idr drm_minors_idr; 

    idr 全称 ID Radix IDR 主要用于建立 id 与指针(指向对应的数据结构) 之间的对应关系. 在 drm 框架中用来维护 drm 设备对应的次设备号. 次设备号在 drm_minors_idr 中申请. (0 - 64) 对应 DRM_MINOR_PRIMARY 设备, (64 - 128) 对应 DRM_MINOR_CONTROL 设备, (128 - 192) 对应 DRIVER_RENDER 设备.

关于 idr 的用法参考: IDR: ID Radix

1. 注册 drm_dev

    drm_device 的注册为两个部分, 首先是调用 drm_dev_init 创建并初始化一个 drm_device 然后调用 drm_dev_register 进行注册.

    注册完成之后会分别创建三个属性节点 dev/dri/cardxdev/dri/controlxdev/dri/enderDx, 用来和 kms 进行交互, 关系如下所示

    当我们使用 openioctlmmap等函数接口最终会回调到 drm_driver 的 drm_open, drm_ioctl, drm_gem_cma_mmap. 其中 drm_ioctl它提供了很多默认的实现。

1
2
3
4
5
6
7
static const struct drm_ioctl_desc drm_ioctls[] = {
DRM_IOCTL_DEF(DRM_IOCTL_VERSION, drm_version,DRM_UNLOCKED|DRM_RENDER_ALLOW),
DRM_IOCTL_DEF(DRM_IOCTL_GET_UNIQUE, drm_getunique, DRM_UNLOCKED),
DRM_IOCTL_DEF(DRM_IOCTL_GET_MAGIC, drm_getmagic, DRM_UNLOCKED),
DRM_IOCTL_DEF(DRM_IOCTL_IRQ_BUSID, drm_irq_by_busid, DRM_MASTER|DRM_ROOT_ONLY),
......
};

    libdrm 也是基于这些 ioctl 来实现的.这些 ioctl 有啥用呢?, 用户空间通过这些 ioctl 就可以与 CRTC, CONNECTOR, ENCODER, FB, PLANE, PROPERTY 等等这些 kms 中的 obj 进行交互.当然也支持客制化自己的 ioctl. 例如 rk 平台自己增加的 ioctl

1
2
3
4
5
6
7
8
9
10
11
12
13
static const struct drm_ioctl_desc rockchip_ioctls[] = {
DRM_IOCTL_DEF_DRV(ROCKCHIP_GEM_CREATE, rockchip_gem_create_ioctl, DRM_UNLOCKED | DRM_AUTH | DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(ROCKCHIP_GEM_MAP_OFFSET, rockchip_gem_map_offset_ioctl, DRM_UNLOCKED | DRM_AUTH | DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(ROCKCHIP_GEM_GET_PHYS, rockchip_gem_get_phys_ioctl, DRM_UNLOCKED | DRM_AUTH | DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(ROCKCHIP_GET_VCNT_EVENT, rockchip_drm_get_vcnt_event_ioctl, DRM_UNLOCKED),
};

static struct drm_driver rockchip_drm_driver = {
......
.ioctls = rockchip_ioctls,
.num_ioctls = ARRAY_SIZE(rockchip_ioctls),
......
};

1.1 connector 属性文件

drm_device 注册完成之后会创建一系列 connector 相关的属性文件用于 debug connector 的状态.

  • status 节点: 有三个属性值 “connected”“disconnected”“unknown” 用来描述显示器的连接状态. 实例:
1
2
3
4
rk3566_rgo:/ # cat /sys/class/drm/card0-DSI-1/status
connected
rk3566_rgo:/ # cat /sys/class/drm/card0-Writeback-1/status
connected

小知识: Writeback 用于获取的图片不需要显示, 可以先通过 Writeback 回写系统内存(屏幕截图, 视频捕获等), 需要显示再写入显存.

  • enabled 节点: 有两个属性值 “enabled”“disabled”. 用来判断当前 connector 是否连接上了 encoder
1
2
3
4
rk3566_rgo:/ # cat /sys/class/drm/card0-DSI-1/enabled // 唤醒时
enabled
rk3566_rgo:/ # cat /sys/class/drm/card0-DSI-1/enabled // 休眠时
disabled
  • dpms 节点: 有四个属性值, 用来描述显示器的电源状态. “On”, 表示显示器正常工作. “Standby”", 表示显示器出于待机状态, 即仅仅背光没亮. “Suspend”, 显示器进入低功耗状态. “Off”, 表示显示器关闭电源.
1
2
3
4
rk3566_rgo:/ # cat /sys/class/drm/card0-DSI-1/dpms // 唤醒时
Off
rk3566_rgo:/ # cat /sys/class/drm/card0-DSI-1/dpms // 休眠时
On
  • modes 节点: 用来返回显示分辨率
1
2
rk3566_rgo:/ # cat /sys/class/drm/card0-DSI-1/modes
720x1280

2. 源码阅读

2.1 drm_dev_init

该函数用来创建一个 drm_dev. 每一个 drm 系统都需要创建一个 drm_dev 来和 kms 中的 obj 进行交互…

  1. 对应成员变量的简单初始化
  2. 检测 driver 的 driver_features 标志位是否设置 DRIVER_RENDER , 有则创建对应的设备 dev/dri/enderD(128 - 192)
  3. 创建一个 DRM_MINOR_PRIMARY 子设备, 每个 drm_dev 必须有一个默认的 DRM_MINOR_PRIMARY 设备 dev/dri/card(0 - 64)
  4. 检测 driver 的 driver_features 标志位是否设置 DRIVER_GEM , 如果设置了则会为我们分配并创建一个默认的起始偏移地址为 DRM_FILE_PAGE_OFFSET_START内存大小为 DRM_FILE_PAGE_OFFSET_SIZE的 vma_offset_manager
  5. 将父设备名称用作 DRM 设备的唯一标识符 unique(drm dev的成员变量),没有父设备则使用驱动程序名称作为 unique 唯一标识符.
  • 小贴士: 这个接口仅仅创建并初始设备化并不会注册
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
int drm_dev_init(struct drm_device *dev,
struct drm_driver *driver,
struct device *parent)
{
int ret;

if (!drm_core_init_complete) {
DRM_ERROR("DRM core is not initialized\n");
return -ENODEV;
}

kref_init(&dev->ref); // 设置 kref 引用计数
dev->dev = parent; // 设置父设备
dev->driver = driver; // 设置 driver

INIT_LIST_HEAD(&dev->filelist); /* 一系列链表的初始化 */
INIT_LIST_HEAD(&dev->ctxlist);
INIT_LIST_HEAD(&dev->vmalist);
INIT_LIST_HEAD(&dev->maplist);
INIT_LIST_HEAD(&dev->vblank_event_list);

spin_lock_init(&dev->buf_lock); /* 一系列锁的初始化 */
spin_lock_init(&dev->event_lock);
mutex_init(&dev->struct_mutex);
mutex_init(&dev->filelist_mutex);
mutex_init(&dev->ctxlist_mutex);
mutex_init(&dev->master_mutex);

dev->anon_inode = drm_fs_inode_new();
if (IS_ERR(dev->anon_inode)) {
ret = PTR_ERR(dev->anon_inode);
DRM_ERROR("Cannot allocate anonymous inode: %d\n", ret);
goto err_free;
}

// 检测 driver 的 driver_features 标志位是否设置 DRIVER_RENDER
// 如果设置了调用 drm_minor_alloc 创建一个 DRIVER_RENDER 设备
// dev/dri/enderD128 - dev/dri/enderD192
if (drm_core_check_feature(dev, DRIVER_RENDER)) {
ret = drm_minor_alloc(dev, DRM_MINOR_RENDER);
if (ret)
goto err_minors;
}

// 默认创建一个 DRM_MINOR_PRIMARY 子设备
ret = drm_minor_alloc(dev, DRM_MINOR_PRIMARY);
if (ret)
goto err_minors;

// 暂时不知道干啥的
ret = drm_ht_create(&dev->map_hash, 12);
if (ret)
goto err_minors;

drm_legacy_ctxbitmap_init(dev);

// 如果驱动支持 DRIVER_GEM ,做 gem 相关初始化
if (drm_core_check_feature(dev, DRIVER_GEM)) {
ret = drm_gem_init(dev);
if (ret) {
DRM_ERROR("Cannot initialize graphics execution manager (GEM)\n");
goto err_ctxbitmap;
}
}

// 将父设备名称用作 DRM 设备的唯一标识符,但对于虚拟设备(例如 vgem),则使用驱动程序名称作为后备标识符
ret = drm_dev_set_unique(dev, parent ? dev_name(parent) : driver->name);
if (ret)
goto err_setunique;

return 0;

err_setunique:
if (drm_core_check_feature(dev, DRIVER_GEM))
drm_gem_destroy(dev);
err_ctxbitmap:
drm_legacy_ctxbitmap_cleanup(dev);
drm_ht_remove(&dev->map_hash);
err_minors:
drm_minor_free(dev, DRM_MINOR_PRIMARY);
drm_minor_free(dev, DRM_MINOR_RENDER);
drm_minor_free(dev, DRM_MINOR_CONTROL);
drm_fs_inode_free(dev->anon_inode);
err_free:
mutex_destroy(&dev->master_mutex);
mutex_destroy(&dev->ctxlist_mutex);
mutex_destroy(&dev->filelist_mutex);
mutex_destroy(&dev->struct_mutex);
return ret;
}
EXPORT_SYMBOL(drm_dev_init);

1) drm_minor_alloc

  1. 根据传入的 type 在 drm_minors_idr 链表中申请一个可用的 id, 并且使用这个 id 作为次设备号, 创建对应的 minor 设备.
  • DRM_MINOR_PRIMARY id 范围 0 - 64 ==> dev/dri/card0 - dev/dri/card64
  • DRM_MINOR_CONTROL id 范围 64 - 128 ==> dev/dri/controlD64 - dev/dri/controlD128
  • DRM_MINOR_RENDER id 范围 128 - 192 ==> dev/dri/enderD128 - dev/dri/enderD192
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
static int drm_minor_alloc(struct drm_device *dev, unsigned int type)
{
struct drm_minor *minor;
unsigned long flags;
int r;

minor = kzalloc(sizeof(*minor), GFP_KERNEL);
if (!minor)
return -ENOMEM;

/*
* 总共有 3 种 type
* enum drm_minor_type {
* DRM_MINOR_PRIMARY, id 范围 0 - 64, , 对应的设备节点 dev/dri/card0 - dev/dri/card64
* DRM_MINOR_CONTROL, id 范围 64 - 128 , 对应的设备节点 dev/dri/controlD64 - dev/dri/controlD128
* DRM_MINOR_RENDER, id 范围 128 - 192 , 对应的设备节点 dev/dri/enderD128 - dev/dri/enderD192
* };
*/
minor->type = type; // 设置 type
minor->dev = dev; // 设置所属的 drm_dev

idr_preload(GFP_KERNEL);
spin_lock_irqsave(&drm_minor_lock, flags);

// 根据设备类型,申请一个空 id 用来做此设备的次设备号,从这里可以得到
// DRM_MINOR_PRIMARY id 范围 0 - 64
// DRM_MINOR_CONTROL id 范围 64 - 128
// DRM_MINOR_RENDER id 范围 128 - 192
r = idr_alloc(&drm_minors_idr,
NULL,
64 * type,
64 * (type + 1),
GFP_NOWAIT);
spin_unlock_irqrestore(&drm_minor_lock, flags);
idr_preload_end();

if (r < 0)
goto err_free;

// 设置申请到的 id 也是次设备号
minor->index = r;

// 根据前面申请到的次设备号, 为 minor 创建一个对应类型的 dev ,并初始化.
minor->kdev = drm_sysfs_minor_alloc(minor);
if (IS_ERR(minor->kdev)) {
r = PTR_ERR(minor->kdev);
goto err_index;
}

// 设置根据类型 type 将 minor 挂接到对应的位置.
*drm_minor_get_slot(dev, type) = minor;
return 0;

err_index:
spin_lock_irqsave(&drm_minor_lock, flags);
idr_remove(&drm_minors_idr, minor->index);
spin_unlock_irqrestore(&drm_minor_lock, flags);
err_free:
kfree(minor);
return r;
}

2) drm_sysfs_minor_alloc

  1. 根据 drm_minor_alloc 申请的设备号创建对应名称的设备,可选的设备如下
  • DRM_MINOR_PRIMARY ==> card0 --> card64
  • DRM_MINOR_CONTROL ==> controlD64 --> controlD128
  • DRM_MINOR_RENDER ==> enderD128 --> enderD192
  1. 设置父设备为所属 drm_dev
  2. 设置私有数据为对应的 minor
  3. 设置设备所属设备类 drm_class ==> “/sys/class/drm”
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
struct device *drm_sysfs_minor_alloc(struct drm_minor *minor)
{
const char *minor_str;
struct device *kdev;
int r;

// 根据类型设置设备名称
if (minor->type == DRM_MINOR_CONTROL)
minor_str = "controlD%d";
else if (minor->type == DRM_MINOR_RENDER)
minor_str = "renderD%d";
else
minor_str = "card%d";

// 创建设备结构
kdev = kzalloc(sizeof(*kdev), GFP_KERNEL);
if (!kdev)
return ERR_PTR(-ENOMEM);

// 设备初始化
device_initialize(kdev);
kdev->devt = MKDEV(DRM_MAJOR, minor->index); // 设置设备号
kdev->class = drm_class; // 设置设备所属设备类
kdev->type = &drm_sysfs_device_minor; // 设置设备类型 drm_minor
kdev->parent = minor->dev->dev; // 设置父设备为所属 drm_dev
kdev->release = drm_sysfs_release; // 设置 release 回调函数
dev_set_drvdata(kdev, minor); // 设置私有数据为 minor

// 设置设备名称
r = dev_set_name(kdev, minor_str, minor->index);
if (r < 0)
goto err_free;

return kdev;

err_free:
put_device(kdev);
return ERR_PTR(r);
}

3) drm_minor_get_slot

返回 type 对应的 dev 下的设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static struct drm_minor **drm_minor_get_slot(struct drm_device *dev,
unsigned int type)
{
switch (type) {
case DRM_MINOR_PRIMARY:
return &dev->primary;
case DRM_MINOR_RENDER:
return &dev->render;
case DRM_MINOR_CONTROL:
return &dev->control;
default:
return NULL;
}
}

4) drm_gem_init

  1. 分配一个 vma_offset_manager 用来管理内存
  2. 初始化起始偏移地址为 DRM_FILE_PAGE_OFFSET_START, 内存大小为 DRM_FILE_PAGE_OFFSET_SIZE 的 vma_offset_manager
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int drm_gem_init(struct drm_device *dev)
{
struct drm_vma_offset_manager *vma_offset_manager;

mutex_init(&dev->object_name_lock);
idr_init_base(&dev->object_name_idr, 1);

// 分配一个 vma_offset_manager 用来管理内存
vma_offset_manager = kzalloc(sizeof(*vma_offset_manager), GFP_KERNEL);
if (!vma_offset_manager) {
DRM_ERROR("out of memory\n");
return -ENOMEM;
}

// 初始化起始偏移地址为 DRM_FILE_PAGE_OFFSET_START, 内存大小为 DRM_FILE_PAGE_OFFSET_SIZE 的 vma_offset_manager
dev->vma_offset_manager = vma_offset_manager;
drm_vma_offset_manager_init(vma_offset_manager,
DRM_FILE_PAGE_OFFSET_START,
DRM_FILE_PAGE_OFFSET_SIZE);

return 0;
}

2.2 drm_dev_register

  1. 注册 dev 下对应的 tpye 的设备, 创建对应设备的设备节点 dev/dri/cardx, dev/dri/controlDx, dev/dri/enderx 创建 /sys/kernel/debug/dri下的调试文件.

  2. 注册 dev 下的 planecrtcconnector 即回调对应的 xxxx->funcs->late_registe回调函数, 如果没设置也不会报错

  3. 注册 connector 设备到 drm_class即创建 /sys/class/drm/card%d(index)-%s(connector->name), 创建默认的属性文件 statusenabledpmsmodes, 回调 connector->funcs->late_registe最后调用 drm_mode_object_register以及设置 connector->registered标志位

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
int drm_dev_register(struct drm_device *dev, unsigned long flags)
{
int ret;

mutex_lock(&drm_global_mutex);

// 注册 DRM_MINOR_CONTROL 设备
ret = drm_minor_register(dev, DRM_MINOR_CONTROL);
if (ret)
goto err_minors;

// 注册 DRM_MINOR_RENDER 设备
ret = drm_minor_register(dev, DRM_MINOR_RENDER);
if (ret)
goto err_minors;

// DRM_MINOR_PRIMARY
ret = drm_minor_register(dev, DRM_MINOR_PRIMARY);
if (ret)
goto err_minors;

// 设置 registered 标志位
dev->registered = true;

// 回调 dev->driver->load 接口
if (dev->driver->load) {
ret = dev->driver->load(dev, flags);
if (ret)
goto err_minors;
}

// 如果支持 DRIVER_MODESET
if (drm_core_check_feature(dev, DRIVER_MODESET))
drm_modeset_register_all(dev);

ret = 0;
goto out_unlock;

err_minors:
drm_minor_unregister(dev, DRM_MINOR_PRIMARY);
drm_minor_unregister(dev, DRM_MINOR_RENDER);
drm_minor_unregister(dev, DRM_MINOR_CONTROL);
out_unlock:
mutex_unlock(&drm_global_mutex);
return ret;
}

1) drm_minor_register

真正的设备注册函数. 注册 dev 下对应的 tpye 的设备, 创建 /sys/kernel/debug/dri 下的调试文件. 没有对应的设备直接返回.

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
static int drm_minor_register(struct drm_device *dev, unsigned int type)
{
struct drm_minor *minor;
unsigned long flags;
int ret;

DRM_DEBUG("\n");

// 获取对应的 type 的 drm_minor
minor = *drm_minor_get_slot(dev, type);
if (!minor)
return 0;

// 创建 /sys/kernel/debug/dri 下的调试文件
ret = drm_debugfs_init(minor, minor->index, drm_debugfs_root);
if (ret) {
DRM_ERROR("DRM: Failed to initialize /sys/kernel/debug/dri.\n");
goto err_debugfs;
}

// 注册设备, 即设备真正注册的地方
ret = device_add(minor->kdev);
if (ret)
goto err_debugfs;

/* replace NULL with @minor so lookups will succeed from now on */
spin_lock_irqsave(&drm_minor_lock, flags);
idr_replace(&drm_minors_idr, minor, minor->index);
spin_unlock_irqrestore(&drm_minor_lock, flags);

DRM_DEBUG("new minor registered %d\n", minor->index);
return 0;

err_debugfs:
drm_debugfs_cleanup(minor);
return ret;
}

2) modeset_register_all

  1. 注册 dev 下的 planecrtc
    encoder 设备并调用对应的 xxxx->funcs->late_registe回调函数
  2. 注册 dev->mode_config.connector_list上所有的 encoder 创建默认属性节点 statusenabledpmsmodes并调用 connector->funcs->late_registe最后调用 drm_mode_object_register以及设置 connector->registered标志位
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
int drm_modeset_register_all(struct drm_device *dev)
{
int ret;

// 遍历 dev下所有的 plane 如果设置了plane->funcs->late_register 则调用
ret = drm_plane_register_all(dev);
if (ret)
goto err_plane;

// 遍历 dev 下所有的 crtc , 并回调 crtc->funcs->late_register();
ret = drm_crtc_register_all(dev);
if (ret)
goto err_crtc;

// 遍历 dev 下所有的 encoder , 并回调 encoder->funcs->late_register();
ret = drm_encoder_register_all(dev);
if (ret)
goto err_encoder;

// 遍历 dev->mode_config.connector_list 上所有的 connector, 如果有未注册的, 则进程注册.
// 1. 注册 connector 设备到 drm_class 即创建 /sys/class/drm/card%d(index)-%s(connector->name)
// 2. 创建默认的属性文件 status、enable、dpms、modes
// 3. 如果设置了 connector->funcs->late_registe 则调用该函数.
ret = drm_connector_register_all(dev);
if (ret)
goto err_connector;

return 0;

err_connector:
drm_encoder_unregister_all(dev);
err_encoder:
drm_crtc_unregister_all(dev);
err_crtc:
drm_plane_unregister_all(dev);
err_plane:
return ret;
}
drm_plane_register_all

遍历 dev 下所有的 plane , 并回调 plane->funcs->late_register();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int drm_plane_register_all(struct drm_device *dev)
{
struct drm_plane *plane;
int ret = 0;

drm_for_each_plane(plane, dev) {
if (plane->funcs->late_register)
ret = plane->funcs->late_register(plane);
if (ret)
return ret;
}

return 0;
}
drm_crtc_register_all

遍历 dev 下所有的 crtc , 并回调 crtc->funcs->late_register();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static int drm_crtc_register_all(struct drm_device *dev)
{
struct drm_crtc *crtc;
int ret = 0;

drm_for_each_crtc(crtc, dev) {
if (crtc->funcs->late_register)
ret = crtc->funcs->late_register(crtc);
if (ret)
return ret;
}

return 0;
}
drm_encoder_register_all

遍历 dev 下所有的 encoder , 并回调 encoder->funcs->late_register();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int drm_encoder_register_all(struct drm_device *dev)
{
struct drm_encoder *encoder;
int ret = 0;

drm_for_each_encoder(encoder, dev) {
if (encoder->funcs->late_register)
ret = encoder->funcs->late_register(encoder);
if (ret)
return ret;
}

return 0;
}
drm_connector_register_all

遍历 dev->mode_config.connector_list 上所有的 connector, 如果有未注册的, 则进程注册.

  1. 注册 connector 设备到 drm_class 即创建 /sys/class/drm/card%d(index)-%s(connector->name)
  2. 创建默认的属性文件 status、enable、dpms、modes
  3. 如果设置了 connector->funcs->late_registe 则调用该函数.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int drm_connector_register_all(struct drm_device *dev)
{
struct drm_connector *connector;
int ret;

/* FIXME: taking the mode config mutex ends up in a clash with
* fbcon/backlight registration */
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
ret = drm_connector_register(connector);
if (ret)
goto err;
}

return 0;

err:
mutex_unlock(&dev->mode_config.mutex);
drm_connector_unregister_all(dev);
return ret;
}
drm_connector_register
  1. 注册 connector 设备到 drm_class 即 /sys/class/drm/card%d(index)-%s(connector->name) 创建默认的属性文件
  • /sys/class/drm/card%d-%s/status
  • /sys/class/drm/card%d-%s/enable
  • /sys/class/drm/card%d-%s/dpms
  • /sys/class/drm/card%d-%s/modes
  1. 创建 dbug 文件
  2. 调用 connector->funcs->late_registe
  3. drm_mode_object_register
  4. 设置 registered 标志位
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
int drm_connector_register(struct drm_connector *connector)
{
int ret = 0;

if (!connector->dev->registered)
return 0;

mutex_lock(&connector->mutex);
if (connector->registered)
goto unlock;

// 注册 connector 设备到 drm_class 即 /sys/class/drm/card%d(index)-%s(connector->name)
// 创建默认的属性文件
// /sys/class/drm/card%d-%s/status
// /sys/class/drm/card%d-%s/enabled
// /sys/class/drm/card%d-%s/dpms
// /sys/class/drm/card%d-%s/modes
ret = drm_sysfs_connector_add(connector);
if (ret)
goto unlock;

// 创建 dbug 文件
ret = drm_debugfs_connector_add(connector);
if (ret) {
goto err_sysfs;
}

// 调用 connector->funcs->late_registe
if (connector->funcs->late_register) {
ret = connector->funcs->late_register(connector);
if (ret)
goto err_debugfs;
}

// drm_mode_object_register
drm_mode_object_register(connector->dev, &connector->base);

connector->registered = true;
goto unlock;

err_debugfs:
drm_debugfs_connector_remove(connector);
err_sysfs:
drm_sysfs_connector_remove(connector);
unlock:
mutex_unlock(&connector->mutex);
return ret;
}
EXPORT_SYMBOL(drm_connector_register);

二、 KMS

KMS(Kernel Mode Setting) 的核心思想就是将图形子系统的各个组件进行拆分和抽象,相对于 fbdev 使得内核能够更灵活、可扩展地管理这些组件。他们被拆分为 PLANE, CRTC, CONNECTOR, ENCODER, FB. 等组件

kms = drm_mode_config + 组件(obj)


元素 说明
CRTC 对显示 buffer 进行扫描,并产生时序信号的硬件模块,通常指 Display Controller
ENCODER 负责将 CRTC 输出的 timing 时序转换成外部设备所需要的信号的模块,如 HDMI 转换器或 DSI Controller
CONNECTOR 连接物理显示设备的连接器,如 HDMI、DisplayPort、DSI 总线,通常和 Encoder 驱动绑定在一起
PLANE 硬件图层,有的 Display 硬件支持多层合成显示,但所有的 Display Controller 至少要有 1 个 plane
FB Framebuffer,单个图层的显示内容,唯一一个和硬件无关的基本元素
VBLANK 软件和硬件的同步机制,RGB 时序中的垂直消影区,软件通常使用硬件 VSYNC 来实现
property 任何你想设置的参数,都可以做成 property,是 DRM 驱动中最灵活、最方便的 Mode setting 机制
DUMB 只支持连续物理内存,基于 kernel 中通用 CMA API 实现,多用于小分辨率简单场景
PRIME 连续、非连续物理内存都支持,基于 DMA-BUF 机制,可以实现 buffer 共享,多用于大内存复杂场景
fence buffer 同步机制,基于内核 dma_fence 机制实现,用于防止显示内容出现异步问题

此处参考最简单的DRM应用程序 (double-buffer)


上述的组件都由 drm_mode_object 描述. 通过 type 来确定 obj 描述的对象类型.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct drm_mode_object {
uint32_t id; // 由 dev->mode_config.crtc_idr 申请过来的 idr, 本质是查找 obj 的索引

/*
#define DRM_MODE_OBJECT_CRTC 0xcccccccc
#define DRM_MODE_OBJECT_CONNECTOR 0xc0c0c0c0
#define DRM_MODE_OBJECT_ENCODER 0xe0e0e0e0
#define DRM_MODE_OBJECT_MODE 0xdededede
#define DRM_MODE_OBJECT_PROPERTY 0xb0b0b0b0
#define DRM_MODE_OBJECT_FB 0xfbfbfbfb
#define DRM_MODE_OBJECT_BLOB 0xbbbbbbbb
#define DRM_MODE_OBJECT_PLANE 0xeeeeeeee
#define DRM_MODE_OBJECT_ANY 0
*/
uint32_t type; // obj 类型, 不同的 type 表示不同的对象.
struct drm_object_properties *properties; // 最多支持 24 个 properties
struct kref refcount;
void (*free_cb)(struct kref *kref); // 释放回调接口
};

从结构体描述可知这些对象主要包括三个部分的内容.

  1. 对象 id 管理, 通过对象的 id, 可以通过 dev->mode_config.crtc_idr 查找到对应的对象.
  2. 属性(paropertise)管理, 对象的属性统一被存放到 properties.
  3. 对象生命周期管理, 释放对象时提供了 free_cb 接口.

内核提供了接口 drm_mode_object_get用初始化 obj. 该函数从 dev->mode_config.crtc_idr上申请一个 dir , 并用这个 idr 作为索引初始化 obj 的 id

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
// dev : 所属的 drm_device
// obj : 要初始化的 obj
// obj_type : obj 是什么对象. 对象包括 CRTC, CONNECTOR, ENCODER, FB, PLANE, PROPERTY 等这些类型
int drm_mode_object_get(struct drm_device *dev,
struct drm_mode_object *obj, uint32_t obj_type)
{
return drm_mode_object_get_reg(dev, obj, obj_type, true, NULL);
}

int drm_mode_object_get_reg(struct drm_device *dev,
struct drm_mode_object *obj,
uint32_t obj_type,
bool register_obj,
void (*obj_free_cb)(struct kref *kref))
{
int ret;

mutex_lock(&dev->mode_config.idr_mutex);
ret = idr_alloc(&dev->mode_config.crtc_idr, register_obj ? obj : NULL, 1, 0, GFP_KERNEL);
if (ret >= 0) {
obj->id = ret; // 设置 id
obj->type = obj_type; // 设置类型
if (obj_free_cb) { // 设置 free_cb
obj->free_cb = obj_free_cb;
kref_init(&obj->refcount);
}
}
mutex_unlock(&dev->mode_config.idr_mutex);

return ret < 0 ? ret : 0;
}

上述 kms 相关的组件 (obj) 通过 drm_mode_config 进行维护和管理. 所以这个数据结构非常重要, 它也是 kms 的核心. 他们的关系如下图所示.

这些组件 (obj) + drm_mode_config 就组成了 kms. 通过 drm_dev 导出的 ioctl 我们就可以使用任意 plane + crtc + encoder + connector + panel 来组合我们需要的硬件显示链路了.例如我们有如下硬件结构

1
2
3
plane0 --> crtc0 --> encoder0(mipi) --> connector0 --> panel0(lcd1)
--> encoder1(hdmi) --> connector1 --> panel1(lcd2)
--> panel2(lcd3)

我们有个 mipi lcd3 想用它显示, 用户可以通过 ioctl 设置我们需要的硬件链路, 十分方便.

1
plane0 --> crtc0 --> encoder0(mipi) --> connector0 --> panel3

三、 gem 内存管理

drm 子系统使用 GEM (Graphic Execution Manager) 负责显示显存的分配和释放,他的整体框架如下图所示(图片是高清的, 如果看不清可以下载下来放大). drm 的内存管理分为几个部分组成.

  1. 显存的跟踪和管理这一块由 drm_vma_offset_manager 中 的 drm_mm 完成, 也就是图中的绿色部分
  2. 显存的分配和创建, 也就是图中的蓝色部分
  3. 显存的使用, 显存的使用分为两个部分, 一个是用户空间读写显存也就是红色部分, 另一个则是紫色部分

1、 drm_mm

drm 使用 drm_mm 来管理内存, 它使用 drm_mm_node 来对内存进行分区. 每一个 drm_mm_node 代表一块连续的内存区域. 这块内存可以被认为是被 “分配”或“占用”的,也可以是空闲的。

1
2
3
4
5
6
struct drm_mm {

struct list_head hole_stack;
struct drm_mm_node head_node; // 内存的分区
......
};

显卡驱动如果需要使用到 drm_mm 来管理内存则需要在一开始就调用 void drm_mm_init(struct drm_mm *mm, u64 start, u64 size);分配好需要管理的内存空间大小, 需要注意的是这里并不会实际分配真正的内存. 只是设置一个内存区域. 这个内存区域就我们要用到的总的显存大小, 如果你需要使用双 buffer , 则 size = buffer_size x 2 . 我们调用 drm_mm_init 初始化之后, 就会为我们创建一个 drm_mm 用来管理显存. 并且初始化一个默认的 drm_mm_node 用来表示整个空间的大小.

1
2
3
4
5
6
7
8
9
10
struct drm_mm_node {
struct list_head node_list;
struct list_head hole_stack;
struct rb_node rb;
......
u64 start; // 保存地址的偏移量, 也就是我们申请的内存的 offset ==> drm_mode_map_dumb->offset
u64 size; // 这块内存的大小
......
struct drm_mm *mm;
};

其中 start 成员变量表示 drm_mm_node 表示的显存的起始偏移地址.也就是我们调用 drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req);时返回的 map_req->offset. 它表示的是内存的相对偏移, 这个 node 对应 drm_mm 中的内存偏移. 他的作用就是索引该 node.

1) 给驱动设置内存

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
#include <linux/module.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_plane_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drmP.h>
#include <drm/drm_mm.h>
#include <drm/drm_vma_manager.h>

#define WIDTH 1920
#define HEIGHT 1080

static struct drm_device *drm;

static const struct file_operations vkms_fops = {
.owner = THIS_MODULE,
.open = drm_open,
.release = drm_release,
.unlocked_ioctl = drm_ioctl,
.poll = drm_poll,
.read = drm_read,
};

static struct drm_driver vkms_driver = {
.fops = &vkms_fops,
.name = "vkms",
.desc = "Virtual Kernel Mode Setting",
.date = "20180514",
.major = 1,
.minor = 0,
};

static int vkms_drm_mm_init(struct drm_device *dev)
{
struct drm_vma_offset_manager *mgr;

mgr = kzalloc(sizeof(*mgr), GFP_KERNEL);

drm->vma_offset_manager = mgr;
drm_mm_init(&mgr->vm_addr_space_mm, 0, WIDTH * HEIGHT * 2);

return 0;
}

static void vkms_drm_mm_cleanup(struct drm_device *dev)
{
kfree(dev->vma_offset_manager);
}

static int __init vkms_init(void)
{

drm = drm_dev_alloc(&vkms_driver, NULL);
vkms_drm_mm_init(drm); // 初始化 drm_mm
drm_dev_register(drm, 0);

return 0;
}

static void __exit vkms_exit(void)
{
drm_dev_unregister(drm);
vkms_drm_mm_cleanup(drm);
drm_dev_unref(drm);
}

module_init(vkms_init);
module_exit(vkms_exit);

MODULE_AUTHOR("baron");
MODULE_DESCRIPTION("drm mm test drv");
MODULE_LICENSE("GPL");

在这段代码中我们初始化了 drm_mm 并设置了我们要管理的显存大小 1920 x 1080 x 2. 这里只是初始化, 因此我们还不能做什么. 但好歹我们有了一块可以分配的显存区域.为我们后续实验做准备.

2、 显存的分配和创建

drm 子系统已经为我们提供了一个默认的函数 drm_gem_cma_dumb_create用来分配和创建显存, 从名字就可以知道他包含三个部分 drm_gem_object、drm_gem_cma_object、dumb buffer(物理显存), 这个函数遵循先分配在创建的规则. 申请和创建的显存由 drm_gem_object 进行统一管理.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

struct drm_gem_cma_object {
struct drm_gem_object base; // 显存管理接口
dma_addr_t paddr; // 显存的物理地址
struct sg_table *sgt; // sg_table 本质上是由一块块单个物理连续的 buffer 所组成的链表,可以描述高端内存上分配出的离散 buffer. 也可以用来描述从低端内存上分配出的物理连续 buffe
void *vaddr; // 显存的虚拟地址
};

struct drm_gem_object {
......
struct drm_device *dev;
struct file *filp; // 该 gem
struct drm_vma_offset_node vma_node; // 管理分配显存描述符
......
struct dma_buf *dma_buf; // dma_buf 用于直接将这块显存进行共享
struct dma_buf_attachment *import_attach;
}

这两个结构体是一体的, 在创建的时候我们创建 drm_gem_cma_object 结构时就会包含 drm_gem_object 接口. 这个技巧可以节省一点内存.其中 drm_gem_cma_object 还包含了我们申请的显存的地址信息. 因此 drm_gem_object 对内存的管理可以分为三个部分:

  1. 分配显存时的显存描述符 drm_vma_offset_node
  2. 分配到的物理内存的地址信息
  3. 提供 dma_buf 接口让显存实现共享.

drm_gem_cma_dumb_create 首先在 drm_mm 中申请一片需要的空间描述符, 然后再创建实际的物理内存. 上图中的蓝色的 drm_mm_node 就是 user 使用 libdrm 分配显存时申请到的内存空间描述符.之后再调用 dma_alloc_wc 分配实际的物理内存到drm_gem_object->drm_gem_cma_object. 具体流程如下所示.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create); --> // 开放给上层的 libdrm 接口
drm_gem_cma_dumb_create() -->
drm_gem_cma_create_with_handle() -->
drm_gem_cma_create() -->
__drm_gem_cma_create() -->
if (drm->driver->gem_create_object) // 回调 gem_create_object 接口创建 gem_obj
gem_obj = drm->driver->gem_create_object(drm, size);
else
gem_obj = kzalloc(sizeof(*cma_obj), GFP_KERNEL); // 创建一个 gem obj
drm_gem_create_mmap_offset() -->
drm_gem_create_mmap_offset_size() -->
drm_vma_offset_add() -->
drm_mm_insert_node() --> // 在 drm_mm 中查找到空闲可用的空间, 然后插入一个 drm_vma_offset_node 表示这个可用的空间.
dma_alloc_wc() // 申请物理内存, 并将内存地址保存到 cma_obj

drm_gem_cma_dumb_create 主要实现了下面功能.

  1. 创建一个 cma_obj , 并且 gem_obj 是其成员变量. 初始化 gem_obj. 支持用户客制化, 用户需要自己实现这个接口 drm->driver->gem_create_object(drm, size);
  2. 在 dr_mm 中查找一段符合长度的内存空间, 并将 gem_obj->vma_node 插入到 drm_mm 并且占有这块空间. 即分配过程.
  3. 拿到这一段空间之后调用 dma_alloc_wc 申请实际的物理空间并将地址信息保存到 cma_obj.
  4. 在 drm_file->object_idr 中申请一个 handle, 用来关联 gem obj. 并返回给用户空间, 用户通过这个 handle 即可找到这个 gem.

1) 给驱动增加分配显存接口

给我们的驱动加上创建和分配的功能即 drm_gem_cma_dumb_create, 为了能找到对应的 node 因此提供 drm_gem_dumb_map_offset , 改接口用于返回 node 对应的 start 偏移.

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
#include <linux/module.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_plane_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drmP.h>
#include <drm/drm_mm.h>
#include <drm/drm_vma_manager.h>

#define WIDTH 1920
#define HEIGHT 1080

static struct drm_device *drm;

static const struct file_operations vkms_fops = {
.owner = THIS_MODULE,
.open = drm_open,
.release = drm_release,
.unlocked_ioctl = drm_ioctl,
.poll = drm_poll,
.read = drm_read,
};

static struct drm_driver vkms_driver = {
.fops = &vkms_fops,
.driver_features = DRIVER_GEM,
.name = "vkms",
.desc = "Virtual Kernel Mode Setting",
.date = "20180514",
.major = 1,
.minor = 0,
.dumb_create = drm_gem_cma_dumb_create, // 在 drm_mm 中申请一个 node , 并分配物理内存
.dumb_map_offset = drm_gem_dumb_map_offset, // 返回 node 中的 start 内存偏移, 即该 node 的索引
};

static int vkms_drm_mm_init(struct drm_device *dev)
{
struct drm_vma_offset_manager *mgr;

mgr = kzalloc(sizeof(*mgr), GFP_KERNEL);

drm->vma_offset_manager = mgr;
drm_mm_init(&mgr->vm_addr_space_mm, 0, WIDTH * HEIGHT * 2);

return 0;
}

static void vkms_drm_mm_cleanup(struct drm_device *dev)
{
kfree(dev->vma_offset_manager);
}

static int __init vkms_init(void)
{

drm = drm_dev_alloc(&vkms_driver, NULL);

vkms_drm_mm_init(drm); // 初始化 drm_mm

drm_dev_register(drm, 0);

return 0;
}

static void __exit vkms_exit(void)
{
drm_dev_unregister(drm);
vkms_drm_mm_cleanup(drm);
drm_dev_unref(drm);
}

module_init(vkms_init);
module_exit(vkms_exit);

MODULE_AUTHOR("baron");
MODULE_DESCRIPTION("drm mm test drv");
MODULE_LICENSE("GPL");

3、用户空间读写显存

经过前面的操作, 我们已经能够分配并且创建显存了, 现在我们需要在用户空间读写显存, 因此需要将显存映射到用户空间, 映射的方式有很多种. drm 子系统默认提供了 drm_gem_cma_mmap 接口来进行映射.

1
2
3
4
5
drm_gem_cma_mmap() --> 
drm_gem_mmap(filp, vma); --> // 从 vma_offset_manager 中查找 vm_pgoff 偏移对应的 node, 通过 node 返回对应的 drm_gem_object, 对 vma 进行一初始化
vma->vm_ops = dev->driver->gem_vm_ops; // drm_gem_mmap 中设置了 vm_ops 的回调, 在我们驱动中也要实现, 不然会 mmap 失败. 博主也不知道为啥, 有知道的同学可以告知一下.
drm_gem_cma_mmap_obj(cma_obj, vma); -->
dma_mmap_wc(cma_obj->base.dev->dev, vma, cma_obj->vaddr, cma_obj->paddr, vma->vm_end - vma->vm_start); // 把 cma_obj 中描述的物理内存返映射到用户空间

1) 编程实验

一共增加了两个个接口 drm_gem_cma_mmap、drm_gem_cma_vm_ops

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
#include <linux/module.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_plane_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drmP.h>
#include <drm/drm_mm.h>
#include <drm/drm_vma_manager.h>

#define WIDTH 1920
#define HEIGHT 1080

static struct drm_device *drm;

static const struct file_operations vkms_fops = {
.owner = THIS_MODULE,
.open = drm_open,
.release = drm_release,
.unlocked_ioctl = drm_ioctl,
.poll = drm_poll,
.read = drm_read,
.mmap = drm_gem_cma_mmap, // 实现 mmap 操作
};

static struct drm_driver vkms_driver = {
.fops = &vkms_fops,
.driver_features = DRIVER_GEM,
.name = "vkms",
.desc = "Virtual Kernel Mode Setting",
.date = "20180514",
.major = 1,
.minor = 0,
// 在 drm_gem_cma_mmap 中会设置这个回调函数. 在我们驱动中也要实现,
// 不然会 mmap 失败. 博主也不知道为啥, 有知道的同学可以告知一下.
.gem_vm_ops = &drm_gem_cma_vm_ops,
.dumb_create = drm_gem_cma_dumb_create, // 在 drm_mm 中申请一个 node , 并分配物理内存
.dumb_map_offset = drm_gem_dumb_map_offset, // 返回 node 中的 start 内存偏移, 即该 node 的索引
};

static int vkms_drm_mm_init(struct drm_device *dev)
{
struct drm_vma_offset_manager *mgr;

mgr = kzalloc(sizeof(*mgr), GFP_KERNEL);

drm->vma_offset_manager = mgr;
drm_mm_init(&mgr->vm_addr_space_mm, 0, WIDTH * HEIGHT * 2);

return 0;
}

static void vkms_drm_mm_cleanup(struct drm_device *dev)
{
kfree(dev->vma_offset_manager);
}

static int __init vkms_init(void)
{

drm = drm_dev_alloc(&vkms_driver, NULL);

vkms_drm_mm_init(drm); // 初始化 drm_mm

drm_dev_register(drm, 0);

return 0;
}

static void __exit vkms_exit(void)
{
drm_dev_unregister(drm);
vkms_drm_mm_cleanup(drm);
drm_dev_unref(drm);
}

module_init(vkms_init);
module_exit(vkms_exit);

MODULE_AUTHOR("baron");
MODULE_DESCRIPTION("drm mm test drv");
MODULE_LICENSE("GPL");

用户空间对显存进行读写. 这个程序就是照抄的龙哥的程序 https://blog.csdn.net/hexiaolong2009/article/details/106532966

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
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>

int main(int argc, char **argv)
{
int fd;
char *vaddr;
struct drm_mode_create_dumb create_req = {};
struct drm_mode_destroy_dumb destroy_req = {};
struct drm_mode_map_dumb map_req = {};

fd = open("/dev/dri/card0", O_RDWR);

create_req.bpp = 32;
create_req.width = 240;
create_req.height = 320;


// 在 drm_vma_offset_manager 中分配一个 240 x 320 的显存, 并返回对应 gem obj 的 handle.
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req);
printf("create dumb: handle = %u, pitch = %u, size = %llu\n",
create_req.handle, create_req.pitch, create_req.size);

// 通过 handle 找到 gem, 返回对应 node 的 start 偏移 == map_req.offset
map_req.handle = create_req.handle;
drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req);
printf("get mmap offset 0x%llx\n", map_req.offset);

// 使用这个 offeet 找到对应 node 并映射其描述的内存
vaddr = mmap(0, create_req.size, PROT_WRITE, MAP_SHARED, fd, map_req.offset);
strcpy(vaddr, "This is a dumb buffer!");
munmap(vaddr, create_req.size);

// 使用这个 offeet 找到对应 node 并映射其描述的内存
vaddr = mmap(0, create_req.size, PROT_READ, MAP_SHARED, fd, map_req.offset);
printf("read from mmap: %s\n", vaddr);
munmap(vaddr, create_req.size);

getchar();

destroy_req.handle = create_req.handle;
drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_req);
close(fd);

return 0;
}

运行结果:

1
2
3
4
5
6
7
[root@100ask:/dmabuf/2]# insmod drm_read_gpu.ko // 加载模块 ===> 不要纠结名字, 随便起的
[root@100ask:/dmabuf/2]#
[root@100ask:/dmabuf/2]#
[root@100ask:/dmabuf/2]# ./a.out
create dumb: handle = 1, pitch = 960, size = 307200
get mmap offset 0x0
read from mmap: This is a dumb buffer!

未完待续…