drm 学习记录, 基础知识部分请参考何小龙的drm专题 , 该系列博客非常详细的介绍了drm 的整个框架. 博主也是通过该系列文章入门, 只是有很多地方不是很理解, 于是参考内核源码进行一些补充. 文章内容引用会该专题部分内容.
一、 drm device
drm 子系统的核心数据结构, linux 中的每个 drm 子系统都由一个 drm_device 统一进行描述. drm_device 的核心功能就是提供用户空间和内核中的 kms 进行交互. 他的本质是一个主设备号为 DRM_MAJOR 226
的字符设备, 每一个 drm_minor 都是一个次设备. 总共有三种次设备 primary 、control 、render .
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 ; void *dev_private; struct drm_minor *control ; struct drm_minor *primary ; struct 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; int type; struct device *kdev ; struct drm_device *dev ; struct dentry *debugfs_root ; struct list_head debugfs_list ; struct mutex debugfs_lock ; };
内核提供了两个接口 drm_dev_init
和 drm_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/cardx
、dev/dri/controlx
、dev/dri/enderDx
, 用来和 kms 进行交互, 关系如下所示
当我们使用 open
、ioctl
、mmap
等函数接口最终会回调到 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
1 2 rk3566_rgo:/ # cat /sys/class/drm/card0-DSI-1/modes 720 x1280
2. 源码阅读
2.1 drm_dev_init
该函数用来创建一个 drm_dev. 每一个 drm 系统都需要创建一个 drm_dev 来和 kms 中的 obj 进行交互…
对应成员变量的简单初始化
检测 driver 的 driver_features 标志位是否设置 DRIVER_RENDER , 有则创建对应的设备 dev/dri/enderD(128 - 192)
创建一个 DRM_MINOR_PRIMARY 子设备, 每个 drm_dev 必须有一个默认的 DRM_MINOR_PRIMARY 设备 dev/dri/card(0 - 64)
检测 driver 的 driver_features 标志位是否设置 DRIVER_GEM , 如果设置了则会为我们分配并创建一个默认的起始偏移地址为 DRM_FILE_PAGE_OFFSET_START
内存大小为 DRM_FILE_PAGE_OFFSET_SIZE
的 vma_offset_manager
将父设备名称用作 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); dev->dev = parent; dev->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; } if (drm_core_check_feature(dev, DRIVER_RENDER)) { ret = drm_minor_alloc(dev, DRM_MINOR_RENDER); if (ret) goto err_minors; } 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); 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; } } 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
根据传入的 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; minor->type = type; minor->dev = dev; idr_preload(GFP_KERNEL); spin_lock_irqsave(&drm_minor_lock, flags); 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; minor->index = r; minor->kdev = drm_sysfs_minor_alloc(minor); if (IS_ERR(minor->kdev)) { r = PTR_ERR(minor->kdev); goto err_index; } *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
根据 drm_minor_alloc 申请的设备号创建对应名称的设备,可选的设备如下
DRM_MINOR_PRIMARY ==> card0 --> card64
DRM_MINOR_CONTROL ==> controlD64 --> controlD128
DRM_MINOR_RENDER ==> enderD128 --> enderD192
设置父设备为所属 drm_dev
设置私有数据为对应的 minor
设置设备所属设备类 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; kdev->parent = minor->dev->dev; kdev->release = drm_sysfs_release; dev_set_drvdata(kdev, 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
分配一个 vma_offset_manager 用来管理内存
初始化起始偏移地址为 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 = kzalloc (sizeof (*vma_offset_manager), GFP_KERNEL); if (!vma_offset_manager) { DRM_ERROR ("out of memory\n" ); return -ENOMEM; } 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
注册 dev 下对应的 tpye 的设备, 创建对应设备的设备节点 dev/dri/cardx , dev/dri/controlDx , dev/dri/enderx 创建 /sys/kernel/debug/dri
下的调试文件.
注册 dev 下的 plane 、crtc 、connector 即回调对应的 xxxx->funcs->late_registe
回调函数, 如果没设置也不会报错
注册 connector 设备到 drm_class
即创建 /sys/class/drm/card%d(index)-%s(connector->name)
, 创建默认的属性文件 status
、enable
、dpms
、modes
, 回调 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); ret = drm_minor_register(dev, DRM_MINOR_CONTROL); if (ret) goto err_minors; ret = drm_minor_register(dev, DRM_MINOR_RENDER); if (ret) goto err_minors; ret = drm_minor_register(dev, DRM_MINOR_PRIMARY); if (ret) goto err_minors; dev->registered = true ; if (dev->driver->load) { ret = dev->driver->load(dev, flags); if (ret) goto err_minors; } 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" ); minor = *drm_minor_get_slot(dev, type); if (!minor) return 0 ; 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; 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
注册 dev 下的 plane 、crtc
、encoder 设备并调用对应的 xxxx->funcs->late_registe
回调函数
注册 dev->mode_config.connector_list
上所有的 encoder 创建默认属性节点 status
、enable
、dpms
、modes
并调用 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; ret = drm_plane_register_all(dev); if (ret) goto err_plane; ret = drm_crtc_register_all(dev); if (ret) goto err_crtc; ret = drm_encoder_register_all(dev); if (ret) goto err_encoder; 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, 如果有未注册的, 则进程注册.
注册 connector 设备到 drm_class 即创建 /sys/class/drm/card%d(index)-%s(connector->name)
创建默认的属性文件 status、enable、dpms、modes
如果设置了 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; 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
注册 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
创建 dbug 文件
调用 connector->funcs->late_registe
drm_mode_object_register
设置 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; ret = drm_sysfs_connector_add(connector); if (ret) goto unlock; ret = drm_debugfs_connector_add(connector); if (ret) { goto err_sysfs; } if (connector->funcs->late_register) { ret = connector->funcs->late_register(connector); if (ret) goto err_debugfs; } 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; uint32_t type; struct drm_object_properties *properties ; struct kref refcount ; void (*free_cb)(struct kref *kref); };
从结构体描述可知这些对象主要包括三个部分的内容.
对象 id 管理 , 通过对象的 id, 可以通过 dev->mode_config.crtc_idr 查找到对应的对象.
属性(paropertise)管理 , 对象的属性统一被存放到 properties.
对象生命周期管理 , 释放对象时提供了 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 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; obj->type = obj_type; if (obj_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 的内存管理分为几个部分组成.
显存的跟踪和管理这一块由 drm_vma_offset_manager 中 的 drm_mm 完成, 也就是图中的绿色部分
显存的分配和创建, 也就是图中的蓝色部分
显存的使用, 显存的使用分为两个部分, 一个是用户空间读写显存也就是红色部分, 另一个则是紫色部分
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; 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_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 ; void *vaddr; }; struct drm_gem_object { ...... struct drm_device *dev ; struct file *filp ; struct drm_vma_offset_node vma_node ; ...... struct dma_buf *dma_buf ; struct dma_buf_attachment *import_attach ; }
这两个结构体是一体的, 在创建的时候我们创建 drm_gem_cma_object 结构时就会包含 drm_gem_object 接口. 这个技巧可以节省一点内存.其中 drm_gem_cma_object 还包含了我们申请的显存的地址信息. 因此 drm_gem_object 对内存的管理可以分为三个部分:
分配显存时的显存描述符 drm_vma_offset_node
分配到的物理内存的地址信息
提供 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); --> 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_obj = drm->driver->gem_create_object (drm, size); else gem_obj = kzalloc (sizeof (*cma_obj), GFP_KERNEL); drm_gem_create_mmap_offset () --> drm_gem_create_mmap_offset_size () --> drm_vma_offset_add () --> drm_mm_insert_node () --> dma_alloc_wc ()
drm_gem_cma_dumb_create 主要实现了下面功能.
创建一个 cma_obj , 并且 gem_obj 是其成员变量. 初始化 gem_obj. 支持用户客制化, 用户需要自己实现这个接口 drm->driver->gem_create_object(drm, size);
在 dr_mm 中查找一段符合长度的内存空间, 并将 gem_obj->vma_node 插入到 drm_mm 并且占有这块空间 . 即分配过程.
拿到这一段空间之后调用 dma_alloc_wc 申请实际的物理空间并将地址信息保存到 cma_obj.
在 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, .dumb_map_offset = drm_gem_dumb_map_offset, }; 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_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->vm_ops = dev->driver->gem_vm_ops; 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);
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, }; 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 , .gem_vm_ops = &drm_gem_cma_vm_ops, .dumb_create = drm_gem_cma_dumb_create, .dumb_map_offset = drm_gem_dumb_map_offset, }; 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_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 ; 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); 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); 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); 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@100 ask:/dmabuf/2 ]# insmod drm_read_gpu.ko [root@100 ask:/dmabuf/2 ]# [root@100 ask:/dmabuf/2 ]# [root@100 ask:/dmabuf/2 ]# ./a.out create dumb: handle = 1 , pitch = 960 , size = 307200 get mmap offset 0x0 read from mmap: This is a dumb buffer!
未完待续…