/* * 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)
/** * 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, unsignedint 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;
/* * Given that we want to guarantee enough RCS throughput to avoid missing * frames, we set the yield policy to 20% of each 80ms interval.
*/ #define RC_YIELD_DURATION 80 /* in ms */ #define RC_YIELD_RATIO 20 /* in percent */ static u32 *emit_render_compute_yield_klv(u32 *emit)
{
*emit++ = PREP_GUC_KLV_TAG(SCHEDULING_POLICIES_RENDER_COMPUTE_YIELD);
*emit++ = RC_YIELD_DURATION;
*emit++ = RC_YIELD_RATIO;
if (xa_empty(&guc->submission_state.exec_queue_lookup))
wake_up(&guc->submission_state.fini_wq);
}
staticint 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;
}
/* * 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);
}
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);
dma_fence_put(job->fence); /* Drop ref from xe_sched_job_arm */
} else {
fence = job->fence;
}
/* * 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);
}
/** 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_gt *gt = guc_to_gt(guc); struct xe_exec_queue *q; unsignedlong index; int err;
/* * If device is being wedged even before submission_state is * initialized, there's nothing to do here.
*/ if (!guc->submission_state.initialized) return;
err = devm_add_action_or_reset(guc_to_xe(guc)->drm.dev,
guc_submit_wedged_fini, guc); if (err) {
xe_gt_err(gt, "Failed to register 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);
}
if (!exec_queue_killed(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;
/* * 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) ||
xe_guc_read_stopped(guc), HZ * 5); if (!ret) {
xe_gt_warn(q->gt, "Schedule disable failed to respond, guc_id=%d\n",
q->guc->id);
xe_devcoredump(q, NULL, "Schedule disable failed to respond, guc_id=%d\n",
q->guc->id);
xe_sched_submission_start(sched);
xe_gt_reset_async(q->gt); return;
}
}
/* * 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);
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));
/* * 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)) return DRM_GPU_SCHED_STAT_NO_HANG;
/* 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);
/* * If devcoredump not captured and GuC capture for the job is not ready * do manual capture first and decide later if we need to use it
*/ if (!exec_queue_killed(q) && !xe->devcoredump.captured &&
!xe_guc_capture_get_matching_and_lock(q)) { /* take force wake before engine register manual capture */
fw_ref = xe_force_wake_get(gt_to_fw(q->gt), XE_FORCEWAKE_ALL); if (!xe_force_wake_ref_has_domain(fw_ref, XE_FORCEWAKE_ALL))
xe_gt_info(q->gt, "failed to get forcewake for coredump capture\n");
xe_engine_snapshot_capture_for_queue(q);
xe_force_wake_put(gt_to_fw(q->gt), fw_ref);
}
/* * 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.
*/ if (!exec_queue_killed(q))
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) &&
!exec_queue_pending_disable(q)) ||
xe_guc_read_stopped(guc), HZ * 5); if (!ret || xe_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) ||
xe_guc_read_stopped(guc), HZ * 5); if (!ret || xe_guc_read_stopped(guc)) {
trigger_reset: if (!ret)
xe_gt_warn(guc_to_gt(guc), "Schedule disable failed to respond, guc_id=%d",
q->guc->id);
xe_devcoredump(q, job, "Schedule disable failed to respond, guc_id=%d, ret=%d, guc_read=%d",
q->guc->id, ret, xe_guc_read_stopped(guc));
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;
}
/* * 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);
}
/* * 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);
sched_enable:
enable_scheduling(q);
rearm: /* * XXX: Ideally want to adjust timeout based on current execution 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_submission_start(sched); return DRM_GPU_SCHED_STAT_NO_HANG;
}
if (xe_exec_queue_is_lr(q))
cancel_work_sync(&ge->lr_tdr); /* Confirm no work left behind accessing device structures */
cancel_delayed_work_sync(&ge->sched.base.work_tdr);
/* 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_destroy_async(&q->guc->destroy_async); else
queue_work(xe->destroy_wq, &q->guc->destroy_async);
}
staticvoid __guc_exec_queue_destroy(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_destroy_async(q);
}
/* * Expected state transitions for cleanup: * - If the exec queue is registered and GuC firmware is running, we must first * disable scheduling and deregister the queue to ensure proper teardown and * resource release in the GuC, then destroy the exec queue on driver side. * - If the GuC is already stopped (e.g., during driver unload or GPU reset), * we cannot expect a response for the deregister request. In this case, * it is safe to directly destroy the exec queue on driver side, as the GuC * will not process further requests and all resources must be cleaned up locally.
*/ if (exec_queue_registered(q) && xe_uc_fw_is_running(&guc->fw))
disable_scheduling_deregister(guc, q); else
__guc_exec_queue_destroy(guc, q);
}
/* * 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) ||
xe_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;
}
/* * 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.
*/ staticconststruct xe_exec_queue_ops guc_exec_queue_ops = {
.init = guc_exec_queue_init,
.kill = guc_exec_queue_kill,
.fini = guc_exec_queue_fini,
.destroy = guc_exec_queue_destroy,
.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,
};
/* 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); elseif (exec_queue_destroyed(q))
__guc_exec_queue_destroy(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;
}
} elseif (xe_exec_queue_is_lr(q) &&
!xe_lrc_ring_is_idle(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;
if (!guc->submission_state.initialized) return 0;
/* * 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);
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);
}
if (q->guc->suspend_pending) {
suspend_fence_signal(q);
clear_exec_queue_pending_disable(q);
} else { if (exec_queue_banned(q) || check_timeout) {
smp_wmb();
wake_up_all(&guc->ct.wq);
} if (!check_timeout && exec_queue_destroyed(q)) { /* * Make sure to clear the pending_disable only * after sampling the destroyed state. We want * to ensure we don't trigger the unregister too * early with something intending to only * disable scheduling. The caller doing the * destroy must wait for an ongoing * pending_disable before marking as destroyed.
*/
clear_exec_queue_pending_disable(q);
deregister_exec_queue(guc, q);
} else {
clear_exec_queue_pending_disable(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;
}
/* * xe_guc_error_capture_handler - Handler of GuC captured message * @guc: The GuC object * @msg: Point to the message * @len: The message length * * When GuC captured data is ready, GuC will send message * XE_GUC_ACTION_STATE_CAPTURE_NOTIFICATION to host, this function will be * called 1st to check status before process the data comes with the message. * * Returns: error code. 0 if success
*/ int xe_guc_error_capture_handler(struct xe_guc *guc, u32 *msg, u32 len)
{
u32 status;
if (unlikely(len != XE_GUC_ACTION_STATE_CAPTURE_NOTIFICATION_DATA_LEN)) return -EPROTO;
status = msg[0] & XE_GUC_STATE_CAPTURE_EVENT_STATUS_MASK; if (status == XE_GUC_STATE_CAPTURE_EVENT_STATUS_NOSPACE)
xe_gt_warn(guc_to_gt(guc), "G2H-Error capture no space");
if (guc_id == GUC_ID_UNKNOWN) { /* * GuC uses GUC_ID_UNKNOWN if it can not map the CAT fault to any PF/VF * context. In such case only PF will be notified about that fault.
*/
xe_gt_err_ratelimited(gt, "Memory CAT error reported by GuC!\n"); return 0;
}
q = g2h_exec_queue_lookup(guc, guc_id); if (unlikely(!q)) return -EPROTO;
/* * The type is HW-defined and changes based on platform, so we don't * decode it in the kernel and only check if it is valid. * See bspec 54047 and 72187 for details.
*/ if (type != XE_GUC_CAT_ERR_TYPE_INVALID)
xe_gt_dbg(gt, "Engine memory CAT error [%u]: class=%s, logical_mask: 0x%x, guc_id=%d",
type, xe_hw_engine_class_to_str(q->class), q->logical_mask, guc_id); else
xe_gt_dbg(gt, "Engine memory CAT error: 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);
/* Unexpected failure of a hardware feature, log an actual error */
xe_gt_err(gt, "GuC engine reset request failed on %d:%d because 0x%08X",
guc_class, instance, reason);
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)]);
}
}
staticvoid
guc_exec_queue_wq_snapshot_print(struct xe_guc_submit_exec_queue_snapshot *snapshot, struct drm_printer *p)
{ int i;
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;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.