summaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/i915/gt/selftest_reset.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/i915/gt/selftest_reset.c')
-rw-r--r--drivers/gpu/drm/i915/gt/selftest_reset.c392
1 files changed, 392 insertions, 0 deletions
diff --git a/drivers/gpu/drm/i915/gt/selftest_reset.c b/drivers/gpu/drm/i915/gt/selftest_reset.c
new file mode 100644
index 0000000000..79aa6ac66a
--- /dev/null
+++ b/drivers/gpu/drm/i915/gt/selftest_reset.c
@@ -0,0 +1,392 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2018 Intel Corporation
+ */
+
+#include <linux/crc32.h>
+
+#include "gem/i915_gem_stolen.h"
+
+#include "i915_memcpy.h"
+#include "i915_selftest.h"
+#include "intel_gpu_commands.h"
+#include "selftests/igt_reset.h"
+#include "selftests/igt_atomic.h"
+#include "selftests/igt_spinner.h"
+
+static int
+__igt_reset_stolen(struct intel_gt *gt,
+ intel_engine_mask_t mask,
+ const char *msg)
+{
+ struct i915_ggtt *ggtt = gt->ggtt;
+ const struct resource *dsm = &gt->i915->dsm.stolen;
+ resource_size_t num_pages, page;
+ struct intel_engine_cs *engine;
+ intel_wakeref_t wakeref;
+ enum intel_engine_id id;
+ struct igt_spinner spin;
+ long max, count;
+ void *tmp;
+ u32 *crc;
+ int err;
+
+ if (!drm_mm_node_allocated(&ggtt->error_capture))
+ return 0;
+
+ num_pages = resource_size(dsm) >> PAGE_SHIFT;
+ if (!num_pages)
+ return 0;
+
+ crc = kmalloc_array(num_pages, sizeof(u32), GFP_KERNEL);
+ if (!crc)
+ return -ENOMEM;
+
+ tmp = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!tmp) {
+ err = -ENOMEM;
+ goto err_crc;
+ }
+
+ igt_global_reset_lock(gt);
+ wakeref = intel_runtime_pm_get(gt->uncore->rpm);
+
+ err = igt_spinner_init(&spin, gt);
+ if (err)
+ goto err_lock;
+
+ for_each_engine(engine, gt, id) {
+ struct intel_context *ce;
+ struct i915_request *rq;
+
+ if (!(mask & engine->mask))
+ continue;
+
+ if (!intel_engine_can_store_dword(engine))
+ continue;
+
+ ce = intel_context_create(engine);
+ if (IS_ERR(ce)) {
+ err = PTR_ERR(ce);
+ goto err_spin;
+ }
+ rq = igt_spinner_create_request(&spin, ce, MI_ARB_CHECK);
+ intel_context_put(ce);
+ if (IS_ERR(rq)) {
+ err = PTR_ERR(rq);
+ goto err_spin;
+ }
+ i915_request_add(rq);
+ }
+
+ for (page = 0; page < num_pages; page++) {
+ dma_addr_t dma = (dma_addr_t)dsm->start + (page << PAGE_SHIFT);
+ void __iomem *s;
+ void *in;
+
+ ggtt->vm.insert_page(&ggtt->vm, dma,
+ ggtt->error_capture.start,
+ i915_gem_get_pat_index(gt->i915,
+ I915_CACHE_NONE),
+ 0);
+ mb();
+
+ s = io_mapping_map_wc(&ggtt->iomap,
+ ggtt->error_capture.start,
+ PAGE_SIZE);
+
+ if (!__drm_mm_interval_first(&gt->i915->mm.stolen,
+ page << PAGE_SHIFT,
+ ((page + 1) << PAGE_SHIFT) - 1))
+ memset_io(s, STACK_MAGIC, PAGE_SIZE);
+
+ in = (void __force *)s;
+ if (i915_memcpy_from_wc(tmp, in, PAGE_SIZE))
+ in = tmp;
+ crc[page] = crc32_le(0, in, PAGE_SIZE);
+
+ io_mapping_unmap(s);
+ }
+ mb();
+ ggtt->vm.clear_range(&ggtt->vm, ggtt->error_capture.start, PAGE_SIZE);
+
+ if (mask == ALL_ENGINES) {
+ intel_gt_reset(gt, mask, NULL);
+ } else {
+ for_each_engine(engine, gt, id) {
+ if (mask & engine->mask)
+ intel_engine_reset(engine, NULL);
+ }
+ }
+
+ max = -1;
+ count = 0;
+ for (page = 0; page < num_pages; page++) {
+ dma_addr_t dma = (dma_addr_t)dsm->start + (page << PAGE_SHIFT);
+ void __iomem *s;
+ void *in;
+ u32 x;
+
+ ggtt->vm.insert_page(&ggtt->vm, dma,
+ ggtt->error_capture.start,
+ i915_gem_get_pat_index(gt->i915,
+ I915_CACHE_NONE),
+ 0);
+ mb();
+
+ s = io_mapping_map_wc(&ggtt->iomap,
+ ggtt->error_capture.start,
+ PAGE_SIZE);
+
+ in = (void __force *)s;
+ if (i915_memcpy_from_wc(tmp, in, PAGE_SIZE))
+ in = tmp;
+ x = crc32_le(0, in, PAGE_SIZE);
+
+ if (x != crc[page] &&
+ !__drm_mm_interval_first(&gt->i915->mm.stolen,
+ page << PAGE_SHIFT,
+ ((page + 1) << PAGE_SHIFT) - 1)) {
+ pr_debug("unused stolen page %pa modified by GPU reset\n",
+ &page);
+ if (count++ == 0)
+ igt_hexdump(in, PAGE_SIZE);
+ max = page;
+ }
+
+ io_mapping_unmap(s);
+ }
+ mb();
+ ggtt->vm.clear_range(&ggtt->vm, ggtt->error_capture.start, PAGE_SIZE);
+
+ if (count > 0) {
+ pr_info("%s reset clobbered %ld pages of stolen, last clobber at page %ld\n",
+ msg, count, max);
+ }
+ if (max >= I915_GEM_STOLEN_BIAS >> PAGE_SHIFT) {
+ pr_err("%s reset clobbered unreserved area [above %x] of stolen; may cause severe faults\n",
+ msg, I915_GEM_STOLEN_BIAS);
+ err = -EINVAL;
+ }
+
+err_spin:
+ igt_spinner_fini(&spin);
+
+err_lock:
+ intel_runtime_pm_put(gt->uncore->rpm, wakeref);
+ igt_global_reset_unlock(gt);
+
+ kfree(tmp);
+err_crc:
+ kfree(crc);
+ return err;
+}
+
+static int igt_reset_device_stolen(void *arg)
+{
+ return __igt_reset_stolen(arg, ALL_ENGINES, "device");
+}
+
+static int igt_reset_engines_stolen(void *arg)
+{
+ struct intel_gt *gt = arg;
+ struct intel_engine_cs *engine;
+ enum intel_engine_id id;
+ int err;
+
+ if (!intel_has_reset_engine(gt))
+ return 0;
+
+ for_each_engine(engine, gt, id) {
+ err = __igt_reset_stolen(gt, engine->mask, engine->name);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int igt_global_reset(void *arg)
+{
+ struct intel_gt *gt = arg;
+ unsigned int reset_count;
+ intel_wakeref_t wakeref;
+ int err = 0;
+
+ /* Check that we can issue a global GPU reset */
+
+ igt_global_reset_lock(gt);
+ wakeref = intel_runtime_pm_get(gt->uncore->rpm);
+
+ reset_count = i915_reset_count(&gt->i915->gpu_error);
+
+ intel_gt_reset(gt, ALL_ENGINES, NULL);
+
+ if (i915_reset_count(&gt->i915->gpu_error) == reset_count) {
+ pr_err("No GPU reset recorded!\n");
+ err = -EINVAL;
+ }
+
+ intel_runtime_pm_put(gt->uncore->rpm, wakeref);
+ igt_global_reset_unlock(gt);
+
+ if (intel_gt_is_wedged(gt))
+ err = -EIO;
+
+ return err;
+}
+
+static int igt_wedged_reset(void *arg)
+{
+ struct intel_gt *gt = arg;
+ intel_wakeref_t wakeref;
+
+ /* Check that we can recover a wedged device with a GPU reset */
+
+ igt_global_reset_lock(gt);
+ wakeref = intel_runtime_pm_get(gt->uncore->rpm);
+
+ intel_gt_set_wedged(gt);
+
+ GEM_BUG_ON(!intel_gt_is_wedged(gt));
+ intel_gt_reset(gt, ALL_ENGINES, NULL);
+
+ intel_runtime_pm_put(gt->uncore->rpm, wakeref);
+ igt_global_reset_unlock(gt);
+
+ return intel_gt_is_wedged(gt) ? -EIO : 0;
+}
+
+static int igt_atomic_reset(void *arg)
+{
+ struct intel_gt *gt = arg;
+ const typeof(*igt_atomic_phases) *p;
+ int err = 0;
+
+ /* Check that the resets are usable from atomic context */
+
+ intel_gt_pm_get(gt);
+ igt_global_reset_lock(gt);
+
+ /* Flush any requests before we get started and check basics */
+ if (!igt_force_reset(gt))
+ goto unlock;
+
+ for (p = igt_atomic_phases; p->name; p++) {
+ intel_engine_mask_t awake;
+
+ GEM_TRACE("__intel_gt_reset under %s\n", p->name);
+
+ awake = reset_prepare(gt);
+ p->critical_section_begin();
+
+ err = __intel_gt_reset(gt, ALL_ENGINES);
+
+ p->critical_section_end();
+ reset_finish(gt, awake);
+
+ if (err) {
+ pr_err("__intel_gt_reset failed under %s\n", p->name);
+ break;
+ }
+ }
+
+ /* As we poke around the guts, do a full reset before continuing. */
+ igt_force_reset(gt);
+
+unlock:
+ igt_global_reset_unlock(gt);
+ intel_gt_pm_put(gt);
+
+ return err;
+}
+
+static int igt_atomic_engine_reset(void *arg)
+{
+ struct intel_gt *gt = arg;
+ const typeof(*igt_atomic_phases) *p;
+ struct intel_engine_cs *engine;
+ enum intel_engine_id id;
+ int err = 0;
+
+ /* Check that the resets are usable from atomic context */
+
+ if (!intel_has_reset_engine(gt))
+ return 0;
+
+ if (intel_uc_uses_guc_submission(&gt->uc))
+ return 0;
+
+ intel_gt_pm_get(gt);
+ igt_global_reset_lock(gt);
+
+ /* Flush any requests before we get started and check basics */
+ if (!igt_force_reset(gt))
+ goto out_unlock;
+
+ for_each_engine(engine, gt, id) {
+ struct tasklet_struct *t = &engine->sched_engine->tasklet;
+
+ if (t->func)
+ tasklet_disable(t);
+ intel_engine_pm_get(engine);
+
+ for (p = igt_atomic_phases; p->name; p++) {
+ GEM_TRACE("intel_engine_reset(%s) under %s\n",
+ engine->name, p->name);
+ if (strcmp(p->name, "softirq"))
+ local_bh_disable();
+
+ p->critical_section_begin();
+ err = __intel_engine_reset_bh(engine, NULL);
+ p->critical_section_end();
+
+ if (strcmp(p->name, "softirq"))
+ local_bh_enable();
+
+ if (err) {
+ pr_err("intel_engine_reset(%s) failed under %s\n",
+ engine->name, p->name);
+ break;
+ }
+ }
+
+ intel_engine_pm_put(engine);
+ if (t->func) {
+ tasklet_enable(t);
+ tasklet_hi_schedule(t);
+ }
+ if (err)
+ break;
+ }
+
+ /* As we poke around the guts, do a full reset before continuing. */
+ igt_force_reset(gt);
+
+out_unlock:
+ igt_global_reset_unlock(gt);
+ intel_gt_pm_put(gt);
+
+ return err;
+}
+
+int intel_reset_live_selftests(struct drm_i915_private *i915)
+{
+ static const struct i915_subtest tests[] = {
+ SUBTEST(igt_global_reset), /* attempt to recover GPU first */
+ SUBTEST(igt_reset_device_stolen),
+ SUBTEST(igt_reset_engines_stolen),
+ SUBTEST(igt_wedged_reset),
+ SUBTEST(igt_atomic_reset),
+ SUBTEST(igt_atomic_engine_reset),
+ };
+ struct intel_gt *gt = to_gt(i915);
+
+ if (!intel_has_gpu_reset(gt))
+ return 0;
+
+ if (intel_gt_is_wedged(gt))
+ return -EIO; /* we're long past hope of a successful reset */
+
+ return intel_gt_live_subtests(tests, gt);
+}