// 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);
}