drm 驱动系列- 第一章 drm_device

2024-01-28 08:20

本文主要是介绍drm 驱动系列- 第一章 drm_device,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 1. 注册 drm_dev
      • 1.1 connector 属性文件
    • 2. 源码阅读
      • 2.1 drm_dev_init
        • 1) drm_minor_alloc
        • 2) drm_sysfs_minor_alloc
        • 3) drm_minor_get_slot
        • 4) drm_gem_init
      • 2.2 drm_dev_register
        • 1) drm_minor_register
        • 2) modeset_register_all
          • drm_plane_register_all
          • drm_crtc_register_all
          • drm_encoder_register_all
          • drm_connector_register_all
          • drm_connector_register

作者: baron

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

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

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

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

  • DRM_MINOR_PRIMARY 对应主设备
  • DRM_MINOR_CONTROL 对应控制设备
  • DRM_MINOR_RENDER 对应管理设备
struct drm_minor {int index;                 // 次设备号, 用来标识该设备. 它由 drm_minors_idr 维护. int type;                  // 表示该次设备的设备类型, DRM_MINOR_PRIMARY, DRM_MINOR_CONTROL, DRM_MINOR_RENDERstruct device *kdev;       // 设备模型中的设备结构struct drm_device *dev;    // 指向所属的 drm 设备struct dentry *debugfs_root;   // 指向调试文件系统struct list_head debugfs_list; // 用于将次设备的 DebugFS 目录添加到全局列表中的链表头。struct mutex debugfs_lock;     // 互斥锁
};

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

static struct idr drm_minors_idr; 

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

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

1. 注册 drm_dev

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

请添加图片描述

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

在这里插入图片描述

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

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

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” 用来描述显示器的连接状态. 实例:
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
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”, 表示显示器关闭电源.
rk3566_rgo:/ # cat /sys/class/drm/card0-DSI-1/dpms // 唤醒时
Off
rk3566_rgo:/ # cat /sys/class/drm/card0-DSI-1/dpms // 休眠时
On
  • modes 节点: 用来返回显示分辨率
rk3566_rgo:/ # cat /sys/class/drm/card0-DSI-1/modes
720x1280

2. 源码阅读

2.1 drm_dev_init

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

  1. 对应成员变量的简单初始化
  2. 检测 driver 的 driver_features 标志位是否设置 DRIVER_RENDER , 有则创建对应的设备 dev/dri/enderD(128 - 192)
  3. 创建一个 DRM_MINOR_PRIMARY 子设备, 每个 drm_dev 必须有一个默认的 DRM_MINOR_PRIMARY 设备 dev/dri/card(0 - 64)
  4. 检测 driver 的 driver_features 标志位是否设置 DRIVER_GEM , 如果设置了则会为我们分配并创建一个默认的起始偏移地址为 DRM_FILE_PAGE_OFFSET_START内存大小为 DRM_FILE_PAGE_OFFSET_SIZE的 vma_offset_manager
  5. 将父设备名称用作 DRM 设备的唯一标识符 unique(drm dev的成员变量),没有父设备则使用驱动程序名称作为 unique 唯一标识符.
  • 小贴士: 这个接口仅仅创建并初始设备化并不会注册
int drm_dev_init(struct drm_device *dev,struct drm_driver *driver,struct device *parent)
{int ret;if (!drm_core_init_complete) {DRM_ERROR("DRM core is not initialized\n");return -ENODEV;}kref_init(&dev->ref);    // 设置 kref 引用计数dev->dev = parent;       // 设置父设备dev->driver = driver;    // 设置 driverINIT_LIST_HEAD(&dev->filelist);  /* 一系列链表的初始化 */ INIT_LIST_HEAD(&dev->ctxlist);INIT_LIST_HEAD(&dev->vmalist);INIT_LIST_HEAD(&dev->maplist);INIT_LIST_HEAD(&dev->vblank_event_list);spin_lock_init(&dev->buf_lock);    /* 一系列锁的初始化 */spin_lock_init(&dev->event_lock);mutex_init(&dev->struct_mutex);mutex_init(&dev->filelist_mutex);mutex_init(&dev->ctxlist_mutex);mutex_init(&dev->master_mutex);dev->anon_inode = drm_fs_inode_new();if (IS_ERR(dev->anon_inode)) {ret = PTR_ERR(dev->anon_inode);DRM_ERROR("Cannot allocate anonymous inode: %d\n", ret);goto err_free;}// 检测 driver 的 driver_features 标志位是否设置 DRIVER_RENDER// 如果设置了调用 drm_minor_alloc 创建一个 DRIVER_RENDER 设备// dev/dri/enderD128 - dev/dri/enderD192if (drm_core_check_feature(dev, DRIVER_RENDER)) {ret = drm_minor_alloc(dev, DRM_MINOR_RENDER);if (ret)goto err_minors;}// 默认创建一个 DRM_MINOR_PRIMARY 子设备ret = drm_minor_alloc(dev, DRM_MINOR_PRIMARY);if (ret)goto err_minors;// 暂时不知道干啥的ret = drm_ht_create(&dev->map_hash, 12);if (ret)goto err_minors;drm_legacy_ctxbitmap_init(dev);// 如果驱动支持 DRIVER_GEM ,做 gem 相关初始化if (drm_core_check_feature(dev, DRIVER_GEM)) {ret = drm_gem_init(dev);if (ret) {DRM_ERROR("Cannot initialize graphics execution manager (GEM)\n");goto err_ctxbitmap;}}// 将父设备名称用作 DRM 设备的唯一标识符,但对于虚拟设备(例如 vgem),则使用驱动程序名称作为后备标识符ret = drm_dev_set_unique(dev, parent ? dev_name(parent) : driver->name);if (ret)goto err_setunique;return 0;err_setunique:if (drm_core_check_feature(dev, DRIVER_GEM))drm_gem_destroy(dev);
err_ctxbitmap:drm_legacy_ctxbitmap_cleanup(dev);drm_ht_remove(&dev->map_hash);
err_minors:drm_minor_free(dev, DRM_MINOR_PRIMARY);drm_minor_free(dev, DRM_MINOR_RENDER);drm_minor_free(dev, DRM_MINOR_CONTROL);drm_fs_inode_free(dev->anon_inode);
err_free:mutex_destroy(&dev->master_mutex);mutex_destroy(&dev->ctxlist_mutex);mutex_destroy(&dev->filelist_mutex);mutex_destroy(&dev->struct_mutex);return ret;
}
EXPORT_SYMBOL(drm_dev_init);
1) drm_minor_alloc
  1. 根据传入的 type 在 drm_minors_idr 链表中申请一个可用的 id, 并且使用这个 id 作为次设备号, 创建对应的 minor 设备.
  • DRM_MINOR_PRIMARY id 范围 0 - 64 ==> dev/dri/card0 - dev/dri/card64
  • DRM_MINOR_CONTROL id 范围 64 - 128 ==> dev/dri/controlD64 - dev/dri/controlD128
  • DRM_MINOR_RENDER id 范围 128 - 192 ==> dev/dri/enderD128 - dev/dri/enderD192
static int drm_minor_alloc(struct drm_device *dev, unsigned int type)
{struct drm_minor *minor;unsigned long flags;int r;minor = kzalloc(sizeof(*minor), GFP_KERNEL);if (!minor)return -ENOMEM;/** 总共有 3 种 type*   enum drm_minor_type {*       DRM_MINOR_PRIMARY, id 范围 0  - 64,  , 对应的设备节点 dev/dri/card0 - dev/dri/card64*       DRM_MINOR_CONTROL, id 范围 64 - 128  , 对应的设备节点 dev/dri/controlD64 - dev/dri/controlD128*       DRM_MINOR_RENDER,  id 范围 128 - 192 , 对应的设备节点 dev/dri/enderD128 - dev/dri/enderD192*   };*/minor->type = type; // 设置 type minor->dev = dev;   // 设置所属的 drm_devidr_preload(GFP_KERNEL);spin_lock_irqsave(&drm_minor_lock, flags);// 根据设备类型,申请一个空 id 用来做此设备的次设备号,从这里可以得到 // DRM_MINOR_PRIMARY id 范围 0 - 64// DRM_MINOR_CONTROL id 范围 64 - 128// DRM_MINOR_RENDER  id 范围 128 - 192r = idr_alloc(&drm_minors_idr,NULL,64 * type,64 * (type + 1),GFP_NOWAIT);spin_unlock_irqrestore(&drm_minor_lock, flags);idr_preload_end();if (r < 0)goto err_free;// 设置申请到的 id 也是次设备号minor->index = r;// 根据前面申请到的次设备号, 为 minor 创建一个对应类型的 dev ,并初始化.minor->kdev = drm_sysfs_minor_alloc(minor);if (IS_ERR(minor->kdev)) {r = PTR_ERR(minor->kdev);goto err_index;}// 设置根据类型 type 将 minor 挂接到对应的位置.*drm_minor_get_slot(dev, type) = minor;return 0;err_index:spin_lock_irqsave(&drm_minor_lock, flags);idr_remove(&drm_minors_idr, minor->index);spin_unlock_irqrestore(&drm_minor_lock, flags);
err_free:kfree(minor);return r;
}
2) drm_sysfs_minor_alloc
  1. 根据 drm_minor_alloc 申请的设备号创建对应名称的设备,可选的设备如下
  • DRM_MINOR_PRIMARY ==> card0 --> card64
  • DRM_MINOR_CONTROL ==> controlD64 --> controlD128
  • DRM_MINOR_RENDER ==> enderD128 --> enderD192
  1. 设置父设备为所属 drm_dev
  2. 设置私有数据为对应的 minor
  3. 设置设备所属设备类 drm_class ==> “/sys/class/drm”
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";elseminor_str = "card%d";// 创建设备结构kdev = kzalloc(sizeof(*kdev), GFP_KERNEL);if (!kdev)return ERR_PTR(-ENOMEM);// 设备初始化device_initialize(kdev);kdev->devt = MKDEV(DRM_MAJOR, minor->index); // 设置设备号kdev->class = drm_class;                     // 设置设备所属设备类kdev->type = &drm_sysfs_device_minor;        // 设置设备类型 drm_minorkdev->parent = minor->dev->dev;              // 设置父设备为所属 drm_devkdev->release = drm_sysfs_release;           // 设置 release 回调函数dev_set_drvdata(kdev, minor);                // 设置私有数据为 minor// 设置设备名称r = dev_set_name(kdev, minor_str, minor->index);if (r < 0)goto err_free;return kdev;err_free:put_device(kdev);return ERR_PTR(r);
}
3) drm_minor_get_slot

返回 type 对应的 dev 下的设备

static struct drm_minor **drm_minor_get_slot(struct drm_device *dev,unsigned int type)
{switch (type) {case DRM_MINOR_PRIMARY:return &dev->primary;case DRM_MINOR_RENDER:return &dev->render;case DRM_MINOR_CONTROL:return &dev->control;default:return NULL;}
}
4) drm_gem_init
  1. 分配一个 vma_offset_manager 用来管理内存
  2. 初始化起始偏移地址为 DRM_FILE_PAGE_OFFSET_START, 内存大小为 DRM_FILE_PAGE_OFFSET_SIZE 的 vma_offset_manager
int drm_gem_init(struct drm_device *dev)
{struct drm_vma_offset_manager *vma_offset_manager;mutex_init(&dev->object_name_lock);idr_init_base(&dev->object_name_idr, 1);// 分配一个 vma_offset_manager 用来管理内存vma_offset_manager = kzalloc(sizeof(*vma_offset_manager), GFP_KERNEL);if (!vma_offset_manager) {DRM_ERROR("out of memory\n");return -ENOMEM;}// 初始化起始偏移地址为 DRM_FILE_PAGE_OFFSET_START, 内存大小为 DRM_FILE_PAGE_OFFSET_SIZE 的 vma_offset_managerdev->vma_offset_manager = vma_offset_manager;drm_vma_offset_manager_init(vma_offset_manager,DRM_FILE_PAGE_OFFSET_START,DRM_FILE_PAGE_OFFSET_SIZE);return 0;
}

2.2 drm_dev_register

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

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

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

int drm_dev_register(struct drm_device *dev, unsigned long flags)
{int ret;mutex_lock(&drm_global_mutex);// 注册 DRM_MINOR_CONTROL 设备ret = drm_minor_register(dev, DRM_MINOR_CONTROL);if (ret)goto err_minors;// 注册 DRM_MINOR_RENDER 设备ret = drm_minor_register(dev, DRM_MINOR_RENDER);if (ret)goto err_minors;// DRM_MINOR_PRIMARYret = drm_minor_register(dev, DRM_MINOR_PRIMARY);if (ret)goto err_minors;// 设置 registered 标志位dev->registered = true;// 回调 dev->driver->load 接口if (dev->driver->load) {ret = dev->driver->load(dev, flags);if (ret)goto err_minors;}// 如果支持 DRIVER_MODESETif (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 下的调试文件. 没有对应的设备直接返回.

static int drm_minor_register(struct drm_device *dev, unsigned int type)
{struct drm_minor *minor;unsigned long flags;int ret;DRM_DEBUG("\n");// 获取对应的 type 的 drm_minorminor = *drm_minor_get_slot(dev, type);if (!minor)return 0;// 创建 /sys/kernel/debug/dri 下的调试文件ret = drm_debugfs_init(minor, minor->index, drm_debugfs_root);if (ret) {DRM_ERROR("DRM: Failed to initialize /sys/kernel/debug/dri.\n");goto err_debugfs;}// 注册设备, 即设备真正注册的地方ret = device_add(minor->kdev);if (ret)goto err_debugfs;/* replace NULL with @minor so lookups will succeed from now on */spin_lock_irqsave(&drm_minor_lock, flags);idr_replace(&drm_minors_idr, minor, minor->index);spin_unlock_irqrestore(&drm_minor_lock, flags);DRM_DEBUG("new minor registered %d\n", minor->index);return 0;err_debugfs:drm_debugfs_cleanup(minor);return ret;
}
2) modeset_register_all
  1. 注册 dev 下的 planecrtc
    encoder 设备并调用对应的 xxxx->funcs->late_registe回调函数
  2. 注册 dev->mode_config.connector_list上所有的 encoder 创建默认属性节点 statusenabledpmsmodes并调用 connector->funcs->late_registe最后调用 drm_mode_object_register以及设置 connector->registered标志位
int drm_modeset_register_all(struct drm_device *dev)
{int ret;// 遍历 dev下所有的 plane 如果设置了plane->funcs->late_register 则调用ret = drm_plane_register_all(dev);if (ret)goto err_plane;// 遍历 dev 下所有的 crtc , 并回调 crtc->funcs->late_register();ret = drm_crtc_register_all(dev);if  (ret)goto err_crtc;// 遍历 dev 下所有的 encoder , 并回调 encoder->funcs->late_register();ret = drm_encoder_register_all(dev);if (ret)goto err_encoder;// 遍历 dev->mode_config.connector_list 上所有的 connector, 如果有未注册的, 则进程注册.// 1.  注册 connector 设备到 drm_class 即创建 /sys/class/drm/card%d(index)-%s(connector->name) // 2.  创建默认的属性文件 status、enable、dpms、modes// 3.  如果设置了 connector->funcs->late_registe 则调用该函数. ret = drm_connector_register_all(dev);if (ret)goto err_connector;return 0;err_connector:drm_encoder_unregister_all(dev);
err_encoder:drm_crtc_unregister_all(dev);
err_crtc:drm_plane_unregister_all(dev);
err_plane:return ret;
}
drm_plane_register_all

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

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();

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();

int drm_encoder_register_all(struct drm_device *dev)
{struct drm_encoder *encoder;int ret = 0;drm_for_each_encoder(encoder, dev) {if (encoder->funcs->late_register)ret = encoder->funcs->late_register(encoder);if (ret)return ret;}return 0;
}
drm_connector_register_all

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

  1. 注册 connector 设备到 drm_class 即创建 /sys/class/drm/card%d(index)-%s(connector->name)
  2. 创建默认的属性文件 status、enable、dpms、modes
  3. 如果设置了 connector->funcs->late_registe 则调用该函数.
int drm_connector_register_all(struct drm_device *dev)
{struct drm_connector *connector;int ret;/* FIXME: taking the mode config mutex ends up in a clash with* fbcon/backlight registration */list_for_each_entry(connector, &dev->mode_config.connector_list, head) {ret = drm_connector_register(connector);if (ret)goto err;}return 0;err:mutex_unlock(&dev->mode_config.mutex);drm_connector_unregister_all(dev);return ret;
}
drm_connector_register
  1. 注册 connector 设备到 drm_class 即 /sys/class/drm/card%d(index)-%s(connector->name) 创建默认的属性文件
  • /sys/class/drm/card%d-%s/status
  • /sys/class/drm/card%d-%s/enable
  • /sys/class/drm/card%d-%s/dpms
  • /sys/class/drm/card%d-%s/modes
  1. 创建 dbug 文件
  2. 调用 connector->funcs->late_registe
  3. drm_mode_object_register
  4. 设置 registered 标志位
int drm_connector_register(struct drm_connector *connector)
{int ret = 0;if (!connector->dev->registered)return 0;mutex_lock(&connector->mutex);if (connector->registered)goto unlock;// 注册 connector 设备到 drm_class 即 /sys/class/drm/card%d(index)-%s(connector->name)// 创建默认的属性文件// /sys/class/drm/card%d-%s/status// /sys/class/drm/card%d-%s/enabled// /sys/class/drm/card%d-%s/dpms// /sys/class/drm/card%d-%s/modesret = drm_sysfs_connector_add(connector);if (ret)goto unlock;// 创建 dbug 文件ret = drm_debugfs_connector_add(connector);if (ret) {goto err_sysfs;}// 调用 connector->funcs->late_registeif (connector->funcs->late_register) {ret = connector->funcs->late_register(connector);if (ret)goto err_debugfs;}// drm_mode_object_registerdrm_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);

这篇关于drm 驱动系列- 第一章 drm_device的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/653044

相关文章

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

flume系列之:查看flume系统日志、查看统计flume日志类型、查看flume日志

遍历指定目录下多个文件查找指定内容 服务器系统日志会记录flume相关日志 cat /var/log/messages |grep -i oom 查找系统日志中关于flume的指定日志 import osdef search_string_in_files(directory, search_string):count = 0

GPT系列之:GPT-1,GPT-2,GPT-3详细解读

一、GPT1 论文:Improving Language Understanding by Generative Pre-Training 链接:https://cdn.openai.com/research-covers/languageunsupervised/language_understanding_paper.pdf 启发点:生成loss和微调loss同时作用,让下游任务来适应预训

Java基础回顾系列-第七天-高级编程之IO

Java基础回顾系列-第七天-高级编程之IO 文件操作字节流与字符流OutputStream字节输出流FileOutputStream InputStream字节输入流FileInputStream Writer字符输出流FileWriter Reader字符输入流字节流与字符流的区别转换流InputStreamReaderOutputStreamWriter 文件复制 字符编码内存操作流(

Java基础回顾系列-第五天-高级编程之API类库

Java基础回顾系列-第五天-高级编程之API类库 Java基础类库StringBufferStringBuilderStringCharSequence接口AutoCloseable接口RuntimeSystemCleaner对象克隆 数字操作类Math数学计算类Random随机数生成类BigInteger/BigDecimal大数字操作类 日期操作类DateSimpleDateForma

Java基础回顾系列-第三天-Lambda表达式

Java基础回顾系列-第三天-Lambda表达式 Lambda表达式方法引用引用静态方法引用实例化对象的方法引用特定类型的方法引用构造方法 内建函数式接口Function基础接口DoubleToIntFunction 类型转换接口Consumer消费型函数式接口Supplier供给型函数式接口Predicate断言型函数式接口 Stream API 该篇博文需重点了解:内建函数式

Java基础回顾系列-第二天-面向对象编程

面向对象编程 Java类核心开发结构面向对象封装继承多态 抽象类abstract接口interface抽象类与接口的区别深入分析类与对象内存分析 继承extends重写(Override)与重载(Overload)重写(Override)重载(Overload)重写与重载之间的区别总结 this关键字static关键字static变量static方法static代码块 代码块String类特