内容和参考

内容

前边学习的内容,有以下几个未解决:

  • 在学习 DRM应用程序进阶-何小龙 的时候,提到了property属性,这个是怎么使用的?
  • libdrm中讲述了 connector, encoder, crtc,plane, framebuffer 在之前都没有提到过?
  • modetest打印硬件资源时,这些资源从哪来的?-a的atomic参数是干什么的?

本文的目的是先扫盲,大概了解下KMS的相关作用以及使用,比较生疏难懂,也是在不停学习中整理和更新,每一遍都学到一些新的知识。

DRM的初始化 中描述了,drm驱动在初始化时,除了调用 drm_dev_alloc + drm_dev_register,还需要初始化mode_config配置

  1. include/uapi/drm/drm_mode.h // 用户空间接口
  2. include/drm/drm_mode_config.h
  3. include/drm/drm_mode_object.h
  4. drivers/gpu/drm/drm_mode_config.c
  5. drivers/gpu/drm/drm_mode_object.c
  6. drivers/gpu/drm/drm_property.c

DRM的初始化: 在前边描述了一个最简单的drm驱动,但实际上啥也干不了,真实使用并不仅仅是 调用了 drm_dev_alloc + drm_dev_register 就可以完成一个显卡驱动的; 比如之前学习libdrm中的 connector,encoder, crtc, framebuffer, plane等 都没有提到过;

Linux DRM Internals 中描述了,必须初始化 struct drm_mode_config 的下列字段:

  • int min_width, min_height; int max_width, max_height; Minimum and maximum width and height of the frame buffers in pixel units.
  • [struct drm_mode_config_funcs](https://www.kernel.org/doc/html/v5.12/gpu/drm-kms.html#c.drm_mode_config_funcs) *funcs; Mode setting functions.

image.png

参考: linux-5.8/drivers/gpu/drm/arm/malidp_drv.c 中 malidp_bind,显卡驱动中都做了哪些事

  1. drm_mode_config_init(drm);
  2. drm->mode_config.min_width = hwdev->min_line_size;
  3. drm->mode_config.min_height = hwdev->min_line_size;
  4. drm->mode_config.max_width = hwdev->max_line_size;
  5. drm->mode_config.max_height = hwdev->max_line_size;
  6. drm->mode_config.funcs = &malidp_mode_config_funcs;
  7. drm->mode_config.helper_private = &malidp_mode_config_helpers;
  8. drm->mode_config.allow_fb_modifiers = true;

所以本节内容主要是: 理清楚drm的Property

参考

KMS输出原理

调试日志

在调试kms的配置时候, 为了看跟多信息,需要打开debug级别
echo 0x1ff > /sys/module/drm/parameters/debug # 打开调试信息
echo 0x0f > /sys/module/drm/parameters/debug # 关闭部分调试信息

  1. /*
  2. * __drm_debug: Enable debug output.
  3. * Bitmask of DRM_UT_x. See include/drm/drm_print.h for details.
  4. */
  5. unsigned int __drm_debug;
  6. EXPORT_SYMBOL(__drm_debug);
  7. MODULE_PARM_DESC(debug, "Enable debug output, where each bit enables a debug category.\n"
  8. "\t\tBit 0 (0x01) will enable CORE messages (drm core code)\n"
  9. "\t\tBit 1 (0x02) will enable DRIVER messages (drm controller code)\n"
  10. "\t\tBit 2 (0x04) will enable KMS messages (modesetting code)\n"
  11. "\t\tBit 3 (0x08) will enable PRIME messages (prime code)\n"
  12. "\t\tBit 4 (0x10) will enable ATOMIC messages (atomic code)\n" // DRM_DEBUG_ATOMIC
  13. "\t\tBit 5 (0x20) will enable VBL messages (vblank code)\n"
  14. "\t\tBit 6 (0x40) Used for verbose atomic state debugging.\n" // 使用后打开状态更新
  15. "\t\tBit 7 (0x80) will enable LEASE messages (leasing code)\n"
  16. "\t\tBit 8 (0x100) will enable DP messages (displayport code)");
  17. module_param_named(debug, __drm_debug, int, 0600);

输出框架

image.png
image.png

图像输入

KMS呈现给用户空间的基本对象结构比较简单:

  • GPU/用户软件 将渲染完的图片放到drm_framebuffer中
  • Framebuffers 将图片feed into planes 中
  • 每个plane(struct drm_plane); plane将Framebuffer给的图片 pixel data 填充到CRTC 来混合 blending

数据输出

  • 数据输出路由的第一步是 encoder(struct drm_encoder); KMS驱动程序的 helper libraries 组件;

    用来封装connector,使得用户不需要关心crtc与connector的链接;用户直接查找crtc和encoder的关系即可;
    一个crtc需要接1-多个encoder

  • 显示链中的最后一个真正的端点是connector(struct drm_connector);

    connector可以有不同的encoders,但是内核驱动程序为每个connector选择要使用的encoder 。

Modeset对象抽象

在学习kms前需要先熟悉下对象和属性, 因为kms中每一个参数都是一个个属性组成的,对这部分不熟悉后期看起来比较吃力,因此先学习下。

什么是对象

看了基础代码,我们知道 drm_device->mode_config.object_idr 中挂了当前drm的所有object,在驱动中 通过 __drm_mode_object_find 函数来查找我们需要的object,但drm中的object是什么?
驱动中通过 drm_mode_object_add 函数创建并给当前drm_device—>mode_config中添加一个对象,根据对象的类型,可以看到 内核中将 CRTC,CONNECTOR, FRAMEBUFFER, Encoder, Plane 都抽象了一个描述:object

  1. #define DRM_MODE_OBJECT_CRTC 0xcccccccc
  2. #define DRM_MODE_OBJECT_CONNECTOR 0xc0c0c0c0
  3. #define DRM_MODE_OBJECT_ENCODER 0xe0e0e0e0
  4. #define DRM_MODE_OBJECT_FB 0xfbfbfbfb
  5. #define DRM_MODE_OBJECT_PLANE 0xeeeeeeee
  6. #define DRM_MODE_OBJECT_MODE 0xdededede
  7. #define DRM_MODE_OBJECT_PROPERTY 0xb0b0b0b0 // 每个属性,自己内部也会分配一个object
  8. #define DRM_MODE_OBJECT_BLOB 0xbbbbbbbb
  9. #define DRM_MODE_OBJECT_ANY 0

在创建plane的时候:

  1. drm_universal_plane_init
  2. drm_mode_object_add(dev, &plane->base, DRM_MODE_OBJECT_PLANE);
  3. idr_alloc(&dev->mode_config.object_idr, register_obj ? obj......

在创建crtc的时候

  1. drm_crtc_init_with_planes
  2. drm_mode_object_add(dev, &crtc->base, DRM_MODE_OBJECT_CRTC);

……

  1. inno@inno-MS-7B89:linux-git$ git grep -n "drm_mode_object_add"
  2. drivers/gpu/drm/drm_connector.c:231: ret = __drm_mode_object_add(dev, &connector->base,DRM_MODE_OBJECT_CONNECTOR...
  3. drivers/gpu/drm/drm_crtc.c:280: ret = drm_mode_object_add(dev, &crtc->base, DRM_MODE_OBJECT_CRTC);
  4. drivers/gpu/drm/drm_crtc_internal.h:140:int __drm_mode_object_add(struct drm_device *dev, struct drm_mode_object *obj,
  5. drivers/gpu/drm/drm_crtc_internal.h:143:int drm_mode_object_add(struct drm_device *dev, struct drm_mode_object *obj,
  6. drivers/gpu/drm/drm_encoder.c:120: ret = drm_mode_object_add(dev, &encoder->base, DRM_MODE_OBJECT_ENCODER);
  7. drivers/gpu/drm/drm_framebuffer.c:858: ret = __drm_mode_object_add(dev, &fb->base, DRM_MODE_OBJECT_FB,
  8. drivers/gpu/drm/drm_mode_object.c:39:int __drm_mode_object_add(struct drm_device *dev, struct drm_mode_object *obj,
  9. drivers/gpu/drm/drm_mode_object.c:68: * drm_mode_object_add - allocate a new modeset identifier
  10. drivers/gpu/drm/drm_mode_object.c:79:int drm_mode_object_add(struct drm_device *dev,
  11. drivers/gpu/drm/drm_mode_object.c:82: return __drm_mode_object_add(dev, obj, obj_type, true, NULL);
  12. drivers/gpu/drm/drm_plane.c:193: ret = drm_mode_object_add(dev, &plane->base, DRM_MODE_OBJECT_PLANE);
  13. drivers/gpu/drm/drm_property.c:122: ret = drm_mode_object_add(dev, &property->base, DRM_MODE_OBJECT_PROPERTY);
  14. drivers/gpu/drm/drm_property.c:581: ret = __drm_mode_object_add(dev, &blob->base, DRM_MODE_OBJECT_BLOB,

对象的描述

image.png

image.png

  • id:这个不用说就是 drm_device->mode_config.object_idr 中挂的ID索引。
  • type:这里只考虑crtc,connector,encoder,fb,plane, 在我们初始化这五大模块时会分配相应的类型。
  • properities:这个字段有些意思, 包含了 有效属性的个数:count,属性的指针, 属性的值。
  • refcount和free_cb 当然就是 drm_mode_object_get()[drm_mode_object_put()](https://www.kernel.org/doc/html/v5.12/gpu/drm-kms.html#c.drm_mode_object_put) 用来获取引用计数,是否对象的函数,但好像 大部分都使用 drm_mode_object_add 接口注册,所以并未使用这两个计数来自动清理,由驱动自己来管理和删除。<br />

所以说 一个属性 可以挂在多个object上,且值不会出现同步问题,因为值绑定在了object域里,而不是放在了属性域里边

drm_mode_object 有以下几个功能:

  • 内核通过 drm_mode_object_find()来查找获取KMS object对象当前结构;
  • 在初始化时,通过drm_object_attach_property() 链接(attached)了属性和值,
  • 由drm_crtc, drm_plane and drm_connector 等使用
  • 支持引用计数,可通过 drm_mode_object_get()drm_mode_object_put() 注:前提要自己实现free_cb接口
  • [drm_object_property_set_value()](https://www.kernel.org/doc/html/v5.12/gpu/drm-kms.html#c.drm_object_property_set_value) 和 [drm_object_property_get_value()](https://www.kernel.org/doc/html/v5.12/gpu/drm-kms.html#c.drm_object_property_get_value) 来设置和获取自己的属性值<br />

image.png

相关接口

  1. // include/drm/drm_mode_object.h
  2. // drivers/gpu/drm/drm_mode_object.c
  3. // include/drm/drm_mode_config.h
  4. // drivers/gpu/drm/drm_mode_config.c
  5. // 用户态接口, drm_mode_object_find 可被用户来根据id,type等字段查找并获取kms object
  6. // id: drm_object的查找id, 用户空间可见
  7. // type: 就是 drm_mode_object 中的type字段, 上边一堆 DRM_MODE_OBJECT_xxx, 懒得话可以写DRM_MODE_OBJECT_ANY
  8. struct drm_mode_object *drm_mode_object_find(struct drm_device *dev,
  9. struct drm_file *file_priv,
  10. uint32_t id, uint32_t type);
  11. => obj = idr_find(&dev->mode_config.object_idr, id); // id 其实 是 idr的查找索引
  12. => kref_get_unless_zero(&obj->refcount); // drm_mode_object_get
  13. drm_mode_object_get() and drm_mode_object_put(); // 这两个没啥好说的,引用和释放
  14. // 前边说过,属性独立存在,kms object 通过 drm_object_attach_property 来挂载一个属性
  15. // * @obj: drm modeset object
  16. // * @property: property to attach
  17. // * @init_val: initial value of the property
  18. void drm_object_attach_property(struct drm_mode_object *obj, struct drm_property *property, uint64_t init_val);
  19. => obj->properties->properties[count] = property; // 指针数组,指向属于域的指针
  20. => obj->properties->values[count] = init_val; // !!! 给property添加初始值,
  21. // 因为属性独立存在且可以挂载多个obj上,所以值需要存在自己的结构中
  22. => obj->properties->count++; // 属性个数++
  23. // 简单说就是从 struct drm_mode_object *obj 中获取属性的值
  24. int drm_object_property_set_value(struct drm_mode_object *obj, struct drm_property *property, uint64_t val);
  25. int drm_object_property_get_value(struct drm_mode_object *obj, struct drm_property *property, uint64_t *val);

KMS的属性property

参考: DRM应用程序进阶-何小龙Linux DRM Internals-kms

采用property机制的好处是:

减少上层应用接口的维护工作量。当开发者有新的功能需要添加时,无需增加新的函数名和IOCTL,只需在底层驱动中新增一个property,然后在自己的应用程序中获取/操作该property的值即可。 增强了参数设置的灵活性。一次IOCTL可以同时设置多个property,减少了user space与kernel space切换的次数,同时最大限度的满足了不同硬件对于参数设置的要求,提高了软件效率。

什么是属性

KMS的属性,其实就是一个参数的描述, 我们在描述一个参数的时候,至少包含了以下信息:
参数 名称、参数的类型(整型,枚举,bool等),参数的值域(我们给参数赋值必须在这个值域范围内)

属性类型

参考: DRM应用程序进阶-何小龙

  1. /* Property flags and type 支持以下几种类型的属性
  2. * !!!这个主要是描述 属性对应值得类型
  3. * DRM_MODE_PROP_RANGE // drm_property_create_range() 创建,unsigned类型,限制值得最大值和最小值
  4. * DRM_MODE_PROP_SIGNED_RANGE // drm_property_create_signed_range() 创建,同上,不过是signed类型
  5. * DRM_MODE_PROP_ENUM // drm_property_create_enum() 创建,枚举类型
  6. * DRM_MODE_PROP_BITMASK // drm_property_create_bitmask() 创建,bitmap类型(其实也是一种枚举类型)
  7. * // 以下几个属性是难点
  8. * DRM_MODE_PROB_OBJECT // drm_property_create_object() 创建,数据为 drm_mode_object ID 的索引
  9. * DRM_MODE_PROP_BLOB // drm_property_create() 创建,自定义长度的内存块, 后续详细描述
  10. // 在DRM的property type中,还有2种特殊的type,它们分别是 IMMUTABLE TYPE 和 ATOMIC TYPE。
  11. // 这两种type的特殊性在于,它们可以和上面任意一种property进行组合使用,用来修饰上面的property。
  12. * DRM_MODE_PROP_ATOMIC // 表示该property只有在drm应用程序(drm client)支持ATOMIC操作时才可见。
  13. * DRM_MODE_PROP_IMMUTABLE // 表示该property为只读,应用程序无法修改它的值,如"IN_FORMATS"。
  14. */

属性的描述

image.png

  • base : 每个drm驱动在创建属性的时候, 也会分配一个 DRM_MODE_OBJECT_PROPERTY 的对象,放在drm_mode_config中。
  • head:将所有属性挂在 drm_mode_config.property_list 上。
  • flags: 就是属性的类型
  • name:创建的属性名称
  • values:每种属性类型的value不一样

    1. DRM_MODE_PROP_RANGE DRM_MODE_PROP_SIGNED_RANGE
    2. property->values[0] = min;
    3. property->values[1] = max;
    4. DRM_MODE_PROP_OBJECT
    5. property->values[0] = type; type为: DRM_MODE_OBJECT_CRTC DRM_MODE_OBJECT_CONNECTOR ......之一
    6. DRM_MODE_PROP_ENUM DRM_MODE_PROP_BITMASK
    7. property->value 是一个数组, 存放 struct drm_prop_enum_list 中提供的type
  • dev: 当前的drm_device

枚举类型和bitmap类型
drm_property_create_enum 函数,会先去创建一个枚举类型的property,然后去根据用户提供的property表:struct drm_prop_enum_list 去 分配空间并赋值。
image.png
参考 drm_mode_create_standard_properties

  1. // 1. 先初始化一个属性描述数组
  2. static const struct drm_prop_enum_list drm_plane_type_enum_list[] = {
  3. { DRM_PLANE_TYPE_OVERLAY, "Overlay" },
  4. { DRM_PLANE_TYPE_PRIMARY, "Primary" },
  5. { DRM_PLANE_TYPE_CURSOR, "Cursor" },
  6. };
  7. drm_property_create_enum(dev, DRM_MODE_PROP_IMMUTABLE,
  8. "type", drm_plane_type_enum_list,
  9. ARRAY_SIZE(drm_plane_type_enum_list));
  10. // 2. 创建并初始化property结构
  11. drm_property_create(dev, flags, name, num_values);
  12. // 3. 给每个枚举分配结构并赋值
  13. for (i = 0; i < num_values; i++)
  14. drm_property_add_enum

相关接口

这里关于blob的后续再看,先学习基础功能;

  1. // include/drm/drm_property.h
  2. // drivers/gpu/drm/drm_property.c
  3. // 判断property的 flags & type
  4. bool drm_property_type_is(struct drm_property *property, uint32_t type);
  5. // 根据id 从 struct drm_device *dev->mode_config.object_idr 中 查找property的属性
  6. // obj = idr_find(&dev->mode_config.object_idr, id);
  7. struct drm_property * drm_property_find(struct drm_device *dev, struct drm_file *file_priv, uint32_t id);
  8. // !!!
  9. // 创建property,并添加到 struct drm_device *dev->mode_config.property_list,.
  10. // 同时__drm_mode_object_add 添加idr到struct drm_device *dev->mode_config.object_idr 中,便于 drm_property_find 查找
  11. // kms调用 drm_object_attach_property 来链接上当前property
  12. // kms调用 drm_mode_config_cleanup=>drm_property_destroy 来是否property
  13. struct drm_property * drm_property_create(struct drm_device *dev, u32 flags, const char *name, int num_values);
  14. struct drm_property * drm_property_create_enum(struct drm_device *dev, u32 flags, const char *name,
  15. const struct drm_prop_enum_list *props, int num_values);
  16. struct drm_property * drm_property_create_bitmask(struct drm_device *dev, u32 flags, const char *name,
  17. const struct drm_prop_enum_list *props, int num_props, uint64_t supported_bits);
  18. struct drm_property * drm_property_create_range(struct drm_device *dev, u32 flags, const char *name, uint64_t min, uint64_t max);
  19. struct drm_property * drm_property_create_signed_range(struct drm_device *dev, u32 flags, const char *name, int64_t min, int64_t max);
  20. struct drm_property * drm_property_create_object(struct drm_device *dev, u32 flags, const char *name, uint32_t type);
  21. struct drm_property * drm_property_create_bool(struct drm_device *dev, u32 flags, const char *name);
  22. // 给枚举添加一个枚举值 以及对应的键值对
  23. int drm_property_add_enum(struct drm_property *property, uint64_t value, const char *name);
  24. // kms调用 drm_mode_config_cleanup=>drm_property_destroy 来是否property
  25. void drm_property_destroy(struct drm_device *dev, struct drm_property *property);
  26. // TBD
  27. struct drm_property_blob * drm_property_create_blob(struct drm_device *dev, size_t length, const void *data);
  28. void drm_property_blob_put(struct drm_property_blob *blob);
  29. struct drm_property_blob * drm_property_blob_get(struct drm_property_blob *blob);
  30. struct drm_property_blob * drm_property_lookup_blob(struct drm_device *dev, uint32_t id);
  31. int drm_property_replace_global_blob(struct drm_device *dev, struct drm_property_blob **replace, size_t length, const void *data,
  32. struct drm_mode_object *obj_holds_id, struct drm_property *prop_holds_id);
  33. bool drm_property_replace_blob(struct drm_property_blob **blob, struct drm_property_blob *new_blob)

内核标准属性

参考: DRM应用程序进阶-何小龙
drm在调用 drm_mode_config_init 初始化 mode_config时,会调用 drm_mode_create_standard_properties 创建一系列的标准属性,可以说是必备的参数
image.png

Linux DRM Internals-kms 中 KMS Properities末尾,提供了一些标准属性,包含:

并提供了 : Existing KMS Properties
这里不一一列举,直接参考内核文档,在用到的时候在来枚举

Atomic下得属性配置

在libdrm得源码中,下边实现区分了atomic和非atomic模式, 用户使用都推荐使用atomic模式来进行参数得配置

用户层接口

其实 驱动支持了 atomic, 无论应用 是否 带有 -a 参数, 最终底层调用都是一致得,不过-a得好处是一次ioctl配置完成了所有参数,减少新接口得注册,且对用户来说原子操作
image.png

atomic设置模式

在执行 drmModeAtomicCommit 时底层支持三种模式:
atomic配置一次属性, 需要经过 atomic_check + atomic_commit,以下是这三种模式得区别

  1. /* page-flip flags are valid, plus: */
  2. #define DRM_MODE_ATOMIC_TEST_ONLY 0x0100 // 仅仅调用atomic_check
  3. #define DRM_MODE_ATOMIC_NONBLOCK 0x0200 // atomic_check + atomic_commit(nonblock)
  4. #define DRM_MODE_ATOMIC_ALLOW_MODESET 0x0400 // atomic_check + atomic_commit(block), modetest 使用这种模式,

drm_atomic_state得分配

在配置过程中,首先看到的是 drm_atomic_state的更替,这个是做什么的

  1. state = drm_atomic_state_alloc(dev);
  2. drm_atomic_set_property // 以Plane为例, connector和crtc类似
  3. drm_atomic_get_plane_state(state, plane);
  4. drm_atomic_plane_set_property(plane,plane_state, file_priv,prop, prop_value);

内核层属性配置流程

drm.drawio

prepare_signaling 和 complete_signaling

KMS核心功能

  1. struct drm_mode_config_funcs {
  2. struct drm_framebuffer *(*fb_create)(struct drm_device *dev,struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd);
  3. const struct drm_format_info *(*get_format_info)(const struct drm_mode_fb_cmd2 *mode_cmd);
  4. void (*output_poll_changed)(struct drm_device *dev);
  5. enum drm_mode_status (*mode_valid)(struct drm_device *dev, const struct drm_display_mode *mode);
  6. int (*atomic_check)(struct drm_device *dev, struct drm_atomic_state *state);
  7. int (*atomic_commit)(struct drm_device *dev,struct drm_atomic_state *state, bool nonblock);
  8. struct drm_atomic_state *(*atomic_state_alloc)(struct drm_device *dev);
  9. void (*atomic_state_clear)(struct drm_atomic_state *state);
  10. void (*atomic_state_free)(struct drm_atomic_state *state);
  11. };

fb_create

在 libdrm 测试中 ,分配drm buffer 使用 DRM_IOCTL_MODE_ADDFB2 进行分配,这部分在内核实现如下:

atomic提交操作

9.Linux KMS-2 - 图12