/*
 * i915_stolen_harness.c -- DF-4679 logic-level verification
 *
 * Verifies, on the DragonFly guest, the u64-wrap chain in
 *   sys/dev/drm/i915/i915_gem_stolen.c
 *   g4x_get_stolen_reserved()   (:201-229, *size = stolen_top - *base :228)
 *   bdw_get_stolen_reserved()   (:349-366, *size = stolen_top - *base :365)
 *   i915_gem_init_stolen()      (:475-503: !reserved_base check, containment
 *                                 :485, reserved_total = stolen_top -
 *                                 reserved_base :493, stolen_usable_size
 *                                 :499-500, drm_mm_init(0, usable) :503)
 *
 * The inline helpers are transcribed VERBATIM from
 *   sys/dev/drm/include/linux/pci.h:621-642  (struct linux_resource,
 *   DEFINE_RES_MEM, resource_size, resource_contains)
 * and the two register masks from sys/dev/drm/i915/i915_reg.h:401,3507.
 * build.sh grep-verifies every transcribed line against the guest's
 * /usr/src copy before compiling.
 *
 * Register values (CTG/ELK/GEN6_STOLEN_RESERVED) are chipset config
 * space programmed by the BIOS: garbage / malicious firmware values are
 * the threat model.  A reserved base ABOVE stolen_top makes
 * "stolen_top - *base" wrap mod 2^64; DEFINE_RES_MEM then computes
 * dsm_reserved.end = base + wrapped_size - 1 == stolen_top - 1 == dsm.end,
 * so resource_contains() PASSES even though the reservation starts above
 * the stolen region -- and stolen_usable_size inflates ABOVE the real
 * stolen size, so the drm_mm stolen allocator hands out offsets beyond
 * dsm.end.  i915_pages_create_for_stolen() (:516,536) then builds
 * sg_dma_address = dsm.start + offset pointing OUTSIDE stolen memory
 * (the GEM_BUG_ON at :516 is a no-op in release: i915_gem.h:38-61),
 * i.e. the GPU is pointed at arbitrary physical memory it must not touch.
 */
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

typedef uint64_t resource_size_t;	/* linux/types.h:35 */

/* ---- verbatim: sys/dev/drm/include/linux/pci.h:621-642 ---- */
struct linux_resource {
	resource_size_t start;
	resource_size_t end;
};

#define DEFINE_RES_MEM(_start, _size)		\
	{					\
		.start = (_start),		\
		.end = (_start) + (_size) - 1,	\
	}

static inline resource_size_t
resource_size(const struct linux_resource *res)
{
	return res->end - res->start + 1;
}

static inline bool
resource_contains(struct linux_resource *a, struct linux_resource *b)
{
	return a->start <= b->start && a->end >= b->end;
}

/* ---- transcribed from i915_gem_stolen.c (see build.sh grep checks) ---- */
static struct linux_resource dsm, dsm_reserved;

static void g4x_get_stolen_reserved(resource_size_t reg_val,
				    resource_size_t *base,
				    resource_size_t *size)
{
	resource_size_t stolen_top = dsm.end + 1;		/* :208 */

	*base = (reg_val & 0xFFF0u) << 16;	/* G4X_STOLEN_RESERVED_ADDR2_MASK
						 * = (0xFFF << 4), then <<16 :225 */
	*size = stolen_top - *base;					/* :228 */
}

static void bdw_get_stolen_reserved(resource_size_t reg_val,
				    resource_size_t *base,
				    resource_size_t *size)
{
	resource_size_t stolen_top = dsm.end + 1;		/* :354 */

	*base = reg_val & 0xFFF00000ull;	/* GEN6_STOLEN_RESERVED_ADDR_MASK
						 * = (0xFFF << 20) :364 */
	*size = stolen_top - *base;					/* :365 */
}

/* i915_gem_init_stolen() tail, :475-503 (reserved_base != 0 by
 * construction here).  Returns stolen_usable_size passed to
 * drm_mm_init(&dev_priv->mm.stolen, 0, ...) at :503. */
static resource_size_t init_stolen_tail(resource_size_t reserved_base,
					resource_size_t reserved_size)
{
	resource_size_t stolen_top = dsm.end + 1;
	resource_size_t reserved_total;

	dsm_reserved = (struct linux_resource)
		DEFINE_RES_MEM(reserved_base, reserved_size);	/* :482-483 */

	if (!resource_contains(&dsm, &dsm_reserved))		/* :485 */
		return 0;					/* stolen disabled */

	reserved_total = stolen_top - reserved_base;		/* :493 */
	return resource_size(&dsm) - reserved_total;		/* :499-500 */
}

/* The fix.diff variant of the same tail: the sanity check at :475
 * becomes "if (!reserved_base || reserved_base > stolen_top)" and the
 * reservation is ignored (existing !reserved_base fallback semantics)
 * when the BIOS-programmed base sits above stolen_top. */
static resource_size_t init_stolen_tail_fixed(resource_size_t reserved_base,
					      resource_size_t reserved_size)
{
	resource_size_t stolen_top = dsm.end + 1;
	resource_size_t reserved_total;

	if (!reserved_base || reserved_base > stolen_top) {
		reserved_base = stolen_top;
		reserved_size = 0;
	}

	dsm_reserved = (struct linux_resource)
		DEFINE_RES_MEM(reserved_base, reserved_size);

	if (!resource_contains(&dsm, &dsm_reserved))
		return 0;

	reserved_total = stolen_top - reserved_base;
	return resource_size(&dsm) - reserved_total;
}

static int g_fixed;

static int case_run(const char *name, struct linux_resource region,
		    void (*get)(resource_size_t, resource_size_t *,
				resource_size_t *),
		    resource_size_t reg_val, bool expect_inflate)
{
	resource_size_t base = 0, size = 0, usable, real, maxphys;
	int fail = 0;

	dsm = region;
	real = resource_size(&dsm);
	get(reg_val, &base, &size);
	usable = g_fixed ? init_stolen_tail_fixed(base, size)
			 : init_stolen_tail(base, size);

	maxphys = dsm.start + (usable ? usable - 1 : 0);	/* :536 worst case */

	printf("== %s ==\n", name);
	printf("  stolen (dsm)          : [%#llx..%#llx] (%llu MiB)\n",
	    (unsigned long long)dsm.start, (unsigned long long)dsm.end,
	    (unsigned long long)real >> 20);
	printf("  reserved base (reg)   : %#llx   size(raw u64): %#llx\n",
	    (unsigned long long)base, (unsigned long long)size);
	printf("  resource_contains     : %s\n",
	    resource_contains(&dsm, &dsm_reserved) ? "PASS" : "FAIL");
	printf("  stolen_usable_size    : %llu (%.2fx the real %llu)\n",
	    (unsigned long long)usable,
	    real ? (double)usable / (double)real : 0.0,
	    (unsigned long long)real);
	printf("  worst-case alloc phys : dsm.start+usable-1 = %#llx "
	       "(dsm.end=%#llx => %s stolen)\n",
	    (unsigned long long)maxphys, (unsigned long long)dsm.end,
	    maxphys > dsm.end ? "OUTSIDE" : "inside");

	if (expect_inflate) {
		bool bypassed = resource_contains(&dsm, &dsm_reserved) &&
		    base > dsm.end;
		bool inflated = usable > real;
		if (g_fixed) {
			if (inflated) {
				printf("  !! FIX FAILED: inflation still occurs\n");
				fail = 1;
			} else {
				printf("  FIX VERIFIED: no wrap, no containment "
				       "bypass, usable (%llu) <= real (%llu); "
				       "allocations stay inside stolen\n",
				       (unsigned long long)usable,
				       (unsigned long long)real);
			}
		} else if (!bypassed || !inflated) {
			printf("  !! expected containment bypass + usable-size "
			       "inflation, did not occur\n");
			fail = 1;
		} else {
			printf("  DF-4679 REPRODUCED: reserved base above "
			       "stolen_top wraps u64 size; containment check "
			       "(:485) PASSES via end-wrap; drm_mm initialized "
			       "with %llu bytes (%.2fx real) => allocations "
			       "beyond dsm.end => OOB physical memory\n",
			       (unsigned long long)usable,
			       (double)usable / (double)real);
		}
	} else {
		if (usable == 0 || usable > real) {
			printf("  !! sane-firmware control case misbehaved\n");
			fail = 1;
		} else {
			printf("  (control: sane firmware base => usable < "
			       "real; behavior normal)\n");
		}
	}
	return fail;
}

int main(int argc, char **argv)
{
	int fail = 0;

	g_fixed = (argc > 1 && strcmp(argv[1], "fixed") == 0);
	if (g_fixed)
		printf(">>> FIXED build (fix.diff guard active) <<<\n");
	else
		printf(">>> BASELINE build (pristine :475 logic) <<<\n");
	const unsigned long long MB = 1ull << 20;

	/* Realistic G4X: 32 MiB stolen at 0x00D00000 (top of low RAM) */
	fail |= case_run("G4X garbage reserved base (0xABC -> 0x0ABC0000)",
	    (struct linux_resource) DEFINE_RES_MEM(0x00D00000, 32 * MB),
	    g4x_get_stolen_reserved, 0x0ABC0u /* ADDR2 field 0xABC, EN set */,
	    true);

	/* Control: same machine, sane BIOS reserving top 1 MiB of stolen
	 * (base 0x2C00000 = reg ADDR2 field 0x2C0) */
	fail |= case_run("G4X sane reserved base (top 1 MiB of stolen)",
	    (struct linux_resource) DEFINE_RES_MEM(0x00D00000, 32 * MB),
	    g4x_get_stolen_reserved, 0x002C0u,
	    false);

	/* BDW: 32 MiB stolen at 0x40000000; ADDR field all-ones 0xFFF */
	fail |= case_run("BDW garbage reserved base (0xFFF -> 0xFFF00000)",
	    (struct linux_resource) DEFINE_RES_MEM(0x40000000, 32 * MB),
	    bdw_get_stolen_reserved, 0xFFF00001ull /* ADDR + EN bit */,
	    true);

	printf(fail ? "\nRESULT: FAIL\n" : "\nRESULT: ALL CASES OK\n");
	return fail;
}
