usb: dwc_otg_310: gadget: stop current transfer on dequeue
authorWu Liang feng <wulf@rock-chips.com>
Wed, 2 Sep 2015 02:07:56 +0000 (10:07 +0800)
committerWu Liang feng <wulf@rock-chips.com>
Sun, 6 Sep 2015 07:53:33 +0000 (15:53 +0800)
If the request being dequeued is already started, disable endpoint
to stop the transfer and then call dwc_otg_request_done().
Endpoint will be re-enabled on next call to dwc_otg_ep_start_transfer().

TEST=use RK3128/RK3288/RK3368 board, adb root and then adb reboot,
check if usb_req->buf is used after free.

Change-Id: I8c9304f29c578f679ad0841350920f1350bd32df
Signed-off-by: Wu Liang feng <wulf@rock-chips.com>
drivers/usb/dwc_otg_310/dwc_otg_pcd.c

index a82bd0f89e347cd6400ce8299d9b358ce3337380..00f1ea90c289cb47feba288d51a95dac533467fb 100755 (executable)
@@ -1712,6 +1712,127 @@ out_unlocked:
 
 }
 
+static int dwc_otg_wait_bit_set(volatile uint32_t *reg,
+                               uint32_t bit, uint32_t timeout)
+{
+       int i;
+
+       for (i = 0; i < timeout; i++) {
+               if (DWC_READ_REG32(reg) & bit)
+                       return 0;
+
+               dwc_udelay(1);
+       }
+
+       return -ETIMEDOUT;
+}
+
+static void dwc_otg_pcd_ep_stop_transfer(dwc_otg_core_if_t
+                                        *core_if, dwc_ep_t *ep)
+{
+       depctl_data_t depctl = {.d32 = 0 };
+
+       /* Read DEPCTLn register */
+       if (ep->is_in == 1)
+               depctl.d32 = DWC_READ_REG32(&core_if->dev_if->
+                                            in_ep_regs[ep->num]->
+                                            diepctl);
+       else
+               depctl.d32 = DWC_READ_REG32(&core_if->dev_if->
+                                           out_ep_regs[ep->num]->
+                                           doepctl);
+
+       if (ep->is_in == 1) {
+               diepint_data_t diepint = {.d32 = 0 };
+
+               depctl.b.snak = 1;
+               DWC_WRITE_REG32(&core_if->dev_if->
+                               in_ep_regs[ep->num]->diepctl,
+                               depctl.d32);
+
+               diepint.b.inepnakeff = 1;
+               /* Wait for Nak effect */
+               if (dwc_otg_wait_bit_set(&core_if->dev_if->
+                                        in_ep_regs[ep->num]
+                                        ->diepint,
+                                        diepint.d32,
+                                        100)) {
+                       DWC_WARN("%s: timeout diepctl.snak\n",
+                                __func__);
+               } else {
+                       DWC_WRITE_REG32(&core_if->dev_if->
+                                       in_ep_regs[ep->num]->
+                                       diepint, diepint.d32);
+               }
+
+               depctl.d32 = 0;
+               depctl.b.epdis = 1;
+               DWC_WRITE_REG32(&core_if->dev_if->
+                               in_ep_regs[ep->num]->diepctl,
+                               depctl.d32);
+
+               diepint.d32 = 0;
+               diepint.b.epdisabled = 1;
+               if (dwc_otg_wait_bit_set(&core_if->dev_if->
+                                        in_ep_regs[ep->num]
+                                        ->diepint,
+                                        diepint.d32,
+                                        100)) {
+                       DWC_WARN("%s: timeout diepctl.epdis\n",
+                                __func__);
+               } else {
+                       DWC_WRITE_REG32(&core_if->dev_if->
+                                       in_ep_regs[ep->num]->
+                                       diepint, diepint.d32);
+               }
+       } else {
+               dctl_data_t dctl = {.d32 = 0 };
+               gintmsk_data_t gintsts = {.d32 = 0 };
+               doepint_data_t doepint = {.d32 = 0 };
+
+               dctl.b.sgoutnak = 1;
+               DWC_MODIFY_REG32(&core_if->dev_if->
+                                dev_global_regs->dctl, 0, dctl.d32);
+
+               /* Wait for global nak to take effect */
+               gintsts.d32 = 0;
+               gintsts.b.goutnakeff = 1;
+               if (dwc_otg_wait_bit_set(&core_if->core_global_regs->
+                                        gintsts, gintsts.d32,
+                                        100)) {
+                       DWC_WARN("%s: timeout dctl.sgoutnak\n",
+                                __func__);
+               } else {
+                       DWC_WRITE_REG32(&core_if->core_global_regs
+                                       ->gintsts, gintsts.d32);
+               }
+
+               depctl.d32 = 0;
+               depctl.b.epdis = 1;
+               depctl.b.snak = 1;
+               DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[ep->num]->
+                               doepctl, depctl.d32);
+
+               doepint.b.epdisabled = 1;
+               if (dwc_otg_wait_bit_set(&core_if->dev_if
+                                        ->out_ep_regs[ep->num]
+                                        ->doepint, doepint.d32,
+                                        100)) {
+                       DWC_WARN("%s: timeout doepctl.epdis\n",
+                                __func__);
+               } else {
+                       DWC_WRITE_REG32(&core_if->dev_if->
+                                       out_ep_regs[ep->num]->
+                                       doepint, doepint.d32);
+               }
+
+               dctl.d32 = 0;
+               dctl.b.cgoutnak = 1;
+               DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->
+                                dctl, 0, dctl.d32);
+       }
+}
+
 /******************************************************************************/
 #ifdef DWC_UTE_PER_IO
 
@@ -2369,6 +2490,18 @@ int dwc_otg_pcd_ep_dequeue(dwc_otg_pcd_t *pcd, void *ep_handle,
        }
 
        if (!DWC_CIRCLEQ_EMPTY_ENTRY(req, queue_entry)) {
+               dwc_otg_pcd_ep_stop_transfer(GET_CORE_IF(pcd),
+                                            &ep->dwc_ep);
+               /* Flush the Tx FIFO */
+               if (ep->dwc_ep.is_in) {
+                       dwc_otg_flush_tx_fifo(GET_CORE_IF(pcd),
+                                             ep->dwc_ep.tx_fifo_num);
+                       release_perio_tx_fifo(GET_CORE_IF(pcd),
+                                             ep->dwc_ep.tx_fifo_num);
+                       release_tx_fifo(GET_CORE_IF(pcd),
+                                       ep->dwc_ep.tx_fifo_num);
+               }
+
                dwc_otg_request_done(ep, req, -DWC_E_RESTART);
        } else {
                req = NULL;