/*
 * ppbharness2 -- KLD harness for sys/bus/ppbus/ppbconf.c (DragonFly 6.5-DEVEL)
 *
 * Evidence class: "KLD-harness-with-production-code" (no parallel port in
 * QEMU), same rig family as the DF-5418/DF-5419 ppb_1284 batch.
 *
 * The module compiles the *unmodified* production sources ppbconf.c,
 * ppb_1284.c and ppb_base.c out of /usr/src/sys/bus/ppbus and drives them
 * through a fake parallel-port chipset driver that registers itself under
 * devclass "ppc" on nexus.  ppbconf.c's own DRIVER_MODULE(ppbus, ppc, ...)
 * then attaches the REAL ppbus driver onto it, so
 *
 *	ppbus_attach -> ppb_scan_bus -> ppb_1284_negotiate/terminate
 *		      -> ppb_pnp_detect -> ppb_1284_read_id -> search_token
 *
 * all execute production code.  The fake "ppc" implements a fully scripted
 * (malicious) IEEE1284 peripheral: it hands ppb_1284_read_id() an
 * attacker-chosen 256-byte IEEE1284 device-ID string, nibble by nibble,
 * exactly as hostile parallel-port hardware would.
 *
 * Tests (all output prefixed "PPBX2:"):
 *
 *  DF-5510  ppbconf.c:278 search_token(class, len, pnp_tokens[i])
 *	     -- `class' is an INTERIOR pointer into str[] but `len' is the
 *	     ABSOLUTE length of the whole ID string, so the token scan runs
 *	     up to (class - str) bytes past the end of the 257-byte stack
 *	     object str[PPB_PnP_STRING_SIZE+1].
 *	     Ta: in-bounds variant of the same wrong-length bug: ID
 *	         "CLS:QQ;CMD:PRINTER;" -- the class value is "QQ" yet the
 *	         scan finds PRINTER in the *following* field (should be
 *	         PPB_PnP_UNKNOWN=10; buggy returns 0=PRINTER).
 *	     Tb: OOB variant: 256-byte ID whose "CLS:" sits at offset 248,
 *	         so class=str+252 and the scan reads str[252..507] -- 251
 *	         bytes past the 257-byte object.  The stack below the attach
 *	         chain is pre-dirtied with "PRINTER"/"PORTS"/"NET"/"FDC"
 *	         patterns; any class-token hit could only have come from
 *	         bytes at/after str+256 (>= 5 bytes past the object for the
 *	         7-byte tokens).  Control Tc: short ID, same dirt, expects
 *	         UNKNOWN (scan stays in bounds).
 *
 *  DF-5511  ppbconf.c:528-535 ppb_release_bus() saves ppbdev->ctx.mode
 *	     AFTER clearing ppb->ppb_owner (and outside any crit section),
 *	     so a second device can acquire the bus and set its own chipset
 *	     mode in the window; the releasing device then snapshots the
 *	     *other* device's mode into its context and later restores it.
 *	     Two kernel threads race releaser(A)/stealer(B) and we count
 *	     how often A captures B's mode (expect 0; buggy > 0).
 *
 *  DF-5512  ppbconf.c:415-430 ppbus_setup_intr() happily registers a
 *	     second interrupt handler for a child that already has one and
 *	     silently overwrites ppbdev->intr_cookie/intr_resource, so the
 *	     bus layer loses the ability to force-unregister the first
 *	     handler at ppb_release_bus() (reachable via lpt.c write-error
 *	     paths that keep the bus and re-register on the next write).
 *	     Deterministic double-setup then release; the fake parent
 *	     records registrations: after release, registration #1 is still
 *	     live while ppbus believes none is.
 *
 *  DF-5513  ppbconf.c:457-500 ppb_request_bus() is not idempotent for the
 *	     current owner: an already-owning device that re-requests is put
 *	     to tsleep(ppb, 0 [no PCATCH], "ppbreq", 0 [forever]).
 *	     pcfclock_open() reaches this (no HAVEBUS-style guard), giving
 *	     an unkillable hang.  Rig: owner re-requests with
 *	     PPB_WAIT|PPB_NOINTR on a kernel thread; after 3 s it is still
 *	     asleep; only ppb_release_bus() from another thread revives it.
 */

#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 <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                       */
/* ------------------------------------------------------------------ */

#define	HX_MAXREG	16

struct fakeppc_softc {
	/* peer script */
	int		phase;		/* PH_IDLE / PH_NEG / PH_READ */
#define PH_IDLE	0
#define PH_NEG	1
#define PH_READ	2
	u_char		last_wctr;
	u_char		last_wdtr;
	int		req_id_count;

	/* delivered ID stream */
	char		stream[256];
	int		stream_len;
	int		byte_i;
	int		nib_sel;	/* 0 = low nibble next, 1 = high */

	/* chipset model */
	int		mode;
	int		n_setmode;
	int		setmode_log[16];

	/* fake intr registrations (DF-5512) */
	int		n_setup, n_teardown;
	void		*reg_cookie[HX_MAXREG];
	int		reg_live[HX_MAXREG];
};

static __inline u_char
hx_enc(int nib)
{
	/* inverse of nibble2char(): bit0=nFAULT bit1=SELECT bit2=PERROR
	 * bit3=!nBUSY */
	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:
		/* cooperating IEEE1284 negotiation (NIBBLE_REQUEST_ID) */
		if (sc->last_wctr == 0x04)	/* post Event-4 */
			return (0xD8);	/* nACK|PERROR|SELECT|nFAULT high */
		return (0x38);			/* nACK low, rest high (Event 2) */

	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) {
				/* Event-7 phase: nACK LOW + data bits */
				return ((u_char)(hx_enc(nib) & ~nACK));
			}
			if (sc->last_wctr == 0x04) {
				/* Event-10 phase: nACK HIGH; nFAULT forced
				 * clear so the read loop keeps going */
				return ((u_char)((hx_enc(nib) | nACK) &
				    ~nFAULT));
			}
			/* terminate waits while data remains: cooperate */
			if (sc->last_wctr == 0x0C)
				return (0x88);
			if (sc->last_wctr == 0x0E)
				return (0xD8);
			return (0x48);
		}
		/* stream exhausted */
		if (sc->last_wctr == 0x0C)
			return (0x88);	/* nACK low, nBUSY|nFAULT high */
		if (sc->last_wctr == 0x0E)
			return (0xD8);	/* nACK high */
		return (0x48);		/* nACK|nFAULT high: end read */

	default:
		/* not cooperating: nothing ever matches -> clean timeouts */
		return (0x00);
	}
}

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

	(void)ppbus;

	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->req_id_count++;
			sc->phase = PH_NEG;
		}
		break;
	case PPB_WCTR:
		sc->last_wctr = byte;
		if (sc->phase == PH_READ && byte == 0x04) {
			/* Event-10 strobe consumed a nibble */
			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 (sc->n_setmode < 16)
		sc->setmode_log[sc->n_setmode] = mode;
	sc->n_setmode++;
	sc->mode = mode;

	/* PH_NEG -> PH_READ on the successful NIBBLE negotiation */
	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);
}

/* fake bus-level intr accounting for DF-5512 */
static int
fakeppc_setup_intr(device_t bus, device_t child, struct resource *r,
		   int flags, void (*ihand)(void *), void *arg,
		   void **cookiep, lwkt_serialize_t serializer,
		   const char *desc)
{
	struct fakeppc_softc *sc = device_get_softc(bus);

	if (sc->n_setup >= HX_MAXREG)
		return (ENOMEM);
	sc->reg_cookie[sc->n_setup] = (void *)(intptr_t)(0x1000 + sc->n_setup);
	sc->reg_live[sc->n_setup] = 1;
	*cookiep = sc->reg_cookie[sc->n_setup];
	sc->n_setup++;
	return (0);
}

static int
fakeppc_teardown_intr(device_t bus, device_t child, struct resource *r,
		      void *ih)
{
	struct fakeppc_softc *sc = device_get_softc(bus);
	int i;

	sc->n_teardown++;
	for (i = 0; i < sc->n_setup; i++) {
		if (sc->reg_cookie[i] == ih) {
			sc->reg_live[i] = 0;
			return (0);
		}
	}
	return (EINVAL);
}

static void
fakeppc_identify(driver_t *drv, device_t parent)
{
	/*
	 * Two fake chipsets -> two independent ppbus instances:
	 *   unit 0: DF-5510 crafted-256-byte-ID probe (+ stack dirt)
	 *   unit 1: DF-5510 in-bounds cross-field + short-ID control
	 */
	if (device_find_child(parent, "ppc", 0) == NULL)
		BUS_ADD_CHILD(parent, parent, 0, "ppc", 0);
	if (device_find_child(parent, "ppc", 1) == NULL)
		BUS_ADD_CHILD(parent, parent, 0, "ppc", 1);
}

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

static int
fakeppc_attach(device_t dev);
static int
fakeppc_detach(device_t dev)
{
	bus_generic_detach(dev);
	device_delete_children(dev);
	return (0);
}

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

	/* ppbus interface */
	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),

	/* bus interface (intr accounting for DF-5512) */
	DEVMETHOD(bus_setup_intr,	fakeppc_setup_intr),
	DEVMETHOD(bus_teardown_intr,	fakeppc_teardown_intr),

	DEVMETHOD_END
};

static driver_t fakeppc_driver = {
	"ppc",				/* devclass the ppbus driver binds to */
	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 (gets real ppb_device ivars via ppbus_add_child) */
/* ------------------------------------------------------------------ */

static int
hxc_probe(device_t dev)
{
	device_set_desc(dev, "ppbus child (ppbharness2)");
	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 = {
	"hxc",
	hxc_methods,
	0,
};

static devclass_t hxc_devclass;

DRIVER_MODULE(hxc, ppbus, hxc_driver, hxc_devclass, NULL, NULL);

/* ------------------------------------------------------------------ */
/* the tests                                                           */
/* ------------------------------------------------------------------ */

static const char *pnp_class_name[] = { "PRINTER", "MODEM", "NET", "HDC",
	"PCMCIA", "MEDIA", "FDC", "PORTS", "SCANNER", "DIGICAM", "UNKNOWN" };

/* pre-dirty the stack region the attach chain (and the OOB token scan)
 * will later run in.  noinline + post-call touch so -O2 cannot turn the
 * recursion into a loop and collapse the frames. */
static void __attribute__((noinline))
hx_dirty(int depth)
{
	volatile char pad[1024];
	size_t i;

	/* rotating blocks: PRINTER / PORTS / MODEM / NET / FDC / PRINTER */
	for (i = 0; i < sizeof(pad); i++) {
		int blk = (i >> 4) % 6;
		const char *tok;
		size_t tlen;

		switch (blk) {
		case 0: tok = "PRINTER"; tlen = 7; break;
		case 1: tok = "PORTS";   tlen = 5; break;
		case 2: tok = "MODEM";   tlen = 5; break;
		case 3: tok = "NET";     tlen = 3; break;
		case 4: tok = "FDC";     tlen = 3; break;
		default: tok = "PRINTER"; tlen = 7; break;
		}
		pad[i] = tok[i % tlen];
	}
	if (depth > 0)
		hx_dirty(depth - 1);
	pad[0] = pad[0] ^ (char)(depth & 1);	/* defeat tail-call opt */
}

/* report surviving dirt in the window above `sp' */
static void
hx_dirt_map(const char *tag, char *sp)
{
	static const char *toks[] = { "PRINTER", "PORTS", "MODEM", "NET",
				      "FDC", NULL };
	int t, n;

	kprintf("PPBX2: %s dirt-survival map below sp=%p:", tag, sp);
	for (t = 0; toks[t] != NULL; t++) {
		size_t L = strlen(toks[t]);
		int first = -1;

		/* the attach chain (and the OOB scan window) ran BELOW this
		 * frame -- scan downward into the dead, dirtied frames */
		for (n = 0; n < 2048; n++) {
			if (memcmp(sp - n, toks[t], L) == 0) {
				if (first < 0)
					first = n;
			}
		}
		kprintf(" %s@-%d", toks[t], first);
	}
	kprintf("\n");
}

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

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

	if (dirt)
		hx_dirty(7);		/* ~7 KB below current sp */

	device_probe_and_attach(ppbusdev);

	ppb = (struct ppb_data *)device_get_softc(ppbusdev);
	return (ppb ? ppb->class_id : -99);
}

/* ---- DF-5512 / DF-5513 / DF-5511 driver-level tests ---- */

struct hx_work {
	device_t	ppbus;		/* ppbus0 device */
	device_t	devA, devB;	/* hxc children (ppb_device ivars) */
	struct fakeppc_softc *ppcsc;
	volatile int	stop;
	volatile int	f4_done;
	volatile int	f4_ret;
	int		f2_iters, f2_hits, f2_ok;
};

static struct hx_work W;

static void
hx_f4_sleeper(void *arg)
{
	struct hx_work *w = arg;

	w->f4_ret = ppb_request_bus(w->ppbus, w->devA, PPB_WAIT | PPB_NOINTR);
	w->f4_done = 1;
	/* woke up owning the bus: release for the next tests */
	ppb_release_bus(w->ppbus, w->devA);
	kthread_exit();
}

static void
hx_f2_releaser(void *arg)
{
	struct hx_work *w = arg;
	struct ppb_device *ivA = device_get_ivars(w->devA);
	int i;

	for (i = 0; i < 50000000 && !w->stop; i++) {
		if (ppb_request_bus(w->ppbus, w->devA, PPB_WAIT | PPB_INTR))
			continue;
		ppb_set_mode(w->ppbus, PPB_PS2);	/* A's mode */
		ppb_release_bus(w->ppbus, w->devA);
		if (ivA->ctx.mode == PPB_EPP)		/* B's mode */
			w->f2_hits++;
		else if (ivA->ctx.mode == PPB_PS2)
			w->f2_ok++;
		w->f2_iters++;
	}
	w->stop = 1;
	kthread_exit();
}

static void
hx_f2_stealer(void *arg)
{
	struct hx_work *w = arg;

	while (!w->stop) {
		if (ppb_request_bus(w->ppbus, w->devB, PPB_DONTWAIT) == 0) {
			ppb_set_mode(w->ppbus, PPB_EPP);	/* B's mode */
			ppb_release_bus(w->ppbus, w->devB);
		} else {
			tsleep(w, 0, "hxsteal", 1);
		}
	}
	kthread_exit();
}

static void
hx_intr_handler(void *arg)
{
}

static void
hx_f3_intr_test(struct hx_work *w)
{
	struct ppb_device *ivA = device_get_ivars(w->devA);
	struct resource *dummy = (struct resource *)&W;
	void *ck1 = NULL, *ck2 = NULL;
	int err, live, i;

	kprintf("PPBX2: DF-5512 ppbus_setup_intr re-registration test\n");
	if (ppb_request_bus(w->ppbus, w->devA, PPB_DONTWAIT)) {
		kprintf("PPBX2: DF-5512 setup fail (bus busy)\n");
		return;
	}

	err = BUS_SETUP_INTR(w->ppbus, w->devA, dummy, 0, hx_intr_handler,
			     NULL, &ck1, NULL, NULL);
	kprintf("PPBX2: DF-5512 setup#1 err=%d ck1=%p ivars.cookie=%p\n",
		err, ck1, ivA->intr_cookie);
	err = BUS_SETUP_INTR(w->ppbus, w->devA, dummy, 0, hx_intr_handler,
			     NULL, &ck2, NULL, NULL);
	kprintf("PPBX2: DF-5512 setup#2 err=%d ck2=%p ivars.cookie=%p "
		"(lpt.c write-error-path re-register)\n",
		err, ck2, ivA->intr_cookie);

	/* release forces teardown of exactly ONE registration */
	ppb_release_bus(w->ppbus, w->devA);

	live = 0;
	for (i = 0; i < w->ppcsc->n_setup; i++)
		if (w->ppcsc->reg_live[i])
			live++;
	kprintf("PPBX2: DF-5512 after ppb_release_bus: parent setups=%d "
		"teardowns=%d still-live=%d; ppbus ivars cookie=%p resource=%p\n",
		w->ppcsc->n_setup, w->ppcsc->n_teardown, live,
		ivA->intr_cookie, ivA->intr_resource);
	kprintf("PPBX2: DF-5512 %s: live==%d handlers remain registered while "
		"ppbus bookkeeping says none (cookie NULL)\n",
		(live >= 1 && ivA->intr_cookie == NULL) ?
		"REPRODUCED" : "not-reproduced", live);

	/* cleanup fake registrations */
	for (i = 0; i < w->ppcsc->n_setup; i++)
		w->ppcsc->reg_live[i] = 0;
}

static void
hx_f4_selfreq_test(struct hx_work *w)
{
	struct thread *td;
	int err;

	kprintf("PPBX2: DF-5513 ppb_request_bus owner-self-request test\n");
	if (ppb_request_bus(w->ppbus, w->devA, PPB_DONTWAIT)) {
		kprintf("PPBX2: DF-5513 setup fail\n");
		return;
	}

	err = ppb_request_bus(w->ppbus, w->devB, PPB_DONTWAIT);
	kprintf("PPBX2: DF-5513 control: non-owner DONTWAIT request -> %d "
		"(expect EWOULDBLOCK=%d)\n", err, EWOULDBLOCK);

	w->f4_done = 0;
	kthread_create(hx_f4_sleeper, w, &td, "hxf4");
	/* give the sleeper 3 seconds to come back on its own */
	tsleep(w, 0, "hxf4wait", 3 * hz);
	if (w->f4_done == 0) {
		kprintf("PPBX2: DF-5513 REPRODUCED: owner re-requested with "
			"PPB_WAIT|PPB_NOINTR is STILL ASLEEP after 3s "
			"(tsleep \"ppbreq\", no PCATCH, no timeout -> sleeps "
			"forever in production; pcfclock_open has no "
			"HAVEBUS guard)\n");
		/* revive it so the module can continue: release the bus */
		ppb_release_bus(w->ppbus, w->devA);
		tsleep(w, 0, "hxf4b", hz);
		kprintf("PPBX2: DF-5513 after ppb_release_bus: sleeper done=%d "
			"ret=%d (only an explicit release ever wakes it)\n",
			w->f4_done, w->f4_ret);
	} else {
		kprintf("PPBX2: DF-5513 not-reproduced: request returned "
			"spontaneously ret=%d\n", w->f4_ret);
	}
}

static void
hx_f2_ctxrace_test(struct hx_work *w)
{
	struct thread *tdr, *tds;

	kprintf("PPBX2: DF-5511 ppb_release_bus ctx-save race test (10s)\n");
	w->stop = 0;
	kthread_create(hx_f2_releaser, w, &tdr, "hxf2r");
	kthread_create(hx_f2_stealer, w, &tds, "hxf2s");
	tsleep(w, 0, "hxf2wait", 10 * hz);
	w->stop = 1;
	tsleep(w, 0, "hxf2done", 2 * hz);
	kprintf("PPBX2: DF-5511 releaser iterations=%d correct-mode=%d "
		"WRONG-mode-captured(B's EPP in A's ctx)=%d -> %s\n",
		w->f2_iters, w->f2_ok, w->f2_hits,
		w->f2_hits > 0 ? "REPRODUCED" :
		(w->f2_iters > 1000 ? "race not hit this run (window exists, "
		 "see code order)" : "insufficient iterations"));
}

static void
hx_worker(void *arg)
{
	device_t ppc0, ppc1, pp0, pp1;
	struct fakeppc_softc *sc0;
	struct ppb_device *ivA;
	int id0, id1;
	char sp_marker;

	kprintf("PPBX2: harness worker start (production ppbconf.c + "
		"ppb_1284.c + ppb_base.c)\n");

	ppc0 = devclass_get_device(devclass_find("ppc"), 0);
	ppc1 = devclass_get_device(devclass_find("ppc"), 1);
	if (ppc0 == NULL || ppc1 == NULL) {
		kprintf("PPBX2: no fake ppc devices\n");
		kthread_exit();
	}
	pp0 = device_add_child(ppc0, "ppbus", 0);
	pp1 = device_add_child(ppc1, "ppbus", 0);
	sc0 = device_get_softc(ppc0);

	/* ---------------- DF-5510 ---------------- */
	kprintf("PPBX2: --- DF-5510 search_token(class, len) length "
		"confusion ---\n");

	/* Ta (unit 1): in-bounds cross-field misclassification */
	{
		static const char taid[] = "CLS:QQ;CMD:PRINTER;";

		id1 = hx_arm_and_attach(ppc1, pp1, taid, strlen(taid), 0);
		kprintf("PPBX2: DF-5510 Ta in-bounds cross-field: ID "
			"\"%s\" class_value=QQ -> class_id=%d (%s)  "
			"[correct: 10 UNKNOWN; buggy: 0 PRINTER]\n",
			taid, id1, pnp_class_name[id1 >= 0 && id1 <= 10 ?
			id1 : 10]);
	}

	/* Tb (unit 0): OOB read past the 257-byte stack object */
	{
		char crafted[256];
		int i;

		for (i = 0; i < 248; i++)
			crafted[i] = 'A';
		crafted[248] = 'C'; crafted[249] = 'L';
		crafted[250] = 'S'; crafted[251] = ':';
		crafted[252] = 'Q'; crafted[253] = 'Q';
		crafted[254] = 'A';
		crafted[255] = ';';

		kprintf("PPBX2: DF-5510 Tb OOB: 256-byte ID, CLS: at 248, "
			"class=str+252, scan window str[252..507] (251 bytes "
			"past the 257-byte str[] object); stack pre-dirtied "
			"with class tokens\n");
		id0 = hx_arm_and_attach(ppc0, pp0, crafted, 256, 1);
		kprintf("PPBX2: DF-5510 Tb OOB result: class_id=%d (%s)  "
			"[correct: 10 UNKNOWN; any 0..9 = token matched in "
			"uninitialized/out-of-bounds stack bytes]\n",
			id0, pnp_class_name[id0 >= 0 && id0 <= 10 ?
			id0 : 10]);
		hx_dirt_map("Tb", &sp_marker);
	}

	/* Tc (unit 0 again is consumed; use pp1 style control on pp0 with
	 * a second child) -- short ID with dirt: proves the dirt alone
	 * cannot flip an in-bounds scan */
	{
		static const char tcid[] = "CLS:QQ;";
		device_t pp0c;
		int idc;

		pp0c = device_add_child(ppc0, "ppbus", 1);
		idc = hx_arm_and_attach(ppc0, pp0c, tcid, strlen(tcid), 1);
		kprintf("PPBX2: DF-5510 Tc control (short ID, same dirt): "
			"class_id=%d (%s) [expect 10 UNKNOWN]\n",
			idc, pnp_class_name[idc >= 0 && idc <= 10 ?
			idc : 10]);
	}

	/* ---------------- DF-5512 / 5513 / 5511 on ppbus0 ---------------- */
	W.ppbus = pp0;
	W.ppcsc = sc0;
	W.devA = BUS_ADD_CHILD(pp0, pp0, 0, "hxc", 0);
	W.devB = BUS_ADD_CHILD(pp0, pp0, 1, "hxc", 1);
	if (W.devA && W.devB) {
		device_probe_and_attach(W.devA);
		device_probe_and_attach(W.devB);
		ivA = device_get_ivars(W.devA);
		kprintf("PPBX2: hxc children on ppbus0 (ivars %p)\n", ivA);

		hx_f3_intr_test(&W);
		hx_f4_selfreq_test(&W);
		hx_f2_ctxrace_test(&W);
	} else {
		kprintf("PPBX2: FAILED to add hxc children\n");
	}

	kprintf("PPBX2: harness worker done\n");
	kthread_exit();
}

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

	/* only unit 0 drives the tests (unit 1 is driven by the worker) */
	if (device_get_unit(dev) != 0)
		return (0);
	/*
	 * Defer the work to a kernel thread so every DRIVER_MODULE in the
	 * module (ppbus, hxc) is registered before we attach anything.
	 */
	kprintf("PPBX2: fakeppc%d attached, starting worker\n",
		device_get_unit(dev));
	kthread_create(hx_worker, NULL, &td, "ppbhx2");
	return (0);
}
