/*
 * DF-5515 KLD harness -- production-code demonstration of the ubser
 * (BWCT multi-port USB serial, ucom core) cfg-path synchronous-request
 * stall.  Same rig family as DF-5496 umct / DF-5504 ucycom / DF-5487
 * uvisor (cfg-stall family), strengthened with the DF-5447
 * production-code-include technique:
 *
 *   - the UNMODIFIED production sys/bus/u4b/serial/ubser.c is #included
 *     into this translation unit (newbus registration neutered), so the
 *     REAL static ubser_cfg_set_break() (ubser.c:462-487) runs;
 *   - the REAL kernel usb_process machinery (usb_proc_create /
 *     usb_proc_msignal / usb_proc_mwait / usb_proc_free -- the exact
 *     taskqueue ucom's cfg callbacks execute on, usb_serial.c:322)
 *     schedules and serializes the work;
 *   - only the EP0 sink is modeled: ucom_cfg_do_request is a macro
 *     (usb_serial.h:220-221) expanding to usbd_do_request_proc(), which
 *     this harness defines (there is no prototype in any header, so the
 *     harness pre-declares it).  The model reproduces the production
 *     requester exactly where it matters: it is called with the ucom
 *     taskqueue's lock held (usb_process protocol), sleeps the full
 *     caller-supplied timeout in the "WXFER" wait channel
 *     (usb_request.c:643 style) on a UNIQUE per-request wchan, and for
 *     the "silent evil adapter" returns USB_ERR_TIMEOUT -- which
 *     production ubser DISCARDS (ubser.c:482-485: DPRINTFN only).
 *
 * PRODUCTION REQUEST ACCOUNTING (pass-2 precision -- this driver's
 * entire cfg surface is ONE request):
 *
 *   ubser_callback (ubser.c:175-184) registers ucom_cfg_set_break,
 *   ucom_cfg_get_status, ucom_pre_param, start/stop read/write, poll.
 *   It registers NO ucom_cfg_param, NO ucom_cfg_open/close, NO
 *   set_dtr/rts/ring.  In ucom_cfg_line_state (usb_serial.c:1372-1381)
 *   the callback mask is therefore UCOM_LS_BREAK ONLY:
 *     - first open(2)  (usb_serial.c:842-880): the queued line-state
 *       task computes break-off, set_break(sc, 0) -- ubser.c:470
 *       `if (onoff)' gates it: 0 EP0 requests.  cfg_open has no
 *       callback -> no hz/10 pause (pause is inside the callback-if,
 *       usb_serial.c:773-778).  status task = fake bits (ubser.c:
 *       489-495), 0 requests.  => first open costs ZERO requests.
 *     - every tcsetattr: ubser has no ucom_cfg_param -> the param task
 *       issues 0 requests.
 *     - TIOCSBRK (usb_serial.c:1190-1193 -> ucom_break:1458-1466 ->
 *       ucom_line_state:1423-1445): any_bits&BREAK -> set_break(sc,1)
 *       -> 1 x 1000 ms synchronous EP0 vendor request (ubser.c:480-481).
 *     - TIOCCBRK: set_break(sc,0) -> 0 requests (the `if (onoff)'
 *       gate -- see DF-5517: the clear command is never sent at all).
 *     - a paced TIOCSBRK+TIOCCBRK PAIR coalesces into ONE line-state
 *       task: notch_bits=BREAK -> set_break(prev=0) [0 requests] +
 *       any_bits=BREAK -> set_break(last=1) [1 request] -- the pair
 *       still costs 1 x 1000 ms (usb_serial.c:1384-1420).
 *     - close(2) HUPCL drops DTR only: mask excludes DTR -> 0 requests;
 *       the closer's cost is purely usb_proc_mwait() behind the queue
 *       (usb_serial.c:671-672, uninterruptible cv_wait).
 *     - unplug: ucom_detach -> usb_proc_drain (usb_serial.c:394) waits
 *       out the same queue on the USB hub event thread.
 *
 *   => An evil BWCT adapter that NAKs the VENDOR_SET_BREAK data stage
 *      pins the per-adapter ucom taskqueue 1 s per TIOCSBRK (or per
 *      SBRK/CBRK pair); close(2) of any of the adapter's ttys blocks
 *      uninterruptibly behind it; a sustained break loop extends the
 *      occupancy without bound.  The ttyU and cuaU device nodes are
 *      0600 root / 0660 dialer (usb_serial.c:477-488), so local
 *      amplification needs root or group dialer; the evil device
 *      itself needs nothing.
 *
 * PHASE A  open + 1 x (TIOCSBRK, 100 ms, TIOCCBRK) + close: the set
 *          issues 1 stalled request; the clear task computes
 *          set_break(sc,0) and issues NOTHING (ubser.c:470 gate --
 *          the DF-5517 tie-in).  EXPECT closer ELAPSED ~= 700-900 ms
 *          behind the stalled set request, 1 request total.
 * PHASE B  open + 5 pairs spaced 1.1 s (outpacing the 1 s request so
 *          the double buffer cannot coalesce -- a sustained loop):
 *          every set issues its own request.  EXPECT 5 requests,
 *          ~5.5 s of continuous per-adapter taskqueue occupancy, closer
 *          ELAPSED ~= 900-1100 ms behind the last in-flight request.
 *          The occupancy extends without bound while the abuser keeps
 *          a >=1 s paced break loop running; only UNPACED bursts
 *          coalesce in the double buffer.
 * PHASE B2 slow-answer evil variant: the request SUCCEEDS at
 *          timeout-100 ms and no error is visible anywhere -- same
 *          occupancy, fully invisible to the driver.
 * PHASE C  (fixed build only, fix.diff: timeout 1000 -> 250 ms at
 *          ubser.c:481): same single TIOCSBRK.  EXPECT closer ELAPSED
 *          ~= 200-300 ms.  See run.fixed.log.
 */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/lock.h>
#include <sys/time.h>

/* NOTE: no usb_debug.h here on purpose: ubser.c must include its own
 * debug machinery itself (its USB_DEBUG_VAR-before-usb_debug.h include
 * order is required for DPRINTF/DPRINTFN).  The prototype for
 * usbd_do_request_proc comes from usbdi_util.h (line 42), which
 * ubser.c itself includes at :88 -- visible at the production call
 * site inside ubser_cfg_set_break (ubser.c:480). */

MODULE_DEPEND(ub5515stall, usb, 1, 1, 1);
MODULE_DEPEND(ub5515stall, ucom, 1, 1, 1);

/* Neuter the newbus/module registration the production file would
 * otherwise perform. */
#undef DRIVER_MODULE
#undef DRIVER_MODULE_ORDERED
#undef MODULE_DEPEND
#undef MODULE_VERSION
#define DRIVER_MODULE(name, busname, driver, devclass, evh, arg)
#define DRIVER_MODULE_ORDERED(name, busname, driver, devclass, evh, arg, order)
#define MODULE_DEPEND(module, mdepend, vmin, vpref, vmax)
#define MODULE_VERSION(module, version)

/* THE PRODUCTION DRIVER, textually unmodified (md5-gated by build.sh). */
#include "../ubser.c"

/* one adapter instance (the production driver's softc, allocated
 * zeroed by newbus at subr_bus.c:1953-1954 M_ZERO) */
static struct ubser_softc ub_sc;

/* ------------------------------------------------------------------ */
/* Timing helpers / wait idents                                        */

static int ub_tq_dummy;		/* tsleep ident only */
static struct timeval ub_t0;

static int64_t
ub_ms(struct timeval *t0, struct timeval *t1)
{
	return ((int64_t)(t1->tv_sec - t0->tv_sec) * 1000 +
	    (t1->tv_usec - t0->tv_usec) / 1000);
}

static int64_t
ub_abs_ms(void)
{
	struct timeval now;

	microtime(&now);
	return (ub_ms(&ub_t0, &now));
}

/* ------------------------------------------------------------------ */
/* Modeled EP0 sink: the silent / slow evil BWCT adapter              */

static int ub_evil_mode;	/* 0 = silent (timeout), 1 = slow-answer */
static int ub_req_count;	/* requests issued this phase */
static char ub_req_wchan[16];	/* unique per-request wait channel */

usb_error_t
usbd_do_request_proc(struct usb_device *udev, struct usb_process *pproc,
    struct usb_device_request *req, void *data, uint16_t flags,
    uint16_t *actlen, usb_timeout_t timeout)
{
	int ms = (int)timeout;

	/* production gate, usb_request.c:771-774 */
	if (usb_proc_is_gone(pproc))
		return (USB_ERR_IOERROR);

	ub_req_count++;
	kprintf("UB5515[%dms]: EP0 bRequest=0x%02x wValue=0x%02x "
	    "(%d ms timeout): device %s\n",
	    (int)ub_abs_ms(), req->bRequest, req->wValue[0], ms,
	    ub_evil_mode ? "answers at just-under-timeout" :
	    "silent -- NAKs until timeout");

	if (ub_evil_mode) {
		/* evil adapter answers successfully at timeout-100 ms */
		lksleep(&ub_req_wchan, pproc->up_lock, 0, "WXFER",
		    USB_MS_TO_TICKS(ms - 100));
		if (req->wLength[0] != 0 && data != NULL)
			*(uint8_t *)data = 0;	/* answer payload */
		return (USB_ERR_NORMAL_COMPLETION);
	}
	/* silent adapter: full timeout, then error -- which production
	 * ubser_cfg_set_break DISCARDS (ubser.c:482-485) */
	lksleep(&ub_req_wchan, pproc->up_lock, 0, "WXFER",
	    USB_MS_TO_TICKS(ms));
	return (USB_ERR_TIMEOUT);
}

/* ------------------------------------------------------------------ */
/* The ucom cfg taskqueue: REAL usb_process machinery                 */

struct ub_msg {
	struct usb_proc_msg hdr;
};
static struct ub_msg ub_open_msg[2];	/* ucom sc_open_task[2] */
static struct ub_msg ub_line_msg[2];	/* ucom sc_line_state_task[2] */
static struct ub_msg ub_close_msg[2];	/* ucom sc_close_task[2] */

/* modeled ucom_softc line-state bits (usb_serial.c:1384-1392
 * arithmetic), BREAK-only mask as computed for ubser at
 * usb_serial.c:1378-1379 */
static int ub_ll_ready;		/* UCOM_FLAG_LL_READY */
static int ub_pls_curr, ub_pls_set, ub_pls_clr;
#define	UB_LS_BREAK	0x04		/* UCOM_LS_BREAK, usb_serial.h:208 */

/*
 * ucom_cfg_open (usb_serial.c:756-780) for a driver with no
 * ucom_cfg_open callback: sets LL_READY; NO pause (the hz/10 pause at
 * :777 is inside the callback-if).
 */
static void
ub_cfg_open_task(struct usb_proc_msg *pm)
{
	kprintf("UB5515[%dms]: cfg-open task: no ucom_cfg_open callback in "
	    "ubser -> set LL_READY only, 0 requests, no pause "
	    "(usb_serial.c:771-778)\n", (int)ub_abs_ms());
	ub_ll_ready = 1;
}

/*
 * ucom_cfg_line_state (usb_serial.c:1356-1421) with the BREAK-only
 * callback mask ubser earns (usb_serial.c:1378-1379), driving the REAL
 * production ubser_cfg_set_break().
 */
static void
ub_cfg_line_task(struct usb_proc_msg *pm)
{
	uint8_t notch_bits, any_bits, prev_value, last_value;

	if (!ub_ll_ready)
		return;		/* usb_serial.c:1368-1370 */

	/* mask arithmetic, usb_serial.c:1384-1392 */
	notch_bits = (uint8_t)((ub_pls_set & ub_pls_clr) & UB_LS_BREAK);
	any_bits = (uint8_t)((ub_pls_set | ub_pls_clr) & UB_LS_BREAK);
	prev_value = (uint8_t)(ub_pls_curr ^ notch_bits);
	last_value = (uint8_t)ub_pls_curr;
	ub_pls_curr = 0;
	ub_pls_set = 0;
	ub_pls_clr = 0;

	kprintf("UB5515[%dms]: line-state task: enter "
	    "(notch=0x%x any=0x%x prev=0x%x last=0x%x)\n",
	    (int)ub_abs_ms(), notch_bits, any_bits, prev_value, last_value);

	/* notch pass, usb_serial.c:1401-1403 */
	if (notch_bits & UB_LS_BREAK)
		ubser_cfg_set_break(&ub_sc.sc_ucom[0],
		    (prev_value & UB_LS_BREAK) ? 1 : 0);
	/* last-value pass, usb_serial.c:1415-1417 */
	if (any_bits & UB_LS_BREAK)
		ubser_cfg_set_break(&ub_sc.sc_ucom[0],
		    (last_value & UB_LS_BREAK) ? 1 : 0);

	kprintf("UB5515[%dms]: line-state task: leave\n", (int)ub_abs_ms());
}

/* ucom_cfg_close (usb_serial.c:915-921) for a driver with no
 * ucom_cfg_close callback: issues nothing. */
static void
ub_cfg_close_task(struct usb_proc_msg *pm)
{
	kprintf("UB5515[%dms]: cfg-close task: no ucom_cfg_close callback "
	    "in ubser -> 0 requests (usb_serial.c:915-921)\n",
	    (int)ub_abs_ms());
}

/*
 * The user thread: TIOCSBRK / TIOCCBRK as ucomioctl dispatches them
 * (usb_serial.c:1190-1197 -> ucom_break:1458-1466 -> ucom_line_state:
 * 1423-1445: update pls bits, queue the double-buffered line task).
 */
static void
ub_user_break(int on)
{
	if (on) {
		ub_pls_curr |= UB_LS_BREAK;
		ub_pls_set |= UB_LS_BREAK;
	} else {
		ub_pls_curr &= ~UB_LS_BREAK;
		ub_pls_clr |= UB_LS_BREAK;
	}

	lockmgr(&ub_sc.sc_lock, LK_EXCLUSIVE);
	if (!usb_proc_is_gone(&ub_sc.sc_super_ucom.sc_tq)) {
		struct ub_msg *m = usb_proc_msignal(
		    &ub_sc.sc_super_ucom.sc_tq,
		    &ub_line_msg[0], &ub_line_msg[1]);
		m->hdr.pm_callback = &ub_cfg_line_task;
	}
	lockmgr(&ub_sc.sc_lock, LK_RELEASE);
}

/*
 * The closer (or the USB hub event thread on unplug): exactly
 * ucom_close()'s synchronous tail -- queue cfg_close then
 * usb_proc_mwait() UNINTERRUPTIBLY behind whatever is already queued
 * (usb_serial.c:671-672 via ucom_queue_command).
 */
static int64_t
ub_closer(void)
{
	struct timeval t0, t1;

	microtime(&t0);
	kprintf("UB5515[%dms]: closer: close(2) queues cfg_close then "
	    "usb_proc_mwait() uninterruptibly (usb_serial.c:671-672)\n",
	    (int)ub_abs_ms());
	lockmgr(&ub_sc.sc_lock, LK_EXCLUSIVE);
	if (!usb_proc_is_gone(&ub_sc.sc_super_ucom.sc_tq)) {
		struct ub_msg *m = usb_proc_msignal(
		    &ub_sc.sc_super_ucom.sc_tq,
		    &ub_close_msg[0], &ub_close_msg[1]);
		m->hdr.pm_callback = &ub_cfg_close_task;
		usb_proc_mwait(&ub_sc.sc_super_ucom.sc_tq,
		    &ub_close_msg[0], &ub_close_msg[1]);
	}
	lockmgr(&ub_sc.sc_lock, LK_RELEASE);
	microtime(&t1);
	kprintf("UB5515[%dms]: closer: unblocked  ELAPSED=%jd ms\n",
	    (int)ub_abs_ms(), (intmax_t)ub_ms(&t0, &t1));
	return (ub_ms(&t0, &t1));
}

static int
ub_phase(const char *name, char tag, int pairs, int pair_gap_ms,
    int evil_mode)
{
	int i;

	/* fresh adapter instance, exactly as production creates them:
	 * ubser_attach (ubser.c:233) + ucom_attach (usb_serial.c:322) */
	memset(&ub_sc, 0, sizeof(ub_sc));
	lockinit(&ub_sc.sc_lock, "ubser", 0, LK_CANRECURSE);
	ub_sc.sc_numser = 1;		/* evil device claims 1 port */
	ub_sc.sc_udev = (void *)&ub_tq_dummy;	/* model sink ignores it */
	ub_sc.sc_ucom[0].sc_parent = &ub_sc;
	ub_sc.sc_ucom[0].sc_portno = 0;
	ub_sc.sc_ucom[0].sc_super = &ub_sc.sc_super_ucom;
	ub_sc.sc_ucom[0].sc_lock = &ub_sc.sc_lock;
	ub_sc.sc_ucom[0].sc_flag = UCOM_FLAG_LL_READY | UCOM_FLAG_HL_READY;

	ub_ll_ready = 0;
	ub_pls_curr = ub_pls_set = ub_pls_clr = 0;
	ub_req_count = 0;
	ub_evil_mode = evil_mode;

	if (usb_proc_create(&ub_sc.sc_super_ucom.sc_tq, &ub_sc.sc_lock,
	    "ucom", USB_PRI_MED) != 0) {
		kprintf("UB5515: usb_proc_create failed\n");
		lockuninit(&ub_sc.sc_lock);
		return (ENXIO);
	}

	microtime(&ub_t0);
	kprintf("UB5515[%c]: --- phase %s: open + %d SBRK/CBRK pair(s) + "
	    "close, device=%s --- (t=0)\n", tag, name, pairs,
	    evil_mode ? "slow-answer" : "silent-NAK");

	/* open(2): production queue order usb_serial.c:842-880; of the
	 * whole open chain only cfg_open does anything here (param task
	 * bails on !LL_READY, usb_serial.c:1626-1628; line-state computes
	 * break-off -> set_break(0) -> gated off by ubser.c:470). */
	lockmgr(&ub_sc.sc_lock, LK_EXCLUSIVE);
	{
		struct ub_msg *m = usb_proc_msignal(
		    &ub_sc.sc_super_ucom.sc_tq,
		    &ub_open_msg[0], &ub_open_msg[1]);
		m->hdr.pm_callback = &ub_cfg_open_task;
	}
	lockmgr(&ub_sc.sc_lock, LK_RELEASE);
	tsleep(&ub_tq_dummy, 0, "ubopen", hz / 5);	/* open returns */

	/* the user's break traffic.  Within a pair SBRK and CBRK are
	 * 100 ms apart; pairs are spaced pair_gap_ms so that when
	 * pair_gap_ms exceeds the per-request stall every set gets its
	 * own line-state task (a sustained >=1 s paced loop keeps the
	 * taskqueue occupied without end); tighter pacing coalesces in
	 * the double buffer, losing sets -- not gaining them. */
	for (i = 0; i < pairs; i++) {
		ub_user_break(1);		/* TIOCSBRK */
		tsleep(&ub_tq_dummy, 0, "ubsbrk", hz / 10);
		ub_user_break(0);		/* TIOCCBRK */
		if (pair_gap_ms)
			tsleep(&ub_tq_dummy, 0, "ubcbrk",
			    USB_MS_TO_TICKS(pair_gap_ms));
	}

	/* close(2) behind whatever is stalled in the queue */
	ub_closer();

	kprintf("UB5515[%c]: phase done: %d EP0 request(s) issued\n",
	    tag, ub_req_count);

	usb_proc_free(&ub_sc.sc_super_ucom.sc_tq);
	lockuninit(&ub_sc.sc_lock);
	return (0);
}

static void
ub_touch_statics(void)
{

	if (ubser_devclass != NULL)
		kprintf("UB5515: unexpected devclass %p\n", ubser_devclass);
	kprintf("UB5515: production driver \"%s\" compiled into harness "
	    "(registration neutered)\n", ubser_driver.name);
}

static int
ub_modevent(module_t mod, int type, void *data)
{
	int error = 0;

	switch (type) {
	case MOD_LOAD:
		kprintf("UB5515: DF-5515 ubser cfg-stall harness, hz=%d; "
		    "ubser cfg surface = 1 request (VENDOR_SET_BREAK, "
		    "1000 ms, TIOCSBRK only; TIOCCBRK gated by "
		    "ubser.c:470 -- see DF-5517)\n", hz);
		ub_touch_statics();
		ub_phase("A  open + 1 SBRK/CBRK pair + close (silent evil)",
		    'A', 1, 0, 0);
		ub_phase("B  open + 5 pairs @1.1s + close (silent, sustained)",
		    'B', 5, 1100, 0);
		ub_phase("B2 open + 1 SBRK + close (slow-answer evil)",
		    '2', 1, 0, 1);
		kprintf("UB5515: harness complete (phase C = fixed build "
		    "rerun, see run.fixed.log)\n");
		break;
	case MOD_UNLOAD:
		break;
	default:
		error = EOPNOTSUPP;
		break;
	}
	return (error);
}

static moduledata_t ub_mod = { "ub5515stall", ub_modevent, 0 };
DECLARE_MODULE(ub5515stall, ub_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
