Re: [PATCH v3] drm/amdkfd: preserve VRAM MQD across hibernation via GTT eviction
"Kuehling, Felix" <[email protected]> Wed, 29 Jul 2026 10:53:08 -0400
| Newsgroups | org.freedesktop.lists.amd-gfx |
|---|---|
| Message-ID | <[email protected]> |
On 2026-07-29 07:00, Shikang Fan wrote: > On gfx942/944 and gfx9.5.0, KFD compute-queue MQDs live in a pinned VRAM > buffer object (mqd_on_vram()). Pinned VRAM is skipped by TTM eviction and > is not saved across S4 hibernation, so the MQD is garbage on resume and > the first submission faults (no-retry page fault, UTCL2/gfxhub0). > > A bare unpin is not enough: amdgpu's eviction policy relocates the BO > within VRAM rather than to system memory, so its contents are still lost > when VRAM powers off. Instead, at suspend, unpin the MQD BO and move it to > GTT (ttm_bo_validate to GTT) while it is unpinned, so ttm_device_swapout() > captures it in the hibernation image. On resume, repin it to VRAM and > refresh the cached MQD GPU address (and kernel mapping) since the BO may > come back at a new location. > > Signed-off-by: Shikang Fan <[email protected]> > --- > .../drm/amd/amdkfd/kfd_device_queue_manager.c | 115 ++++++++++++++++++ > drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.h | 8 ++ > .../gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c | 40 ++++++ > drivers/gpu/drm/amd/amdkfd/kfd_priv.h | 6 + > 4 files changed, 169 insertions(+) > > diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c > index 51ee9c39104b..f4da27da0af7 100644 > --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c > +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c > @@ -1254,6 +1254,106 @@ static int resume_single_queue(struct device_queue_manager *dqm, > return 0; > } > > +/* The MQD of a gfx9 compute queue lives in a pinned VRAM buffer object when > + * mqd_on_vram(). Pinned VRAM is skipped by TTM eviction, and a bare unpin only > + * lets amdgpu relocate the BO within VRAM - whose contents are lost across S4 > + * hibernation. To have the MQD captured in the hibernation image, move the BO to > + * GTT (system memory) while it is unpinned so ttm_device_swapout() snapshots it, > + * then pin it back to VRAM on resume. > + * > + * dqm_evict_mqd_bo() runs at suspend, gated on adev->in_s4 so runtime eviction > + * (where the pinned VRAM MQD stays intact) is untouched. > + */ > +static void dqm_evict_mqd_bo(struct device_queue_manager *dqm, struct queue *q) > +{ > + struct ttm_operation_ctx ctx = { false, false }; > + struct amdgpu_bo *bo; > + int r; > + > + if (!dqm->dev->adev->in_s4) > + return; > + if (!mqd_on_vram(dqm->dev->adev)) > + return; > + if (q->properties.type != KFD_QUEUE_TYPE_COMPUTE) > + return; > + if (!q->mqd_mem_obj || !q->mqd_mem_obj->mem) > + return; > + > + bo = q->mqd_mem_obj->mem; > + if (amdgpu_bo_reserve(bo, false)) > + return; > + > + amdgpu_bo_unpin(bo); > + amdgpu_bo_placement_from_domain(bo, AMDGPU_GEM_DOMAIN_GTT); > + r = ttm_bo_validate(&bo->tbo, &bo->placement, &ctx); > + amdgpu_bo_unreserve(bo); > + if (r) { > + dev_err(dqm->dev->adev->dev, > + "Failed to evict MQD of queue %d to GTT: %d\n", > + q->properties.queue_id, r); > + return; > + } > + q->needs_mqd_repin = true; > +} > + > +/* Repin the MQD BO to VRAM on resume. Gated on the per-queue needs_mqd_repin flag > + * rather than adev->in_s4, because in_s4 is cleared (PM_POST_HIBERNATION) partway > + * through the KFD resume sequence and is unreliable here. The BO may come back at a > + * new VRAM address, so refresh the cached copies (mqd_mem_obj->gpu_addr, > + * q->gart_mqd_addr, the kernel mapping, and the self-address inside the MQD) before > + * the queue is handed back to HW. > + */ > +static int dqm_repin_mqd_bo(struct device_queue_manager *dqm, struct queue *q) > +{ > + struct mqd_manager *mqd_mgr; > + struct amdgpu_bo *bo; > + void *cpu_ptr; > + int r; > + > + if (!q->needs_mqd_repin) > + return 0; > + if (!q->mqd_mem_obj || !q->mqd_mem_obj->mem) > + return 0; > + > + bo = q->mqd_mem_obj->mem; > + r = amdgpu_bo_reserve(bo, false); > + if (r) > + return r; > + r = amdgpu_bo_pin(bo, AMDGPU_GEM_DOMAIN_VRAM); > + if (r) { > + amdgpu_bo_unreserve(bo); > + dev_err(dqm->dev->adev->dev, > + "Failed to repin MQD of queue %d to VRAM: %d\n", > + q->properties.queue_id, r); > + return r; > + } > + /* The BO may have moved; refresh the kernel mapping and gpu address. */ > + amdgpu_bo_kunmap(bo); > + r = amdgpu_bo_kmap(bo, &cpu_ptr); > + amdgpu_bo_unreserve(bo); > + if (r) { > + dev_err(dqm->dev->adev->dev, > + "Failed to remap MQD of queue %d: %d\n", > + q->properties.queue_id, r); > + return r; > + } > + > + q->mqd_mem_obj->cpu_ptr = cpu_ptr; > + q->mqd_mem_obj->gpu_addr = amdgpu_bo_gpu_offset(bo); > + q->gart_mqd_addr = q->mqd_mem_obj->gpu_addr; > + q->mqd = cpu_ptr; > + > + mqd_mgr = dqm->mqd_mgrs[get_mqd_type_from_queue_type( > + q->properties.type)]; > + if (mqd_mgr->update_mqd_gpu_addr) > + mqd_mgr->update_mqd_gpu_addr(mqd_mgr, q->mqd, > + q->mqd_mem_obj, > + &q->properties); We should add some kind of warning or even prevent evicting MQDs in the first place (with a warning) if mqd_mgr->update_mqd_gpu_addr is not set. E.g. I expect that we'll move MQDs to VRAM on GFX12 as well, so we'll need an update_mqd_ gpu_addr function there, too. If we forget this, we'll likely get unexpected crashes. Other than that, this patch looks good to me. Regards, Felix > + > + q->needs_mqd_repin = false; > + return 0; > +} > + > static int evict_process_queues_nocpsch(struct device_queue_manager *dqm, > struct qcm_process_device *qpd) > { > @@ -1297,6 +1397,8 @@ static int evict_process_queues_nocpsch(struct device_queue_manager *dqm, > * maintain a consistent eviction state > */ > ret = retval; > + > + dqm_evict_mqd_bo(dqm, q); > } > > out: > @@ -1350,6 +1452,8 @@ static int evict_process_queues_cpsch(struct device_queue_manager *dqm, > goto out; > } > } > + > + dqm_evict_mqd_bo(dqm, q); > } > > if (!dqm->dev->kfd->shared_resources.enable_mes) { > @@ -1429,6 +1533,10 @@ static int restore_process_queues_nocpsch(struct device_queue_manager *dqm, > if (WARN_ONCE(!dqm->sched_running, "Restore when stopped\n")) > continue; > > + retval = dqm_repin_mqd_bo(dqm, q); > + if (retval && !ret) > + ret = retval; > + > retval = mqd_mgr->load_mqd(mqd_mgr, q->mqd, q->pipe, > q->queue, &q->properties, mm); > if (retval && !ret) > @@ -1489,6 +1597,13 @@ static int restore_process_queues_cpsch(struct device_queue_manager *dqm, > q->properties.is_active = true; > increment_queue_count(dqm, &pdd->qpd, q); > > + retval = dqm_repin_mqd_bo(dqm, q); > + if (retval) { > + dev_err(dev, "Failed to repin MQD for queue %d\n", > + q->properties.queue_id); > + goto out; > + } > + > if (dqm->dev->kfd->shared_resources.enable_mes) { > retval = add_queue_mes(dqm, q, qpd); > if (retval) { > diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.h b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.h > index 59eff3389d39..38b46b696243 100644 > --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.h > +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager.h > @@ -117,6 +117,14 @@ struct mqd_manager { > const void *ctl_stack_src, > const u32 ctl_stack_size); > > + /* Patch the MQD's cached self GPU address after the MQD BO has moved > + * (e.g. repinned to a new VRAM location on hibernation resume). The MQD > + * contents are otherwise preserved. > + */ > + void (*update_mqd_gpu_addr)(struct mqd_manager *mm, void *mqd, > + struct kfd_mem_obj *mqd_mem_obj, > + struct queue_properties *p); > + > #if defined(CONFIG_DEBUG_FS) > int (*debugfs_show_mqd)(struct seq_file *m, void *data); > #endif > diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c > index 75e5a9f67d50..b95720198e28 100644 > --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c > +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c > @@ -476,6 +476,20 @@ static void restore_mqd(struct mqd_manager *mm, void **mqd, > qp->is_active = 0; > } > > +static void update_mqd_gpu_addr(struct mqd_manager *mm, void *mqd, > + struct kfd_mem_obj *mqd_mem_obj, > + struct queue_properties *qp) > +{ > + struct v9_mqd *m = get_mqd(mqd); > + uint64_t addr = mqd_mem_obj->gpu_addr; > + > + m->cp_mqd_base_addr_lo = lower_32_bits(addr); > + m->cp_mqd_base_addr_hi = upper_32_bits(addr); > + > + if (mqd_on_vram(mm->dev->adev)) > + amdgpu_device_flush_hdp(mm->dev->adev, NULL); > +} > + > static void init_mqd_hiq(struct mqd_manager *mm, void **mqd, > struct kfd_mem_obj *mqd_mem_obj, uint64_t *gart_addr, > struct queue_properties *q) > @@ -860,6 +874,30 @@ static void restore_mqd_v9_4_3(struct mqd_manager *mm, void **mqd, > if (mqd_on_vram(mm->dev->adev)) > amdgpu_device_flush_hdp(mm->dev->adev, NULL); > } > + > +static void update_mqd_gpu_addr_v9_4_3(struct mqd_manager *mm, void *mqd, > + struct kfd_mem_obj *mqd_mem_obj, > + struct queue_properties *qp) > +{ > + struct kfd_mem_obj xcc_mqd_mem_obj; > + uint64_t offset = mm->mqd_stride(mm, qp); > + u32 num_xcc = NUM_XCC(mm->dev->xcc_mask); > + struct v9_mqd *m; > + int xcc; > + > + memset(&xcc_mqd_mem_obj, 0x0, sizeof(struct kfd_mem_obj)); > + > + for (xcc = 0; xcc < num_xcc; xcc++) { > + get_xcc_mqd(mqd_mem_obj, &xcc_mqd_mem_obj, offset * xcc); > + m = get_mqd(mqd + offset * xcc); > + m->cp_mqd_base_addr_lo = lower_32_bits(xcc_mqd_mem_obj.gpu_addr); > + m->cp_mqd_base_addr_hi = upper_32_bits(xcc_mqd_mem_obj.gpu_addr); > + } > + > + if (mqd_on_vram(mm->dev->adev)) > + amdgpu_device_flush_hdp(mm->dev->adev, NULL); > +} > + > static int destroy_mqd_v9_4_3(struct mqd_manager *mm, void *mqd, > enum kfd_preempt_type type, unsigned int timeout, > uint32_t pipe_id, uint32_t queue_id) > @@ -1017,6 +1055,7 @@ struct mqd_manager *mqd_manager_init_v9(enum KFD_MQD_TYPE type, > mqd->get_wave_state = get_wave_state_v9_4_3; > mqd->checkpoint_mqd = checkpoint_mqd_v9_4_3; > mqd->restore_mqd = restore_mqd_v9_4_3; > + mqd->update_mqd_gpu_addr = update_mqd_gpu_addr_v9_4_3; > } else { > mqd->init_mqd = init_mqd; > mqd->load_mqd = load_mqd; > @@ -1025,6 +1064,7 @@ struct mqd_manager *mqd_manager_init_v9(enum KFD_MQD_TYPE type, > mqd->get_wave_state = get_wave_state; > mqd->checkpoint_mqd = checkpoint_mqd; > mqd->restore_mqd = restore_mqd; > + mqd->update_mqd_gpu_addr = update_mqd_gpu_addr; > } > break; > case KFD_MQD_TYPE_HIQ: > diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h > index 88191a4c1657..3d0ffcc91e3b 100644 > --- a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h > +++ b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h > @@ -637,6 +637,12 @@ struct queue { > void *gang_ctx_cpu_ptr; > > struct amdgpu_bo *wptr_bo_gart; > + > + /* The VRAM-resident MQD BO (mqd_on_vram()) is unpinned at S4 suspend so > + * TTM evicts it into the hibernation image, and repinned on resume. Set > + * while the BO is unpinned so the resume path knows to repin it. > + */ > + bool needs_mqd_repin; > }; > > enum KFD_MQD_TYPE {