/*
 * umcs_harness.c -- verification harness for DF-2343 / DF-2344.
 * Audit target: sys/bus/u4b/serial/umcs.c (MCS7840 USB multiport
 * serial adapter driver), VERBATIM, TU-compiled into the kernel.
 *
 * VERBATIM-INTERPOSITION DESIGN (established pattern, cf. DF-2314
 * pnp.c rig / DF-2300 fwohci rig):
 *   - This TU #includes the UNMODIFIED sys/bus/u4b/serial/umcs.c, so
 *     the audited code runs byte-identical to stock (sha256-gated by
 *     prep.sh against the pristine audit-tree hash).  Because its
 *     statics (umcs7840_attach, umcs7840_calc_baudrate,
 *     umcs7840_callback, ...) become callable, the rig can drive the
 *     REAL driver code with a fabricated USB device.
 *   - Interposed externals: ONLY the ones that cannot execute against
 *     a hardware-less fabricated device -- the synchronous control
 *     transfer (usbd_do_request_proc) is simulated (configurable
 *     per-register values / failure), the bulk/interrupt transfer
 *     plumbing (usbd_transfer_setup/start/stop/poll/unsetup,
 *     usbd_xfer_set_stall) is stubbed (no real endpoints exist), and
 *     the newbus device_t accessors (device_get_softc/get_ivars/
 *     printf, device_set_usb_desc, ucom_set_pnpinfo_usb) are shims.
 *     EVERYTHING ELSE passes through to the stock in-kernel
 *     implementation: the ucom framework (usb_serial.c: ucom_attach
 *     creates the real /dev/ttyU* /dev/cuaU* nodes via the real
 *     ttymalloc/make_dev and runs the real console-attach block at
 *     usb_serial.c:511-535), the tty layer, lockmgr, usb core
 *     helpers.
 *
 * DF-2343 (umcs.c:1053-1069): rate==0 falls through the bucket search
 *   with i==8, evaluates umcs7840_baudrate_divisors[9] (1-element OOB
 *   read of the 9-entry array) and divides by rate==0 -> #DE kernel
 *   panic.  Reached in production via
 *     (a) first open of /dev/ttyU* / cuaU*: usb_serial.c:839-842
 *         ucom_param(tp, &t) with t = sc_it_in, which is never
 *         initialized (zeroed) -> c_ospeed == 0 straight into
 *         ucom_pre_param (usb_serial.c:1680);
 *     (b) USB serial console: usb_serial.c:523-533 with
 *         hw.usb.ucom.cons_baud=0 loader tunable;
 *     while the TIOCSETA* ioctl route is blocked by
 *     sys/kern/tty.c:1033-1041 + usb_serial.c:1670-1674 (verified
 *     live, see tioc0.c).
 *
 * DF-2344 (umcs.c:301/324-325): umcs7840_attach() discards the return
 *   of the GPIO register read; on control-transfer failure `data'
 *   (never initialized) decides the 2-vs-4-port configuration.  The
 *   rig grooms the kernel stack below attach()'s frame with a chosen
 *   byte and shows sc_numports follows the poison, not the device.
 *
 * Control device: /dev/umcsh0 (root-only, 0600).
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/bus.h>
#include <sys/types.h>
#include <sys/lock.h>

/* u4b headers first (in umcs.c's own order: condvar.h provides the
 * struct cv usb_process.h embeds), so the shim prototypes below have
 * complete types */
#include <sys/condvar.h>
#include <bus/u4b/usb.h>
#include <bus/u4b/usbdi.h>
#include <bus/u4b/usbdi_util.h>
#include <bus/u4b/usb_process.h>
#include <bus/u4b/serial/usb_serial.h>

/*
 * Interposition of the u4b/usb externals that cannot run against a
 * fabricated (hardware-less) USB device.  All headers are already
 * included above, so the object-like renames below only rewrite the
 * call sites inside the verbatim umcs.c body.
 */
#define	usbd_do_request_proc	umcsh_do_request_proc
#define	usbd_transfer_setup	umcsh_transfer_setup
#define	usbd_transfer_unsetup	umcsh_transfer_unsetup
#define	usbd_transfer_start	umcsh_transfer_start
#define	usbd_transfer_stop	umcsh_transfer_stop
#define	usbd_transfer_poll	umcsh_transfer_poll
#define	usbd_xfer_set_stall	umcsh_xfer_set_stall
#define	device_set_usb_desc	umcsh_set_usb_desc
#define	ucom_set_pnpinfo_usb	umcsh_set_pnpinfo
#define	device_printf		umcsh_dev_printf
#define	device_get_ivars	umcsh_dev_get_ivars
#define	device_get_softc	umcsh_dev_get_softc

/* forward prototypes for the shims (defined below the verbatim unit);
 * the renames above rewrite umcs.c's call sites to these */
static usb_error_t	umcsh_do_request_proc(struct usb_device *,
			    struct usb_process *, struct usb_device_request *,
			    void *, uint16_t, uint16_t *, usb_timeout_t);
static usb_error_t	umcsh_transfer_setup(struct usb_device *,
			    const uint8_t *, struct usb_xfer **,
			    const struct usb_config *, uint16_t, void *,
			    struct lock *);
static void		umcsh_transfer_unsetup(struct usb_xfer **, uint16_t);
static void		umcsh_transfer_start(struct usb_xfer *);
static void		umcsh_transfer_stop(struct usb_xfer *);
static void		umcsh_transfer_poll(struct usb_xfer **, uint16_t);
static void		umcsh_xfer_set_stall(struct usb_xfer *);
static void		umcsh_set_usb_desc(device_t);
static void		umcsh_set_pnpinfo(struct ucom_super_softc *, device_t);
static int		umcsh_dev_printf(device_t, const char *, ...);
static void		*umcsh_dev_get_ivars(device_t);
static void		*umcsh_dev_get_softc(device_t);

/*
 * Codegen selector for the verbatim unit.
 *
 * 0 (default): stock kernel codegen (gcc80 -O2, exactly what the
 *   shipping kernel does).  IMPORTANT: at -O2 gcc80 exploits the
 *   div-by-zero UB in umcs7840_calc_baudrate(rate==0) and compiles
 *   the loop-exhausted path to "*divisor=0, *clk=0x80, ret 0" with
 *   NEITHER the divisors[9] load NOR the division executed -- see
 *   adjacency.txt disassembly, path at +0x33.
 *
 * 1 (-O0 semantics): translate the C source literally, so the
 *   source-level defect executes: load umcs7840_baudrate_divisors[9]
 *   (1-element OOB read), then divide by rate==0 -> #DE panic.
 *   This demonstrates the C-level behavior under different codegen
 *   (-O0, other compilers / versions); it is NOT the shipping
 *   behavior.
 */
#define	UMCSH_VERBATIM_CODEGEN_O0	0

#if UMCSH_VERBATIM_CODEGEN_O0
#pragma GCC push_options
#pragma GCC optimize ("O0")
#endif

#include "umcs.c"			/* VERBATIM, sha256-gated by prep.sh */

#if UMCSH_VERBATIM_CODEGEN_O0
#pragma GCC pop_options
#endif

#undef	usbd_do_request_proc
#undef	usbd_transfer_setup
#undef	usbd_transfer_unsetup
#undef	usbd_transfer_start
#undef	usbd_transfer_stop
#undef	usbd_transfer_poll
#undef	usbd_xfer_set_stall
#undef	device_set_usb_desc
#undef	ucom_set_pnpinfo_usb
#undef	device_printf
#undef	device_get_ivars
#undef	device_get_softc

#include "umcsh.h"

/* ------------------------------------------------------------------ */
/* fabricated device state                                             */
/* ------------------------------------------------------------------ */

static struct umcs7840_softc umcsh_sc;	/* the REAL driver softc */
static int umcsh_attached;		/* attach() returned 0 at least once */

static struct usb_attach_arg umcsh_uaa = {
	.info = {
		.idVendor = USB_VENDOR_MOSCHIP,
		.idProduct = USB_PRODUCT_MOSCHIP_MCS7840,
		.bIfaceIndex = MCS7840_IFACE_INDEX,
		.bConfigIndex = MCS7840_CONFIG_INDEX,
	},
	.device = NULL,			/* transfers are simulated */
	.usb_mode = USB_MODE_HOST,
};

/* opaque token standing in for the newbus device_t */
static device_t umcsh_fake_dev = (device_t)0xC0FFEE42UL;

/* ------------------------------------------------------------------ */
/* simulated control transfers (the only functional seam)              */
/* ------------------------------------------------------------------ */

static uint32_t sim_fail_mask;		/* UMCSH_AT_* bits */
static uint8_t sim_gpio_val = 0x01;
static uint8_t sim_mode_val = 0x40;	/* EEPROMWR default-ish */
static uint8_t sim_reg_default = 0x00;
static uint32_t sim_rd_cnt, sim_wr_cnt;

static void
sim_reset(void)
{
	sim_fail_mask = 0;
	sim_gpio_val = 0x01;
	sim_mode_val = 0x40;
	sim_reg_default = 0x00;
	sim_rd_cnt = sim_wr_cnt = 0;
}

/*
 * The seam the real driver uses for every register access:
 * umcs7840_get_reg_sync / umcs7840_set_reg_sync / *_UART_reg_sync
 * (umcs.c:914-1001).  Faithful failure semantics: on error NOTHING is
 * written to *data -- exactly like the real usbd_do_request_proc(),
 * which is what leaves `data' uninitialized in umcs7840_attach().
 */
static usb_error_t
umcsh_do_request_proc(struct usb_device *udev, struct usb_process *pp,
    struct usb_device_request *req, void *data, uint16_t flags,
    uint16_t *actlen, uint32_t timeout)
{
	uint16_t index = UGETW(req->wIndex);
	uint16_t value = UGETW(req->wValue);
	uint8_t portno = (uint8_t)(value >> 8);

	if (req->bmRequestType == UT_READ_VENDOR_DEVICE &&
	    req->bRequest == MCS7840_RDREQ) {
		sim_rd_cnt++;
		if ((sim_fail_mask & UMCSH_AT_GPIOFAIL) &&
		    index == MCS7840_DEV_REG_GPIO && value == 0) {
			kprintf("UMCSH SIM: RD  reg 0x%02x -> STALLED "
			    "(no byte written to caller buffer!)\n", index);
			return (USB_ERR_STALLED);
		}
		if ((sim_fail_mask & UMCSH_AT_MODEFAIL) &&
		    index == MCS7840_DEV_REG_MODE && value == 0) {
			kprintf("UMCSH SIM: RD  reg 0x%02x -> STALLED\n", index);
			return (USB_ERR_STALLED);
		}
		*(uint8_t *)data =
		    (index == MCS7840_DEV_REG_GPIO) ? sim_gpio_val :
		    (index == MCS7840_DEV_REG_MODE) ? sim_mode_val :
		    sim_reg_default;
		if (actlen)
			*actlen = 1;
		kprintf("UMCSH SIM: RD  reg 0x%02x port %u -> 0x%02x\n",
		    index, portno, *(uint8_t *)data);
		return (USB_ERR_NORMAL_COMPLETION);
	}
	if (req->bmRequestType == UT_WRITE_VENDOR_DEVICE &&
	    req->bRequest == MCS7840_WRREQ) {
		sim_wr_cnt++;
		kprintf("UMCSH SIM: WR  reg 0x%02x port %u <- 0x%02x\n",
		    index, portno, (uint8_t)(value & 0xff));
		return (USB_ERR_NORMAL_COMPLETION);
	}
	kprintf("UMCSH SIM: unexpected request type 0x%02x/%02x\n",
	    req->bmRequestType, req->bRequest);
	return (USB_ERR_STALLED);
}

/* no real endpoints exist on the fabricated device */
static usb_error_t
umcsh_transfer_setup(struct usb_device *udev, const uint8_t *ifaces,
    struct usb_xfer **pxfer, const struct usb_config *setup_start,
    uint16_t n_setup, void *priv_sc, struct lock *priv_lock)
{
	uint16_t n;

	for (n = 0; n != n_setup; n++)
		pxfer[n] = NULL;
	kprintf("UMCSH SIM: transfer_setup ep 0x%02x n=%u -> OK (NULL)\n",
	    setup_start[0].endpoint, n_setup);
	return (0);
}

static void umcsh_transfer_unsetup(struct usb_xfer **pxfer, uint16_t n) { }
static void umcsh_transfer_start(struct usb_xfer *xfer) { }
static void umcsh_transfer_stop(struct usb_xfer *xfer) { }
static void umcsh_transfer_poll(struct usb_xfer **pxfer, uint16_t n) { }
static void umcsh_xfer_set_stall(struct usb_xfer *xfer) { }
static void umcsh_set_usb_desc(device_t dev) { }
static void umcsh_set_pnpinfo(struct ucom_super_softc *ssc, device_t dev) { }

static int
umcsh_dev_printf(device_t dev, const char *fmt, ...)
{
	__va_list ap;

	kprintf("umcs7840: ");		/* same prefix as on real hardware */
	__va_start(ap, fmt);
	kvprintf(fmt, ap);
	__va_end(ap);
	return (0);
}

static void *
umcsh_dev_get_ivars(device_t dev)
{
	return (&umcsh_uaa);
}

static void *
umcsh_dev_get_softc(device_t dev)
{
	return (&umcsh_sc);
}

/* ------------------------------------------------------------------ */
/* DF-2344: kernel-stack groom                                         */
/*                                                                     */
/* `data' in umcs7840_attach() has its address taken (umcs.c:324), so  */
/* it lives in attach()'s stack frame.  groom_stack() burns a pad of   */
/* the chosen byte immediately before attach() is entered; the frame   */
/* (and the uninitialized slot) lands inside the poisoned pad.         */
/* ------------------------------------------------------------------ */
#define	GROOM_SPAN	8192
static int umcsh_groom_sink;

static __noinline void
groom_stack(uint8_t fill)
{
	volatile char pad[GROOM_SPAN];
	int i;

	for (i = 0; i < GROOM_SPAN; i++)
		pad[i] = (char)fill;
	if (pad[GROOM_SPAN - 1] == (char)0xAB)	/* keep pad materialized */
		umcsh_groom_sink = 1;
}

/* ------------------------------------------------------------------ */
/* scenarios                                                           */
/* ------------------------------------------------------------------ */

static int
umcsh_scenario(struct umcsh_req *r)
{
	uint32_t sc = r->scenario;
	int rc = 0;

	switch (sc) {
	case UMCSH_SC_LAYOUT: {
		const uint32_t *arr = umcs7840_baudrate_divisors;
		const uint32_t *oob = arr + 9;	/* what index 9 fetches */
		int i;

		kprintf("UMCSH layout: umcs7840_baudrate_divisors=%p "
		    "len=%u entries=%u bytes; UMCS7840_MAX_PORTS=%u; "
		    "GPIO_4PORTS bit=0x%02x\n",
		    arr, (unsigned)umcs7840_baudrate_divisors_len,
		    (unsigned)(sizeof(umcs7840_baudrate_divisors)),
		    (unsigned)UMCS7840_MAX_PORTS,
		    (unsigned)MCS7840_DEV_GPIO_4PORTS);
		kprintf("UMCSH layout: entries:");
		for (i = 0; i < (int)umcs7840_baudrate_divisors_len; i++)
			kprintf(" %u", arr[i]);
		kprintf("\n");
		/*
		 * Instrumentation (harness-only) read of divisors[9]:
		 * the exact dword the faulting instruction at umcs.c:1066
		 * loads before dividing by rate==0.
		 */
		r->r_oob_dword = *oob;
		kprintf("UMCSH layout: divisors[9] (OOB, instrumentation) "
		    "@%p = 0x%08x (%u)\n", oob, *oob, *oob);
		kprintf("UMCSH layout: spill context [9]=%08x "
		    "[10]=%08x [11]=%08x [12]=%08x\n",
		    arr[9], arr[10], arr[11], arr[12]);
		break;
	}
	case UMCSH_SC_CALC: {
		uint16_t divisor = 0xDEAD;
		uint8_t clk = 0xEE;
		uint32_t rate = r->arg;

		if (rate == 0 && !(r->arg2 & 1)) {
			ksnprintf(r->r_msg, sizeof(r->r_msg),
			    "refusing rate=0 without arg2&1 (it panics on "
			    "the stock kernel)");
			return (EPERM);
		}
		kprintf("UMCSH calc: rate=%u divisor(before)=%u\n",
		    rate, divisor);
		rc = umcs7840_calc_baudrate(rate, &divisor, &clk);
		r->r_divisor = divisor;
		r->r_clk = clk;
		kprintf("UMCSH calc: rate=%u -> rc=%d divisor=%u clk=0x%02x"
		    "\n", rate, rc, divisor, clk);
		break;
	}
	case UMCSH_SC_PREPARAM: {
		struct termios t;

		bzero(&t, sizeof(t));
		t.c_ospeed = r->arg;
		t.c_ispeed = r->arg;
		kprintf("UMCSH preparam: dispatching real "
		    "umcs7840_pre_param(ucom, t.c_ospeed=%u) -- the exact "
		    "call usb_serial.c:1680 makes\n", t.c_ospeed);
		kprintf("UMCSH preparam: ABOUT TO ENTER (stock kernel: "
		    "expect Fatal trap 0 divide error)\n");
		rc = umcs7840_callback.ucom_pre_param(&umcsh_sc.sc_ucom[0],
		    &t);
		kprintf("UMCSH preparam: returned %d (%s panic on stock)\n",
		    rc, rc ? "EINVAL, no" : "accepted, no");
		break;
	}
	case UMCSH_SC_ATTACH: {
		uint8_t poison = (uint8_t)r->arg;

		if (umcsh_attached) {
			ksnprintf(r->r_msg, sizeof(r->r_msg),
			    "already attached, run DETACH first");
			return (EBUSY);
		}
		sim_reset();
		sim_gpio_val = poison;		/* used when not failing */
		sim_fail_mask = r->arg2;
		bzero(&umcsh_sc, sizeof(umcsh_sc));	/* fresh softc, like newbus */

		kprintf("UMCSH attach: gpio_fail=%u mode_fail=%u "
		    "groom=%u poison=0x%02x gpio_ok_value=0x%02x\n",
		    !!(r->arg2 & UMCSH_AT_GPIOFAIL),
		    !!(r->arg2 & UMCSH_AT_MODEFAIL),
		    !!(r->arg2 & UMCSH_AT_GROOM), poison, sim_gpio_val);
		kprintf("UMCSH attach: ABOUT TO enter real umcs7840_attach"
		    "()\n");
		/* groom LAST: any call between groom_stack() and attach()
		 * would overwrite the poisoned pad with its own frame */
		if (r->arg2 & UMCSH_AT_GROOM)
			groom_stack(poison);
		else
			groom_stack(0x5A);	/* distinctive default residue */
		rc = umcs7840_attach(umcsh_fake_dev);
		umcsh_attached = (rc == 0);
		r->r_numports = umcsh_sc.sc_numports;
		r->r_rd_cnt = sim_rd_cnt;
		r->r_wr_cnt = sim_wr_cnt;
		if (rc == 0)
			ksnprintf(r->r_msg, sizeof(r->r_msg),
			    "attached, tty=%s",
			    umcsh_sc.sc_super_ucom.sc_ttyname);
		kprintf("UMCSH attach: rc=%d numports=%u rd=%u wr=%u\n",
		    rc, r->r_numports, sim_rd_cnt, sim_wr_cnt);
		break;
	}
	case UMCSH_SC_DETACH:
		if (!umcsh_attached) {
			ksnprintf(r->r_msg, sizeof(r->r_msg), "not attached");
			return (ENXIO);
		}
		rc = umcs7840_detach(umcsh_fake_dev);
		umcsh_attached = 0;
		kprintf("UMCSH detach: rc=%d (numports was %u)\n",
		    rc, umcsh_sc.sc_numports);
		break;
	case UMCSH_SC_SEED_ITIN: {
		struct termios tmpl;
		uint32_t speed = r->arg;

		if (!umcsh_attached) {
			ksnprintf(r->r_msg, sizeof(r->r_msg),
			    "not attached (seed needs live sc_ucom)");
			return (ENXIO);
		}
		bzero(&tmpl, sizeof(tmpl));
		tmpl.c_cflag = TTYDEF_CFLAG;
		tmpl.c_ospeed = speed;
		tmpl.c_ispeed = speed;
		if (r->arg2 == UMCSH_SEED_IN || r->arg2 == UMCSH_SEED_BOTH)
			umcsh_sc.sc_ucom[0].sc_it_in = tmpl;
		if (r->arg2 == UMCSH_SEED_OUT || r->arg2 == UMCSH_SEED_BOTH)
			umcsh_sc.sc_ucom[0].sc_it_out = tmpl;
		kprintf("UMCSH seed: it_in.c_ospeed=%u it_out.c_ospeed=%u "
		    "(simulating a driver that initializes its termios)\n",
		    umcsh_sc.sc_ucom[0].sc_it_in.c_ospeed,
		    umcsh_sc.sc_ucom[0].sc_it_out.c_ospeed);
		break;
	}
	case UMCSH_SC_STATUS:
		r->r_attached = umcsh_attached;
		r->r_numports = umcsh_sc.sc_numports;
		r->r_rd_cnt = sim_rd_cnt;
		r->r_wr_cnt = sim_wr_cnt;
		ksnprintf(r->r_msg, sizeof(r->r_msg), "attached=%d numports=%u",
		    umcsh_attached, umcsh_sc.sc_numports);
		kprintf("UMCSH status: %s\n", r->r_msg);
		break;
	default:
		rc = EINVAL;
		break;
	}
	return (rc);
}

/* ------------------------------------------------------------------ */
/* control device                                                      */
/* ------------------------------------------------------------------ */
static int	umcsh_ioctl(struct dev_ioctl_args *ap);
static int	umcsh_open(struct dev_open_args *ap);

static int
umcsh_open(struct dev_open_args *ap)
{
	return (0);
}

static int
umcsh_ioctl(struct dev_ioctl_args *ap)
{
	struct umcsh_req r;

	if (ap->a_cmd != UMCSH_IOC)
		return (ENOTTY);
	bcopy(ap->a_data, &r, sizeof(r));
	r.r_rc = (uint32_t)umcsh_scenario(&r);
	bcopy(&r, ap->a_data, sizeof(r));
	/*
	 * Always return 0 so the syscall layer copyouts r (and never sees
	 * a raw negative rc: -1 is ERESTART on DragonFly, which would
	 * restart the ioctl forever).  Scenario results travel in r_rc.
	 */
	return (0);
}

static struct dev_ops umcsh_ops = {
	{ "umcsh", 0, D_MEM },
	.d_open =	umcsh_open,
	.d_ioctl =	umcsh_ioctl,
};

static void
umcsh_init(void *dummy)
{
	make_dev(&umcsh_ops, 0, UID_ROOT, GID_WHEEL, 0600, "umcsh0");
	kprintf("UMCSHARNESS-READY: umcs.c verbatim (TU-compiled) "
	    "/dev/umcsh0 ready\n");
}
SYSINIT(umcs_harness, SI_SUB_PSEUDO, SI_ORDER_MIDDLE, umcsh_init, NULL);
