|
@@ -27,7 +27,7 @@
|
|
|
#include <linux/utsname.h>
|
|
|
|
|
|
#include <linux/usb/composite.h>
|
|
|
-
|
|
|
+#include <asm/unaligned.h>
|
|
|
|
|
|
/*
|
|
|
* The code in this file is utility code, used to build a gadget driver
|
|
@@ -128,6 +128,9 @@ int config_ep_by_speed(struct usb_gadget *g,
|
|
|
struct usb_endpoint_descriptor *chosen_desc = NULL;
|
|
|
struct usb_descriptor_header **speed_desc = NULL;
|
|
|
|
|
|
+ struct usb_ss_ep_comp_descriptor *comp_desc = NULL;
|
|
|
+ int want_comp_desc = 0;
|
|
|
+
|
|
|
struct usb_descriptor_header **d_spd; /* cursor for speed desc */
|
|
|
|
|
|
if (!g || !f || !_ep)
|
|
@@ -135,6 +138,13 @@ int config_ep_by_speed(struct usb_gadget *g,
|
|
|
|
|
|
/* select desired speed */
|
|
|
switch (g->speed) {
|
|
|
+ case USB_SPEED_SUPER:
|
|
|
+ if (gadget_is_superspeed(g)) {
|
|
|
+ speed_desc = f->ss_descriptors;
|
|
|
+ want_comp_desc = 1;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ /* else: Fall trough */
|
|
|
case USB_SPEED_HIGH:
|
|
|
if (gadget_is_dualspeed(g)) {
|
|
|
speed_desc = f->hs_descriptors;
|
|
@@ -156,7 +166,36 @@ ep_found:
|
|
|
/* commit results */
|
|
|
_ep->maxpacket = le16_to_cpu(chosen_desc->wMaxPacketSize);
|
|
|
_ep->desc = chosen_desc;
|
|
|
+ _ep->comp_desc = NULL;
|
|
|
+ _ep->maxburst = 0;
|
|
|
+ _ep->mult = 0;
|
|
|
+ if (!want_comp_desc)
|
|
|
+ return 0;
|
|
|
|
|
|
+ /*
|
|
|
+ * Companion descriptor should follow EP descriptor
|
|
|
+ * USB 3.0 spec, #9.6.7
|
|
|
+ */
|
|
|
+ comp_desc = (struct usb_ss_ep_comp_descriptor *)*(++d_spd);
|
|
|
+ if (!comp_desc ||
|
|
|
+ (comp_desc->bDescriptorType != USB_DT_SS_ENDPOINT_COMP))
|
|
|
+ return -EIO;
|
|
|
+ _ep->comp_desc = comp_desc;
|
|
|
+ if (g->speed == USB_SPEED_SUPER) {
|
|
|
+ switch (usb_endpoint_type(_ep->desc)) {
|
|
|
+ case USB_ENDPOINT_XFER_BULK:
|
|
|
+ case USB_ENDPOINT_XFER_INT:
|
|
|
+ _ep->maxburst = comp_desc->bMaxBurst;
|
|
|
+ break;
|
|
|
+ case USB_ENDPOINT_XFER_ISOC:
|
|
|
+ /* mult: bits 1:0 of bmAttributes */
|
|
|
+ _ep->mult = comp_desc->bmAttributes & 0x3;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ /* Do nothing for control endpoints */
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -208,6 +247,8 @@ int usb_add_function(struct usb_configuration *config,
|
|
|
config->fullspeed = true;
|
|
|
if (!config->highspeed && function->hs_descriptors)
|
|
|
config->highspeed = true;
|
|
|
+ if (!config->superspeed && function->ss_descriptors)
|
|
|
+ config->superspeed = true;
|
|
|
|
|
|
done:
|
|
|
if (value)
|
|
@@ -351,10 +392,17 @@ static int config_buf(struct usb_configuration *config,
|
|
|
list_for_each_entry(f, &config->functions, list) {
|
|
|
struct usb_descriptor_header **descriptors;
|
|
|
|
|
|
- if (speed == USB_SPEED_HIGH)
|
|
|
+ switch (speed) {
|
|
|
+ case USB_SPEED_SUPER:
|
|
|
+ descriptors = f->ss_descriptors;
|
|
|
+ break;
|
|
|
+ case USB_SPEED_HIGH:
|
|
|
descriptors = f->hs_descriptors;
|
|
|
- else
|
|
|
+ break;
|
|
|
+ default:
|
|
|
descriptors = f->descriptors;
|
|
|
+ }
|
|
|
+
|
|
|
if (!descriptors)
|
|
|
continue;
|
|
|
status = usb_descriptor_fillbuf(next, len,
|
|
@@ -377,9 +425,10 @@ static int config_desc(struct usb_composite_dev *cdev, unsigned w_value)
|
|
|
u8 type = w_value >> 8;
|
|
|
enum usb_device_speed speed = USB_SPEED_UNKNOWN;
|
|
|
|
|
|
- if (gadget_is_dualspeed(gadget)) {
|
|
|
- int hs = 0;
|
|
|
-
|
|
|
+ if (gadget->speed == USB_SPEED_SUPER)
|
|
|
+ speed = gadget->speed;
|
|
|
+ else if (gadget_is_dualspeed(gadget)) {
|
|
|
+ int hs = 0;
|
|
|
if (gadget->speed == USB_SPEED_HIGH)
|
|
|
hs = 1;
|
|
|
if (type == USB_DT_OTHER_SPEED_CONFIG)
|
|
@@ -393,13 +442,20 @@ static int config_desc(struct usb_composite_dev *cdev, unsigned w_value)
|
|
|
w_value &= 0xff;
|
|
|
list_for_each_entry(c, &cdev->configs, list) {
|
|
|
/* ignore configs that won't work at this speed */
|
|
|
- if (speed == USB_SPEED_HIGH) {
|
|
|
+ switch (speed) {
|
|
|
+ case USB_SPEED_SUPER:
|
|
|
+ if (!c->superspeed)
|
|
|
+ continue;
|
|
|
+ break;
|
|
|
+ case USB_SPEED_HIGH:
|
|
|
if (!c->highspeed)
|
|
|
continue;
|
|
|
- } else {
|
|
|
+ break;
|
|
|
+ default:
|
|
|
if (!c->fullspeed)
|
|
|
continue;
|
|
|
}
|
|
|
+
|
|
|
if (w_value == 0)
|
|
|
return config_buf(c, speed, cdev->req->buf, type);
|
|
|
w_value--;
|
|
@@ -413,16 +469,22 @@ static int count_configs(struct usb_composite_dev *cdev, unsigned type)
|
|
|
struct usb_configuration *c;
|
|
|
unsigned count = 0;
|
|
|
int hs = 0;
|
|
|
+ int ss = 0;
|
|
|
|
|
|
if (gadget_is_dualspeed(gadget)) {
|
|
|
if (gadget->speed == USB_SPEED_HIGH)
|
|
|
hs = 1;
|
|
|
+ if (gadget->speed == USB_SPEED_SUPER)
|
|
|
+ ss = 1;
|
|
|
if (type == USB_DT_DEVICE_QUALIFIER)
|
|
|
hs = !hs;
|
|
|
}
|
|
|
list_for_each_entry(c, &cdev->configs, list) {
|
|
|
/* ignore configs that won't work at this speed */
|
|
|
- if (hs) {
|
|
|
+ if (ss) {
|
|
|
+ if (!c->superspeed)
|
|
|
+ continue;
|
|
|
+ } else if (hs) {
|
|
|
if (!c->highspeed)
|
|
|
continue;
|
|
|
} else {
|
|
@@ -434,6 +496,71 @@ static int count_configs(struct usb_composite_dev *cdev, unsigned type)
|
|
|
return count;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * bos_desc() - prepares the BOS descriptor.
|
|
|
+ * @cdev: pointer to usb_composite device to generate the bos
|
|
|
+ * descriptor for
|
|
|
+ *
|
|
|
+ * This function generates the BOS (Binary Device Object)
|
|
|
+ * descriptor and its device capabilities descriptors. The BOS
|
|
|
+ * descriptor should be supported by a SuperSpeed device.
|
|
|
+ */
|
|
|
+static int bos_desc(struct usb_composite_dev *cdev)
|
|
|
+{
|
|
|
+ struct usb_ext_cap_descriptor *usb_ext;
|
|
|
+ struct usb_ss_cap_descriptor *ss_cap;
|
|
|
+ struct usb_dcd_config_params dcd_config_params;
|
|
|
+ struct usb_bos_descriptor *bos = cdev->req->buf;
|
|
|
+
|
|
|
+ bos->bLength = USB_DT_BOS_SIZE;
|
|
|
+ bos->bDescriptorType = USB_DT_BOS;
|
|
|
+
|
|
|
+ bos->wTotalLength = cpu_to_le16(USB_DT_BOS_SIZE);
|
|
|
+ bos->bNumDeviceCaps = 0;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * A SuperSpeed device shall include the USB2.0 extension descriptor
|
|
|
+ * and shall support LPM when operating in USB2.0 HS mode.
|
|
|
+ */
|
|
|
+ usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
|
|
|
+ bos->bNumDeviceCaps++;
|
|
|
+ le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
|
|
|
+ usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
|
|
|
+ usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
|
|
|
+ usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
|
|
|
+ usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * The Superspeed USB Capability descriptor shall be implemented by all
|
|
|
+ * SuperSpeed devices.
|
|
|
+ */
|
|
|
+ ss_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
|
|
|
+ bos->bNumDeviceCaps++;
|
|
|
+ le16_add_cpu(&bos->wTotalLength, USB_DT_USB_SS_CAP_SIZE);
|
|
|
+ ss_cap->bLength = USB_DT_USB_SS_CAP_SIZE;
|
|
|
+ ss_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
|
|
|
+ ss_cap->bDevCapabilityType = USB_SS_CAP_TYPE;
|
|
|
+ ss_cap->bmAttributes = 0; /* LTM is not supported yet */
|
|
|
+ ss_cap->wSpeedSupported = cpu_to_le16(USB_LOW_SPEED_OPERATION |
|
|
|
+ USB_FULL_SPEED_OPERATION |
|
|
|
+ USB_HIGH_SPEED_OPERATION |
|
|
|
+ USB_5GBPS_OPERATION);
|
|
|
+ ss_cap->bFunctionalitySupport = USB_LOW_SPEED_OPERATION;
|
|
|
+
|
|
|
+ /* Get Controller configuration */
|
|
|
+ if (cdev->gadget->ops->get_config_params)
|
|
|
+ cdev->gadget->ops->get_config_params(&dcd_config_params);
|
|
|
+ else {
|
|
|
+ dcd_config_params.bU1devExitLat = USB_DEFULT_U1_DEV_EXIT_LAT;
|
|
|
+ dcd_config_params.bU2DevExitLat =
|
|
|
+ cpu_to_le16(USB_DEFULT_U2_DEV_EXIT_LAT);
|
|
|
+ }
|
|
|
+ ss_cap->bU1devExitLat = dcd_config_params.bU1devExitLat;
|
|
|
+ ss_cap->bU2DevExitLat = dcd_config_params.bU2DevExitLat;
|
|
|
+
|
|
|
+ return le16_to_cpu(bos->wTotalLength);
|
|
|
+}
|
|
|
+
|
|
|
static void device_qual(struct usb_composite_dev *cdev)
|
|
|
{
|
|
|
struct usb_qualifier_descriptor *qual = cdev->req->buf;
|
|
@@ -477,20 +604,27 @@ static int set_config(struct usb_composite_dev *cdev,
|
|
|
unsigned power = gadget_is_otg(gadget) ? 8 : 100;
|
|
|
int tmp;
|
|
|
|
|
|
- if (cdev->config)
|
|
|
- reset_config(cdev);
|
|
|
-
|
|
|
if (number) {
|
|
|
list_for_each_entry(c, &cdev->configs, list) {
|
|
|
if (c->bConfigurationValue == number) {
|
|
|
+ /*
|
|
|
+ * We disable the FDs of the previous
|
|
|
+ * configuration only if the new configuration
|
|
|
+ * is a valid one
|
|
|
+ */
|
|
|
+ if (cdev->config)
|
|
|
+ reset_config(cdev);
|
|
|
result = 0;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
if (result < 0)
|
|
|
goto done;
|
|
|
- } else
|
|
|
+ } else { /* Zero configuration value - need to reset the config */
|
|
|
+ if (cdev->config)
|
|
|
+ reset_config(cdev);
|
|
|
result = 0;
|
|
|
+ }
|
|
|
|
|
|
INFO(cdev, "%s speed config #%d: %s\n",
|
|
|
({ char *speed;
|
|
@@ -504,6 +638,9 @@ static int set_config(struct usb_composite_dev *cdev,
|
|
|
case USB_SPEED_HIGH:
|
|
|
speed = "high";
|
|
|
break;
|
|
|
+ case USB_SPEED_SUPER:
|
|
|
+ speed = "super";
|
|
|
+ break;
|
|
|
default:
|
|
|
speed = "?";
|
|
|
break;
|
|
@@ -528,10 +665,16 @@ static int set_config(struct usb_composite_dev *cdev,
|
|
|
* function's setup callback instead of the current
|
|
|
* configuration's setup callback.
|
|
|
*/
|
|
|
- if (gadget->speed == USB_SPEED_HIGH)
|
|
|
+ switch (gadget->speed) {
|
|
|
+ case USB_SPEED_SUPER:
|
|
|
+ descriptors = f->ss_descriptors;
|
|
|
+ break;
|
|
|
+ case USB_SPEED_HIGH:
|
|
|
descriptors = f->hs_descriptors;
|
|
|
- else
|
|
|
+ break;
|
|
|
+ default:
|
|
|
descriptors = f->descriptors;
|
|
|
+ }
|
|
|
|
|
|
for (; *descriptors; ++descriptors) {
|
|
|
struct usb_endpoint_descriptor *ep;
|
|
@@ -624,8 +767,9 @@ int usb_add_config(struct usb_composite_dev *cdev,
|
|
|
} else {
|
|
|
unsigned i;
|
|
|
|
|
|
- DBG(cdev, "cfg %d/%p speeds:%s%s\n",
|
|
|
+ DBG(cdev, "cfg %d/%p speeds:%s%s%s\n",
|
|
|
config->bConfigurationValue, config,
|
|
|
+ config->superspeed ? " super" : "",
|
|
|
config->highspeed ? " high" : "",
|
|
|
config->fullspeed
|
|
|
? (gadget_is_dualspeed(cdev->gadget)
|
|
@@ -904,6 +1048,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
|
|
|
struct usb_composite_dev *cdev = get_gadget_data(gadget);
|
|
|
struct usb_request *req = cdev->req;
|
|
|
int value = -EOPNOTSUPP;
|
|
|
+ int status = 0;
|
|
|
u16 w_index = le16_to_cpu(ctrl->wIndex);
|
|
|
u8 intf = w_index & 0xFF;
|
|
|
u16 w_value = le16_to_cpu(ctrl->wValue);
|
|
@@ -931,18 +1076,29 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
|
|
|
case USB_DT_DEVICE:
|
|
|
cdev->desc.bNumConfigurations =
|
|
|
count_configs(cdev, USB_DT_DEVICE);
|
|
|
+ cdev->desc.bMaxPacketSize0 =
|
|
|
+ cdev->gadget->ep0->maxpacket;
|
|
|
+ if (gadget_is_superspeed(gadget)) {
|
|
|
+ if (gadget->speed >= USB_SPEED_SUPER)
|
|
|
+ cdev->desc.bcdUSB = cpu_to_le16(0x0300);
|
|
|
+ else
|
|
|
+ cdev->desc.bcdUSB = cpu_to_le16(0x0210);
|
|
|
+ }
|
|
|
+
|
|
|
value = min(w_length, (u16) sizeof cdev->desc);
|
|
|
memcpy(req->buf, &cdev->desc, value);
|
|
|
break;
|
|
|
case USB_DT_DEVICE_QUALIFIER:
|
|
|
- if (!gadget_is_dualspeed(gadget))
|
|
|
+ if (!gadget_is_dualspeed(gadget) ||
|
|
|
+ gadget->speed >= USB_SPEED_SUPER)
|
|
|
break;
|
|
|
device_qual(cdev);
|
|
|
value = min_t(int, w_length,
|
|
|
sizeof(struct usb_qualifier_descriptor));
|
|
|
break;
|
|
|
case USB_DT_OTHER_SPEED_CONFIG:
|
|
|
- if (!gadget_is_dualspeed(gadget))
|
|
|
+ if (!gadget_is_dualspeed(gadget) ||
|
|
|
+ gadget->speed >= USB_SPEED_SUPER)
|
|
|
break;
|
|
|
/* FALLTHROUGH */
|
|
|
case USB_DT_CONFIG:
|
|
@@ -956,6 +1112,12 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
|
|
|
if (value >= 0)
|
|
|
value = min(w_length, (u16) value);
|
|
|
break;
|
|
|
+ case USB_DT_BOS:
|
|
|
+ if (gadget_is_superspeed(gadget)) {
|
|
|
+ value = bos_desc(cdev);
|
|
|
+ value = min(w_length, (u16) value);
|
|
|
+ }
|
|
|
+ break;
|
|
|
}
|
|
|
break;
|
|
|
|
|
@@ -1023,6 +1185,61 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
|
|
|
*((u8 *)req->buf) = value;
|
|
|
value = min(w_length, (u16) 1);
|
|
|
break;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * USB 3.0 additions:
|
|
|
+ * Function driver should handle get_status request. If such cb
|
|
|
+ * wasn't supplied we respond with default value = 0
|
|
|
+ * Note: function driver should supply such cb only for the first
|
|
|
+ * interface of the function
|
|
|
+ */
|
|
|
+ case USB_REQ_GET_STATUS:
|
|
|
+ if (!gadget_is_superspeed(gadget))
|
|
|
+ goto unknown;
|
|
|
+ if (ctrl->bRequestType != (USB_DIR_IN | USB_RECIP_INTERFACE))
|
|
|
+ goto unknown;
|
|
|
+ value = 2; /* This is the length of the get_status reply */
|
|
|
+ put_unaligned_le16(0, req->buf);
|
|
|
+ if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
|
|
|
+ break;
|
|
|
+ f = cdev->config->interface[intf];
|
|
|
+ if (!f)
|
|
|
+ break;
|
|
|
+ status = f->get_status ? f->get_status(f) : 0;
|
|
|
+ if (status < 0)
|
|
|
+ break;
|
|
|
+ put_unaligned_le16(status & 0x0000ffff, req->buf);
|
|
|
+ break;
|
|
|
+ /*
|
|
|
+ * Function drivers should handle SetFeature/ClearFeature
|
|
|
+ * (FUNCTION_SUSPEND) request. function_suspend cb should be supplied
|
|
|
+ * only for the first interface of the function
|
|
|
+ */
|
|
|
+ case USB_REQ_CLEAR_FEATURE:
|
|
|
+ case USB_REQ_SET_FEATURE:
|
|
|
+ if (!gadget_is_superspeed(gadget))
|
|
|
+ goto unknown;
|
|
|
+ if (ctrl->bRequestType != (USB_DIR_OUT | USB_RECIP_INTERFACE))
|
|
|
+ goto unknown;
|
|
|
+ switch (w_value) {
|
|
|
+ case USB_INTRF_FUNC_SUSPEND:
|
|
|
+ if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
|
|
|
+ break;
|
|
|
+ f = cdev->config->interface[intf];
|
|
|
+ if (!f)
|
|
|
+ break;
|
|
|
+ value = 0;
|
|
|
+ if (f->func_suspend)
|
|
|
+ value = f->func_suspend(f, w_index >> 8);
|
|
|
+ if (value < 0) {
|
|
|
+ ERROR(cdev,
|
|
|
+ "func_suspend() returned error %d\n",
|
|
|
+ value);
|
|
|
+ value = 0;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ break;
|
|
|
default:
|
|
|
unknown:
|
|
|
VDBG(cdev,
|
|
@@ -1340,7 +1557,11 @@ composite_resume(struct usb_gadget *gadget)
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
static struct usb_gadget_driver composite_driver = {
|
|
|
+#ifdef CONFIG_USB_GADGET_SUPERSPEED
|
|
|
+ .speed = USB_SPEED_SUPER,
|
|
|
+#else
|
|
|
.speed = USB_SPEED_HIGH,
|
|
|
+#endif
|
|
|
|
|
|
.unbind = composite_unbind,
|
|
|
|