root/drivers/gpu/drm/xe/xe_hw_engine_group.c
// SPDX-License-Identifier: MIT
/*
 * Copyright © 2024 Intel Corporation
 */

#include <drm/drm_managed.h>

#include "xe_assert.h"
#include "xe_device_types.h"
#include "xe_exec_queue.h"
#include "xe_gt.h"
#include "xe_gt_stats.h"
#include "xe_hw_engine_group.h"
#include "xe_sync.h"
#include "xe_vm.h"

static void
hw_engine_group_resume_lr_jobs_func(struct work_struct *w)
{
        struct xe_exec_queue *q;
        struct xe_hw_engine_group *group = container_of(w, struct xe_hw_engine_group, resume_work);
        int err;
        enum xe_hw_engine_group_execution_mode previous_mode;

        err = xe_hw_engine_group_get_mode(group, EXEC_MODE_LR, &previous_mode,
                                          NULL, 0);
        if (err)
                return;

        if (previous_mode == EXEC_MODE_LR)
                goto put;

        list_for_each_entry(q, &group->exec_queue_list, hw_engine_group_link) {
                if (!xe_vm_in_fault_mode(q->vm))
                        continue;

                q->ops->resume(q);
        }

put:
        xe_hw_engine_group_put(group);
}

static struct xe_hw_engine_group *
hw_engine_group_alloc(struct xe_device *xe)
{
        struct xe_hw_engine_group *group;
        int err;

        group = drmm_kzalloc(&xe->drm, sizeof(*group), GFP_KERNEL);
        if (!group)
                return ERR_PTR(-ENOMEM);

        group->resume_wq = alloc_workqueue("xe-resume-lr-jobs-wq", WQ_PERCPU,
                                           0);
        if (!group->resume_wq)
                return ERR_PTR(-ENOMEM);

        err = drmm_add_action_or_reset(&xe->drm, __drmm_workqueue_release, group->resume_wq);
        if (err)
                return ERR_PTR(err);

        init_rwsem(&group->mode_sem);
        INIT_WORK(&group->resume_work, hw_engine_group_resume_lr_jobs_func);
        INIT_LIST_HEAD(&group->exec_queue_list);

        return group;
}

/**
 * xe_hw_engine_setup_groups() - Setup the hw engine groups for the gt
 * @gt: The gt for which groups are setup
 *
 * Return: 0 on success, negative error code on error.
 */
int xe_hw_engine_setup_groups(struct xe_gt *gt)
{
        struct xe_hw_engine *hwe;
        enum xe_hw_engine_id id;
        struct xe_hw_engine_group *group_rcs_ccs, *group_bcs, *group_vcs_vecs;
        struct xe_device *xe = gt_to_xe(gt);

        group_rcs_ccs = hw_engine_group_alloc(xe);
        if (IS_ERR(group_rcs_ccs))
                return PTR_ERR(group_rcs_ccs);

        group_bcs = hw_engine_group_alloc(xe);
        if (IS_ERR(group_bcs))
                return PTR_ERR(group_bcs);

        group_vcs_vecs = hw_engine_group_alloc(xe);
        if (IS_ERR(group_vcs_vecs))
                return PTR_ERR(group_vcs_vecs);

        for_each_hw_engine(hwe, gt, id) {
                switch (hwe->class) {
                case XE_ENGINE_CLASS_COPY:
                        hwe->hw_engine_group = group_bcs;
                        break;
                case XE_ENGINE_CLASS_RENDER:
                case XE_ENGINE_CLASS_COMPUTE:
                        hwe->hw_engine_group = group_rcs_ccs;
                        break;
                case XE_ENGINE_CLASS_VIDEO_DECODE:
                case XE_ENGINE_CLASS_VIDEO_ENHANCE:
                        hwe->hw_engine_group = group_vcs_vecs;
                        break;
                case XE_ENGINE_CLASS_OTHER:
                        break;
                case XE_ENGINE_CLASS_MAX:
                        xe_gt_assert(gt, false);
                }
        }

        return 0;
}

/**
 * xe_hw_engine_group_add_exec_queue() - Add an exec queue to a hw engine group
 * @group: The hw engine group
 * @q: The exec_queue
 *
 * Return: 0 on success,
 *          -EINTR if the lock could not be acquired
 */
int xe_hw_engine_group_add_exec_queue(struct xe_hw_engine_group *group, struct xe_exec_queue *q)
{
        int err;
        struct xe_device *xe = gt_to_xe(q->gt);

        xe_assert(xe, group);
        xe_assert(xe, !(q->flags & EXEC_QUEUE_FLAG_VM));
        xe_assert(xe, q->vm);

        if (xe_vm_in_preempt_fence_mode(q->vm))
                return 0;

        err = down_write_killable(&group->mode_sem);
        if (err)
                return err;

        if (xe_vm_in_fault_mode(q->vm) && group->cur_mode == EXEC_MODE_DMA_FENCE) {
                q->ops->suspend(q);
                err = q->ops->suspend_wait(q);
                if (err)
                        goto err_suspend;

                xe_hw_engine_group_resume_faulting_lr_jobs(group);
        }

        list_add(&q->hw_engine_group_link, &group->exec_queue_list);
        up_write(&group->mode_sem);

        return 0;

err_suspend:
        up_write(&group->mode_sem);
        return err;
}
ALLOW_ERROR_INJECTION(xe_hw_engine_group_add_exec_queue, ERRNO);

/**
 * xe_hw_engine_group_del_exec_queue() - Delete an exec queue from a hw engine group
 * @group: The hw engine group
 * @q: The exec_queue
 */
void xe_hw_engine_group_del_exec_queue(struct xe_hw_engine_group *group, struct xe_exec_queue *q)
{
        struct xe_device *xe = gt_to_xe(q->gt);

        xe_assert(xe, group);
        xe_assert(xe, q->vm);

        down_write(&group->mode_sem);

        if (!list_empty(&q->hw_engine_group_link))
                list_del(&q->hw_engine_group_link);

        up_write(&group->mode_sem);
}

/**
 * xe_hw_engine_group_resume_faulting_lr_jobs() - Asynchronously resume the hw engine group's
 * faulting LR jobs
 * @group: The hw engine group
 */
void xe_hw_engine_group_resume_faulting_lr_jobs(struct xe_hw_engine_group *group)
{
        queue_work(group->resume_wq, &group->resume_work);
}

/**
 * xe_hw_engine_group_suspend_faulting_lr_jobs() - Suspend the faulting LR jobs of this group
 * @group: The hw engine group
 * @has_deps: dma-fence job triggering suspend has dependencies
 *
 * Return: 0 on success, negative error code on error.
 */
static int xe_hw_engine_group_suspend_faulting_lr_jobs(struct xe_hw_engine_group *group,
                                                       bool has_deps)
{
        int err;
        struct xe_exec_queue *q;
        struct xe_gt *gt = NULL;
        bool need_resume = false;
        ktime_t start = xe_gt_stats_ktime_get();

        lockdep_assert_held_write(&group->mode_sem);

        list_for_each_entry(q, &group->exec_queue_list, hw_engine_group_link) {

                if (!xe_vm_in_fault_mode(q->vm))
                        continue;

                if (has_deps)
                        return -EAGAIN;

                xe_gt_stats_incr(q->gt, XE_GT_STATS_ID_HW_ENGINE_GROUP_SUSPEND_LR_QUEUE_COUNT, 1);
                need_resume = true;
                q->ops->suspend(q);
                gt = q->gt;
        }

        list_for_each_entry(q, &group->exec_queue_list, hw_engine_group_link) {
                if (!xe_vm_in_fault_mode(q->vm))
                        continue;

                err = q->ops->suspend_wait(q);
                if (err)
                        return err;
        }

        if (gt) {
                xe_gt_stats_incr(gt,
                                 XE_GT_STATS_ID_HW_ENGINE_GROUP_SUSPEND_LR_QUEUE_US,
                                 xe_gt_stats_ktime_us_delta(start));
        }

        if (need_resume)
                xe_hw_engine_group_resume_faulting_lr_jobs(group);

        return 0;
}

/**
 * xe_hw_engine_group_wait_for_dma_fence_jobs() - Wait for dma fence jobs to complete
 * @group: The hw engine group
 *
 * This function is not meant to be called directly from a user IOCTL as dma_fence_wait()
 * is not interruptible.
 *
 * Return: 0 on success,
 *         -ETIME if waiting for one job failed
 */
static int xe_hw_engine_group_wait_for_dma_fence_jobs(struct xe_hw_engine_group *group)
{
        long timeout;
        struct xe_exec_queue *q;
        struct xe_gt *gt = NULL;
        struct dma_fence *fence;
        ktime_t start = xe_gt_stats_ktime_get();

        lockdep_assert_held_write(&group->mode_sem);

        list_for_each_entry(q, &group->exec_queue_list, hw_engine_group_link) {
                if (xe_vm_in_lr_mode(q->vm))
                        continue;

                xe_gt_stats_incr(q->gt, XE_GT_STATS_ID_HW_ENGINE_GROUP_WAIT_DMA_QUEUE_COUNT, 1);
                fence = xe_exec_queue_last_fence_get_for_resume(q, q->vm);
                timeout = dma_fence_wait(fence, false);
                dma_fence_put(fence);
                gt = q->gt;

                if (timeout < 0)
                        return -ETIME;
        }

        if (gt) {
                xe_gt_stats_incr(gt,
                                 XE_GT_STATS_ID_HW_ENGINE_GROUP_WAIT_DMA_QUEUE_US,
                                 xe_gt_stats_ktime_us_delta(start));
        }

        return 0;
}

static int switch_mode(struct xe_hw_engine_group *group, bool has_deps)
{
        int err = 0;
        enum xe_hw_engine_group_execution_mode new_mode;

        lockdep_assert_held_write(&group->mode_sem);

        switch (group->cur_mode) {
        case EXEC_MODE_LR:
                new_mode = EXEC_MODE_DMA_FENCE;
                err = xe_hw_engine_group_suspend_faulting_lr_jobs(group,
                                                                  has_deps);
                break;
        case EXEC_MODE_DMA_FENCE:
                new_mode = EXEC_MODE_LR;
                err = xe_hw_engine_group_wait_for_dma_fence_jobs(group);
                break;
        }

        if (err)
                return err;

        group->cur_mode = new_mode;

        return 0;
}

static int wait_syncs(struct xe_sync_entry *syncs, int num_syncs)
{
        int err, i;

        for (i = 0; i < num_syncs; ++i) {
                err = xe_sync_entry_wait(syncs + i);
                if (err)
                        return err;
        }

        return 0;
}

/**
 * xe_hw_engine_group_get_mode() - Get the group to execute in the new mode
 * @group: The hw engine group
 * @new_mode: The new execution mode
 * @previous_mode: Pointer to the previous mode provided for use by caller
 * @syncs: Syncs from exec IOCTL
 * @num_syncs: Number of syncs from exec IOCTL
 *
 * Return: 0 if successful, -EINTR if locking failed.
 */
int xe_hw_engine_group_get_mode(struct xe_hw_engine_group *group,
                                enum xe_hw_engine_group_execution_mode new_mode,
                                enum xe_hw_engine_group_execution_mode *previous_mode,
                                struct xe_sync_entry *syncs, int num_syncs)
__acquires(&group->mode_sem)
{
        bool has_deps = !!num_syncs;
        int err = down_read_interruptible(&group->mode_sem);

        if (err)
                return err;

        *previous_mode = group->cur_mode;

        if (new_mode != group->cur_mode) {
                up_read(&group->mode_sem);
retry:
                err = down_write_killable(&group->mode_sem);
                if (err)
                        return err;

                if (new_mode != group->cur_mode) {
                        err = switch_mode(group, has_deps);
                        if (err) {
                                up_write(&group->mode_sem);

                                if (err != -EAGAIN)
                                        return err;

                                err = wait_syncs(syncs, num_syncs);
                                if (err)
                                        return err;

                                has_deps = false;
                                goto retry;
                        }
                }
                downgrade_write(&group->mode_sem);
        }

        return err;
}

/**
 * xe_hw_engine_group_put() - Put the group
 * @group: The hw engine group
 */
void xe_hw_engine_group_put(struct xe_hw_engine_group *group)
__releases(&group->mode_sem)
{
        up_read(&group->mode_sem);
}

/**
 * xe_hw_engine_group_find_exec_mode() - Find the execution mode for this exec queue
 * @q: The exec_queue
 */
enum xe_hw_engine_group_execution_mode
xe_hw_engine_group_find_exec_mode(struct xe_exec_queue *q)
{
        if (xe_vm_in_fault_mode(q->vm))
                return EXEC_MODE_LR;
        else
                return EXEC_MODE_DMA_FENCE;
}