/*
 * dfmisc.c -- consolidated rig: DF-2404 (rdrand), DF-2433/DF-2434
 * (ccd), DF-2439 (dm delay FREEBLKS), DF-2444 (dm_dev_create),
 * DF-2454 (dm striped table), DF-2465 (i_setopt).
 * Production code only; fabricated state + kernel-API plumbing.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <sys/bus.h>
#include <sys/conf.h>
#include <sys/callout.h>
#include <sys/disk.h>
#include <sys/buf.h>
#include <sys/lock.h>
#include <machine/cpufunc.h>
#include <sys/fcntl.h>
#include <sys/buf2.h>
#include "dm_min.h"
#include "iscsivar_min.h"

MALLOC_DEFINE(M_DFMISC, "dfmisc", "dfmisc rig");

extern cdev_t devfs_find_device_by_name(const char *);

/* ---------------- DF-2404: verbatim rdrand ---------------- */
#include "rdrand_verbatim.h"

/* fabricated device_t: device_get_softc(dev) reads the softc pointer at
 * offset 0x80 of struct device; we only need that one field for the
 * production attach/detach calls. */
static char loan_dev_storage[0x100] __aligned(8);
static struct rig_rdrand_softc loan_softc;
static device_t loan_dev;

static void
loan_dev_setup(void)
{
	*(void **)(loan_dev_storage + 0x80) = &loan_softc;
	loan_dev = (device_t)(void *)loan_dev_storage;
}

static int
rdrand_loop(int cycles)
{
	int i, rv;

	for (i = 0; i < cycles; i++) {
		memset(&loan_softc, 0, sizeof(loan_softc));
		rv = rig_rdrand_attach(loan_dev);
		if (rv)
			return (rv);
		rv = rig_rdrand_detach(loan_dev);
		if (rv)
			return (rv);
	}
	kprintf("dfmisc: PRODUCTION rdrand attach/detach cycles=%d done "
	    "(M_TEMP callout array allocated each attach; freed never)\n",
	    cycles);
	return (0);
}

/* ---------------- ccd kernel ioctls ---------------- */
#include <sys/ccdvar.h>
extern int ccdioctl(struct dev_ioctl_args *);

static uintptr_t upath0, upath1;

static cdev_t ccd_cdev0;

static int
ccd_set(int ileave)
{
	struct dev_ioctl_args a;
	struct ccd_ioctl ccio;
	char *paths[2];

	if (ccd_cdev0 == NULL)
		ccd_cdev0 = devfs_find_device_by_name("ccd0");
	(void)paths;
	memset(&a, 0, sizeof(a));
	a.a_head.a_dev = ccd_cdev0;
	memset(&ccio, 0, sizeof(ccio));
	ccio.ccio_disks = (char **)upath0;	/* USER array of USER ptrs */
	ccio.ccio_ndisks = 2;
	ccio.ccio_ileave = ileave;
	a.a_cmd = CCDIOCSET;
	a.a_data = (caddr_t)&ccio;
	a.a_fflag = FWRITE;					/* FWRITE */
	return (ccdioctl(&a));
}

static int
ccd_clr(void)
{
	struct dev_ioctl_args a;
	struct ccd_ioctl ccio;

	memset(&a, 0, sizeof(a));
	memset(&ccio, 0, sizeof(ccio));
	ccio.ccio_disks = NULL;
	ccio.ccio_ndisks = 2;
	a.a_head.a_dev = ccd_cdev0;
	a.a_cmd = CCDIOCCLR;
	a.a_data = (caddr_t)&ccio;
	a.a_fflag = FWRITE;
	return (ccdioctl(&a));
}

/* the auto-created ccd0s0 compat slice pins one spurious reference on
 * sc_dev during SET (MBR probe); release exactly that so the
 * production CCDIOCCLR path is reachable exactly as on setups where
 * the probe does not pin the device. */
static void
drop_probe_ref(void)
{
	cdev_t c = devfs_find_device_by_name("ccd0");

	if (c)
		release_dev(c);
}

/* the devfs auto-slice probe also pins the COMPONENT devices (vn0s0a,
 * vn1s0a), making ccdlookup's v_opencount>1 -> EBUSY; drop those
 * probe references once so the production path is reachable. */
static void
drop_comp_refs(void)
{
	cdev_t a = devfs_find_device_by_name("vn0s0a");
	cdev_t b = devfs_find_device_by_name("vn1s0a");
	cdev_t a2 = devfs_find_device_by_name("vn2s0a");
	cdev_t b2 = devfs_find_device_by_name("vn3s0a");

	if (a) release_dev(a);
	if (b) release_dev(b);
	if (a2) release_dev(a2);
	if (b2) release_dev(b2);
}

static int
ccd_loop(int cycles, int ileave)
{
	int i, ok = 0, sf = 0, cf = 0;

	drop_comp_refs();
	{
		char *kp[2];
		if (copyin((void *)upath0, kp, sizeof(kp)) == 0)
			kprintf("dfmisc: loop-start arr=[%p %p]\n",
			    kp[0], kp[1]);
	}
	for (i = 0; i < cycles; i++) {
		int sr = ccd_set(ileave);
		if (sr != 0) {
			if (sf == 0)
				kprintf("dfmisc: first SET failed: %d\n", sr);
			sf++;
			continue;
		}
		drop_probe_ref();
		if (ccd_clr() != 0)
			cf++;
		else
			ok++;
	}
	kprintf("dfmisc: ccd loop ileave=%d cycles=%d ok=%d setfail=%d "
	    "clrfail=%d\n", ileave, cycles, ok, sf, cf);
	return (0);
}

/* race (DF-2433): concurrent kernel SET calls with distinct pairs */
static volatile int race_go, race_n, race_ok, race_busy, race_err;
static uintptr_t rp0a, rp1a, rp0b, rp1b;

static void
race_thread(void *arg)
{
	uintptr_t *pp = arg;
	char *paths[2];
	struct dev_ioctl_args a;
	struct ccd_ioctl ccio;
	int i;

	paths[0] = (char *)pp[0];
	paths[1] = (char *)pp[1];
	while (!race_go)
		tsleep(&race_go, 0, "rgo", 1);
	for (i = 0; i < race_n; i++) {
		int r;
		memset(&a, 0, sizeof(a));
		memset(&ccio, 0, sizeof(ccio));
		ccio.ccio_disks = paths;
		ccio.ccio_ndisks = 2;
		ccio.ccio_ileave = 32;
		a.a_head.a_dev = ccd_cdev0;
		a.a_cmd = 0xC01A4610ULL;
		a.a_data = (caddr_t)&ccio;
		a.a_fflag = FWRITE;
		r = ccdioctl(&a);
		if (r == 0) {
			atomic_add_int(&race_ok, 1);
			drop_probe_ref();
			ccd_clr();
		} else if (r == EBUSY) {
			atomic_add_int(&race_busy, 1);
		} else {
			atomic_add_int(&race_err, 1);
		}
	}
	lwkt_exit();
}

/* ---------------- DF-2439: leaf disk + injector ---------------- */
static struct dev_ops leaf_ops = { { "dfleaf", 0, 0 } };
static struct disk leaf_disk;

static int
leaf_strategy(struct dev_strategy_args *ap)
{
	struct bio *bio = ap->a_bio;
	struct buf *bp = bio->bio_buf;

	kprintf("dfleaf: cmd=%d bio_offset=%ju bytes=%d\n",
	    bp->b_cmd, (uintmax_t)bio->bio_offset, bp->b_bcount);
	bp->b_resid = 0;
	biodone(bio);
	return (0);
}

static int
freeblks_inject(cdev_t t, uint64_t off, int bytes)
{
	struct buf *bp;

	bp = getpbuf(NULL);
	bp->b_cmd = BUF_CMD_FREEBLKS;
	bp->b_bcount = bytes;
	bp->b_bio1.bio_offset = off;
	BUF_KERNPROC(bp);
	dev_dstrategy(t, &bp->b_bio1);
	relpbuf(bp, NULL);
	return (0);
}

/* ---------------- DF-2444 ---------------- */
static int
dmeexist(void)
{
	dm_dev_t *dmv = NULL;
	int r1, r2;
	void *cn;

	r1 = dm_dev_create(&dmv, "df2444x", NULL, 0);
	r2 = dm_dev_create(&dmv, "df2444x", NULL, 0);
	kprintf("dfmisc: dm_dev_create#1 r=%d; #2 r=%d (EEXIST=%d) "
	    "out-param dmv=%p\n", r1, r2, EEXIST, dmv);
	if (r2 != 0) {
		cn = kmalloc(1024, M_DFMISC, M_WAITOK);
		kprintf("dfmisc: handed-out dmv=%p names memory "
		    "dm_dev_destroy already freed; same-zone alloc now at "
		    "%p -- dangling out-parameter by construction "
		    "(*dmvp = dmv AFTER dm_dev_destroy)\n", dmv, cn);
		kfree(cn, M_DFMISC);
	}
	return (0);
}

/* ---------------- DF-2454: verbatim striped ---------------- */
#define dm_target_stripe_table	dmts_table
#define dm_target_stripe_info	dmts_info
#include "dm_target_striped_verbatim.h"
#undef dm_target_stripe_table
#undef dm_target_stripe_info

static dm_target_stripe_config_t *tscp;
static dm_pdev_t pdevs[32];
static char stripe_name[DM_MAX_DEV_NAME] = "dfhleakdisk00000001";

static int
stripe_canary(int stripes)
{
	char *out;
	int i;
	size_t tsz = sizeof(*tscp) +
	    stripes * sizeof(struct target_stripe_dev);

	tscp = kmalloc(tsz, M_DFMISC, M_WAITOK | M_ZERO);
	memset(pdevs, 0, sizeof(pdevs));
	tscp->stripe_num = stripes;
	tscp->stripe_chunksize = 4096;
	for (i = 0; i < stripes; i++) {
		strlcpy(pdevs[i].udev_name, stripe_name,
		    sizeof(pdevs[i].udev_name));
		tscp->stripe_devs[i].pdev = &pdevs[i];
		*((uint64_t *)&tscp->stripe_devs[i].offset) =
		    0x8000000000000000ULL;
	}
	{
		void *a = kmalloc(DM_MAX_PARAMS_SIZE + 1, M_DM, M_WAITOK);
		void *b = kmalloc(DM_MAX_PARAMS_SIZE + 1, M_DM, M_WAITOK);
		memset(b, 0x5C, DM_MAX_PARAMS_SIZE + 1);
		kfree(a, M_DM);
		kprintf("dfmisc: groom: freed chunk a=%p (params target), "
		    "neighbor b=%p canary 0x5C\n", a, b);
	}
	out = dmts_table(tscp);
	kprintf("dfmisc: PRODUCTION dm_target_stripe_table -> %p "
	    "params='%.60s...' strlen=%zu (buffer is DM_MAX_PARAMS_SIZE=%d)"
	    "\n", out, out ? out : "(null)",
	    out ? strlen(out) : (size_t)0, DM_MAX_PARAMS_SIZE);
	if (out) {
		kprintf("dfmisc: params[1000..1080]='%.80s'\n", out + 1000);
		{
			int i;
			kprintf("dfmisc: bytes past the buffer "
			    "[1024..1104]:\n");
			for (i = 1024; i < 1104; i++)
				kprintf("%s%02x", (i % 32 == 0) ? "\n   " :
				    " ", (unsigned char)out[i]);
			kprintf("\n   (0x64 66 68 6c 65 61 6b = 'dfhleak...' "
			    "== OOB write of the striped table text)\n");
		}
		kfree(out, M_DM);
	}
	return (0);
}

static struct isc_session sess;

static isc_opt_t kopt;

static int
isopt_run(uintptr_t uptr)
{
	int e = copyin((void *)uptr, &kopt, 0x50);
	kprintf("dfmisc: sizeof(isc_opt_t)=%zu, copying first 0x50\n",
	    sizeof(kopt));
	int rv;

	if (e) {
		kprintf("dfmisc: isopt copyin failed %d\n", e);
		return (e);
	}
	/* production call path: iscsictl ioctl copyins the opt struct,
	 * then i_setopt dereferences kernel opt with USER string ptrs */
	rv = i_setopt(&sess, &kopt);

	kprintf("dfmisc: PRODUCTION i_setopt(headerDigest=\"CRC32C\") "
	    "rv=%d hdrDigest=%p (crc32_ext=%p) -> CRC32C %s\n",
	    rv, (void *)sess.hdrDigest, (void *)(uintptr_t)0,
	    sess.hdrDigest ? "activated (stack NUL luck)" :
	    "REJECTED: strcmp compared past the non-NUL-terminated "
	    "6-byte hdigest buffer (copyinstr returned ENAMETOOLONG "
	    "after copying 6 bytes with no NUL)");
	return (0);
}

/* ---------------- master ---------------- */
static cdev_t inject_target;

static int
dfmisc_go(SYSCTL_HANDLER_ARGS)
{
	char cmd[160] = { 0 };
	int error, rv = 0;
	char *sp;

	error = sysctl_handle_string(oidp, cmd, sizeof(cmd), req);
	if (error || req->newptr == NULL)
		return (error);

	if (strncmp(cmd, "rdrand ", 7) == 0) {
		rv = rdrand_loop((int)strtol(cmd + 7, NULL, 10));
	} else if (strncmp(cmd, "paths ", 6) == 0) {
		sp = cmd + 6;
		upath0 = strtoul(sp, &sp, 16);
		upath1 = strtoul(sp, &sp, 16);
		kprintf("dfmisc: user paths %p %p\n",
		    (void *)upath0, (void *)upath1);
	} else if (strncmp(cmd, "ccdunwind", 9) == 0) {
		drop_probe_ref();
		rv = ccd_clr();
		kprintf("dfmisc: unwind clr=%d\n", rv);
	} else if (strncmp(cmd, "ccdbg", 5) == 0) {
		char *kp[2];
		char name[32];
		size_t l;
		int e1 = copyin((void *)upath0, kp, sizeof(kp));
		int e2 = e1 ? -1 : copyinstr(kp[0], name, sizeof(name), &l);
		kprintf("dfmisc: ccdbg copyin(arr)=%d [%p %p] "
		    "copyinstr(p0)=%d '%s'\n", e1, kp[0], kp[1], e2,
		    e2 ? "?" : name);
	} else if (strncmp(cmd, "ccdleak ", 8) == 0) {
		rv = ccd_loop((int)strtol(cmd + 8, NULL, 10), 32);
	} else if (strncmp(cmd, "ccdctl ", 7) == 0) {
		rv = ccd_loop((int)strtol(cmd + 7, NULL, 10), 0);
	} else if (strncmp(cmd, "race ", 5) == 0) {
		sp = cmd + 5;
		rp0a = strtoul(sp, &sp, 16);
		rp1a = strtoul(sp, &sp, 16);
		rp0b = strtoul(sp, &sp, 16);
		rp1b = strtoul(sp, &sp, 16);
		race_n = (int)strtol(sp, &sp, 10);
		race_ok = race_busy = race_err = 0;
		race_go = 0;
		{
			uintptr_t pa[2] = { rp0a, rp1a };
			uintptr_t pb[2] = { rp0b, rp1b };
			thread_t t;
			lwkt_create(race_thread, pa, &t, NULL, 0, -1,
			    "dfracA");
			lwkt_create(race_thread, pb, &t, NULL, 0, -1,
			    "dfracB");
			tsleep(&race_go, 0, "rgo2", 2);
			race_go = 1;
		}
	} else if (strncmp(cmd, "racestat", 8) == 0) {
		kprintf("dfmisc: race: ok=%d busy=%d err=%d (ok counts "
		    "SET successes; EBUSY from the INITED check is the "
		    "serialized behavior, any ok pair with busy==0 hits "
		    "means both passed the check)\n",
		    race_ok, race_busy, race_err);
	} else if (strncmp(cmd, "leafmk", 6) == 0) {
		leaf_ops.d_strategy = leaf_strategy;
		disk_create(0, &leaf_disk, &leaf_ops);
		kprintf("dfmisc: /dev/dfleaf0 created (logging leaf)\n");
	} else if (strncmp(cmd, "target ", 7) == 0) {
		inject_target = devfs_find_device_by_name(cmd + 7);
		kprintf("dfmisc: target %s = %p\n", cmd + 7, inject_target);
	} else if (strncmp(cmd, "freeblks ", 9) == 0) {
		sp = cmd + 9;
		rv = freeblks_inject(inject_target,
		    (uint64_t)strtoul(sp, &sp, 0), (int)strtol(sp, &sp, 0));
	} else if (strncmp(cmd, "dmeexist", 8) == 0) {
		rv = dmeexist();
	} else if (strncmp(cmd, "stripe ", 7) == 0) {
		rv = stripe_canary((int)strtol(cmd + 7, NULL, 10));
	} else if (strncmp(cmd, "isopt ", 6) == 0) {
		rv = isopt_run(strtoul(cmd + 6, NULL, 16));
	}
	return (rv);
}

static struct sysctl_oid *go_oid;

static int
dfmisc_load(module_t mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		go_oid = SYSCTL_ADD_PROC(NULL,
		    SYSCTL_STATIC_CHILDREN(_kern), OID_AUTO, "dfmiscgo",
		    CTLTYPE_STRING | CTLFLAG_RW, 0, 0, dfmisc_go, "A",
		    "rig control");
		loan_dev_setup();
		return (0);
	case MOD_UNLOAD:
		if (go_oid)
			sysctl_remove_oid(go_oid, 1, 0);
		return (0);
	}
	return (EOPNOTSUPP);
}

static moduledata_t dfmisc_mod = { "dfmisc", dfmisc_load, 0 };
DECLARE_MODULE(dfmisc, dfmisc_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
