[PATCH 16/25] drm/amdkfd: Implement KFD process eviction/restore

Oded Gabbay oded.gabbay at gmail.com
Mon Feb 12 09:36:37 UTC 2018


On Wed, Feb 7, 2018 at 3:32 AM, Felix Kuehling <Felix.Kuehling at amd.com> wrote:
> When the TTM memory manager in KGD evicts BOs, all user mode queues
> potentially accessing these BOs must be evicted temporarily. Once
> user mode queues are evicted, the eviction fence is signaled,
> allowing the migration of the BO to proceed.
>
> A delayed worker is scheduled to restore all the BOs belonging to
> the evicted process and restart its queues.
>
> During suspend/resume of the GPU we also evict all processes to allow
> KGD to save BOs in system memory, since VRAM will be lost.
>
> v2:
> * Account for eviction when updating of q->is_active in MQD manager
>
> Signed-off-by: Harish Kasiviswanathan <Harish.Kasiviswanathan at amd.com>
> Signed-off-by: Felix Kuehling <Felix.Kuehling at amd.com>
> ---
>  drivers/gpu/drm/amd/amdkfd/kfd_device.c            |  65 +++++-
>  .../gpu/drm/amd/amdkfd/kfd_device_queue_manager.c  | 219 ++++++++++++++++++++-
>  .../gpu/drm/amd/amdkfd/kfd_device_queue_manager.h  |   9 +
>  drivers/gpu/drm/amd/amdkfd/kfd_module.c            |   2 +
>  drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_cik.c   |   9 +-
>  drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c    |   6 +-
>  drivers/gpu/drm/amd/amdkfd/kfd_priv.h              |  32 ++-
>  drivers/gpu/drm/amd/amdkfd/kfd_process.c           | 213 ++++++++++++++++++++
>  8 files changed, 547 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device.c b/drivers/gpu/drm/amd/amdkfd/kfd_device.c
> index 4ac2d61..334669996 100644
> --- a/drivers/gpu/drm/amd/amdkfd/kfd_device.c
> +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device.c
> @@ -33,6 +33,7 @@
>  #include "kfd_iommu.h"
>
>  #define MQD_SIZE_ALIGNED 768
> +static atomic_t kfd_device_suspended = ATOMIC_INIT(0);
>
>  #ifdef KFD_SUPPORT_IOMMU_V2
>  static const struct kfd_device_info kaveri_device_info = {
> @@ -469,6 +470,10 @@ void kgd2kfd_suspend(struct kfd_dev *kfd)
>         if (!kfd->init_complete)
>                 return;
>
> +       /* For first KFD device suspend all the KFD processes */
> +       if (atomic_inc_return(&kfd_device_suspended) == 1)
> +               kfd_suspend_all_processes();
> +
>         kfd->dqm->ops.stop(kfd->dqm);
>
>         kfd_iommu_suspend(kfd);
> @@ -476,11 +481,21 @@ void kgd2kfd_suspend(struct kfd_dev *kfd)
>
>  int kgd2kfd_resume(struct kfd_dev *kfd)
>  {
> +       int ret, count;
> +
>         if (!kfd->init_complete)
>                 return 0;
>
> -       return kfd_resume(kfd);
> +       ret = kfd_resume(kfd);
> +       if (ret)
> +               return ret;
> +
> +       count = atomic_dec_return(&kfd_device_suspended);
> +       WARN_ONCE(count < 0, "KFD suspend / resume ref. error");
> +       if (count == 0)
> +               ret = kfd_resume_all_processes();
>
> +       return ret;
>  }
>
>  static int kfd_resume(struct kfd_dev *kfd)
> @@ -526,6 +541,54 @@ void kgd2kfd_interrupt(struct kfd_dev *kfd, const void *ih_ring_entry)
>         spin_unlock(&kfd->interrupt_lock);
>  }
>
> +/** kgd2kfd_schedule_evict_and_restore_process - Schedules work queue that will
> + *   prepare for safe eviction of KFD BOs that belong to the specified
> + *   process.
> + *
> + * @mm: mm_struct that identifies the specified KFD process
> + * @fence: eviction fence attached to KFD process BOs
> + *
> + */
> +int kgd2kfd_schedule_evict_and_restore_process(struct mm_struct *mm,
> +                                              struct dma_fence *fence)
> +{
> +       struct kfd_process *p;
> +       unsigned long active_time;
> +       unsigned long delay_jiffies = msecs_to_jiffies(PROCESS_ACTIVE_TIME_MS);
> +
> +       if (!fence)
> +               return -EINVAL;
> +
> +       if (dma_fence_is_signaled(fence))
> +               return 0;
> +
> +       p = kfd_lookup_process_by_mm(mm);
> +       if (!p)
> +               return -ENODEV;
> +
> +       if (fence->seqno == p->last_eviction_seqno)
> +               goto out;
> +
> +       p->last_eviction_seqno = fence->seqno;
> +
> +       /* Avoid KFD process starvation. Wait for at least
> +        * PROCESS_ACTIVE_TIME_MS before evicting the process again
> +        */
> +       active_time = get_jiffies_64() - p->last_restore_timestamp;
> +       if (delay_jiffies > active_time)
> +               delay_jiffies -= active_time;
> +       else
> +               delay_jiffies = 0;
> +
> +       /* During process initialization eviction_work.dwork is initialized
> +        * to kfd_evict_bo_worker
> +        */
> +       schedule_delayed_work(&p->eviction_work, delay_jiffies);
> +out:
> +       kfd_unref_process(p);
> +       return 0;
> +}
> +
>  static int kfd_gtt_sa_init(struct kfd_dev *kfd, unsigned int buf_size,
>                                 unsigned int chunk_size)
>  {
> 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 b7d0639..b3b6dab 100644
> --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c
> +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c
> @@ -21,10 +21,11 @@
>   *
>   */
>
> +#include <linux/ratelimit.h>
> +#include <linux/printk.h>
>  #include <linux/slab.h>
>  #include <linux/list.h>
>  #include <linux/types.h>
> -#include <linux/printk.h>
>  #include <linux/bitops.h>
>  #include <linux/sched.h>
>  #include "kfd_priv.h"
> @@ -180,6 +181,14 @@ static int create_queue_nocpsch(struct device_queue_manager *dqm,
>                         goto out_unlock;
>         }
>         q->properties.vmid = qpd->vmid;
> +       /*
> +        * Eviction state logic: we only mark active queues as evicted
> +        * to avoid the overhead of restoring inactive queues later
> +        */
> +       if (qpd->evicted)
> +               q->properties.is_evicted = (q->properties.queue_size > 0 &&
> +                                           q->properties.queue_percent > 0 &&
> +                                           q->properties.queue_address != 0);
>
>         q->properties.tba_addr = qpd->tba_addr;
>         q->properties.tma_addr = qpd->tma_addr;
> @@ -377,15 +386,29 @@ static int update_queue(struct device_queue_manager *dqm, struct queue *q)
>  {
>         int retval;
>         struct mqd_manager *mqd;
> +       struct kfd_process_device *pdd;
>         bool prev_active = false;
>
>         mutex_lock(&dqm->lock);
> +       pdd = kfd_get_process_device_data(q->device, q->process);
> +       if (!pdd) {
> +               retval = -ENODEV;
> +               goto out_unlock;
> +       }
>         mqd = dqm->ops.get_mqd_manager(dqm,
>                         get_mqd_type_from_queue_type(q->properties.type));
>         if (!mqd) {
>                 retval = -ENOMEM;
>                 goto out_unlock;
>         }
> +       /*
> +        * Eviction state logic: we only mark active queues as evicted
> +        * to avoid the overhead of restoring inactive queues later
> +        */
> +       if (pdd->qpd.evicted)
> +               q->properties.is_evicted = (q->properties.queue_size > 0 &&
> +                                           q->properties.queue_percent > 0 &&
> +                                           q->properties.queue_address != 0);
>
>         /* Save previous activity state for counters */
>         prev_active = q->properties.is_active;
> @@ -457,6 +480,187 @@ static struct mqd_manager *get_mqd_manager(
>         return mqd;
>  }
>
> +static int evict_process_queues_nocpsch(struct device_queue_manager *dqm,
> +                                       struct qcm_process_device *qpd)
> +{
> +       struct queue *q;
> +       struct mqd_manager *mqd;
> +       struct kfd_process_device *pdd;
> +       int retval = 0;
> +
> +       mutex_lock(&dqm->lock);
> +       if (qpd->evicted++ > 0) /* already evicted, do nothing */
> +               goto out;
> +
> +       pdd = qpd_to_pdd(qpd);
> +       pr_info_ratelimited("Evicting PASID %u queues\n",
> +                           pdd->process->pasid);
> +
> +       /* unactivate all active queues on the qpd */
> +       list_for_each_entry(q, &qpd->queues_list, list) {
> +               if (!q->properties.is_active)
> +                       continue;
> +               mqd = dqm->ops.get_mqd_manager(dqm,
> +                       get_mqd_type_from_queue_type(q->properties.type));
> +               if (!mqd) { /* should not be here */
> +                       pr_err("Cannot evict queue, mqd mgr is NULL\n");
> +                       retval = -ENOMEM;
> +                       goto out;
> +               }
> +               q->properties.is_evicted = true;
> +               q->properties.is_active = false;
> +               retval = mqd->destroy_mqd(mqd, q->mqd,
> +                               KFD_PREEMPT_TYPE_WAVEFRONT_DRAIN,
> +                               KFD_UNMAP_LATENCY_MS, q->pipe, q->queue);
> +               if (retval)
> +                       goto out;
> +               dqm->queue_count--;
> +       }
> +
> +out:
> +       mutex_unlock(&dqm->lock);
> +       return retval;
> +}
> +
> +static int evict_process_queues_cpsch(struct device_queue_manager *dqm,
> +                                     struct qcm_process_device *qpd)
> +{
> +       struct queue *q;
> +       struct kfd_process_device *pdd;
> +       int retval = 0;
> +
> +       mutex_lock(&dqm->lock);
> +       if (qpd->evicted++ > 0) /* already evicted, do nothing */
> +               goto out;
> +
> +       pdd = qpd_to_pdd(qpd);
> +       pr_info_ratelimited("Evicting PASID %u queues\n",
> +                           pdd->process->pasid);
> +
> +       /* unactivate all active queues on the qpd */
> +       list_for_each_entry(q, &qpd->queues_list, list) {
> +               if (!q->properties.is_active)
> +                       continue;
> +               q->properties.is_evicted = true;
> +               q->properties.is_active = false;
> +               dqm->queue_count--;
> +       }
> +       retval = execute_queues_cpsch(dqm,
> +                               qpd->is_debug ?
> +                               KFD_UNMAP_QUEUES_FILTER_ALL_QUEUES :
> +                               KFD_UNMAP_QUEUES_FILTER_DYNAMIC_QUEUES, 0);
> +
> +out:
> +       mutex_unlock(&dqm->lock);
> +       return retval;
> +}
> +
> +static int restore_process_queues_nocpsch(struct device_queue_manager *dqm,
> +                                         struct qcm_process_device *qpd)
> +{
> +       struct queue *q;
> +       struct mqd_manager *mqd;
> +       struct kfd_process_device *pdd;
> +       uint32_t pd_base;
> +       int retval = 0;
> +
> +       pdd = qpd_to_pdd(qpd);
> +       /* Retrieve PD base */
> +       pd_base = dqm->dev->kfd2kgd->get_process_page_dir(pdd->vm);
> +
> +       mutex_lock(&dqm->lock);
> +       if (WARN_ON_ONCE(!qpd->evicted)) /* already restored, do nothing */
> +               goto out;
> +       if (qpd->evicted > 1) { /* ref count still > 0, decrement & quit */
> +               qpd->evicted--;
> +               goto out;
> +       }
> +
> +       pr_info_ratelimited("Restoring PASID %u queues\n",
> +                           pdd->process->pasid);
> +
> +       /* Update PD Base in QPD */
> +       qpd->page_table_base = pd_base;
> +       pr_debug("Updated PD address to 0x%08x\n", pd_base);
> +
> +       if (!list_empty(&qpd->queues_list)) {
> +               dqm->dev->kfd2kgd->set_vm_context_page_table_base(
> +                               dqm->dev->kgd,
> +                               qpd->vmid,
> +                               qpd->page_table_base);
> +               kfd_flush_tlb(pdd);
> +       }
> +
> +       /* activate all active queues on the qpd */
> +       list_for_each_entry(q, &qpd->queues_list, list) {
> +               if (!q->properties.is_evicted)
> +                       continue;
> +               mqd = dqm->ops.get_mqd_manager(dqm,
> +                       get_mqd_type_from_queue_type(q->properties.type));
> +               if (!mqd) { /* should not be here */
> +                       pr_err("Cannot restore queue, mqd mgr is NULL\n");
> +                       retval = -ENOMEM;
> +                       goto out;
> +               }
> +               q->properties.is_evicted = false;
> +               q->properties.is_active = true;
> +               retval = mqd->load_mqd(mqd, q->mqd, q->pipe,
> +                                      q->queue, &q->properties,
> +                                      q->process->mm);
> +               if (retval)
> +                       goto out;
> +               dqm->queue_count++;
> +       }
> +       qpd->evicted = 0;
> +out:
> +       mutex_unlock(&dqm->lock);
> +       return retval;
> +}
> +
> +static int restore_process_queues_cpsch(struct device_queue_manager *dqm,
> +                                       struct qcm_process_device *qpd)
> +{
> +       struct queue *q;
> +       struct kfd_process_device *pdd;
> +       uint32_t pd_base;
> +       int retval = 0;
> +
> +       pdd = qpd_to_pdd(qpd);
> +       /* Retrieve PD base */
> +       pd_base = dqm->dev->kfd2kgd->get_process_page_dir(pdd->vm);
> +
> +       mutex_lock(&dqm->lock);
> +       if (WARN_ON_ONCE(!qpd->evicted)) /* already restored, do nothing */
> +               goto out;
> +       if (qpd->evicted > 1) { /* ref count still > 0, decrement & quit */
> +               qpd->evicted--;
> +               goto out;
> +       }
> +
> +       pr_info_ratelimited("Restoring PASID %u queues\n",
> +                           pdd->process->pasid);
> +
> +       /* Update PD Base in QPD */
> +       qpd->page_table_base = pd_base;
> +       pr_debug("Updated PD address to 0x%08x\n", pd_base);
> +
> +       /* activate all active queues on the qpd */
> +       list_for_each_entry(q, &qpd->queues_list, list) {
> +               if (!q->properties.is_evicted)
> +                       continue;
> +               q->properties.is_evicted = false;
> +               q->properties.is_active = true;
> +               dqm->queue_count++;
> +       }
> +       retval = execute_queues_cpsch(dqm,
> +                               KFD_UNMAP_QUEUES_FILTER_DYNAMIC_QUEUES, 0);
> +       if (!retval)
> +               qpd->evicted = 0;
> +out:
> +       mutex_unlock(&dqm->lock);
> +       return retval;
> +}
> +
>  static int register_process(struct device_queue_manager *dqm,
>                                         struct qcm_process_device *qpd)
>  {
> @@ -853,6 +1057,14 @@ static int create_queue_cpsch(struct device_queue_manager *dqm, struct queue *q,
>                 retval = -ENOMEM;
>                 goto out;
>         }
> +       /*
> +        * Eviction state logic: we only mark active queues as evicted
> +        * to avoid the overhead of restoring inactive queues later
> +        */
> +       if (qpd->evicted)
> +               q->properties.is_evicted = (q->properties.queue_size > 0 &&
> +                                           q->properties.queue_percent > 0 &&
> +                                           q->properties.queue_address != 0);
>
>         dqm->asic_ops.init_sdma_vm(dqm, q, qpd);
>
> @@ -1291,6 +1503,8 @@ struct device_queue_manager *device_queue_manager_init(struct kfd_dev *dev)
>                 dqm->ops.set_cache_memory_policy = set_cache_memory_policy;
>                 dqm->ops.set_trap_handler = set_trap_handler;
>                 dqm->ops.process_termination = process_termination_cpsch;
> +               dqm->ops.evict_process_queues = evict_process_queues_cpsch;
> +               dqm->ops.restore_process_queues = restore_process_queues_cpsch;
>                 break;
>         case KFD_SCHED_POLICY_NO_HWS:
>                 /* initialize dqm for no cp scheduling */
> @@ -1307,6 +1521,9 @@ struct device_queue_manager *device_queue_manager_init(struct kfd_dev *dev)
>                 dqm->ops.set_cache_memory_policy = set_cache_memory_policy;
>                 dqm->ops.set_trap_handler = set_trap_handler;
>                 dqm->ops.process_termination = process_termination_nocpsch;
> +               dqm->ops.evict_process_queues = evict_process_queues_nocpsch;
> +               dqm->ops.restore_process_queues =
> +                       restore_process_queues_nocpsch;
>                 break;
>         default:
>                 pr_err("Invalid scheduling policy %d\n", dqm->sched_policy);
> diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.h b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.h
> index 68be0aa..412beff 100644
> --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.h
> +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.h
> @@ -79,6 +79,10 @@ struct device_process_node {
>   *
>   * @process_termination: Clears all process queues belongs to that device.
>   *
> + * @evict_process_queues: Evict all active queues of a process
> + *
> + * @restore_process_queues: Restore all evicted queues queues of a process
> + *
>   */
>
>  struct device_queue_manager_ops {
> @@ -129,6 +133,11 @@ struct device_queue_manager_ops {
>
>         int (*process_termination)(struct device_queue_manager *dqm,
>                         struct qcm_process_device *qpd);
> +
> +       int (*evict_process_queues)(struct device_queue_manager *dqm,
> +                                   struct qcm_process_device *qpd);
> +       int (*restore_process_queues)(struct device_queue_manager *dqm,
> +                                     struct qcm_process_device *qpd);
>  };
>
>  struct device_queue_manager_asic_ops {
> diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_module.c b/drivers/gpu/drm/amd/amdkfd/kfd_module.c
> index 3ac72be..65574c6 100644
> --- a/drivers/gpu/drm/amd/amdkfd/kfd_module.c
> +++ b/drivers/gpu/drm/amd/amdkfd/kfd_module.c
> @@ -43,6 +43,8 @@ static const struct kgd2kfd_calls kgd2kfd = {
>         .interrupt      = kgd2kfd_interrupt,
>         .suspend        = kgd2kfd_suspend,
>         .resume         = kgd2kfd_resume,
> +       .schedule_evict_and_restore_process =
> +                         kgd2kfd_schedule_evict_and_restore_process,
>  };
>
>  int sched_policy = KFD_SCHED_POLICY_HWS;
> diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_cik.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_cik.c
> index fbe3f83..c00c325 100644
> --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_cik.c
> +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_cik.c
> @@ -202,7 +202,8 @@ static int __update_mqd(struct mqd_manager *mm, void *mqd,
>
>         q->is_active = (q->queue_size > 0 &&
>                         q->queue_address != 0 &&
> -                       q->queue_percent > 0);
> +                       q->queue_percent > 0 &&
> +                       !q->is_evicted);
>
>         return 0;
>  }
> @@ -245,7 +246,8 @@ static int update_mqd_sdma(struct mqd_manager *mm, void *mqd,
>
>         q->is_active = (q->queue_size > 0 &&
>                         q->queue_address != 0 &&
> -                       q->queue_percent > 0);
> +                       q->queue_percent > 0 &&
> +                       !q->is_evicted);
>
>         return 0;
>  }
> @@ -377,7 +379,8 @@ static int update_mqd_hiq(struct mqd_manager *mm, void *mqd,
>
>         q->is_active = (q->queue_size > 0 &&
>                         q->queue_address != 0 &&
> -                       q->queue_percent > 0);
> +                       q->queue_percent > 0 &&
> +                       !q->is_evicted);
>
>         return 0;
>  }
> diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c
> index 58221c1..89e4242 100644
> --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c
> +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_vi.c
> @@ -198,7 +198,8 @@ static int __update_mqd(struct mqd_manager *mm, void *mqd,
>
>         q->is_active = (q->queue_size > 0 &&
>                         q->queue_address != 0 &&
> -                       q->queue_percent > 0);
> +                       q->queue_percent > 0 &&
> +                       !q->is_evicted);
>
>         return 0;
>  }
> @@ -342,7 +343,8 @@ static int update_mqd_sdma(struct mqd_manager *mm, void *mqd,
>
>         q->is_active = (q->queue_size > 0 &&
>                         q->queue_address != 0 &&
> -                       q->queue_percent > 0);
> +                       q->queue_percent > 0 &&
> +                       !q->is_evicted);
>
>         return 0;
>  }
> diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h
> index 56c2e36..cac7aa2 100644
> --- a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h
> +++ b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h
> @@ -335,7 +335,11 @@ enum kfd_queue_format {
>   * @is_interop: Defines if this is a interop queue. Interop queue means that
>   * the queue can access both graphics and compute resources.
>   *
> - * @is_active: Defines if the queue is active or not.
> + * @is_evicted: Defines if the queue is evicted. Only active queues
> + * are evicted, rendering them inactive.
> + *
> + * @is_active: Defines if the queue is active or not. @is_active and
> + * @is_evicted are protected by the DQM lock.
>   *
>   * @vmid: If the scheduling mode is no cp scheduling the field defines the vmid
>   * of the queue.
> @@ -357,6 +361,7 @@ struct queue_properties {
>         uint32_t __iomem *doorbell_ptr;
>         uint32_t doorbell_off;
>         bool is_interop;
> +       bool is_evicted;
>         bool is_active;
>         /* Not relevant for user mode queues in cp scheduling */
>         unsigned int vmid;
> @@ -460,6 +465,7 @@ struct qcm_process_device {
>         unsigned int queue_count;
>         unsigned int vmid;
>         bool is_debug;
> +       unsigned int evicted; /* eviction counter, 0=active */
>
>         /* This flag tells if we should reset all wavefronts on
>          * process termination
> @@ -486,6 +492,17 @@ struct qcm_process_device {
>         uint64_t tma_addr;
>  };
>
> +/* KFD Memory Eviction */
> +
> +/* Approx. wait time before attempting to restore evicted BOs */
> +#define PROCESS_RESTORE_TIME_MS 100
> +/* Approx. back off time if restore fails due to lack of memory */
> +#define PROCESS_BACK_OFF_TIME_MS 100
> +/* Approx. time before evicting the process again */
> +#define PROCESS_ACTIVE_TIME_MS 10
> +
> +int kgd2kfd_schedule_evict_and_restore_process(struct mm_struct *mm,
> +                                              struct dma_fence *fence);
>
>  enum kfd_pdd_bound {
>         PDD_UNBOUND = 0,
> @@ -600,6 +617,16 @@ struct kfd_process {
>          * during restore
>          */
>         struct dma_fence *ef;
> +
> +       /* Work items for evicting and restoring BOs */
> +       struct delayed_work eviction_work;
> +       struct delayed_work restore_work;
> +       /* seqno of the last scheduled eviction */
> +       unsigned int last_eviction_seqno;
> +       /* Approx. the last timestamp (in jiffies) when the process was
> +        * restored after an eviction
> +        */
> +       unsigned long last_restore_timestamp;
>  };
>
>  #define KFD_PROCESS_TABLE_SIZE 5 /* bits: 32 entries */
> @@ -629,7 +656,10 @@ void kfd_process_destroy_wq(void);
>  struct kfd_process *kfd_create_process(struct file *filep);
>  struct kfd_process *kfd_get_process(const struct task_struct *);
>  struct kfd_process *kfd_lookup_process_by_pasid(unsigned int pasid);
> +struct kfd_process *kfd_lookup_process_by_mm(const struct mm_struct *mm);
>  void kfd_unref_process(struct kfd_process *p);
> +void kfd_suspend_all_processes(void);
> +int kfd_resume_all_processes(void);
>
>  struct kfd_process_device *kfd_bind_process_to_device(struct kfd_dev *dev,
>                                                 struct kfd_process *p);
> diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_process.c b/drivers/gpu/drm/amd/amdkfd/kfd_process.c
> index cf4fa25..18b2b86 100644
> --- a/drivers/gpu/drm/amd/amdkfd/kfd_process.c
> +++ b/drivers/gpu/drm/amd/amdkfd/kfd_process.c
> @@ -55,6 +55,9 @@ static struct kfd_process *create_process(const struct task_struct *thread,
>                                         struct file *filep);
>  static int kfd_process_init_cwsr(struct kfd_process *p, struct file *filep);
>
> +static void evict_process_worker(struct work_struct *work);
> +static void restore_process_worker(struct work_struct *work);
> +
>
>  void kfd_process_create_wq(void)
>  {
> @@ -230,6 +233,9 @@ static void kfd_process_notifier_release(struct mmu_notifier *mn,
>         mutex_unlock(&kfd_processes_mutex);
>         synchronize_srcu(&kfd_processes_srcu);
>
> +       cancel_delayed_work_sync(&p->eviction_work);
> +       cancel_delayed_work_sync(&p->restore_work);
> +
>         mutex_lock(&p->mutex);
>
>         /* Iterate over all process device data structures and if the
> @@ -351,6 +357,10 @@ static struct kfd_process *create_process(const struct task_struct *thread,
>         if (err != 0)
>                 goto err_init_apertures;
>
> +       INIT_DELAYED_WORK(&process->eviction_work, evict_process_worker);
> +       INIT_DELAYED_WORK(&process->restore_work, restore_process_worker);
> +       process->last_restore_timestamp = get_jiffies_64();
> +
>         err = kfd_process_init_cwsr(process, filep);
>         if (err)
>                 goto err_init_cwsr;
> @@ -402,6 +412,7 @@ struct kfd_process_device *kfd_create_process_device_data(struct kfd_dev *dev,
>         INIT_LIST_HEAD(&pdd->qpd.priv_queue_list);
>         pdd->qpd.dqm = dev->dqm;
>         pdd->qpd.pqm = &p->pqm;
> +       pdd->qpd.evicted = 0;
>         pdd->process = p;
>         pdd->bound = PDD_UNBOUND;
>         pdd->already_dequeued = false;
> @@ -490,6 +501,208 @@ struct kfd_process *kfd_lookup_process_by_pasid(unsigned int pasid)
>         return ret_p;
>  }
>
> +/* This increments the process->ref counter. */
> +struct kfd_process *kfd_lookup_process_by_mm(const struct mm_struct *mm)
> +{
> +       struct kfd_process *p;
> +
> +       int idx = srcu_read_lock(&kfd_processes_srcu);
> +
> +       p = find_process_by_mm(mm);
> +       if (p)
> +               kref_get(&p->ref);
> +
> +       srcu_read_unlock(&kfd_processes_srcu, idx);
> +
> +       return p;
> +}
> +
> +/* process_evict_queues - Evict all user queues of a process
> + *
> + * Eviction is reference-counted per process-device. This means multiple
> + * evictions from different sources can be nested safely.
> + */
> +static int process_evict_queues(struct kfd_process *p)
> +{
> +       struct kfd_process_device *pdd;
> +       int r = 0;
> +       unsigned int n_evicted = 0;
> +
> +       list_for_each_entry(pdd, &p->per_device_data, per_device_list) {
> +               r = pdd->dev->dqm->ops.evict_process_queues(pdd->dev->dqm,
> +                                                           &pdd->qpd);
> +               if (r) {
> +                       pr_err("Failed to evict process queues\n");
> +                       goto fail;
> +               }
> +               n_evicted++;
> +       }
> +
> +       return r;
> +
> +fail:
> +       /* To keep state consistent, roll back partial eviction by
> +        * restoring queues
> +        */
> +       list_for_each_entry(pdd, &p->per_device_data, per_device_list) {
> +               if (n_evicted == 0)
> +                       break;
> +               if (pdd->dev->dqm->ops.restore_process_queues(pdd->dev->dqm,
> +                                                             &pdd->qpd))
> +                       pr_err("Failed to restore queues\n");
> +
> +               n_evicted--;
> +       }
> +
> +       return r;
> +}
> +
> +/* process_restore_queues - Restore all user queues of a process */
> +static  int process_restore_queues(struct kfd_process *p)
> +{
> +       struct kfd_process_device *pdd;
> +       int r, ret = 0;
> +
> +       list_for_each_entry(pdd, &p->per_device_data, per_device_list) {
> +               r = pdd->dev->dqm->ops.restore_process_queues(pdd->dev->dqm,
> +                                                             &pdd->qpd);
> +               if (r) {
> +                       pr_err("Failed to restore process queues\n");
> +                       if (!ret)
> +                               ret = r;
> +               }
> +       }
> +
> +       return ret;
> +}
> +
> +static void evict_process_worker(struct work_struct *work)
> +{
> +       int ret;
> +       struct kfd_process *p;
> +       struct delayed_work *dwork;
> +
> +       dwork = to_delayed_work(work);
> +
> +       /* Process termination destroys this worker thread. So during the
> +        * lifetime of this thread, kfd_process p will be valid
> +        */
> +       p = container_of(dwork, struct kfd_process, eviction_work);
> +       WARN_ONCE(p->last_eviction_seqno != p->ef->seqno,
> +                 "Eviction fence mismatch\n");
> +
> +       /* Narrow window of overlap between restore and evict work
> +        * item is possible. Once amdgpu_amdkfd_gpuvm_restore_process_bos
> +        * unreserves KFD BOs, it is possible to evicted again. But
> +        * restore has few more steps of finish. So lets wait for any
> +        * previous restore work to complete
> +        */
> +       flush_delayed_work(&p->restore_work);
> +
> +       pr_debug("Started evicting pasid %d\n", p->pasid);
> +       ret = process_evict_queues(p);
> +       if (!ret) {
> +               dma_fence_signal(p->ef);
> +               dma_fence_put(p->ef);
> +               p->ef = NULL;
> +               schedule_delayed_work(&p->restore_work,
> +                               msecs_to_jiffies(PROCESS_RESTORE_TIME_MS));
> +
> +               pr_debug("Finished evicting pasid %d\n", p->pasid);
> +       } else
> +               pr_err("Failed to evict queues of pasid %d\n", p->pasid);
> +}
> +
> +static void restore_process_worker(struct work_struct *work)
> +{
> +       struct delayed_work *dwork;
> +       struct kfd_process *p;
> +       struct kfd_process_device *pdd;
> +       int ret = 0;
> +
> +       dwork = to_delayed_work(work);
> +
> +       /* Process termination destroys this worker thread. So during the
> +        * lifetime of this thread, kfd_process p will be valid
> +        */
> +       p = container_of(dwork, struct kfd_process, restore_work);
> +
> +       /* Call restore_process_bos on the first KGD device. This function
> +        * takes care of restoring the whole process including other devices.
> +        * Restore can fail if enough memory is not available. If so,
> +        * reschedule again.
> +        */
> +       pdd = list_first_entry(&p->per_device_data,
> +                              struct kfd_process_device,
> +                              per_device_list);
> +
> +       pr_debug("Started restoring pasid %d\n", p->pasid);
> +
> +       /* Setting last_restore_timestamp before successful restoration.
> +        * Otherwise this would have to be set by KGD (restore_process_bos)
> +        * before KFD BOs are unreserved. If not, the process can be evicted
> +        * again before the timestamp is set.
> +        * If restore fails, the timestamp will be set again in the next
> +        * attempt. This would mean that the minimum GPU quanta would be
> +        * PROCESS_ACTIVE_TIME_MS - (time to execute the following two
> +        * functions)
> +        */
> +
> +       p->last_restore_timestamp = get_jiffies_64();
> +       ret = pdd->dev->kfd2kgd->restore_process_bos(p->kgd_process_info,
> +                                                    &p->ef);
> +       if (ret) {
> +               pr_debug("Failed to restore BOs of pasid %d, retry after %d ms\n",
> +                        p->pasid, PROCESS_BACK_OFF_TIME_MS);
> +               ret = schedule_delayed_work(&p->restore_work,
> +                               msecs_to_jiffies(PROCESS_BACK_OFF_TIME_MS));
> +               WARN(!ret, "reschedule restore work failed\n");
> +               return;
> +       }
> +
> +       ret = process_restore_queues(p);
> +       if (!ret)
> +               pr_debug("Finished restoring pasid %d\n", p->pasid);
> +       else
> +               pr_err("Failed to restore queues of pasid %d\n", p->pasid);
> +}
> +
> +void kfd_suspend_all_processes(void)
> +{
> +       struct kfd_process *p;
> +       unsigned int temp;
> +       int idx = srcu_read_lock(&kfd_processes_srcu);
> +
> +       hash_for_each_rcu(kfd_processes_table, temp, p, kfd_processes) {
> +               cancel_delayed_work_sync(&p->eviction_work);
> +               cancel_delayed_work_sync(&p->restore_work);
> +
> +               if (process_evict_queues(p))
> +                       pr_err("Failed to suspend process %d\n", p->pasid);
> +               dma_fence_signal(p->ef);
> +               dma_fence_put(p->ef);
> +               p->ef = NULL;
> +       }
> +       srcu_read_unlock(&kfd_processes_srcu, idx);
> +}
> +
> +int kfd_resume_all_processes(void)
> +{
> +       struct kfd_process *p;
> +       unsigned int temp;
> +       int ret = 0, idx = srcu_read_lock(&kfd_processes_srcu);
> +
> +       hash_for_each_rcu(kfd_processes_table, temp, p, kfd_processes) {
> +               if (!schedule_delayed_work(&p->restore_work, 0)) {
> +                       pr_err("Restore process %d failed during resume\n",
> +                              p->pasid);
> +                       ret = -EFAULT;
> +               }
> +       }
> +       srcu_read_unlock(&kfd_processes_srcu, idx);
> +       return ret;
> +}
> +
>  int kfd_reserved_mem_mmap(struct kfd_process *process,
>                           struct vm_area_struct *vma)
>  {
> --
> 2.7.4
>

This patch is:
Acked-by: Oded Gabbay <oded.gabbay at gmail.com>


More information about the amd-gfx mailing list