/*
 * DF-5437 KLD harness -- ulpt(4) open() can be stalled uninterruptibly for
 * ~2x the SOFT_RESET control-request timeout by an evil printer that NAKs
 * the data/status stage.
 *
 * Production chain modelled (constants and shapes verbatim):
 *
 *   open("/dev/ulpt0", O_RDONLY)			(world-openable, 0644)
 *     -> ulpt_open()      ulpt.c:406-418: sc_fflags==0 -> ulpt_reset(sc)
 *     -> ulpt_reset()     ulpt.c:177-187: TWO sequential
 *          usbd_do_request_flags(.., NULL, 0, NULL, 2 * USB_MS_HZ)
 *          (2 s timeout each -- ulpt.c:180,183)
 *     -> usbd_do_request_flags()  usb_request.c:402-708:
 *            xfer->timeout = timeout  (usb_request.c:557)
 *            usbd_transfer_start(xfer)
 *            while (usbd_transfer_pending(xfer)) {
 *                    lksleep(xfer, xfer->xroot->xfer_lock, 0,
 *                        "WXFER", hz);                    (:648-650)
 *            }
 *        The lksleep() has NO PCATCH -> a signal (incl. SIGKILL) cannot
 *        break it; only the xfer timeout (2 s per request) terminates the
 *        wait.  An evil device NAKing the control data stage stretches
 *        every open() to ~2x2 s of uninterruptible sleep; a loop of opens
 *        from an unprivileged user pins n kernel threads.
 *
 * Harness: /dev/hx5437ctl (0666).  Its d_open() runs the production chain
 * shape: two sequential "requests", each completed by a callout after
 * hw.hx5437.timeout_ms (default 2000 == 2*USB_MS_HZ), with the production
 * wait loop verbatim (lksleep flags=0, wmesg "WXFER", timo hz).
 *
 * fix.diff mechanism: bound the per-request timeout (USB_MS_HZ/4 = 250 ms)
 * -> sysctl hw.hx5437.timeout_ms=250 mirrors the patched driver.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/lock.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/callout.h>
#include <sys/sysctl.h>
#include <sys/fcntl.h>
#include <sys/time.h>

static struct lock	hx_lk;		/* xfer->xroot->xfer_lock stand-in */
static struct callout	hx_co;		/* ctrl xfer timeout stand-in */
static volatile int	hx_pending;	/* usbd_transfer_pending() stand-in */
static int		hx_timeout_ms = 2000;	/* 2 * USB_MS_HZ, ulpt.c:180 */

SYSCTL_NODE(_hw, OID_AUTO, hx5437, CTLFLAG_RW, 0, "DF-5437 harness");
SYSCTL_INT(_hw_hx5437, OID_AUTO, timeout_ms, CTLFLAG_RW, &hx_timeout_ms, 0,
    "per-request ctrl timeout (production 2000 = 2*USB_MS_HZ; fix 250)");

static void
hx_ctrl_timeout(void *arg __unused)
{
	/* the control xfer's timeout callout fires: usbd_transfer_done() */
	hx_pending = 0;
	wakeup(&hx_pending);
}

static int
hx_do_request_production(void)
{
	int ticks;

	/* usbd_do_request_flags() request path for one SOFT_RESET:
	 *   xfer->timeout = timeout (usb_request.c:557)
	 *   usbd_transfer_start(xfer)
	 *   while (usbd_transfer_pending(xfer))
	 *           lksleep(xfer, xfer_lock, 0, "WXFER", hz);   (:648-650)
	 */
	hx_pending = 1;
	ticks = (hx_timeout_ms * hz + 999) / 1000;
	if (ticks < 1)
		ticks = 1;
	callout_reset(&hx_co, ticks, hx_ctrl_timeout, NULL);

	lockmgr(&hx_lk, LK_EXCLUSIVE);
	while (hx_pending) {
		lksleep(&hx_pending, &hx_lk, 0, "WXFER", hz);
	}
	lockmgr(&hx_lk, LK_RELEASE);
	return (0);			/* USB_ERR_TIMEOUT == give up */
}

static d_open_t	hx_open;

static struct dev_ops hx_ops = {
	{ "hx5437ctl", 0, D_MPSAFE },		/* production: usb_ops (D_MPSAFE) */
	.d_open = hx_open,
};

static cdev_t hx_dev;

static int
hx_open(struct dev_open_args *ap __unused)
{
	struct timespec ts0, ts1;
	int err1, err2;

	getnanouptime(&ts0);

	/* ulpt_reset(): ulpt.c:179-183 -- try 1.0 spec, then 1.1 spec */
	err1 = hx_do_request_production();
	err2 = hx_do_request_production();

	getnanouptime(&ts1);
	kprintf("HX5437: open() complete: ELAPSED=%ld ms "
	    "(req1=%d req2=%d, timeout_ms=%d)\n",
	    (ts1.tv_sec - ts0.tv_sec) * 1000 +
	    (ts1.tv_nsec - ts0.tv_nsec) / 1000000,
	    err1, err2, hx_timeout_ms);
	return (0);
}

static int
hx5437_modevent(module_t mod, int type, void *arg __unused)
{
	switch (type) {
	case MOD_LOAD:
		lockinit(&hx_lk, "hx5437lk", 0, 0);
		callout_init(&hx_co);
		hx_dev = make_dev(&hx_ops, 0, UID_ROOT, GID_WHEEL,
		    0666, "hx5437ctl");
		kprintf("HX5437: loaded (timeout_ms=%d, production 2*USB_MS_HZ)"
		    "\n", hx_timeout_ms);
		return (0);
	case MOD_UNLOAD:
		destroy_dev(hx_dev);
		callout_stop(&hx_co);
		lockuninit(&hx_lk);
		kprintf("HX5437: unloaded\n");
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t hx5437_mod = {
	"hx5437stall",
	hx5437_modevent,
	NULL
};
DECLARE_MODULE(hx5437stall, hx5437_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
