/*
 * rig2_main.c -- driver for the VERBATIM pccard.c lifecycle verification rig.
 *
 * The production code is machine-extracted byte-identical from the guest's
 * /usr/src (extract2.sh, sha256-gated):
 *   pccardvar2.inc    structs + read macros + accessors (pccardvar.h)
 *   pccard_cis.h/.    constant headers (whole-file copies)
 *   pccard_cis_core.inc pccard_read_cis + pccard_scan_cis +
 *                     pccard_parse_cis_tuple (pccard_cis.c)
 *   pccard_core.inc   pccard_attach_card / pccard_detach_card /
 *                     pccard_function_init / _free / _enable / _disable /
 *                     pccard_set_default_descr / pccard_safe_quote /
 *                     pccard_child_pnpinfo_str / pccard_read_ivar (pccard.c)
 *   subrbus_rl.inc    resource_list_init/free/add/find/delete +
 *                     bus_child_pnpinfo_str (kern/subr_bus.c)
 *
 * Scenarios (each a crafted CIS attribute-memory image driven through the
 * REAL entry pccard_attach_card(rig_bus_dev), in a forked crash-instrumented
 * child):
 *
 *   no_vers1_devadded  DF-2318: card without CISTPL_VERS_1; driver attaches
 *                      -> devadded -> pnpinfo -> pccard_safe_quote(NULL)
 *   no_vers1_nomatch   DF-2318: same card, no driver -> devnomatch -> ditto
 *   no_vers1_sysctl    DF-2318: attach survives (silent), then a
 *                      hw.bus.devices walk (devinfo(8)) hits pnpinfo
 *   vers1_pnpinfo_ok   negative: card WITH VERS_1 -> pnpinfo string builds
 *   busypanic          DF-5355: cfe#0 partial allocation (anywhere ok +
 *                      fixed 0x3F8 refused) leaves rid-0 busy; cfe#1's
 *                      resource_list_add -> "resource entry is busy"
 *   reentry_leak       DF-5356: attach -> CD-bounce re-attach (no detach) ->
 *                      attach again -> detach once: orphaned pf/cfe list
 *                      (permanent kmem leak), zombie children, held bridge
 *                      windows; N cycles -> linear unbounded growth
 *   benign_cycle       negative: attach -> detach repeated: zero leak
 *
 * Build:  sh extract2.sh . && cc -O2 -g -Wall -o rig2 rig2_main.c
 * Run:    ./rig2 <scenario> | all
 */
#include "rig2_shims.h"
#include "sysbus_rl.inc"		/* VERBATIM resource_list_entry/struct */
#include "pccardvar2.inc"		/* VERBATIM structs + macros */
#include "pccard_cis.h"			/* VERBATIM tuple codes */
#include "pccardreg.h"			/* VERBATIM device-type codes */
#include "subrbus_rl.inc"		/* VERBATIM resource_list_* + pnpinfo */
#include "pccard_cis_core.inc"		/* VERBATIM production CIS parser */
/* pccard.c is a separate TU in production; its DPRINTF set is distinct */
#undef DPRINTF
#undef DEVPRINTF
#undef PRVERBOSE
#undef DEVPRVERBOSE
#include "pccard_macros.inc"		/* VERBATIM PCCARDDEBUG + DPRINTF */
#include "pccard_core.inc"		/* VERBATIM pccard.c lifecycle */

#include <stddef.h>
#include <sys/wait.h>
#include <time.h>

/* ---------------- rig globals (shims extern them) ---------------- */
unsigned char rig_attr_img[RIG_TOTBUF];
unsigned char rig_com_img[RIG_TOTBUF];
unsigned char rig_ccr_img[RIG_CCRBUF];
struct resource rig_cis_res = { .handle = rig_attr_img, .size = RIG_MAPSIZE,
    .start = 0xD0000, .end = 0xD0000 + RIG_MAPSIZE - 1 };
struct resource rig_ccr_res = { .handle = rig_ccr_img, .size = 1 << 10,
    .start = 0xD1000, .end = 0xD1000 + (1 << 10) - 1 };
struct resource rig_irq_res = { .handle = NULL, .size = 1,
    .start = 11, .end = 11 };
struct resource rig_io_pool[RIG_IO_POOL];
unsigned long rig_io_cursor = RIG_IO_WIN_START;
long rig_io_grants, rig_io_releases, rig_ccr_allocs, rig_ccr_releases;
int  rig_space;
long rig_power_enables, rig_power_disables, rig_flag_switches;
int  rig_probe_mode = PROBE_ATTACH_OK;
int  rig_nchildren;
struct rig_device *rig_devices;
void *rig_sc_ptr;

struct rig_device rig_bus_dev = { .nameunit = "pccard0" };
struct rig_device rig_bridge_dev = { .nameunit = "pcic0" };

/* the real softc (type known now) */
static struct pccard_softc rig_sc;

/* pccard_check_cis_quirks/pccard_print_cis: production consults a static
 * quirk table (pccard_cis_quirks.c) and debug printing; neither affects the
 * generic-CIS paths verified here. */
void pccard_check_cis_quirks(device_t dev) { (void)dev; }
void pccard_print_cis(device_t dev) { (void)dev; }

int
rig_bus_read_ivar(device_t bus, device_t dev, int which, u_char *result)
{
	if (bus == &rig_bus_dev)
		return pccard_read_ivar(bus, dev, which, result);
	return (EINVAL);
}

struct resource *
rig_bus_alloc_io(unsigned long start, unsigned long end, unsigned long count)
{
	int i;

	if (count == 0)
		return NULL;
	if (start == 0 && end == (unsigned long)~0) {
		/* "anywhere" request: allocate at the window cursor */
		if (rig_io_cursor + count - 1 > RIG_IO_WIN_END)
			return NULL;
	} else {
		/* fixed range: must lie inside the bridge window */
		if (start < RIG_IO_WIN_START || end > RIG_IO_WIN_END ||
		    end < start || end - start + 1 < count)
			return NULL;
	}
	for (i = 0; i < RIG_IO_POOL; i++)
		if (rig_io_pool[i].size == 0)
			break;
	if (i == RIG_IO_POOL)
		return NULL;
	if (start == 0 && end == (unsigned long)~0) {
		rig_io_pool[i].start = rig_io_cursor;
		rig_io_cursor += count;
	} else {
		rig_io_pool[i].start = start;
	}
	rig_io_pool[i].end = rig_io_pool[i].start + count - 1;
	rig_io_pool[i].size = count;
	rig_io_pool[i].handle = NULL;
	rig_io_grants++;
	return &rig_io_pool[i];
}

/* ---------------- CIS image construction (attribute memory) --------- */
static void
img_reset(void)
{
	size_t i;
	for (i = 0; i < RIG_MAPSIZE; i++) {
		rig_attr_img[i] = 0xFF;		/* unwritten attr mem = END */
		rig_com_img[i]  = 0xFF;
	}
	for (i = RIG_MAPSIZE; i < RIG_TOTBUF; i++) {
		rig_attr_img[i] = 0xA5;		/* adjacent-mapping canary */
		rig_com_img[i]  = 0xA5;
	}
}

/* write tuple at tuple-index *t (attribute spacing: bus offset = 2*t) */
static void
pt(int *t, int code, const unsigned char *body, int blen)
{
	int i, t2 = *t;
	if (2 * t2 + 4 + 2 * blen >= (int)RIG_MAPSIZE) {
		printf("RIG scenario-build overflow t=%d\n", t2);
		abort();
	}
	rig_attr_img[2 * t2]     = (unsigned char)code;
	rig_attr_img[2 * t2 + 2] = (unsigned char)blen;
	for (i = 0; i < blen; i++)
		rig_attr_img[2 * t2 + 4 + 2 * i] = body[i];
	*t = t2 + 2 + blen;
}

/* --- tuple bodies --- */
static const unsigned char vers1[] = {
	0x04, 0x01,
	'A','C','M','E', 0, 'W','i','d','g','e','t', 0,
};
static const unsigned char manfid[] = { 0x34, 0x12, 0x78, 0x56 };
static const unsigned char config[] = { 0x00, 0xFF, 0x10, 0x20 };
/* CFTABLE: intface|DEFAULT|num=0, iftype=IO(1), IOSPACE only, 16bit+HASRANGE */
static const unsigned char cfe_io1_any[] = {	/* 1 range: anywhere len 9 */
	0xC0, 0x41, 0x08, 0xC0, 0xA0,
	0x00, 0x00,	/* start 0x0000 (anywhere) */
	0x08, 0x00,	/* length 8 (+1 => 9) */
};
static const unsigned char cfe_io2_any_f8[] = {	/* 2 ranges: anywhere + 0x3F8 */
	0xC0, 0x41, 0x08, 0xC0, 0xA1,
	0x00, 0x00,	/* range0 start 0 (anywhere), */
	0x08, 0x00,	/*            length 8 (+1 => 9) */
	0xF8, 0x03,	/* range1 start 0x03F8 (fixed, OUTSIDE the bridge
			 * window [0x110,0x2FF] -- a legacy-occupied host
			 * range the bridge cannot decode) */
	0x08, 0x00,	/*            length 8 (+1 => 9) */
};
static const unsigned char cfe_io1_fix200[] = {	/* 1 range: fixed 0x200 */
	0x81, 0x41, 0x08, 0xC0, 0xA0,
	0x00, 0x02,	/* start 0x0200 (inside the window) */
	0x08, 0x00,	/* length 8 (+1 => 9) */
};

static void
build_benign(void)			/* valid card with VERS_1 + 1 io cfe */
{
	int t = 0;
	pt(&t, CISTPL_VERS_1, vers1, sizeof(vers1));
	pt(&t, CISTPL_MANFID, manfid, sizeof(manfid));
	pt(&t, CISTPL_CONFIG, config, sizeof(config));
	pt(&t, CISTPL_CFTABLE_ENTRY, cfe_io1_any, sizeof(cfe_io1_any));
}

static void
build_no_vers1(void)			/* same card, NO CISTPL_VERS_1 */
{
	int t = 0;
	pt(&t, CISTPL_MANFID, manfid, sizeof(manfid));
	pt(&t, CISTPL_CONFIG, config, sizeof(config));
	pt(&t, CISTPL_CFTABLE_ENTRY, cfe_io1_any, sizeof(cfe_io1_any));
}

static void
build_busypanic(void)			/* cfe0 partial-fail, cfe1 clean */
{
	int t = 0;
	pt(&t, CISTPL_VERS_1, vers1, sizeof(vers1));
	pt(&t, CISTPL_MANFID, manfid, sizeof(manfid));
	pt(&t, CISTPL_CONFIG, config, sizeof(config));
	pt(&t, CISTPL_CFTABLE_ENTRY, cfe_io2_any_f8, sizeof(cfe_io2_any_f8));
	pt(&t, CISTPL_CFTABLE_ENTRY, cfe_io1_fix200, sizeof(cfe_io1_fix200));
}

/* ---------------- full rig reset between scenarios ---------------- */
static void
rig_reset(void)
{
	int i;

	img_reset();
	memset(&rig_sc, 0, sizeof(rig_sc));
	rig_sc.dev = &rig_bus_dev;
	rig_sc_ptr = &rig_sc;
	rig_bus_dev.parent = &rig_bridge_dev;
	rig_bus_dev.ivars = NULL;
	for (i = 0; i < RIG_MAX_BLOCKS; i++)
		rig_blocks[i].live = 0;		/* storage freed below */
	rig_nblocks = 0;
	rig_devices = NULL;
	rig_nchildren = 0;
	rig_probe_mode = PROBE_ATTACH_OK;
	rig_power_enables = rig_power_disables = 0;
	rig_flag_switches = 0;
	for (i = 0; i < RIG_IO_POOL; i++)
		rig_io_pool[i].size = 0;
	rig_io_cursor = RIG_IO_WIN_START;
	rig_io_grants = rig_io_releases = 0;
	rig_ccr_allocs = rig_ccr_releases = 0;
}

/* ---------------- leak accounting (DF-5356) ---------------- */
static size_t rig_sz_pf, rig_sz_cfe, rig_sz_ivar;

static int
ptr_in_current_list(void *p)
{
	struct pccard_function *pf;
	struct pccard_config_entry *cfe;

	STAILQ_FOREACH(pf, &rig_sc.card.pf_head, pf_list) {
		if ((void *)pf == p)
			return 1;
		STAILQ_FOREACH(cfe, &pf->cfe_head, cfe_list)
			if ((void *)cfe == p)
				return 1;
	}
	return 0;
}

static void
orphan_accounting(const char *tag)
{
	int i, orph_pf = 0, orph_cfe = 0, zombies = 0, cur = 0;
	size_t leak = 0;
	struct pccard_function *pf;
	device_t d;
	int npf = 0, ncfe = 0;

	STAILQ_FOREACH(pf, &rig_sc.card.pf_head, pf_list) {
		struct pccard_config_entry *cfe;
		npf++;
		STAILQ_FOREACH(cfe, &pf->cfe_head, cfe_list)
			ncfe++;
	}
	for (d = rig_devices; d != NULL; d = d->next) {
		if (d->deleted)
			continue;
		zombies++;
	}
	for (i = 0; i < rig_nblocks; i++) {
		if (!rig_blocks[i].live)
			continue;
		if (rig_blocks[i].site && strcmp(rig_blocks[i].site,
		    "pccard_parse_cis_tuple") == 0) {
			if (rig_blocks[i].size == rig_sz_pf ||
			    rig_blocks[i].size == rig_sz_cfe) {
				if (!ptr_in_current_list(rig_blocks[i].p)) {
					if (rig_blocks[i].size == rig_sz_pf)
						orph_pf++;
					else
						orph_cfe++;
					leak += rig_blocks[i].size;
				}
			}
		}
	}
	cur = npf;
	printf("[%s] current_list: pf=%d cfe=%d | ORPHANED: pf=%d cfe=%d "
	    "(leak=%zu bytes) | live-children(zombies)=%d | io-windows-held=%ld "
	    "ccr-held=%ld\n",
	    tag, npf, ncfe, orph_pf, orph_cfe, leak, zombies,
	    rig_io_grants - rig_io_releases, rig_ccr_allocs - rig_ccr_releases);
	(void)cur;
}

/* free a pf/cfe chain without the detach path (pre-check cleanup only) */
static void
free_pf_chain(void)
{
	struct pccard_function *pf;
	struct pccard_config_entry *cfe;

	while ((pf = STAILQ_FIRST(&rig_sc.card.pf_head)) != NULL) {
		while ((cfe = STAILQ_FIRST(&pf->cfe_head)) != NULL) {
			STAILQ_REMOVE_HEAD(&pf->cfe_head, cfe_list);
			kfree(cfe, M_DEVBUF);
		}
		STAILQ_REMOVE_HEAD(&rig_sc.card.pf_head, pf_list);
		kfree(pf, M_DEVBUF);
	}
}

/* ---------------- SIGSEGV instrumentation ---------------- */
static void
rig_segv(int sig, siginfo_t *si, void *uc)
{
	(void)uc;
	printf("RIG SIGSEGV si_addr=%p (signal %d)\n", si->si_addr, sig);
	fflush(stdout);
	_exit(135);
}

/* ---------------- scenarios ---------------- */

/* DF-2318 primary: card without VERS_1, driver attaches -> devadded */
static void
sc_no_vers1_devadded(void)
{
	build_no_vers1();
	rig_probe_mode = PROBE_ATTACH_OK;
	printf("-- pre-checks: verbatim pccard_read_cis on the no-VERS_1 image\n");
	pccard_read_cis(&rig_sc);
	printf("PRE: card.error=%d pf_head_empty=%d cis1_info[0]=%p "
	    "cis1_info[1]=%p manufacturer=%#x product=%#x\n",
	    rig_sc.card.error, STAILQ_EMPTY(&rig_sc.card.pf_head),
	    rig_sc.card.cis1_info[0], rig_sc.card.cis1_info[1],
	    rig_sc.card.manufacturer, rig_sc.card.product);
	free_pf_chain();
	printf("-- entering verbatim pccard_attach_card (driver will attach)\n");
	pccard_attach_card(&rig_bus_dev);
	printf("attach returned (no fault)\n");
}

/* DF-2318 variant: no driver matches -> devnomatch */
static void
sc_no_vers1_nomatch(void)
{
	build_no_vers1();
	rig_probe_mode = PROBE_FAIL;
	printf("-- entering verbatim pccard_attach_card (NO driver matches)\n");
	pccard_attach_card(&rig_bus_dev);
	printf("attach returned (no fault)\n");
}

/* DF-2318 re-trigger: attach survives (notify suppressed -- rig-only
 * deviation), then an unprivileged hw.bus.devices walk (devinfo(8),
 * subr_bus.c:3863-3899) asks every device for its pnpinfo string. */
static void
sc_no_vers1_sysctl(void)
{
	char buf[1024];
	device_t d;

	build_no_vers1();
	rig_probe_mode = PROBE_SILENT_FAIL;
	printf("-- entering verbatim pccard_attach_card (silent probe fail)\n");
	pccard_attach_card(&rig_bus_dev);
	printf("-- emulating `devinfo -v` / sysctl hw.bus.devices walk "
	    "(subr_bus.c:3899)\n");
	for (d = rig_devices; d != NULL; d = d->next) {
		printf("    pnpinfo of %s ...\n", d->nameunit);
		*buf = '\0';
		bus_child_pnpinfo_str(d, buf, sizeof(buf));
		printf("    -> \"%s\"\n", buf);
	}
	printf("walk survived (no fault)\n");
}

/* negative: VERS_1 present -> pnpinfo builds normally */
static void
sc_vers1_pnpinfo_ok(void)
{
	build_benign();
	rig_probe_mode = PROBE_ATTACH_OK;
	pccard_attach_card(&rig_bus_dev);
	orphan_accounting("vers1_pnpinfo_ok");
}

/* DF-5355: partial allocation in cfe#0 -> busy rid -> cfe#1 re-add panics */
static void
sc_busypanic(void)
{
	build_busypanic();
	rig_probe_mode = PROBE_ATTACH_OK;
	printf("-- entering verbatim pccard_attach_card -> pccard_function_init "
	    "(cfe#0: anywhere+0x3F8; bridge window [%#x,%#x])\n",
	    RIG_IO_WIN_START, RIG_IO_WIN_END);
	pccard_attach_card(&rig_bus_dev);
	orphan_accounting("busypanic");
}

/* DF-5356: CD-bounce re-attach without detach */
static void
sc_reentry_leak(void)
{
	int i, N = 50;
	struct pccard_function *pf;

	build_benign();
	rig_probe_mode = PROBE_ATTACH_OK;

	printf("-- cycle 0 (detailed):\n");
	pccard_attach_card(&rig_bus_dev);
	orphan_accounting("after attach #1");
	pccard_attach_card(&rig_bus_dev);	/* CD bounce: no detach ran */
	orphan_accounting("after attach #2 (bounce)");
	pccard_detach_card(&rig_bus_dev);
	orphan_accounting("after detach (final removal)");

	printf("-- %d more (attach, bounce-attach, detach) cycles:\n", N);
	for (i = 0; i < N; i++) {
		pccard_attach_card(&rig_bus_dev);
		pccard_attach_card(&rig_bus_dev);
		pccard_detach_card(&rig_bus_dev);
		if (i == 0 || i == N - 1)
			orphan_accounting(i == 0 ? "cycle 1" : "cycle last");
	}
	{
		size_t leak = 0;
		int orph = 0, zom = 0;
		int k;
		for (k = 0; k < rig_nblocks; k++) {
			if (!rig_blocks[k].live)
				continue;
			if (rig_blocks[k].site &&
			    strcmp(rig_blocks[k].site, "pccard_parse_cis_tuple") == 0 &&
			    !ptr_in_current_list(rig_blocks[k].p)) {
				orph++;
				leak += rig_blocks[k].size;
			}
		}
		{
			device_t d;
			for (d = rig_devices; d != NULL; d = d->next)
				if (!d->deleted)
					zom++;
		}
		printf("TOTAL after %d bounce cycles: orphaned-kmalloc-blocks=%d "
		    "leak=%zu bytes | zombie-children=%d | io-held=%ld ccr-held=%ld"
		    "%s\n",
		    N + 1, orph, leak, zom,
		    rig_io_grants - rig_io_releases,
		    rig_ccr_allocs - rig_ccr_releases,
		    STAILQ_EMPTY(&rig_sc.card.pf_head) ? "" :
		    " (current list NOT empty?)");
	}
	printf("NOTE: leak grows linearly with bounce cycles (evil card CD "
	    "glitch every ~2s) -> unbounded kernel memory exhaustion.\n");
	(void)pf;
}

/* negative: clean attach/detach cycles leak nothing */
static void
sc_benign_cycle(void)
{
	int i;

	build_benign();
	rig_probe_mode = PROBE_ATTACH_OK;
	for (i = 0; i < 51; i++) {
		pccard_attach_card(&rig_bus_dev);
		pccard_detach_card(&rig_bus_dev);
	}
	orphan_accounting("benign_cycle x51");
}

/* ---------------- harness ---------------- */
struct sc {
	const char *name;
	void (*fn)(void);
	const char *desc;
};
static struct sc scenarios[] = {
	{ "no_vers1_devadded", sc_no_vers1_devadded,
	    "DF-2318: no VERS_1, driver attaches -> devadded -> pnpinfo" },
	{ "no_vers1_nomatch", sc_no_vers1_nomatch,
	    "DF-2318: no VERS_1, no driver -> devnomatch -> pnpinfo" },
	{ "no_vers1_sysctl", sc_no_vers1_sysctl,
	    "DF-2318: no VERS_1, hw.bus.devices walk -> pnpinfo" },
	{ "vers1_pnpinfo_ok", sc_vers1_pnpinfo_ok,
	    "negative: VERS_1 present, pnpinfo builds" },
	{ "busypanic", sc_busypanic,
	    "DF-5355: partial alloc leaves busy rid -> re-add panics" },
	{ "reentry_leak", sc_reentry_leak,
	    "DF-5356: CD-bounce re-attach orphans pf list, stacks children" },
	{ "benign_cycle", sc_benign_cycle,
	    "negative: clean attach/detach cycles" },
};
static const int NSC = sizeof(scenarios) / sizeof(scenarios[0]);

static int
run_one(const char *name)
{
	int i, st;
	pid_t pid;
	struct sigaction sa;

	for (i = 0; i < NSC; i++)
		if (strcmp(scenarios[i].name, name) == 0)
			break;
	if (i == NSC) {
		printf("unknown scenario %s\n", name);
		return (98);
	}
	printf("=========================================================\n");
	printf("SCENARIO %s: %s\n", scenarios[i].name, scenarios[i].desc);
	printf("=========================================================\n");
	fflush(stdout);
	pid = fork();
	if (pid < 0) { perror("fork"); return (99); }
	if (pid == 0) {
		memset(&sa, 0, sizeof(sa));
		sa.sa_sigaction = rig_segv;
		sa.sa_flags = SA_SIGINFO;
		sigaction(SIGSEGV, &sa, NULL);
		sigaction(SIGBUS, &sa, NULL);
		rig_reset();
		scenarios[i].fn();
		fflush(stdout);
		_exit(0);
	}
	waitpid(pid, &st, 0);
	if (WIFSIGNALED(st))
		printf("RESULT %s: KILLED by signal %d\n", name, WTERMSIG(st));
	else
		printf("RESULT %s: exit %d\n", name, WEXITSTATUS(st));
	fflush(stdout);
	return (WIFSIGNALED(st) ? 1000 + WTERMSIG(st) : WEXITSTATUS(st));
}

int
main(int argc, char **argv)
{
	int i;

	rig_sz_pf = sizeof(struct pccard_function);
	rig_sz_cfe = sizeof(struct pccard_config_entry);
	rig_sz_ivar = sizeof(struct pccard_ivar);
	printf("rig2: verbatim pccard.c lifecycle rig "
	    "(sizeof pf=%zu cfe=%zu ivar=%zu)\n",
	    rig_sz_pf, rig_sz_cfe, rig_sz_ivar);
	printf("offsetof(pccard_softc, card.cis1_info[0]) = %#zx\n",
	    offsetof(struct pccard_softc, card.cis1_info[0]));
	if (argc == 2 && strcmp(argv[1], "all") == 0) {
		for (i = 0; i < NSC; i++)
			run_one(scenarios[i].name);
		return (0);
	}
	if (argc == 2)
		return (run_one(argv[1]));
	printf("usage: %s <scenario|all>\n", argv[0]);
	for (i = 0; i < NSC; i++)
		printf("  %-20s %s\n", scenarios[i].name, scenarios[i].desc);
	return (1);
}
