in usbip/vhci_hcd.c [315:662]
static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
u16 wIndex, char *buf, u16 wLength)
{
struct vhci_hcd *vhci_hcd;
struct vhci *vhci;
int retval = 0;
int rhport = -1;
unsigned long flags;
bool invalid_rhport = false;
u32 prev_port_status[VHCI_HC_PORTS];
if (!HCD_HW_ACCESSIBLE(hcd))
return -ETIMEDOUT;
/*
* NOTE:
* 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);
/*
* wIndex can be 0 for some request types (typeReq). rhport is
* in valid range when wIndex >= 1 and < VHCI_HC_PORTS.
*
* Reference port_status[] only with valid rhport when
* invalid_rhport is false.
*/
if (wIndex < 1 || wIndex > VHCI_HC_PORTS) {
invalid_rhport = true;
if (wIndex > VHCI_HC_PORTS)
pr_err("invalid port number %d\n", wIndex);
} else
rhport = wIndex - 1;
vhci_hcd = hcd_to_vhci_hcd(hcd);
vhci = vhci_hcd->vhci;
spin_lock_irqsave(&vhci->lock, flags);
/* store old status and compare now and old later */
if (usbip_dbg_flag_vhci_rh) {
if (!invalid_rhport)
memcpy(prev_port_status, vhci_hcd->port_status,
sizeof(prev_port_status));
}
switch (typeReq) {
case ClearHubFeature:
usbip_dbg_vhci_rh(" ClearHubFeature\n");
break;
case ClearPortFeature:
if (invalid_rhport) {
pr_err("invalid port number %d\n", wIndex);
goto error;
}
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);
}
break;
case USB_PORT_FEAT_POWER:
usbip_dbg_vhci_rh(
" ClearPortFeature: 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;
default:
usbip_dbg_vhci_rh(" ClearPortFeature: default %x\n",
wValue);
if (wValue >= 32)
goto error;
vhci_hcd->port_status[rhport] &= ~(1 << wValue);
break;
}
break;
case GetHubDescriptor:
usbip_dbg_vhci_rh(" GetHubDescriptor\n");
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");
*(__le32 *) buf = cpu_to_le32(0);
break;
case GetPortStatus:
usbip_dbg_vhci_rh(" GetPortStatus port %x\n", wIndex);
if (invalid_rhport) {
pr_err("invalid port number %d\n", wIndex);
retval = -EPIPE;
goto error;
}
/* we do not care about resume. */
/* whoever resets or resumes must GetPortStatus to
* 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->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->re_timeout = 0;
/*
* A few drivers do usb reset during probe when
* the device could be in VDEV_ST_USED state
*/
if (vhci_hcd->vdev[rhport].ud.status ==
VDEV_ST_NOTASSIGNED ||
vhci_hcd->vdev[rhport].ud.status ==
VDEV_ST_USED) {
usbip_dbg_vhci_rh(
" enable rhport %d (status %u)\n",
rhport,
vhci_hcd->vdev[rhport].ud.status);
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] =
cpu_to_le16(vhci_hcd->port_status[rhport] >> 16);
usbip_dbg_vhci_rh(" GetPortStatus bye %x %x\n", ((u16 *)buf)[0],
((u16 *)buf)[1]);
break;
case SetHubFeature:
usbip_dbg_vhci_rh(" SetHubFeature\n");
retval = -EPIPE;
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");
fallthrough;
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;
}
if (invalid_rhport) {
pr_err("invalid port number %d\n", wIndex);
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 (invalid_rhport) {
pr_err("invalid port number %d\n", wIndex);
goto error;
}
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");
if (invalid_rhport) {
pr_err("invalid port number %d\n", wIndex);
goto error;
}
/* 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;
}
fallthrough;
case USB_PORT_FEAT_RESET:
usbip_dbg_vhci_rh(
" SetPortFeature: USB_PORT_FEAT_RESET\n");
if (invalid_rhport) {
pr_err("invalid port number %d\n", wIndex);
goto error;
}
/* 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;
default:
usbip_dbg_vhci_rh(" SetPortFeature: default %d\n",
wValue);
if (invalid_rhport) {
pr_err("invalid port number %d\n", wIndex);
goto error;
}
if (wValue >= 32)
goto error;
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 hub control req: %04x v%04x i%04x l%d\n",
typeReq, wValue, wIndex, wLength);
error:
/* "protocol stall" on error */
retval = -EPIPE;
}
if (usbip_dbg_flag_vhci_rh) {
pr_debug("port %d\n", rhport);
/* Only dump valid port status */
if (!invalid_rhport) {
dump_port_status_diff(prev_port_status[rhport],
vhci_hcd->port_status[rhport],
hcd->speed == HCD_USB3);
}
}
usbip_dbg_vhci_rh(" bye\n");
spin_unlock_irqrestore(&vhci->lock, flags);
if (!invalid_rhport &&
(vhci_hcd->port_status[rhport] & PORT_C_MASK) != 0) {
usb_hcd_poll_rh_status(hcd);
}
return retval;
}