#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;
}
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;
}
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);
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);
}
void xe_hw_engine_group_resume_faulting_lr_jobs(struct xe_hw_engine_group *group)
{
queue_work(group->resume_wq, &group->resume_work);
}
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;
}
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;
}
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;
}
void xe_hw_engine_group_put(struct xe_hw_engine_group *group)
__releases(&group->mode_sem)
{
up_read(&group->mode_sem);
}
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;
}