/*
 * DF-5478 — arithmetic model of the runtime-map guard and page-fill loop in
 * efi_create_1t1_map() (sys/platform/pc64/x86_64/efirt.c:249-297).
 *
 * The guard at efirt.c:255-263 is reproduced VERBATIM (same types: all
 * arithmetic in uint64_t / vm_offset_t; EFI_PAGE_SIZE == 4096;
 * VM_MAX_USER_ADDRESS == UVADDR(255,511,511,0) == 0x00007fffffffe000 on
 * this platform, per pmap.h NUPDP_USER=256 / vmparam.h).
 *
 * The check tests only the *end pointer* of md_phys + md_pages*PAGE. When
 * md_pages*PAGE_SIZE wraps mod 2^64 to a small value W with
 * md_phys + W in [md_phys, VM_MAX_USER_ADDRESS), the guard passes while the
 * fill loop at efirt.c:288-297 still iterates md_pages (up to 2^52+)
 * times, each iteration doing kmalloc(sizeof(struct vm_page),
 * M_EFI, M_WAITOK) + vm_page_initfake + vm_page_insert — i.e. an
 * effectively unbounded driver of kernel allocation until memory (and the
 * machine, at boot, M_WAITOK under exhaustion) wedges.
 *
 * Build: cc -O2 -o df5478_wrap df5478_wrap.c
 * Run:   ./df5478_wrap
 */
#include <stdio.h>
#include <stdint.h>

typedef uint64_t u64;

#define	EFI_PAGE_SIZE	4096UL
/* pmap.h:88 NUPDP_USER = NPML4EPG/2 = 256; vmparam.h:139-140 */
#define	VM_MAX_USER_ADDRESS	0x00007fffffffe000UL

/* efirt.c:255-263, verbatim arithmetic */
static int
guard_rejects(u64 md_phys, u64 md_pages)
{
	if (md_phys + md_pages * EFI_PAGE_SIZE < md_phys)
		return 1;	/* wrap -> fail */
	if (md_phys + md_pages * EFI_PAGE_SIZE >= VM_MAX_USER_ADDRESS)
		return 1;	/* out of mappable range -> fail */
	return 0;		/* guard PASSES */
}

/* FIXED guard (fix.diff): direct md_pages cap makes product wrap impossible */
static int
fixed_guard_rejects(u64 md_phys, u64 md_pages)
{
	if (md_pages >= VM_MAX_USER_ADDRESS / EFI_PAGE_SIZE)
		return 1;	/* pages alone exceed mappable range */
	if (md_phys + md_pages * EFI_PAGE_SIZE < md_phys)
		return 1;
	if (md_phys + md_pages * EFI_PAGE_SIZE >= VM_MAX_USER_ADDRESS)
		return 1;
	return 0;
}

/* what the fill loop (efirt.c:288-297) would then do per RT descriptor */
static void
report_fixed(const char *tag, u64 md_phys, u64 md_pages)
{
	int rejected = fixed_guard_rejects(md_phys, md_pages);

	printf("%-28s md_phys=%#018lx md_pages=%#018lx -> fixed guard: %s"
	       " (loop %s)\n",
	       tag, md_phys, md_pages,
	       rejected ? "REJECT" : "accept",
	       rejected ? "never starts" : "runs");
}

static void
report(const char *tag, u64 md_phys, u64 md_pages)
{
	int rejected = guard_rejects(md_phys, md_pages);
	u64 iter = md_pages;
	double gib = (double)iter * 88.0 / (1024.0 * 1024.0 * 1024.0); /* ~88B/vm_page */

	printf("%-28s md_phys=%#018lx md_pages=%#018lx\n"
	       "%-28s   product=%#018lx  end=%#018lx  guard: %s\n",
	       tag, md_phys, md_pages, "",
	       md_pages * EFI_PAGE_SIZE, md_phys + md_pages * EFI_PAGE_SIZE,
	       rejected ? "REJECT (correct)" : "PASS  (BUG: loop runs)");
	if (!rejected)
		printf("%-28s   -> fill-loop iterations=%llu (%.3g), "
		       "kmalloc(M_WAITOK) bytes > %.3g GiB\n\n",
		       "", (unsigned long long)iter, (double)iter, gib);
	else
		printf("\n");
}

int
main(void)
{
	printf("VM_MAX_USER_ADDRESS model = %#lx\n\n", VM_MAX_USER_ADDRESS);

	/* sanity: a normal 256-page RT region is accepted (and should be) */
	report("sane 256 pages @16MB", 0x1000000UL, 256);

	/* md_pages = 2^52: product wraps to exactly 0, end == md_phys */
	report("wrap: md_pages = 2^52", 0x1000000UL, 1UL << 52);

	/* md_pages = 2^52 + 1: product wraps to 4096, end = phys+4096 */
	report("wrap: md_pages = 2^52+1", 0x1000000UL, (1UL << 52) + 1);

	/* high phys base chosen so product == 2^64 exactly */
	report("wrap: product == 2^64",
	    0xF0000000UL, (1UL << 52) - (0xF0000000UL / 4096));

	/* control: genuinely huge but non-wrapping extent is still rejected */
	report("no-wrap 2^40 pages", 0x1000000UL, 1UL << 40);

	/* ---- fix validation: same cases under the FIXED guard ---- */
	printf("=== under FIXED guard (fix.diff) ===\n\n");
	report_fixed("sane 256 pages @16MB", 0x1000000UL, 256);
	report_fixed("wrap: md_pages = 2^52", 0x1000000UL, 1UL << 52);
	report_fixed("wrap: md_pages = 2^52+1", 0x1000000UL, (1UL << 52) + 1);
	report_fixed("no-wrap 2^40 pages", 0x1000000UL, 1UL << 40);

	return 0;
}
