summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/usb/core/config.c189
-rw-r--r--include/linux/usb.h16
-rw-r--r--include/linux/usb/ch9.h16
3 files changed, 212 insertions, 9 deletions
diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c
index e9426acf5682..7103758bb486 100644
--- a/drivers/usb/core/config.c
+++ b/drivers/usb/core/config.c
@@ -19,6 +19,32 @@ static inline const char *plural(int n)
return (n == 1 ? "" : "s");
}
+/* FIXME: this is a kludge */
+static int find_next_descriptor_more(unsigned char *buffer, int size,
+ int dt1, int dt2, int dt3, int *num_skipped)
+{
+ struct usb_descriptor_header *h;
+ int n = 0;
+ unsigned char *buffer0 = buffer;
+
+ /* Find the next descriptor of type dt1 or dt2 or dt3 */
+ while (size > 0) {
+ h = (struct usb_descriptor_header *) buffer;
+ if (h->bDescriptorType == dt1 || h->bDescriptorType == dt2 ||
+ h->bDescriptorType == dt3)
+ break;
+ buffer += h->bLength;
+ size -= h->bLength;
+ ++n;
+ }
+
+ /* Store the number of descriptors skipped and return the
+ * number of bytes skipped */
+ if (num_skipped)
+ *num_skipped = n;
+ return buffer - buffer0;
+}
+
static int find_next_descriptor(unsigned char *buffer, int size,
int dt1, int dt2, int *num_skipped)
{
@@ -43,6 +69,128 @@ static int find_next_descriptor(unsigned char *buffer, int size,
return buffer - buffer0;
}
+static int usb_parse_endpoint_companion(struct device *ddev, int cfgno,
+ int inum, int asnum, struct usb_host_endpoint *ep,
+ int num_ep, unsigned char *buffer, int size)
+{
+ unsigned char *buffer_start = buffer;
+ struct usb_ep_comp_descriptor *desc;
+ int retval;
+ int num_skipped;
+ int max_tx;
+ int i;
+
+ /* Allocate space for the companion descriptor */
+ ep->ep_comp = kzalloc(sizeof(struct usb_host_ep_comp), GFP_KERNEL);
+ if (!ep->ep_comp)
+ return -ENOMEM;
+ desc = (struct usb_ep_comp_descriptor *) buffer;
+ if (desc->bDescriptorType != USB_DT_SS_ENDPOINT_COMP) {
+ dev_warn(ddev, "No SuperSpeed endpoint companion for config %d "
+ " interface %d altsetting %d ep %d: "
+ "using minimum values\n",
+ cfgno, inum, asnum, ep->desc.bEndpointAddress);
+ ep->ep_comp->desc.bLength = USB_DT_EP_COMP_SIZE;
+ ep->ep_comp->desc.bDescriptorType = USB_DT_SS_ENDPOINT_COMP;
+ ep->ep_comp->desc.bMaxBurst = 0;
+ /*
+ * Leave bmAttributes as zero, which will mean no streams for
+ * bulk, and isoc won't support multiple bursts of packets.
+ * With bursts of only one packet, and a Mult of 1, the max
+ * amount of data moved per endpoint service interval is one
+ * packet.
+ */
+ if (usb_endpoint_xfer_isoc(&ep->desc) ||
+ usb_endpoint_xfer_int(&ep->desc))
+ ep->ep_comp->desc.wBytesPerInterval =
+ ep->desc.wMaxPacketSize;
+ /*
+ * The next descriptor is for an Endpoint or Interface,
+ * no extra descriptors to copy into the companion structure,
+ * and we didn't eat up any of the buffer.
+ */
+ retval = 0;
+ goto valid;
+ }
+ memcpy(&ep->ep_comp->desc, desc, USB_DT_EP_COMP_SIZE);
+ desc = &ep->ep_comp->desc;
+ buffer += desc->bLength;
+ size -= desc->bLength;
+
+ /* Eat up the other descriptors we don't care about */
+ ep->ep_comp->extra = buffer;
+ i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT,
+ USB_DT_INTERFACE, &num_skipped);
+ ep->ep_comp->extralen = i;
+ buffer += i;
+ size -= i;
+ retval = buffer - buffer_start + i;
+ if (num_skipped > 0)
+ dev_dbg(ddev, "skipped %d descriptor%s after %s\n",
+ num_skipped, plural(num_skipped),
+ "SuperSpeed endpoint companion");
+
+ /* Check the various values */
+ if (usb_endpoint_xfer_control(&ep->desc) && desc->bMaxBurst != 0) {
+ dev_warn(ddev, "Control endpoint with bMaxBurst = %d in "
+ "config %d interface %d altsetting %d ep %d: "
+ "setting to zero\n", desc->bMaxBurst,
+ cfgno, inum, asnum, ep->desc.bEndpointAddress);
+ desc->bMaxBurst = 0;
+ }
+ if (desc->bMaxBurst > 15) {
+ dev_warn(ddev, "Endpoint with bMaxBurst = %d in "
+ "config %d interface %d altsetting %d ep %d: "
+ "setting to 15\n", desc->bMaxBurst,
+ cfgno, inum, asnum, ep->desc.bEndpointAddress);
+ desc->bMaxBurst = 15;
+ }
+ if ((usb_endpoint_xfer_control(&ep->desc) || usb_endpoint_xfer_int(&ep->desc))
+ && desc->bmAttributes != 0) {
+ dev_warn(ddev, "%s endpoint with bmAttributes = %d in "
+ "config %d interface %d altsetting %d ep %d: "
+ "setting to zero\n",
+ usb_endpoint_xfer_control(&ep->desc) ? "Control" : "Bulk",
+ desc->bmAttributes,
+ cfgno, inum, asnum, ep->desc.bEndpointAddress);
+ desc->bmAttributes = 0;
+ }
+ if (usb_endpoint_xfer_bulk(&ep->desc) && desc->bmAttributes > 16) {
+ dev_warn(ddev, "Bulk endpoint with more than 65536 streams in "
+ "config %d interface %d altsetting %d ep %d: "
+ "setting to max\n",
+ cfgno, inum, asnum, ep->desc.bEndpointAddress);
+ desc->bmAttributes = 16;
+ }
+ if (usb_endpoint_xfer_isoc(&ep->desc) && desc->bmAttributes > 2) {
+ dev_warn(ddev, "Isoc endpoint has Mult of %d in "
+ "config %d interface %d altsetting %d ep %d: "
+ "setting to 3\n", desc->bmAttributes + 1,
+ cfgno, inum, asnum, ep->desc.bEndpointAddress);
+ desc->bmAttributes = 2;
+ }
+ if (usb_endpoint_xfer_isoc(&ep->desc)) {
+ max_tx = ep->desc.wMaxPacketSize * (desc->bMaxBurst + 1) *
+ (desc->bmAttributes + 1);
+ } else if (usb_endpoint_xfer_int(&ep->desc)) {
+ max_tx = ep->desc.wMaxPacketSize * (desc->bMaxBurst + 1);
+ } else {
+ goto valid;
+ }
+ if (desc->wBytesPerInterval > max_tx) {
+ dev_warn(ddev, "%s endpoint with wBytesPerInterval of %d in "
+ "config %d interface %d altsetting %d ep %d: "
+ "setting to %d\n",
+ usb_endpoint_xfer_isoc(&ep->desc) ? "Isoc" : "Int",
+ desc->wBytesPerInterval,
+ cfgno, inum, asnum, ep->desc.bEndpointAddress,
+ max_tx);
+ desc->wBytesPerInterval = max_tx;
+ }
+valid:
+ return retval;
+}
+
static int usb_parse_endpoint(struct device *ddev, int cfgno, int inum,
int asnum, struct usb_host_interface *ifp, int num_ep,
unsigned char *buffer, int size)
@@ -50,7 +198,7 @@ static int usb_parse_endpoint(struct device *ddev, int cfgno, int inum,
unsigned char *buffer0 = buffer;
struct usb_endpoint_descriptor *d;
struct usb_host_endpoint *endpoint;
- int n, i, j;
+ int n, i, j, retval;
d = (struct usb_endpoint_descriptor *) buffer;
buffer += d->bLength;
@@ -162,17 +310,38 @@ static int usb_parse_endpoint(struct device *ddev, int cfgno, int inum,
cfgno, inum, asnum, d->bEndpointAddress,
maxp);
}
-
- /* Skip over any Class Specific or Vendor Specific descriptors;
- * find the next endpoint or interface descriptor */
- endpoint->extra = buffer;
- i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT,
- USB_DT_INTERFACE, &n);
- endpoint->extralen = i;
+ /* Allocate room for and parse any endpoint companion descriptors */
+ if (to_usb_device(ddev)->speed == USB_SPEED_SUPER) {
+ endpoint->extra = buffer;
+ i = find_next_descriptor_more(buffer, size, USB_DT_SS_ENDPOINT_COMP,
+ USB_DT_ENDPOINT, USB_DT_INTERFACE, &n);
+ endpoint->extralen = i;
+ buffer += i;
+ size -= i;
+
+ if (size > 0) {
+ retval = usb_parse_endpoint_companion(ddev, cfgno, inum, asnum,
+ endpoint, num_ep, buffer, size);
+ if (retval >= 0) {
+ buffer += retval;
+ retval = buffer - buffer0;
+ }
+ } else {
+ retval = buffer - buffer0;
+ }
+ } else {
+ /* Skip over any Class Specific or Vendor Specific descriptors;
+ * find the next endpoint or interface descriptor */
+ endpoint->extra = buffer;
+ i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT,
+ USB_DT_INTERFACE, &n);
+ endpoint->extralen = i;
+ retval = buffer - buffer0 + i;
+ }
if (n > 0)
dev_dbg(ddev, "skipped %d descriptor%s after %s\n",
n, plural(n), "endpoint");
- return buffer - buffer0 + i;
+ return retval;
skip_to_next_endpoint_or_interface_descriptor:
i = find_next_descriptor(buffer, size, USB_DT_ENDPOINT,
@@ -453,6 +622,8 @@ static int usb_parse_configuration(struct device *ddev, int cfgidx,
kref_init(&intfc->ref);
}
+ /* FIXME: parse the BOS descriptor */
+
/* Skip over any Class Specific or Vendor Specific descriptors;
* find the first interface descriptor */
config->extra = buffer;
diff --git a/include/linux/usb.h b/include/linux/usb.h
index 112a2d6e922f..13bced521b83 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -36,6 +36,7 @@ struct wusb_dev;
* - configs have one (often) or more interfaces;
* - interfaces have one (usually) or more settings;
* - each interface setting has zero or (usually) more endpoints.
+ * - a SuperSpeed endpoint has a companion descriptor
*
* And there might be other descriptors mixed in with those.
*
@@ -44,6 +45,19 @@ struct wusb_dev;
struct ep_device;
+/* For SS devices */
+/**
+ * struct usb_host_ep_comp - Valid for SuperSpeed devices only
+ * @desc: endpoint companion descriptor, wMaxPacketSize in native byteorder
+ * @extra: descriptors following this endpoint companion descriptor
+ * @extralen: how many bytes of "extra" are valid
+ */
+struct usb_host_ep_comp {
+ struct usb_ep_comp_descriptor desc;
+ unsigned char *extra; /* Extra descriptors */
+ int extralen;
+};
+
/**
* struct usb_host_endpoint - host-side endpoint descriptor and queue
* @desc: descriptor for this endpoint, wMaxPacketSize in native byteorder
@@ -51,6 +65,7 @@ struct ep_device;
* @hcpriv: for use by HCD; typically holds hardware dma queue head (QH)
* with one or more transfer descriptors (TDs) per urb
* @ep_dev: ep_device for sysfs info
+ * @ep_comp: companion descriptor information for this endpoint
* @extra: descriptors following this endpoint in the configuration
* @extralen: how many bytes of "extra" are valid
* @enabled: URBs may be submitted to this endpoint
@@ -63,6 +78,7 @@ struct usb_host_endpoint {
struct list_head urb_list;
void *hcpriv;
struct ep_device *ep_dev; /* For sysfs info */
+ struct usb_host_ep_comp *ep_comp; /* For SS devices */
unsigned char *extra; /* Extra descriptors */
int extralen;
diff --git a/include/linux/usb/ch9.h b/include/linux/usb/ch9.h
index 93bfe6352342..9e9c5c0a3d72 100644
--- a/include/linux/usb/ch9.h
+++ b/include/linux/usb/ch9.h
@@ -191,6 +191,8 @@ struct usb_ctrlrequest {
#define USB_DT_WIRE_ADAPTER 0x21
#define USB_DT_RPIPE 0x22
#define USB_DT_CS_RADIO_CONTROL 0x23
+/* From the USB 3.0 spec */
+#define USB_DT_SS_ENDPOINT_COMP 0x30
/* Conventional codes for class-specific descriptors. The convention is
* defined in the USB "Common Class" Spec (3.11). Individual class specs
@@ -535,6 +537,20 @@ static inline int usb_endpoint_is_isoc_out(
/*-------------------------------------------------------------------------*/
+/* USB_DT_SS_ENDPOINT_COMP: SuperSpeed Endpoint Companion descriptor */
+struct usb_ep_comp_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+
+ __u8 bMaxBurst;
+ __u8 bmAttributes;
+ __u16 wBytesPerInterval;
+} __attribute__ ((packed));
+
+#define USB_DT_EP_COMP_SIZE 6
+
+/*-------------------------------------------------------------------------*/
+
/* USB_DT_DEVICE_QUALIFIER: Device Qualifier descriptor */
struct usb_qualifier_descriptor {
__u8 bLength;
OpenPOWER on IntegriCloud