/*
 * gpo_race2.c -- DF-5518 deterministic race trigger.
 *
 * The lottery variant (gpo_detach.c) races the ~us window between
 * AcpiRemoveAddressSpaceHandler returning and the (already in-flight)
 * gpio_acpi_space_handler completing its 59-pin read loop.  This variant
 * pins the handler in-flight deterministically the way real contention
 * would (a slow provider, a preempted CPU between pins):
 *
 *   1. hold gpio_intel0's sc->lk exclusively from this kmod;
 *   2. the TZ0._TMP hammer's next GeneralPurposeIo field access
 *      dispatches into gpio_acpi_space_handler and blocks inside
 *      gpio_intel_alloc_io_pin's lockmgr(&sc->lk) -- an in-flight,
 *      mid-dispatch space handler;
 *   3. device_detach(gpio_intel0) now runs without any quiesce:
 *        - gpio_acpi0 detaches: AcpiRemoveAddressSpaceHandler frees the
 *          HandlerObj (and the ContextMutex the blocked handler's dispatch
 *          path still uses);
 *        - gpio_intel0 teardown: bus_teardown_intr / release resources /
 *          lockuninit(&sc->lk) -> KKASSERT on the waiter (panic), or
 *        - device_set_driver(NULL) (subr_bus.c:2150) kfrees BOTH softcs
 *          and kobj_delete kfrees the device_t -- all while the blocked
 *          handler still holds dev/sc pointers;
 *   4. this kmod releases the lock: the blocked handler resumes INSIDE
 *      lockmgr on freed memory and dereferences the freed softc.
 */
#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/bus.h>
#include <sys/rman.h>
#include <sys/systm.h>
#include <machine/cpufunc.h>


static int
gpo_race2_modevent(module_t mod, int type, void *arg)
{
	devclass_t dc;
	device_t dev;
	struct gpio_intel_softc_hack {
		device_t dev;
		struct resource *mem_res;
		struct resource *irq_res;
		void *intrhand;
		struct lock lk;
	};
	struct gpio_intel_softc_hack *schk;
	void *p[256];
	int error, i, j, n;
	static const int sizes[] = { 64, 128, 256, 512, 1024, 2048 };

	switch (type) {
	case MOD_LOAD:
		dc = devclass_find("gpio_intel");
		if (dc == NULL) {
			kprintf("gpo_race2: no gpio_intel devclass\n");
			return (ENXIO);
		}
		dev = devclass_get_device(dc, 0);
		if (dev == NULL) {
			kprintf("gpo_race2: no gpio_intel0\n");
			return (ENXIO);
		}
		schk = device_get_softc(dev);

		kprintf("gpo_race2: holding gpio_intel0 sc->lk; next GPIO "
		    "dispatch will block mid-handler\n");
		lockmgr(&schk->lk, LK_EXCLUSIVE);

		/* let the hammer dispatch into the blocked handler */
		tsleep((const void *)&n, 0, "gporace", 200);

		kprintf("gpo_race2: detaching with in-flight handler...\n");
		error = device_detach(dev);
		kprintf("gpo_race2: detach returned %d; freeing lock\n", error);

		lockmgr(&schk->lk, LK_RELEASE);

		/* poison whatever was freed */
		n = 0;
		for (i = 0; i < (int)NELEM(sizes); i++) {
			for (j = 0; j < 32 && n < 256; j++, n++) {
				p[n] = kmalloc(sizes[i], M_DEVBUF, M_WAITOK);
				if (p[n] != NULL)
					memset(p[n], 0xDE, sizes[i]);
			}
		}
		kprintf("gpo_race2: done (%d poison allocs)\n", n);
		break;
	case MOD_UNLOAD:
		break;
	default:
		return (EOPNOTSUPP);
	}
	return (0);
}

static moduledata_t gpo_race2_mod = {
	"gpo_race2", gpo_race2_modevent, NULL
};
DECLARE_MODULE(gpo_race2, gpo_race2_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(gpo_race2, 1);
