int xhci_hub_control()

in host/xhci-hub.c [1178:1607]


int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
		u16 wIndex, char *buf, u16 wLength)
{
	struct xhci_hcd	*xhci = hcd_to_xhci(hcd);
	int max_ports;
	unsigned long flags;
	u32 temp, status;
	int retval = 0;
	int slot_id;
	struct xhci_bus_state *bus_state;
	u16 link_state = 0;
	u16 wake_mask = 0;
	u16 timeout = 0;
	u16 test_mode = 0;
	struct xhci_hub *rhub;
	struct xhci_port **ports;

	rhub = xhci_get_rhub(hcd);
	ports = rhub->ports;
	max_ports = rhub->num_ports;
	bus_state = &rhub->bus_state;

	spin_lock_irqsave(&xhci->lock, flags);
	switch (typeReq) {
	case GetHubStatus:
		/* No power source, over-current reported per port */
		memset(buf, 0, 4);
		break;
	case GetHubDescriptor:
		/* Check to make sure userspace is asking for the USB 3.0 hub
		 * descriptor for the USB 3.0 roothub.  If not, we stall the
		 * endpoint, like external hubs do.
		 */
		if (hcd->speed >= HCD_USB3 &&
				(wLength < USB_DT_SS_HUB_SIZE ||
				 wValue != (USB_DT_SS_HUB << 8))) {
			xhci_dbg(xhci, "Wrong hub descriptor type for "
					"USB 3.0 roothub.\n");
			goto error;
		}
		xhci_hub_descriptor(hcd, xhci,
				(struct usb_hub_descriptor *) buf);
		break;
	case DeviceRequest | USB_REQ_GET_DESCRIPTOR:
		if ((wValue & 0xff00) != (USB_DT_BOS << 8))
			goto error;

		if (hcd->speed < HCD_USB3)
			goto error;

		retval = xhci_create_usb3x_bos_desc(xhci, buf, wLength);
		spin_unlock_irqrestore(&xhci->lock, flags);
		return retval;
	case GetPortStatus:
		if (!wIndex || wIndex > max_ports)
			goto error;
		wIndex--;
		temp = readl(ports[wIndex]->addr);
		if (temp == ~(u32)0) {
			xhci_hc_died(xhci);
			retval = -ENODEV;
			break;
		}
		trace_xhci_get_port_status(wIndex, temp);
		status = xhci_get_port_status(hcd, bus_state, wIndex, temp,
					      &flags);
		if (status == 0xffffffff)
			goto error;

		xhci_dbg(xhci, "Get port status %d-%d read: 0x%x, return 0x%x",
			 hcd->self.busnum, wIndex + 1, temp, status);

		put_unaligned(cpu_to_le32(status), (__le32 *) buf);
		/* if USB 3.1 extended port status return additional 4 bytes */
		if (wValue == 0x02) {
			u32 port_li;

			if (hcd->speed < HCD_USB31 || wLength != 8) {
				xhci_err(xhci, "get ext port status invalid parameter\n");
				retval = -EINVAL;
				break;
			}
			port_li = readl(ports[wIndex]->addr + PORTLI);
			status = xhci_get_ext_port_status(temp, port_li);
			put_unaligned_le32(status, &buf[4]);
		}
		break;
	case SetPortFeature:
		if (wValue == USB_PORT_FEAT_LINK_STATE)
			link_state = (wIndex & 0xff00) >> 3;
		if (wValue == USB_PORT_FEAT_REMOTE_WAKE_MASK)
			wake_mask = wIndex & 0xff00;
		if (wValue == USB_PORT_FEAT_TEST)
			test_mode = (wIndex & 0xff00) >> 8;
		/* The MSB of wIndex is the U1/U2 timeout */
		timeout = (wIndex & 0xff00) >> 8;
		wIndex &= 0xff;
		if (!wIndex || wIndex > max_ports)
			goto error;
		wIndex--;
		temp = readl(ports[wIndex]->addr);
		if (temp == ~(u32)0) {
			xhci_hc_died(xhci);
			retval = -ENODEV;
			break;
		}
		temp = xhci_port_state_to_neutral(temp);
		/* FIXME: What new port features do we need to support? */
		switch (wValue) {
		case USB_PORT_FEAT_SUSPEND:
			temp = readl(ports[wIndex]->addr);
			if ((temp & PORT_PLS_MASK) != XDEV_U0) {
				/* Resume the port to U0 first */
				xhci_set_link_state(xhci, ports[wIndex],
							XDEV_U0);
				spin_unlock_irqrestore(&xhci->lock, flags);
				msleep(10);
				spin_lock_irqsave(&xhci->lock, flags);
			}
			/* In spec software should not attempt to suspend
			 * a port unless the port reports that it is in the
			 * enabled (PED = ‘1’,PLS < ‘3’) state.
			 */
			temp = readl(ports[wIndex]->addr);
			if ((temp & PORT_PE) == 0 || (temp & PORT_RESET)
				|| (temp & PORT_PLS_MASK) >= XDEV_U3) {
				xhci_warn(xhci, "USB core suspending port %d-%d not in U0/U1/U2\n",
					  hcd->self.busnum, wIndex + 1);
				goto error;
			}

			slot_id = xhci_find_slot_id_by_port(hcd, xhci,
					wIndex + 1);
			if (!slot_id) {
				xhci_warn(xhci, "slot_id is zero\n");
				goto error;
			}
			/* unlock to execute stop endpoint commands */
			spin_unlock_irqrestore(&xhci->lock, flags);
			xhci_stop_device(xhci, slot_id, 1);
			spin_lock_irqsave(&xhci->lock, flags);

			xhci_set_link_state(xhci, ports[wIndex], XDEV_U3);

			spin_unlock_irqrestore(&xhci->lock, flags);
			msleep(10); /* wait device to enter */
			spin_lock_irqsave(&xhci->lock, flags);

			temp = readl(ports[wIndex]->addr);
			bus_state->suspended_ports |= 1 << wIndex;
			break;
		case USB_PORT_FEAT_LINK_STATE:
			temp = readl(ports[wIndex]->addr);
			/* Disable port */
			if (link_state == USB_SS_PORT_LS_SS_DISABLED) {
				xhci_dbg(xhci, "Disable port %d-%d\n",
					 hcd->self.busnum, wIndex + 1);
				temp = xhci_port_state_to_neutral(temp);
				/*
				 * Clear all change bits, so that we get a new
				 * connection event.
				 */
				temp |= PORT_CSC | PORT_PEC | PORT_WRC |
					PORT_OCC | PORT_RC | PORT_PLC |
					PORT_CEC;
				writel(temp | PORT_PE, ports[wIndex]->addr);
				temp = readl(ports[wIndex]->addr);
				break;
			}

			/* Put link in RxDetect (enable port) */
			if (link_state == USB_SS_PORT_LS_RX_DETECT) {
				xhci_dbg(xhci, "Enable port %d-%d\n",
					 hcd->self.busnum, wIndex + 1);
				xhci_set_link_state(xhci, ports[wIndex],
							link_state);
				temp = readl(ports[wIndex]->addr);
				break;
			}

			/*
			 * For xHCI 1.1 according to section 4.19.1.2.4.1 a
			 * root hub port's transition to compliance mode upon
			 * detecting LFPS timeout may be controlled by an
			 * Compliance Transition Enabled (CTE) flag (not
			 * software visible). This flag is set by writing 0xA
			 * to PORTSC PLS field which will allow transition to
			 * compliance mode the next time LFPS timeout is
			 * encountered. A warm reset will clear it.
			 *
			 * The CTE flag is only supported if the HCCPARAMS2 CTC
			 * flag is set, otherwise, the compliance substate is
			 * automatically entered as on 1.0 and prior.
			 */
			if (link_state == USB_SS_PORT_LS_COMP_MOD) {
				if (!HCC2_CTC(xhci->hcc_params2)) {
					xhci_dbg(xhci, "CTC flag is 0, port already supports entering compliance mode\n");
					break;
				}

				if ((temp & PORT_CONNECT)) {
					xhci_warn(xhci, "Can't set compliance mode when port is connected\n");
					goto error;
				}

				xhci_dbg(xhci, "Enable compliance mode transition for port %d-%d\n",
					 hcd->self.busnum, wIndex + 1);
				xhci_set_link_state(xhci, ports[wIndex],
						link_state);

				temp = readl(ports[wIndex]->addr);
				break;
			}
			/* Port must be enabled */
			if (!(temp & PORT_PE)) {
				retval = -ENODEV;
				break;
			}
			/* Can't set port link state above '3' (U3) */
			if (link_state > USB_SS_PORT_LS_U3) {
				xhci_warn(xhci, "Cannot set port %d-%d link state %d\n",
					  hcd->self.busnum, wIndex + 1,
					  link_state);
				goto error;
			}

			/*
			 * set link to U0, steps depend on current link state.
			 * U3: set link to U0 and wait for u3exit completion.
			 * U1/U2:  no PLC complete event, only set link to U0.
			 * Resume/Recovery: device initiated U0, only wait for
			 * completion
			 */
			if (link_state == USB_SS_PORT_LS_U0) {
				u32 pls = temp & PORT_PLS_MASK;
				bool wait_u0 = false;

				/* already in U0 */
				if (pls == XDEV_U0)
					break;
				if (pls == XDEV_U3 ||
				    pls == XDEV_RESUME ||
				    pls == XDEV_RECOVERY) {
					wait_u0 = true;
					reinit_completion(&bus_state->u3exit_done[wIndex]);
				}
				if (pls <= XDEV_U3) /* U1, U2, U3 */
					xhci_set_link_state(xhci, ports[wIndex],
							    USB_SS_PORT_LS_U0);
				if (!wait_u0) {
					if (pls > XDEV_U3)
						goto error;
					break;
				}
				spin_unlock_irqrestore(&xhci->lock, flags);
				if (!wait_for_completion_timeout(&bus_state->u3exit_done[wIndex],
								 msecs_to_jiffies(100)))
					xhci_dbg(xhci, "missing U0 port change event for port %d-%d\n",
						 hcd->self.busnum, wIndex + 1);
				spin_lock_irqsave(&xhci->lock, flags);
				temp = readl(ports[wIndex]->addr);
				break;
			}

			if (link_state == USB_SS_PORT_LS_U3) {
				int retries = 16;
				slot_id = xhci_find_slot_id_by_port(hcd, xhci,
						wIndex + 1);
				if (slot_id) {
					/* unlock to execute stop endpoint
					 * commands */
					spin_unlock_irqrestore(&xhci->lock,
								flags);
					xhci_stop_device(xhci, slot_id, 1);
					spin_lock_irqsave(&xhci->lock, flags);
				}
				xhci_set_link_state(xhci, ports[wIndex], USB_SS_PORT_LS_U3);
				spin_unlock_irqrestore(&xhci->lock, flags);
				while (retries--) {
					usleep_range(4000, 8000);
					temp = readl(ports[wIndex]->addr);
					if ((temp & PORT_PLS_MASK) == XDEV_U3)
						break;
				}
				spin_lock_irqsave(&xhci->lock, flags);
				temp = readl(ports[wIndex]->addr);
				bus_state->suspended_ports |= 1 << wIndex;
			}
			break;
		case USB_PORT_FEAT_POWER:
			/*
			 * Turn on ports, even if there isn't per-port switching.
			 * HC will report connect events even before this is set.
			 * However, hub_wq will ignore the roothub events until
			 * the roothub is registered.
			 */
			xhci_set_port_power(xhci, hcd, wIndex, true, &flags);
			break;
		case USB_PORT_FEAT_RESET:
			temp = (temp | PORT_RESET);
			writel(temp, ports[wIndex]->addr);

			temp = readl(ports[wIndex]->addr);
			xhci_dbg(xhci, "set port reset, actual port %d-%d status  = 0x%x\n",
				 hcd->self.busnum, wIndex + 1, temp);
			break;
		case USB_PORT_FEAT_REMOTE_WAKE_MASK:
			xhci_set_remote_wake_mask(xhci, ports[wIndex],
						  wake_mask);
			temp = readl(ports[wIndex]->addr);
			xhci_dbg(xhci, "set port remote wake mask, actual port %d-%d status  = 0x%x\n",
				 hcd->self.busnum, wIndex + 1, temp);
			break;
		case USB_PORT_FEAT_BH_PORT_RESET:
			temp |= PORT_WR;
			writel(temp, ports[wIndex]->addr);
			temp = readl(ports[wIndex]->addr);
			break;
		case USB_PORT_FEAT_U1_TIMEOUT:
			if (hcd->speed < HCD_USB3)
				goto error;
			temp = readl(ports[wIndex]->addr + PORTPMSC);
			temp &= ~PORT_U1_TIMEOUT_MASK;
			temp |= PORT_U1_TIMEOUT(timeout);
			writel(temp, ports[wIndex]->addr + PORTPMSC);
			break;
		case USB_PORT_FEAT_U2_TIMEOUT:
			if (hcd->speed < HCD_USB3)
				goto error;
			temp = readl(ports[wIndex]->addr + PORTPMSC);
			temp &= ~PORT_U2_TIMEOUT_MASK;
			temp |= PORT_U2_TIMEOUT(timeout);
			writel(temp, ports[wIndex]->addr + PORTPMSC);
			break;
		case USB_PORT_FEAT_TEST:
			/* 4.19.6 Port Test Modes (USB2 Test Mode) */
			if (hcd->speed != HCD_USB2)
				goto error;
			if (test_mode > USB_TEST_FORCE_ENABLE ||
			    test_mode < USB_TEST_J)
				goto error;
			retval = xhci_enter_test_mode(xhci, test_mode, wIndex,
						      &flags);
			break;
		default:
			goto error;
		}
		/* unblock any posted writes */
		temp = readl(ports[wIndex]->addr);
		break;
	case ClearPortFeature:
		if (!wIndex || wIndex > max_ports)
			goto error;
		wIndex--;
		temp = readl(ports[wIndex]->addr);
		if (temp == ~(u32)0) {
			xhci_hc_died(xhci);
			retval = -ENODEV;
			break;
		}
		/* FIXME: What new port features do we need to support? */
		temp = xhci_port_state_to_neutral(temp);
		switch (wValue) {
		case USB_PORT_FEAT_SUSPEND:
			temp = readl(ports[wIndex]->addr);
			xhci_dbg(xhci, "clear USB_PORT_FEAT_SUSPEND\n");
			xhci_dbg(xhci, "PORTSC %04x\n", temp);
			if (temp & PORT_RESET)
				goto error;
			if ((temp & PORT_PLS_MASK) == XDEV_U3) {
				if ((temp & PORT_PE) == 0)
					goto error;

				set_bit(wIndex, &bus_state->resuming_ports);
				usb_hcd_start_port_resume(&hcd->self, wIndex);
				xhci_set_link_state(xhci, ports[wIndex],
						    XDEV_RESUME);
				spin_unlock_irqrestore(&xhci->lock, flags);
				msleep(USB_RESUME_TIMEOUT);
				spin_lock_irqsave(&xhci->lock, flags);
				xhci_set_link_state(xhci, ports[wIndex],
							XDEV_U0);
				clear_bit(wIndex, &bus_state->resuming_ports);
				usb_hcd_end_port_resume(&hcd->self, wIndex);
			}
			bus_state->port_c_suspend |= 1 << wIndex;

			slot_id = xhci_find_slot_id_by_port(hcd, xhci,
					wIndex + 1);
			if (!slot_id) {
				xhci_dbg(xhci, "slot_id is zero\n");
				goto error;
			}
			xhci_ring_device(xhci, slot_id);
			break;
		case USB_PORT_FEAT_C_SUSPEND:
			bus_state->port_c_suspend &= ~(1 << wIndex);
			fallthrough;
		case USB_PORT_FEAT_C_RESET:
		case USB_PORT_FEAT_C_BH_PORT_RESET:
		case USB_PORT_FEAT_C_CONNECTION:
		case USB_PORT_FEAT_C_OVER_CURRENT:
		case USB_PORT_FEAT_C_ENABLE:
		case USB_PORT_FEAT_C_PORT_LINK_STATE:
		case USB_PORT_FEAT_C_PORT_CONFIG_ERROR:
			xhci_clear_port_change_bit(xhci, wValue, wIndex,
					ports[wIndex]->addr, temp);
			break;
		case USB_PORT_FEAT_ENABLE:
			xhci_disable_port(hcd, xhci, wIndex,
					ports[wIndex]->addr, temp);
			break;
		case USB_PORT_FEAT_POWER:
			xhci_set_port_power(xhci, hcd, wIndex, false, &flags);
			break;
		case USB_PORT_FEAT_TEST:
			retval = xhci_exit_test_mode(xhci);
			break;
		default:
			goto error;
		}
		break;
	default:
error:
		/* "stall" on error */
		retval = -EPIPE;
	}
	spin_unlock_irqrestore(&xhci->lock, flags);
	return retval;
}