Contributors: 8
Author Tokens Token Proportion Commits Commit Proportion
Francois Dugast 1114 77.47% 11 42.31%
Matthew Brost 288 20.03% 6 23.08%
Michal Wajdeczko 19 1.32% 3 11.54%
Shuicheng Lin 9 0.63% 2 7.69%
Nirmoy Das 3 0.21% 1 3.85%
Brian Welty 3 0.21% 1 3.85%
Matthew Auld 1 0.07% 1 3.85%
Matt Roper 1 0.07% 1 3.85%
Total 1438 26


// 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", 0, 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) {
		bool idle_skip_suspend;

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

		idle_skip_suspend = xe_exec_queue_idle_skip_suspend(q);
		if (!idle_skip_suspend && has_deps)
			return -EAGAIN;

		xe_gt_stats_incr(q->gt, XE_GT_STATS_ID_HW_ENGINE_GROUP_SUSPEND_LR_QUEUE_COUNT, 1);
		if (idle_skip_suspend)
			xe_gt_stats_incr(q->gt,
					 XE_GT_STATS_ID_HW_ENGINE_GROUP_SKIP_LR_QUEUE_COUNT, 1);

		need_resume |= !idle_skip_suspend;
		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;
}