Hi Sarah, It is the modified one. And smoke tested, I add a parameter to set SP bit, and a variable "last_ep" to trigger complete call. - Crane --- From: Crane Cai <crane.cai@xxxxxxx> drivers/usb/host/xhci-hcd.c | 2 +- drivers/usb/host/xhci-hub.c | 145 +++++++++++++++++++++++++++++++++++++++++- drivers/usb/host/xhci-mem.c | 1 + drivers/usb/host/xhci-ring.c | 12 +++- drivers/usb/host/xhci.h | 17 ++++- 5 files changed, 170 insertions(+), 7 deletions(-) diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c index 4cb69e0..da69521 100644 --- a/drivers/usb/host/xhci-hcd.c +++ b/drivers/usb/host/xhci-hcd.c @@ -839,7 +839,7 @@ int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) ep->stop_cmd_timer.expires = jiffies + XHCI_STOP_EP_CMD_TIMEOUT * HZ; add_timer(&ep->stop_cmd_timer); - xhci_queue_stop_endpoint(xhci, urb->dev->slot_id, ep_index); + xhci_queue_stop_endpoint(xhci, urb->dev->slot_id, ep_index, 0); xhci_ring_cmd_db(xhci); } done: diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 208b805..fce04e9 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -128,6 +128,69 @@ static u32 xhci_port_state_to_neutral(u32 state) /* Save read-only status and port state */ return (state & XHCI_PORT_RO) | (state & XHCI_PORT_RWS); } +/* + * stop endpoints before the port suspend, and ring the bell before resume. + * caller need xhci lock and may release it to finish commands. + */ +static u32 xhci_stop_endpoints(struct xhci_hcd *xhci, u16 port, int stop, + unsigned long *flags) +{ + int i, slot_id; + struct xhci_container_ctx *out_ctx; + struct xhci_slot_ctx *slot_ctx; + struct xhci_ep_ctx *ep_ctx; + struct xhci_virt_ep *ep; + + slot_id = 0; + if (!xhci->devs) + return 0; + for (i = 0; i < MAX_HC_SLOTS; i++) { + if (!xhci->devs[i]) + continue; + out_ctx = xhci->devs[i]->out_ctx; + slot_ctx = xhci_get_slot_ctx(xhci, out_ctx); + if (GET_SLOT_STATE(slot_ctx->dev_state) == 3 && + ROOT_HUB_PORT(slot_ctx->dev_info2) == port) { + slot_id = i; + break; + } + } + + if (!slot_id) + return 0; + + if (stop) { + for (i = 0; i < 31; i++) { + ep_ctx = xhci_get_ep_ctx(xhci, out_ctx, i); + if ((ep_ctx->ep_info & EP_STATE_MASK) + != EP_STATE_RUNNING) + continue; + ep = &xhci->devs[slot_id]->eps[i]; + if (!(ep->ep_state & EP_HALT_PENDING)) { + ep->ep_state |= EP_HALT_PENDING; + ep->stop_cmds_pending++; + ep->stop_cmd_timer.expires = jiffies + + XHCI_STOP_EP_CMD_TIMEOUT * HZ; + add_timer(&ep->stop_cmd_timer); + xhci_queue_stop_endpoint(xhci, slot_id, i, 1); + xhci->devs[slot_id]->last_ep = i; + } + } + xhci_ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, *flags); + wait_for_completion(&xhci->devs[slot_id]->cmd_completion); + spin_lock_irqsave(&xhci->lock, *flags); + xhci->devs[slot_id]->last_ep = -1; + } else { + for (i = 0; i < 31; i++) { + ep_ctx = xhci_get_ep_ctx(xhci, out_ctx, i); + if ((ep_ctx->ep_info & EP_STATE_MASK) + == EP_STATE_STOPPED) + ring_ep_doorbell(xhci, slot_id, i); + } + } + return 0; +} static void xhci_disable_port(struct xhci_hcd *xhci, u16 wIndex, u32 __iomem *addr, u32 port_status) @@ -162,6 +225,10 @@ static void xhci_clear_port_change_bit(struct xhci_hcd *xhci, u16 wValue, status = PORT_PEC; port_change_bit = "enable/disable"; break; + case USB_PORT_FEAT_C_SUSPEND: + status = PORT_PLC; + port_change_bit = "suspend/resume"; + break; default: /* Should never happen */ return; @@ -211,9 +278,21 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, if ((temp & PORT_OCC)) status |= 1 << USB_PORT_FEAT_C_OVER_CURRENT; /* - * FIXME ignoring suspend, reset, and USB 2.1/3.0 specific + * FIXME ignoring reset and USB 2.1/3.0 specific * changes */ + /* XDEV_U3 is Device Suspended, XDEV_RESUME is in resuming and + * the device may not directly transit in to (Running) state. + * So Report SUSPEND for simple. + */ + if ((temp & PORT_PLS_MASK) == XDEV_U3 || + (temp & PORT_PLS_MASK) == XDEV_RESUME) + status |= 1 << USB_PORT_FEAT_SUSPEND; + if ((temp & PORT_PLS_MASK) == XDEV_U0 + && test_bit(wIndex, &xhci->suspended_ports)) { + clear_bit(wIndex, &xhci->suspended_ports); + set_bit(wIndex, &xhci->port_c_suspend); + } if (temp & PORT_CONNECT) { status |= 1 << USB_PORT_FEAT_CONNECTION; status |= xhci_port_speed(temp); @@ -226,6 +305,8 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, status |= 1 << USB_PORT_FEAT_RESET; if (temp & PORT_POWER) status |= 1 << USB_PORT_FEAT_POWER; + if (test_bit(wIndex, &xhci->port_c_suspend)) + status |= 1 << USB_PORT_FEAT_C_SUSPEND; xhci_dbg(xhci, "Get port status returned 0x%x\n", status); put_unaligned(cpu_to_le32(status), (__le32 *) buf); break; @@ -238,6 +319,31 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, temp = xhci_readl(xhci, addr); temp = xhci_port_state_to_neutral(temp); switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + temp = xhci_readl(xhci, addr); + /* spec 4.15.1 Software should not attempt to suspend + * a port unless the port reports that it is in the + * enabled (PED = ‘1’,PLS < ‘3’) state. + */ + if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) + || (temp & PORT_PLS_MASK) >= XDEV_U3) { + xhci_dbg(xhci, "goto error status = 0x%x\n", + temp); + goto error; + } + xhci_stop_endpoints(xhci, wIndex + 1, 1, &flags); + temp = xhci_port_state_to_neutral(temp); + temp &= ~PORT_PLS_MASK; + temp |= PORT_LINK_STROBE | XDEV_U3; + xhci_writel(xhci, temp, addr); + + spin_unlock_irqrestore(&xhci->lock, flags); + msleep(10); /* wait device to enter */ + spin_lock_irqsave(&xhci->lock, flags); + + temp = xhci_readl(xhci, addr); + set_bit(wIndex, &xhci->suspended_ports); + break; case USB_PORT_FEAT_POWER: /* * Turn on ports, even if there isn't per-port switching. @@ -271,6 +377,43 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, temp = xhci_readl(xhci, addr); temp = xhci_port_state_to_neutral(temp); switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + temp = xhci_readl(xhci, 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 & XDEV_U3) { + if ((temp & PORT_PE) == 0) + goto error; + if (DEV_SUPERSPEED(temp)) { + temp = xhci_port_state_to_neutral(temp); + temp &= ~PORT_PLS_MASK; + temp |= PORT_LINK_STROBE | XDEV_U0; + xhci_writel(xhci, temp, addr); + } else { + temp = xhci_port_state_to_neutral(temp); + temp &= ~PORT_PLS_MASK; + temp |= PORT_LINK_STROBE | XDEV_RESUME; + xhci_writel(xhci, temp, addr); + + spin_unlock_irqrestore(&xhci->lock, + flags); + msleep(20); + spin_lock_irqsave(&xhci->lock, flags); + + temp = xhci_readl(xhci, addr); + temp = xhci_port_state_to_neutral(temp); + temp &= ~PORT_PLS_MASK; + temp |= PORT_LINK_STROBE | XDEV_U0; + xhci_writel(xhci, temp, addr); + } + set_bit(wIndex, &xhci->port_c_suspend); + } + xhci_stop_endpoints(xhci, wIndex + 1, 0, &flags); + break; + case USB_PORT_FEAT_C_SUSPEND: + clear_bit(wIndex, &xhci->port_c_suspend); case USB_PORT_FEAT_C_RESET: case USB_PORT_FEAT_C_CONNECTION: case USB_PORT_FEAT_C_OVER_CURRENT: diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 8045bc6..a48dee3 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -400,6 +400,7 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, init_completion(&dev->cmd_completion); INIT_LIST_HEAD(&dev->cmd_list); + dev->last_ep = -1; /* Point to output device context in dcbaa. */ xhci->dcbaa->dev_context_ptrs[slot_id] = dev->out_ctx->dma; diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 6ba841b..558c0e6 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1,4 +1,5 @@ /* +int xhci_queue_stop_endpoint(struct xhci_hcd *xhci, int slot_id, * xHCI host controller driver * * Copyright (C) 2008 Intel Corp. @@ -292,7 +293,7 @@ void xhci_ring_cmd_db(struct xhci_hcd *xhci) xhci_readl(xhci, &xhci->dba->doorbell[0]); } -static void ring_ep_doorbell(struct xhci_hcd *xhci, +void ring_ep_doorbell(struct xhci_hcd *xhci, unsigned int slot_id, unsigned int ep_index) { @@ -524,6 +525,7 @@ static void handle_stopped_endpoint(struct xhci_hcd *xhci, struct list_head *entry; struct xhci_td *cur_td = 0; struct xhci_td *last_unlinked_td; + int last_ep; struct xhci_dequeue_state deq_state; @@ -532,6 +534,7 @@ static void handle_stopped_endpoint(struct xhci_hcd *xhci, ep_index = TRB_TO_EP_INDEX(trb->generic.field[3]); ep = &xhci->devs[slot_id]->eps[ep_index]; ep_ring = ep->ring; + last_ep = xhci->devs[slot_id]->last_ep; if (list_empty(&ep->cancelled_td_list)) { xhci_stop_watchdog_timer_in_irq(xhci, ep); @@ -602,6 +605,8 @@ static void handle_stopped_endpoint(struct xhci_hcd *xhci, return; } while (cur_td != last_unlinked_td); + if (last_ep == ep_index) + complete(&xhci->devs[slot_id]->cmd_completion); /* Return to the event handler with xhci->lock re-acquired */ } @@ -2229,14 +2234,15 @@ int xhci_queue_evaluate_context(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, } int xhci_queue_stop_endpoint(struct xhci_hcd *xhci, int slot_id, - unsigned int ep_index) + unsigned int ep_index, int sp) { u32 trb_slot_id = SLOT_ID_FOR_TRB(slot_id); u32 trb_ep_index = EP_ID_FOR_TRB(ep_index); u32 type = TRB_TYPE(TRB_STOP_RING); + u32 trb_sp = SP_FOR_TRB(sp); return queue_command(xhci, 0, 0, 0, - trb_slot_id | trb_ep_index | type, false); + trb_slot_id | trb_ep_index | type | trb_sp, false); } /* Set Transfer Ring Dequeue Pointer command. diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index e5eb09b..b104177 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -269,6 +269,10 @@ struct xhci_op_regs { * A read gives the current link PM state of the port, * a write with Link State Write Strobe set sets the link state. */ +#define PORT_PLS_MASK (0xf << 5) +#define XDEV_U0 (0x0 << 5) +#define XDEV_U3 (0x3 << 5) +#define XDEV_RESUME (0xf << 5) /* true: port has power (see HCC_PPC) */ #define PORT_POWER (1 << 9) /* bits 10:13 indicate device speed: @@ -686,6 +690,10 @@ struct xhci_virt_device { /* Status of the last command issued for this device */ u32 cmd_status; struct list_head cmd_list; + /* Inditcator of the last stop end point. Let low level function + * to judgement in what situation complete will be called. + */ + int last_ep; }; @@ -823,7 +831,7 @@ struct xhci_event_cmd { /* Stop Endpoint TRB - ep_index to endpoint ID for this TRB */ #define TRB_TO_EP_INDEX(p) ((((p) & (0x1f << 16)) >> 16) - 1) #define EP_ID_FOR_TRB(p) ((((p) + 1) & 0x1f) << 16) - +#define SP_FOR_TRB(p) (((p) & 1) << 23) /* Port Status Change Event TRB fields */ /* Port ID - bits 31:24 */ @@ -1112,6 +1120,9 @@ struct xhci_hcd { unsigned int quirks; #define XHCI_LINK_TRB_QUIRK (1 << 0) #define XHCI_RESET_EP_QUIRK (1 << 1) + unsigned long port_c_suspend; /* port suspend change*/ + unsigned long suspended_ports; /* which ports are + suspended */ }; /* For testing purposes */ @@ -1288,7 +1299,7 @@ int xhci_queue_slot_control(struct xhci_hcd *xhci, u32 trb_type, u32 slot_id); int xhci_queue_address_device(struct xhci_hcd *xhci, dma_addr_t in_ctx_ptr, u32 slot_id); int xhci_queue_stop_endpoint(struct xhci_hcd *xhci, int slot_id, - unsigned int ep_index); + unsigned int ep_index, int sp); int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, int slot_id, unsigned int ep_index); int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, @@ -1314,6 +1325,8 @@ void xhci_queue_config_ep_quirk(struct xhci_hcd *xhci, unsigned int slot_id, unsigned int ep_index, struct xhci_dequeue_state *deq_state); void xhci_stop_endpoint_command_watchdog(unsigned long arg); +void ring_ep_doorbell(struct xhci_hcd *xhci, unsigned int slot_id, + unsigned int ep_index); /* xHCI roothub code */ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html