diff options
author | Yuyang Du <yuyang.du@intel.com> | 2017-06-08 07:04:10 +0200 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2017-06-13 10:51:10 +0200 |
commit | 1c9de5bf428612458427943b724bea51abde520a (patch) | |
tree | deb6f9bed592da3a5952239dc5212e721ff4ab5f /drivers/usb/usbip/vhci_hcd.c | |
parent | usbip: vhci-hcd: Set the vhci structure up to work (diff) | |
download | linux-1c9de5bf428612458427943b724bea51abde520a.tar.xz linux-1c9de5bf428612458427943b724bea51abde520a.zip |
usbip: vhci-hcd: Add USB3 SuperSpeed support
This patch adds a USB3 HCD to an existing USB2 HCD and provides
the support of SuperSpeed, in case the device can only be enumerated
with SuperSpeed.
The bulk of the added code in usb3_bos_desc and hub_control to support
SuperSpeed is borrowed from the commit 1cd8fd2887e162ad ("usb: gadget:
dummy_hcd: add SuperSpeed support").
With this patch, each vhci will have VHCI_HC_PORTS HighSpeed ports
and VHCI_HC_PORTS SuperSpeed ports.
Suggested-by: Krzysztof Opasiak <k.opasiak@samsung.com>
Signed-off-by: Yuyang Du <yuyang.du@intel.com>
Acked-by: Shuah Khan <shuahkh@osg.samsung.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/usbip/vhci_hcd.c')
-rw-r--r-- | drivers/usb/usbip/vhci_hcd.c | 323 |
1 files changed, 263 insertions, 60 deletions
diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c index c96dd44ff162..af42a6010205 100644 --- a/drivers/usb/usbip/vhci_hcd.c +++ b/drivers/usb/usbip/vhci_hcd.c @@ -232,6 +232,40 @@ done: return changed ? retval : 0; } +/* usb 3.0 root hub device descriptor */ +static struct { + struct usb_bos_descriptor bos; + struct usb_ss_cap_descriptor ss_cap; +} __packed usb3_bos_desc = { + + .bos = { + .bLength = USB_DT_BOS_SIZE, + .bDescriptorType = USB_DT_BOS, + .wTotalLength = cpu_to_le16(sizeof(usb3_bos_desc)), + .bNumDeviceCaps = 1, + }, + .ss_cap = { + .bLength = USB_DT_USB_SS_CAP_SIZE, + .bDescriptorType = USB_DT_DEVICE_CAPABILITY, + .bDevCapabilityType = USB_SS_CAP_TYPE, + .wSpeedSupported = cpu_to_le16(USB_5GBPS_OPERATION), + .bFunctionalitySupport = ilog2(USB_5GBPS_OPERATION), + }, +}; + +static inline void +ss_hub_descriptor(struct usb_hub_descriptor *desc) +{ + memset(desc, 0, sizeof *desc); + desc->bDescriptorType = USB_DT_SS_HUB; + desc->bDescLength = 12; + desc->wHubCharacteristics = cpu_to_le16( + HUB_CHAR_INDV_PORT_LPSM | HUB_CHAR_COMMON_OCPM); + desc->bNbrPorts = VHCI_HC_PORTS; + desc->u.ss.bHubHdrDecLat = 0x04; /* Worst case: 0.4 micro sec*/ + desc->u.ss.DeviceRemovable = 0xffff; +} + static inline void hub_descriptor(struct usb_hub_descriptor *desc) { int width; @@ -265,13 +299,15 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, /* * NOTE: - * wIndex shows the port number and begins from 1. + * wIndex (bits 0-7) shows the port number and begins from 1? */ + wIndex = ((__u8)(wIndex & 0x00ff)); usbip_dbg_vhci_rh("typeReq %x wValue %x wIndex %x\n", typeReq, wValue, wIndex); + if (wIndex > VHCI_HC_PORTS) pr_err("invalid port number %d\n", wIndex); - rhport = ((__u8)(wIndex & 0x00ff)) - 1; + rhport = wIndex - 1; vhci_hcd = hcd_to_vhci_hcd(hcd); vhci = vhci_hcd->vhci; @@ -291,34 +327,26 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, case ClearPortFeature: switch (wValue) { case USB_PORT_FEAT_SUSPEND: + if (hcd->speed == HCD_USB3) { + pr_err(" ClearPortFeature: USB_PORT_FEAT_SUSPEND req not " + "supported for USB 3.0 roothub\n"); + goto error; + } + usbip_dbg_vhci_rh( + " ClearPortFeature: USB_PORT_FEAT_SUSPEND\n"); if (vhci_hcd->port_status[rhport] & USB_PORT_STAT_SUSPEND) { /* 20msec signaling */ vhci_hcd->resuming = 1; - vhci_hcd->re_timeout = - jiffies + msecs_to_jiffies(20); + vhci_hcd->re_timeout = jiffies + msecs_to_jiffies(20); } break; case USB_PORT_FEAT_POWER: usbip_dbg_vhci_rh( " ClearPortFeature: USB_PORT_FEAT_POWER\n"); - vhci_hcd->port_status[rhport] = 0; - vhci_hcd->resuming = 0; - break; - case USB_PORT_FEAT_C_RESET: - usbip_dbg_vhci_rh( - " ClearPortFeature: USB_PORT_FEAT_C_RESET\n"); - switch (vhci_hcd->vdev[rhport].speed) { - case USB_SPEED_HIGH: - vhci_hcd->port_status[rhport] |= - USB_PORT_STAT_HIGH_SPEED; - break; - case USB_SPEED_LOW: - vhci_hcd->port_status[rhport] |= - USB_PORT_STAT_LOW_SPEED; - break; - default: - break; - } + if (hcd->speed == HCD_USB3) + vhci_hcd->port_status[rhport] &= ~USB_SS_PORT_STAT_POWER; + else + vhci_hcd->port_status[rhport] &= ~USB_PORT_STAT_POWER; break; default: usbip_dbg_vhci_rh(" ClearPortFeature: default %x\n", @@ -329,7 +357,26 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, break; case GetHubDescriptor: usbip_dbg_vhci_rh(" GetHubDescriptor\n"); - hub_descriptor((struct usb_hub_descriptor *) buf); + if (hcd->speed == HCD_USB3 && + (wLength < USB_DT_SS_HUB_SIZE || + wValue != (USB_DT_SS_HUB << 8))) { + pr_err("Wrong hub descriptor type for USB 3.0 roothub.\n"); + goto error; + } + if (hcd->speed == HCD_USB3) + ss_hub_descriptor((struct usb_hub_descriptor *) buf); + else + hub_descriptor((struct usb_hub_descriptor *) buf); + break; + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + if (hcd->speed != HCD_USB3) + goto error; + + if ((wValue >> 8) != USB_DT_BOS) + goto error; + + memcpy(buf, &usb3_bos_desc, sizeof(usb3_bos_desc)); + retval = sizeof(usb3_bos_desc); break; case GetHubStatus: usbip_dbg_vhci_rh(" GetHubStatus\n"); @@ -337,7 +384,7 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, break; case GetPortStatus: usbip_dbg_vhci_rh(" GetPortStatus port %x\n", wIndex); - if (wIndex > VHCI_HC_PORTS || wIndex < 1) { + if (wIndex < 1) { pr_err("invalid port number %d\n", wIndex); retval = -EPIPE; } @@ -348,20 +395,16 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, * complete it!! */ if (vhci_hcd->resuming && time_after(jiffies, vhci_hcd->re_timeout)) { - vhci_hcd->port_status[rhport] |= - (1 << USB_PORT_FEAT_C_SUSPEND); - vhci_hcd->port_status[rhport] &= - ~(1 << USB_PORT_FEAT_SUSPEND); + vhci_hcd->port_status[rhport] |= (1 << USB_PORT_FEAT_C_SUSPEND); + vhci_hcd->port_status[rhport] &= ~(1 << USB_PORT_FEAT_SUSPEND); vhci_hcd->resuming = 0; vhci_hcd->re_timeout = 0; } if ((vhci_hcd->port_status[rhport] & (1 << USB_PORT_FEAT_RESET)) != 0 && time_after(jiffies, vhci_hcd->re_timeout)) { - vhci_hcd->port_status[rhport] |= - (1 << USB_PORT_FEAT_C_RESET); - vhci_hcd->port_status[rhport] &= - ~(1 << USB_PORT_FEAT_RESET); + vhci_hcd->port_status[rhport] |= (1 << USB_PORT_FEAT_C_RESET); + vhci_hcd->port_status[rhport] &= ~(1 << USB_PORT_FEAT_RESET); vhci_hcd->re_timeout = 0; if (vhci_hcd->vdev[rhport].ud.status == @@ -373,6 +416,22 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, vhci_hcd->port_status[rhport] |= USB_PORT_STAT_ENABLE; } + + if (hcd->speed < HCD_USB3) { + switch (vhci_hcd->vdev[rhport].speed) { + case USB_SPEED_HIGH: + vhci_hcd->port_status[rhport] |= + USB_PORT_STAT_HIGH_SPEED; + break; + case USB_SPEED_LOW: + vhci_hcd->port_status[rhport] |= + USB_PORT_STAT_LOW_SPEED; + break; + default: + pr_err("vhci_device speed not set\n"); + break; + } + } } ((__le16 *) buf)[0] = cpu_to_le16(vhci_hcd->port_status[rhport]); ((__le16 *) buf)[1] = @@ -387,36 +446,119 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, break; case SetPortFeature: switch (wValue) { + case USB_PORT_FEAT_LINK_STATE: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_LINK_STATE\n"); + if (hcd->speed != HCD_USB3) { + pr_err("USB_PORT_FEAT_LINK_STATE req not " + "supported for USB 2.0 roothub\n"); + goto error; + } + /* + * Since this is dummy we don't have an actual link so + * there is nothing to do for the SET_LINK_STATE cmd + */ + break; + case USB_PORT_FEAT_U1_TIMEOUT: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_U1_TIMEOUT\n"); + case USB_PORT_FEAT_U2_TIMEOUT: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_U2_TIMEOUT\n"); + /* TODO: add suspend/resume support! */ + if (hcd->speed != HCD_USB3) { + pr_err("USB_PORT_FEAT_U1/2_TIMEOUT req not " + "supported for USB 2.0 roothub\n"); + goto error; + } + break; case USB_PORT_FEAT_SUSPEND: usbip_dbg_vhci_rh( " SetPortFeature: USB_PORT_FEAT_SUSPEND\n"); + /* Applicable only for USB2.0 hub */ + if (hcd->speed == HCD_USB3) { + pr_err("USB_PORT_FEAT_SUSPEND req not " + "supported for USB 3.0 roothub\n"); + goto error; + } + + vhci_hcd->port_status[rhport] |= USB_PORT_STAT_SUSPEND; + break; + case USB_PORT_FEAT_POWER: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_POWER\n"); + if (hcd->speed == HCD_USB3) + vhci_hcd->port_status[rhport] |= USB_SS_PORT_STAT_POWER; + else + vhci_hcd->port_status[rhport] |= USB_PORT_STAT_POWER; break; + case USB_PORT_FEAT_BH_PORT_RESET: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_BH_PORT_RESET\n"); + /* Applicable only for USB3.0 hub */ + if (hcd->speed != HCD_USB3) { + pr_err("USB_PORT_FEAT_BH_PORT_RESET req not " + "supported for USB 2.0 roothub\n"); + goto error; + } + /* FALLS THROUGH */ case USB_PORT_FEAT_RESET: usbip_dbg_vhci_rh( " SetPortFeature: USB_PORT_FEAT_RESET\n"); - /* if it's already running, disconnect first */ - if (vhci_hcd->port_status[rhport] & USB_PORT_STAT_ENABLE) { - vhci_hcd->port_status[rhport] &= - ~(USB_PORT_STAT_ENABLE | - USB_PORT_STAT_LOW_SPEED | - USB_PORT_STAT_HIGH_SPEED); - /* FIXME test that code path! */ + /* if it's already enabled, disable */ + if (hcd->speed == HCD_USB3) { + vhci_hcd->port_status[rhport] = 0; + vhci_hcd->port_status[rhport] = + (USB_SS_PORT_STAT_POWER | + USB_PORT_STAT_CONNECTION | + USB_PORT_STAT_RESET); + } else if (vhci_hcd->port_status[rhport] & USB_PORT_STAT_ENABLE) { + vhci_hcd->port_status[rhport] &= ~(USB_PORT_STAT_ENABLE + | USB_PORT_STAT_LOW_SPEED + | USB_PORT_STAT_HIGH_SPEED); } + /* 50msec reset signaling */ vhci_hcd->re_timeout = jiffies + msecs_to_jiffies(50); - /* FALLTHROUGH */ + /* FALLS THROUGH */ default: usbip_dbg_vhci_rh(" SetPortFeature: default %d\n", wValue); - vhci_hcd->port_status[rhport] |= (1 << wValue); - break; + if (hcd->speed == HCD_USB3) { + if ((vhci_hcd->port_status[rhport] & + USB_SS_PORT_STAT_POWER) != 0) { + vhci_hcd->port_status[rhport] |= (1 << wValue); + } + } else + if ((vhci_hcd->port_status[rhport] & + USB_PORT_STAT_POWER) != 0) { + vhci_hcd->port_status[rhport] |= (1 << wValue); + } + } + break; + case GetPortErrorCount: + usbip_dbg_vhci_rh(" GetPortErrorCount\n"); + if (hcd->speed != HCD_USB3) { + pr_err("GetPortErrorCount req not " + "supported for USB 2.0 roothub\n"); + goto error; + } + /* We'll always return 0 since this is a dummy hub */ + *(__le32 *) buf = cpu_to_le32(0); + break; + case SetHubDepth: + usbip_dbg_vhci_rh(" SetHubDepth\n"); + if (hcd->speed != HCD_USB3) { + pr_err("SetHubDepth req not supported for " + "USB 2.0 roothub\n"); + goto error; } break; - default: - pr_err("default: no such request\n"); - + pr_err("default hub control req: %04x v%04x i%04x l%d\n", + typeReq, wValue, wIndex, wLength); +error: /* "protocol stall" on error */ retval = -EPIPE; } @@ -433,6 +575,9 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, spin_unlock_irqrestore(&vhci->lock, flags); + if ((vhci_hcd->port_status[rhport] & PORT_C_MASK) != 0) + usb_hcd_poll_rh_status(hcd); + return retval; } @@ -471,8 +616,7 @@ static void vhci_tx_urb(struct urb *urb, struct vhci_device *vdev) spin_unlock_irqrestore(&vdev->priv_lock, flags); } -static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, - gfp_t mem_flags) +static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) { struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd); struct vhci *vhci = vhci_hcd->vhci; @@ -850,7 +994,6 @@ static void vhci_shutdown_connection(struct usbip_device *ud) pr_info("disconnect device\n"); } - static void vhci_device_reset(struct usbip_device *ud) { struct vhci_device *vdev = container_of(ud, struct vhci_device, ud); @@ -926,12 +1069,22 @@ static int vhci_setup(struct usb_hcd *hcd) { struct vhci *vhci = *((void **)dev_get_platdata(hcd->self.controller)); hcd->self.sg_tablesize = ~0; - - vhci->vhci_hcd_hs = hcd_to_vhci_hcd(hcd); - vhci->vhci_hcd_hs->vhci = vhci; - hcd->speed = HCD_USB2; - hcd->self.root_hub->speed = USB_SPEED_HIGH; - + if (usb_hcd_is_primary_hcd(hcd)) { + vhci->vhci_hcd_hs = hcd_to_vhci_hcd(hcd); + vhci->vhci_hcd_hs->vhci = vhci; + /* + * Mark the first roothub as being USB 2.0. + * The USB 3.0 roothub will be registered later by + * vhci_hcd_probe() + */ + hcd->speed = HCD_USB2; + hcd->self.root_hub->speed = USB_SPEED_HIGH; + } else { + vhci->vhci_hcd_ss = hcd_to_vhci_hcd(hcd); + vhci->vhci_hcd_ss->vhci = vhci; + hcd->speed = HCD_USB3; + hcd->self.root_hub->speed = USB_SPEED_SUPER; + } return 0; } @@ -943,7 +1096,8 @@ static int vhci_start(struct usb_hcd *hcd) usbip_dbg_vhci_hc("enter vhci_start\n"); - spin_lock_init(&vhci_hcd->vhci->lock); + if (usb_hcd_is_primary_hcd(hcd)) + spin_lock_init(&vhci_hcd->vhci->lock); /* initialize private data of usb_hcd */ @@ -970,7 +1124,7 @@ static int vhci_start(struct usb_hcd *hcd) } /* vhci_hcd is now ready to be controlled through sysfs */ - if (id == 0) { + if (id == 0 && usb_hcd_is_primary_hcd(hcd)) { err = vhci_init_attr_group(); if (err) { pr_err("init attr group\n"); @@ -997,7 +1151,7 @@ static void vhci_stop(struct usb_hcd *hcd) /* 1. remove the userland interface of vhci_hcd */ id = hcd_name_to_id(hcd_name(hcd)); - if (id == 0) { + if (id == 0 && usb_hcd_is_primary_hcd(hcd)) { sysfs_remove_group(&hcd_dev(hcd)->kobj, &vhci_attr_group); vhci_finish_attr_group(); } @@ -1058,12 +1212,30 @@ static int vhci_bus_resume(struct usb_hcd *hcd) #define vhci_bus_resume NULL #endif +/* Change a group of bulk endpoints to support multiple stream IDs */ +static int vhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint **eps, unsigned int num_eps, + unsigned int num_streams, gfp_t mem_flags) +{ + dev_dbg(&hcd->self.root_hub->dev, "vhci_alloc_streams not implemented\n"); + return 0; +} + +/* Reverts a group of bulk endpoints back to not using stream IDs. */ +static int vhci_free_streams(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint **eps, unsigned int num_eps, + gfp_t mem_flags) +{ + dev_dbg(&hcd->self.root_hub->dev, "vhci_free_streams not implemented\n"); + return 0; +} + static struct hc_driver vhci_hc_driver = { .description = driver_name, .product_desc = driver_desc, .hcd_priv_size = sizeof(struct vhci_hcd), - .flags = HCD_USB2, + .flags = HCD_USB3 | HCD_SHARED, .reset = vhci_setup, .start = vhci_start, @@ -1078,12 +1250,16 @@ static struct hc_driver vhci_hc_driver = { .hub_control = vhci_hub_control, .bus_suspend = vhci_bus_suspend, .bus_resume = vhci_bus_resume, + + .alloc_streams = vhci_alloc_streams, + .free_streams = vhci_free_streams, }; static int vhci_hcd_probe(struct platform_device *pdev) { struct vhci *vhci; struct usb_hcd *hcd_hs; + struct usb_hcd *hcd_ss; int ret; usbip_dbg_vhci_hc("name %s id %d\n", pdev->name, pdev->id); @@ -1094,7 +1270,7 @@ static int vhci_hcd_probe(struct platform_device *pdev) */ hcd_hs = usb_create_hcd(&vhci_hc_driver, &pdev->dev, dev_name(&pdev->dev)); if (!hcd_hs) { - pr_err("create hcd failed\n"); + pr_err("create primary hcd failed\n"); return -ENOMEM; } hcd_hs->has_tt = 1; @@ -1109,12 +1285,31 @@ static int vhci_hcd_probe(struct platform_device *pdev) goto put_usb2_hcd; } + hcd_ss = usb_create_shared_hcd(&vhci_hc_driver, &pdev->dev, + dev_name(&pdev->dev), hcd_hs); + if (!hcd_ss) { + ret = -ENOMEM; + pr_err("create shared hcd failed\n"); + goto remove_usb2_hcd; + } + + ret = usb_add_hcd(hcd_ss, 0, 0); + if (ret) { + pr_err("usb_add_hcd ss failed %d\n", ret); + goto put_usb3_hcd; + } + usbip_dbg_vhci_hc("bye\n"); return 0; +put_usb3_hcd: + usb_put_hcd(hcd_ss); +remove_usb2_hcd: + usb_remove_hcd(hcd_hs); put_usb2_hcd: usb_put_hcd(hcd_hs); vhci->vhci_hcd_hs = NULL; + vhci->vhci_hcd_ss = NULL; return ret; } @@ -1127,10 +1322,14 @@ static int vhci_hcd_remove(struct platform_device *pdev) * then reverses the effects of usb_add_hcd(), * invoking the HCD's stop() methods. */ + usb_remove_hcd(vhci_hcd_to_hcd(vhci->vhci_hcd_ss)); + usb_put_hcd(vhci_hcd_to_hcd(vhci->vhci_hcd_ss)); + usb_remove_hcd(vhci_hcd_to_hcd(vhci->vhci_hcd_hs)); usb_put_hcd(vhci_hcd_to_hcd(vhci->vhci_hcd_hs)); vhci->vhci_hcd_hs = NULL; + vhci->vhci_hcd_ss = NULL; return 0; } @@ -1142,7 +1341,7 @@ static int vhci_hcd_suspend(struct platform_device *pdev, pm_message_t state) { struct usb_hcd *hcd; struct vhci *vhci; - int rhport = 0; + int rhport; int connected = 0; int ret = 0; unsigned long flags; @@ -1161,6 +1360,10 @@ static int vhci_hcd_suspend(struct platform_device *pdev, pm_message_t state) if (vhci->vhci_hcd_hs->port_status[rhport] & USB_PORT_STAT_CONNECTION) connected += 1; + + if (vhci->vhci_hcd_ss->port_status[rhport] & + USB_PORT_STAT_CONNECTION) + connected += 1; } spin_unlock_irqrestore(&vhci->lock, flags); |