/*
 * DF-5562 harness — ohci_pci_detach() swallows device_delete_children()
 * failure (ohci_pci.c:329) and unconditionally tears down IRQ/MMIO/DMA
 * under a child that vetoed detach.  OHCI member of the family proven in
 * DF-5544 (uhci_pci.c:374) / DF-5545 (xhci_pci.c:355) / DF-5528
 * (fwohci_pci.c) / DF-4558 (atapi-cam).
 *
 * The device_detach() is issued from a dedicated kernel thread, NOT from
 * the sysctl handler (per the DF-5544 v2 lesson: 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 ohci0, attaches "veto0" child (detach returns EBUSY while
 *      the parent ohci driver is still bound)
 *
 * sysctl hw.df5562=1  : snapshot live HCCA/ctrl-ED DMA pages, then
 *      device_detach(ohci0) — exactly what devclass_delete_driver()
 *      (subr_bus.c:897, i.e. kldunload / PCI hot-unplug) does.
 *      EXPECTED (bug): returns 0, "ohci0: detached", all IRQ/MMIO/DMA
 *      freed, while veto0 is STILL ATTACHED to ohci0.
 * sysctl hw.df5562=2  : dump post-teardown softc state (page caches
 *      freed -> buffer == NULL) — safe read of live softc memory that
 *      demonstrates the softc is gone (NULL after device_set_driver
 *      kfree) — deterministic Fatal trap 12.
 * sysctl hw.df5562=3  : dereference the STALE HCCA/ED VAs saved while
 *      the controller was alive — use-after-free read of memory
 *      released by usb_bus_mem_free_all (may fault; that is the point).
 * sysctl hw.df5562=4  : delete the veto child (cooperates post-orphan).
 * sysctl hw.df5562=5  : make the veto child cooperate on next detach.
 */
#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/ohci.h>

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

/* HCCA / control-ED markers captured while the controller was alive */
static void *hcca_va;
static uint32_t hcca_mark[4];
static void *ed_va;
static uint32_t ed_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, ohci, veto_driver, veto_devclass, NULL, NULL);

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

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

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

	hcca_va = sc->sc_hw.hcca_pc.buffer;
	ed_va = sc->sc_hw.ctrl_start_pc.buffer;
	memcpy(hcca_mark, hcca_va, sizeof(hcca_mark));
	memcpy(ed_mark, ed_va, sizeof(ed_mark));
	kprintf("DF5562: ohci0 alive: HCCA VA=%p [%08x %08x %08x %08x] "
	    "ctrl-ED VA=%p\n", hcca_va, hcca_mark[0], hcca_mark[1],
	    hcca_mark[2], hcca_mark[3], ed_va);
}

static void
df5562_cmd1(void)
{
	int err;

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

		if (device_get_children(ohci_dev, &list, &count) == 0) {
			kprintf("DF5562: ohci0 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
df5562_cmd2(void)
{
	ohci_softc_t *sc = device_get_softc(ohci_dev);

	/*
	 * After the "successful" detach, device_set_driver(dev, NULL)
	 * kfree'd and NULLed dev->softc: sc == NULL, and touching
	 * sc->sc_hw.hcca_pc.buffer is a NULL+offset deref -> Fatal trap 12.
	 * That is the point: the orphaned attached child's ONLY path to its
	 * parent context is freed memory.
	 */
	kprintf("DF5562: post-teardown softc: hcca_pc.buffer=%p (was %p), "
	    "ctrl-ED buffer=%p (was %p)\n",
	    sc->sc_hw.hcca_pc.buffer, hcca_va,
	    sc->sc_hw.ctrl_start_pc.buffer, ed_va);
}

static void
df5562_cmd3(void)
{
	kprintf("DF5562: UAF read of stale HCCA VA %p: "
	    "%08x %08x %08x %08x (marker %08x %08x %08x %08x)\n",
	    hcca_va, ((uint32_t *)hcca_va)[0], ((uint32_t *)hcca_va)[1],
	    ((uint32_t *)hcca_va)[2], ((uint32_t *)hcca_va)[3],
	    hcca_mark[0], hcca_mark[1], hcca_mark[2], hcca_mark[3]);
	kprintf("DF5562: UAF read of stale ctrl-ED VA %p: "
	    "%08x %08x %08x %08x (marker %08x %08x %08x %08x)\n", ed_va,
	    ((uint32_t *)ed_va)[0], ((uint32_t *)ed_va)[1],
	    ((uint32_t *)ed_va)[2], ((uint32_t *)ed_va)[3],
	    ed_mark[0], ed_mark[1], ed_mark[2], ed_mark[3]);
}

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

	kprintf("DF5562: worker thread ready (sysctl hw.df5562=N)\n");
	for (;;) {
		tsleep(&kick, 0, "df5562", 0);
		c = kick;
		kick = 0;
		switch (c) {
		case 1:
			df5562_cmd1();
			break;
		case 2:
			df5562_cmd2();
			break;
		case 3:
			df5562_cmd3();
			break;
		case 5:
			cooperate = 1;
			kprintf("DF5562: veto child will cooperate on next "
			    "detach\n");
			break;
		case 4:
			if (veto_child) {
				int err = device_delete_child(ohci_dev,
				    veto_child);
				kprintf("DF5562: 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
df5562_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, df5562, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, df5562_sysctl, "I",
    "DF-5562 harness control (1=detach 2=state 3=UAF 4=cleanup 5=cooperate)");

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

	switch (type) {
	case MOD_LOAD:
		dc = devclass_find("ohci");
		if (dc == NULL) {
			kprintf("DF5562: no ohci devclass\n");
			return (ENXIO);
		}
		ohci_dev = devclass_get_device(dc, 0);
		if (ohci_dev == NULL) {
			kprintf("DF5562: no ohci0 device\n");
			return (ENXIO);
		}
		kprintf("DF5562: found %s (driver %s)\n",
		    device_get_nameunit(ohci_dev),
		    device_get_driver(ohci_dev) ?
		    device_get_driver(ohci_dev)->name : "(none)");
		veto_child = device_add_child(ohci_dev, "veto", -1);
		if (veto_child == NULL) {
			kprintf("DF5562: 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("DF5562: probe/attach deferred (driver-added "
			    "event will attach)\n");
		dead = 0;
		kthread_create(df5562_thread, NULL, &df_td, "df5562_td");
		kprintf("DF5562: harness loaded\n");
		return (0);
	case MOD_UNLOAD:
		kick = 9;
		wakeup(&kick);
		while (!dead)
			tsleep(&dead, 0, "df5562x", hz / 4);
		kprintf("DF5562: 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);
