/*
 * DF-5487 KLD harness -- production-machinery demonstration of the uvisor
 * (Handspring Visor / Palm USB serial, ucom core) cfg-close synchronous-
 * request stall.  Same evidence class as the proven family findings
 * DF-5367/5383/5421/5424/5425/5449/5456/5476.
 *
 * WHAT IT PROVES (with the real in-kernel u4b usb_process machinery, the
 * same taskqueue ucom's cfg callbacks run on):
 *
 * uvisor has the smallest cfg surface in the family (uvisor.c:224-231):
 * NO ucom_cfg_param, NO get_status, NO set_dtr/set_rts/set_break, and
 * uvisor_cfg_open() is EMPTY (uvisor.c:519-523).  So the open(2) chain
 * costs only the core hz/10 pause (usb_serial.c:777) and ZERO requests,
 * and tcsetattr costs nothing (ucom_cfg_param returns early when the
 * driver callback is NULL, usb_serial.c:1623-1625 -- before the pause).
 *
 * The ONLY EP0 request in the whole cfg path is uvisor_cfg_close()'s
 * CLOSE_NOTIFICATION (uvisor.c:529-545):
 *
 *   ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom, &req, buffer, 0, 1000)
 *     = usbd_do_request_proc(udev, &sc_super->sc_tq, req, ptr, 0, NULL,
 *                            timo=1000)                    (usb_serial.h:220-221)
 *
 * whose usb_error_t is DISCARDED (DPRINTFN only, uvisor.c:541-544).
 *
 * close(2) is SYNCHRONOUS against that queue:
 *   ucom_close() (usb_serial.c:939-978)
 *     -> ucom_shutdown() HUPCL -> ucom_modem(0, SER_DTR)
 *        (line-state task: uvisor has no callbacks -> 0 requests)
 *     -> ucom_queue_command(ucom_cfg_close) -> usb_proc_mwait()
 *        (usb_serial.c:671-672, 955-957), UNINTERRUPTIBLE
 *        -> uvisor_cfg_close(): 1 x 1000 ms vendor control-IN
 *
 * An evil Visor-VID:PID device (HANDSPRING 082d:0100 etc., uvisor.c:254)
 * that NAKs the CLOSE_NOTIFICATION data stage makes the request eat its
 * full 1000 ms timeout (usb_request.c:642-644 "WXFER" lksleep loop,
 * 699-704) => every close(2) blocks ~1 s, SIGKILL-immune; unplug
 * mid-cycle stalls the per-bus USB hub event thread the same way
 * (uvisor_detach -> ucom_detach -> usb_proc_drain, usb_serial.c:394),
 * plus the per-device enum lock every request holds (usb_request.c:467).
 *
 * PHASE A (baseline): closer queues behind the open chain's hz/10 pause
 *   and then waits out its own 1 x 1000 ms cfg-close request.
 *   EXPECT ~1100 ms.
 * PHASE B (fix mechanism from fix.diff: bound the cfg-path control
 *   timeout to 250 ms): EXPECT ~350 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_OPEN_REQS	0	/* uvisor_cfg_open() is empty (uvisor.c:519-523) */
#define	HX_CLOSE_REQS	1	/* uvisor.c:539-540 (CLOSE_NOTIFICATION) */
#define	HX_REQ_MS	1000	/* uvisor.c:540 production timeout */
#define	HX_REQ_MS_FIXED	250	/* fix.diff bounds the cfg-path timeout */

static struct lock hx_lock;		/* stands in for sc->sc_lock */
static struct usb_process hx_tq;	/* sc_super_ucom.sc_tq (usb_serial.c:322) */

struct hx_msg {
	struct usb_proc_msg hdr;
};
static struct hx_msg hx_open_msg[2];	/* ucom sc_open_task[2] */
static struct hx_msg hx_line_msg[2];	/* ucom sc_line_state_task[2]:
						 * open: DTR|RTS (usb_serial.c:874)
						 * + BREAK-clear (:878);
						 * close: HUPCL drop (:694-696) */
static struct hx_close_msg {
	struct usb_proc_msg hdr;
} hx_close[2];				/* ucom sc_close_task[2] */

static int hx_fixed;			/* phase B: fix mechanism */
static int hx_req_ms;

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 uvisor_cfg_close() against the silent evil Visor clone: the device
 * never answers the vendor data stage, so the requester sleeps in the
 * "WXFER" loop until the timeout fires (usb_request.c:642-644, 699-704)
 * and the request returns USB_ERR_TIMEOUT -- which production uvisor
 * discards (uvisor.c:541-544, DPRINTFN only).
 */
static int
hx_one_request(void)
{
	lksleep(&hx_lock, &hx_lock, 0, "WXFER", USB_MS_TO_TICKS(hx_req_ms));
	return (0);	/* production: even a timeout is ignored */
}

/* open(2) task chain on sc_tq: ucom_cfg_open -> uvisor_cfg_open() (EMPTY,
 * uvisor.c:519-523) then the core hz/10 pause (usb_serial.c:777) */
static void
hx_cfg_open(struct usb_proc_msg *pm)
{

	kprintf("HX5487: cfg-open task: uvisor_cfg_open() is EMPTY "
	    "(uvisor.c:519-523): 0 EP0 requests + core hz/10 pause "
	    "(usb_serial.c:777)\n");
	usb_pause_mtx(&hx_lock, hz / 10);
	kprintf("HX5487: cfg-open task: leave\n");
}

/* line-state tasks (open DTR|RTS + BREAK-clear, close HUPCL drop):
 * uvisor_callback has NO line callbacks (uvisor.c:224-231) -> 0 requests */
static void
hx_cfg_line(struct usb_proc_msg *pm)
{

	kprintf("HX5487: line-state task: uvisor has no set_dtr/set_rts/"
	    "set_break callbacks (uvisor.c:224-231): 0 EP0 requests\n");
}

/* uvisor_cfg_close(): 1 x CLOSE_NOTIFICATION vendor control-IN,
 * uvisor.c:539-540, timeout 1000 ms, error discarded (541-544) */
static void
hx_cfg_close(struct usb_proc_msg *pm)
{

	kprintf("HX5487: cfg-close task: enter, 1 x %d ms CLOSE_NOTIFICATION "
	    "vendor control-IN (uvisor.c:539-540), error DISCARDED "
	    "(uvisor.c:541-544)\n", hx_req_ms);
	hx_one_request();
	kprintf("HX5487: cfg-close task: leave (timeout ignored)\n");
}

/*
 * Exactly ucom_close()'s synchronous close (usb_serial.c:671-672 +
 * 955-957): HUPCL line-state task (0 requests for uvisor), then queue
 * cfg_close behind everything and mwait UNINTERRUPTIBLY
 * (usb_process.c cv_wait).
 */
static int64_t
hx_closer_thread(char tag)
{
	struct timeval t0, t1;

	/* ~50 ms into the open chain, like a real close() racing the queue */
	tsleep(&hx_tq, 0, "hxlead", hz / 20);

	kprintf("HX5487[%c]: closer: HUPCL line task (0 reqs) + queue "
	    "cfg_close + usb_proc_mwait() (uninterruptible, "
	    "usb_serial.c:671-672) behind the open chain\n", tag);
	microtime(&t0);
	lockmgr(&hx_lock, LK_EXCLUSIVE);
	if (!usb_proc_is_gone(&hx_tq)) {
		struct hx_msg *m;
		struct hx_close_msg *c;

		/* ucom_shutdown() -> ucom_modem(0, SER_DTR): 0 requests */
		m = usb_proc_msignal(&hx_tq, &hx_line_msg[0], &hx_line_msg[1]);
		m->hdr.pm_callback = &hx_cfg_line;

		/* usb_serial.c:671-672 */
		c = usb_proc_msignal(&hx_tq, &hx_close[0], &hx_close[1]);
		c->hdr.pm_callback = &hx_cfg_close;
		usb_proc_mwait(&hx_tq, &hx_close[0], &hx_close[1]);
	}
	lockmgr(&hx_lock, LK_RELEASE);
	microtime(&t1);
	kprintf("HX5487[%c]: closer: unblocked  ELAPSED=%jd ms\n",
	    tag, (intmax_t)hx_ms(&t0, &t1));
	return (hx_ms(&t0, &t1));
}

/*
 * The open(2) path: ucom_open() queues ucom_cfg_open first
 * (usb_serial.c:865-867), then the line-state tasks (874-878).
 */
static void
hx_open(void)
{
	struct hx_msg *m;

	lockmgr(&hx_lock, LK_EXCLUSIVE);
	m = usb_proc_msignal(&hx_tq, &hx_open_msg[0], &hx_open_msg[1]);
	m->hdr.pm_callback = &hx_cfg_open;
	/* line-state tasks: DTR|RTS then BREAK-clear -- 0 requests each */
	m = usb_proc_msignal(&hx_tq, &hx_line_msg[0], &hx_line_msg[1]);
	m->hdr.pm_callback = &hx_cfg_line;
	m = usb_proc_msignal(&hx_tq, &hx_line_msg[0], &hx_line_msg[1]);
	m->hdr.pm_callback = &hx_cfg_line;
	lockmgr(&hx_lock, LK_RELEASE);
}

static int
hx_phase(int phase)
{
	int error;

	memset(&hx_tq, 0, sizeof(hx_tq));
	error = usb_proc_create(&hx_tq, &hx_lock,
	    phase == 2 ? "hx5487b" : "hx5487a", USB_PRI_MED);
	if (error) {
		kprintf("HX5487: usb_proc_create failed: %d\n", error);
		return (error);
	}
	hx_fixed = (phase == 2);
	hx_req_ms = hx_fixed ? HX_REQ_MS_FIXED : HX_REQ_MS;

	/* the user opens the tty (0-request open chain + hz/10 pause) */
	hx_open();

	/* the closing thread (or the hub event thread on unplug) */
	hx_closer_thread(phase == 2 ? 'B' : 'A');

	usb_proc_free(&hx_tq);
	return (0);
}

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

	switch (type) {
	case MOD_LOAD:
		lockinit(&hx_lock, "hx5487", 0, LK_CANRECURSE);

		kprintf("HX5487: DF-5487 uvisor cfg-close stall harness, "
		    "hz=%d, %d reqs/open-chain, %d reqs/close-chain, "
		    "%d ms/request\n",
		    hz, HX_OPEN_REQS, HX_CLOSE_REQS, hx_req_ms);

		hx_phase(1);		/* A: close ~1.1 s */
		hx_phase(2);		/* B: fix mechanism ~0.35 s */

		lockuninit(&hx_lock);
		kprintf("HX5487: harness complete\n");
		break;
	case MOD_UNLOAD:
		break;
	default:
		error = EOPNOTSUPP;
		break;
	}
	return (error);
}

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