DRM 内部原理

本章介绍与驱动程序作者和开发人员相关的 DRM 内部原理,他们致力于为现有驱动程序添加对最新功能的支持。

首先,我们将介绍一些典型的驱动程序初始化要求,例如设置命令缓冲区、创建初始输出配置以及初始化核心服务。后续章节将更详细地介绍核心内部原理,提供实现说明和示例。

DRM 层为图形驱动程序提供多项服务,其中许多服务由它通过 libdrm 提供的应用程序接口驱动,libdrm 是包装大多数 DRM ioctl 的库。这些服务包括 vblank 事件处理、内存管理、输出管理、帧缓冲区管理、命令提交和围栏、挂起/恢复支持和 DMA 服务。

驱动程序初始化

每个 DRM 驱动程序的核心是一个 struct drm_driver 结构体。驱动程序通常静态初始化一个 drm_driver 结构体,然后将其传递给 drm_dev_alloc() 来分配一个设备实例。在设备实例完全初始化后,可以使用 drm_dev_register() 注册该设备实例(这使其可以从用户空间访问)。

struct drm_driver 结构体包含描述驱动程序及其支持的特性的静态信息,以及指向 DRM 核心将调用以实现 DRM API 的方法的指针。我们将首先介绍 struct drm_driver 静态信息字段,然后将在后面的章节中使用时详细描述各个操作。

驱动程序信息

主版本号、次版本号和补丁级别

int major; int minor; int patchlevel; DRM 核心通过主版本号、次版本号和补丁级别三元组来识别驱动程序版本。该信息在初始化时打印到内核日志,并通过 DRM_IOCTL_VERSION ioctl 传递给用户空间。

主版本号和次版本号还用于验证传递给 DRM_IOCTL_SET_VERSION 的请求的驱动程序 API 版本。当驱动程序 API 在次版本之间发生更改时,应用程序可以调用 DRM_IOCTL_SET_VERSION 来选择 API 的特定版本。如果请求的主版本号不等于驱动程序主版本号,或者请求的次版本号大于驱动程序次版本号,则 DRM_IOCTL_SET_VERSION 调用将返回错误。否则,将使用请求的版本调用驱动程序的 set_version() 方法。

名称和描述

char *name; char *desc; char *date; 驱动程序名称在初始化时打印到内核日志,用于 IRQ 注册,并通过 DRM_IOCTL_VERSION 传递给用户空间。

驱动程序描述是一个纯粹的信息性字符串,通过 DRM_IOCTL_VERSION ioctl 传递给用户空间,内核中未使用。

模块初始化

此库提供在模块初始化和关闭期间注册 DRM 驱动程序的助手函数。提供的助手函数类似于特定于总线的模块助手函数,例如 module_pci_driver(),但遵循控制 DRM 驱动程序注册的附加参数。

以下是在 PCI 总线上为设备初始化 DRM 驱动程序的示例。

struct pci_driver my_pci_drv = {
};

drm_module_pci_driver(my_pci_drv);

生成的代码将测试是否启用了 DRM 驱动程序并注册 PCI 驱动程序 my_pci_drv。对于更复杂的模块初始化,您仍然可以在驱动程序中使用 module_init()module_exit()

设备实例和驱动程序处理

drm 驱动程序的设备实例由 struct drm_device 表示。这使用 devm_drm_dev_alloc() 分配和初始化,通常来自驱动程序实现的特定于总线的 ->probe() 回调。然后,驱动程序需要初始化 drm 设备的所有各种子系统,例如内存管理、vblank 处理、模式设置支持和初始输出配置,并显然初始化所有相应的硬件位。最后,当一切都启动并运行并准备好供用户空间使用时,可以使用 drm_dev_register() 发布设备实例。

还存在对使用特定于总线的助手函数和 drm_driver.load 回调来初始化设备实例的已弃用支持。但是,由于向后兼容性需要,必须过早地发布设备实例,这需要使用不漂亮的全局锁定才能安全,因此仅支持尚未转换为新方案的现有驱动程序。

清理设备实例时,所有操作都需要反向执行:首先使用 drm_dev_unregister() 取消发布设备实例。然后清理在设备初始化时分配的任何其他资源,并使用 drm_dev_put() 删除驱动程序对 drm_device 的引用。

请注意,只有在调用最终的 drm_dev_put() 时,而不是在驱动程序与底层物理结构 device 解绑时,才必须释放任何用户空间可见的分配或资源。最好使用 drm_device 管理的资源与 drmm_add_action()drmm_kmalloc() 和相关的函数。

devres 管理的资源,如 devm_kmalloc(),只能用于与底层硬件设备直接相关的资源,并且只能在完全受 drm_dev_enter()drm_dev_exit() 保护的代码路径中使用。

显示驱动程序示例

以下示例显示了 DRM 显示驱动程序的典型结构。该示例重点介绍 probe() 函数以及几乎总是存在的其他函数,并作为 devm_drm_dev_alloc() 的演示。

struct driver_device {
        struct drm_device drm;
        void *userspace_facing;
        struct clk *pclk;
};

static const struct drm_driver driver_drm_driver = {
        [...]
};

static int driver_probe(struct platform_device *pdev)
{
        struct driver_device *priv;
        struct drm_device *drm;
        int ret;

        priv = devm_drm_dev_alloc(&pdev->dev, &driver_drm_driver,
                                  struct driver_device, drm);
        if (IS_ERR(priv))
                return PTR_ERR(priv);
        drm = &priv->drm;

        ret = drmm_mode_config_init(drm);
        if (ret)
                return ret;

        priv->userspace_facing = drmm_kzalloc(..., GFP_KERNEL);
        if (!priv->userspace_facing)
                return -ENOMEM;

        priv->pclk = devm_clk_get(dev, "PCLK");
        if (IS_ERR(priv->pclk))
                return PTR_ERR(priv->pclk);

        // Further setup, display pipeline etc

        platform_set_drvdata(pdev, drm);

        drm_mode_config_reset(drm);

        ret = drm_dev_register(drm);
        if (ret)
                return ret;

        drm_fbdev_{...}_setup(drm, 32);

        return 0;
}

// This function is called before the devm_ resources are released
static int driver_remove(struct platform_device *pdev)
{
        struct drm_device *drm = platform_get_drvdata(pdev);

        drm_dev_unregister(drm);
        drm_atomic_helper_shutdown(drm)

        return 0;
}

// This function is called on kernel restart and shutdown
static void driver_shutdown(struct platform_device *pdev)
{
        drm_atomic_helper_shutdown(platform_get_drvdata(pdev));
}

static int __maybe_unused driver_pm_suspend(struct device *dev)
{
        return drm_mode_config_helper_suspend(dev_get_drvdata(dev));
}

static int __maybe_unused driver_pm_resume(struct device *dev)
{
        drm_mode_config_helper_resume(dev_get_drvdata(dev));

        return 0;
}

static const struct dev_pm_ops driver_pm_ops = {
        SET_SYSTEM_SLEEP_PM_OPS(driver_pm_suspend, driver_pm_resume)
};

static struct platform_driver driver_driver = {
        .driver = {
                [...]
                .pm = &driver_pm_ops,
        },
        .probe = driver_probe,
        .remove = driver_remove,
        .shutdown = driver_shutdown,
};
module_platform_driver(driver_driver);

希望支持设备拔插(USB、DT 叠加卸载)的驱动程序应使用 drm_dev_unplug() 而不是 drm_dev_unregister()。驱动程序必须保护访问设备资源的区域,以防止在释放后使用它们。这可以使用 drm_dev_enter()drm_dev_exit() 完成。但是,存在一个缺点,drm_dev_unplug() 会在调用 drm_atomic_helper_shutdown() 之前将 drm_device 标记为已拔插。这意味着如果禁用代码路径受到保护,则它们不会在常规驱动程序模块卸载时运行,这可能会使硬件保持启用状态。

enum switch_power_state

drm 设备的电源状态

常量

DRM_SWITCH_POWER_ON

电源状态为 ON

DRM_SWITCH_POWER_OFF

电源状态为 OFF

DRM_SWITCH_POWER_CHANGING

电源状态正在改变

DRM_SWITCH_POWER_DYNAMIC_OFF

已挂起

struct drm_device

DRM 设备结构体

定义:

struct drm_device {
    int if_version;
    struct kref ref;
    struct device *dev;
    struct device *dma_dev;
    struct {
        struct list_head resources;
        void *final_kfree;
        spinlock_t lock;
    } managed;
    const struct drm_driver *driver;
    void *dev_private;
    struct drm_minor *primary;
    struct drm_minor *render;
    struct drm_minor *accel;
    bool registered;
    struct drm_master *master;
    u32 driver_features;
    bool unplugged;
    struct inode *anon_inode;
    char *unique;
    struct mutex struct_mutex;
    struct mutex master_mutex;
    atomic_t open_count;
    struct mutex filelist_mutex;
    struct list_head filelist;
    struct list_head filelist_internal;
    struct mutex clientlist_mutex;
    struct list_head clientlist;
    bool vblank_disable_immediate;
    struct drm_vblank_crtc *vblank;
    spinlock_t vblank_time_lock;
    spinlock_t vbl_lock;
    u32 max_vblank_count;
    struct list_head vblank_event_list;
    spinlock_t event_lock;
    unsigned int num_crtcs;
    struct drm_mode_config mode_config;
    struct mutex object_name_lock;
    struct idr object_name_idr;
    struct drm_vma_offset_manager *vma_offset_manager;
    struct drm_vram_mm *vram_mm;
    enum switch_power_state switch_power_state;
    struct drm_fb_helper *fb_helper;
    struct dentry *debugfs_root;
};

成员

if_version

设置的最高接口版本

ref

对象引用计数

dev

总线设备的设备结构体

dma_dev

用于 DMA 操作的设备。只有在设备 dev 无法自行执行 DMA 时才需要。否则应为 NULL。调用 drm_dev_dma_dev() 获取 DMA 设备,而不是直接使用此字段。调用 drm_dev_set_dma_dev() 设置此字段。

DRM 设备有时绑定到无法自行执行 DMA 的虚拟设备。驱动程序应将此字段设置为相应的 DMA 控制器。

USB 和其他外围总线上的设备也无法自行执行 DMA。dma_dev 字段应指向代表此类设备执行 DMA 的总线控制器。通过 dma-buf 导入缓冲区时需要。

如果设置,DRM 核心会自动释放对该设备的引用。

managed

链接到此 drm_device 的生存期的托管资源,由 ref 跟踪。

driver

管理设备的 DRM 驱动程序

dev_private

DRM 驱动程序私有数据。此项已弃用,应保留设置为 NULL。

建议驱动程序使用 devm_drm_dev_alloc() 并将其更大的每个设备结构体中的 drm_device 结构体嵌入到其中,而不是使用此指针。

primary

主节点。驱动程序不应直接与此交互。可以使用 drm_debugfs_add_file() 注册 debugfs 接口,sysfs 应直接添加到硬件(而不是字符设备节点)struct device dev

render

渲染节点。驱动程序永远不应直接与此交互。驱动程序不应在此节点上的 debugfs 或 sysfs 中公开任何其他接口。

accel

计算加速节点

registered

drm_dev_register()drm_connector_register() 内部使用。

master

此设备当前活动的 master。受 master_mutex 保护

driver_features

每个设备的驱动程序特性

驱动程序可以在此处清除特定标志,以禁止在每个设备上使用某些特性,同时仍然在所有设备之间共享单个 struct drm_driver 实例。

unplugged

用于指示设备是否已拔出的标志。请参见 drm_dev_enter()drm_dev_is_unplugged()

anon_inode

私有地址空间的 inode

unique

设备的唯一名称

struct_mutex

用于其他人(不是 drm_minor.masterdrm_file.is_master)的锁

TODO:此锁曾经是 DRM 子系统的 BKL。将锁移动到 i915 中,i915 是唯一剩余的用户。

锁移动到 i915 中,i915 是唯一剩余的用户。

master_mutex

用于 drm_minor.masterdrm_file.is_master 的锁

open_count

未完成的打开的文件使用计数器,受 drm_global_mutex 保护

filelist_mutex

保护 filelist

filelist

用户空间客户端列表,通过 drm_file.lhead 链接。

filelist_internal

用于内核客户端的已打开 DRM 文件列表。受 filelist_mutex 保护。

clientlist_mutex

保护 clientlist 访问。

clientlist

内核客户端列表。受 clientlist_mutex 保护。

vblank_disable_immediate

如果为 true,当引用计数降至零时,将立即禁用 vblank 中断,而不是通过 vblank 禁用计时器。

如果硬件具有带有高精度时间戳的正常工作的 vblank 计数器(否则存在竞争),并且驱动程序适当使用 drm_crtc_vblank_on()drm_crtc_vblank_off(),则可以将其设置为 true。另请参见 max_vblank_countdrm_crtc_funcs.get_vblank_counterdrm_vblank_crtc_config.disable_immediate

vblank

vblank 跟踪结构体的数组,每个 struct drm_crtc 一个。由于历史原因(vblank 支持早于内核模式设置),它是独立的,而不是 struct drm_crtc 本身的一部分。必须通过调用 drm_vblank_init() 显式初始化它。

vblank_time_lock

保护 vblank 启用/禁用期间的 vblank 计数和时间更新

vbl_lock

顶级 vblank 引用锁,包装低级 vblank_time_lock

max_vblank_count

vblank 寄存器的最大值。此值 + 1 将导致 vblank 寄存器回绕。vblank 核心使用它来处理回绕。

如果设置为零,vblank 核心将尝试通过高精度时间戳猜测在通过高精度时间戳禁用 vblank 中断的时间之间经过的 vblank。这种方法会受到小竞争的影响,并且在较长时间内精度较低,因此始终建议公开硬件 vblank 计数器。

这是静态配置的设备范围最大值。驱动程序可以选择使用运行时可配置的每个 crtc 值 drm_vblank_crtc.max_vblank_count,在这种情况下,max_vblank_count 必须保留为零。请参见 drm_crtc_set_max_vblank_count(),了解如何使用每个 crtc 值。

如果非零,则必须设置 drm_crtc_funcs.get_vblank_counter

vblank_event_list

vblank 事件列表

event_lock

保护 vblank_event_list 和总体事件传递。请参见 drm_send_event()drm_send_event_locked()

num_crtcs

此设备上的 CRTC 数量

mode_config

当前模式配置

object_name_lock

GEM 信息

object_name_idr

GEM 信息

vma_offset_manager

GEM 信息

vram_mm

VRAM MM 内存管理器

switch_power_state

客户端的电源状态。供支持 switcheroo 驱动程序的驱动程序使用。该状态在 vga_switcheroo_client_ops.set_gpu_state 回调中维护

fb_helper

指向 fbdev 仿真结构的指针。由 drm_fb_helper_init() 设置,并由 drm_fb_helper_fini() 清除。

debugfs_root

debugfs 文件的根目录。

描述

此结构体表示可能包含多个头的完整卡。

struct device *drm_dev_dma_dev(struct drm_device *dev)

返回 DRM 设备的 DMA 设备

参数

struct drm_device *dev

DRM 设备

描述

返回给定 DRM 设备的 DMA 设备。默认情况下,这是 DRM 设备的父设备。请参见 drm_dev_set_dma_dev()

返回值

DRM 设备的 DMA 功能设备。

enum drm_driver_feature

特性标志

常量

DRIVER_GEM

驱动程序使用 GEM 内存管理器。应为所有现代驱动程序设置此项。

DRIVER_MODESET

驱动程序支持模式设置接口 (KMS)。

DRIVER_RENDER

驱动程序支持专用渲染节点。另请参见 关于渲染节点的章节,以了解详细信息。

DRIVER_ATOMIC

驱动程序支持完整的原子模式设置用户空间 API。仅在内部使用原子,但不支持完整用户空间 API 的驱动程序(例如,并非所有属性都转换为原子,或者不能保证多平面更新是无撕裂的)不应设置此标志。

DRIVER_SYNCOBJ

驱动程序支持 drm_syncobj 用于显式同步命令提交。

DRIVER_SYNCOBJ_TIMELINE

驱动程序支持时间线风格的 drm_syncobj 用于显式同步命令提交。

DRIVER_COMPUTE_ACCEL

驱动程序支持计算加速设备。此标志与 DRIVER_RENDERDRIVER_MODESET 互斥。支持图形和计算加速的设备应由两个使用辅助总线连接的驱动程序处理。

DRIVER_GEM_GPUVA

驱动程序支持 GEM 对象的用户定义的 GPU VA 绑定。

DRIVER_CURSOR_HOTSPOT

驱动程序支持并需要光标平面中的光标热点信息(例如,光标平面必须实际跟踪鼠标光标,并且客户端需要设置热点才能使光标平面正常工作)。

DRIVER_USE_AGP

设置 DRM AGP 支持,请参见 drm_agp_init(),DRM 核心将管理 AGP 资源。新驱动程序不需要此项。

DRIVER_LEGACY

表示使用阴影附加的旧驱动程序。请勿使用。

DRIVER_PCI_DMA

驱动程序能够进行 PCI DMA,将启用 PCI DMA 缓冲区到用户空间的映射。仅用于旧驱动程序。请勿使用。

DRIVER_SG

驱动程序可以执行分散/收集 DMA,将启用分散/收集缓冲区的分配和映射。仅用于旧驱动程序。请勿使用。

DRIVER_HAVE_DMA

驱动程序支持 DMA,将支持用户空间 DMA API。仅用于旧驱动程序。请勿使用。

DRIVER_HAVE_IRQ

旧的 irq 支持。仅用于旧驱动程序。请勿使用。

描述

请参见 drm_driver.driver_features、drm_device.driver_features 和 drm_core_check_feature()

struct drm_driver

DRM 驱动程序结构体

定义:

struct drm_driver {
    int (*load) (struct drm_device *, unsigned long flags);
    int (*open) (struct drm_device *, struct drm_file *);
    void (*postclose) (struct drm_device *, struct drm_file *);
    void (*unload) (struct drm_device *);
    void (*release) (struct drm_device *);
    void (*master_set)(struct drm_device *dev, struct drm_file *file_priv, bool from_open);
    void (*master_drop)(struct drm_device *dev, struct drm_file *file_priv);
    void (*debugfs_init)(struct drm_minor *minor);
    struct drm_gem_object *(*gem_create_object)(struct drm_device *dev, size_t size);
    int (*prime_handle_to_fd)(struct drm_device *dev, struct drm_file *file_priv, uint32_t handle, uint32_t flags, int *prime_fd);
    int (*prime_fd_to_handle)(struct drm_device *dev, struct drm_file *file_priv, int prime_fd, uint32_t *handle);
    struct drm_gem_object * (*gem_prime_import)(struct drm_device *dev, struct dma_buf *dma_buf);
    struct drm_gem_object *(*gem_prime_import_sg_table)(struct drm_device *dev,struct dma_buf_attachment *attach, struct sg_table *sgt);
    int (*dumb_create)(struct drm_file *file_priv,struct drm_device *dev, struct drm_mode_create_dumb *args);
    int (*dumb_map_offset)(struct drm_file *file_priv,struct drm_device *dev, uint32_t handle, uint64_t *offset);
    int (*fbdev_probe)(struct drm_fb_helper *fbdev_helper, struct drm_fb_helper_surface_size *sizes);
    void (*show_fdinfo)(struct drm_printer *p, struct drm_file *f);
    int major;
    int minor;
    int patchlevel;
    char *name;
    char *desc;
    u32 driver_features;
    const struct drm_ioctl_desc *ioctls;
    int num_ioctls;
    const struct file_operations *fops;
};

成员

load

向后兼容的驱动程序回调,用于在驱动程序注册后完成初始化步骤。因此,可能存在竞争条件,并且不建议新驱动程序使用它。因此,仅支持尚未转换为新方案的现有驱动程序。请参见 devm_drm_dev_alloc()drm_dev_register(),了解设置 struct drm_device 的正确且无竞争的方式。

此项已弃用,请勿使用!

返回值

成功时为零,失败时为非零值。

open

打开新的 struct drm_file 时的驱动程序回调。对于设置驱动程序私有数据结构(如缓冲区分配器、执行上下文或类似事物)非常有用。此类驱动程序私有资源必须在 postclose 中再次释放。

由于 DRM 的显示/模式设置端只能由一个 struct drm_file 拥有(请参见 drm_file.is_masterdrm_device.master),因此永远不需要在此回调中设置任何模式设置相关资源。这样做是驱动程序设计错误。

返回值

成功时为 0,失败时为负错误代码,将作为 open() 系统调用的结果提升到用户空间。

postclose

关闭新的 struct drm_file 时的驱动程序回调之一。对于拆除在 open 中分配的驱动程序私有数据结构(如缓冲区分配器、执行上下文或类似事物)非常有用。

由于 DRM 的显示/模式设置端只能由一个 struct drm_file 拥有(请参见 drm_file.is_masterdrm_device.master),因此永远不需要在此回调中拆除任何模式设置相关资源。这样做是驱动程序设计错误。

unload

反转驱动程序加载回调的效果。理想情况下,驱动程序执行的清理应以初始化的相反顺序进行。与加载挂钩类似,此处理程序已弃用,应放弃使用它,而支持驱动程序层中的开放编码拆卸函数。请参见 drm_dev_unregister()drm_dev_put(),以了解删除 struct drm_device 的正确方法。

unload() 挂钩在注销设备后立即调用。

release

用于在释放最终引用后销毁设备数据的可选回调,即正在销毁设备。

此项已弃用,请使用 drmm_add_action()drmm_kmalloc() 和相关的托管资源函数清理与 drm_device 关联的所有内存分配。

master_set

每当设置次要 master 时调用。仅由 vmwgfx 使用。

master_drop

每当删除次要 master 时调用。仅由 vmwgfx 使用。

debugfs_init

允许驱动程序创建特定于驱动程序的 debugfs 文件。

gem_create_object

gem 对象的构造函数

用于分配 GEM 对象结构体的挂钩,供 CMA 和 SHMEM GEM 助手使用。成功时返回一个 GEM 对象,否则返回一个 ERR_PTR() 编码的错误代码。

prime_handle_to_fd

PRIME 导出函数。仅由 vmwgfx 使用。

prime_fd_to_handle

PRIME 导入函数。仅由 vmwgfx 使用。

gem_prime_import

GEM 驱动程序的导入挂钩。

如果未设置,则默认为 drm_gem_prime_import()

gem_prime_import_sg_table

PRIME 助手函数 drm_gem_prime_import()drm_gem_prime_import_dev() 使用的可选挂钩。

dumb_create

这会在驱动程序的后备存储管理器(GEM、TTM 或其他完全不同的内容)中创建一个新的 dumb 缓冲区,并返回生成的缓冲区句柄。然后,可以将此句柄包装到帧缓冲区模式设置对象中。

请注意,不允许用户空间将此类对象用于渲染加速 - 驱动程序必须为此用例创建自己的私有 ioctl。

宽度、高度和深度在 drm_mode_create_dumb 参数中指定。回调需要填充为创建的缓冲区的句柄、pitch 和大小。

由用户通过 ioctl 调用。

返回值

成功时为零,失败时为负 errno。

dumb_map_offset

在 DRM 设备节点的地址空间中分配一个偏移量,以便能够内存映射一个 dumb 缓冲区。

默认实现是 drm_gem_create_mmap_offset()。基于 GEM 的驱动程序不得覆盖此项。

由用户通过 ioctl 调用。

返回值

成功时为零,失败时为负 errno。

fbdev_probe

为 fbdev 模拟分配并初始化 fb_info 结构。此外,还需要分配用于支持 fbdev 的 DRM 帧缓冲区。

对于 fbdev 支持,此回调是必需的。

返回值

成功时返回 0,否则返回负错误代码。

show_fdinfo

打印设备特定的 fdinfo。请参阅 DRM 客户端使用统计

major

驱动程序主设备号

minor

驱动程序次设备号

patchlevel

驱动程序补丁级别

name

驱动程序名称

desc

驱动程序描述

driver_features

驱动程序功能,请参阅 enum drm_driver_feature。驱动程序可以使用 drm_device.driver_features 逐实例禁用某些功能。

ioctls

驱动程序私有 IOCTL 描述条目的数组。有关完整详细信息,请参阅有关 用户空间接口章节中的 IOCTL 支持 的章节。

num_ioctls

ioctls 中的条目数。

fops

DRM 设备节点的文件操作。有关深入的讨论和一些示例,请参阅 文件操作 中的讨论。

描述

此结构表示一类卡的通用代码。对于此类卡中的每个卡,都将有一个 struct drm_device。它包含许多 vfunc 条目,其中很多条目可能应该移动到更合适的位置,如 drm_mode_config_funcs 或 GEM 驱动程序的新操作结构中。

devm_drm_dev_alloc

devm_drm_dev_alloc (parent, driver, type, member)

资源管理的 drm_device 实例的分配

参数

parent

父设备对象

driver

DRM 驱动程序

type

包含 struct drm_device 的结构的类型

member

typedrm_device 的名称。

描述

这会分配并初始化一个新的 DRM 设备。不进行设备注册。调用 drm_dev_register() 以向用户空间公布该设备,并将其注册到其他核心子系统。这应该在设备初始化序列的最后完成,以确保用户空间无法访问不一致的状态。

对象的初始引用计数为 1。使用 drm_dev_get()drm_dev_put() 来获取和删除进一步的引用计数。

建议驱动程序将 struct drm_device 嵌入到他们自己的设备结构中。

请注意,这使用 devres 自动管理生成的 drm_device 的生命周期。使用此函数初始化的 DRM 设备在驱动程序分离时使用 drm_dev_put() 自动释放。

返回值

指向新 DRM 设备的指针,如果失败,则为 ERR_PTR。

bool drm_dev_is_unplugged(struct drm_device *dev)

DRM 设备是否已拔出

参数

struct drm_device *dev

DRM 设备

描述

可以调用此函数来检查热插拔设备是否已拔出。拔出本身通过 drm_dev_unplug() 发出信号。如果设备已拔出,则这两个函数保证在调用 drm_dev_unplug() 之前进行的任何存储在该函数完成后对该函数的调用者可见

警告:此函数从根本上与 drm_dev_unplug() 竞争。建议驱动程序改为使用底层的 drm_dev_enter()drm_dev_exit() 函数对。

bool drm_core_check_all_features(const struct drm_device *dev, u32 features)

检查驱动程序功能标志掩码

参数

const struct drm_device *dev

要检查的 DRM 设备

u32 features

功能标志掩码

描述

这将检查 dev 的驱动程序功能,请参阅 drm_driver.driver_featuresdrm_device.driver_features 以及各种 enum drm_driver_feature 标志。

如果支持 features 掩码中的所有功能,则返回 true,否则返回 false。

bool drm_core_check_feature(const struct drm_device *dev, enum drm_driver_feature feature)

检查驱动程序功能标志

参数

const struct drm_device *dev

要检查的 DRM 设备

enum drm_driver_feature feature

功能标志

描述

这将检查 dev 的驱动程序功能,请参阅 drm_driver.driver_featuresdrm_device.driver_features 以及各种 enum drm_driver_feature 标志。

如果支持 feature,则返回 true,否则返回 false。

bool drm_drv_uses_atomic_modeset(struct drm_device *dev)

检查驱动程序是否实现了 atomic_commit()

参数

struct drm_device *dev

DRM 设备

描述

如果驱动程序未设置 DRIVER_ATOMIC 但在内部实现了原子模式设置,则此检查非常有用。

void drm_put_dev(struct drm_device *dev)

注销并释放 DRM 设备

参数

struct drm_device *dev

DRM 设备

描述

在模块卸载时或 PCI 设备拔出时调用。

清理所有 DRM 设备,调用 drm_lastclose()。

注意

不建议使用此函数。它最终将完全消失。请明确使用 drm_dev_unregister()drm_dev_put(),以确保在拆卸过程中用户空间不再可访问该设备,从而确保用户空间无法访问不一致的状态。

bool drm_dev_enter(struct drm_device *dev, int *idx)

进入设备临界区

参数

struct drm_device *dev

DRM 设备

int *idx

指向将传递给匹配的 drm_dev_exit() 的索引的指针

描述

此函数标记并保护在设备拔出后不应进入的区域的开头。区域结尾用 drm_dev_exit() 标记。可以嵌套对此函数的调用。

返回值

如果可以进入该区域,则为 True,否则为 false。

void drm_dev_exit(int idx)

退出设备临界区

参数

int idx

drm_dev_enter() 返回的索引

描述

此函数标记了设备拔出后不应进入的区域的结尾。

void drm_dev_unplug(struct drm_device *dev)

拔出 DRM 设备

参数

struct drm_device *dev

DRM 设备

描述

这将拔出一个热插拔 DRM 设备,使其无法进行用户空间操作。入口点可以使用 drm_dev_enter()drm_dev_exit() 以无竞争的方式保护设备资源。这本质上是注销设备,如 drm_dev_unregister(),但可以在仍有 dev 的开放用户时调用。

void drm_dev_set_dma_dev(struct drm_device *dev, struct device *dma_dev)

为 DRM 设备设置 DMA 设备

参数

struct drm_device *dev

DRM 设备

struct device *dma_dev

DMA 设备或 NULL

描述

设置给定 DRM 设备的 DMA 设备。仅当 DMA 设备与 DRM 设备的父设备不同时才需要。调用此函数后,DRM 设备将持有 dma_dev 上的引用。传递 NULL 以清除 DMA 设备。

int drm_dev_wedged_event(struct drm_device *dev, unsigned long method)

生成设备卡住的 uevent

参数

struct drm_device *dev

DRM 设备

unsigned long method

用于恢复的方法

描述

这将为 dev 指定的 DRM 设备生成一个设备卡住的 uevent。所选的恢复 method 将按副作用从少到多的顺序在 uevent 环境中作为 WEDGED=<method1>[,..,<methodN>] 发送。如果调用者不确定恢复或 method 未知 (0),则将改为发送 WEDGED=unknown

有关更多详细信息,请参阅 用户空间接口 中的“设备卡住”章节。

返回值

成功时返回 0,否则返回负错误代码。

void *__drm_dev_alloc(struct device *parent, const struct drm_driver *driver, size_t size, size_t offset)

drm_device 实例的分配

参数

struct device *parent

父设备对象

const struct drm_driver *driver

DRM 驱动程序

size_t size

包含 struct drm_device 的结构的大小

size_t offset

drm_device 在容器中的偏移量。

描述

任何驱动程序都应使用它,但它是用于相应的 Rust 抽象的专用接口。

这与 devm_drm_dev_alloc() 相同,但没有通过父设备的相应资源管理,但与 drm_dev_alloc() 不同,因为后者是已弃用的版本,不支持子类化。

返回值

指向新 DRM 设备的指针,如果失败,则为 ERR_PTR。

struct drm_device *drm_dev_alloc(const struct drm_driver *driver, struct device *parent)

分配新的 DRM 设备

参数

const struct drm_driver *driver

用于为其分配设备的 DRM 驱动程序

struct device *parent

父设备对象

描述

这是 devm_drm_dev_alloc() 的已弃用版本,它不支持通过在驱动程序私有结构中嵌入 struct drm_device 来实现子类化,也不支持通过 devres 进行自动清理。

返回值

指向新 DRM 设备的指针,如果失败,则为 ERR_PTR。

void drm_dev_get(struct drm_device *dev)

获取 DRM 设备的引用

参数

struct drm_device *dev

要获取引用的设备或 NULL

描述

这将使 dev 的引用计数增加 1。调用此函数时,必须已经拥有一个引用。使用 drm_dev_put() 再次删除此引用。

此函数永远不会失败。但是,此函数不提供任何关于设备是否处于活动状态或正在运行的保证。它仅提供对该对象及其关联内存的引用。

void drm_dev_put(struct drm_device *dev)

删除 DRM 设备的引用

参数

struct drm_device *dev

要删除引用的设备或 NULL

描述

这将使 dev 的引用计数减少 1。如果引用计数降至零,则销毁该设备。

struct dmem_cgroup_region *drmm_cgroup_register_region(struct drm_device *dev, const char *region_name, u64 size)

将 DRM 设备的区域注册到 cgroups

参数

struct drm_device *dev

区域的设备

const char *region_name

要注册的区域名称

u64 size

区域大小(以字节为单位)

描述

这将使 dev 的引用计数减少 1。如果引用计数降至零,则销毁该设备。

int drm_dev_register(struct drm_device *dev, unsigned long flags)

注册 DRM 设备

参数

struct drm_device *dev

要注册的设备

unsigned long flags

传递给驱动程序的 .load() 函数的标志

描述

向系统注册 DRM 设备 dev,向用户空间公布设备并启动正常设备操作。必须先前通过 drm_dev_init() 初始化 dev

切勿在任何设备上调用此函数两次!

注意

为了确保与现有驱动程序方法的向后兼容性,此函数在注册设备节点后调用 drm_driver.load 方法,从而导致竞争条件。因此,不建议使用 drm_driver.load 方法,驱动程序必须在调用 drm_dev_register() 之前执行所有初始化。

返回值

成功时返回 0,失败时返回负错误代码。

void drm_dev_unregister(struct drm_device *dev)

注销 DRM 设备

参数

struct drm_device *dev

要注销的设备

描述

从系统注销 DRM 设备。这与 drm_dev_register() 相反,但不取消分配设备。调用者必须调用 drm_dev_put() 以删除其最终引用,除非它使用 devres 管理(如使用 devm_drm_dev_alloc() 分配的设备),在这种情况下,已经注册了解除操作。

热插拔设备的一种特殊注销形式是 drm_dev_unplug(),可以在仍有 dev 的开放用户时调用。

这应该在设备拆卸代码中首先调用,以确保用户空间无法再访问设备实例。

驱动程序加载

组件助手使用

建议驱动由多个独立硬件块组成的逻辑设备的 DRM 驱动程序使用 组件助手库。为了保持一致性并更好地重用代码,适用以下准则

内存管理器初始化

每个 DRM 驱动程序都需要一个内存管理器,该管理器必须在加载时初始化。DRM 当前包含两个内存管理器,即转换表管理器 (TTM) 和图形执行管理器 (GEM)。本文档仅描述 GEM 内存管理器的使用。有关详细信息,请参阅?

其他设备配置

配置期间 PCI 设备可能需要的另一项任务是映射视频 BIOS。在许多设备上,VBIOS 描述了设备配置、LCD 面板时序(如果有)并包含指示设备状态的标志。可以使用 pci_map_rom() 调用来完成 BIOS 的映射,这是一个方便的函数,用于处理映射实际 ROM,无论它是否已阴影到内存中(通常在地址 0xc0000)或存在于 ROM BAR 中的 PCI 设备上。请注意,在映射 ROM 并提取任何必要信息后,应取消映射 ROM;在许多设备上,ROM 地址解码器与其他 BAR 共享,因此保持映射状态可能会导致不良行为,如挂起或内存损坏。

托管资源

受 struct device 托管资源的启发,但与 struct drm_device 的生命周期相关联,struct drm_device 可能比底层物理设备存在的时间更长,通常是当用户空间有一些打开的文件和其他资源句柄仍处于打开状态时。

可以使用 drmm_add_action() 添加发布操作,可以使用 drmm_kmalloc() 和相关函数直接完成内存分配。所有内容将在最终的 drm_dev_put() 中以添加发布操作的相反顺序释放,并且自驱动程序加载开始时使用 devm_drm_dev_alloc() 分配内存。

请注意,也可以在驱动程序的生命周期内添加和删除发布操作和托管内存,所有函数都是完全并发安全的。但建议仅将托管资源用于在 drm_device 实例的生命周期内很少或从未更改的资源。

void drmm_release_action(struct drm_device *dev, drmres_release_t action, void *data)

drm_device 释放托管操作

参数

struct drm_device *dev

DRM 设备

drmres_release_t action

当释放 dev 时将调用的函数

void *data

传递给 action 的不透明指针

描述

此函数立即调用先前由 drmm_add_action() 添加的 actionaction 会从 dev 的清理操作列表中移除,这意味着它不会在最终的 drm_dev_put() 中被调用。

void *drmm_kmalloc(struct drm_device *dev, size_t size, gfp_t gfp)

drm_device 管理的 kmalloc()

参数

struct drm_device *dev

DRM 设备

size_t size

内存分配的大小

gfp_t gfp

GFP 分配标志

描述

这是 drm_device 管理的 kmalloc() 版本。分配的内存将在最终的 drm_dev_put() 上自动释放。内存也可以通过调用 drmm_kfree() 在最终的 drm_dev_put() 之前释放。

char *drmm_kstrdup(struct drm_device *dev, const char *s, gfp_t gfp)

drm_device 管理的 kstrdup()

参数

struct drm_device *dev

DRM 设备

const char *s

要复制的 0 结尾的字符串

gfp_t gfp

GFP 分配标志

描述

这是 drm_device 管理的 kstrdup() 版本。分配的内存将在最终的 drm_dev_put() 上自动释放,并且工作方式与通过 drmm_kmalloc() 获得的内存分配完全相同。

void drmm_kfree(struct drm_device *dev, void *data)

drm_device 管理的 kfree()

参数

struct drm_device *dev

DRM 设备

void *data

要释放的内存分配

描述

这是 drm_device 管理的 kfree() 版本,可用于在 dev 的最终 drm_dev_put() 之前释放通过 drmm_kmalloc() 或其任何相关函数分配的内存。

drmm_add_action

drmm_add_action (dev, action, data)

drm_device 添加托管的释放操作

参数

dev

DRM 设备

action

dev 被释放时应该调用的函数

data

传递给 action 的不透明指针

描述

此函数将带有可选参数 datarelease 操作添加到 dev 的清理操作列表中。清理操作将在 dev 的最终 drm_dev_put() 调用中以相反的顺序运行。

drmm_add_action_or_reset

drmm_add_action_or_reset (dev, action, data)

drm_device 添加托管的释放操作

参数

dev

DRM 设备

action

dev 被释放时应该调用的函数

data

传递给 action 的不透明指针

描述

类似于 drmm_add_action(),唯一的区别是,如果失败,action 会被直接调用,以便对失败进行必要的清理工作。

void *drmm_kzalloc(struct drm_device *dev, size_t size, gfp_t gfp)

drm_device 管理的 kzalloc()

参数

struct drm_device *dev

DRM 设备

size_t size

内存分配的大小

gfp_t gfp

GFP 分配标志

描述

这是 drm_device 管理的 kzalloc() 版本。分配的内存将在最终的 drm_dev_put() 上自动释放。内存也可以通过调用 drmm_kfree() 在最终的 drm_dev_put() 之前释放。

void *drmm_kmalloc_array(struct drm_device *dev, size_t n, size_t size, gfp_t flags)

drm_device 管理的 kmalloc_array()

参数

struct drm_device *dev

DRM 设备

size_t n

要分配的数组元素的数量

size_t size

数组成员的大小

gfp_t flags

GFP 分配标志

描述

这是 drm_device 管理的 kmalloc_array() 版本。分配的内存将在最终的 drm_dev_put() 上自动释放,并且工作方式与通过 drmm_kmalloc() 获得的内存分配完全相同。

void *drmm_kcalloc(struct drm_device *dev, size_t n, size_t size, gfp_t flags)

drm_device 管理的 kcalloc()

参数

struct drm_device *dev

DRM 设备

size_t n

要分配的数组元素的数量

size_t size

数组成员的大小

gfp_t flags

GFP 分配标志

描述

这是 drm_device 管理的 kcalloc() 版本。分配的内存将在最终的 drm_dev_put() 上自动释放,并且工作方式与通过 drmm_kmalloc() 获得的内存分配完全相同。

drmm_mutex_init

drmm_mutex_init (dev, lock)

参数

dev

DRM 设备

lock

要初始化的锁

返回值

成功时为 0,否则为负 errno 代码。

描述

这是 drm_device 管理的 mutex_init() 版本。初始化的锁在最终的 drm_dev_put() 上自动销毁。

打开/关闭、文件操作和 IOCTL

文件操作

驱动程序必须定义文件操作结构,该结构构成 DRM 用户空间 API 入口点,即使大多数操作都在 DRM 核心中实现。生成的 struct file_operations 必须存储在 drm_driver.fops 字段中。强制函数是 drm_open()drm_read()drm_ioctl()drm_compat_ioctl()(如果启用了 CONFIG_COMPAT)。请注意,如果 CONFIG_COMPAT=n,则 drm_compat_ioctl 将为 NULL,因此无需在代码中散布 #ifdef。如果驱动程序实现需要 32/64 位兼容性支持的私有 ioctl,则必须提供自己的 file_operations.compat_ioctl 处理程序,该处理程序处理私有 ioctl 并为核心 ioctl 调用 drm_compat_ioctl()

此外,drm_read()drm_poll() 提供对 DRM 事件的支持。DRM 事件是一种通用且可扩展的方式,用于通过文件描述符将异步事件发送到用户空间。它们用于通过 KMS API 发送垂直消隐事件和页面翻转完成。但是驱动程序也可以将其用于自己的需求,例如,指示渲染完成。

对于驱动程序侧事件接口,请参阅 drm_event_reserve_init()drm_send_event() 作为主要起点。

内存映射的实现将根据驱动程序管理内存的方式而有所不同。对于基于 GEM 的驱动程序,这是 drm_gem_mmap()

DRM 用户空间 API 不支持其他文件操作。总的来说,以下是一个示例 file_operations 结构

static const example_drm_fops = {
        .owner = THIS_MODULE,
        .open = drm_open,
        .release = drm_release,
        .unlocked_ioctl = drm_ioctl,
        .compat_ioctl = drm_compat_ioctl, // NULL if CONFIG_COMPAT=n
        .poll = drm_poll,
        .read = drm_read,
        .mmap = drm_gem_mmap,
};

对于普通的基于 GEM 的驱动程序,有 DEFINE_DRM_GEM_FOPS() 宏,对于基于 DMA 的驱动程序,有 DEFINE_DRM_GEM_DMA_FOPS() 宏,以简化此操作。

驱动程序的 file_operations 必须存储在 drm_driver.fops 中。

有关驱动程序私有 IOCTL 处理的更多详细讨论,请参见 用户区接口章节中的 IOCTL 支持

struct drm_minor

DRM 设备次要结构

定义:

struct drm_minor {
};

成员

描述

此结构表示 /dev 中设备节点的 DRM 次要编号。对于驱动程序完全不透明,驱动程序绝不应直接检查它。相反,驱动程序应仅与 struct drm_file 以及当然 struct drm_device 交互,这也是驱动程序私有数据和资源可以附加到的地方。

struct drm_pending_event

为用户空间排队以供读取的事件

定义:

struct drm_pending_event {
    struct completion *completion;
    void (*completion_release)(struct completion *completion);
    struct drm_event *event;
    struct dma_fence *fence;
    struct drm_file *file_priv;
    struct list_head link;
    struct list_head pending_link;
};

成员

completion

当调用 drm_send_event() 时发出信号的内核内部完成的可选指针,对于内部与非阻塞操作同步很有用。

completion_release

可选回调,目前仅由原子模式设置助手用于清理存储 completion 的结构的引用计数。

event

应该发送到用户空间以使用 drm_read() 读取的实际事件的指针。可以是可选的,因为现在事件也用于通过 completion 向内核内部线程发出信号或使用 fence 进行 DMA 事务。

fence

可选的 DMA fence,用于取消阻止依赖于此事件表示的非阻塞 DRM 操作的其他硬件事务。

file_priv

应将 event 传递到的 struct drm_file。仅在设置 event 时设置。

link

双向链表,用于跟踪此事件。驱动程序可以在调用 drm_send_event() 之前使用此链表,之后此列表条目由核心拥有以进行自己的簿记。

pending_link

drm_file.pending_event_list 上的条目,用于跟踪 file_priv 的所有挂起事件,以便在用户空间关闭文件之前正确展开它们。

描述

这表示 DRM 事件。驱动程序可以将其用作通用完成机制,该机制支持内核内部 struct completionstruct dma_fence 和 DRM 特定的 struct drm_event 传递机制。

struct drm_file

DRM 文件私有数据

定义:

struct drm_file {
    bool authenticated;
    bool stereo_allowed;
    bool universal_planes;
    bool atomic;
    bool aspect_ratio_allowed;
    bool writeback_connectors;
    bool was_master;
    bool is_master;
    bool supports_virtualized_cursor_plane;
    struct drm_master *master;
    spinlock_t master_lookup_lock;
    struct pid __rcu *pid;
    u64 client_id;
    drm_magic_t magic;
    struct list_head lhead;
    struct drm_minor *minor;
    struct idr object_idr;
    spinlock_t table_lock;
    struct idr syncobj_idr;
    spinlock_t syncobj_table_lock;
    struct file *filp;
    void *driver_priv;
    struct list_head fbs;
    struct mutex fbs_lock;
    struct list_head blobs;
    wait_queue_head_t event_wait;
    struct list_head pending_event_list;
    struct list_head event_list;
    int event_space;
    struct mutex event_read_lock;
    struct drm_prime_file_private prime;
    const char *client_name;
    struct mutex client_name_lock;
};

成员

authenticated

是否允许客户端提交渲染,对于旧节点,这意味着必须进行身份验证。

另请参阅 关于主节点和身份验证的部分

stereo_allowed

当客户端要求我们公开立体 3D 模式标志时为 True。

universal_planes

如果客户端了解平面列表中的 CRTC 主平面和光标平面,则为 True。当设置 atomic 时自动设置。

atomic

如果客户端了解原子属性,则为 True。

aspect_ratio_allowed

如果客户端可以处理图片纵横比,并且已请求随模式一起传递此信息,则为 True。

writeback_connectors

如果客户端了解写回连接器,则为 True

was_master

此客户端具有或曾经具有主控功能。受结构 drm_device.master_mutex 保护。

这用于确保不强制执行 CAP_SYS_ADMIN,如果客户端过去或曾经是主节点。

is_master

此客户端是 master 的创建者。受结构 drm_device.master_mutex 保护,并由 master_lookup_lock 序列化。

另请参阅 关于主节点和身份验证的部分

supports_virtualized_cursor_plane

此客户端能够处理光标平面,并且光标平面受到虚拟化驱动程序施加的限制。

这意味着光标平面的行为必须类似于光标,即跟踪光标移动。它还需要客户端在光标平面上设置热点属性。

master

此节点当前关联的主节点。受结构 drm_device.master_mutex 保护,并由 master_lookup_lock 序列化。

仅当 drm_is_primary_client() 返回 true 时才相关。请注意,如果主节点是当前活动的主节点,则此节点仅与 drm_device.master 匹配。

要更新 master,需要同时持有 drm_device.master_mutexmaster_lookup_lock,因此持有它们中的任何一个对于读取侧都是安全且足够的。

当取消引用此指针时,要么持有结构 drm_device.master_mutex 以便在指针的使用期间,要么使用 drm_file_get_master(),如果当前未持有结构 drm_device.master_mutex 并且没有其他需要持有它的需求。这可以防止 master 在使用期间被释放。

另请参阅 authenticationis_master 以及 关于主节点和身份验证的部分

master_lookup_lock

序列化 master

pid

正在使用此文件的进程。

只能在 rcu_read_lock 或等效项下取消引用。

更新受 dev->filelist_mutex 保护,并且在 RCU 宽限期后必须删除引用,以适应无锁读取器。

client_id

fdinfo 的唯一 ID

magic

身份验证魔术,请参阅 authenticated

lhead

DRM 设备的所有打开文件的列表,链接到 drm_device.filelist。受 drm_device.filelist_mutex 保护。

minor

此文件的 struct drm_minor

object_idr

mm 对象句柄到对象指针的映射。由 GEM 子系统使用。受 table_lock 保护。

table_lock

保护 object_idr

syncobj_idr

同步对象句柄到对象指针的映射。

syncobj_table_lock

保护 syncobj_idr

filp

指向核心文件结构的指针。

driver_priv

驱动程序私有数据的可选指针。可以在 drm_driver.open 中分配,并且应该在 drm_driver.postclose 中释放。

fbs

与此文件关联的 struct drm_framebuffer 列表,使用 drm_framebuffer.filp_head 条目。

fbs_lock 保护。请注意,fbs 列表持有对帧缓冲区对象的引用,以防止它过早消失。

fbs_lock

保护 fbs

blobs

用户创建的 blob 属性;这保留对属性的引用。

drm_mode_config.blob_lock 保护;

event_wait

添加到 event_list 的新事件的等待队列。

pending_event_list

挂起的 struct drm_pending_event 的列表,用于清理挂起的事件,以防此文件在发出事件信号之前关闭。使用 drm_pending_event.pending_link 条目。

drm_device.event_lock 保护。

event_list

struct drm_pending_event 的列表,已准备好通过 drm_read() 传递到用户空间。使用 drm_pending_event.link 条目。

drm_device.event_lock 保护。

event_space

可用事件空间,以防止用户空间耗尽内核内存。目前限制为 4KB 的相当任意的值。

event_read_lock

序列化 drm_read()

prime

PRIME 缓冲区共享代码使用的每个文件的缓冲区缓存。

client_name

用户空间提供的名称;对于核算和调试很有用。

client_name_lock

保护 client_name

描述

此结构跟踪每个打开文件描述符的 DRM 状态。

bool drm_is_primary_client(const struct drm_file *file_priv)

这是否是主节点的打开文件

参数

const struct drm_file *file_priv

DRM 文件

描述

如果这是主节点的打开文件,即 file_privdrm_file.minor 是主节点,则返回 true。

另请参阅 关于主节点和身份验证的部分

bool drm_is_render_client(const struct drm_file *file_priv)

这是否是渲染节点的打开文件

参数

const struct drm_file *file_priv

DRM 文件

描述

如果这是渲染节点的打开文件,即 file_privdrm_file.minor 是渲染节点,则返回 true。

另请参阅 关于渲染节点的部分

bool drm_is_accel_client(const struct drm_file *file_priv)

这是否是计算加速节点的打开文件

参数

const struct drm_file *file_priv

DRM 文件

描述

如果这是计算加速节点的打开文件,即 file_privdrm_file.minor 是加速节点,则返回 true。

另请参阅 计算加速器子系统简介

struct drm_memory_stats

GEM 对象统计信息关联

定义:

struct drm_memory_stats {
    u64 shared;
    u64 private;
    u64 resident;
    u64 purgeable;
    u64 active;
};

成员

shared

进程间共享的 GEM 对象总大小

私有

GEM 对象总大小

常驻

GEM 对象后备页的总大小

可清除

可以清除的 GEM 对象总大小(常驻且不活跃)

活跃

在一个或多个引擎上活跃的 GEM 对象总大小

描述

drm_print_memory_stats() 使用

int drm_open(struct inode *inode, struct file *filp)

DRM 文件的 open 方法

参数

struct inode *inode

设备 inode

struct file *filp

文件指针。

描述

驱动程序必须使用此函数作为它们的 file_operations.open 方法。它会查找正确的 DRM 设备并实例化它的所有每个文件的资源。它还会调用 drm_driver.open 驱动程序回调。

返回值

成功时返回 0,失败时返回负的 errno 值。

int drm_release(struct inode *inode, struct file *filp)

DRM 文件的 release 方法

参数

struct inode *inode

设备 inode

struct file *filp

文件指针。

描述

驱动程序必须使用此函数作为它们的 file_operations.release 方法。它会释放与打开的文件关联的所有资源。如果这是 DRM 设备的最后一个打开的文件,它还会恢复活跃的内核 DRM 客户端。

返回值

总是成功并返回 0。

int drm_release_noglobal(struct inode *inode, struct file *filp)

DRM 文件的 release 方法

参数

struct inode *inode

设备 inode

struct file *filp

文件指针。

描述

驱动程序可以使用此函数作为它们的 file_operations.release 方法。它会在获取 drm_global_mutex 之前释放与打开的文件关联的所有资源。如果这是 DRM 设备的最后一个打开的文件,那么它会恢复活跃的内核 DRM 客户端。

返回值

总是成功并返回 0。

ssize_t drm_read(struct file *filp, char __user *buffer, size_t count, loff_t *offset)

DRM 文件的 read 方法

参数

struct file *filp

文件指针

char __user *buffer

用户空间的读取目标指针

size_t count

要读取的字节数

loff_t *offset

读取偏移量

描述

如果驱动程序使用 DRM 事件来进行异步信号发送到用户空间,则必须使用此函数作为它们的 file_operations.read 方法。由于 KMS API 使用事件来进行垂直消隐和页面翻转完成,这意味着所有现代显示驱动程序都必须使用它。

offset 被忽略,DRM 事件像管道一样被读取。drm_poll() 提供轮询支持。

此函数将只读取一个完整的事件。因此,用户空间必须提供足够大的缓冲区来容纳任何事件,以确保向前进展。由于最大事件空间当前为 4K,建议使用它来确保安全。

返回值

读取的字节数(总是与完整事件对齐,并且可以为 0),或者失败时返回负的错误代码。

__poll_t drm_poll(struct file *filp, struct poll_table_struct *wait)

DRM 文件的 poll 方法

参数

struct file *filp

文件指针

struct poll_table_struct *wait

poll 等待表

描述

如果驱动程序使用 DRM 事件来进行异步信号发送到用户空间,则必须使用此函数作为它们的 file_operations.read 方法。由于 KMS API 使用事件来进行垂直消隐和页面翻转完成,这意味着所有现代显示驱动程序都必须使用它。

另请参阅 drm_read()

返回值

指示文件当前状态的 POLL 标志的掩码。

int drm_event_reserve_init_locked(struct drm_device *dev, struct drm_file *file_priv, struct drm_pending_event *p, struct drm_event *e)

初始化 DRM 事件并为其预留空间

参数

struct drm_device *dev

DRM 设备

struct drm_file *file_priv

DRM 文件私有数据

struct drm_pending_event *p

挂起事件的跟踪结构

struct drm_event *e

要传递给用户空间的实际事件数据

描述

此函数准备传入的事件以供最终传递。如果事件没有被传递(因为 IOCTL 稍后失败,在将任何内容排队之前),那么事件必须使用 drm_event_cancel_free() 取消和释放。成功初始化的事件应该使用 drm_send_event()drm_send_event_locked() 发送,以向用户空间发出异步事件完成的信号。

如果调用者将 p 嵌入到更大的结构中,它必须使用 kmalloc 分配,并且 p 必须是第一个成员元素。

这是 drm_event_reserve_init() 的锁定版本,适用于已经持有 drm_device.event_lock 的调用者。

返回值

成功时返回 0,失败时返回负的错误代码。

int drm_event_reserve_init(struct drm_device *dev, struct drm_file *file_priv, struct drm_pending_event *p, struct drm_event *e)

初始化 DRM 事件并为其预留空间

参数

struct drm_device *dev

DRM 设备

struct drm_file *file_priv

DRM 文件私有数据

struct drm_pending_event *p

挂起事件的跟踪结构

struct drm_event *e

要传递给用户空间的实际事件数据

描述

此函数准备传入的事件以供最终传递。如果事件没有被传递(因为 IOCTL 稍后失败,在将任何内容排队之前),那么事件必须使用 drm_event_cancel_free() 取消和释放。成功初始化的事件应该使用 drm_send_event()drm_send_event_locked() 发送,以向用户空间发出异步事件完成的信号。

如果调用者将 p 嵌入到更大的结构中,它必须使用 kmalloc 分配,并且 p 必须是第一个成员元素。

已经持有 drm_device.event_lock 的调用者应该改用 drm_event_reserve_init_locked()

返回值

成功时返回 0,失败时返回负的错误代码。

void drm_event_cancel_free(struct drm_device *dev, struct drm_pending_event *p)

释放 DRM 事件并释放其空间

参数

struct drm_device *dev

DRM 设备

struct drm_pending_event *p

挂起事件的跟踪结构

描述

此函数释放使用 drm_event_reserve_init() 初始化的事件 p 并释放任何已分配的空间。当无法提交非阻塞操作并且需要中止时,它用于取消事件。

void drm_send_event_timestamp_locked(struct drm_device *dev, struct drm_pending_event *e, ktime_t timestamp)

将 DRM 事件发送到文件描述符

参数

struct drm_device *dev

DRM 设备

struct drm_pending_event *e

要传递的 DRM 事件

ktime_t timestamp

要在内核 CLOCK_MONOTONIC 时域中为 fence 事件设置的时间戳

描述

此函数将事件 e(使用 drm_event_reserve_init() 初始化)发送到其关联的用户空间 DRM 文件。调用者必须已经持有 drm_device.event_lock

请注意,当相应的 DRM 文件关闭时,核心将负责取消链接和解除事件。驱动程序不必担心此事件的 DRM 文件是否仍然存在,并且可以在异步工作完成后无条件地调用此函数。

void drm_send_event_locked(struct drm_device *dev, struct drm_pending_event *e)

将 DRM 事件发送到文件描述符

参数

struct drm_device *dev

DRM 设备

struct drm_pending_event *e

要传递的 DRM 事件

描述

此函数将事件 e(使用 drm_event_reserve_init() 初始化)发送到其关联的用户空间 DRM 文件。调用者必须已经持有 drm_device.event_lock,有关未锁定版本,请参见 drm_send_event()

请注意,当相应的 DRM 文件关闭时,核心将负责取消链接和解除事件。驱动程序不必担心此事件的 DRM 文件是否仍然存在,并且可以在异步工作完成后无条件地调用此函数。

void drm_send_event(struct drm_device *dev, struct drm_pending_event *e)

将 DRM 事件发送到文件描述符

参数

struct drm_device *dev

DRM 设备

struct drm_pending_event *e

要传递的 DRM 事件

描述

此函数将事件 e(使用 drm_event_reserve_init() 初始化)发送到其关联的用户空间 DRM 文件。此函数获取 drm_device.event_lock,对于已经持有此锁的调用者,请参见 drm_send_event_locked()

请注意,当相应的 DRM 文件关闭时,核心将负责取消链接和解除事件。驱动程序不必担心此事件的 DRM 文件是否仍然存在,并且可以在异步工作完成后无条件地调用此函数。

void drm_print_memory_stats(struct drm_printer *p, const struct drm_memory_stats *stats, enum drm_gem_object_status supported_status, const char *region)

一个用于打印内存统计信息的助手函数

参数

struct drm_printer *p

用于打印输出的打印机

const struct drm_memory_stats *stats

收集的内存统计信息

enum drm_gem_object_status supported_status

可用可选统计信息的位掩码

const char *region

内存区域

void drm_show_memory_stats(struct drm_printer *p, struct drm_file *file)

用于收集和显示标准 fdinfo 内存统计信息的助手函数

参数

struct drm_printer *p

用于打印输出的打印机

struct drm_file *file

DRM 文件

描述

用于迭代 GEM 对象(在指定文件中分配了句柄)的助手函数。

void drm_show_fdinfo(struct seq_file *m, struct file *f)

DRM 文件 fops 的助手函数

参数

struct seq_file *m

输出流

struct file *f

设备文件实例

描述

用于实现 fdinfo 的助手函数,供用户空间查询使用 GPU 的进程的使用统计信息等。另请参阅 drm_driver.show_fdinfo

有关文本输出格式的说明,请参见 DRM 客户端使用统计信息

void drm_file_err(struct drm_file *file_priv, const char *fmt, ...)

记录与 drm_file 关联的进程名称、pid 和 client_name

参数

struct drm_file *file_priv

感兴趣的进程名称和 pid 上下文

const char *fmt

printf() 样式的格式字符串

...

可变参数

描述

用于需要记录进程详细信息(如名称和 pid 等)以及用户日志的客户端的助手函数。

杂项实用程序

打印机

dev_printk()、seq_printf() 等的简单包装器。允许相同的调试代码用于 debugfs 和 printk 日志记录。

例如

void log_some_info(struct drm_printer *p)
{
        drm_printf(p, "foo=%d\n", foo);
        drm_printf(p, "bar=%d\n", bar);
}

#ifdef CONFIG_DEBUG_FS
void debugfs_show(struct seq_file *f)
{
        struct drm_printer p = drm_seq_file_printer(f);
        log_some_info(&p);
}
#endif

void some_other_function(...)
{
        struct drm_printer p = drm_info_printer(drm->dev);
        log_some_info(&p);
}
enum drm_debug_category

DRM 调试类别

常量

DRM_UT_CORE

在通用 DRM 代码中使用:drm_ioctl.c、drm_mm.c、drm_memory.c 等。

DRM_UT_DRIVER

在驱动程序的供应商特定部分中使用:i915、radeon 等宏。

DRM_UT_KMS

在 modesetting 代码中使用。

DRM_UT_PRIME

在 prime 代码中使用。

DRM_UT_ATOMIC

在 atomic 代码中使用。

DRM_UT_VBL

用于 vblank 代码中的详细调试消息。

DRM_UT_STATE

用于详细的 atomic 状态调试。

DRM_UT_LEASE

在 lease 代码中使用。

DRM_UT_DP

在 DP 代码中使用。

DRM_UT_DRMRES

在 DRM 托管资源代码中使用。

描述

每个 DRM 调试日志记录宏都使用一个特定的类别,并且日志记录由 drm.debug 模块参数过滤。此枚举指定接口的值。

每个 DRM_DEBUG_<CATEGORY> 宏都记录到 DRM_UT_<CATEGORY> 类别,除了 DRM_DEBUG() 记录到 DRM_UT_CORE。

启用详细调试消息是通过 drm.debug 参数完成的,每个类别都由一位启用

  • drm.debug=0x1 将启用 CORE 消息

  • drm.debug=0x2 将启用 DRIVER 消息

  • drm.debug=0x3 将启用 CORE 和 DRIVER 消息

  • ...

  • drm.debug=0x1ff 将启用所有消息

一个有趣的功能是,可以通过在其 sysfs 节点中回显调试值来在运行时启用详细日志记录

# echo 0xf > /sys/module/drm/parameters/debug
struct drm_printer

DRM 输出“流”

定义:

struct drm_printer {
};

成员

描述

请勿直接使用结构成员。使用 drm_printer_seq_file()、drm_printer_info() 等进行初始化。并使用 drm_printf() 进行输出。

void drm_vprintf(struct drm_printer *p, const char *fmt, va_list *va)

打印到 drm_printer

参数

struct drm_printer *p

drm_printer

const char *fmt

格式字符串

va_list *va

va_list

drm_printf_indent

drm_printf_indent (printer, indent, fmt, ...)

使用缩进打印到 drm_printer

参数

printer

DRM 打印机

indent

制表符缩进级别(最大 5)

fmt

格式字符串

...

可变参数

struct drm_print_iterator

与 drm_printer_coredump 一起使用的本地结构

定义:

struct drm_print_iterator {
    void *data;
    ssize_t start;
    ssize_t remain;
};

成员

data

指向 devcoredump 输出缓冲区的指针,如果使用 drm_printer_coredump 来确定 devcoredump 的大小,则可以为 NULL

start

在缓冲区中开始写入的偏移量

remain

此迭代要写入的字节数

struct drm_printer drm_coredump_printer(struct drm_print_iterator *iter)

构造一个 drm_printer,它可以将输出输出到来自 devcoredump 的读取函数的缓冲区

参数

struct drm_print_iterator *iter

指向读取实例的 struct drm_print_iterator 的指针

描述

此包装器扩展了 drm_printf() 以使用 dev_coredumpm() 回调函数。传入的 drm_print_iterator 结构包含缓冲区指针、大小和偏移量,这些都从 devcoredump 传入。

例如

void coredump_read(char *buffer, loff_t offset, size_t count,
        void *data, size_t datalen)
{
        struct drm_print_iterator iter;
        struct drm_printer p;

        iter.data = buffer;
        iter.start = offset;
        iter.remain = count;

        p = drm_coredump_printer(&iter);

        drm_printf(p, "foo=%d\n", foo);
}

void makecoredump(...)
{
        ...
        dev_coredumpm(dev, THIS_MODULE, data, 0, GFP_KERNEL,
                coredump_read, ...)
}

上面的示例的时间复杂度为 O(N^2),其中 N 是 devcoredump 的大小。这对于小型的 devcoredump 来说是可以接受的,但对于较大的 devcoredump 来说,扩展性较差。

drm_coredump_printer 的另一个用例是在 dev_coredump() 回调之前将 devcoredump 捕获到保存的缓冲区中。这涉及两次传递:一次确定 devcoredump 的大小,另一次将其打印到缓冲区中。然后,在 dev_coredump() 中,从保存的缓冲区复制到 devcoredump 读取缓冲区中。

例如

char *devcoredump_saved_buffer;

ssize_t __coredump_print(char *buffer, ssize_t count, ...)
{
        struct drm_print_iterator iter;
        struct drm_printer p;

        iter.data = buffer;
        iter.start = 0;
        iter.remain = count;

        p = drm_coredump_printer(&iter);

        drm_printf(p, "foo=%d\n", foo);
        ...
        return count - iter.remain;
}

void coredump_print(...)
{
        ssize_t count;

        count = __coredump_print(NULL, INT_MAX, ...);
        devcoredump_saved_buffer = kvmalloc(count, GFP_KERNEL);
        __coredump_print(devcoredump_saved_buffer, count, ...);
}

void coredump_read(char *buffer, loff_t offset, size_t count,
                   void *data, size_t datalen)
{
        ...
        memcpy(buffer, devcoredump_saved_buffer + offset, count);
        ...
}

上面的示例的时间复杂度为 O(N*2),其中 N 是 devcoredump 的大小。对于较大的 devcoredump,这比之前的示例具有更好的扩展性。

返回值

drm_printer 对象

bool drm_coredump_printer_is_full(struct drm_printer *p)

DRM coredump 打印机输出已满

参数

struct drm_printer *p

DRM coredump 打印机

描述

DRM 打印机输出已满,可用于在打印机已满后短路 coredump 打印。

返回值

如果 DRM coredump 打印机输出缓冲区已满,则返回 True,否则返回 False

struct drm_printer drm_seq_file_printer(struct seq_file *f)

构造一个 drm_printer,它输出到 seq_file

参数

struct seq_file *f

要输出到的 struct seq_file

返回值

drm_printer 对象

struct drm_printer drm_info_printer(struct device *dev)

构造一个 drm_printer,它输出到 dev_printk()

参数

struct device *dev

struct device 指针

返回值

drm_printer 对象

struct drm_printer drm_dbg_printer(struct drm_device *drm, enum drm_debug_category category, const char *prefix)

构造一个用于 DRM 设备特定输出的 drm_printer

参数

struct drm_device *drm

struct drm_device 指针,或 NULL

enum drm_debug_category category

要使用的调试类别

const char *prefix

调试输出前缀,或 NULL 表示无前缀

返回值

drm_printer 对象

struct drm_printer drm_err_printer(struct drm_device *drm, const char *prefix)

构造一个输出到 drm_err() 的 drm_printer

参数

struct drm_device *drm

struct drm_device 指针

const char *prefix

调试输出前缀,或 NULL 表示无前缀

返回值

drm_printer 对象

struct drm_printer drm_line_printer(struct drm_printer *p, const char *prefix, unsigned int series)

构造一个 drm_printer,它使用行号作为输出的前缀

参数

struct drm_printer *p

实际生成输出的 struct drm_printer

const char *prefix

可选的输出前缀,如果没有前缀则为 NULL

unsigned int series

可选的唯一序列标识符,如果要在输出中省略标识符则为 0

描述

此打印机可用于提高捕获输出的鲁棒性,以确保我们不会丢失输出的任何中间行。在捕获一些崩溃数据时很有用。

示例 1

void crash_dump(struct drm_device *drm)
{
        static unsigned int id;
        struct drm_printer p = drm_err_printer(drm, "crash");
        struct drm_printer lp = drm_line_printer(&p, "dump", ++id);

        drm_printf(&lp, "foo");
        drm_printf(&lp, "bar");
}

上面的代码将打印到 dmesg 中,如下所示

[ ] 0000:00:00.0: [drm] *ERROR* crash dump 1.1: foo
[ ] 0000:00:00.0: [drm] *ERROR* crash dump 1.2: bar

示例 2

void line_dump(struct device *dev)
{
        struct drm_printer p = drm_info_printer(dev);
        struct drm_printer lp = drm_line_printer(&p, NULL, 0);

        drm_printf(&lp, "foo");
        drm_printf(&lp, "bar");
}

上面的代码将打印

[ ] 0000:00:00.0: [drm] 1: foo
[ ] 0000:00:00.0: [drm] 2: bar

返回值

drm_printer 对象

DRM_DEV_ERROR

DRM_DEV_ERROR (dev, fmt, ...)

错误输出。

参数

dev

设备指针

fmt

类似于 printf() 的格式字符串。

...

可变参数

注意

此方法已被弃用,请使用 drm_err() 或 dev_err()。

DRM_DEV_ERROR_RATELIMITED

DRM_DEV_ERROR_RATELIMITED (dev, fmt, ...)

速率限制错误输出。

参数

dev

设备指针

fmt

类似于 printf() 的格式字符串。

...

可变参数

注意

此方法已被弃用,请使用 drm_err_ratelimited() 或 dev_err_ratelimited()。

描述

类似于 DRM_ERROR(),但不会使日志泛滥。

DRM_DEV_DEBUG

DRM_DEV_DEBUG (dev, fmt, ...)

用于通用 DRM 代码的调试输出

参数

dev

设备指针

fmt

类似于 printf() 的格式字符串。

...

可变参数

注意

此方法已被弃用,请使用 drm_dbg_core()。

DRM_DEV_DEBUG_DRIVER

DRM_DEV_DEBUG_DRIVER (dev, fmt, ...)

用于驱动程序供应商特定部分的调试输出

参数

dev

设备指针

fmt

类似于 printf() 的格式字符串。

...

可变参数

注意

此方法已被弃用,请使用 drm_dbg() 或 dev_dbg()。

DRM_DEV_DEBUG_KMS

DRM_DEV_DEBUG_KMS (dev, fmt, ...)

用于模式设置代码的调试输出

参数

dev

设备指针

fmt

类似于 printf() 的格式字符串。

...

可变参数

注意

此方法已被弃用,请使用 drm_dbg_kms()。

void drm_puts(struct drm_printer *p, const char *str)

将常量字符串打印到 drm_printer

参数

struct drm_printer *p

drm 打印机

const char *str

常量字符串

描述

允许具有常量字符串选项的 drm_printer 类型使用它。

void drm_printf(struct drm_printer *p, const char *f, ...)

打印到 drm_printer

参数

struct drm_printer *p

drm_printer

const char *f

格式字符串

...

可变参数

void drm_print_bits(struct drm_printer *p, unsigned long value, const char *const bits[], unsigned int nbits)

将位打印到 drm_printer

参数

struct drm_printer *p

drm_printer

unsigned long value

字段值。

const char * const bits[]

带有位名称的数组。

unsigned int nbits

位名称数组的大小。

描述

以人类可读的形式打印位(例如,在标志字段中)。

void drm_print_regset32(struct drm_printer *p, struct debugfs_regset32 *regset)

将寄存器的内容打印到 drm_printer 流。

参数

struct drm_printer *p

drm 打印机

struct debugfs_regset32 *regset

要打印的寄存器列表。

描述

通常在驱动程序调试中,能够使用 debugfs 或在操作期间的特定点捕获寄存器的内容很有用。 这使驱动程序可以为两者提供单个寄存器列表。

void drm_print_hex_dump(struct drm_printer *p, const char *prefix, const u8 *buf, size_t len)

将十六进制转储打印到 drm_printer

参数

struct drm_printer *p

drm_printer

const char *prefix

每行的前缀,如果没有前缀则可能为 NULL

const u8 *buf

要转储的缓冲区

size_t len

缓冲区长度

描述

将十六进制转储打印到 drm_printer,每行有 16 个空格分隔的十六进制字节,可以选择在每行上带有前缀。 前缀后不会添加分隔符。

实用程序

不自然地属于其他位置的宏和内联函数

bool drm_can_sleep(void)

如果当前可以休眠,则返回 true

参数

void

没有参数

描述

此函数不应在新代码中使用。 对在原子上下文中运行的检查可能不起作用 - 请参见 linux/preempt.h。

FIXME:应删除 drm_can_sleep 的所有用户(请参见 TODO 列表

返回值

如果 kgdb 处于活动状态、我们在原子上下文中或 irq 被禁用,则为 False。

单元测试

KUnit

KUnit(内核单元测试框架)为 Linux 内核中的单元测试提供了一个通用框架。

本节介绍 DRM 子系统的具体信息。 有关 KUnit 的一般信息,请参阅 入门

如何运行测试?

为了方便运行测试套件,配置文件位于 drivers/gpu/drm/tests/.kunitconfig 中。 可以通过 kunit.py 按如下方式使用它

$ ./tools/testing/kunit/kunit.py run --kunitconfig=drivers/gpu/drm/tests \
        --kconfig_add CONFIG_VIRTIO_UML=y \
        --kconfig_add CONFIG_UML_PCI_OVER_VIRTIO=y

注意

包含在 .kunitconfig 中的配置应尽可能通用。 CONFIG_VIRTIO_UMLCONFIG_UML_PCI_OVER_VIRTIO 未包含在其中,因为它们仅对于用户模式 Linux 是必需的。

KUnit 覆盖率规则

KUnit 支持正逐步添加到 DRM 框架和辅助函数中。 目前对于框架和辅助函数没有强制要求必须具有 KUnit 测试。 但是,如果更改需要,影响 KUnit 测试已覆盖的函数或辅助函数的补丁必须提供测试。

旧版支持代码

本节简要介绍了一些旧的旧版支持代码,这些代码仅由旧的 DRM 驱动程序使用,这些驱动程序已对底层设备进行了所谓的影子附加,而不是注册为真正的驱动程序。 这还包括一些旧的通用缓冲区管理和命令提交代码。 不要在新的和现代的驱动程序中使用任何这些代码。

旧版挂起/恢复

DRM 核心提供了一些挂起/恢复代码,但希望获得完全挂起/恢复支持的驱动程序应提供 save() 和 restore() 函数。 这些函数在挂起、休眠或恢复时被调用,并且应在挂起或休眠状态之间执行您的设备所需的任何状态保存或恢复。

int (*suspend) (struct drm_device *, pm_message_t state); int (*resume) (struct drm_device *); 这些是旧版的挂起和恢复方法,适用于旧版的影子附加驱动程序注册函数。 新驱动程序应使用其总线类型提供的电源管理接口(通常通过 struct device_driver dev_pm_ops)并将这些方法设置为 NULL。

旧版 DMA 服务

这应涵盖核心如何支持 DMA 映射等。 这些函数已被弃用,不应使用。