/*
 * DF-4797 PoC — trigger side: detach the live nata ad0 device while the
 * raw-I/O storm has the cdev open and is inside / re-enters ad_strategy().
 *
 *	sysctl kern.df4797_cycles=4
 *	sysctl kern.df4797_ms=200
 *	sysctl kern.df4797_run=1
 *
 * Each cycle: device_detach(ad0) — the exact ad_detach() path
 * (ata-disk.c:167-203) that ends in kfree(adp) — then re-attach via
 * device_probe_and_attach() so cycles can repeat.
 *
 * Expected baseline (bug present): kernel panic on the next post-detach
 * raw pread — ad_strategy() fetches adp = device_get_ivars(dev) == NULL
 * and does devstat_start_transaction(&adp->stats) (ata-disk.c:360), a
 * write to near-NULL; or ad_done() does devstat_end_transaction_buf on
 * &((struct ad_softc *)NULL)->stats (ata-disk.c:376).  Fatal trap 12.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/kthread.h>
#include <sys/thread2.h>
#include <sys/bus.h>

static int	df4797_run;
static int	df4797_cycles = 4;
static int	df4797_ms = 200;
static int	df4797_last = -999;
static int	df4797_active;

static void
df4797_worker(void *arg __unused)
{
	devclass_t dc;
	device_t dev;
	int i;

	dc = devclass_find("ad");
	if (dc == NULL) {
		kprintf("df4797: ad devclass not found (no ad disk?)\n");
		goto out;
	}
	dev = devclass_get_device(dc, 0);
	if (dev == NULL) {
		kprintf("df4797: ad0 device_t is NULL\n");
		goto out;
	}
	for (i = 0; i < df4797_cycles; i++) {
		kprintf("df4797: cycle %d/%d detaching ad0 (%p)...\n",
		    i + 1, df4797_cycles, dev);
		df4797_last = device_detach(dev);
		kprintf("df4797: device_detach -> %d\n", df4797_last);
		if (df4797_last != 0)
			break;
		tsleep(&df4797_worker, 0, "df4797a", (df4797_ms * hz) / 1000);
		kprintf("df4797: re-attaching ad0...\n");
		if (device_probe_and_attach(dev)) {
			kprintf("df4797: re-attach failed\n");
			break;
		}
	}
out:
	df4797_run = 0;
	wakeup(&df4797_run);
	kthread_exit();
}

static int
df4797_sysctl_run(SYSCTL_HANDLER_ARGS)
{
	int error, v = df4797_run;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (v != 0 && df4797_run == 0 && df4797_active == 0) {
		df4797_run = 1;
		df4797_active = 1;
		kthread_create(df4797_worker, NULL, NULL, "df4797w");
	}
	return (0);
}

static int
df4797_load(struct module *m, int what, void *arg __unused)
{
	switch (what) {
	case MOD_LOAD:
		kprintf("df4797 detach-trigger loaded "
		    "(kern.df4797_run=1 to go)\n");
		break;
	case MOD_UNLOAD:
		kprintf("df4797 detach-trigger unloaded\n");
		break;
	}
	return (0);
}

static moduledata_t df4797_mod = { "df4797", df4797_load, NULL };
DECLARE_MODULE(df4797, df4797_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);

SYSCTL_NODE(_kern, OID_AUTO, df4797, CTLFLAG_RW, 0, "DF-4797 PoC");
SYSCTL_PROC(_kern_df4797, OID_AUTO, run, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, df4797_sysctl_run, "I", "start one detach/attach run");
SYSCTL_INT(_kern_df4797, OID_AUTO, cycles, CTLFLAG_RW,
    &df4797_cycles, 0, "detach cycles");
SYSCTL_INT(_kern_df4797, OID_AUTO, ms, CTLFLAG_RW,
    &df4797_ms, 0, "ms between detach and re-attach");
SYSCTL_INT(_kern_df4797, OID_AUTO, last, CTLFLAG_RD,
    &df4797_last, 0, "last device_detach() result");
