/*
 * msqhx — KLD harness for sys/bus/ppbus/ppb_msq.c (DragonFly 6.5-DEVEL)
 *
 * Evidence class: "KLD-harness-with-production-code" (no parallel port in
 * the QEMU rig — family standard, cf. DF-5418).  The module compiles the
 * *unmodified* production source ppb_msq.c out of /usr/src/sys/bus/ppbus
 * and drives its microsequence interpreter against a fake ppbus controller
 * ("msqad") attached to nexus.  The interpreter target ("msqbus") has a
 * softc of struct ppb_data exactly as the real ppbus driver provides, and
 * its child ("msqcli") carries a struct ppb_device in its ivars, so
 * mode2xfer()/ppb_MS_init()/ppb_MS_exec()/ppb_MS_microseq()/ppb_MS_loop()
 * all execute their production code paths.
 *
 * The fake adapter implements the ppbus INTERFACE exec_microseq method
 * with the exact dispatch semantics of ppc_exec_microseq()
 * (sys/dev/misc/ppc/ppc.c:1332-1542) for the opcodes used by the tests
 * (SET, PTR, DBRA, CALL, SUBRET, C_CALL, RET/PUT/GET return-to-ppb-level
 * contract).  Register I/O is replaced by counters; r_str() is scripted.
 *
 * Cases (kenv tunable msqhx.case, read at module load):
 *
 *  case 0 (default, non-fatal):
 *    DF-5575  ppb_MS_loop() builds MS_DBRA(-1) at ppb_msq.c:166 — with
 *             the adapter contract "target = mi + offset + 1"
 *             (ppc.c:1447-1451, corroborated by the working sequences
 *             nibble_inbyte DBRA(-7)@6->0 and ps2_inbyte DBRA(-4)@4->1)
 *             the branch lands on the DBRA itself: the loop body
 *             executes exactly ONCE regardless of `iter`.
 *    DF-5573  ppb_MS_init_msq() validates only `arg` (ppb_msq.c:206);
 *             the instruction index `ins` (MS_INS(param), 8 bits wide)
 *             is unchecked -> msq[ins].arg[arg] writes up to
 *             255*32+16+8 = 8184 bytes past the caller's array with a
 *             fully controlled int/pointer value.  Proven on a private
 *             kmalloc'd 256-instruction buffer with canaries: ins=60
 *             and ins=200 both land outside the caller-visible array.
 *
 *  case 2 (fatal): DF-5574 — register a PUT transfer loop that itself
 *             contains MS_OP_PUT via ppb_MS_init() (the invariant "the
 *             xfer microsequence should not contain any MS_OP_PUT or
 *             MS_OP_GET!" is a comment at ppb_msq.c:306-307, not a
 *             check) -> ppb_MS_microseq() recurses into itself once per
 *             level with no depth limit -> kernel stack exhaustion.
 *
 *  case 4 (fatal): DF-5576a — MS_OP_GET with no mode loop initialized
 *             (get_xfer[].loop == NULL) panics at ppb_msq.c:292
 *             ("IEEE1284 read not supported") instead of returning an
 *             error.  vpoio_instr() (vpoio.c:461-476) reaches this exact
 *             call shape whenever the current bus mode maps to a slot
 *             the driver never initialized.
 *
 *  case 5 (fatal): DF-5576b — mode2xfer() panics on any ppb->mode not
 *             in {0,1,2,4,8} (ppb_msq.c:100-102), e.g. the PPB_SPP
 *             (0x3) mode alias that ppbconf.h itself defines and
 *             ppb_set_mode() happily stores (ppb_base.c:138 masks only
 *             0x0f).  Also prints the wrong variable (ppbdev->mode)
 *             instead of the bus mode.
 *  case 6 (non-fatal): DF-5577 — ppb_MS_microseq() discards the return
 *             value of its recursive self-calls (ppb_msq.c:301/:309 in
 *             the pristine tree): a transfer loop that aborts on error
 *             (adapter exec_microseq failure — e.g. vpio's
 *             nibble_inbyte_hook timeout) is skipped and the whole
 *             MS_OP_PUT reports success, because the initxfer MS_RET(0)
 *             has already zeroed `error' via the *ret side-channel.
 */

#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/malloc.h>
#include <sys/sysctl.h>

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

#include "ppbus_if.h"

#define	MSEQ	"MSQHX: "

/* ------------------------------------------------------------------ */
/* the two ppb_base.c helpers ppb_msq.c calls, provided locally so the */
/* production engine file compiles standalone (ppbus.ko does not even */
/* export ppb_MS_*; nothing here touches hardware)                     */
/* ------------------------------------------------------------------ */
int
ppb_get_mode(device_t bus)
{
	return (((struct ppb_data *)device_get_softc(bus))->mode);
}

int
ppb_get_epp_protocol(device_t bus)
{
	return (EPP_1_9);
}

/* ------------------------------------------------------------------ */
/* fake ppbus controller on nexus: implements the ppbus INTERFACE      */
/* exec_microseq with the exact dispatch semantics of                  */
/* ppc_exec_microseq() (ppc.c:1332-1542) for the opcodes exercised.     */
/* ------------------------------------------------------------------ */

static char	*hx_ptr;			/* mirrors ppc->ppc_ptr   */
static int	 hx_accum;			/* mirrors ppc->ppc_accum */
static struct ppb_microseq *hx_stack;	/* mirrors ppc's 1-deep stack */
static int	 hx_exec_depth, hx_exec_maxdepth;
static u_int	 hx_regwrites;
static u_int	 hx_dbra_spins;
static char	 hx_status = (char)(SELECT | nBUSY | nFAULT);	/* r_str */

static int
fakeppc_exec_microseq(device_t dev, struct ppb_microseq **p_msq)
{
	struct ppb_microseq *mi;
	int error;

	hx_exec_depth++;
	if (hx_exec_depth > hx_exec_maxdepth)
		hx_exec_maxdepth = hx_exec_depth;
	if ((hx_exec_depth % 25) == 0)
		kprintf(MSEQ "ppb_MS_microseq recursion depth %d "
		    "(no bound in ppb_msq.c)\n", hx_exec_depth);

	mi = *p_msq;
	for (;;) {
		switch (mi->opcode) {
		case MS_OP_SET:				/* ppc.c:1442-1445 */
			hx_accum = mi->arg[0].i;
			mi++;
			break;

		case MS_OP_PTR:				/* ppc.c:1486-1489 */
			hx_ptr = (char *)mi->arg[0].p;
			mi++;
			break;

		case MS_OP_DBRA:			/* ppc.c:1447-1451 */
			if (--hx_accum > 0) {
				mi += mi->arg[0].i;
				hx_dbra_spins++;
			}
			mi++;
			break;

		case MS_OP_CALL:			/* ppc.c:1491-1506 */
			if (hx_stack)
				panic("%s: too much calls", __func__);
			if (mi->arg[0].p) {
				hx_stack = mi;
				mi = (struct ppb_microseq *)mi->arg[0].p;
			} else
				mi++;
			break;

		case MS_OP_SUBRET:			/* ppc.c:1508-1518 */
			mi = hx_stack;
			hx_stack = NULL;
			mi++;
			break;

		case MS_OP_C_CALL:			/* ppc.c:1475-1484 */
			if ((error = mi->arg[0].f(mi->arg[1].p, hx_ptr))) {
				hx_exec_depth--;
				return (error);
			}
			mi++;
			break;

		case MS_OP_PUT:				/* ppc.c:1520-1533 */
		case MS_OP_GET:
		case MS_OP_RET:
			if (hx_stack)
				panic("%s: can't return to ppb level",
				    __func__);
			*p_msq = mi;
			hx_exec_depth--;
			return (0);

		default:
			kprintf(MSEQ "fake exec: opcode %d unhandled\n",
			    mi->opcode);
			hx_exec_depth--;
			return (EINVAL);
		}
	}
}

static int
fakeppc_write(device_t dev, char *buf, int len, int how)
{
	hx_regwrites += len;
	return (0);
}

static int
fakeppc_setmode(device_t dev, int mode)
{
	return (0);
}

static u_char
fakeppc_io(device_t dev, int opcode, u_char *addr, int cnt, u_char byte)
{
	switch (opcode) {
	case PPB_RSTR:
		return ((u_char)hx_status);
	case PPB_WDTR:
	case PPB_WCTR:
	case PPB_WSTR:
		hx_regwrites++;
		break;
	default:
		break;
	}
	return (0);
}

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

static int
fakeppc_probe(device_t dev)
{
	device_set_desc(dev, "fake ppbus controller (msqhx harness)");
	return (0);
}

static int
fakeppc_attach(device_t dev)
{
	/*
	 * NB: plain device_add_child(), NOT BUS_ADD_CHILD(): DF's
	 * bus_generic_add_child() forwards to dev->parent (nexus), which
	 * would create the child on the wrong bus (DF-5418 lesson).
	 */
	if (device_add_child(dev, "msqbus", 0) == NULL)
		return (ENOMEM);
	return (bus_generic_attach(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_exec_microseq,	fakeppc_exec_microseq),
	DEVMETHOD(ppbus_write,		fakeppc_write),
	DEVMETHOD(ppbus_setmode,	fakeppc_setmode),
	DEVMETHOD(ppbus_io,		fakeppc_io),

	DEVMETHOD_END
};

static driver_t fakeppc_driver = {
	"msqad",
	fakeppc_methods,
	0,
};

static devclass_t fakeppc_devclass;

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

/* ------------------------------------------------------------------ */
/* msqbus: the "ppbus" device; softc is a real struct ppb_data         */
/* ------------------------------------------------------------------ */

static device_t	hx_bus;
static device_t	hx_dev;
static struct ppb_device g_ppbdev;	/* ivars of msqcli, as ppbus gives */

static int
msqbus_probe(device_t dev)
{
	device_set_desc(dev, "ppb_msq engine target (msqhx harness)");
	return (0);
}

static int
msqbus_attach(device_t dev)
{
	device_t child;

	hx_bus = dev;

	child = device_add_child(dev, "msqcli", 0);
	if (child == NULL)
		return (ENOMEM);
	device_set_ivars(child, &g_ppbdev);
	return (bus_generic_attach(dev));
}

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

static device_method_t msqbus_methods[] = {
	DEVMETHOD(device_probe,		msqbus_probe),
	DEVMETHOD(device_attach,	msqbus_attach),
	DEVMETHOD(device_detach,	msqbus_detach),
	DEVMETHOD_END
};

static driver_t msqbus_driver = {
	"msqbus",
	msqbus_methods,
	sizeof(struct ppb_data),
};

static devclass_t msqbus_devclass;

DRIVER_MODULE(msqbus, msqad, msqbus_driver, msqbus_devclass, NULL, NULL);

/* ------------------------------------------------------------------ */
/* msqcli: the ppbus client device (ivars = struct ppb_device)         */
/* ------------------------------------------------------------------ */

static int	hx_prolog_n, hx_body_n, hx_epilog_n;

static int
hx_hook(void *which, char *ptr)
{
	switch ((intptr_t)which) {
	case 1: hx_prolog_n++; break;
	case 2: hx_body_n++;   break;
	case 3: hx_epilog_n++; break;
	}
	return (0);
}

/* DF-5575: ppb_MS_loop() must execute `body` iter times ---------------- */
static void
run_df5575(void)
{
	struct ppb_microseq prolog[] = {
		{ MS_OP_C_CALL, { { .f = hx_hook }, { .p = (void *)1 } } },
		MS_SUBRET(0)
	};
	struct ppb_microseq body[] = {
		{ MS_OP_C_CALL, { { .f = hx_hook }, { .p = (void *)2 } } },
		MS_SUBRET(0)
	};
	struct ppb_microseq epilog[] = {
		{ MS_OP_C_CALL, { { .f = hx_hook }, { .p = (void *)3 } } },
		MS_SUBRET(0)
	};
	struct ppb_data *ppb = (struct ppb_data *)device_get_softc(hx_bus);
	int ret, iter = 5;

	kprintf(MSEQ "--- DF-5575 ppb_MS_loop() DBRA(-1) off-by-one ---\n");

	ppb->ppb_owner = hx_dev;	/* pass the EACCES gate */
	hx_prolog_n = hx_body_n = hx_epilog_n = 0;
	hx_dbra_spins = 0;

	ret = ppb_MS_loop(hx_bus, hx_dev, prolog, body, epilog, iter, &ret);

	kprintf(MSEQ "DF-5575 iter=%d -> prolog=%d body=%d epilog=%d "
	    "dbra_spins=%u ret=%d\n",
	    iter, hx_prolog_n, hx_body_n, hx_epilog_n, hx_dbra_spins, ret);
	kprintf(MSEQ "DF-5575 expected body=%d; observed body=%d -> %s\n",
	    iter, hx_body_n,
	    hx_body_n == iter ? "loop works" :
	    (hx_body_n == 1 ? "BODY EXECUTED ONCE (off-by-one CONFIRMED)" :
	    "unexpected"));
}

/* DF-5573: ppb_MS_init_msq() never bounds `ins` ------------------------ */
static void
run_df5573(void)
{
	struct ppb_microseq *probe;
	const int visible = 4;		/* what a driver would declare */
	int i, nb;
#define	MSQ_CANARY	0xCAFEF00D

	kprintf(MSEQ "--- DF-5573 ppb_MS_init_msq() unchecked `ins' ---\n");
	kprintf(MSEQ "sizeof(struct ppb_microseq)=%zu\n",
	    sizeof(struct ppb_microseq));

	probe = kmalloc(256 * sizeof(struct ppb_microseq), M_DEVBUF,
	    M_WAITOK | M_ZERO);
	for (i = 0; i < 256; i++) {
		probe[i].opcode = 0xE0;
		probe[i].arg[0].i = MSQ_CANARY;
		probe[i].arg[1].i = MSQ_CANARY;
		probe[i].arg[2].i = MSQ_CANARY;
	}

	/* in-bounds regression: ins=2 arg=1 (must work before and after fix) */
	nb = ppb_MS_init_msq(probe, 1, MS_PARAM(2, 1, MS_TYP_INT), 0x11223344);
	kprintf(MSEQ "in-bounds ins=2 arg=1: ret=%d probe[2].arg[1].i=0x%x "
	    "(expect 0x11223344)\n", nb, probe[2].arg[1].i);

	/* OOB write #1: 60 instructions = 60*32 = 1792 bytes past a
	 * 4-instruction array, arg 0, MS_TYP_INT */
	ppb_MS_init_msq(probe, 1, MS_PARAM(60, 0, MS_TYP_INT), 0x41424344);
	kprintf(MSEQ "OOB ins=60 (1792B past %d-ins array): probe[60].arg[0]"
	    ".i=0x%x %s\n", visible, probe[60].arg[0].i,
	    probe[60].arg[0].i == 0x41424344 ?
	    "<-- CONTROLLED WRITE LANDED" : "(untouched)");

	/* OOB write #2: 200 instructions, arg 2, MS_TYP_PTR -> a kernel
	 * pointer value at +6416 bytes */
	ppb_MS_init_msq(probe, 1, MS_PARAM(200, 2, MS_TYP_PTR),
	    (void *)0x1234dead);
	kprintf(MSEQ "OOB ins=200 arg=2 (+6416B): probe[200].arg[2].i=0x%x "
	    "%s\n", probe[200].arg[2].i,
	    probe[200].arg[2].i == 0x1234dead ?
	    "<-- CONTROLLED WRITE LANDED" : "(untouched)");

	kprintf(MSEQ "DF-5573 MS_INS(param) is 8 bits wide (ppb_msq.h:38), "
	    "ppb_msq.c:206 checks only `arg' -- `ins' write index is "
	    "unvalidated\n");

	kfree(probe, M_DEVBUF);
#undef	MSQ_CANARY
}

/* DF-5574: PUT-in-PUT-loop -> unbounded ppb_MS_microseq recursion ----- */
static void
run_df5574(void)
{
	static char buf[8];
	/* the transfer loop a (buggy) driver registers: it contains
	 * MS_OP_PUT itself, violating the ppb_msq.c:306-307 comment */
	static struct ppb_microseq evil_loop[] = {
		{ MS_OP_PUT, { { .p = buf }, { .i = 8 } } },
		MS_RET(0)
	};
	static struct ppb_microseq top[] = {
		{ MS_OP_PUT, { { .p = buf }, { .i = 8 } } },
		MS_RET(0)
	};
	struct ppb_data *ppb = (struct ppb_data *)device_get_softc(hx_bus);
	int ret = 0x77;

	kprintf(MSEQ "--- DF-5574 MS_OP_PUT/GET unbounded recursion ---\n");

	ppb->ppb_owner = hx_dev;
	ppb->mode = PPB_NIBBLE;

	/* production ppb_MS_init(): registers evil_loop as the NIBBLE-mode
	 * PUT transfer loop (mode2xfer -> put_xfer[NIBBLE_MSQ].loop) */
	{
		int irc = ppb_MS_init(hx_bus, hx_dev, evil_loop, MS_OP_PUT);
		kprintf(MSEQ "DF-5574 ppb_MS_init rc=%d put_xfer[1].loop=%p "
		    "(evil=%p) mode=%d\n",
		    irc, g_ppbdev.put_xfer[1].loop, evil_loop, ppb->mode);
	}

	kprintf(MSEQ "triggering ppb_MS_microseq(top) with put_xfer[NIBBLE]"
	    ".loop containing MS_OP_PUT; expecting stack exhaustion "
	    "(panic)...\n");

	{
		int rc = ppb_MS_microseq(hx_bus, hx_dev, top, &ret);
		kprintf(MSEQ "DF-5574 ppb_MS_microseq returned rc=%d ret=%d "
		    "maxdepth=%d regwrites=%u (no crash)\n",
		    rc, ret, hx_exec_maxdepth, hx_regwrites);
	}

	kprintf(MSEQ "DF-5574 note: baseline (unfixed) hit DOUBLE FAULT - "
	    "KERNEL STACK GUARD on this same case\n");
}

/* DF-5576a: MS_OP_GET with no loop -> panic --------------------------- */
static void
run_df5576a(void)
{
	union ppb_insarg p1 = { .c = (char *)0 }, p2 = { .i = 4 },
	    p3 = { .i = MS_UNKNOWN };
	struct ppb_data *ppb = (struct ppb_data *)device_get_softc(hx_bus);
	int ret = 0;

	kprintf(MSEQ "--- DF-5576a MS_OP_GET without mode loop ---\n");

	ppb->ppb_owner = hx_dev;
	ppb->mode = PPB_NIBBLE;
	/* g_ppbdev stays zeroed: get_xfer[*].loop == NULL everywhere,
	 * exactly like a driver that never called ppb_MS_GET_init() */

	kprintf(MSEQ "calling ppb_MS_exec(MS_OP_GET); correct behavior: an "
	    "error return; production ppb_msq.c:292: panic()\n");

	ret = ppb_MS_exec(hx_bus, hx_dev, MS_OP_GET, p1, p2, p3, &ret);
	kprintf(MSEQ "DF-5576a returned %d (no panic)\n", ret);
}

/* DF-5576b: mode2xfer() unknown-mode panic ----------------------------- */
static void
run_df5576b(void)
{
	union ppb_insarg p1 = { .c = (char *)0 }, p2 = { .i = 4 },
	    p3 = { .i = MS_UNKNOWN };
	struct ppb_data *ppb = (struct ppb_data *)device_get_softc(hx_bus);
	int ret = 0;

	kprintf(MSEQ "--- DF-5576b mode2xfer() unknown-mode panic ---\n");

	ppb->ppb_owner = hx_dev;
	/* PPB_SPP (0x3) is a mode *alias* ppbconf.h itself defines
	 * (ppbconf.h:47); ppb_set_mode() stores it verbatim after masking
	 * 0x0f (ppb_base.c:138) and mode2xfer() has no case for it */
	ppb->mode = PPB_SPP;

	kprintf(MSEQ "calling ppb_MS_exec(MS_OP_PUT) with ppb->mode=PPB_SPP"
	    "(0x%x); production ppb_msq.c:100-102: panic()\n", PPB_SPP);

	ret = ppb_MS_exec(hx_bus, hx_dev, MS_OP_PUT, p1, p2, p3, &ret);
	kprintf(MSEQ "DF-5576b returned %d (no panic)\n", ret);
}

/* DF-5577: transfer-loop failure swallowed (return value discarded) --- */
static int	hx_fail_hook_calls;

static int
hx_hook_fail(void *arg, char *ptr)
{
	hx_fail_hook_calls++;
	return (5);			/* pretend adapter-level failure */
}

static void
run_df5577(void)
{
	static char buf[8];
	/* a transfer loop whose C hook reports failure, exactly like
	 * vpoio's nibble_inbyte_hook returning VP0_TIMEOUT mid-transfer */
	static struct ppb_microseq failing_loop[] = {
		{ MS_OP_C_CALL, { { .f = hx_hook_fail }, { .p = (void *)2 } } },
		MS_RET(0)
	};
	static struct ppb_microseq top[] = {
		{ MS_OP_PUT, { { .p = buf }, { .i = 8 } } },
		MS_RET(0)
	};
	struct ppb_data *ppb = (struct ppb_data *)device_get_softc(hx_bus);
	int ret = 0x77, rc;

	kprintf(MSEQ "--- DF-5577 xfer-loop failure swallowed ---\n");

	ppb->ppb_owner = hx_dev;
	ppb->mode = PPB_NIBBLE;
	ppb_MS_init(hx_bus, hx_dev, failing_loop, MS_OP_PUT);
	hx_fail_hook_calls = 0;

	rc = ppb_MS_microseq(hx_bus, hx_dev, top, &ret);

	kprintf(MSEQ "DF-5577 hook_failed=%d (rc=%d ret=0x%x): %s\n",
	    hx_fail_hook_calls, rc, ret,
	    (rc == 0 && hx_fail_hook_calls > 0) ?
	    "FAILURE SWALLOWED - reported success (bug)" :
	    "failure propagated (fixed)");
}

static int
msqcli_probe(device_t dev)
{
	device_set_desc(dev, "ppbus microseq client (msqhx harness)");
	return (0);
}

/*
 * run the selected case; 0 = the non-fatal suite (DF-5575 + DF-5573),
 * 2/4/5 = the fatal cases (DF-5574 / DF-5576a / DF-5576b).
 */
static void
hx_run_case(int c)
{
	if (hx_bus == NULL || hx_dev == NULL) {
		kprintf(MSEQ "device tree not attached yet\n");
		return;
	}

	kprintf(MSEQ "harness case=%d start (production ppb_msq.c)\n", c);

	switch (c) {
	case 0:
		run_df5575();	/* non-fatal */
		run_df5573();	/* non-fatal */
		break;
	case 2:
		run_df5574();	/* fatal (unfixed): stack overflow */
		break;
	case 4:
		run_df5576a();	/* fatal (unfixed): panic */
		break;
	case 5:
		run_df5576b();	/* fatal (unfixed): panic */
		break;
	case 6:
		run_df5577();	/* non-fatal */
		break;
	default:
		kprintf(MSEQ "unknown case %d\n", c);
	}

	kprintf(MSEQ "harness case=%d done\n", c);
}

/* sysctl-driven case selector: sysctl hw.msqhx.case=N runs case N */
static int
hx_sysctl_case(SYSCTL_HANDLER_ARGS)
{
	int val = 0;
	int error;

	error = sysctl_handle_int(oidp, &val, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);

	hx_run_case(val);
	return (0);
}
SYSCTL_PROC(_hw, OID_AUTO, msqhx_case, CTLTYPE_INT | CTLFLAG_RW,
	0, 0, hx_sysctl_case, "I", "msqhx test case selector (0|2|4|5)");

static int
msqcli_attach(device_t dev)
{
	hx_dev = dev;

	kprintf(MSEQ "harness attached (production ppb_msq.c); "
	    "drive cases via sysctl hw.msqhx_case={0,2,4,5,6}\n");

	return (0);
}

static int
msqcli_detach(device_t dev)
{
	return (0);
}

static device_method_t msqcli_methods[] = {
	DEVMETHOD(device_probe,		msqcli_probe),
	DEVMETHOD(device_attach,	msqcli_attach),
	DEVMETHOD(device_detach,	msqcli_detach),
	DEVMETHOD_END
};

static driver_t msqcli_driver = {
	"msqcli",
	msqcli_methods,
	0,
};

static devclass_t msqcli_devclass;

DRIVER_MODULE(msqcli, msqbus, msqcli_driver, msqcli_devclass, NULL, NULL);
