内容和参考

内容

学习Linux 中drm驱动编写方法以及drm驱动初始化;

另外,学习了struct drm_driver 的部分字段是如何使用:

  1. struct drm_driver {
  2. int (*load) (struct drm_device *, unsigned long flags); // 不推荐使用
  3. void (*unload) (struct drm_device *); // 不推荐使用
  4. void (*lastclose) (struct drm_device *); // legacy
  5. void (*release) (struct drm_device *); // 不推荐使用
  6. int major;
  7. int minor;
  8. int patchlevel;
  9. char *name;
  10. char *desc;
  11. char *date;
  12. u32 driver_features;
  13. const struct file_operations *fops;
  14. };

参考

drm软件流程

文章都是参考 Linux-5.8学习的内容

  1. linux-5.8\drivers\gpu\drm\drm_drv.c
  2. linux-5.8\drivers\gpu\drm\drm_file.c
  3. linux-5.8\drivers\gpu\drm\drm_ioctl.c
  4. linux-5.8\include\drm\drm_drv.h

drm驱动初始化

驱动初始化和卸载流程非常简单,基本模板如下: 3.驱动层-DRM的初始化 - 图1 最小化代码如下:参考 何小龙-DRM 驱动程序开发(VKMS)Linux DRM Internals

  1. #define pr_fmt(fmt) "[%s:%d] " fmt, __func__, __LINE__
  2. #include <drm/drmP.h>
  3. #include <drm/drm_drv.h>
  4. #include <drm/drm_gem.h>
  5. #include <drm/drm_vblank.h>
  6. static struct drm_device *vkms_dev;
  7. static struct drm_driver vkms_drv = {
  8. .name = "vkms",
  9. .desc = "Virtual Kernel Mode Setting",
  10. .date = "20180514",
  11. };
  12. static int __init vkms_init(void)
  13. {
  14. pr_info("[E]");
  15. vkms_dev = drm_dev_alloc(&vkms_drv, NULL);
  16. if (!vkms_dev) {
  17. pr_err("[vkms] dev alloc failed");
  18. return -ENOMEM;
  19. }
  20. drm_dev_register(vkms_dev, 0);
  21. pr_info("[X]\n");
  22. return 0;
  23. }
  24. static void __exit vkms_exit(void)
  25. {
  26. pr_info("[E]");
  27. if (vkms_dev) {
  28. drm_dev_unregister(vkms_dev);
  29. drm_dev_put(vkms_dev);
  30. }
  31. pr_info("[X]\n");
  32. }
  33. module_init(vkms_init);
  34. module_exit(vkms_exit);
  35. MODULE_LICENSE("GPL v2");
  36. MODULE_INFO(supported, "Test drm driver");

drm_driver

  1. struct drm_driver {
  2. // 属性域
  3. int major;
  4. int minor;
  5. int patchlevel;
  6. char *name;
  7. char *desc;
  8. char *date;
  9. u32 driver_features; // !!! 重点配置:enum drm_driver_feature; DRIVER_GEM | DRIVER_MODESET 必须有把
  10. const struct drm_ioctl_desc *ioctls;
  11. int num_ioctls;
  12. const struct file_operations *fops;
  13. };

drm_device

  1. struct drm_device {
  2. const struct drm_driver *driver; // 在 drm_dev_init 中初始化指向struct drm_driver
  3. };

drm_dev_alloc和drm_dev_release

drivers/gpu/drm/drm_drv.c 3.驱动层-DRM的初始化 - 图2 3.驱动层-DRM的初始化 - 图3这里调用 drm_minor_alloc 会给 struct drm_device dev- > primary/rendor 字段分配 struct drm_minor minor 结构体;

  1. struct drm_minor { // drm_dev_init初始化,根据drm_driver的 driver_features 字段进行分配
  2. int index; // idr_alloc 动态分配一个ID
  3. int type; // DRM_MINOR_RENDER ? DRM_MINOR_PRIMARY ?
  4. struct device *kdev; // !!! drm_minor_register => drm_sysfs_minor_alloc, minor_str=card%d / renderD%d
  5. // !!! 也就是/dev/dri/card%d 和 /dev/dri/renderD%d 后期的由来
  6. struct drm_device *dev; // 指向 struct drm_device
  7. struct dentry *debugfs_root; // drm_minor_register->drm_debugfs_init创建以index为索引的目录
  8. struct list_head debugfs_list;
  9. struct mutex debugfs_lock;
  10. };

drm_dev_register

drivers/gpu/drm/drm_drv.c 3.驱动层-DRM的初始化 - 图4

DRM 框架还为我们做了下面这些事情:

  • 创建设备节点:/dev/dri/card0
  • 创建 sysfs 节点:/sys/class/drm/card0
  • 创建 debugfs 节点:/sys/kernel/debug/dri/0

其中 drm_minor_register 会在 /sys/kernel/debug/dri/ 下注册 以 struct drm_minor中index字段的调试接口

  1. root@baiy:/sys/kernel/debug/dri# ls
  2. 0 128

此时,系统启动中有以下打印:

  1. [ 2.573455] [drm] Initialized vmwgfx 2.14.0 20170612 for 0000:00:0f.0 on minor 0
  2. driver->name : vmwgfx
  3. driver->major : 2
  4. driver->minor : 14
  5. driver->patchlevel : 0
  6. driver->date : 20170612
  7. struct drm_device dev->dev ? dev_name(dev->dev) : "virtual device"
  8. struct drm_device dev->primary->index : 0


drm_dev_unregister

drivers/gpu/drm/drm_drv.c 3.驱动层-DRM的初始化 - 图5注:因为我们写新的驱动不可能在支持 legacy特性,所以这里不过多描述,lastclose只是注明下

drm_dev_release

drivers/gpu/drm/drm_drv.c 3.驱动层-DRM的初始化 - 图6这里的kfree释放设计比较有意思,其实就是释放了自己:

  1. drm_dev_alloc
  2. => drmm_add_final_kfree
  3. => dev->managed.final_kfree = container; == struct drm_device *dev;
  4. drm_dev_release
  5. => kfree(dev->managed.final_kfree); == kfree(struct drm_device);

初始化过程疑惑(重点)

到底是card还是render?

在 drm_dev_init 中

ls -al /dev/dri/ 有时是cardx,有时是rendorx,这两个有啥区别?
蜗窝Linux graphic subsytem-2 中有人提到了这个问题,

首先在 drm_dev_init 中 ,drm_minor_register => drm_sysfs_minor_alloc 给初始化了 struct drm_minor 的 struct device *kdev;名称
然后在 drm_dev_register中,device_add添加了设备,所以有 /dev/dri/cardx和 、/dev/dri/rendorx

struct drm_driver drv->driver_feature 中 只要有 DRIVER_RENDER属性,就会在 drm_minor_alloc => drm_sysfs_minor_alloc* 会创建一个 renderD%d
另外,必须要有 PRIMARY,所以 drm_minor_alloc(dev, DRM_MINOR_PRIMARY); 也会创建一个 card%d
其中 %d就是minor从idr分配的一个数字索引;

/dev/dri/card0 /dev/dri/renderXX 这俩个和/dev/fb 是什么样的千丝万缕的关系. drm会模拟fb,请问这句话是什么意思?

/dev/dri/card0 显示管理+显示合成(fb)+3D加速 /dev/dri/renderXX 仅仅包括3D加速 drm会模拟fb,/dev/dri/card0在fb功能的使用上跟framebuffer没什么太大区别 card0与renderXX一一对应,可以认为renderXX只是card0的一个子集,renderXX多数情况下用作离线渲染,渲染结果可以交由cardX显示(一般用gbm框架传递数据)。cardX也可以做离线渲染,单独给出一个renderXX的意义目前我也搞不清楚。

drm_driver中的major等字段

何小龙-DRM 驱动程序开发(VKMS)提供的最简化驱动中有以下字段

  1. static struct drm_driver vkms_driver = {
  2. .name = "vkms",
  3. .desc = "Virtual Kernel Mode Setting",
  4. .date = "20180514",
  5. .major = 1,
  6. .minor = 0,
  7. };

那么 major,minor, name, desc等作用是啥? 难道只是打印下?
其实在 Linux DRM Internals 中说的很清楚了:
其实这几个合起来表明版本信息,在 系统启动时打印,在
调用DRM_IOCTL_VERSION 获取版本描述**

  1. static const struct drm_ioctl_desc drm_ioctls[] = {
  2. DRM_IOCTL_DEF(DRM_IOCTL_VERSION, drm_version, DRM_RENDER_ALLOW), // drm_version会给用户态传递
  3. ......
  4. }
  5. struct drm_version {
  6. int version_major; /**< Major version */
  7. int version_minor; /**< Minor version */
  8. int version_patchlevel; /**< Patch level */
  9. __kernel_size_t name_len; /**< Length of name buffer */
  10. char __user *name; /**< Name of driver */
  11. __kernel_size_t date_len; /**< Length of date buffer */
  12. char __user *date; /**< User-space buffer to hold date */
  13. __kernel_size_t desc_len; /**< Length of desc buffer */
  14. char __user *desc; /**< User-space buffer to hold desc */
  15. };

libdrm的ioctrl是怎么掉的?

参考: drm_drv初始化,实际上调用的是 struct drm_driver *driver->fops;

drm_drv初始化

前边先看了 添加drm设备 drm_xxx 的初始化,但有以下疑问?

  • 系统在初始化前给我们做了哪些工作?
  • libdrm调用的ioctrl接口仍然不见?
  • kms这个参数控制仍然没找到?

初始化接口

  1. drm_core_init
  2. register_chrdev(DRM_MAJOR, "drm", &drm_stub_fops); // drm 字符设备驱动 /proc/devices => 226 drm
  3. drm_sysfs_init(); // 创建 /sys/class/drm

用户态接口

  1. drm_stub_fops
  2. .open = drm_stub_open,
  3. .llseek = noop_llseek,
  4. drm_stub_open
  5. struct drm_minor *minor = drm_minor_acquire(iminor(inode));
  6. const struct file_operations *new_fops = fops_get(minor->dev->driver->fops); // 获取drm_driver->ops
  7. replace_fops(filp, new_fops); // filp->ops = drm_driver->ops;
  8. filp->f_op->open(inode, filp); // drm_driver->ops->open();
  9. drm_minor_release(minor);

所以 drm里边的 file->fops在打开时会使用drm_driver->fops来替换;

  1. static const struct file_operations vkms_fops = {
  2. .owner = THIS_MODULE,
  3. .open = drm_open,
  4. .release = drm_release,
  5. .unlocked_ioctl = drm_ioctl,
  6. .poll = drm_poll,
  7. .read = drm_read,
  8. };
  9. static struct drm_driver vkms_driver = {
  10. .fops = &vkms_fops, // drm_stub_open 会替换file->fops = vkms_fops
  11. .name = "vkms",
  12. .desc = "Virtual Kernel Mode Setting",
  13. .date = "20180514",
  14. .major = 1,
  15. .minor = 0,
  16. };

为什么minor可以根据次设备号获取?
struct drm_minor * minor = drm_minor_acquire(iminor(inode));

  1. (base) baiy@baiy:~$ ls -al /dev/dri/*
  2. crw-rw----+ 1 root video 226, 0 May 2 21:42 /dev/dri/card0
  3. crw-rw----+ 1 root video 226, 128 May 2 21:42 /dev/dri/renderD128
  4. 可以看到 次设备号 == minor->index,所以直接用次设备号作为idr的索引即可

遗留问题

dev/dri/xxx节点的创建

/dev/dri/xxx等节点是如何创建的???