/*
 * DF-5382 KLD harness -- production-code demonstration of the umcs7840
 * interrupt-callback bus-freeze mechanism.
 *
 * WHAT IT PROVES (with the real in-kernel u4b usb_process machinery, the
 * same code that runs umcs7840_intr_callback()):
 *
 *   Every non-giant USB transfer callback on a bus executes on ONE shared
 *   per-bus kernel thread, bus->non_giant_callback_proc
 *   (usb_bus.h:62,72; selected as xfer->xroot->done_p at
 *   usb_transfer.c:1009-1014 "USB_BUS_NON_GIANT_PROC(udev->bus)";
 *   pumped by usb_callback_proc() at usb_transfer.c:2286-2306 which
 *   runs usb_command_wrapper() on done_q, and the callback itself is
 *   invoked at usb_transfer.c:2437 while the message is still head of
 *   the queue -- usb_process.c:146-158).
 *
 *   umcs7840_intr_callback() (umcs.c:729-790) is such a callback.  For
 *   every port whose interrupt byte is not MCS7840_UART_ISR_NOPENDING and
 *   matches RXERR/RXHASDATA/RXTIMEOUT (umcs.c:752-754) or MSCHANGE
 *   (umcs.c:764-770) it calls umcs7840_get_UART_reg_sync()
 *   (umcs.c:756,766) -> usbd_do_request_proc() (umcs.c:972) ->
 *   usbd_do_request_flags(): a SYNCHRONOUS EP0 control request with
 *   UMCS7840_CTRL_TIMEOUT = 500 ms (umcs.h:33; timeout applied at
 *   usb_request.c:421-428,557), sleeping in the "WXFER" loop
 *   (usb_request.c:642-644) and, when the request times out, pausing
 *   hz/16 == 62.5 ms more (usb_request.c:735-745, default case).
 *   A failed read only breaks the inner switch (umcs.c:757 "Inner
 *   switch"), so with a 4-port device ALL FOUR ports cost their 562.5 ms
 *   even when every request fails.
 *
 *   => ONE crafted 13-byte interrupt packet (all port bytes 0x00:
 *      0x00 & MCS7840_UART_ISR_NOPENDING(0x01) == 0 -> not skipped,
 *      0x00 & MCS7840_UART_ISR_INTMASK(0x3f) == MCS7840_UART_ISR_MSCHANGE)
 *      keeps the shared bus callback thread busy for
 *      4 x (500 + 62.5) = 2250 ms.  The interrupt pipe is resubmitted at
 *      umcs.c:776-780, so an evil device can stream packets and freeze
 *      EVERY USB transfer callback on the bus (keyboards, mass storage,
 *      network adapters on the same controller) indefinitely.
 *
 * PHASE A (baseline): queue an "evil intr packet" message that performs
 *   exactly umcs.c:745-771's loop (4 x [lksleep("WXFER",500ms) +
 *   usb_pause_mtx(hz/16)] -- the production per-request cost), then 10 ms
 *   later queue a "victim" message (any other device's completed transfer
 *   on this bus).  Measure the victim's dispatch delay.  EXPECT ~2.25 s.
 *   Then stream 5 packets back-to-back with a second victim: EXPECT ~11 s.
 *
 * PHASE B (fix mechanism from fix.diff: defer the LSR/MSR refresh to the
 *   ucom super taskqueue where synchronous requests are sanctioned --
 *   usbd_do_request_proc()'s contract, usb_request.c:756-757): the evil
 *   packet message only records pending-port bits and msignal()s a second
 *   usb_process (stands for sc_super_ucom.sc_tq, created by
 *   usb_serial.c:322); the deferred task pays the 4 x 562.5 ms there.
 *   Victim dispatch delay EXPECT ~0 ms.
 */
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/lock.h>
#include <sys/condvar.h>
#include <sys/time.h>
#include <sys/types.h>

#include <sys/bus.h>
#include <bus/u4b/usb.h>
#include <bus/u4b/usbdi.h>
#include <bus/u4b/usb_process.h>

#define	HX_PORTS	4	/* == UMCS7840_MAX_PORTS (umcs.h:30) */
#define	HX_REQ_MS	500	/* == UMCS7840_CTRL_TIMEOUT (umcs.h:33) */
#define	HX_ERR_PAUSE	(hz / 16)	/* == usb_request.c:743 */

static struct lock hx_buslk;		/* stands in for sc->sc_lock */
static struct usb_process hx_busproc;	/* bus->non_giant_callback_proc */
static struct usb_process hx_ucomtq;	/* sc_super_ucom.sc_tq (fix path) */

struct hx_msg {
	struct usb_proc_msg hdr;
};
static struct hx_msg hx_evil[2];	/* intr-packet msg pair (bus proc) */
static struct hx_msg hx_victim[2];	/* other-device xfer msg (bus proc) */
static struct hx_msg hx_defer[2];	/* deferred status task (ucom tq) */

static int hx_victim_ran;		/* set by the victim callback */
static struct timeval hx_victim_queued;
static struct timeval hx_victim_ran_at;

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

/*
 * One umcs7840_get_UART_reg_sync() against an unresponsive device:
 * the "WXFER" poll sleep for the full 500 ms transfer timeout
 * (usb_request.c:642-644 with xfer->timeout=500 set at :557), then the
 * post-error hz/16 pause taken with the lock held (usb_request.c:743).
 */
static void
hx_one_timedout_request(void)
{
	lksleep(&hx_buslk, &hx_buslk, 0, "WXFER", USB_MS_TO_TICKS(HX_REQ_MS));
	usb_pause_mtx(&hx_buslk, HX_ERR_PAUSE);
}


/* umcs.c:745-771: the per-port loop of umcs7840_intr_callback, 4-port
 * device, all port bytes == 0x00 (MSCHANGE), EP0 never answers. */
static void
hx_evil_packet(struct usb_proc_msg *pm)
{
	int pn;

	for (pn = 0; pn < HX_PORTS; ++pn)
		hx_one_timedout_request();
	kprintf("HX5382: evil intr packet done (4 x 562.5 ms on the bus "
	    "callback thread)\n");
}

/* fix.diff pattern: intr callback only records pending bits and defers */
static uint8_t hx_status_pending;
static void hx_status_task_fixed(struct usb_proc_msg *pm);

static void
hx_evil_packet_fixed(struct usb_proc_msg *pm)
{
	struct hx_msg *m;

	hx_status_pending = (1 << HX_PORTS) - 1;
	/* umcs7840_queue_status_refresh(): msignal on the ucom super tq,
	 * allowed because the intr callback runs with sc->sc_lock held and
	 * sc_tq's up_lock IS sc->sc_lock (usb_serial.c:322). */
	m = usb_proc_msignal(&hx_ucomtq, &hx_defer[0], &hx_defer[1]);
	m->hdr.pm_callback = &hx_status_task_fixed;
	kprintf("HX5382: (fixed) intr packet: deferred, bus thread free\n");
}

/* deferred status task on the ucom tq: pays the sync requests there */
static void
hx_status_task_fixed(struct usb_proc_msg *pm)
{
	int pn;

	for (pn = 0; pn < HX_PORTS; ++pn)
		if (hx_status_pending & (1 << pn))
			hx_one_timedout_request();
	hx_status_pending = 0;
	kprintf("HX5382: (fixed) deferred status task done (4 x 562.5 ms on "
	    "the ucom taskqueue, NOT on the bus thread)\n");
}

/* any other USB device's transfer completing on this bus */
static void
hx_victim_cb(struct usb_proc_msg *pm)
{
	microtime(&hx_victim_ran_at);
	hx_victim_ran = 1;
	kprintf("HX5382: victim callback (other device's xfer) ran\n");
}

static struct hx_msg *
hx_signal(struct usb_process *up, struct hx_msg pair[2],
    usb_proc_callback_t fn)
{
	struct hx_msg *m;

	lockmgr(&hx_buslk, LK_EXCLUSIVE);
	m = usb_proc_msignal(up, &pair[0], &pair[1]);
	m->hdr.pm_callback = fn;
	lockmgr(&hx_buslk, LK_RELEASE);
	return (m);
}

static int64_t
hx_measure_packet(int fixed)
{
	int deadline;

	hx_victim_ran = 0;
	/* the crafted interrupt packet completes first */
	hx_signal(&hx_busproc, hx_evil,
	    fixed ? &hx_evil_packet_fixed : &hx_evil_packet);
	tsleep(&hx_buslk, 0, "hxgap", hz / 100 + 1);	/* 10 ms */

	/* 10 ms later some other device's transfer completes on this bus */
	microtime(&hx_victim_queued);
	lockmgr(&hx_buslk, LK_EXCLUSIVE);
	usb_proc_msignal(&hx_busproc, &hx_victim[0], &hx_victim[1]);
	hx_victim[0].hdr.pm_callback = &hx_victim_cb;
	hx_victim[1].hdr.pm_callback = &hx_victim_cb;
	lockmgr(&hx_buslk, LK_RELEASE);

	/* wait (bounded, 120 s) for the victim callback to actually execute */
	deadline = ticks + 120 * hz;
	while (!hx_victim_ran && (int)(ticks - deadline) < 0)
		tsleep(&hx_victim_ran, 0, "hxvic", hz / 20);
	if (!hx_victim_ran) {
		kprintf("HX5382: VICTIM NEVER RAN?!\n");
		return (-1);
	}
	return (hx_ms(&hx_victim_queued, &hx_victim_ran_at));
}

static int64_t hx_measure_stream_round(int fixed, int round);

static void
hx_phase(int fixed)
{
	int64_t d1, d5;
	int i;

	kprintf("HX5382[%c]: --- %s ---\n", fixed ? 'B' : 'A',
	    fixed ? "fix mechanism (deferred status task)" :
	    "baseline (production umcs.c:756/766)");

	/* single crafted packet */
	d1 = hx_measure_packet(fixed);
	kprintf("HX5382[%c]: victim delay after 1 packet = %jd ms "
	    "(expect ~2250 baseline / ~0 fixed)\n",
	    fixed ? 'B' : 'A', (intmax_t)d1);

	/*
	 * "Streaming": the evil device resubmits its interrupt pipe as soon
	 * as the previous packet's callback finishes (umcs.c:776-780), so it
	 * can deliver a new crafted packet immediately -- one every 2.25 s of
	 * processing, forever.  msignal() coalesces signals issued while the
	 * message is still queued (production usb_process.c:258-332), so the
	 * honest stream emulation is packet -> wait for completion -> next
	 * packet, with a victim queued 100 ms into each packet's processing.
	 * Every round MUST show the victim blocked ~2.15+ s.
	 */
	for (i = 1; i <= 3; ++i) {
		d5 = hx_measure_stream_round(fixed, i);
		kprintf("HX5382[%c]: stream round %d: victim delay = %jd ms\n",
		    fixed ? 'B' : 'A', i, (intmax_t)d5);
	}
}

static int64_t
hx_measure_stream_round(int fixed, int round)
{
	int deadline;

	hx_victim_ran = 0;
	hx_signal(&hx_busproc, hx_evil,
	    fixed ? &hx_evil_packet_fixed : &hx_evil_packet);
	tsleep(&hx_buslk, 0, "hxgap", hz / 10);	/* keystroke 100 ms later */

	microtime(&hx_victim_queued);
	lockmgr(&hx_buslk, LK_EXCLUSIVE);
	usb_proc_msignal(&hx_busproc, &hx_victim[0], &hx_victim[1]);
	hx_victim[0].hdr.pm_callback = &hx_victim_cb;
	hx_victim[1].hdr.pm_callback = &hx_victim_cb;
	lockmgr(&hx_buslk, LK_RELEASE);

	deadline = ticks + 120 * hz;
	while (!hx_victim_ran && (int)(ticks - deadline) < 0)
		tsleep(&hx_victim_ran, 0, "hxvic", hz / 20);
	if (!hx_victim_ran) {
		kprintf("HX5382: VICTIM NEVER RAN (stream)?!\n");
		return (-1);
	}
	return (hx_ms(&hx_victim_queued, &hx_victim_ran_at));
}

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

	switch (type) {
	case MOD_LOAD:
		lockinit(&hx_buslk, "hx5382", 0, LK_CANRECURSE);

		memset(&hx_busproc, 0, sizeof(hx_busproc));
		error = usb_proc_create(&hx_busproc, &hx_buslk,
		    "hx5382bus", USB_PRI_MED);
		if (error) {
			kprintf("HX5382: usb_proc_create(bus) failed: %d\n",
			    error);
			break;
		}
		memset(&hx_ucomtq, 0, sizeof(hx_ucomtq));
		error = usb_proc_create(&hx_ucomtq, &hx_buslk,
		    "hx5382tq", USB_PRI_MED);
		if (error) {
			kprintf("HX5382: usb_proc_create(tq) failed: %d\n",
			    error);
			break;
		}

		kprintf("HX5382: DF-5382 umcs intr-callback bus-freeze "
		    "harness, hz=%d, 4 ports, %d ms/request, errpause=%d ms\n",
		    hz, HX_REQ_MS, 1000 / 16);

		hx_phase(0);		/* A: baseline */
		hx_phase(1);		/* B: fix mechanism */

		usb_proc_free(&hx_ucomtq);
		usb_proc_free(&hx_busproc);
		lockuninit(&hx_buslk);
		kprintf("HX5382: harness complete\n");
		break;
	case MOD_UNLOAD:
		break;
	default:
		error = EOPNOTSUPP;
		break;
	}
	return (error);
}

static moduledata_t hx_mod = { "umcsbusfreeze", hx_modevent, 0 };
DECLARE_MODULE(umcsbusfreeze, hx_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_DEPEND(umcsbusfreeze, usb, 1, 1, 1);
