OSDN Git Service

usb: dwc3: gadget: Handle stream transfers
authorThinh Nguyen <Thinh.Nguyen@synopsys.com>
Wed, 6 May 2020 02:47:09 +0000 (19:47 -0700)
committerFelipe Balbi <balbi@kernel.org>
Mon, 25 May 2020 08:09:42 +0000 (11:09 +0300)
Overview of stream transfer requirement:
 * A transfer will have a set of TRBs of the same stream ID.
 * A transfer is started with a stream ID in START_TRANSFER command.
 * A new stream will only start when the previous completes.

Overview of stream events:
 * A "prime" from host indicates that its endpoints are active
   (buffers prepared and ready to receive/transmit data). The controller
   automatically initiates stream if it sees this.
 * A "NoStream" rejection event indicates that the host isn't ready.
   Host will put the endpoint back to idle state. Device may need to
   reinitiate the stream to start transfer again.
 * A Stream Found event means host accepted device initiated stream.
   Nothing needs to be done from driver.

To initiate a stream, the driver will issue START_TRANSFER command with
a stream ID. To reinitiate the stream, the driver must issue
END_TRANSFER and restart the transfer with START_TRANSFER command with
the same stream ID.

This implementation handles device-initated streams (e.g. UASP driver).
It also handles some hosts' quirky behavior where they only prime each
endpoint once.

Signed-off-by: Thinh Nguyen <thinhn@synopsys.com>
Signed-off-by: Felipe Balbi <balbi@kernel.org>
drivers/usb/dwc3/core.h
drivers/usb/dwc3/debug.h
drivers/usb/dwc3/gadget.c

index b11183a..4def088 100644 (file)
 #define DWC3_DGCMD_SELECTED_FIFO_FLUSH 0x09
 #define DWC3_DGCMD_ALL_FIFO_FLUSH      0x0a
 #define DWC3_DGCMD_SET_ENDPOINT_NRDY   0x0c
+#define DWC3_DGCMD_SET_ENDPOINT_PRIME  0x0d
 #define DWC3_DGCMD_RUN_SOC_BUS_LOOPBACK        0x10
 
 #define DWC3_DGCMD_STATUS(n)           (((n) >> 12) & 0x0F)
@@ -702,6 +703,9 @@ struct dwc3_ep {
 #define DWC3_EP_PENDING_REQUEST        BIT(5)
 #define DWC3_EP_DELAY_START    BIT(6)
 #define DWC3_EP_WAIT_TRANSFER_COMPLETE BIT(7)
+#define DWC3_EP_IGNORE_NEXT_NOSTREAM   BIT(8)
+#define DWC3_EP_FORCE_RESTART_STREAM   BIT(9)
+#define DWC3_EP_FIRST_STREAM_PRIMED    BIT(10)
 
        /* This last one is specific to EP0 */
 #define DWC3_EP0_DIR_IN                BIT(31)
@@ -1301,6 +1305,10 @@ struct dwc3_event_depevt {
 #define DEPEVT_STREAMEVT_FOUND         1
 #define DEPEVT_STREAMEVT_NOTFOUND      2
 
+/* Stream event parameter */
+#define DEPEVT_STREAM_PRIME            0xfffe
+#define DEPEVT_STREAM_NOSTREAM         0x0
+
 /* Control-only Status */
 #define DEPEVT_STATUS_CONTROL_DATA     1
 #define DEPEVT_STATUS_CONTROL_STATUS   2
index 0f95656..d8f600e 100644 (file)
@@ -68,6 +68,8 @@ dwc3_gadget_generic_cmd_string(u8 cmd)
                return "All FIFO Flush";
        case DWC3_DGCMD_SET_ENDPOINT_NRDY:
                return "Set Endpoint NRDY";
+       case DWC3_DGCMD_SET_ENDPOINT_PRIME:
+               return "Set Endpoint Prime";
        case DWC3_DGCMD_RUN_SOC_BUS_LOOPBACK:
                return "Run SoC Bus Loopback Test";
        default:
index 07824b6..0380f76 100644 (file)
@@ -610,6 +610,9 @@ static int dwc3_gadget_set_ep_config(struct dwc3_ep *dep, unsigned int action)
        return dwc3_send_gadget_ep_cmd(dep, DWC3_DEPCMD_SETEPCONFIG, &params);
 }
 
+static void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force,
+               bool interrupt);
+
 /**
  * __dwc3_gadget_ep_enable - initializes a hw endpoint
  * @dep: endpoint to be initialized
@@ -670,7 +673,7 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, unsigned int action)
         * Issue StartTransfer here with no-op TRB so we can always rely on No
         * Response Update Transfer command.
         */
-       if ((usb_endpoint_xfer_bulk(desc) && !dep->stream_capable) ||
+       if (usb_endpoint_xfer_bulk(desc) ||
                        usb_endpoint_xfer_int(desc)) {
                struct dwc3_gadget_ep_cmd_params params;
                struct dwc3_trb *trb;
@@ -689,6 +692,29 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, unsigned int action)
                ret = dwc3_send_gadget_ep_cmd(dep, cmd, &params);
                if (ret < 0)
                        return ret;
+
+               if (dep->stream_capable) {
+                       /*
+                        * For streams, at start, there maybe a race where the
+                        * host primes the endpoint before the function driver
+                        * queues a request to initiate a stream. In that case,
+                        * the controller will not see the prime to generate the
+                        * ERDY and start stream. To workaround this, issue a
+                        * no-op TRB as normal, but end it immediately. As a
+                        * result, when the function driver queues the request,
+                        * the next START_TRANSFER command will cause the
+                        * controller to generate an ERDY to initiate the
+                        * stream.
+                        */
+                       dwc3_stop_active_transfer(dep, true, true);
+
+                       /*
+                        * All stream eps will reinitiate stream on NoStream
+                        * rejection until we can determine that the host can
+                        * prime after the first transfer.
+                        */
+                       dep->flags |= DWC3_EP_FORCE_RESTART_STREAM;
+               }
        }
 
 out:
@@ -697,8 +723,6 @@ out:
        return 0;
 }
 
-static void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force,
-               bool interrupt);
 static void dwc3_remove_requests(struct dwc3 *dwc, struct dwc3_ep *dep)
 {
        struct dwc3_request             *req;
@@ -2772,6 +2796,63 @@ static void dwc3_gadget_endpoint_transfer_not_ready(struct dwc3_ep *dep,
        (void) __dwc3_gadget_start_isoc(dep);
 }
 
+static void dwc3_gadget_endpoint_stream_event(struct dwc3_ep *dep,
+               const struct dwc3_event_depevt *event)
+{
+       struct dwc3 *dwc = dep->dwc;
+
+       if (event->status == DEPEVT_STREAMEVT_FOUND) {
+               dep->flags |= DWC3_EP_FIRST_STREAM_PRIMED;
+               goto out;
+       }
+
+       /* Note: NoStream rejection event param value is 0 and not 0xFFFF */
+       switch (event->parameters) {
+       case DEPEVT_STREAM_PRIME:
+               /*
+                * If the host can properly transition the endpoint state from
+                * idle to prime after a NoStream rejection, there's no need to
+                * force restarting the endpoint to reinitiate the stream. To
+                * simplify the check, assume the host follows the USB spec if
+                * it primed the endpoint more than once.
+                */
+               if (dep->flags & DWC3_EP_FORCE_RESTART_STREAM) {
+                       if (dep->flags & DWC3_EP_FIRST_STREAM_PRIMED)
+                               dep->flags &= ~DWC3_EP_FORCE_RESTART_STREAM;
+                       else
+                               dep->flags |= DWC3_EP_FIRST_STREAM_PRIMED;
+               }
+
+               break;
+       case DEPEVT_STREAM_NOSTREAM:
+               if ((dep->flags & DWC3_EP_IGNORE_NEXT_NOSTREAM) ||
+                   !(dep->flags & DWC3_EP_FORCE_RESTART_STREAM) ||
+                   !(dep->flags & DWC3_EP_WAIT_TRANSFER_COMPLETE))
+                       break;
+
+               /*
+                * If the host rejects a stream due to no active stream, by the
+                * USB and xHCI spec, the endpoint will be put back to idle
+                * state. When the host is ready (buffer added/updated), it will
+                * prime the endpoint to inform the usb device controller. This
+                * triggers the device controller to issue ERDY to restart the
+                * stream. However, some hosts don't follow this and keep the
+                * endpoint in the idle state. No prime will come despite host
+                * streams are updated, and the device controller will not be
+                * triggered to generate ERDY to move the next stream data. To
+                * workaround this and maintain compatibility with various
+                * hosts, force to reinitate the stream until the host is ready
+                * instead of waiting for the host to prime the endpoint.
+                */
+               dep->flags |= DWC3_EP_DELAY_START;
+               dwc3_stop_active_transfer(dep, true, true);
+               return;
+       }
+
+out:
+       dep->flags &= ~DWC3_EP_IGNORE_NEXT_NOSTREAM;
+}
+
 static void dwc3_endpoint_interrupt(struct dwc3 *dwc,
                const struct dwc3_event_depevt *event)
 {
@@ -2820,6 +2901,8 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc,
                dwc3_gadget_endpoint_transfer_complete(dep, event);
                break;
        case DWC3_DEPEVT_STREAMEVT:
+               dwc3_gadget_endpoint_stream_event(dep, event);
+               break;
        case DWC3_DEPEVT_RXTXFIFOEVT:
                break;
        }
@@ -2911,6 +2994,14 @@ static void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force,
        WARN_ON_ONCE(ret);
        dep->resource_index = 0;
 
+       /*
+        * The END_TRANSFER command will cause the controller to generate a
+        * NoStream Event, and it's not due to the host DP NoStream rejection.
+        * Ignore the next NoStream event.
+        */
+       if (dep->stream_capable)
+               dep->flags |= DWC3_EP_IGNORE_NEXT_NOSTREAM;
+
        if (!interrupt)
                dep->flags &= ~DWC3_EP_TRANSFER_STARTED;
        else