DRM 内部机制

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

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

DRM 层为图形驱动程序提供了多项服务,其中许多服务由它通过 libdrm 提供的应用程序接口驱动,libdrm 是封装了大多数 DRM ioctl 的库。这些服务包括垂直同步事件处理、内存管理、输出管理、帧缓冲管理、命令提交和围栏、暂停/恢复支持以及 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 设备的所有各种子系统,例如内存管理、垂直同步处理、模式设置支持和初始输出配置,以及明显地初始化所有相应的硬件位。最后,当一切都启动并运行并为用户空间做好准备时,可以使用 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

电源状态为开

DRM_SWITCH_POWER_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 {
        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

总线设备的设备结构

managed

与此 drm_device 的生命周期关联的托管资源,由 ref 跟踪。

driver

管理设备的 DRM 驱动程序

dev_private

DRM 驱动程序私有数据。这已被弃用,应设置为 NULL。

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

主节点

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

渲染节点

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

加速节点

计算加速节点

已注册

drm_dev_register()drm_connector_register() 内部使用。

主设备

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

驱动程序特性

每个设备的驱动程序特性

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

已拔出

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

匿名 inode

用于私有地址空间的 inode

唯一名称

设备的唯一名称

结构体互斥锁

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

待办事项:此锁曾经是 DRM 子系统的 BKL。将

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

主设备互斥锁

用于 drm_minor.masterdrm_file.is_master 的锁

打开计数

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

文件列表互斥锁

保护 filelist

文件列表

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

内部文件列表

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

客户端列表互斥锁

保护 clientlist 的访问。

客户端列表

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

立即禁用垂直同步

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

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

垂直同步

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

垂直同步时间锁

保护垂直同步启用/禁用期间的垂直同步计数和时间更新

垂直同步锁

顶层垂直同步引用锁,封装了底层 vblank_time_lock

最大垂直同步计数

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

如果设置为零,则垂直同步核心将尝试通过高精度时间戳来猜测垂直同步中断被禁用时之间经过的垂直同步次数。这种方法存在较小的竞争,并且在较长时间内不精确,因此始终建议公开硬件垂直同步计数器。

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

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

垂直同步事件列表

垂直同步事件列表

事件锁

保护 vblank_event_list 和通常的事件传递。请参阅 drm_send_event()drm_send_event_locked()

CRTC 数量

此设备上的 CRTC 数量

模式配置

当前模式配置

对象名称锁

GEM 信息

对象名称 idr

GEM 信息

vma 偏移管理器

GEM 信息

vram_mm

VRAM MM 内存管理器

切换电源状态

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

fb_helper

指向 fbdev 模拟结构的指针。由 drm_fb_helper_init() 设置,由 drm_fb_helper_fini() 清除。

debugfs 根目录

debugfs 文件的根目录。

描述

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

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;
    char *date;
    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

反转驱动程序加载回调的效果。理想情况下,驱动程序执行的清理应按初始化的相反顺序进行。与 load 钩子类似,此处理程序已弃用,应删除其使用,而赞成在驱动程序层使用开放编码的拆卸函数。有关删除 struct drm_device 的正确方法,请参阅 drm_dev_unregister()drm_dev_put()

卸载() 钩子在取消注册设备后立即调用。

release

在最后一个引用被释放后(即设备正在被销毁时),用于销毁设备数据的可选回调。

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

master_set

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

master_drop

每当次要主设备被删除时调用。仅由 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 或其他完全不同的东西)中创建一个新的哑缓冲区,并返回生成的缓冲区句柄。然后,可以将此句柄封装到帧缓冲模式设置对象中。

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

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

由用户通过 ioctl 调用。

返回值

成功时返回零,失败时返回负 errno。

dumb_map_offset

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

默认实现是 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

驱动程序描述

date

驱动程序日期,未使用,将被删除

驱动程序特性

驱动程序功能,请参阅 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

type 中的 drm_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 的打开用户时调用。

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() 的已弃用版本,它不支持通过在驱动程序私有结构中嵌入 drm_device 结构来进行子类化,并且不支持通过 devres 进行自动清理。

返回值

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

void drm_dev_get(struct drm_device *dev)

获取 DRM 设备的引用

参数

struct drm_device *dev

要获取引用的设备,或 NULL

描述

这会将 dev 的引用计数加一。调用此函数时,您必须已经拥有一个引用。使用 drm_dev_put() 再次释放此引用。

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

void drm_dev_put(struct drm_device *dev)

释放 DRM 设备的引用

参数

struct drm_device *dev

要释放引用的设备,或 NULL

描述

这会将 dev 的引用计数减一。如果引用计数降至零,则销毁设备。

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

注册 DRM 设备

参数

struct drm_device *dev

要注册的设备

unsigned long flags

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

描述

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

永远不要在任何设备上调用此函数两次!

注意

为了确保与现有驱动程序方法的向后兼容性,此函数在注册设备节点后调用 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 地址解码器与其他 BAR 共享,因此将其保持映射状态可能会导致不希望出现的行为,如挂起或内存损坏。

托管资源

受结构体 device 管理的资源的启发,但与结构体 drm_device 的生命周期绑定,后者可能比底层的物理设备更长寿,通常当用户空间有一些打开的文件和其他资源句柄仍然打开时会发生这种情况。

可以使用 drmm_add_action() 添加释放动作,可以使用 drmm_kmalloc() 和相关函数直接进行内存分配。自从驱动程序通过 devm_drm_dev_alloc() 加载开始,所有操作都会在最终的 drm_dev_put() 中以添加释放动作和分配内存的相反顺序释放。

请注意,释放动作和托管内存也可以在驱动程序的生命周期内添加和删除,所有函数都是完全并发安全的。但是,建议仅对在 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() 添加的 action。该 action 会从 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() 时自动释放。也可以在最终的 drm_dev_put() 之前通过调用 drmm_kfree() 释放内存。

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,否则返回负的错误代码。

描述

这是 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 事件是一种通用的、可扩展的方法,用于通过文件描述符将异步事件发送到用户空间。它们用于发送 vblank 事件和 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_filestruct 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;
};

成员

完成

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

completion_release

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

event

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

fence

可选的 DMA 栅栏,用于解除阻止依赖于此事件表示的非阻塞 DRM 操作的其他硬件事务。

file_priv

struct drm_file,其中应将 event 传递到该文件。仅当设置 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

此客户端拥有或曾经拥有主设备功能。受 struct drm_device.master_mutex 保护。

如果客户端是或曾经是主设备,则使用它来确保不强制执行 CAP_SYS_ADMIN。

is_master

此客户端是 master 的创建者。受 struct drm_device.master_mutex 保护。

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

supports_virtualized_cursor_plane

此客户端能够处理具有虚拟化驱动程序施加的限制的光标平面。

这意味着光标平面必须像光标一样工作,即跟踪光标移动。它还需要客户端在光标平面上设置热点属性。

主设备

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

仅当 drm_is_primary_client() 返回 true 时才相关。请注意,只有当主设备是当前活动主设备时,它才与 drm_device.master 匹配。

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

取消引用此指针时,请在指针的使用期间持有 struct drm_device.master_mutex,或者如果当前未持有 struct drm_device.master_mutex 并且没有其他需要持有它的情况,则使用 drm_file_get_master()。这可以防止在使用的过程中释放 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 文件

描述

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

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

bool drm_is_render_client(const struct drm_file *file_priv)

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

参数

const struct drm_file *file_priv

DRM 文件

描述

如果这是一个渲染节点的打开文件,则返回 true,即 file_privdrm_file.minor 是一个渲染次设备号。

另请参阅关于 渲染节点 的章节。

bool drm_is_accel_client(const struct drm_file *file_priv)

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

参数

const struct drm_file *file_priv

DRM 文件

描述

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

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

struct drm_memory_stats

关联的 GEM 对象统计信息

定义:

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

成员

shared

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

private

GEM 对象的总大小

resident

GEM 对象后备页面的总大小

purgeable

可以清除的 GEM 对象的总大小(驻留且未激活)

active

在一个或多个引擎上处于活动状态的 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

轮询等待者表

描述

如果驱动程序使用 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_device.event_lock 的调用者,这是 drm_event_reserve_init() 的锁定版本。

返回值

成功时返回 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 时间域中的栅栏事件设置的时间戳

描述

此函数将使用 drm_event_reserve_init() 初始化的事件 e 发送到其关联的用户空间 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 事件

描述

此函数将使用 drm_event_reserve_init() 初始化的事件 e 发送到其关联的用户空间 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 事件

描述

此函数将使用 drm_event_reserve_init() 初始化的事件 e 发送到其关联的用户空间 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 客户端使用统计

杂项实用工具

打印机

一个简单的 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

用于模式设置代码。

DRM_UT_PRIME

用于 prime 代码。

DRM_UT_ATOMIC

用于原子代码。

DRM_UT_VBL

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

DRM_UT_STATE

用于详细原子状态调试。

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

参数

打印机

DRM 打印机

缩进

制表符缩进级别(最大 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

开始

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

剩余

此迭代要写入的字节数

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 对象

struct drm_printer drm_seq_file_printer(struct seq_file *f)

构造一个输出到 seq_filedrm_printer

参数

struct seq_file *f

要输出到的 struct seq_file

返回值

drm_printer 对象

struct drm_printer drm_info_printer(struct device *dev)

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

参数

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_printer,将输出定向到 drm_err()

参数

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 或在操作期间的特定点捕获寄存器的内容非常有用。这使驱动程序可以为两者使用单个寄存器列表。

实用程序

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

for_each_if

for_each_if (condition)

用于处理各种 for_each 宏中的条件语句的辅助工具

参数

condition

要检查的条件

描述

典型用法

#define for_each_foo_bar(x, y) \'
        list_for_each_entry(x, y->list, head) \'
                for_each_if(x->something == SOMETHING)

for_each_if() 宏使 for_each_foo_bar() 的使用不易出错。

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 中需要。

遗留支持代码

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

遗留的挂起/恢复

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 映射等。这些函数已弃用,不应使用。