一,DRM core模块入口
DRM core模块入口函数为drm_core_init,位于drivers/gpu/drm/drm_drv.c;
static int __init drm_core_init(void)
{
int ret;
drm_connector_ida_init();
//idr初始化
idr_init(&drm_minors_idr);
drm_memcpy_init_early();
//创建class类drm_class,同时会在/sys/class/目录下创建一个新的文件夹drm
ret = drm_sysfs_init();
if (ret < 0) {
DRM_ERROR("Cannot create DRM class: %d\n", ret);
goto error;
}
//在/sys/kernel/debug/下创建dri目录
drm_debugfs_root = debugfs_create_dir("dri", NULL);
//申请主设备号,同时初始化以及注册字符设备cdev(这里注册的字符设备数量为256),并将字符设备的ops和drm_stub_fops绑定在一起
ret = register_chrdev(DRM_MAJOR, "drm", &drm_stub_fops);
if (ret < 0)
goto error;
drm_privacy_screen_lookup_init();
//设置标志位
drm_core_init_complete = true;
DRM_DEBUG("Initialized\n");
return 0;
error:
drm_core_exit();
return ret;
}
在DRM core初始化函数中,主要进行了如下操作:
-
调用drm_sysfs_init创建class类drm_class,在/sys/class目录下一个名称为drm的文件夹;
-
调用debugfs_create_dir在/sys/kernel/debug下创建dri目录;
-
调用register_chrdev申请主设备号为DRM_MAJOR(值为226),同时注册256个字符设备,并将字符设备的ops和drm_stub_fops绑定在一起;
1.1 drm_sysfs_init
drm_sysfs_init定义在drivers/gpu/drm/drm_sysfs.c,用于创建一个DRM class类;
/**
* drm_sysfs_init - initialize sysfs helpers
*
* This is used to create the DRM class, which is the implicit parent of any
* other top-level DRM sysfs objects.
*
* You must call drm_sysfs_destroy() to release the allocated resources.
*
* Return: 0 on success, negative error code on failure.
*/
int drm_sysfs_init(void)
{
int err;
//创建设备类,此函数的执行会在/sys/class/目录下创建一个新的文件夹drm
drm_class = class_create(THIS_MODULE, "drm");
if (IS_ERR(drm_class))
return PTR_ERR(drm_class);
//创建/sys/class/drm/version节点
err = class_create_file(drm_class, &class_attr_version.attr);
if (err) {
class_destroy(drm_class);
drm_class = NULL;
return err;
}
//设置设备节点
drm_class->devnode = drm_devnode;
drm_sysfs_acpi_register();
return 0;
}
可以看到在drm_sysfs_init函数中创建了class类drm_class,名称为drm,并设置devnode指向了drm_devnode;
static char *drm_devnode(struct device *dev, umode_t *mode)
{
return kasprintf(GFP_KERNEL, "dri/%s", dev_name(dev));
}
那么udev就会根据devnode的返回值来决定创建的设备节点文件的相对路径。同时,udev还会为这些设备节点文件设置相应的权限、所属用户和组等信息,以确保用户可以正确访问这些设备节点文件。
示例:
arcfox:/ # ls -la /sys/class/drm/
total 0
drwxr-xr-x 2 root root 0 2024-11-06 14:06 .
drwxr-xr-x 131 root root 0 1970-09-02 13:04 ..
lrwxrwxrwx 1 root root 0 2024-11-06 14:19 card0 -> ../../devices/platform/soc/ae00000.qcom,mdss_mdp/drm/card0
lrwxrwxrwx 1 root root 0 2024-11-06 14:16 card0-DSI-1 -> ../../devices/platform/soc/ae00000.qcom,mdss_mdp/drm/card0/card0-DSI-1
lrwxrwxrwx 1 root root 0 2024-11-06 14:06 card0-DSI-2 -> ../../devices/platform/soc/ae00000.qcom,mdss_mdp/drm/card0/card0-DSI-2
lrwxrwxrwx 1 root root 0 2024-11-06 14:19 card0-Virtual-1 -> ../../devices/platform/soc/ae00000.qcom,mdss_mdp/drm/card0/card0-Virtual-1
lrwxrwxrwx 1 root root 0 2024-11-06 14:19 card0-Virtual-2 -> ../../devices/platform/soc/ae00000.qcom,mdss_mdp/drm/card0/card0-Virtual-2
lrwxrwxrwx 1 root root 0 2024-11-06 14:19 renderD128 -> ../../devices/platform/soc/ae00000.qcom,mdss_mdp/drm/renderD128
lrwxrwxrwx 1 root root 0 2024-11-06 14:19 sde-crtc-0 -> ../../devices/platform/soc/ae00000.qcom,mdss_mdp/drm/card0/sde-crtc-0
lrwxrwxrwx 1 root root 0 2024-11-06 14:19 sde-crtc-1 -> ../../devices/platform/soc/ae00000.qcom,mdss_mdp/drm/card0/sde-crtc-1
lrwxrwxrwx 1 root root 0 2024-11-06 14:19 sde-crtc-2 -> ../../devices/platform/soc/ae00000.qcom,mdss_mdp/drm/card0/sde-crtc-2
lrwxrwxrwx 1 root root 0 2024-11-06 14:19 sde-crtc-3 -> ../../devices/platform/soc/ae00000.qcom,mdss_mdp/drm/card0/sde-crtc-3
-r--r--r-- 1 root root 4096 1970-09-02 13:04 version
1.2 drm_stub_fops
字符设备文件操作集被设置为了drm_stub_fops,其定义在drivers/gpu/drm/drm_drv.c;
/*
* DRM Core
* The DRM core module initializes all global DRM objects and makes them
* available to drivers. Once setup, drivers can probe their respective
* devices.
* Currently, core management includes:
* - The "DRM-Global" key/value database
* - Global ID management for connectors
* - DRM major number allocation
* - DRM minor management
* - DRM sysfs class
* - DRM debugfs root
*
* Furthermore, the DRM core provides dynamic char-dev lookups. For each
* interface registered on a DRM device, you can request minor numbers from DRM
* core. DRM core takes care of major-number management and char-dev
* registration. A stub ->open() callback forwards any open() requests to the
* registered minor.
*/
static int drm_stub_open(struct inode *inode, struct file *filp)
{
const struct file_operations *new_fops;
struct drm_minor *minor;
int err;
DRM_DEBUG("\n");
//根据设备节点获取struct drm_minor
minor = drm_minor_acquire(iminor(inode));
if (IS_ERR(minor))
return PTR_ERR(minor);
//获取drm driver的文件操作集
new_fops = fops_get(minor->dev->driver->fops);
if (!new_fops) {
err = -ENODEV;
goto out;
}
//用new_fops替换file->f_op
replace_fops(filp, new_fops);
//执行设备的文件open函数
if (filp->f_op->open)
err = filp->f_op->open(inode, filp);
else
err = 0;
out:
drm_minor_release(minor);
return err;
}
static const struct file_operations drm_stub_fops = {
.owner = THIS_MODULE,
.open = drm_stub_open,
.llseek = noop_llseek,
};
当上层应用打开drm设备时,通过drm设备节点获取到drm_minor。通过对文件指针进行重定向,打开真正的drm设备的open函数。
二,初始化drm设备
drm_dev_init函数用于初始化struct drm_device实例,函数定义在drivers/gpu/drm/drm_drv.c;
static int drm_dev_init(struct drm_device *dev,
const struct drm_driver *driver,
struct device *parent)
{
struct inode *inode;
int ret;
if (!drm_core_init_complete) {
DRM_ERROR("DRM core is not initialized\n");
return -ENODEV;
}
if (WARN_ON(!parent))
return -EINVAL;
//初始化drm_device对象的引用计数为1
kref_init(&dev->ref);
dev->dev = get_device(parent);
dev->driver = driver;
INIT_LIST_HEAD(&dev->managed.resources);
spin_lock_init(&dev->managed.lock);
/* no per-device feature limits by default */
dev->driver_features = ~0u;
drm_legacy_init_members(dev);
//初始化各种链表头节点
INIT_LIST_HEAD(&dev->filelist);
INIT_LIST_HEAD(&dev->filelist_internal);
INIT_LIST_HEAD(&dev->clientlist);
INIT_LIST_HEAD(&dev->vblank_event_list);
//初始化各种锁
spin_lock_init(&dev->event_lock);
mutex_init(&dev->struct_mutex);
mutex_init(&dev->filelist_mutex);
mutex_init(&dev->clientlist_mutex);
mutex_init(&dev->master_mutex);
ret = drmm_add_action_or_reset(dev, drm_dev_init_release, NULL);
if (ret)
return ret;
inode = drm_fs_inode_new();
if (IS_ERR(inode)) {
ret = PTR_ERR(inode);
DRM_ERROR("Cannot allocate anonymous inode: %d\n", ret);
goto err;
}
dev->anon_inode = inode;
//如果driver设置了DRIVER_RENDER flag,设置了render flag才会分配
if (drm_core_check_feature(dev, DRIVER_RENDER)) {
//动态分配drm_minor并初始化,type类型为DRM_MINOR_RENDER
ret = drm_minor_alloc(dev, DRM_MINOR_RENDER);
if (ret)
goto err;
}
//动态分配drm_minor并初始化,type类型为DRM_MINOR_PRIMARY,这个默认就会分配
ret = drm_minor_alloc(dev, DRM_MINOR_PRIMARY);
if (ret)
goto err;
ret = drm_legacy_create_map_hash(dev);
if (ret)
goto err;
drm_legacy_ctxbitmap_init(dev);
//如果driver设置了DRIVER_GEM flag
if (drm_core_check_feature(dev, DRIVER_GEM)) {
//分配并初始化DRM设备的GEM
ret = drm_gem_init(dev);
if (ret) {
DRM_ERROR("Cannot initialize graphics execution manager (GEM)\n");
goto err;
}
}
ret = drm_dev_set_unique(dev, dev_name(parent));
if (ret)
goto err;
return 0;
err:
drm_managed_release(dev);
return ret;
}
该函数需要传入struct drm_driver,qcom平台为例:
static struct drm_driver msm_driver = {
.driver_features = DRIVER_GEM |
DRIVER_RENDER |
DRIVER_ATOMIC |
DRIVER_MODESET,
.open = msm_open,
.postclose = msm_postclose,
.lastclose = msm_lastclose,
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0))
.irq_handler = msm_irq,
.irq_preinstall = msm_irq_preinstall,
.irq_postinstall = msm_irq_postinstall,
.irq_uninstall = msm_irq_uninstall,
.gem_free_object_unlocked = msm_gem_free_object,
.gem_vm_ops = &vm_ops,
.gem_prime_export = drm_gem_prime_export,
.gem_prime_pin = msm_gem_prime_pin,
.gem_prime_unpin = msm_gem_prime_unpin,
.gem_prime_get_sg_table = msm_gem_prime_get_sg_table,
.gem_prime_vmap = msm_gem_prime_vmap,
.gem_prime_vunmap = msm_gem_prime_vunmap,
#endif
.dumb_create = msm_gem_dumb_create,
.dumb_map_offset = msm_gem_dumb_map_offset,
.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
.gem_prime_import = msm_gem_prime_import,
.gem_prime_import_sg_table = msm_gem_prime_import_sg_table,
.gem_prime_mmap = msm_gem_prime_mmap,
.ioctls = msm_ioctls,
.num_ioctls = ARRAY_SIZE(msm_ioctls),
.fops = &fops,
.name = "msm_drm",
.desc = "MSM Snapdragon DRM",
.date = "20130625",
.major = MSM_VERSION_MAJOR,
.minor = MSM_VERSION_MINOR,
.patchlevel = MSM_VERSION_PATCHLEVEL,
};
2.1 drm_minor_alloc
drm_minor_alloc定义在drivers/gpu/drm/drm_drv.c,用以动态分配一个struct drm_minor,然后动态分配和初始化drm_minor->kdev,其最终目的是为了注册字符设备并创建设备节点/dev/dri/card%d;
static int drm_minor_alloc(struct drm_device *dev, unsigned int type)
{
struct drm_minor *minor;
unsigned long flags;
int r;
//动态分配struct drm_minor
minor = drmm_kzalloc(dev, sizeof(*minor), GFP_KERNEL);
if (!minor)
return -ENOMEM;
//minor的type
minor->type = type;
//minor的drm_device
minor->dev = dev;
idr_preload(GFP_KERNEL);
spin_lock_irqsave(&drm_minor_lock, flags);
//基于基数树分配唯一的ID
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)
return r;
//设置次设备编号
minor->index = r;
r = drmm_add_action_or_reset(dev, drm_minor_alloc_release, minor);
if (r)
return r;
//为minor分配并初始化一个struct device
minor->kdev = drm_sysfs_minor_alloc(minor);
if (IS_ERR(minor->kdev))
return PTR_ERR(minor->kdev);
//初始化drm_device的primary node和render node
*drm_minor_get_slot(dev, type) = minor;
return 0;
}
2.1.1 drm_sysfs_minor_alloc
drm_sysfs_minor_alloc函数实际上主要就是为minor分配并初始化一个struct device,定义在drivers/gpu/drm/drm_sysfs.c;
struct device *drm_sysfs_minor_alloc(struct drm_minor *minor)
{
const char *minor_str;
struct device *kdev;
int r;
//device的名字
if (minor->type == DRM_MINOR_RENDER)
minor_str = "renderD%d";
else
minor_str = "card%d";
//动态分配一个struct device
kdev = kzalloc(sizeof(*kdev), GFP_KERNEL);
if (!kdev)
return ERR_PTR(-ENOMEM);
//初始化设备
device_initialize(kdev);
//设置设备号,主设备号为226,次设备号minor->index
kdev->devt = MKDEV(DRM_MAJOR, minor->index);
//设置设备class
kdev->class = drm_class;
//设备类型
kdev->type = &drm_sysfs_device_minor;
//设置父设备
kdev->parent = minor->dev->dev;
kdev->release = drm_sysfs_release;
// 设置设备驱动数据为minor
dev_set_drvdata(kdev, minor);
//设置设备的名字为card%d , renderD%d
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);
}
具体流程如下:
-
动态分配一个struct device;
-
调用device_initialize初始化设备,这个函数是device_register函数的前半部分的实现,主要用于设备的初始化;
* 而device_add是在drm_minor_register函数中调用,该函数执行完会在/sys/class/drm创建card%d 和 renderD%d文件,同时创建设备节点/dev/drm/card%d和/dev/drm/renderD%d;
-
初始化设备号、class、设备类型、父设备、设备名称等;
那drm_class是在哪里创建的呢?drm_class是在drm驱动模块注册函数drm_core_init中创建的。
2.1.2 drm_minor_get_slot
函数drm_minor_get_slot定义在drivers/gpu/drm/drm_drv.c;
/*
* DRM Minors
* A DRM device can provide several char-dev interfaces on the DRM-Major. Each
* of them is represented by a drm_minor object. Depending on the capabilities
* of the device-driver, different interfaces are registered.
*
* Minors can be accessed via dev->$minor_name. This pointer is either
* NULL or a valid drm_minor pointer and stays valid as long as the device is
* valid. This means, DRM minors have the same life-time as the underlying
* device. However, this doesn't mean that the minor is active. Minors are
* registered and unregistered dynamically according to device-state.
*/
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;
default:
BUG();
}
}
2.2 drm_gem_init
分配并初始化DRM设备的GEM;
/**
* drm_gem_init - Initialize the GEM device fields
* @dev: drm_devic structure to initialize
*/
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 = drmm_kzalloc(dev, 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 drmm_add_action(dev, drm_gem_init_release, NULL);
}
三,注册drm设备
drm_dev_register函数向内核注册一个drm设备,同时在用户空间创建drm设备节点/dev/dri/card%d /dev/dri/renderD%d,函数定义在drivers/gpu/drm/drm_drv.c;
/**
* drm_dev_register - Register DRM device
* @dev: Device to register
* @flags: Flags passed to the driver's .load() function
*
* Register the DRM device @dev with the system, advertise device to user-space
* and start normal device operation. @dev must be initialized via drm_dev_init()
* previously.
*
* Never call this twice on any device!
*
* NOTE: To ensure backward compatibility with existing drivers method this
* function calls the &drm_driver.load method after registering the device
* nodes, creating race conditions. Usage of the &drm_driver.load methods is
* therefore deprecated, drivers must perform all initialization before calling
* drm_dev_register().
*
* RETURNS:
* 0 on success, negative error code on failure.
*/
int drm_dev_register(struct drm_device *dev, unsigned long flags)
{
const struct drm_driver *driver = dev->driver;
int ret;
//如果没有配置load,则校验drm模式配置
if (!driver->load)
drm_mode_config_validate(dev);
WARN_ON(!dev->managed.final_kfree);
// 如果需要全局互斥锁,则获取互斥锁
if (drm_dev_needs_global_mutex(dev))
mutex_lock(&drm_global_mutex);
//注册dev->render这个minor
ret = drm_minor_register(dev, DRM_MINOR_RENDER);
if (ret)
goto err_minors;
//注册dev->primary这个minor
ret = drm_minor_register(dev, DRM_MINOR_PRIMARY);
if (ret)
goto err_minors;
ret = create_compat_control_link(dev);
if (ret)
goto err_minors;
//设备注册标志设备为true
dev->registered = true;
//如果定义了load,会先执行load
if (dev->driver->load) {
ret = dev->driver->load(dev, flags);
if (ret)
goto err_minors;
}
//如果设置了DRIVER_MODESET标志位
if (drm_core_check_feature(dev, DRIVER_MODESET))
//注册plane、crtc、encoder、connector这4个drm_mode_object
drm_modeset_register_all(dev);
DRM_INFO("Initialized %s %d.%d.%d %s for %s on minor %d\n",
driver->name, driver->major, driver->minor,
driver->patchlevel, driver->date,
dev->dev ? dev_name(dev->dev) : "virtual device",
dev->primary->index);
goto out_unlock;
err_minors:
remove_compat_control_link(dev);
drm_minor_unregister(dev, DRM_MINOR_PRIMARY);
drm_minor_unregister(dev, DRM_MINOR_RENDER);
out_unlock:
//释放互斥锁
if (drm_dev_needs_global_mutex(dev))
mutex_unlock(&drm_global_mutex);
return ret;
}
3.1 drm_mode_config_validate
drm_mode_config_validate函数用于校验drm模式配置,定义在drivers/gpu/drm/drm_mode_config.c;
void drm_mode_config_validate(struct drm_device *dev)
{
struct drm_encoder *encoder;
struct drm_crtc *crtc;
struct drm_plane *plane;
u32 primary_with_crtc = 0, cursor_with_crtc = 0;
unsigned int num_primary = 0;
//如果drm_driver没设置DRIVER_MODESET flag
if (!drm_core_check_feature(dev, DRIVER_MODESET))
return;
drm_for_each_encoder(encoder, dev)
fixup_encoder_possible_clones(encoder);
drm_for_each_encoder(encoder, dev) {
validate_encoder_possible_clones(encoder);
validate_encoder_possible_crtcs(encoder);
}
drm_for_each_crtc(crtc, dev) {
WARN(!crtc->primary, "Missing primary plane on [CRTC:%d:%s]\n",
crtc->base.id, crtc->name);
WARN(crtc->cursor && crtc->funcs->cursor_set,
"[CRTC:%d:%s] must not have both a cursor plane and a cursor_set func",
crtc->base.id, crtc->name);
WARN(crtc->cursor && crtc->funcs->cursor_set2,
"[CRTC:%d:%s] must not have both a cursor plane and a cursor_set2 func",
crtc->base.id, crtc->name);
WARN(crtc->cursor && crtc->funcs->cursor_move,
"[CRTC:%d:%s] must not have both a cursor plane and a cursor_move func",
crtc->base.id, crtc->name);
if (crtc->primary) {
WARN(!(crtc->primary->possible_crtcs & drm_crtc_mask(crtc)),
"Bogus primary plane possible_crtcs: [PLANE:%d:%s] must be compatible with [CRTC:%d:%s]\n",
crtc->primary->base.id, crtc->primary->name,
crtc->base.id, crtc->name);
WARN(primary_with_crtc & drm_plane_mask(crtc->primary),
"Primary plane [PLANE:%d:%s] used for multiple CRTCs",
crtc->primary->base.id, crtc->primary->name);
primary_with_crtc |= drm_plane_mask(crtc->primary);
}
if (crtc->cursor) {
WARN(!(crtc->cursor->possible_crtcs & drm_crtc_mask(crtc)),
"Bogus cursor plane possible_crtcs: [PLANE:%d:%s] must be compatible with [CRTC:%d:%s]\n",
crtc->cursor->base.id, crtc->cursor->name,
crtc->base.id, crtc->name);
WARN(cursor_with_crtc & drm_plane_mask(crtc->cursor),
"Cursor plane [PLANE:%d:%s] used for multiple CRTCs",
crtc->cursor->base.id, crtc->cursor->name);
cursor_with_crtc |= drm_plane_mask(crtc->cursor);
}
}
drm_for_each_plane(plane, dev) {
if (plane->type == DRM_PLANE_TYPE_PRIMARY)
num_primary++;
}
WARN(num_primary != dev->mode_config.num_crtc,
"Must have as many primary planes as there are CRTCs, but have %u primary planes and %u CRTCs",
num_primary, dev->mode_config.num_crtc);
}
3.2 drm_minor_register
在drm_dev_register函数中多次调用了drm_minor_register,那drm_minor_register函数是干嘛用的呢?
该函数的目的主要是用来注册drm_minor的,会在/dev/dri目录下创建card%d renderD%d设备节点;函数定义在drivers/gpu/drm/drm_drv.c;
static int drm_minor_register(struct drm_device *dev, unsigned int type)
{
struct drm_minor *minor;
unsigned long flags;
int ret;
DRM_DEBUG("\n");
//根据drm_minor的type或者drm_minor,control or render
minor = *drm_minor_get_slot(dev, type);
if (!minor)
return 0;
//初始化debugfs
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;
}
//注册minor->kdev设备,这样在模块加载的时候,udev daemon就会自动为我们创建设备节点文件/dev/dri/card%d /dev/dri/renderD%d
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);
//将minor与minor->index这id关联起来,这样就可以通过id查找到minor
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;
}
主要进行了如下操作:
-
调用drm_minor_get_slot获取dev->primary这个minor或者dev->render;
-
调用drm_debugfs_init进行debugfs的初始化工作;
-
调用device_add注册minor->kdev设备,这样在模块加载的时候,udev daemon就会自动为我们创建设备节点文件/dev/dri/card%d;
-
调用idr_replace将minor与minor->index这个id关联起来,这样就可以在基数树中通过id查找到minor;
3.2.1 drm_minior_get_slot
通过drm_minor_get_slot获取drm->primary这个minor或者drm->render。
3.2.2 drm_debugfs_init
调用drm_debugfs_init进行debugfs的初始化工作,函数定义在drivers/gpu/drm/drm_debugfs.c;
int drm_debugfs_init(struct drm_minor *minor, int minor_id,
struct dentry *root)
{
struct drm_device *dev = minor->dev;
char name[64];
//初始化debugfs_list链表
INIT_LIST_HEAD(&minor->debugfs_list);
//初始化互斥锁
mutex_init(&minor->debugfs_lock);
//初始化name
sprintf(name, "%d", minor_id);
//在debugfs以name命名的目录,父目录为drm_debugfs_root
minor->debugfs_root = debugfs_create_dir(name, root);
//遍历drm_debugfs_list数组,在minor->debugfs_root目录,为每一个drm_info_node创建节点
//位于/sys/kernel/debug/dri/${name}目录下
drm_debugfs_create_files(drm_debugfs_list, DRM_DEBUGFS_ENTRIES,
minor->debugfs_root, minor);
if (drm_drv_uses_atomic_modeset(dev)) {
drm_atomic_debugfs_init(minor);
}
if (drm_core_check_feature(dev, DRIVER_MODESET)) {
drm_framebuffer_debugfs_init(minor);
drm_client_debugfs_init(minor);
}
//如果指定了debugfs_init回调函数,则执行
if (dev->driver->debugfs_init)
dev->driver->debugfs_init(minor);
return 0;
}
drm_debugfs_root在drm_core_init函数中被初始化,定义在drivers/gpu/drm/drm_drv.c,该函数会在/sys/kernel/debug下创建dri目录;
static int __init drm_core_init(void) {
......
// 在/sys/kernel/debug下创建dri目录
drm_debugfs_root = debugfs_create_dir("dri", NULL);
......
}
drm_debugfs_list是一个全局数组,定义在drivers/gpu/drm/drm_debugfs.c,内容如下:
/***************************************************
* Initialization, etc.
**************************************************/
static int drm_name_info(struct seq_file *m, void *data)
{
struct drm_info_node *node = (struct drm_info_node *) m->private;
struct drm_minor *minor = node->minor;
struct drm_device *dev = minor->dev;
struct drm_master *master;
mutex_lock(&dev->master_mutex);
master = dev->master;
seq_printf(m, "%s", dev->driver->name);
if (dev->dev)
seq_printf(m, " dev=%s", dev_name(dev->dev));
if (master && master->unique)
seq_printf(m, " master=%s", master->unique);
if (dev->unique)
seq_printf(m, " unique=%s", dev->unique);
seq_printf(m, "\n");
mutex_unlock(&dev->master_mutex);
return 0;
}
static int drm_clients_info(struct seq_file *m, void *data)
{
struct drm_info_node *node = (struct drm_info_node *) m->private;
struct drm_device *dev = node->minor->dev;
struct drm_file *priv;
kuid_t uid;
seq_printf(m,
"%20s %5s %3s master a %5s %10s\n",
"command",
"pid",
"dev",
"uid",
"magic");
/* dev->filelist is sorted youngest first, but we want to present
* oldest first (i.e. kernel, servers, clients), so walk backwardss.
*/
mutex_lock(&dev->filelist_mutex);
list_for_each_entry_reverse(priv, &dev->filelist, lhead) {
struct task_struct *task;
bool is_current_master = drm_is_current_master(priv);
rcu_read_lock(); /* locks pid_task()->comm */
task = pid_task(priv->pid, PIDTYPE_PID);
uid = task ? __task_cred(task)->euid : GLOBAL_ROOT_UID;
seq_printf(m, "%20s %5d %3d %c %c %5d %10u\n",
task ? task->comm : "<unknown>",
pid_vnr(priv->pid),
priv->minor->index,
is_current_master ? 'y' : 'n',
priv->authenticated ? 'y' : 'n',
from_kuid_munged(seq_user_ns(m), uid),
priv->magic);
rcu_read_unlock();
}
mutex_unlock(&dev->filelist_mutex);
return 0;
}
static int drm_gem_one_name_info(int id, void *ptr, void *data)
{
struct drm_gem_object *obj = ptr;
struct seq_file *m = data;
seq_printf(m, "%6d %8zd %7d %8d\n",
obj->name, obj->size,
obj->handle_count,
kref_read(&obj->refcount));
return 0;
}
static int drm_gem_name_info(struct seq_file *m, void *data)
{
struct drm_info_node *node = (struct drm_info_node *) m->private;
struct drm_device *dev = node->minor->dev;
seq_printf(m, " name size handles refcount\n");
mutex_lock(&dev->object_name_lock);
idr_for_each(&dev->object_name_idr, drm_gem_one_name_info, m);
mutex_unlock(&dev->object_name_lock);
return 0;
}
static const struct drm_info_list drm_debugfs_list[] = {
{"name", drm_name_info, 0},
{"clients", drm_clients_info, 0},
{"gem_names", drm_gem_name_info, DRIVER_GEM},
};
#define DRM_DEBUGFS_ENTRIES ARRAY_SIZE(drm_debugfs_list)
drm_debugfs_create_files函数会遍历drm_debugfs_list数组,依次为每个成员在/sys/kernel/debug/dri/${name}目录下创建以name为名称的文件,在对文件进行打开操作时会执行相应的show方法;
示例:
arcfox:/ # ls -la /sys/kernel/debug/dri/
total 0
drwxr-xr-x 4 root root 0 1970-01-01 08:00 .
drwxr-xr-x 117 root root 0 1970-01-01 08:00 ..
drwxr-xr-x 33 root root 0 1970-09-02 13:04 0
drwxr-xr-x 2 root root 0 1970-09-02 13:04 128
arcfox:/ # ls -la /sys/kernel/debug/dri/0/
total 0
drwxr-xr-x 33 root root 0 1970-09-02 13:04 .
drwxr-xr-x 4 root root 0 1970-01-01 08:00 ..
drwxr-xr-x 2 root root 0 1970-09-02 13:04 DSI-1
drwxr-xr-x 2 root root 0 1970-09-02 13:04 DSI-2
drwxr-xr-x 2 root root 0 1970-09-02 13:04 Virtual-1
drwxr-xr-x 2 root root 0 1970-09-02 13:04 Virtual-2
-r--r--r-- 1 root root 0 1970-09-02 13:04 clients
drwxr-xr-x 2 root root 0 1970-09-02 13:04 crtc-0
drwxr-xr-x 2 root root 0 1970-09-02 13:04 crtc-1
drwxr-xr-x 2 root root 0 1970-09-02 13:04 crtc-2
drwxr-xr-x 2 root root 0 1970-09-02 13:04 crtc-3
drwxr-xr-x 2 root root 0 1970-09-02 13:04 crtc179
drwxr-xr-x 2 root root 0 1970-09-02 13:04 crtc244
drwxr-xr-x 2 root root 0 1970-09-02 13:04 crtc253
drwxr-xr-x 2 root root 0 1970-09-02 13:04 crtc262
drwxr-xr-x 4 root root 0 1970-09-02 13:04 debug
drwxr-xr-x 2 root root 0 1970-09-02 13:04 encoder32
drwxr-xr-x 2 root root 0 1970-09-02 13:04 encoder63
drwxr-xr-x 2 root root 0 1970-09-02 13:04 encoder66
drwxr-xr-x 2 root root 0 1970-09-02 13:04 encoder86
-r--r--r-- 1 root root 0 1970-09-02 13:04 framebuffer
-r--r--r-- 1 root root 0 1970-09-02 13:04 gem_names
-r--r--r-- 1 root root 0 1970-09-02 13:04 internal_clients
-r--r--r-- 1 root root 0 1970-09-02 13:04 name
drwxr-xr-x 2 root root 0 1970-09-02 13:04 plane126
drwxr-xr-x 2 root root 0 1970-09-02 13:04 plane130
drwxr-xr-x 2 root root 0 1970-09-02 13:04 plane134
drwxr-xr-x 2 root root 0 1970-09-02 13:04 plane138
drwxr-xr-x 2 root root 0 1970-09-02 13:04 plane142
drwxr-xr-x 2 root root 0 1970-09-02 13:04 plane146
drwxr-xr-x 2 root root 0 1970-09-02 13:04 plane150
drwxr-xr-x 2 root root 0 1970-09-02 13:04 plane155
drwxr-xr-x 2 root root 0 1970-09-02 13:04 plane159
drwxr-xr-x 2 root root 0 1970-09-02 13:04 plane163
drwxr-xr-x 2 root root 0 1970-09-02 13:04 plane167
drwxr-xr-x 2 root root 0 1970-09-02 13:04 plane171
drwxr-xr-x 2 root root 0 1970-09-02 13:04 plane175
drwxr-xr-x 2 root root 0 1970-09-02 13:04 plane95
-r--r--r-- 1 root root 0 1970-09-02 13:04 state
arcfox:/ # ls -la /sys/kernel/debug/dri/128/
total 0
drwxr-xr-x 2 root root 0 1970-09-02 13:04 .
drwxr-xr-x 4 root root 0 1970-01-01 08:00 ..
-r--r--r-- 1 root root 0 1970-09-02 13:04 clients
-r--r--r-- 1 root root 0 1970-09-02 13:04 framebuffer
-r--r--r-- 1 root root 0 1970-09-02 13:04 gem_names
-r--r--r-- 1 root root 0 1970-09-02 13:04 internal_clients
-r--r--r-- 1 root root 0 1970-09-02 13:04 name
-r--r--r-- 1 root root 0 1970-09-02 13:04 state
当调用drm_minor_register(dev,xxx)时,如果minor->index=0,在会创建/sys/kernel/debug/dri/0目录以及文件clients、gem_names、name;
arcfox:/ # cat /sys/kernel/debug/dri/0/name
msm_drm dev=ae00000.qcom,mdss_mdp unique=ae00000.qcom,mdss_mdp
arcfox:/ # cat /sys/kernel/debug/dri/0/clients
command pid dev master a uid magic
binder:1735_2 1735 0 y y 1000 0
binder:1735_2 1735 0 n n 1000 0
binder:1735_2 1735 0 n n 1000 0
binder:1735_2 1735 0 n n 1000 0
binder:1735_2 1735 0 n n 1000 0
binder:1735_2 1735 0 n n 1000 0
binder:1735_2 1735 0 n n 1000 0
binder:1735_2 1735 0 n n 1000 0
binder:1735_2 1735 0 n n 1000 0
binder:1735_2 1735 0 n n 1000 0
binder:1735_2 1735 0 n n 1000 0
binder:1735_2 1735 0 n n 1000 0
binder:1735_2 1735 0 n n 1000 0
binder:1735_2 1735 0 n n 1000 0
binder:1735_1 2195 0 n n 1000 0
binder:1735_1 2195 0 n n 1000 0
binder:1735_1 2195 0 n n 1000 0
binder:1735_1 2195 0 n n 1000 0
binder:1735_1 2195 0 n n 1000 0
binder:1735_1 2195 0 n n 1000 0
binder:1735_1 2195 0 n n 1000 0
binder:1735_1 2195 0 n n 1000 0
binder:1735_1 2195 0 n n 1000 0
binder:1735_1 2195 0 n n 1000 0
binder:1735_1 2195 0 n n 1000 0
binder:1735_1 2195 0 n n 1000 0
binder:1735_1 2195 0 n n 1000 0
binder:1735_1 2195 0 n n 1000 0
<unknown> 3306 0 n n 0 0
<unknown> 3306 0 n n 0 0
<unknown> 3306 0 n n 0 0
<unknown> 3306 0 n n 0 0
<unknown> 3306 0 n n 0 0
<unknown> 3306 0 n n 0 0
<unknown> 3307 0 n n 0 0
<unknown> 3307 0 n n 0 0
<unknown> 3307 0 n n 0 0
<unknown> 3307 0 n n 0 0
<unknown> 3307 0 n n 0 0
<unknown> 3307 0 n n 0 0
com.motorola.ha 1666 0 n n 1000 0
arcfox:/ # cat /sys/kernel/debug/dri/0/gem_names
name size handles refcount
3.2.3 device_add
调用device_add(minor->kdev)注册minor->kdev设备,这样在模块加载的时候,udev daemon就会自动为我们创建设备节点文件/dev/dri/card%d /dev/dri/renderD%d,其中minor->kdev设备的class被设置为了drm_class;
1|arcfox:/ # ls -la /dev/dri/
total 0
drwxr-xr-x 2 root root 80 1970-09-02 13:04 .
drwxr-xr-x 26 root root 5400 2024-11-05 10:15 ..
crw-rw-rw- 1 root graphics 226, 0 1970-09-02 13:04 card0
crw-rw-rw- 1 root graphics 226, 128 1970-09-02 13:04 renderD128
arcfox:/ # ls -la /sys/class/drm/
total 0
drwxr-xr-x 2 root root 0 2024-11-06 11:17 .
drwxr-xr-x 131 root root 0 1970-09-02 13:04 ..
lrwxrwxrwx 1 root root 0 2024-11-06 11:18 card0 -> ../../devices/platform/soc/ae00000.qcom,mdss_mdp/drm/card0
lrwxrwxrwx 1 root root 0 2024-11-06 11:18 card0-DSI-1 -> ../../devices/platform/soc/ae00000.qcom,mdss_mdp/drm/card0/card0-DSI-1
lrwxrwxrwx 1 root root 0 2024-11-06 11:18 card0-DSI-2 -> ../../devices/platform/soc/ae00000.qcom,mdss_mdp/drm/card0/card0-DSI-2
lrwxrwxrwx 1 root root 0 2024-11-06 11:18 card0-Virtual-1 -> ../../devices/platform/soc/ae00000.qcom,mdss_mdp/drm/card0/card0-Virtual-1
lrwxrwxrwx 1 root root 0 2024-11-06 11:18 card0-Virtual-2 -> ../../devices/platform/soc/ae00000.qcom,mdss_mdp/drm/card0/card0-Virtual-2
lrwxrwxrwx 1 root root 0 2024-11-06 11:18 renderD128 -> ../../devices/platform/soc/ae00000.qcom,mdss_mdp/drm/renderD128
lrwxrwxrwx 1 root root 0 2024-11-06 11:18 sde-crtc-0 -> ../../devices/platform/soc/ae00000.qcom,mdss_mdp/drm/card0/sde-crtc-0
lrwxrwxrwx 1 root root 0 2024-11-06 11:18 sde-crtc-1 -> ../../devices/platform/soc/ae00000.qcom,mdss_mdp/drm/card0/sde-crtc-1
lrwxrwxrwx 1 root root 0 2024-11-06 11:18 sde-crtc-2 -> ../../devices/platform/soc/ae00000.qcom,mdss_mdp/drm/card0/sde-crtc-2
lrwxrwxrwx 1 root root 0 2024-11-06 11:18 sde-crtc-3 -> ../../devices/platform/soc/ae00000.qcom,mdss_mdp/drm/card0/sde-crtc-3
-r--r--r-- 1 root root 4096 1970-09-02 13:04 version
3.3 drm_modeset_register_all
drm_modeset_register_all用于注册plane、crtc、encoder、connector这4个drm_mode_object,函数定义在drivers/gpu/drm/drm_mode_config.c;
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_xxx_register_all函数的具体实现我们在后面章节一一介绍。
四,模式配置初始化
drm_mode_config_init用于初始化drm_device的mode_config结构体,函数定义在include/drm/drm_mode_config.h;
/**
* drm_mode_config_init - DRM mode_configuration structure initialization
* @dev: DRM device
*
* This is the unmanaged version of drmm_mode_config_init() for drivers which
* still explicitly call drm_mode_config_cleanup().
*
* FIXME: This function is deprecated and drivers should be converted over to
* drmm_mode_config_init().
*/
static inline int drm_mode_config_init(struct drm_device *dev)
{
return drmm_mode_config_init(dev);
}
drmm_mode_config_init定义在drivers/gpu/drm/drm_mode_config.c;
/**
* drmm_mode_config_init - managed DRM mode_configuration structure
* initialization
* @dev: DRM device
*
* Initialize @dev's mode_config structure, used for tracking the graphics
* configuration of @dev.
*
* Since this initializes the modeset locks, no locking is possible. Which is no
* problem, since this should happen single threaded at init time. It is the
* driver's problem to ensure this guarantee.
*
* Cleanup is automatically handled through registering drm_mode_config_cleanup
* with drmm_add_action().
*
* Returns: 0 on success, negative error value on failure.
*/
int drmm_mode_config_init(struct drm_device *dev)
{
int ret;
mutex_init(&dev->mode_config.mutex);
drm_modeset_lock_init(&dev->mode_config.connection_mutex);
mutex_init(&dev->mode_config.idr_mutex);
mutex_init(&dev->mode_config.fb_lock);
mutex_init(&dev->mode_config.blob_lock);
INIT_LIST_HEAD(&dev->mode_config.fb_list);
INIT_LIST_HEAD(&dev->mode_config.crtc_list);
INIT_LIST_HEAD(&dev->mode_config.connector_list);
INIT_LIST_HEAD(&dev->mode_config.encoder_list);
INIT_LIST_HEAD(&dev->mode_config.property_list);
INIT_LIST_HEAD(&dev->mode_config.property_blob_list);
INIT_LIST_HEAD(&dev->mode_config.plane_list);
INIT_LIST_HEAD(&dev->mode_config.privobj_list);
idr_init_base(&dev->mode_config.object_idr, 1);
idr_init_base(&dev->mode_config.tile_idr, 1);
ida_init(&dev->mode_config.connector_ida);
spin_lock_init(&dev->mode_config.connector_list_lock);
init_llist_head(&dev->mode_config.connector_free_list);
INIT_WORK(&dev->mode_config.connector_free_work, drm_connector_free_work_fn);
//创建mode_config里的property objects
ret = drm_mode_create_standard_properties(dev);
if (ret) {
drm_mode_config_cleanup(dev);
return ret;
}
/* Just to be sure */
dev->mode_config.num_fb = 0;
dev->mode_config.num_connector = 0;
dev->mode_config.num_crtc = 0;
dev->mode_config.num_encoder = 0;
dev->mode_config.num_total_plane = 0;
if (IS_ENABLED(CONFIG_LOCKDEP)) {
struct drm_modeset_acquire_ctx modeset_ctx;
struct ww_acquire_ctx resv_ctx;
struct dma_resv resv;
int ret;
dma_resv_init(&resv);
drm_modeset_acquire_init(&modeset_ctx, 0);
ret = drm_modeset_lock(&dev->mode_config.connection_mutex,
&modeset_ctx);
if (ret == -EDEADLK)
ret = drm_modeset_backoff(&modeset_ctx);
ww_acquire_init(&resv_ctx, &reservation_ww_class);
ret = dma_resv_lock(&resv, &resv_ctx);
if (ret == -EDEADLK)
dma_resv_lock_slow(&resv, &resv_ctx);
dma_resv_unlock(&resv);
ww_acquire_fini(&resv_ctx);
drm_modeset_drop_locks(&modeset_ctx);
drm_modeset_acquire_fini(&modeset_ctx);
dma_resv_fini(&resv);
}
return drmm_add_action_or_reset(dev, drm_mode_config_init_release,
NULL);
}
五,drm_xxx_init
drm_xxx_init 则分别用于初始化framebuffer 、plane、crtc、encoder、connector 这5个 drm_mode_object。
具体实现函数drm_framebuffer_init、drm_universal_plane_init、drm_crtc_init_with_planes、drm_encoder_init、drm_connector_init我们在后面章节单独介绍。
六,怎么编写一个drm驱动
如果我们需要编写一个DRM驱动,我们应该怎么做呢?具体流程如下:
(1) 定义struct drm_driver,并初始化成员name、desc、data、major、minor、driver_features、fops、dumb_create等;
(2)调用drm_dev_alloc函数分配并初始化一个struct drm_device;
(3) 调用drm_mode_config_init初始化drm_device中mode_config结构体;
(4) 调用drm_xxx_init创建 framebuffer、plane、crtc、encoder、connector 这5个 drm_mode_object;
在DRM子系统中是通过component框架完成各个功能模块的注册,比如在:
-
CRTC驱动程序:包含了plane和crtc的初始化工作;
-
HDMI驱动程序:包含了encoder和connector的初始化工作;
-
edp驱动程序:包含了encoder和connector的初始化工作;
-
......
(5) 调用drm_dev_register注册drm_device;
-
创建drm设备节点/dev/dri/card%d;
-
注册plane、crtc、encoder、connector这4个drm_mode_object;
参考链接:
Rockchip RK3399 - DRM子系统 - 大奥特曼打小怪兽 - 博客园
Linux显示(三):DRM子系统(以及LCDC/Panel/Backlight驱动) - ArnoldLu - 博客园