/*
 * DF-5505 KLD harness -- production-code demonstration of the
 * ucycom_attach() per-bus USB explore-thread stall via the HID report
 * descriptor fetch.
 *
 * EVIDENCE CLASS: KLD-harness-with-production-code (DF-5488 class, no
 * Cypress/Earthmate device model on the guest):
 *   - usb_hid.c:801-843 usbd_req_get_hid_desc()  -- VERBATIM (extracted
 *     at build time from the guest's own /usr/src, md5-gated).  This is
 *     the core function ucycom_attach() calls at ucycom.c:231-233,
 *     synchronously, in newbus attach context on the per-bus USB
 *     explore (hub event) thread.
 *   - ucycom.c:229-254 -- VERBATIM: the report-descriptor fetch, its
 *     error path, the three hid_report_size() calls and the report-size
 *     validation of ucycom_attach(), running against the REAL kernel
 *     hid_report_size() (usb_hid.c:558) with a synthetic HID report
 *     descriptor.
 *   - Interposed (evil-device models only): usbd_req_get_report_descriptor
 *     (usb_request.c:1877-1893 -- the EP0 GET_DESCRIPTOR(REPORT) whose
 *     `usbd_do_request' call at usb_request.c:1892 carries the default
 *     USB_DEFAULT_TIMEOUT = 5000 ms, usbdi.h:87/511-512),
 *     usbd_get_iface / usbd_get_config_descriptor /
 *     hid_get_descriptor_from_usb (returning a fake 21-byte HID
 *     interface), and device_printf (kprintf stand-in).
 *
 * WHAT IT PROVES:
 *   An evil DeLorme Earthmate (the only VID:PID in ucycom_devs[],
 *   ucycom.c:177-179) that NAKs the GET_DESCRIPTOR(REPORT) data stage
 *   pins the USB explore thread that is running ucycom_attach() for the
 *   FULL core timeout: ~5 s per insertion, bus-wide (no other device on
 *   that bus can enumerate: DF-5359/DF-5407/DF-5488 mechanism, ucycom
 *   instance -- the smallest member of the family: exactly ONE request).
 *   A device that answers at just-under-timeout makes the request
 *   SUCCEED and the chain continue (worst case).
 *
 * PHASES:
 *   A  evil answers at 4900 ms (just under the 5000 ms core timeout):
 *      occupancy ~= 4900 ms, attach continues into the verbatim
 *      report-size block (validation passes: i=27 o=27 f=5).
 *   B  evil never answers: occupancy ~= 5000 ms (USB_DEFAULT_TIMEOUT),
 *      USB_ERR_TIMEOUT -> verbatim error path (ucycom.c:235-240) ->
 *      goto detach (ucycom.c:279-284).
 *   C  fix modeled (usb_request.c:1892 timeout bounded 5000 -> 1000 ms,
 *      fix.diff): occupancy ~= 1000 ms, request times out first ->
 *      attach fails fast instead of pinning the explore thread 5 s.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/lock.h>
#include <sys/condvar.h>		/* struct cv (usb_process.h) */
#include <sys/time.h>
#include <sys/types.h>
#include <sys/bus.h>
#include <sys/malloc.h>		/* kmalloc/kfree for the verbatim core fn */

#include <bus/u4b/usb.h>
#include <bus/u4b/usbdi.h>
#include <bus/u4b/usbdi_util.h>
#include <bus/u4b/usb_process.h>	/* struct usb_process (usb_serial.h embeds it) */
#include <bus/u4b/usbhid.h>
#include <bus/u4b/serial/usb_serial.h>	/* struct ucom_softc: embedded by value in ucycom_softc */

/*=====================================================================
 * VERBATIM production code (extracted by build.sh / extract.sh from the
 * guest's /usr/src tree, md5-gated):
 *   ucycom.c:62-101,181-182   constants + struct ucycom_softc
 *   usb_hid.c:801-843         usbd_req_get_hid_desc
 *=====================================================================*/
#include "ua5505_ucycom_defs.inc"
#include "ua5505_gethiddesc.inc"

/*=====================================================================
 * Interposed evil-device / fake-descriptor plumbing.  Only these stand
 * between the verbatim code above and the hardware.
 *=====================================================================*/

/* synthetic 21-byte HID report descriptor: ilen=27, olen=27, flen=5 */
static const uint8_t uc_report_desc[] = {
	0x05, 0x01,		/* Usage Page (Generic Desktop) */
	0x09, 0x06,		/* Usage (Keyboard) */
	0xA1, 0x01,		/* Collection (Application) */
	0x75, 0x08,		/*   Report Size (8) */
	0x95, 0x1B,		/*   Report Count (27) */
	0x81, 0x00,		/*   Input  -> ilen = 27 */
	0x95, 0x1B,		/*   Report Count (27) */
	0x91, 0x00,		/*   Output -> olen = 27 */
	0x95, 0x05,		/*   Report Count (5) */
	0xB1, 0x02,		/*   Feature -> flen = 5 */
	0xC0			/* End Collection */
};

static struct usb_interface_descriptor uc_idesc = {
	.bInterfaceNumber = 0,
	.bInterfaceClass = UICLASS_HID,
	.bLength = sizeof(struct usb_interface_descriptor),
	.bDescriptorType = UDESC_INTERFACE,
};
static struct usb_interface uc_iface = {
	.idesc = &uc_idesc,
};
static struct usb_hid_descriptor uc_hiddesc = {
	.bLength = sizeof(struct usb_hid_descriptor),
	.bDescriptorType = UDESC_HID,		/* HID class descriptor */
	.descrs = {
		{
		 .bDescriptorType = UDESC_REPORT,
		},
	},
};

/* evil-device + fix-model knobs */
static int ua_mode;			/* 0: silent, 1: NAK 4.9 s then answer */
static int ua_cap_ms = 5000;		/* core timeout at usb_request.c:1892 */
static struct lock ua_lock;
static int ua_reqs;

/* fake descriptor lookups (real ones need a live usb_device) */
struct usb_interface *
usbd_get_iface(struct usb_device *udev, uint8_t iface_index)
{
	return (&uc_iface);
}

struct usb_config_descriptor *
usbd_get_config_descriptor(struct usb_device *udev)
{
	return ((struct usb_config_descriptor *)1);	/* opaque to the fake */
}

struct usb_hid_descriptor *
hid_get_descriptor_from_usb(struct usb_config_descriptor *cd,
    struct usb_interface_descriptor *id)
{
	USETW(uc_hiddesc.descrs[0].wDescriptorLength,
	    sizeof(uc_report_desc));
	return (&uc_hiddesc);
}

/*
 * The EP0 GET_DESCRIPTOR(REPORT) the verbatim usbd_req_get_hid_desc
 * issues: real code at usb_request.c:1876-1893, whose terminal call is
 * `return (usbd_do_request(udev, lock, &req, d));' -- i.e. 5000 ms
 * (USB_DEFAULT_TIMEOUT) unless fix.diff bounds it.  The model sleeps
 * min(evil_delay, cap) and answers or times out accordingly.
 */
usb_error_t
usbd_req_get_report_descriptor(struct usb_device *udev, struct lock *lock,
    void *d, uint16_t size, uint8_t iface_index)
{
	int delay = ua_mode ? 4900 : ua_cap_ms;
	int slp = (delay < ua_cap_ms) ? delay : ua_cap_ms;

	ua_reqs++;
	kprintf("UA5505:   EP0 GET_DESCRIPTOR(REPORT) wLength=%d, evil=%s, "
	    "core timeout cap=%d ms (usb_request.c:1892)\n",
	    size, ua_mode ? "NAK 4.9 s then answer" : "silent",
	    ua_cap_ms);
	if (slp > 0) {
		/* production requester sleeps in the WXFER loop holding the
		 * device lock (released while asleep, reacquired on wake) */
		lockmgr(&ua_lock, LK_EXCLUSIVE);
		lksleep(&ua_reqs, &ua_lock, 0, "WXFER",
		    USB_MS_TO_TICKS(slp));
		lockmgr(&ua_lock, LK_RELEASE);
	}
	if (ua_mode && (4900 < ua_cap_ms)) {
		/* answered just under the timeout: SUCCESS */
		memcpy(d, uc_report_desc,
		    (size < sizeof(uc_report_desc)) ? size :
		    sizeof(uc_report_desc));
		return (0);
	}
	/* timeout fired first (or silent device) */
	return (USB_ERR_TIMEOUT);
}

/* device_printf stand-in (real one needs a live device_t) */
int
device_printf(device_t dev, const char *fmt, ...)
{
	__va_list ap;

	__va_start(ap, fmt);
	kvprintf(fmt, ap);
	__va_end(ap);
	return (0);
}

/*=====================================================================
 * Harness driver: run the VERBATIM ucycom_attach() EP0 block
 * (ucycom.c:229-254) as the explore thread would, and time it.
 *=====================================================================*/
static struct ucycom_softc ua_sc;
static struct usb_attach_arg ua_uaa;

static int64_t
ua_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 int
ua_attach_run(char tag, const char *name, int mode, int cap_ms)
{
	/* locals named exactly as ucycom_attach() declares them
	 * (ucycom.c:204-209) so the verbatim block binds */
	struct usb_attach_arg *uaa = &ua_uaa;
	struct ucycom_softc *sc = &ua_sc;
	device_t dev = NULL;
	struct timeval t0, t1;
	void *urd_ptr = NULL;
	int32_t error;
	uint16_t urd_len;
	int rv;

	ua_mode = mode;
	ua_cap_ms = cap_ms;
	ua_reqs = 0;

	kprintf("UA5505[%c]: === phase %s (explore-thread insertion of the "
	    "evil Earthmate)\n", tag, name);

	microtime(&t0);
#include "ua5505_attach.inc"		/* VERBATIM ucycom.c:229-254 */
	microtime(&t1);
	rv = 0;
	goto done;
detach:
	microtime(&t1);
	rv = ENXIO;
	if (urd_ptr)				/* ucycom.c:280-282 */
		kfree(urd_ptr, M_USBDEV);
done:
	kprintf("UA5505[%c]: attach %s after %jd ms of explore-thread "
	    "occupancy (%d EP0 request(s)); report sizes i=%d o=%d f=%d\n",
	    tag, rv ? "ABORT (ucycom.c:279-284 detach path)" :
	    "CONTINUES (ucycom.c:255+)", (intmax_t)ua_ms(&t0, &t1),
	    ua_reqs, ua_sc.sc_ilen, ua_sc.sc_olen, ua_sc.sc_flen);
	return (rv);
}

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

	switch (type) {
	case MOD_LOAD:
		lockinit(&ua_lock, "ua5505", 0, 0);
		memset(&ua_sc, 0, sizeof(ua_sc));	/* M_ZERO softc */
		memset(&ua_uaa, 0, sizeof(ua_uaa));
		ua_sc.sc_model = MODEL_CY7C64013;	/* Earthmate */
		ua_reqs = 0;

		kprintf("UA5505: DF-5505 ucycom_attach explore-thread stall "
		    "harness (verbatim usbd_req_get_hid_desc + verbatim "
		    "ucycom.c:229-254); USB_DEFAULT_TIMEOUT=%d ms\n",
		    USB_DEFAULT_TIMEOUT);

		ua_attach_run('A', "A (evil answers at 4900 ms)", 1, 5000);
		ua_attach_run('B', "B (evil silent: full core timeout)",
		    0, 5000);
		ua_attach_run('C',
		    "C (fix: core timeout bounded to 1000 ms)", 1, 1000);

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

static moduledata_t ua_mod = { "ucycomattachstall", ua_modevent, 0 };
DECLARE_MODULE(ucycomattachstall, ua_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_DEPEND(ucycomattachstall, usb, 1, 1, 1);
