/*
 * DF-5544 harness v2 — uhci_pci_detach() swallows device_delete_children()
 * failure (uhci_pci.c:374) and unconditionally tears down IRQ/IO/DMA
 * under a child that vetoed detach.
 *
 * v2: the device_detach() is issued from a dedicated kernel thread, NOT
 * from the sysctl handler (v1 self-deadlocked: the sysctl path holds the
 * per-CPU gd_sysctllock across handler dispatch, and the usb teardown
 * calls device_sysctl_fini -> sysctl_unregister_oid which needs that
 * lock).  The sysctl only sets a command word and wakes the thread.
 *
 * kldload ./veto.ko
 *   -> finds uhci0, attaches "veto0" child (detach returns EBUSY while
 *      the parent uhci driver is still bound)
 *
 * sysctl hw.df5544=1  : snapshot live frame-list DMA page, then
 *      device_detach(uhci0) — exactly what devclass_delete_driver()
 *      (subr_bus.c:897, i.e. kldunload / PCI hot-unplug) does.
 *      EXPECTED (bug): returns 0, "uhci0: detached", all IRQ/IO/DMA
 *      freed, while veto0 is STILL ATTACHED to uhci0.
 * sysctl hw.df5544=2  : dump post-teardown softc state (page caches
 *      freed -> buffer == NULL) — safe, reads live softc memory.
 * sysctl hw.df5544=3  : dereference the STALE frame-list/QH VAs saved
 *      while the controller was alive — use-after-free read of memory
 *      released by bus_dmamem_free (may fault; that is the point).
 * sysctl hw.df5544=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/uhci.h>

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

/* frame-list marker captured while the controller was alive */
static void *fl_va;
static uint32_t fl_mark[4];
static void *qh_va;
static uint32_t qh_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, uhci, veto_driver, veto_devclass, NULL, NULL);

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

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

static int
veto_detach(device_t dev)
{
	if (uhci_dev && device_get_driver(uhci_dev) != NULL && !cooperate) {
		device_printf(dev, "DETACH VETO: returning EBUSY -- watch "
		    "uhci_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
df5544_snapshot(void)
{
	uhci_softc_t *sc = device_get_softc(uhci_dev);

	fl_va = sc->sc_hw.pframes_pc.buffer;
	qh_va = sc->sc_hw.ls_ctl_start_pc.buffer;
	memcpy(fl_mark, fl_va, sizeof(fl_mark));
	memcpy(qh_mark, qh_va, sizeof(qh_mark));
	kprintf("DF5544: uhci0 alive: frame-list VA=%p [%08x %08x %08x %08x] "
	    "QH-pool VA=%p\n", fl_va, fl_mark[0], fl_mark[1], fl_mark[2],
	    fl_mark[3], qh_va);
}

static void
df5544_cmd1(void)
{
	int err;

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

		if (device_get_children(uhci_dev, &list, &count) == 0) {
			kprintf("DF5544: uhci0 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
df5544_cmd2(void)
{
	uhci_softc_t *sc = device_get_softc(uhci_dev);

	kprintf("DF5544: post-teardown softc: pframes_pc.buffer=%p (was %p), "
	    "qh buffer=%p (was %p)\n",
	    sc->sc_hw.pframes_pc.buffer, fl_va,
	    sc->sc_hw.ls_ctl_start_pc.buffer, qh_va);
}

static void
df5544_cmd3(void)
{
	kprintf("DF5544: UAF read of stale frame-list VA %p: "
	    "%08x %08x %08x %08x (marker %08x %08x %08x %08x)\n",
	    fl_va, ((uint32_t *)fl_va)[0], ((uint32_t *)fl_va)[1],
	    ((uint32_t *)fl_va)[2], ((uint32_t *)fl_va)[3],
	    fl_mark[0], fl_mark[1], fl_mark[2], fl_mark[3]);
	kprintf("DF5544: UAF read of stale QH-pool VA %p: %08x %08x %08x %08x "
	    "(marker %08x %08x %08x %08x)\n", qh_va,
	    ((uint32_t *)qh_va)[0], ((uint32_t *)qh_va)[1],
	    ((uint32_t *)qh_va)[2], ((uint32_t *)qh_va)[3],
	    qh_mark[0], qh_mark[1], qh_mark[2], qh_mark[3]);
}

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

	kprintf("DF5544: worker thread ready (sysctl hw.df5544=N)\n");
	for (;;) {
		tsleep(&kick, 0, "df5544", 0);
		c = kick;
		kick = 0;
		switch (c) {
		case 1:
			df5544_cmd1();
			break;
		case 2:
			df5544_cmd2();
			break;
		case 3:
			df5544_cmd3();
			break;
		case 5:
			cooperate = 1;
			kprintf("DF5544: veto child will cooperate on next "
			    "detach\n");
			break;
		case 4:
			if (veto_child) {
				int err = device_delete_child(uhci_dev,
				    veto_child);
				kprintf("DF5544: 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
df5544_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, df5544, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, df5544_sysctl, "I", "DF-5544 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("uhci");
		if (dc == NULL) {
			kprintf("DF5544: no uhci devclass\n");
			return (ENXIO);
		}
		uhci_dev = devclass_get_device(dc, 0);
		if (uhci_dev == NULL) {
			kprintf("DF5544: no uhci0 device\n");
			return (ENXIO);
		}
		kprintf("DF5544: found %s (driver %s)\n",
		    device_get_nameunit(uhci_dev),
		    device_get_driver(uhci_dev) ?
		    device_get_driver(uhci_dev)->name : "(none)");
		veto_child = device_add_child(uhci_dev, "veto", -1);
		if (veto_child == NULL) {
			kprintf("DF5544: 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("DF5544: probe/attach deferred (driver-added "
			    "event will attach)\n");
		dead = 0;
		kthread_create(df5544_thread, NULL, &df_td, "df5544_td");
		kprintf("DF5544: harness loaded\n");
		return (0);
	case MOD_UNLOAD:
		kick = 9;
		wakeup(&kick);
		while (!dead)
			tsleep(&dead, 0, "df5544x", hz / 4);
		kprintf("DF5544: 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);
