内容和参考
内容
学习Linux 中drm驱动编写方法以及drm驱动初始化;
另外,学习了struct drm_driver 的部分字段是如何使用:
struct drm_driver {
int (*load) (struct drm_device *, unsigned long flags); // 不推荐使用
void (*unload) (struct drm_device *); // 不推荐使用
void (*lastclose) (struct drm_device *); // legacy
void (*release) (struct drm_device *); // 不推荐使用
int major;
int minor;
int patchlevel;
char *name;
char *desc;
char *date;
u32 driver_features;
const struct file_operations *fops;
};
参考
drm软件流程
文章都是参考 Linux-5.8学习的内容
linux-5.8\drivers\gpu\drm\drm_drv.c
linux-5.8\drivers\gpu\drm\drm_file.c
linux-5.8\drivers\gpu\drm\drm_ioctl.c
linux-5.8\include\drm\drm_drv.h
drm驱动初始化
驱动初始化和卸载流程非常简单,基本模板如下: 最小化代码如下:参考 何小龙-DRM 驱动程序开发(VKMS) 和 Linux DRM Internals
#define pr_fmt(fmt) "[%s:%d] " fmt, __func__, __LINE__
#include <drm/drmP.h>
#include <drm/drm_drv.h>
#include <drm/drm_gem.h>
#include <drm/drm_vblank.h>
static struct drm_device *vkms_dev;
static struct drm_driver vkms_drv = {
.name = "vkms",
.desc = "Virtual Kernel Mode Setting",
.date = "20180514",
};
static int __init vkms_init(void)
{
pr_info("[E]");
vkms_dev = drm_dev_alloc(&vkms_drv, NULL);
if (!vkms_dev) {
pr_err("[vkms] dev alloc failed");
return -ENOMEM;
}
drm_dev_register(vkms_dev, 0);
pr_info("[X]\n");
return 0;
}
static void __exit vkms_exit(void)
{
pr_info("[E]");
if (vkms_dev) {
drm_dev_unregister(vkms_dev);
drm_dev_put(vkms_dev);
}
pr_info("[X]\n");
}
module_init(vkms_init);
module_exit(vkms_exit);
MODULE_LICENSE("GPL v2");
MODULE_INFO(supported, "Test drm driver");
drm_driver
struct drm_driver {
// 属性域
int major;
int minor;
int patchlevel;
char *name;
char *desc;
char *date;
u32 driver_features; // !!! 重点配置:enum drm_driver_feature; DRIVER_GEM | DRIVER_MODESET 必须有把
const struct drm_ioctl_desc *ioctls;
int num_ioctls;
const struct file_operations *fops;
};
drm_device
struct drm_device {
const struct drm_driver *driver; // 在 drm_dev_init 中初始化指向struct drm_driver
};
drm_dev_alloc和drm_dev_release
drivers/gpu/drm/drm_drv.c 这里调用 drm_minor_alloc 会给 struct drm_device dev- > primary/rendor 字段分配 struct drm_minor minor 结构体;
struct drm_minor { // drm_dev_init初始化,根据drm_driver的 driver_features 字段进行分配
int index; // idr_alloc 动态分配一个ID
int type; // DRM_MINOR_RENDER ? DRM_MINOR_PRIMARY ?
struct device *kdev; // !!! drm_minor_register => drm_sysfs_minor_alloc, minor_str=card%d / renderD%d
// !!! 也就是/dev/dri/card%d 和 /dev/dri/renderD%d 后期的由来
struct drm_device *dev; // 指向 struct drm_device
struct dentry *debugfs_root; // drm_minor_register->drm_debugfs_init创建以index为索引的目录
struct list_head debugfs_list;
struct mutex debugfs_lock;
};
drm_dev_register
drivers/gpu/drm/drm_drv.c
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字段的调试接口
root@baiy:/sys/kernel/debug/dri# ls
0 128
此时,系统启动中有以下打印:
[ 2.573455] [drm] Initialized vmwgfx 2.14.0 20170612 for 0000:00:0f.0 on minor 0
driver->name : vmwgfx
driver->major : 2
driver->minor : 14
driver->patchlevel : 0
driver->date : 20170612
struct drm_device dev->dev ? dev_name(dev->dev) : "virtual device"
struct drm_device dev->primary->index : 0
drm_dev_unregister
drivers/gpu/drm/drm_drv.c 注:因为我们写新的驱动不可能在支持 legacy特性,所以这里不过多描述,lastclose只是注明下
drm_dev_release
drivers/gpu/drm/drm_drv.c 这里的kfree释放设计比较有意思,其实就是释放了自己:
drm_dev_alloc
=> drmm_add_final_kfree
=> dev->managed.final_kfree = container; == struct drm_device *dev;
drm_dev_release
=> 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)提供的最简化驱动中有以下字段
static struct drm_driver vkms_driver = {
.name = "vkms",
.desc = "Virtual Kernel Mode Setting",
.date = "20180514",
.major = 1,
.minor = 0,
};
那么 major,minor, name, desc等作用是啥? 难道只是打印下?
其实在 Linux DRM Internals 中说的很清楚了:
其实这几个合起来表明版本信息,在 系统启动时打印,在 调用DRM_IOCTL_VERSION 获取版本描述**
static const struct drm_ioctl_desc drm_ioctls[] = {
DRM_IOCTL_DEF(DRM_IOCTL_VERSION, drm_version, DRM_RENDER_ALLOW), // drm_version会给用户态传递
......
}
struct drm_version {
int version_major; /**< Major version */
int version_minor; /**< Minor version */
int version_patchlevel; /**< Patch level */
__kernel_size_t name_len; /**< Length of name buffer */
char __user *name; /**< Name of driver */
__kernel_size_t date_len; /**< Length of date buffer */
char __user *date; /**< User-space buffer to hold date */
__kernel_size_t desc_len; /**< Length of desc buffer */
char __user *desc; /**< User-space buffer to hold desc */
};
libdrm的ioctrl是怎么掉的?
参考: drm_drv初始化,实际上调用的是 struct drm_driver *driver->fops;
drm_drv初始化
前边先看了 添加drm设备 drm_xxx 的初始化,但有以下疑问?
- 系统在初始化前给我们做了哪些工作?
- libdrm调用的ioctrl接口仍然不见?
- kms这个参数控制仍然没找到?
初始化接口
drm_core_init
register_chrdev(DRM_MAJOR, "drm", &drm_stub_fops); // drm 字符设备驱动 /proc/devices => 226 drm
drm_sysfs_init(); // 创建 /sys/class/drm
用户态接口
drm_stub_fops
.open = drm_stub_open,
.llseek = noop_llseek,
drm_stub_open
struct drm_minor *minor = drm_minor_acquire(iminor(inode));
const struct file_operations *new_fops = fops_get(minor->dev->driver->fops); // 获取drm_driver->ops
replace_fops(filp, new_fops); // filp->ops = drm_driver->ops;
filp->f_op->open(inode, filp); // drm_driver->ops->open();
drm_minor_release(minor);
所以 drm里边的 file->fops在打开时会使用drm_driver->fops来替换;
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, // drm_stub_open 会替换file->fops = vkms_fops
.name = "vkms",
.desc = "Virtual Kernel Mode Setting",
.date = "20180514",
.major = 1,
.minor = 0,
};
为什么minor可以根据次设备号获取?
struct drm_minor * minor = drm_minor_acquire(iminor(inode));
(base) baiy@baiy:~$ ls -al /dev/dri/*
crw-rw----+ 1 root video 226, 0 May 2 21:42 /dev/dri/card0
crw-rw----+ 1 root video 226, 128 May 2 21:42 /dev/dri/renderD128
可以看到 次设备号 == minor->index,所以直接用次设备号作为idr的索引即可
遗留问题
dev/dri/xxx节点的创建
/dev/dri/xxx等节点是如何创建的???