// SPDX-License-Identifier: MIT /* * Copyright �� 2021 Intel Corporation */ #include "gt/intel_gt_print.h" #include "intel_guc_print.h" #include "selftests/igt_spinner.h" #include "selftests/intel_scheduler_helpers.h" static int request_add_spin(struct i915_request *rq, struct igt_spinner *spin) { int err = 0; i915_request_get(rq); i915_request_add(rq); if (spin && !igt_wait_for_spinner(spin, rq)) err = -ETIMEDOUT; return err; } static struct i915_request *nop_user_request(struct intel_context *ce, struct i915_request *from) { struct i915_request *rq; int ret; rq = intel_context_create_request(ce); if (IS_ERR(rq)) return rq; if (from) { ret = i915_sw_fence_await_dma_fence(&rq->submit, &from->fence, 0, I915_FENCE_GFP); if (ret < 0) { i915_request_put(rq); return ERR_PTR(ret); } } i915_request_get(rq); i915_request_add(rq); return rq; } static int intel_guc_scrub_ctbs(void *arg) { struct intel_gt *gt = arg; int ret = 0; int i; struct i915_request *last[3] = {NULL, NULL, NULL}, *rq; intel_wakeref_t wakeref; struct intel_engine_cs *engine; struct intel_context *ce; if (!intel_has_gpu_reset(gt)) return 0; wakeref = intel_runtime_pm_get(gt->uncore->rpm); engine = intel_selftest_find_any_engine(gt); /* Submit requests and inject errors forcing G2H to be dropped */ for (i = 0; i < 3; ++i) { ce = intel_context_create(engine); if (IS_ERR(ce)) { ret = PTR_ERR(ce); gt_err(gt, "Failed to create context %d: %pe\n", i, ce); goto err; } switch (i) { case 0: ce->drop_schedule_enable = true; break; case 1: ce->drop_schedule_disable = true; break; case 2: ce->drop_deregister = true; break; } rq = nop_user_request(ce, NULL); intel_context_put(ce); if (IS_ERR(rq)) { ret = PTR_ERR(rq); gt_err(gt, "Failed to create request %d: %pe\n", i, rq); goto err; } last[i] = rq; } for (i = 0; i < 3; ++i) { ret = i915_request_wait(last[i], 0, HZ); if (ret < 0) { gt_err(gt, "Last request failed to complete: %pe\n", ERR_PTR(ret)); goto err; } i915_request_put(last[i]); last[i] = NULL; } /* Force all H2G / G2H to be submitted / processed */ intel_gt_retire_requests(gt); msleep(500); /* Scrub missing G2H */ intel_gt_handle_error(engine->gt, -1, 0, "selftest reset"); /* GT will not idle if G2H are lost */ ret = intel_gt_wait_for_idle(gt, HZ); if (ret < 0) { gt_err(gt, "GT failed to idle: %pe\n", ERR_PTR(ret)); goto err; } err: for (i = 0; i < 3; ++i) if (last[i]) i915_request_put(last[i]); intel_runtime_pm_put(gt->uncore->rpm, wakeref); return ret; } /* * intel_guc_steal_guc_ids - Test to exhaust all guc_ids and then steal one * * This test creates a spinner which is used to block all subsequent submissions * until it completes. Next, a loop creates a context and a NOP request each * iteration until the guc_ids are exhausted (request creation returns -EAGAIN). * The spinner is ended, unblocking all requests created in the loop. At this * point all guc_ids are exhausted but are available to steal. Try to create * another request which should successfully steal a guc_id. Wait on last * request to complete, idle GPU, verify a guc_id was stolen via a counter, and * exit the test. Test also artificially reduces the number of guc_ids so the * test runs in a timely manner. */ static int intel_guc_steal_guc_ids(void *arg) { struct intel_gt *gt = arg; struct intel_guc *guc = >->uc.guc; int ret, sv, context_index = 0; intel_wakeref_t wakeref; struct intel_engine_cs *engine; struct intel_context **ce; struct igt_spinner spin; struct i915_request *spin_rq = NULL, *rq, *last = NULL; int number_guc_id_stolen = guc->number_guc_id_stolen; ce = kcalloc(GUC_MAX_CONTEXT_ID, sizeof(*ce), GFP_KERNEL); if (!ce) { guc_err(guc, "Context array allocation failed\n"); return -ENOMEM; } wakeref = intel_runtime_pm_get(gt->uncore->rpm); engine = intel_selftest_find_any_engine(gt); sv = guc->submission_state.num_guc_ids; guc->submission_state.num_guc_ids = 512; /* Create spinner to block requests in below loop */ ce[context_index] = intel_context_create(engine); if (IS_ERR(ce[context_index])) { ret = PTR_ERR(ce[context_index]); guc_err(guc, "Failed to create context: %pe\n", ce[context_index]); ce[context_index] = NULL; goto err_wakeref; } ret = igt_spinner_init(&spin, engine->gt); if (ret) { guc_err(guc, "Failed to create spinner: %pe\n", ERR_PTR(ret)); goto err_contexts; } spin_rq = igt_spinner_create_request(&spin, ce[context_index], MI_ARB_CHECK); if (IS_ERR(spin_rq)) { ret = PTR_ERR(spin_rq); guc_err(guc, "Failed to create spinner request: %pe\n", spin_rq); goto err_contexts; } ret = request_add_spin(spin_rq, &spin); if (ret) { guc_err(guc, "Failed to add Spinner request: %pe\n", ERR_PTR(ret)); goto err_spin_rq; } /* Use all guc_ids */ while (ret != -EAGAIN) { ce[++context_index] = intel_context_create(engine); if (IS_ERR(ce[context_index])) { ret = PTR_ERR(ce[context_index]); guc_err(guc, "Failed to create context: %pe\n", ce[context_index]); ce[context_index--] = NULL; goto err_spin_rq; } rq = nop_user_request(ce[context_index], spin_rq); if (IS_ERR(rq)) { ret = PTR_ERR(rq); rq = NULL; if ((ret != -EAGAIN) || !last) { guc_err(guc, "Failed to create %srequest %d: %pe\n", last ? "" : "first ", context_index, ERR_PTR(ret)); goto err_spin_rq; } } else { if (last) i915_request_put(last); last = rq; } } /* Release blocked requests */ igt_spinner_end(&spin); ret = intel_selftest_wait_for_rq(spin_rq); if (ret) { guc_err(guc, "Spin request failed to complete: %pe\n", ERR_PTR(ret)); i915_request_put(last); goto err_spin_rq; } i915_request_put(spin_rq); igt_spinner_fini(&spin); spin_rq = NULL; /* Wait for last request */ ret = i915_request_wait(last, 0, HZ * 30); i915_request_put(last); if (ret < 0) { guc_err(guc, "Last request failed to complete: %pe\n", ERR_PTR(ret)); goto err_spin_rq; } /* Try to steal guc_id */ rq = nop_user_request(ce[context_index], NULL); if (IS_ERR(rq)) { ret = PTR_ERR(rq); guc_err(guc, "Failed to steal guc_id %d: %pe\n", context_index, rq); goto err_spin_rq; } /* Wait for request with stolen guc_id */ ret = i915_request_wait(rq, 0, HZ); i915_request_put(rq); if (ret < 0) { guc_err(guc, "Request with stolen guc_id failed to complete: %pe\n", ERR_PTR(ret)); goto err_spin_rq; } /* Wait for idle */ ret = intel_gt_wait_for_idle(gt, HZ * 30); if (ret < 0) { guc_err(guc, "GT failed to idle: %pe\n", ERR_PTR(ret)); goto err_spin_rq; } /* Verify a guc_id was stolen */ if (guc->number_guc_id_stolen == number_guc_id_stolen) { guc_err(guc, "No guc_id was stolen"); ret = -EINVAL; } else { ret = 0; } err_spin_rq: if (spin_rq) { igt_spinner_end(&spin); intel_selftest_wait_for_rq(spin_rq); i915_request_put(spin_rq); igt_spinner_fini(&spin); intel_gt_wait_for_idle(gt, HZ * 30); } err_contexts: for (; context_index >= 0 && ce[context_index]; --context_index) intel_context_put(ce[context_index]); err_wakeref: intel_runtime_pm_put(gt->uncore->rpm, wakeref); kfree(ce); guc->submission_state.num_guc_ids = sv; return ret; } /* * Send a context schedule H2G message with an invalid context id. * This should generate a GUC_RESULT_INVALID_CONTEXT response. */ static int bad_h2g(struct intel_guc *guc) { u32 action[] = { INTEL_GUC_ACTION_SCHED_CONTEXT, 0x12345678, }; return intel_guc_send_nb(guc, action, ARRAY_SIZE(action), 0); } /* * Set a spinner running to make sure the system is alive and active, * then send a bad but asynchronous H2G command and wait to see if an * error response is returned. If no response is received or if the * spinner dies then the test will fail. */ #define FAST_RESPONSE_TIMEOUT_MS 1000 static int intel_guc_fast_request(void *arg) { struct intel_gt *gt = arg; struct intel_context *ce; struct igt_spinner spin; struct i915_request *rq; intel_wakeref_t wakeref; struct intel_engine_cs *engine = intel_selftest_find_any_engine(gt); bool spinning = false; int ret = 0; if (!engine) return 0; wakeref = intel_runtime_pm_get(gt->uncore->rpm); ce = intel_context_create(engine); if (IS_ERR(ce)) { ret = PTR_ERR(ce); gt_err(gt, "Failed to create spinner request: %pe\n", ce); goto err_pm; } ret = igt_spinner_init(&spin, engine->gt); if (ret) { gt_err(gt, "Failed to create spinner: %pe\n", ERR_PTR(ret)); goto err_pm; } spinning = true; rq = igt_spinner_create_request(&spin, ce, MI_ARB_CHECK); intel_context_put(ce); if (IS_ERR(rq)) { ret = PTR_ERR(rq); gt_err(gt, "Failed to create spinner request: %pe\n", rq); goto err_spin; } ret = request_add_spin(rq, &spin); if (ret) { gt_err(gt, "Failed to add Spinner request: %pe\n", ERR_PTR(ret)); goto err_rq; } gt->uc.guc.fast_response_selftest = 1; ret = bad_h2g(>->uc.guc); if (ret) { gt_err(gt, "Failed to send H2G: %pe\n", ERR_PTR(ret)); goto err_rq; } ret = wait_for(gt->uc.guc.fast_response_selftest != 1 || i915_request_completed(rq), FAST_RESPONSE_TIMEOUT_MS); if (ret) { gt_err(gt, "Request wait failed: %pe\n", ERR_PTR(ret)); goto err_rq; } if (i915_request_completed(rq)) { gt_err(gt, "Spinner died waiting for fast request error!\n"); ret = -EIO; goto err_rq; } if (gt->uc.guc.fast_response_selftest != 2) { gt_err(gt, "Unexpected fast response count: %d\n", gt->uc.guc.fast_response_selftest); goto err_rq; } igt_spinner_end(&spin); spinning = false; ret = intel_selftest_wait_for_rq(rq); if (ret) { gt_err(gt, "Request failed to complete: %pe\n", ERR_PTR(ret)); goto err_rq; } err_rq: i915_request_put(rq); err_spin: if (spinning) igt_spinner_end(&spin); igt_spinner_fini(&spin); err_pm: intel_runtime_pm_put(gt->uncore->rpm, wakeref); return ret; } int intel_guc_live_selftests(struct drm_i915_private *i915) { static const struct i915_subtest tests[] = { SUBTEST(intel_guc_scrub_ctbs), SUBTEST(intel_guc_steal_guc_ids), SUBTEST(intel_guc_fast_request), }; struct intel_gt *gt = to_gt(i915); if (intel_gt_is_wedged(gt)) return 0; if (!intel_uc_uses_guc_submission(>->uc)) return 0; return intel_gt_live_subtests(tests, gt); }