drm/i915 Intel GFX 驱动

drm/i915 驱动支持所有(除了一些非常早期的型号)集成的 GFX 芯片组,包括 Intel 显示和渲染模块。这不包括使用 SGX 渲染单元的一组 SoC 平台,这些平台通过 gma500 drm 驱动提供基本支持。

核心驱动基础设施

本节介绍驱动的显示和 GEM 部分使用的核心驱动基础设施。

运行时电源管理

i915 驱动支持在运行时动态启用和禁用整个硬件模块。这在显示方面尤其重要,在最新的硬件上,软件应该手动控制许多电源门,因为在 GT 方面,大部分电源管理由硬件完成。但即使在那里,也需要在设备级别进行一些手动控制。

由于 i915 支持具有统一代码库的各种平台,并且硬件工程师喜欢在电源域之间随意切换功能,因此需要大量的间接寻址。此文件为驱动程序提供通用函数,用于获取和释放抽象电源域的引用。然后,它将这些映射到给定平台的实际电源井。

intel_wakeref_t intel_runtime_pm_get_raw(struct intel_runtime_pm *rpm)

获取原始运行时 pm 引用

参数

struct intel_runtime_pm *rpm

intel_runtime_pm 结构

描述

这是 intel_display_power_is_enabled() 的解锁版本,仅应用于可能发生死锁的错误捕获和恢复代码。此函数获取设备级运行时 pm 引用(主要用于显示代码的异步 PM 管理),并确保其已加电。在 wakelock 断言检查期间不考虑原始引用。

通过此函数获得的任何运行时 pm 引用都必须对称调用 intel_runtime_pm_put_raw() 以再次释放引用。

返回

传递给 intel_runtime_pm_put_raw() 的 wakeref cookie,如果获取了 wakeref,则评估为 True,否则为 False。

intel_wakeref_t intel_runtime_pm_get(struct intel_runtime_pm *rpm)

获取运行时 pm 引用

参数

struct intel_runtime_pm *rpm

intel_runtime_pm 结构

描述

此函数获取设备级运行时 pm 引用(主要用于 GEM 代码以确保 GTT 或 GT 已打开),并确保其已加电。

通过此函数获得的任何运行时 pm 引用都必须对称调用 intel_runtime_pm_put() 以再次释放引用。

返回

传递给 intel_runtime_pm_put() 的 wakeref cookie

intel_wakeref_t __intel_runtime_pm_get_if_active(struct intel_runtime_pm *rpm, bool ignore_usecount)

如果设备处于活动状态,则获取运行时 pm 引用

参数

struct intel_runtime_pm *rpm

intel_runtime_pm 结构

bool ignore_usecount

即使 dev->power.usage_count 为 0 也获取引用

描述

如果设备已处于活动状态,此函数将获取设备级运行时 pm 引用,并确保其已加电。如果 intel_runtime_pm_get_if_active() 报告失败,则尝试访问硬件是违法的。

如果 ignore_usecount 为 true,即使没有用户要求设备加电(dev->power.usage_count == 0),也会获取引用。如果此函数在这种情况下返回 false,则保证已调用设备的运行时挂起钩子,或者将调用该钩子(因此也保证最终将调用设备的运行时恢复钩子)。

通过此函数获得的任何运行时 pm 引用都必须对称调用 intel_runtime_pm_put() 以再次释放引用。

返回

传递给 intel_runtime_pm_put() 的 wakeref cookie,如果获取了 wakeref,则评估为 True,否则为 False。

intel_wakeref_t intel_runtime_pm_get_noresume(struct intel_runtime_pm *rpm)

获取运行时 pm 引用

参数

struct intel_runtime_pm *rpm

intel_runtime_pm 结构

描述

此函数获取设备级运行时 pm 引用。

它将_不_恢复设备,而仅获得额外的 wakeref。因此,仅在已知设备处于活动状态且先前持有另一个 wakeref 的上下文中调用此函数才有效。

通过此函数获得的任何运行时 pm 引用都必须对称调用 intel_runtime_pm_put() 以再次释放引用。

返回

传递给 intel_runtime_pm_put() 的 wakeref cookie

void intel_runtime_pm_put_raw(struct intel_runtime_pm *rpm, intel_wakeref_t wref)

释放原始运行时 pm 引用

参数

struct intel_runtime_pm *rpm

intel_runtime_pm 结构

intel_wakeref_t wref

为正在释放的引用获取的 wakeref

描述

此函数会删除通过 intel_runtime_pm_get_raw() 获取的设备级运行时 pm 引用,如果这是最后一个引用,可能会立即关闭相应的硬件模块的电源。

void intel_runtime_pm_put_unchecked(struct intel_runtime_pm *rpm)

释放未检查的运行时 pm 引用

参数

struct intel_runtime_pm *rpm

intel_runtime_pm 结构

描述

此函数会删除通过 intel_runtime_pm_get() 获取的设备级运行时 pm 引用,如果这是最后一个引用,可能会立即关闭相应的硬件模块的电源。

此函数的存在仅出于历史原因,应避免在新代码中使用,因为无法检查其使用的正确性。始终改用 intel_runtime_pm_put()

void intel_runtime_pm_put(struct intel_runtime_pm *rpm, intel_wakeref_t wref)

释放运行时 pm 引用

参数

struct intel_runtime_pm *rpm

intel_runtime_pm 结构

intel_wakeref_t wref

为正在释放的引用获取的 wakeref

描述

此函数会删除通过 intel_runtime_pm_get() 获取的设备级运行时 pm 引用,如果这是最后一个引用,可能会立即关闭相应的硬件模块的电源。

void intel_runtime_pm_enable(struct intel_runtime_pm *rpm)

启用运行时 pm

参数

struct intel_runtime_pm *rpm

intel_runtime_pm 结构

描述

此函数在驱动程序加载序列结束时启用运行时 pm。

请注意,此函数当前不为从属显示电源域启用运行时 pm。这是由 intel_power_domains_enable() 完成的。

void intel_uncore_forcewake_get(struct intel_uncore *uncore, enum forcewake_domains fw_domains)

获取 forcewake 域引用

参数

struct intel_uncore *uncore

intel_uncore 结构体

enum forcewake_domains fw_domains

要获取引用的 forcewake 域

描述

此函数可用于获取 GT 的 forcewake 域引用。正常的寄存器访问将自动处理 forcewake 域。但是,如果某些序列要求 GT 不关闭特定的 forcewake 域,则应在该序列的开头调用此函数。随后,应通过对称调用 intel_unforce_forcewake_put() 来释放引用。通常,调用者希望所有域都保持唤醒状态,因此 fw_domains 将为 FORCEWAKE_ALL。

void intel_uncore_forcewake_user_get(struct intel_uncore *uncore)

代表用户空间声明 forcewake

参数

struct intel_uncore *uncore

intel_uncore 结构体

描述

此函数是 intel_uncore_forcewake_get() 的包装器,用于获取 GT 电源井,并在用户空间的旁路期间禁用我们的调试。

void intel_uncore_forcewake_user_put(struct intel_uncore *uncore)

代表用户空间释放 forcewake

参数

struct intel_uncore *uncore

intel_uncore 结构体

描述

此函数与 intel_uncore_forcewake_user_get() 互补,并释放代表用户空间旁路获取的 GT 电源井。

void intel_uncore_forcewake_get__locked(struct intel_uncore *uncore, enum forcewake_domains fw_domains)

获取 forcewake 域引用

参数

struct intel_uncore *uncore

intel_uncore 结构体

enum forcewake_domains fw_domains

要获取引用的 forcewake 域

描述

请参阅 intel_uncore_forcewake_get()。此变体将显式处理 dev_priv->uncore.lock 自旋锁的责任置于调用者身上。

void intel_uncore_forcewake_put(struct intel_uncore *uncore, enum forcewake_domains fw_domains)

释放 forcewake 域引用

参数

struct intel_uncore *uncore

intel_uncore 结构体

enum forcewake_domains fw_domains

要释放引用的 forcewake 域

描述

此函数会删除通过 intel_uncore_forcewake_get() 获取的指定域的设备级 forcewake。

void intel_uncore_forcewake_flush(struct intel_uncore *uncore, enum forcewake_domains fw_domains)

刷新延迟释放

参数

struct intel_uncore *uncore

intel_uncore 结构体

enum forcewake_domains fw_domains

要刷新的 forcewake 域

void intel_uncore_forcewake_put__locked(struct intel_uncore *uncore, enum forcewake_domains fw_domains)

释放 forcewake 域引用

参数

struct intel_uncore *uncore

intel_uncore 结构体

enum forcewake_domains fw_domains

要释放引用的 forcewake 域

描述

请参阅 intel_uncore_forcewake_put()。此变体将显式处理 dev_priv->uncore.lock 自旋锁的责任置于调用者身上。

int __intel_wait_for_register_fw(struct intel_uncore *uncore, i915_reg_t reg, u32 mask, u32 value, unsigned int fast_timeout_us, unsigned int slow_timeout_ms, u32 *out_value)

等待直到寄存器与预期状态匹配

参数

struct intel_uncore *uncore

struct intel_uncore

i915_reg_t reg

要读取的寄存器

u32 mask

应用于寄存器值的掩码

u32 value

期望值

unsigned int fast_timeout_us

原子/紧密等待的快速超时(以微秒为单位)

unsigned int slow_timeout_ms

慢超时(以毫秒为单位)

u32 *out_value

用于保存寄存器值的可选占位符

描述

此例程会等待直到目标寄存器 reg 在应用 mask 后包含预期的 value,即它会等待直到

(intel_uncore_read_fw(uncore, reg) & mask) == value

否则,等待将在 slow_timeout_ms 毫秒后超时。对于原子上下文,slow_timeout_ms 必须为零,并且 fast_timeout_us 必须不大于 200,000 微秒。

请注意,此例程假定调用方断言了 forcewake,它不适合长时间等待。如果要等待而不保持 forcewake 状态(即,你预计等待会很慢),请参阅 intel_wait_for_register()。

返回

如果寄存器与期望条件匹配,则为 0;否则为 -ETIMEDOUT。

int __intel_wait_for_register(struct intel_uncore *uncore, i915_reg_t reg, u32 mask, u32 value, unsigned int fast_timeout_us, unsigned int slow_timeout_ms, u32 *out_value)

等待直到寄存器与预期状态匹配

参数

struct intel_uncore *uncore

struct intel_uncore

i915_reg_t reg

要读取的寄存器

u32 mask

应用于寄存器值的掩码

u32 value

期望值

unsigned int fast_timeout_us

原子/紧密等待的快速超时(以微秒为单位)

unsigned int slow_timeout_ms

慢超时(以毫秒为单位)

u32 *out_value

用于保存寄存器值的可选占位符

描述

此例程会等待直到目标寄存器 reg 在应用 mask 后包含预期的 value,即它会等待直到

(intel_uncore_read(uncore, reg) & mask) == value

否则,等待将在 timeout_ms 毫秒后超时。

返回

如果寄存器与期望条件匹配,则为 0;否则为 -ETIMEDOUT。

enum forcewake_domains intel_uncore_forcewake_for_reg(struct intel_uncore *uncore, i915_reg_t reg, unsigned int op)

访问寄存器需要哪些 forcewake 域

参数

struct intel_uncore *uncore

指向 struct intel_uncore 的指针

i915_reg_t reg

有问题的寄存器

unsigned int op

FW_REG_READ 和/或 FW_REG_WRITE 的操作位掩码

描述

返回一组 forcewake 域,这些域需要与 intel_uncore_forcewake_get 一起使用,以便在指定模式(读取、写入或读取/写入)下通过原始 mmio 访问器访问指定的寄存器。

注意

在 Gen6 和 Gen7 上,写入 forcewake 域 (FORCEWAKE_RENDER) 要求调用者自行进行 FIFO 管理,否则可能会丢失写入。

中断处理

这些函数提供了启用和禁用中断处理支持的基本支持。在 i915_irq.c 和相关文件中还有更多功能,但这将在单独的章节中介绍。

void intel_irq_init(struct drm_i915_private *dev_priv)

初始化 irq 支持

参数

struct drm_i915_private *dev_priv

i915 设备实例

描述

此函数初始化所有 irq 支持,包括工作项、计时器和所有 vtable。但它不会设置中断本身。

void intel_irq_suspend(struct drm_i915_private *i915)

挂起中断

参数

struct drm_i915_private *i915

i915 设备实例

描述

此函数用于在运行时禁用中断。

void intel_irq_resume(struct drm_i915_private *i915)

恢复中断

参数

struct drm_i915_private *i915

i915 设备实例

描述

此函数用于在运行时启用中断。

英特尔 GVT-g 访客支持(vGPU)

英特尔 GVT-g 是一种图形虚拟化技术,它以分时方式在多个虚拟机之间共享 GPU。每个虚拟机都呈现一个虚拟 GPU (vGPU),它具有与底层物理 GPU (pGPU) 相同的功能,因此 i915 驱动程序可以在虚拟机中无缝运行。此文件提供了在虚拟机中运行时 vGPU 特定的优化,以降低 vGPU 仿真的复杂性并提高整体性能。

此处介绍的主要功能是所谓的“地址空间气球”技术。英特尔 GVT-g 在多个虚拟机之间对全局图形内存进行分区,因此每个虚拟机都可以直接访问一部分内存而无需虚拟机管理程序的干预,例如填充纹理或排队命令。然而,通过分区,未修改的 i915 驱动程序会假定一个从地址零开始的较小图形内存,然后需要 vGPU 仿真模块来转换“访客视图”和“主机视图”之间的图形地址,对于包含图形内存地址的所有寄存器和命令操作码。为了降低复杂性,英特尔 GVT-g 引入了“地址空间气球”,方法是将确切的分区知识告知每个访客 i915 驱动程序,然后驱动程序保留并防止分配未分配的部分。因此,vGPU 仿真模块只需要扫描和验证图形地址,而无需复杂的地址转换。

void intel_vgpu_detect(struct drm_i915_private *dev_priv)

检测虚拟 GPU

参数

struct drm_i915_private *dev_priv

i915 设备私有数据

描述

此函数在初始化阶段调用,以检测是否在 vGPU 上运行。

void intel_vgt_deballoon(struct i915_ggtt *ggtt)

释放保留的图形地址段

参数

struct i915_ggtt *ggtt

我们之前从中保留的全局 GGTT

描述

当驱动程序卸载或气球化失败时,将调用此函数来释放膨胀出的图形内存。

int intel_vgt_balloon(struct i915_ggtt *ggtt)

膨胀保留的图形地址段

参数

struct i915_ggtt *ggtt

要从中保留的全局 GGTT

描述

此函数在初始化阶段调用,通过将这些空间标记为保留来膨胀分配给其他 vGPU 的图形地址空间。膨胀相关知识(可映射/不可映射图形内存的起始地址和大小)在保留的 mmio 范围内的 vgt_if 结构中描述。

举例来说,下面的图描绘了气球化之后的一个典型场景。这里 vGPU1 有 2 个图形地址空间,每个地址空间都被膨胀出来,分别用于可映射和不可映射部分。从 vGPU1 的角度来看,总大小与物理大小相同,其图形空间的起始地址为零。但是,有一些部分膨胀出来了(阴影部分,由 drm 分配器标记为保留)。从主机的角度来看,图形地址空间由不同虚拟机中的多个 vGPU 分区。

                       vGPU1 view         Host view
            0 ------> +-----------+     +-----------+
              ^       |###########|     |   vGPU3   |
              |       |###########|     +-----------+
              |       |###########|     |   vGPU2   |
              |       +-----------+     +-----------+
       mappable GM    | available | ==> |   vGPU1   |
              |       +-----------+     +-----------+
              |       |###########|     |           |
              v       |###########|     |   Host    |
              +=======+===========+     +===========+
              ^       |###########|     |   vGPU3   |
              |       |###########|     +-----------+
              |       |###########|     |   vGPU2   |
              |       +-----------+     +-----------+
     unmappable GM    | available | ==> |   vGPU1   |
              |       +-----------+     +-----------+
              |       |###########|     |           |
              |       |###########|     |   Host    |
              v       |###########|     |           |
total GM size ------> +-----------+     +-----------+

返回

成功返回零,如果配置无效或气球化失败,则返回非零值

英特尔 GVT-g 主机支持(vGPU 设备模型)

英特尔 GVT-g 是一种图形虚拟化技术,它以分时方式在多个虚拟机之间共享 GPU。每个虚拟机都呈现一个虚拟 GPU (vGPU),它具有与底层物理 GPU (pGPU) 相同的功能,因此 i915 驱动程序可以在虚拟机中无缝运行。

为了虚拟化 GPU 资源,GVT-g 驱动程序依赖于虚拟机管理程序技术,例如 KVM/VFIO/mdev、Xen 等,以提供资源访问捕获功能并在 GVT-g 设备模块中虚拟化。更多架构设计文档可在 https://github.com/intel/gvt-linux/wiki 上找到。

int intel_gvt_init(struct drm_i915_private *dev_priv)

初始化 GVT 组件

参数

struct drm_i915_private *dev_priv

drm i915 私有数据

描述

此函数在初始化阶段调用,以创建 GVT 设备。

返回

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

void intel_gvt_driver_remove(struct drm_i915_private *dev_priv)

当 i915 驱动程序取消绑定时,清理 GVT 组件

参数

struct drm_i915_private *dev_priv

drm i915 私有 *

描述

此函数在 i915 驱动程序卸载阶段调用,以关闭 GVT 组件并释放相关资源。

void intel_gvt_resume(struct drm_i915_private *dev_priv)

GVT 恢复例程包装器

参数

struct drm_i915_private *dev_priv

drm i915 私有 *

描述

此函数在 i915 驱动程序恢复阶段调用,以恢复 GVT 所需的硬件状态,以便 vGPU 在恢复后可以继续运行。

解决方法

硬件解决方法是文档中记录的、在驱动程序中执行的寄存器编程,它不属于平台的正常编程序列。根据应用方式/时间,解决方法有一些基本类别

  • 上下文解决方法:访问保存/恢复到/从硬件上下文映像的寄存器的解决方法。该列表在初始化设备时发出一次(通过加载寄存器立即命令),并保存在默认上下文中。然后,在每次创建上下文时都使用该默认上下文来拥有“初始化的黄金上下文”,即一个已经包含所有寄存器所需更改的上下文映像。

    上下文解决方法应在分别针对目标平台的 *_ctx_workarounds_init() 变体中实现。

  • 引擎解决方法:每当特定引擎重置时都会应用这些 WA 列表。一组引擎类也可能共享一个公共电源域,并且它们会一起重置。这种情况发生在某些平台上,渲染引擎和计算引擎会一起重置。在这种情况下,(至少)其中一个需要保留解决方法编程:驱动程序中采用的方法是将这些解决方法与注册的第一个计算/渲染引擎绑定。当使用 GuC 提交执行时,引擎重置不在内核驱动程序控制范围内,因此所涉及的寄存器列表会在引擎初始化时写入一次,然后传递给 GuC,GuC 会在重置发生之前/之后保存/恢复它们的值。有关参考,请参阅 drivers/gpu/drm/i915/gt/uc/intel_guc_ads.c

    RCS 和 CCS 特有的寄存器解决方法应分别在 rcs_engine_wa_init() 和 ccs_engine_wa_init() 中实现;属于 BCS、VCS 或 VECS 的寄存器解决方法应在 xcs_engine_wa_init() 中实现。不属于特定引擎 MMIO 范围但属于公共 RCS/CCS 重置域的寄存器解决方法应在 general_render_compute_wa_init() 中实现。有关 CCS 负载平衡的设置应添加到 ccs_engine_wa_mode() 中。

  • GT 解决方法:每当这些寄存器恢复到其默认值时,都会应用这些 WA 列表:在 GPU 重置、挂起/恢复时 [1] 等。

    GT 解决方法应在分别针对目标平台的 *_gt_workarounds_init() 变体中实现。

  • 寄存器白名单:一些解决方法需要在用户空间中实现,但需要访问特权寄存器。内核中的白名单指示硬件允许访问发生。从内核方面来看,这只是 MMIO 解决方法的一个特例(因为我们将这些要列入白名单的寄存器列表写入一些特殊的硬件寄存器)。

    寄存器白名单应在分别针对目标平台的 *_whitelist_build() 变体中完成。

  • 解决方法批处理缓冲区:每次硬件上下文恢复时都会由硬件自动执行的缓冲区。这些缓冲区是在默认上下文中创建和编程的,因此硬件在切换上下文时始终会经历这些编程序列。对解决方法批处理缓冲区的支持是通过以下硬件机制启用的

    1. INDIRECT_CTX:在默认上下文中提供了一个批处理缓冲区和一个偏移量,指示硬件在上下文恢复中到达该偏移量时跳转到该位置。驱动程序中的解决方法批处理缓冲区当前对所有平台都使用此机制。

    2. BB_PER_CTX_PTR:在默认上下文中提供了一个批处理缓冲区,指示硬件在上下文恢复序列中恢复引擎寄存器后继续执行的缓冲区。目前驱动程序中未使用此功能。

  • 其他:由于其性质,有一些 WA 无法从中心位置应用。这些 WA 根据需要在代码的其他部分中散布。与显示 IP 相关的解决方法是主要示例。

显示硬件处理

本节涵盖了与显示硬件相关的所有内容,包括模式设置基础设施、平面、精灵和光标处理与显示、输出探测以及相关主题。

模式设置基础设施

到目前为止,i915 驱动程序是唯一不使用通用 DRM 辅助代码来实现模式设置序列的 DRM 驱动程序。因此,它有自己定制的基础设施来执行显示配置更改。

前缓冲区跟踪

许多功能需要我们跟踪当前活动的前缓冲区的更改,特别是针对前缓冲区的渲染。

为了能够做到这一点,我们通过 intel_frontbuffer_track(),使用位掩码跟踪所有可能的前缓冲区插槽的前缓冲区。当 前缓冲区的内容失效时,当 前缓冲区渲染再次停止以刷新所有更改时,以及当 前缓冲区与翻转交换时,会调用此文件中的函数。对前缓冲区更改感兴趣的子系统(例如 PSR、FBC、DRRS)应直接将其回调放入相关位置,并筛选它们感兴趣的前缓冲区插槽。

在高层面上,有两种类型的节能功能。第一种的工作方式类似于特殊缓存(FBC 和 PSR),它们感兴趣的是何时停止缓存以及何时重新开始缓存。这可以通过将回调放入失效和刷新函数中来完成:在失效时必须停止缓存,在刷新时可以重新启动缓存。也许他们需要知道前缓冲区何时更改(例如,当硬件不自行启动失效和刷新时),这可以通过将回调放入翻转函数中来实现。

另一种类型的显示节能功能只关心忙碌程度(例如 DRRS)。在这种情况下,所有三个(失效、刷新和翻转)都表示忙碌。没有直接的方法来检测空闲。相反,应该从刷新和翻转函数启动一个延迟工作的空闲计时器,并在检测到忙碌时取消。

bool intel_frontbuffer_invalidate(struct intel_frontbuffer *front, enum fb_op_origin origin)

使前缓冲区对象失效

参数

struct intel_frontbuffer *front

要失效的 GEM 对象

enum fb_op_origin origin

导致失效的操作

描述

每次在给定对象上开始渲染时都会调用此函数,并且必须使前缓冲区缓存(fbc、DRRS 的低刷新率、面板自刷新)失效。对于 ORIGIN_CS,任何后续失效都将延迟,直到渲染完成或在此前缓冲区平面上调度翻转。

void intel_frontbuffer_flush(struct intel_frontbuffer *front, enum fb_op_origin origin)

刷新前缓冲区对象

参数

struct intel_frontbuffer *front

要刷新的 GEM 对象

enum fb_op_origin origin

导致刷新的操作

描述

每次在给定对象上完成渲染时都会调用此函数,并且可以再次启动前缓冲区缓存。

void frontbuffer_flush(struct drm_i915_private *i915, unsigned int frontbuffer_bits, enum fb_op_origin origin)

刷新前缓冲区

参数

struct drm_i915_private *i915

i915 设备

unsigned int frontbuffer_bits

前缓冲区平面跟踪位

enum fb_op_origin origin

导致刷新的操作

描述

每次在给定平面上完成渲染时都会调用此函数,并且可以再次启动前缓冲区缓存。如果刷新被某些未完成的异步渲染阻止,则刷新将被延迟。

可以在未持有任何锁的情况下调用。

void intel_frontbuffer_flip_prepare(struct drm_i915_private *i915, unsigned frontbuffer_bits)

准备异步前缓冲区翻转

参数

struct drm_i915_private *i915

i915 设备

unsigned frontbuffer_bits

前缓冲区平面跟踪位

描述

obj 上调度翻转后调用此函数。实际的前缓冲区刷新将延迟,直到使用 intel_frontbuffer_flip_complete 发出完成信号。如果在此期间发生失效,则此刷新将被取消。

可以在未持有任何锁的情况下调用。

void intel_frontbuffer_flip_complete(struct drm_i915_private *i915, unsigned frontbuffer_bits)

完成异步前缓冲区翻转

参数

struct drm_i915_private *i915

i915 设备

unsigned frontbuffer_bits

前缓冲区平面跟踪位

描述

在翻转被锁定后调用此函数,并且将在下一个垂直消隐时完成。如果尚未取消,它将执行刷新。

可以在未持有任何锁的情况下调用。

void intel_frontbuffer_flip(struct drm_i915_private *i915, unsigned frontbuffer_bits)

同步前缓冲区翻转

参数

struct drm_i915_private *i915

i915 设备

unsigned frontbuffer_bits

前缓冲区平面跟踪位

描述

obj 上调度翻转后调用此函数。这用于将在下一个垂直消隐上发生的同步平面更新,并且不会被挂起的 GPU 渲染延迟。

可以在未持有任何锁的情况下调用。

void intel_frontbuffer_queue_flush(struct intel_frontbuffer *front)

排队刷新前缓冲区对象

参数

struct intel_frontbuffer *front

要刷新的 GEM 对象

描述

此函数旨在用于我们的脏回调,用于在 DMA 栅栏发出信号时排队刷新

void intel_frontbuffer_track(struct intel_frontbuffer *old, struct intel_frontbuffer *new, unsigned int frontbuffer_bits)

更新前缓冲区跟踪

参数

struct intel_frontbuffer *old

前缓冲区插槽的当前缓冲区

struct intel_frontbuffer *new

前缓冲区插槽的新缓冲区

unsigned int frontbuffer_bits

前缓冲区插槽的位掩码

描述

此操作通过从 old 中清除它们并在 new 中设置它们来更新前缓冲区跟踪位 frontbuffer_bitsoldnew 都可以为 NULL。

显示 FIFO 下溢报告

i915 驱动程序使用硬件提供的中断信号检查显示 FIFO 下溢。默认情况下启用此功能,并且对于调试显示问题(尤其是水印设置)非常有用。

如果检测到下溢,则会将其记录到 dmesg 中。为避免日志泛滥和占用 CPU,在给定管道上的下一次模式设置之前,下溢中断将在第一次发生后禁用。

请注意,gmch 平台上的下溢检测有点难看,因为没有中断(尽管信令位在 PIPESTAT 管道中断寄存器中)。同样在其他一些平台上,下溢中断是共享的,这意味着如果我们检测到下溢,我们需要禁用所有管道上的下溢报告。

该代码还支持在 PCH 转码器上进行下溢检测。

bool intel_set_cpu_fifo_underrun_reporting(struct drm_i915_private *dev_priv, enum pipe pipe, bool enable)

设置 CPU FIFO 下溢报告状态

参数

struct drm_i915_private *dev_priv

i915 设备实例

enum pipe pipe

要设置状态的(CPU)管道

bool enable

是否应报告下溢

描述

此函数为 pipe 设置 FIFO 下溢状态。它在模式设置代码中使用,以避免误报,因为在许多平台上,禁用或启用管道时会预期下溢。

请注意,在某些平台上,由于共享中断,禁用一个管道的下溢报告会禁用所有管道的下溢报告。但实际报告仍然是按管道进行的。

返回下溢报告的先前状态。

bool intel_set_pch_fifo_underrun_reporting(struct drm_i915_private *dev_priv, enum pipe pch_transcoder, bool enable)

设置 PCH fifo 下溢报告状态

参数

struct drm_i915_private *dev_priv

i915 设备实例

enum pipe pch_transcoder

PCH 转码器(与 IVB 和更早版本上的 pipe 相同)

bool enable

是否应报告下溢

描述

此函数使我们能够为特定的 PCH 转码器禁用或启用 PCH fifo 下溢。请注意,在某些 PCH(例如 CPT/PPT)上,禁用一个转码器的 FIFO 下溢报告也可能会禁用其他转码器的所有其他 PCH 错误中断,因为所有转码器只有一个中断掩码/使能位。

返回下溢报告的先前状态。

void intel_cpu_fifo_underrun_irq_handler(struct drm_i915_private *dev_priv, enum pipe pipe)

处理 CPU fifo 下溢中断

参数

struct drm_i915_private *dev_priv

i915 设备实例

enum pipe pipe

要设置状态的(CPU)管道

描述

这将处理 CPU fifo 下溢中断,如果启用了下溢报告,则会在 dmesg 中生成下溢警告,然后禁用下溢中断以避免 irq 风暴。

void intel_pch_fifo_underrun_irq_handler(struct drm_i915_private *dev_priv, enum pipe pch_transcoder)

处理 PCH fifo 下溢中断

参数

struct drm_i915_private *dev_priv

i915 设备实例

enum pipe pch_transcoder

PCH 转码器(与 IVB 和更早版本上的 pipe 相同)

描述

这将处理 PCH fifo 下溢中断,如果启用了下溢报告,则会在 dmesg 中生成下溢警告,然后禁用下溢中断以避免 irq 风暴。

void intel_check_cpu_fifo_underruns(struct drm_i915_private *dev_priv)

立即检查 CPU fifo 下溢

参数

struct drm_i915_private *dev_priv

i915 设备实例

描述

立即检查 CPU fifo 下溢。在 IVB/HSW 上很有用,其中共享错误中断可能已被禁用,因此 CPU fifo 下溢不一定会引发中断,并且在 GMCH 平台上,下溢永远不会引发中断。

void intel_check_pch_fifo_underruns(struct drm_i915_private *dev_priv)

立即检查 PCH fifo 下溢

参数

struct drm_i915_private *dev_priv

i915 设备实例

描述

立即检查 PCH fifo 下溢。在 CPT/PPT 上很有用,其中共享错误中断可能已被禁用,因此 PCH fifo 下溢不一定会引发中断。

平面配置

本节介绍平面配置以及与主平面、精灵、光标和叠加层的组合。这包括执行所有此状态的原子同步更新的基础结构,以及紧密耦合的主题,如水印设置和计算、帧缓冲压缩和面板自刷新。

原子平面辅助函数

此处的功能由原子平面辅助函数使用,以实现旧式平面更新(即,drm_plane->update_plane() 和 drm_plane->disable_plane())。这允许平面更新使用原子状态基础结构,并将平面更新作为单独的准备/检查/提交/清理步骤执行。

struct drm_plane_state *intel_plane_duplicate_state(struct drm_plane *plane)

复制平面状态

参数

struct drm_plane *plane

drm 平面

描述

为指定的平面分配并返回平面状态的副本(包括通用和特定于 Intel 的状态)。

返回

新分配的平面状态,如果失败则返回 NULL。

void intel_plane_destroy_state(struct drm_plane *plane, struct drm_plane_state *state)

销毁平面状态

参数

struct drm_plane *plane

drm 平面

struct drm_plane_state *state

要销毁的状态对象

描述

销毁指定平面的平面状态(包括通用和特定于 Intel 的状态)。

int intel_prepare_plane_fb(struct drm_plane *_plane, struct drm_plane_state *_new_plane_state)

准备在平面上使用的 fb

参数

struct drm_plane *_plane

要准备的 drm 平面

struct drm_plane_state *_new_plane_state

正在准备的平面状态

描述

准备要在显示平面上使用的帧缓冲区。通常,这涉及固定底层对象并更新前缓冲跟踪位。一些较旧的平台需要为光标平面进行特殊的物理地址处理。

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

void intel_cleanup_plane_fb(struct drm_plane *plane, struct drm_plane_state *_old_plane_state)

在平面使用后清理 fb

参数

struct drm_plane *plane

要清理的 drm 平面

struct drm_plane_state *_old_plane_state

来自上一个模式集的状态

描述

清理刚刚从平面中删除的帧缓冲区。

异步页面翻转

异步页面翻转是 DRM_MODE_PAGE_FLIP_ASYNC 标志的实现。当前,仅通过 drmModePageFlip IOCTL 支持异步翻转。相应地,当前仅为主平面添加支持。

异步翻转只能更改平面表面地址,因此 intel_async_flip_check_hw() 函数会拒绝更改的其他任何内容。清除此检查后,将使用 intel_crtc_enable_flip_done() 函数启用翻转完成中断。

只要写入表面地址寄存器,就会生成翻转完成中断,并且请求的事件会在中断处理程序本身中发送到用户空间。在翻转完成事件期间发送的时间戳和序列对应于上次垂直消隐,并且与发送翻转完成事件的实际时间无关。

输出探测

本节介绍输出探测和相关基础结构,如热插拔中断风暴检测和缓解代码。请注意,i915 驱动程序仍将大多数常用的 DRM 辅助代码用于输出探测,因此这些部分完全适用。

热插拔

简而言之,热插拔发生在显示器连接到系统或从系统断开连接时。但是,可能涉及适配器、扩展坞和 Display Port 短脉冲以及 MST 设备,使情况复杂化。

i915 中的热插拔在许多不同的抽象级别上处理。

i915_irq.c 中的平台相关中断处理代码启用、禁用并初步处理中断。中断处理程序将热插拔检测 (HPD) 信息从相关寄存器收集到已触发的热插拔引脚的平台无关掩码中。

intel_hotplug.c 中的平台无关中断处理程序 intel_hpd_irq_handler() 执行热插拔 irq 风暴检测和缓解,并将进一步处理传递给相应的下半部分(特定于 Display Port 和常规热插拔)。

Display Port 工作函数 i915_digport_work_func() 通过钩子调用 intel_dp_hpd_pulse(),后者处理 DP 短脉冲和 DP MST 长脉冲,而故障和非 MST 长脉冲则在连接器上触发常规热插拔处理。

常规热插拔工作函数 i915_hotplug_work_func() 调用连接器检测钩子,并且如果连接器状态发生更改,则通过 drm_kms_helper_hotplug_event() 触发向用户空间发送热插拔 uevent。

最后,用户空间负责在收到热插拔 uevent 时触发模式设置,根据需要禁用或启用 crtc。

热插拔中断风暴检测和缓解代码会跟踪每个热插拔引脚在一段时间内的中断次数,如果中断次数超过某个阈值,则会禁用该中断一段时间,然后再重新启用。这样做的目的是为了缓解因硬件故障触发大量中断并导致系统停止运行的问题。

当前的实现假设在连接 DisplayPort 输出设备时不会出现热插拔中断风暴,因此,对于 DP 回调由 i915_digport_work_func 处理的平台,不会执行 HPD 的重新启用(本来也不应该被禁用;))。这仅适用于此例程处理的 DP 输出设备,任何其他在同一端口上启用的显示器(如 HDMI 或 DVI)都将具有正确的逻辑,因为它将使用 i915_hotplug_work_func,其中处理了此逻辑。

enum hpd_pin intel_hpd_pin_default(struct drm_i915_private *dev_priv, enum port port)

返回与特定端口关联的默认引脚。

参数

struct drm_i915_private *dev_priv

私有驱动程序数据指针

enum port port

要获取关联引脚的 HPD 端口

描述

它仅对数字端口编码器有效且使用。

返回与 port 关联的引脚。

bool intel_hpd_irq_storm_detect(struct drm_i915_private *dev_priv, enum hpd_pin pin, bool long_hpd)

收集统计信息并检测引脚上的 HPD IRQ 风暴

参数

struct drm_i915_private *dev_priv

私有驱动程序数据指针

enum hpd_pin pin

要收集统计信息的引脚

bool long_hpd

HPD IRQ 是长脉冲还是短脉冲

描述

收集有关指定 pin 的 HPD IRQ 的统计信息,并检测 IRQ 风暴。仅更改引脚特定的统计信息和状态,调用者负责进一步的操作。

HPD_STORM_DETECT_PERIOD 内允许的 IRQ 数量存储在 dev_priv->display.hotplug.hpd_storm_threshold 中,默认为 HPD_STORM_DEFAULT_THRESHOLD。长 IRQ 在此阈值上计数为 +10,短 IRQ 计数为 +1。如果超过此阈值,则认为发生了 IRQ 风暴,并且 IRQ 状态设置为 HPD_MARK_DISABLED

默认情况下,大多数系统仅将长 IRQ 计入 dev_priv->display.hotplug.hpd_storm_threshold。但是,一些较旧的系统也遭受短 IRQ 风暴的困扰,因此也必须跟踪这些。因为短 IRQ 风暴自然是由与 DP MST 设备的旁路交互引起的,所以仅对不支持 DP MST 的系统启用短 IRQ 检测。足够新以支持 DP MST 的系统不太可能遭受 IRQ 风暴,因此这没问题。

HPD 阈值可以通过 debugfs 中的 i915_hpd_storm_ctl 进行控制,并且仅应为自动热插拔测试进行调整。

如果检测到 pin 上发生了 IRQ 风暴,则返回 true。

void intel_hpd_trigger_irq(struct intel_digital_port *dig_port)

触发端口的 hpd irq 事件

参数

struct intel_digital_port *dig_port

数字端口

描述

为给定端口触发 HPD 中断事件,模拟输出设备生成的短脉冲,并调度 dig 端口工作来处理它。

void intel_hpd_irq_handler(struct drm_i915_private *dev_priv, u32 pin_mask, u32 long_mask)

主热插拔 irq 处理程序

参数

struct drm_i915_private *dev_priv

drm_i915_private

u32 pin_mask

已触发 irq 的 hpd 引脚的掩码

u32 long_mask

可能是长 hpd 脉冲的 hpd 引脚的掩码

描述

这是所有平台的主热插拔 irq 处理程序。特定于平台的 irq 处理程序会调用特定于平台的热插拔 irq 处理程序,后者会将适当的寄存器读取并解码为有关已触发的 hpd 引脚 (pin_mask) 以及其中哪些引脚可能是长脉冲 (long_mask) 的位掩码。如果与引脚对应的端口不是数字端口,则忽略 long_mask

在这里,我们进行热插拔 irq 风暴检测和缓解,并将进一步处理传递给适当的下半部分。

void intel_hpd_init(struct drm_i915_private *dev_priv)

初始化并启用 hpd 支持

参数

struct drm_i915_private *dev_priv

i915 设备实例

描述

此函数启用热插拔支持。它要求已使用 intel_irq_init_hw() 启用了中断。从那时起,热插拔和轮询请求可以与其他代码同时运行,因此必须遵守锁定规则。

这是与中断启用分开的一个步骤,以简化驱动程序加载和恢复代码中的锁定规则。

另请参阅: intel_hpd_poll_enable()intel_hpd_poll_disable()

void intel_hpd_poll_enable(struct drm_i915_private *dev_priv)

启用具有 hpd 的连接器的轮询

参数

struct drm_i915_private *dev_priv

i915 设备实例

描述

此函数启用对所有支持 HPD 的连接器的轮询。在某些情况下,HPD 可能无法正常工作。在大多数 Intel GPU 上,当我们进入运行时挂起时会发生这种情况。在 Valleyview 和 Cherryview 系统上,当我们关闭所有电源域时也会发生这种情况。

由于此函数可以在我们已经持有 dev->mode_config.mutex 的上下文中被调用,因此我们在单独的工作线程中进行实际的热插拔启用。

另请参阅: intel_hpd_init()intel_hpd_poll_disable()

void intel_hpd_poll_disable(struct drm_i915_private *dev_priv)

禁用具有 hpd 的连接器的轮询

参数

struct drm_i915_private *dev_priv

i915 设备实例

描述

此函数禁用对所有支持 HPD 的连接器的轮询。在某些情况下,HPD 可能无法正常工作。在大多数 Intel GPU 上,当我们进入运行时挂起时会发生这种情况。在 Valleyview 和 Cherryview 系统上,当我们关闭所有电源域时也会发生这种情况。

由于此函数可以在我们已经持有 dev->mode_config.mutex 的上下文中被调用,因此我们在单独的工作线程中进行实际的热插拔启用。

也用于驱动程序初始化期间,以根据所有连接器适当地初始化 connector->polled。

另请参阅: intel_hpd_init()intel_hpd_poll_enable()

高清晰度音频

图形和音频驱动程序共同支持通过 HDMI 和 DisplayPort 的高清晰度音频。音频编程序列分为音频编解码器和控制器启用和禁用序列。图形驱动程序处理音频编解码器序列,而音频驱动程序处理音频控制器序列。

禁用序列必须在禁用转码器或端口之前执行。启用序列只能在启用转码器和端口以及完成链路训练后执行。因此,音频启用/禁用序列是模式设置序列的一部分。

编解码器和控制器序列可以并行或串行完成,但通常编解码器序列中的 ELDV/PD 更改会指示音频驱动程序应启动控制器序列。实际上,图形和音频驱动程序之间的大多数合作都是通过与音频相关的寄存器处理的。(一个明显的例外是电源管理,此处未涵盖。)

结构体 i915_audio_component 用于图形和音频驱动程序之间的交互。其中的结构体 i915_audio_component_ops ops 在图形驱动程序中定义,并在音频驱动程序中调用。结构体 i915_audio_component_audio_ops audio_ops 从 i915 驱动程序调用。

void intel_audio_codec_enable(struct intel_encoder *encoder, const struct intel_crtc_state *crtc_state, const struct drm_connector_state *conn_state)

启用高清音频的音频编解码器。

参数

struct intel_encoder *encoder

要在其上启用音频的编码器。

const struct intel_crtc_state *crtc_state

指向当前 CRTC 状态的指针。

const struct drm_connector_state *conn_state

指向当前连接器状态的指针。

描述

启用序列只能在启用转码器和端口,以及完成链路训练后执行。

void intel_audio_codec_disable(struct intel_encoder *encoder, const struct intel_crtc_state *old_crtc_state, const struct drm_connector_state *old_conn_state)

禁用高清音频的音频编解码器。

参数

struct intel_encoder *encoder

在其上禁用音频的编码器。

const struct intel_crtc_state *old_crtc_state

指向旧 CRTC 状态的指针。

const struct drm_connector_state *old_conn_state

指向旧连接器状态的指针。

描述

禁用序列必须在禁用转码器或端口之前执行。

void intel_audio_hooks_init(struct drm_i915_private *i915)

设置芯片特定的音频钩子。

参数

struct drm_i915_private *i915

设备私有数据。

void i915_audio_component_init(struct drm_i915_private *i915)

初始化并注册音频组件。

参数

struct drm_i915_private *i915

i915 设备实例

描述

这将向组件框架注册一个子组件,当后者注册时,该子组件将动态绑定到 snd_hda_intel 驱动程序的相应主组件。在绑定期间,子组件会初始化一个 struct i915_audio_component 的实例,该实例从主组件接收。然后,主组件可以开始使用此结构定义的接口。任何一方都可以在任何时候通过注销自己的组件来断开绑定,之后会调用每一方的组件的解除绑定回调。

我们忽略注册期间的任何错误,并继续使用精简的功能(即没有 HDMI 音频)。

void i915_audio_component_cleanup(struct drm_i915_private *i915)

注销音频组件。

参数

struct drm_i915_private *i915

i915 设备实例

描述

注销音频组件,断开与相应的 snd_hda_intel 驱动程序的主组件的任何现有绑定。

void intel_audio_init(struct drm_i915_private *i915)

使用组件框架或使用 LPE 音频桥初始化音频驱动程序。

参数

struct drm_i915_private *i915

i915 DRM 设备私有数据。

void intel_audio_deinit(struct drm_i915_private *i915)

反初始化音频驱动程序。

参数

struct drm_i915_private *i915

i915 DRM 设备私有数据。

struct i915_audio_component

用于 i915 和 HDA 驱动程序之间的直接通信。

定义:

struct i915_audio_component {
    struct drm_audio_component      base;
    int aud_sample_rate[MAX_PORTS];
};

成员

base

drm_audio_component 基类。

aud_sample_rate

每个端口的音频采样率数组。

Intel HDMI LPE 音频支持

动机:Atom 平台(例如 valleyview 和 cherryTrail)集成了一个基于 DMA 的接口,作为传统 HDaudio 路径的替代方案。虽然此模式与 LPE(又名 SST 音频引擎)无关,但文档将此模式称为 LPE,因此为了保持一致性,我们保留此符号。

该接口由 ALSA 子系统中维护的单独的独立驱动程序处理,为了简化。为了最大限度地减少两个子系统之间的交互,在 hdmi-lpe-audio 和 i915 之间建立了一个桥梁:1. 创建一个平台设备来共享 MMIO/IRQ 资源 2. 使平台设备成为 i915 设备的子设备,用于运行时 PM。3. 创建 IRQ 芯片以转发 LPE 音频 IRQ。hdmi-lpe-audio 驱动程序探测 LPE 音频设备并创建新的声卡。

威胁:由于 Linux 平台设备模型的限制,用户需要在卸载 i915 模块之前手动卸载 hdmi-lpe-audio 驱动程序,否则在 i915 删除平台设备后,我们可能会遇到释放后使用问题:即使 hdmi-lpe-audio 驱动程序已释放,模块仍处于“已安装”状态。

实现:MMIO/REG 平台资源根据寄存器规范创建。在转发 LPE 音频 IRQ 时,流控制处理程序选择取决于平台,例如在 valleyview 上,handle_simple_irq 就足够了。

void intel_lpe_audio_irq_handler(struct drm_i915_private *dev_priv)

转发 LPE 音频 IRQ。

参数

struct drm_i915_private *dev_priv

i915 DRM 设备私有数据。

描述

LPE 音频 IRQ 将转发到 LPE 音频驱动程序注册的 IRQ 处理程序。

int intel_lpe_audio_init(struct drm_i915_private *dev_priv)

检测并设置 HDMI LPE 音频驱动程序和 i915 之间的桥梁。

参数

struct drm_i915_private *dev_priv

i915 DRM 设备私有数据。

返回

成功则返回 0。如果检测或分配/初始化失败,则返回非零值。

void intel_lpe_audio_teardown(struct drm_i915_private *dev_priv)

销毁 HDMI LPE 音频驱动程序和 i915 之间的桥梁。

参数

struct drm_i915_private *dev_priv

i915 DRM 设备私有数据。

描述

释放 LPE 音频 <-> i915 桥梁的所有资源。

void intel_lpe_audio_notify(struct drm_i915_private *dev_priv, enum transcoder cpu_transcoder, enum port port, const void *eld, int ls_clock, bool dp_output)

通知 LPE 音频事件音频驱动程序和 i915。

参数

struct drm_i915_private *dev_priv

i915 DRM 设备私有数据。

enum transcoder cpu_transcoder

CPU 转码器。

enum port port

端口

const void *eld

ELD 数据。

int ls_clock

链接符号时钟(以 kHz 为单位)。

bool dp_output

是否正在驱动 DP 输出?

描述

通知 LPE 音频驱动程序 ELD 更改。

面板自刷新 PSR (PSR/SRD)

由于 Haswell 显示控制器支持在显示面板上进行面板自刷新,这些面板根据 eDP1.3 中的 PSR 规范实现了远程帧缓冲区 (RFB)。PSR 功能允许显示器在系统空闲但显示器开启时进入较低的待机状态,因为它完全消除了对 DDR 内存的显示刷新请求,只要该显示的帧缓冲区保持不变即可。

面板自刷新必须由硬件(源)和面板(接收器)支持。

PSR 通过将帧缓冲区缓存在面板 RFB 中来节省功耗,这使我们可以关闭链路和内存控制器的电源。对于 DSI 面板,相同的想法称为“手动模式”。

该实现使用基于硬件的 PSR 支持,它可以自动进入/退出自刷新模式。硬件负责发送所需的 DP aux 消息,甚至可以重新训练链路(但这部分尚未启用)。硬件还会跟踪任何前缓冲区的更改,以了解何时再次退出自刷新模式。不幸的是,这部分工作效果不佳,因此 i915 PSR 支持使用软件前缓冲区跟踪来确保不会错过屏幕更新。对于此集成,intel_psr_invalidate()intel_psr_flush() 由前缓冲区跟踪代码调用。请注意,由于锁定问题,自刷新重新启用代码是从工作队列完成的,该队列在关闭管道时必须正确同步/取消。”

DC3CO(DC3 时钟关闭)

在 PSR2 的基础上,GEN12 添加了一个中间省电状态,该状态在 PSR2 空闲状态期间自动关闭时钟。与 PSR2 深度睡眠进入/退出开销相比,DC3co 进入/退出开销更小,这使得硬件即使在页面定期翻转时(例如,30fps 视频播放场景)也能进入低功耗状态。

每次发生翻转时,PSR2 将退出深度睡眠状态(如果它处于深度睡眠状态),因此启用 DC3CO,并且 tgl_dc3co_disable_work 被安排在 6 帧后运行。如果没有发生其他翻转并且执行了上述函数,则禁用 DC3CO,并将 PSR2 配置为进入深度睡眠,并在发生另一次翻转时再次重置。前缓冲区修改不会故意触发 DC3CO 激活,因为它会带来很多复杂性,而且大多数现代系统只会使用页面翻转。

void intel_psr_disable(struct intel_dp *intel_dp, const struct intel_crtc_state *old_crtc_state)

禁用 PSR

参数

struct intel_dp *intel_dp

Intel DP

const struct intel_crtc_state *old_crtc_state

旧的 CRTC 状态

描述

此函数需要在禁用管道之前调用。

void intel_psr_pause(struct intel_dp *intel_dp)

暂停 PSR

参数

struct intel_dp *intel_dp

Intel DP

描述

此函数需要在启用 psr 后调用。

void intel_psr_resume(struct intel_dp *intel_dp)

恢复 PSR

参数

struct intel_dp *intel_dp

Intel DP

描述

此函数需要在暂停 psr 后调用。

bool intel_psr_needs_block_dc_vblank(const struct intel_crtc_state *crtc_state)

检查是否需要阻止 dc 进入

参数

const struct intel_crtc_state *crtc_state

CRTC 状态

描述

如果使用面板重放,我们需要阻止 DC6 进入,因为启用 VBI 并不能阻止它。面板重放在 DC 进入时会关闭主链路。这意味着不会触发垂直空白中断,如果用户空间轮询垂直空白事件,则会出现问题。

void intel_psr_wait_for_idle_locked(const struct intel_crtc_state *new_crtc_state)

等待 PSR 为管道更新做好准备

参数

const struct intel_crtc_state *new_crtc_state

新的 CRTC 状态

描述

此函数预计从 pipe_update_start() 中调用,其中它不应与 PSR 启用或禁用竞争。

void intel_psr_invalidate(struct intel_display *display, unsigned frontbuffer_bits, enum fb_op_origin origin)

使 PSR 无效

参数

struct intel_display *display

显示设备

unsigned frontbuffer_bits

前缓冲区平面跟踪位

enum fb_op_origin origin

哪个操作导致无效

描述

由于硬件前缓冲区跟踪存在差距,我们需要与软件前缓冲区跟踪集成。每次前缓冲区渲染开始并且缓冲区变脏时,都会调用此函数。如果前缓冲区掩码包含与 PSR 相关的缓冲区,则必须禁用 PSR。

与 PSR 相关的脏前缓冲区在 busy_frontbuffer_bits 中跟踪。”

void intel_psr_flush(struct intel_display *display, unsigned frontbuffer_bits, enum fb_op_origin origin)

刷新 PSR

参数

struct intel_display *display

显示设备

unsigned frontbuffer_bits

前缓冲区平面跟踪位

enum fb_op_origin origin

导致刷新的操作

描述

由于硬件前缓冲区跟踪存在差距,我们需要与软件前缓冲区跟踪集成。每次前缓冲区渲染完成并刷新到内存时,都会调用此函数。如果没有其他与 PSR 相关的脏前缓冲区,则可以再次启用 PSR。

与 PSR 相关的脏前缓冲区在 busy_frontbuffer_bits 中跟踪。

void intel_psr_init(struct intel_dp *intel_dp)

初始化基本的 PSR 工作和互斥锁。

参数

struct intel_dp *intel_dp

Intel DP

描述

此函数在初始化连接器后调用。(连接器的初始化处理连接器功能)它为每个 DP 编码器初始化基本的 PSR 内容。

返回 psr->link_ok

参数

struct intel_dp *intel_dp

struct intel_dp

描述

我们看到某些面板出现意外的链路重新训练。这是由面板在启用 PSR 后声明错误的链路状态引起的。检查链路状态的代码可以调用此函数以确保它可以忽略面板声明的错误链路状态。例如,如果面板声明链路错误并且 intel_psr_link_ok 声明链路正常,则调用者应依赖后者。

link_ok 的返回值

void intel_psr_lock(const struct intel_crtc_state *crtc_state)

获取 PSR 锁

参数

const struct intel_crtc_state *crtc_state

crtc 状态

描述

最初的目的是在 CRTC 更新时使用,当更新对垂直空白敏感的寄存器时,我们需要先获取锁以避免垂直空白逃逸。

void intel_psr_unlock(const struct intel_crtc_state *crtc_state)

释放 PSR 锁

参数

const struct intel_crtc_state *crtc_state

crtc 状态

描述

释放在管道更新期间持有的 PSR 锁。

帧缓冲区压缩 (FBC)

FBC 尝试通过压缩显示器使用的内存量来节省内存带宽(从而降低功耗)。它对用户空间完全透明,并且完全在内核中处理。

FBC 的好处在具有纯色背景和变化较少的图案时最明显。它来自保持较小的内存占用空间,并减少打开和访问用于刷新显示的内存页面。

i915 负责为 FBC 保留被盗内存,并在正确的寄存器上配置其偏移量。硬件负责所有压缩/解压缩。但是,在许多已知的情况下,我们必须强制禁用它以允许正确的屏幕更新。

void intel_fbc_disable(struct intel_crtc *crtc)

如果 FBC 与 crtc 相关联,则禁用 FBC

参数

struct intel_crtc *crtc

CRTC

描述

如果 FBC 与提供的 CRTC 相关联,此函数将禁用 FBC。

void intel_fbc_handle_fifo_underrun_irq(struct intel_display *display)

当发生 FIFO 下溢时禁用 FBC

参数

struct intel_display *display

显示

描述

在没有 FBC 的情况下,大多数下溢是无害的,并且不会真正导致太多问题,除了 dmesg 上出现烦人的消息。 使用 FBC,下溢可能会变成黑屏,甚至更糟,尤其是在与不良水印配对时。因此,为了安全起见,如果我们在任何管道上检测到 FIFO 下溢,则完全禁用 FBC。 任何管道上的下溢都表明水印可能不良,因此请尽量安全。

此函数是从 IRQ 处理程序调用的。

void intel_fbc_init(struct intel_display *display)

初始化 FBC

参数

struct intel_display *display

显示

描述

此函数可能会在 PM 初始化过程中被调用。

void intel_fbc_sanitize(struct intel_display *display)

清理 FBC

参数

struct intel_display *display

显示

描述

确保 FBC 最初被禁用,因为我们不知道它可能会涂写到被盗取的哪个部分。

显示刷新率切换 (DRRS)

显示刷新率切换 (DRRS) 是一种节能功能,可根据使用场景动态地在低刷新率和高刷新率之间切换。此功能适用于内部面板。

面板 EDID 会提供面板支持 DRRS 的指示,它会列出一种分辨率的多个刷新率。

DRRS 有两种类型 - 静态和无缝。 静态 DRRS 涉及通过执行完整模式设置来更改刷新率 (RR)(可能在屏幕上显示为闪烁),并用于停靠/取消停靠场景。 无缝 DRRS 涉及在不给用户带来任何视觉效果的情况下更改 RR,并且可以在正常系统使用期间使用。 这是通过编程某些寄存器来完成的。

基于面板规格的输入,VBT 中可能会指示对静态/无缝 DRRS 的支持。

DRRS 通过根据使用场景切换到低 RR 来节省功耗。

该实现基于前缓冲区跟踪实现。 当屏幕上因用户活动或定期系统活动而出现干扰时,DRRS 将被禁用(RR 将更改为高 RR)。当屏幕上没有移动时,在 1 秒的超时后,将切换到低 RR。

为了与前缓冲区跟踪代码集成,会调用 intel_drrs_invalidate()intel_drrs_flush()

DRRS 可以进一步扩展以支持其他内部面板,以及视频播放场景,其中 RR 基于用户空间请求的速率进行设置。

void intel_drrs_activate(const struct intel_crtc_state *crtc_state)

激活 DRRS

参数

const struct intel_crtc_state *crtc_state

crtc 状态

描述

在 crtc 上激活 DRRS。

void intel_drrs_deactivate(const struct intel_crtc_state *old_crtc_state)

停用 DRRS

参数

const struct intel_crtc_state *old_crtc_state

旧的 crtc 状态

描述

停用 crtc 上的 DRRS。

void intel_drrs_invalidate(struct drm_i915_private *dev_priv, unsigned int frontbuffer_bits)

禁用空闲 DRRS

参数

struct drm_i915_private *dev_priv

i915 设备

unsigned int frontbuffer_bits

前缓冲区平面跟踪位

描述

每次在给定平面上开始渲染时都会调用此函数。因此,DRRS 需要升频,即(LOW_RR -> HIGH_RR)。

与 DRRS 相关的脏前缓冲区在 busy_frontbuffer_bits 中进行跟踪。

void intel_drrs_flush(struct drm_i915_private *dev_priv, unsigned int frontbuffer_bits)

重新启动空闲 DRRS

参数

struct drm_i915_private *dev_priv

i915 设备

unsigned int frontbuffer_bits

前缓冲区平面跟踪位

描述

每次完成在给定平面上的渲染或完成 crtc 上的翻转时都会调用此函数。 因此,DRRS 应该升频(LOW_RR -> HIGH_RR)。 如果没有其他平面是脏的,还应该再次开始空闲检测。

与 DRRS 相关的脏前缓冲区在 busy_frontbuffer_bits 中进行跟踪。

void intel_drrs_crtc_init(struct intel_crtc *crtc)

为 CRTC 初始化 DRRS

参数

struct intel_crtc *crtc

crtc

描述

此函数仅在驱动程序加载时调用一次,以初始化基本的 DRRS 内容。

DPIO

VLV、CHV 和 BXT 具有用于驱动 DP/HDMI 端口的稍微特殊的显示 PHY。DPIO 是赋予此类显示 PHY 的名称。这些 PHY 不遵循使用直接 MMIO 寄存器的标准编程模型,相反,它们的寄存器必须通过 IOSF 侧带访问。VLV 有一个这样的 PHY 用于驱动端口 B 和 C,而 CHV 为驱动端口 D 添加了另一个 PHY。每个 PHY 都响应特定的 IOSF-SB 端口。

每个显示 PHY 由一个或两个通道组成。每个通道包含一个公共通道部分,其中包含 PLL 和其他公共逻辑。CH0 公共通道还包含用于公共寄存器接口 (CRI)(即 DPIO 寄存器)的 IOSF-SB 逻辑。当访问任何 DPIO 寄存器时,CRI 时钟必须运行。

除了拥有自己的寄存器外,PHY 还通过显示控制器的一些专用信号进行控制。其中包括 PLL 参考时钟使能、PLL 使能和 CRI 时钟选择等。

每个通道还有两个样条(也称为数据通道),每个样条由一个物理访问编码子层 (PCS) 块和两个 TX 通道组成。因此,每个通道都有两个 PCS 块和四个 TX 通道。TX 通道用作 DP 通道或 TMDS 数据/时钟对,具体取决于输出类型。

此外,PHY 还包含每个通道都有 AUX 块的 AUX 通道。这用于 DP AUX 通信,但此事实与驱动程序无关,因为 AUX 是从显示控制器端控制的。在 AUX 通信期间不需要访问任何 DPIO 寄存器。

通常,在 VLV/CHV 上,公共通道对应于管道,而样条 (PCS/TX) 对应于端口。

对于双通道 PHY (VLV/CHV)

管道 A == CMN/PLL/REF CH0

管道 B == CMN/PLL/REF CH1

端口 B == PCS/TX CH0

端口 C == PCS/TX CH1

当我们跨流时(即用管道 B 驱动端口 B,或用管道 A 驱动端口 C)这一点尤其重要。

对于单通道 PHY (CHV)

管道 C == CMN/PLL/REF CH0

端口 D == PCS/TX CH0

在 BXT 上,整个 PHY 通道对应于端口。这意味着 PLL 现在也与端口而不是管道相关联,因此时钟需要路由到适当的转码器。端口 A PLL 直接连接到转码器 EDP,端口 B/C PLL 可以路由到任何转码器 A/B/C。

注意:DDI0 是数字端口 B,DDI1 是数字端口 C,而 DDI2 是数字端口 D (CHV) 或端口 A (BXT)。

Dual channel PHY (VLV/CHV/BXT)
---------------------------------
|      CH0      |      CH1      |
|  CMN/PLL/REF  |  CMN/PLL/REF  |
|---------------|---------------| Display PHY
| PCS01 | PCS23 | PCS01 | PCS23 |
|-------|-------|-------|-------|
|TX0|TX1|TX2|TX3|TX0|TX1|TX2|TX3|
---------------------------------
|     DDI0      |     DDI1      | DP/HDMI ports
---------------------------------

Single channel PHY (CHV/BXT)
-----------------
|      CH0      |
|  CMN/PLL/REF  |
|---------------| Display PHY
| PCS01 | PCS23 |
|-------|-------|
|TX0|TX1|TX2|TX3|
-----------------
|     DDI2      | DP/HDMI port
-----------------

DMC 固件支持

从 gen9 开始,我们在显示引擎中新添加了 DMC(显示微控制器),以便在显示引擎进入低功耗状态并返回正常状态时保存和恢复显示引擎的状态。

void intel_dmc_load_program(struct intel_display *display)

将固件从内存写入寄存器。

参数

struct intel_display *display

显示实例

描述

DMC 固件从 .bin 文件读取并一次性保存在内部内存中。每次显示从低功耗状态返回时,都会调用此函数将固件从内部内存复制到寄存器。

void intel_dmc_disable_program(struct intel_display *display)

禁用固件

参数

struct intel_display *display

显示实例

描述

禁用固件中的所有事件处理程序,确保在显示取消初始化后固件处于非活动状态。

void intel_dmc_init(struct intel_display *display)

初始化固件加载。

参数

struct intel_display *display

显示实例

描述

此函数在加载显示驱动程序时被调用,从 .bin 文件读取固件并复制到内部内存。

void intel_dmc_suspend(struct intel_display *display)

在系统挂起之前准备 DMC 固件

参数

struct intel_display *display

显示实例

描述

在进入系统挂起之前准备 DMC 固件。这包括刷新挂起的工作项和释放初始化期间获取的任何资源。

void intel_dmc_resume(struct intel_display *display)

在系统恢复期间初始化 DMC 固件

参数

struct intel_display *display

显示实例

描述

在系统恢复期间重新初始化 DMC 固件,重新获取在 intel_dmc_suspend() 中释放的任何资源。

void intel_dmc_fini(struct intel_display *display)

卸载 DMC 固件。

参数

struct intel_display *display

显示实例

描述

固件卸载包括释放内部内存和重置固件加载状态。

DMC 唤醒锁支持

唤醒锁是一种使显示引擎退出 DC 状态的机制,以便对这些状态下断电的寄存器进行编程。之前的项目在检测到编程时会自动退出 DC 状态。现在,软件通过对唤醒锁进行编程来控制退出。这提高了系统性能和系统交互,并且更适合编程的翻转队列风格。仅当在 DC_STATE_EN 中启用了 DC5、DC6 或 DC6v,并且已启用唤醒锁操作模式时,才需要唤醒锁。

DMC 中的唤醒锁机制允许显示引擎在对可能断电的寄存器进行编程之前显式退出 DC 状态。在较早的硬件中,当显示引擎访问寄存器时,这是自动且隐式完成的。使用唤醒锁实现,驱动程序在 DMC 中断言唤醒锁,这将强制其退出 DC 状态,直到取消断言唤醒锁。

可以通过写入 DMC_WAKELOCK_CFG 寄存器来启用和禁用该机制。还有 13 个控制寄存器,可用于保持和释放不同的唤醒锁。在当前实现中,我们只需要一个唤醒锁,因此只使用 DMC_WAKELOCK1_CTL。此处提供了其他定义,以备将来使用。

视频 BIOS 表 (VBT)

视频 BIOS 表 (VBT) 向驱动程序提供平台和板特定的配置信息,这些信息无法通过其他方式发现或获得。该配置主要与显示硬件相关。VBT 可通过 ACPI OpRegion 获得,或者在较旧的系统上,可通过 PCI ROM 获得。

VBT 由 VBT 标头(定义为 struct vbt_header)、BDB 标头 (struct bdb_header) 和多个包含实际配置信息的 BIOS 数据块 (BDB) 组成。VBT 标头(以及 VBT)以“$VBT”签名开头。VBT 标头包含 BDB 标头的偏移量。数据块在 BDB 标头之后串联。数据块具有 1 字节的块 ID、2 字节的块大小以及块大小字节的数据。(块 53,MIPI 序列块是一个例外。)

驱动程序在加载期间解析 VBT。相关信息存储在驱动程序私有数据中,以便于使用,之后不再读取实际 VBT。

bool intel_bios_is_valid_vbt(struct intel_display *display, const void *buf, size_t size)

给定的缓冲区是否包含有效的 VBT

参数

struct intel_display *display

显示设备

const void *buf

指向要验证的缓冲区的指针

size_t size

缓冲区的大小

描述

在有效的 VBT 上返回 true。

void intel_bios_init(struct intel_display *display)

查找 VBT 并从 BIOS 初始化设置

参数

struct intel_display *display

显示设备实例

描述

解析视频 BIOS 表 (VBT) 并从中初始化设置。如果在 ACPI OpRegion 中未找到 VBT,请尝试先在 PCI ROM 中查找它。如果根本不存在 VBT,则初始化一些默认值。

void intel_bios_driver_remove(struct intel_display *display)

释放由 intel_bios_init() 分配的任何资源

参数

struct intel_display *display

显示设备实例

bool intel_bios_is_tv_present(struct intel_display *display)

VBT 中是否存在集成电视

参数

struct intel_display *display

显示设备实例

描述

如果存在电视,则返回 true。如果未从 VBT 解析任何子设备,则假定存在电视。

bool intel_bios_is_lvds_present(struct intel_display *display, u8 *i2c_pin)

VBT 中是否存在 LVDS

参数

struct intel_display *display

显示设备实例

u8 *i2c_pin

如果存在,LVDS 的 i2c 引脚

描述

如果存在 LVDS,则返回 true。如果未从 VBT 解析任何子设备,则假定存在 LVDS。

bool intel_bios_is_port_present(struct intel_display *display, enum port port)

指定的数字端口是否存在

参数

struct intel_display *display

显示设备实例

enum port port

要检查的端口

描述

如果port中的设备存在,则返回 true。

bool intel_bios_is_dsi_present(struct intel_display *display, enum port *port)

DSI 是否存在于 VBT 中

参数

struct intel_display *display

显示设备实例

enum port *port

如果存在,则为 DSI 的端口

描述

如果 DSI 存在,则返回 true,并在 port 中返回端口。

struct vbt_header

VBT 头部结构

定义:

struct vbt_header {
    u8 signature[20];
    u16 version;
    u16 header_size;
    u16 vbt_size;
    u8 vbt_checksum;
    u8 reserved0;
    u32 bdb_offset;
    u32 aim_offset[4];
};

成员

签名

VBT 签名,始终以 “$VBT” 开头

版本

此结构的版本

header_size

此结构的大小

vbt_size

VBT 的大小(VBT 头部、BDB 头部和数据块)

vbt_checksum

校验和

reserved0

保留

bdb_offset

从 VBT 开头到 struct bdb_header 的偏移量

aim_offset

从 VBT 开头到附加数据块的偏移量

struct bdb_header

BDB 头部结构

定义:

struct bdb_header {
    u8 signature[16];
    u16 version;
    u16 header_size;
    u16 bdb_size;
};

成员

签名

BDB 签名 “BIOS_DATA_BLOCK”

版本

数据块定义的版本

header_size

此结构的大小

bdb_size

BDB 的大小(BDB 头部和数据块)

显示时钟

显示引擎使用几个不同的时钟来完成其工作。其中涉及两个主要时钟,它们与实际像素时钟或任何实际输出端口的符号/位时钟没有直接关系。它们是核心显示时钟 (CDCLK) 和 RAWCLK。

CDCLK 为大多数显示管道逻辑计时,因此其频率必须足够高,以支持像素流经管道的速率。还必须考虑缩小,因为它会增加有效像素速率。

在多个平台上,可以动态更改 CDCLK 频率,以最大限度地减少给定显示配置的功耗。通常,更改 CDCLK 频率需要关闭所有显示管道,同时更改频率。

在 SKL+ 上,DMC 将在 DC5/6 进入/退出期间切换 CDCLK 的开关。但是,DMC 不会更改活动的 CDCLK 频率,因此该部分仍将由驱动程序直接执行。

CDCLK 频率的生成涉及多个组件

  • 我们有 CDCLK PLL,它根据参考时钟和比率参数生成输出时钟。

  • CD2X 分频器,它根据从一组预定义选项中选择的分频器来划分 PLL 的输出。

  • CD2X 抑制器,它根据表示为位序列的波形进一步划分输出,其中每个零“抑制”一个时钟周期。

  • 最后,一个固定分频器,将输出频率除以 2。

因此,可以使用以下公式计算得到的 CDCLK 频率

cdclk = vco / cd2x_div / (sq_len / sq_div) / 2

,其中 vco 是由 PLL 生成的频率;cd2x_div 表示 CD2X 分频器;sq_len 和 sq_div 分别是 CD2X 抑制器波形的位长度和高位数量;而 2 表示固定分频器。

请注意,一些较旧的平台不包含 CD2X 分频器和/或 CD2X 抑制器,在这种情况下,我们可以忽略上面公式中它们各自的因子。

存在多种方法来更改 CDCLK 频率,支持哪些方法取决于平台

  • 完全禁用 PLL + 使用新的 VCO 频率重新启用。管道必须处于非活动状态。

  • CD2X 分频器更新。单个管道可以处于活动状态,因为分频器更新可以与管道的垂直消隐开始同步。

  • 将 PLL 平滑爬升到新的 VCO 频率。管道可以处于活动状态。

  • 抑制波形更新。管道可以处于活动状态。

  • 爬升和抑制也可以背靠背完成。管道可以处于活动状态。

RAWCLK 是一种固定频率时钟,通常被各种辅助块使用,例如 AUX CH 或背光 PWM。因此,我们真正需要了解的关于 RAWCLK 的唯一信息是其频率,以便可以正确地对各种分频器进行编程。

void intel_cdclk_init_hw(struct intel_display *display)

初始化 CDCLK 硬件

参数

struct intel_display *display

显示实例

描述

初始化 CDCLK。这主要包括初始化 display->cdclk.hw 并清理硬件状态(如果需要)。这通常仅在显示核心初始化序列期间完成,之后 DMC 将负责根据需要关闭/打开 CDCLK。

void intel_cdclk_uninit_hw(struct intel_display *display)

取消初始化 CDCLK 硬件

参数

struct intel_display *display

显示实例

描述

取消初始化 CDCLK。这仅在显示核心取消初始化序列期间完成。

bool intel_cdclk_clock_changed(const struct intel_cdclk_config *a, const struct intel_cdclk_config *b)

检查时钟是否已更改

参数

const struct intel_cdclk_config *a

第一个 CDCLK 配置

const struct intel_cdclk_config *b

第二个 CDCLK 配置

返回

如果 CDCLK 的更改方式需要重新编程,则为 True,否则为 False。

bool intel_cdclk_can_cd2x_update(struct intel_display *display, const struct intel_cdclk_config *a, const struct intel_cdclk_config *b)

确定在两个 CDCLK 配置之间进行更改是否只需要更新 cd2x 分频器

参数

struct intel_display *display

显示实例

const struct intel_cdclk_config *a

第一个 CDCLK 配置

const struct intel_cdclk_config *b

第二个 CDCLK 配置

返回

如果仅通过更新 cd2x 分频器即可完成两个 CDCLK 配置之间的更改,则为 True,否则为 false。

bool intel_cdclk_changed(const struct intel_cdclk_config *a, const struct intel_cdclk_config *b)

确定两个 CDCLK 配置是否不同

参数

const struct intel_cdclk_config *a

第一个 CDCLK 配置

const struct intel_cdclk_config *b

第二个 CDCLK 配置

返回

如果 CDCLK 配置不匹配,则为 True,如果匹配,则为 false。

void intel_set_cdclk_pre_plane_update(struct intel_atomic_state *state)

将 CDCLK 状态推送到硬件

参数

struct intel_atomic_state *state

英特尔原子状态

描述

如果需要,请在根据新的 CDCLK 状态更新硬件平面状态之前对硬件进行编程。

void intel_set_cdclk_post_plane_update(struct intel_atomic_state *state)

将 CDCLK 状态推送到硬件

参数

struct intel_atomic_state *state

英特尔原子状态

描述

如果需要,请在根据新的 CDCLK 状态更新硬件平面状态之后对硬件进行编程。

void intel_update_max_cdclk(struct intel_display *display)

确定最大支持的 CDCLK 频率

参数

struct intel_display *display

显示实例

描述

确定平台支持的最大 CDCLK 频率,并推导出最大 CDCLK 频率允许的最大点时钟频率。

void intel_update_cdclk(struct intel_display *display)

确定当前的 CDCLK 频率

参数

struct intel_display *display

显示实例

描述

确定当前的 CDCLK 频率。

u32 intel_read_rawclk(struct intel_display *display)

确定当前的 RAWCLK 频率

参数

struct intel_display *display

显示实例

描述

确定当前的 RAWCLK 频率。RAWCLK 是一个固定频率时钟,因此只需要执行一次此操作。

void intel_init_cdclk_hooks(struct intel_display *display)

初始化与 CDCLK 相关的模式设置钩子

参数

struct intel_display *display

显示实例

显示 PLL

用于驱动输出的显示 PLL 因平台而异。虽然有些平台具有每个管道或每个编码器专用的 PLL,但其他平台允许使用池中的任何 PLL。在后一种情况下,如果多个管道的配置匹配,则它们可能共享一个 PLL。

此文件提供了显示 PLL 的抽象。函数 intel_shared_dpll_init() 为给定的平台初始化 PLL。跟踪 PLL 的用户,并且该跟踪与原子模式设置接口集成。在原子操作期间,可以通过调用 intel_reserve_shared_dplls() 为给定的 CRTC 和编码器配置保留所需的 PLL,并且可以使用 intel_release_shared_dplls() 释放先前保留的 PLL。对用户的更改首先在原子状态中进行暂存,然后在原子提交阶段通过调用 intel_shared_dpll_swap_state() 使其生效。

struct intel_shared_dpll *intel_get_shared_dpll_by_id(struct drm_i915_private *i915, enum intel_dpll_id id)

根据 ID 获取 DPLL

参数

struct drm_i915_private *i915

i915 设备实例

enum intel_dpll_id id

PLL ID

返回

指向具有 id 的 DPLL 的指针

void intel_enable_shared_dpll(const struct intel_crtc_state *crtc_state)

启用 CRTC 的共享 DPLL

参数

const struct intel_crtc_state *crtc_state

CRTC 及其状态,其中包含共享 DPLL

描述

启用 crtc 使用的共享 DPLL。

void intel_disable_shared_dpll(const struct intel_crtc_state *crtc_state)

禁用 CRTC 的共享 DPLL

参数

const struct intel_crtc_state *crtc_state

CRTC 及其状态,其中包含共享 DPLL

描述

禁用 crtc 使用的共享 DPLL。

void intel_reference_shared_dpll_crtc(const struct intel_crtc *crtc, const struct intel_shared_dpll *pll, struct intel_shared_dpll_state *shared_dpll_state)

获取 CRTC 的 DPLL 引用

参数

const struct intel_crtc *crtc

代表其获取引用的 CRTC

const struct intel_shared_dpll *pll

获取引用的 DPLL

struct intel_shared_dpll_state *shared_dpll_state

在其中跟踪引用的 DPLL 原子状态

描述

获取 pll 的引用,跟踪 crtc 对它的使用情况。

void intel_unreference_shared_dpll_crtc(const struct intel_crtc *crtc, const struct intel_shared_dpll *pll, struct intel_shared_dpll_state *shared_dpll_state)

删除 CRTC 的 DPLL 引用

参数

const struct intel_crtc *crtc

代表其删除引用的 CRTC

const struct intel_shared_dpll *pll

删除引用的 DPLL

struct intel_shared_dpll_state *shared_dpll_state

在其中跟踪引用的 DPLL 原子状态

描述

删除 pll 的引用,跟踪 crtc 对它的使用结束。

void intel_shared_dpll_swap_state(struct intel_atomic_state *state)

使原子 DPLL 配置生效

参数

struct intel_atomic_state *state

原子状态

描述

这是 drm_atomic_helper_swap_state() 的 DPLL 版本,因为辅助程序不处理驱动程序特定的全局状态。

为了与原子辅助程序保持一致,此函数会进行完整的交换,即,它还会将当前状态放入 state 中,即使目前没有此需求。

void icl_set_active_port_dpll(struct intel_crtc_state *crtc_state, enum icl_port_dpll_id port_dpll_id)

为给定的 CRTC 选择活动端口 DPLL

参数

struct intel_crtc_state *crtc_state

用于选择 DPLL 的 CRTC 的状态

enum icl_port_dpll_id port_dpll_id

要选择的活动 port_dpll_id

描述

从为 CRTC 保留的 DPLL 中选择给定的 port_dpll_id 实例。

void intel_shared_dpll_init(struct drm_i915_private *i915)

初始化共享 DPLL

参数

struct drm_i915_private *i915

i915 设备

描述

i915 初始化共享 DPLL。

int intel_compute_shared_dplls(struct intel_atomic_state *state, struct intel_crtc *crtc, struct intel_encoder *encoder)

计算 CRTC 和编码器组合的 DPLL 状态

参数

struct intel_atomic_state *state

原子状态

struct intel_crtc *crtc

为其计算 DPLL 的 CRTC

struct intel_encoder *encoder

编码器

描述

此函数计算给定 CRTC 和编码器的 DPLL 状态。

原子提交状态中的新配置通过调用 intel_shared_dpll_swap_state() 生效。

返回

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

int intel_reserve_shared_dplls(struct intel_atomic_state *state, struct intel_crtc *crtc, struct intel_encoder *encoder)

为 CRTC 和编码器组合预留 DPLL

参数

struct intel_atomic_state *state

原子状态

struct intel_crtc *crtc

要为其预留 DPLL 的 CRTC

struct intel_encoder *encoder

编码器

描述

此函数在当前原子提交状态和新的 crtc 原子状态中,为给定的 CRTC 和编码器组合预留所有必需的 DPLL。

原子提交状态中的新配置通过调用 intel_shared_dpll_swap_state() 生效。

预留的 DPLL 应该通过调用 intel_release_shared_dplls() 释放。

返回

如果所有必需的 DPLL 都成功预留,则返回 0,否则返回负错误代码。

void intel_release_shared_dplls(struct intel_atomic_state *state, struct intel_crtc *crtc)

在原子状态中结束 CRTC 对 DPLL 的使用

参数

struct intel_atomic_state *state

原子状态

struct intel_crtc *crtc

要从中释放 DPLL 的 crtc

描述

此函数从当前原子提交状态和旧的 crtc 原子状态中,释放 intel_reserve_shared_dplls() 预留的所有 DPLL。

原子提交状态中的新配置通过调用 intel_shared_dpll_swap_state() 生效。

void intel_update_active_dpll(struct intel_atomic_state *state, struct intel_crtc *crtc, struct intel_encoder *encoder)

更新 CRTC/编码器的活动 DPLL

参数

struct intel_atomic_state *state

原子状态

struct intel_crtc *crtc

要为其更新活动 DPLL 的 CRTC

struct intel_encoder *encoder

确定端口 DPLL 类型的编码器

描述

intel_reserve_shared_dplls() 之前预留的端口 DPLL 中,更新给定 crtc/encodercrtc 原子状态中的活动 DPLL。选择的 DPLL 将基于编码器端口的当前模式。

int intel_dpll_get_freq(struct drm_i915_private *i915, const struct intel_shared_dpll *pll, const struct intel_dpll_hw_state *dpll_hw_state)

计算 DPLL 的输出频率

参数

struct drm_i915_private *i915

i915 设备

const struct intel_shared_dpll *pll

要计算输出频率的 DPLL

const struct intel_dpll_hw_state *dpll_hw_state

从中计算输出频率的 DPLL 状态

描述

返回与传入的 dpll_hw_statepll 对应的输出频率。

bool intel_dpll_get_hw_state(struct drm_i915_private *i915, struct intel_shared_dpll *pll, struct intel_dpll_hw_state *dpll_hw_state)

读取 DPLL 的硬件状态

参数

struct drm_i915_private *i915

i915 设备

struct intel_shared_dpll *pll

要计算输出频率的 DPLL

struct intel_dpll_hw_state *dpll_hw_state

DPLL 的硬件状态

描述

pll 的硬件状态读取到 dpll_hw_state 中。

void intel_dpll_dump_hw_state(struct drm_i915_private *i915, struct drm_printer *p, const struct intel_dpll_hw_state *dpll_hw_state)

转储 hw_state

参数

struct drm_i915_private *i915

i915 drm 设备

struct drm_printer *p

将状态打印到哪里

const struct intel_dpll_hw_state *dpll_hw_state

要转储的 hw 状态

描述

dpll_hw_state 中转储相关值。

bool intel_dpll_compare_hw_state(struct drm_i915_private *i915, const struct intel_dpll_hw_state *a, const struct intel_dpll_hw_state *b)

比较两个状态

参数

struct drm_i915_private *i915

i915 drm 设备

const struct intel_dpll_hw_state *a

第一个 DPLL hw 状态

const struct intel_dpll_hw_state *b

第二个 DPLL hw 状态

描述

比较 DPLL hw 状态 ab

返回

如果状态相等,则为 true;如果不同,则为 false

enum intel_dpll_id

可能的 DPLL ID

常量

DPLL_ID_PRIVATE

正在使用的非共享 dpll

DPLL_ID_PCH_PLL_A

ILK、SNB 和 IVB 中的 DPLL A

DPLL_ID_PCH_PLL_B

ILK、SNB 和 IVB 中的 DPLL B

DPLL_ID_WRPLL1

HSW 和 BDW WRPLL1

DPLL_ID_WRPLL2

HSW 和 BDW WRPLL2

DPLL_ID_SPLL

HSW 和 BDW SPLL

DPLL_ID_LCPLL_810

HSW 和 BDW 0.81 GHz LCPLL

DPLL_ID_LCPLL_1350

HSW 和 BDW 1.35 GHz LCPLL

DPLL_ID_LCPLL_2700

HSW 和 BDW 2.7 GHz LCPLL

DPLL_ID_SKL_DPLL0

SKL 及更高版本的 DPLL0

DPLL_ID_SKL_DPLL1

SKL 及更高版本的 DPLL1

DPLL_ID_SKL_DPLL2

SKL 及更高版本的 DPLL2

DPLL_ID_SKL_DPLL3

SKL 及更高版本的 DPLL3

DPLL_ID_ICL_DPLL0

ICL/TGL 组合 PHY DPLL0

DPLL_ID_ICL_DPLL1

ICL/TGL 组合 PHY DPLL1

DPLL_ID_EHL_DPLL4

EHL 组合 PHY DPLL4

DPLL_ID_ICL_TBTPLL

ICL/TGL TBT PLL

DPLL_ID_ICL_MGPLL1
ICL MG PLL 1 端口 1 (C),

TGL TC PLL 1 端口 1 (TC1)

DPLL_ID_ICL_MGPLL2
ICL MG PLL 1 端口 2 (D)

TGL TC PLL 1 端口 2 (TC2)

DPLL_ID_ICL_MGPLL3
ICL MG PLL 1 端口 3 (E)

TGL TC PLL 1 端口 3 (TC3)

DPLL_ID_ICL_MGPLL4
ICL MG PLL 1 端口 4 (F)

TGL TC PLL 1 端口 4 (TC4)

DPLL_ID_TGL_MGPLL5

TGL TC PLL 端口 5 (TC5)

DPLL_ID_TGL_MGPLL6

TGL TC PLL 端口 6 (TC6)

DPLL_ID_DG1_DPLL0

DG1 组合 PHY DPLL0

DPLL_ID_DG1_DPLL1

DG1 组合 PHY DPLL1

DPLL_ID_DG1_DPLL2

DG1 组合 PHY DPLL2

DPLL_ID_DG1_DPLL3

DG1 组合 PHY DPLL3

描述

DPLL 的可能 ID 的枚举。真正的共享 dpll ID 必须 >= 0。

struct intel_shared_dpll_state

保持 DPLL 原子状态

定义:

struct intel_shared_dpll_state {
    u8 pipe_mask;
    struct intel_dpll_hw_state hw_state;
};

成员

pipe_mask

使用此 DPLL 的管道的掩码,无论是否处于活动状态

hw_state

存储在结构 intel_dpll_hw_state 中的 DPLL 的硬件配置。

描述

此结构保存 DPLL 的原子状态,该状态可以表示其当前状态(在结构 intel_shared_dpll 中)或所需未来状态,该状态将由原子模式集应用(存储在结构 intel_atomic_state 中)。

另请参阅 intel_reserve_shared_dplls()intel_release_shared_dplls()

struct dpll_info

显示 PLL 平台特定信息

定义:

struct dpll_info {
    const char *name;
    const struct intel_shared_dpll_funcs *funcs;
    enum intel_dpll_id id;
    enum intel_display_power_domain power_domain;
    bool always_on;
    bool is_alt_port_dpll;
};

成员

name

DPLL 名称;用于日志记录

funcs

平台特定的钩子

id

此 DPLL 的唯一标识符

power_domain

DPLL 所需的额外电源域

always_on

通知状态检查器,即使没有任何 CRTC 使用,DPLL 仍保持启用状态。

is_alt_port_dpll

通知状态检查器,DPLL 可用作后备(用于 TC->TBT 后备)。

struct intel_shared_dpll

具有跟踪状态和用户的显示 PLL

定义:

struct intel_shared_dpll {
    struct intel_shared_dpll_state state;
    u8 index;
    u8 active_mask;
    bool on;
    const struct dpll_info *info;
    intel_wakeref_t wakeref;
};

成员

state

存储 PLL 的状态,包括其硬件状态和使用它的 CRTC。

index

原子状态的索引

active_mask

使用此 DPLL 的活动管道(即 DPMS 开启)的掩码

on

PLL 实际上是否处于活动状态?在模式设置期间禁用

info

平台特定信息

wakeref

在某些平台上,可能需要获取设备级的运行时电源管理引用,以便在此 DPLL 启用时禁用 DC 状态

显示状态缓冲区

DSB(显示状态缓冲区)是内存中 MMIO 指令的队列,可以卸载到显示控制器中的 DSB 硬件。DSB 硬件是一个 DMA 引擎,可以编程为从内存下载 DSB。它允许驱动程序批量提交显示硬件编程。这有助于减少加载时间和 CPU 活动,从而加快上下文切换速度。从 Gen12 Intel 图形平台开始添加 DSB 支持。

DSB 只能访问管道、平面和转码器数据岛数据包寄存器。

DSB 硬件只能支持寄存器写入(索引的和直接 MMIO 写入)。DSB 硬件引擎无法进行寄存器读取。

void intel_dsb_reg_write_indexed(struct intel_dsb *dsb, i915_reg_t reg, u32 val)

将寄存器写入发送到 DSB 上下文

参数

struct intel_dsb *dsb

DSB 上下文

i915_reg_t reg

寄存器地址。

u32 val

值。

描述

此函数用于在 DSB 的命令缓冲区中写入寄存器值对。

请注意,对于少量(大约少于 5 个)写入同一寄存器的情况,索引写入比普通 MMIO 写入慢。

void intel_dsb_commit(struct intel_dsb *dsb, bool wait_for_vblank)

触发 DSB 的工作负载执行。

参数

struct intel_dsb *dsb

DSB 上下文

bool wait_for_vblank

在执行之前等待垂直空白

描述

此函数用于使用 DSB 对硬件进行实际写入。

struct intel_dsb *intel_dsb_prepare(struct intel_atomic_state *state, struct intel_crtc *crtc, enum intel_dsb_id dsb_id, unsigned int max_cmds)

分配、固定和映射 DSB 命令缓冲区。

参数

struct intel_atomic_state *state

原子状态

struct intel_crtc *crtc

CRTC

enum intel_dsb_id dsb_id

要使用的 DSB 引擎

unsigned int max_cmds

我们需要放入命令缓冲区的命令数量

描述

此函数准备用于存储带有数据的 dsb 指令的命令缓冲区。

返回

DSB 上下文,失败时为 NULL

void intel_dsb_cleanup(struct intel_dsb *dsb)

清理 DSB 上下文。

参数

struct intel_dsb *dsb

DSB 上下文

描述

此函数通过取消固定并释放与其关联的 VMA 对象来清理 DSB 上下文。

GT 编程

多播/复制 (MCR) 寄存器

某些 GT 寄存器被设计为“多播”或“复制”寄存器:同一寄存器的多个实例共享一个 MMIO 偏移量。当硬件需要潜在地跟踪每个硬件单元(例如,每个子切片、每个 L3 存储体等)的寄存器独立值时,通常会使用 MCR 寄存器。存在的具体复制类型因平台而异。

对 MCR 寄存器的 MMIO 访问根据平台 MCR_SELECTOR 寄存器中编程的设置进行控制。对 MCR 寄存器的 MMIO 写入可以以多播方式进行(即,单次写入将所有寄存器实例更新为相同的值),也可以以单播方式进行(一次写入仅更新一个特定实例)。无论 MCR_SELECTOR 中的多播/单播位如何设置,对 MCR 寄存器的读取始终以单播方式进行。选择用于单播操作的特定 MCR 实例称为“转向”。

如果 MCR 寄存器操作转向由于电源门控而熔断或当前已断电的硬件单元,则硬件将“终止”MMIO 操作。终止的读取操作将返回值零,而终止的单播写入操作将被静默忽略。

void intel_gt_mcr_lock(struct intel_gt *gt, unsigned long *flags)

获取 MCR 转向锁

参数

struct intel_gt *gt

GT 结构

unsigned long *flags

用于保存 IRQ 标志的存储

描述

执行锁定以在 MCR 操作期间保护转向。在 MTL 及更高版本上,还将获取硬件锁以对访问进行序列化,不仅对于驱动程序,而且对于外部硬件和固件代理。

上下文

获取 gt->mcr_lock。调用此函数时不应持有 uncore->lock,尽管可以在此函数调用后获取它。

void intel_gt_mcr_unlock(struct intel_gt *gt, unsigned long flags)

释放 MCR 转向锁

参数

struct intel_gt *gt

GT 结构

unsigned long flags

要恢复的 IRQ 标志

描述

释放由 intel_gt_mcr_lock() 获取的锁。

上下文

释放 gt->mcr_lock

void intel_gt_mcr_lock_sanitize(struct intel_gt *gt)

清理 MCR 转向锁

参数

struct intel_gt *gt

GT 结构

描述

这将用于在驱动程序加载和恢复期间清理硬件锁的初始状态,因为此时不会有来自其他代理的并发访问,但引导固件可能将锁留在错误状态。

u32 intel_gt_mcr_read(struct intel_gt *gt, i915_mcr_reg_t reg, int group, int instance)

读取 MCR 寄存器的特定实例

参数

struct intel_gt *gt

GT 结构

i915_mcr_reg_t reg

要读取的 MCR 寄存器

int group

MCR 组

int instance

MCR 实例

上下文

获取并释放 gt->mcr_lock

描述

返回从 MCR 寄存器读取的值,该值已转向特定组/实例。

void intel_gt_mcr_unicast_write(struct intel_gt *gt, i915_mcr_reg_t reg, u32 value, int group, int instance)

写入 MCR 寄存器的特定实例

参数

struct intel_gt *gt

GT 结构

i915_mcr_reg_t reg

要写入的 MCR 寄存器

u32 value

要写入的值

int group

MCR 组

int instance

MCR 实例

描述

在转向特定组/实例后,以单播模式写入 MCR 寄存器。

上下文

调用一个获取并释放 gt->mcr_lock 的函数

void intel_gt_mcr_multicast_write(struct intel_gt *gt, i915_mcr_reg_t reg, u32 value)

将一个值写入 MCR 寄存器的所有实例

参数

struct intel_gt *gt

GT 结构

i915_mcr_reg_t reg

要写入的 MCR 寄存器

u32 value

要写入的值

描述

以多播模式写入 MCR 寄存器以更新所有实例。

上下文

获取并释放 gt->mcr_lock

void intel_gt_mcr_multicast_write_fw(struct intel_gt *gt, i915_mcr_reg_t reg, u32 value)

将一个值写入 MCR 寄存器的所有实例

参数

struct intel_gt *gt

GT 结构

i915_mcr_reg_t reg

要写入的 MCR 寄存器

u32 value

要写入的值

描述

以多播模式写入 MCR 寄存器以更新所有实例。 此函数假定调用者已持有任何必要的 forcewake 域;在应自动获取 forcewake 的情况下,请使用 intel_gt_mcr_multicast_write()

上下文

调用者必须持有 gt->mcr_lock。

u32 intel_gt_mcr_multicast_rmw(struct intel_gt *gt, i915_mcr_reg_t reg, u32 clear, u32 set)

执行多播 RMW 操作

参数

struct intel_gt *gt

GT 结构

i915_mcr_reg_t reg

要读取和写入的 MCR 寄存器

u32 clear

在 RMW 期间要清除的位

u32 set

在 RMW 期间要设置的位

描述

以多播方式在 MCR 寄存器上执行读取-修改-写入操作。 此操作仅在预期所有实例都具有相同值的 MCR 寄存器上才有意义。 读取将针对任何未终止的实例,并且写入将应用于所有实例。

此函数假定调用者已持有任何必要的 forcewake 域;在应自动获取 forcewake 的情况下,请使用 intel_gt_mcr_multicast_rmw()

返回读取的旧(未修改的)值。

上下文

调用获取并释放 gt->mcr_lock 的函数

void intel_gt_mcr_get_nonterminated_steering(struct intel_gt *gt, i915_mcr_reg_t reg, u8 *group, u8 *instance)

查找将寄存器转向未终止实例的组/实例值

参数

struct intel_gt *gt

GT 结构

i915_mcr_reg_t reg

需要转向的寄存器

u8 *group

用于组转向的返回变量

u8 *instance

用于实例转向的返回变量

描述

此函数返回一个组/实例对,保证用于给定寄存器的读取转向。请注意,即使寄存器未被复制,因此实际上不需要转向,也会返回一个值。

u32 intel_gt_mcr_read_any_fw(struct intel_gt *gt, i915_mcr_reg_t reg)

读取 MCR 寄存器的一个实例

参数

struct intel_gt *gt

GT 结构

i915_mcr_reg_t reg

要读取的寄存器

描述

读取 GT MCR 寄存器。 读取将转向未终止的实例(即,未熔断或被电源门控关闭的实例)。 此函数假定调用者已持有任何必要的 forcewake 域;在应自动获取 forcewake 的情况下,请使用 intel_gt_mcr_read_any()

返回来自 reg 的未终止实例的值。

上下文

调用者必须持有 gt->mcr_lock。

u32 intel_gt_mcr_read_any(struct intel_gt *gt, i915_mcr_reg_t reg)

读取 MCR 寄存器的一个实例

参数

struct intel_gt *gt

GT 结构

i915_mcr_reg_t reg

要读取的寄存器

描述

读取 GT MCR 寄存器。读取将转向未终止的实例(即,未熔断或被电源门控关闭的实例)。

返回来自 reg 的未终止实例的值。

上下文

调用一个获取并释放 gt->mcr_lock 的函数。

void intel_gt_mcr_get_ss_steering(struct intel_gt *gt, unsigned int dss, unsigned int *group, unsigned int *instance)

返回 SS 的组/实例转向

参数

struct intel_gt *gt

GT 结构

unsigned int dss

要获取转向的 DSS ID

unsigned int *group

指向转向组 ID 的存储的指针

unsigned int *instance

指向转向实例 ID 的存储的指针

描述

返回与特定子切片/DSS ID 对应的转向 ID(通过 groupinstance 参数)。

int intel_gt_mcr_wait_for_reg(struct intel_gt *gt, i915_mcr_reg_t reg, u32 mask, u32 value, unsigned int fast_timeout_us, unsigned int slow_timeout_ms)

等待 MCR 寄存器匹配预期状态

参数

struct intel_gt *gt

GT 结构

i915_mcr_reg_t reg

要读取的寄存器

u32 mask

应用于寄存器值的掩码

u32 value

等待的值

unsigned int fast_timeout_us

原子/紧密等待的快速超时(以微秒为单位)

unsigned int slow_timeout_ms

慢超时(以毫秒为单位)

描述

此例程会等待直到目标寄存器 reg 在应用 mask 后包含预期的 value,即它会等待直到

(intel_gt_mcr_read_any_fw(gt, reg) & mask) == value

否则,等待将在 slow_timeout_ms 毫秒后超时。对于原子上下文,slow_timeout_ms 必须为零,并且 fast_timeout_us 必须不大于 200,000 微秒。

此函数基本上是 MCR 友好的 __intel_wait_for_register_fw() 版本。通常,此函数仅用于 GAM 寄存器,它们有点特殊——尽管它们是 MCR 寄存器,但读取(例如,等待状态更新)始终定向到主实例。

请注意,此例程假定调用者持有 forcewake 断言,因此不适合非常长的等待。

上下文

调用一个获取并释放 gt->mcr_lock 的函数

返回

如果寄存器与期望条件匹配,则为 0;否则为 -ETIMEDOUT。

内存管理和命令提交

本节涵盖 i915 驱动程序中与 GEM 实现相关的所有内容。

Intel GPU 基础知识

Intel GPU 有多个引擎。有几种引擎类型

  • 渲染命令流 (RCS)。用于渲染 3D 和执行计算的引擎。

  • Blitting 命令流 (BCS)。用于执行 blitting 和/或复制操作的引擎。

  • 视频命令流。用于视频编码和解码的引擎。在硬件文档中有时也称为“BSD”。

  • 视频增强命令流 (VECS)。用于视频增强的引擎。在硬件文档中有时也称为“VEBOX”。

  • 计算命令流 (CCS)。一个可以访问媒体和 GPGPU 管道,但不能访问 3D 管道的引擎。

  • 图形安全控制器 (GSCCS)。一个专门的引擎,用于与 GSC 控制器进行内部通信,以执行与安全相关的任务,例如高带宽数字内容保护 (HDCP)、受保护的 Xe 路径 (PXP) 和 HuC 固件身份验证。

Intel GPU 系列是使用统一内存访问的集成 GPU 系列。为了让 GPU “工作”,用户空间将通过 ioctl DRM_IOCTL_I915_GEM_EXECBUFFER2DRM_IOCTL_I915_GEM_EXECBUFFER2_WR 向 GPU 提供批处理缓冲区。大多数此类批处理缓冲区将指示 GPU 执行工作(例如渲染),并且该工作需要从中读取的内存和写入的内存。所有内存都封装在 GEM 缓冲区对象中(通常使用 ioctl DRM_IOCTL_I915_GEM_CREATE 创建)。为 GPU 创建提供批处理缓冲区的 ioctl 还会列出批处理缓冲区读取和/或写入的所有 GEM 缓冲区对象。有关内存管理的实现细节,请参阅 GEM BO 管理实现细节

i915 驱动程序允许用户空间通过 ioctl DRM_IOCTL_I915_GEM_CONTEXT_CREATE 创建一个上下文,该上下文由一个 32 位整数标识。用户空间应将此类上下文视为与操作系统 CPU 进程的概念大致类似。i915 驱动程序保证,向固定上下文发出的命令的执行方式是,先前发出的命令的写入会被后续命令的读取看到。在不同上下文之间发出的操作(即使来自同一个文件描述符)也不保证这一点,并且跨上下文同步(即使来自同一个文件描述符)的唯一方法是通过使用栅栏。至少可以追溯到 Gen4,上下文还带有 GPU HW 上下文;HW 上下文本质上是(至少是大部分)GPU 的状态。除了排序保证之外,当向上下文发出命令时,内核将通过 HW 上下文恢复 GPU 状态,这节省了用户空间在每个批处理缓冲区开始时恢复(至少是大部分)GPU 状态的需要。提交批处理缓冲区工作的非弃用 ioctl 可以传递该 ID(在 drm_i915_gem_execbuffer2::rsvd1 的低位)以标识要用于该命令的上下文。

GPU 有自己的内存管理和地址空间。内核驱动程序维护 GPU 的内存转换表。对于较旧的 GPU(即 Gen8 之前的 GPU),有一个全局的此类转换表,即全局图形转换表 (GTT)。对于较新的 GPU,每个上下文都有自己的转换表,称为每个进程图形转换表 (PPGTT)。值得注意的是,尽管 PPGTT 是按进程命名的,但实际上是按上下文命名的。当用户空间提交批处理缓冲区时,内核会遍历批处理缓冲区使用的 GEM 缓冲区对象列表,并保证每个此类 GEM 缓冲区对象的内存不仅驻留,而且还存在于 (PP)GTT 中。如果 GEM 缓冲区对象尚未放置在 (PP)GTT 中,则会为其分配一个地址。这会产生两个后果:内核需要编辑提交的批处理缓冲区,以在将 GEM BO 分配给 GPU 地址时写入 GPU 地址的正确值,并且内核可能会从 (PP)GTT 中逐出另一个 GEM BO,以便为另一个 GEM BO 腾出地址空间。因此,提交执行批处理缓冲区的 ioctl 还包括缓冲区内所有引用 GPU 地址的位置的列表,以便内核可以正确编辑缓冲区。此过程被称为重定位。

锁定指南

注意

这是对重构完成后应如何进行锁定的描述。不一定反映 WIP 期间锁定的样子。

  1. 所有锁定规则和与跨驱动程序接口(dma-buf、dma_fence)的接口约定都需要遵循。

  2. 代码中的任何位置都没有 struct_mutex

  3. dma_resv 将是最外层的锁(如果需要),并且 ww_acquire_ctx 应提升到最高级别,并在调用链中在 i915_gem_ctx 中传递下来

  4. 在持有 lru/内存管理器(buddy、drm_mm 等)锁时,不允许进行系统内存分配

    • 通过启动 lockdep(使用 fs_reclaim)来强制执行此操作。如果我们持有这些锁时分配内存,我们将重新遇到收缩器与 struct_mutex 的问题,那将非常糟糕。

  5. 不要将不同的 lru/内存管理器锁相互嵌套。依次获取它们以更新内存分配,并依赖对象的 dma_resv ww_mutex 来与其他操作序列化。

  6. 对 lru/内存管理器锁的建议是,它们应该足够小以成为自旋锁。

  7. 所有功能都需要在适当时附带详尽的内核自测试和/或 IGT 测试

  8. 所有 LMEM uAPI 路径都需要完全可重启 (_interruptible() 用于所有锁/等待/睡眠)

    • 通过信号注入进行错误处理验证。仍然是我们验证 GEM uAPI 极端情况的最佳策略。必须在 IGT 中过度使用,并且我们需要检查是否真正完全覆盖了所有错误情况。

    • 使用 ww_mutex 进行 -EDEADLK 处理

GEM BO 管理实现细节

VMA 表示绑定到地址空间中的 GEM BO。因此,在将对象绑定到地址空间或从地址空间解除绑定之前或之后,都不能保证 VMA 的存在。

为了尽可能简单(即没有引用计数),VMA 的生命周期将始终 <= 对象的生命周期。因此,对象引用计数应该可以覆盖我们。

缓冲区对象逐出

本节介绍用于逐出缓冲区对象以在虚拟 GPU 地址空间中释放空间的接口函数。请注意,这在很大程度上与收缩缓冲区对象缓存无关,后者旨在使主内存(通过统一内存架构与 GPU 共享)可用。

int i915_gem_evict_something(struct i915_address_space *vm, struct i915_gem_ww_ctx *ww, u64 min_size, u64 alignment, unsigned long color, u64 start, u64 end, unsigned flags)

逐出 vma 以便为绑定新的 vma 腾出空间

参数

struct i915_address_space *vm

要从中逐出的地址空间

struct i915_gem_ww_ctx *ww

一个可选的 struct i915_gem_ww_ctx。

u64 min_size

所需可用空间的大小

u64 alignment

所需可用空间的对齐约束

unsigned long color

所需空间的颜色

u64 start

从中逐出对象的范围的开始(包含)

u64 end

从中逐出对象的范围的结束(不包含)

unsigned flags

用于控制逐出算法的其他标志

描述

此函数将尝试逐出 vma,直到找到满足要求的可用空间。调用者必须先检查是否已存在任何此类空洞,然后再调用此函数。

此函数由对象/vma 绑定代码使用。

由于此函数仅用于释放虚拟地址空间,因此它仅忽略已固定的 vma,而不忽略后备存储本身已固定的对象。因此,obj->pages_pin_count 不能防止逐出。

澄清一下:这是为了释放虚拟地址空间,而不是为了释放例如收缩器中的内存。

int i915_gem_evict_for_node(struct i915_address_space *vm, struct i915_gem_ww_ctx *ww, struct drm_mm_node *target, unsigned int flags)

逐出 vma 以便为绑定新的 vma 腾出空间

参数

struct i915_address_space *vm

要从中逐出的地址空间

struct i915_gem_ww_ctx *ww

一个可选的 struct i915_gem_ww_ctx。

struct drm_mm_node *target

要驱逐的范围(和颜色)

unsigned int flags

用于控制逐出算法的其他标志

描述

此函数将尝试驱逐与目标节点重叠的 vma。

澄清一下:这是为了释放虚拟地址空间,而不是为了释放例如收缩器中的内存。

int i915_gem_evict_vm(struct i915_address_space *vm, struct i915_gem_ww_ctx *ww, struct drm_i915_gem_object **busy_bo)

从 vm 中驱逐所有空闲的 vma

参数

struct i915_address_space *vm

要清除的地址空间

struct i915_gem_ww_ctx *ww

一个可选的 struct i915_gem_ww_ctx。如果不是 NULL,i915_gem_evict_vm 也将能够驱逐被 ww 锁定的 vma。

struct drm_i915_gem_object **busy_bo

指向 struct drm_i915_gem_object 的可选指针。如果不是 NULL,则在 i915_gem_evict_vm() 无法尝试锁定对象进行驱逐的情况下,busy_bo 将指向它。还会返回 -EBUSY。调用者必须先释放 vm->mutex,然后再尝试获取争用的锁。调用者还拥有对该对象的引用。

描述

此函数从 vm 中驱逐所有 vma。

execbuf 代码使用它作为整理地址空间的最后手段。

澄清一下:这是为了释放虚拟地址空间,而不是为了释放例如收缩器中的内存。

缓冲对象内存收缩

本节介绍用于收缩缓冲对象缓存的内存使用量的接口函数。收缩用于释放主内存。请注意,这主要与驱逐缓冲对象无关,驱逐缓冲对象的目标是在 GPU 虚拟地址空间中腾出空间。

unsigned long i915_gem_shrink(struct i915_gem_ww_ctx *ww, struct drm_i915_private *i915, unsigned long target, unsigned long *nr_scanned, unsigned int shrink)

收缩缓冲对象缓存

参数

struct i915_gem_ww_ctx *ww

i915 gem ww 获取上下文,或 NULL

struct drm_i915_private *i915

i915 设备

unsigned long target

要释放的内存量,以页为单位

unsigned long *nr_scanned

可选的已扫描页数输出(增量)

unsigned int shrink

用于选择缓存类型的控制标志

描述

此函数是收缩器的主要接口。它将尝试从缓冲对象中释放最多 **target** 页的后备存储主内存。可以使用 **flags** 完成对特定缓存的选择。例如,当应优先从缓存中删除可清除对象时,这非常有用。

请注意,不能保证释放的量实际上可用作空闲系统内存 - 这些页面可能由于其他原因(如 CPU mmaps)仍在使用中,或者 mm 内核在我们能够获取它们之前已重复使用它们。因此,需要显式收缩缓冲对象缓存(例如,为了避免内存回收中的死锁)的代码必须回退到 i915_gem_shrink_all()

另请注意,任何类型的固定(包括每个 vma 地址空间固定和缓冲对象级别的后备存储固定)都会导致收缩器代码必须跳过该对象。

返回

实际释放的后备存储的页数。

unsigned long i915_gem_shrink_all(struct drm_i915_private *i915)

完全收缩缓冲对象缓存

参数

struct drm_i915_private *i915

i915 设备

描述

这是 i915_gem_shrink() 的一个简单的包装器,用于完全积极地收缩所有缓存。它还首先等待并撤回所有未完成的请求,以便还能够释放活动对象的后备存储。

这应该仅在代码中用于有意使 GPU 静止或在内存似乎已耗尽时作为最后手段。

返回

实际释放的后备存储的页数。

void i915_gem_object_make_unshrinkable(struct drm_i915_gem_object *obj)

在收缩器中隐藏该对象。默认情况下,所有支持收缩的对象类型(请参阅 IS_SHRINKABLE)在分配系统内存页后也会使该对象对收缩器可见。

参数

struct drm_i915_gem_object *obj

GEM 对象。

描述

这通常用于收缩器无法轻松处理的特殊内核内部对象,例如,如果它们是永久固定的。

void __i915_gem_object_make_shrinkable(struct drm_i915_gem_object *obj)

将对象移动到可收缩列表的末尾。此列表中的对象可能会被换出。与 WILLNEED 对象一起使用。

参数

struct drm_i915_gem_object *obj

GEM 对象。

描述

请勿使用。这旨在在尚未拥有 mm.pages 但保证在下面具有潜在可回收页面的非常特殊的对象上调用。

void __i915_gem_object_make_purgeable(struct drm_i915_gem_object *obj)

将对象移动到可清除列表的末尾。此列表中的对象可能会被换出。与 DONTNEED 对象一起使用。

参数

struct drm_i915_gem_object *obj

GEM 对象。

描述

请勿使用。这旨在在尚未拥有 mm.pages 但保证在下面具有潜在可回收页面的非常特殊的对象上调用。

void i915_gem_object_make_shrinkable(struct drm_i915_gem_object *obj)

将对象移动到可收缩列表的末尾。此列表中的对象可能会被换出。与 WILLNEED 对象一起使用。

参数

struct drm_i915_gem_object *obj

GEM 对象。

描述

必须仅在具有后备页的对象上调用。

必须与之前对 i915_gem_object_make_unshrinkable() 的调用保持平衡。

void i915_gem_object_make_purgeable(struct drm_i915_gem_object *obj)

将对象移动到可清除列表的末尾。与 DONTNEED 对象一起使用。与可收缩对象不同,收缩器将尝试丢弃后备页,而不是尝试将其换出。

参数

struct drm_i915_gem_object *obj

GEM 对象。

描述

必须仅在具有后备页的对象上调用。

必须与之前对 i915_gem_object_make_unshrinkable() 的调用保持平衡。

批处理缓冲区解析

动机:某些 OpenGL 功能(例如,变换反馈、性能监控)需要用户空间代码提交包含诸如 MI_LOAD_REGISTER_IMM 之类的命令的批处理,以访问各种寄存器。不幸的是,某些硬件代次会在“不安全”批处理(包括通过 i915 提交的所有用户空间批处理)中 noop 这些命令,即使这些命令是安全的并且表示设备的预期编程模型。

软件命令解析器在操作上类似于硬件为不安全批处理完成的命令解析。但是,如果解析器确定操作是安全的,并且将批处理作为“安全”提交以防止硬件解析,则软件解析器允许某些会被硬件 noop 的操作。

威胁:在较高的层面上,硬件(和软件)检查尝试防止授予用户空间不应有的权限。权限分为三个类别。

首先,明确定义为特权或仅应由内核驱动程序使用的命令。解析器会拒绝此类命令

第二,访问寄存器的命令。为了支持正确/增强的用户空间功能,特别是某些 OpenGL 扩展,解析器提供了一个用户空间可以安全访问的寄存器白名单。

第三,访问特权内存(即 GGTT、HWS 页等)的命令。解析器始终拒绝此类命令。

大多数有问题的命令都属于 MI_* 范围,每个引擎上只有少数特定命令(例如 PIPE_CONTROL 和 MI_FLUSH_DW)。

实现:每个引擎都维护命令和寄存器表,解析器在扫描提交给该引擎的批处理缓冲区时使用这些表。

由于解析器必须检查的命令集远小于支持的命令数量,因此解析器表仅包含解析器所需的命令。这通常是可行的,因为命令操作码范围具有标准命令长度编码。因此,对于解析器不需要检查的命令,它可以轻松地跳过它们。这是通过每个引擎的长度解码 vfunc 实现的。

不幸的是,有许多命令不遵循其操作码范围的标准长度编码,主要是在 MI_* 命令中。为了处理这个问题,解析器提供了一种在每个引擎的命令表中定义显式“跳过”条目的方法。

其他命令表条目直接映射到上面提到的一些高级类别:拒绝、寄存器白名单。解析器通过通用的位掩码机制实现了一些检查,包括特权内存检查。

int intel_engine_init_cmd_parser(struct intel_engine_cs *engine)

为引擎设置命令解析器相关字段

参数

struct intel_engine_cs *engine

要初始化的引擎

描述

根据平台是否需要软件命令解析,可以选择性地初始化 struct intel_engine_cs 中与批处理缓冲区命令解析相关的字段。

void intel_engine_cleanup_cmd_parser(struct intel_engine_cs *engine)

清理命令解析器相关字段

参数

struct intel_engine_cs *engine

要清理的引擎

描述

释放可能为指定引擎初始化的任何与命令解析相关的资源。

int intel_engine_cmd_parser(struct intel_engine_cs *engine, struct i915_vma *batch, unsigned long batch_offset, unsigned long batch_length, struct i915_vma *shadow, bool trampoline)

解析批处理缓冲区以查找特权违规

参数

struct intel_engine_cs *engine

批处理将要执行的引擎

struct i915_vma *batch

有问题的批处理缓冲区

unsigned long batch_offset

执行开始时批处理中的字节偏移量

unsigned long batch_length

batch_obj 中命令的长度

struct i915_vma *shadow

有问题的批处理缓冲区的已验证副本

bool trampoline

如果我们需要跳入特权执行,则为 true

描述

解析指定的批处理缓冲区,查找如概述中所述的特权违规。

返回

如果解析器发现违规或以其他方式失败,则为非零;如果批处理看起来合法但应使用硬件解析,则为 -EACCES

int i915_cmd_parser_get_version(struct drm_i915_private *dev_priv)

获取命令解析器版本号

参数

struct drm_i915_private *dev_priv

i915 设备私有数据

描述

命令解析器维护一个简单的递增整数版本号,适用于传递给用户空间客户端以确定允许的操作。

返回

命令解析器的当前版本号

用户批处理缓冲区执行

struct i915_gem_engines

一组引擎

定义:

struct i915_gem_engines {
    union {
        struct list_head link;
        struct rcu_head rcu;
    };
    struct i915_sw_fence fence;
    struct i915_gem_context *ctx;
    unsigned int num_engines;
    struct intel_context *engines[];
};

成员

{unnamed_union}

匿名

link

i915_gem_context::stale::engines 中的链接

rcu

释放时使用的 RCU

fence

用于延迟销毁引擎的栅栏

ctx

i915_gem_context 反向指针

num_engines

此集合中的引擎数量

engines

引擎数组

struct i915_gem_engines_iter

i915_gem_engines 集的迭代器

定义:

struct i915_gem_engines_iter {
    unsigned int idx;
    const struct i915_gem_engines *engines;
};

成员

idx

i915_gem_engines::engines 的索引

engines

正在迭代的引擎集

enum i915_gem_engine_type

描述 i915_gem_proto_engine 的类型

常量

I915_GEM_ENGINE_TYPE_INVALID

无效引擎

I915_GEM_ENGINE_TYPE_PHYSICAL

单个物理引擎

I915_GEM_ENGINE_TYPE_BALANCED

负载均衡的引擎集

I915_GEM_ENGINE_TYPE_PARALLEL

并行引擎集

struct i915_gem_proto_engine

原型引擎

定义:

struct i915_gem_proto_engine {
    enum i915_gem_engine_type type;
    struct intel_engine_cs *engine;
    unsigned int num_siblings;
    unsigned int width;
    struct intel_engine_cs **siblings;
    struct intel_sseu sseu;
};

成员

type

此引擎的类型

engine

物理引擎

num_siblings

负载均衡或并行同级节点的数量

width

每个同级节点的宽度

siblings

负载均衡同级节点或并行的 num_siblings * width

sseu

客户端设置的 SSEU 参数

描述

此结构描述了上下文可能包含的引擎。引擎有四种类型

  • I915_GEM_ENGINE_TYPE_INVALID:可以创建无效引擎,但它们在 i915_gem_engines::engines[i] 中显示为 NULL,并且用户尝试使用它们的任何尝试都会导致 -EINVAL。它们在原型上下文构建期间也很有用,因为客户端可能会创建无效引擎,然后稍后将其设置为虚拟引擎。

  • I915_GEM_ENGINE_TYPE_PHYSICAL:单个物理引擎,由 i915_gem_proto_engine::engine 描述。

  • I915_GEM_ENGINE_TYPE_BALANCED:负载均衡的引擎集,由 i915_gem_proto_engine::num_siblings 和 i915_gem_proto_engine::siblings 描述。

  • I915_GEM_ENGINE_TYPE_PARALLEL:并行提交引擎集,由 i915_gem_proto_engine::width、i915_gem_proto_engine::num_siblings 和 i915_gem_proto_engine::siblings 描述。

struct i915_gem_proto_context

原型上下文

定义:

struct i915_gem_proto_context {
    struct drm_i915_file_private *fpriv;
    struct i915_address_space *vm;
    unsigned long user_flags;
    struct i915_sched_attr sched;
    int num_user_engines;
    struct i915_gem_proto_engine *user_engines;
    struct intel_sseu legacy_rcs_sseu;
    bool single_timeline;
    bool uses_protected_content;
    intel_wakeref_t pxp_wakeref;
};

成员

fpriv

创建上下文的客户端

vm

参见 i915_gem_context.vm

user_flags

参见 i915_gem_context.user_flags

sched

参见 i915_gem_context.sched

num_user_engines

用户指定的引擎数量,或 -1

user_engines

用户指定的引擎

legacy_rcs_sseu

旧版 RCS 的客户端设置的 SSEU 参数

single_timeline

参见 i915_gem_context.syncobj

uses_protected_content

参见 i915_gem_context.uses_protected_content

pxp_wakeref

参见 i915_gem_context.pxp_wakeref

描述

struct i915_gem_proto_context 表示 struct i915_gem_context 的创建参数。它用于收集通过创建标志或通过 SET_CONTEXT_PARAM 提供的参数,以便在我们创建最终的 i915_gem_context 时,这些参数可以是不可变的。

上下文 uAPI 允许使用两种方法来设置上下文参数:SET_CONTEXT_PARAM 和 CONTEXT_CREATE_EXT_SETPARAM。前者允许在任何时候调用,而后者则作为 GEM_CONTEXT_CREATE 的一部分发生。最初添加这些参数时,目前可以通过一个参数设置的所有内容都可以通过另一个参数设置。虽然有些参数相当简单,并且在实时上下文中设置它们是无害的,例如上下文优先级,但其他参数则要棘手得多,例如 VM 或引擎集。为了避免一些非常糟糕的竞争条件,我们不允许在实时上下文上设置 VM 或引擎集。

我们处理这个问题的方式是在不破坏旧的用户空间(通过 SET_CONTEXT_PARAM 设置 VM 或引擎)的情况下,延迟实际上下文的创建,直到客户端使用 SET_CONTEXT_PARAM 完成配置。从客户端的角度来看,它始终具有相同的 u32 上下文 ID。 然而,从 i915 的角度来看,它是一个 i915_gem_proto_context,直到我们尝试执行 proto-context 无法处理的操作时,才会创建真正的上下文。

这是通过一个小的 xarray 操作实现的。当调用 GEM_CONTEXT_CREATE 时,我们创建一个 proto-context,在 context_xa 中保留一个槽,但将其保留为 NULL,proto-context 则在 proto_context_xa 中对应的槽中。然后,每当我们查找上下文时,我们首先检查 context_xa。 如果它存在,我们返回 i915_gem_context,然后完成操作。 如果它不存在,我们查看 proto_context_xa,如果我们在那里找到它,我们就创建实际的上下文并销毁 proto-context。

在我们进行此更改时(2021 年 4 月),我们对现有用户空间进行了相当完整的审计,以确保不会破坏任何东西

  • Mesa/i965 完全没有使用引擎或 VM API

  • Mesa/ANV 使用了引擎 API,但通过 CONTEXT_CREATE_EXT_SETPARAM,没有使用 VM API。

  • Mesa/iris 完全没有使用引擎或 VM API

  • 开源计算运行时尚未开始使用引擎 API,但通过 SET_CONTEXT_PARAM 使用了 VM API。然而,CONTEXT_SETPARAM 始终是该上下文上的第二个 ioctl,紧跟在 GEM_CONTEXT_CREATE 之后。

  • 媒体驱动程序通过 SET_CONTEXT_PARAM 设置引擎和绑定/平衡。然而,设置 VM 的 CONTEXT_SETPARAM 始终是该上下文上的第二个 ioctl,紧跟在 GEM_CONTEXT_CREATE 之后,并且设置引擎紧随其后。

为了使此操作正常工作,任何通过 drm_i915_file_private::proto_context_xa 向客户端公开的 i915_gem_proto_context 修改都必须由 drm_i915_file_private::proto_context_lock 保护。例外情况是当尚未公开 proto-context 时,例如在 GEM_CONTEXT_CREATE 期间处理 CONTEXT_CREATE_SET_PARAM 时。

struct i915_gem_context

客户端状态

定义:

struct i915_gem_context {
    struct drm_i915_private *i915;
    struct drm_i915_file_private *file_priv;
    struct i915_gem_engines __rcu *engines;
    struct mutex engines_mutex;
    struct drm_syncobj *syncobj;
    struct i915_address_space *vm;
    struct pid *pid;
    struct list_head link;
    struct i915_drm_client *client;
    struct list_head client_link;
    struct kref ref;
    struct work_struct release_work;
    struct rcu_head rcu;
    unsigned long user_flags;
#define UCONTEXT_NO_ERROR_CAPTURE       1;
#define UCONTEXT_BANNABLE               2;
#define UCONTEXT_RECOVERABLE            3;
#define UCONTEXT_PERSISTENCE            4;
#define UCONTEXT_LOW_LATENCY            5;
    unsigned long flags;
#define CONTEXT_CLOSED                  0;
#define CONTEXT_USER_ENGINES            1;
    bool uses_protected_content;
    intel_wakeref_t pxp_wakeref;
    struct mutex mutex;
    struct i915_sched_attr sched;
    atomic_t guilty_count;
    atomic_t active_count;
    unsigned long hang_timestamp[2];
#define CONTEXT_FAST_HANG_JIFFIES (120 * HZ) ;
    u8 remap_slice;
    struct radix_tree_root handles_vma;
    struct mutex lut_mutex;
    char name[TASK_COMM_LEN + 8];
    struct {
        spinlock_t lock;
        struct list_head engines;
    } stale;
};

成员

i915

i915 设备后向指针

file_priv

拥有的文件描述符

engines

此上下文的用户定义引擎

各种 uAPI 提供了从这个数组中查找索引以选择要操作的引擎的功能。

可以在数组中定义同一引擎的多个逻辑上不同的实例,以及组合的虚拟引擎。

Execbuf 使用 I915_EXEC_RING_MASK 作为此数组的索引,以选择要执行的 HW 上下文 + 引擎。对于默认数组,使用 user_ring_map[] 将旧版 uABI 转换为合适的索引(例如,I915_EXEC_DEFAULT 和 I915_EXEC_RENDER 都选择相同的上下文,而 I915_EXEC_BSD 则很奇怪)。对于用户定义的数组,execbuf 使用 I915_EXEC_RING_MASK 作为纯索引。

由 I915_CONTEXT_PARAM_ENGINE 用户定义(当设置 CONTEXT_USER_ENGINES 标志时)。

engines_mutex

保护对引擎的写入

syncobj

共享时间线 syncobj

当在上下文创建时设置 SHARED_TIMELINE 标志时,我们使用此 syncobj 模拟跨所有引擎的单个时间线。对于每个 execbuffer2 调用,此 syncobj 用作输入和输出栅栏。与真正的 intel_timeline 不同,如果客户端通过同时两次调用 execbuffer2 来与自身竞争,则它不提供完美的原子顺序保证。 然而,如果用户空间与自身竞争,则不太可能产生明确定义的结果,因此我们选择不关心。

vm

唯一的地址空间 (GTT)

在全 ppgtt 模式下,每个上下文都有自己的地址空间,确保一个客户端与所有其他客户端完全分离。

在其他模式下,这是一个 NULL 指针,期望调用者使用共享的全局 GTT。

pid

创建者的进程 ID

请注意,创建上下文的人可能不是主要用户,因为上下文可能在本地套接字之间共享。 然而,这应该只会影响默认上下文,客户端显式创建的所有上下文都应该隔离。

link

位于 drm_i915_private.context_list 的位置

客户端

struct i915_drm_client

client_link

用于链接到 i915_drm_client.ctx_list

ref

引用计数

上下文的引用由创建它的客户端和使用请求提交给硬件的每个请求持有(以确保硬件可以访问状态,直到它完成所有待处理的写入)。 有关访问,请参阅 i915_gem_context_get() 和 i915_gem_context_put()。

release_work

用于延迟清理的工作项,因为 i915_gem_context_put() 倾向于从硬中断上下文调用。

FIXME:唯一的真正原因是 i915_gem_engines.fence,所有其他调用者都来自进程上下文,并且最多需要一些轻微的调整才能将 i915_gem_context_put() 调用从自旋锁中拉出。

rcu

用于延迟释放的 rcu_head。

user_flags

由用户控制的一小组布尔值

flags

一小组布尔值

uses_protected_content

上下文使用 PXP 加密对象。

此标志只能在 ctx 创建时设置,并且在上下文的生命周期内是不可变的。 有关设置限制和标记上下文的预期行为的更多信息,请参阅 uapi/drm/i915_drm.h 中的 I915_CONTEXT_PARAM_PROTECTED_CONTENT。

pxp_wakeref

当使用 PXP 时,wakeref 会保持设备唤醒

当设备挂起时,PXP 会话会失效,这反过来会使所有使用它的上下文和对象失效。 为了保持流程简单,当使用 PXP 对象的上下文在使用时,我们保持设备唤醒。 用户空间应用程序应该只在显示器打开时使用 PXP,因此在此处采用 wakeref 不应使我们的功耗指标恶化。

mutex

保护除引擎或 handles_vma 之外的所有内容

sched

调度程序参数

guilty_count

此上下文导致 GPU 挂起的次数。

active_count

此上下文在 GPU 挂起期间处于活动状态的次数,但没有导致其挂起。

hang_timestamp

此上下文上次导致 GPU 挂起的时间

remap_slice

需要重新映射的缓存行的位掩码

handles_vma

rbtree 用于查找用户句柄的特定于上下文的 obj/vma。(用户句柄是每个 fd 的,但绑定是每个 vm 的,每个 vm 可能是一个上下文,也可能与全局 GTT 共享)

lut_mutex

锁定 handles_vma

name

任意名称,用于用户调试

为上下文构造一个名称,该名称来自创建者的进程名称、pid 和用户句柄,以便在消息中唯一标识该上下文。

stale

跟踪要销毁的过时引擎

描述

struct i915_gem_context 表示驱动程序和特定客户端的逻辑硬件状态的组合视图。

用户空间以 GEM 对象(我们称之为批处理缓冲区)内的指令流的形式提交要在 GPU 上执行的命令。 这些指令可能引用其他包含辅助状态(例如内核、采样器、渲染目标甚至辅助批处理缓冲区)的 GEM 对象。 用户空间不知道这些对象在 GPU 内存中的位置,因此在将批处理缓冲区传递给 GPU 执行之前,会更新批处理缓冲区和辅助对象中的那些地址。 这被称为重定位或修补。 为了尽量避免在下次执行时重定位每个对象,用户空间会在此次传递中获知这些对象的位置,但这仍然只是一个提示,因为内核将来可能会为任何对象选择新位置。

在与硬件通信的级别上,提交批处理缓冲区供 GPU 执行是将内容添加到 HW 命令流读取的缓冲区中。

  1. 添加一个命令来加载 HW 上下文。 对于逻辑环上下文,即 Execlist,此命令不会与其余项放在同一个缓冲区中。

  2. 向缓冲区添加使缓存失效的命令。

  3. 向缓冲区添加批处理缓冲区启动命令; 启动命令本质上是一个令牌,以及要执行的批处理缓冲区的 GPU 地址。

  4. 向缓冲区添加管道刷新。

  5. 向缓冲区添加内存写入命令,以记录 GPU 何时完成执行批处理缓冲区。 内存写入写入请求的全局序列号 i915_request::global_seqno; i915 驱动程序使用寄存器中的当前值来确定 GPU 是否已完成批处理缓冲区。

  6. 向缓冲区添加用户中断命令。 此命令指示 GPU 在命令、管道刷新和内存写入完成时发出中断。

  7. 通知硬件已添加到缓冲区的其他命令(通过更新尾指针)。

处理 execbuf ioctl 在概念上分为几个阶段。

  1. 验证 - 确保所有指针、句柄和标志都有效。

  2. 保留 - 为每个对象分配 GPU 地址空间

  3. 重定位 - 更新所有地址以指向最终位置

  4. 序列化 - 根据其依赖关系对请求进行排序

  5. 构造 - 构造一个用于执行批处理缓冲区的请求

  6. 提交(在未来的某个时间执行)

为 execbuf 保留资源是最复杂的阶段。 我们既不想在地址空间中迁移对象,也不想更新指向此对象的任何重定位。 理想情况下,我们希望将对象保留在原处,并且所有现有的重定位都匹配。 如果对象被赋予新地址,或者用户空间认为该对象在其他地方,我们必须解析所有重定位条目并更新地址。 用户空间可以设置 I915_EXEC_NORELOC 标志来提示其所有对象中的所有目标地址都与重定位条目中的值匹配,并且它们都与 execbuffer 对象列表给出的假定偏移量匹配。 利用这些知识,我们知道如果我们没有移动任何缓冲区,则所有重定位条目都是有效的,我们可以跳过更新。 (如果用户空间是错误的,则可能的结果是即席 GPU 挂起。)使用 I915_EXEC_NO_RELOC 的要求是

写入对象中的地址必须与相应的 reloc.presumed_offset 匹配,而 reloc.presumed_offset 又必须与相应的 execobject.offset 匹配。

批处理中写入的任何渲染目标都必须使用 EXEC_OBJECT_WRITE 标记。

为了避免停顿,execobject.offset 应该与该对象在活动上下文中的当前地址匹配。

预留过程分为多个阶段。首先,我们会尝试将任何已绑定对象保留在其当前位置——只要该位置符合新执行缓冲区施加的约束。在第一遍之后,任何未绑定的对象都会被放入任何可用的空闲空间。如果对象不适合,则会从预留中删除所有对象,并在将对象按优先级顺序排序后重新运行该过程(首先尝试放置更难适应的对象)。如果失败,则会清除整个虚拟机,并在最后一次尝试拟合执行缓冲区,然后得出结论,它根本无法拟合。

所有这些的一个小复杂之处在于,我们允许用户空间不仅为地址空间中的对象指定对齐方式和大小,还允许用户空间指定确切的偏移量。这些对象的放置比较简单(位置是先验已知的),我们所要做的就是确保空间可用。

一旦所有对象都就位,修补指向最终位置的隐藏指针就是一个相当简单的工作,即遍历重定位条目数组,查找正确的地址并将值重写到对象中。简单!... 重定位条目存储在用户内存中,因此要访问它们,我们必须将它们复制到本地缓冲区中。该副本必须避免任何页面错误,因为它们可能会导致返回需要 struct_mutex 的 GEM 对象(即递归死锁)。因此,我们再次将重定位拆分为多个过程。首先,我们尝试在原子上下文中完成所有操作(避免页面错误),这要求我们永远不要等待。如果我们检测到可能需要等待,或者需要发生页面错误,那么我们必须回退到较慢的路径。慢速路径必须释放互斥锁。(您听到警报了吗?)释放互斥锁意味着我们失去了到目前为止为执行缓冲区构建的所有状态,并且我们必须重置任何全局数据。但是,我们确实将对象固定在它们的最终位置 - 这对于并发执行缓冲区来说是一个潜在的问题。一旦我们释放了互斥锁,我们就可以随意分配并将所有重定位条目复制到一个大数组中,重新获取互斥锁,回收所有对象和其他状态,然后继续使用这些对象更新任何不正确的地址。

在处理重定位条目时,我们会维护一个记录,指示该对象是否正在被写入。使用 NORELOC,我们期望用户空间提供此信息。我们还会通过比较重定位条目内的预期值与目标的最终地址来检查是否可以跳过重定位。如果它们不同,我们必须映射当前对象并重写其中的 4 或 8 字节指针。

根据 GEM ABI 的规则,序列化执行缓冲区非常简单。每个上下文内的执行顺序由提交的顺序决定。对任何 GEM 对象的写入都是按提交顺序进行的,并且是独占的。从 GEM 对象的读取相对于其他读取是无序的,但按写入顺序排序。在读取之后提交的写入不能发生在读取之前,类似地,在写入之后提交的任何读取都不能发生在写入之前。写入在引擎之间排序,以便任何时候只发生一次写入(提前完成任何读取) - 在可用时使用信号量,否则使用 CPU 序列化。其他 GEM 访问遵循相同的规则,任何写入(通过使用 set-domain 的 mmaps 或通过 pwrite)都必须在开始之前刷新所有 GPU 读取,并且任何读取(通过使用 set-domain 或 pread)都必须在开始之前刷新所有 GPU 写入。(请注意,我们目前只在之前使用屏障,我们目前依赖于用户空间在读取或写入对象时不同时开始新的执行。这可能是一个优点,也可能不是,具体取决于您有多信任用户空间不会搬起石头砸自己的脚。)序列化可能只是导致请求被插入到 DAG 中等待轮到它,但最简单的是在 CPU 上等待,直到所有依赖关系都解决。

完成所有这些之后,剩下的就是关闭请求并将其交给硬件(好吧,将其留在队列中等待执行)。但是,我们还提供了以提升的权限运行批处理缓冲区的功能,以便它们访问其他隐藏的寄存器。(用于调整 L3 缓存等。)在为任何批处理提供额外的权限之前,我们必须首先检查它是否包含任何恶意指令,我们检查每条指令是否来自我们的白名单,并且所有寄存器也来自允许列表。我们首先将用户的批处理缓冲区复制到阴影(以便用户无法通过 CPU 或 GPU 访问它,因为我们正在扫描它),然后解析每条指令。如果一切正常,我们会设置一个标志,告诉硬件以信任模式运行批处理缓冲区,否则将拒绝 ioctl。

调度

struct i915_sched_engine

调度器引擎

定义:

struct i915_sched_engine {
    struct kref ref;
    spinlock_t lock;
    struct list_head requests;
    struct list_head hold;
    struct tasklet_struct tasklet;
    struct i915_priolist default_priolist;
    int queue_priority_hint;
    struct rb_root_cached queue;
    bool no_priolist;
    void *private_data;
    void (*destroy)(struct kref *kref);
    bool (*disabled)(struct i915_sched_engine *sched_engine);
    void (*kick_backend)(const struct i915_request *rq, int prio);
    void (*bump_inflight_request_prio)(struct i915_request *rq, int prio);
    void (*retire_inflight_request_prio)(struct i915_request *rq);
    void (*schedule)(struct i915_request *request, const struct i915_sched_attr *attr);
};

成员

ref

调度引擎对象的引用计数

保护优先级列表中的请求、请求、在运行中保留和 tasklet

请求

此调度引擎上正在进行的请求列表

保留

已准备就绪的请求列表,但处于保留状态

tasklet

用于提交的 softirq tasklet

default_priolist

I915_PRIORITY_NORMAL 的优先级列表

queue_priority_hint

最高挂起优先级。

当我们向队列中添加请求或调整正在执行的请求的优先级时,我们会计算这些挂起请求的最大优先级。然后,我们可以使用此值来确定是否需要抢占正在执行的请求以服务队列。但是,由于我们可能记录了我们想要抢占的正在进行的请求的优先级,但由于已完成,因此在出队时,优先级提示可能不再与最高可用请求优先级匹配。

队列

请求队列,位于优先级列表中

no_priolist

禁用优先级列表

private_data

提交后端的私有数据

destroy

销毁调度引擎 / 后端清理

disabled

检查后端是否已禁用提交

kick_backend

在请求的优先级更改后踢后端

bump_inflight_request_prio

更新正在进行的请求的优先级

retire_inflight_request_prio

指示请求已从优先级跟踪中退出

schedule

调整请求的优先级

当请求的优先级发生变化并且其及其依赖项可能需要重新调度时调用。请注意,请求本身可能尚未准备好运行!

描述

调度引擎表示具有不同优先级段的提交队列。它包含所有常见的状态(相对于后端)以排队、跟踪和提交请求。

此对象目前是 i915 特有的,但一旦 i915 与 DRM 调度器集成,它将转换为 drm_gpu_scheduler 的容器以及其他一些变量。

逻辑环、逻辑环上下文和执行列表

动机:GEN8 带来了 HW 上下文的扩展:“逻辑环上下文”。这些扩展的上下文启用了许多新功能,尤其是“执行列表”(也在此文件中实现)。

与传统 HW 上下文的主要区别之一是,逻辑环上下文将更多内容合并到上下文的状态中,例如 PDP 或环形缓冲区控制寄存器

PDP 包含在上下文中的原因很简单:由于 PPGTT(每个进程的 GTT)实际上是每个上下文的,因此将 PDP 包含在那里意味着您不需要自己执行 ppgtt->switch_mm,而是由 GPU 在上下文切换时为您执行。

但是,环形缓冲区控制寄存器(头部、尾部等)呢?我们不应该只需要每个引擎命令流处理器的一组这些吗?这就是名称“逻辑环”开始变得有意义的地方:通过虚拟化环,引擎 cs 在每次上下文切换时都会转移到一个新的“环形缓冲区”。当您要将工作负载提交到 GPU 时,您需要:A) 选择您的上下文,B) 找到其合适的虚拟化环,C) 向其写入命令,然后最后,D) 告诉 GPU 切换到该上下文。

您告诉 GPU 切换到上下文的方式不是使用传统的 MI_SET_CONTEXT,而是通过上下文执行列表,即“执行列表”。

LRC 实现:关于上下文的创建,我们有

  • 一个全局默认上下文。

  • 每个打开的 fd 一个本地默认上下文。

  • 每个上下文创建 ioctl 调用一个本地额外上下文。

现在环形缓冲区属于每个上下文(而不是像以前那样属于每个引擎),并且上下文唯一地绑定到给定的引擎(而不是像以前那样可重用),我们需要

  • 每个上下文中每个引擎一个环形缓冲区。

  • 每个上下文中每个引擎一个后备对象。

全局默认上下文开始其生命周期时,这些新对象已完全分配和填充。每个打开的 fd 的本地默认上下文更为复杂,因为我们在创建时不知道哪个引擎将使用它们。为了处理这种情况,我们实现了 LR 上下文的延迟创建

本地上下文开始其生命周期时是一个空洞或空白的持有者,只有当我们收到执行缓冲区时,它才会针对给定的引擎填充。如果稍后我们收到针对同一上下文但不同引擎的另一个 execbuffer ioctl,我们将分配/填充一个新的环形缓冲区和上下文后备对象,依此类推。

最后,关于使用 ioctl 调用创建的本地上下文:由于它们只允许使用渲染环,因此我们可以立即分配和填充它们(至少现在不需要延迟任何操作)。

执行列表实现:执行列表是在 gen8+ 硬件上提交工作负载以进行执行的新方法(与传统的基于环形缓冲区的方法相反)。此方法的工作方式如下

当一个请求被提交时,它的命令(BB启动命令以及任何前导或尾随命令,如 seqno 面包屑)会被放入相应上下文的环形缓冲区中。此时,硬件上下文中的尾指针不会更新,而是由驱动程序保存在环形缓冲区结构中。一个代表此请求的结构被添加到相应引擎的请求队列中:此结构包含在请求写入环形缓冲区后,上下文尾部的副本和一个指向上下文本身的指针。

如果引擎的请求队列在添加请求之前为空,则会立即处理该队列。否则,队列将在上下文切换中断期间处理。在任何情况下,队列中的元素都将(成对)发送到 GPU 的执行列表提交端口(简称 ELSP),并带有一个全局唯一的 20 位提交 ID。

当请求执行完成时,GPU 会使用上下文完成事件更新上下文状态缓冲区,并生成一个上下文切换中断。在中断处理期间,驱动程序会检查缓冲区中的事件:对于每个上下文完成事件,如果公布的 ID 与请求队列头部的 ID 匹配,则该请求将被撤回并从队列中删除。

处理完成后,如果有任何请求被撤回并且队列不为空,则可以提交新的执行列表。队列头部的两个请求接下来将被提交,但由于一个上下文在执行列表中不能出现两次,如果后续请求与第一个请求具有相同的 ID,则必须合并这两个请求。这只需丢弃队列头部的请求,直到只剩下一个请求(在这种情况下,我们使用 NULL 作为第二个上下文)或者前两个请求具有唯一的 ID 为止。

通过始终执行队列中的前两个请求,驱动程序可确保 GPU 尽可能保持忙碌状态。在单个上下文完成但第二个上下文仍在执行的情况下,当我们移除第一个上下文时,此第二个上下文的请求将位于队列的头部。然后,此请求将与另一个不同上下文的新请求一起重新提交,这将导致硬件继续执行第二个请求并将新请求排队(GPU 检测到上下文被具有相同上下文抢占的情况,并通过不执行抢占来优化上下文切换流程,而只是采样新的尾指针)。

全局 GTT 视图

背景和之前的状态

历史上,对象只能作为单一实例存在于全局 GTT 空间中(被绑定),其视图以线性的方式表示对象的所有后备页面。此视图将被称为“正常视图”。

为了支持同一对象的多个视图,其中映射页面的数量不等于后备存储,或者页面的布局不是线性的,添加了 GGTT 视图的概念。

替代视图的一个例子是由单个图像驱动的立体显示器。在这种情况下,我们将有一个看起来像这样的帧缓冲区(2x2 页):

12 34

以上将表示一个正常的 GGTT 视图,通常为 GPU 或 CPU 渲染而映射。相比之下,馈送到显示引擎的将是一个替代视图,它可能看起来像这样:

1212 3434

在此示例中,替代视图中页面的大小和布局都与正常视图不同。

实现和用法

GGTT 视图是使用 VMA 实现的,并通过枚举 i915_gtt_view_type 和结构体 i915_gtt_view 来区分。

添加了新的核心 GEM 函数风格,这些函数使用 GGTT 绑定对象,并带有 _ggtt_ 中缀,有时带有 _view 后缀,以避免在大量代码中重命名。它们采用结构体 i915_gtt_view 参数,封装了实现视图所需的所有元数据。

作为仅对正常视图感兴趣的调用者的助手,存在全局常量 i915_gtt_view_normal 单例实例。所有旧的核心 GEM API 函数(未采用视图参数的函数)都在正常 GGTT 视图上或使用正常 GGTT 视图进行操作。

想要添加或使用新的 GGTT 视图的代码需要:

  1. 添加一个具有适当名称的新枚举。

  2. 如果需要,扩展 i915_gtt_view 结构中的元数据。

  3. 向 i915_get_vma_pages() 添加支持。

新的视图需要从 i915_get_vma_pages 函数内部构建一个散布-聚集表。此表存储在 vma.gtt_view 中,并且在 VMA 的生命周期内存在。

核心 API 被设计为具有复制语义,这意味着传入的结构体 i915_gtt_view 不需要是持久的(在调用核心 API 函数后保留)。

int i915_gem_gtt_reserve(struct i915_address_space *vm, struct i915_gem_ww_ctx *ww, struct drm_mm_node *node, u64 size, u64 offset, unsigned long color, unsigned int flags)

在地址空间 (GTT) 中保留一个节点

参数

struct i915_address_space *vm

struct i915_address_space

struct i915_gem_ww_ctx *ww

一个可选的 struct i915_gem_ww_ctx。

struct drm_mm_node *node

struct drm_mm_node(通常是 i915_vma.mode)

u64 size

要在 GTT 内分配多少空间,必须与 #I915_GTT_PAGE_SIZE 对齐

u64 offset

在 GTT 内插入的位置,必须与 #I915_GTT_MIN_ALIGNMENT 对齐,并且节点 (offset + size) 必须适合地址空间

unsigned long color

应用于节点的颜色,如果此节点不是来自 VMA,则颜色必须是 #I915_COLOR_UNEVICTABLE

unsigned int flags

控制搜索和驱逐行为

描述

i915_gem_gtt_reserve() 尝试将 node 精确地插入到地址空间内的 offset 处(使用 sizecolor)。如果 node 不适合,它会尝试从 GTT 中驱逐任何重叠的节点,包括颜色不匹配的任何相邻节点(以确保不同域之间存在保护页)。有关驱逐算法的详细信息,请参阅 i915_gem_evict_for_node()。#PIN_NONBLOCK 可用于防止等待驱逐活动重叠对象,并且任何被固定或标记为不可驱逐的重叠节点也会导致失败。

返回

成功时返回 0,如果未找到合适的空洞则返回 -ENOSPC,如果请求等待驱逐并被中断则返回 -EINTR。

int i915_gem_gtt_insert(struct i915_address_space *vm, struct i915_gem_ww_ctx *ww, struct drm_mm_node *node, u64 size, u64 alignment, unsigned long color, u64 start, u64 end, unsigned int flags)

将节点插入到地址空间 (GTT) 中

参数

struct i915_address_space *vm

struct i915_address_space

struct i915_gem_ww_ctx *ww

一个可选的 struct i915_gem_ww_ctx。

struct drm_mm_node *node

struct drm_mm_node(通常是 i915_vma.node)

u64 size

要在 GTT 内分配多少空间,必须与 #I915_GTT_PAGE_SIZE 对齐

u64 alignment

起始偏移量的所需对齐,可以为 0,但如果指定,则必须是 2 的幂并且至少为 #I915_GTT_MIN_ALIGNMENT

unsigned long color

应用于节点的颜色

u64 start

GTT 内任何范围限制的起始位置(全部为 0),必须与 #I915_GTT_PAGE_SIZE 对齐

u64 end

GTT 内任何范围限制的结束位置(全部为 U64_MAX),如果不是 U64_MAX,则必须与 #I915_GTT_PAGE_SIZE 对齐

unsigned int flags

控制搜索和驱逐行为

描述

i915_gem_gtt_insert() 首先搜索一个可用的空洞,可以将节点插入其中。空洞地址与 alignment 对齐,其 size 必须完全适合 [start, end] 边界内。空洞两侧的节点必须匹配 color,否则将在两个节点之间插入一个保护页(或驱逐该节点)。如果找不到合适的空洞,则首先随机选择一个受害者并测试其是否可驱逐,否则扫描 GTT 内对象的 LRU 列表,以找到第一组替换节点来创建空洞。这些旧的重叠节点将从 GTT 中驱逐(因此必须在将来使用之前重新绑定)。任何当前被固定的节点都不能被驱逐(参见 i915_vma_pin())。类似地,如果节点的 VMA 当前处于活动状态并且指定了 #PIN_NONBLOCK,则在搜索驱逐候选节点时也会跳过该节点。有关驱逐算法的详细信息,请参阅 i915_gem_evict_something()

返回

成功时返回 0,如果未找到合适的空洞则返回 -ENOSPC,如果请求等待驱逐并被中断则返回 -EINTR。

GTT 栅栏和交错

void i915_vma_revoke_fence(struct i915_vma *vma)

强制删除 VMA 的栅栏

参数

struct i915_vma *vma

将VMA线性映射(不通过栅栏寄存器)

描述

此函数强制移除给定对象上的任何栅栏,如果内核想要进行非瓦片的GTT访问,这将非常有用。

int i915_vma_pin_fence(struct i915_vma *vma)

为 VMA 设置栅栏

参数

struct i915_vma *vma

通过栅栏寄存器映射的 VMA

描述

当通过 GTT 映射对象时,用户空间希望能够写入它们,而不必担心如果对象是瓦片的则进行数据重排。此函数会遍历栅栏寄存器,查找 **obj** 的空闲栅栏,如果找不到,则会窃取一个。

然后,它会根据对象的属性设置寄存器:地址、步幅和瓦片格式。

对于非瓦片的表面,这将移除任何现有的栅栏。

返回

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

struct i915_fence_reg *i915_reserve_fence(struct i915_ggtt *ggtt)

为 vGPU 预留栅栏

参数

struct i915_ggtt *ggtt

全局 GTT

描述

此函数遍历栅栏寄存器,查找空闲的寄存器,并将其从 fence_list 中删除。它用于为 vGPU 预留栅栏。

void i915_unreserve_fence(struct i915_fence_reg *fence)

回收预留的栅栏

参数

struct i915_fence_reg *fence

栅栏寄存器

描述

此函数将 vGPU 中预留的栅栏寄存器添加到 fence_list 中。

void intel_ggtt_restore_fences(struct i915_ggtt *ggtt)

恢复栅栏状态

参数

struct i915_ggtt *ggtt

全局 GTT

描述

恢复硬件栅栏状态以再次匹配软件跟踪,在 GPU 重置和恢复后调用。请注意,在运行时挂起时,我们只取消栅栏,以便稍后由用户重新获取。

void detect_bit_6_swizzle(struct i915_ggtt *ggtt)

检测第 6 位交换模式

参数

struct i915_ggtt *ggtt

全局 GGTT

描述

检测通过主内存进行 IGD 访问和 CPU 访问之间的地址查找的第 6 位交换。

void i915_gem_object_do_bit_17_swizzle(struct drm_i915_gem_object *obj, struct sg_table *pages)

修复第 17 位交换

参数

struct drm_i915_gem_object *obj

i915 GEM 缓冲区对象

struct sg_table *pages

物理页面的散布/聚集列表

描述

如果此对象的任何页面帧号自使用 i915_gem_object_save_bit_17_swizzle() 保存该状态以来,第 17 位已更改,则此函数会修复交换。

这是在再次固定后备存储时调用的,因为内核可以自由移动未固定的后备存储(直接移动页面或将其换出并再次换入)。

void i915_gem_object_save_bit_17_swizzle(struct drm_i915_gem_object *obj, struct sg_table *pages)

保存第 17 位交换

参数

struct drm_i915_gem_object *obj

i915 GEM 缓冲区对象

struct sg_table *pages

物理页面的散布/聚集列表

描述

此函数保存每个页面帧号的第 17 位,以便稍后可以使用 i915_gem_object_do_bit_17_swizzle() 修复交换。必须在可以取消固定后备存储之前调用此函数。

全局 GTT 栅栏处理

为了避免混淆,请务必注意:i915 驱动程序中的“栅栏”不是用于跟踪命令完成的执行栅栏,而是硬件解瓦片对象,它们包装了全局 GTT 的给定范围。每个平台只有一组相当有限的这些对象。

栅栏用于解瓦片 GTT 内存映射。它们还连接到硬件前缓冲区渲染跟踪,因此与前缓冲区压缩交互。此外,在较旧的平台上,显示引擎使用的瓦片对象需要栅栏。渲染引擎也可以使用它们 - 它们是 blitter 命令所必需的,对于渲染命令是可选的。但在 gen4+ 上,显示(fbc 除外)和渲染都有自己的瓦片状态位,不需要栅栏。

另请注意,栅栏仅支持 X 和 Y 瓦片,因此不能用于更高级的新瓦片格式,例如 W、Ys 和 Yf。

最后请注意,由于栅栏是一种受限制的资源,因此它们是动态地与对象关联的。此外,栅栏状态会延迟提交到硬件,以避免 gen2/3 上不必要的停顿。因此,代码必须显式调用 i915_gem_object_get_fence() 以同步 CPU 访问的栅栏状态。另请注意,某些代码需要未设置栅栏的视图,对于这些情况,可以使用 i915_gem_object_put_fence() 强制删除栅栏。

在内部,这些函数将通过删除 GTT mmap 中 CPU pte(而不是 GTT pte 本身)与用户空间访问同步,如需要。

硬件瓦片和交换详细信息

瓦片背后的想法是通过重新排列像素数据来提高缓存命中率,以便一组像素访问在同一个缓存行中。在后/深度缓冲区上执行此操作的性能提升约为 30%。

但是,英特尔架构通过在内存处于交错模式(成对的 DIMM)时对数据寻址进行调整以提高内存带宽,使情况变得更加复杂。对于交错内存,CPU 会将每个连续的 64 字节发送到备用内存通道,以便它可以从两者获取带宽。

GPU 还会重新排列其访问以提高交错内存的带宽,并且它会匹配 CPU 对非瓦片内存的操作。但是,当进行瓦片时,它会略有不同,因为不仅沿 X 方向而且沿 Y 方向遍历地址。因此,除了当地址的第 6 位翻转时交替通道外,当其他位翻转时也会交替通道 - 位 9(每 512 字节,X 瓦片扫描线)和 10(每两个 X 瓦片扫描线)是 915 和 965 类硬件所共有的。

CPU 有时还会异或更高的位,以提高在图形中频繁进行的步进访问的带宽。这在 MCH 文档中称为“通道异或随机化”。结果是 CPU 将位 11 或位 17 异或到其地址解码的第 6 位。

所有这些第 6 位异或都会对我们的内存管理产生影响,因为我们需要确保 3D 驱动程序可以正确寻址对象内容。

如果我们没有交错内存,则所有瓦片都是安全的,不需要交换。

当第 17 位异或时,我们只是拒绝进行任何瓦片。第 17 位不仅仅是页面偏移量,因此当我们将对象换出并换回时,其中的各个页面将具有不同的第 17 位地址,导致每个 64 字节与其邻居交换!

否则,如果交错,我们必须告诉 3D 驱动程序它需要执行的地址交换,因为它使用 CPU 写入页面(异或第 6 位和可能第 11 位),并且 GPU 从页面读取(异或第 6、9 和 10 位),导致 CPU 需要累积位交换,即异或第 6、9、10 和可能 11 位,以便匹配 GPU 的预期。

对象瓦片 IOCTL

u32 i915_gem_fence_size(struct drm_i915_private *i915, u32 size, unsigned int tiling, unsigned int stride)

栅栏所需的全局 GTT 大小

参数

struct drm_i915_private *i915

i915 设备

u32 size

对象大小

unsigned int tiling

平铺模式

unsigned int stride

平铺步幅

描述

返回栅栏(平铺对象的视图)所需的全局GTT大小,并考虑潜在的栅栏寄存器映射。

u32 i915_gem_fence_alignment(struct drm_i915_private *i915, u32 size, unsigned int tiling, unsigned int stride)

栅栏所需的全局 GTT 对齐

参数

struct drm_i915_private *i915

i915 设备

u32 size

对象大小

unsigned int tiling

平铺模式

unsigned int stride

平铺步幅

描述

返回栅栏(平铺对象的视图)所需的全局GTT对齐,并考虑潜在的栅栏寄存器映射。

int i915_gem_set_tiling_ioctl(struct drm_device *dev, void *data, struct drm_file *file)

用于设置平铺模式的 IOCTL 处理程序

参数

struct drm_device *dev

DRM 设备

void *data

ioctl 的数据指针

struct drm_file *file

ioctl 调用的 DRM 文件

描述

设置对象的平铺模式,返回对象中地址的第 6 位所需的置换。

由用户通过 ioctl 调用。

返回

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

int i915_gem_get_tiling_ioctl(struct drm_device *dev, void *data, struct drm_file *file)

用于获取平铺模式的 IOCTL 处理程序

参数

struct drm_device *dev

DRM 设备

void *data

ioctl 的数据指针

struct drm_file *file

ioctl 调用的 DRM 文件

描述

返回对象的当前平铺模式和所需的第 6 位置换。

由用户通过 ioctl 调用。

返回

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

i915_gem_set_tiling_ioctl()i915_gem_get_tiling_ioctl() 是用于声明栅栏寄存器要求的用户空间接口。

原则上,GEM 完全不关心对象的内部数据布局,因此它也不关心平铺或置换。但有两个例外

  • 对于 X 和 Y 平铺,硬件为 CPU 访问提供了解平铺器,称为栅栏。由于它们的数量有限,内核必须管理它们,因此如果用户空间想要使用栅栏进行解平铺,则必须告知内核对象的平铺。

  • 在 gen3 和 gen4 平台上,平铺对象有一个置换模式,它取决于物理页帧号。当交换此类对象时,页帧号可能会更改,内核必须能够修复此问题,因此现在需要平铺。请注意,在具有不对称内存通道填充的部分平台上,置换模式会以未知的方式更改,对于这些平台,内核会完全禁止交换。

由于这两种情况都不适用于现代平台(如 W、Ys 和 Yf 平铺)上的新平铺布局,GEM 只允许将对象平铺设置为 X 或 Y 平铺。任何其他事情都可以在用户空间中完全处理,而无需内核的参与。

受保护的对象

PXP(受保护的 Xe 路径)是 Gen12 和更新平台中可用的功能。它允许执行和翻转到受保护(即加密)对象的显示。SW 支持通过 CONFIG_DRM_I915_PXP kconfig 启用。

对象可以在创建时通过 I915_GEM_CREATE_EXT_PROTECTED_CONTENT create_ext 标志选择加入 PXP 加密。为了正确保护对象,它们必须与使用 I915_CONTEXT_PARAM_PROTECTED_CONTENT 标志创建的上下文结合使用。有关详细信息和限制,请参阅这两个 uapi 标志的文档。

受保护的对象与 pxp 会话相关联;目前我们只支持一个会话,由 i915 管理,其索引可在 uapi (I915_PROTECTED_CONTENT_DEFAULT_SESSION) 中用于针对受保护对象的指令。当某些事件发生时(例如,暂停/恢复),会话将由 HW 使无效。发生这种情况时,所有与该会话一起使用的对象都会被标记为无效,并且所有标记为使用受保护内容的上下文都会被禁止。任何进一步尝试在 execbuf 调用中使用它们的操作都会被拒绝,而翻转则会转换为黑帧。

一些 PXP 设置操作由管理引擎执行,该引擎由 mei 驱动程序处理;i915 和 mei 之间的通信通过 mei_pxp 组件模块执行。

struct intel_pxp

pxp 状态

定义:

struct intel_pxp {
    struct intel_gt *ctrl_gt;
    bool platform_cfg_is_bad;
    u32 kcr_base;
    struct gsccs_session_resources {
        u64 host_session_handle;
        struct intel_context *ce;
        struct i915_vma *pkt_vma;
        void *pkt_vaddr;
        struct i915_vma *bb_vma;
        void *bb_vaddr;
    } gsccs_res;
    struct i915_pxp_component *pxp_component;
    struct device_link *dev_link;
    bool pxp_component_added;
    struct intel_context *ce;
    struct mutex arb_mutex;
    bool arb_is_valid;
    u32 key_instance;
    struct mutex tee_mutex;
    struct {
        struct drm_i915_gem_object *obj;
        void *vaddr;
    } stream_cmd;
    bool hw_state_invalidated;
    bool irq_enabled;
    struct completion termination;
    struct work_struct session_work;
    u32 session_events;
#define PXP_TERMINATION_REQUEST  BIT(0);
#define PXP_TERMINATION_COMPLETE BIT(1);
#define PXP_INVAL_REQUIRED       BIT(2);
#define PXP_EVENT_TYPE_IRQ       BIT(3);
};

成员

ctrl_gt

指向拥有 VDBOX、KCR 引擎(以及取决于平台的 GSC CS)的 PXP 子系统资产控制的 tile 的指针

platform_cfg_is_bad

用于跟踪先前的任何 arb 会话创建是否因平台配置问题而导致失败,这意味着如果不更改平台(而不是内核),如 BIOS 配置、固件更新等,则无法解决失败。当调用 GET_PARAM:I915_PARAM_PXP_STATUS 时,此 bool 值将被反映。

kcr_base

KCR 引擎的基本 mmio 偏移量,在传统平台上与 KCR 位于媒体 tile 内的较新平台上有所不同。

gsccs_res

对于具有 GSC 引擎的平台,请求提交的资源。

pxp_component

绑定的 mei_pxp 模块的 i915_pxp_component 结构。仅在组件绑定/解绑定函数内部设置和清除,这些函数受 tee_mutex 保护。

dev_link

强制执行用于电源管理排序的模块关系。

pxp_component_added

跟踪是否已添加 pxp 组件。分别在 tee 初始化和 fini 函数中设置和清除。

ce

内核拥有的用于 PXP 操作的上下文

arb_mutex

保护 arb 会话启动

arb_is_valid

跟踪 arb 会话状态。拆卸后,即使密钥已丢失,arb 会话仍可能在 HW 上运行,因此我们不能依赖会话的 HW 状态来了解它是否有效,而是需要在 SW 中跟踪状态。

key_instance

跟踪我们正在使用的密钥实例,以便我们可以使用它来确定对象是使用当前密钥还是之前的密钥创建的。

tee_mutex

保护 tee 通道绑定和消息传递。

stream_cmd

用于将流 PXP 命令发送到 GSC 的 LMEM 对象

hw_state_invalidated

如果 HW 感知到对加密完整性的攻击,它将使密钥无效并期望 SW 重新初始化会话。我们会跟踪此状态,以确保仅在需要时重新启动 arb 会话。

irq_enabled

跟踪 kcr irq 的状态

termination

跟踪挂起的终止的状态。仅在 gt->irq_lock 下重新初始化,并在 session_work 中完成。

session_work

管理会话事件的工作线程。

session_events

挂起的会话事件,受 gt->irq_lock 保护。

微控制器

从 gen9 开始,硬件上提供了三个微控制器:图形微控制器 (GuC)、HEVC/H.265 微控制器 (HuC) 和显示微控制器 (DMC)。驱动程序负责在微控制器上加载固件;GuC 和 HuC 固件使用 DMA 引擎传输到 WOPCM,而 DMC 固件通过 MMIO 写入。

WOPCM

WOPCM 布局

在写入 GuC WOPCM 大小和偏移量寄存器后,WOPCM 的布局将固定,这些寄存器的值由 HuC/GuC 固件大小和一组硬件要求/限制计算和确定,如下所示

  +=========> +====================+ <== WOPCM Top
  ^           |  HW contexts RSVD  |
  |     +===> +====================+ <== GuC WOPCM Top
  |     ^     |                    |
  |     |     |                    |
  |     |     |                    |
  |    GuC    |                    |
  |   WOPCM   |                    |
  |    Size   +--------------------+
WOPCM   |     |    GuC FW RSVD     |
  |     |     +--------------------+
  |     |     |   GuC Stack RSVD   |
  |     |     +------------------- +
  |     v     |   GuC WOPCM RSVD   |
  |     +===> +====================+ <== GuC WOPCM base
  |           |     WOPCM RSVD     |
  |           +------------------- + <== HuC Firmware Top
  v           |      HuC FW        |
  +=========> +====================+ <== WOPCM Base

GuC 可访问的 WOPCM 从 GuC WOPCM 基址开始,到 GuC WOPCM 顶部结束。WOPCM 的顶部保留用于硬件上下文(例如 RC6 上下文)。

GuC

GuC 是 gen9 中引入的 GT HW 内部的微控制器。GuC 旨在卸载通常由主机驱动程序执行的某些功能;目前,它可以处理的主要操作是

  • HuC 的身份验证,这是完全启用 HuC 使用所必需的。

  • 低延迟图形上下文调度(也称为 GuC 提交)。

  • GT 电源管理。

enable_guc 模块参数可用于选择在 GuC 中启用哪些操作。请注意,并非所有 gen9+ 平台都支持所有操作。

启用 GuC 不是强制性的,因此仅当至少选择一个操作时才会加载固件。但是,不加载 GuC 可能会导致丢失一些确实需要 GuC 的功能(目前只有 HuC,但预计将来会增加更多)。

struct intel_guc

GuC 的顶层结构。

定义:

struct intel_guc {
    struct intel_uc_fw fw;
    struct intel_guc_log log;
    struct intel_guc_ct ct;
    struct intel_guc_slpc slpc;
    struct intel_guc_state_capture *capture;
    struct dentry *dbgfs_node;
    struct i915_sched_engine *sched_engine;
    struct i915_request *stalled_request;
    enum {
        STALL_NONE,
        STALL_REGISTER_CONTEXT,
        STALL_MOVE_LRC_TAIL,
        STALL_ADD_REQUEST,
    } submission_stall_reason;
    spinlock_t irq_lock;
    unsigned int msg_enabled_mask;
    atomic_t outstanding_submission_g2h;
    struct xarray tlb_lookup;
    u32 serial_slot;
    u32 next_seqno;
    struct {
        bool enabled;
        void (*reset)(struct intel_guc *guc);
        void (*enable)(struct intel_guc *guc);
        void (*disable)(struct intel_guc *guc);
    } interrupts;
    struct {
        spinlock_t lock;
        struct ida guc_ids;
        int num_guc_ids;
        unsigned long *guc_ids_bitmap;
        struct list_head guc_id_list;
        unsigned int guc_ids_in_use;
        struct list_head destroyed_contexts;
        struct work_struct destroyed_worker;
        struct work_struct reset_fail_worker;
        intel_engine_mask_t reset_fail_mask;
        unsigned int sched_disable_delay_ms;
        unsigned int sched_disable_gucid_threshold;
    } submission_state;
    bool submission_supported;
    bool submission_selected;
    bool submission_initialized;
    struct intel_uc_fw_ver submission_version;
    bool rc_supported;
    bool rc_selected;
    struct i915_vma *ads_vma;
    struct iosys_map ads_map;
    u32 ads_regset_size;
    u32 ads_regset_count[I915_NUM_ENGINES];
    struct guc_mmio_reg *ads_regset;
    u32 ads_golden_ctxt_size;
    u32 ads_waklv_size;
    u32 ads_capture_size;
    struct i915_vma *lrc_desc_pool_v69;
    void *lrc_desc_pool_vaddr_v69;
    struct xarray context_lookup;
    u32 params[GUC_CTL_MAX_DWORDS];
    struct {
        u32 base;
        unsigned int count;
        enum forcewake_domains fw_domains;
    } send_regs;
    i915_reg_t notify_reg;
    u32 mmio_msg;
    struct mutex send_mutex;
    struct {
        spinlock_t lock;
        u64 gt_stamp;
        unsigned long ping_delay;
        struct delayed_work work;
        u32 shift;
        unsigned long last_stat_jiffies;
    } timestamp;
    struct work_struct dead_guc_worker;
    unsigned long last_dead_guc_jiffies;
#ifdef CONFIG_DRM_I915_SELFTEST;
    int number_guc_id_stolen;
    u32 fast_response_selftest;
#endif;
};

成员

fw

GuC 固件

log

包含 GuC 日志相关数据和对象的子结构

ct

命令传输通信通道

slpc

包含 SLPC 相关数据和对象的子结构

capture

错误状态捕获模块的数据和对象

dbgfs_node

debugfs 节点

sched_engine

用于向 GuC 提交请求的全局引擎

stalled_request

如果 GuC 由于任何原因无法处理请求,我们会保存该请求,直到 GuC 重新开始处理。在已暂停的请求被处理之前,不能提交其他请求。

submission_stall_reason

提交暂停的原因

irq_lock

保护 GuC 的 irq 状态

msg_enabled_mask

接收到 INTEL_GUC_ACTION_DEFAULT G2H 消息时处理的事件掩码。

outstanding_submission_g2h

与 GuC 提交相关的未完成的 GuC 到主机响应的数量,用于确定 GT 是否空闲

tlb_lookup

用于存储所有待处理的 TLB 失效请求的 xarray

serial_slot

指向 tlb_lookup 中创建的初始等待者的 ID,仅在分配新的等待者失败时使用。

next_seqno

要分配的下一个 ID(序列号)。

interrupts

指向 GuC 中断管理函数的指针。

submission_state

由单个锁保护的提交状态子结构

submission_state.lock

保护 submission_state 中的所有内容,ce->guc_id.id 和 ce->guc_id.ref 在进出零状态时也受保护

submission_state.guc_ids

用于分配新的 guc_id,单 lrc

submission_state.num_guc_ids

guc_ids 的数量,自检功能可以在测试时减少此数量。

submission_state.guc_ids_bitmap

用于分配新的 guc_id,多 lrc

submission_state.guc_id_list

具有有效的 guc_id 但没有引用的 intel_context 列表

submission_state.guc_ids_in_use

正在使用的单 lrc guc_ids 的数量

submission_state.destroyed_contexts

等待销毁(在 GuC 中注销)的上下文列表

submission_state.destroyed_worker

用于注销上下文的工作线程,这是因为我们需要获取 GT PM 引用,并且不能从销毁函数中获取,因为它可能处于原子上下文中(不休眠)

submission_state.reset_fail_worker

在引擎重置失败后触发 GT 重置的工作线程

submission_state.reset_fail_mask

重置失败的引擎掩码

submission_state.sched_disable_delay_ms

上下文的计划禁用延迟,单位为毫秒

submission_state.sched_disable_gucid_threshold

在开始绕过计划禁用延迟之前,最小剩余可用 guc_ids 的阈值

submission_supported

跟踪当前平台是否支持 GuC 提交

submission_selected

跟踪用户是否启用了 GuC 提交

submission_initialized

跟踪 GuC 提交是否已初始化

submission_version

当前加载的固件的提交 API 版本

rc_supported

跟踪当前平台是否支持 GuC rc

rc_selected

跟踪用户是否启用了 GuC rc

ads_vma

分配的用于保存 GuC ADS 的对象

ads_map

GuC ADS 的内容

ads_regset_size

ADS 中保存/恢复寄存器集的大小

ads_regset_count

ADS 中每个引擎的保存/恢复寄存器数量

ads_regset

ADS 中的保存/恢复寄存器集

ads_golden_ctxt_size

ADS 中黄金上下文的大小

ads_waklv_size

解决方法 KLV 的大小

ads_capture_size

ADS 中用于错误捕获的寄存器列表的大小

lrc_desc_pool_v69

分配的用于保存 GuC LRC 描述符池的对象

lrc_desc_pool_vaddr_v69

GuC LRC 描述符池的内容

context_lookup

用于从 guc_id 解析 intel_context,如果上下文存在于此结构中,则它已在 GuC 中注册

params

固件初始化的控制参数

send_regs

GuC 的固件特定寄存器,用于发送 MMIO H2G

notify_reg

用于向 GuC 固件发送中断的寄存器

mmio_msg

当 CT 通道被禁用时,GuC 在其寄存器之一中写入的通知位掩码,在通道恢复后进行处理。

send_mutex

用于序列化 intel_guc_send 操作

timestamp

GT 时间戳对象,用于存储时间戳的副本,并使用工作线程对其进行溢出调整。

timestamp.lock

保护以下字段和引擎统计信息的锁。

timestamp.gt_stamp

GT 时间戳的 64 位扩展值。

timestamp.ping_delay

轮询 GT 时间戳以检测溢出的周期。

timestamp.work

定期工作,用于调整 GT 时间戳、引擎和上下文使用情况以处理溢出。

timestamp.shift

gpm 时间戳的右移值

timestamp.last_stat_jiffies

上次实际收集统计信息时的 jiffies。我们使用此时间戳来确保我们不会过度采样统计信息,因为运行时电源管理事件可能会以比所需更高的速率触发统计信息收集。

dead_guc_worker

用于强制 GuC 重置的异步工作线程。专门用于当 G2H 处理程序想要发出重置时。重置需要刷新 G2H 队列。因此,G2H 处理本身不得直接触发重置。相反,通过此工作线程进行。

last_dead_guc_jiffies

上次“死 GuC”发生的时间戳,用于防止基本损坏的系统持续重新加载 GuC。

number_guc_id_stolen

已被盗用的 guc_ids 的数量

fast_response_selftest

CT 处理程序的后门,用于快速响应自检

描述

它处理固件加载并管理客户端池。intel_guc 拥有一个用于提交的 i915_sched_engine。

u32 intel_guc_ggtt_offset(struct intel_guc *guc, struct i915_vma *vma)

获取并验证 vma 的 GGTT 偏移量

参数

struct intel_guc *guc

intel_guc 结构。

struct i915_vma *vma

i915 图形虚拟内存区域。

描述

GuC 不允许任何落在范围 [0, ggtt.pin_bias) 内的 gfx GGTT 地址,该范围保留给 Boot ROM、SRAM 和 WOPCM。目前,为了将 [0, ggtt.pin_bias) 地址空间从 GGTT 中排除,GuC 使用的所有 gfx 对象都使用 intel_guc_allocate_vma() 分配,并使用 PIN_OFFSET_BIAS 以及 ggtt.pin_bias 的值进行固定。

返回

vma 的 GGTT 偏移量。

GuC 固件布局

GuC/HuC 固件布局如下所示

+======================================================================+
|  Firmware blob                                                       |
+===============+===============+============+============+============+
|  CSS header   |     uCode     |  RSA key   |  modulus   |  exponent  |
+===============+===============+============+============+============+
 <-header size->                 <---header size continued ----------->
 <--- size ----------------------------------------------------------->
                                 <-key size->
                                              <-mod size->
                                                           <-exp size->

固件可能包含或不包含模数密钥和指数数据。标头、uCode 和 RSA 签名是驱动程序必须使用的组件。每个组件的长度(均以 dword 为单位)可以在标头中找到。如果模数和指数不存在于固件中(又名截断图像),则长度值仍会出现在标头中。

驱动程序将根据以下规则执行一些基本的固件大小验证

  1. 标头、uCode 和 RSA 是必须的组件。

  2. 如果存在,所有固件组件都按照上面布局表中所示的顺序排列。

  3. 每个组件的长度信息可以在标头中找到,以 dword 为单位。

  4. 驱动程序不需要模数和指数密钥。它们可能不会出现在固件中。因此,在这种情况下,驱动程序将加载截断的固件。

从 DG2 开始,HuC 由 GSC 而不是 i915 加载。GSC 固件执行所有必需的完整性检查,我们只需要检查版本。请注意,GSC 管理的 blob 的标头与用于 DMA 加载固件的 CSS 不同。

GuC 内存管理

GuC 不能为其自身使用分配任何内存,因此所有分配都必须由主机驱动程序处理。GuC 通过 GGTT 访问内存,除了 4GB 地址空间的顶部和底部,这两个部分由 GuC HW 重新映射到固件本身 (WOPCM) 或 HW 的其他部分的内存位置。驱动程序必须注意不要将 GuC 将要访问的对象放置在这些保留范围内。GuC 地址空间的布局如下所示

   +===========> +====================+ <== FFFF_FFFF
   ^             |      Reserved      |
   |             +====================+ <== GUC_GGTT_TOP
   |             |                    |
   |             |        DRAM        |
  GuC            |                    |
Address    +===> +====================+ <== GuC ggtt_pin_bias
 Space     ^     |                    |
   |       |     |                    |
   |      GuC    |        GuC         |
   |     WOPCM   |       WOPCM        |
   |      Size   |                    |
   |       |     |                    |
   v       v     |                    |
   +=======+===> +====================+ <== 0000_0000

GuC 地址空间的下部 [0, ggtt_pin_bias) 映射到 GuC WOPCM,而 GuC 地址空间的上部 [ggtt_pin_bias, GUC_GGTT_TOP) 映射到 DRAM。GuC ggtt_pin_bias 的值是 GuC WOPCM 的大小。

struct i915_vma *intel_guc_allocate_vma(struct intel_guc *guc, u32 size)

为 GuC 使用分配一个 GGTT VMA

参数

struct intel_guc *guc

guc

u32 size

要分配的区域大小(包括虚拟空间和内存)

描述

这是一个用于创建供 GuC 使用的对象的包装器。为了在 GuC 内部使用它,一个对象需要被固定生命周期,因此我们同时分配一些后备存储和全局 GTT 内的范围。我们必须将其固定在 GGTT 中的 [0, GUC ggtt_pin_bias) 之外的某个位置,因为该范围在 GuC 内部保留。

返回

如果成功,则返回 i915_vma,否则返回 ERR_PTR。

特定于 GuC 的固件加载器

int intel_guc_fw_upload(struct intel_guc *guc)

将 GuC uCode 加载到设备

参数

struct intel_guc *guc

intel_guc 结构

描述

在驱动程序加载、从睡眠状态恢复以及 GPU 重置后,从 intel_uc_init_hw() 调用。

固件映像应该已经提取到内存中,因此只需检查提取是否成功,然后将映像传输到硬件。

返回

错误时返回非零代码

基于 GuC 的命令提交

暂存寄存器:从 0xC180 开始有 16 个基于 MMIO 的寄存器。内核驱动程序将值写入操作寄存器 (SOFT_SCRATCH_0) 以及任何数据。然后,它通过另一个寄存器写入 (0xC4C8) 在 GuC 上触发中断。固件在处理请求后将成功/失败代码写回操作寄存器。内核驱动程序轮询等待此更新,然后继续。

命令传输缓冲区 (CTB):在其他部分详细介绍,但 CTB(主机到 GuC - H2G,GuC 到主机 - G2H)是 i915 和 GuC 之间的消息接口。

上下文注册:在提交上下文之前,必须通过 H2G 在 GuC 中注册。每个上下文都与唯一的 guc_id 关联。上下文在请求创建时(正常操作)或提交时(异常操作,例如重置后)注册。

上下文提交:i915 更新内存中的 LRC 尾部值。i915 必须在 GuC 中启用上下文的调度,GuC 才能实际考虑它。因此,第一次提交禁用的上下文时,我们使用计划启用 H2G,而后续提交则通过上下文提交 H2G 完成,该 H2G 通知 GuC 之前启用的上下文有新的工作可用。

上下文取消固定:要取消固定上下文,需要使用 H2G 来禁用调度。当对应的 G2H 返回,表明调度禁用操作已完成时,取消固定上下文才是安全的。在禁用操作进行期间,重新提交上下文是不安全的,因此会使用栅栏来阻止该上下文的所有未来请求,直到 G2H 返回。由于与 GuC 的这种交互需要一定的时间,因此我们在引脚计数归零后会延迟一段时间(请参阅 SCHED_DISABLE_DELAY_MS)禁用调度。这样做的想法是,在执行此代价高昂的操作之前,给用户一个时间窗口来重新提交上下文中的内容。只有在上下文未关闭且 guc_id 的使用量低于阈值(请参阅 NUM_SCHED_DISABLE_GUC_IDS_THRESHOLD)时,才会执行此延迟。

上下文注销:在销毁上下文之前,或者如果我们窃取其 guc_id,我们必须通过 H2G 向 GuC 注销该上下文。如果窃取 guc_id,那么在该注销完成之前,向该 guc_id 提交任何内容都是不安全的,因此会使用栅栏来阻止与该 guc_id 相关的所有请求,直到对应的 G2H 返回,表明 guc_id 已被注销。

submission_state.guc_ids:与在上下文注册/提交/注销期间传入的私有 GuC 上下文数据关联的唯一编号。可用 64k。使用简单的 ida 进行分配。

窃取 guc_ids:如果没有可用的 guc_ids,则可以在请求创建时从另一个未固定的上下文中窃取它们。如果找不到 guc_id,我们会将此问题转嫁给用户,因为我们认为在正常使用情况下几乎不可能遇到这种情况。

锁定:在 GuC 提交代码中,我们有 3 个基本的自旋锁来保护所有内容。以下是关于每个锁的详细信息。

sched_engine->lock:这是共享 i915 调度引擎 (sched_engine) 的所有上下文的提交锁,因此在任何时候,只有一个共享 sched_engine 的上下文可以提交。目前,只有一个 sched_engine 用于所有 GuC 提交,但将来可能会发生变化。

guc->submission_state.lock:GuC 提交状态的全局锁。保护 guc_ids 和已销毁上下文列表。

ce->guc_state.lock:保护 ce->guc_state 下的所有内容。确保在发出 H2G 之前,上下文处于正确的状态。例如,我们不会在禁用的上下文中发出调度禁用(这是个坏主意),我们不会在调度禁用正在进行时发出调度启用,等等... 还会保护上下文中的正在进行的请求列表和优先级管理状态。锁对于每个上下文都是独立的。

锁的顺序规则:sched_engine->lock -> ce->guc_state.lock;guc->submission_state.lock -> ce->guc_state.lock

重置竞争:当触发完全 GT 重置时,假设某些对 H2G 的 G2H 响应可能会丢失,因为 GuC 也会被重置。丢失这些 G2H 可能会导致致命错误,因为我们在收到 G2H 时会执行某些操作(例如,销毁上下文、释放 guc_ids 等...)。当这种情况发生时,我们可以清理上下文状态并进行相应的清理,但这会很棘手。为了避免竞争,重置代码必须在清理丢失的 G2H 之前禁用提交,而提交代码必须检查提交是否被禁用,并在禁用时跳过发送 H2G 和更新上下文状态。双方还必须确保持有相关的锁。

GuC ABI

HXG 消息

与 GuC 交换的所有消息都使用 32 位双字定义。第一个双字被视为消息头。其余的双字是可选的。

描述

0

31

ORIGIN - 消息的始发者
  • GUC_HXG_ORIGIN_HOST = 0

  • GUC_HXG_ORIGIN_GUC = 1

30:28

TYPE - 消息类型
  • GUC_HXG_TYPE_REQUEST = 0

  • GUC_HXG_TYPE_EVENT = 1

  • GUC_HXG_TYPE_FAST_REQUEST = 2

  • GUC_HXG_TYPE_NO_RESPONSE_BUSY = 3

  • GUC_HXG_TYPE_NO_RESPONSE_RETRY = 5

  • GUC_HXG_TYPE_RESPONSE_FAILURE = 6

  • GUC_HXG_TYPE_RESPONSE_SUCCESS = 7

27:0

AUX - 辅助数据(取决于 TYPE)

1

31:0

PAYLOAD - 可选有效负载(取决于 TYPE)

...

n

31:0

HXG 请求

应使用 HXG 请求消息来启动需要确认或返回数据的同步活动。

此消息的接收者应使用 HXG 响应HXG 失败HXG 重试消息作为明确的答复,并且可以使用 HXG 忙碌消息作为中间答复。

DATA0 和所有 DATAn 字段的格式取决于 ACTION 代码。

描述

0

31

ORIGIN

30:28

TYPE = GUC_HXG_TYPE_REQUEST

27:16

DATA0 - 请求数据(取决于 ACTION)

15:0

ACTION - 请求的操作代码

1

31:0

DATAn - 可选数据(取决于 ACTION)

...

n

31:0

HXG 快速请求

应使用 HXG 请求消息来启动不需要确认或返回数据的异步活动。

如果需要确认,则应改用 HXG 请求

此消息的接收者如果无法接受此请求(例如数据无效),则只能使用 HXG 失败消息。

HXG 快速请求消息的格式与 HXG 请求相同,只是 TYPE 不同。

描述

0

31

ORIGIN - 请参阅 HXG 消息

30:28

TYPE = GUC_HXG_TYPE_FAST_REQUEST

27:16

DATA0 - 请参阅 HXG 请求

15:0

ACTION - 请参阅 HXG 请求

...

DATAn - 请参阅 HXG 请求

HXG 事件

应使用 HXG 事件消息来启动不涉及立即确认或数据的异步活动。

DATA0 和所有 DATAn 字段的格式取决于 ACTION 代码。

描述

0

31

ORIGIN

30:28

TYPE = GUC_HXG_TYPE_EVENT

27:16

DATA0 - 事件数据(取决于 ACTION)

15:0

ACTION - 事件操作代码

1

31:0

DATAn - 可选事件数据(取决于 ACTION)

...

n

31:0

HXG 忙碌

如果接收者预期其处理时间将长于默认超时,则可以使用 HXG 忙碌消息来确认收到 HXG 请求消息。

COUNTER 字段可以用作进度指示器。

描述

0

31

ORIGIN

30:28

TYPE = GUC_HXG_TYPE_NO_RESPONSE_BUSY

27:0

COUNTER - 进度指示器

HXG 重试

接收者应使用 HXG 重试消息来指示 HXG 请求消息已被丢弃,应再次发送。

REASON 字段可用于提供其他信息。

描述

0

31

ORIGIN

30:28

TYPE = GUC_HXG_TYPE_NO_RESPONSE_RETRY

27:0

REASON - 重试原因
  • GUC_HXG_RETRY_REASON_UNSPECIFIED = 0

HXG 失败

应使用 HXG 失败消息作为对由于错误而无法处理的 HXG 请求消息的答复。

描述

0

31

ORIGIN

30:28

TYPE = GUC_HXG_TYPE_RESPONSE_FAILURE

27:16

HINT - 其他错误提示

15:0

ERROR - 错误/结果代码

HXG 响应

应使用 HXG 响应消息作为对已成功处理且没有错误的 HXG 请求消息的答复。

描述

0

31

ORIGIN

30:28

TYPE = GUC_HXG_TYPE_RESPONSE_SUCCESS

27:0

DATA0 - 数据(取决于 HXG 请求中的 ACTION)

1

31:0

DATAn - 数据(取决于 HXG 请求中的 ACTION)

...

n

31:0

基于 GuC MMIO 的通信

主机和 GuC 之间的基于 MMIO 的通信依赖于特殊的硬件寄存器,其格式可以由软件定义(所谓的暂存寄存器)。

每个基于 MMIO 的消息,无论是主机到 GuC (H2G) 消息还是 GuC 到主机 (G2H) 消息,其最大长度取决于可用的暂存寄存器的数量,都是直接写入这些暂存寄存器的。

对于 Gen9+,有 16 个软件暂存寄存器 0xC180-0xC1B8,但是没有 H2G 命令采用超过 4 个参数,并且 GuC 固件本身使用一个 4 元素数组来存储 H2G 消息。

对于 Gen11+,还有另外 4 个寄存器 0x190240-0x19024C,无论数量较少,都优先于旧的寄存器。

基于 MMIO 的通信主要在驱动程序初始化阶段使用,以设置随后将使用的 基于 CTB 的通信

MMIO HXG 消息

MMIO 消息的格式遵循 HXG 消息的定义。

描述

0

31:0

[嵌入式 HXG 消息]

...

n

31:0

CT 缓冲区

用于发送 CTB 消息的循环缓冲区

CTB 描述符

描述

0

31:0

HEAD - 从 CT 缓冲区读取的最后一个双字的偏移量(以双字为单位)。只能由接收者更新。

1

31:0

TAIL - 写入 CT 缓冲区的最后一个双字的偏移量(以双字为单位)。只能由发送者更新。

2

31:0

STATUS - CTB 的状态

  • GUC_CTB_STATUS_NO_ERROR = 0(正常操作)

  • GUC_CTB_STATUS_OVERFLOW = 1(头/尾太大)

  • GUC_CTB_STATUS_UNDERFLOW = 2(消息被截断)

  • GUC_CTB_STATUS_MISMATCH = 4(头/尾被修改)

  • GUC_CTB_STATUS_UNUSED = 8(CTB 未使用)

...

RESERVED = MBZ

15

31:0

RESERVED = MBZ

CTB 消息

描述

0

31:16

FENCE - 消息标识符

15:12

FORMAT - CTB 消息的格式

11:8

RESERVED

7:0

NUM_DWORDS - CTB 消息的长度(不包括头)

1

31:0

可选(取决于 FORMAT)

...

n

31:0

CTB HXG 消息

描述

0

31:16

FENCE

15:12

FORMAT = GUC_CTB_FORMAT_HXG

11:8

RESERVED = MBZ

7:0

NUM_DWORDS = 嵌入式 HXG 消息的长度(以双字为单位)

1

31:0

[嵌入式 HXG 消息]

...

n

31:0

基于 CTB 的通信

主机和 GuC 之间的 CTB(命令传输缓冲区)通信基于写入共享缓冲区的 u32 数据流。一个缓冲区只能用于在一个方向(单向通道)上传输数据。

每个缓冲区的当前状态都存储在缓冲区描述符中。缓冲区描述符包含表示活动数据流的尾部和头部字段。尾部字段由数据生产者(发送者)更新,头部字段由数据消费者(接收者)更新。

+------------+
| DESCRIPTOR |          +=================+============+========+
+============+          |                 | MESSAGE(s) |        |
| address    |--------->+=================+============+========+
+------------+
| head       |          ^-----head--------^
+------------+
| tail       |          ^---------tail-----------------^
+------------+
| size       |          ^---------------size--------------------^
+------------+

数据流中的每条消息都以单个 u32 开头,该 u32 被视为头,后跟可选的一组 u32 数据,这些数据构成特定于消息的有效负载

+------------+---------+---------+---------+
|         MESSAGE                          |
+------------+---------+---------+---------+
|   msg[0]   |   [1]   |   ...   |  [n-1]  |
+------------+---------+---------+---------+
|   MESSAGE  |       MESSAGE PAYLOAD       |
+   HEADER   +---------+---------+---------+
|            |    0    |   ...   |    n    |
+======+=====+=========+=========+=========+
| 31:16| code|         |         |         |
+------+-----+         |         |         |
|  15:5|flags|         |         |         |
+------+-----+         |         |         |
|   4:0|  len|         |         |         |
+------+-----+---------+---------+---------+

             ^-------------len-------------^

消息头由以下部分组成

  • len,指示消息有效负载的长度(以 u32 为单位)

  • code,指示消息代码

  • flags,包含用于控制消息处理的各种位

HOST2GUC_SELF_CFG

此消息由主机 KMD 用于设置 GuC 自配置 KLV

此消息必须作为 MMIO HXG 消息发送。

描述

0

31

ORIGIN = GUC_HXG_ORIGIN_HOST

30:28

TYPE = GUC_HXG_TYPE_REQUEST

27:16

DATA0 = MBZ

15:0

ACTION = GUC_ACTION_HOST2GUC_SELF_CFG = 0x0508

1

31:16

KLV_KEY - KLV 键,请参阅 GuC 自配置 KLV

15:0

KLV_LEN - KLV 长度

  • 32 位 KLV = 1

  • 64 位 KLV = 2

2

31:0

VALUE32 - KLV 值的第 31-0 位

3

31:0

VALUE64 - KLV 值的第 63-32 位 (KLV_LEN = 2)

描述

0

31

ORIGIN = GUC_HXG_ORIGIN_GUC

30:28

TYPE = GUC_HXG_TYPE_RESPONSE_SUCCESS

27:0

DATA0 = NUM - 如果已解析 KLV,则为 1,如果未识别,则为 0

HOST2GUC_CONTROL_CTB

此 H2G 操作允许 Vf Host 启用或禁用 H2G 和 G2H CT 缓冲区

此消息必须作为 MMIO HXG 消息发送。

描述

0

31

ORIGIN = GUC_HXG_ORIGIN_HOST

30:28

TYPE = GUC_HXG_TYPE_REQUEST

27:16

DATA0 = MBZ

15:0

ACTION = GUC_ACTION_HOST2GUC_CONTROL_CTB = 0x4509

1

31:0

CONTROL - 控制 基于 CTB 的通信

  • GUC_CTB_CONTROL_DISABLE = 0

  • GUC_CTB_CONTROL_ENABLE = 1

描述

0

31

ORIGIN = GUC_HXG_ORIGIN_GUC

30:28

TYPE = GUC_HXG_TYPE_RESPONSE_SUCCESS

27:0

DATA0 = MBZ

GuC KLV

描述

0

31:16

KEY - KLV 键标识符

15:0

LEN - VALUE 的长度(以 32 位双字为单位)

1

31:0

VALUE - KLV 的实际值(格式取决于 KEY)

...

n

31:0

GuC 自配置 KLV

可用于 HOST2GUC_SELF_CFGGuC KLV 键。

GUC_KLV_SELF_CFG_H2G_CTB_ADDR0x0902

指 H2G CT 缓冲区的 64 位全局 Gfx 地址。应高于 WOPCM 地址,但低于本机模式下的 APIC 基址。

GUC_KLV_SELF_CFG_H2G_CTB_DESCRIPTOR_ADDR0x0903

指 H2G CTB 描述符的 64 位全局 Gfx 地址。应高于 WOPCM 地址,但低于本机模式下的 APIC 基址。

GUC_KLV_SELF_CFG_H2G_CTB_SIZE0x0904

指 H2G CT 缓冲区的大小(以字节为单位)。应为 4K 的倍数。

GUC_KLV_SELF_CFG_G2H_CTB_ADDR0x0905

指 G2H CT 缓冲区的 64 位全局 Gfx 地址。应高于 WOPCM 地址,但低于本机模式下的 APIC 基址。

GUC_KLV_SELF_CFG_G2H_CTB_DESCRIPTOR_ADDR0x0906

指 G2H CTB 描述符的 64 位全局 Gfx 地址。应高于 WOPCM 地址,但低于本机模式下的 APIC 基址。

GUC_KLV_SELF_CFG_G2H_CTB_SIZE0x0907

指 G2H CT 缓冲区的大小(以字节为单位)。应为 4K 的倍数。

HuC

HuC 是一个专用的微控制器,用于媒体 HEVC(高效视频编码)操作。用户空间可以通过向批处理缓冲区添加 HuC 特定命令来直接使用固件功能。

内核驱动程序仅负责加载 HuC 固件并触发其安全身份验证。根据平台的不同,此操作有所不同

  • 较旧的平台(从 Gen9 到大多数 Gen12):加载通过 DMA 执行,身份验证通过 GuC 执行

  • DG2:加载和身份验证都通过 GSC 执行。

  • MTL 和更新的平台:加载通过 DMA 执行(与非 DG2 较旧平台相同),而身份验证分两步完成,首先通过 GuC 对清晰媒体工作负载进行身份验证,然后通过 GSC 对所有工作负载进行身份验证。

在 GuC 执行身份验证的平台上,要正确执行此操作,HuC 二进制文件必须在 GuC 二进制文件之前加载。加载 HuC 是可选的;但是,不使用 HuC 可能会对媒体工作负载的功耗和/或性能产生负面影响,具体取决于用例。在导致 WOPCM 丢失其内容(S3/S4、FLR)的事件时,必须重新加载 HuC;在较旧的平台上,HuC 还必须在 GuC/GT 重置时重新加载,而在较新的平台上,它将存活下来。

有关 HuC 功能的最新详细信息,请参阅 https://github.com/intel/media-driver

int intel_huc_auth(struct intel_huc *huc, enum intel_huc_authentication_type type)

验证 HuC uCode

参数

struct intel_huc *huc

intel_huc 结构

enum intel_huc_authentication_type type

身份验证类型(通过 GuC 或通过 GSC)

描述

在 intel_uc_init_hw() 期间加载 HuC 和 GuC 固件后调用。

此函数调用 GuC 操作以验证 HuC 固件,并将 RSA 签名的偏移量传递给 intel_guc_auth_huc()。然后,它会等待最长 50 毫秒以接收固件验证 ACK。

HuC 内存管理

与 GuC 类似,HuC 也不能自行进行任何内存分配,不同之处在于 HuC 使用的分配由用户空间驱动程序而不是内核驱动程序处理。HuC 通过属于在执行 HuC 特定命令的 VCS 上加载的上下文的 PPGTT 访问内存。

HuC 固件布局

HuC FW 布局与 GuC 相同,请参阅 GuC 固件布局

DMC

请参阅 DMC 固件支持

跟踪

本节介绍 i915 驱动程序中实现的所有与跟踪点相关的内容。

i915_ppgtt_create 和 i915_ppgtt_release

启用完整 ppgtt 后,每个使用 drm 的进程都将分配至少一个转换表。通过这些跟踪,可以跟踪表的分配和生命周期;这可以在测试/调试期间用于验证我们是否未泄漏 ppgtt。这些跟踪通过 vm 指针识别 ppgtt,该指针也由 i915_vma_bind 和 i915_vma_unbind 跟踪点打印。

i915_context_create 和 i915_context_free

这些跟踪点用于跟踪上下文的创建和删除。如果启用完整 ppgtt,它们还会打印分配给上下文的 vm 的地址。

Perf

概述

Gen 图形支持大量性能计数器,这些计数器可以帮助驱动程序和应用程序开发人员了解和优化其 GPU 的使用。

此 i915 perf 接口使用户空间能够配置并打开一个文件描述符,该文件描述符表示 GPU 指标流,然后可以将其作为样本记录流 read()。

该接口特别适合于公开由 DMA 从 GPU 捕获的缓冲指标,这些指标与 CPU 不同步且不相关。

具有相应 drm 文件描述符的应用程序可以访问表示单个上下文的流,以便 OpenGL 可以在没有特殊权限的情况下使用该接口。默认情况下,访问系统范围的指标需要 root 权限,除非通过 dev.i915.perf_event_paranoid sysctl 选项进行更改。

与核心 Perf 的比较

该接口最初受核心 Perf 基础结构的启发,但一些显著差异是

i915 perf 文件描述符表示“流”而不是“事件”;其中 perf 事件主要对应于单个 64 位值,而流可能会采样紧密耦合的计数器集,具体取决于配置。例如,Gen OA 单元并非设计为支持单个计数器的正交配置;它配置为一组相关的计数器。用于捕获 OA 指标的 i915 perf 流的样本将包括以紧凑的 HW 特定格式打包的一组计数器值。OA 单元支持多种不同的打包格式,用户可以通过打开流来选择这些格式。Perf 支持对事件进行分组,但组中的每个事件都会单独配置、验证和身份验证,并使用单独的系统调用。

i915 perf 流配置以 u64(键,值)对数组的形式提供,而不是具有多个杂项配置成员的固定结构,这些成员与事件类型特定的成员交错。

i915 perf 不支持通过 mmap'd 循环缓冲区公开指标。所支持的指标由 GPU 以与 CPU 不同步的方式写入内存,并使用 HW 特定打包格式来设置计数器。有时,对 HW 配置的约束要求在将报告公开给非特权应用程序之前对其进行筛选 - 以隐藏其他进程/上下文的指标。对于这些用例,基于 read() 的接口非常合适,并提供了在将数据从 GPU 映射缓冲区复制到用户空间缓冲区时筛选数据的机会。

基于核心 Perf 的第一个原型遇到的问题

此驱动程序的第一个原型基于核心 perf 基础结构,虽然我们确实使该原型大部分都能工作,并且对 perf 进行了一些更改,但我们发现我们正在破坏或绕过 perf 当前以 CPU 为中心的设计中嵌入的太多假设。

最终,我们没有看到通过更改设计假设来使 perf 的实现和接口更加复杂的好处,同时我们知道我们仍然无法使用任何现有的基于 perf 的用户空间工具。

考虑到 Gen 特定性质的观测硬件以及用户空间有时将需要将 i915 perf OA 指标与通过 MI_REPORT_PERF_COUNT 命令捕获的侧带 OA 数据相结合;我们期望该接口被特定于平台的的用户空间(例如 OpenGL 或工具)使用。也就是说;我们并没有因为不使用 perf 而固有地错过拥有标准的供应商/架构无关接口的机会。

为了后代着想,以防我们重新考虑调整核心 perf 以更好地暴露 i915 指标,以下是我们遇到的主要痛点:

  • 基于 perf 的 OA PMU 驱动程序打破了一些重要的设计假设。

    现有的 perf pmu 用于 CPU 上的分析工作,而我们引入了 _IS_DEVICE pmu 的概念,它具有不同的安全含义,需要伪造与 CPU 相关的数据(例如用户/内核寄存器)以适应 perf 当前的设计,并添加 _DEVICE 记录作为转发设备特定状态记录的方式。

    OA 单元将计数器报告写入循环缓冲区,无需 CPU 的参与,这使得我们的 PMU 驱动程序成为首例。

    鉴于我们定期将数据从 GPU 映射的 OA 缓冲区转发到 perf 缓冲区的方式,这些采样写入的爆发在 perf 看来就像我们采样过快,因此我们不得不颠覆其节流检查。

    Perf 支持计数器组,并允许通过内部事务读取这些计数器,但事务目前似乎设计为由 CPU 显式启动(例如,响应用户空间 read()),虽然我们可以从 OA 缓冲区中提取报告,但我们无法按需从 CPU 触发报告。

    与基于报告相关;OA 计数器在硬件中配置为一组,而 perf 通常期望计数器配置是正交的。尽管计数器可以在打开时与组领导者关联,但没有明显的先例可以提供组范围的配置属性(例如,我们希望允许用户空间选择用于捕获集合中所有计数器的 OA 单元报告格式,或指定用于过滤指标的 GPU 上下文)。我们避免使用 perf 的分组功能,并通过 perf 的“原始”采样字段将 OA 报告转发到用户空间。考虑到在处理规范化时计数器之间的耦合程度,这很适合我们的用户空间。将计数器拆分为单独的事件是不方便的,只会要求用户空间重新组合它们。对于 Mesa 而言,转发原始的定期报告也很方便,以便与它使用 MI_REPORT_PERF_COUNT 命令捕获的边带原始报告进行组合。

    • 关于 perf 的分组功能,顺便提一下;还有一些担忧,即使用 PERF_FORMAT_GROUP 作为将计数器值打包在一起的方式会大幅增加我们的采样大小,这可能会在可用内存带宽有限时降低我们可用的有效采样分辨率。

      使用 OA 单元的报告格式,计数器被打包为 32 位或 40 位值,最大报告大小为 256 字节。

      PERF_FORMAT_GROUP 值是 64 位的,但这些值的排序似乎没有文档记录,这意味着还必须使用 PERF_FORMAT_ID 在每个值之前添加一个 64 位 ID;每个计数器给出 16 个字节。

    与计数器正交性相关;我们无法分时共享 OA 单元,而事件调度是 perf 中的一个核心设计理念,允许用户空间打开 + 启用比一次可以在硬件中配置的更多事件。OA 单元的设计不允许在使用时重新配置。我们无法在不丢失内部 OA 单元状态的情况下重新配置 OA 单元,我们无法显式访问这些状态来保存和恢复。重新配置 OA 单元也相对较慢,涉及大约 100 次寄存器写入。从用户空间来看,Mesa 在发出 MI_REPORT_PERF_COUNT 命令时也依赖于稳定的 OA 配置,重要的是,在有未完成的 MI_RPC 命令时,无法禁用 OA 单元,否则我们会挂起命令流。

    采样记录的内容不能被设备驱动程序扩展(即 sample_type 位)。例如;Sourab Gupta 一直在尝试将 GPU 时间戳附加到我们的 OA 采样中。我们通过使用“原始”字段将 OA 报告强行放入采样记录中,但是将多个东西打包到此字段中很棘手,因为 events/core.c 当前只允许 pmu 提供单个原始数据指针和 len,它们将被复制到环形缓冲区中。要包含 OA 报告以外的内容,我们必须将报告复制到中间的更大的缓冲区中。我一直在考虑允许指定一个 data+len 值的向量来复制原始数据,但这感觉像是为了将原始字段用于此目的而进行的临时解决方案。

  • 感觉我们基于 perf 的 PMU 只是为了使用 perf 而做出了一些技术上的妥协。

    perf_event_open() 要求事件要么与 pid 相关,要么与特定的 CPU 核心相关,而我们的设备 pmu 与两者都不相关。使用 pid 打开的事件将根据该进程的调度自动启用/禁用 - 因此不适合我们。当事件与 CPU id 相关时,perf 确保将通过该核心上的进程间中断调用 pmu 方法。为了避免侵入式更改,我们的用户空间为特定的 CPU 打开了 OA perf 事件。这是可行的,但这意味着大部分 OA 驱动程序在原子上下文中运行,包括所有 OA 报告转发,这在我们的例子中并不是真正必要的,并且似乎使我们的锁定要求有些复杂,因为我们处理了与 i915 驱动程序其余部分的交互。

i915 驱动程序入口点

本节介绍了在 i915_perf.c 外部导出的入口点,用于与 drm/i915 集成并处理 DRM_I915_PERF_OPEN ioctl。

int i915_perf_init(struct drm_i915_private *i915)

在模块绑定时初始化 i915-perf 状态

参数

struct drm_i915_private *i915

i915 设备实例

描述

初始化 i915-perf 状态,而不向用户空间公开任何内容。

注意

i915-perf 初始化分为“init”和“register”阶段,其中 i915_perf_register() 向用户空间公开状态。

void i915_perf_fini(struct drm_i915_private *i915)

i915_perf_init() 对应的部分

参数

struct drm_i915_private *i915

i915 设备实例

void i915_perf_register(struct drm_i915_private *i915)

向用户空间公开 i915-perf

参数

struct drm_i915_private *i915

i915 设备实例

描述

特别是,OA 指标集在 sysfs metrics/ 目录下发布,允许用户空间枚举可用于打开 i915-perf 流的有效 ID。

void i915_perf_unregister(struct drm_i915_private *i915)

从用户空间隐藏 i915-perf

参数

struct drm_i915_private *i915

i915 设备实例

描述

i915-perf 状态清理分为“unregister”和“deinit”阶段,其中接口首先通过 i915_perf_unregister() 从用户空间隐藏,然后在 i915_perf_fini() 中清理剩余状态。

int i915_perf_open_ioctl(struct drm_device *dev, void *data, struct drm_file *file)

DRM ioctl(),供用户空间打开流 FD

参数

struct drm_device *dev

drm 设备

void *data

从用户空间复制的 ioctl 数据(未经验证)

struct drm_file *file

drm 文件

描述

验证用户空间给定的流打开参数,包括标志和 u64 键值对属性数组。

关于打开的流的性质,预先假设的很少(例如,我们不假设它是用于定期 OA 单元指标的)。i915-perf 流应该是一个适合 GPU 编写的其他形式的缓冲数据的接口,而不是定期的 OA 指标。

请注意,我们在 i915 perf 互斥锁之外从用户空间复制属性,以避免与 mmap_lock 的尴尬的锁依赖。

大多数实现细节由 i915_perf_open_ioctl_locked() 在获取 gt->perf.lock 互斥锁以与任何非文件操作驱动程序钩子进行序列化后处理。

返回

新打开的 i915 Perf 流文件描述符,或者失败时为负错误代码。

int i915_perf_release(struct inode *inode, struct file *file)

处理用户空间关闭流文件的情况

参数

struct inode *inode

与文件关联的匿名 inode

struct file *file

一个 i915 perf 流文件

描述

清除与打开的 i915 perf 流文件相关联的任何资源。

注意:从用户空间的角度来看,close() 实际上不会失败。

返回

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

int i915_perf_add_config_ioctl(struct drm_device *dev, void *data, struct drm_file *file)

用于用户空间添加新的 OA 配置的 DRM ioctl()

参数

struct drm_device *dev

drm 设备

void *data

从用户空间复制的 ioctl 数据(指向 struct drm_i915_perf_oa_config 的指针)(未经验证)

struct drm_file *file

drm 文件

描述

验证提交的 OA 寄存器,将其保存到新的 OA 配置中,然后可以用于编程 OA 单元及其 NOA 网络。

返回

用于 perf open ioctl 的新分配的配置号,或者失败时返回负错误代码。

int i915_perf_remove_config_ioctl(struct drm_device *dev, void *data, struct drm_file *file)

用于用户空间删除 OA 配置的 DRM ioctl()

参数

struct drm_device *dev

drm 设备

void *data

从用户空间复制的 ioctl 数据(指向 u64 整数的指针)

struct drm_file *file

drm 文件

描述

可以在使用时删除配置,它们将停止出现在 sysfs 中,并且当使用该配置的流关闭时,其内容将被释放。

返回

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

i915 Perf 流

本节介绍用于表示 i915 perf 流 FD 和相关文件操作的流语义无关的结构和函数。

struct i915_perf_stream

单个打开的流 FD 的状态

定义:

struct i915_perf_stream {
    struct i915_perf *perf;
    struct intel_uncore *uncore;
    struct intel_engine_cs *engine;
    struct mutex lock;
    u32 sample_flags;
    int sample_size;
    struct i915_gem_context *ctx;
    bool enabled;
    bool hold_preemption;
    const struct i915_perf_stream_ops *ops;
    struct i915_oa_config *oa_config;
    struct llist_head oa_config_bos;
    struct intel_context *pinned_ctx;
    u32 specific_ctx_id;
    u32 specific_ctx_id_mask;
    struct hrtimer poll_check_timer;
    wait_queue_head_t poll_wq;
    bool pollin;
    bool periodic;
    int period_exponent;
    struct {
        const struct i915_oa_format *format;
        struct i915_vma *vma;
        u8 *vaddr;
        u32 last_ctx_id;
        spinlock_t ptr_lock;
        u32 head;
        u32 tail;
    } oa_buffer;
    struct i915_vma *noa_wait;
    u64 poll_oa_period;
};

成员

perf

i915_perf 反向指针

uncore

mmio 访问路径

engine

与此性能流关联的引擎。

与流操作关联的锁

sample_flags

在打开流时给定的表示 DRM_I915_PERF_PROP_SAMPLE_* 属性的标志,表示用户空间通过 read() 读取的单个样本的内容。

sample_size

考虑到样本的配置内容和所需的标头大小,这是单个样本记录的总大小。

ctx

如果跨所有上下文或正在监视的特定上下文进行系统范围的测量,则为 NULL

enabled

考虑到流是否在禁用状态下打开以及基于 I915_PERF_IOCTL_ENABLEI915_PERF_IOCTL_DISABLE 调用,流当前是否已启用。

hold_preemption

是否为在 ctx 上完成的命令提交保持抢占。这对于一些难以对 OA 缓冲区上下文进行后处理以减去与 ctx 无关的性能计数器增量的驱动程序很有用。

ops

提供此特定类型配置的流实现的 回调。

oa_config

流使用的 OA 配置。

oa_config_bos

每次 oa_config 更改时延迟分配的 struct i915_oa_config_bo 列表。

pinned_ctx

OA 上下文的特定信息。

specific_ctx_id

特定上下文的 ID。

specific_ctx_id_mask

用于屏蔽 specific_ctx_id 位的掩码。

poll_check_timer

高分辨率定时器,它会定期检查循环 OA 缓冲区中的数据,以便通知用户空间(例如,在 read() 或 poll() 期间)。

poll_wq

当 hrtimer 回调看到循环 OA 缓冲区中数据准备好读取时,它会唤醒的等待队列。

pollin

是否有可读取的数据。

periodic

当前是否启用了定期采样。

period_exponent

OA 单元采样频率由此得出。

oa_buffer

OA 缓冲区的状态。

oa_buffer.ptr_lock

锁定对所有头/尾状态的读取和写入

考虑:需要从 hrtimer 回调(原子上下文)和 read() fop(用户上下文)中一致地读取头和尾指针状态,尾指针更新发生在原子上下文中,头更新发生在用户上下文中,并且(不太可能)read() 错误需要重置所有头/尾状态。

注意:考虑到 hrtimer 回调的频率相对较低(5 毫秒周期),并且读取通常仅在响应 hrtimer 事件时发生,并且可能在下一次回调之前完成,因此当前的争用/性能不是一个重大问题。

注意:此锁不会在读取和将数据复制到用户空间时保持,因此在 hrtimer 回调中观察到的头的值不会表示对数据的任何部分消耗。

oa_buffer.head

尽管我们始终可以读回头指针寄存器,但我们更倾向于避免信任 HW 状态,只是为了避免某些硬件条件 * 以某种方式意外地跳动头指针并导致我们将错误的 OA 缓冲区数据转发给用户空间的任何风险。

oa_buffer.tail

用户空间可以读取的最后一个经过验证的尾部。

noa_wait

在 GPU 上执行等待的批处理缓冲区,用于重新编程 NOA 逻辑。

poll_oa_period

应该检查 OA 缓冲区中是否有可用数据的纳秒周期。

struct i915_perf_stream_ops

支持特定流类型的 OPs

定义:

struct i915_perf_stream_ops {
    void (*enable)(struct i915_perf_stream *stream);
    void (*disable)(struct i915_perf_stream *stream);
    void (*poll_wait)(struct i915_perf_stream *stream,struct file *file, poll_table *wait);
    int (*wait_unlocked)(struct i915_perf_stream *stream);
    int (*read)(struct i915_perf_stream *stream,char __user *buf,size_t count, size_t *offset);
    void (*destroy)(struct i915_perf_stream *stream);
};

成员

enable

启用 HW 样本的收集,以响应 I915_PERF_IOCTL_ENABLE,或者在没有 I915_PERF_FLAG_DISABLED 的情况下打开流时隐式调用。

disable

禁用 HW 样本的收集,以响应 I915_PERF_IOCTL_DISABLE,或者在销毁流之前隐式调用。

poll_wait

调用 poll_wait,传递一个等待队列,当有可为流读取的内容时,将唤醒该队列

wait_unlocked

对于处理阻塞读取,请等待,直到有可为流读取的内容。例如,等待将传递给 poll_wait() 的同一等待队列。

read

将缓冲的指标作为记录复制到用户空间 buf:用户空间、目标缓冲区 count:要复制的字节数,由用户空间请求 offset:在读取开始时为零,随着读取的进行而更新,它表示到目前为止已复制的字节数以及用于复制下一个记录的缓冲区偏移量。

将此流的尽可能多的缓冲 i915 perf 样本和记录复制到用户空间,使其适合给定的缓冲区。

仅写入完整的记录;如果没有足够的空间容纳完整的记录,则返回 -ENOSPC

返回任何导致短读取的错误情况,例如 -ENOSPC 或 -EFAULT,即使这些错误可能在返回到用户空间之前被压制。

destroy

清理任何特定于流的资源。

在调用此函数之前,流将始终被禁用。

int read_properties_unlocked(struct i915_perf *perf, u64 __user *uprops, u32 n_props, struct perf_open_properties *props)

验证 + 复制用户空间流打开属性

参数

struct i915_perf *perf

i915 perf 实例

u64 __user *uprops

用户空间给定的 u64 键值对数组

u32 n_props

uprops 中预期的键值对的数量

struct perf_open_properties *props

在验证属性时构建的流配置

描述

注意,此函数仅隔离验证属性,它不验证属性的组合是否有意义,或者是否已设置特定类型的流所需的所有属性。

请注意,目前属性没有任何排序要求,因此我们不应在此处验证或假设任何关于排序的信息。但这并不排除将来定义具有排序要求的新属性。

int i915_perf_open_ioctl_locked(struct i915_perf *perf, struct drm_i915_perf_open_param *param, struct perf_open_properties *props, struct drm_file *file)

DRM ioctl(),供用户空间打开流 FD

参数

struct i915_perf *perf

i915 perf 实例

struct drm_i915_perf_open_param *param

传递给 ‘DRM_I915_PERF_OPEN’ 的打开参数

struct perf_open_properties *props

单独验证的 u64 属性值对

struct drm_file *file

drm 文件

描述

有关接口详细信息,请参阅 i915_perf_ioctl_open()。

代表 i915_perf_open_ioctl(),使用 gt->perf.lock 互斥锁进行序列化,以与任何非文件操作驱动程序挂钩。实现进一步的流配置验证和流初始化。

如果用户空间对 OA 单元指标感兴趣,那么进一步的配置验证和流初始化细节将由 i915_oa_stream_init() 处理。 此处的代码仅应验证与所有流类型/后端相关的配置状态。

注意

此时,props 仅被单独验证,仍然需要验证属性的组合是否合理。

返回

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

void i915_perf_destroy_locked(struct i915_perf_stream *stream)

销毁 i915 perf 流

参数

struct i915_perf_stream *stream

i915 perf 流

描述

释放与给定 i915 perf stream 关联的所有资源,在此过程中禁用任何相关的数据捕获。

注意

已获取 gt->perf.lock 互斥锁,以与任何非文件操作驱动程序挂钩进行序列化。

ssize_t i915_perf_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)

处理 i915 perf 流 FD 的 read() FOP

参数

struct file *file

一个 i915 perf 流文件

char __user *buf

用户空间给定的目标缓冲区

size_t count

用户空间想要读取的字节数

loff_t *ppos

(输入/输出)文件查找位置(未使用)

描述

从用户空间处理流文件描述符上的 read() 的入口点。 大部分工作留给 i915_perf_read_locked() 和 i915_perf_stream_ops->read,但为了避免流实现(我们以后可能会有多个),我们在此处处理阻塞读取。

我们还可以一致地将尝试从禁用的流中读取视为 IO 错误,因此实现可以假设流在读取时已启用。

返回

复制的字节数,如果失败则返回负的错误代码。

long i915_perf_ioctl(struct file *file, unsigned int cmd, unsigned long arg)

支持 i915 perf 流 FD 的 ioctl() 使用

参数

struct file *file

一个 i915 perf 流文件

unsigned int cmd

ioctl 请求

unsigned long arg

ioctl 数据

描述

实现推迟到 i915_perf_ioctl_locked()

返回

成功返回零,失败返回负的错误代码。对于未知 ioctl 请求返回 -EINVAL。

void i915_perf_enable_locked(struct i915_perf_stream *stream)

处理 I915_PERF_IOCTL_ENABLE ioctl

参数

struct i915_perf_stream *stream

禁用的 i915 perf 流

描述

重新启用此流的相关数据捕获。

如果先前启用了流,则目前无意向用户空间提供有关保留先前缓冲数据的任何保证。

void i915_perf_disable_locked(struct i915_perf_stream *stream)

处理 I915_PERF_IOCTL_DISABLE ioctl

参数

struct i915_perf_stream *stream

已启用的 i915 perf 流

描述

禁用此流的相关数据捕获。

目的是禁用和重新启用流在理想情况下比销毁和重新打开具有相同配置的流更便宜,尽管对于禁用和重新启用流之间必须保留什么状态或缓冲数据没有正式保证。

注意

当流被禁用时,用户空间尝试从流中读取被认为是错误 (-EIO)。

__poll_t i915_perf_poll(struct file *file, poll_table *wait)

使用流的合适等待队列调用 poll_wait()

参数

struct file *file

一个 i915 perf 流文件

poll_table *wait

poll() 状态表

描述

对于处理 i915 perf 流上的用户空间轮询,这可确保使用将为新流数据唤醒的等待队列调用 poll_wait()。

注意

实现推迟到 i915_perf_poll_locked()

返回

任何无需休眠即可准备就绪的轮询事件

__poll_t i915_perf_poll_locked(struct i915_perf_stream *stream, struct file *file, poll_table *wait)

使用流的合适等待队列调用 poll_wait()

参数

struct i915_perf_stream *stream

i915 perf 流

struct file *file

一个 i915 perf 流文件

poll_table *wait

poll() 状态表

描述

对于处理 i915 perf 流上的用户空间轮询,此函数将调用 i915_perf_stream_ops->poll_wait,以使用将为新流数据唤醒的等待队列调用 poll_wait()。

返回

任何无需休眠即可准备就绪的轮询事件

i915 性能观察架构流

struct i915_oa_ops

OA 单元流的 Gen 特定实现

定义:

struct i915_oa_ops {
    bool (*is_valid_b_counter_reg)(struct i915_perf *perf, u32 addr);
    bool (*is_valid_mux_reg)(struct i915_perf *perf, u32 addr);
    bool (*is_valid_flex_reg)(struct i915_perf *perf, u32 addr);
    int (*enable_metric_set)(struct i915_perf_stream *stream, struct i915_active *active);
    void (*disable_metric_set)(struct i915_perf_stream *stream);
    void (*oa_enable)(struct i915_perf_stream *stream);
    void (*oa_disable)(struct i915_perf_stream *stream);
    int (*read)(struct i915_perf_stream *stream,char __user *buf,size_t count, size_t *offset);
    u32 (*oa_hw_tail_read)(struct i915_perf_stream *stream);
};

成员

is_valid_b_counter_reg

验证用于为特定平台编程布尔计数器的寄存器地址。

is_valid_mux_reg

验证用于为特定平台编程多路复用器的寄存器地址。

is_valid_flex_reg

验证用于为特定平台编程灵活 EU 过滤的寄存器地址。

enable_metric_set

选择并应用任何 MUX 配置来设置作为正在采样的计数器报告一部分的布尔和自定义 (B/C) 计数器。可能会根据需要应用系统约束,例如禁用 EU 时钟门控。

disable_metric_set

删除与使用 OA 单元相关的系统约束。

oa_enable

启用定期采样

oa_disable

禁用定期采样

read

将数据从圆形 OA 缓冲区复制到给定的用户空间缓冲区。

oa_hw_tail_read

读取 OA 尾指针寄存器

特别是,这使我们能够共享用于处理影响多代的 OA 单元尾指针竞争的所有棘手代码。

int i915_oa_stream_init(struct i915_perf_stream *stream, struct drm_i915_perf_open_param *param, struct perf_open_properties *props)

验证 OA 流的组合属性并进行初始化

参数

struct i915_perf_stream *stream

i915 perf 流

struct drm_i915_perf_open_param *param

传递给 DRM_I915_PERF_OPEN 的打开参数

struct perf_open_properties *props

配置流的属性状态(单独验证)

描述

尽管 read_properties_unlocked() 隔离地验证了属性,但它不能确保组合必然有意义。

此时,已确定用户空间需要 OA 指标流,但我们仍然需要进一步验证组合属性是否正常。

如果配置有意义,那么我们可以为循环 OA 缓冲区分配内存并应用请求的指标集配置。

返回

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

int i915_oa_read(struct i915_perf_stream *stream, char __user *buf, size_t count, size_t *offset)

只是调用 i915_oa_ops->read

参数

struct i915_perf_stream *stream

为 OA 指标打开的 i915-perf 流

char __user *buf

用户空间给定的目标缓冲区

size_t count

用户空间想要读取的字节数

size_t *offset

(输入/输出):写入 buf 的当前位置

描述

根据成功复制到用户空间缓冲区的字节数更新 offset

返回

成功时为零或负错误代码

void i915_oa_stream_enable(struct i915_perf_stream *stream)

处理 OA 流的 I915_PERF_IOCTL_ENABLE

参数

struct i915_perf_stream *stream

为 OA 指标打开的 i915 perf 流

描述

根据打开流时配置的周期[重新]启用硬件定期采样。这也会启动一个 hrtimer,它会定期检查循环 OA 缓冲区中的数据,以通知用户空间(例如,在 read() 或 poll() 期间)。

void i915_oa_stream_disable(struct i915_perf_stream *stream)

处理 OA 流的 I915_PERF_IOCTL_DISABLE

参数

struct i915_perf_stream *stream

为 OA 指标打开的 i915 perf 流

描述

停止 OA 单元将计数器报告定期写入循环 OA 缓冲区。这也会停止定期检查循环 OA 缓冲区中数据的 hrtimer,以通知用户空间。

int i915_oa_wait_unlocked(struct i915_perf_stream *stream)

处理阻塞 IO,直到 OA 数据可用

参数

struct i915_perf_stream *stream

为 OA 指标打开的 i915-perf 流

描述

当用户空间尝试从为 OA 指标打开的阻塞流 FD 进行 read() 时调用。它会等待直到 hrtimer 回调找到一个非空的 OA 缓冲区并唤醒我们。

注意

如果随后的任何读取处理在还没有真正为用户空间准备好数据时返回 -EAGAIN,则可以接受此返回一些误报。

返回

成功时为零或负错误代码

void i915_oa_poll_wait(struct i915_perf_stream *stream, struct file *file, poll_table *wait)

为 OA 流 poll() 调用 poll_wait()

参数

struct i915_perf_stream *stream

为 OA 指标打开的 i915-perf 流

struct file *file

一个 i915 perf 流文件

poll_table *wait

poll() 状态表

描述

为了处理用户空间对为 OA 指标打开的 i915 perf 流的轮询,这会启动一个带有等待队列的 poll_wait,当我们的 hrtimer 回调看到循环 OA 缓冲区中有数据可以读取时会唤醒该队列。

其他 i915 Perf 内部机制

本节仅包含所有当前记录的 i915 perf 内部机制,没有特定的顺序,但可能包括比更高级别部分中找到的更多小的实用程序或特定于平台的详细信息。

struct perf_open_properties

用于验证的属性,用于打开流

定义:

struct perf_open_properties {
    u32 sample_flags;
    u64 single_context:1;
    u64 hold_preemption:1;
    u64 ctx_handle;
    int metrics_set;
    int oa_format;
    bool oa_periodic;
    int oa_period_exponent;
    struct intel_engine_cs *engine;
    bool has_sseu;
    struct intel_sseu sseu;
    u64 poll_oa_period;
};

成员

sample_flags

DRM_I915_PERF_PROP_SAMPLE_* 属性被跟踪为标志

single_context

是否应监视单个或所有 GPU 上下文

hold_preemption

是否为筛选的上下文禁用了抢占

ctx_handle

用于 single_context 的 gem 上下文句柄

metrics_set

通过 sysfs 公告的 OA 单元指标集的 ID

oa_format

OA 单元硬件报告格式

oa_periodic

是否启用定期 OA 单元采样

oa_period_exponent

OA 单元采样周期由此导出

engine

OA 单元正在监视的引擎(通常为 rcs0)

has_sseu

用户空间是否指定了 sseu

sseu

从打开参数中用户空间指定的配置或默认值(请参阅 get_default_sseu_config())计算出的内部 SSEU 配置

poll_oa_period

CPU 检查 OA 数据可用性的周期(以纳秒为单位)

描述

read_properties_unlocked() 枚举并验证给定属性以打开指标流时,配置将在从零初始化的结构中构建。

bool oa_buffer_check_unlocked(struct i915_perf_stream *stream)

检查数据并更新尾指针状态

参数

struct i915_perf_stream *stream

i915 流实例

描述

这可以通过 fops(对于用户上下文中的阻塞读取)或轮询检查 hrtimer(原子上下文)调用,以检查 OA 缓冲区尾指针,并检查是否有数据可供用户空间读取。

此函数对于解决 OA 单元尾指针在 CPU 可见的数据方面存在竞争的问题至关重要。它负责从硬件读取尾指针,并使指针有时间“老化”,然后再使其可用于读取。(有关更多详细信息,请参阅上面的 OA_TAIL_MARGIN_NSEC 的描述。)

除了在有数据可用于 read() 时返回 true 之外,此函数还会更新 oa_buffer 对象中的尾部。

注意

在这里读取 OA 配置状态是安全的,假设仅在启用流时调用此状态,而不能修改全局 OA 配置。

返回

如果 OA 缓冲区包含数据,则为 true,否则为 false

int append_oa_status(struct i915_perf_stream *stream, char __user *buf, size_t count, size_t *offset, enum drm_i915_perf_record_type type)

将状态记录附加到用户空间 read() 缓冲区。

参数

struct i915_perf_stream *stream

为 OA 指标打开的 i915-perf 流

char __user *buf

用户空间给定的目标缓冲区

size_t count

用户空间想要读取的字节数

size_t *offset

(输入/输出):写入 buf 的当前位置

enum drm_i915_perf_record_type type

要向用户空间报告的状态类型

描述

将状态记录(例如 DRM_I915_PERF_RECORD_OA_REPORT_LOST)写入用户空间 read() 缓冲区。

仅在成功时更新 buf offset

返回

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

int append_oa_sample(struct i915_perf_stream *stream, char __user *buf, size_t count, size_t *offset, const u8 *report)

将单个 OA 报告复制到用户空间 read() 缓冲区中。

参数

struct i915_perf_stream *stream

为 OA 指标打开的 i915-perf 流

char __user *buf

用户空间给定的目标缓冲区

size_t count

用户空间想要读取的字节数

size_t *offset

(输入/输出):写入 buf 的当前位置

const u8 *report

一个单独的 OA 报告,(可选)包含在样本中

描述

样本的内容通过打开流时的 DRM_I915_PERF_PROP_SAMPLE_* 属性配置,并作为 stream->sample_flags 进行跟踪。此函数将单个样本的请求组件复制到给定的 read() buf 中。

仅在成功时更新 buf offset

返回

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

int gen8_append_oa_reports(struct i915_perf_stream *stream, char __user *buf, size_t count, size_t *offset)

将所有缓冲的 OA 报告复制到用户空间的 read() 缓冲区中。

参数

struct i915_perf_stream *stream

为 OA 指标打开的 i915-perf 流

char __user *buf

用户空间给定的目标缓冲区

size_t count

用户空间想要读取的字节数

size_t *offset

(输入/输出):写入 buf 的当前位置

描述

值得注意的是,即使一个或多个记录可能已成功复制,任何导致短读取的错误情况(-ENOSPC 或 -EFAULT)都将返回。在这种情况下,由调用者决定是否应在返回到用户空间之前取消错误。

注意

报告从头部消耗,并附加到尾部,因此尾部追逐头部?... 如果你认为这很疯狂并且是倒退的,你并不孤单,但这符合 Gen PRM 的命名约定。

返回

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

int gen8_oa_read(struct i915_perf_stream *stream, char __user *buf, size_t count, size_t *offset)

复制状态记录,然后复制缓冲的 OA 报告

参数

struct i915_perf_stream *stream

为 OA 指标打开的 i915-perf 流

char __user *buf

用户空间给定的目标缓冲区

size_t count

用户空间想要读取的字节数

size_t *offset

(输入/输出):写入 buf 的当前位置

描述

检查 OA 单元状态寄存器,并在必要时为用户空间附加相应的状态记录(例如,用于缓冲区满的情况),然后启动附加任何缓冲的 OA 报告。

根据成功复制到用户空间缓冲区的字节数更新 offset

注意:即使返回错误,某些数据也可能已成功复制到用户空间缓冲区,这反映在更新的 offset 中。

返回

成功时为零或负错误代码

int gen7_append_oa_reports(struct i915_perf_stream *stream, char __user *buf, size_t count, size_t *offset)

将所有缓冲的 OA 报告复制到用户空间的 read() 缓冲区中。

参数

struct i915_perf_stream *stream

为 OA 指标打开的 i915-perf 流

char __user *buf

用户空间给定的目标缓冲区

size_t count

用户空间想要读取的字节数

size_t *offset

(输入/输出):写入 buf 的当前位置

描述

值得注意的是,即使一个或多个记录可能已成功复制,任何导致短读取的错误情况(-ENOSPC 或 -EFAULT)都将返回。在这种情况下,由调用者决定是否应在返回到用户空间之前取消错误。

注意

报告从头部消耗,并附加到尾部,因此尾部追逐头部?... 如果你认为这很疯狂并且是倒退的,你并不孤单,但这符合 Gen PRM 的命名约定。

返回

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

int gen7_oa_read(struct i915_perf_stream *stream, char __user *buf, size_t count, size_t *offset)

复制状态记录,然后复制缓冲的 OA 报告

参数

struct i915_perf_stream *stream

为 OA 指标打开的 i915-perf 流

char __user *buf

用户空间给定的目标缓冲区

size_t count

用户空间想要读取的字节数

size_t *offset

(输入/输出):写入 buf 的当前位置

描述

检查 Gen 7 特定的 OA 单元状态寄存器,并在必要时为用户空间附加相应的状态记录(例如,用于缓冲区满的情况),然后启动附加任何缓冲的 OA 报告。

根据成功复制到用户空间缓冲区的字节数更新 offset

返回

成功时为零或负错误代码

int oa_get_render_ctx_id(struct i915_perf_stream *stream)

确定并保持 ctx 硬件 id

参数

struct i915_perf_stream *stream

为 OA 指标打开的 i915-perf 流

描述

确定渲染上下文硬件 ID,并确保其在流的生命周期内保持固定。这确保我们不必担心动态更新 OACONTROL 中的上下文 ID。

返回

成功时为零或负错误代码

void oa_put_render_ctx_id(struct i915_perf_stream *stream)

oa_get_render_ctx_id 的对应部分释放保持

参数

struct i915_perf_stream *stream

为 OA 指标打开的 i915-perf 流

描述

如果需要执行任何操作以确保上下文硬件 ID 在流的生命周期内保持有效,则可以在此处撤消该操作。

long i915_perf_ioctl_locked(struct i915_perf_stream *stream, unsigned int cmd, unsigned long arg)

支持 i915 perf 流 FD 的 ioctl() 使用

参数

struct i915_perf_stream *stream

i915 perf 流

unsigned int cmd

ioctl 请求

unsigned long arg

ioctl 数据

返回

成功返回零,失败返回负的错误代码。对于未知 ioctl 请求返回 -EINVAL。

int i915_perf_ioctl_version(struct drm_i915_private *i915)

i915-perf 子系统的版本

参数

struct drm_i915_private *i915

i915 设备

描述

用户空间使用此版本号来检测可用功能。

样式

除了(并且在某些情况下,偏离)内核编码风格之外,drm/i915 驱动程序代码库还具有一些样式规则。

寄存器宏定义样式

i915_reg.h 的样式指南。

对于新的宏,请遵循此处描述的样式,并在更改现有宏时遵循此样式。不要 批量更改现有定义,只是为了更新样式。

文件布局

将辅助宏放在顶部附近。例如,_PIPE() 及其朋友。

使用下划线“_”作为通常不应在此文件外部使用的宏的前缀。例如,_PIPE() 及其朋友,仅为函数式宏使用的寄存器的单个实例。

避免在此文件外部使用下划线前缀的宏。存在例外情况,但请尽量减少使用。

寄存器定义有两种基本类型:单个寄存器和寄存器组。寄存器组是具有两个或多个实例的寄存器,例如每个管道、端口、转码器等一个实例。寄存器组应使用类似函数的宏定义。

对于单个寄存器,首先定义寄存器偏移量,然后定义寄存器内容。

对于寄存器组,首先定义寄存器实例偏移量,并以下划线为前缀,然后定义一个类似函数的宏,该宏根据参数选择正确的实例,然后定义寄存器内容。

从最高有效位到最低有效位定义寄存器内容(即位和位字段宏)。在 #define 和宏名称之间使用两个额外的空格缩进寄存器内容宏。

使用 REG_GENMASK(h, l) 定义位字段。使用 REG_FIELD_PREP(mask, value) 定义位字段内容。这将定义已移入到位的值,因此它们可以直接进行 OR 运算。为方便起见,可以使用类似函数的宏定义位字段,但请注意,可能需要使用这些宏来读取和写入寄存器内容。

使用 REG_BIT(N) 定义位。不要 在名称中添加 _BIT 后缀。

将寄存器及其内容组合在一起,不留空行,用一个空行与其他寄存器及其内容分开。

使用 TAB 从宏名称缩进宏值。垂直对齐值。根据需要使用宏值中的大括号,以避免宏替换后出现意外的优先级。根据内核编码风格,在宏值中使用空格。在十六进制值中使用小写字母。

命名

尝试根据规范命名寄存器。如果规范中的寄存器名称在平台之间发生更改,请坚持使用原始名称。

尝试重用现有的寄存器宏定义。仅为新的寄存器偏移量添加新的宏,或者当寄存器内容发生足够大的变化以至于需要完全重新定义时才添加。

当新平台的寄存器宏发生更改时,请使用平台首字母缩写或世代作为新宏的前缀。例如,SKL_GEN8_。前缀表示使用寄存器的起始平台/世代。

当为新平台更改或添加位(字段)宏,同时保留现有寄存器宏时,请在名称中添加平台首字母缩写或世代后缀。例如,_SKL_GEN8

示例

(请注意,示例中的值使用空格而不是 TAB 缩进,以避免在生成的文档中出现未对齐。在定义中使用 TAB。)

#define _FOO_A                      0xf000
#define _FOO_B                      0xf001
#define FOO(pipe)                   _MMIO_PIPE(pipe, _FOO_A, _FOO_B)
#define   FOO_ENABLE                REG_BIT(31)
#define   FOO_MODE_MASK             REG_GENMASK(19, 16)
#define   FOO_MODE_BAR              REG_FIELD_PREP(FOO_MODE_MASK, 0)
#define   FOO_MODE_BAZ              REG_FIELD_PREP(FOO_MODE_MASK, 1)
#define   FOO_MODE_QUX_SNB          REG_FIELD_PREP(FOO_MODE_MASK, 2)

#define BAR                         _MMIO(0xb000)
#define GEN8_BAR                    _MMIO(0xb888)

i915 DRM 客户端使用情况统计实现

drm/i915 驱动程序实现了 DRM 客户端使用情况统计 中记录的 DRM 客户端使用情况统计规范。

显示已实现的键值对和当前可能格式选项的整体的输出示例

pos:    0
flags:  0100002
mnt_id: 21
drm-driver: i915
drm-pdev:   0000:00:02.0
drm-client-id:      7
drm-engine-render:  9288864723 ns
drm-engine-copy:    2035071108 ns
drm-engine-video:   0 ns
drm-engine-capacity-video:   2
drm-engine-video-enhance:   0 ns

可能的 drm-engine- 键名称为:rendercopyvideovideo-enhance