From 78ee3623adb113102330f17b2799675722dec7f0 Mon Sep 17 00:00:00 2001 From: Udipto Goswami Date: Fri, 1 Nov 2019 18:05:13 +0530 Subject: [PATCH 1/2] usb: gadget: Prevent race between composite_setup & USB disconnect While doing usb disconnect the ep0 is disabled, therefore when composite_setup tries to queue on ep0 endpoint, the operation result in race between the two functions leading slab poison error since the request is already freed. Fix this by disabling irqs, introducing flush for any pending works to be cleared and enabling the irqs back. Change-Id: I82f7a428929905762bae17dedbe717e0110155c5 Signed-off-by: Udipto Goswami --- drivers/usb/dwc3/gadget.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 401b9271a455..cb3f93ac1831 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2233,6 +2233,11 @@ static int dwc3_gadget_vbus_session(struct usb_gadget *_gadget, int is_active) is_active = !!is_active; dbg_event(0xFF, "VbusSess", is_active); + + disable_irq(dwc->irq); + + flush_work(&dwc->bh_work); + spin_lock_irqsave(&dwc->lock, flags); /* Mark that the vbus was powered */ @@ -2264,6 +2269,8 @@ static int dwc3_gadget_vbus_session(struct usb_gadget *_gadget, int is_active) } spin_unlock_irqrestore(&dwc->lock, flags); + + enable_irq(dwc->irq); return 0; } From 66a6e72914bcbe565ff4014781395edadeebd342 Mon Sep 17 00:00:00 2001 From: Hemant Kumar Date: Thu, 5 Sep 2019 12:14:54 -0700 Subject: [PATCH 2/2] usb: dwc3: Issue core soft reset upon controller halt failure In case of composition switch there is a possibility of run/stop bit gets cleared in the middle of a control transfer. If controller has cached a SETUP packet but not yet written it to ep0 TRB, controller halt fails even if there are no pending events in event ring. As part of pull up enable run/stop bit gets set and control transfer starts, due to that controller writes cached SETUP packet to ep0 TRB and triggers xfer completion. Since run/stop bit is set Host issues bus reset and end transfer and Halt are issued on ep0 because ep0 is not in SETUP phase. However this does not recover the controller's ep0 state machine. As a result controller is unable to handle SET ADDRESS SETUP packet issued by host. Before completing data stage of control transfer if vbus disconnects controller halt fails even after issuing end transfer cmd on ep0 IN. There is a possibility of race between controller accessing the ep0 buffer from cached TRB which was queued in data stage of control transfer and end xfer command completion. This is resulting into accessing of TRB buffer after controller halt failure by this time buffer is already unmapped by dwc3_remove_requests(). Fix above issues by issuing core soft reset if control halt fails to prevent controller from accessing cached TRB buffer. Also move issuing of end xfer command for ep0 IN before un-mapping the ep0 TRB buffer in case ep0 state is not in SETUP phase in dwc3_remove_requests(). Change-Id: Ie159e06a2537afcc9deee3f6670aac5d66b97b90 Signed-off-by: Hemant Kumar Signed-off-by: Elson Roy Serrao --- drivers/usb/dwc3/gadget.c | 84 +++++++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 21 deletions(-) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index cb3f93ac1831..1bedfb92fd0d 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -811,25 +811,6 @@ static void dwc3_remove_requests(struct dwc3 *dwc, struct dwc3_ep *dep) dwc3_stop_active_transfer(dep, true, false); - /* - giveback all requests to gadget driver */ - while (!list_empty(&dep->started_list)) { - req = next_request(&dep->started_list); - - dwc3_gadget_giveback(dep, req, -ESHUTDOWN); - } - - while (!list_empty(&dep->pending_list)) { - req = next_request(&dep->pending_list); - - dwc3_gadget_giveback(dep, req, -ESHUTDOWN); - } - - while (!list_empty(&dep->cancelled_list)) { - req = next_request(&dep->cancelled_list); - - dwc3_gadget_giveback(dep, req, -ESHUTDOWN); - } - if (dep->number == 1 && dwc->ep0state != EP0_SETUP_PHASE) { unsigned int dir; @@ -844,6 +825,25 @@ static void dwc3_remove_requests(struct dwc3 *dwc, struct dwc3_ep *dep) dwc->eps[1]->trb_enqueue = 0; } + /* - giveback all requests to gadget driver */ + while (!list_empty(&dep->started_list)) { + req = next_request(&dep->started_list); + if (req) + dwc3_gadget_giveback(dep, req, -ESHUTDOWN); + } + + while (!list_empty(&dep->pending_list)) { + req = next_request(&dep->pending_list); + if (req) + dwc3_gadget_giveback(dep, req, -ESHUTDOWN); + } + + while (!list_empty(&dep->cancelled_list)) { + req = next_request(&dep->cancelled_list); + if (req) + dwc3_gadget_giveback(dep, req, -ESHUTDOWN); + } + dbg_log_string("DONE for %s(%d)", dep->name, dep->number); } @@ -1980,6 +1980,38 @@ static void dwc3_stop_active_transfers(struct dwc3 *dwc) } } +/** + * dwc3_device_core_soft_reset - Issues device core soft reset + * @dwc: pointer to our context structure + */ +static int dwc3_device_core_soft_reset(struct dwc3 *dwc) +{ + u32 reg; + int retries = 10; + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg |= DWC3_DCTL_CSFTRST; + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + + do { + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + if (!(reg & DWC3_DCTL_CSFTRST)) + goto done; + + usleep_range(1000, 1100); + } while (--retries); + + dev_err(dwc->dev, "%s timedout\n", __func__); + + return -ETIMEDOUT; + +done: + /* phy sync delay as per data book */ + msleep(50); + + return 0; +} + static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend) { u32 reg, reg1; @@ -2142,6 +2174,10 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) ret = dwc3_gadget_run_stop(dwc, is_on, false); spin_unlock_irqrestore(&dwc->lock, flags); + if (!is_on && ret == -ETIMEDOUT) { + dev_err(dwc->dev, "%s: Core soft reset...\n", __func__); + dwc3_device_core_soft_reset(dwc); + } enable_irq(dwc->irq); pm_runtime_mark_last_busy(dwc->dev); @@ -2226,6 +2262,7 @@ static int dwc3_gadget_vbus_session(struct usb_gadget *_gadget, int is_active) { struct dwc3 *dwc = gadget_to_dwc(_gadget); unsigned long flags; + int ret = 0; if (dwc->dr_mode != USB_DR_MODE_OTG) return -EPERM; @@ -2253,9 +2290,9 @@ static int dwc3_gadget_vbus_session(struct usb_gadget *_gadget, int is_active) * Both vbus was activated by otg and pullup was * signaled by the gadget driver. */ - dwc3_gadget_run_stop(dwc, 1, false); + ret = dwc3_gadget_run_stop(dwc, 1, false); } else { - dwc3_gadget_run_stop(dwc, 0, false); + ret = dwc3_gadget_run_stop(dwc, 0, false); } } @@ -2269,8 +2306,13 @@ static int dwc3_gadget_vbus_session(struct usb_gadget *_gadget, int is_active) } spin_unlock_irqrestore(&dwc->lock, flags); + if (!is_active && ret == -ETIMEDOUT) { + dev_err(dwc->dev, "%s: Core soft reset...\n", __func__); + dwc3_device_core_soft_reset(dwc); + } enable_irq(dwc->irq); + return 0; }