Re: [PATCH 2/3] xhci: Wait until link state trainsits to U0 after setting USB_SS_PORT_LS_U0

From: Kai-Heng Feng
Date: Mon Jan 13 2020 - 04:18:51 EST


On Fri, Jan 10, 2020 at 11:27 PM Mathias Nyman
<mathias.nyman@xxxxxxxxxxxxxxx> wrote:
>
> On 3.1.2020 10.40, Kai-Heng Feng wrote:
> > Like U3 case, xHCI spec doesn't specify the upper bound of U0 transition
> > time. The 20ms is not enough for some devices.
> >
> > Intead of polling PLS or PLC, we can facilitate the port change event to
> > know that the link transits to U0 is completed.
> >
> > Signed-off-by: Kai-Heng Feng <kai.heng.feng@xxxxxxxxxxxxx>
> > ---
> > drivers/usb/host/xhci-hub.c | 8 +++++++-
> > drivers/usb/host/xhci-mem.c | 1 +
> > drivers/usb/host/xhci-ring.c | 1 +
> > drivers/usb/host/xhci.h | 1 +
> > 4 files changed, 10 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
> > index 2b2e9d004dbf..07886a1bce62 100644
> > --- a/drivers/usb/host/xhci-hub.c
> > +++ b/drivers/usb/host/xhci-hub.c
> > @@ -1310,11 +1310,17 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
> > spin_lock_irqsave(&xhci->lock, flags);
> > }
> > }
> > + if (link_state == USB_SS_PORT_LS_U0)
> > + reinit_completion(&ports[wIndex]->link_state_changed);
>
> All the other suspend and resume related port flags/completions are
> in struct xhci_bus_state. See for example rexit_done[].
> Not sure that is a better place but at least it would be consistent.
>
> Could actually make sense to move more of them to the xhci_port structure,
> but perhaps in some later suspend/resume rework patch.

Ok. Should I keep this part of the patch as is? Or move it to
xhci_bus_state and probably move it back to xhci_port in later rework
patch?

> >
> > xhci_set_link_state(xhci, ports[wIndex], link_state);
> >
> > spin_unlock_irqrestore(&xhci->lock, flags);
> > - msleep(20); /* wait device to enter */
> > + if (link_state == USB_SS_PORT_LS_U0) {
> > + if (!wait_for_completion_timeout(&ports[wIndex]->link_state_changed, msecs_to_jiffies(100)))
> > + xhci_dbg(xhci, "missing U0 port change event for port %d-%d\n", hcd->self.busnum, wIndex + 1);
>
> We might be waiting for completion here in unnecessary.
> No completion is called if port is already in U0, either set by
> xhci_bus_resume(), or we race with a device initiated resume.

Is there a way to know if device initiated resume is inplace?

>
> Maybe read the current port link state first, and don't do anything if it's
> already in U0, or fail if it's in a state where we can't resume to U0.

What happens if device initiated resume happens right after we query the PLS?

>
> > + } else
> > + msleep(20); /* wait device to enter */
> > spin_lock_irqsave(&xhci->lock, flags);
>
> Code might also be cleaner if we have separate if() statements for U0 and U3 link
> states, and skip the generic xhci_set_link_state()
>
> USB 3.2 specs only support PORT_LINK_STATE request feature selectors for
> U0, U1, U2, U3, SS.Disabled, Rx.Detect and Compliance mode.
> Out of these xhci driver already handles the SS.Disabled, Rx.detect and Compliance in
> separate if statements, and xHC hardware can't force a U1 or U2 state by writing
> the PLS field of the PORTSC register, so the xhci_set_link_state() here
> is only useful for U0 and U3.
>
> So maybe something like this:
>
> if (link_state == U0)
> if (active_link_state == U0)
> break
> else if (active_link_state != a proper link state)
> return error
> xhci_set_link_state(U0)
> wait_for_completion_timeout()
> break;
>
> if (link_state == U3)
> xhci_stop_device(slot_id)
> xhci_set_link_state(U3)
> for (max 10 tries) {
> msleep_range(~10ms)
> if (readl(PORTSC(PLS) == U3)
> break
> }
> break

Ok, will rework the next patch in this direction.

Kai-Heng

> >
> > if (link_state == USB_SS_PORT_LS_U3) {
> > diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
> > index 3b1388fa2f36..c760a28e3556 100644
> > --- a/drivers/usb/host/xhci-mem.c
> > +++ b/drivers/usb/host/xhci-mem.c
> > @@ -2268,6 +2268,7 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags)
> > xhci->hw_ports[i].addr = &xhci->op_regs->port_status_base +
> > NUM_PORT_REGS * i;
> > xhci->hw_ports[i].hw_portnum = i;
> > + init_completion(&xhci->hw_ports[i].link_state_changed);
> > }
> >
> > xhci->rh_bw = kcalloc_node(num_ports, sizeof(*xhci->rh_bw), flags,
> > diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
> > index d23f7408c81f..44d91a53bf07 100644
> > --- a/drivers/usb/host/xhci-ring.c
> > +++ b/drivers/usb/host/xhci-ring.c
> > @@ -1677,6 +1677,7 @@ static void handle_port_status(struct xhci_hcd *xhci,
> > (portsc & PORT_PLS_MASK) == XDEV_U1 ||
> > (portsc & PORT_PLS_MASK) == XDEV_U2)) {
> > xhci_dbg(xhci, "resume SS port %d finished\n", port_id);
> > + complete(&port->link_state_changed);
>
> Completion will only be called if there was a port link change (PLC bit set)
> and link is in U0/U1/U2. Completion will also be called for device
> initiated resume even when no one is waiting for it. (probably harmless)
>
> -Mathias