Re: [PATCH v3] drm/amdkfd: preserve VRAM MQD across hibernation via GTT eviction

"Fan, Shikang" <[email protected]> Wed, 5 Aug 2026 10:09:08 +0000
Newsgroups org.freedesktop.lists.amd-gfx
Message-ID <SA1PR12MB7343D1E528A35A37555EE290EBD32@SA1PR12MB7343.namprd12.prod.outlook.com>
AMD General

Hi Christian,

I submitted a v4 version to drop that logic. That was a misreading on code, and I also did an experiment to dump mqd bo's header on suspend and resume and verified that mqd is indeed evicted by the regular eviction path. Corrected in v4 patch.

Regards,
Shikang

________________________________
From: Koenig, Christian <[email protected]>
Sent: Wednesday, August 5, 2026 4:40 PM
To: Fan, Shikang <[email protected]>; [email protected] <[email protected]>; Deucher, Alexander <[email protected]>; Kuehling, Felix <[email protected]>
Cc: Yang, Philip <[email protected]>; Limonciello, Mario <[email protected]>; SHANMUGAM, SRINIVASAN <[email protected]>; Zhang, Tiantian (Celine) <[email protected]>; Zhao, Victor <[email protected]>
Subject: Re: [PATCH v3] drm/amdkfd: preserve VRAM MQD across hibernation via GTT eviction

On 7/29/26 13: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.

That is clearly not correct. Unpinned BOs in VRAM are moved to GTT during suspend.

What can be is that you unpin the BO to late! In which case moving it to GTT using ttm_bo_validate would be illegal or at leats very quzestionable as well.

So that patch here is clearly incorrect.

Regards,
Christian.


>
> 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);
> +
> +     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 {