usb: dwc3: gadget: Handle stream transfers
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>
This commit is contained in:
parent
aefe3d232b
commit
140ca4cfea
|
@ -495,6 +495,7 @@
|
|||
#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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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, ¶ms);
|
||||
}
|
||||
|
||||
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, ¶ms);
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue