// SPDX-License-Identifier: MIT /* * Copyright © 2022 Intel Corporation */ #include "xe_guc_submit.h" #include #include #include #include #include #include #include #include "abi/guc_actions_abi.h" #include "abi/guc_klvs_abi.h" #include "regs/xe_lrc_layout.h" #include "xe_assert.h" #include "xe_devcoredump.h" #include "xe_device.h" #include "xe_exec_queue.h" #include "xe_force_wake.h" #include "xe_gpu_scheduler.h" #include "xe_gt.h" #include "xe_gt_clock.h" #include "xe_gt_printk.h" #include "xe_guc.h" #include "xe_guc_ct.h" #include "xe_guc_exec_queue_types.h" #include "xe_guc_id_mgr.h" #include "xe_guc_submit_types.h" #include "xe_hw_engine.h" #include "xe_hw_fence.h" #include "xe_lrc.h" #include "xe_macros.h" #include "xe_map.h" #include "xe_mocs.h" #include "xe_pm.h" #include "xe_ring_ops_types.h" #include "xe_sched_job.h" #include "xe_trace.h" #include "xe_vm.h" static struct xe_guc * exec_queue_to_guc(struct xe_exec_queue *q) { return &q->gt->uc.guc; } /* * Helpers for engine state, using an atomic as some of the bits can transition * as the same time (e.g. a suspend can be happning at the same time as schedule * engine done being processed). */ #define EXEC_QUEUE_STATE_REGISTERED (1 << 0) #define EXEC_QUEUE_STATE_ENABLED (1 << 1) #define EXEC_QUEUE_STATE_PENDING_ENABLE (1 << 2) #define EXEC_QUEUE_STATE_PENDING_DISABLE (1 << 3) #define EXEC_QUEUE_STATE_DESTROYED (1 << 4) #define EXEC_QUEUE_STATE_SUSPENDED (1 << 5) #define EXEC_QUEUE_STATE_RESET (1 << 6) #define EXEC_QUEUE_STATE_KILLED (1 << 7) #define EXEC_QUEUE_STATE_WEDGED (1 << 8) #define EXEC_QUEUE_STATE_BANNED (1 << 9) #define EXEC_QUEUE_STATE_CHECK_TIMEOUT (1 << 10) #define EXEC_QUEUE_STATE_EXTRA_REF (1 << 11) static bool exec_queue_registered(struct xe_exec_queue *q) { return atomic_read(&q->guc->state) & EXEC_QUEUE_STATE_REGISTERED; } static void set_exec_queue_registered(struct xe_exec_queue *q) { atomic_or(EXEC_QUEUE_STATE_REGISTERED, &q->guc->state); } static void clear_exec_queue_registered(struct xe_exec_queue *q) { atomic_and(~EXEC_QUEUE_STATE_REGISTERED, &q->guc->state); } static bool exec_queue_enabled(struct xe_exec_queue *q) { return atomic_read(&q->guc->state) & EXEC_QUEUE_STATE_ENABLED; } static void set_exec_queue_enabled(struct xe_exec_queue *q) { atomic_or(EXEC_QUEUE_STATE_ENABLED, &q->guc->state); } static void clear_exec_queue_enabled(struct xe_exec_queue *q) { atomic_and(~EXEC_QUEUE_STATE_ENABLED, &q->guc->state); } static bool exec_queue_pending_enable(struct xe_exec_queue *q) { return atomic_read(&q->guc->state) & EXEC_QUEUE_STATE_PENDING_ENABLE; } static void set_exec_queue_pending_enable(struct xe_exec_queue *q) { atomic_or(EXEC_QUEUE_STATE_PENDING_ENABLE, &q->guc->state); } static void clear_exec_queue_pending_enable(struct xe_exec_queue *q) { atomic_and(~EXEC_QUEUE_STATE_PENDING_ENABLE, &q->guc->state); } static bool exec_queue_pending_disable(struct xe_exec_queue *q) { return atomic_read(&q->guc->state) & EXEC_QUEUE_STATE_PENDING_DISABLE; } static void set_exec_queue_pending_disable(struct xe_exec_queue *q) { atomic_or(EXEC_QUEUE_STATE_PENDING_DISABLE, &q->guc->state); } static void clear_exec_queue_pending_disable(struct xe_exec_queue *q) { atomic_and(~EXEC_QUEUE_STATE_PENDING_DISABLE, &q->guc->state); } static bool exec_queue_destroyed(struct xe_exec_queue *q) { return atomic_read(&q->guc->state) & EXEC_QUEUE_STATE_DESTROYED; } static void set_exec_queue_destroyed(struct xe_exec_queue *q) { atomic_or(EXEC_QUEUE_STATE_DESTROYED, &q->guc->state); } static bool exec_queue_banned(struct xe_exec_queue *q) { return atomic_read(&q->guc->state) & EXEC_QUEUE_STATE_BANNED; } static void set_exec_queue_banned(struct xe_exec_queue *q) { atomic_or(EXEC_QUEUE_STATE_BANNED, &q->guc->state); } static bool exec_queue_suspended(struct xe_exec_queue *q) { return atomic_read(&q->guc->state) & EXEC_QUEUE_STATE_SUSPENDED; } static void set_exec_queue_suspended(struct xe_exec_queue *q) { atomic_or(EXEC_QUEUE_STATE_SUSPENDED, &q->guc->state); } static void clear_exec_queue_suspended(struct xe_exec_queue *q) { atomic_and(~EXEC_QUEUE_STATE_SUSPENDED, &q->guc->state); } static bool exec_queue_reset(struct xe_exec_queue *q) { return atomic_read(&q->guc->state) & EXEC_QUEUE_STATE_RESET; } static void set_exec_queue_reset(struct xe_exec_queue *q) { atomic_or(EXEC_QUEUE_STATE_RESET, &q->guc->state); } static bool exec_queue_killed(struct xe_exec_queue *q) { return atomic_read(&q->guc->state) & EXEC_QUEUE_STATE_KILLED; } static void set_exec_queue_killed(struct xe_exec_queue *q) { atomic_or(EXEC_QUEUE_STATE_KILLED, &q->guc->state); } static bool exec_queue_wedged(struct xe_exec_queue *q) { return atomic_read(&q->guc->state) & EXEC_QUEUE_STATE_WEDGED; } static void set_exec_queue_wedged(struct xe_exec_queue *q) { atomic_or(EXEC_QUEUE_STATE_WEDGED, &q->guc->state); } static bool exec_queue_check_timeout(struct xe_exec_queue *q) { return atomic_read(&q->guc->state) & EXEC_QUEUE_STATE_CHECK_TIMEOUT; } static void set_exec_queue_check_timeout(struct xe_exec_queue *q) { atomic_or(EXEC_QUEUE_STATE_CHECK_TIMEOUT, &q->guc->state); } static void clear_exec_queue_check_timeout(struct xe_exec_queue *q) { atomic_and(~EXEC_QUEUE_STATE_CHECK_TIMEOUT, &q->guc->state); } static bool exec_queue_extra_ref(struct xe_exec_queue *q) { return atomic_read(&q->guc->state) & EXEC_QUEUE_STATE_EXTRA_REF; } static void set_exec_queue_extra_ref(struct xe_exec_queue *q) { atomic_or(EXEC_QUEUE_STATE_EXTRA_REF, &q->guc->state); } static bool exec_queue_killed_or_banned_or_wedged(struct xe_exec_queue *q) { return (atomic_read(&q->guc->state) & (EXEC_QUEUE_STATE_WEDGED | EXEC_QUEUE_STATE_KILLED | EXEC_QUEUE_STATE_BANNED)); } static void guc_submit_fini(struct drm_device *drm, void *arg) { struct xe_guc *guc = arg; xa_destroy(&guc->submission_state.exec_queue_lookup); } static void guc_submit_wedged_fini(void *arg) { struct xe_guc *guc = arg; struct xe_exec_queue *q; unsigned long index; mutex_lock(&guc->submission_state.lock); xa_for_each(&guc->submission_state.exec_queue_lookup, index, q) { if (exec_queue_wedged(q)) { mutex_unlock(&guc->submission_state.lock); xe_exec_queue_put(q); mutex_lock(&guc->submission_state.lock); } } mutex_unlock(&guc->submission_state.lock); } static const struct xe_exec_queue_ops guc_exec_queue_ops; static void primelockdep(struct xe_guc *guc) { if (!IS_ENABLED(CONFIG_LOCKDEP)) return; fs_reclaim_acquire(GFP_KERNEL); mutex_lock(&guc->submission_state.lock); mutex_unlock(&guc->submission_state.lock); fs_reclaim_release(GFP_KERNEL); } /** * xe_guc_submit_init() - Initialize GuC submission. * @guc: the &xe_guc to initialize * @num_ids: number of GuC context IDs to use * * The bare-metal or PF driver can pass ~0 as &num_ids to indicate that all * GuC context IDs supported by the GuC firmware should be used for submission. * * Only VF drivers will have to provide explicit number of GuC context IDs * that they can use for submission. * * Return: 0 on success or a negative error code on failure. */ int xe_guc_submit_init(struct xe_guc *guc, unsigned int num_ids) { struct xe_device *xe = guc_to_xe(guc); struct xe_gt *gt = guc_to_gt(guc); int err; err = drmm_mutex_init(&xe->drm, &guc->submission_state.lock); if (err) return err; err = xe_guc_id_mgr_init(&guc->submission_state.idm, num_ids); if (err) return err; gt->exec_queue_ops = &guc_exec_queue_ops; xa_init(&guc->submission_state.exec_queue_lookup); init_waitqueue_head(&guc->submission_state.fini_wq); primelockdep(guc); return drmm_add_action_or_reset(&xe->drm, guc_submit_fini, guc); } static void __release_guc_id(struct xe_guc *guc, struct xe_exec_queue *q, u32 xa_count) { int i; lockdep_assert_held(&guc->submission_state.lock); for (i = 0; i < xa_count; ++i) xa_erase(&guc->submission_state.exec_queue_lookup, q->guc->id + i); xe_guc_id_mgr_release_locked(&guc->submission_state.idm, q->guc->id, q->width); if (xa_empty(&guc->submission_state.exec_queue_lookup)) wake_up(&guc->submission_state.fini_wq); } static int alloc_guc_id(struct xe_guc *guc, struct xe_exec_queue *q) { int ret; int i; /* * Must use GFP_NOWAIT as this lock is in the dma fence signalling path, * worse case user gets -ENOMEM on engine create and has to try again. * * FIXME: Have caller pre-alloc or post-alloc /w GFP_KERNEL to prevent * failure. */ lockdep_assert_held(&guc->submission_state.lock); ret = xe_guc_id_mgr_reserve_locked(&guc->submission_state.idm, q->width); if (ret < 0) return ret; q->guc->id = ret; for (i = 0; i < q->width; ++i) { ret = xa_err(xa_store(&guc->submission_state.exec_queue_lookup, q->guc->id + i, q, GFP_NOWAIT)); if (ret) goto err_release; } return 0; err_release: __release_guc_id(guc, q, i); return ret; } static void release_guc_id(struct xe_guc *guc, struct xe_exec_queue *q) { mutex_lock(&guc->submission_state.lock); __release_guc_id(guc, q, q->width); mutex_unlock(&guc->submission_state.lock); } struct exec_queue_policy { u32 count; struct guc_update_exec_queue_policy h2g; }; static u32 __guc_exec_queue_policy_action_size(struct exec_queue_policy *policy) { size_t bytes = sizeof(policy->h2g.header) + (sizeof(policy->h2g.klv[0]) * policy->count); return bytes / sizeof(u32); } static void __guc_exec_queue_policy_start_klv(struct exec_queue_policy *policy, u16 guc_id) { policy->h2g.header.action = XE_GUC_ACTION_HOST2GUC_UPDATE_CONTEXT_POLICIES; policy->h2g.header.guc_id = guc_id; policy->count = 0; } #define MAKE_EXEC_QUEUE_POLICY_ADD(func, id) \ static void __guc_exec_queue_policy_add_##func(struct exec_queue_policy *policy, \ u32 data) \ { \ XE_WARN_ON(policy->count >= GUC_CONTEXT_POLICIES_KLV_NUM_IDS); \ \ policy->h2g.klv[policy->count].kl = \ FIELD_PREP(GUC_KLV_0_KEY, \ GUC_CONTEXT_POLICIES_KLV_ID_##id) | \ FIELD_PREP(GUC_KLV_0_LEN, 1); \ policy->h2g.klv[policy->count].value = data; \ policy->count++; \ } MAKE_EXEC_QUEUE_POLICY_ADD(execution_quantum, EXECUTION_QUANTUM) MAKE_EXEC_QUEUE_POLICY_ADD(preemption_timeout, PREEMPTION_TIMEOUT) MAKE_EXEC_QUEUE_POLICY_ADD(priority, SCHEDULING_PRIORITY) #undef MAKE_EXEC_QUEUE_POLICY_ADD static const int xe_exec_queue_prio_to_guc[] = { [XE_EXEC_QUEUE_PRIORITY_LOW] = GUC_CLIENT_PRIORITY_NORMAL, [XE_EXEC_QUEUE_PRIORITY_NORMAL] = GUC_CLIENT_PRIORITY_KMD_NORMAL, [XE_EXEC_QUEUE_PRIORITY_HIGH] = GUC_CLIENT_PRIORITY_HIGH, [XE_EXEC_QUEUE_PRIORITY_KERNEL] = GUC_CLIENT_PRIORITY_KMD_HIGH, }; static void init_policies(struct xe_guc *guc, struct xe_exec_queue *q) { struct exec_queue_policy policy; struct xe_device *xe = guc_to_xe(guc); enum xe_exec_queue_priority prio = q->sched_props.priority; u32 timeslice_us = q->sched_props.timeslice_us; u32 preempt_timeout_us = q->sched_props.preempt_timeout_us; xe_assert(xe, exec_queue_registered(q)); __guc_exec_queue_policy_start_klv(&policy, q->guc->id); __guc_exec_queue_policy_add_priority(&policy, xe_exec_queue_prio_to_guc[prio]); __guc_exec_queue_policy_add_execution_quantum(&policy, timeslice_us); __guc_exec_queue_policy_add_preemption_timeout(&policy, preempt_timeout_us); xe_guc_ct_send(&guc->ct, (u32 *)&policy.h2g, __guc_exec_queue_policy_action_size(&policy), 0, 0); } static void set_min_preemption_timeout(struct xe_guc *guc, struct xe_exec_queue *q) { struct exec_queue_policy policy; __guc_exec_queue_policy_start_klv(&policy, q->guc->id); __guc_exec_queue_policy_add_preemption_timeout(&policy, 1); xe_guc_ct_send(&guc->ct, (u32 *)&policy.h2g, __guc_exec_queue_policy_action_size(&policy), 0, 0); } #define parallel_read(xe_, map_, field_) \ xe_map_rd_field(xe_, &map_, 0, struct guc_submit_parallel_scratch, \ field_) #define parallel_write(xe_, map_, field_, val_) \ xe_map_wr_field(xe_, &map_, 0, struct guc_submit_parallel_scratch, \ field_, val_) static void __register_mlrc_exec_queue(struct xe_guc *guc, struct xe_exec_queue *q, struct guc_ctxt_registration_info *info) { #define MAX_MLRC_REG_SIZE (13 + XE_HW_ENGINE_MAX_INSTANCE * 2) struct xe_device *xe = guc_to_xe(guc); u32 action[MAX_MLRC_REG_SIZE]; int len = 0; int i; xe_assert(xe, xe_exec_queue_is_parallel(q)); action[len++] = XE_GUC_ACTION_REGISTER_CONTEXT_MULTI_LRC; action[len++] = info->flags; action[len++] = info->context_idx; action[len++] = info->engine_class; action[len++] = info->engine_submit_mask; action[len++] = info->wq_desc_lo; action[len++] = info->wq_desc_hi; action[len++] = info->wq_base_lo; action[len++] = info->wq_base_hi; action[len++] = info->wq_size; action[len++] = q->width; action[len++] = info->hwlrca_lo; action[len++] = info->hwlrca_hi; for (i = 1; i < q->width; ++i) { struct xe_lrc *lrc = q->lrc[i]; action[len++] = lower_32_bits(xe_lrc_descriptor(lrc)); action[len++] = upper_32_bits(xe_lrc_descriptor(lrc)); } xe_assert(xe, len <= MAX_MLRC_REG_SIZE); #undef MAX_MLRC_REG_SIZE xe_guc_ct_send(&guc->ct, action, len, 0, 0); } static void __register_exec_queue(struct xe_guc *guc, struct guc_ctxt_registration_info *info) { u32 action[] = { XE_GUC_ACTION_REGISTER_CONTEXT, info->flags, info->context_idx, info->engine_class, info->engine_submit_mask, info->wq_desc_lo, info->wq_desc_hi, info->wq_base_lo, info->wq_base_hi, info->wq_size, info->hwlrca_lo, info->hwlrca_hi, }; xe_guc_ct_send(&guc->ct, action, ARRAY_SIZE(action), 0, 0); } static void register_exec_queue(struct xe_exec_queue *q) { struct xe_guc *guc = exec_queue_to_guc(q); struct xe_device *xe = guc_to_xe(guc); struct xe_lrc *lrc = q->lrc[0]; struct guc_ctxt_registration_info info; xe_assert(xe, !exec_queue_registered(q)); memset(&info, 0, sizeof(info)); info.context_idx = q->guc->id; info.engine_class = xe_engine_class_to_guc_class(q->class); info.engine_submit_mask = q->logical_mask; info.hwlrca_lo = lower_32_bits(xe_lrc_descriptor(lrc)); info.hwlrca_hi = upper_32_bits(xe_lrc_descriptor(lrc)); info.flags = CONTEXT_REGISTRATION_FLAG_KMD; if (xe_exec_queue_is_parallel(q)) { u64 ggtt_addr = xe_lrc_parallel_ggtt_addr(lrc); struct iosys_map map = xe_lrc_parallel_map(lrc); info.wq_desc_lo = lower_32_bits(ggtt_addr + offsetof(struct guc_submit_parallel_scratch, wq_desc)); info.wq_desc_hi = upper_32_bits(ggtt_addr + offsetof(struct guc_submit_parallel_scratch, wq_desc)); info.wq_base_lo = lower_32_bits(ggtt_addr + offsetof(struct guc_submit_parallel_scratch, wq[0])); info.wq_base_hi = upper_32_bits(ggtt_addr + offsetof(struct guc_submit_parallel_scratch, wq[0])); info.wq_size = WQ_SIZE; q->guc->wqi_head = 0; q->guc->wqi_tail = 0; xe_map_memset(xe, &map, 0, 0, PARALLEL_SCRATCH_SIZE - WQ_SIZE); parallel_write(xe, map, wq_desc.wq_status, WQ_STATUS_ACTIVE); } /* * We must keep a reference for LR engines if engine is registered with * the GuC as jobs signal immediately and can't destroy an engine if the * GuC has a reference to it. */ if (xe_exec_queue_is_lr(q)) xe_exec_queue_get(q); set_exec_queue_registered(q); trace_xe_exec_queue_register(q); if (xe_exec_queue_is_parallel(q)) __register_mlrc_exec_queue(guc, q, &info); else __register_exec_queue(guc, &info); init_policies(guc, q); } static u32 wq_space_until_wrap(struct xe_exec_queue *q) { return (WQ_SIZE - q->guc->wqi_tail); } static int wq_wait_for_space(struct xe_exec_queue *q, u32 wqi_size) { struct xe_guc *guc = exec_queue_to_guc(q); struct xe_device *xe = guc_to_xe(guc); struct iosys_map map = xe_lrc_parallel_map(q->lrc[0]); unsigned int sleep_period_ms = 1; #define AVAILABLE_SPACE \ CIRC_SPACE(q->guc->wqi_tail, q->guc->wqi_head, WQ_SIZE) if (wqi_size > AVAILABLE_SPACE) { try_again: q->guc->wqi_head = parallel_read(xe, map, wq_desc.head); if (wqi_size > AVAILABLE_SPACE) { if (sleep_period_ms == 1024) { xe_gt_reset_async(q->gt); return -ENODEV; } msleep(sleep_period_ms); sleep_period_ms <<= 1; goto try_again; } } #undef AVAILABLE_SPACE return 0; } static int wq_noop_append(struct xe_exec_queue *q) { struct xe_guc *guc = exec_queue_to_guc(q); struct xe_device *xe = guc_to_xe(guc); struct iosys_map map = xe_lrc_parallel_map(q->lrc[0]); u32 len_dw = wq_space_until_wrap(q) / sizeof(u32) - 1; if (wq_wait_for_space(q, wq_space_until_wrap(q))) return -ENODEV; xe_assert(xe, FIELD_FIT(WQ_LEN_MASK, len_dw)); parallel_write(xe, map, wq[q->guc->wqi_tail / sizeof(u32)], FIELD_PREP(WQ_TYPE_MASK, WQ_TYPE_NOOP) | FIELD_PREP(WQ_LEN_MASK, len_dw)); q->guc->wqi_tail = 0; return 0; } static void wq_item_append(struct xe_exec_queue *q) { struct xe_guc *guc = exec_queue_to_guc(q); struct xe_device *xe = guc_to_xe(guc); struct iosys_map map = xe_lrc_parallel_map(q->lrc[0]); #define WQ_HEADER_SIZE 4 /* Includes 1 LRC address too */ u32 wqi[XE_HW_ENGINE_MAX_INSTANCE + (WQ_HEADER_SIZE - 1)]; u32 wqi_size = (q->width + (WQ_HEADER_SIZE - 1)) * sizeof(u32); u32 len_dw = (wqi_size / sizeof(u32)) - 1; int i = 0, j; if (wqi_size > wq_space_until_wrap(q)) { if (wq_noop_append(q)) return; } if (wq_wait_for_space(q, wqi_size)) return; wqi[i++] = FIELD_PREP(WQ_TYPE_MASK, WQ_TYPE_MULTI_LRC) | FIELD_PREP(WQ_LEN_MASK, len_dw); wqi[i++] = xe_lrc_descriptor(q->lrc[0]); wqi[i++] = FIELD_PREP(WQ_GUC_ID_MASK, q->guc->id) | FIELD_PREP(WQ_RING_TAIL_MASK, q->lrc[0]->ring.tail / sizeof(u64)); wqi[i++] = 0; for (j = 1; j < q->width; ++j) { struct xe_lrc *lrc = q->lrc[j]; wqi[i++] = lrc->ring.tail / sizeof(u64); } xe_assert(xe, i == wqi_size / sizeof(u32)); iosys_map_incr(&map, offsetof(struct guc_submit_parallel_scratch, wq[q->guc->wqi_tail / sizeof(u32)])); xe_map_memcpy_to(xe, &map, 0, wqi, wqi_size); q->guc->wqi_tail += wqi_size; xe_assert(xe, q->guc->wqi_tail <= WQ_SIZE); xe_device_wmb(xe); map = xe_lrc_parallel_map(q->lrc[0]); parallel_write(xe, map, wq_desc.tail, q->guc->wqi_tail); } #define RESUME_PENDING ~0x0ull static void submit_exec_queue(struct xe_exec_queue *q) { struct xe_guc *guc = exec_queue_to_guc(q); struct xe_device *xe = guc_to_xe(guc); struct xe_lrc *lrc = q->lrc[0]; u32 action[3]; u32 g2h_len = 0; u32 num_g2h = 0; int len = 0; bool extra_submit = false; xe_assert(xe, exec_queue_registered(q)); if (xe_exec_queue_is_parallel(q)) wq_item_append(q); else xe_lrc_set_ring_tail(lrc, lrc->ring.tail); if (exec_queue_suspended(q) && !xe_exec_queue_is_parallel(q)) return; if (!exec_queue_enabled(q) && !exec_queue_suspended(q)) { action[len++] = XE_GUC_ACTION_SCHED_CONTEXT_MODE_SET; action[len++] = q->guc->id; action[len++] = GUC_CONTEXT_ENABLE; g2h_len = G2H_LEN_DW_SCHED_CONTEXT_MODE_SET; num_g2h = 1; if (xe_exec_queue_is_parallel(q)) extra_submit = true; q->guc->resume_time = RESUME_PENDING; set_exec_queue_pending_enable(q); set_exec_queue_enabled(q); trace_xe_exec_queue_scheduling_enable(q); } else { action[len++] = XE_GUC_ACTION_SCHED_CONTEXT; action[len++] = q->guc->id; trace_xe_exec_queue_submit(q); } xe_guc_ct_send(&guc->ct, action, len, g2h_len, num_g2h); if (extra_submit) { len = 0; action[len++] = XE_GUC_ACTION_SCHED_CONTEXT; action[len++] = q->guc->id; trace_xe_exec_queue_submit(q); xe_guc_ct_send(&guc->ct, action, len, 0, 0); } } static struct dma_fence * guc_exec_queue_run_job(struct drm_sched_job *drm_job) { struct xe_sched_job *job = to_xe_sched_job(drm_job); struct xe_exec_queue *q = job->q; struct xe_guc *guc = exec_queue_to_guc(q); struct xe_device *xe = guc_to_xe(guc); bool lr = xe_exec_queue_is_lr(q); xe_assert(xe, !(exec_queue_destroyed(q) || exec_queue_pending_disable(q)) || exec_queue_banned(q) || exec_queue_suspended(q)); trace_xe_sched_job_run(job); if (!exec_queue_killed_or_banned_or_wedged(q) && !xe_sched_job_is_error(job)) { if (!exec_queue_registered(q)) register_exec_queue(q); if (!lr) /* LR jobs are emitted in the exec IOCTL */ q->ring_ops->emit_job(job); submit_exec_queue(q); } if (lr) { xe_sched_job_set_error(job, -EOPNOTSUPP); return NULL; } else if (test_and_set_bit(JOB_FLAG_SUBMIT, &job->fence->flags)) { return job->fence; } else { return dma_fence_get(job->fence); } } static void guc_exec_queue_free_job(struct drm_sched_job *drm_job) { struct xe_sched_job *job = to_xe_sched_job(drm_job); trace_xe_sched_job_free(job); xe_sched_job_put(job); } static int guc_read_stopped(struct xe_guc *guc) { return atomic_read(&guc->submission_state.stopped); } #define MAKE_SCHED_CONTEXT_ACTION(q, enable_disable) \ u32 action[] = { \ XE_GUC_ACTION_SCHED_CONTEXT_MODE_SET, \ q->guc->id, \ GUC_CONTEXT_##enable_disable, \ } static void disable_scheduling_deregister(struct xe_guc *guc, struct xe_exec_queue *q) { MAKE_SCHED_CONTEXT_ACTION(q, DISABLE); struct xe_device *xe = guc_to_xe(guc); int ret; set_min_preemption_timeout(guc, q); smp_rmb(); ret = wait_event_timeout(guc->ct.wq, !exec_queue_pending_enable(q) || guc_read_stopped(guc), HZ * 5); if (!ret) { struct xe_gpu_scheduler *sched = &q->guc->sched; drm_warn(&xe->drm, "Pending enable failed to respond"); xe_sched_submission_start(sched); xe_gt_reset_async(q->gt); xe_sched_tdr_queue_imm(sched); return; } clear_exec_queue_enabled(q); set_exec_queue_pending_disable(q); set_exec_queue_destroyed(q); trace_xe_exec_queue_scheduling_disable(q); /* * Reserve space for both G2H here as the 2nd G2H is sent from a G2H * handler and we are not allowed to reserved G2H space in handlers. */ xe_guc_ct_send(&guc->ct, action, ARRAY_SIZE(action), G2H_LEN_DW_SCHED_CONTEXT_MODE_SET + G2H_LEN_DW_DEREGISTER_CONTEXT, 2); } static void xe_guc_exec_queue_trigger_cleanup(struct xe_exec_queue *q) { struct xe_guc *guc = exec_queue_to_guc(q); struct xe_device *xe = guc_to_xe(guc); /** to wakeup xe_wait_user_fence ioctl if exec queue is reset */ wake_up_all(&xe->ufence_wq); if (xe_exec_queue_is_lr(q)) queue_work(guc_to_gt(guc)->ordered_wq, &q->guc->lr_tdr); else xe_sched_tdr_queue_imm(&q->guc->sched); } /** * xe_guc_submit_wedge() - Wedge GuC submission * @guc: the GuC object * * Save exec queue's registered with GuC state by taking a ref to each queue. * Register a DRMM handler to drop refs upon driver unload. */ void xe_guc_submit_wedge(struct xe_guc *guc) { struct xe_device *xe = guc_to_xe(guc); struct xe_exec_queue *q; unsigned long index; int err; xe_gt_assert(guc_to_gt(guc), guc_to_xe(guc)->wedged.mode); err = devm_add_action_or_reset(guc_to_xe(guc)->drm.dev, guc_submit_wedged_fini, guc); if (err) { drm_err(&xe->drm, "Failed to register xe_guc_submit clean-up on wedged.mode=2. Although device is wedged.\n"); return; } mutex_lock(&guc->submission_state.lock); xa_for_each(&guc->submission_state.exec_queue_lookup, index, q) if (xe_exec_queue_get_unless_zero(q)) set_exec_queue_wedged(q); mutex_unlock(&guc->submission_state.lock); } static bool guc_submit_hint_wedged(struct xe_guc *guc) { struct xe_device *xe = guc_to_xe(guc); if (xe->wedged.mode != 2) return false; if (xe_device_wedged(xe)) return true; xe_device_declare_wedged(xe); return true; } static void xe_guc_exec_queue_lr_cleanup(struct work_struct *w) { struct xe_guc_exec_queue *ge = container_of(w, struct xe_guc_exec_queue, lr_tdr); struct xe_exec_queue *q = ge->q; struct xe_guc *guc = exec_queue_to_guc(q); struct xe_device *xe = guc_to_xe(guc); struct xe_gpu_scheduler *sched = &ge->sched; bool wedged; xe_assert(xe, xe_exec_queue_is_lr(q)); trace_xe_exec_queue_lr_cleanup(q); wedged = guc_submit_hint_wedged(exec_queue_to_guc(q)); /* Kill the run_job / process_msg entry points */ xe_sched_submission_stop(sched); /* * Engine state now mostly stable, disable scheduling / deregister if * needed. This cleanup routine might be called multiple times, where * the actual async engine deregister drops the final engine ref. * Calling disable_scheduling_deregister will mark the engine as * destroyed and fire off the CT requests to disable scheduling / * deregister, which we only want to do once. We also don't want to mark * the engine as pending_disable again as this may race with the * xe_guc_deregister_done_handler() which treats it as an unexpected * state. */ if (!wedged && exec_queue_registered(q) && !exec_queue_destroyed(q)) { struct xe_guc *guc = exec_queue_to_guc(q); int ret; set_exec_queue_banned(q); disable_scheduling_deregister(guc, q); /* * Must wait for scheduling to be disabled before signalling * any fences, if GT broken the GT reset code should signal us. */ ret = wait_event_timeout(guc->ct.wq, !exec_queue_pending_disable(q) || guc_read_stopped(guc), HZ * 5); if (!ret) { drm_warn(&xe->drm, "Schedule disable failed to respond"); xe_sched_submission_start(sched); xe_gt_reset_async(q->gt); return; } } xe_sched_submission_start(sched); } #define ADJUST_FIVE_PERCENT(__t) mul_u64_u32_div(__t, 105, 100) static bool check_timeout(struct xe_exec_queue *q, struct xe_sched_job *job) { struct xe_gt *gt = guc_to_gt(exec_queue_to_guc(q)); u32 ctx_timestamp, ctx_job_timestamp; u32 timeout_ms = q->sched_props.job_timeout_ms; u32 diff; u64 running_time_ms; if (!xe_sched_job_started(job)) { xe_gt_warn(gt, "Check job timeout: seqno=%u, lrc_seqno=%u, guc_id=%d, not started", xe_sched_job_seqno(job), xe_sched_job_lrc_seqno(job), q->guc->id); return xe_sched_invalidate_job(job, 2); } ctx_timestamp = xe_lrc_ctx_timestamp(q->lrc[0]); ctx_job_timestamp = xe_lrc_ctx_job_timestamp(q->lrc[0]); /* * Counter wraps at ~223s at the usual 19.2MHz, be paranoid catch * possible overflows with a high timeout. */ xe_gt_assert(gt, timeout_ms < 100 * MSEC_PER_SEC); if (ctx_timestamp < ctx_job_timestamp) diff = ctx_timestamp + U32_MAX - ctx_job_timestamp; else diff = ctx_timestamp - ctx_job_timestamp; /* * Ensure timeout is within 5% to account for an GuC scheduling latency */ running_time_ms = ADJUST_FIVE_PERCENT(xe_gt_clock_interval_to_ms(gt, diff)); xe_gt_dbg(gt, "Check job timeout: seqno=%u, lrc_seqno=%u, guc_id=%d, running_time_ms=%llu, timeout_ms=%u, diff=0x%08x", xe_sched_job_seqno(job), xe_sched_job_lrc_seqno(job), q->guc->id, running_time_ms, timeout_ms, diff); return running_time_ms >= timeout_ms; } static void enable_scheduling(struct xe_exec_queue *q) { MAKE_SCHED_CONTEXT_ACTION(q, ENABLE); struct xe_guc *guc = exec_queue_to_guc(q); int ret; xe_gt_assert(guc_to_gt(guc), !exec_queue_destroyed(q)); xe_gt_assert(guc_to_gt(guc), exec_queue_registered(q)); xe_gt_assert(guc_to_gt(guc), !exec_queue_pending_disable(q)); xe_gt_assert(guc_to_gt(guc), !exec_queue_pending_enable(q)); set_exec_queue_pending_enable(q); set_exec_queue_enabled(q); trace_xe_exec_queue_scheduling_enable(q); xe_guc_ct_send(&guc->ct, action, ARRAY_SIZE(action), G2H_LEN_DW_SCHED_CONTEXT_MODE_SET, 1); ret = wait_event_timeout(guc->ct.wq, !exec_queue_pending_enable(q) || guc_read_stopped(guc), HZ * 5); if (!ret || guc_read_stopped(guc)) { xe_gt_warn(guc_to_gt(guc), "Schedule enable failed to respond"); set_exec_queue_banned(q); xe_gt_reset_async(q->gt); xe_sched_tdr_queue_imm(&q->guc->sched); } } static void disable_scheduling(struct xe_exec_queue *q, bool immediate) { MAKE_SCHED_CONTEXT_ACTION(q, DISABLE); struct xe_guc *guc = exec_queue_to_guc(q); xe_gt_assert(guc_to_gt(guc), !exec_queue_destroyed(q)); xe_gt_assert(guc_to_gt(guc), exec_queue_registered(q)); xe_gt_assert(guc_to_gt(guc), !exec_queue_pending_disable(q)); if (immediate) set_min_preemption_timeout(guc, q); clear_exec_queue_enabled(q); set_exec_queue_pending_disable(q); trace_xe_exec_queue_scheduling_disable(q); xe_guc_ct_send(&guc->ct, action, ARRAY_SIZE(action), G2H_LEN_DW_SCHED_CONTEXT_MODE_SET, 1); } static void __deregister_exec_queue(struct xe_guc *guc, struct xe_exec_queue *q) { u32 action[] = { XE_GUC_ACTION_DEREGISTER_CONTEXT, q->guc->id, }; xe_gt_assert(guc_to_gt(guc), !exec_queue_destroyed(q)); xe_gt_assert(guc_to_gt(guc), exec_queue_registered(q)); xe_gt_assert(guc_to_gt(guc), !exec_queue_pending_enable(q)); xe_gt_assert(guc_to_gt(guc), !exec_queue_pending_disable(q)); set_exec_queue_destroyed(q); trace_xe_exec_queue_deregister(q); xe_guc_ct_send(&guc->ct, action, ARRAY_SIZE(action), G2H_LEN_DW_DEREGISTER_CONTEXT, 1); } static enum drm_gpu_sched_stat guc_exec_queue_timedout_job(struct drm_sched_job *drm_job) { struct xe_sched_job *job = to_xe_sched_job(drm_job); struct xe_sched_job *tmp_job; struct xe_exec_queue *q = job->q; struct xe_gpu_scheduler *sched = &q->guc->sched; struct xe_guc *guc = exec_queue_to_guc(q); const char *process_name = "no process"; int err = -ETIME; pid_t pid = -1; int i = 0; bool wedged, skip_timeout_check; /* * TDR has fired before free job worker. Common if exec queue * immediately closed after last fence signaled. Add back to pending * list so job can be freed and kick scheduler ensuring free job is not * lost. */ if (test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &job->fence->flags)) { xe_sched_add_pending_job(sched, job); xe_sched_submission_start(sched); return DRM_GPU_SCHED_STAT_NOMINAL; } /* Kill the run_job entry point */ xe_sched_submission_stop(sched); /* Must check all state after stopping scheduler */ skip_timeout_check = exec_queue_reset(q) || exec_queue_killed_or_banned_or_wedged(q) || exec_queue_destroyed(q); /* * XXX: Sampling timeout doesn't work in wedged mode as we have to * modify scheduling state to read timestamp. We could read the * timestamp from a register to accumulate current running time but this * doesn't work for SRIOV. For now assuming timeouts in wedged mode are * genuine timeouts. */ wedged = guc_submit_hint_wedged(exec_queue_to_guc(q)); /* Engine state now stable, disable scheduling to check timestamp */ if (!wedged && exec_queue_registered(q)) { int ret; if (exec_queue_reset(q)) err = -EIO; if (!exec_queue_destroyed(q)) { /* * Wait for any pending G2H to flush out before * modifying state */ ret = wait_event_timeout(guc->ct.wq, !exec_queue_pending_enable(q) || guc_read_stopped(guc), HZ * 5); if (!ret || guc_read_stopped(guc)) goto trigger_reset; /* * Flag communicates to G2H handler that schedule * disable originated from a timeout check. The G2H then * avoid triggering cleanup or deregistering the exec * queue. */ set_exec_queue_check_timeout(q); disable_scheduling(q, skip_timeout_check); } /* * Must wait for scheduling to be disabled before signalling * any fences, if GT broken the GT reset code should signal us. * * FIXME: Tests can generate a ton of 0x6000 (IOMMU CAT fault * error) messages which can cause the schedule disable to get * lost. If this occurs, trigger a GT reset to recover. */ smp_rmb(); ret = wait_event_timeout(guc->ct.wq, !exec_queue_pending_disable(q) || guc_read_stopped(guc), HZ * 5); if (!ret || guc_read_stopped(guc)) { trigger_reset: if (!ret) xe_gt_warn(guc_to_gt(guc), "Schedule disable failed to respond"); set_exec_queue_extra_ref(q); xe_exec_queue_get(q); /* GT reset owns this */ set_exec_queue_banned(q); xe_gt_reset_async(q->gt); xe_sched_tdr_queue_imm(sched); goto rearm; } } /* * Check if job is actually timed out, if so restart job execution and TDR */ if (!wedged && !skip_timeout_check && !check_timeout(q, job) && !exec_queue_reset(q) && exec_queue_registered(q)) { clear_exec_queue_check_timeout(q); goto sched_enable; } if (q->vm && q->vm->xef) { process_name = q->vm->xef->process_name; pid = q->vm->xef->pid; } xe_gt_notice(guc_to_gt(guc), "Timedout job: seqno=%u, lrc_seqno=%u, guc_id=%d, flags=0x%lx in %s [%d]", xe_sched_job_seqno(job), xe_sched_job_lrc_seqno(job), q->guc->id, q->flags, process_name, pid); trace_xe_sched_job_timedout(job); if (!exec_queue_killed(q)) xe_devcoredump(job); /* * Kernel jobs should never fail, nor should VM jobs if they do * somethings has gone wrong and the GT needs a reset */ xe_gt_WARN(q->gt, q->flags & EXEC_QUEUE_FLAG_KERNEL, "Kernel-submitted job timed out\n"); xe_gt_WARN(q->gt, q->flags & EXEC_QUEUE_FLAG_VM && !exec_queue_killed(q), "VM job timed out on non-killed execqueue\n"); if (!wedged && (q->flags & EXEC_QUEUE_FLAG_KERNEL || (q->flags & EXEC_QUEUE_FLAG_VM && !exec_queue_killed(q)))) { if (!xe_sched_invalidate_job(job, 2)) { clear_exec_queue_check_timeout(q); xe_gt_reset_async(q->gt); goto rearm; } } /* Finish cleaning up exec queue via deregister */ set_exec_queue_banned(q); if (!wedged && exec_queue_registered(q) && !exec_queue_destroyed(q)) { set_exec_queue_extra_ref(q); xe_exec_queue_get(q); __deregister_exec_queue(guc, q); } /* Stop fence signaling */ xe_hw_fence_irq_stop(q->fence_irq); /* * Fence state now stable, stop / start scheduler which cleans up any * fences that are complete */ xe_sched_add_pending_job(sched, job); xe_sched_submission_start(sched); xe_guc_exec_queue_trigger_cleanup(q); /* Mark all outstanding jobs as bad, thus completing them */ spin_lock(&sched->base.job_list_lock); list_for_each_entry(tmp_job, &sched->base.pending_list, drm.list) xe_sched_job_set_error(tmp_job, !i++ ? err : -ECANCELED); spin_unlock(&sched->base.job_list_lock); /* Start fence signaling */ xe_hw_fence_irq_start(q->fence_irq); return DRM_GPU_SCHED_STAT_NOMINAL; sched_enable: enable_scheduling(q); rearm: /* * XXX: Ideally want to adjust timeout based on current exection time * but there is not currently an easy way to do in DRM scheduler. With * some thought, do this in a follow up. */ xe_sched_add_pending_job(sched, job); xe_sched_submission_start(sched); return DRM_GPU_SCHED_STAT_NOMINAL; } static void __guc_exec_queue_fini_async(struct work_struct *w) { struct xe_guc_exec_queue *ge = container_of(w, struct xe_guc_exec_queue, fini_async); struct xe_exec_queue *q = ge->q; struct xe_guc *guc = exec_queue_to_guc(q); xe_pm_runtime_get(guc_to_xe(guc)); trace_xe_exec_queue_destroy(q); if (xe_exec_queue_is_lr(q)) cancel_work_sync(&ge->lr_tdr); release_guc_id(guc, q); xe_sched_entity_fini(&ge->entity); xe_sched_fini(&ge->sched); kfree(ge); xe_exec_queue_fini(q); xe_pm_runtime_put(guc_to_xe(guc)); } static void guc_exec_queue_fini_async(struct xe_exec_queue *q) { struct xe_guc *guc = exec_queue_to_guc(q); struct xe_device *xe = guc_to_xe(guc); INIT_WORK(&q->guc->fini_async, __guc_exec_queue_fini_async); /* We must block on kernel engines so slabs are empty on driver unload */ if (q->flags & EXEC_QUEUE_FLAG_PERMANENT || exec_queue_wedged(q)) __guc_exec_queue_fini_async(&q->guc->fini_async); else queue_work(xe->destroy_wq, &q->guc->fini_async); } static void __guc_exec_queue_fini(struct xe_guc *guc, struct xe_exec_queue *q) { /* * Might be done from within the GPU scheduler, need to do async as we * fini the scheduler when the engine is fini'd, the scheduler can't * complete fini within itself (circular dependency). Async resolves * this we and don't really care when everything is fini'd, just that it * is. */ guc_exec_queue_fini_async(q); } static void __guc_exec_queue_process_msg_cleanup(struct xe_sched_msg *msg) { struct xe_exec_queue *q = msg->private_data; struct xe_guc *guc = exec_queue_to_guc(q); struct xe_device *xe = guc_to_xe(guc); xe_assert(xe, !(q->flags & EXEC_QUEUE_FLAG_PERMANENT)); trace_xe_exec_queue_cleanup_entity(q); if (exec_queue_registered(q)) disable_scheduling_deregister(guc, q); else __guc_exec_queue_fini(guc, q); } static bool guc_exec_queue_allowed_to_change_state(struct xe_exec_queue *q) { return !exec_queue_killed_or_banned_or_wedged(q) && exec_queue_registered(q); } static void __guc_exec_queue_process_msg_set_sched_props(struct xe_sched_msg *msg) { struct xe_exec_queue *q = msg->private_data; struct xe_guc *guc = exec_queue_to_guc(q); if (guc_exec_queue_allowed_to_change_state(q)) init_policies(guc, q); kfree(msg); } static void __suspend_fence_signal(struct xe_exec_queue *q) { if (!q->guc->suspend_pending) return; WRITE_ONCE(q->guc->suspend_pending, false); wake_up(&q->guc->suspend_wait); } static void suspend_fence_signal(struct xe_exec_queue *q) { struct xe_guc *guc = exec_queue_to_guc(q); struct xe_device *xe = guc_to_xe(guc); xe_assert(xe, exec_queue_suspended(q) || exec_queue_killed(q) || guc_read_stopped(guc)); xe_assert(xe, q->guc->suspend_pending); __suspend_fence_signal(q); } static void __guc_exec_queue_process_msg_suspend(struct xe_sched_msg *msg) { struct xe_exec_queue *q = msg->private_data; struct xe_guc *guc = exec_queue_to_guc(q); if (guc_exec_queue_allowed_to_change_state(q) && !exec_queue_suspended(q) && exec_queue_enabled(q)) { wait_event(guc->ct.wq, q->guc->resume_time != RESUME_PENDING || guc_read_stopped(guc)); if (!guc_read_stopped(guc)) { s64 since_resume_ms = ktime_ms_delta(ktime_get(), q->guc->resume_time); s64 wait_ms = q->vm->preempt.min_run_period_ms - since_resume_ms; if (wait_ms > 0 && q->guc->resume_time) msleep(wait_ms); set_exec_queue_suspended(q); disable_scheduling(q, false); } } else if (q->guc->suspend_pending) { set_exec_queue_suspended(q); suspend_fence_signal(q); } } static void __guc_exec_queue_process_msg_resume(struct xe_sched_msg *msg) { struct xe_exec_queue *q = msg->private_data; if (guc_exec_queue_allowed_to_change_state(q)) { clear_exec_queue_suspended(q); if (!exec_queue_enabled(q)) { q->guc->resume_time = RESUME_PENDING; enable_scheduling(q); } } else { clear_exec_queue_suspended(q); } } #define CLEANUP 1 /* Non-zero values to catch uninitialized msg */ #define SET_SCHED_PROPS 2 #define SUSPEND 3 #define RESUME 4 #define OPCODE_MASK 0xf #define MSG_LOCKED BIT(8) static void guc_exec_queue_process_msg(struct xe_sched_msg *msg) { struct xe_device *xe = guc_to_xe(exec_queue_to_guc(msg->private_data)); trace_xe_sched_msg_recv(msg); switch (msg->opcode) { case CLEANUP: __guc_exec_queue_process_msg_cleanup(msg); break; case SET_SCHED_PROPS: __guc_exec_queue_process_msg_set_sched_props(msg); break; case SUSPEND: __guc_exec_queue_process_msg_suspend(msg); break; case RESUME: __guc_exec_queue_process_msg_resume(msg); break; default: XE_WARN_ON("Unknown message type"); } xe_pm_runtime_put(xe); } static const struct drm_sched_backend_ops drm_sched_ops = { .run_job = guc_exec_queue_run_job, .free_job = guc_exec_queue_free_job, .timedout_job = guc_exec_queue_timedout_job, }; static const struct xe_sched_backend_ops xe_sched_ops = { .process_msg = guc_exec_queue_process_msg, }; static int guc_exec_queue_init(struct xe_exec_queue *q) { struct xe_gpu_scheduler *sched; struct xe_guc *guc = exec_queue_to_guc(q); struct xe_device *xe = guc_to_xe(guc); struct xe_guc_exec_queue *ge; long timeout; int err, i; xe_assert(xe, xe_device_uc_enabled(guc_to_xe(guc))); ge = kzalloc(sizeof(*ge), GFP_KERNEL); if (!ge) return -ENOMEM; q->guc = ge; ge->q = q; init_waitqueue_head(&ge->suspend_wait); for (i = 0; i < MAX_STATIC_MSG_TYPE; ++i) INIT_LIST_HEAD(&ge->static_msgs[i].link); timeout = (q->vm && xe_vm_in_lr_mode(q->vm)) ? MAX_SCHEDULE_TIMEOUT : msecs_to_jiffies(q->sched_props.job_timeout_ms); err = xe_sched_init(&ge->sched, &drm_sched_ops, &xe_sched_ops, NULL, q->lrc[0]->ring.size / MAX_JOB_SIZE_BYTES, 64, timeout, guc_to_gt(guc)->ordered_wq, NULL, q->name, gt_to_xe(q->gt)->drm.dev); if (err) goto err_free; sched = &ge->sched; err = xe_sched_entity_init(&ge->entity, sched); if (err) goto err_sched; if (xe_exec_queue_is_lr(q)) INIT_WORK(&q->guc->lr_tdr, xe_guc_exec_queue_lr_cleanup); mutex_lock(&guc->submission_state.lock); err = alloc_guc_id(guc, q); if (err) goto err_entity; q->entity = &ge->entity; if (guc_read_stopped(guc)) xe_sched_stop(sched); mutex_unlock(&guc->submission_state.lock); xe_exec_queue_assign_name(q, q->guc->id); trace_xe_exec_queue_create(q); return 0; err_entity: mutex_unlock(&guc->submission_state.lock); xe_sched_entity_fini(&ge->entity); err_sched: xe_sched_fini(&ge->sched); err_free: kfree(ge); return err; } static void guc_exec_queue_kill(struct xe_exec_queue *q) { trace_xe_exec_queue_kill(q); set_exec_queue_killed(q); __suspend_fence_signal(q); xe_guc_exec_queue_trigger_cleanup(q); } static void guc_exec_queue_add_msg(struct xe_exec_queue *q, struct xe_sched_msg *msg, u32 opcode) { xe_pm_runtime_get_noresume(guc_to_xe(exec_queue_to_guc(q))); INIT_LIST_HEAD(&msg->link); msg->opcode = opcode & OPCODE_MASK; msg->private_data = q; trace_xe_sched_msg_add(msg); if (opcode & MSG_LOCKED) xe_sched_add_msg_locked(&q->guc->sched, msg); else xe_sched_add_msg(&q->guc->sched, msg); } static bool guc_exec_queue_try_add_msg(struct xe_exec_queue *q, struct xe_sched_msg *msg, u32 opcode) { if (!list_empty(&msg->link)) return false; guc_exec_queue_add_msg(q, msg, opcode | MSG_LOCKED); return true; } #define STATIC_MSG_CLEANUP 0 #define STATIC_MSG_SUSPEND 1 #define STATIC_MSG_RESUME 2 static void guc_exec_queue_fini(struct xe_exec_queue *q) { struct xe_sched_msg *msg = q->guc->static_msgs + STATIC_MSG_CLEANUP; if (!(q->flags & EXEC_QUEUE_FLAG_PERMANENT) && !exec_queue_wedged(q)) guc_exec_queue_add_msg(q, msg, CLEANUP); else __guc_exec_queue_fini(exec_queue_to_guc(q), q); } static int guc_exec_queue_set_priority(struct xe_exec_queue *q, enum xe_exec_queue_priority priority) { struct xe_sched_msg *msg; if (q->sched_props.priority == priority || exec_queue_killed_or_banned_or_wedged(q)) return 0; msg = kmalloc(sizeof(*msg), GFP_KERNEL); if (!msg) return -ENOMEM; q->sched_props.priority = priority; guc_exec_queue_add_msg(q, msg, SET_SCHED_PROPS); return 0; } static int guc_exec_queue_set_timeslice(struct xe_exec_queue *q, u32 timeslice_us) { struct xe_sched_msg *msg; if (q->sched_props.timeslice_us == timeslice_us || exec_queue_killed_or_banned_or_wedged(q)) return 0; msg = kmalloc(sizeof(*msg), GFP_KERNEL); if (!msg) return -ENOMEM; q->sched_props.timeslice_us = timeslice_us; guc_exec_queue_add_msg(q, msg, SET_SCHED_PROPS); return 0; } static int guc_exec_queue_set_preempt_timeout(struct xe_exec_queue *q, u32 preempt_timeout_us) { struct xe_sched_msg *msg; if (q->sched_props.preempt_timeout_us == preempt_timeout_us || exec_queue_killed_or_banned_or_wedged(q)) return 0; msg = kmalloc(sizeof(*msg), GFP_KERNEL); if (!msg) return -ENOMEM; q->sched_props.preempt_timeout_us = preempt_timeout_us; guc_exec_queue_add_msg(q, msg, SET_SCHED_PROPS); return 0; } static int guc_exec_queue_suspend(struct xe_exec_queue *q) { struct xe_gpu_scheduler *sched = &q->guc->sched; struct xe_sched_msg *msg = q->guc->static_msgs + STATIC_MSG_SUSPEND; if (exec_queue_killed_or_banned_or_wedged(q)) return -EINVAL; xe_sched_msg_lock(sched); if (guc_exec_queue_try_add_msg(q, msg, SUSPEND)) q->guc->suspend_pending = true; xe_sched_msg_unlock(sched); return 0; } static int guc_exec_queue_suspend_wait(struct xe_exec_queue *q) { struct xe_guc *guc = exec_queue_to_guc(q); int ret; /* * Likely don't need to check exec_queue_killed() as we clear * suspend_pending upon kill but to be paranoid but races in which * suspend_pending is set after kill also check kill here. */ ret = wait_event_interruptible_timeout(q->guc->suspend_wait, !READ_ONCE(q->guc->suspend_pending) || exec_queue_killed(q) || guc_read_stopped(guc), HZ * 5); if (!ret) { xe_gt_warn(guc_to_gt(guc), "Suspend fence, guc_id=%d, failed to respond", q->guc->id); /* XXX: Trigger GT reset? */ return -ETIME; } return ret < 0 ? ret : 0; } static void guc_exec_queue_resume(struct xe_exec_queue *q) { struct xe_gpu_scheduler *sched = &q->guc->sched; struct xe_sched_msg *msg = q->guc->static_msgs + STATIC_MSG_RESUME; struct xe_guc *guc = exec_queue_to_guc(q); struct xe_device *xe = guc_to_xe(guc); xe_assert(xe, !q->guc->suspend_pending); xe_sched_msg_lock(sched); guc_exec_queue_try_add_msg(q, msg, RESUME); xe_sched_msg_unlock(sched); } static bool guc_exec_queue_reset_status(struct xe_exec_queue *q) { return exec_queue_reset(q) || exec_queue_killed_or_banned_or_wedged(q); } /* * All of these functions are an abstraction layer which other parts of XE can * use to trap into the GuC backend. All of these functions, aside from init, * really shouldn't do much other than trap into the DRM scheduler which * synchronizes these operations. */ static const struct xe_exec_queue_ops guc_exec_queue_ops = { .init = guc_exec_queue_init, .kill = guc_exec_queue_kill, .fini = guc_exec_queue_fini, .set_priority = guc_exec_queue_set_priority, .set_timeslice = guc_exec_queue_set_timeslice, .set_preempt_timeout = guc_exec_queue_set_preempt_timeout, .suspend = guc_exec_queue_suspend, .suspend_wait = guc_exec_queue_suspend_wait, .resume = guc_exec_queue_resume, .reset_status = guc_exec_queue_reset_status, }; static void guc_exec_queue_stop(struct xe_guc *guc, struct xe_exec_queue *q) { struct xe_gpu_scheduler *sched = &q->guc->sched; /* Stop scheduling + flush any DRM scheduler operations */ xe_sched_submission_stop(sched); /* Clean up lost G2H + reset engine state */ if (exec_queue_registered(q)) { if (exec_queue_extra_ref(q) || xe_exec_queue_is_lr(q)) xe_exec_queue_put(q); else if (exec_queue_destroyed(q)) __guc_exec_queue_fini(guc, q); } if (q->guc->suspend_pending) { set_exec_queue_suspended(q); suspend_fence_signal(q); } atomic_and(EXEC_QUEUE_STATE_WEDGED | EXEC_QUEUE_STATE_BANNED | EXEC_QUEUE_STATE_KILLED | EXEC_QUEUE_STATE_DESTROYED | EXEC_QUEUE_STATE_SUSPENDED, &q->guc->state); q->guc->resume_time = 0; trace_xe_exec_queue_stop(q); /* * Ban any engine (aside from kernel and engines used for VM ops) with a * started but not complete job or if a job has gone through a GT reset * more than twice. */ if (!(q->flags & (EXEC_QUEUE_FLAG_KERNEL | EXEC_QUEUE_FLAG_VM))) { struct xe_sched_job *job = xe_sched_first_pending_job(sched); bool ban = false; if (job) { if ((xe_sched_job_started(job) && !xe_sched_job_completed(job)) || xe_sched_invalidate_job(job, 2)) { trace_xe_sched_job_ban(job); ban = true; } } else if (xe_exec_queue_is_lr(q) && (xe_lrc_ring_head(q->lrc[0]) != xe_lrc_ring_tail(q->lrc[0]))) { ban = true; } if (ban) { set_exec_queue_banned(q); xe_guc_exec_queue_trigger_cleanup(q); } } } int xe_guc_submit_reset_prepare(struct xe_guc *guc) { int ret; /* * Using an atomic here rather than submission_state.lock as this * function can be called while holding the CT lock (engine reset * failure). submission_state.lock needs the CT lock to resubmit jobs. * Atomic is not ideal, but it works to prevent against concurrent reset * and releasing any TDRs waiting on guc->submission_state.stopped. */ ret = atomic_fetch_or(1, &guc->submission_state.stopped); smp_wmb(); wake_up_all(&guc->ct.wq); return ret; } void xe_guc_submit_reset_wait(struct xe_guc *guc) { wait_event(guc->ct.wq, xe_device_wedged(guc_to_xe(guc)) || !guc_read_stopped(guc)); } void xe_guc_submit_stop(struct xe_guc *guc) { struct xe_exec_queue *q; unsigned long index; struct xe_device *xe = guc_to_xe(guc); xe_assert(xe, guc_read_stopped(guc) == 1); mutex_lock(&guc->submission_state.lock); xa_for_each(&guc->submission_state.exec_queue_lookup, index, q) { /* Prevent redundant attempts to stop parallel queues */ if (q->guc->id != index) continue; guc_exec_queue_stop(guc, q); } mutex_unlock(&guc->submission_state.lock); /* * No one can enter the backend at this point, aside from new engine * creation which is protected by guc->submission_state.lock. */ } static void guc_exec_queue_start(struct xe_exec_queue *q) { struct xe_gpu_scheduler *sched = &q->guc->sched; if (!exec_queue_killed_or_banned_or_wedged(q)) { int i; trace_xe_exec_queue_resubmit(q); for (i = 0; i < q->width; ++i) xe_lrc_set_ring_head(q->lrc[i], q->lrc[i]->ring.tail); xe_sched_resubmit_jobs(sched); } xe_sched_submission_start(sched); xe_sched_submission_resume_tdr(sched); } int xe_guc_submit_start(struct xe_guc *guc) { struct xe_exec_queue *q; unsigned long index; struct xe_device *xe = guc_to_xe(guc); xe_assert(xe, guc_read_stopped(guc) == 1); mutex_lock(&guc->submission_state.lock); atomic_dec(&guc->submission_state.stopped); xa_for_each(&guc->submission_state.exec_queue_lookup, index, q) { /* Prevent redundant attempts to start parallel queues */ if (q->guc->id != index) continue; guc_exec_queue_start(q); } mutex_unlock(&guc->submission_state.lock); wake_up_all(&guc->ct.wq); return 0; } static struct xe_exec_queue * g2h_exec_queue_lookup(struct xe_guc *guc, u32 guc_id) { struct xe_device *xe = guc_to_xe(guc); struct xe_exec_queue *q; if (unlikely(guc_id >= GUC_ID_MAX)) { drm_err(&xe->drm, "Invalid guc_id %u", guc_id); return NULL; } q = xa_load(&guc->submission_state.exec_queue_lookup, guc_id); if (unlikely(!q)) { drm_err(&xe->drm, "Not engine present for guc_id %u", guc_id); return NULL; } xe_assert(xe, guc_id >= q->guc->id); xe_assert(xe, guc_id < (q->guc->id + q->width)); return q; } static void deregister_exec_queue(struct xe_guc *guc, struct xe_exec_queue *q) { u32 action[] = { XE_GUC_ACTION_DEREGISTER_CONTEXT, q->guc->id, }; xe_gt_assert(guc_to_gt(guc), exec_queue_destroyed(q)); xe_gt_assert(guc_to_gt(guc), exec_queue_registered(q)); xe_gt_assert(guc_to_gt(guc), !exec_queue_pending_disable(q)); xe_gt_assert(guc_to_gt(guc), !exec_queue_pending_enable(q)); trace_xe_exec_queue_deregister(q); xe_guc_ct_send_g2h_handler(&guc->ct, action, ARRAY_SIZE(action)); } static void handle_sched_done(struct xe_guc *guc, struct xe_exec_queue *q, u32 runnable_state) { trace_xe_exec_queue_scheduling_done(q); if (runnable_state == 1) { xe_gt_assert(guc_to_gt(guc), exec_queue_pending_enable(q)); q->guc->resume_time = ktime_get(); clear_exec_queue_pending_enable(q); smp_wmb(); wake_up_all(&guc->ct.wq); } else { bool check_timeout = exec_queue_check_timeout(q); xe_gt_assert(guc_to_gt(guc), runnable_state == 0); xe_gt_assert(guc_to_gt(guc), exec_queue_pending_disable(q)); clear_exec_queue_pending_disable(q); if (q->guc->suspend_pending) { suspend_fence_signal(q); } else { if (exec_queue_banned(q) || check_timeout) { smp_wmb(); wake_up_all(&guc->ct.wq); } if (!check_timeout) deregister_exec_queue(guc, q); } } } int xe_guc_sched_done_handler(struct xe_guc *guc, u32 *msg, u32 len) { struct xe_device *xe = guc_to_xe(guc); struct xe_exec_queue *q; u32 guc_id = msg[0]; u32 runnable_state = msg[1]; if (unlikely(len < 2)) { drm_err(&xe->drm, "Invalid length %u", len); return -EPROTO; } q = g2h_exec_queue_lookup(guc, guc_id); if (unlikely(!q)) return -EPROTO; if (unlikely(!exec_queue_pending_enable(q) && !exec_queue_pending_disable(q))) { xe_gt_err(guc_to_gt(guc), "SCHED_DONE: Unexpected engine state 0x%04x, guc_id=%d, runnable_state=%u", atomic_read(&q->guc->state), q->guc->id, runnable_state); return -EPROTO; } handle_sched_done(guc, q, runnable_state); return 0; } static void handle_deregister_done(struct xe_guc *guc, struct xe_exec_queue *q) { trace_xe_exec_queue_deregister_done(q); clear_exec_queue_registered(q); if (exec_queue_extra_ref(q) || xe_exec_queue_is_lr(q)) xe_exec_queue_put(q); else __guc_exec_queue_fini(guc, q); } int xe_guc_deregister_done_handler(struct xe_guc *guc, u32 *msg, u32 len) { struct xe_device *xe = guc_to_xe(guc); struct xe_exec_queue *q; u32 guc_id = msg[0]; if (unlikely(len < 1)) { drm_err(&xe->drm, "Invalid length %u", len); return -EPROTO; } q = g2h_exec_queue_lookup(guc, guc_id); if (unlikely(!q)) return -EPROTO; if (!exec_queue_destroyed(q) || exec_queue_pending_disable(q) || exec_queue_pending_enable(q) || exec_queue_enabled(q)) { xe_gt_err(guc_to_gt(guc), "DEREGISTER_DONE: Unexpected engine state 0x%04x, guc_id=%d", atomic_read(&q->guc->state), q->guc->id); return -EPROTO; } handle_deregister_done(guc, q); return 0; } int xe_guc_exec_queue_reset_handler(struct xe_guc *guc, u32 *msg, u32 len) { struct xe_gt *gt = guc_to_gt(guc); struct xe_device *xe = guc_to_xe(guc); struct xe_exec_queue *q; u32 guc_id = msg[0]; if (unlikely(len < 1)) { drm_err(&xe->drm, "Invalid length %u", len); return -EPROTO; } q = g2h_exec_queue_lookup(guc, guc_id); if (unlikely(!q)) return -EPROTO; xe_gt_info(gt, "Engine reset: engine_class=%s, logical_mask: 0x%x, guc_id=%d", xe_hw_engine_class_to_str(q->class), q->logical_mask, guc_id); /* FIXME: Do error capture, most likely async */ trace_xe_exec_queue_reset(q); /* * A banned engine is a NOP at this point (came from * guc_exec_queue_timedout_job). Otherwise, kick drm scheduler to cancel * jobs by setting timeout of the job to the minimum value kicking * guc_exec_queue_timedout_job. */ set_exec_queue_reset(q); if (!exec_queue_banned(q) && !exec_queue_check_timeout(q)) xe_guc_exec_queue_trigger_cleanup(q); return 0; } int xe_guc_exec_queue_memory_cat_error_handler(struct xe_guc *guc, u32 *msg, u32 len) { struct xe_gt *gt = guc_to_gt(guc); struct xe_device *xe = guc_to_xe(guc); struct xe_exec_queue *q; u32 guc_id = msg[0]; if (unlikely(len < 1)) { drm_err(&xe->drm, "Invalid length %u", len); return -EPROTO; } q = g2h_exec_queue_lookup(guc, guc_id); if (unlikely(!q)) return -EPROTO; xe_gt_dbg(gt, "Engine memory cat error: engine_class=%s, logical_mask: 0x%x, guc_id=%d", xe_hw_engine_class_to_str(q->class), q->logical_mask, guc_id); trace_xe_exec_queue_memory_cat_error(q); /* Treat the same as engine reset */ set_exec_queue_reset(q); if (!exec_queue_banned(q) && !exec_queue_check_timeout(q)) xe_guc_exec_queue_trigger_cleanup(q); return 0; } int xe_guc_exec_queue_reset_failure_handler(struct xe_guc *guc, u32 *msg, u32 len) { struct xe_device *xe = guc_to_xe(guc); u8 guc_class, instance; u32 reason; if (unlikely(len != 3)) { drm_err(&xe->drm, "Invalid length %u", len); return -EPROTO; } guc_class = msg[0]; instance = msg[1]; reason = msg[2]; /* Unexpected failure of a hardware feature, log an actual error */ drm_err(&xe->drm, "GuC engine reset request failed on %d:%d because 0x%08X", guc_class, instance, reason); xe_gt_reset_async(guc_to_gt(guc)); return 0; } static void guc_exec_queue_wq_snapshot_capture(struct xe_exec_queue *q, struct xe_guc_submit_exec_queue_snapshot *snapshot) { struct xe_guc *guc = exec_queue_to_guc(q); struct xe_device *xe = guc_to_xe(guc); struct iosys_map map = xe_lrc_parallel_map(q->lrc[0]); int i; snapshot->guc.wqi_head = q->guc->wqi_head; snapshot->guc.wqi_tail = q->guc->wqi_tail; snapshot->parallel.wq_desc.head = parallel_read(xe, map, wq_desc.head); snapshot->parallel.wq_desc.tail = parallel_read(xe, map, wq_desc.tail); snapshot->parallel.wq_desc.status = parallel_read(xe, map, wq_desc.wq_status); if (snapshot->parallel.wq_desc.head != snapshot->parallel.wq_desc.tail) { for (i = snapshot->parallel.wq_desc.head; i != snapshot->parallel.wq_desc.tail; i = (i + sizeof(u32)) % WQ_SIZE) snapshot->parallel.wq[i / sizeof(u32)] = parallel_read(xe, map, wq[i / sizeof(u32)]); } } static void guc_exec_queue_wq_snapshot_print(struct xe_guc_submit_exec_queue_snapshot *snapshot, struct drm_printer *p) { int i; drm_printf(p, "\tWQ head: %u (internal), %d (memory)\n", snapshot->guc.wqi_head, snapshot->parallel.wq_desc.head); drm_printf(p, "\tWQ tail: %u (internal), %d (memory)\n", snapshot->guc.wqi_tail, snapshot->parallel.wq_desc.tail); drm_printf(p, "\tWQ status: %u\n", snapshot->parallel.wq_desc.status); if (snapshot->parallel.wq_desc.head != snapshot->parallel.wq_desc.tail) { for (i = snapshot->parallel.wq_desc.head; i != snapshot->parallel.wq_desc.tail; i = (i + sizeof(u32)) % WQ_SIZE) drm_printf(p, "\tWQ[%zu]: 0x%08x\n", i / sizeof(u32), snapshot->parallel.wq[i / sizeof(u32)]); } } /** * xe_guc_exec_queue_snapshot_capture - Take a quick snapshot of the GuC Engine. * @q: faulty exec queue * * This can be printed out in a later stage like during dev_coredump * analysis. * * Returns: a GuC Submit Engine snapshot object that must be freed by the * caller, using `xe_guc_exec_queue_snapshot_free`. */ struct xe_guc_submit_exec_queue_snapshot * xe_guc_exec_queue_snapshot_capture(struct xe_exec_queue *q) { struct xe_gpu_scheduler *sched = &q->guc->sched; struct xe_guc_submit_exec_queue_snapshot *snapshot; int i; snapshot = kzalloc(sizeof(*snapshot), GFP_ATOMIC); if (!snapshot) return NULL; snapshot->guc.id = q->guc->id; memcpy(&snapshot->name, &q->name, sizeof(snapshot->name)); snapshot->class = q->class; snapshot->logical_mask = q->logical_mask; snapshot->width = q->width; snapshot->refcount = kref_read(&q->refcount); snapshot->sched_timeout = sched->base.timeout; snapshot->sched_props.timeslice_us = q->sched_props.timeslice_us; snapshot->sched_props.preempt_timeout_us = q->sched_props.preempt_timeout_us; snapshot->lrc = kmalloc_array(q->width, sizeof(struct xe_lrc_snapshot *), GFP_ATOMIC); if (snapshot->lrc) { for (i = 0; i < q->width; ++i) { struct xe_lrc *lrc = q->lrc[i]; snapshot->lrc[i] = xe_lrc_snapshot_capture(lrc); } } snapshot->schedule_state = atomic_read(&q->guc->state); snapshot->exec_queue_flags = q->flags; snapshot->parallel_execution = xe_exec_queue_is_parallel(q); if (snapshot->parallel_execution) guc_exec_queue_wq_snapshot_capture(q, snapshot); spin_lock(&sched->base.job_list_lock); snapshot->pending_list_size = list_count_nodes(&sched->base.pending_list); snapshot->pending_list = kmalloc_array(snapshot->pending_list_size, sizeof(struct pending_list_snapshot), GFP_ATOMIC); if (snapshot->pending_list) { struct xe_sched_job *job_iter; i = 0; list_for_each_entry(job_iter, &sched->base.pending_list, drm.list) { snapshot->pending_list[i].seqno = xe_sched_job_seqno(job_iter); snapshot->pending_list[i].fence = dma_fence_is_signaled(job_iter->fence) ? 1 : 0; snapshot->pending_list[i].finished = dma_fence_is_signaled(&job_iter->drm.s_fence->finished) ? 1 : 0; i++; } } spin_unlock(&sched->base.job_list_lock); return snapshot; } /** * xe_guc_exec_queue_snapshot_capture_delayed - Take delayed part of snapshot of the GuC Engine. * @snapshot: Previously captured snapshot of job. * * This captures some data that requires taking some locks, so it cannot be done in signaling path. */ void xe_guc_exec_queue_snapshot_capture_delayed(struct xe_guc_submit_exec_queue_snapshot *snapshot) { int i; if (!snapshot || !snapshot->lrc) return; for (i = 0; i < snapshot->width; ++i) xe_lrc_snapshot_capture_delayed(snapshot->lrc[i]); } /** * xe_guc_exec_queue_snapshot_print - Print out a given GuC Engine snapshot. * @snapshot: GuC Submit Engine snapshot object. * @p: drm_printer where it will be printed out. * * This function prints out a given GuC Submit Engine snapshot object. */ void xe_guc_exec_queue_snapshot_print(struct xe_guc_submit_exec_queue_snapshot *snapshot, struct drm_printer *p) { int i; if (!snapshot) return; drm_printf(p, "\nGuC ID: %d\n", snapshot->guc.id); drm_printf(p, "\tName: %s\n", snapshot->name); drm_printf(p, "\tClass: %d\n", snapshot->class); drm_printf(p, "\tLogical mask: 0x%x\n", snapshot->logical_mask); drm_printf(p, "\tWidth: %d\n", snapshot->width); drm_printf(p, "\tRef: %d\n", snapshot->refcount); drm_printf(p, "\tTimeout: %ld (ms)\n", snapshot->sched_timeout); drm_printf(p, "\tTimeslice: %u (us)\n", snapshot->sched_props.timeslice_us); drm_printf(p, "\tPreempt timeout: %u (us)\n", snapshot->sched_props.preempt_timeout_us); for (i = 0; snapshot->lrc && i < snapshot->width; ++i) xe_lrc_snapshot_print(snapshot->lrc[i], p); drm_printf(p, "\tSchedule State: 0x%x\n", snapshot->schedule_state); drm_printf(p, "\tFlags: 0x%lx\n", snapshot->exec_queue_flags); if (snapshot->parallel_execution) guc_exec_queue_wq_snapshot_print(snapshot, p); for (i = 0; snapshot->pending_list && i < snapshot->pending_list_size; i++) drm_printf(p, "\tJob: seqno=%d, fence=%d, finished=%d\n", snapshot->pending_list[i].seqno, snapshot->pending_list[i].fence, snapshot->pending_list[i].finished); } /** * xe_guc_exec_queue_snapshot_free - Free all allocated objects for a given * snapshot. * @snapshot: GuC Submit Engine snapshot object. * * This function free all the memory that needed to be allocated at capture * time. */ void xe_guc_exec_queue_snapshot_free(struct xe_guc_submit_exec_queue_snapshot *snapshot) { int i; if (!snapshot) return; if (snapshot->lrc) { for (i = 0; i < snapshot->width; i++) xe_lrc_snapshot_free(snapshot->lrc[i]); kfree(snapshot->lrc); } kfree(snapshot->pending_list); kfree(snapshot); } static void guc_exec_queue_print(struct xe_exec_queue *q, struct drm_printer *p) { struct xe_guc_submit_exec_queue_snapshot *snapshot; snapshot = xe_guc_exec_queue_snapshot_capture(q); xe_guc_exec_queue_snapshot_print(snapshot, p); xe_guc_exec_queue_snapshot_free(snapshot); } /** * xe_guc_submit_print - GuC Submit Print. * @guc: GuC. * @p: drm_printer where it will be printed out. * * This function capture and prints snapshots of **all** GuC Engines. */ void xe_guc_submit_print(struct xe_guc *guc, struct drm_printer *p) { struct xe_exec_queue *q; unsigned long index; if (!xe_device_uc_enabled(guc_to_xe(guc))) return; mutex_lock(&guc->submission_state.lock); xa_for_each(&guc->submission_state.exec_queue_lookup, index, q) guc_exec_queue_print(q, p); mutex_unlock(&guc->submission_state.lock); }