Commit 1d1d0af6 authored by Chris Wilson's avatar Chris Wilson
Browse files

drm/i915/selftests: Verify mmap_gtt revocation on unbinding



Whenever, we unbind (or change fence registers) on an object, we must
revoke any and all mmap_gtt using the previous bindings. Those user PTEs
point at the GGTT which know points into a new object, the wrong object.
Ergo, those PTEs must be cleared so that any user access provokes a new
page fault.

Signed-off-by: default avatarChris Wilson <chris@chris-wilson.co.uk>
Cc: Abdiel Janulgue <abdiel.janulgue@linux.intel.com>
Reviewed-by: default avatarAbdiel Janulgue <abdiel.janulgue@linux.intel.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20191107180601.30815-5-chris@chris-wilson.co.uk
parent 6fedafac
Loading
Loading
Loading
Loading
+111 −0
Original line number Diff line number Diff line
@@ -793,6 +793,116 @@ out:
	return err;
}

static int check_present_pte(pte_t *pte, unsigned long addr, void *data)
{
	if (!pte_present(*pte) || pte_none(*pte)) {
		pr_err("missing PTE:%lx\n",
		       (addr - (unsigned long)data) >> PAGE_SHIFT);
		return -EINVAL;
	}

	return 0;
}

static int check_absent_pte(pte_t *pte, unsigned long addr, void *data)
{
	if (pte_present(*pte) && !pte_none(*pte)) {
		pr_err("present PTE:%lx; expected to be revoked\n",
		       (addr - (unsigned long)data) >> PAGE_SHIFT);
		return -EINVAL;
	}

	return 0;
}

static int check_present(unsigned long addr, unsigned long len)
{
	return apply_to_page_range(current->mm, addr, len,
				   check_present_pte, (void *)addr);
}

static int check_absent(unsigned long addr, unsigned long len)
{
	return apply_to_page_range(current->mm, addr, len,
				   check_absent_pte, (void *)addr);
}

static int prefault_range(u64 start, u64 len)
{
	const char __user *addr, *end;
	char __maybe_unused c;
	int err;

	addr = u64_to_user_ptr(start);
	end = addr + len;

	for (; addr < end; addr += PAGE_SIZE) {
		err = __get_user(c, addr);
		if (err)
			return err;
	}

	return __get_user(c, end - 1);
}

static int igt_mmap_gtt_revoke(void *arg)
{
	struct drm_i915_private *i915 = arg;
	struct drm_i915_gem_object *obj;
	unsigned long addr;
	int err;

	if (!i915_ggtt_has_aperture(&i915->ggtt))
		return 0;

	obj = i915_gem_object_create_internal(i915, SZ_4M);
	if (IS_ERR(obj))
		return PTR_ERR(obj);

	err = create_mmap_offset(obj);
	if (err)
		goto out;

	addr = igt_mmap_node(i915, &obj->base.vma_node,
			     0, PROT_WRITE, MAP_SHARED);
	if (IS_ERR_VALUE(addr)) {
		err = addr;
		goto out;
	}

	err = prefault_range(addr, obj->base.size);
	if (err)
		goto out_unmap;

	GEM_BUG_ON(!atomic_read(&obj->bind_count));

	err = check_present(addr, obj->base.size);
	if (err)
		goto out_unmap;

	/*
	 * After unbinding the object from the GGTT, its address may be reused
	 * for other objects. Ergo we have to revoke the previous mmap PTE
	 * access as it no longer points to the same object.
	 */
	err = i915_gem_object_unbind(obj, I915_GEM_OBJECT_UNBIND_ACTIVE);
	if (err) {
		pr_err("Failed to unbind object!\n");
		goto out_unmap;
	}
	GEM_BUG_ON(atomic_read(&obj->bind_count));

	err = check_absent(addr, obj->base.size);
	if (err)
		goto out_unmap;

out_unmap:
	vm_munmap(addr, obj->base.size);
out:
	i915_gem_object_put(obj);
	return err;
}

int i915_gem_mman_live_selftests(struct drm_i915_private *i915)
{
	static const struct i915_subtest tests[] = {
@@ -800,6 +910,7 @@ int i915_gem_mman_live_selftests(struct drm_i915_private *i915)
		SUBTEST(igt_smoke_tiling),
		SUBTEST(igt_mmap_offset_exhaustion),
		SUBTEST(igt_mmap_gtt),
		SUBTEST(igt_mmap_gtt_revoke),
	};

	return i915_subtests(tests, i915);