AMD显卡休眠唤醒流程分析

休眠

不论是系统suspend to mem还是suspend to disk,对于PCI外设而言都是调用pci_pm_suspend进行设备挂起。对于AMD显卡来讲就是radeon_pmops_suspend或者amdgpu_pmops_suspend,根据显卡chip不同调用的接口不通。目前手头只有CAICOS显卡走的是radeon驱动,那就分析radeon_pmops_suspemd,下面是相关代码:

radeon_pm_ops_suspend
	radeon_suspend_kms
		drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF)			//遍历每一个connector,关闭屏幕
		// 遍历每一个crtc,unpin桌面和鼠标相关的bo,这是为了后面evict做准备,pin过的bo是不能被驱逐的。
		// 但是不要unpin fb,在suspend之后可能会需要屏幕显示东西,比如说suspend to disk最后创建和写入image需要屏幕提示
		radeon_bo_evict_vram			//第一次驱逐bo
		radeon_fence_driver_force_completion			//wait到gpu上正在执行的指令执行完毕
		radeon_save_bios_scratch_regs			//保存scratch区域的临时数据,scratch 区域通常用于存储临时数据,例如渲染过程中的中间结果
		radeon_suspend					//这会调用到asic->suspend,对于caicos卡来讲,就是evergreen_suspend
		radeon_hpd_fini				//关掉hpd中断
		radeon_bo_evict_vram			//第二次驱逐BO
		pci_disable_device

流程比较简单,先关闭相关相关的部分,之后驱逐bo,调用evergreen_suspend挂起设备,第二次驱逐BO,最后调用shutdown关闭设备。其中evergreen_suspend代码如下:

radeon_pm_suspend				//释放电源管理相关资源并清理状态
radeon_audio_fini					//清理声卡相关状态
r600_dma_stop						//关闭DMA
evergreen_irq_suspend			//禁用中断
radeon_wb_disable					//禁用写缓存
evergreen_pcie_gart_disable	 //禁用gart表

上面看到BO驱逐了两次,第一次驱逐BO是为了确保内存中的数据能被正确的写回显存中。第一次驱逐完成后,等待GPU当前指令执行结束,结果成功的保存在scratch或者显存中。关闭GPU硬件相关内容,禁用gart表,开始第二次驱逐。第二次是为了确保显存中的数据能够正确的保存到内存或者休眠镜像中,

BO驱逐是通过调用ttm接口实现的,大致代码如下:

radeon_bo_evict_vram
	ttm_bo_evict_mm
		ttm_bo_force_list_clean
			ttm_mem_evict_first
				list_for_each_entry(bo, &man->lru[i], lru) {
						bdev->driver->eviction_valuable(bo,place);				//决定这个bo要不要被驱逐出去
						ttm_bo_evict
							bdev->driver->evict_flags(bo, &placement);				//确定要驱逐到哪里,GTT?VRAM还是主存
							ttm_bo_mem_space					//在刚刚找到的place里面申请空间
							ttm_bo_handle_move_mem			//把BO 搬过去
				}

调用radeon_bo_evict_vram开始第一次驱逐vram bo,每一个通过radeon_gem_create_ioctl申请出去的bo,都会被添加到链表rdev->gem链表中。遍历gem链表,先通过eviction_valuable判断是否驱逐此BO,ttm_bo_eviction_valuable只驱逐在place区域里面的BO,这里的place参数传递的是NULL,表示任何区域都可以。注意BO create的时候,添加gem链表调用的是add_tail,那么驱逐顺序那就是先创建的先驱逐,调用ttm_bo_evict驱逐BO。

ttm_bo_evict函数中,调用ttm_bo_drivers->evict_flags,确定需要把BO驱逐到系统主存、GTT还是不可见显存中。radeon_evict_flags根据以下规则确定需要把bo驱逐到哪里去:

  • 如果类型是VRAM
    • 先读取GPU ASIC DMA ring寄存器,确定能不能这个ring是不是enable的,如果是,发起DMA搬运把bo搬移到主存中(测试中,只有gart表直接从VRAM搬到主存中去了);
    • 如果显卡存在不可见显存且bo位于可见显存 ,BO驱逐到不可见显存中,如果不可见显存空间满了,驱逐到GTT中;
    • 如果BO位于不可见显存,驱逐到GTT中;假如GTT申请不到空间了,调用radeon_bo_evict_GTT,把GTT中的BO驱逐到主存中
  • 如果类型是GTT,驱逐到主存中

在glxgear单个测试中,申请的BO不是很多,基本都是在可见显存中,第一次驱逐BO从可见显存move到不可见显存,第二次驱逐再驱逐到GTT。

下面是主要的流程:

唤醒

一般来讲唤醒流程和suspend是成镜像的,suspend的时候最后驱逐的gart表,那resume里面,第一个恢复的bo就是gart表,在pci_pmops_resume->radeon_resume->evergreen_resume这么一串调用下来,进入asic的resume流程。首先初始化golden寄存器,表示开始gpu的init流程;然后初始化VRAM scratch区域,scratch 区域通常用于存储临时数据,例如渲染过程中的中间结果;最后就是enable gart表,evergreen_pcie_gart_enable函数把gart表pin到VRAM中。根据之前suspend的分析,几乎所有的BO都被evict到GTT或主存中了,所以gart表在pin的时候会被move到vram中。

在所有的pci设备都resume完成,恢复上层进程的时候,需要占用GPU的应用进程通过ioctl提交command到gpu,ioctl从sys到drm最终走到radeon_cs_ioctl,此函数需要检测相关BO是否合法。调用链是:

radeon_cs_ioctl
    radeon_cs_parser_relocs     //更新BO地址
        radeon_bo_list_validate     //遍历链表check相关BO是否合法
            ttm_bo_validate         //通过TTM提供接口检测 BO是否和bo->placements对得上
                ttm_bo_handle_move_mem      //如果对不上的话move到placement指定位置去

综合来看,BO的resume是真正需要用的时候(上层APP已经指定必须在VRAM里)再一个一个从GTT中move过去的,实际上在单个glxgears测试中,发现move过去的是很小一部分bo,大部分bo直到最后释放都是在GTT中的。

测试

VRAM: 2GB

GTT: 1GB

写一个用户进程,每次申请100M BO,sleep 5s。

C++ 复制代码
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <drm/drm.h>
#include <drm/radeon_drm.h>

int main() {
    int fd;
    struct drm_radeon_gem_create args;

    // 打开设备文件
    fd = open("/dev/dri/card0", O_RDWR);
    if (fd < 0) {
        perror("Failed to open device");
        return 1;
    }

    // 设置参数
    args.size = 1024*1024*100;  // 申请一个 100M 的 BO
    args.alignment = 0;
    args.initial_domain = RADEON_GEM_DOMAIN_VRAM;
    args.flags = 0;

    // 调用 ioctl
    for (i = 0; i < 1000; i++) {
    	if (ioctl(fd, DRM_IOCTL_RADEON_GEM_CREATE, &args) < 0) {
        	perror("Failed to create BO");
	        return 1;
   	 	}
   	 	printf("Created BO with handle %u, i=%d\n", args.handle, i);
   	 	sleep(5);
    }


    // 关闭设备文件
    close(fd);
    return 0;
}

当打印到第15个的时候,调用了ttm_bo_evict, 也就是申请了1500MB VRAM之后开始驱逐VRAM里面的BO到GTT中,在这时候点击系统休眠。

第一次驱逐BO的时候,从VRAMevict了5个刚刚申请的BO到GTT,也就是500M之后GTT找不到空闲的100M空间,也就是说这次suspend一共要保存GTT 里500M数据和VRAM里1.8G数据。GTT中的BO 驱逐到主存中,差不多驱逐了500多M出去,继续驱逐VRAM BO,又过了5个之后,GTT空间又不够了,又开始把GTT evict到主存中,这会往主存还是驱逐500M。这样子循环往复,直到两回evict vram都结束。经过统计,差不多驱逐了1.3G的BO到主存中去加上GTT里面的1G数据,刚好是2.3GB。

系统内存是8GB,这时候suspend to disk提示Creating Image failed失败,因为系统内存不够创建image。

相关推荐
内核程序员kevin2 小时前
TCP Listen 队列详解与优化指南
linux·网络·tcp/ip
朝九晚五ฺ6 小时前
【Linux探索学习】第十四弹——进程优先级:深入理解操作系统中的进程优先级
linux·运维·学习
自由的dream6 小时前
Linux的桌面
linux
xiaozhiwise7 小时前
Makefile 之 自动化变量
linux
意疏9 小时前
【Linux 篇】Docker 的容器之海与镜像之岛:于 Linux 系统内探索容器化的奇妙航行
linux·docker
BLEACH-heiqiyihu9 小时前
RedHat7—Linux中kickstart自动安装脚本制作
linux·运维·服务器
一只爱撸猫的程序猿9 小时前
一个简单的Linux 服务器性能优化案例
linux·mysql·nginx
我的K840910 小时前
Flink整合Hudi及使用
linux·服务器·flink
19004311 小时前
linux6:常见命令介绍
linux·运维·服务器