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 管理),并确保已启动电源。在唤醒锁断言检查期间不考虑原始引用。
通过此函数获得的任何运行时 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 设备实例
描述
此函数用于在运行时启用中断。
Intel GVT-g 客户机支持 (vGPU)¶
Intel GVT-g 是一种图形虚拟化技术,它以分时方式在多个虚拟机之间共享 GPU。每个虚拟机都呈现一个虚拟 GPU (vGPU),它具有与底层物理 GPU (pGPU) 等效的功能,因此 i915 驱动程序可以在虚拟机中无缝运行。此文件提供了在虚拟机中运行时 vGPU 特定的优化,以降低 vGPU 仿真的复杂性并提高整体性能。
此处介绍的主要功能是所谓的“地址空间膨胀”技术。Intel GVT-g 在多个 VM 之间划分全局图形内存,因此每个 VM 都可以直接访问一部分内存,而无需管理程序的干预,例如填充纹理或排队命令。然而,通过分区,未修改的 i915 驱动程序会假定一个从地址 ZERO 开始的较小图形内存,然后要求 vGPU 仿真模块在“客户机视图”和“主机视图”之间转换图形地址,对于所有包含图形内存地址的寄存器和命令操作码。为了降低复杂性,Intel 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 分配器保留)。从主机的角度来看,图形地址空间由不同 VM 中的多个 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 ------> +-----------+ +-----------+
返回
成功时为零,如果配置无效或膨胀失败则为非零
Intel GVT-g 主机支持 (vGPU 设备模型)¶
Intel 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 private *
描述
此函数在 i915 驱动程序卸载阶段调用,以关闭 GVT 组件并释放相关资源。
-
void intel_gvt_resume(struct drm_i915_private *dev_priv)¶
GVT 恢复例程包装器
参数
struct drm_i915_private *dev_priv
drm i915 private *
描述
此函数在 i915 驱动程序恢复阶段调用,以恢复 GVT 所需的 HW 状态,以便 vGPU 在恢复后可以继续运行。
解决方法¶
硬件解决方法是记录在驱动程序中执行的寄存器编程,这些编程不在平台的正常编程序列之外。根据应用的方式/时间,有一些基本类别的解决方法
上下文解决方法:触及保存/从 HW 上下文映像恢复的寄存器的解决方法。该列表在初始化设备时发出一次(通过加载寄存器立即命令),并保存在默认上下文中。然后,每个上下文创建都使用该默认上下文来获得“原始黄金上下文”,即已经包含所有寄存器所需更改的上下文映像。
上下文解决方法应在分别针对目标平台的 *_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 解决方法:每当这些寄存器恢复为默认值时(在 GPU 重置、挂起/恢复 [1] 等时),都会应用这些 WA 的列表。
GT 解决方法应在分别针对目标平台的 *_gt_workarounds_init() 变体中实现。
寄存器白名单:一些解决方法需要在用户空间中实现,但需要触及特权寄存器。内核中的白名单指示硬件允许访问发生。从内核方面来看,这只是 MMIO 解决方法的一种特殊情况(因为我们将这些要列入白名单的寄存器列表写入一些特殊的 HW 寄存器)。
寄存器列入白名单应在分别针对目标平台的 *_whitelist_build() 变体中完成。
解决方法批处理缓冲区:硬件在每次 HW 上下文恢复时自动执行的缓冲区。这些缓冲区在默认上下文中创建和编程,因此硬件在切换上下文时始终会通过这些编程序列。对解决方法批处理缓冲区的支持已启用这些硬件机制
INDIRECT_CTX:在默认上下文中提供一个批处理缓冲区和一个偏移量,指示硬件在该偏移量在上下文恢复中达到时跳转到该位置。驱动程序中的解决方法批处理缓冲区当前对所有平台都使用此机制。
BB_PER_CTX_PTR:在默认上下文中提供一个批处理缓冲区,指示硬件在上下文恢复序列中恢复引擎寄存器后继续执行的缓冲区。当前在驱动程序中未使用此功能。
其他:有些 WA 由于其性质,无法从中心位置应用。这些 WA 根据需要在代码的其他部分中进行调整。与显示 IP 相关的解决方法是主要示例。
显示硬件处理¶
本节涵盖与显示硬件相关的所有内容,包括模式设置基础结构、平面、精灵和光标处理以及显示、输出探测和相关主题。
模式设置基础结构¶
到目前为止,i915 驱动程序是唯一不使用通用 DRM 帮助程序代码来实现模式设置序列的 DRM 驱动程序。因此,它具有自己量身定制的基础结构来执行显示配置更改。
前缓冲跟踪¶
许多功能要求我们跟踪当前活动前缓冲的更改,特别是针对前缓冲的渲染。
为了能够做到这一点,我们通过 intel_frontbuffer_track()
使用位掩码来跟踪所有可能的前缓冲槽中的前缓冲。然后,当使前缓冲的内容无效时、当再次停止前缓冲渲染以清除所有更改时以及当使用翻转交换前缓冲时,将调用此文件中的函数。对前缓冲更改感兴趣的子系统(例如 PSR、FBC、DRRS)应直接将其回调放入相关位置,并筛选出它们感兴趣的前缓冲槽。
在高层上,有两种类型的节能功能。第一种像特殊的缓存(FBC 和 PSR)一样工作,并且对它们应该何时停止缓存以及何时重新启动缓存感兴趣。这可以通过将回调放入无效和刷新函数来实现:在无效时必须停止缓存,而在刷新时可以重新启动缓存。并且可能需要知道前缓冲何时更改(例如,当 hw 不自行启动无效和刷新时),这可以通过将回调放入翻转函数来实现。
另一种类型的显示节能功能只关心繁忙程度(例如 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 intel_display *display, unsigned int frontbuffer_bits, enum fb_op_origin origin)¶
刷新前缓冲
参数
struct intel_display *display
显示设备
unsigned int frontbuffer_bits
前缓冲平面跟踪位
enum fb_op_origin origin
哪个操作导致了刷新
描述
每次在给定平面上完成渲染时都会调用此函数,并且可以再次启动前缓冲缓存。如果刷新被一些未完成的异步渲染阻止,刷新将被延迟。
可以在不持有任何锁的情况下调用。
-
void intel_frontbuffer_flip_prepare(struct intel_display *display, unsigned frontbuffer_bits)¶
准备异步前缓冲翻转
参数
struct intel_display *display
显示设备
unsigned frontbuffer_bits
前缓冲平面跟踪位
描述
此函数在计划在 obj 上进行翻转后调用。实际的前缓冲刷新将被延迟,直到使用 intel_frontbuffer_flip_complete 发出完成信号。如果在此期间发生无效,则将取消此刷新。
可以在不持有任何锁的情况下调用。
-
void intel_frontbuffer_flip_complete(struct intel_display *display, unsigned frontbuffer_bits)¶
完成异步前缓冲翻转
参数
struct intel_display *display
显示设备
unsigned frontbuffer_bits
前缓冲平面跟踪位
描述
此函数在翻转已闩锁并将于下一个 vblank 完成后调用。如果尚未取消刷新,它将执行刷新。
可以在不持有任何锁的情况下调用。
-
void intel_frontbuffer_flip(struct intel_display *display, unsigned frontbuffer_bits)¶
同步前缓冲翻转
参数
struct intel_display *display
显示设备
unsigned frontbuffer_bits
前缓冲平面跟踪位
描述
此函数在计划在 obj 上进行翻转后调用。这是用于将在下一个 vblank 上发生的同步平面更新,并且不会因挂起的 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_bits。 old 和 new 都可以为 NULL。
显示 FIFO 欠载报告¶
i915 驱动程序使用硬件提供的中断信号检查显示 fifo 欠载。默认情况下启用此功能,并且对于调试显示问题(尤其是水印设置)非常有用。
如果检测到欠载,则会将其记录到 dmesg 中。为了避免日志泛滥和占用 cpu,在给定管道上的下一次模式设置之前,欠载中断会在第一次发生后被禁用。
请注意,由于没有中断(尽管信令位位于 PIPESTAT 管道中断寄存器中),因此在 gmch 平台上进行欠载检测会更加麻烦。同样,在某些其他平台上,欠载中断是共享的,这意味着如果我们检测到欠载,则需要禁用所有管道上的欠载报告。
该代码还支持 PCH 转码器上的欠载检测。
-
bool intel_set_cpu_fifo_underrun_reporting(struct intel_display *display, enum pipe pipe, bool enable)¶
设置 cpu fifo 欠载报告状态
参数
struct intel_display *display
显示设备实例
enum pipe pipe
要设置状态的 (CPU) 管道
bool enable
是否应报告欠载
描述
此函数设置 pipe 的 fifo 欠载状态。它在模式设置代码中使用,以避免误报,因为在许多平台上,禁用或启用管道时会预期出现欠载。
请注意,在某些平台上,由于共享中断,禁用一个管道的欠载报告会禁用所有管道的欠载报告。但是,实际报告仍然是按管道进行的。
返回欠载报告的先前状态。
-
bool intel_set_pch_fifo_underrun_reporting(struct intel_display *display, enum pipe pch_transcoder, bool enable)¶
设置 PCH fifo 欠载报告状态
参数
struct intel_display *display
显示设备实例
enum pipe pch_transcoder
PCH 转码器(在 IVB 和更早版本上与管道相同)
bool enable
是否应报告欠载
描述
此函数使我们能够为特定的 PCH 转码器禁用或启用 PCH fifo 欠载。请注意,在某些 PCH(例如 CPT/PPT)上,由于所有转码器只有一个中断掩码/启用位,因此禁用一个转码器的 FIFO 欠载报告也可能禁用其他转码器的所有其他 PCH 错误中断。
返回欠载报告的先前状态。
-
void intel_cpu_fifo_underrun_irq_handler(struct intel_display *display, enum pipe pipe)¶
处理 CPU fifo 欠载中断
参数
struct intel_display *display
显示设备实例
enum pipe pipe
要设置状态的 (CPU) 管道
描述
此函数处理 CPU fifo 欠载中断,如果在 dmesg 中启用了欠载报告,则生成欠载警告,然后禁用欠载中断以避免 irq 风暴。
-
void intel_pch_fifo_underrun_irq_handler(struct intel_display *display, enum pipe pch_transcoder)¶
处理 PCH fifo 欠载中断
参数
struct intel_display *display
显示设备实例
enum pipe pch_transcoder
PCH 转码器(在 IVB 和更早版本上与管道相同)
描述
此函数处理 PCH fifo 欠载中断,如果在 dmesg 中启用了欠载报告,则生成欠载警告,然后禁用欠载中断以避免 irq 风暴。
-
void intel_check_cpu_fifo_underruns(struct intel_display *display)¶
立即检查 CPU fifo 欠载
参数
struct intel_display *display
显示设备实例
描述
立即检查 CPU fifo 欠载。在 IVB/HSW 上很有用,因为共享错误中断可能已被禁用,因此 CPU fifo 欠载不一定会引发中断,并且在 gmch 平台上,欠载永远不会引发中断。
-
void intel_check_pch_fifo_underruns(struct intel_display *display)¶
立即检查 PCH fifo 欠载
参数
struct intel_display *display
显示设备实例
描述
立即检查 PCH fifo 欠载。在 CPT/PPT 上很有用,因为共享错误中断可能已被禁用,因此 PCH fifo 欠载不一定会引发中断。
平面配置¶
本节介绍平面配置以及与主平面、精灵、光标和覆盖的合成。这包括对所有此状态进行原子 vsync 更新的基础结构,以及紧密耦合的主题,如水印设置和计算、帧缓冲区压缩和面板自刷新。
原子平面帮助程序¶
原子平面帮助程序函数使用此处的函数来实现旧式平面更新(即,drm_plane->update_plane() 和 drm_plane->disable_plane())。这允许平面更新使用原子状态基础结构,并将平面更新作为单独的 prepare/check/commit/cleanup 步骤执行。
参数
struct drm_plane *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() 函数启用翻转完成中断。
只要写入表面地址寄存器,就会生成翻转完成中断,并且所请求的事件会通过中断处理程序本身发送到用户空间。在翻转完成事件期间发送的时间戳和序列对应于上次 vblank,并且与发送翻转完成事件的实际时间无关。
输出探测¶
本节介绍输出探测和相关基础结构,如热插拔中断风暴检测和缓解代码。请注意,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。
热插拔中断风暴检测和缓解代码跟踪每个热插拔引脚在一段时间内的中断次数,如果中断次数超过某个阈值,则会禁用中断一段时间,然后再重新启用。目的是缓解由损坏的硬件触发大量中断并使系统停止的问题。
当前的实现预计在连接 display port sink 时不会看到热插拔中断风暴,因此对于其 DP 回调由 i915_digport_work_func 处理的平台,不执行 hpd 的重新启用(从一开始就不希望禁用它 ;) )这特定于此例程处理的 DP sink,并且在同一端口上启用的任何其他显示器(如 HDMI 或 DVI)都将具有正确的逻辑,因为它将使用 i915_hotplug_work_func,其中已处理此逻辑。
参数
enum port port
要获取关联引脚的 hpd 端口
描述
它仅对数字端口编码器有效并由其使用。
返回与 port 关联的引脚。
-
bool intel_hpd_irq_storm_detect(struct intel_display *display, enum hpd_pin pin, bool long_hpd)¶
收集统计信息并检测引脚上的 HPD IRQ 风暴
参数
struct intel_display *display
显示设备
enum hpd_pin pin
要在其上收集统计信息的引脚
bool long_hpd
HPD IRQ 是长脉冲还是短脉冲
描述
从指定的 pin 收集有关 HPD IRQ 的统计信息,并检测 IRQ 风暴。仅更改特定于引脚的统计信息和状态,调用方负责采取进一步操作。
在 HPD_STORM_DETECT_PERIOD 内允许的 IRQ 数量存储在 display->hotplug.hpd_storm_threshold 中,默认为 HPD_STORM_DEFAULT_THRESHOLD。长 IRQ 计为 +10 到此阈值,短 IRQ 计为 +1。如果超过此阈值,则将其视为 IRQ 风暴,并将 IRQ 状态设置为 HPD_MARK_DISABLED。
默认情况下,大多数系统只会将长 IRQ 计入 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 中断事件,模拟 sink 生成的短脉冲,并安排 dig 端口工作来处理它。
-
void intel_hpd_irq_handler(struct intel_display *display, u32 pin_mask, u32 long_mask)¶
主要的熱插拔IRQ处理程序。
参数
struct intel_display *display
显示设备
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 intel_display *display)¶
初始化并启用HPD支持。
参数
struct intel_display *display
显示设备实例
描述
此函数启用热插拔支持。它要求中断已通过 intel_irq_init_hw() 启用。从那时起,热插拔和轮询请求可以与其他代码同时运行,因此必须遵守锁定规则。
这是与中断启用分开的一步,以简化驱动程序加载和恢复代码中的锁定规则。
-
void intel_hpd_poll_enable(struct intel_display *display)¶
为带有HPD的连接器启用轮询。
参数
struct intel_display *display
显示设备实例
描述
此函数为所有支持 HPD 的连接器启用轮询。在某些情况下,HPD 可能无法正常工作。在大多数 Intel GPU 上,当我们进入运行时挂起时,会发生这种情况。在 Valleyview 和 Cherryview 系统上,当我们关闭所有电源域时,也会发生这种情况。
由于此函数可以在我们已经持有 dev->mode_config.mutex 的上下文中调用,因此我们在单独的 worker 中进行实际的热插拔启用。
-
void intel_hpd_poll_disable(struct intel_display *display)¶
禁用带有HPD的连接器的轮询。
参数
struct intel_display *display
显示设备实例
描述
此函数为所有支持 HPD 的连接器禁用轮询。在某些情况下,HPD 可能无法正常工作。在大多数 Intel GPU 上,当我们进入运行时挂起时,会发生这种情况。在 Valleyview 和 Cherryview 系统上,当我们关闭所有电源域时,也会发生这种情况。
由于此函数可以在我们已经持有 dev->mode_config.mutex 的上下文中调用,因此我们在单独的 worker 中进行实际的热插拔启用。
也在驱动程序初始化期间使用,以根据所有连接器适当初始化 connector->polled。
-
void intel_hpd_block(struct intel_encoder *encoder)¶
阻止HPD引脚上的HPD IRQ处理。
参数
struct intel_encoder *encoder
要阻止其HPD处理的编码器。
描述
阻止 encoder 的 HPD 引脚上的 HPD IRQ 处理。
返回时
保证被阻止的编码器的 HPD 脉冲处理程序(通过 intel_digital_port::hpd_pulse())未运行。
调用此函数时挂起的 HPD IRQ 的热插拔事件处理(通过 intel_encoder::hotplug())可能仍在运行。
仍然允许在编码器的连接器上进行检测(通过 drm_connector_helper_funcs::detect_ctx(), drm_connector_funcs::detect()),例如作为用户空间连接器探测或 DRM 核心连接器轮询的一部分。
该调用后必须调用 intel_hpd_unblock()
或 intel_hpd_clear_and_unblock()
。
请注意,还将阻止使用与 encoder 相同 HPD 引脚的另一个编码器的 HPD IRQ 处理。
-
void intel_hpd_unblock(struct intel_encoder *encoder)¶
取消阻止 HPD 引脚上的 HPD IRQ 处理。
参数
struct intel_encoder *encoder
要取消阻止其 HPD 处理的编码器。
描述
取消阻止 encoder 的 HPD 引脚上的 HPD IRQ 处理,该处理以前被 intel_hpd_block()
阻止。在 HPD 引脚被阻止时,在该 HPD 引脚上引发的任何 HPD IRQ 都将为 encoder 和任何其他共享相同 HPD 引脚的编码器处理。
-
void intel_hpd_clear_and_unblock(struct intel_encoder *encoder)¶
取消阻止 HPD 引脚上的新 HPD IRQ 处理。
参数
struct intel_encoder *encoder
要取消阻止其 HPD 处理的编码器。
描述
取消阻止 encoder 的 HPD 引脚上的 HPD IRQ 处理,该处理以前被 intel_hpd_block()
阻止。在 HPD 引脚被阻止时,在该 HPD 引脚上引发的任何 HPD IRQ 都将被清除,仅处理新的 IRQ。
高清晰度音频¶
图形和音频驱动程序共同支持通过 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)¶
为HD音频启用音频编解码器。
参数
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)¶
为HD音频禁用音频编解码器。
参数
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 intel_display *display)¶
设置芯片特定的音频钩子。
参数
struct intel_display *display
显示设备
-
void intel_audio_component_init(struct intel_display *display)¶
初始化并注册音频组件。
参数
struct intel_display *display
显示设备
描述
这将在组件框架中注册一个子组件,当后者注册时,该子组件将动态绑定到 snd_hda_intel 驱动程序的相应主组件。在绑定期间,子组件初始化它从主组件接收的 struct i915_audio_component
的实例。然后,主组件可以开始使用此结构定义的接口。每一方都可以在任何时候通过注销自己的组件来断开绑定,之后将调用每一方的组件取消绑定回调。
我们忽略注册期间的任何错误,并继续使用减少的功能(即没有HDMI音频)。
-
void intel_audio_component_cleanup(struct intel_display *display)¶
注销音频组件。
参数
struct intel_display *display
显示设备
描述
注销音频组件,断开与相应的 snd_hda_intel 驱动程序的主组件的任何现有绑定。
-
void intel_audio_init(struct intel_display *display)¶
使用组件框架或使用lpe音频桥初始化音频驱动程序。
参数
struct intel_display *display
显示设备
-
void intel_audio_deinit(struct intel_display *display)¶
取消初始化音频驱动程序。
参数
struct intel_display *display
显示设备
-
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 intel_display *display)¶
转发 LPE 音频 irq。
参数
struct intel_display *display
显示设备
描述
LPE 音频 irq 被转发到 LPE 音频驱动程序注册的 irq 处理程序。
-
int intel_lpe_audio_init(struct intel_display *display)¶
检测并设置 HDMI LPE 音频驱动程序和 i915 之间的桥梁。
参数
struct intel_display *display
显示设备
返回
成功时为0。如果检测或 llocation/初始化失败,则为非零。
-
void intel_lpe_audio_teardown(struct intel_display *display)¶
销毁 HDMI LPE 音频驱动程序和 i915 之间的桥梁。
参数
struct intel_display *display
显示设备
描述
释放 LPE 音频 <-> i915 桥梁的所有资源。
-
void intel_lpe_audio_notify(struct intel_display *display, enum transcoder cpu_transcoder, enum port port, const void *eld, int ls_clock, bool dp_output)¶
通知 lpe 音频事件音频驱动程序和 i915。
参数
struct intel_display *display
显示设备
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 显示控制器支持在显示面板上进行面板自刷新 (Panel Self-Refresh, PSR),这些面板根据 eDP1.3 中的 PSR 规范实现了远程帧缓冲区 (Remote Frame Buffer, RFB)。PSR 功能允许系统空闲但显示器开启时,显示器进入较低的待机状态,因为它完全消除了对 DDR 内存的显示刷新请求,只要该显示器的帧缓冲区保持不变。
面板自刷新必须得到硬件(源)和面板(宿)的支持。
PSR 通过将帧缓冲区缓存在面板 RFB 中来节省功耗,这允许我们关闭链路和内存控制器的电源。对于 DSI 面板,相同的概念被称为“手动模式”。
该实现使用基于硬件的 PSR 支持,该支持自动进入/退出自刷新模式。硬件负责发送所需的 DP aux 消息,甚至可以重新训练链路(但该部分尚未启用)。硬件还会跟踪任何前缓冲区更改,以便知道何时再次退出自刷新模式。不幸的是,这部分工作效果不佳,因此 i915 PSR 支持使用软件前缓冲区跟踪来确保它不会错过屏幕更新。对于此集成,intel_psr_invalidate()
和 intel_psr_flush()
由前缓冲区跟踪代码调用。请注意,由于锁定问题,自刷新重新启用代码是从一个工作队列完成的,在关闭管道时必须正确同步/取消该队列。”
DC3CO (DC3 时钟关闭)
在 PSR2 的基础上,GEN12 增加了一个中间省电状态,该状态在 PSR2 空闲状态期间自动关闭时钟。DC3co 进入/退出开销与 PSR2 深度睡眠进入/退出开销相比更小,这使得 HW 即使在定期翻页时(例如 30fps 视频播放场景)也能进入低功耗状态。
每次发生翻页时,PSR2 都会退出深度睡眠状态(如果它处于深度睡眠状态),因此启用 DC3CO,并且计划在 6 帧后运行 tgl_dc3co_disable_work。如果没有其他翻页发生并且执行了上面的函数,则禁用 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 状态
描述
需要在禁用管道之前调用此函数。
参数
struct intel_dp *intel_dp
Intel DP
描述
需要在启用 psr 后调用此函数。
参数
struct intel_dp *intel_dp
Intel DP
描述
需要在暂停 psr 后调用此函数。
-
bool intel_psr_needs_vblank_notification(const struct intel_crtc_state *crtc_state)¶
检查 PSR 是否需要垂直消隐启用/禁用通知。
参数
const struct intel_crtc_state *crtc_state
CRTC 状态
描述
我们需要阻止面板重放中的 DC6 进入,因为在面板重放中启用 VBI 并不能阻止它。面板重放在 DC 进入时关闭主链路。这意味着不会触发垂直消隐中断,如果用户空间正在轮询垂直消隐事件,则会出现问题。此外,Wa_16025596647 需要有关何时启用/禁用垂直消隐的信息。
-
void intel_psr_trigger_frame_change_event(struct intel_dsb *dsb, struct intel_atomic_state *state, struct intel_crtc *crtc)¶
触发“帧更改”事件
参数
struct intel_dsb *dsb
DSB 上下文
struct intel_atomic_state *state
原子状态
struct intel_crtc *crtc
CRTC
描述
生成 PSR“帧更改”事件。
-
int intel_psr_min_vblank_delay(const struct intel_crtc_state *crtc_state)¶
PSR 所需的最小垂直消隐延迟
参数
const struct intel_crtc_state *crtc_state
crtc 状态
描述
返回 PSR 所需的最小垂直消隐延迟。
-
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 中跟踪。
参数
struct intel_dp *intel_dp
Intel DP
描述
此函数在初始化连接器后调用。(连接器的初始化处理连接器功能的处理)并且它为每个 DP 编码器初始化基本的 PSR 内容。
参数
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 锁。
-
void intel_psr_notify_dc5_dc6(struct intel_display *display)¶
通知 PSR 有关启用/禁用 dc5/dc6
参数
struct intel_display *display
intel 原子状态
描述
这是针对空闲 PSR HW 错误(Wa_16025596647)上的欠载运行,以计划用于应用/删除该变通方法的 psr_dc5_dc6_wa_work。
-
void intel_psr_dc5_dc6_wa_init(struct intel_display *display)¶
初始化空闲 PSR HW 错误 wa 上的欠载运行的工作
参数
struct intel_display *display
intel 原子状态
描述
这是针对空闲 PSR HW 错误(Wa_16025596647)上的欠载运行,以初始化用于应用变通方法的 psr_dc5_dc6_wa_work。
-
void intel_psr_notify_pipe_change(struct intel_atomic_state *state, struct intel_crtc *crtc, bool enable)¶
通知 PSR 有关启用/禁用管道的信息
参数
struct intel_atomic_state *state
intel 原子状态
struct intel_crtc *crtc
intel crtc
bool enable
启用/禁用
描述
这是针对空闲 PSR HW 错误(Wa_16025596647)上的欠载运行,以在启用/禁用管道时应用删除变通方法。
-
void intel_psr_notify_vblank_enable_disable(struct intel_display *display, bool enable)¶
通知 PSR 有关启用/禁用垂直消隐的信息
参数
struct intel_display *display
intel 显示结构
bool enable
启用/禁用
描述
这是针对空闲 PSR HW 错误(Wa_16025596647)上的欠载运行,以在启用/禁用垂直消隐时应用删除变通方法。
帧缓冲区压缩 (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) 是一种节能功能,它能够基于使用场景动态地在低刷新率和高刷新率之间切换。此功能适用于内部面板。
面板是否支持 DRRS 由面板 EDID 指示,它将列出一种分辨率的多个刷新率。
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 intel_display *display, unsigned int frontbuffer_bits)¶
禁用空闲 DRRS
参数
struct intel_display *display
显示设备
unsigned int frontbuffer_bits
前缓冲平面跟踪位
描述
每次在给定平面上开始渲染时,都会调用此函数。 因此,需要将 DRRS 升级,即(LOW_RR -> HIGH_RR)。
与 DRRS 相关的脏前置缓冲区在 busy_frontbuffer_bits 中被追踪。
-
void intel_drrs_flush(struct intel_display *display, unsigned int frontbuffer_bits)¶
重启空闲 DRRS
参数
struct intel_display *display
显示设备
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 具有略微特殊的显示 PHY,用于驱动 DP/HDMI 端口。 DPIO 是赋予此类显示 PHY 的名称。 这些 PHY 不遵循使用直接 MMIO 寄存器的标准编程模型,而是必须通过 IOSF 侧带访问其寄存器。 VLV 有一个这样的 PHY 用于驱动端口 B 和 C,CHV 增加了另一个 PHY 用于驱动端口 D。 每个 PHY 响应特定的 IOSF-SB 端口。
每个显示 PHY 由一个或两个通道组成。 每个通道包含一个公共通道部分,该部分包含 PLL 和其他公共逻辑。 CH0 公共通道还包含用于公共寄存器接口 (CRI) 的 IOSF-SB 逻辑,即 DPIO 寄存器。 访问任何 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,DD1 是数字端口 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(显示微控制器),以便在显示引擎进入低功耗状态并返回正常状态时保存和恢复显示引擎的状态。
参数
struct intel_display *display
显示实例
enum pipe pipe
用于阻止的管线寄存器
bool block
阻止/解除阻止
描述
此接口的目标是 Wa_16025596647 用法。 即,在 PIPEDMC_BLOCK_PKGC_SW 寄存器中设置/清除 PIPEDMC_BLOCK_PKGC_SW_BLOCK_PKGC_ALWAYS 位。
-
void intel_dmc_start_pkgc_exit_at_start_of_undelayed_vblank(struct intel_display *display, enum pipe pipe, bool enable)¶
PKG C 状态退出开始
参数
struct intel_display *display
显示实例
enum pipe pipe
用于阻止的管线寄存器
bool enable
启用/禁用
描述
此接口的目标是 Wa_16025596647 用法。 即,在未延迟的 vblank 开始时启动软件包 C 退出
-
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 固件
-
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,则为 LVDS 的 i2c 引脚
描述
如果存在 LVDS,则返回 true。 如果没有从 VBT 解析任何子设备,则假定存在 LVDS。
参数
struct intel_display *display
显示设备实例
enum port port
要检查的端口
描述
如果 port
中的设备存在,则返回 true。
参数
struct intel_display *display
显示设备实例
enum port *port
如果存在 DSI,则为 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
struct bdb_header
从 VBT 开头的偏移量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 配置
返回
如果在两个 CDCLK 配置之间进行更改只能通过 cd2x 分频器更新来完成,则为 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
intel 原子状态
描述
如果需要,在更新基于新 CDCLK 状态的 HW 平面状态之前,对硬件进行编程。
-
void intel_set_cdclk_post_plane_update(struct intel_atomic_state *state)¶
将 CDCLK 状态推送到硬件
参数
struct intel_atomic_state *state
intel 原子状态
描述
如果需要,在更新基于新 CDCLK 状态的 HW 平面状态之后,对硬件进行编程。
-
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 的用户,并且该跟踪与原子 modset 接口集成。在原子操作期间,可以通过调用 intel_reserve_shared_dplls()
为给定的 CRTC 和编码器配置保留所需的 PLL,并且可以使用 intel_release_shared_dplls()
释放先前保留的 PLL。对用户的更改首先在原子状态中进行暂存,然后通过在原子提交阶段调用 intel_shared_dpll_swap_state()
使其生效。
给定 ID,获取 DPLL
参数
struct intel_display *display
intel_display 设备实例
enum intel_dpll_id id
pll id
返回
指向具有 id 的 DPLL 的指针
启用 CRTC 的共享 DPLL
参数
const struct intel_crtc_state *crtc_state
CRTC 及其状态,具有共享 DPLL
描述
启用 crtc 使用的共享 DPLL。
禁用 CRTC 的共享 DPLL
参数
const struct intel_crtc_state *crtc_state
CRTC 及其状态,具有共享 DPLL
描述
禁用 crtc 使用的共享 DPLL。
获取 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 对它的使用。
删除 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 对它的使用的结束。
使原子 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 实例。
初始化共享 DPLL
参数
struct intel_display *display
intel_display 设备
描述
为 display 初始化共享 DPLL。
计算 CRTC 和编码器组合的 DPLL 状态
参数
struct intel_atomic_state *state
原子状态
struct intel_crtc *crtc
要计算 DPLL 的 CRTC
struct intel_encoder *encoder
编码器
描述
此函数计算给定 CRTC 和编码器的 DPLL 状态。
通过调用 intel_shared_dpll_swap_state()
使原子提交 state 中的新配置生效。
返回
成功时为 0,失败时为负错误代码。
为 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()
使原子提交 state 中的新配置生效。
应通过调用 intel_release_shared_dplls()
释放保留的 DPLL。
返回
如果所有必需的 DPLL 都已成功保留,则为 0;否则为负错误代码。
结束 CRTC 在原子状态下对 DPLL 的使用
参数
struct intel_atomic_state *state
原子状态
struct intel_crtc *crtc
要从中释放 DPLL 的 crtc
描述
此函数从当前原子提交状态和旧的 crtc 原子状态释放由 intel_reserve_shared_dplls()
保留的所有 DPLL。
通过调用 intel_shared_dpll_swap_state()
使原子提交 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 的原子状态中给定的 crtc/encoder 的活动 DPLL。
-
int intel_dpll_get_freq(struct intel_display *display, const struct intel_shared_dpll *pll, const struct intel_dpll_hw_state *dpll_hw_state)¶
计算 DPLL 的输出频率
参数
struct intel_display *display
intel_display 设备
const struct intel_shared_dpll *pll
要为其计算输出频率的 DPLL
const struct intel_dpll_hw_state *dpll_hw_state
从中计算输出频率的 DPLL 状态
描述
返回与传入的 dpll_hw_state 的 pll 对应的输出频率。
-
bool intel_dpll_get_hw_state(struct intel_display *display, struct intel_shared_dpll *pll, struct intel_dpll_hw_state *dpll_hw_state)¶
读出 DPLL 的硬件状态
参数
struct intel_display *display
intel_display 设备实例
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 intel_display *display, struct drm_printer *p, const struct intel_dpll_hw_state *dpll_hw_state)¶
转储 hw_state
参数
struct intel_display *display
intel_display 结构
struct drm_printer *p
将状态打印到哪里
const struct intel_dpll_hw_state *dpll_hw_state
要转储的硬件状态
描述
转储 dpll_hw_state 中的相关值。
-
bool intel_dpll_compare_hw_state(struct intel_display *display, const struct intel_dpll_hw_state *a, const struct intel_dpll_hw_state *b)¶
比较两个状态
参数
struct intel_display *display
intel_display 结构
const struct intel_dpll_hw_state *a
第一个 DPLL 硬件状态
const struct intel_dpll_hw_state *b
第二个 DPLL 硬件状态
描述
比较 DPLL 硬件状态 a 和 b。
返回
如果状态相等,则为 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。
保存 DPLL 原子状态
定义:
struct intel_shared_dpll_state {
u8 pipe_mask;
struct intel_dpll_hw_state hw_state;
};
成员
pipe_mask
使用此 DPLL 的管道的掩码,无论是否激活
hw_state
DPLL 的硬件配置存储在结构
intel_dpll_hw_state
中。
描述
此结构保存 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 备用)。
具有跟踪状态和用户的显示 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
在某些平台上,可能需要获取设备级运行时 pm 引用,以便在此 DPLL 启用时禁用 DC 状态
显示状态缓冲区¶
DSB(显示状态缓冲区)是内存中 MMIO 指令的队列,可以卸载到显示控制器中的 DSB 硬件。DSB 硬件是一个 DMA 引擎,可以对其进行编程以从内存下载 DSB。它允许驱动程序批量提交显示硬件编程。这有助于减少加载时间和 CPU 活动,从而加快上下文切换速度。从基于 Gen12 英特尔显卡的平台开始添加 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 偏移。 当硬件需要潜在地跟踪每个硬件单元(例如,每个子切片、每个 L3bank 等)的寄存器的独立值时,通常会使用 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(通过 group 和 instance 参数)。
-
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 微秒。
此函数基本上是 __intel_wait_for_register_fw()
的 MCR 友好版本。 通常,此函数仅在 GAM 寄存器上使用,这些寄存器有点特殊 --- 尽管它们是 MCR 寄存器,但读取(例如,等待状态更新)始终定向到主实例。
请注意,此例程假定调用者已断言 forcewake,它不适合长时间等待。
上下文
调用一个获取和释放 gt->mcr_lock 的函数
返回
如果寄存器与所需的条件匹配,则为 0,否则为 -ETIMEDOUT。
内存管理和命令提交¶
本节涵盖了 i915 驱动程序中与 GEM 实现相关的所有内容。
Intel GPU 基础知识¶
Intel GPU 有多个引擎。 有几种引擎类型
渲染命令流式传输器 (RCS)。 用于渲染 3D 和执行计算的引擎。
位块传输命令流式传输器 (BCS)。 用于执行位块传输和/或复制操作的引擎。
视频命令流式传输器。 用于视频编码和解码的引擎。 在硬件文档中也称为“BSD”。
视频增强命令流式传输器 (VECS)。 用于视频增强的引擎。 在硬件文档中也称为“VEBOX”。
计算命令流式传输器 (CCS)。 可以访问媒体和 GPGPU 管道,但不能访问 3D 管道的引擎。
图形安全控制器 (GSCCS)。 专用于与 GSC 控制器进行内部通信的引擎,用于处理与安全相关的任务,例如高带宽数字内容保护 (HDCP)、受保护的 Xe 路径 (PXP) 和 HuC 固件身份验证。
Intel GPU 系列是使用统一内存访问的集成 GPU 系列。 为了使 GPU“工作”,用户空间将通过 ioctl DRM_IOCTL_I915_GEM_EXECBUFFER2 或 DRM_IOCTL_I915_GEM_EXECBUFFER2_WR 将 GPU 批处理缓冲区馈送给 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 时锁定的样子。
需要遵循所有锁定规则和与跨驱动程序接口(dma-buf、dma_fence)的接口约定。
代码中任何地方都没有 struct_mutex
dma_resv 将是最外层的锁(如果需要),并且 ww_acquire_ctx 将提升到最高级别,并在 i915_gem_ctx 的调用链中传递下去
在持有 lru/内存管理器(buddy、drm_mm,无论什么)锁时,不允许系统内存分配
通过启动 lockdep(使用 fs_reclaim)来强制执行此操作。 如果我们在持有这些锁时分配内存,我们会得到收缩器与 struct_mutex 的重新散列,这将非常糟糕。
不要在彼此内部嵌套不同的 lru/内存管理器锁。 依次获取它们以更新内存分配,依靠对象的 dma_resv ww_mutex 与其他操作序列化。
对于 lru/内存管理器锁的建议是它们足够小,可以作为自旋锁。
所有功能都需要在适当时提供详尽的内核自测和/或 IGT 测试
所有 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 代码使用此函数作为碎片整理地址空间的最后一搏。
澄清:这是为了释放虚拟地址空间,而不是为了在例如收缩器中释放内存。
Buffer Object Memory Shrinking¶
本节记录了用于减少缓冲对象缓存内存使用量的接口函数。收缩用于使主内存可用。请注意,这主要与驱逐缓冲对象正交,后者旨在在 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 获取 ctx,或 NULL
struct drm_i915_private *i915
i915 设备
unsigned long target
要提供的内存量,以页面为单位
unsigned long *nr_scanned
可选的已扫描页面数输出(增量)
unsigned int shrink
用于选择缓存类型的控制标志
描述
此函数是收缩器的主要接口。它将尝试从缓冲对象中释放最多 target 页面的主内存后备存储。可以使用 flags 选择特定的缓存。例如,当应优先从缓存中删除可清除对象时,这很有用。
请注意,不能保证释放的数量实际上可以作为可用系统内存 - 由于其他原因(例如 CPU mmap),页面可能仍在使用中,或者 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()
的调用保持平衡。
Batchbuffer Parsing¶
动机:某些 OpenGL 功能(例如,变换反馈、性能监控)要求用户空间代码提交包含诸如 MI_LOAD_REGISTER_IMM 之类的命令的批处理来访问各种寄存器。不幸的是,某些硬件世代将在“不安全”批处理(包括通过 i915 提交的所有用户空间批处理)中取消这些命令,即使这些命令是安全的并且代表设备的预期编程模型。
软件命令解析器在操作上与硬件针对不安全批处理完成的命令解析类似。但是,如果解析器确定操作是安全的,并且将批处理作为“安全”提交以防止硬件解析,则软件解析器允许某些硬件会 noop 的操作。
威胁:在高层次上,硬件(和软件)检查尝试防止授予用户空间不应有的特权。有三种特权类别。
首先,明确定义为特权的命令或仅应由内核驱动程序使用的命令。解析器拒绝此类命令
其次,访问寄存器的命令。为了支持正确/增强的用户空间功能,特别是某些 OpenGL 扩展,解析器提供了一个用户空间可以安全访问的寄存器白名单
第三,访问特权内存的命令(即 GGTT、HWS 页面等)。解析器始终拒绝此类命令。
大多数有问题命令属于 MI_* 范围,每个引擎上只有几个特定命令(例如 PIPE_CONTROL 和 MI_FLUSH_DW)。
实现:每个引擎维护命令和寄存器表,解析器在扫描提交到该引擎的批处理缓冲区时使用这些表。
由于解析器必须检查的命令集明显小于支持的命令数,因此解析器表仅包含解析器所需的那些命令。这通常有效,因为命令操作码范围具有标准命令长度编码。因此,对于解析器不需要检查的命令,它可以轻松跳过它们。这是通过每个引擎的长度解码 vfunc 实现的。
不幸的是,有许多命令不遵循其操作码范围的标准长度编码,主要是在 MI_* 命令中。为了处理此问题,解析器提供了一种在每个引擎的命令表中定义显式“跳过”条目的方法。
其他命令表条目直接映射到上面提到的大多数高级类别:拒绝、寄存器白名单。解析器通过通用的位掩码机制实现许多检查,包括特权内存检查。
-
int intel_engine_init_cmd_parser(struct intel_engine_cs *engine)¶
为引擎设置 cmd 解析器相关字段
参数
struct intel_engine_cs *engine
要初始化的引擎
描述
根据平台是否需要软件命令解析,有选择地初始化 struct intel_engine_cs 中与批处理缓冲区命令解析相关的字段。
-
void intel_engine_cleanup_cmd_parser(struct intel_engine_cs *engine)¶
清理 cmd 解析器相关字段
参数
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)¶
获取 cmd 解析器版本号
参数
struct drm_i915_private *dev_priv
i915 设备私有数据
描述
cmd 解析器维护一个简单的递增整数版本号,适用于传递给用户空间客户端以确定允许的操作。
返回
cmd 解析器的当前版本号
User Batchbuffer Execution¶
-
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}
anonymous
link
i915_gem_context::stale::engines 中的链接
rcu
释放时使用的 RCU
fence
用于延迟销毁引擎的 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
平衡兄弟节点,或并行兄弟节点的数量 * 宽度
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
user_flags
sched
num_user_engines
用户指定的引擎数量,或 -1
user_engines
用户指定的引擎
legacy_rcs_sseu
传统 RCS 的客户端设置的 SSEU 参数
single_timeline
uses_protected_content
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 设置引擎和绑定/平衡。 然而,CONTEXT_SETPARAM 设置 VM 始终是该上下文上的第二个 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 都用作输入和输出 fence。 与真正的 intel_timeline 不同,如果客户端通过同时调用两次 execbuffer2 与自身竞争,这不会提供完美的原子按顺序保证。 然而,如果用户空间与自身竞争,则不太可能产生明确定义的结果,因此我们选择不关心。
vm
唯一的地址空间 (GTT)
在 full-ppgtt 模式下,每个上下文都有其自己的地址空间,确保一个客户端与所有其他客户端完全分离。
在其他模式下,这是一个 NULL 指针,期望调用者使用共享的全局 GTT。
pid
创建者的进程 ID
请注意,创建上下文的人可能不是主要用户,因为上下文可能会通过本地套接字共享。 然而,这应该只影响默认上下文,预计客户端显式创建的所有上下文都是隔离的。
link
放置在
drm_i915_private.context_list
中client
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
wakeref 用于在使用 PXP 时保持设备唤醒
当设备挂起时,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
表示特定客户端的驱动程序和逻辑硬件状态的组合视图。
用户空间提交要在 GPU 上执行的命令,作为 GEM 对象中的指令流,我们称之为批处理缓冲区。 这些指令可以引用包含辅助状态的其他 GEM 对象,例如内核、采样器、渲染目标,甚至二级批处理缓冲区。 用户空间不知道这些对象位于 GPU 内存中的哪个位置,因此在将批处理缓冲区传递给 GPU 执行之前,批处理缓冲区和辅助对象中的那些地址会更新。 这被称为重定位或修补。 为了尽量避免在下次执行时重定位每个对象,用户空间被告知在此传递中这些对象的位置,但这仍然只是一个提示,因为内核将来可能会为任何对象选择新的位置。
在与硬件对话的层面上,提交批处理缓冲区以供 GPU 执行是将内容添加到 HW 命令流读取的缓冲区。
将命令添加到加载 HW 上下文。 对于逻辑环上下文(即 Execlists),此命令不放置在与剩余项目相同的缓冲区上。
将命令添加到使缓存失效的缓冲区。
将批处理缓冲区启动命令添加到缓冲区; 启动命令本质上是一个令牌,以及要执行的批处理缓冲区的 GPU 地址。
将管道刷新添加到缓冲区。
将内存写入命令添加到缓冲区,以记录 GPU 何时完成执行批处理缓冲区。 内存写入写入请求的全局序列号,
i915_request::global_seqno
; i915 驱动程序使用寄存器中的当前值来确定 GPU 是否已完成批处理缓冲区。将用户中断命令添加到缓冲区。 此命令指示 GPU 在命令、管道刷新和内存写入完成后发出中断。
通知硬件添加到缓冲区的其他命令(通过更新尾指针)。
处理 execbuf ioctl 在概念上分为几个阶段。
验证 - 确保所有指针、句柄和标志都有效。
预留 - 为每个对象分配 GPU 地址空间
重定位 - 更新任何地址以指向最终位置
序列化 - 根据其依赖关系对请求进行排序
构造 - 构造请求以执行批处理缓冲区
提交(在将来的某个时间点执行)
为 execbuf 预留资源是最复杂的阶段。 我们既不希望必须在地址空间中迁移对象,也不希望必须更新指向此对象的任何重定位。 理想情况下,我们希望将对象留在其所在的位置,并且所有现有重定位都匹配。 如果对象被赋予新地址,或者如果用户空间认为该对象位于其他位置,我们必须解析所有重定位条目并更新地址。 用户空间可以设置 I915_EXEC_NORELOC 标志来提示其所有对象中的所有目标地址都与重定位条目中的值匹配,并且它们都与 execbuffer 对象列表给出的假定偏移量匹配。 利用此知识,我们知道,如果我们没有移动任何缓冲区,则所有重定位条目都有效,我们可以跳过更新。 (如果用户空间错误,则可能的结果是即兴 GPU 挂起。)使用 I915_EXEC_NO_RELOC 的要求是
对象中写入的地址必须与相应的 reloc.presumed_offset 匹配,而 reloc.presumed_offset 又必须与相应的 execobject.offset 匹配。
批处理中写入的任何渲染目标都必须使用 EXEC_OBJECT_WRITE 标记。
为避免停顿,execobject.offset 应与活动上下文中该对象的当前地址匹配。
预留分多个阶段完成。 首先,我们尝试保留已绑定在其当前位置的任何对象 - 只要满足新 execbuffer 施加的约束即可。 第一次传递后剩下的任何未绑定的对象都将安装到任何可用的空闲空间中。 如果对象不适合,则从预留中删除所有对象,并在按优先级顺序对对象进行排序后重新运行该过程(首先尝试安装更难安装的对象)。 如果失败,则清除整个 VM,我们在得出结论认为它根本无法安装之前,最后一次尝试安装 execbuf。
所有这些的一个小复杂之处是,我们不仅允许用户空间为地址空间中的对象指定对齐方式和大小,还允许用户空间指定确切的偏移量。 这些对象更容易放置(位置是先验已知的),我们必须做的只是确保空间可用。
一旦所有对象都到位,修补埋藏的指针以指向最终位置就是一个相当简单的工作,即遍历重定位条目数组,查找正确的地址并将该值重写到对象中。 简单! ... 重定位条目存储在用户内存中,因此为了访问它们,我们必须将它们复制到本地缓冲区中。 该副本必须避免任何缺页错误,因为它们可能会导致回到需要 struct_mutex 的 GEM 对象(即递归死锁)。 因此,我们再次将重定位拆分为多个传递。 首先,我们尝试在原子上下文中完成所有操作(避免缺页错误),这要求我们永远不要等待。 如果我们检测到我们可能会等待,或者如果我们需要缺页错误,那么我们必须回退到较慢的路径。 慢速路径必须放弃 mutex。 (你能听到警钟了吗?)放弃 mutex 意味着我们失去了到目前为止为 execbuf 构建的所有状态,我们必须重置任何全局数据。 然而,我们将对象锁定在它们的最终位置 - 这对于并发 execbuf 来说是一个潜在的问题。 一旦我们离开了 mutex,我们可以随意分配并将所有重定位条目复制到一个大的数组中,重新获取 mutex,回收所有对象和其他状态,然后继续使用对象更新任何不正确的地址。
当我们处理重定位条目时,我们会维护对象是否正在写入的记录。 使用 NORELOC,我们希望用户空间改为提供此信息。 我们还会通过将重定位条目中的预期值与目标的最终地址进行比较来检查是否可以跳过重定位。 如果它们不同,我们必须映射当前对象并重写其中的 4 或 8 字节指针。
根据 GEM ABI 的规则,序列化 execbuf 非常简单。 每个上下文中的执行都按提交的顺序排序。 对任何 GEM 对象的写入都按提交的顺序进行,并且是独占的。 从 GEM 对象的读取相对于其他读取是无序的,但按写入排序。 在读取之后提交的写入不能在读取之前发生,类似地,在写入之后提交的任何读取不能在写入之前发生。 写入在引擎之间排序,以便一次只发生一次写入(预先完成任何读取) - 在可用时使用信号量,否则使用 CPU 序列化。 其他 GEM 访问服从相同的规则,任何写入(无论是通过使用 set-domain 的 mmap 还是通过 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
调度引擎对象的引用计数
lock
保护优先级列表、请求、保留和正在运行的任务中的请求
requests
在此调度引擎上正在进行的请求列表
hold
就绪请求列表,但处于保留状态
tasklet
用于提交的 softirq 任务
default_priolist
I915_PRIORITY_NORMAL 的优先级列表
queue_priority_hint
最高挂起的优先级。
当我们向队列中添加请求或调整正在执行的请求的优先级时,我们会计算那些挂起的请求的最大优先级。 然后,我们可以使用此值来确定我们是否需要抢占正在执行的请求以服务于队列。 然而,由于我们可能已经记录了我们想要抢占的正在进行的请求的优先级,但由于已经完成,因此在出队时优先级提示可能不再与最高可用请求优先级匹配。
queue
请求队列,在优先级列表中
no_priolist
禁用优先级列表
private_data
提交后端的私有数据
destroy
销毁调度引擎 / 清理后端
disabled
检查后端是否已禁用提交
kick_backend
在请求的优先级更改后启动后端
bump_inflight_request_prio
更新正在进行的请求的优先级
retire_inflight_request_prio
指示请求已从优先级跟踪中撤回
schedule
调整请求的优先级
当请求的优先级发生更改并且它及其依赖项可能需要重新安排时调用。 请注意,请求本身可能尚未准备好运行!
描述
调度引擎表示具有不同优先级带的提交队列。 它包含所有公共状态(相对于后端)以排队、跟踪和提交请求。
此对象目前非常特定于 i915,但一旦 i915 与 DRM 调度程序集成,它将转换为 drm_gpu_scheduler 的容器以及其他一些变量。
逻辑环、逻辑环上下文和 Execlists¶
动机:GEN8 带来了 HW 上下文的扩展:“逻辑环上下文”。 这些扩展的上下文启用了许多新功能,尤其是“Execlists”(也在此文件中实现)。
与传统 HW 上下文的主要区别之一是逻辑环上下文将更多内容合并到上下文的状态中,例如 PDP 或环缓冲区控制寄存器
PDP 包含在上下文中的原因是简单的:由于 PPGTT(每个进程的 GTT)实际上是每个上下文的,因此将 PDP 包含在那里意味着你不需要自己进行 ppgtt->switch_mm,而是 GPU 会在上下文切换时为你执行此操作。
但是,环缓冲区控制寄存器(head、tail 等)呢? 我们是否只需要每个引擎命令流的一组这些寄存器? 这就是名称“逻辑环”开始有意义的地方:通过虚拟化环,引擎 cs 会在每次上下文切换时切换到新的“环缓冲区”。 当你想要将工作负载提交到 GPU 时,你会:A) 选择你的上下文,B) 找到其合适的虚拟化环,C) 将命令写入其中,然后,最后,D) 告诉 GPU 切换到该上下文。
你告诉 GPU 切换到上下文的方式不是传统的 MI_SET_CONTEXT,而是通过上下文执行列表,即“Execlists”。
LRC 实现:关于上下文的创建,我们有
一个全局默认上下文。
每个打开的 fd 的一个本地默认上下文。
每个上下文创建 ioctl 调用一个本地额外上下文。
现在,环缓冲区属于每个上下文(而不是像以前那样属于每个引擎),并且上下文与给定的引擎唯一绑定(而不是像以前那样可重用),我们需要
每个上下文中的每个引擎一个环缓冲区。
每个上下文中的每个引擎一个后备对象。
全局默认上下文在其生命周期开始时,这些新对象已完全分配和填充。 每个打开的 fd 的本地默认上下文更复杂,因为我们在创建时不知道哪个引擎将使用它们。 为了处理这个问题,我们实现了延迟创建 LR 上下文
本地上下文在其生命周期开始时是一个空洞或空白的占位符,只有在我们收到 execbuffer 时才会被填充给定的引擎。 如果稍后我们收到另一个用于同一上下文但不同引擎的 execbuffer ioctl,我们将分配/填充一个新的环缓冲区和上下文后备对象,依此类推。
最后,关于使用 ioctl 调用创建的本地上下文:由于它们仅允许使用渲染环,因此我们可以立即分配和填充它们(至少现在不需要延迟任何操作)。
Execlists 实现:Execlists 是 gen8+ 硬件上提交工作负载以供执行的新方法(与传统的基于环缓冲区的方法相反)。 此方法的工作方式如下
当提交请求时,其命令(BB 启动和任何前导或尾随命令,例如 seqno 面包屑)放置在相应上下文的环缓冲区中。 此时硬件上下文中的尾指针不会更新,而是由驱动程序保留在环缓冲区结构中。 表示此请求的结构会添加到相应引擎的请求队列中:此结构包含在将请求写入环缓冲区后上下文尾部的副本,以及指向上下文本身的指针。
如果引擎的请求队列在添加请求之前为空,则队列会立即被处理。否则,队列将在上下文切换中断期间被处理。在任何情况下,队列中的元素都将以成对的形式发送到 GPU 的 ExecLists 提交端口(简称 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 视图的代码需要
添加一个新的带有合适名称的枚举。
如果需要,扩展 i915_gtt_view 结构中的元数据。
添加对 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)¶
在 address_space (GTT) 中保留一个节点
参数
struct i915_address_space *vm
the
struct i915_address_space
struct i915_gem_ww_ctx *ww
一个可选的 struct i915_gem_ww_ctx。
struct drm_mm_node *node
the
struct drm_mm_node
(通常是 i915_vma.node)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(使用 size 和 color)。如果 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)¶
将节点插入到 address_space (GTT) 中
参数
struct i915_address_space *vm
the
struct i915_address_space
struct i915_gem_ww_ctx *ww
一个可选的 struct i915_gem_ww_ctx。
struct drm_mm_node *node
the
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 的空闲寄存器,如果找不到任何寄存器,则窃取一个寄存器。
然后,它基于对象的属性设置寄存器:地址、pitch 和平铺格式。
对于非平铺表面,这将移除任何现有的栅栏。
返回
成功时为 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() 强制移除栅栏。
在内部,这些函数将通过根据需要将 CPU ptes 移除到 GTT mmap 中(而不是 GTT ptes 本身)与用户空间访问同步。
硬件平铺和交织细节¶
平铺背后的想法是通过重新排列像素数据来增加缓存命中率,以便一组像素访问位于同一缓存行中。在后缓冲区/深度缓冲区上执行此操作的性能提升约为 30%。
但是,英特尔架构通过在内存处于交错模式(成对匹配的 DIMMS)时对数据寻址进行调整以提高内存带宽,从而使这变得更加复杂。对于交错内存,CPU 将每个连续的 64 字节发送到备用内存通道,以便它可以从两个通道获取带宽。
GPU 还重新排列其访问以提高交错内存的带宽,并且它与 CPU 对非平铺内存的操作相匹配。但是,当平铺时,它的做法略有不同,因为不仅在 X 方向上,而且在 Y 方向上也遍历地址。因此,除了当地址的位 6 翻转时交替通道之外,当其他位翻转时也会交替通道 - 位 9(每 512 字节,即 X 平铺扫描线)和 10(每两个 X 平铺扫描线)是 915 和 965 类硬件共有的。
CPU 有时也会异或更高位,以提高执行步幅式访问的带宽,就像我们在图形中经常做的那样。这在 MCH 文档中称为“通道 XOR 随机化”。结果是 CPU 将位 11 或位 17 异或到位 6 的地址解码中。
所有这些位 6 XOR 都对我们的内存管理产生影响,因为我们需要确保 3d 驱动程序可以正确寻址对象内容。
如果我们没有交错内存,则所有平铺都是安全的,并且不需要交织。
当位 17 被 XOR 时,我们 просто 拒绝进行任何平铺。位 17 不仅仅是页面偏移量,因此当我们分页进出对象时,其中的各个页面将具有不同的位 17 地址,导致每个 64 字节与其邻居交换!
否则,如果交错,我们必须告诉 3d 驱动程序它需要执行的地址交织是什么,因为它正在使用 CPU 写入页面(位 6 和可能位 11 被 XOR),并且 GPU 正在从页面读取(位 6、9 和 10 被 XOR),导致 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)¶
fence所需的全局GTT对齐
参数
struct drm_i915_private *i915
i915 设备
u32 size
对象大小
unsigned int tiling
平铺模式
unsigned int stride
平铺步幅
描述
返回fence所需的全局GTT对齐(平铺对象的视图),同时考虑潜在的fence寄存器映射。
-
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()
是用于声明fence寄存器要求的用户空间接口。
原则上,GEM完全不关心对象的内部数据布局,因此它也不关心平铺或交换。但有两个例外:
对于X和Y平铺,硬件为CPU访问提供了解平铺器,称为fence。由于它们的数量有限,内核必须管理这些,因此,如果用户空间想要使用fence进行解平铺,则必须告诉内核对象的平铺方式。
在gen3和gen4平台上,平铺对象具有一种交换模式,该模式取决于物理页面帧编号。当交换此类对象时,页面帧编号可能会更改,内核必须能够修复此问题,因此需要知道平铺方式。请注意,在具有非对称内存通道填充的部分平台上,交换模式以未知的方式更改,对于这些平台,内核会完全禁止交换。
由于这两种情况都不适用于现代平台(如W、Ys和Yf平铺)上的新平铺布局,因此GEM仅允许将对象平铺设置为X或Y平铺。其他任何事情都可以在用户空间中完全处理,而无需内核的参与。
受保护的对象¶
PXP(受保护的Xe路径)是Gen12及更高版本平台上提供的一项功能。它允许执行受保护(即加密)对象的显示和翻转。通过CONFIG_DRM_I915_PXP kconfig启用SW支持。
对象可以通过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 init和fini函数中设置和清除。
ce
用于PXP操作的内核拥有的上下文
arb_mutex
保护arb会话启动
arb_is_valid
跟踪arb会话状态。拆卸后,即使密钥已消失,arb会话仍可以在HW上运行,因此我们不能依赖会话的HW状态来判断其是否有效,而需要在SW中跟踪状态。
key_instance
跟踪我们所在的密钥实例,因此我们可以使用它来确定对象是使用当前密钥还是使用先前的密钥创建的。
tee_mutex
保护tee通道绑定和消息传递。
stream_cmd
用于将流PXP命令发送到GSC的LMEM obj
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是GT HW内部的微控制器,在gen9中引入。 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_ids,single-lrc
submission_state.num_guc_ids
guc_ids的数量,自检功能,以便在测试时能够减少此数量。
submission_state.guc_ids_bitmap
用于分配新的guc_ids,multi-lrc
submission_state.guc_id_list
具有有效guc_ids但没有refs的intel_context列表
submission_state.guc_ids_in_use
正在使用的单lrc guc_ids的数量
submission_state.destroyed_contexts
等待销毁的上下文列表(在GuC中取消注册)
submission_state.destroyed_worker
取消注册上下文的工作程序,我们需要获取GT PM引用,并且不能从destroy函数中获取,因为它可能位于原子上下文中(无法睡眠)
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中保存/恢复regset的大小
ads_regset_count
每个引擎的ADS中保存/恢复寄存器的数量
ads_regset
ADS中保存/恢复regset
ads_golden_ctxt_size
ADS中golden上下文的大小
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的FW特定寄存器,用于发送MMIO H2G
notify_reg
用于将中断发送到GuC FW的寄存器
mmio_msg
GuC在其寄存器之一中写入的通知位掩码,用于在CT通道被禁用时进行处理,以便在通道恢复时进行处理。
send_mutex
用于序列化intel_guc_send操作
timestamp
GT时间戳对象,用于存储时间戳的副本,并使用worker调整其溢出。
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重置的异步worker线程。专门用于G2H处理程序想要发出重置时。重置需要刷新G2H队列。因此,G2H处理本身不得直接触发重置。而是通过此worker进行。
last_dead_guc_jiffies
先前“dead guc”事件的时间戳,用于防止从根本上损坏的系统持续重新加载GuC。
number_guc_id_stolen
已被盗的guc_ids的数量
fast_response_selftest
用于快速响应自检的CT处理程序的后门
描述
它处理固件加载并管理客户端池。 intel_guc拥有用于提交的i915_sched_engine。
参数
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 为单位)可以在标头中找到。 如果模数和指数不存在于 fw 中(也称为截断图像),则长度值仍然出现在标头中。
驱动程序将根据以下规则执行一些基本的 fw 大小验证
标头、uCode 和 RSA 是必备组件。
所有固件组件(如果存在)都按照上述布局表中的顺序排列。
每个组件的长度信息都可以在标头中找到,以 dword 为单位。
驱动程序不需要模数和指数密钥。 它们可能不会出现在 fw 中。 因此,在这种情况下,驱动程序将加载截断的固件。
从 DG2 开始,HuC 由 GSC 而不是 i915 加载。 GSC 固件执行所有必需的完整性检查,我们只需要检查版本。 请注意,GSC 管理的 blob 的标头与用于 DMA 加载固件的 CSS 不同。
GuC 内存管理¶
GuC 无法为其自己的使用分配任何内存,因此所有分配都必须由主机驱动程序处理。 GuC 通过 GGTT 访问内存,但 4GB 地址空间顶部和底部除外,它们改为由 GuC HW 重新映射到 FW 本身(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 intel_guc *guc
guc
u32 size
要分配区域的大小(虚拟空间和内存)
描述
这是创建用于 GuC 的对象的包装器。 为了在 GuC 中使用它,需要终身锁定一个对象,因此我们在 Global GTT 中分配一些后备存储和一个范围。 我们必须将其锁定在 GGTT 中的 [0, GUC ggtt_pin_bias) 之外的某个位置,因为该范围在 GuC 中是保留的。
返回
如果成功,则为 i915_vma,否则为 ERR_PTR。
GuC 特定固件加载器¶
参数
struct intel_guc *guc
intel_guc 结构体
描述
从驱动程序加载、从睡眠中恢复以及 GPU 重置后,从 intel_uc_init_hw() 调用。
固件镜像应该已经被提取到内存中,因此只需检查提取是否成功,然后将镜像传输到硬件。
返回
错误时返回非零代码
基于 GuC 的命令提交¶
暂存寄存器:有 16 个基于 MMIO 的寄存器,从 0xC180 开始。内核驱动程序将一个值写入操作寄存器 (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 都是不安全的,因此使用栅栏来阻止与此 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 对 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
27:0
COUNTER - 进度指示器
HXG 重试
接收者应使用 HXG 重试消息来指示 HXG 请求消息已被丢弃,应再次发送。
REASON 字段可用作提供附加信息。
位
描述
0
31
ORIGIN
30:28
27:0
- REASON - 重试原因
GUC_HXG_RETRY_REASON_UNSPECIFIED = 0
HXG 失败
由于错误无法处理 HXG 请求消息时,应使用 HXG 失败消息作为回复。
位
描述
0
31
ORIGIN
30:28
27:16
HINT - 附加错误提示
15:0
ERROR - 错误/结果代码
HXG 响应
成功处理了 HXG 请求消息且没有错误时,应使用 HXG 响应消息作为回复。
基于 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(head/tail 太大)
GUC_CTB_STATUS_UNDERFLOW = 2(消息被截断)
GUC_CTB_STATUS_MISMATCH = 4(head/tail 被修改)
GUC_CTB_STATUS_UNUSED = 8(CTB 未使用)
...
保留 = MBZ
15
31:0
保留 = MBZ
CTB 消息
位
描述
0
31:16
FENCE - 消息标识符
15:12
- FORMAT - CTB 消息的格式
GUC_CTB_FORMAT_HXG = 0 - 参见 CTB HXG 消息
11:8
保留
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
保留 = 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
27:0
DATA0 = NUM - 如果 KLV 已被解析,则为 1,如果未被识别,则为 0
HOST2GUC_CONTROL_CTB
此 H2G 操作允许 Vf 主机启用或禁用 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
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_CFG 的 GuC 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 可能会对媒体工作负载的功耗和/或性能产生负面影响,具体取决于使用场景。HuC 必须在导致 WOPCM 丢失其内容(S3/S4、FLR)的事件中重新加载;在较旧的平台上,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 graphics 支持大量的性能计数器,可以帮助驱动程序和应用程序开发人员了解和优化他们对 GPU 的使用。
这个 i915 perf 接口允许用户空间配置和打开一个文件描述符,该文件描述符表示 GPU 指标的流,然后可以将其读作 () 一系列样本记录。
该接口特别适合暴露由 GPU 通过 DMA 捕获的缓冲指标,这些指标与 CPU 不同步且不相关。
表示单个上下文的流可以被具有相应 drm 文件描述符的应用程序访问,这样 OpenGL 可以在没有特殊权限的情况下使用该接口。对系统范围指标的访问默认需要 root 权限,除非通过 dev.i915.perf_event_paranoid sysctl 选项进行更改。
与 Core Perf 的比较¶
该接口最初受到核心 Perf 基础结构的启发,但一些显著的差异是
i915 perf 文件描述符表示“流”而不是“事件”;perf 事件主要对应于单个 64 位值,而流可能会采样紧密耦合的计数器集,具体取决于配置。例如,Gen OA 单元并非设计为支持各个计数器的正交配置;它配置为一组相关计数器。用于捕获 OA 指标的 i915 perf 流的样本将包括以紧凑的 HW 特定格式打包的一组计数器值。OA 单元支持多种不同的打包格式,用户可以通过打开流来选择这些格式。Perf 支持对事件进行分组,但组中的每个事件都使用单独的系统调用进行单独配置、验证和身份验证。
i915 perf 流配置作为 u64(键,值)对的数组提供,而不是带有多个杂项配置成员(与事件类型特定成员交错)的固定结构。
i915 perf 不支持通过 mmap'd 循环缓冲区公开指标。受支持的指标由 GPU 使用 HW 特定打包格式为计数器集写入内存,与 CPU 不同步。有时,HW 配置的约束要求在将报告公开给非特权应用程序之前对其进行过滤 - 以隐藏其他进程/上下文的指标。对于这些用例,基于 read() 的接口非常合适,并提供了一个在数据从 GPU 映射的缓冲区复制到用户空间缓冲区时过滤数据的机会。
基于 Core 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 计数器在 HW 中配置为一组,而 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 中允许用户空间打开 + 启用比一次可以在 HW 中配置的更多事件的中心设计思想。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 报告以外的内容,我们必须将报告复制到中间的更大缓冲区中。我一直在考虑允许指定数据 + 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)¶
用于用户空间打开流 FD 的 DRM ioctl()
参数
struct drm_device *dev
drm 设备
void *data
从用户空间复制的 ioctl 数据(未经验证)
struct drm_file *file
drm 文件
描述
验证用户空间给出的流打开参数,包括标志和 u64 键值对属性的数组。
关于正在打开的流的性质,预先做出的假设很少(例如,我们不假设它是用于定期 OA 单元指标)。i915-perf 流有望成为 GPU 写入的其他形式的缓冲数据的合适接口,除了定期 OA 指标。
请注意,我们在 i915 perf 互斥锁之外复制用户空间的属性,以避免与 mmap_lock 发生笨拙的 lockdep。
大多数实现细节由 i915_perf_open_ioctl_locked()
在获取 gt->perf
.lock 互斥锁以与任何非文件操作驱动程序挂钩进行序列化后处理。
返回
新打开的 i915 Perf 流文件描述符或失败时的负错误代码。
参数
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
与此性能流关联的引擎。
lock
与流操作关联的锁
sample_flags
表示打开流时给出的 DRM_I915_PERF_PROP_SAMPLE_* 属性的标志,表示用户空间通过 read() 读取的单个样本的内容。
sample_size
考虑到样本的配置内容与所需的标头大小相结合,这是单个样本记录的总大小。
ctx
如果在所有上下文中测量系统范围,则为
NULL
,或者为正在监视的特定上下文。enabled
考虑到流是否以禁用状态打开,并基于 I915_PERF_IOCTL_ENABLE 和 I915_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
锁定对所有 head/tail 状态的读取和写入
考虑:head 和 tail 指针状态需要从 hrtimer 回调(原子上下文)和 read() fop(用户上下文)一致地读取,tail 指针更新发生在原子上下文中,head 更新发生在用户上下文中,并且(不太可能)read() 错误可能需要重置所有 head/tail 状态。
注意:考虑到 hrtimer 回调的频率相对较低(5 毫秒周期),并且读取通常只在响应 hrtimer 事件时发生,并且可能在下一次回调之前完成,因此当前对争用/性能没有明显的担忧。
注意:在读取和将数据复制到用户空间时,不持有此锁,因此 htrimer 回调中观察到的 head 值将不表示数据的任何部分消耗。
oa_buffer.head
虽然我们总是可以读回 head 指针寄存器,但我们更喜欢避免信任 HW 状态,只是为了避免任何硬件条件 * 以某种方式意外地增加 head 指针并导致我们将错误的 OA 缓冲区数据转发到用户空间的风险。
oa_buffer.tail
用户空间可以读取的最后一个验证的尾部。
noa_wait
一个批处理缓冲区,在 GPU 上等待 NOA 逻辑被重新编程。
poll_oa_period
应该检查 OA 缓冲区中是否有可用数据的时间段(以纳秒为单位)。
-
struct i915_perf_stream_ops¶
支持特定流类型的 OP
定义:
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,传递一个等待队列,一旦有可供流 read() 的内容,该队列将被唤醒
wait_unlocked
对于处理阻塞读取,请等待直到有可供流 read() 的内容。例如,等待将传递给 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)¶
用于用户空间打开流 FD 的 DRM ioctl()
参数
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)。
参数
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 Perf 观测架构流¶
-
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
验证用于为特定平台编程 flex 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,该 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 中读取时调用。它会等待,直到 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() 状态表
描述
为了处理 i915 perf 流上为 OA 指标打开的用户空间轮询,这将启动一个 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
一个 gem ctx 句柄,用于 single_context
metrics_set
通过 sysfs 宣传的 OA 单元指标集的 ID
oa_format
OA 单元 HW 报告格式
oa_periodic
是否启用定期 OA 单元采样
oa_period_exponent
OA 单元采样周期由此导出
engine
正由 OA 单元监视的引擎(通常为 rcs0)
has_sseu
用户空间是否指定了 sseu
sseu
内部 SSEU 配置,可以从打开参数中的用户空间指定配置或默认值计算得出(参见 get_default_sseu_config())
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(对于用户 ctx 中的阻塞读取)或轮询检查 hrtimer(原子 ctx)调用,以检查 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 报告
描述
样本的内容通过打开流时,作为 stream->sample_flags 跟踪的 DRM_I915_PERF_PROP_SAMPLE_* 属性配置。 此函数将单个样本的请求组件复制到给定的 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 hw 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 流
描述
如果需要执行任何操作以确保上下文 HW 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- 键名称为:render、copy、video 和 video-enhance。