[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