[PATCH] USB dummy_hcd: Centralize link state computations

This patch adds to the dummy_hcd driver a new routine for keeping track of
all changes in the state of the emulated USB link.  The logic is now kept
in one spot instead of spread around, and it's easier to verify and
update the code.  The behavior of the port features has been corrected in
a few respects as well (for instance, if the POWER feature is clear then
none of the other features can be set).

Also added is support for the (relatively new) _connect() and
_disconnect() calls of the Gadget API.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
diff --git a/drivers/usb/gadget/dummy_hcd.c b/drivers/usb/gadget/dummy_hcd.c
index dc0e323..2d6d229 100644
--- a/drivers/usb/gadget/dummy_hcd.c
+++ b/drivers/usb/gadget/dummy_hcd.c
@@ -163,12 +163,16 @@
 	struct dummy_request		fifo_req;
 	u8				fifo_buf [FIFO_SIZE];
 	u16				devstatus;
+	unsigned			pullup:1;
+	unsigned			active:1;
+	unsigned			old_active:1;
 
 	/*
 	 * MASTER/HOST side support
 	 */
 	struct timer_list		timer;
 	u32				port_status;
+	u32				old_status;
 	unsigned			resuming:1;
 	unsigned long			re_timeout;
 
@@ -215,6 +219,98 @@
 
 /*-------------------------------------------------------------------------*/
 
+/* SLAVE/GADGET SIDE UTILITY ROUTINES */
+
+/* called with spinlock held */
+static void nuke (struct dummy *dum, struct dummy_ep *ep)
+{
+	while (!list_empty (&ep->queue)) {
+		struct dummy_request	*req;
+
+		req = list_entry (ep->queue.next, struct dummy_request, queue);
+		list_del_init (&req->queue);
+		req->req.status = -ESHUTDOWN;
+
+		spin_unlock (&dum->lock);
+		req->req.complete (&ep->ep, &req->req);
+		spin_lock (&dum->lock);
+	}
+}
+
+/* caller must hold lock */
+static void
+stop_activity (struct dummy *dum)
+{
+	struct dummy_ep	*ep;
+
+	/* prevent any more requests */
+	dum->address = 0;
+
+	/* The timer is left running so that outstanding URBs can fail */
+
+	/* nuke any pending requests first, so driver i/o is quiesced */
+	list_for_each_entry (ep, &dum->gadget.ep_list, ep.ep_list)
+		nuke (dum, ep);
+
+	/* driver now does any non-usb quiescing necessary */
+}
+
+/* caller must hold lock */
+static void
+set_link_state (struct dummy *dum)
+{
+	dum->active = 0;
+	if ((dum->port_status & USB_PORT_STAT_POWER) == 0)
+		dum->port_status = 0;
+	else if (!dum->pullup) {
+		dum->port_status &= ~(USB_PORT_STAT_CONNECTION |
+					USB_PORT_STAT_ENABLE |
+					USB_PORT_STAT_LOW_SPEED |
+					USB_PORT_STAT_HIGH_SPEED |
+					USB_PORT_STAT_SUSPEND);
+		if ((dum->old_status & USB_PORT_STAT_CONNECTION) != 0)
+			dum->port_status |= (USB_PORT_STAT_C_CONNECTION << 16);
+	} else {
+		dum->port_status |= USB_PORT_STAT_CONNECTION;
+		if ((dum->old_status & USB_PORT_STAT_CONNECTION) == 0)
+			dum->port_status |= (USB_PORT_STAT_C_CONNECTION << 16);
+		if ((dum->port_status & USB_PORT_STAT_ENABLE) == 0)
+			dum->port_status &= ~USB_PORT_STAT_SUSPEND;
+		else if ((dum->port_status & USB_PORT_STAT_SUSPEND) == 0)
+			dum->active = 1;
+	}
+
+	if ((dum->port_status & USB_PORT_STAT_ENABLE) == 0 || dum->active)
+		dum->resuming = 0;
+
+	if ((dum->port_status & USB_PORT_STAT_CONNECTION) == 0 ||
+			(dum->port_status & USB_PORT_STAT_RESET) != 0) {
+		if ((dum->old_status & USB_PORT_STAT_CONNECTION) != 0 &&
+				(dum->old_status & USB_PORT_STAT_RESET) == 0 &&
+				dum->driver) {
+			stop_activity (dum);
+			spin_unlock (&dum->lock);
+			dum->driver->disconnect (&dum->gadget);
+			spin_lock (&dum->lock);
+		}
+	} else if (dum->active != dum->old_active) {
+		if (dum->old_active && dum->driver->suspend) {
+			spin_unlock (&dum->lock);
+			dum->driver->suspend (&dum->gadget);
+			spin_lock (&dum->lock);
+		} else if (!dum->old_active && dum->driver->resume) {
+			spin_unlock (&dum->lock);
+			dum->driver->resume (&dum->gadget);
+			spin_lock (&dum->lock);
+		}
+	}
+
+	dum->old_status = dum->port_status;
+	dum->old_active = dum->active;
+}
+
+/*-------------------------------------------------------------------------*/
+
 /* SLAVE/GADGET SIDE DRIVER
  *
  * This only tracks gadget state.  All the work is done when the host
@@ -339,22 +435,6 @@
 	return retval;
 }
 
-/* called with spinlock held */
-static void nuke (struct dummy *dum, struct dummy_ep *ep)
-{
-	while (!list_empty (&ep->queue)) {
-		struct dummy_request	*req;
-
-		req = list_entry (ep->queue.next, struct dummy_request, queue);
-		list_del_init (&req->queue);
-		req->req.status = -ESHUTDOWN;
-
-		spin_unlock (&dum->lock);
-		req->req.complete (&ep->ep, &req->req);
-		spin_lock (&dum->lock);
-	}
-}
-
 static int dummy_disable (struct usb_ep *_ep)
 {
 	struct dummy_ep		*ep;
@@ -603,7 +683,7 @@
 
 	/* hub notices our request, issues downstream resume, etc */
 	dum->resuming = 1;
-	dum->port_status |= (USB_PORT_STAT_C_SUSPEND << 16);
+	dum->re_timeout = jiffies + msecs_to_jiffies(20);
 	return 0;
 }
 
@@ -619,10 +699,24 @@
 	return 0;
 }
 
+static int dummy_pullup (struct usb_gadget *_gadget, int value)
+{
+	struct dummy	*dum;
+	unsigned long	flags;
+
+	dum = gadget_to_dummy (_gadget);
+	spin_lock_irqsave (&dum->lock, flags);
+	dum->pullup = (value != 0);
+	set_link_state (dum);
+	spin_unlock_irqrestore (&dum->lock, flags);
+	return 0;
+}
+
 static const struct usb_gadget_ops dummy_ops = {
 	.get_frame	= dummy_g_get_frame,
 	.wakeup		= dummy_wakeup,
 	.set_selfpowered = dummy_set_selfpowered,
+	.pullup		= dummy_pullup,
 };
 
 /*-------------------------------------------------------------------------*/
@@ -675,7 +769,6 @@
 	 */
 
 	dum->devstatus = 0;
-	dum->resuming = 0;
 
 	INIT_LIST_HEAD (&dum->gadget.ep_list);
 	for (i = 0; i < DUMMY_ENDPOINTS; i++) {
@@ -714,35 +807,14 @@
 	device_bind_driver (&dum->gadget.dev);
 
 	/* khubd will enumerate this in a while */
-	dum->port_status |= USB_PORT_STAT_CONNECTION
-		| (USB_PORT_STAT_C_CONNECTION << 16);
+	spin_lock_irq (&dum->lock);
+	dum->pullup = 1;
+	set_link_state (dum);
+	spin_unlock_irq (&dum->lock);
 	return 0;
 }
 EXPORT_SYMBOL (usb_gadget_register_driver);
 
-/* caller must hold lock */
-static void
-stop_activity (struct dummy *dum, struct usb_gadget_driver *driver)
-{
-	struct dummy_ep	*ep;
-
-	/* prevent any more requests */
-	dum->address = 0;
-
-	/* The timer is left running so that outstanding URBs can fail */
-
-	/* nuke any pending requests first, so driver i/o is quiesced */
-	list_for_each_entry (ep, &dum->gadget.ep_list, ep.ep_list)
-		nuke (dum, ep);
-
-	/* driver now does any non-usb quiescing necessary */
-	if (driver) {
-		spin_unlock (&dum->lock);
-		driver->disconnect (&dum->gadget);
-		spin_lock (&dum->lock);
-	}
-}
-
 int
 usb_gadget_unregister_driver (struct usb_gadget_driver *driver)
 {
@@ -758,10 +830,8 @@
 			driver->driver.name);
 
 	spin_lock_irqsave (&dum->lock, flags);
-	stop_activity (dum, driver);
-	dum->port_status &= ~(USB_PORT_STAT_CONNECTION | USB_PORT_STAT_ENABLE |
-			USB_PORT_STAT_LOW_SPEED | USB_PORT_STAT_HIGH_SPEED);
-	dum->port_status |= (USB_PORT_STAT_C_CONNECTION << 16);
+	dum->pullup = 0;
+	set_link_state (dum);
 	spin_unlock_irqrestore (&dum->lock, flags);
 
 	driver->unbind (&dum->gadget);
@@ -770,6 +840,11 @@
 	device_release_driver (&dum->gadget.dev);
 	driver_unregister (&driver->driver);
 
+	spin_lock_irqsave (&dum->lock, flags);
+	dum->pullup = 0;
+	set_link_state (dum);
+	spin_unlock_irqrestore (&dum->lock, flags);
+
 	return 0;
 }
 EXPORT_SYMBOL (usb_gadget_unregister_driver);
@@ -1432,6 +1507,13 @@
 	dum = hcd_to_dummy (hcd);
 
 	spin_lock_irqsave (&dum->lock, flags);
+
+	if (dum->resuming && time_after_eq (jiffies, dum->re_timeout)) {
+		dum->port_status |= (USB_PORT_STAT_C_SUSPEND << 16);
+		dum->port_status &= ~USB_PORT_STAT_SUSPEND;
+		set_link_state (dum);
+	}
+
 	if (!(dum->port_status & PORT_C_MASK))
 		retval = 0;
 	else {
@@ -1480,16 +1562,16 @@
 				/* 20msec resume signaling */
 				dum->resuming = 1;
 				dum->re_timeout = jiffies +
-							msecs_to_jiffies(20);
+						msecs_to_jiffies(20);
 			}
 			break;
 		case USB_PORT_FEAT_POWER:
-			dum->port_status = 0;
-			dum->resuming = 0;
-			stop_activity(dum, dum->driver);
-			break;
+			if (dum->port_status & USB_PORT_STAT_POWER)
+				dev_dbg (dummy_dev(dum), "power-off\n");
+			/* FALLS THROUGH */
 		default:
 			dum->port_status &= ~(1 << wValue);
+			set_link_state (dum);
 		}
 		break;
 	case GetHubDescriptor:
@@ -1505,23 +1587,16 @@
 		/* whoever resets or resumes must GetPortStatus to
 		 * complete it!!
 		 */
-		if (dum->resuming && time_after (jiffies, dum->re_timeout)) {
+		if (dum->resuming &&
+				time_after_eq (jiffies, dum->re_timeout)) {
 			dum->port_status |= (USB_PORT_STAT_C_SUSPEND << 16);
 			dum->port_status &= ~USB_PORT_STAT_SUSPEND;
-			dum->resuming = 0;
-			dum->re_timeout = 0;
-			if (dum->driver && dum->driver->resume) {
-				spin_unlock (&dum->lock);
-				dum->driver->resume (&dum->gadget);
-				spin_lock (&dum->lock);
-			}
 		}
-		if ((dum->port_status & USB_PORT_STAT_RESET) != 0
-				&& time_after (jiffies, dum->re_timeout)) {
+		if ((dum->port_status & USB_PORT_STAT_RESET) != 0 &&
+				time_after_eq (jiffies, dum->re_timeout)) {
 			dum->port_status |= (USB_PORT_STAT_C_RESET << 16);
 			dum->port_status &= ~USB_PORT_STAT_RESET;
-			dum->re_timeout = 0;
-			if (dum->driver) {
+			if (dum->pullup) {
 				dum->port_status |= USB_PORT_STAT_ENABLE;
 				/* give it the best speed we agree on */
 				dum->gadget.speed = dum->driver->speed;
@@ -1542,6 +1617,7 @@
 				}
 			}
 		}
+		set_link_state (dum);
 		((u16 *) buf)[0] = cpu_to_le16 (dum->port_status);
 		((u16 *) buf)[1] = cpu_to_le16 (dum->port_status >> 16);
 		break;
@@ -1551,42 +1627,36 @@
 	case SetPortFeature:
 		switch (wValue) {
 		case USB_PORT_FEAT_SUSPEND:
-			if ((dum->port_status & USB_PORT_STAT_SUSPEND)
-					== 0) {
+			if (dum->active) {
 				dum->port_status |= USB_PORT_STAT_SUSPEND;
-				if (dum->driver && dum->driver->suspend) {
-					spin_unlock (&dum->lock);
-					dum->driver->suspend (&dum->gadget);
-					spin_lock (&dum->lock);
-					/* HNP would happen here; for now we
-					 * assume b_bus_req is always true.
-					 */
-					if (((1 << USB_DEVICE_B_HNP_ENABLE)
-							& dum->devstatus) != 0)
-						dev_dbg (dummy_dev(dum),
+
+				/* HNP would happen here; for now we
+				 * assume b_bus_req is always true.
+				 */
+				set_link_state (dum);
+				if (((1 << USB_DEVICE_B_HNP_ENABLE)
+						& dum->devstatus) != 0)
+					dev_dbg (dummy_dev(dum),
 							"no HNP yet!\n");
-				}
 			}
 			break;
+		case USB_PORT_FEAT_POWER:
+			dum->port_status |= USB_PORT_STAT_POWER;
+			set_link_state (dum);
+			break;
 		case USB_PORT_FEAT_RESET:
-			/* if it's already running, disconnect first */
-			if (dum->port_status & USB_PORT_STAT_ENABLE) {
-				dum->port_status &= ~(USB_PORT_STAT_ENABLE
-						| USB_PORT_STAT_LOW_SPEED
-						| USB_PORT_STAT_HIGH_SPEED);
-				if (dum->driver) {
-					dev_dbg (udc_dev(dum),
-							"disconnect\n");
-					stop_activity (dum, dum->driver);
-				}
-
-				/* FIXME test that code path! */
-			}
+			/* if it's already enabled, disable */
+			dum->port_status &= ~(USB_PORT_STAT_ENABLE
+					| USB_PORT_STAT_LOW_SPEED
+					| USB_PORT_STAT_HIGH_SPEED);
 			/* 50msec reset signaling */
 			dum->re_timeout = jiffies + msecs_to_jiffies(50);
-			/* FALLTHROUGH */
+			/* FALLS THROUGH */
 		default:
-			dum->port_status |= (1 << wValue);
+			if ((dum->port_status & USB_PORT_STAT_POWER) != 0) {
+				dum->port_status |= (1 << wValue);
+				set_link_state (dum);
+			}
 		}
 		break;