/*
 * DF-5545 harness — xhci_pci_detach() swallows device_delete_children()
 * failure (xhci_pci.c:355) and unconditionally tears down the controller,
 * IRQ, MMIO BAR and all DMA (xhci_uninit) under a child that vetoed
 * detach, then reports success (:380).
 *
 * Same KLD pattern as the proven DF-5544 rig (uhci), adapted for xhci:
 * the device_detach() is issued from a dedicated kernel thread (the
 * sysctl path holds gd_sysctllock across handler dispatch; the usb
 * teardown calls device_sysctl_fini -> sysctl_unregister_oid).
 *
 * Guest must be booted with a real xHCI controller:
 *   QEMU_EXTRA="-device qemu-xhci" dfbsd-qemu/vm.sh up
 *
 * kldload /root/df5545/veto.ko
 *   -> finds xhci0, attaches "veto0" child (detach returns EBUSY while
 *      the parent xhci driver is still bound)
 *
 * sysctl hw.df5545=1  : snapshot live event/command-ring + DCBAA DMA
 *      pages, then device_detach(xhci0) — exactly what
 *      devclass_delete_driver() (subr_bus.c:897, i.e. kldunload / PCI
 *      hot-unplug) does.
 *      EXPECTED (bug): returns 0, "xhci0: detached", IRQ/MMIO released,
 *      xhci_uninit frees ring/DCBAA/scratchpad DMA, while veto0 is
 *      STILL ATTACHED to xhci0.
 * sysctl hw.df5545=2  : touch the parent context through the orphan —
 *      device_get_softc() now returns NULL (device_set_driver(dev,NULL)
 *      kfree'd the softc) -> Fatal trap 12.
 * sysctl hw.df5545=3  : dereference the STALE ring/DCBAA VAs saved while
 *      the controller was alive — use-after-free read of memory freed by
 *      usb_bus_mem_free_all (may fault; that is the point).
 * sysctl hw.df5545=5  : make the veto child cooperate.
 * sysctl hw.df5545=4  : delete the veto child (cooperates post-orphan).
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/sysctl.h>
#include <sys/malloc.h>
#include <sys/condvar.h>
#include <sys/kthread.h>
#include <sys/proc.h>
#include <sys/unistd.h>

#include <bus/u4b/usb.h>
#include <bus/u4b/usbdi.h>
#include <bus/u4b/usb_core.h>
#include <bus/u4b/usb_busdma.h>
#include <bus/u4b/usb_process.h>
#include <bus/u4b/usb_controller.h>
#include <bus/u4b/usb_bus.h>
#include <bus/u4b/controller/xhci.h>

static device_t xhci_dev;
static device_t veto_child;
static struct thread *df_td;
static volatile int kick;
static volatile int cooperate;
static volatile int dead;

/* ring/DCBAA markers captured while the controller was alive */
static void *ring_va;
static uint32_t ring_mark[4];
static void *dctx_va;
static uint32_t dctx_mark[4];

static int veto_probe(device_t dev);
static int veto_attach(device_t dev);
static int veto_detach(device_t dev);

static device_method_t veto_methods[] = {
	DEVMETHOD(device_probe, veto_probe),
	DEVMETHOD(device_attach, veto_attach),
	DEVMETHOD(device_detach, veto_detach),
	DEVMETHOD_END
};

static driver_t veto_driver = { "veto", veto_methods, 0 };
static devclass_t veto_devclass;

DRIVER_MODULE(veto, xhci, veto_driver, veto_devclass, NULL, NULL);

static int
veto_probe(device_t dev)
{
	device_set_desc(dev, "DF-5545 detach-veto child");
	return (0);
}

static int
veto_attach(device_t dev)
{
	device_printf(dev, "attached to %s; my detach will EBUSY while the "
	    "xhci driver is bound to the parent\n",
	    xhci_dev ? device_get_nameunit(xhci_dev) : "?");
	return (0);
}

static int
veto_detach(device_t dev)
{
	if (xhci_dev && device_get_driver(xhci_dev) != NULL && !cooperate) {
		device_printf(dev, "DETACH VETO: returning EBUSY -- watch "
		    "xhci_pci_detach swallow this and tear down anyway\n");
		return (EBUSY);
	}
	device_printf(dev, "post-teardown detach (parent orphaned): "
	    "cooperating\n");
	return (0);
}

static void
df5545_snapshot(void)
{
	struct xhci_softc *sc = device_get_softc(xhci_dev);

	ring_va = sc->sc_hw.root_pc.buffer;	/* event+command ring page */
	dctx_va = sc->sc_hw.ctx_pc.buffer;	/* device ctx addr array page */
	memcpy(ring_mark, ring_va, sizeof(ring_mark));
	memcpy(dctx_mark, dctx_va, sizeof(dctx_mark));
	kprintf("DF5545: xhci0 alive: ring VA=%p [%08x %08x %08x %08x] "
	    "DCBAA VA=%p [%08x %08x %08x %08x]\n", ring_va,
	    ring_mark[0], ring_mark[1], ring_mark[2], ring_mark[3],
	    dctx_va, dctx_mark[0], dctx_mark[1], dctx_mark[2], dctx_mark[3]);
}

static void
df5545_cmd1(void)
{
	int err;

	if (!xhci_dev) {
		kprintf("DF5545: no xhci device\n");
		return;
	}
	if (veto_child && !device_is_attached(veto_child)) {
		kprintf("DF5545: veto child not attached yet, retrying\n");
		device_probe_and_attach(veto_child);
	}
	df5545_snapshot();
	kprintf("DF5545: calling device_detach(%s) [== what "
	    "devclass_delete_driver does on kldunload / PCI hot-unplug]\n",
	    device_get_nameunit(xhci_dev));
	err = device_detach(xhci_dev);
	kprintf("DF5545: device_detach(xhci0) returned %d -- %s\n", err,
	    err ? "honest failure" :
	    "SUCCESS reported DESPITE live veto child (the lie)");
	kprintf("DF5545: post: xhci0 driver=%s state=%d\n",
	    device_get_driver(xhci_dev) ?
	    device_get_driver(xhci_dev)->name : "(none)",
	    (int)device_get_state(xhci_dev));
	{
		device_t *list;
		int count, i;

		if (device_get_children(xhci_dev, &list, &count) == 0) {
			kprintf("DF5545: xhci0 STILL has %d child device(s):",
			    count);
			for (i = 0; i < count; i++)
				kprintf(" %s(attached=%d)",
				    device_get_nameunit(list[i]),
				    device_is_attached(list[i]));
			kfree(list, M_TEMP);
			kprintf("\n");
		}
	}
}

static void
df5545_cmd2(void)
{
	/*
	 * After the swallowed veto, device_set_driver(xhci0, NULL) kfree'd
	 * the softc: device_get_softc() returns NULL and this dereference
	 * is the guaranteed Fatal trap 12.
	 */
	struct xhci_softc *sc = device_get_softc(xhci_dev);

	kprintf("DF5545: post-teardown softc=%p: ring buffer=%p (was %p), "
	    "DCBAA buffer=%p (was %p)\n", sc,
	    sc->sc_hw.root_pc.buffer, ring_va,
	    sc->sc_hw.ctx_pc.buffer, dctx_va);
}

static void
df5545_cmd3(void)
{
	kprintf("DF5545: UAF read of stale ring VA %p: "
	    "%08x %08x %08x %08x (marker %08x %08x %08x %08x)\n",
	    ring_va, ((uint32_t *)ring_va)[0], ((uint32_t *)ring_va)[1],
	    ((uint32_t *)ring_va)[2], ((uint32_t *)ring_va)[3],
	    ring_mark[0], ring_mark[1], ring_mark[2], ring_mark[3]);
	kprintf("DF5545: UAF read of stale DCBAA VA %p: "
	    "%08x %08x %08x %08x (marker %08x %08x %08x %08x)\n", dctx_va,
	    ((uint32_t *)dctx_va)[0], ((uint32_t *)dctx_va)[1],
	    ((uint32_t *)dctx_va)[2], ((uint32_t *)dctx_va)[3],
	    dctx_mark[0], dctx_mark[1], dctx_mark[2], dctx_mark[3]);
}

static void
df5545_thread(void *arg)
{
	int c;

	kprintf("DF5545: worker thread ready (sysctl hw.df5545=N)\n");
	for (;;) {
		tsleep(&kick, 0, "df5545", 0);
		c = kick;
		kick = 0;
		switch (c) {
		case 1:
			df5545_cmd1();
			break;
		case 2:
			df5545_cmd2();
			break;
		case 3:
			df5545_cmd3();
			break;
		case 5:
			cooperate = 1;
			kprintf("DF5545: veto child will cooperate on next "
			    "detach\n");
			break;
		case 4:
			if (veto_child) {
				int err = device_delete_child(xhci_dev,
				    veto_child);
				kprintf("DF5545: veto child delete: %d\n", err);
				if (err == 0)
					veto_child = NULL;
			}
			break;
		default:
			break;
		}
		if (c == 9)
			break;
	}
	dead = 1;
	wakeup(&dead);
	kthread_exit();
}

static int
df5545_sysctl(SYSCTL_HANDLER_ARGS)
{
	int error, cmd = 0;

	error = sysctl_handle_int(oidp, &cmd, 0, req);
	if (error || req->newptr == NULL)
		return (error);
	kick = cmd;
	wakeup(&kick);
	return (0);
}
SYSCTL_PROC(_hw, OID_AUTO, df5545, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, df5545_sysctl, "I", "DF-5545 harness control (1=detach 2=state 3=UAF 4=cleanup)");

static int
veto_modevent(module_t mod, int type, void *data)
{
	devclass_t dc;

	switch (type) {
	case MOD_LOAD:
		dc = devclass_find("xhci");
		if (dc == NULL) {
			kprintf("DF5545: no xhci devclass\n");
			return (ENXIO);
		}
		xhci_dev = devclass_get_device(dc, 0);
		if (xhci_dev == NULL) {
			kprintf("DF5545: no xhci0 device (boot with "
			    "-device qemu-xhci)\n");
			return (ENXIO);
		}
		kprintf("DF5545: found %s (driver %s)\n",
		    device_get_nameunit(xhci_dev),
		    device_get_driver(xhci_dev) ?
		    device_get_driver(xhci_dev)->name : "(none)");
		veto_child = device_add_child(xhci_dev, "veto", -1);
		if (veto_child == NULL) {
			kprintf("DF5545: device_add_child failed\n");
			return (ENXIO);
		}
		/*
		 * The attach may complete asynchronously via the
		 * driver-added event; tolerate a nonzero return here.
		 */
		if (device_probe_and_attach(veto_child) != 0)
			kprintf("DF5545: probe/attach deferred (driver-added "
			    "event will attach)\n");
		dead = 0;
		kthread_create(df5545_thread, NULL, &df_td, "df5545_td");
		kprintf("DF5545: harness loaded\n");
		return (0);
	case MOD_UNLOAD:
		kick = 9;
		wakeup(&kick);
		while (!dead)
			tsleep(&dead, 0, "df5545x", hz / 4);
		kprintf("DF5545: module unload\n");
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}
static moduledata_t veto_mod = { "veto", veto_modevent, 0 };
DECLARE_MODULE(veto, veto_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_VERSION(veto, 1);
