/*
 * ppbbasehx — KLD harness for sys/bus/ppbus/ppb_base.c (DragonFly 6.5-DEVEL)
 *
 * Evidence class: "KLD-harness-with-production-code" (no parallel port in
 * the QEMU rig — family standard proven with DF-5418/5511/5573 this pass).
 * The module compiles the UNMODIFIED production source ppb_base.c out of
 * the guest's /usr/src/sys/bus/ppbus and drives it against a fake ppbus
 * controller ("fakeppc") on nexus.  The poll/status/epp targets run on a
 * "ppbshim" child device whose softc is a real struct ppb_data, exactly
 * as the ppbus driver would provide.
 *
 * The fake controller mirrors the PRODUCTION ppc.c controller shapes:
 *   - fakeppc_ecp_sync()  is `static void ... (device_t)'  == ppc.c:144
 *   - fakeppc_reset_epp() is `static void ... (device_t)'  == ppc.c:1785
 *     against the interface declared in ppbus_if.m:57/:72 as `METHOD int'
 *     — exactly the production interface/implementation mismatch.
 *   - fakeppc_read_ivar() returns ENOENT WITHOUT writing *result, the
 *     generic behavior of any controller that does not implement the
 *     PPC ivars (ppc.c:2048 does implement PPC_IVAR_EPP_PROTO — the
 *     only in-tree controller — so ppb_get_epp_protocol's ignored
 *     BUS_READ_IVAR error never manifests in-tree today).
 *
 * Tests (output prefixed "PPBHX:", all execute at attach time):
 *
 *  DF-5625  ppb_base.c:159-163/170-174 return PPBUS_RESET_EPP /
 *           PPBUS_ECP_SYNC although the only implementations are void:
 *           the DEVMETHOD dispatch calls a void function through an
 *           int-returning prototype — the return value is whatever the
 *           callee last left in %eax (indeterminate per C, tracked here
 *           deterministically via a marker call to demonstrate the
 *           mechanism).
 *
 *  DF-5626  ppb_base.c:60-67: the PPB_POLL path busy-spins
 *           max*10000*(port read + DELAY(1)) with no tsleep, no signal
 *           checkpoint and no clamp on max.  Proven by RSTR-read count
 *           (exactly 10000 per max unit — a sleeping poll reads the
 *           status once per 10 ms window) and wall time.  max=100 is
 *           do_peripheral_wait()'s in-tree argument (ppb_1284.c:64) =>
 *           >=1 s single uninterruptible busy window per call.
 *
 *  DF-5627  ppb_base.c:100-108: BUS_READ_IVAR's return value is
 *           discarded and `protocol' is returned uninitialized whenever
 *           the parent fails / does not implement the ivar.  Proven by
 *           dirtying the stack with distinct byte patterns immediately
 *           before each call: the "protocol" leaks those bytes.
 *
 *  DF-5628  ppb_base.c:77/:83 sleep tick arithmetic `hz/100': with the
 *           boot tunable kern.hz < 100 (HZ_DEFAULT=100, subr_param.c:54,
 *           tunable subr_param.c:199) the quotient is 0 and tsleep()
 *           arms NO timeout at all (kern_synch.c:691 `if (timo)').
 *           Demonstrated by a kernel thread parked on tsleep(...,0)
 *           still being asleep many ticks later, waking only on the
 *           explicit wakeup().
 */

#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/time.h>
#include <sys/kthread.h>
#include <sys/thread.h>

#include <machine/clock.h>

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

#include "ppbus_if.h"

/* ------------------------------------------------------------------ */
/* scripted peer + counters                                            */
/* ------------------------------------------------------------------ */

static char	hx_default_status = (char)0xD0;	/* nBUSY|nACK|SELECT set: */
						/* SELECT never clears, so */
						/* a SELECT==0 poll fails  */
static int	hx_rstr_calls;
static int	hx_setmode_calls;

static void
hx_reset_peer(void)
{
	hx_rstr_calls = 0;
	hx_setmode_calls = 0;
}

/* ------------------------------------------------------------------ */
/* fake ppbus controller (parent) — mirrors production ppc.c shapes    */
/* ------------------------------------------------------------------ */

static u_char
fakeppc_io(device_t dev, int opcode, u_char *addr, int cnt, u_char byte)
{
	switch (opcode) {
	case PPB_RSTR:
		hx_rstr_calls++;
		return ((u_char)hx_default_status);
	default:
		break;
	}
	return (0);
}

static int
fakeppc_setmode(device_t dev, int mode)
{
	hx_setmode_calls++;
	return (0);			/* chipset accepts everything */
}

/*
 * Production shape: ppc.c:144  `static void ppc_ecp_sync(device_t dev)'
 * and ppc.c:1785 `static void ppc_reset_epp(device_t dev)' — both void
 * while ppbus_if.m:57/:72 declare `METHOD int'.  The marker's return
 * value is what the void epilogue leaves in %eax, i.e. what the int-
 * typed dispatch will hand to ppb_base.c's `return'.
 */
static int	hx_marker_val = 0x66;
static int	hx_marker_hits;

static int __noinline
hx_marker(void)
{
	hx_marker_hits++;		/* side effect: cannot be elided */
	return (hx_marker_val);
}

static void
fakeppc_ecp_sync(device_t dev)			/* void == ppc.c:144 */
{
	(void)hx_marker();
}

static void
fakeppc_reset_epp(device_t dev)			/* void == ppc.c:1785 */
{
	(void)hx_marker();
}

/*
 * Generic-controller behavior for ivars: fail without writing *result
 * (ppc.c:2048-2063's own default branch does exactly this for unknown
 * indexes; a non-ppc controller has no PPC ivars at all).
 */
static int
fakeppc_read_ivar(device_t bus, device_t child, int index, uintptr_t *result)
{
	return (ENOENT);		/* *result deliberately NOT written */
}

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

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

static int
fakeppc_attach(device_t dev)
{
	/* NB: plain device_add_child(), NOT BUS_ADD_CHILD() (DF-5418 lore) */
	if (device_add_child(dev, "ppbshim", 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),

	/* bus */
	DEVMETHOD(bus_read_ivar,	fakeppc_read_ivar),

	/* ppbus interface (shapes mirror production ppc.c) */
	DEVMETHOD(ppbus_io,		fakeppc_io),
	DEVMETHOD(ppbus_setmode,	fakeppc_setmode),
	DEVMETHOD(ppbus_ecp_sync,	fakeppc_ecp_sync),	/* void impl */
	DEVMETHOD(ppbus_reset_epp,	fakeppc_reset_epp),	/* void impl */

	DEVMETHOD_END
};

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

static devclass_t fakeppc_devclass;

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

/* ------------------------------------------------------------------ */
/* ppbshim: child whose softc is a real struct ppb_data                */
/* ------------------------------------------------------------------ */

static device_t	hx_bus;

static int
ppbshim_probe(device_t dev)
{
	device_set_desc(dev, "ppb_base.c target (harness)");
	return (0);
}

static suseconds_t
hx_dt_us(struct timeval *t0, struct timeval *t1)
{
	return ((t1->tv_sec - t0->tv_sec) * 1000000 + (t1->tv_usec - t0->tv_usec));
}

/* ---- DF-5626: PPB_POLL busy-spin vs sleeping poll ------------------ */

static void
run_df5626_tests(void)
{
	struct timeval t0, t1;
	int ret;

	kprintf("PPBHX: --- DF-5626 ppb_poll_bus PPB_POLL busy-spin (ppb_base.c:60-67) ---\n");

	/* never-matching peer: SELECT bit always set, we require it clear */
	hx_reset_peer();
	getmicrouptime(&t0);
	ret = ppb_poll_bus(hx_bus, 5, SELECT, 0, PPB_NOINTR | PPB_POLL);
	getmicrouptime(&t1);
	kprintf("PPBHX: DF-5626 busy  max=5  : ret=%d rstr_reads=%d "
		"(expect exactly 50000 = 5*10000 busy port reads) "
		"wall=%ldus (expect >=50000: pure DELAY, never sleeps)\n",
		ret, hx_rstr_calls, (long)hx_dt_us(&t0, &t1));

	/* do_peripheral_wait()'s exact production argument (ppb_1284.c:64) */
	hx_reset_peer();
	getmicrouptime(&t0);
	ret = ppb_poll_bus(hx_bus, 100, SELECT, 0, PPB_NOINTR | PPB_POLL);
	getmicrouptime(&t1);
	kprintf("PPBHX: DF-5626 busy  max=100: ret=%d rstr_reads=%d "
		"(expect exactly 1000000) wall=%ldus (>=1s single "
		"uninterruptible busy window = do_peripheral_wait arg)\n",
		ret, hx_rstr_calls, (long)hx_dt_us(&t0, &t1));

	/* control: same wall order WITHOUT PPB_POLL -> the sleeping path:
	 * 1x10000 busy (10ms) + (max-1)=2 status checks + 2x tsleep(10ms) */
	hx_reset_peer();
	getmicrouptime(&t0);
	ret = ppb_poll_bus(hx_bus, 3, SELECT, 0, PPB_NOINTR);
	getmicrouptime(&t1);
	kprintf("PPBHX: DF-5626 sleep ctrl max=3: ret=%d rstr_reads=%d "
		"(expect 10002 = 10000 busy + 2 checks) wall=%ldus "
		"(~30ms but only 10ms busy: PPB_POLL flag is what turns the "
		"wait into a full busy spin)\n",
		ret, hx_rstr_calls, (long)hx_dt_us(&t0, &t1));
}

/* ---- DF-5625: int wrapper over void methods ------------------------ */

static void
run_df5625_tests(void)
{
	int r1, r2, r3;

	kprintf("PPBHX: --- DF-5625 ppb_ecp_sync/ppb_reset_epp_timeout return "
		"void methods (ppb_base.c:159-163,170-174 vs ppc.c:144,1785) ---\n");

	hx_marker_val = 0x66;
	r1 = ppb_ecp_sync(hx_bus);
	hx_marker_val = 0x99;
	r2 = ppb_ecp_sync(hx_bus);
	hx_marker_val = 0x5A;
	r3 = ppb_reset_epp_timeout(hx_bus);

	kprintf("PPBHX: DF-5625 ppb_ecp_sync()          -> 0x%x "
		"(void method left marker 0x66 in %%eax)\n", r1);
	kprintf("PPBHX: DF-5625 ppb_ecp_sync()          -> 0x%x "
		"(void method left marker 0x99 in %%eax)\n", r2);
	kprintf("PPBHX: DF-5625 ppb_reset_epp_timeout() -> 0x%x "
		"(void method left marker 0x5a in %%eax; marker hits=%d)\n",
		r3, hx_marker_hits);
	kprintf("PPBHX: DF-5625 the 'int' returns track the void callee's "
		"last register state: not a defined value (C UB through the "
		"DEVMETHOD dispatch); callers that error-check get garbage\n");
}

/* ---- DF-5627: uninitialized protocol on failed BUS_READ_IVAR ------- */

static int hx_sink;

static void __noinline
hx_dirty_stack(char c)
{
	volatile char pad[512];
	int i;

	for (i = 0; i < 512; i++)
		pad[i] = c;
	hx_sink = pad[0] + pad[511];
}

static void
run_df5627_tests(void)
{
	int p1, p2, p3;

	kprintf("PPBHX: --- DF-5627 ppb_get_epp_protocol uninitialized on "
		"BUS_READ_IVAR failure (ppb_base.c:100-108) ---\n");

	hx_dirty_stack(0x41);
	p1 = ppb_get_epp_protocol(hx_bus);
	hx_dirty_stack(0x42);
	p2 = ppb_get_epp_protocol(hx_bus);
	hx_dirty_stack(0x43);
	p3 = ppb_get_epp_protocol(hx_bus);

	kprintf("PPBHX: DF-5627 stack dirtied 0x41 -> protocol=0x%08x\n", p1);
	kprintf("PPBHX: DF-5627 stack dirtied 0x42 -> protocol=0x%08x\n", p2);
	kprintf("PPBHX: DF-5627 stack dirtied 0x43 -> protocol=0x%08x\n", p3);
	kprintf("PPBHX: DF-5627 fake controller read_ivar returned ENOENT "
		"without writing (ppb_base.c:105 discards it); the return "
		"value is stale stack content -> in-tree consumer "
		"mode2xfer (ppb_msq.c:85) would take the default PANIC "
		"branch (DF-5576) or silently pick the wrong EPP protocol\n");
}

/* ---- DF-5628: hz/100 == 0 arms no tsleep timeout ------------------- */

static int		hx_park_ident;
static struct thread	*hx_park_td;
static int		hx_done;

static void
hx_park(void *arg)
{
	/* timo == 0: kern_synch.c:691 `if (timo)' -> NO callout armed */
	tsleep((void *)&hx_park_ident, 0, "ppbhz0", 0);
	kprintf("PPBHX: DF-5628 park thread woke (explicit wakeup only, "
		"no timeout existed)\n");
	kthread_exit();
}

/*
 * DF-5628b: park through PRODUCTION ppb_poll_bus at hz=50 — its
 * tsleep(..., hz/100) computes 50/100 == 0, so the sleeping poll path
 * arms no timeout and the thread stays parked until the explicit
 * wakeup.  Cleanup: make the scripted status match the poll condition
 * (SELECT clears -> (r & mask) == status) so the thread returns after
 * being woken.
 */
static struct thread	*hx_hzpark_td;

static void
hx_hzpark(void *arg)
{
	int ret;

	ret = ppb_poll_bus(hx_bus, 3, SELECT, 0, PPB_NOINTR);
	kprintf("PPBHX: DF-5628b hz=50 production ppb_poll_bus returned %d "
		"only after wakeup+match (with a working timeout it would "
		"have returned EWOULDBLOCK=%d on its own long ago)\n",
		ret, EWOULDBLOCK);
	kthread_exit();
}

static void
run_df5628_tests(void)
{
	int error;

	kprintf("PPBHX: --- DF-5628 ppb_poll_bus hz/100 tick arithmetic "
		"(ppb_base.c:77,83) ---\n");

	kprintf("PPBHX: DF-5628 hz=%d -> hz/100=%d tick(s) per poll sleep; "
		"kern.hz is a boot tunable (subr_param.c:199): hz<100 makes "
		"the quotient 0 and tsleep(...,0) arms NO timeout "
		"(kern_synch.c:691)\n", hz, hz / 100);

	error = kthread_create(hx_park, NULL, &hx_park_td, "ppbhz0");
	if (error) {
		kprintf("PPBHX: DF-5628 kthread_create failed: %d\n", error);
		return;
	}

	/* wait 6 ticks (~60ms at hz=100) — 6x the alleged 10ms timeout */
	tsleep(&hx_done, 0, "ppbhxw", 6);

	kprintf("PPBHX: DF-5628 after 6 ticks: park thread wchan==ident:%d "
		"wmesg=%s -> STILL ASLEEP: a 10ms timeout never fired "
		"because timo==0 means 'sleep forever' in DragonFly tsleep\n",
		hx_park_td->td_wchan == (const void *)&hx_park_ident,
		hx_park_td->td_wmesg ? hx_park_td->td_wmesg : "?");

	wakeup((void *)&hx_park_ident);
	tsleep(&hx_done, 0, "ppbhxw", 3);	/* let it exit before unload */

	/*
	 * DF-5628b: lower hz to 50 (the boot tunable's value space,
	 * subr_param.c:199) and drive the PRODUCTION sleeping poll path.
	 * ppb_base.c:77 computes hz/100 == 0 -> tsleep(...,0) -> park.
	 */
	{
		int save_hz = hz;

		hz = 50;
		kprintf("PPBHX: DF-5628b hz forced to %d (kern.hz tunable "
			"space): production ppb_poll_bus(max=3, PPB_NOINTR) "
			"on park thread, peer never matches\n", hz);

		if (kthread_create(hx_hzpark, NULL, &hx_hzpark_td,
				   "ppbhz50") == 0) {
			/*
			 * Wait past the 10ms-nominal busy prologue (measured
			 * ~200ms in this guest: 10000 x (dispatch+DELAY(1)))
			 * so the thread has certainly reached the sleeping
			 * loop and parked on tsleep(...,0).
			 */
			tsleep(&hx_done, 0, "ppbhxw", 30);	/* ~300ms */
			kprintf("PPBHX: DF-5628b after ~300ms (busy prologue "
				"over): hz-park thread wchan==bus:%d "
				"wmesg=%s -> STILL PARKED IN PRODUCTION "
				"ppb_poll_bus: the 30ms timeout never fired "
				"(50/100==0 -> timo==0 -> no callout)\n",
				hx_hzpark_td->td_wchan ==
					(const void *)hx_bus,
				hx_hzpark_td->td_wmesg ?
					hx_hzpark_td->td_wmesg : "?");

			/* free it: let the next condition check match */
			hx_default_status = 0;
			wakeup((caddr_t)hx_bus);
			tsleep(&hx_done, 0, "ppbhxw", 4);
		}
		hz = save_hz;
		hx_default_status = (char)0xD0;
		kprintf("PPBHX: DF-5628b hz restored to %d\n", hz);
	}
}

static int
ppbshim_attach(device_t dev)
{
	hx_bus = dev;

	kprintf("PPBHX: harness start (production ppb_base.c)\n");
	run_df5626_tests();
	run_df5625_tests();
	run_df5627_tests();
	run_df5628_tests();
	kprintf("PPBHX: harness done\n");

	return (0);
}

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

static device_method_t ppbshim_methods[] = {
	DEVMETHOD(device_probe,		ppbshim_probe),
	DEVMETHOD(device_attach,	ppbshim_attach),
	DEVMETHOD(device_detach,	ppbshim_detach),
	DEVMETHOD_END
};

static driver_t ppbshim_driver = {
	"ppbshim",
	ppbshim_methods,
	sizeof(struct ppb_data),
};

static devclass_t ppbshim_devclass;

DRIVER_MODULE(ppbshim, fakeppc, ppbshim_driver, ppbshim_devclass, NULL, NULL);
