Module Name:    src
Committed By:   maxv
Date:           Sat Sep 14 06:57:52 UTC 2019

Modified Files:
        src/etc: MAKEDEV.tmpl
        src/sys/arch/amd64/conf: GENERIC
        src/sys/conf: files majors
Added Files:
        src/sys/dev/usb: vhci.c

Log Message:
Add vHCI, a driver which allows to send and receive USB packets directly
from userland via /dev/vhci. Using this, it becomes possible to test and
fuzz the USB stack and all the USB drivers without having the associated
hardware.

The vHCI device has four ports independently addressable.

For each xfer on each port, we create two packets: a setup packet (which
indicates mostly the type of request) and a data packet (which contains
the raw data). These packets are processed by read and write operations
on /dev/vhci: userland poll-reads it to fetch usb_device_request_t
structures, and dispatches the requests depending on bRequest and
bmRequestType.

A few ioctls are available:

        VHCI_IOC_GET_INFO   - Get the current status
        VHCI_IOC_SET_PORT   - Choose a vHCI port
        VHCI_IOC_USB_ATTACH - Attach a USB device on the current port
        VHCI_IOC_USB_DETACH - Detach the USB device on the current port

vHCI has already allowed me to automatically find several bugs in the USB
stack and its drivers.


To generate a diff of this commit:
cvs rdiff -u -r1.205 -r1.206 src/etc/MAKEDEV.tmpl
cvs rdiff -u -r1.534 -r1.535 src/sys/arch/amd64/conf/GENERIC
cvs rdiff -u -r1.1238 -r1.1239 src/sys/conf/files
cvs rdiff -u -r1.87 -r1.88 src/sys/conf/majors
cvs rdiff -u -r0 -r1.1 src/sys/dev/usb/vhci.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/etc/MAKEDEV.tmpl
diff -u src/etc/MAKEDEV.tmpl:1.205 src/etc/MAKEDEV.tmpl:1.206
--- src/etc/MAKEDEV.tmpl:1.205	Mon Jul 29 17:53:20 2019
+++ src/etc/MAKEDEV.tmpl	Sat Sep 14 06:57:52 2019
@@ -1,5 +1,5 @@
 #!/bin/sh -
-#	$NetBSD: MAKEDEV.tmpl,v 1.205 2019/07/29 17:53:20 gdt Exp $
+#	$NetBSD: MAKEDEV.tmpl,v 1.206 2019/09/14 06:57:52 maxv Exp $
 #
 # Copyright (c) 2003,2007,2008 The NetBSD Foundation, Inc.
 # All rights reserved.
@@ -286,6 +286,7 @@
 #	twe	3ware Escalade control interface
 #	uk*	unknown SCSI device
 #	veriexec Veriexec fingerprint loader
+#	vhci	virtual host controller interface
 #	video*	video capture devices
 #	view*	generic interface to graphic displays (Amiga)
 #	wsfont*	console font control
@@ -1792,6 +1793,10 @@ veriexec)
 	mkdev veriexec c %veriexec_chr% 0 600
 	;;
 
+vhci)
+	mkdev vhci c %vhci% 0
+	;;
+
 ttyv[0-9]*)
 	unit=${i#ttyv}
 	mkdev ttyv$unit c %pc_chr% $unit

Index: src/sys/arch/amd64/conf/GENERIC
diff -u src/sys/arch/amd64/conf/GENERIC:1.534 src/sys/arch/amd64/conf/GENERIC:1.535
--- src/sys/arch/amd64/conf/GENERIC:1.534	Fri Aug  9 08:01:06 2019
+++ src/sys/arch/amd64/conf/GENERIC	Sat Sep 14 06:57:51 2019
@@ -1,4 +1,4 @@
-# $NetBSD: GENERIC,v 1.534 2019/08/09 08:01:06 rin Exp $
+# $NetBSD: GENERIC,v 1.535 2019/09/14 06:57:51 maxv Exp $
 #
 # GENERIC machine description file
 #
@@ -22,7 +22,7 @@ include 	"arch/amd64/conf/std.amd64"
 
 options 	INCLUDE_CONFIG_FILE	# embed config file in kernel binary
 
-#ident		"GENERIC-$Revision: 1.534 $"
+#ident		"GENERIC-$Revision: 1.535 $"
 
 maxusers	64		# estimated number of users
 
@@ -890,6 +890,9 @@ urlphy* at mii? phy ?			# Realtek RTL815
 
 # USB Controller and Devices
 
+# Virtual USB controller
+#pseudo-device	vhci
+
 # PCI USB controllers
 xhci*	at pci?	dev ? function ?	# eXtensible Host Controller
 ehci*	at pci?	dev ? function ?	# Enhanced Host Controller
@@ -908,6 +911,7 @@ uhci*	at cardbus? function ?		# Universa
 slhci*	at pcmcia? function ?		# ScanLogic SL811HS
 
 # USB bus support
+#usb*	at vhci?
 usb*	at xhci?
 usb*	at ehci?
 usb*	at ohci?

Index: src/sys/conf/files
diff -u src/sys/conf/files:1.1238 src/sys/conf/files:1.1239
--- src/sys/conf/files:1.1238	Mon Sep  2 20:09:29 2019
+++ src/sys/conf/files	Sat Sep 14 06:57:52 2019
@@ -1,4 +1,4 @@
-#	$NetBSD: files,v 1.1238 2019/09/02 20:09:29 riastradh Exp $
+#	$NetBSD: files,v 1.1239 2019/09/14 06:57:52 maxv Exp $
 #	@(#)files.newconf	7.5 (Berkeley) 5/10/93
 
 version 	20171118
@@ -1290,6 +1290,11 @@ file	dev/usb/ehci.c			ehci			needs-flag
 device	xhci: usbus, usbroothub, usb_dma
 file	dev/usb/xhci.c			xhci			needs-flag
 
+# vHCI USB controller
+#
+defpseudodev vhci: usbus, usbroothub
+file	dev/usb/vhci.c			vhci			needs-flag
+
 # Mentor graphics OTG IP
 device	motg: usbus, usbroothub
 file	dev/usb/motg.c			motg			needs-flag

Index: src/sys/conf/majors
diff -u src/sys/conf/majors:1.87 src/sys/conf/majors:1.88
--- src/sys/conf/majors:1.87	Sat May 18 08:38:00 2019
+++ src/sys/conf/majors	Sat Sep 14 06:57:52 2019
@@ -1,4 +1,4 @@
-# $NetBSD: majors,v 1.87 2019/05/18 08:38:00 mlelstv Exp $
+# $NetBSD: majors,v 1.88 2019/09/14 06:57:52 maxv Exp $
 #
 # Device majors for Machine-Independent drivers.
 #
@@ -86,3 +86,4 @@ device-major spi       char 347		   spi
 # Major 352 is reserved for external/cddl/osnet/dev/fbt/fbt.c
 # Major 353 is reserved for external/cddl/osnet/dev/sdt/sdt.c
 device-major ipmi      char 354		   ipmi
+device-major vhci      char 346            vhci

Added files:

Index: src/sys/dev/usb/vhci.c
diff -u /dev/null src/sys/dev/usb/vhci.c:1.1
--- /dev/null	Sat Sep 14 06:57:52 2019
+++ src/sys/dev/usb/vhci.c	Sat Sep 14 06:57:52 2019
@@ -0,0 +1,1131 @@
+/*	$NetBSD: vhci.c,v 1.1 2019/09/14 06:57:52 maxv Exp $ */
+
+/*
+ * Copyright (c) 2019 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Maxime Villard.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: vhci.c,v 1.1 2019/09/14 06:57:52 maxv Exp $");
+
+#ifdef _KERNEL_OPT
+#include "opt_usb.h"
+#endif
+
+#include <sys/param.h>
+
+#include <sys/bus.h>
+#include <sys/cpu.h>
+#include <sys/conf.h>
+#include <sys/device.h>
+#include <sys/kernel.h>
+#include <sys/kmem.h>
+#include <sys/mutex.h>
+#include <sys/proc.h>
+#include <sys/queue.h>
+#include <sys/systm.h>
+#include <sys/mman.h>
+#include <sys/file.h>
+#include <sys/filedesc.h>
+
+#include <machine/endian.h>
+
+#include "ioconf.h"
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdivar.h>
+
+#include <dev/usb/usbroothub.h>
+
+#define VHCI_DEBUG
+
+#ifdef VHCI_DEBUG
+#define DPRINTF(fmt, ...)	printf(fmt, __VA_ARGS__)
+#else
+#define DPRINTF(fmt, ...)	__nothing
+#endif
+
+static usbd_status vhci_open(struct usbd_pipe *);
+static void vhci_softintr(void *);
+
+static struct usbd_xfer *vhci_allocx(struct usbd_bus *, unsigned int);
+static void vhci_freex(struct usbd_bus *, struct usbd_xfer *);
+static void vhci_get_lock(struct usbd_bus *, kmutex_t **);
+static int vhci_roothub_ctrl(struct usbd_bus *, usb_device_request_t *,
+    void *, int);
+
+static const struct usbd_bus_methods vhci_bus_methods = {
+	.ubm_open =	vhci_open,
+	.ubm_softint =	vhci_softintr,
+	.ubm_dopoll =	NULL,
+	.ubm_allocx =	vhci_allocx,
+	.ubm_freex =	vhci_freex,
+	.ubm_getlock =	vhci_get_lock,
+	.ubm_rhctrl =	vhci_roothub_ctrl,
+};
+
+static usbd_status vhci_device_ctrl_transfer(struct usbd_xfer *);
+static usbd_status vhci_device_ctrl_start(struct usbd_xfer *);
+static void vhci_device_ctrl_abort(struct usbd_xfer *);
+static void vhci_device_ctrl_close(struct usbd_pipe *);
+static void vhci_device_ctrl_cleartoggle(struct usbd_pipe *);
+static void vhci_device_ctrl_done(struct usbd_xfer *);
+
+static const struct usbd_pipe_methods vhci_device_ctrl_methods = {
+	.upm_init =		NULL,
+	.upm_fini =		NULL,
+	.upm_transfer =		vhci_device_ctrl_transfer,
+	.upm_start =		vhci_device_ctrl_start,
+	.upm_abort =		vhci_device_ctrl_abort,
+	.upm_close =		vhci_device_ctrl_close,
+	.upm_cleartoggle =	vhci_device_ctrl_cleartoggle,
+	.upm_done =		vhci_device_ctrl_done,
+};
+
+static usbd_status vhci_root_intr_transfer(struct usbd_xfer *);
+static usbd_status vhci_root_intr_start(struct usbd_xfer *);
+static void vhci_root_intr_abort(struct usbd_xfer *);
+static void vhci_root_intr_close(struct usbd_pipe *);
+static void vhci_root_intr_cleartoggle(struct usbd_pipe *);
+static void vhci_root_intr_done(struct usbd_xfer *);
+
+static const struct usbd_pipe_methods vhci_root_intr_methods = {
+	.upm_init =		NULL,
+	.upm_fini =		NULL,
+	.upm_transfer =		vhci_root_intr_transfer,
+	.upm_start =		vhci_root_intr_start,
+	.upm_abort =		vhci_root_intr_abort,
+	.upm_close =		vhci_root_intr_close,
+	.upm_cleartoggle =	vhci_root_intr_cleartoggle,
+	.upm_done =		vhci_root_intr_done,
+};
+
+typedef struct vhci_packet {
+	LIST_ENTRY(vhci_packet) portlist;
+	LIST_ENTRY(vhci_packet) xferlist;
+	struct usbd_xfer *xfer; /* also vxfer */
+	uint8_t *buf;
+	size_t size;
+	size_t cursor;
+} vhci_packet_t;
+
+typedef LIST_HEAD(, vhci_packet) vhci_packet_list_t;
+
+typedef struct {
+	kmutex_t lock;
+	int status;
+	int change;
+	struct {
+		vhci_packet_list_t usb_to_host;
+		vhci_packet_list_t host_to_usb;
+	} pkts_device_ctrl;
+} vhci_port_t;
+
+typedef struct {
+	struct usbd_pipe pipe;
+} vhci_pipe_t;
+
+typedef struct vhci_xfer {
+	/* General. */
+	struct usbd_xfer xfer;
+
+	/* vHCI-specific. */
+	size_t refcnt;
+	vhci_port_t *port;
+	vhci_packet_list_t pkts;
+	LIST_ENTRY(vhci_xfer) freelist;
+} vhci_xfer_t;
+
+typedef LIST_HEAD(, vhci_xfer) vhci_xfer_list_t;
+
+#define VHCI_INDEX2PORT(idx)	(idx)
+#define VHCI_NPORTS		4
+
+typedef struct {
+	device_t sc_dev;
+
+	struct usbd_bus sc_bus;
+	bool sc_dying;
+	kmutex_t sc_lock;
+
+	/*
+	 * Intr Root. Used to attach the devices.
+	 */
+	struct usbd_xfer *sc_intrxfer;
+
+	/*
+	 * The ports. Zero is for the roothub, one and beyond for the USB
+	 * devices.
+	 */
+	size_t sc_nports;
+	vhci_port_t sc_port[VHCI_NPORTS];
+
+	device_t sc_child; /* /dev/usb# device */
+} vhci_softc_t;
+
+typedef struct {
+	u_int port;
+	vhci_softc_t *softc;
+} vhci_fd_t;
+
+extern struct cfdriver vhci_cd;
+
+/* -------------------------------------------------------------------------- */
+
+static void
+vhci_pkt_create(vhci_port_t *port, struct usbd_xfer *xfer, bool usb_to_host)
+{
+	vhci_xfer_t *vxfer = (vhci_xfer_t *)xfer;
+	vhci_packet_list_t *reqlist, *pktlist;
+	vhci_packet_t *req, *pkt;
+
+	/* Setup packet. */
+	reqlist = &port->pkts_device_ctrl.host_to_usb;
+	req = kmem_zalloc(sizeof(*req), KM_SLEEP);
+	req->xfer = xfer;
+	req->buf = (uint8_t *)&xfer->ux_request;
+	req->size = sizeof(xfer->ux_request);
+	req->cursor = 0;
+
+	/* Data packet. */
+	if (usb_to_host) {
+		pktlist = &port->pkts_device_ctrl.usb_to_host;
+	} else {
+		pktlist = &port->pkts_device_ctrl.host_to_usb;
+	}
+	pkt = kmem_zalloc(sizeof(*pkt), KM_SLEEP);
+	pkt->xfer = xfer;
+	pkt->buf = xfer->ux_buf;
+	pkt->size = xfer->ux_length;
+	pkt->cursor = 0;
+
+	/* Insert in the xfer. */
+	vxfer->refcnt = 2;
+	vxfer->port = port;
+	LIST_INIT(&vxfer->pkts);
+	LIST_INSERT_HEAD(&vxfer->pkts, req, xferlist);
+	LIST_INSERT_HEAD(&vxfer->pkts, pkt, xferlist);
+
+	/* Insert in the port. */
+	mutex_enter(&port->lock);
+	LIST_INSERT_HEAD(reqlist, req, portlist);
+	LIST_INSERT_HEAD(pktlist, pkt, portlist);
+	mutex_exit(&port->lock);
+}
+
+static void
+vhci_pkt_destroy(vhci_softc_t *sc, vhci_packet_t *pkt)
+{
+	struct usbd_xfer *xfer = pkt->xfer;
+	vhci_xfer_t *vxfer = (vhci_xfer_t *)xfer;
+	vhci_port_t *port = vxfer->port;
+
+	KASSERT(mutex_owned(&port->lock));
+
+	LIST_REMOVE(pkt, portlist);
+	LIST_REMOVE(pkt, xferlist);
+	kmem_free(pkt, sizeof(*pkt));
+
+	KASSERT(vxfer->refcnt > 0);
+	vxfer->refcnt--;
+	if (vxfer->refcnt > 0)
+		return;
+
+	KASSERT(LIST_FIRST(&vxfer->pkts) == NULL);
+}
+
+/* -------------------------------------------------------------------------- */
+
+static usbd_status
+vhci_open(struct usbd_pipe *pipe)
+{
+	struct usbd_device *dev = pipe->up_dev;
+	struct usbd_bus *bus = dev->ud_bus;
+	usb_endpoint_descriptor_t *ed = pipe->up_endpoint->ue_edesc;
+	vhci_softc_t *sc = bus->ub_hcpriv;
+	uint8_t addr = dev->ud_addr;
+
+	if (sc->sc_dying)
+		return USBD_IOERROR;
+
+	DPRINTF("%s: called, type=%d\n", __func__,
+	    UE_GET_XFERTYPE(ed->bmAttributes));
+
+	if (addr == bus->ub_rhaddr) {
+		switch (ed->bEndpointAddress) {
+		case USB_CONTROL_ENDPOINT:
+			DPRINTF("%s: roothub_ctrl\n", __func__);
+			pipe->up_methods = &roothub_ctrl_methods;
+			break;
+		case UE_DIR_IN | USBROOTHUB_INTR_ENDPT:
+			DPRINTF("%s: root_intr\n", __func__);
+			pipe->up_methods = &vhci_root_intr_methods;
+			break;
+		default:
+			DPRINTF("%s: inval\n", __func__);
+			return USBD_INVAL;
+		}
+	} else {
+		switch (UE_GET_XFERTYPE(ed->bmAttributes)) {
+		case UE_CONTROL:
+			pipe->up_methods = &vhci_device_ctrl_methods;
+			break;
+		case UE_BULK:
+		case UE_INTERRUPT:
+		default:
+			goto bad;
+		}
+	}
+
+	return USBD_NORMAL_COMPLETION;
+
+bad:
+	return USBD_NOMEM;
+}
+
+static void
+vhci_softintr(void *v)
+{
+	DPRINTF("%s: called\n", __func__);
+}
+
+static struct usbd_xfer *
+vhci_allocx(struct usbd_bus *bus, unsigned int nframes)
+{
+	vhci_xfer_t *vxfer;
+
+	vxfer = kmem_zalloc(sizeof(*vxfer), KM_SLEEP);
+#ifdef DIAGNOSTIC
+	vxfer->xfer.ux_state = XFER_BUSY;
+#endif
+	return (struct usbd_xfer *)vxfer;
+}
+
+static void
+vhci_freex(struct usbd_bus *bus, struct usbd_xfer *xfer)
+{
+	vhci_xfer_t *vxfer = (vhci_xfer_t *)xfer;
+
+	KASSERT(vxfer->refcnt == 0);
+	KASSERT(LIST_FIRST(&vxfer->pkts) == NULL);
+
+#ifdef DIAGNOSTIC
+	vxfer->xfer.ux_state = XFER_FREE;
+#endif
+	kmem_free(vxfer, sizeof(*vxfer));
+}
+
+static void
+vhci_get_lock(struct usbd_bus *bus, kmutex_t **lock)
+{
+	vhci_softc_t *sc = bus->ub_hcpriv;
+
+	*lock = &sc->sc_lock;
+}
+
+static int
+vhci_roothub_ctrl(struct usbd_bus *bus, usb_device_request_t *req,
+    void *buf, int buflen)
+{
+	vhci_softc_t *sc = bus->ub_hcpriv;
+	vhci_port_t *port;
+	usb_hub_descriptor_t hubd;
+	uint16_t len, value, index;
+	int totlen = 0;
+
+	len = UGETW(req->wLength);
+	value = UGETW(req->wValue);
+	index = UGETW(req->wIndex);
+
+	port = &sc->sc_port[VHCI_INDEX2PORT(index)];
+
+#define C(x,y) ((x) | ((y) << 8))
+	switch (C(req->bRequest, req->bmRequestType)) {
+	case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE):
+		switch (value) {
+		case C(0, UDESC_DEVICE): {
+			usb_device_descriptor_t devd;
+
+			totlen = uimin(buflen, sizeof(devd));
+			memcpy(&devd, buf, totlen);
+			USETW(devd.idVendor, 0);
+			USETW(devd.idProduct, 0);
+			memcpy(buf, &devd, totlen);
+			break;
+		}
+#define sd ((usb_string_descriptor_t *)buf)
+		case C(1, UDESC_STRING):
+			/* Vendor */
+			totlen = usb_makestrdesc(sd, len, "NetBSD");
+			break;
+		case C(2, UDESC_STRING):
+			/* Product */
+			totlen = usb_makestrdesc(sd, len, "VHCI root hub");
+			break;
+#undef sd
+		default:
+			/* default from usbroothub */
+			return buflen;
+		}
+		break;
+
+	case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER):
+		switch (value) {
+		case UHF_PORT_RESET:
+			if (index < 1 || index >= sc->sc_nports) {
+				return -1;
+			}
+			port->status |= UPS_C_PORT_RESET;
+			break;
+		case UHF_PORT_POWER:
+			break;
+		default:
+			return -1;
+		}
+		break;
+
+	/* Hub requests. */
+	case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE):
+		break;
+	case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER):
+		if (index < 1 || index >= sc->sc_nports) {
+			return -1;
+		}
+		switch (value) {
+		case UHF_PORT_ENABLE:
+			port->status &= ~UPS_PORT_ENABLED;
+			break;
+		case UHF_C_PORT_ENABLE:
+			port->change |= UPS_C_PORT_ENABLED;
+			break;
+		default:
+			return -1;
+		}
+		break;
+
+	case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE):
+		totlen = uimin(buflen, sizeof(hubd));
+		memcpy(&hubd, buf, totlen);
+		hubd.bNbrPorts = sc->sc_nports - 1;
+		hubd.bDescLength = USB_HUB_DESCRIPTOR_SIZE;
+		totlen = uimin(totlen, hubd.bDescLength);
+		memcpy(buf, &hubd, totlen);
+		break;
+
+	case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE):
+		/* XXX The other HCs do this */
+		memset(buf, 0, len);
+		totlen = len;
+		break;
+
+	case C(UR_GET_STATUS, UT_READ_CLASS_OTHER): {
+		usb_port_status_t ps;
+
+		if (index < 1 || index >= sc->sc_nports) {
+			return -1;
+		}
+		USETW(ps.wPortStatus, port->status);
+		USETW(ps.wPortChange, port->change);
+		totlen = uimin(len, sizeof(ps));
+		memcpy(buf, &ps, totlen);
+		break;
+	}
+	default:
+		/* default from usbroothub */
+		return buflen;
+	}
+
+	return totlen;
+}
+
+/* -------------------------------------------------------------------------- */
+
+static usbd_status
+vhci_device_ctrl_transfer(struct usbd_xfer *xfer)
+{
+	vhci_softc_t *sc = xfer->ux_bus->ub_hcpriv;
+	usbd_status err;
+
+	DPRINTF("%s: called\n", __func__);
+
+	/* Insert last in queue. */
+	mutex_enter(&sc->sc_lock);
+	err = usb_insert_transfer(xfer);
+	mutex_exit(&sc->sc_lock);
+	if (err)
+		return err;
+
+	/* Pipe isn't running, start first */
+	return vhci_device_ctrl_start(SIMPLEQ_FIRST(&xfer->ux_pipe->up_queue));
+}
+
+static usbd_status
+vhci_device_ctrl_start(struct usbd_xfer *xfer)
+{
+	usb_device_request_t *req = &xfer->ux_request;
+	struct usbd_device *dev = xfer->ux_pipe->up_dev;
+	vhci_softc_t *sc = xfer->ux_bus->ub_hcpriv;
+	vhci_port_t *port;
+	bool polling = sc->sc_bus.ub_usepolling;
+	bool isread = (req->bmRequestType & UT_READ) != 0;
+	int portno;
+
+	KASSERT(xfer->ux_rqflags & URQ_REQUEST);
+	KASSERT(dev->ud_myhsport != NULL);
+	portno = dev->ud_myhsport->up_portno;
+
+	DPRINTF("%s: type=0x%02x, len=%d, isread=%d, portno=%d\n",
+	    __func__, req->bmRequestType, UGETW(req->wLength), isread, portno);
+
+	if (sc->sc_dying)
+		return USBD_IOERROR;
+
+	port = &sc->sc_port[portno];
+
+	if (!polling)
+		mutex_enter(&sc->sc_lock);
+	xfer->ux_status = USBD_IN_PROGRESS;
+	if (!polling)
+		mutex_exit(&sc->sc_lock);
+
+	vhci_pkt_create(port, xfer, isread);
+
+	return USBD_IN_PROGRESS;
+}
+
+static void
+vhci_device_ctrl_abort(struct usbd_xfer *xfer)
+{
+	vhci_xfer_t *vxfer = (vhci_xfer_t *)xfer;
+	vhci_softc_t *sc = xfer->ux_bus->ub_hcpriv;
+	vhci_port_t *port = vxfer->port;
+	vhci_packet_t *pkt;
+
+	DPRINTF("%s: called\n", __func__);
+
+	KASSERT(mutex_owned(&sc->sc_lock));
+
+	callout_halt(&xfer->ux_callout, &sc->sc_lock);
+
+	KASSERT(xfer->ux_status != USBD_CANCELLED);
+
+	/* If anyone else beat us, we're done.  */
+	if (xfer->ux_status != USBD_IN_PROGRESS)
+		return;
+
+	mutex_enter(&port->lock);
+	for (; vxfer->refcnt > 0; vxfer->refcnt--) {
+		pkt = LIST_FIRST(&vxfer->pkts);
+		KASSERT(pkt != NULL);
+		vhci_pkt_destroy(sc, pkt);
+	}
+	KASSERT(LIST_FIRST(&vxfer->pkts) == NULL);
+	mutex_exit(&port->lock);
+
+	xfer->ux_status = USBD_CANCELLED;
+	usb_transfer_complete(xfer);
+	KASSERT(mutex_owned(&sc->sc_lock));
+}
+
+static void
+vhci_device_ctrl_close(struct usbd_pipe *pipe)
+{
+	DPRINTF("%s: called\n", __func__);
+}
+
+static void
+vhci_device_ctrl_cleartoggle(struct usbd_pipe *pipe)
+{
+	DPRINTF("%s: called\n", __func__);
+}
+
+static void
+vhci_device_ctrl_done(struct usbd_xfer *xfer)
+{
+	DPRINTF("%s: called\n", __func__);
+}
+
+/* -------------------------------------------------------------------------- */
+
+static usbd_status
+vhci_root_intr_transfer(struct usbd_xfer *xfer)
+{
+	vhci_softc_t *sc = xfer->ux_bus->ub_hcpriv;
+	usbd_status err;
+
+	DPRINTF("%s: called\n", __func__);
+
+	/* Insert last in queue. */
+	mutex_enter(&sc->sc_lock);
+	err = usb_insert_transfer(xfer);
+	mutex_exit(&sc->sc_lock);
+	if (err)
+		return err;
+
+	/* Pipe isn't running, start first */
+	return vhci_root_intr_start(SIMPLEQ_FIRST(&xfer->ux_pipe->up_queue));
+}
+
+static usbd_status
+vhci_root_intr_start(struct usbd_xfer *xfer)
+{
+	vhci_softc_t *sc = xfer->ux_bus->ub_hcpriv;
+	const bool polling = sc->sc_bus.ub_usepolling;
+
+	DPRINTF("%s: called, len=%zu\n", __func__, (size_t)xfer->ux_length);
+
+	if (sc->sc_dying)
+		return USBD_IOERROR;
+
+	if (!polling)
+		mutex_enter(&sc->sc_lock);
+	sc->sc_intrxfer = xfer;
+	if (!polling)
+		mutex_exit(&sc->sc_lock);
+
+	return USBD_IN_PROGRESS;
+}
+
+static void
+vhci_root_intr_abort(struct usbd_xfer *xfer)
+{
+	vhci_softc_t *sc = xfer->ux_bus->ub_hcpriv;
+
+	DPRINTF("%s: called\n", __func__);
+
+	KASSERT(mutex_owned(&sc->sc_lock));
+	KASSERT(xfer->ux_pipe->up_intrxfer == xfer);
+
+	sc->sc_intrxfer = NULL;
+
+	xfer->ux_status = USBD_CANCELLED;
+	usb_transfer_complete(xfer);
+}
+
+static void
+vhci_root_intr_close(struct usbd_pipe *pipe)
+{
+	vhci_softc_t *sc = pipe->up_dev->ud_bus->ub_hcpriv;
+
+	DPRINTF("%s: called\n", __func__);
+
+	KASSERT(mutex_owned(&sc->sc_lock));
+
+	sc->sc_intrxfer = NULL;
+}
+
+static void
+vhci_root_intr_cleartoggle(struct usbd_pipe *pipe)
+{
+	DPRINTF("%s: called\n", __func__);
+}
+
+static void
+vhci_root_intr_done(struct usbd_xfer *xfer)
+{
+}
+
+/* -------------------------------------------------------------------------- */
+
+struct vhci_ioc_get_info {
+	/* General. */
+	size_t nports;
+
+	/* Current port. */
+	u_int port;
+	int status;
+};
+
+struct vhci_ioc_set_port {
+	u_int port;
+};
+
+struct vhci_ioc_usb_attach {
+	u_int port;
+};
+
+struct vhci_ioc_usb_detach {
+	u_int port;
+};
+
+#define VHCI_IOC_GET_INFO	_IOR('V', 0, struct vhci_ioc_get_info)
+#define VHCI_IOC_SET_PORT	_IOW('V', 1, struct vhci_ioc_set_port)
+#define VHCI_IOC_USB_ATTACH	_IOW('V', 10, struct vhci_ioc_usb_attach)
+#define VHCI_IOC_USB_DETACH	_IOW('V', 11, struct vhci_ioc_usb_detach)
+
+static int
+vhci_usb_attach(vhci_fd_t *vfd, struct vhci_ioc_usb_attach *args)
+{
+	vhci_softc_t *sc = vfd->softc;
+	vhci_port_t *port;
+	struct usbd_xfer *xfer;
+	u_char *p;
+	int ret = 0;
+
+	if (args->port == 0 || args->port >= sc->sc_nports)
+		return EINVAL;
+	port = &sc->sc_port[args->port];
+
+	mutex_enter(&sc->sc_lock);
+
+	port->status = UPS_CURRENT_CONNECT_STATUS | UPS_PORT_ENABLED |
+	    UPS_PORT_POWER;
+	port->change = UPS_C_CONNECT_STATUS | UPS_C_PORT_RESET;
+
+	xfer = sc->sc_intrxfer;
+
+	if (xfer == NULL) {
+		ret = ENOBUFS;
+		goto done;
+	}
+
+	p = xfer->ux_buf;
+	memset(p, 0, xfer->ux_length);
+	p[0] = __BIT(args->port);
+	xfer->ux_actlen = xfer->ux_length;
+	xfer->ux_status = USBD_NORMAL_COMPLETION;
+
+	usb_transfer_complete(xfer);
+
+done:
+	mutex_exit(&sc->sc_lock);
+	return ret;
+}
+
+static void
+vhci_port_flush(vhci_softc_t *sc, vhci_port_t *port)
+{
+	vhci_packet_list_t *pktlist;
+	vhci_packet_t *pkt, *nxt;
+	vhci_xfer_list_t vxferlist;
+	vhci_xfer_t *vxfer;
+
+	KASSERT(mutex_owned(&sc->sc_lock));
+	mutex_enter(&port->lock);
+
+	LIST_INIT(&vxferlist);
+
+	pktlist = &port->pkts_device_ctrl.host_to_usb;
+	LIST_FOREACH_SAFE(pkt, pktlist, portlist, nxt) {
+		vxfer = (vhci_xfer_t *)pkt->xfer;
+		KASSERT(vxfer->xfer.ux_status == USBD_IN_PROGRESS);
+		vhci_pkt_destroy(sc, pkt);
+		if (vxfer->refcnt == 0)
+			LIST_INSERT_HEAD(&vxferlist, vxfer, freelist);
+	}
+	KASSERT(LIST_FIRST(pktlist) == NULL);
+
+	pktlist = &port->pkts_device_ctrl.usb_to_host;
+	LIST_FOREACH_SAFE(pkt, pktlist, portlist, nxt) {
+		vxfer = (vhci_xfer_t *)pkt->xfer;
+		KASSERT(vxfer->xfer.ux_status == USBD_IN_PROGRESS);
+		vhci_pkt_destroy(sc, pkt);
+		if (vxfer->refcnt == 0)
+			LIST_INSERT_HEAD(&vxferlist, vxfer, freelist);
+	}
+	KASSERT(LIST_FIRST(pktlist) == NULL);
+
+	while ((vxfer = LIST_FIRST(&vxferlist)) != NULL) {
+		struct usbd_xfer *xfer = &vxfer->xfer;
+		LIST_REMOVE(vxfer, freelist);
+
+		xfer->ux_actlen = xfer->ux_length;
+		xfer->ux_status = USBD_NORMAL_COMPLETION;
+		usb_transfer_complete(xfer);
+	}
+
+	mutex_exit(&port->lock);
+}
+
+static int
+vhci_usb_detach(vhci_fd_t *vfd, struct vhci_ioc_usb_detach *args)
+{
+	vhci_softc_t *sc = vfd->softc;
+	vhci_port_t *port;
+	struct usbd_xfer *xfer;
+	u_char *p;
+
+	if (args->port == 0 || args->port >= sc->sc_nports)
+		return EINVAL;
+	port = &sc->sc_port[args->port];
+
+	mutex_enter(&sc->sc_lock);
+
+	xfer = sc->sc_intrxfer;
+	if (xfer == NULL) {
+		mutex_exit(&sc->sc_lock);
+		return ENOBUFS;
+	}
+
+	port->status = 0;
+	port->change = UPS_C_CONNECT_STATUS | UPS_C_PORT_RESET;
+
+	p = xfer->ux_buf;
+	memset(p, 0, xfer->ux_length);
+	p[0] = __BIT(args->port);
+	xfer->ux_actlen = xfer->ux_length;
+	xfer->ux_status = USBD_NORMAL_COMPLETION;
+
+	usb_transfer_complete(xfer);
+	vhci_port_flush(sc, port);
+	mutex_exit(&sc->sc_lock);
+
+	return 0;
+}
+
+static int
+vhci_get_info(vhci_fd_t *vfd, struct vhci_ioc_get_info *args)
+{
+	vhci_softc_t *sc = vfd->softc;
+	vhci_port_t *port;
+
+	port = &sc->sc_port[vfd->port];
+
+	args->nports = VHCI_NPORTS;
+	args->port = vfd->port;
+	mutex_enter(&port->lock);
+	args->status = port->status;
+	mutex_exit(&port->lock);
+
+	return 0;
+}
+
+static int
+vhci_set_port(vhci_fd_t *vfd, struct vhci_ioc_set_port *args)
+{
+	vhci_softc_t *sc = vfd->softc;
+
+	if (args->port == 0 || args->port >= sc->sc_nports)
+		return EINVAL;
+
+	vfd->port = args->port;
+
+	return 0;
+}
+
+/* -------------------------------------------------------------------------- */
+
+static dev_type_open(vhci_fd_open);
+
+const struct cdevsw vhci_cdevsw = {
+	.d_open = vhci_fd_open,
+	.d_close = noclose,
+	.d_read = noread,
+	.d_write = nowrite,
+	.d_ioctl = noioctl,
+	.d_stop = nostop,
+	.d_tty = notty,
+	.d_poll = nopoll,
+	.d_mmap = nommap,
+	.d_kqfilter = nokqfilter,
+	.d_discard = nodiscard,
+	.d_flag = D_OTHER | D_MPSAFE
+};
+
+static int vhci_fd_ioctl(file_t *, u_long, void *);
+static int vhci_fd_close(file_t *);
+static int vhci_fd_read(struct file *, off_t *, struct uio *, kauth_cred_t, int);
+static int vhci_fd_write(struct file *, off_t *, struct uio *, kauth_cred_t, int);
+
+const struct fileops vhci_fileops = {
+	.fo_read = vhci_fd_read,
+	.fo_write = vhci_fd_write,
+	.fo_ioctl = vhci_fd_ioctl,
+	.fo_fcntl = fnullop_fcntl,
+	.fo_poll = fnullop_poll,
+	.fo_stat = fbadop_stat,
+	.fo_close = vhci_fd_close,
+	.fo_kqfilter = fnullop_kqfilter,
+	.fo_restart = fnullop_restart,
+	.fo_mmap = NULL,
+};
+
+static int
+vhci_fd_open(dev_t dev, int flags, int type, struct lwp *l)
+{
+	vhci_fd_t *vfd;
+	struct file *fp;
+	int error, fd;
+
+	if (minor(dev) != 0)
+		return EXDEV;
+	error = fd_allocfile(&fp, &fd);
+	if (error)
+		return error;
+
+	vfd = kmem_alloc(sizeof(*vfd), KM_SLEEP);
+	vfd->port = 1;
+	vfd->softc = device_lookup_private(&vhci_cd, minor(dev));
+
+	return fd_clone(fp, fd, flags, &vhci_fileops, vfd);
+}
+
+static int
+vhci_fd_close(file_t *fp)
+{
+	struct vhci_ioc_usb_detach args;
+	vhci_fd_t *vfd = fp->f_data;
+
+	KASSERT(vfd != NULL);
+
+	args.port = vfd->port;
+	vhci_usb_detach(vfd, &args);
+
+	kmem_free(vfd, sizeof(*vfd));
+	fp->f_data = NULL;
+
+	return 0;
+}
+
+static int
+vhci_fd_read(struct file *fp, off_t *offp, struct uio *uio, kauth_cred_t cred,
+    int flags)
+{
+	vhci_fd_t *vfd = fp->f_data;
+	vhci_softc_t *sc = vfd->softc;
+	vhci_packet_list_t *pktlist;
+	vhci_packet_t *pkt, *nxt;
+	vhci_xfer_list_t vxferlist;
+	vhci_xfer_t *vxfer;
+	vhci_port_t *port;
+	int error = 0;
+	uint8_t *buf;
+	size_t size;
+
+	if (uio->uio_resid == 0)
+		return 0;
+	port = &sc->sc_port[vfd->port];
+	pktlist = &port->pkts_device_ctrl.host_to_usb;
+
+	LIST_INIT(&vxferlist);
+
+	mutex_enter(&port->lock);
+
+	if (!(port->status & UPS_PORT_ENABLED)) {
+		error = ENOBUFS;
+		goto out;
+	}
+
+	LIST_FOREACH_SAFE(pkt, pktlist, portlist, nxt) {
+		vxfer = (vhci_xfer_t *)pkt->xfer;
+		buf = pkt->buf + pkt->cursor;
+		size = uimin(uio->uio_resid, pkt->size - pkt->cursor);
+
+		KASSERT(vxfer->xfer.ux_status == USBD_IN_PROGRESS);
+
+		error = uiomove(buf, size, uio);
+		if (error) {
+			DPRINTF("%s: error = %d\n", __func__, error);
+			goto out;
+		}
+
+		pkt->cursor += size;
+
+		if (pkt->cursor == pkt->size) {
+			vhci_pkt_destroy(sc, pkt);
+			if (vxfer->refcnt == 0) {
+				LIST_INSERT_HEAD(&vxferlist, vxfer, freelist);
+			}
+		}
+		if (uio->uio_resid == 0) {
+			break;
+		}
+	}
+
+out:
+	mutex_exit(&port->lock);
+
+	while ((vxfer = LIST_FIRST(&vxferlist)) != NULL) {
+		struct usbd_xfer *xfer = &vxfer->xfer;
+		LIST_REMOVE(vxfer, freelist);
+
+		mutex_enter(&sc->sc_lock);
+		xfer->ux_actlen = xfer->ux_length;
+		xfer->ux_status = USBD_NORMAL_COMPLETION;
+		usb_transfer_complete(xfer);
+		mutex_exit(&sc->sc_lock);
+	}
+
+	return error;
+}
+
+static int
+vhci_fd_write(struct file *fp, off_t *offp, struct uio *uio, kauth_cred_t cred,
+    int flags)
+{
+	vhci_fd_t *vfd = fp->f_data;
+	vhci_softc_t *sc = vfd->softc;
+	vhci_packet_list_t *pktlist;
+	vhci_packet_t *pkt, *nxt;
+	vhci_xfer_list_t vxferlist;
+	vhci_xfer_t *vxfer;
+	vhci_port_t *port;
+	int error = 0;
+	uint8_t *buf;
+	size_t size;
+
+	if (uio->uio_resid == 0)
+		return 0;
+	port = &sc->sc_port[vfd->port];
+	pktlist = &port->pkts_device_ctrl.usb_to_host;
+
+	LIST_INIT(&vxferlist);
+
+	mutex_enter(&port->lock);
+
+	if (!(port->status & UPS_PORT_ENABLED)) {
+		error = ENOBUFS;
+		goto out;
+	}
+
+	LIST_FOREACH_SAFE(pkt, pktlist, portlist, nxt) {
+		vxfer = (vhci_xfer_t *)pkt->xfer;
+		buf = pkt->buf + pkt->cursor;
+		size = uimin(uio->uio_resid, pkt->size - pkt->cursor);
+
+		KASSERT(vxfer->xfer.ux_status == USBD_IN_PROGRESS);
+
+		error = uiomove(buf, size, uio);
+		if (error) {
+			DPRINTF("%s: error = %d\n", __func__, error);
+			goto out;
+		}
+
+		pkt->cursor += size;
+
+		if (pkt->cursor == pkt->size) {
+			vhci_pkt_destroy(sc, pkt);
+			if (vxfer->refcnt == 0) {
+				LIST_INSERT_HEAD(&vxferlist, vxfer, freelist);
+			}
+		}
+		if (uio->uio_resid == 0) {
+			break;
+		}
+	}
+
+out:
+	mutex_exit(&port->lock);
+
+	while ((vxfer = LIST_FIRST(&vxferlist)) != NULL) {
+		struct usbd_xfer *xfer = &vxfer->xfer;
+		LIST_REMOVE(vxfer, freelist);
+
+		mutex_enter(&sc->sc_lock);
+		xfer->ux_actlen = xfer->ux_length;
+		xfer->ux_status = USBD_NORMAL_COMPLETION;
+		usb_transfer_complete(xfer);
+		mutex_exit(&sc->sc_lock);
+	}
+
+	return error;
+}
+
+static int
+vhci_fd_ioctl(file_t *fp, u_long cmd, void *data)
+{
+	vhci_fd_t *vfd = fp->f_data;
+
+	KASSERT(vfd != NULL);
+
+	switch (cmd) {
+	case VHCI_IOC_GET_INFO:
+		return vhci_get_info(vfd, data);
+	case VHCI_IOC_SET_PORT:
+		return vhci_set_port(vfd, data);
+	case VHCI_IOC_USB_ATTACH:
+		return vhci_usb_attach(vfd, data);
+	case VHCI_IOC_USB_DETACH:
+		return vhci_usb_detach(vfd, data);
+	default:
+		return EINVAL;
+	}
+}
+
+/* -------------------------------------------------------------------------- */
+
+static int vhci_match(device_t, cfdata_t, void *);
+static void vhci_attach(device_t, device_t, void *);
+
+CFATTACH_DECL_NEW(vhci, sizeof(vhci_softc_t), vhci_match, vhci_attach,
+    NULL, NULL);
+
+void
+vhciattach(int nunits)
+{
+	static struct cfdata vhci_cfdata = {
+		.cf_name = "vhci",
+		.cf_atname = "vhci",
+		.cf_unit = 0,
+		.cf_fstate = FSTATE_STAR,
+	};
+	int error;
+
+	error = config_cfattach_attach(vhci_cd.cd_name, &vhci_ca);
+	if (error) {
+		aprint_error("%s: unable to register cfattach\n",
+		    vhci_cd.cd_name);
+		(void)config_cfdriver_detach(&vhci_cd);
+		return;
+	}
+
+	config_attach_pseudo(&vhci_cfdata);
+}
+
+static int
+vhci_match(device_t parent, cfdata_t match, void *aux)
+{
+	return 1;
+}
+
+static void
+vhci_attach(device_t parent, device_t self, void *aux)
+{
+	vhci_softc_t *sc = device_private(self);
+	vhci_port_t *port;
+	size_t i;
+
+	sc->sc_dev = self;
+	sc->sc_bus.ub_revision = USBREV_2_0;
+	sc->sc_bus.ub_usedma = false;
+	sc->sc_bus.ub_methods = &vhci_bus_methods;
+	sc->sc_bus.ub_pipesize = sizeof(vhci_pipe_t);
+	sc->sc_bus.ub_hcpriv = sc;
+	sc->sc_dying = false;
+	mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_SOFTUSB);
+
+	sc->sc_nports = VHCI_NPORTS;
+	for (i = 0; i < sc->sc_nports; i++) {
+		port = &sc->sc_port[i];
+		mutex_init(&port->lock, MUTEX_DEFAULT, IPL_SOFTUSB);
+		LIST_INIT(&port->pkts_device_ctrl.usb_to_host);
+		LIST_INIT(&port->pkts_device_ctrl.host_to_usb);
+	}
+
+	sc->sc_child = config_found(self, &sc->sc_bus, usbctlprint);
+}

Reply via email to