/*
 * fwohci_harness.c -- verification harness for DF-2300 / DF-2301 /
 * DF-2302 / DF-2303.  Audit target: sys/bus/firewire/fwohci.c.
 *
 * VERBATIM-INTERPOSITION DESIGN (extends the DF-2293 fwdev.c rig):
 *   - The stock kernel links firewire.o/fwdev.o/fwdma.o/fwmem.o
 *     (device firewire, sys/config/X86_64_GENERIC:319) but NOT
 *     fwohci.o.  This harness #includes the unmodified
 *     sys/bus/firewire/fwohci.c into its own translation unit, so the
 *     audited code runs byte-identical to stock, with its static
 *     functions callable by the rig:
 *       fwohci_ioctl  (fwohci.c:333)   -- DF-2300
 *       print_db      (fwohci.c:2322)  -- DF-2301
 *       fwohci_db_init/fwohci_db_free  -- DF-2302
 *       fwohci_arcv   (fwohci.c:2643)  -- DF-2303
 *   - The harness fabricates only the OHCI-controller externalities a
 *     real PCI card would provide:
 *       1. an emulated MMIO register file fh_regs[0x800/4]: on x86_64
 *          bus_space_read_4/write_4 are direct KVA dereferences
 *          (sys/cpu/x86_64/include/bus_dma.h:155), so sc->bst=MEM(1)
 *          and sc->bsh=(u_long)&fh_regs[0] give the real OWRITE/OREAD
 *          macros a live "register file";
 *       2. an emulated PHY behind OHCI_PHYACCESS, completed by a 1 ms
 *          callout exactly as OHCI link+PHY hardware would
 *          (fwphy_rddata/fwphy_wrdata complete without retry storms);
 *       3. two fabricated parent bus_dma tags (fc.dmat): the realistic
 *          BUS_SPACE_MAXADDR_32BIT one, and a 4 MB-window one that
 *          models a >4 GB-RAM host where the 32-bit DMA window is a
 *          scarce resource (fwohci passes lowaddr=32BIT itself,
 *          fwohci.c:1197);
 *       4. a firewire_softc injected as unit 0 into the real
 *          firewire_devclass (devclass_get_softc in fwdev.c:431 /
 *          fwohci.c:344 finds it);
 *       5. /dev/fw0.0 against the stock firewire_ops with the stock
 *          clone permissions UID_ROOT/GID_OPERATOR/0660, so the real
 *          fw_ioctl -> fc->ioctl(ap) -> fwohci_ioctl chain runs for an
 *          operator-group user;
 *       6. /dev/fwoctl0 (root-only, 0600): rig control (register
 *          peek/poke, real fwohci_init/db_init/db_free drivers, DMA
 *          descriptor fabrication, received-DMA fabrication +
 *          real fwohci_arcv, sub-4MB window exhaustion for DF-2302,
 *          KVA peek for OOB attribution).
 *
 * No line of fwohci.c is modified in the baseline build.  For fix
 * validation the SAME harness kernel is rebuilt with only fix.diff
 * applied to /usr/src/sys/bus/firewire/fwohci.c (guest scratch copy).
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/bus.h>
#include <sys/bus_private.h>
#include <sys/callout.h>
#include <sys/sysctl.h>
#include <sys/queue.h>
#include <sys/thread2.h>
#include <sys/uio.h>

/*
 * The audited file, verbatim, compiled into this TU.  All its statics
 * (tinfo, fwohci_db_init, fwohci_db_free, print_db, dump_db,
 * fwohci_arcv, fwohci_arcv_swap, fwphy_*) are now callable.  NOTE:
 * fwohci.c pulls in the bus/firewire headers itself and
 * firewirereg.h has no include guard, so the harness must not
 * pre-include them.
 */
#include "fwohci.c"

#include "fwohci_harness.h"

extern struct dev_ops firewire_ops;	/* fwdev.c:75, stock kernel symbol */
					/* firewire_debug comes via
					 * firewirereg.h from fwohci.c */

MALLOC_DEFINE(M_FWOH, "fwohci_harness", "fwohci.c verification harness");

static fwohci_softc_t *fh_sc;		/* fabricated OHCI softc */
static struct firewire_softc fh_fwsc;	/* injected unit-0 softc */
static struct bsd_device fh_dev;	/* fabricated device_t shell */
static u_int32_t fh_regs[OHCI_MAX_REG / 4];	/* emulated MMIO file */
static u_char fh_phy[16];		/* emulated PHY registers */
static struct callout fh_phy_cc;	/* PHYACCESS completion timer */
static bus_dma_tag_t fh_parent32;	/* lowaddr = BUS_SPACE_MAXADDR_32BIT */
static bus_dma_tag_t fh_parent4m;	/* lowaddr = 4MB (bounce window) */

/* DMA buffers/eater for DF-2302 */
#define FWOH_EAT_MAX 8192
static void *fh_eaten[FWOH_EAT_MAX];
static u_int fh_eaten_n;

static struct dev_ops fwoctl_ops;
static int fwoctl_open(struct dev_open_args *ap);

/* DMA channel stock parameters, fwohci.c:641-650 */
static void
fh_stock_ch(int ch, u_int *ndesc, u_int *ndb, u_int *psize)
{
	switch (ch) {
	case 0:	*ndesc = 8; *ndb = NDB;     *psize = roundup2(FWPMAX_S400, PAGE_SIZE); break;
	case 1:	*ndesc = 2; *ndb = NDB / 2; *psize = roundup2(FWPMAX_S400, PAGE_SIZE); break;
	case 2:	*ndesc = 1; *ndb = NDB;     *psize = roundup2(FWPMAX_S400, PAGE_SIZE); break;
	default:*ndesc = 1; *ndb = NDB / 2; *psize = roundup2(FWPMAX_S400, PAGE_SIZE); break;
	}
}

static struct fwohci_dbch *
fh_dbch(int ch)
{
	switch (ch) {
	case 0: return (&fh_sc->atrq);
	case 1: return (&fh_sc->atrs);
	case 2: return (&fh_sc->arrq);
	default: return (&fh_sc->arrs);
	}
}

/* ------------------------------------------------------------------ */
/* emulated PHY: OHCI link writes PHYACCESS, callout completes it      */
/* ------------------------------------------------------------------ */
#define PHYACC_REG	(OHCI_PHYACCESS / 4)
static void
fh_phy_tick(void *arg)
{
	u_int32_t v = fh_regs[PHYACC_REG];
	u_int addr;

	if (v & PHYDEV_RDCMD) {
		addr = (v >> PHYDEV_REGADDR) & 0xf;
		/* emulation consumes the command, presents the data */
		fh_regs[PHYACC_REG] = PHYDEV_RDDONE |
		    (addr << PHYDEV_REGADDR) |
		    ((u_int32_t)fh_phy[addr] << PHYDEV_RDDATA);
	} else if (v & PHYDEV_WRCMD) {
		addr = (v >> PHYDEV_REGADDR) & 0xf;
		fh_phy[addr] = (u_char)(v & 0xff);
		/* write done */
		fh_regs[PHYACC_REG] &= ~PHYDEV_WRCMD;
	}
	callout_reset(&fh_phy_cc, 1, fh_phy_tick, NULL);
}

/* ------------------------------------------------------------------ */
/* boot: fabricate softc/register file, inject into devclass, make devs */
/* ------------------------------------------------------------------ */
static struct fw_xferq *fh_ir_xq[4], *fh_it_xq[4], *fh_arq_xq, *fh_ars_xq,
    *fh_atq_xq, *fh_ats_xq;

static struct fw_xferq *
fh_newq(int dmach)
{
	struct fw_xferq *q = kmalloc(sizeof(*q), M_FWOH, M_INTWAIT | M_ZERO);

	q->dmach = dmach;
	q->maxq = FWMAXQUEUE;
	STAILQ_INIT(&q->q);
	STAILQ_INIT(&q->binds);
	STAILQ_INIT(&q->stvalid);
	STAILQ_INIT(&q->stfree);
	STAILQ_INIT(&q->stdma);
	return (q);
}

static void
fh_init(void *arg)
{
	int i;

	fh_sc = kmalloc(sizeof(*fh_sc), M_FWOH, M_INTWAIT | M_ZERO);

	/* emulated controller identity */
	fh_sc->fc.eui.hi = 0x00112233;
	fh_sc->fc.eui.lo = 0x44556677;
	fh_sc->fc.maxrec = 10;
	fh_sc->fc.max_asyretry = 4;
	fh_sc->fc.status = FWBUSCOMPLETION;
	fh_sc->fc.nisodma = 4;
	fh_sc->fc.tcode = tinfo;		/* the real fwohci.c table */
	fh_sc->fc.dev = (device_t)&fh_dev;	/* zeroed shell: prints
						 * "unknown: " */
	fh_sc->fc.ioctl = fwohci_ioctl;		/* THE REAL ONE (DF-2300) */
	fh_sc->bst = X86_64_BUS_SPACE_MEM;
	fh_sc->bsh = (bus_space_handle_t)&fh_regs[0];

	/* fabricated xferqs until/unless real fwohci_init() runs */
	STAILQ_INIT(&fh_sc->fc.binds);
	STAILQ_INIT(&fh_sc->fc.devices);
	for (i = 0; i < 0x40; i++)
		STAILQ_INIT(&fh_sc->fc.tlabels[i]);
	for (i = 0; i < 4; i++) {
		fh_ir_xq[i] = fh_newq(i);
		fh_it_xq[i] = fh_newq(i);
		fh_sc->fc.ir[i] = fh_ir_xq[i];
		fh_sc->fc.it[i] = fh_it_xq[i];
	}
	fh_arq_xq = fh_newq(-1); fh_sc->fc.arq = fh_arq_xq;
	fh_ars_xq = fh_newq(-1); fh_sc->fc.ars = fh_ars_xq;
	fh_atq_xq = fh_newq(-1); fh_atq_xq->maxq = FWMAXQUEUE; fh_sc->fc.atq = fh_atq_xq;
	fh_ats_xq = fh_newq(-1); fh_ats_xq->maxq = FWMAXQUEUE; fh_sc->fc.ats = fh_ats_xq;
	/* start == NULL on purpose: fw_asystart guards it (firewire.c)
	 * so unbound-REQ responses from fw_rcv are dropped safely */

	/* the parent dma tags (what bus_get_dma_tag() would give fwohci) */
	if (bus_dma_tag_create(NULL, 1, 0,
	    BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR,
	    BUS_SPACE_MAXSIZE, BUS_SPACE_UNRESTRICTED,
	    BUS_SPACE_MAXSIZE_32BIT, 0, &fh_parent32) != 0)
		kprintf("fwoh: WARNING parent32 tag failed\n");
	if (bus_dma_tag_create(NULL, PAGE_SIZE, 0,
	    0x00400000ul, BUS_SPACE_MAXADDR,	/* 4MB DMA window: models a
						 * >4GB host for the 32-bit
						 * limited OHCI engine */
	    BUS_SPACE_MAXSIZE, BUS_SPACE_UNRESTRICTED,
	    BUS_SPACE_MAXSIZE_32BIT, 0, &fh_parent4m) != 0)
		kprintf("fwoh: WARNING parent4m tag failed\n");
	fh_sc->fc.dmat = fh_parent32;

	/* stock boot-time OHCI register values (ohci 1.0, 4 iso chans) */
	fh_regs[OHCI_VERSION / 4] = 0x01000100;
	fh_regs[0x90 / 4] = 0x0000000f;		/* IT_MASK readback */
	fh_regs[0xa0 / 4] = 0x0000000f;		/* IR_MASK readback */
	fh_regs[OHCI_BUS_OPT / 4] = 0xf << 12;

	callout_init(&fh_phy_cc);
	callout_reset(&fh_phy_cc, 1, fh_phy_tick, NULL);

	/* fabricate softc as unit 0 of the real firewire devclass */
	if (firewire_devclass == NULL)
		firewire_devclass = devclass_create("firewire");
	if (firewire_devclass->devices == NULL) {
		firewire_devclass->devices = kmalloc(
		    sizeof(device_t) * 4, M_FWOH, M_INTWAIT | M_ZERO);
		firewire_devclass->maxunit = 4;
	}
	fh_fwsc.fc = &fh_sc->fc;
	fh_dev.softc = &fh_fwsc;
	firewire_devclass->devices[0] = (device_t)&fh_dev;

	/* audited device node: real firewire_ops, stock clone perms */
	make_dev(&firewire_ops, MAKEMINOR(0, 0, 0),
	    UID_ROOT, GID_OPERATOR, 0660, "fw0.0");

	/* root-only rig control */
	make_dev(&fwoctl_ops, 0, UID_ROOT, GID_WHEEL, 0600, "fwoctl0");

	kprintf("FWOHARNESS-READY: fwohci.c verbatim (TU-compiled), "
	    "sc=%p regs=%p nisodma=%d /dev/fw0.0 (0660 root:operator) "
	    "+ /dev/fwoctl0 (0600)\n", fh_sc, fh_regs, fh_sc->fc.nisodma);
}
SYSINIT(fwohci_harness, SI_SUB_PSEUDO, SI_ORDER_MIDDLE, fh_init, NULL);

/* ------------------------------------------------------------------ */
/* /dev/fwoctl0: root-only rig control                                 */
/* ------------------------------------------------------------------ */
static int
fh_ctl_init(struct fwo_initreq *ir)
{
	int rc;

	fh_regs[OHCI_VERSION / 4] = ir->version;
	fh_regs[0x90 / 4] = ir->itmask;
	fh_regs[0xa0 / 4] = ir->itmask;
	fh_regs[OHCI_BUS_OPT / 4] = ir->busopt;
	fh_sc->fc.dmat = fh_parent32;

	kprintf("FWOH: INIT running REAL fwohci_init(sc, dev)\n");
	rc = fwohci_init(fh_sc, (device_t)&fh_dev);	/* the real one */
	ir->rc = rc;
	ir->nisodma_out = fh_sc->fc.nisodma;
	kprintf("FWOH: INIT rc=%d nisodma=%d maxrec=%u speed=%u\n",
	    rc, fh_sc->fc.nisodma, fh_sc->fc.maxrec, fh_sc->fc.speed);
	return (0);
}

static int
fh_ctl_dbinit(struct fwo_dbinit *db)
{
	struct fwohci_dbch *dbch = fh_dbch(db->ch);
	u_int ndesc, ndb, psize;

	fh_stock_ch(db->ch, &ndesc, &ndb, &psize);
	if (db->ndesc) ndesc = db->ndesc;
	if (db->ndb) ndb = db->ndb;
	if (db->psize) psize = db->psize;

	if (dbch->flags & FWOHCI_DBCH_INIT) {
		kprintf("FWOH: DBINIT prior channel up, REAL fwohci_db_free\n");
		fwohci_db_free(dbch);
	}
	dbch->ndesc = ndesc;
	dbch->ndb = ndb;
	dbch->xferq.psize = psize;
	fh_sc->fc.dmat = (db->parent == 1) ? fh_parent4m : fh_parent32;

	kprintf("FWOH: DBINIT ch=%u ndesc=%u ndb=%u psize=%u parent=%s "
	    "-> REAL fwohci_db_init\n", db->ch, ndesc, ndb, psize,
	    db->parent == 1 ? "4MB-window" : "32BIT");
	fwohci_db_init(fh_sc, dbch);

	if ((dbch->flags & FWOHCI_DBCH_INIT) == 0) {
		db->rc = ENOMEM;
		return (0);
	}
	db->rc = 0;
	db->o_ndesc = ndesc;
	db->o_ndb = ndb;
	db->o_psize = psize;
	db->esize = dbch->am->esize;
	db->ssize = dbch->am->ssize;
	db->nseg = dbch->am->nseg;
	db->seg0_va = (u_int64_t)(uintptr_t)dbch->am->seg[0].v_addr;
	db->seglast_va =
	    (u_int64_t)(uintptr_t)dbch->am->seg[dbch->am->nseg - 1].v_addr;
	if (db->elem < dbch->ndb) {
		struct fwohcidb *e = fwdma_v_addr(dbch->am, db->elem);
		db->elem_va = (u_int64_t)(uintptr_t)e;
		db->elem_bus = fwdma_bus_addr(dbch->am, db->elem);
		/* KVA where print_db row i==ndesc reads (db[ndesc]) */
		db->past_va = (u_int64_t)(uintptr_t)
		    ((struct fwohcidb *)e + ndesc);
	}
	if (db->elem2 < dbch->ndb) {
		db->elem2_va = (u_int64_t)(uintptr_t)
		    fwdma_v_addr(dbch->am, db->elem2);
		db->elem2_bus = fwdma_bus_addr(dbch->am, db->elem2);
	}
	db->elems_per_seg = dbch->am->ssize / dbch->am->esize;
	kprintf("FWOH: DBINIT ok am=%p esize=%u ssize=%u nseg=%u "
	    "seg0=%p seglast=%p elem[%u]=%p bus=%08jx past=%p "
	    "elem2[%u]=%p bus=%08jx (elems/seg=%u)\n", dbch->am,
	    db->esize, db->ssize, db->nseg,
	    (void *)(uintptr_t)db->seg0_va, (void *)(uintptr_t)db->seglast_va,
	    db->elem, (void *)(uintptr_t)db->elem_va, (uintmax_t)db->elem_bus,
	    (void *)(uintptr_t)db->past_va,
	    db->elem2, (void *)(uintptr_t)db->elem2_va,
	    (uintmax_t)db->elem2_bus, db->elems_per_seg);
	return (0);
}

static int
fh_ctl_descset(struct fwo_descset *ds)
{
	struct fwohci_dbch *dbch = fh_dbch(0);	/* prep targets atrq */
	struct fwohcidb *e;
	u_int i;

	if ((dbch->flags & FWOHCI_DBCH_INIT) == 0 || dbch->am == NULL) {
		ds->rc = ENXIO;
		return (0);
	}
	e = fwdma_v_addr(dbch->am, ds->elem);
	for (i = 0; i < ds->n && i < 8; i++) {
		e[i].db.desc.cmd = ds->d[i].cmd;
		e[i].db.desc.addr = ds->d[i].addr;
		e[i].db.desc.depend = ds->d[i].depend;
		e[i].db.desc.res = ds->d[i].res;
	}
	if (ds->canary && ds->elem + 1 < dbch->am->ssize / dbch->am->esize) {
		struct fwohcidb *c = fwdma_v_addr(dbch->am, ds->elem + 1);
		c[0].db.desc.cmd = 0xCAFE0001;
		c[0].db.desc.addr = 0x11111111;
		c[0].db.desc.depend = 0x22222222;
		c[0].db.desc.res = 0x33333333;
		kprintf("FWOH: DESCSET canary @elem %u db[0] "
		    "{%08x %08x %08x %08x}\n", ds->elem + 1,
		    c[0].db.desc.cmd, c[0].db.desc.addr,
		    c[0].db.desc.depend, c[0].db.desc.res);
	}
	ds->rc = 0;
	return (0);
}

/*
 * DF-2303: fabricate received DMA state and run the REAL fwohci_arcv.
 */
static int
fh_ctl_arcv(struct fwo_arcvreq *ar)
{
	struct fwohci_dbch *dbch = fh_dbch(2);	/* arrq: the AR channel */
	struct fwohcidb_tr *tr0, *tr1;
	u_int psize, hdr, X;
	u_int32_t *b0;

	if ((dbch->flags & FWOHCI_DBCH_INIT) == 0) {
		ar->rc = ENXIO;
		return (0);
	}
	psize = dbch->xferq.psize;
	tr0 = STAILQ_FIRST(&dbch->db_trq);
	tr1 = STAILQ_NEXT(tr0, link);
	if (tr0->buf == NULL || tr1->buf == NULL) {
		/* buffers only exist after rx_enable/add_rx_buf */
		ar->rc = ENXIO;
		kprintf("FWOH: ARCV no buffers (run rx mode first)\n");
		return (0);
	}

	if (ar->mode == FWO_ARCV_INJECT) {
		/*
		 * Impossible-state injection (documented as such): place
		 * buf_offset beyond any value the packet parser can
		 * produce, to demonstrate the latent bcopy landmine.
		 */
		dbch->top = tr0;
		dbch->pdb_tr = tr0;
		dbch->buf_offset = -(int)ar->inj_offset;
		tr0->db[0].db.desc.res = (OHCI_CNTL_DMA_ACTIVE << 16) | 0;
		tr1->db[0].db.desc.res = (OHCI_CNTL_DMA_ACTIVE << 16) | 64;
		memset(tr1->buf, 0x5A, 64);
		ar->rlen_old_obs = psize - ar->inj_offset;
		kprintf("FWOH: ARCV INJECT buf_offset=%d -> rlen_old=%u "
		    "(%s 16) -> REAL fwohci_arcv\n", dbch->buf_offset,
		    ar->rlen_old_obs,
		    ar->rlen_old_obs > 16 ? "EXCEEDS" : "within");
		fwohci_arcv(fh_sc, dbch, -1);
		ar->buf_offset_after = dbch->buf_offset;
		ar->pdb_tr_set = (dbch->pdb_tr != NULL);
		kprintf("FWOH: ARCV INJECT returned (survived)\n");
		return (0);
	}

	/*
	 * Natural straddle: buffer 0 holds a quadlet-aligned chain of
	 * well-formed AR packets (WRES = 16B each, WREQB len=0 = 20B
	 * each, exactly as fwohci_arcv's stream parser consumes them)
	 * spanning [0, X), followed by the first F bytes of packet B's
	 * header; buffer 1 holds the rest of B's header plus its 4-byte
	 * DMA trailer, exactly as the OHCI AR DMA would have written it.
	 *
	 * fwohci_arcv_swap (fwohci.c:2578) does not handle STREAM tcodes
	 * and every AR packet consumes a multiple of 4 bytes (12B hdr +
	 * 4B trailer, or 16B hdr + 4B + roundup2(len,4)), so a split
	 * header can only start at X with X % 4 == 0: the reachable F
	 * set for a 4096B buffer is exactly {4, 8, 12} (16B headers) /
	 * {4, 8} (12B headers).
	 */
	hdr = (ar->tcode == FWTCODE_WREQB) ? 16 : 12;
	if (ar->F == 0 || (ar->F & 3) || ar->F >= hdr + 4) {
		kprintf("FWOH: ARCV natural F=%u not quadlet-reachable\n",
		    ar->F);
		ar->rc = EINVAL;
		return (0);
	}
	X = psize - ar->F;
	b0 = (u_int32_t *)tr0->buf;
	memset(tr0->buf, 0, psize);
	memset(tr1->buf, 0, psize);
	{
		/* filler chain: a * WRES(16B) + b * WREQB20 covering [0,X) */
		u_int q = X / 4, b = q % 4, a = (q - 5 * b) / 4;
		u_int off = 0, i;
		u_int32_t w[5];

		for (i = 0; i < a; i++) {
			/* WRES: 12B header + 4B DMA result trailer */
			w[0] = (1u << 10) | (FWTCODE_WRES << 4);	/* tl=1 */
			w[1] = 0x1234u << 16;			/* src */
			w[2] = 0;
			w[3] = (2 << 5) | FWOHCIEV_ACKCOMPL;	/* trailer */
			bcopy(w, tr0->buf + off, 16);
			off += 16;
		}
		for (i = 0; i < b; i++) {
			/* WREQB len=0: 16B header + 4B trailer */
			w[0] = (1u << 10) | (FWTCODE_WREQB << 4);
			w[1] = 0x1234u << 16;
			w[2] = 0;				/* dest_lo */
			w[3] = 0;				/* len=0 */
			w[4] = (2 << 5) | FWOHCIEV_ACKCOMPL;
			bcopy(w, tr0->buf + off, 20);
			off += 20;
		}
		KKASSERT(off == X);
	}
	/* B: first F bytes of its header at [X, psize) */
	b0[X / 4] = (ar->tcode << 4) | 0x0400;	/* tcode + a tlabel */
	/* buffer 1: rest of B header + trailer */
	{
		u_int8_t *b1 = tr1->buf;
		u_int rest = hdr - ar->F;

		memset(b1, 0, rest);
		*((u_int32_t *)(b1 + rest)) = (2 << 5) | FWOHCIEV_ACKCOMPL;
	}
	tr0->db[0].db.desc.res = (OHCI_CNTL_DMA_ACTIVE << 16) | 0;
	tr1->db[0].db.desc.res = (OHCI_CNTL_DMA_ACTIVE << 16) |
	    (psize - (hdr - ar->F + 4));

	/* reset the AR state machine */
	dbch->top = tr0;
	dbch->pdb_tr = NULL;
	dbch->buf_offset = 0;

	ar->rlen_old_obs = ar->F;
	kprintf("FWOH: ARCV NATURAL tcode=%u hdr=%u F=%u X=%u "
	    "(rlen_old=%u rlen_new=%d) -> REAL fwohci_arcv\n",
	    ar->tcode, hdr, ar->F, X, ar->F, (int)sizeof(struct fw_pkt) -
	    (int)ar->F);
	fwohci_arcv(fh_sc, dbch, -1);
	ar->buf_offset_after = dbch->buf_offset;
	ar->pdb_tr_set = (dbch->pdb_tr != NULL);
	return (0);
}

/*
 * DF-2302: pin every page of the sub-4MB bounce window, release a
 * controlled number, then let the REAL fwohci_db_init run so the REAL
 * bus_dmamap_create fails mid-loop -> real fwohci_db_free.
 */
static int
fh_ctl_memeat(struct fwo_memeat *mm)
{
	void *p;
	u_int i;
	struct fwohci_dbch *dbch = fh_dbch(mm->ch);
	u_int ndesc, ndb, psize;

	/* eat the whole window with the SAME reclamation power the
	 * bus_dmamap_create path has (M_WAITOK = 3-pass incl. active
	 * page flush, sys/vm/vm_contig.c:300-376), so a later 3-pass
	 * contigmalloc in alloc_bounce_pages deterministically fails */
	fh_eaten_n = 0;
	while (fh_eaten_n < FWOH_EAT_MAX) {
		p = contigmalloc(PAGE_SIZE, M_FWOH, M_WAITOK,
		    0ul, 0x00400000ul, PAGE_SIZE, 0);
		if (p == NULL)
			break;
		fh_eaten[fh_eaten_n++] = p;
	}
	mm->ate = fh_eaten_n;
	/* give a controlled number back */
	mm->freed = 0;
	for (i = 0; i < mm->free_pages && fh_eaten_n > 0; i++) {
		contigfree(fh_eaten[--fh_eaten_n], PAGE_SIZE, M_FWOH);
		mm->freed++;
	}

	fh_stock_ch(mm->ch, &ndesc, &ndb, &psize);
	if (mm->ndesc) ndesc = mm->ndesc;
	if (mm->ndb) ndb = mm->ndb;
	if (mm->psize) psize = mm->psize;
	if (dbch->flags & FWOHCI_DBCH_INIT)
		fwohci_db_free(dbch);
	dbch->ndesc = ndesc;
	dbch->ndb = ndb;
	dbch->xferq.psize = psize;
	fh_sc->fc.dmat = fh_parent4m;

	kprintf("FWOH: MEMEAT ate=%u freed=%u -> REAL fwohci_db_init "
	    "(ch=%u ndesc=%u ndb=%u)\n", mm->ate, mm->freed, mm->ch,
	    ndesc, ndb);
	fwohci_db_init(fh_sc, dbch);
	mm->survived = 1;
	mm->rc = 0;
	kprintf("FWOH: MEMEAT db_init RETURNED flags=%x (no failure?)\n",
	    dbch->flags);

	/* undo the pressure */
	while (fh_eaten_n > 0)
		contigfree(fh_eaten[--fh_eaten_n], PAGE_SIZE, M_FWOH);
	return (0);
}

/*
 * Bring an AR channel fully up with the REAL code path:
 * dummy_dma alloc + real fwohci_rx_enable (which runs real
 * fwohci_add_rx_buf on every tr: per-tr psize DMA buffers,
 * descriptor programs, register writes into the emulated MMIO).
 */
static int
fh_ctl_rxen(u_int32_t *chp, u_int32_t *rcp)
{
	struct fwohci_dbch *dbch = fh_dbch(*chp);
	int rc;

	if (fh_sc->dummy_dma.v_addr == NULL) {
		fwdma_malloc(&fh_sc->fc, sizeof(u_int32_t),
		    sizeof(u_int32_t), &fh_sc->dummy_dma, BUS_DMA_WAITOK);
		kprintf("FWOH: RXEN dummy_dma=%p\n",
		    fh_sc->dummy_dma.v_addr);
	}
	dbch->xferq.flag &= ~FWXFERQ_RUNNING;
	kprintf("FWOH: RXEN ch=%u -> REAL fwohci_rx_enable\n", *chp);
	rc = fwohci_rx_enable(fh_sc, dbch);
	*rcp = rc;
	kprintf("FWOH: RXEN rc=%d flags=%x top->buf=%p\n",
	    rc, dbch->flags, dbch->top ? dbch->top->buf : NULL);
	return (0);
}

static int
fwoctl_ioctl(struct dev_ioctl_args *ap)
{
	struct fwo_regreq rr;
	struct fwo_initreq ir;
	struct fwo_dbinit db;
	struct fwo_descset ds;
	struct fwo_arcvreq ar;
	struct fwo_memeat mm;
	struct fwo_kread kr;
	struct fwo_statereq st;
	struct fwohci_dbch *dbch;
	int err = 0;

	switch (ap->a_cmd) {
	case FWOIOC_REGRD:
		bcopy(ap->a_data, &rr, sizeof(rr));
		if (rr.addr <= OHCI_MAX_REG)
			rr.data = fh_regs[rr.addr / 4];
		else
			err = EINVAL;
		bcopy(&rr, ap->a_data, sizeof(rr));
		break;
	case FWOIOC_REGWR:
		bcopy(ap->a_data, &rr, sizeof(rr));
		if (rr.addr <= OHCI_MAX_REG)
			fh_regs[rr.addr / 4] = rr.data;
		else
			err = EINVAL;
		break;
	case FWOIOC_PHYRD:
		bcopy(ap->a_data, &rr, sizeof(rr));
		bcopy(fh_phy, rr.phy, sizeof(fh_phy));
		bcopy(&rr, ap->a_data, sizeof(rr));
		break;
	case FWOIOC_INIT:
		bcopy(ap->a_data, &ir, sizeof(ir));
		err = fh_ctl_init(&ir);
		bcopy(&ir, ap->a_data, sizeof(ir));
		break;
	case FWOIOC_DBINIT:
		bcopy(ap->a_data, &db, sizeof(db));
		err = fh_ctl_dbinit(&db);
		bcopy(&db, ap->a_data, sizeof(db));
		break;
	case FWOIOC_DESCSET:
		bcopy(ap->a_data, &ds, sizeof(ds));
		err = fh_ctl_descset(&ds);
		bcopy(&ds, ap->a_data, sizeof(ds));
		break;
	case FWOIOC_ARCV:
		bcopy(ap->a_data, &ar, sizeof(ar));
		err = fh_ctl_arcv(&ar);
		bcopy(&ar, ap->a_data, sizeof(ar));
		break;
	case FWOIOC_RXEN:
		bcopy(ap->a_data, &ar, sizeof(ar));
		err = fh_ctl_rxen(&ar.mode, &ar.rc);
		bcopy(&ar, ap->a_data, sizeof(ar));
		break;
	case FWOIOC_MEMEAT:
		bcopy(ap->a_data, &mm, sizeof(mm));
		err = fh_ctl_memeat(&mm);
		bcopy(&mm, ap->a_data, sizeof(mm));
		break;
	case FWOIOC_KREAD:
		bcopy(ap->a_data, &kr, sizeof(kr));
		bcopy((void *)(uintptr_t)kr.kaddr, kr.data, 16);
		bcopy(&kr, ap->a_data, sizeof(kr));
		break;
	case FWOIOC_STATE:
		bcopy(ap->a_data, &st, sizeof(st));
		dbch = fh_dbch(st.ch);
		st.flags = dbch->flags;
		st.ndb = dbch->ndb;
		st.ndesc = dbch->ndesc;
		st.buf_offset = dbch->buf_offset;
		st.pdb_tr_set = (dbch->pdb_tr != NULL);
		st.top_set = (dbch->top != NULL);
		bcopy(&st, ap->a_data, sizeof(st));
		break;
	default:
		err = ENOTTY;
	}
	return (err);
}

static struct dev_ops fwoctl_ops = {
	{ "fwoctl", 0, D_MEM },
	.d_open =	fwoctl_open,
	.d_ioctl =	fwoctl_ioctl,
};

static int
fwoctl_open(struct dev_open_args *ap)
{
	return (0);
}
