内容和参考
内容
前边学习的内容,有以下几个未解决:
- 在学习 DRM应用程序进阶-何小龙 的时候,提到了property属性,这个是怎么使用的?
- libdrm中讲述了 connector, encoder, crtc,plane, framebuffer 在之前都没有提到过?
- modetest打印硬件资源时,这些资源从哪来的?-a的atomic参数是干什么的?
本文的目的是先扫盲,大概了解下KMS的相关作用以及使用,比较生疏难懂,也是在不停学习中整理和更新,每一遍都学到一些新的知识。
在 DRM的初始化 中描述了,drm驱动在初始化时,除了调用 drm_dev_alloc + drm_dev_register,还需要初始化mode_config配置:
include/uapi/drm/drm_mode.h // 用户空间接口
include/drm/drm_mode_config.h
include/drm/drm_mode_object.h
drivers/gpu/drm/drm_mode_config.c
drivers/gpu/drm/drm_mode_object.c
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.
参考: linux-5.8/drivers/gpu/drm/arm/malidp_drv.c 中 malidp_bind,显卡驱动中都做了哪些事
drm_mode_config_init(drm);
drm->mode_config.min_width = hwdev->min_line_size;
drm->mode_config.min_height = hwdev->min_line_size;
drm->mode_config.max_width = hwdev->max_line_size;
drm->mode_config.max_height = hwdev->max_line_size;
drm->mode_config.funcs = &malidp_mode_config_funcs;
drm->mode_config.helper_private = &malidp_mode_config_helpers;
drm->mode_config.allow_fb_modifiers = true;
所以本节内容主要是: 理清楚drm的Property
参考
- Atomic mode setting design overview, part 1 和 2 # 建议先看这个,
- 何小龙-DRM 驱动程序开发(VKMS) 和 DRM应用程序进阶Property、atomic-crtc、atomic-plane
- 蜗窝Linux graphic subsytem-1 和 蜗窝Linux graphic subsytem-2
- Linux GPU Driver Developer’s Guide: Linux DRM Internals-kms (主要参考来源)
- Update for Atomic Display Updates
KMS输出原理
调试日志
在调试kms的配置时候, 为了看跟多信息,需要打开debug级别
echo 0x1ff > /sys/module/drm/parameters/debug # 打开调试信息
echo 0x0f > /sys/module/drm/parameters/debug # 关闭部分调试信息
/*
* __drm_debug: Enable debug output.
* Bitmask of DRM_UT_x. See include/drm/drm_print.h for details.
*/
unsigned int __drm_debug;
EXPORT_SYMBOL(__drm_debug);
MODULE_PARM_DESC(debug, "Enable debug output, where each bit enables a debug category.\n"
"\t\tBit 0 (0x01) will enable CORE messages (drm core code)\n"
"\t\tBit 1 (0x02) will enable DRIVER messages (drm controller code)\n"
"\t\tBit 2 (0x04) will enable KMS messages (modesetting code)\n"
"\t\tBit 3 (0x08) will enable PRIME messages (prime code)\n"
"\t\tBit 4 (0x10) will enable ATOMIC messages (atomic code)\n" // DRM_DEBUG_ATOMIC
"\t\tBit 5 (0x20) will enable VBL messages (vblank code)\n"
"\t\tBit 6 (0x40) Used for verbose atomic state debugging.\n" // 使用后打开状态更新
"\t\tBit 7 (0x80) will enable LEASE messages (leasing code)\n"
"\t\tBit 8 (0x100) will enable DP messages (displayport code)");
module_param_named(debug, __drm_debug, int, 0600);
输出框架
图像输入
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。
#define DRM_MODE_OBJECT_CRTC 0xcccccccc
#define DRM_MODE_OBJECT_CONNECTOR 0xc0c0c0c0
#define DRM_MODE_OBJECT_ENCODER 0xe0e0e0e0
#define DRM_MODE_OBJECT_FB 0xfbfbfbfb
#define DRM_MODE_OBJECT_PLANE 0xeeeeeeee
#define DRM_MODE_OBJECT_MODE 0xdededede
#define DRM_MODE_OBJECT_PROPERTY 0xb0b0b0b0 // 每个属性,自己内部也会分配一个object
#define DRM_MODE_OBJECT_BLOB 0xbbbbbbbb
#define DRM_MODE_OBJECT_ANY 0
在创建plane的时候:
drm_universal_plane_init
drm_mode_object_add(dev, &plane->base, DRM_MODE_OBJECT_PLANE);
idr_alloc(&dev->mode_config.object_idr, register_obj ? obj......
在创建crtc的时候
drm_crtc_init_with_planes
drm_mode_object_add(dev, &crtc->base, DRM_MODE_OBJECT_CRTC);
……
inno@inno-MS-7B89:linux-git$ git grep -n "drm_mode_object_add"
drivers/gpu/drm/drm_connector.c:231: ret = __drm_mode_object_add(dev, &connector->base,DRM_MODE_OBJECT_CONNECTOR...
drivers/gpu/drm/drm_crtc.c:280: ret = drm_mode_object_add(dev, &crtc->base, DRM_MODE_OBJECT_CRTC);
drivers/gpu/drm/drm_crtc_internal.h:140:int __drm_mode_object_add(struct drm_device *dev, struct drm_mode_object *obj,
drivers/gpu/drm/drm_crtc_internal.h:143:int drm_mode_object_add(struct drm_device *dev, struct drm_mode_object *obj,
drivers/gpu/drm/drm_encoder.c:120: ret = drm_mode_object_add(dev, &encoder->base, DRM_MODE_OBJECT_ENCODER);
drivers/gpu/drm/drm_framebuffer.c:858: ret = __drm_mode_object_add(dev, &fb->base, DRM_MODE_OBJECT_FB,
drivers/gpu/drm/drm_mode_object.c:39:int __drm_mode_object_add(struct drm_device *dev, struct drm_mode_object *obj,
drivers/gpu/drm/drm_mode_object.c:68: * drm_mode_object_add - allocate a new modeset identifier
drivers/gpu/drm/drm_mode_object.c:79:int drm_mode_object_add(struct drm_device *dev,
drivers/gpu/drm/drm_mode_object.c:82: return __drm_mode_object_add(dev, obj, obj_type, true, NULL);
drivers/gpu/drm/drm_plane.c:193: ret = drm_mode_object_add(dev, &plane->base, DRM_MODE_OBJECT_PLANE);
drivers/gpu/drm/drm_property.c:122: ret = drm_mode_object_add(dev, &property->base, DRM_MODE_OBJECT_PROPERTY);
drivers/gpu/drm/drm_property.c:581: ret = __drm_mode_object_add(dev, &blob->base, DRM_MODE_OBJECT_BLOB,
对象的描述
- KMS object的描述句柄是
drm_mode_object
; - 属性的描述句柄是
drm_property
; - 但这里
drm_property
属性不依赖与object对象,是一个独立的描述; - 但可以通过
drm_object_attach_property()
附加到不同的kms obejct上(drm_mode_object
)
- 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 />
相关接口
// include/drm/drm_mode_object.h
// drivers/gpu/drm/drm_mode_object.c
// include/drm/drm_mode_config.h
// drivers/gpu/drm/drm_mode_config.c
// 用户态接口, drm_mode_object_find 可被用户来根据id,type等字段查找并获取kms object
// id: drm_object的查找id, 用户空间可见
// type: 就是 drm_mode_object 中的type字段, 上边一堆 DRM_MODE_OBJECT_xxx, 懒得话可以写DRM_MODE_OBJECT_ANY
struct drm_mode_object *drm_mode_object_find(struct drm_device *dev,
struct drm_file *file_priv,
uint32_t id, uint32_t type);
=> obj = idr_find(&dev->mode_config.object_idr, id); // id 其实 是 idr的查找索引
=> kref_get_unless_zero(&obj->refcount); // drm_mode_object_get
drm_mode_object_get() and drm_mode_object_put(); // 这两个没啥好说的,引用和释放
// 前边说过,属性独立存在,kms object 通过 drm_object_attach_property 来挂载一个属性
// * @obj: drm modeset object
// * @property: property to attach
// * @init_val: initial value of the property
void drm_object_attach_property(struct drm_mode_object *obj, struct drm_property *property, uint64_t init_val);
=> obj->properties->properties[count] = property; // 指针数组,指向属于域的指针
=> obj->properties->values[count] = init_val; // !!! 给property添加初始值,
// 因为属性独立存在且可以挂载多个obj上,所以值需要存在自己的结构中
=> obj->properties->count++; // 属性个数++
// 简单说就是从 struct drm_mode_object *obj 中获取属性的值
int drm_object_property_set_value(struct drm_mode_object *obj, struct drm_property *property, uint64_t val);
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应用程序进阶-何小龙
/* Property flags and type 支持以下几种类型的属性
* !!!这个主要是描述 属性对应值得类型
* DRM_MODE_PROP_RANGE // drm_property_create_range() 创建,unsigned类型,限制值得最大值和最小值
* DRM_MODE_PROP_SIGNED_RANGE // drm_property_create_signed_range() 创建,同上,不过是signed类型
* DRM_MODE_PROP_ENUM // drm_property_create_enum() 创建,枚举类型
* DRM_MODE_PROP_BITMASK // drm_property_create_bitmask() 创建,bitmap类型(其实也是一种枚举类型)
* // 以下几个属性是难点
* DRM_MODE_PROB_OBJECT // drm_property_create_object() 创建,数据为 drm_mode_object ID 的索引
* DRM_MODE_PROP_BLOB // drm_property_create() 创建,自定义长度的内存块, 后续详细描述
// 在DRM的property type中,还有2种特殊的type,它们分别是 IMMUTABLE TYPE 和 ATOMIC TYPE。
// 这两种type的特殊性在于,它们可以和上面任意一种property进行组合使用,用来修饰上面的property。
* DRM_MODE_PROP_ATOMIC // 表示该property只有在drm应用程序(drm client)支持ATOMIC操作时才可见。
* DRM_MODE_PROP_IMMUTABLE // 表示该property为只读,应用程序无法修改它的值,如"IN_FORMATS"。
*/
属性的描述
- base : 每个drm驱动在创建属性的时候, 也会分配一个 DRM_MODE_OBJECT_PROPERTY 的对象,放在drm_mode_config中。
- head:将所有属性挂在 drm_mode_config.property_list 上。
- flags: 就是属性的类型
- name:创建的属性名称
values:每种属性类型的value不一样
DRM_MODE_PROP_RANGE 和 DRM_MODE_PROP_SIGNED_RANGE
property->values[0] = min;
property->values[1] = max;
DRM_MODE_PROP_OBJECT
property->values[0] = type; type为: DRM_MODE_OBJECT_CRTC, DRM_MODE_OBJECT_CONNECTOR ......之一
DRM_MODE_PROP_ENUM 和 DRM_MODE_PROP_BITMASK
property->value 是一个数组, 存放 struct drm_prop_enum_list 中提供的type
dev: 当前的drm_device
枚举类型和bitmap类型
drm_property_create_enum 函数,会先去创建一个枚举类型的property,然后去根据用户提供的property表:struct drm_prop_enum_list 去 分配空间并赋值。
参考 drm_mode_create_standard_properties:
// 1. 先初始化一个属性描述数组
static const struct drm_prop_enum_list drm_plane_type_enum_list[] = {
{ DRM_PLANE_TYPE_OVERLAY, "Overlay" },
{ DRM_PLANE_TYPE_PRIMARY, "Primary" },
{ DRM_PLANE_TYPE_CURSOR, "Cursor" },
};
drm_property_create_enum(dev, DRM_MODE_PROP_IMMUTABLE,
"type", drm_plane_type_enum_list,
ARRAY_SIZE(drm_plane_type_enum_list));
// 2. 创建并初始化property结构
drm_property_create(dev, flags, name, num_values);
// 3. 给每个枚举分配结构并赋值
for (i = 0; i < num_values; i++)
drm_property_add_enum
相关接口
这里关于blob的后续再看,先学习基础功能;
// include/drm/drm_property.h
// drivers/gpu/drm/drm_property.c
// 判断property的 flags & type
bool drm_property_type_is(struct drm_property *property, uint32_t type);
// 根据id 从 struct drm_device *dev->mode_config.object_idr 中 查找property的属性
// obj = idr_find(&dev->mode_config.object_idr, id);
struct drm_property * drm_property_find(struct drm_device *dev, struct drm_file *file_priv, uint32_t id);
// !!!
// 创建property,并添加到 struct drm_device *dev->mode_config.property_list,.
// 同时__drm_mode_object_add 添加idr到struct drm_device *dev->mode_config.object_idr 中,便于 drm_property_find 查找
// kms调用 drm_object_attach_property 来链接上当前property
// kms调用 drm_mode_config_cleanup=>drm_property_destroy 来是否property
struct drm_property * drm_property_create(struct drm_device *dev, u32 flags, const char *name, int num_values);
struct drm_property * drm_property_create_enum(struct drm_device *dev, u32 flags, const char *name,
const struct drm_prop_enum_list *props, int num_values);
struct drm_property * drm_property_create_bitmask(struct drm_device *dev, u32 flags, const char *name,
const struct drm_prop_enum_list *props, int num_props, uint64_t supported_bits);
struct drm_property * drm_property_create_range(struct drm_device *dev, u32 flags, const char *name, uint64_t min, uint64_t max);
struct drm_property * drm_property_create_signed_range(struct drm_device *dev, u32 flags, const char *name, int64_t min, int64_t max);
struct drm_property * drm_property_create_object(struct drm_device *dev, u32 flags, const char *name, uint32_t type);
struct drm_property * drm_property_create_bool(struct drm_device *dev, u32 flags, const char *name);
// 给枚举添加一个枚举值 以及对应的键值对
int drm_property_add_enum(struct drm_property *property, uint64_t value, const char *name);
// kms调用 drm_mode_config_cleanup=>drm_property_destroy 来是否property
void drm_property_destroy(struct drm_device *dev, struct drm_property *property);
// TBD
struct drm_property_blob * drm_property_create_blob(struct drm_device *dev, size_t length, const void *data);
void drm_property_blob_put(struct drm_property_blob *blob);
struct drm_property_blob * drm_property_blob_get(struct drm_property_blob *blob);
struct drm_property_blob * drm_property_lookup_blob(struct drm_device *dev, uint32_t id);
int drm_property_replace_global_blob(struct drm_device *dev, struct drm_property_blob **replace, size_t length, const void *data,
struct drm_mode_object *obj_holds_id, struct drm_property *prop_holds_id);
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 创建一系列的标准属性,可以说是必备的参数
在 Linux DRM Internals-kms 中 KMS Properities末尾,提供了一些标准属性,包含:
- Standard Connector Properties
- HDMI Specific Connector Properties
- Standard CRTC Properties
- Standard Plane Properties
- Plane Composition Properties
- Damage Tracking Properties
- Color Management Properties
- Tile Group Property
- Explicit Fencing Properties
- Variable Refresh Properties
并提供了 : Existing KMS Properties
这里不一一列举,直接参考内核文档,在用到的时候在来枚举
Atomic下得属性配置
在libdrm得源码中,下边实现区分了atomic和非atomic模式, 用户使用都推荐使用atomic模式来进行参数得配置
用户层接口
其实 驱动支持了 atomic, 无论应用 是否 带有 -a 参数, 最终底层调用都是一致得,不过-a得好处是一次ioctl配置完成了所有参数,减少新接口得注册,且对用户来说原子操作。
atomic设置模式
在执行 drmModeAtomicCommit 时底层支持三种模式:
atomic配置一次属性, 需要经过 atomic_check + atomic_commit,以下是这三种模式得区别
/* page-flip flags are valid, plus: */
#define DRM_MODE_ATOMIC_TEST_ONLY 0x0100 // 仅仅调用atomic_check
#define DRM_MODE_ATOMIC_NONBLOCK 0x0200 // atomic_check + atomic_commit(nonblock)
#define DRM_MODE_ATOMIC_ALLOW_MODESET 0x0400 // atomic_check + atomic_commit(block), modetest 使用这种模式,
drm_atomic_state得分配
在配置过程中,首先看到的是 drm_atomic_state的更替,这个是做什么的
state = drm_atomic_state_alloc(dev);
drm_atomic_set_property // 以Plane为例, connector和crtc类似
drm_atomic_get_plane_state(state, plane);
drm_atomic_plane_set_property(plane,plane_state, file_priv,prop, prop_value);
内核层属性配置流程
prepare_signaling 和 complete_signaling
KMS核心功能
struct drm_mode_config_funcs {
struct drm_framebuffer *(*fb_create)(struct drm_device *dev,struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd);
const struct drm_format_info *(*get_format_info)(const struct drm_mode_fb_cmd2 *mode_cmd);
void (*output_poll_changed)(struct drm_device *dev);
enum drm_mode_status (*mode_valid)(struct drm_device *dev, const struct drm_display_mode *mode);
int (*atomic_check)(struct drm_device *dev, struct drm_atomic_state *state);
int (*atomic_commit)(struct drm_device *dev,struct drm_atomic_state *state, bool nonblock);
struct drm_atomic_state *(*atomic_state_alloc)(struct drm_device *dev);
void (*atomic_state_clear)(struct drm_atomic_state *state);
void (*atomic_state_free)(struct drm_atomic_state *state);
};
fb_create
在 libdrm 测试中 ,分配drm buffer 使用 DRM_IOCTL_MODE_ADDFB2 进行分配,这部分在内核实现如下: