[PATCH v2 7/7] usb: dwc3: ep0: simplify EP0 state machine

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The DesignWare USB3 core tells us which phase
of a control transfer should be started, it
also tells us which physical endpoint needs
that transfer.

With these two informations, we have all we
need to simply EP0 handling quite a lot and
get rid rid of the SW state machine tracking
ep0 states.

For achieving this perfectly, we needed to
add support for situations where we get
XferNotReady while endpoint is still busy
and XferNotReady while gadget driver still
hasn't queued a request.

Signed-off-by: Felipe Balbi <balbi@xxxxxx>
---
 drivers/usb/dwc3/core.h   |   12 +-
 drivers/usb/dwc3/ep0.c    |  363 ++++++++++++++++++++-------------------------
 drivers/usb/dwc3/gadget.c |    3 +-
 3 files changed, 163 insertions(+), 215 deletions(-)

diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index e743969..01892b1 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -373,15 +373,9 @@ enum dwc3_phy {
 
 enum dwc3_ep0_state {
 	EP0_UNCONNECTED		= 0,
-	EP0_IDLE,
-	EP0_IN_DATA_PHASE,
-	EP0_OUT_DATA_PHASE,
-	EP0_IN_WAIT_GADGET,
-	EP0_OUT_WAIT_GADGET,
-	EP0_IN_WAIT_NRDY,
-	EP0_OUT_WAIT_NRDY,
-	EP0_IN_STATUS_PHASE,
-	EP0_OUT_STATUS_PHASE,
+	EP0_SETUP_PHASE,
+	EP0_DATA_PHASE,
+	EP0_STATUS_PHASE,
 	EP0_STALL,
 };
 
diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c
index f1e0a5e..82a40a1 100644
--- a/drivers/usb/dwc3/ep0.c
+++ b/drivers/usb/dwc3/ep0.c
@@ -62,24 +62,12 @@ static const char *dwc3_ep0_state_string(enum dwc3_ep0_state state)
 	switch (state) {
 	case EP0_UNCONNECTED:
 		return "Unconnected";
-	case EP0_IDLE:
-		return "Idle";
-	case EP0_IN_DATA_PHASE:
-		return "IN Data Phase";
-	case EP0_OUT_DATA_PHASE:
-		return "OUT Data Phase";
-	case EP0_IN_WAIT_GADGET:
-		return "IN Wait Gadget";
-	case EP0_OUT_WAIT_GADGET:
-		return "OUT Wait Gadget";
-	case EP0_IN_WAIT_NRDY:
-		return "IN Wait NRDY";
-	case EP0_OUT_WAIT_NRDY:
-		return "OUT Wait NRDY";
-	case EP0_IN_STATUS_PHASE:
-		return "IN Status Phase";
-	case EP0_OUT_STATUS_PHASE:
-		return "OUT Status Phase";
+	case EP0_SETUP_PHASE:
+		return "Setup Phase";
+	case EP0_DATA_PHASE:
+		return "Data Phase";
+	case EP0_STATUS_PHASE:
+		return "Status Phase";
 	case EP0_STALL:
 		return "Stall";
 	default:
@@ -88,7 +76,7 @@ static const char *dwc3_ep0_state_string(enum dwc3_ep0_state state)
 }
 
 static int dwc3_ep0_start_trans(struct dwc3 *dwc, u8 epnum, dma_addr_t buf_dma,
-		u32 len)
+		u32 len, u32 type)
 {
 	struct dwc3_gadget_ep_cmd_params params;
 	struct dwc3_trb_hw		*trb_hw;
@@ -98,52 +86,15 @@ static int dwc3_ep0_start_trans(struct dwc3 *dwc, u8 epnum, dma_addr_t buf_dma,
 	int				ret;
 
 	dep = dwc->eps[epnum];
+	if (dep->flags & DWC3_EP_BUSY) {
+		dev_vdbg(dwc->dev, "%s: still busy\n", dep->name);
+		return 0;
+	}
 
 	trb_hw = dwc->ep0_trb;
 	memset(&trb, 0, sizeof(trb));
 
-	switch (dwc->ep0state) {
-	case EP0_IDLE:
-		trb.trbctl = DWC3_TRBCTL_CONTROL_SETUP;
-		break;
-
-	case EP0_IN_WAIT_NRDY:
-	case EP0_OUT_WAIT_NRDY:
-	case EP0_IN_STATUS_PHASE:
-	case EP0_OUT_STATUS_PHASE:
-		if (dwc->three_stage_setup)
-			trb.trbctl = DWC3_TRBCTL_CONTROL_STATUS3;
-		else
-			trb.trbctl = DWC3_TRBCTL_CONTROL_STATUS2;
-
-		if (dwc->ep0state == EP0_IN_WAIT_NRDY)
-			dwc->ep0state = EP0_IN_STATUS_PHASE;
-		else if (dwc->ep0state == EP0_OUT_WAIT_NRDY)
-			dwc->ep0state = EP0_OUT_STATUS_PHASE;
-		break;
-
-	case EP0_IN_WAIT_GADGET:
-		dwc->ep0state = EP0_IN_WAIT_NRDY;
-		return 0;
-		break;
-
-	case EP0_OUT_WAIT_GADGET:
-		dwc->ep0state = EP0_OUT_WAIT_NRDY;
-		return 0;
-
-		break;
-
-	case EP0_IN_DATA_PHASE:
-	case EP0_OUT_DATA_PHASE:
-		trb.trbctl = DWC3_TRBCTL_CONTROL_DATA;
-		break;
-
-	default:
-		dev_err(dwc->dev, "%s() can't in state %d\n", __func__,
-				dwc->ep0state);
-		return -EINVAL;
-	}
-
+	trb.trbctl = type;
 	trb.bplh = buf_dma;
 	trb.length = len;
 
@@ -167,6 +118,7 @@ static int dwc3_ep0_start_trans(struct dwc3 *dwc, u8 epnum, dma_addr_t buf_dma,
 		return ret;
 	}
 
+	dep->flags |= DWC3_EP_BUSY;
 	dep->res_trans_idx = dwc3_gadget_ep_get_transfer_index(dwc,
 			dep->number);
 
@@ -176,41 +128,46 @@ static int dwc3_ep0_start_trans(struct dwc3 *dwc, u8 epnum, dma_addr_t buf_dma,
 static int __dwc3_gadget_ep0_queue(struct dwc3_ep *dep,
 		struct dwc3_request *req)
 {
-	struct dwc3		*dwc = dep->dwc;
-	int			ret;
+	int			ret = 0;
 
 	req->request.actual	= 0;
 	req->request.status	= -EINPROGRESS;
-	req->direction		= dep->direction;
 	req->epnum		= dep->number;
 
 	list_add_tail(&req->list, &dep->request_list);
-	if (req->request.length == 0) {
-		ret = dwc3_ep0_start_trans(dwc, dep->number,
-				dwc->ctrl_req_addr, 0);
-	} else if ((req->request.length % dep->endpoint.maxpacket)
-			&& (dep->number == 0)) {
-		dwc->ep0_bounced = true;
-
-		WARN_ON(req->request.length > dep->endpoint.maxpacket);
 
-		/*
-		 * REVISIT in case request length is bigger than EP0
-		 * wMaxPacketSize, we will need two chained TRBs to handle
-		 * the transfer.
-		 */
-		ret = dwc3_ep0_start_trans(dwc, dep->number,
-				dwc->ep0_bounce_addr, dep->endpoint.maxpacket);
-	} else {
-		dwc3_map_buffer_to_dma(req);
-
-		ret = dwc3_ep0_start_trans(dwc, dep->number,
-				req->request.dma, req->request.length);
-	}
+	/*
+	 * Gadget driver might not be quick enough to queue a request
+	 * before we get a Transfer Not Ready event on this endpoint.
+	 *
+	 * In that case, we will set DWC3_EP_PENDING_REQUEST. When that
+	 * flag is set, it's telling us that as soon as Gadget queues the
+	 * required request, we should kick the transfer here because the
+	 * IRQ we were waiting for is long gone.
+	 */
+	if (dep->flags & DWC3_EP_PENDING_REQUEST) {
+		struct dwc3	*dwc = dep->dwc;
+		unsigned	direction;
+		u32		type;
+
+		direction = !!(dep->flags & DWC3_EP0_DIR_IN);
+
+		if (dwc->ep0state == EP0_STATUS_PHASE) {
+			type = dwc->three_stage_setup
+				? DWC3_TRBCTL_CONTROL_STATUS3
+				: DWC3_TRBCTL_CONTROL_STATUS2;
+		} else if (dwc->ep0state == EP0_DATA_PHASE) {
+			type = DWC3_TRBCTL_CONTROL_DATA;
+		} else {
+			/* should never happen */
+			WARN_ON(1);
+			return 0;
+		}
 
-	if (ret < 0) {
-		list_del(&req->list);
-		dwc3_unmap_buffer_from_dma(req);
+		ret = dwc3_ep0_start_trans(dwc, direction,
+				req->request.dma, req->request.length, type);
+		dep->flags &= ~(DWC3_EP_PENDING_REQUEST |
+				DWC3_EP0_DIR_IN);
 	}
 
 	return ret;
@@ -227,24 +184,6 @@ int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request,
 
 	int				ret;
 
-	switch (dwc->ep0state) {
-	case EP0_IN_DATA_PHASE:
-	case EP0_IN_WAIT_GADGET:
-	case EP0_IN_WAIT_NRDY:
-	case EP0_IN_STATUS_PHASE:
-		dep = dwc->eps[1];
-		break;
-
-	case EP0_OUT_DATA_PHASE:
-	case EP0_OUT_WAIT_GADGET:
-	case EP0_OUT_WAIT_NRDY:
-	case EP0_OUT_STATUS_PHASE:
-		dep = dwc->eps[0];
-		break;
-	default:
-		return -EINVAL;
-	}
-
 	spin_lock_irqsave(&dwc->lock, flags);
 	if (!dep->desc) {
 		dev_dbg(dwc->dev, "trying to queue request %p to disabled %s\n",
@@ -278,50 +217,19 @@ static void dwc3_ep0_stall_and_restart(struct dwc3 *dwc)
 	/* stall is always issued on EP0 */
 	__dwc3_gadget_ep_set_halt(dwc->eps[0], 1);
 	dwc->eps[0]->flags &= ~DWC3_EP_STALL;
-	dwc->ep0state = EP0_IDLE;
+	dwc->ep0state = EP0_SETUP_PHASE;
 	dwc3_ep0_out_start(dwc);
 }
 
 void dwc3_ep0_out_start(struct dwc3 *dwc)
 {
-	struct dwc3_ep			*dep;
 	int				ret;
 
-	dep = dwc->eps[0];
-
-	ret = dwc3_ep0_start_trans(dwc, 0, dwc->ctrl_req_addr, 8);
+	ret = dwc3_ep0_start_trans(dwc, 0, dwc->ctrl_req_addr, 8,
+			DWC3_TRBCTL_CONTROL_SETUP);
 	WARN_ON(ret < 0);
 }
 
-/*
- * Send a zero length packet for the status phase of the control transfer
- */
-static void dwc3_ep0_do_setup_status(struct dwc3 *dwc,
-		const struct dwc3_event_depevt *event)
-{
-	struct dwc3_ep			*dep;
-	int				ret;
-	u32				epnum;
-
-	epnum = event->endpoint_number;
-	dep = dwc->eps[epnum];
-
-	if (epnum)
-		dwc->ep0state = EP0_IN_STATUS_PHASE;
-	else
-		dwc->ep0state = EP0_OUT_STATUS_PHASE;
-
-	/*
-	 * Not sure Why I need a buffer for a zero transfer. Maybe the
-	 * HW reacts strange on a NULL pointer
-	 */
-	ret = dwc3_ep0_start_trans(dwc, epnum, dwc->ctrl_req_addr, 0);
-	if (ret) {
-		dev_dbg(dwc->dev, "failed to start transfer, stalling\n");
-		dwc3_ep0_stall_and_restart(dwc);
-	}
-}
-
 static struct dwc3_ep *dwc3_wIndex_to_dep(struct dwc3 *dwc, __le16 wIndex_le)
 {
 	struct dwc3_ep		*dep;
@@ -341,15 +249,9 @@ static struct dwc3_ep *dwc3_wIndex_to_dep(struct dwc3 *dwc, __le16 wIndex_le)
 
 static void dwc3_ep0_send_status_response(struct dwc3 *dwc)
 {
-	u32 epnum;
-
-	if (dwc->ep0state == EP0_IN_DATA_PHASE)
-		epnum = 1;
-	else
-		epnum = 0;
-
-	dwc3_ep0_start_trans(dwc, epnum, dwc->ctrl_req_addr,
-			dwc->ep0_usb_req.length);
+	dwc3_ep0_start_trans(dwc, 1, dwc->ctrl_req_addr,
+			dwc->ep0_usb_req.length,
+			DWC3_TRBCTL_CONTROL_DATA);
 	dwc->ep0_status_pending = 1;
 }
 
@@ -505,8 +407,6 @@ static int dwc3_ep0_handle_feature(struct dwc3 *dwc,
 		return -EINVAL;
 	};
 
-	dwc->ep0state = EP0_IN_WAIT_NRDY;
-
 	return 0;
 }
 
@@ -541,7 +441,7 @@ static int dwc3_ep0_set_address(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl)
 		ret = -EINVAL;
 		break;
 	}
-	dwc->ep0state = EP0_IN_WAIT_NRDY;
+
 	return ret;
 }
 
@@ -628,16 +528,10 @@ static void dwc3_ep0_inspect_setup(struct dwc3 *dwc,
 		goto err;
 
 	len = le16_to_cpu(ctrl->wLength);
-	if (!len) {
-		dwc->ep0state = EP0_IN_WAIT_GADGET;
+	if (!len)
 		dwc->three_stage_setup = 0;
-	} else {
+	else
 		dwc->three_stage_setup = 1;
-		if (ctrl->bRequestType & USB_DIR_IN)
-			dwc->ep0state = EP0_IN_DATA_PHASE;
-		else
-			dwc->ep0state = EP0_OUT_DATA_PHASE;
-	}
 
 	if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD)
 		ret = dwc3_ep0_std_request(dwc, ctrl);
@@ -665,7 +559,7 @@ static void dwc3_ep0_complete_data(struct dwc3 *dwc,
 	dep = dwc->eps[epnum];
 
 	if (!dwc->ep0_status_pending) {
-		r = next_request(&dep->request_list);
+		r = next_request(&dwc->eps[0]->request_list);
 		ur = &r->request;
 	} else {
 		ur = &dwc->ep0_usb_req;
@@ -677,7 +571,8 @@ static void dwc3_ep0_complete_data(struct dwc3 *dwc,
 	if (dwc->ep0_bounced) {
 		struct dwc3_ep	*ep0 = dwc->eps[0];
 
-		transferred = min(ur->length, dep->endpoint.maxpacket - trb.length);
+		transferred = min_t(u32, ur->length,
+				ep0->endpoint.maxpacket - trb.length);
 		memcpy(ur->buf, dwc->ep0_bounce, transferred);
 		dwc->ep0_bounced = false;
 	} else {
@@ -695,12 +590,6 @@ static void dwc3_ep0_complete_data(struct dwc3 *dwc,
 		 * handle the case where we have to send a zero packet. This
 		 * seems to be case when req.length > maxpacket. Could it be?
 		 */
-		/* The transfer is complete, wait for HOST */
-		if (epnum & 1)
-			dwc->ep0state = EP0_IN_WAIT_NRDY;
-		else
-			dwc->ep0state = EP0_OUT_WAIT_NRDY;
-
 		if (r)
 			dwc3_gadget_giveback(dep, r, 0);
 	}
@@ -711,10 +600,8 @@ static void dwc3_ep0_complete_req(struct dwc3 *dwc,
 {
 	struct dwc3_request	*r;
 	struct dwc3_ep		*dep;
-	u8			epnum;
 
-	epnum = event->endpoint_number;
-	dep = dwc->eps[epnum];
+	dep = dwc->eps[0];
 
 	if (!list_empty(&dep->request_list)) {
 		r = next_request(&dep->request_list);
@@ -722,62 +609,130 @@ static void dwc3_ep0_complete_req(struct dwc3 *dwc,
 		dwc3_gadget_giveback(dep, r, 0);
 	}
 
-	dwc->ep0state = EP0_IDLE;
+	dwc->ep0state = EP0_SETUP_PHASE;
 	dwc3_ep0_out_start(dwc);
 }
 
 static void dwc3_ep0_xfer_complete(struct dwc3 *dwc,
 			const struct dwc3_event_depevt *event)
 {
+	struct dwc3_ep		*dep = dwc->eps[event->endpoint_number];
+
+	dep->flags &= ~DWC3_EP_BUSY;
+
 	switch (dwc->ep0state) {
-	case EP0_IDLE:
+	case EP0_SETUP_PHASE:
+		dev_vdbg(dwc->dev, "Inspecting Setup Bytes\n");
 		dwc3_ep0_inspect_setup(dwc, event);
 		break;
 
-	case EP0_IN_DATA_PHASE:
-	case EP0_OUT_DATA_PHASE:
+	case EP0_DATA_PHASE:
+		dev_vdbg(dwc->dev, "Data Phase\n");
 		dwc3_ep0_complete_data(dwc, event);
 		break;
 
-	case EP0_IN_STATUS_PHASE:
-	case EP0_OUT_STATUS_PHASE:
+	case EP0_STATUS_PHASE:
+		dev_vdbg(dwc->dev, "Status Phase\n");
 		dwc3_ep0_complete_req(dwc, event);
 		break;
+	default:
+		WARN(true, "UNKNOWN ep0state %d\n", dwc->ep0state);
+	}
+}
 
-	case EP0_IN_WAIT_NRDY:
-	case EP0_OUT_WAIT_NRDY:
-	case EP0_IN_WAIT_GADGET:
-	case EP0_OUT_WAIT_GADGET:
-	case EP0_UNCONNECTED:
-	case EP0_STALL:
-		break;
+static void dwc3_ep0_do_control_setup(struct dwc3 *dwc,
+		const struct dwc3_event_depevt *event)
+{
+	dwc->ep0state = EP0_SETUP_PHASE;
+	dwc3_ep0_out_start(dwc);
+}
+
+static void dwc3_ep0_do_control_data(struct dwc3 *dwc,
+		const struct dwc3_event_depevt *event)
+{
+	struct dwc3_ep		*dep;
+	struct dwc3_request	*req;
+	int			ret;
+
+	dep = dwc->eps[0];
+	dwc->ep0state = EP0_DATA_PHASE;
+
+	if (list_empty(&dep->request_list)) {
+		dev_vdbg(dwc->dev, "pending request for EP0 Data phase\n");
+		dep->flags |= DWC3_EP_PENDING_REQUEST;
+
+		if (event->endpoint_number)
+			dep->flags |= DWC3_EP0_DIR_IN;
+		return;
 	}
+
+	req = next_request(&dep->request_list);
+	req->direction = !!event->endpoint_number;
+
+	dwc->ep0state = EP0_DATA_PHASE;
+	if (req->request.length == 0) {
+		ret = dwc3_ep0_start_trans(dwc, event->endpoint_number,
+				dwc->ctrl_req_addr, 0,
+				DWC3_TRBCTL_CONTROL_DATA);
+	} else if ((req->request.length % dep->endpoint.maxpacket)
+			&& (event->endpoint_number == 0)) {
+		dwc3_map_buffer_to_dma(req);
+
+		WARN_ON(req->request.length > dep->endpoint.maxpacket);
+
+		dwc->ep0_bounced = true;
+
+		/*
+		 * REVISIT in case request length is bigger than EP0
+		 * wMaxPacketSize, we will need two chained TRBs to handle
+		 * the transfer.
+		 */
+		ret = dwc3_ep0_start_trans(dwc, event->endpoint_number,
+				dwc->ep0_bounce_addr, dep->endpoint.maxpacket,
+				DWC3_TRBCTL_CONTROL_DATA);
+	} else {
+		dwc3_map_buffer_to_dma(req);
+
+		ret = dwc3_ep0_start_trans(dwc, event->endpoint_number,
+				req->request.dma, req->request.length,
+				DWC3_TRBCTL_CONTROL_DATA);
+	}
+
+	WARN_ON(ret < 0);
 }
 
-static void dwc3_ep0_xfernotready(struct dwc3 *dwc,
+static void dwc3_ep0_do_control_status(struct dwc3 *dwc,
 		const struct dwc3_event_depevt *event)
 {
-	switch (dwc->ep0state) {
-	case EP0_IN_WAIT_GADGET:
-		dwc->ep0state = EP0_IN_WAIT_NRDY;
-		break;
-	case EP0_OUT_WAIT_GADGET:
-		dwc->ep0state = EP0_OUT_WAIT_NRDY;
-		break;
+	u32			type;
+	int			ret;
 
-	case EP0_IN_WAIT_NRDY:
-	case EP0_OUT_WAIT_NRDY:
-		dwc3_ep0_do_setup_status(dwc, event);
-		break;
+	dwc->ep0state = EP0_STATUS_PHASE;
 
-	case EP0_IDLE:
-	case EP0_IN_STATUS_PHASE:
-	case EP0_OUT_STATUS_PHASE:
-	case EP0_IN_DATA_PHASE:
-	case EP0_OUT_DATA_PHASE:
-	case EP0_UNCONNECTED:
-	case EP0_STALL:
-		break;
+	type = dwc->three_stage_setup ? DWC3_TRBCTL_CONTROL_STATUS3
+		: DWC3_TRBCTL_CONTROL_STATUS2;
+
+	ret = dwc3_ep0_start_trans(dwc, event->endpoint_number,
+			dwc->ctrl_req_addr, 0, type);
+
+	WARN_ON(ret < 0);
+}
+
+static void dwc3_ep0_xfernotready(struct dwc3 *dwc,
+		const struct dwc3_event_depevt *event)
+{
+	switch (event->status) {
+	case DEPEVT_STATUS_CONTROL_SETUP:
+		dev_vdbg(dwc->dev, "Control Setup\n");
+		dwc3_ep0_do_control_setup(dwc, event);
+		break;
+	case DEPEVT_STATUS_CONTROL_DATA:
+		dev_vdbg(dwc->dev, "Control Data\n");
+		dwc3_ep0_do_control_data(dwc, event);
+		break;
+	case DEPEVT_STATUS_CONTROL_STATUS:
+		dev_vdbg(dwc->dev, "Control Status\n");
+		dwc3_ep0_do_control_status(dwc, event);
 	}
 }
 
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 569473b..b4f1aef 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -1178,7 +1178,7 @@ static int dwc3_gadget_start(struct usb_gadget *g,
 	}
 
 	/* begin to receive SETUP packets */
-	dwc->ep0state = EP0_IDLE;
+	dwc->ep0state = EP0_SETUP_PHASE;
 	dwc3_ep0_out_start(dwc);
 
 	spin_unlock_irqrestore(&dwc->lock, flags);
@@ -1728,7 +1728,6 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc)
 
 	memset(&params, 0x00, sizeof(params));
 
-	dwc->ep0state = EP0_IDLE;
 	reg = dwc3_readl(dwc->regs, DWC3_DSTS);
 	speed = reg & DWC3_DSTS_CONNECTSPD;
 	dwc->speed = speed;
-- 
1.7.6.396.ge0613

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux