/*
 * ppbharness3 -- KLD harness for sys/bus/ppbus/ppbconf.c
 * (DF-2327 / DF-2328 / DF-2329 evidence rig)
 *
 * Rig family: "KLD-harness-with-production-code" (no parallel port in
 * QEMU) -- same pattern as the DF-5510/DF-5513 ppbharness2 rig, extended:
 *   - compiles the *unmodified* production sources ppbconf.c, ppb_1284.c,
 *     ppb_base.c out of /usr/src/sys/bus/ppbus;
 *   - a fake "ppc" chipset on nexus serves a fully scripted IEEE1284
 *     peer (crafted device-ID strings);
 *   - tests are ARMED VIA SYSCTL (hw.ppbx3.test) so the panic cases run
 *     one per boot under explicit control:
 *       test=1  DF-2327  ID "MFG;MFG" (semicolon->NUL, no colon)
 *                search_token(...,":")==NULL -> NULL+1 -> kprintf %s @0x1
 *                -> Fatal trap 12 (ppbconf.c:242)
 *       test=2  DF-2328  256-byte NUL-free ID with trailing "REV" and
 *                no colon: search_token(UNKNOWN_LENGTH,":") walks past
 *                str[255] into str[256] (uninitialized; read_id bzeros
 *                only PPB_PnP_STRING_SIZE bytes) and beyond into stack
 *                (ppbconf.c:187-188,253-259) -> OOB read; on this guest
 *                it finds no ':' in the crossed bytes -> NULL+1 trap
 *                (same symptom as 2327 but reached via the missing
 *                NUL termination)
 *       test=3  DF-2329  lost-wakeup race: owner releases in the window
 *                between requester's crit_exit() (ppbconf.c:467) and
 *                tsleep() (:470/:474); wakeup is delivered before the
 *                sleep -> requester sleeps forever on a FREE bus
 *                (PPB_WAIT|PPB_NOINTR = uninterruptible)
 *       test=4  control: well-formed ID "MFG:X;MDL:Y;VER:1;CLS:PRINTER;"
 *                -> clean probe, no trap
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/kthread.h>
#include <sys/thread.h>
#include <sys/time.h>
#include <sys/sysctl.h>

#include <machine/clock.h>

#include <bus/ppbus/ppbconf.h>
#include <bus/ppbus/ppb_1284.h>

#include "ppbus_if.h"

/* ------------------------------------------------------------------ */
/* fake ppc: per-instance scripted IEEE1284 peer                       */
/* ------------------------------------------------------------------ */

struct fakeppc_softc {
	int		phase;
#define PH_IDLE	0
#define PH_NEG	1
#define PH_READ	2
	u_char		last_wctr;
	u_char		last_wdtr;

	char		stream[256];
	int		stream_len;
	int		byte_i;
	int		nib_sel;
};

static __inline u_char
hx_enc(int nib)
{
	return ((nib & 1) ? nFAULT : 0) | ((nib & 2) ? SELECT : 0) |
	       ((nib & 4) ? PERROR : 0) | ((nib & 8) ? 0 : nBUSY);
}

static u_char
hx_rstr(struct fakeppc_softc *sc)
{
	switch (sc->phase) {
	case PH_NEG:
		if (sc->last_wctr == 0x04)
			return (0xD8);
		return (0x38);
	case PH_READ:
		if (sc->byte_i < sc->stream_len) {
			int nib = (sc->nib_sel == 0) ?
			    (sc->stream[sc->byte_i] & 0x0f) :
			    ((sc->stream[sc->byte_i] >> 4) & 0x0f);
			if (sc->last_wctr == 0x06)
				return ((u_char)(hx_enc(nib) & ~nACK));
			if (sc->last_wctr == 0x04)
				return ((u_char)((hx_enc(nib) | nACK) & ~nFAULT));
			if (sc->last_wctr == 0x0C)
				return (0x88);
			if (sc->last_wctr == 0x0E)
				return (0xD8);
			return (0x48);
		}
		if (sc->last_wctr == 0x0C)
			return (0x88);
		if (sc->last_wctr == 0x0E)
			return (0xD8);
		return (0x48);
	default:
		return (0x00);
	}
}

static u_char
fakeppc_io(device_t dev, int opcode, u_char *addr, int cnt, u_char byte)
{
	struct fakeppc_softc *sc = device_get_softc(dev);

	switch (opcode) {
	case PPB_RSTR:
		return (hx_rstr(sc));
	case PPB_RDTR:
		return (0x55);
	case PPB_WDTR:
		sc->last_wdtr = byte;
		if (byte == (u_char)NIBBLE_1284_REQUEST_ID)
			sc->phase = PH_NEG;
		break;
	case PPB_WCTR:
		sc->last_wctr = byte;
		if (sc->phase == PH_READ && byte == 0x04) {
			sc->nib_sel ^= 1;
			if (sc->nib_sel == 0)
				sc->byte_i++;
		}
		break;
	default:
		break;
	}
	return (0);
}

static int fakeppc_setmode(device_t dev, int mode)
{
	struct fakeppc_softc *sc = device_get_softc(dev);

	if (mode != PPB_COMPATIBLE && sc->phase == PH_NEG) {
		sc->phase = PH_READ;
		sc->byte_i = 0;
		sc->nib_sel = 0;
	}
	if (mode == PPB_COMPATIBLE)
		sc->phase = PH_IDLE;
	return (0);
}

static int fakeppc_exec_microseq(device_t dev, struct ppb_microseq **msq) { return (0); }
static int fakeppc_reset_epp(device_t dev) { return (0); }
static int fakeppc_ecp_sync(device_t dev) { return (0); }
static int fakeppc_read(device_t dev, char *buf, int len, int how) { return (0); }
static int fakeppc_write(device_t dev, char *buf, int len, int how) { return (0); }

static void
fakeppc_identify(driver_t *drv, device_t parent)
{
	if (device_find_child(parent, "ppc", 0) == NULL)
		BUS_ADD_CHILD(parent, parent, 0, "ppc", 0);
}

static int
fakeppc_probe(device_t dev)
{
	device_set_desc(dev, "scripted IEEE1284 peer (ppbharness3)");
	return (0);
}

static int fakeppc_attach(device_t dev);

static device_method_t fakeppc_methods[] = {
	DEVMETHOD(device_identify,	fakeppc_identify),
	DEVMETHOD(device_probe,		fakeppc_probe),
	DEVMETHOD(device_attach,	fakeppc_attach),

	DEVMETHOD(ppbus_io,		fakeppc_io),
	DEVMETHOD(ppbus_exec_microseq,	fakeppc_exec_microseq),
	DEVMETHOD(ppbus_reset_epp,	fakeppc_reset_epp),
	DEVMETHOD(ppbus_setmode,	fakeppc_setmode),
	DEVMETHOD(ppbus_ecp_sync,	fakeppc_ecp_sync),
	DEVMETHOD(ppbus_read,		fakeppc_read),
	DEVMETHOD(ppbus_write,		fakeppc_write),

	DEVMETHOD_END
};

static driver_t fakeppc_driver = {
	"ppc",
	fakeppc_methods,
	sizeof(struct fakeppc_softc),
};

static devclass_t fakeppc_devclass;

DRIVER_MODULE(fakeppc, nexus, fakeppc_driver, fakeppc_devclass, NULL, NULL);

/* ------------------------------------------------------------------ */
/* hxc: dummy ppbus child (real ppb_device ivars via ppbus_add_child)  */
/* ------------------------------------------------------------------ */

static int hxc_probe(device_t dev) { device_set_desc(dev, "ppbus child"); return (0); }
static int hxc_attach(device_t dev) { return (0); }

static device_method_t hxc_methods[] = {
	DEVMETHOD(device_probe,		hxc_probe),
	DEVMETHOD(device_attach,	hxc_attach),
	DEVMETHOD_END
};

static driver_t hxc_driver = { "hxc3", hxc_methods, 0 };
static devclass_t hxc_devclass;
DRIVER_MODULE(hxc3, ppbus, hxc_driver, hxc_devclass, NULL, NULL);

/* ------------------------------------------------------------------ */
/* test orchestration                                                  */
/* ------------------------------------------------------------------ */

static int ppbx3_test;			/* sysctl hw.ppbx3.test */
static int ppbx3_racetrials;
static int ppbx3_lostwake;
static int ppbx3_caught;

SYSCTL_NODE(_hw, OID_AUTO, ppbx3, CTLFLAG_RW, 0, "ppbharness3");
SYSCTL_INT(_hw_ppbx3, OID_AUTO, test, CTLFLAG_RW, &ppbx3_test, 0,
    "1=DF2327-nocolon 2=DF2328-noNUL 3=DF2329-race 4=control");
SYSCTL_INT(_hw_ppbx3, OID_AUTO, racetrials, CTLFLAG_RD, &ppbx3_racetrials, 0,
    "DF2329 trials so far");
SYSCTL_INT(_hw_ppbx3, OID_AUTO, lostwake, CTLFLAG_RD, &ppbx3_lostwake, 0,
    "DF2329 lost-wakeup catches");
SYSCTL_INT(_hw_ppbx3, OID_AUTO, caught, CTLFLAG_RD, &ppbx3_caught, 0,
    "1 when the current waiter is stuck on a free bus");

static device_t ppcdev_g, ppbus_g, devA_g, devB_g;
static int ppbus_unit;

static void
hx_arm_and_attach(device_t ppcdev, device_t ppbusdev, const char *id,
    int idlen)
{
	struct fakeppc_softc *sc = device_get_softc(ppcdev);

	bzero(sc, sizeof(*sc));
	sc->phase = PH_IDLE;
	memcpy(sc->stream, id, idlen);
	sc->stream_len = idlen;

	device_probe_and_attach(ppbusdev);
}

/* ---- DF-2329: lost-wakeup race ---- */

struct r2329 {
	volatile int	go;
	volatile int	waiter_done;
	volatile int	waiter_ret;
	int		delay_us;
};

static struct r2329 R;

static void
hx_waiter(void *arg)
{
	struct r2329 *r = arg;

	/* run on a DIFFERENT cpu than the racer so the release can land
	 * while we are between crit_exit() and tsleep() */
	lwkt_migratecpu(1);

	for (;;) {
		while (!r->go)
			tsleep(&r->go, 0, "hxgo", 1);
		r->go = 0;
		r->waiter_ret = ppb_request_bus(ppbus_g, devB_g,
		    PPB_WAIT | PPB_NOINTR);
		if (r->waiter_ret == 0)
			ppb_release_bus(ppbus_g, devB_g);
		r->waiter_done = 1;
		wakeup(&r->waiter_done);
	}
	kthread_exit();
}

static void
hx_racer(void *arg)
{
	struct r2329 *r = arg;
	struct ppb_data *ppb = (struct ppb_data *)device_get_softc(ppbus_g);
	struct thread *td;
	int trial, spin;

	kprintf("PPBX3: DF-2329 lost-wakeup race: owner releases between "
	    "requester crit_exit (ppbconf.c:467) and tsleep (:474); "
	    "wakeup has no interlock -> lost\n");
	kthread_create(hx_waiter, r, &td, "hxw2329");
	tsleep(r, 0, "hxsync", hz / 10);		/* let waiter start */

	for (trial = 0; trial < 50000; trial++) {
		if (ppb_request_bus(ppbus_g, devA_g, PPB_DONTWAIT) != 0)
			continue;

		r->waiter_done = 0;
		r->delay_us = trial % 400;	/* sweep 0..399 us */
		r->go = 1;
		wakeup(&r->go);

		/* burn delay_us while the waiter is somewhere between its
		 * owner check (inside crit) and its tsleep() */
		DELAY(r->delay_us);

		ppb_release_bus(ppbus_g, devA_g);	/* owner=0 + wakeup(ppb) */

		/* fast-poll up to ~250us for the waiter to finish */
		for (spin = 0; spin < 5 && !r->waiter_done; spin++)
			DELAY(50);
		if (!r->waiter_done)
			tsleep(&r->waiter_done, 0, "hxdone", 1);

		ppbx3_racetrials = trial + 1;
		if (r->waiter_done == 0 && ppb->ppb_owner == NULL) {
			ppbx3_lostwake++;
			ppbx3_caught = 1;
			kprintf("PPBX3: DF-2329 REPRODUCED at trial %d "
			    "(delay %d us): waiter STILL ASLEEP with "
			    "PPB owner==NULL -- wakeup was lost; "
			    "PPB_WAIT|PPB_NOINTR sleep is uninterruptible "
			    "(kill-proof)\n", trial, r->delay_us);
			/* revive: emulate a later release so the module
			 * can continue (production has none) */
			wakeup(ppb);
			for (spin = 0; spin < 100 && !r->waiter_done; spin++)
				DELAY(50);
			ppbx3_caught = 0;
			break;
		}
		/* drain the waiter for the next trial */
		for (spin = 0; spin < 100 && !r->waiter_done; spin++)
			DELAY(50);
	}
	kprintf("PPBX3: DF-2329 done: trials=%d lostwake=%d\n",
	    ppbx3_racetrials, ppbx3_lostwake);
	kthread_exit();
}

static void
hx_worker(void *arg)
{
	struct thread *td;
	char id2327[16];
	char id2328[256];
	static const char idctl[] = "MFG:X;MDL:Y;VER:1;CLS:PRINTER;";
	int i;

	kprintf("PPBX3: harness worker start (production ppbconf.c "
	    "verbatim)\n");

	ppcdev_g = devclass_get_device(devclass_find("ppc"), 0);
	if (ppcdev_g == NULL) {
		kprintf("PPBX3: no fake ppc device\n");
		kthread_exit();
	}
	ppbus_unit = 0;
	ppbus_g = device_add_child(ppcdev_g, "ppbus", ppbus_unit);

	/* DF-2327: "MFG;MFG" -- ';'->'\0' at ppbconf.c:236-237 leaves
	 * "MFG\0MFG\0"; search_token(str,len,"MFG") hits at 0, then
	 * search_token(str,UNKNOWN_LENGTH,":") scans "MFG" only -> NULL
	 * -> +1 -> kprintf("%s", (char*)0x1) -> Fatal trap 12 */
	strcpy(id2327, "MFG;MFG");

	/* DF-2328: 256 non-NUL bytes, "REV" at 252, no ':' anywhere:
	 * ':'-scan walks str[252..255] -> str[256] (uninit: read_id bzeros
	 * only PPB_PnP_STRING_SIZE=256 bytes) -> stack above the object */
	memset(id2328, 'A', sizeof(id2328));
	id2328[252] = 'R'; id2328[253] = 'E'; id2328[254] = 'V';
	/* id2328[255] stays 'A'; NOTE total stream_len = 256 -> no NUL */

	for (i = 0; ; i++) {
		int t = ppbx3_test;
		if (t == 1) {
			/* fresh ppbus child so the crafted ID is really
			 * probed (an already-attached ppbus would no-op) */
			ppbus_g = device_add_child(ppcdev_g, "ppbus",
			    ++ppbus_unit);
			kprintf("PPBX3: arming DF-2327 (MFG;MFG -- no colon) "
			    "attach NOW; stock: Fatal trap 12 at "
			    "kprintf-via-NULL+1\n");
			hx_arm_and_attach(ppcdev_g, ppbus_g, id2327,
			    strlen(id2327));
			kprintf("PPBX3: DF-2327 attach returned -- no trap?! "
			    "(not reproduced)\n");
			ppbx3_test = 0;
		} else if (t == 2) {
			ppbus_g = device_add_child(ppcdev_g, "ppbus",
			    ++ppbus_unit);
			kprintf("PPBX3: arming DF-2328 (256B NUL-free ID, "
			    "REV at 252, no colon) attach NOW; stock: OOB "
			    "walk past str[255] -> NULL+1 trap\n");
			hx_arm_and_attach(ppcdev_g, ppbus_g, id2328, 256);
			kprintf("PPBX3: DF-2328 attach returned -- no trap?! "
			    "(not reproduced)\n");
			ppbx3_test = 0;
		} else if (t == 3) {
			ppbx3_test = 0;
			/* attach ppbus first (benign control ID) so the
			 * ppbus driver methods exist for the children */
			hx_arm_and_attach(ppcdev_g, ppbus_g, idctl,
			    strlen(idctl));
			kprintf("PPBX3: control attach for test3 done\n");
			devA_g = BUS_ADD_CHILD(ppbus_g, ppbus_g, 0, "hxc3", 0);
			devB_g = BUS_ADD_CHILD(ppbus_g, ppbus_g, 1, "hxc3", 1);
			if (devA_g == NULL || devB_g == NULL) {
				kprintf("PPBX3: FAILED to add hxc3 children\n");
				continue;
			}
			device_probe_and_attach(devA_g);
			device_probe_and_attach(devB_g);
			kthread_create(hx_racer, &R, &td, "hxr2329");
		} else if (t == 4) {
			ppbx3_test = 0;
			kprintf("PPBX3: control probe (well-formed ID)\n");
			hx_arm_and_attach(ppcdev_g, ppbus_g, idctl,
			    strlen(idctl));
			kprintf("PPBX3: control attach OK (no trap)\n");
		}
		tsleep(&ppbx3_test, 0, "hxidle", hz);
	}
	kthread_exit();
}

static int
fakeppc_attach(device_t dev)
{
	struct thread *td;

	if (device_get_unit(dev) != 0)
		return (0);
	kprintf("PPBX3: fakeppc0 attached, starting worker\n");
	kthread_create(hx_worker, NULL, &td, "ppbhx3");
	return (0);
}
