svn commit: r213435 - in head/sys/dev/usb: . controller

Hans Petter Selasky hselasky at FreeBSD.org
Mon Oct 4 23:18:06 UTC 2010


Author: hselasky
Date: Mon Oct  4 23:18:05 2010
New Revision: 213435
URL: http://svn.freebsd.org/changeset/base/213435

Log:
  This commit adds full support for USB 3.0 devices in host and device
  mode in the USB core.  The patch mostly consists of updating the USB
  HUB code to support USB 3.0 HUBs. This patch also add some more USB
  controller methods to support more active-alike USB controllers like
  the XHCI which needs to be informed about various device state events.
  
  USB 3.0 HUBs are not tested yet, due to lack of hardware, but are
  believed to work.
  
  After this update the initial device descriptor is only read twice
  when we know that the bMaxPacketSize is too small for a single packet
  transfer of this descriptor.
  
  Approved by:    thompsa (mentor)

Modified:
  head/sys/dev/usb/controller/usb_controller.c
  head/sys/dev/usb/usb_controller.h
  head/sys/dev/usb/usb_device.c
  head/sys/dev/usb/usb_device.h
  head/sys/dev/usb/usb_hub.c
  head/sys/dev/usb/usb_hub.h
  head/sys/dev/usb/usb_parse.c
  head/sys/dev/usb/usb_request.c
  head/sys/dev/usb/usb_request.h
  head/sys/dev/usb/usb_transfer.c
  head/sys/dev/usb/usb_transfer.h
  head/sys/dev/usb/usbdi.h
  head/sys/dev/usb/usbdi_util.h

Modified: head/sys/dev/usb/controller/usb_controller.c
==============================================================================
--- head/sys/dev/usb/controller/usb_controller.c	Mon Oct  4 22:50:08 2010	(r213434)
+++ head/sys/dev/usb/controller/usb_controller.c	Mon Oct  4 23:18:05 2010	(r213435)
@@ -107,6 +107,7 @@ static driver_t usb_driver = {
 DRIVER_MODULE(usbus, ohci, usb_driver, usb_devclass, 0, 0);
 DRIVER_MODULE(usbus, uhci, usb_driver, usb_devclass, 0, 0);
 DRIVER_MODULE(usbus, ehci, usb_driver, usb_devclass, 0, 0);
+DRIVER_MODULE(usbus, xhci, usb_driver, usb_devclass, 0, 0);
 
 /* Device Only Drivers */
 DRIVER_MODULE(usbus, at91_udp, usb_driver, usb_devclass, 0, 0);
@@ -359,6 +360,11 @@ usb_bus_attach(struct usb_proc_msg *pm)
 		device_printf(bus->bdev, "480Mbps Wireless USB v2.5\n");
 		break;
 
+	case USB_REV_3_0:
+		speed = USB_SPEED_SUPER;
+		device_printf(bus->bdev, "4.8Gbps Super Speed USB v3.0\n");
+		break;
+
 	default:
 		device_printf(bus->bdev, "Unsupported USB revision\n");
 		usb_root_mount_rel(bus);

Modified: head/sys/dev/usb/usb_controller.h
==============================================================================
--- head/sys/dev/usb/usb_controller.h	Mon Oct  4 22:50:08 2010	(r213434)
+++ head/sys/dev/usb/usb_controller.h	Mon Oct  4 23:18:05 2010	(r213435)
@@ -97,6 +97,9 @@ struct usb_bus_methods {
 
 	void    (*get_hw_ep_profile) (struct usb_device *udev, const struct usb_hw_ep_profile **ppf, uint8_t ep_addr);
 	void    (*set_stall) (struct usb_device *udev, struct usb_xfer *xfer, struct usb_endpoint *ep, uint8_t *did_stall);
+
+	/* USB Device mode mandatory. USB Host mode optional. */
+
 	void    (*clear_stall) (struct usb_device *udev, struct usb_endpoint *ep);
 
 	/* Optional transfer polling support */
@@ -106,6 +109,28 @@ struct usb_bus_methods {
 	/* Optional fixed power mode support */
 
 	void	(*get_power_mode) (struct usb_device *udev, int8_t *pmode);
+
+	/* Optional endpoint uninit */
+
+	void    (*endpoint_uninit) (struct usb_device *, struct usb_endpoint *);
+
+	/* Optional device init */
+
+	usb_error_t	(*device_init) (struct usb_device *);
+
+	/* Optional device uninit */
+
+	void	(*device_uninit) (struct usb_device *);
+
+	/* Optional for device and host mode */
+
+	void	(*start_dma_delay) (struct usb_xfer *);
+
+	void	(*device_state_change) (struct usb_device *);
+
+	/* Optional for host mode */
+
+	usb_error_t	(*set_address) (struct usb_device *, struct mtx *, uint16_t);
 };
 
 /*

Modified: head/sys/dev/usb/usb_device.c
==============================================================================
--- head/sys/dev/usb/usb_device.c	Mon Oct  4 22:50:08 2010	(r213434)
+++ head/sys/dev/usb/usb_device.c	Mon Oct  4 23:18:05 2010	(r213435)
@@ -45,12 +45,16 @@
 #include <sys/priv.h>
 #include <sys/conf.h>
 #include <sys/fcntl.h>
-#include <sys/sbuf.h>
 
 #include <dev/usb/usb.h>
 #include <dev/usb/usbdi.h>
 #include <dev/usb/usbdi_util.h>
 #include <dev/usb/usb_ioctl.h>
+
+#if USB_HAVE_UGEN
+#include <sys/sbuf.h>
+#endif
+
 #include "usbdevs.h"
 
 #define	USB_DEBUG_VAR usb_debug
@@ -79,7 +83,9 @@
 /* function prototypes  */
 
 static void	usb_init_endpoint(struct usb_device *, uint8_t,
-		    struct usb_endpoint_descriptor *, struct usb_endpoint *);
+		    struct usb_endpoint_descriptor *,
+		    struct usb_endpoint_ss_comp_descriptor *,
+		    struct usb_endpoint *);
 static void	usb_unconfigure(struct usb_device *, uint8_t);
 static void	usb_detach_device_sub(struct usb_device *, device_t *,
 		    uint8_t);
@@ -90,7 +96,7 @@ static void	usb_init_attach_arg(struct u
 static void	usb_suspend_resume_sub(struct usb_device *, device_t,
 		    uint8_t);
 static void	usbd_clear_stall_proc(struct usb_proc_msg *_pm);
-usb_error_t	usb_config_parse(struct usb_device *, uint8_t, uint8_t);
+static usb_error_t usb_config_parse(struct usb_device *, uint8_t, uint8_t);
 static void	usbd_set_device_strings(struct usb_device *);
 #if USB_HAVE_UGEN
 static void	usb_notify_addq(const char *type, struct usb_device *);
@@ -360,7 +366,9 @@ usbd_interface_count(struct usb_device *
  *------------------------------------------------------------------------*/
 static void
 usb_init_endpoint(struct usb_device *udev, uint8_t iface_index,
-    struct usb_endpoint_descriptor *edesc, struct usb_endpoint *ep)
+    struct usb_endpoint_descriptor *edesc,
+    struct usb_endpoint_ss_comp_descriptor *ecomp,
+    struct usb_endpoint *ep)
 {
 	struct usb_bus_methods *methods;
 
@@ -370,6 +378,7 @@ usb_init_endpoint(struct usb_device *ude
 
 	/* initialise USB endpoint structure */
 	ep->edesc = edesc;
+	ep->ecomp = ecomp;
 	ep->iface_index = iface_index;
 	TAILQ_INIT(&ep->endpoint_q.head);
 	ep->endpoint_q.command = &usbd_pipe_start;
@@ -640,7 +649,7 @@ done:
  *    0: Success
  * Else: Failure
  *------------------------------------------------------------------------*/
-usb_error_t
+static usb_error_t
 usb_config_parse(struct usb_device *udev, uint8_t iface_index, uint8_t cmd)
 {
 	struct usb_idesc_parse_state ips;
@@ -763,8 +772,14 @@ usb_config_parse(struct usb_device *udev
 			ep = udev->endpoints + temp;
 
 			if (do_init) {
+				void *ecomp;
+
+				ecomp = usb_ed_comp_foreach(udev->cdesc, (void *)ed);
+				if (ecomp != NULL)
+					DPRINTFN(5, "Found endpoint companion descriptor\n");
+
 				usb_init_endpoint(udev, 
-				    ips.iface_index, ed, ep);
+				    ips.iface_index, ed, ecomp, ep);
 			}
 
 			temp ++;
@@ -904,8 +919,8 @@ done:
 /*------------------------------------------------------------------------*
  *	usbd_set_endpoint_stall
  *
- * This function is used to make a BULK or INTERRUPT endpoint
- * send STALL tokens.
+ * This function is used to make a BULK or INTERRUPT endpoint send
+ * STALL tokens in USB device mode.
  *
  * Returns:
  *    0: Success
@@ -1536,7 +1551,6 @@ usb_alloc_device(device_t parent_dev, st
 	udev->bus = bus;
 	udev->address = USB_START_ADDR;	/* default value */
 	udev->plugtime = (usb_ticks_t)ticks;
-	usb_set_device_state(udev, USB_STATE_POWERED);
 	/*
 	 * We need to force the power mode to "on" because there are plenty
 	 * of USB devices out there that do not work very well with
@@ -1555,6 +1569,11 @@ usb_alloc_device(device_t parent_dev, st
 	udev->ctrl_ep_desc.wMaxPacketSize[0] = USB_MAX_IPACKET;
 	udev->ctrl_ep_desc.wMaxPacketSize[1] = 0;
 	udev->ctrl_ep_desc.bInterval = 0;
+
+	/* set up default endpoint companion descriptor */
+	udev->ctrl_ep_comp_desc.bLength = sizeof(udev->ctrl_ep_comp_desc);
+	udev->ctrl_ep_comp_desc.bDescriptorType = UDESC_ENDPOINT_SS_COMP;
+
 	udev->ddesc.bMaxPacketSize = USB_MAX_IPACKET;
 
 	udev->speed = speed;
@@ -1579,6 +1598,7 @@ usb_alloc_device(device_t parent_dev, st
 	/* init the default endpoint */
 	usb_init_endpoint(udev, 0,
 	    &udev->ctrl_ep_desc,
+	    &udev->ctrl_ep_comp_desc,
 	    &udev->ctrl_ep);
 
 	/* set device index */
@@ -1597,13 +1617,29 @@ usb_alloc_device(device_t parent_dev, st
 	/* Create a link from /dev/ugenX.X to the default endpoint */
 	make_dev_alias(udev->ctrl_dev, "%s", udev->ugen_name);
 #endif
+	/* Initialise device */
+	if (bus->methods->device_init != NULL) {
+		err = (bus->methods->device_init) (udev);
+		if (err != 0) {
+			DPRINTFN(0, "device init %d failed "
+			    "(%s, ignored)\n", device_index, 
+			    usbd_errstr(err));
+			goto done;
+		}
+	}
+	/* set powered device state after device init is complete */
+	usb_set_device_state(udev, USB_STATE_POWERED);
+
 	if (udev->flags.usb_mode == USB_MODE_HOST) {
 
 		err = usbd_req_set_address(udev, NULL, device_index);
 
-		/* This is the new USB device address from now on */
-
-		udev->address = device_index;
+		/*
+		 * This is the new USB device address from now on, if
+		 * the set address request didn't set it already.
+		 */
+		if (udev->address == USB_START_ADDR)
+			udev->address = device_index;
 
 		/*
 		 * We ignore any set-address errors, hence there are
@@ -1619,9 +1655,6 @@ usb_alloc_device(device_t parent_dev, st
 			    "(%s, ignored)\n", udev->address, 
 			    usbd_errstr(err));
 		}
-		/* allow device time to set new address */
-		usb_pause_mtx(NULL, 
-		    USB_MS_TO_TICKS(USB_SET_ADDRESS_SETTLE));
 	} else {
 		/* We are not self powered */
 		udev->flags.self_powered = 0;
@@ -1640,45 +1673,16 @@ usb_alloc_device(device_t parent_dev, st
 	}
 	usb_set_device_state(udev, USB_STATE_ADDRESSED);
 
-	/*
-	 * Get the first 8 bytes of the device descriptor !
-	 *
-	 * NOTE: "usbd_do_request" will check the device descriptor
-	 * next time we do a request to see if the maximum packet size
-	 * changed! The 8 first bytes of the device descriptor
-	 * contains the maximum packet size to use on control endpoint
-	 * 0. If this value is different from "USB_MAX_IPACKET" a new
-	 * USB control request will be setup!
-	 */
-	err = usbd_req_get_desc(udev, NULL, NULL, &udev->ddesc,
-	    USB_MAX_IPACKET, USB_MAX_IPACKET, 0, UDESC_DEVICE, 0, 0);
-	if (err) {
-		DPRINTFN(0, "getting device descriptor "
-		    "at addr %d failed, %s\n", udev->address,
-		    usbd_errstr(err));
+	/* setup the device descriptor and the initial "wMaxPacketSize" */
+	err = usbd_setup_device_desc(udev, NULL);
+
+	if (err != 0) {
 		/* XXX try to re-enumerate the device */
 		err = usbd_req_re_enumerate(udev, NULL);
-		if (err) {
+		if (err)
 			goto done;
-		}
 	}
-	DPRINTF("adding unit addr=%d, rev=%02x, class=%d, "
-	    "subclass=%d, protocol=%d, maxpacket=%d, len=%d, speed=%d\n",
-	    udev->address, UGETW(udev->ddesc.bcdUSB),
-	    udev->ddesc.bDeviceClass,
-	    udev->ddesc.bDeviceSubClass,
-	    udev->ddesc.bDeviceProtocol,
-	    udev->ddesc.bMaxPacketSize,
-	    udev->ddesc.bLength,
-	    udev->speed);
 
-	/* get the full device descriptor */
-	err = usbd_req_get_device_desc(udev, NULL, &udev->ddesc);
-	if (err) {
-		DPRINTF("addr=%d, getting full desc failed\n",
-		    udev->address);
-		goto done;
-	}
 	/*
 	 * Setup temporary USB attach args so that we can figure out some
 	 * basic quirks for this device.
@@ -2068,6 +2072,10 @@ usb_free_device(struct usb_device *udev,
 	KASSERT(LIST_FIRST(&udev->pd_list) == NULL, ("leaked cdev entries"));
 #endif
 
+	/* Uninitialise device */
+	if (bus->methods->device_uninit != NULL)
+		(bus->methods->device_uninit) (udev);
+
 	/* free device */
 	free(udev->serial, M_USB);
 	free(udev->manufacturer, M_USB);
@@ -2598,6 +2606,17 @@ usb_set_device_state(struct usb_device *
 	DPRINTF("udev %p state %s -> %s\n", udev,
 	    usb_statestr(udev->state), usb_statestr(state));
 	udev->state = state;
+
+	if (udev->bus->methods->device_state_change != NULL)
+		(udev->bus->methods->device_state_change) (udev);
+}
+
+enum usb_dev_state
+usb_get_device_state(struct usb_device *udev)
+{
+	if (udev == NULL)
+		return (USB_STATE_DETACHED);
+	return (udev->state);
 }
 
 uint8_t

Modified: head/sys/dev/usb/usb_device.h
==============================================================================
--- head/sys/dev/usb/usb_device.h	Mon Oct  4 22:50:08 2010	(r213434)
+++ head/sys/dev/usb/usb_device.h	Mon Oct  4 23:18:05 2010	(r213435)
@@ -151,6 +151,7 @@ struct usb_device {
 
 	uint8_t	address;		/* device addess */
 	uint8_t	device_index;		/* device index in "bus->devices" */
+	uint8_t	controller_slot_id;	/* controller specific value */
 	uint8_t	curr_config_index;	/* current configuration index */
 	uint8_t	curr_config_no;		/* current configuration number */
 	uint8_t	depth;			/* distance from root HUB */
@@ -169,11 +170,12 @@ struct usb_device {
 	struct usb_device_flags flags;
 
 	struct usb_endpoint_descriptor ctrl_ep_desc;	/* for endpoint 0 */
+	struct usb_endpoint_ss_comp_descriptor ctrl_ep_comp_desc;	/* for endpoint 0 */
 	struct usb_device_descriptor ddesc;	/* device descriptor */
 
-	char	*serial;		/* serial number */
-	char	*manufacturer;		/* manufacturer string */
-	char	*product;		/* product string */
+	char	*serial;		/* serial number, can be NULL */
+	char	*manufacturer;		/* manufacturer string, can be NULL */
+	char	*product;		/* product string, can be NULL */
 
 #if USB_HAVE_COMPAT_LINUX
 	/* Linux compat */
@@ -213,8 +215,9 @@ void	usb_free_device(struct usb_device *
 void	usb_linux_free_device(struct usb_device *dev);
 uint8_t	usb_peer_can_wakeup(struct usb_device *udev);
 struct usb_endpoint *usb_endpoint_foreach(struct usb_device *udev, struct usb_endpoint *ep);
-void	usb_set_device_state(struct usb_device *udev,
-	    enum usb_dev_state state);
+void	usb_set_device_state(struct usb_device *, enum usb_dev_state);
+enum usb_dev_state usb_get_device_state(struct usb_device *);
+
 void	usbd_enum_lock(struct usb_device *);
 void	usbd_enum_unlock(struct usb_device *);
 void	usbd_sr_lock(struct usb_device *);

Modified: head/sys/dev/usb/usb_hub.c
==============================================================================
--- head/sys/dev/usb/usb_hub.c	Mon Oct  4 22:50:08 2010	(r213434)
+++ head/sys/dev/usb/usb_hub.c	Mon Oct  4 23:18:05 2010	(r213435)
@@ -2,7 +2,7 @@
 /*-
  * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved.
  * Copyright (c) 1998 Lennart Augustsson. All rights reserved.
- * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ * Copyright (c) 2008-2010 Hans Petter Selasky. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -110,6 +110,7 @@ struct uhub_softc {
 #define	UHUB_PROTO(sc) ((sc)->sc_udev->ddesc.bDeviceProtocol)
 #define	UHUB_IS_HIGH_SPEED(sc) (UHUB_PROTO(sc) != UDPROTO_FSHUB)
 #define	UHUB_IS_SINGLE_TT(sc) (UHUB_PROTO(sc) == UDPROTO_HSHUBSTT)
+#define	UHUB_IS_SUPER_SPEED(sc) (UHUB_PROTO(sc) == UDPROTO_SSHUB)
 
 /* prototypes for type checking: */
 
@@ -236,6 +237,7 @@ uhub_explore_sub(struct uhub_softc *sc, 
 		/* nothing to do */
 		goto done;
 	}
+
 	/* check if device should be re-enumerated */
 
 	if (child->flags.usb_mode == USB_MODE_HOST) {
@@ -268,14 +270,14 @@ uhub_explore_sub(struct uhub_softc *sc, 
 	}
 	/* start control transfer, if device mode */
 
-	if (child->flags.usb_mode == USB_MODE_DEVICE) {
+	if (child->flags.usb_mode == USB_MODE_DEVICE)
 		usbd_ctrl_transfer_setup(child);
-	}
+
 	/* if a HUB becomes present, do a recursive HUB explore */
 
-	if (child->hub) {
+	if (child->hub)
 		err = (child->hub->explore) (child);
-	}
+
 done:
 	return (err);
 }
@@ -374,11 +376,17 @@ repeat:
 		DPRINTF("Port %d is in Host Mode\n", portno);
 
 		if (sc->sc_st.port_status & UPS_SUSPEND) {
+			/*
+			 * NOTE: Should not get here in SuperSpeed
+			 * mode, because the HUB should report this
+			 * bit as zero.
+			 */
 			DPRINTF("Port %d was still "
 			    "suspended, clearing.\n", portno);
-			err = usbd_req_clear_port_feature(sc->sc_udev,
+			err = usbd_req_clear_port_feature(udev,
 			    NULL, portno, UHF_PORT_SUSPEND);
 		}
+
 		/* USB Host Mode */
 
 		/* wait for maximum device power up time */
@@ -439,11 +447,49 @@ repeat:
 	case USB_SPEED_LOW:
 		speed = USB_SPEED_LOW;
 		break;
+	case USB_SPEED_SUPER:
+		if (udev->parent_hub == NULL) {
+			/* Root HUB - special case */
+			switch (sc->sc_st.port_status & UPS_OTHER_SPEED) {
+			case 0:
+				speed = USB_SPEED_FULL;
+				break;
+			case UPS_LOW_SPEED:
+				speed = USB_SPEED_LOW;
+				break;
+			case UPS_HIGH_SPEED:
+				speed = USB_SPEED_HIGH;
+				break;
+			default:
+				speed = USB_SPEED_SUPER;
+				break;
+			}
+		} else {
+			speed = USB_SPEED_SUPER;
+		}
+		break;
 	default:
 		/* same speed like parent */
 		speed = udev->speed;
 		break;
 	}
+	if (speed == USB_SPEED_SUPER) {
+		err = usbd_req_set_hub_u1_timeout(udev, NULL,
+		    portno, 128 - (2 * udev->depth));
+		if (err) {
+			DPRINTFN(0, "port %d U1 timeout "
+			    "failed, error=%s\n",
+			    portno, usbd_errstr(err));
+		}
+		err = usbd_req_set_hub_u2_timeout(udev, NULL,
+		    portno, 128 - (2 * udev->depth));
+		if (err) {
+			DPRINTFN(0, "port %d U2 timeout "
+			    "failed, error=%s\n",
+			    portno, usbd_errstr(err));
+		}
+	}
+
 	/*
 	 * Figure out the device mode
 	 *
@@ -486,6 +532,28 @@ error:
 }
 
 /*------------------------------------------------------------------------*
+ *	usb_device_20_compatible
+ *
+ * Returns:
+ *    0: HUB does not support suspend and resume
+ * Else: HUB supports suspend and resume
+ *------------------------------------------------------------------------*/
+static uint8_t
+usb_device_20_compatible(struct usb_device *udev)
+{
+	if (udev == NULL)
+		return (0);
+	switch (udev->speed) {
+	case USB_SPEED_LOW:
+	case USB_SPEED_FULL:
+	case USB_SPEED_HIGH:
+		return (1);
+	default:
+		return (0);
+	}
+}
+
+/*------------------------------------------------------------------------*
  *	uhub_suspend_resume_port
  *
  * Returns:
@@ -508,8 +576,14 @@ uhub_suspend_resume_port(struct uhub_sof
 
 	/* first clear the port suspend change bit */
 
-	err = usbd_req_clear_port_feature(udev, NULL,
-	    portno, UHF_C_PORT_SUSPEND);
+	if (usb_device_20_compatible(udev)) {
+		err = usbd_req_clear_port_feature(udev, NULL,
+		    portno, UHF_C_PORT_SUSPEND);
+	} else {
+		err = usbd_req_clear_port_feature(udev, NULL,
+		    portno, UHF_C_PORT_LINK_STATE);
+	}
+
 	if (err) {
 		DPRINTF("clearing suspend failed.\n");
 		goto done;
@@ -521,12 +595,24 @@ uhub_suspend_resume_port(struct uhub_sof
 		DPRINTF("reading port status failed.\n");
 		goto done;
 	}
-	/* get current state */
+	/* convert current state */
 
-	if (sc->sc_st.port_status & UPS_SUSPEND) {
-		is_suspend = 1;
+	if (usb_device_20_compatible(udev)) {
+		if (sc->sc_st.port_status & UPS_SUSPEND) {
+			is_suspend = 1;
+		} else {
+			is_suspend = 0;
+		}
 	} else {
-		is_suspend = 0;
+		switch (UPS_PORT_LINK_STATE_GET(sc->sc_st.port_status)) {
+		case UPS_PORT_LS_U0:
+		case UPS_PORT_LS_U1:
+			is_suspend = 0;
+			break;
+		default:
+			is_suspend = 1;
+			break;
+		}
 	}
 
 	DPRINTF("suspended=%u\n", is_suspend);
@@ -541,7 +627,8 @@ uhub_suspend_resume_port(struct uhub_sof
 		 */
 		if (is_suspend == 0)
 			usb_dev_resume_peer(child);
-		else if (child->flags.usb_mode == USB_MODE_DEVICE)
+		else if ((child->flags.usb_mode == USB_MODE_DEVICE) ||
+		    (usb_device_20_compatible(child) == 0))
 			usb_dev_suspend_peer(child);
 	}
 done:
@@ -563,6 +650,26 @@ uhub_root_intr(struct usb_bus *bus, cons
 	usb_needs_explore(bus, 0);
 }
 
+static uint8_t
+uhub_is_too_deep(struct usb_device *udev)
+{
+	switch (udev->speed) {
+	case USB_SPEED_FULL:
+	case USB_SPEED_LOW:
+	case USB_SPEED_HIGH:
+		if (udev->depth > USB_HUB_MAX_DEPTH)
+			return (1);
+		break;
+	case USB_SPEED_SUPER:
+		if (udev->depth > USB_SS_HUB_DEPTH_MAX)
+			return (1);
+		break;
+	default:
+		break;
+	}
+	return (0);
+}
+
 /*------------------------------------------------------------------------*
  *	uhub_explore
  *
@@ -585,11 +692,11 @@ uhub_explore(struct usb_device *udev)
 
 	DPRINTFN(11, "udev=%p addr=%d\n", udev, udev->address);
 
-	/* ignore hubs that are too deep */
-	if (udev->depth > USB_HUB_MAX_DEPTH) {
+	/* ignore devices that are too deep */
+	if (uhub_is_too_deep(udev))
 		return (USB_ERR_TOO_DEEP);
-	}
 
+	/* check if device is suspended */
 	if (udev->flags.self_suspended) {
 		/* need to wait until the child signals resume */
 		DPRINTF("Device is suspended!\n");
@@ -656,7 +763,7 @@ uhub_explore(struct usb_device *udev)
 				break;
 			}
 		}
-		if (sc->sc_st.port_change & UPS_C_SUSPEND) {
+		if (sc->sc_st.port_change & (UPS_C_SUSPEND | UPS_C_PORT_LINK_STATE)) {
 			err = uhub_suspend_resume_port(sc, portno);
 			if (err) {
 				/* most likely the HUB is gone */
@@ -684,20 +791,81 @@ uhub_probe(device_t dev)
 {
 	struct usb_attach_arg *uaa = device_get_ivars(dev);
 
-	if (uaa->usb_mode != USB_MODE_HOST) {
+	if (uaa->usb_mode != USB_MODE_HOST)
 		return (ENXIO);
-	}
+
 	/*
-	 * The subclass for USB HUBs is ignored because it is 0 for
-	 * some and 1 for others.
+	 * The subclass for USB HUBs is currently ignored because it
+	 * is 0 for some and 1 for others.
 	 */
-	if ((uaa->info.bConfigIndex == 0) &&
-	    (uaa->info.bDeviceClass == UDCLASS_HUB)) {
+	if (uaa->info.bConfigIndex == 0 &&
+	    uaa->info.bDeviceClass == UDCLASS_HUB)
 		return (0);
-	}
+
 	return (ENXIO);
 }
 
+/* NOTE: The information returned by this function can be wrong. */
+usb_error_t
+uhub_query_info(struct usb_device *udev, uint8_t *pnports, uint8_t *ptt)
+{
+	struct usb_hub_descriptor hubdesc20;
+	struct usb_hub_ss_descriptor hubdesc30;
+	usb_error_t err;
+	uint8_t nports;
+	uint8_t tt;
+
+	if (udev->ddesc.bDeviceClass != UDCLASS_HUB)
+		return (USB_ERR_INVAL);
+
+	nports = 0;
+	tt = 0;
+
+	switch (udev->speed) {
+	case USB_SPEED_LOW:
+	case USB_SPEED_FULL:
+	case USB_SPEED_HIGH:
+		/* assuming that there is one port */
+		err = usbd_req_get_hub_descriptor(udev, NULL, &hubdesc20, 1);
+		if (err) {
+			DPRINTFN(0, "getting USB 2.0 HUB descriptor failed,"
+			    "error=%s\n", usbd_errstr(err));
+			break;
+		}
+		nports = hubdesc20.bNbrPorts;
+		if (nports > 127)
+			nports = 127;
+
+		if (udev->speed == USB_SPEED_HIGH)
+			tt = (UGETW(hubdesc20.wHubCharacteristics) >> 5) & 3;
+		break;
+
+	case USB_SPEED_SUPER:
+		err = usbd_req_get_ss_hub_descriptor(udev, NULL, &hubdesc30, 1);
+		if (err) {
+			DPRINTFN(0, "Getting USB 3.0 HUB descriptor failed,"
+			    "error=%s\n", usbd_errstr(err));
+			break;
+		}
+		nports = hubdesc30.bNbrPorts;
+		if (nports > 16)
+			nports = 16;
+		break;
+
+	default:
+		err = USB_ERR_INVAL;
+		break;
+	}
+
+	if (pnports != NULL)
+		*pnports = nports;
+
+	if (ptt != NULL)
+		*ptt = tt;
+
+	return (err);
+}
+
 static int
 uhub_attach(device_t dev)
 {
@@ -706,7 +874,8 @@ uhub_attach(device_t dev)
 	struct usb_device *udev = uaa->device;
 	struct usb_device *parent_hub = udev->parent_hub;
 	struct usb_hub *hub;
-	struct usb_hub_descriptor hubdesc;
+	struct usb_hub_descriptor hubdesc20;
+	struct usb_hub_ss_descriptor hubdesc30;
 	uint16_t pwrdly;
 	uint8_t x;
 	uint8_t nports;
@@ -733,38 +902,114 @@ uhub_attach(device_t dev)
 	    parent_hub ?
 	    parent_hub->flags.self_powered : 0);
 
-	if (udev->depth > USB_HUB_MAX_DEPTH) {
-		DPRINTFN(0, "hub depth, %d, exceeded. HUB ignored\n",
-		    USB_HUB_MAX_DEPTH);
+	if (uhub_is_too_deep(udev)) {
+		DPRINTFN(0, "HUB at depth %d, "
+		    "exceeds maximum. HUB ignored\n", (int)udev->depth);
 		goto error;
 	}
+
 	if (!udev->flags.self_powered && parent_hub &&
-	    (!parent_hub->flags.self_powered)) {
-		DPRINTFN(0, "bus powered HUB connected to "
+	    !parent_hub->flags.self_powered) {
+		DPRINTFN(0, "Bus powered HUB connected to "
 		    "bus powered HUB. HUB ignored\n");
 		goto error;
 	}
 	/* get HUB descriptor */
 
-	DPRINTFN(2, "getting HUB descriptor\n");
+	DPRINTFN(2, "Getting HUB descriptor\n");
 
-	/* assuming that there is one port */
-	err = usbd_req_get_hub_descriptor(udev, NULL, &hubdesc, 1);
+	switch (udev->speed) {
+	case USB_SPEED_LOW:
+	case USB_SPEED_FULL:
+	case USB_SPEED_HIGH:
+		/* assuming that there is one port */
+		err = usbd_req_get_hub_descriptor(udev, NULL, &hubdesc20, 1);
+		if (err) {
+			DPRINTFN(0, "getting USB 2.0 HUB descriptor failed,"
+			    "error=%s\n", usbd_errstr(err));
+			goto error;
+		}
+		/* get number of ports */
+		nports = hubdesc20.bNbrPorts;
 
-	nports = hubdesc.bNbrPorts;
+		/* get power delay */
+		pwrdly = ((hubdesc20.bPwrOn2PwrGood * UHD_PWRON_FACTOR) +
+		    USB_EXTRA_POWER_UP_TIME);
 
-	if (!err && (nports >= 8)) {
 		/* get complete HUB descriptor */
-		err = usbd_req_get_hub_descriptor(udev, NULL, &hubdesc, nports);
-	}
-	if (err) {
-		DPRINTFN(0, "getting hub descriptor failed,"
-		    "error=%s\n", usbd_errstr(err));
-		goto error;
-	}
-	if (hubdesc.bNbrPorts != nports) {
-		DPRINTFN(0, "number of ports changed\n");
-		goto error;
+		if (nports >= 8) {
+			/* check number of ports */
+			if (nports > 127) {
+				DPRINTFN(0, "Invalid number of USB 2.0 ports,"
+				    "error=%s\n", usbd_errstr(err));
+				goto error;
+			}
+			/* get complete HUB descriptor */
+			err = usbd_req_get_hub_descriptor(udev, NULL, &hubdesc20, nports);
+
+			if (err) {
+				DPRINTFN(0, "Getting USB 2.0 HUB descriptor failed,"
+				    "error=%s\n", usbd_errstr(err));
+				goto error;
+			}
+			if (hubdesc20.bNbrPorts != nports) {
+				DPRINTFN(0, "Number of ports changed\n");
+				goto error;
+			}
+		}
+		break;
+	case USB_SPEED_SUPER:
+		if (udev->parent_hub != NULL) {
+			err = usbd_req_set_hub_depth(udev, NULL,
+			    udev->depth - 1);
+			if (err) {
+				DPRINTFN(0, "Setting USB 3.0 HUB depth failed,"
+				    "error=%s\n", usbd_errstr(err));
+				goto error;
+			}
+		}
+		err = usbd_req_get_ss_hub_descriptor(udev, NULL, &hubdesc30, 1);
+		if (err) {
+			DPRINTFN(0, "Getting USB 3.0 HUB descriptor failed,"
+			    "error=%s\n", usbd_errstr(err));
+			goto error;
+		}
+		/* get number of ports */
+		nports = hubdesc30.bNbrPorts;
+
+		/* get power delay */
+		pwrdly = ((hubdesc30.bPwrOn2PwrGood * UHD_PWRON_FACTOR) +
+		    USB_EXTRA_POWER_UP_TIME);
+
+		/* get complete HUB descriptor */
+		if (nports >= 8) {
+			/* check number of ports */
+			if (nports > ((udev->parent_hub != NULL) ? 15 : 127)) {
+				DPRINTFN(0, "Invalid number of USB 3.0 ports,"
+				    "error=%s\n", usbd_errstr(err));
+				goto error;
+			}
+			/* get complete HUB descriptor */
+			err = usbd_req_get_ss_hub_descriptor(udev, NULL, &hubdesc30, nports);
+
+			if (err) {
+				DPRINTFN(0, "Getting USB 2.0 HUB descriptor failed,"
+				    "error=%s\n", usbd_errstr(err));
+				goto error;
+			}
+			if (hubdesc30.bNbrPorts != nports) {
+				DPRINTFN(0, "Number of ports changed\n");
+				goto error;
+			}
+		}
+		break;
+	default:
+		DPRINTF("Assuming HUB has only one port\n");
+		/* default number of ports */
+		nports = 1;
+		/* default power delay */
+		pwrdly = ((10 * UHD_PWRON_FACTOR) + USB_EXTRA_POWER_UP_TIME);
+		break;
 	}
 	if (nports == 0) {
 		DPRINTFN(0, "portless HUB\n");
@@ -785,7 +1030,7 @@ uhub_attach(device_t dev)
 	/* initialize HUB structure */
 	hub->hubsoftc = sc;
 	hub->explore = &uhub_explore;
-	hub->nports = hubdesc.bNbrPorts;
+	hub->nports = nports;
 	hub->hubudev = udev;
 
 	/* if self powered hub, give ports maximum current */
@@ -841,8 +1086,6 @@ uhub_attach(device_t dev)
 	/* XXX should check for none, individual, or ganged power? */
 
 	removable = 0;
-	pwrdly = ((hubdesc.bPwrOn2PwrGood * UHD_PWRON_FACTOR) +
-	    USB_EXTRA_POWER_UP_TIME);
 
 	for (x = 0; x != nports; x++) {
 		/* set up data structures */
@@ -853,8 +1096,21 @@ uhub_attach(device_t dev)
 		portno = x + 1;
 
 		/* check if port is removable */
-		if (!UHD_NOT_REMOV(&hubdesc, portno)) {
+		switch (udev->speed) {
+		case USB_SPEED_LOW:
+		case USB_SPEED_FULL:
+		case USB_SPEED_HIGH:
+			if (!UHD_NOT_REMOV(&hubdesc20, portno))
+				removable++;
+			break;
+		case USB_SPEED_SUPER:
+			if (!UHD_NOT_REMOV(&hubdesc30, portno))
+				removable++;
+			break;
+		default:
+			DPRINTF("Assuming removable port\n");
 			removable++;
+			break;
 		}
 		if (!err) {
 			/* turn the power on */
@@ -915,9 +1171,8 @@ uhub_detach(device_t dev)
 	struct usb_device *child;
 	uint8_t x;
 
-	if (hub == NULL) {		/* must be partially working */
+	if (hub == NULL)		/* must be partially working */
 		return (0);
-	}
 
 	/* Make sure interrupt transfer is gone. */
 	usbd_transfer_unsetup(sc->sc_xfer, UHUB_N_TRANSFER);
@@ -1789,6 +2044,7 @@ usb_peer_should_wakeup(struct usb_device
 	    (udev->pwr_save.write_refs != 0) ||
 	    ((udev->pwr_save.read_refs != 0) &&
 	    (udev->flags.usb_mode == USB_MODE_HOST) &&
+	    (usb_device_20_compatible(udev) != 0) &&
 	    (usb_peer_can_wakeup(udev) == 0)));
 }
 
@@ -1960,13 +2216,16 @@ usb_dev_resume_peer(struct usb_device *u
 	/* reduce chance of instant resume failure by waiting a little bit */
 	usb_pause_mtx(NULL, USB_MS_TO_TICKS(20));
 
-	/* resume current port (Valid in Host and Device Mode) */
-	err = usbd_req_clear_port_feature(udev->parent_hub,
-	    NULL, udev->port_no, UHF_PORT_SUSPEND);
-	if (err) {
-		DPRINTFN(0, "Resuming port failed\n");
-		return;
+	if (usb_device_20_compatible(udev)) {
+		/* resume current port (Valid in Host and Device Mode) */
+		err = usbd_req_clear_port_feature(udev->parent_hub,
+		    NULL, udev->port_no, UHF_PORT_SUSPEND);
+		if (err) {
+			DPRINTFN(0, "Resuming port failed\n");
+			return;
+		}
 	}
+
 	/* resume settle time */
 	usb_pause_mtx(NULL, USB_MS_TO_TICKS(USB_PORT_RESUME_DELAY));
 
@@ -2006,7 +2265,8 @@ usb_dev_resume_peer(struct usb_device *u
 	usbd_sr_unlock(udev);
 
 	/* check if peer has wakeup capability */
-	if (usb_peer_can_wakeup(udev)) {
+	if (usb_peer_can_wakeup(udev) &&
+	    usb_device_20_compatible(udev)) {
 		/* clear remote wakeup */
 		err = usbd_req_clear_device_feature(udev,
 		    NULL, UF_DEVICE_REMOTE_WAKEUP);
@@ -2016,7 +2276,6 @@ usb_dev_resume_peer(struct usb_device *u
 			    usbd_errstr(err));
 		}
 	}
-	return;
 }
 
 /*------------------------------------------------------------------------*
@@ -2054,7 +2313,6 @@ repeat:
 
 		/* check if all devices on the HUB are suspended */
 		for (x = 0; x != nports; x++) {
-
 			child = usb_bus_port_get_device(udev->bus,
 			    udev->hub->ports + x);
 
@@ -2069,6 +2327,22 @@ repeat:
 		}
 	}
 
+	if (usb_peer_can_wakeup(udev) &&
+	    usb_device_20_compatible(udev)) {
+		/*
+		 * This request needs to be done before we set
+		 * "udev->flags.self_suspended":
+		 */
+
+		/* allow device to do remote wakeup */
+		err = usbd_req_set_device_feature(udev,
+		    NULL, UF_DEVICE_REMOTE_WAKEUP);
+		if (err) {
+			DPRINTFN(0, "Setting device "
+			    "remote wakeup failed\n");
+		}
+	}
+
 	USB_BUS_LOCK(udev->bus);
 	/*
 	 * Checking for suspend condition and setting suspended bit
@@ -2086,6 +2360,17 @@ repeat:
 	USB_BUS_UNLOCK(udev->bus);
 
 	if (err != 0) {
+		if (usb_peer_can_wakeup(udev) &&
+		    usb_device_20_compatible(udev)) {
+			/* allow device to do remote wakeup */
+			err = usbd_req_clear_device_feature(udev,
+			    NULL, UF_DEVICE_REMOTE_WAKEUP);
+			if (err) {
+				DPRINTFN(0, "Setting device "
+				    "remote wakeup failed\n");
+			}
+		}
+
 		if (udev->flags.usb_mode == USB_MODE_DEVICE) {
 			/* resume parent HUB first */
 			usb_dev_resume_peer(udev->parent_hub);
@@ -2111,16 +2396,6 @@ repeat:
 
 	usbd_sr_unlock(udev);
 
-	if (usb_peer_can_wakeup(udev)) {
-		/* allow device to do remote wakeup */
-		err = usbd_req_set_device_feature(udev,
-		    NULL, UF_DEVICE_REMOTE_WAKEUP);
-		if (err) {
-			DPRINTFN(0, "Setting device "
-			    "remote wakeup failed\n");
-		}
-	}
-
 	if (udev->bus->methods->device_suspend != NULL) {
 		usb_timeout_t temp;
 
@@ -2133,12 +2408,15 @@ repeat:

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***


More information about the svn-src-all mailing list