(四)- DRM驱动基础

一,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 - 博客园

Rockchip RK3399 - DRM驱动程序 - 大奥特曼打小怪兽 - 博客园