/*
 * mddiag.c - DF-5535 / DF-5536 diagnostic rig for
 * sys/platform/pc64/x86_64/minidump_machdep.c
 *
 * Modes (via `sysctl debug.mddiag=<mode>` as root):
 *   report - print the PDP layout facts, do nothing
 *   panic  - print facts, then panic() so a real minidump is written
 *   booby  - print facts, plant a booby-trapped PD entry (bogus frame, V set)
 *            in the slot-1 PD page, then panic()
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <vm/vm.h>
#include <vm/vm_page.h>
#include <vm/vm_page2.h>
#include <vm/pmap.h>
#include <machine/pmap.h>
#include <machine/md_var.h>
#include <machine/vmparam.h>

/* 2^47 physical frame: far beyond phys_avail; targets bitmap+4GB (unused slot 5 VA) */
#define BOGUS_FRAME 0x0000800000000000UL

static void
report(void)
{
	uint64_t *pdp;
	int slots[] = { 0, 1, 2, 8189, 8190, 8191 };
	int s;

	pdp = (uint64_t *)PHYS_TO_DMAP(KPDPphys);

	kprintf("DFRIG: VM_MIN_KERNEL_ADDRESS = %p\n",
	    (void *)VM_MIN_KERNEL_ADDRESS);
	kprintf("DFRIG: KPDPPHYS_KVA          = %p\n", (void *)KPDPPHYS_KVA);
	kprintf("DFRIG: KERNBASE              = %p\n", (void *)KERNBASE);
	kprintf("DFRIG: KvaEnd                = %p\n", (void *)KvaEnd);
	kprintf("DFRIG: KPDPphys=0x%lx, pdp array entries = NKPML4E*NPDPEPG = %ld\n",
	    (unsigned long)KPDPphys, (long)(NKPML4E * NPDPEPG));

	for (s = 0; s < (int)(sizeof(slots) / sizeof(slots[0])); s++) {
		int i = slots[s];
		kprintf("DFRIG: pdp[%d] = 0x%016lx (frame 0x%09lx V=%d)\n",
		    i, (unsigned long)pdp[i],
		    (unsigned long)(pdp[i] & PG_FRAME),
		    (pdp[i] & kernel_pmap->pmap_bits[PG_V_IDX]) ? 1 : 0);
	}

	kprintf("DFRIG: walk1 computes i = (va - VM_MIN_KERNEL_ADDRESS) >> PDPSHIFT\n");
	kprintf("DFRIG:   for va=KERNBASE: walk1 i = %d, correct slot = %d\n",
	    (int)(((unsigned long)KERNBASE - VM_MIN_KERNEL_ADDRESS) >> PDPSHIFT),
	    (int)(((unsigned long)KERNBASE - KPDPPHYS_KVA) >> PDPSHIFT));
	kprintf("DFRIG:   walk1 max i (va=KvaEnd-1) = %d, so pdp[8191] is never read\n",
	    (int)(((unsigned long)(KvaEnd - 1) - VM_MIN_KERNEL_ADDRESS) >> PDPSHIFT));
	kprintf("DFRIG:   hardware truth: vtophys(KERNBASE)=0x%lx = pdp[8190]&PG_FRAME=0x%lx\n",
	    (unsigned long)vtophys(KERNBASE),
	    (unsigned long)(pdp[8190] & PG_FRAME));

	/* census of slot 8191's PD page: what actually lives there */
	{
		uint64_t *pd;
		int n = 0, firstj = -1, j;

		pd = (uint64_t *)PHYS_TO_DMAP(pdp[8191] & PG_FRAME);
		for (j = 0; j < NPDEPG; j++) {
			if (pd[j] & kernel_pmap->pmap_bits[PG_V_IDX]) {
				if (firstj < 0)
					firstj = j;
				n++;
			}
		}
		kprintf("DFRIG: slot8191 PD page (0x%lx): %d/%ld present entries",
		    (unsigned long)(pdp[8191] & PG_FRAME), n, (long)NPDEPG);
		if (firstj >= 0)
			kprintf(" (first j=%d val=0x%lx)", firstj,
			    (unsigned long)pd[firstj]);
		kprintf("\n");
		/* count leaf mappings inside slot8191's PTs */
		{
			uint64_t psv = kernel_pmap->pmap_bits[PG_PS_IDX];
			uint64_t v = kernel_pmap->pmap_bits[PG_V_IDX];
			long leaf = 0, ps2m = 0, ptpages = 0;
			int jj;

			for (jj = 0; jj < NPDEPG; jj++) {
				if ((pd[jj] & v) == 0)
					continue;
				if (pd[jj] & psv) {
					ps2m++;
					continue;
				}
				ptpages++;
				{
					uint64_t *pt = (uint64_t *)PHYS_TO_DMAP(
					    pd[jj] & PG_FRAME);
					int k;
					for (k = 0; k < NPTEPG; k++)
						if (pt[k] & v)
							leaf++;
				}
			}
			kprintf("DFRIG: slot8191 content: %ld PT pages, "
			    "%ld 2M pages, %ld leaf 4K mappings LOST from dump\n",
			    ptpages, ps2m, leaf);
		}
	}

	kprintf("DFRIG: vm_page_dump=%p size=%lu (%lu pages covered)\n",
	    (void *)vm_page_dump, (unsigned long)vm_page_dump_size,
	    (unsigned long)(vm_page_dump_size * NBBY));
	kprintf("DFRIG: booby write target: vm_page_dump[0x%lx] = %p\n",
	    BOGUS_FRAME >> 18, (void *)&vm_page_dump[BOGUS_FRAME >> 18]);
}

static int
dumpbit(vm_paddr_t pa)
{
	uint64_t pg = pa >> PAGE_SHIFT;

	return (vm_page_dump[pg >> 6] >> (pg & 63)) & 1;
}

static void
maptest(void)
{
	vm_offset_t va1 = 0xffffffffd0000000UL;	/* slot 8191 (via PDP[8191]) */
	vm_offset_t va2 = 0xffffffffa0000000UL;	/* slot 8190 (via PDP[8190]) */
	vm_page_t m1 = NULL, m2 = NULL;
	int t;

	for (t = 0; t < 128; t++) {
		m1 = vm_page_alloc(NULL, 0, VM_ALLOC_NORMAL | VM_ALLOC_SYSTEM);
		if (m1 == NULL)
			break;
		if (dumpbit(VM_PAGE_TO_PHYS(m1)) == 0)
			break;
		vm_page_free(m1);
		m1 = NULL;
	}
	for (t = 0; t < 128; t++) {
		m2 = vm_page_alloc(NULL, 0, VM_ALLOC_NORMAL | VM_ALLOC_SYSTEM);
		if (m2 == NULL)
			break;
		if (dumpbit(VM_PAGE_TO_PHYS(m2)) == 0)
			break;
		vm_page_free(m2);
		m2 = NULL;
	}
	if (m1 == NULL || m2 == NULL)
		kprintf("DFRIG: maptest: alloc failed m1=%p m2=%p\n", m1, m2);

	pmap_growkernel(va1, va1 + PAGE_SIZE);
	pmap_growkernel(va2, va2 + PAGE_SIZE);
	pmap_kenter(va1, VM_PAGE_TO_PHYS(m1));
	pmap_kenter(va2, VM_PAGE_TO_PHYS(m2));

	kprintf("DFRIG: maptest: va1=%p (slot 8191) -> pa 0x%lx, pre-panic bit=%d\n",
	    (void *)va1, (unsigned long)VM_PAGE_TO_PHYS(m1),
	    dumpbit(VM_PAGE_TO_PHYS(m1)));
	kprintf("DFRIG: maptest: va2=%p (slot 8190) -> pa 0x%lx, pre-panic bit=%d\n",
	    (void *)va2, (unsigned long)VM_PAGE_TO_PHYS(m2),
	    dumpbit(VM_PAGE_TO_PHYS(m2)));
	kprintf("DFRIG: maptest: after dump, pa(slot8191) must be CLEAR, "
	    "pa(slot8190) must be SET\n");
}

static void
booby(void)
{
	uint64_t *pdp;
	uint64_t vbit = kernel_pmap->pmap_bits[PG_V_IDX];
	int i, nused = 0;

	pdp = (uint64_t *)PHYS_TO_DMAP(KPDPphys);

	/*
	 * Find an unused PD slot far beyond the allocation frontier
	 * (kernel_vm_end), so nothing allocates into it between now and
	 * the dump walk.
	 */
	for (i = 64; i < NKPML4E * NPDPEPG; i++) {
		if (pdp[i] & vbit)
			nused++;
		else
			break;
	}
	if (i >= NKPML4E * NPDPEPG) {
		kprintf("DFRIG: booby: no unused slot; aborting\n");
		return;
	}
	kprintf("DFRIG: booby: first unused PD slot = %d (%d in use); VA %p..%p unused\n",
	    i, nused,
	    (void *)(KPDPPHYS_KVA + ((vm_offset_t)i << PDPSHIFT)),
	    (void *)(KPDPPHYS_KVA + ((vm_offset_t)(i + 1) << PDPSHIFT)));

	/*
	 * Plant a fake PD entry pointing at a bogus physical frame.
	 * Slot i has no mappings, so nothing live is disturbed.
	 * Walk hits it: line 254 V-check passes, line 260 does the
	 * unguarded dump_add_page(BOGUS_FRAME), line 263 does
	 * PHYS_TO_DMAP(BOGUS_FRAME).
	 */
	pdp[i] = BOGUS_FRAME | vbit;
	kprintf("DFRIG: booby planted: pdp[%d] = 0x%lx|V\n", i, BOGUS_FRAME);
	kprintf("DFRIG: dump walk will call dump_add_page(0x%lx) unguarded -> "
	    "vm_page_dump[0x%lx] = %p\n", BOGUS_FRAME, BOGUS_FRAME >> 18,
	    (void *)&vm_page_dump[BOGUS_FRAME >> 18]);
}

static int
sysctl_mddiag(SYSCTL_HANDLER_ARGS)
{
	char buf[32];
	int error;

	bzero(buf, sizeof(buf));
	error = sysctl_handle_string(oidp, buf, sizeof(buf), req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (strcmp(buf, "report") == 0) {
		report();
	} else if (strcmp(buf, "panic") == 0) {
		report();
		panic("DF-5535 rig: intentional panic for minidump capture");
		/* NOTREACHED */
	} else if (strcmp(buf, "maptest") == 0) {
		report();
		maptest();
		panic("DF-5535 maptest: slot-8191 mapping vs slot-8190 control");
		/* NOTREACHED */
	} else if (strcmp(buf, "booby") == 0) {
		report();
		booby();
		panic("DF-5536 rig: intentional panic with booby PD entry");
		/* NOTREACHED */
	}
	return (0);
}
SYSCTL_PROC(_debug, OID_AUTO, mddiag, CTLTYPE_STRING | CTLFLAG_RW,
    0, 0, sysctl_mddiag, "A", "minidump diagnostic rig");

static int
mddiag_load(module_t mod, int cmd, void *arg)
{

	switch (cmd) {
	case MOD_LOAD:
		kprintf("mddiag loaded; use: sysctl debug.mddiag=report|panic|booby\n");
		report();
		break;
	default:
		break;
	}
	return (0);
}

static moduledata_t mddiag_mod = {
	"mddiag",
	mddiag_load,
	NULL
};
DECLARE_MODULE(mddiag, mddiag_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
