Skip to content

Commit

Permalink
usb: chipidea: udc: don't do hardware access if gadget has stopped
Browse files Browse the repository at this point in the history
After _gadget_stop_activity is executed, we can consider the hardware
operation for gadget has finished, and the udc can be stopped and enter
low power mode. So, any later hardware operations (from usb_ep_ops APIs
or usb_gadget_ops APIs) should be considered invalid, any deinitializatons
has been covered at _gadget_stop_activity.

I meet this problem when I plug out usb cable from PC using mass_storage
gadget, my callstack like: vbus interrupt->.vbus_session->
composite_disconnect ->pm_runtime_put_sync(&_gadget->dev),
the composite_disconnect will call fsg_disable, but fsg_disable calls
usb_ep_disable using async way, there are register accesses for
usb_ep_disable. So sometimes, I get system hang due to visit register
without clock, sometimes not.

The Linux Kernel USB maintainer Alan Stern suggests this kinds of solution.
See: http://marc.info/?l=linux-usb&m=138541769810983&w=2.

Cc: <[email protected]> #v4.9+
Signed-off-by: Peter Chen <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
Signed-off-by: Greg Kroah-Hartman <[email protected]>
  • Loading branch information
Peter Chen authored and gregkh committed Aug 21, 2019
1 parent de7b9aa commit cbe85c8
Showing 1 changed file with 24 additions and 8 deletions.
32 changes: 24 additions & 8 deletions drivers/usb/chipidea/udc.c
Original file line number Diff line number Diff line change
Expand Up @@ -709,12 +709,6 @@ static int _gadget_stop_activity(struct usb_gadget *gadget)
struct ci_hdrc *ci = container_of(gadget, struct ci_hdrc, gadget);
unsigned long flags;

spin_lock_irqsave(&ci->lock, flags);
ci->gadget.speed = USB_SPEED_UNKNOWN;
ci->remote_wakeup = 0;
ci->suspended = 0;
spin_unlock_irqrestore(&ci->lock, flags);

/* flush all endpoints */
gadget_for_each_ep(ep, gadget) {
usb_ep_fifo_flush(ep);
Expand All @@ -732,6 +726,12 @@ static int _gadget_stop_activity(struct usb_gadget *gadget)
ci->status = NULL;
}

spin_lock_irqsave(&ci->lock, flags);
ci->gadget.speed = USB_SPEED_UNKNOWN;
ci->remote_wakeup = 0;
ci->suspended = 0;
spin_unlock_irqrestore(&ci->lock, flags);

return 0;
}

Expand Down Expand Up @@ -1303,6 +1303,10 @@ static int ep_disable(struct usb_ep *ep)
return -EBUSY;

spin_lock_irqsave(hwep->lock, flags);
if (hwep->ci->gadget.speed == USB_SPEED_UNKNOWN) {
spin_unlock_irqrestore(hwep->lock, flags);
return 0;
}

/* only internal SW should disable ctrl endpts */

Expand Down Expand Up @@ -1392,6 +1396,10 @@ static int ep_queue(struct usb_ep *ep, struct usb_request *req,
return -EINVAL;

spin_lock_irqsave(hwep->lock, flags);
if (hwep->ci->gadget.speed == USB_SPEED_UNKNOWN) {
spin_unlock_irqrestore(hwep->lock, flags);
return 0;
}
retval = _ep_queue(ep, req, gfp_flags);
spin_unlock_irqrestore(hwep->lock, flags);
return retval;
Expand All @@ -1415,8 +1423,8 @@ static int ep_dequeue(struct usb_ep *ep, struct usb_request *req)
return -EINVAL;

spin_lock_irqsave(hwep->lock, flags);

hw_ep_flush(hwep->ci, hwep->num, hwep->dir);
if (hwep->ci->gadget.speed != USB_SPEED_UNKNOWN)
hw_ep_flush(hwep->ci, hwep->num, hwep->dir);

list_for_each_entry_safe(node, tmpnode, &hwreq->tds, td) {
dma_pool_free(hwep->td_pool, node->ptr, node->dma);
Expand Down Expand Up @@ -1487,6 +1495,10 @@ static void ep_fifo_flush(struct usb_ep *ep)
}

spin_lock_irqsave(hwep->lock, flags);
if (hwep->ci->gadget.speed == USB_SPEED_UNKNOWN) {
spin_unlock_irqrestore(hwep->lock, flags);
return;
}

hw_ep_flush(hwep->ci, hwep->num, hwep->dir);

Expand Down Expand Up @@ -1559,6 +1571,10 @@ static int ci_udc_wakeup(struct usb_gadget *_gadget)
int ret = 0;

spin_lock_irqsave(&ci->lock, flags);
if (ci->gadget.speed == USB_SPEED_UNKNOWN) {
spin_unlock_irqrestore(&ci->lock, flags);
return 0;
}
if (!ci->remote_wakeup) {
ret = -EOPNOTSUPP;
goto out;
Expand Down

0 comments on commit cbe85c8

Please sign in to comment.