Module Name:    src
Committed By:   skrll
Date:           Tue Apr  7 06:49:10 UTC 2015

Modified Files:
        src/sys/dev/usb [nick-nhusb]: xhci.c

Log Message:
USB 3.0 snapshot from Takahiro HAYASHI


To generate a diff of this commit:
cvs rdiff -u -r1.28.2.18 -r1.28.2.19 src/sys/dev/usb/xhci.c

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

Modified files:

Index: src/sys/dev/usb/xhci.c
diff -u src/sys/dev/usb/xhci.c:1.28.2.18 src/sys/dev/usb/xhci.c:1.28.2.19
--- src/sys/dev/usb/xhci.c:1.28.2.18	Mon Apr  6 19:28:40 2015
+++ src/sys/dev/usb/xhci.c	Tue Apr  7 06:49:10 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: xhci.c,v 1.28.2.18 2015/04/06 19:28:40 skrll Exp $	*/
+/*	$NetBSD: xhci.c,v 1.28.2.19 2015/04/07 06:49:10 skrll Exp $	*/
 
 /*
  * Copyright (c) 2013 Jonathan A. Kollasch
@@ -27,7 +27,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: xhci.c,v 1.28.2.18 2015/04/06 19:28:40 skrll Exp $");
+__KERNEL_RCSID(0, "$NetBSD: xhci.c,v 1.28.2.19 2015/04/07 06:49:10 skrll Exp $");
 
 #include "opt_usb.h"
 
@@ -50,6 +50,7 @@ __KERNEL_RCSID(0, "$NetBSD: xhci.c,v 1.2
 #include <dev/usb/usb.h>
 #include <dev/usb/usbdi.h>
 #include <dev/usb/usbdivar.h>
+#include <dev/usb/usbdi_util.h>
 #include <dev/usb/usbhist.h>
 #include <dev/usb/usb_mem.h>
 #include <dev/usb/usb_quirks.h>
@@ -126,18 +127,22 @@ static int xhci_roothub_ctrl(struct usbd
     void *, int);
 
 static usbd_status xhci_configure_endpoint(struct usbd_pipe *);
-static usbd_status xhci_unconfigure_endpoint(struct usbd_pipe *);
+//static usbd_status xhci_unconfigure_endpoint(struct usbd_pipe *);
 static usbd_status xhci_reset_endpoint(struct usbd_pipe *);
-//static usbd_status xhci_stop_endpoint(struct usbd_pipe *);
+static usbd_status xhci_stop_endpoint(struct usbd_pipe *);
 
 static usbd_status xhci_set_dequeue(struct usbd_pipe *);
 
 static usbd_status xhci_do_command(struct xhci_softc * const,
     struct xhci_trb * const, int);
-static usbd_status xhci_init_slot(struct xhci_softc * const, uint32_t,
-    int, int, int, int);
+static usbd_status xhci_do_command1(struct xhci_softc * const,
+    struct xhci_trb * const, int, int);
+static usbd_status xhci_do_command_locked(struct xhci_softc * const,
+    struct xhci_trb * const, int);
+static usbd_status xhci_init_slot(struct usbd_device *, uint32_t, int, int);
 static usbd_status xhci_enable_slot(struct xhci_softc * const,
     uint8_t * const);
+static usbd_status xhci_disable_slot(struct xhci_softc * const, uint8_t);
 static usbd_status xhci_address_device(struct xhci_softc * const,
     uint64_t, uint8_t, bool);
 static usbd_status xhci_update_ep0_mps(struct xhci_softc * const,
@@ -228,11 +233,24 @@ static const struct usbd_pipe_methods xh
 };
 
 static inline uint32_t
+xhci_read_1(const struct xhci_softc * const sc, bus_size_t offset)
+{
+	return bus_space_read_1(sc->sc_iot, sc->sc_ioh, offset);
+}
+
+static inline uint32_t
 xhci_read_4(const struct xhci_softc * const sc, bus_size_t offset)
 {
 	return bus_space_read_4(sc->sc_iot, sc->sc_ioh, offset);
 }
 
+static inline void
+xhci_write_1(const struct xhci_softc * const sc, bus_size_t offset,
+    uint32_t value)
+{
+	bus_space_write_1(sc->sc_iot, sc->sc_ioh, offset, value);
+}
+
 #if 0 /* unused */
 static inline void
 xhci_write_4(const struct xhci_softc * const sc, bus_size_t offset,
@@ -374,7 +392,7 @@ xhci_db_write_4(const struct xhci_softc 
 static inline uint8_t
 xhci_ep_get_type(usb_endpoint_descriptor_t * const ed)
 {
-	u_int eptype;
+	u_int eptype = 0;
 
 	switch (UE_GET_XFERTYPE(ed->bmAttributes)) {
 	case UE_CONTROL:
@@ -517,6 +535,7 @@ xhci_detach(struct xhci_softc *sc, int f
 
 	mutex_destroy(&sc->sc_lock);
 	mutex_destroy(&sc->sc_intr_lock);
+	cv_destroy(&sc->sc_softwake_cv);
 
 	pool_cache_destroy(sc->sc_xferpool);
 
@@ -658,6 +677,28 @@ xhci_init(struct xhci_softc *sc)
 			}
 			break;
 		}
+		case XHCI_ID_USB_LEGACY: {
+			uint8_t bios_sem;
+
+			/* Take host controller from BIOS */
+			bios_sem = xhci_read_1(sc, ecp + XHCI_XECP_BIOS_SEM);
+			if (bios_sem) {
+				/* sets xHCI to be owned by OS */
+				xhci_write_1(sc, ecp + XHCI_XECP_OS_SEM, 1);
+				aprint_debug(
+				    "waiting for BIOS to give up control\n");
+				for (i = 0; i < 5000; i++) {
+					bios_sem = xhci_read_1(sc, ecp +
+					    XHCI_XECP_BIOS_SEM);
+					if (bios_sem == 0)
+						break;
+					DELAY(1000);
+				}
+				if (bios_sem)
+					printf("timed out waiting for BIOS\n");
+			}
+			break;
+		}
 		default:
 			break;
 		}
@@ -732,6 +773,7 @@ xhci_init(struct xhci_softc *sc)
 	aprint_debug_dev(sc->sc_dev, "sc_pgsz 0x%08x\n", (uint32_t)sc->sc_pgsz);
 	aprint_debug_dev(sc->sc_dev, "sc_maxslots 0x%08x\n",
 	    (uint32_t)sc->sc_maxslots);
+	aprint_debug_dev(sc->sc_dev, "sc_maxports %d\n", sc->sc_maxports);
 
 	usbd_status err;
 
@@ -825,6 +867,16 @@ xhci_init(struct xhci_softc *sc)
 	    KM_SLEEP);
 
 	cv_init(&sc->sc_command_cv, "xhcicmd");
+	mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_SOFTUSB);
+	mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_SCHED);
+	cv_init(&sc->sc_softwake_cv, "xhciab");
+
+	sc->sc_xferpool = pool_cache_init(sizeof(struct xhci_xfer), 0, 0, 0,
+	    "xhcixfer", NULL, IPL_USB, NULL, NULL, NULL);
+
+	/* Set up the bus struct. */
+	sc->sc_bus.ub_methods = &xhci_bus_methods;
+	sc->sc_bus.ub_pipesize = sizeof(struct xhci_pipe);
 
 	struct xhci_erste *erst;
 	erst = KERNADDR(&sc->sc_eventst_dma, 0);
@@ -848,23 +900,18 @@ xhci_init(struct xhci_softc *sc)
 #endif
 
 	xhci_rt_write_4(sc, XHCI_IMAN(0), XHCI_IMAN_INTR_ENA);
-	xhci_rt_write_4(sc, XHCI_IMOD(0), 0);
+#ifdef XHCI_QUIRK_INTEL
+	if ((sc->sc_quirks & XHCI_QUIRK_INTEL) != 0)
+		/* Intel xhci needs interrupt rate moderated. */
+		xhci_rt_write_4(sc, XHCI_IMOD(0), XHCI_IMOD_DEFAULT_LP);
+	else
+#endif /* XHCI_QUIRK_INTEL */
+		xhci_rt_write_4(sc, XHCI_IMOD(0), 0);
 
 	xhci_op_write_4(sc, XHCI_USBCMD, XHCI_CMD_INTE|XHCI_CMD_RS); /* Go! */
 	aprint_debug_dev(sc->sc_dev, "USBCMD %08"PRIx32"\n",
 	    xhci_op_read_4(sc, XHCI_USBCMD));
 
-	mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_SOFTUSB);
-	mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_SCHED);
-	cv_init(&sc->sc_softwake_cv, "xhciab");
-
-	sc->sc_xferpool = pool_cache_init(sizeof(struct xhci_xfer), 0, 0, 0,
-	    "xhcixfer", NULL, IPL_USB, NULL, NULL, NULL);
-
-	/* Set up the bus struct. */
-	sc->sc_bus.ub_methods = &xhci_bus_methods;
-	sc->sc_bus.ub_pipesize = sizeof(struct xhci_pipe);
-
 	return USBD_NORMAL_COMPLETION;
 }
 
@@ -920,9 +967,19 @@ xhci_intr1(struct xhci_softc * const sc)
 
 	iman = xhci_rt_read_4(sc, XHCI_IMAN(0));
 	DPRINTFN(16, "IMAN0 %08x", iman, 0, 0, 0);
+#ifdef XHCI_QUIRK_FORCE_INTR
+
+	if (!(sc->sc_quirks & XHCI_QUIRK_FORCE_INTR)) {
+		if ((iman & XHCI_IMAN_INTR_PEND) == 0) {
+			return 0;
+		}
+	}
+
+#else
 	if ((iman & XHCI_IMAN_INTR_PEND) == 0) {
 		return 0;
 	}
+#endif /* XHCI_QUIRK_FORCE_INTR */
 	xhci_rt_write_4(sc, XHCI_IMAN(0), iman);
 	iman = xhci_rt_read_4(sc, XHCI_IMAN(0));
 	DPRINTFN(16, "IMAN0 %08x", iman, 0, 0, 0);
@@ -934,6 +991,138 @@ xhci_intr1(struct xhci_softc * const sc)
 	return 1;
 }
 
+/*
+ * 3 port speed types used in USB stack
+ *
+ * usbdi speed
+ *	definition: USB_SPEED_* in usb.h
+ *	They are used in struct usbd_device in USB stack.
+ *	ioctl interface uses these values too.
+ * port_status speed
+ *	definition: UPS_*_SPEED in usb.h
+ *	They are used in usb_port_status_t.
+ *	Some 3.0 values overlap with 2.0 values.
+ *	(e.g. 0x200 means UPS_POER_POWER_SS in SS and
+ *	            means UPS_LOW_SPEED in HS.)
+ *	port status sent from hub also uses these values.
+ *	(but I've never seen UPS_SUPER_SPEED in port_status from hub.)
+ * xspeed:
+ *	definition: Protocol Speed ID (PSI) (xHCI 1.1 7.2.1)
+ *	They are used in only slot context and PORTSC reg of xhci.
+ *	The difference between usbdi speed and them are that
+ *	FS and LS values are swapped.
+ */
+
+static int
+xhci_speed2xspeed(int speed)
+{
+	switch (speed) {
+	case USB_SPEED_LOW:	return 2;
+	case USB_SPEED_FULL:	return 1;
+	case USB_SPEED_HIGH:	return 3;
+	case USB_SPEED_SUPER:	return 4;
+	default:
+		break;
+	}
+	return 0;
+}
+
+/* construct slot context */
+static void
+xhci_setup_sctx(struct usbd_device *dev, uint32_t *cp)
+{
+	usb_device_descriptor_t * const dd = &dev->ud_ddesc;
+	int speed = dev->ud_speed;
+	int tthubslot, ttportnum;
+	bool ishub;
+	bool usemtt;
+
+	XHCIHIST_FUNC(); XHCIHIST_CALLED();
+
+	/* 6.2.2 */
+	/*
+	 * tthubslot:
+	 *   This is the slot ID of parent HS hub
+	 *   if LS/FS device is connected && connected through HS hub.
+	 *   This is 0 if device is not LS/FS device ||
+	 *   parent hub is not HS hub ||
+	 *   attached to root hub.
+	 * ttportnum:
+	 *   This is the downstream facing port of parent HS hub
+	 *   if LS/FS device is connected.
+	 *   This is 0 if device is not LS/FS device ||
+	 *   parent hub is not HS hub ||
+	 *   attached to root hub.
+	 */
+	if (dev->ud_myhsport != NULL &&
+	    dev->ud_myhub != NULL && dev->ud_myhub->ud_depth != 0 &&
+	    (dev->ud_myhub != NULL &&
+	     dev->ud_myhub->ud_speed == USB_SPEED_HIGH) &&
+	    (speed == USB_SPEED_LOW || speed == USB_SPEED_FULL)) {
+		ttportnum = dev->ud_myhsport->up_portno;
+		/* XXX addr == slot ? */
+		tthubslot = dev->ud_myhsport->up_parent->ud_addr;
+	} else {
+		ttportnum = 0;
+		tthubslot = 0;
+	}
+	DPRINTFN(4, "myhsport %p ttportnum=%d tthubslot=%d",
+	    dev->ud_myhsport, ttportnum, tthubslot, 0);
+
+	/* ishub is valid after reading UDESC_DEVICE */
+	ishub = (dd->bDeviceClass == UDCLASS_HUB);
+
+	/* dev->ud_hub is valid after reading UDESC_HUB */
+	if (ishub && dev->ud_hub) {
+		usb_hub_descriptor_t *hd = &dev->ud_hub->uh_hubdesc;
+
+		cp[1] |= htole32(XHCI_SCTX_1_NUM_PORTS_SET(hd->bNbrPorts));
+		cp[2] |= htole32(XHCI_SCTX_2_TT_THINK_TIME_SET(
+		    __SHIFTOUT(UGETW(hd->wHubCharacteristics), UHD_TT_THINK)));
+		DPRINTFN(4, "nports=%d ttt=%d",
+		    hd->bNbrPorts, XHCI_SCTX_2_TT_THINK_TIME_GET(cp[2]), 0, 0);
+	}
+
+#define IS_TTHUB(dd) \
+    ((dd)->bDeviceProtocol == UDPROTO_HSHUBSTT || \
+     (dd)->bDeviceProtocol == UDPROTO_HSHUBMTT)
+
+	/*
+	 * MTT flag is set if
+	 * 1. this is HS hub && MTT is enabled
+	 *  or
+	 * 2. this is not hub && this is LS or FS device &&
+	 *    MTT of parent HS hub (and its parent, too) is enabled
+	 */
+	if (ishub && speed == USB_SPEED_HIGH && IS_TTHUB(dd))
+		usemtt = true;
+	else if (!ishub &&
+	     (speed == USB_SPEED_LOW || speed == USB_SPEED_FULL) &&
+	     dev->ud_myhub != NULL && dev->ud_myhub->ud_depth != 0 &&
+	     (dev->ud_myhub != NULL &&
+	      dev->ud_myhub->ud_speed == USB_SPEED_HIGH) &&
+	     dev->ud_myhsport != NULL &&
+	     IS_TTHUB(&dev->ud_myhsport->up_parent->ud_ddesc))
+		usemtt = true;
+	else
+		usemtt = false;
+	DPRINTFN(4, "class %u proto %u ishub %d usemtt %d",
+	    dd->bDeviceClass, dd->bDeviceProtocol, ishub, usemtt);
+
+	cp[0] |= htole32(
+	    XHCI_SCTX_0_SPEED_SET(xhci_speed2xspeed(speed)) |
+	    XHCI_SCTX_0_HUB_SET(ishub ? 1 : 0) |
+	    XHCI_SCTX_0_MTT_SET(usemtt ? 1 : 0)
+	    );
+	cp[1] |= htole32(0);
+	cp[2] |= htole32(
+	    XHCI_SCTX_2_IRQ_TARGET_SET(0) |
+	    XHCI_SCTX_2_TT_HUB_SID_SET(tthubslot) |
+	    XHCI_SCTX_2_TT_PORT_NUM_SET(ttportnum)
+	    );
+	cp[3] |= htole32(0);
+}
+
 static usbd_status
 xhci_configure_endpoint(struct usbd_pipe *pipe)
 {
@@ -945,10 +1134,14 @@ xhci_configure_endpoint(struct usbd_pipe
 	struct xhci_trb trb;
 	usbd_status err;
 	uint32_t *cp;
+	uint32_t mps = UGETW(ed->wMaxPacketSize);
+	uint32_t maxb = 0;
+	int speed = pipe->up_dev->ud_speed;
+	uint32_t ival = ed->bInterval;
 
 	XHCIHIST_FUNC(); XHCIHIST_CALLED();
-	DPRINTFN(4, "dci %u epaddr 0x%02x attr 0x%02x",
-	    dci, ed->bEndpointAddress, ed->bmAttributes, 0);
+	DPRINTFN(4, "slot %u dci %u epaddr 0x%02x attr 0x%02x",
+	    xs->xs_idx, dci, ed->bEndpointAddress, ed->bmAttributes);
 
 	/* XXX ensure input context is available? */
 
@@ -960,34 +1153,121 @@ xhci_configure_endpoint(struct usbd_pipe
 
 	/* set up input slot context */
 	cp = xhci_slot_get_icv(sc, xs, xhci_dci_to_ici(XHCI_DCI_SLOT));
-	cp[0] = htole32(XHCI_SCTX_0_CTX_NUM_SET(dci));
-	cp[1] = htole32(0);
-	cp[2] = htole32(0);
-	cp[3] = htole32(0);
+	xhci_setup_sctx(pipe->up_dev, cp);
+	cp[0] |= htole32(XHCI_SCTX_0_CTX_NUM_SET(dci));
 
-	uint8_t eptype = xhci_ep_get_type(pipe->up_endpoint->ue_edesc);
 	cp = xhci_slot_get_icv(sc, xs, xhci_dci_to_ici(dci));
-	if (xfertype == UE_INTERRUPT) {
-		cp[0] = htole32(
-		    XHCI_EPCTX_0_IVAL_SET(3) /* XXX */
-		    );
-		cp[1] = htole32(
-		    XHCI_EPCTX_1_CERR_SET(3) |
-		    XHCI_EPCTX_1_EPTYPE_SET(eptype) |
-		    XHCI_EPCTX_1_MAXB_SET(0) |
-		    XHCI_EPCTX_1_MAXP_SIZE_SET(8) /* XXX */
-		    );
+	cp[0] = htole32(
+	    XHCI_EPCTX_0_EPSTATE_SET(0) |
+	    XHCI_EPCTX_0_MAXP_STREAMS_SET(0) |
+	    XHCI_EPCTX_0_LSA_SET(0)
+	    );
+	cp[1] = htole32(
+	    XHCI_EPCTX_1_EPTYPE_SET(xhci_ep_get_type(ed)) |
+	    XHCI_EPCTX_1_MAXB_SET(0)
+	    );
+	if (xfertype != UE_ISOCHRONOUS)
+		cp[1] |= htole32(XHCI_EPCTX_1_CERR_SET(3));
+
+	if (speed == USB_SPEED_SUPER) {
+		usbd_desc_iter_t iter;
+		const usb_cdc_descriptor_t *cdcd;
+		const usb_endpoint_ss_comp_descriptor_t * esscd = NULL;
+		uint8_t ep;
+
+		cdcd = (const usb_cdc_descriptor_t *)usb_find_desc(
+		    pipe->up_dev, UDESC_INTERFACE, USBD_CDCSUBTYPE_ANY);
+		usb_desc_iter_init(pipe->up_dev, &iter);
+		iter.cur = (const void *)cdcd;
+
+		/* find endpoint_ss_comp desc for ep of this pipe */
+		for(ep = 0;;) {
+			cdcd = (const usb_cdc_descriptor_t *)
+			    usb_desc_iter_next(&iter);
+			if (cdcd == NULL)
+				break;
+			if (ep == 0 &&
+			    cdcd->bDescriptorType == UDESC_ENDPOINT) {
+				ep = ((const usb_endpoint_descriptor_t *)cdcd)->
+				    bEndpointAddress;
+				if (UE_GET_ADDR(ep) ==
+				    UE_GET_ADDR(ed->bEndpointAddress)) {
+					cdcd = (const usb_cdc_descriptor_t *)
+					    usb_desc_iter_next(&iter);
+					break;
+				}
+				ep = 0;
+			}
+		}
+		if (cdcd != NULL &&
+		    cdcd->bDescriptorType == UDESC_ENDPOINT_SS_COMP) {
+			esscd = (const usb_endpoint_ss_comp_descriptor_t *)cdcd;
+			maxb = esscd->bMaxBurst;
+			cp[1] |= htole32(XHCI_EPCTX_1_MAXB_SET(maxb));
+			DPRINTFN(4, "setting SS MaxBurst %u", maxb, 0, 0, 0);
+		}
+	}
+	if (speed == USB_SPEED_HIGH &&
+	    (xfertype == UE_ISOCHRONOUS || xfertype == UE_INTERRUPT)) {
+		maxb = UE_GET_TRANS(UGETW(ed->wMaxPacketSize));
+		cp[1] |= htole32(XHCI_EPCTX_1_MAXB_SET(maxb));
+		DPRINTFN(4, "setting HS MaxBurst %u", maxb, 0, 0, 0);
+	}
+
+	switch (xfertype) {
+	case UE_INTERRUPT:
+		/* 6.2.3.6  */
+		if (speed == USB_SPEED_LOW || speed == USB_SPEED_FULL) {
+			ival = ival > 10 ? 10 : ival;
+			ival = ival < 3 ? 3 : ival;
+		} else {
+			ival = ival > 15 ? 15 : ival;
+		}
+		if (speed == USB_SPEED_SUPER) {
+			if (maxb > 0)
+				mps = 1024;
+		} else {
+			mps = mps ? mps : 8;
+		}
+		cp[0] |= htole32(XHCI_EPCTX_0_IVAL_SET(ival));
+		cp[1] |= htole32(XHCI_EPCTX_1_MAXP_SIZE_SET(mps));
 		cp[4] = htole32(
-		    XHCI_EPCTX_4_AVG_TRB_LEN_SET(8)
-		    );
-	} else {
-		cp[0] = htole32(0);
-		cp[1] = htole32(
-		    XHCI_EPCTX_1_CERR_SET(3) |
-		    XHCI_EPCTX_1_EPTYPE_SET(eptype) |
-		    XHCI_EPCTX_1_MAXB_SET(0) |
-		    XHCI_EPCTX_1_MAXP_SIZE_SET(512) /* XXX */
+		    XHCI_EPCTX_4_AVG_TRB_LEN_SET(8) /* XXX */
 		    );
+		break;
+	case UE_CONTROL:
+		if (speed == USB_SPEED_SUPER)
+			mps = 512;
+		else
+			mps = mps ? mps : 8;
+		cp[1] |= htole32(XHCI_EPCTX_1_MAXP_SIZE_SET(mps));
+		cp[4] = htole32(XHCI_EPCTX_4_AVG_TRB_LEN_SET(8)); /* XXX */
+		break;
+#ifdef notyet
+	case UE_ISOCHRONOUS:
+		if (speed == USB_SPEED_FULL) {
+			ival = ival > 18 ? 18 : ival;
+			ival = ival < 3 ? 3 : ival;
+		} else {
+			ival = ival > 15 ? 15 : ival;
+		}
+		if (speed == USB_SPEED_SUPER) {
+			mps = 1024;
+		} else {
+			mps = mps ? mps : 1024;
+		}
+		cp[1] |= htole32(XHCI_EPCTX_1_MAXP_SIZE_SET(mps));
+		cp[4] = htole32(XHCI_EPCTX_4_AVG_TRB_LEN_SET(1024)); /* XXX */
+		break;
+#endif
+	default:
+		if (speed == USB_SPEED_SUPER)
+			mps = 1024;
+		else
+			mps = mps ? mps : 512;
+		cp[1] |= htole32(XHCI_EPCTX_1_MAXP_SIZE_SET(mps));
+		cp[4] = htole32(XHCI_EPCTX_4_AVG_TRB_LEN_SET(1024)); /* XXX */
+		break;
 	}
 	*(uint64_t *)(&cp[2]) = htole64(
 	    xhci_ring_trbp(&xs->xs_ep[dci].xe_tr, 0) |
@@ -1014,6 +1294,7 @@ xhci_configure_endpoint(struct usbd_pipe
 	return err;
 }
 
+#if 0
 static usbd_status
 xhci_unconfigure_endpoint(struct usbd_pipe *pipe)
 {
@@ -1026,6 +1307,7 @@ xhci_unconfigure_endpoint(struct usbd_pi
 
 	return USBD_NORMAL_COMPLETION;
 }
+#endif
 
 static usbd_status
 xhci_reset_endpoint(struct usbd_pipe *pipe)
@@ -1037,7 +1319,9 @@ xhci_reset_endpoint(struct usbd_pipe *pi
 	usbd_status err;
 
 	XHCIHIST_FUNC(); XHCIHIST_CALLED();
-	DPRINTFN(4, "dci %u", dci, 0, 0, 0);
+	DPRINTFN(4, "slot %u dci %u", xs->xs_idx, dci, 0, 0);
+
+	KASSERT(!mutex_owned(&sc->sc_lock));
 
 	trb.trb_0 = 0;
 	trb.trb_2 = 0;
@@ -1050,7 +1334,6 @@ xhci_reset_endpoint(struct usbd_pipe *pi
 	return err;
 }
 
-#if 0
 static usbd_status
 xhci_stop_endpoint(struct usbd_pipe *pipe)
 {
@@ -1061,7 +1344,9 @@ xhci_stop_endpoint(struct usbd_pipe *pip
 	const u_int dci = xhci_ep_get_dci(pipe->up_endpoint->ue_edesc);
 
 	XHCIHIST_FUNC(); XHCIHIST_CALLED();
-	DPRINTFN(4, "dci %u", dci, 0, 0, 0);
+	DPRINTFN(4, "slot %u dci %u", xs->xs_idx, dci, 0, 0);
+
+	KASSERT(mutex_owned(&sc->sc_lock));
 
 	trb.trb_0 = 0;
 	trb.trb_2 = 0;
@@ -1069,11 +1354,10 @@ xhci_stop_endpoint(struct usbd_pipe *pip
 	    XHCI_TRB_3_EP_SET(dci) |
 	    XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_STOP_EP);
 
-	err = xhci_do_command(sc, &trb, USBD_DEFAULT_TIMEOUT);
+	err = xhci_do_command_locked(sc, &trb, USBD_DEFAULT_TIMEOUT);
 
 	return err;
 }
-#endif
 
 static usbd_status
 xhci_set_dequeue(struct usbd_pipe *pipe)
@@ -1116,14 +1400,14 @@ xhci_open(struct usbd_pipe *pipe)
 
 	XHCIHIST_FUNC(); XHCIHIST_CALLED();
 	DPRINTFN(1, "addr %d depth %d port %d speed %d",
-	    dev->ud_addr, dev->ud_depth, dev->ud_powersrc->up_portno, dev->ud_speed);
+	    dev->ud_addr, dev->ud_depth, dev->ud_powersrc->up_portno,
+	    dev->ud_speed);
 
 	if (sc->sc_dying)
 		return USBD_IOERROR;
 
 	/* Root Hub */
-	if (dev->ud_depth == 0 && dev->ud_powersrc->up_portno == 0 &&
-	    dev->ud_speed != USB_SPEED_SUPER) {
+	if (dev->ud_depth == 0 && dev->ud_powersrc->up_portno == 0) {
 		switch (ed->bEndpointAddress) {
 		case USB_CONTROL_ENDPOINT:
 			pipe->up_methods = &roothub_ctrl_methods;
@@ -1160,11 +1444,146 @@ xhci_open(struct usbd_pipe *pipe)
 	}
 
 	if (ed->bEndpointAddress != USB_CONTROL_ENDPOINT)
-		xhci_configure_endpoint(pipe);
+		return xhci_configure_endpoint(pipe);
 
 	return USBD_NORMAL_COMPLETION;
 }
 
+static usbd_status
+xhci_close_pipe(struct usbd_pipe *pipe)
+{
+	struct xhci_softc * const sc = pipe->up_dev->ud_bus->ub_hcpriv;
+	struct xhci_slot * const xs = pipe->up_dev->ud_hcpriv;
+	usb_endpoint_descriptor_t * const ed = pipe->up_endpoint->ue_edesc;
+	const u_int dci = xhci_ep_get_dci(ed);
+	struct xhci_trb trb;
+	usbd_status err;
+	uint32_t *cp;
+
+	XHCIHIST_FUNC(); XHCIHIST_CALLED();
+
+	if (sc->sc_dying)
+		return USBD_IOERROR;
+
+	if (xs == NULL || xs->xs_idx == 0)
+		/* xs is uninitialized before xhci_init_slot */
+		return USBD_IOERROR;
+
+	DPRINTFN(4, "slot %u dci %u", xs->xs_idx, dci, 0, 0);
+
+	KASSERTMSG(!cpu_intr_p() && !cpu_softintr_p(), "called from intr ctx");
+	KASSERT(mutex_owned(&sc->sc_lock));
+
+	if (pipe->up_dev->ud_depth == 0)
+		return USBD_NORMAL_COMPLETION;
+
+	if (dci == XHCI_DCI_EP_CONTROL) {
+		DPRINTFN(4, "closing ep0", 0, 0, 0, 0);
+		return xhci_disable_slot(sc, xs->xs_idx);
+	}
+
+	(void)xhci_stop_endpoint(pipe);
+
+	/*
+	 * set appropriate bit to be dropped.
+	 * don't set DC bit to 1, otherwise all endpoints
+	 * would be deconfigured.
+	 */
+	cp = xhci_slot_get_icv(sc, xs, XHCI_ICI_INPUT_CONTROL);
+	cp[0] = htole32(XHCI_INCTX_0_DROP_MASK(dci));
+	cp[1] = htole32(0);
+
+	/* XXX should be most significant one, not dci? */
+	cp = xhci_slot_get_icv(sc, xs, xhci_dci_to_ici(XHCI_DCI_SLOT));
+	cp[0] = htole32(XHCI_SCTX_0_CTX_NUM_SET(dci));
+
+	/* sync input contexts before they are read from memory */
+	usb_syncmem(&xs->xs_ic_dma, 0, sc->sc_pgsz, BUS_DMASYNC_PREWRITE);
+
+	trb.trb_0 = xhci_slot_get_icp(sc, xs, 0);
+	trb.trb_2 = 0;
+	trb.trb_3 = XHCI_TRB_3_SLOT_SET(xs->xs_idx) |
+	    XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_CONFIGURE_EP);
+
+	err = xhci_do_command_locked(sc, &trb, USBD_DEFAULT_TIMEOUT);
+	usb_syncmem(&xs->xs_dc_dma, 0, sc->sc_pgsz, BUS_DMASYNC_POSTREAD);
+
+	return err;
+}
+
+static void
+xhci_abort_xfer(struct usbd_xfer *xfer, usbd_status status)
+{
+	struct xhci_softc * const sc = xfer->ux_pipe->up_dev->ud_bus->ub_hcpriv;
+
+	XHCIHIST_FUNC(); XHCIHIST_CALLED();
+	DPRINTFN(4, "xfer %p pipe %p status %d",
+	    xfer, xfer->ux_pipe, status, 0);
+
+	KASSERT(mutex_owned(&sc->sc_lock));
+
+	if (sc->sc_dying) {
+		/* If we're dying, just do the software part. */
+		DPRINTFN(4, "dying", 0, 0, 0, 0);
+		xfer->ux_status = status;  /* make software ignore it */
+		callout_stop(&xfer->ux_callout);
+		usb_transfer_complete(xfer);
+		return;
+	}
+
+	/* XXX need more stuff */
+	xfer->ux_status = status;
+	callout_stop(&xfer->ux_callout);
+	usb_transfer_complete(xfer);
+
+	KASSERT(mutex_owned(&sc->sc_lock));
+}
+
+#if 1 /* XXX experimental */
+static void
+xhci_clear_endpoint_stall_async_task(void *cookie)
+{
+	struct usbd_xfer * const xfer = cookie;
+	struct xhci_softc * const sc = xfer->ux_pipe->up_dev->ud_bus->ub_hcpriv;
+	struct xhci_slot * const xs = xfer->ux_pipe->up_dev->ud_hcpriv;
+	const u_int dci = xhci_ep_get_dci(xfer->ux_pipe->up_endpoint->ue_edesc);
+	struct xhci_ring * const tr = &xs->xs_ep[dci].xe_tr;
+
+	XHCIHIST_FUNC(); XHCIHIST_CALLED();
+	DPRINTFN(4, "xfer %p slot %u dci %u", xfer, xs->xs_idx, dci, 0);
+
+	xhci_reset_endpoint(xfer->ux_pipe);
+	xhci_set_dequeue(xfer->ux_pipe);
+
+	mutex_enter(&sc->sc_lock);
+	tr->is_halted = false;
+	usb_transfer_complete(xfer);
+	mutex_exit(&sc->sc_lock);
+	DPRINTFN(4, "ends", 0, 0, 0, 0);
+}
+
+static usbd_status
+xhci_clear_endpoint_stall_async(struct usbd_xfer *xfer)
+{
+	struct xhci_softc * const sc = xfer->ux_pipe->up_dev->ud_bus->ub_hcpriv;
+
+	XHCIHIST_FUNC(); XHCIHIST_CALLED();
+	DPRINTFN(4, "xfer %p", xfer, 0, 0, 0);
+
+	if (sc->sc_dying) {
+		return USBD_IOERROR;
+	}
+
+	usb_init_task(&xfer->ux_pipe->up_async_task,
+	    xhci_clear_endpoint_stall_async_task, xfer, USB_TASKQ_MPSAFE);
+	usb_add_task(xfer->ux_pipe->up_dev, &xfer->ux_pipe->up_async_task,
+	    USB_TASKQ_HC);
+	DPRINTFN(4, "ends", 0, 0, 0, 0);
+
+	return USBD_NORMAL_COMPLETION;
+}
+
+#endif /* XXX experimental */
 static void
 xhci_rhpsc(struct xhci_softc * const sc, u_int port)
 {
@@ -1177,14 +1596,6 @@ xhci_rhpsc(struct xhci_softc * const sc,
 	if (xfer == NULL)
 		return;
 
-	if (!(port >= sc->sc_hs_port_start &&
-	    port < sc->sc_hs_port_start + sc->sc_hs_port_count))
-		return;
-
-	port -= sc->sc_hs_port_start;
-	port += 1;
-	DPRINTFN(4, "hs port %u status change", port, 0, 0, 0);
-
 	p = xfer->ux_buf;
 	memset(p, 0, xfer->ux_length);
 	p[port/NBBY] |= 1 << (port%NBBY);
@@ -1199,17 +1610,19 @@ xhci_handle_event(struct xhci_softc * co
 {
 	uint64_t trb_0;
 	uint32_t trb_2, trb_3;
+	uint8_t trberr;
 
 	XHCIHIST_FUNC(); XHCIHIST_CALLED();
 
 	trb_0 = le64toh(trb->trb_0);
 	trb_2 = le32toh(trb->trb_2);
 	trb_3 = le32toh(trb->trb_3);
+	trberr = XHCI_TRB_2_ERROR_GET(trb_2);
 
 	DPRINTFN(14, "event: %p 0x%016"PRIx64" 0x%08"PRIx32" 0x%08"PRIx32,
 	    trb, trb_0, trb_2, trb_3);
 
-	switch (XHCI_TRB_3_TYPE_GET(trb_3)){
+	switch (XHCI_TRB_3_TYPE_GET(trb_3)) {
 	case XHCI_TRB_EVENT_TRANSFER: {
 		u_int slot, dci;
 		struct xhci_slot *xs;
@@ -1223,10 +1636,24 @@ xhci_handle_event(struct xhci_softc * co
 
 		xs = &sc->sc_slots[slot];
 		xr = &xs->xs_ep[dci].xe_tr;
+		/* sanity check */
+		KASSERT(xs->xs_idx != 0);
 
 		if ((trb_3 & XHCI_TRB_3_ED_BIT) == 0) {
-			xx = xr->xr_cookies[(trb_0 - xhci_ring_trbp(xr, 0))/
-			    sizeof(struct xhci_trb)];
+			bus_addr_t trbp = xhci_ring_trbp(xr, 0);
+
+			/* trb_0 range sanity check */
+			if (trb_0 < trbp ||
+			    (trb_0 - trbp) % sizeof(struct xhci_trb) != 0 ||
+			    (trb_0 - trbp) / sizeof(struct xhci_trb) >=
+			     xr->xr_ntrb) {
+				DPRINTFN(1,
+				    "invalid trb_0 0x%"PRIx64" trbp 0x%"PRIx64,
+				    trb_0, trbp, 0, 0);
+				break;
+			}
+			int idx = (trb_0 - trbp) / sizeof(struct xhci_trb);
+			xx = xr->xr_cookies[idx];
 		} else {
 			xx = (void *)(uintptr_t)(trb_0 & ~0x3);
 		}
@@ -1243,21 +1670,38 @@ xhci_handle_event(struct xhci_softc * co
 			}
 		}
 
-		if (XHCI_TRB_2_ERROR_GET(trb_2) ==
-		    XHCI_TRB_ERROR_SUCCESS) {
-			xfer->ux_actlen = xfer->ux_length - XHCI_TRB_2_REM_GET(trb_2);
-			err = USBD_NORMAL_COMPLETION;
-		} else if (XHCI_TRB_2_ERROR_GET(trb_2) ==
-		    XHCI_TRB_ERROR_SHORT_PKT) {
-			xfer->ux_actlen = xfer->ux_length - XHCI_TRB_2_REM_GET(trb_2);
+		if (trberr == XHCI_TRB_ERROR_SUCCESS ||
+		    trberr == XHCI_TRB_ERROR_SHORT_PKT) {
+			xfer->ux_actlen =
+			    xfer->ux_length - XHCI_TRB_2_REM_GET(trb_2);
 			err = USBD_NORMAL_COMPLETION;
-		} else if (XHCI_TRB_2_ERROR_GET(trb_2) ==
-		    XHCI_TRB_ERROR_STALL) {
+		} else if (trberr == XHCI_TRB_ERROR_STALL ||
+			   trberr == XHCI_TRB_ERROR_BABBLE) {
 			err = USBD_STALLED;
 			xr->is_halted = true;
-			DPRINTFN(1, "ev: xfer done: err %u slot %u dci %u",
-			    XHCI_TRB_2_ERROR_GET(trb_2), slot, dci, 0);
+			DPRINTFN(1, "evh: xfer done: ERR %u slot %u dci %u",
+			    trberr, slot, dci, 0);
+#if 1 /* XXX experimental */
+			/*
+			 * Stalled endpoints can be recoverd by issuing
+			 * command TRB TYPE_RESET_EP on xHCI instead of
+			 * issuing request CLEAR_PORT_FEATURE UF_ENDPOINT_HALT
+			 * on the endpoint. However, this function may be
+			 * called from softint context (e.g. from umass),
+			 * in that case driver gets KASSERT in cv_timedwait
+			 * in xhci_do_command.
+			 * To avoid this, this runs reset_endpoint and
+			 * usb_transfer_complete in usb task thread
+			 * asynchronously (and then umass issues clear
+			 * UF_ENDPOINT_HALT).
+			 */
+			xfer->ux_status = err;
+			xhci_clear_endpoint_stall_async(xfer);
+			break;
+#endif
 		} else {
+			DPRINTFN(1, "evh: xfer done: ERR %u slot %u dci %u",
+			    trberr, slot, dci, 0);
 			err = USBD_IOERROR;
 		}
 		xfer->ux_status = err;
@@ -1287,7 +1731,7 @@ xhci_handle_event(struct xhci_softc * co
 			}
 			cv_signal(&sc->sc_command_cv);
 		} else {
-			DPRINTFN(1, "event: %p 0x%016"PRIx64" "
+			DPRINTFN(1, "spurious event: %p 0x%016"PRIx64" "
 			    "0x%08"PRIx32" 0x%08"PRIx32, trb, trb_0,
 			    trb_2, trb_3);
 		}
@@ -1418,7 +1862,8 @@ xhci_new_device(device_t parent, struct 
 	int rhport = 0;
 	struct xhci_slot *xs;
 	uint32_t *cp;
-	uint8_t slot;
+	uint32_t route = 0;
+	uint8_t slot = 0;
 	uint8_t addr;
 
 	XHCIHIST_FUNC(); XHCIHIST_CALLED();
@@ -1439,11 +1884,21 @@ xhci_new_device(device_t parent, struct 
 	dev->ud_ep0desc.bDescriptorType = UDESC_ENDPOINT;
 	dev->ud_ep0desc.bEndpointAddress = USB_CONTROL_ENDPOINT;
 	dev->ud_ep0desc.bmAttributes = UE_CONTROL;
-	/* XXX */
-	if (speed == USB_SPEED_LOW)
+	/* 4.3,  4.8.2.1 */
+	switch (speed) {
+	case USB_SPEED_SUPER:
+		USETW(dev->ud_ep0desc.wMaxPacketSize, USB_3_MAX_CTRL_PACKET);
+		break;
+	case USB_SPEED_FULL:
+		/* XXX using 64 as initial mps of ep0 in FS */
+	case USB_SPEED_HIGH:
+		USETW(dev->ud_ep0desc.wMaxPacketSize, USB_2_MAX_CTRL_PACKET);
+		break;
+	case USB_SPEED_LOW:
+	default:
 		USETW(dev->ud_ep0desc.wMaxPacketSize, USB_MAX_IPACKET);
-	else
-		USETW(dev->ud_ep0desc.wMaxPacketSize, 64);
+		break;
+	}
 	dev->ud_ep0desc.bInterval = 0;
 
 	/* doesn't matter, just don't let it uninitialized */
@@ -1461,28 +1916,48 @@ xhci_new_device(device_t parent, struct 
 	up->up_dev = dev;
 
 	/* Locate root hub port */
-	for (adev = dev, hub = dev;
-	    hub != NULL;
-	    adev = hub, hub = hub->ud_myhub) {
-		DPRINTFN(4, "hub %p", hub, 0, 0, 0);
-	}
-	DPRINTFN(4, "hub %p", hub, 0, 0, 0);
+	for (hub = dev; hub != NULL; hub = hub->ud_myhub) {
+		uint32_t dep;
+
+		DPRINTFN(4, "hub %p depth %d upport %p upportno %d",
+		    hub, hub->ud_depth, hub->ud_powersrc,
+		    hub->ud_powersrc ? hub->ud_powersrc->up_portno : -1);
+
+		if (hub->ud_powersrc == NULL)
+			break;
+		dep = hub->ud_depth;
+		if (dep == 0)
+			break;
+		rhport = hub->ud_powersrc->up_portno;
+		if (dep > USB_HUB_MAX_DEPTH)
+			continue;
 
-	if (hub != NULL) {
-		for (int p = 0; p < hub->ud_hub->uh_hubdesc.bNbrPorts; p++) {
+		route |=
+		    (rhport > UHD_SS_NPORTS_MAX ? UHD_SS_NPORTS_MAX : rhport)
+		    << ((dep - 1) * 4);
+	}
+	route = route >> 4;
+	DPRINTFN(4, "rhport %d Route %05x hub %p", rhport, route, hub, 0);
+
+	/* Locate port on upstream high speed hub */
+	for (adev = dev, hub = up->up_parent;
+	     hub != NULL && hub->ud_speed != USB_SPEED_HIGH;
+	     adev = hub, hub = hub->ud_myhub)
+		;
+	if (hub) {
+		int p;
+		for (p = 0; p < hub->ud_hub->uh_hubdesc.bNbrPorts; p++) {
 			if (hub->ud_hub->uh_ports[p].up_dev == adev) {
-				rhport = p;
+				dev->ud_myhsport = &hub->ud_hub->uh_ports[p];
+				goto found;
 			}
 		}
+		panic("xhci_new_device: cannot find HS port");
+	found:
+		DPRINTFN(4, "high speed port %d", p, 0, 0, 0);
 	} else {
-		rhport = port;
+		dev->ud_myhsport = NULL;
 	}
-	if (speed == USB_SPEED_SUPER) {
-		rhport += sc->sc_ss_port_start - 1;
-	} else {
-		rhport += sc->sc_hs_port_start - 1;
-	}
-	DPRINTFN(4, "rhport %d", rhport, 0, 0, 0);
 
 	dev->ud_speed = speed;
 	dev->ud_langid = USBD_NOLANG;
@@ -1492,8 +1967,7 @@ xhci_new_device(device_t parent, struct 
 	err = usbd_setup_pipe(dev, 0, &dev->ud_ep0, USBD_DEFAULT_INTERVAL,
 	    &dev->ud_pipe0);
 	if (err) {
-		usbd_remove_device(dev, up);
-		return err;
+		goto bad;
 	}
 
 	dd = &dev->ud_ddesc;
@@ -1503,19 +1977,24 @@ xhci_new_device(device_t parent, struct 
 		bus->ub_devices[dev->ud_addr] = dev;
 		err = usbd_get_initial_ddesc(dev, dd);
 		if (err)
-			return err;
+			goto bad;
 		err = usbd_reload_device_desc(dev);
 		if (err)
-			return err;
+			goto bad;
 	} else {
 		err = xhci_enable_slot(sc, &slot);
 		if (err)
-			return err;
-		err = xhci_init_slot(sc, slot, depth, speed, port, rhport);
-		if (err)
-			return err;
+			goto bad;
 		xs = &sc->sc_slots[slot];
 		dev->ud_hcpriv = xs;
+		err = xhci_init_slot(dev, slot, route, rhport);
+		if (err) {
+			dev->ud_hcpriv = NULL;
+			goto bad;
+		}
+
+		/* Allow device time to set new address */
+		usbd_delay_ms(dev, USB_SET_ADDRESS_SETTLE);
 		cp = xhci_slot_get_dcv(sc, xs, XHCI_DCI_SLOT);
 		//hexdump("slot context", cp, sc->sc_ctxsz);
 		addr = XHCI_SCTX_3_DEV_ADDR_GET(cp[3]);
@@ -1530,12 +2009,19 @@ xhci_new_device(device_t parent, struct 
 
 		err = usbd_get_initial_ddesc(dev, dd);
 		if (err)
-			return err;
+			goto bad;
 		/* 4.8.2.1 */
-		if (speed == USB_SPEED_SUPER)
+		if (speed == USB_SPEED_SUPER) {
+			if (dd->bMaxPacketSize != 9) {
+				printf("%s: invalid mps 2^%u for SS ep0,"
+				    " using 512\n",
+				    device_xname(sc->sc_dev),
+				    dd->bMaxPacketSize);
+				dd->bMaxPacketSize = 9;
+			}
 			USETW(dev->ud_ep0desc.wMaxPacketSize,
 			    (1 << dd->bMaxPacketSize));
-		else
+		} else
 	 		USETW(dev->ud_ep0desc.wMaxPacketSize,
 			    dd->bMaxPacketSize);
 		DPRINTFN(4, "bMaxPacketSize %u", dd->bMaxPacketSize, 0, 0, 0);
@@ -1543,11 +2029,15 @@ xhci_new_device(device_t parent, struct 
 		    UGETW(dev->ud_ep0desc.wMaxPacketSize));
 		err = usbd_reload_device_desc(dev);
 		if (err)
-			return err;
+			goto bad;
 
+#if 0
+		/* Re-establish the default pipe with the new MPS. */
+		/* In xhci this is done by xhci_update_ep0_mps. */
 		usbd_kill_pipe(dev->ud_pipe0);
 		err = usbd_setup_pipe(dev, 0, &dev->ud_ep0,
 		    USBD_DEFAULT_INTERVAL, &dev->ud_pipe0);
+#endif
 	}
 
 	DPRINTFN(1, "adding unit addr=%d, rev=%02x,",
@@ -1569,12 +2059,12 @@ xhci_new_device(device_t parent, struct 
 
 
 	err = usbd_probe_and_attach(parent, dev, port, dev->ud_addr);
-	if (err) {
+ bad:
+	if (err != USBD_NORMAL_COMPLETION) {
 		usbd_remove_device(dev, up);
-		return err;
 	}
 
-	return USBD_NORMAL_COMPLETION;
+	return err;
 }
 
 static usbd_status
@@ -1713,8 +2203,8 @@ xhci_ring_put(struct xhci_softc * const 
 }
 
 static usbd_status
-xhci_do_command(struct xhci_softc * const sc, struct xhci_trb * const trb,
-    int timeout)
+xhci_do_command1(struct xhci_softc * const sc, struct xhci_trb * const trb,
+    int timeout, int locked)
 {
 	struct xhci_ring * const cr = &sc->sc_cr;
 	usbd_status err;
@@ -1723,7 +2213,10 @@ xhci_do_command(struct xhci_softc * cons
 	DPRINTFN(12, "input: 0x%016"PRIx64" 0x%08"PRIx32" 0x%08"PRIx32,
 	    trb->trb_0, trb->trb_2, trb->trb_3, 0);
 
-	mutex_enter(&sc->sc_lock);
+	KASSERTMSG(!cpu_intr_p() && !cpu_softintr_p(), "called from intr ctx");
+
+	if (!locked)
+		mutex_enter(&sc->sc_lock);
 
 	KASSERT(sc->sc_command_addr == 0);
 	sc->sc_command_addr = xhci_ring_trbp(cr, cr->xr_ep);
@@ -1762,11 +2255,26 @@ xhci_do_command(struct xhci_softc * cons
 
 timedout:
 	sc->sc_command_addr = 0;
-	mutex_exit(&sc->sc_lock);
+	if (!locked)
+		mutex_exit(&sc->sc_lock);
 	return err;
 }
 
 static usbd_status
+xhci_do_command(struct xhci_softc * const sc, struct xhci_trb * const trb,
+    int timeout)
+{
+	return xhci_do_command1(sc, trb, timeout, 0);
+}
+
+static usbd_status
+xhci_do_command_locked(struct xhci_softc * const sc,
+    struct xhci_trb * const trb, int timeout)
+{
+	return xhci_do_command1(sc, trb, timeout, 1);
+}
+
+static usbd_status
 xhci_enable_slot(struct xhci_softc * const sc, uint8_t * const slotp)
 {
 	struct xhci_trb trb;
@@ -1789,6 +2297,36 @@ xhci_enable_slot(struct xhci_softc * con
 }
 
 static usbd_status
+xhci_disable_slot(struct xhci_softc * const sc, uint8_t slot)
+{
+	struct xhci_trb trb;
+	struct xhci_slot *xs;
+
+	XHCIHIST_FUNC(); XHCIHIST_CALLED();
+
+	if (sc->sc_dying)
+		return USBD_IOERROR;
+
+	xs = &sc->sc_slots[slot];
+	if (xs->xs_idx != 0) {
+		for (int i = XHCI_DCI_SLOT + 1; i < 32; i++) {
+			xhci_ring_free(sc, &xs->xs_ep[i].xe_tr);
+			memset(&xs->xs_ep[i], 0, sizeof(xs->xs_ep[i]));
+		}
+		usb_freemem(&sc->sc_bus, &xs->xs_ic_dma);
+		usb_freemem(&sc->sc_bus, &xs->xs_dc_dma);
+	}
+
+	trb.trb_0 = 0;
+	trb.trb_2 = 0;
+	trb.trb_3 = htole32(
+		XHCI_TRB_3_SLOT_SET(slot) |
+		XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_DISABLE_SLOT));
+
+	return xhci_do_command_locked(sc, &trb, USBD_DEFAULT_TIMEOUT);
+}
+
+static usbd_status
 xhci_address_device(struct xhci_softc * const sc,
     uint64_t icp, uint8_t slot_id, bool bsr)
 {
@@ -1855,46 +2393,20 @@ xhci_set_dcba(struct xhci_softc * const 
 }
 
 static usbd_status
-xhci_init_slot(struct xhci_softc * const sc, uint32_t slot, int depth,
-    int speed, int port, int rhport)
+xhci_init_slot(struct usbd_device *dev, uint32_t slot, int route, int rhport)
 {
+	struct xhci_softc * const sc = dev->ud_bus->ub_hcpriv;
 	struct xhci_slot *xs;
 	usbd_status err;
 	u_int dci;
 	uint32_t *cp;
-	uint32_t mps;
-	uint32_t xspeed;
+	uint32_t mps = UGETW(dev->ud_ep0desc.wMaxPacketSize);
 
 	XHCIHIST_FUNC(); XHCIHIST_CALLED();
-	DPRINTFN(4, "slot %u depth %d speed %d",
-	    slot, depth, speed, 0);
-	DPRINTFN(4, " port %d rhport %d",
-	    port, rhport, 0, 0);
-
-	switch (speed) {
-	case USB_SPEED_LOW:
-		xspeed = 2;
-		mps = USB_MAX_IPACKET;
-		break;
-	case USB_SPEED_FULL:
-		xspeed = 1;
-		mps = 64;
-		break;
-	case USB_SPEED_HIGH:
-		xspeed = 3;
-		mps = USB_2_MAX_CTRL_PACKET;
-		break;
-	case USB_SPEED_SUPER:
-		xspeed = 4;
-		mps = USB_3_MAX_CTRL_PACKET;
-		break;
-	default:
-		DPRINTFN(0, "impossible speed: %x", speed, 0, 0, 0);
-		return USBD_INVAL;
-	}
+	DPRINTFN(4, "slot %u speed %d rhport %d route %05x",
+	    slot, dev->ud_speed, route, rhport);
 
 	xs = &sc->sc_slots[slot];
-	xs->xs_idx = slot;
 
 	/* allocate contexts */
 	err = usb_allocmem(&sc->sc_bus, sc->sc_pgsz, sc->sc_pgsz,
@@ -1906,7 +2418,7 @@ xhci_init_slot(struct xhci_softc * const
 	err = usb_allocmem(&sc->sc_bus, sc->sc_pgsz, sc->sc_pgsz,
 	    &xs->xs_ic_dma);
 	if (err)
-		return err;
+		goto bad1;
 	memset(KERNADDR(&xs->xs_ic_dma, 0), 0, sc->sc_pgsz);
 
 	for (dci = 0; dci < 32; dci++) {
@@ -1918,7 +2430,7 @@ xhci_init_slot(struct xhci_softc * const
 		    XHCI_TRANSFER_RING_TRBS, XHCI_TRB_ALIGN);
 		if (err) {
 			DPRINTFN(0, "ring init failure", 0, 0, 0, 0);
-			return err;
+			goto bad2;
 		}
 	}
 
@@ -1930,17 +2442,10 @@ xhci_init_slot(struct xhci_softc * const
 
 	/* set up input slot context */
 	cp = xhci_slot_get_icv(sc, xs, xhci_dci_to_ici(XHCI_DCI_SLOT));
-	cp[0] = htole32(
-		XHCI_SCTX_0_CTX_NUM_SET(1) |
-		XHCI_SCTX_0_SPEED_SET(xspeed)
-		);
-	cp[1] = htole32(
-		XHCI_SCTX_1_RH_PORT_SET(rhport)
-		);
-	cp[2] = htole32(
-		XHCI_SCTX_2_IRQ_TARGET_SET(0)
-		);
-	cp[3] = htole32(0);
+	xhci_setup_sctx(dev, cp);
+	cp[0] |= htole32(XHCI_SCTX_0_CTX_NUM_SET(1));
+	cp[0] |= htole32(XHCI_SCTX_0_ROUTE_SET(route));
+	cp[1] |= htole32(XHCI_SCTX_1_RH_PORT_SET(rhport));
 
 	/* set up input EP0 context */
 	cp = xhci_slot_get_icv(sc, xs, xhci_dci_to_ici(XHCI_DCI_EP_CONTROL));
@@ -1972,6 +2477,20 @@ xhci_init_slot(struct xhci_softc * const
 	hexdump("output context", xhci_slot_get_dcv(sc, xs, 0),
 	    sc->sc_ctxsz * 2);
 
+ bad2:
+	if (err == USBD_NORMAL_COMPLETION) {
+		xs->xs_idx = slot;
+	} else {
+		for (int i = 1; i < dci; i++) {
+			xhci_ring_free(sc, &xs->xs_ep[i].xe_tr);
+			memset(&xs->xs_ep[i], 0, sizeof(xs->xs_ep[i]));
+		}
+		usb_freemem(&sc->sc_bus, &xs->xs_ic_dma);
+ bad1:
+		usb_freemem(&sc->sc_bus, &xs->xs_dc_dma);
+		xs->xs_idx = 0;
+	}
+
 	return err;
 }
 
@@ -2031,10 +2550,10 @@ xhci_roothub_ctrl(struct usbd_bus *bus, 
 	case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER):
 		DPRINTFN(4, "UR_CLEAR_PORT_FEATURE port=%d feature=%d",
 			     index, value, 0, 0);
-		if (index < 1 || index > sc->sc_hs_port_count) {
+		if (index < 1 || index > sc->sc_maxports) {
 			return -1;
 		}
-		port = XHCI_PORTSC(sc->sc_hs_port_start - 1 + index);
+		port = XHCI_PORTSC(index);
 		v = xhci_op_read_4(sc, port);
 		DPRINTFN(4, "portsc=0x%08x", v, 0, 0, 0);
 		v &= ~XHCI_PS_CLEAR;
@@ -2056,9 +2575,18 @@ xhci_roothub_ctrl(struct usbd_bus *bus, 
 		case UHF_C_PORT_SUSPEND:
 		case UHF_C_PORT_OVER_CURRENT:
 			return -1;
+		case UHF_C_BH_PORT_RESET:
+			xhci_op_write_4(sc, port, v | XHCI_PS_WRC);
+			break;
 		case UHF_C_PORT_RESET:
 			xhci_op_write_4(sc, port, v | XHCI_PS_PRC);
 			break;
+		case UHF_C_PORT_LINK_STATE:
+			xhci_op_write_4(sc, port, v | XHCI_PS_PLC);
+			break;
+		case UHF_C_PORT_CONFIG_ERROR:
+			xhci_op_write_4(sc, port, v | XHCI_PS_CEC);
+			break;
 		default:
 			return -1;
 		}
@@ -2073,7 +2601,7 @@ xhci_roothub_ctrl(struct usbd_bus *bus, 
 
 		totlen = min(buflen, sizeof(hubd));
 		memcpy(&hubd, buf, totlen);
-		hubd.bNbrPorts = sc->sc_hs_port_count;
+		hubd.bNbrPorts = sc->sc_maxports;
 		USETW(hubd.wHubCharacteristics, UHD_PWR_NO_SWITCH);
 		hubd.bPwrOn2PwrGood = 200;
 		for (i = 0, l = sc->sc_maxports; l > 0; i++, l -= 8)
@@ -2097,10 +2625,8 @@ xhci_roothub_ctrl(struct usbd_bus *bus, 
 		if (len != 4) {
 			return -1;
 		}
-		v = xhci_op_read_4(sc, XHCI_PORTSC(sc->sc_hs_port_start - 1 +
-		    index));
-		DPRINTFN(4, "READ_CLASS_OTHER GET_STATUS PORTSC %d (%d) %08x",
-		    index, sc->sc_hs_port_start - 1 + index, v, 0);
+		v = xhci_op_read_4(sc, XHCI_PORTSC(index));
+		DPRINTFN(4, "getrhportsc %d %08x", index, v, 0, 0);
 		switch (XHCI_PS_SPEED_GET(v)) {
 		case 1:
 			i = UPS_FULL_SPEED;
@@ -2111,6 +2637,9 @@ xhci_roothub_ctrl(struct usbd_bus *bus, 
 		case 3:
 			i = UPS_HIGH_SPEED;
 			break;
+		case 4:
+			i = UPS_SUPER_SPEED;
+			break;
 		default:
 			i = 0;
 			break;
@@ -2120,26 +2649,39 @@ xhci_roothub_ctrl(struct usbd_bus *bus, 
 		if (v & XHCI_PS_OCA)	i |= UPS_OVERCURRENT_INDICATOR;
 		//if (v & XHCI_PS_SUSP)	i |= UPS_SUSPEND;
 		if (v & XHCI_PS_PR)	i |= UPS_RESET;
-		if (v & XHCI_PS_PP)	i |= UPS_PORT_POWER;
+		if (v & XHCI_PS_PP) {
+			if (i & UPS_SUPER_SPEED)
+					i |= UPS_PORT_POWER_SS;
+			else
+					i |= UPS_PORT_POWER;
+		}
 		USETW(ps.wPortStatus, i);
 		i = 0;
 		if (v & XHCI_PS_CSC)    i |= UPS_C_CONNECT_STATUS;
 		if (v & XHCI_PS_PEC)    i |= UPS_C_PORT_ENABLED;
 		if (v & XHCI_PS_OCC)    i |= UPS_C_OVERCURRENT_INDICATOR;
 		if (v & XHCI_PS_PRC)	i |= UPS_C_PORT_RESET;
+		if (v & XHCI_PS_WRC)	i |= UPS_C_BH_PORT_RESET;
+		if (v & XHCI_PS_PLC)	i |= UPS_C_PORT_LINK_STATE;
+		if (v & XHCI_PS_CEC)	i |= UPS_C_PORT_CONFIG_ERROR;
 		USETW(ps.wPortChange, i);
 		totlen = min(len, sizeof(ps));
 		memcpy(buf, &ps, totlen);
 		break;
 	case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE):
 		return -1;
+	case C(UR_SET_HUB_DEPTH, UT_WRITE_CLASS_DEVICE):
+		break;
 	case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE):
 		break;
 	case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER):
-		if (index < 1 || index > sc->sc_hs_port_count) {
+	{
+		int optval = (index >> 8) & 0xff;
+		index &= 0xff;
+		if (index < 1 || index > sc->sc_maxports) {
 			return -1;
 		}
-		port = XHCI_PORTSC(sc->sc_hs_port_start - 1 + index);
+		port = XHCI_PORTSC(index);
 		v = xhci_op_read_4(sc, port);
 		DPRINTFN(4, "portsc=0x%08x", v, 0, 0, 0);
 		v &= ~XHCI_PS_CLEAR;
@@ -2172,9 +2714,30 @@ xhci_roothub_ctrl(struct usbd_bus *bus, 
 		case UHF_C_PORT_RESET:
 			xhci_op_write_4(sc, port, v | XHCI_PS_PRC);
 			break;
+		case UHF_PORT_U1_TIMEOUT:
+			if (XHCI_PS_SPEED_GET(v) != 4) {
+				return -1;
+			}
+			port = XHCI_PORTPMSC(index);
+			v = xhci_op_read_4(sc, port);
+			v &= ~XHCI_PM3_U1TO_SET(0xff);
+			v |= XHCI_PM3_U1TO_SET(optval);
+			xhci_op_write_4(sc, port, v);
+			break;
+		case UHF_PORT_U2_TIMEOUT:
+			if (XHCI_PS_SPEED_GET(v) != 4) {
+				return -1;
+			}
+			port = XHCI_PORTPMSC(index);
+			v = xhci_op_read_4(sc, port);
+			v &= ~XHCI_PM3_U2TO_SET(0xff);
+			v |= XHCI_PM3_U2TO_SET(optval);
+			xhci_op_write_4(sc, port, v);
+			break;
 		default:
 			return -1;
 		}
+	}
 		break;
 	case C(UR_CLEAR_TT_BUFFER, UT_WRITE_CLASS_OTHER):
 	case C(UR_RESET_TT, UT_WRITE_CLASS_OTHER):
@@ -2237,8 +2800,6 @@ xhci_root_intr_abort(struct usbd_xfer *x
 	KASSERT(mutex_owned(&sc->sc_lock));
 	KASSERT(xfer->ux_pipe->up_intrxfer == xfer);
 
-	DPRINTFN(1, "remove", 0, 0, 0, 0);
-
 	sc->sc_intrxfer = NULL;
 
 	xfer->ux_status = USBD_CANCELLED;
@@ -2311,6 +2872,8 @@ xhci_device_ctrl_start(struct usbd_xfer 
 
 	/* XXX */
 	if (tr->is_halted) {
+		DPRINTFN(1, "ctrl xfer %p halted: slot %u dci %u",
+		    xfer, xs->xs_idx, dci, 0);
 		xhci_reset_endpoint(xfer->ux_pipe);
 		tr->is_halted = false;
 		xhci_set_dequeue(xfer->ux_pipe);
@@ -2400,12 +2963,16 @@ static void
 xhci_device_ctrl_abort(struct usbd_xfer *xfer)
 {
 	XHCIHIST_FUNC(); XHCIHIST_CALLED();
+
+	xhci_abort_xfer(xfer, USBD_CANCELLED);
 }
 
 static void
 xhci_device_ctrl_close(struct usbd_pipe *pipe)
 {
 	XHCIHIST_FUNC(); XHCIHIST_CALLED();
+
+	(void)xhci_close_pipe(pipe);
 }
 
 /* ------------------ */
@@ -2518,12 +3085,16 @@ static void
 xhci_device_bulk_abort(struct usbd_xfer *xfer)
 {
 	XHCIHIST_FUNC(); XHCIHIST_CALLED();
+
+	xhci_abort_xfer(xfer, USBD_CANCELLED);
 }
 
 static void
 xhci_device_bulk_close(struct usbd_pipe *pipe)
 {
 	XHCIHIST_FUNC(); XHCIHIST_CALLED();
+
+	(void)xhci_close_pipe(pipe);
 }
 
 /* ---------------- */
@@ -2646,8 +3217,7 @@ xhci_device_intr_abort(struct usbd_xfer 
 	KASSERT(mutex_owned(&sc->sc_lock));
 	DPRINTFN(15, "%p", xfer, 0, 0, 0);
 	KASSERT(xfer->ux_pipe->up_intrxfer == xfer);
-	xfer->ux_status = USBD_CANCELLED;
-	usb_transfer_complete(xfer);
+	xhci_abort_xfer(xfer, USBD_CANCELLED);
 }
 
 static void
@@ -2658,7 +3228,7 @@ xhci_device_intr_close(struct usbd_pipe 
 	XHCIHIST_FUNC(); XHCIHIST_CALLED();
 	DPRINTFN(15, "%p", pipe, 0, 0, 0);
 
-	xhci_unconfigure_endpoint(pipe);
+	(void)xhci_close_pipe(pipe);
 }
 
 /* ------------ */

Reply via email to