[PATCH 7/9] pxa27x_udc: single-thread setup requests

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

 



From: Vernon Sauder <vsauder@xxxxxxxxxx>

Since the PXA 27x UDC automatically ACK's some control packets such as
SET_INTERFACE, the gadgets may not get a chance to process the request
before another control packet is received. The Linux gadgets do not expect
to receive setup callbacks out of order. The file storage gadget only
saves the "highest" priority request.

The PXA27x UDC driver must make sure it only sends one up at a time, allowing
the gadget to make changes before continuing. In theory, the host would be
NACK'd while the gadget processes the change but the UDC has already ACK'd the
request. If another request is sent by the host that is not automatically
ACK'd by the UDC, then the throttling happens properly to regain sync.

The observed case was the file_storage gadget timing out on a BulkReset
request because the SET_INTERFACE was being processed by the gadget. Since
SET_INTERFACE is higher priority than BulkReset, the BulkReset was dropped.
This was exacerbated by turning on the debug which delayed the fsg signal
processing thread.

This also fixes the "should never get in WAIT_ACK_SET_CONF_INTERF state
here!!!" warning.

Signed-off-by: Vernon Sauder <vsauder@xxxxxxxxxx>
---
 drivers/usb/gadget/pxa27x_udc.c |  183 +++++++++++++++++++++++++++------------
 drivers/usb/gadget/pxa27x_udc.h |    9 ++
 2 files changed, 138 insertions(+), 54 deletions(-)

diff --git a/drivers/usb/gadget/pxa27x_udc.c b/drivers/usb/gadget/pxa27x_udc.c
index b177eae..9aaa6dd 100644
--- a/drivers/usb/gadget/pxa27x_udc.c
+++ b/drivers/usb/gadget/pxa27x_udc.c
@@ -90,6 +90,7 @@ static struct pxa_udc *the_controller;
 
 static void handle_ep(struct pxa_ep *ep);
 static int pxa_ep_disable(struct usb_ep *_ep);
+static void udc_setup_announce_work(struct work_struct *work);
 
 /*
  * Debug filesystem
@@ -579,6 +580,18 @@ static void ep0_idle(struct pxa_udc *dev)
 }
 
 /**
+ * ep0_stall - Put control endpoint into stall state
+ * @dev: udc device
+ */
+static void ep0_stall(struct pxa_udc *dev)
+{
+	struct pxa_ep *ep = &dev->pxa_ep[0];
+	ep_dbg(ep, "protocol STALL, udccsr0=%03x\n", udc_ep_readl(ep, UDCCSR));
+	udc_ep_writel(ep, UDCCSR, UDCCSR0_FST | UDCCSR0_FTF);
+	set_ep0state(dev, STALL);
+}
+
+/**
  * inc_ep_stats_reqs - Update ep stats counts
  * @ep: physical endpoint
  * @req: usb request
@@ -1101,9 +1114,7 @@ static int write_ep0_fifo(struct pxa_ep *ep, struct pxa27x_request *req)
  * @_req: usb request
  * @gfp_flags: flags
  *
- * Context: normally called when !in_interrupt, but callable when in_interrupt()
- * in the special case of ep0 setup :
- *   (irq->handle_ep0_ctrl_req->gadget_setup->pxa_ep_queue)
+ * Context: !in_interrupt
  *
  * Returns 0 if succedeed, error otherwise
  */
@@ -1183,6 +1194,7 @@ static int pxa_ep_queue(struct usb_ep *_ep, struct usb_request *_req,
 				rc = -EL2HLT;
 			}
 			ep0_idle(ep->dev);
+			complete(&dev->conf_intf_compl);
 			break;
 		case IN_DATA_STAGE:
 			if (!ep_is_full(ep))
@@ -1550,6 +1562,11 @@ static __init void udc_init_data(struct pxa_udc *dev)
 	int i;
 	struct pxa_ep *ep;
 
+	/* control request queue init */
+	spin_lock_init(&dev->ctrlreq_lock);
+	init_completion(&dev->conf_intf_compl);
+	INIT_WORK(&dev->ctrlreq_work, udc_setup_announce_work);
+
 	/* device/ep0 records init */
 	INIT_LIST_HEAD(&dev->gadget.ep_list);
 	INIT_LIST_HEAD(&dev->gadget.ep0->ep_list);
@@ -1724,10 +1741,8 @@ EXPORT_SYMBOL(usb_gadget_unregister_driver);
 /**
  * handle_ep0_ctrl_req - handle control endpoint control request
  * @udc: udc device
- * @req: control request
  */
-static void handle_ep0_ctrl_req(struct pxa_udc *udc,
-				struct pxa27x_request *req)
+static void handle_ep0_ctrl_req(struct pxa_udc *udc)
 {
 	struct pxa_ep *ep = &udc->pxa_ep[0];
 	union {
@@ -1736,8 +1751,7 @@ static void handle_ep0_ctrl_req(struct pxa_udc *udc,
 	} u;
 	int i;
 	int have_extrabytes = 0;
-
-	nuke(ep, -EPROTO);
+	struct usb_ctrlrequest	*req;
 
 	/* read SETUP packet */
 	for (i = 0; i < 2; i++) {
@@ -1752,32 +1766,92 @@ static void handle_ep0_ctrl_req(struct pxa_udc *udc,
 		ep_err(ep, "wrong to have extra bytes for setup : 0x%08x\n", i);
 	}
 
-	ep_dbg(ep, "SETUP %02x.%02x v%04x i%04x l%04x\n",
-		u.r.bRequestType, u.r.bRequest,
-		le16_to_cpu(u.r.wValue), le16_to_cpu(u.r.wIndex),
-		le16_to_cpu(u.r.wLength));
 	if (unlikely(have_extrabytes))
 		goto stall;
 
-	if (u.r.bRequestType & USB_DIR_IN)
-		set_ep0state(udc, IN_DATA_STAGE);
-	else
-		set_ep0state(udc, OUT_DATA_STAGE);
+	req = &udc->ctrlreq[udc->ctrlreq_put++];
+	udc->ctrlreq_put %= NR_USB_CTRL_REQ;
+	*req = u.r;
+	schedule_work(&udc->ctrlreq_work);
 
-	/* Tell UDC to enter Data Stage */
-	udc_ep_writel(ep, UDCCSR, UDCCSR0_SA | UDCCSR0_OPC);
-
-	i = udc->driver->setup(&udc->gadget, &u.r);
-	if (i < 0)
-		goto stall;
-out:
 	return;
+
 stall:
-	ep_dbg(ep, "protocol STALL, udccsr0=%03x err %d\n",
-		udc_ep_readl(ep, UDCCSR), i);
-	udc_ep_writel(ep, UDCCSR, UDCCSR0_FST | UDCCSR0_FTF);
-	set_ep0state(udc, STALL);
-	goto out;
+	ep0_stall(udc);
+}
+
+/**
+ * udc_setup_announce_work - handle dispatching control request to gadget
+ * @work: work struct embedded in udc
+ */
+static void udc_setup_announce_work(struct work_struct *work)
+{
+	int i;
+	static int real_req = 0;
+	bool set_intf_conf;
+	struct pxa_udc *udc = container_of(work, struct pxa_udc, ctrlreq_work);
+	struct pxa_ep *ep = &udc->pxa_ep[0];
+	unsigned long flags;
+
+	/* run through the circular list, dispatching setup control items */
+	while (udc->ctrlreq_get != udc->ctrlreq_put) {
+		/* get next item */
+		spin_lock_irqsave(&udc->ctrlreq_lock, flags);
+		struct usb_ctrlrequest *req = &udc->ctrlreq[udc->ctrlreq_get];
+		udc->ctrlreq_get++;
+		udc->ctrlreq_get %= NR_USB_CTRL_REQ;
+		spin_unlock_irqrestore(&udc->ctrlreq_lock, flags);
+
+		ep_dbg(ep, "SETUP %02x.%02x v%04x i%04x l%04x\n",
+				req->bRequestType, req->bRequest,
+				le16_to_cpu(req->wValue), le16_to_cpu(req->wIndex),
+				le16_to_cpu(req->wLength));
+
+		/* if request is SET_CONF or SET_INTF, we will need to wait */
+		set_intf_conf = req->bRequest == USB_REQ_SET_CONFIGURATION ||
+				req->bRequest == USB_REQ_SET_INTERFACE;
+		/* count the number of non-intf/conf items as "real" */
+		if (!set_intf_conf)
+			real_req++;
+		/*
+		 * if we are waiting for an ACK from gadget and there are few
+		 * real requests waiting, then pause here
+		 */
+		if (udc->ep0state == WAIT_ACK_SET_CONF_INTERF && real_req < 2) {
+			wait_for_completion_timeout(&udc->conf_intf_compl,
+					msecs_to_jiffies(100));
+		}
+		/* kill any outstanding urbs */
+		nuke(ep, -EPROTO);
+		/* dispatch */
+		if (set_intf_conf) {
+			if (req->bRequest == USB_REQ_SET_CONFIGURATION) {
+				udc->config = req->wValue;
+				udc->last_interface = 0;
+				udc->last_alternate = 0;
+			}
+			else {
+				udc->last_interface = req->wIndex;
+				udc->last_alternate = req->wValue;
+			}
+			set_ep0state(udc, WAIT_ACK_SET_CONF_INTERF);
+		}
+		else {
+			if (req->bRequestType & USB_DIR_IN)
+				set_ep0state(udc, IN_DATA_STAGE);
+			else
+				set_ep0state(udc, OUT_DATA_STAGE);
+			/* Tell UDC to enter Data Stage */
+			udc_ep_writel(ep, UDCCSR, UDCCSR0_SA | UDCCSR0_OPC);
+		}
+
+		/* send to gadget */
+		i = udc->driver->setup(&udc->gadget, req);
+		if (!set_intf_conf && i < 0) {
+			ep_dbg(ep, "err %d\n", i);
+			ep0_stall(udc);
+		}
+	}
 }
 
 /**
@@ -1865,7 +1939,7 @@ static void handle_ep0(struct pxa_udc *udc, int fifo_irq, int opc_irq)
 	case SETUP_STAGE:
 		udccsr0 &= UDCCSR0_CTRL_REQ_MASK;
 		if (likely(udccsr0 == UDCCSR0_CTRL_REQ_MASK))
-			handle_ep0_ctrl_req(udc, req);
+			handle_ep0_ctrl_req(udc);
 		break;
 	case IN_DATA_STAGE:			/* GET_DESCRIPTOR */
 		if (epout_has_pkt(ep))
@@ -1961,22 +2035,18 @@ static void handle_ep(struct pxa_ep *ep)
  */
 static void pxa27x_change_configuration(struct pxa_udc *udc, int config)
 {
-	struct usb_ctrlrequest req ;
+	struct usb_ctrlrequest *req;
 
-	dev_dbg(udc->dev, "config=%d\n", config);
+	req = &udc->ctrlreq[udc->ctrlreq_put++];
+	udc->ctrlreq_put %= NR_USB_CTRL_REQ;
 
-	udc->config = config;
-	udc->last_interface = 0;
-	udc->last_alternate = 0;
+	req->bRequestType = 0;
+	req->bRequest = USB_REQ_SET_CONFIGURATION;
+	req->wValue = config;
+	req->wIndex = 0;
+	req->wLength = 0;
 
-	req.bRequestType = 0;
-	req.bRequest = USB_REQ_SET_CONFIGURATION;
-	req.wValue = config;
-	req.wIndex = 0;
-	req.wLength = 0;
-
-	set_ep0state(udc, WAIT_ACK_SET_CONF_INTERF);
-	udc->driver->setup(&udc->gadget, &req);
+	schedule_work(&udc->ctrlreq_work);
 }
 
 /**
@@ -1990,21 +2060,18 @@ static void pxa27x_change_configuration(struct pxa_udc *udc, int config)
  */
 static void pxa27x_change_interface(struct pxa_udc *udc, int iface, int alt)
 {
-	struct usb_ctrlrequest  req;
-
-	dev_dbg(udc->dev, "interface=%d, alternate setting=%d\n", iface, alt);
+	struct usb_ctrlrequest *req;
 
-	udc->last_interface = iface;
-	udc->last_alternate = alt;
+	req = &udc->ctrlreq[udc->ctrlreq_put++];
+	udc->ctrlreq_put %= NR_USB_CTRL_REQ;
 
-	req.bRequestType = USB_RECIP_INTERFACE;
-	req.bRequest = USB_REQ_SET_INTERFACE;
-	req.wValue = alt;
-	req.wIndex = iface;
-	req.wLength = 0;
+	req->bRequestType = USB_RECIP_INTERFACE;
+	req->bRequest = USB_REQ_SET_INTERFACE;
+	req->wValue = alt;
+	req->wIndex = iface;
+	req->wLength = 0;
 
-	set_ep0state(udc, WAIT_ACK_SET_CONF_INTERF);
-	udc->driver->setup(&udc->gadget, &req);
+	schedule_work(&udc->ctrlreq_work);
 }
 
 /*
@@ -2284,10 +2351,15 @@ static int __exit pxa_udc_remove(struct platform_device *_dev)
 {
 	struct pxa_udc *udc = platform_get_drvdata(_dev);
 
+	complete_all(&udc->conf_intf_compl);
+
 	usb_gadget_unregister_driver(udc->driver);
 	free_irq(udc->irq, udc);
 	pxa_cleanup_debugfs(udc);
 
+	/* cancel work */
+	cancel_work_sync(&udc->ctrlreq_work);
+
 	platform_set_drvdata(_dev, NULL);
 	the_controller = NULL;
 	clk_put(udc->clk);
@@ -2328,6 +2400,9 @@ static int pxa_udc_suspend(struct platform_device *_dev, pm_message_t state)
 				ep->udccsr_value, ep->udccr_value);
 	}
 
+	/* finish work before sleeping */
+	flush_work(&udc->ctrlreq_work);
+
 	udc_disable(udc);
 
 	return 0;
diff --git a/drivers/usb/gadget/pxa27x_udc.h b/drivers/usb/gadget/pxa27x_udc.h
index 1d1b793..ecec303 100644
--- a/drivers/usb/gadget/pxa27x_udc.h
+++ b/drivers/usb/gadget/pxa27x_udc.h
@@ -25,6 +25,7 @@
 
 #include <linux/types.h>
 #include <linux/spinlock.h>
+#include <linux/completion.h>
 #include <linux/io.h>
 
 /*
@@ -411,6 +412,7 @@ struct udc_stats {
 
 #define NR_USB_ENDPOINTS (1 + 5)	/* ep0 + ep1in-bulk + .. + ep3in-iso */
 #define NR_PXA_ENDPOINTS (1 + 14)	/* ep0 + epA + epB + .. + epX */
+#define NR_USB_CTRL_REQ  (4)
 
 /**
  * struct pxa_udc - udc structure
@@ -454,6 +456,13 @@ struct pxa_udc {
 	unsigned				last_interface:3;
 	unsigned				last_alternate:3;
 
+	struct work_struct 		ctrlreq_work;
+	struct usb_ctrlrequest	ctrlreq[NR_USB_CTRL_REQ];
+	unsigned 				ctrlreq_get;
+	unsigned 				ctrlreq_put;
+	spinlock_t				ctrlreq_lock;		/* Protects the request queue */
+	struct completion		conf_intf_compl;
+
 #ifdef CONFIG_PM
 	unsigned				udccsr0;
 #endif
-- 
1.6.1

--
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