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! 
 
未完待续…