⬢ DragonFlyBSD Kernel Audit
DF-5517 / ub5517clearbreak_harness.c
← back to finding ↓ download raw
/*
 * DF-5517 KLD harness -- production-code demonstration that
 * ubser_cfg_set_break() can NEVER clear a break condition:
 * VENDOR_CLEAR_BREAK (ubser.c:102) is referenced nowhere in the driver
 * and ubser_cfg_set_break() only issues a request when `onoff' is set
 * (ubser.c:470-486).  The clear leg of TIOCCBRK, of ucom_cfg_line_state
 * (usb_serial.c:1401-1420) and of port close is a silent no-op: once a
 * break is set on a BWCT port the device's line stays broken until the
 * adapter is power-cycled, and the kernel never even attempts to tell
 * it otherwise.
 *
 * Rig (DF-5447 production-include class + the DF-5515 usb_process rig):
 * the UNMODIFIED production ubser.c is #included (newbus registration
 * neutered); the REAL static ubser_cfg_set_break() runs on a REAL
 * usb_process taskqueue (usb_proc_create, usb_serial.c:322); only the
 * EP0 sink (usbd_do_request_proc -- the expansion of the
 * ucom_cfg_do_request macro, usb_serial.h:220-221) is modeled, and it
 * only RECORDS the bRequest values it is asked to send.
 *
 * PHASE L (lifecycle): open -> TIOCSBRK -> TIOCCBRK -> close, exactly
 *   the tasks ucom queues for them (usb_serial.c:842-880, 1190-1197,
 *   1356-1421, 671-672).  BASELINE EXPECT: request sequence = { 0x02
 *   (VENDOR_SET_BREAK) } -- exactly ONE request for the entire
 *   lifecycle; the clear leg issues NOTHING; 0x03
 *   (VENDOR_CLEAR_BREAK) is NEVER observed.
 * PHASE D (direct): the real ubser_cfg_set_break(ucom, 0) called
 *   directly: issues NOTHING (the `if (onoff)' gate).
 * FIXED BUILD (fix.diff adds the else-leg sending VENDOR_CLEAR_BREAK):
 *   PHASE L EXPECT = { 0x02, 0x03 } and direct clear = 0x03.
 */
#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>

/* 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(ub5517clrbrk, usb, 1, 1, 1);
MODULE_DEPEND(ub5517clrbrk, ucom, 1, 1, 1);

#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"

static struct ubser_softc ub_sc;

/* recorded request sequence */
static char ub_seq[32];
static int ub_seq_n;

static void
ub_record(struct usb_device_request *req)
{

	if (ub_seq_n < (int)sizeof(ub_seq))
		ub_seq[ub_seq_n++] = (char)req->bRequest;
}

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)
{

	ub_record(req);
	kprintf("UB5517: EP0 SENT bRequest=0x%02x wValue=0x%02x "
	    "(%s)%s\n", req->bRequest, req->wValue[0],
	    req->bRequest == VENDOR_SET_BREAK ? "VENDOR_SET_BREAK" :
	    req->bRequest == VENDOR_CLEAR_BREAK ? "VENDOR_CLEAR_BREAK" :
	    "other", timeout == 1000 ? "" : " (timeout changed?)");
	return (USB_ERR_NORMAL_COMPLETION);
}

struct ub_msg {
	struct usb_proc_msg hdr;
};
static struct ub_msg ub_open_msg[2];
static struct ub_msg ub_line_msg[2];
static struct ub_msg ub_close_msg[2];

static int ub_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 */

static void
ub_cfg_open_task(struct usb_proc_msg *pm)
{

	ub_ll_ready = 1;
}

/* ucom_cfg_line_state (usb_serial.c:1356-1421), BREAK-only mask,
 * 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 */

	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;

	if (notch_bits & UB_LS_BREAK)
		ubser_cfg_set_break(&ub_sc.sc_ucom[0],
		    (prev_value & UB_LS_BREAK) ? 1 : 0);
	if (any_bits & UB_LS_BREAK)
		ubser_cfg_set_break(&ub_sc.sc_ucom[0],
		    (last_value & UB_LS_BREAK) ? 1 : 0);
}

static void
ub_cfg_close_task(struct usb_proc_msg *pm)
{
	/* no ucom_cfg_close callback in ubser: issues nothing
	 * (usb_serial.c:915-921) */
}

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);
}

static void
ub_seq_dump(const char *what)
{
	int i, cleared = 0;

	kprintf("UB5517: %s request sequence = { ", what);
	for (i = 0; i < ub_seq_n; i++) {
		kprintf("%s0x%02x", i ? ", " : "",
		    (unsigned char)ub_seq[i]);
		if ((unsigned char)ub_seq[i] == VENDOR_CLEAR_BREAK)
			cleared = 1;
	}
	kprintf(" } (%d request(s); VENDOR_CLEAR_BREAK=0x03 %s)\n",
	    ub_seq_n, cleared ? "OBSERVED" : "NEVER OBSERVED");
}

static void
ub_touch_statics(void)
{

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

static void
ub_setup(void)
{

	memset(&ub_sc, 0, sizeof(ub_sc));
	lockinit(&ub_sc.sc_lock, "ubser", 0, LK_CANRECURSE);
	ub_sc.sc_numser = 1;
	ub_sc.sc_udev = (void *)&ub_seq_n;	/* 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_seq_n = 0;
}

static void
ub_teardown(void)
{

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

static int
ub_lifecycle(char tag)
{

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

	kprintf("UB5517[%c]: lifecycle open -> TIOCSBRK -> TIOCCBRK -> "
	    "close on the real production callback\n", tag);

	/* open(2): cfg_open sets LL_READY (0 requests) */
	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_seq, 0, "ubopen", hz / 10);

	ub_user_break(1);			/* TIOCSBRK  (set)   */
	tsleep(&ub_seq, 0, "ubsbrk", hz / 10);
	ub_user_break(0);			/* TIOCCBRK  (clear) */
	tsleep(&ub_seq, 0, "ubcbrk", hz / 10);

	/* close(2): synchronous mwait (usb_serial.c:671-672) */
	lockmgr(&ub_sc.sc_lock, LK_EXCLUSIVE);
	{
		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);

	kprintf("UB5517[%c]: lifecycle complete\n", tag);

	ub_teardown();
	return (0);
}

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

	switch (type) {
	case MOD_LOAD:
		kprintf("UB5517: DF-5517 ubser CLEAR_BREAK harness; "
		    "VENDOR_SET_BREAK=0x%02x VENDOR_CLEAR_BREAK=0x%02x "
		    "(ubser.c:101-102)\n", VENDOR_SET_BREAK,
		    VENDOR_CLEAR_BREAK);
		ub_touch_statics();

		/* PHASE L: full lifecycle */
		ub_lifecycle('L');
		ub_seq_dump("PHASE L lifecycle");

		/* PHASE D: direct set / clear on a LIVE port (tq up, so
		 * the sink's usb_proc_is_gone gate, usb_request.c:771,
		 * passes exactly as it would in production) */
		ub_setup();
		if (usb_proc_create(&ub_sc.sc_super_ucom.sc_tq,
		    &ub_sc.sc_lock, "ucom", USB_PRI_MED) != 0) {
			kprintf("UB5517: usb_proc_create failed\n");
			break;
		}
		kprintf("UB5517[D]: direct production calls: "
		    "set_break(1); set_break(0)\n");
		ubser_cfg_set_break(&ub_sc.sc_ucom[0], 1);
		ubser_cfg_set_break(&ub_sc.sc_ucom[0], 0);
		ub_seq_dump("PHASE D direct set+clear");
		ub_teardown();
		kprintf("UB5517: harness complete\n");
		break;
	case MOD_UNLOAD:
		break;
	default:
		error = EOPNOTSUPP;
		break;
	}
	return (error);
}

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