usb gadget: function activation/deactivation
David Brownell [Tue, 19 Aug 2008 00:38:22 +0000 (17:38 -0700)]
Add a new mechanism to the composite gadget framework, letting
functions deactivate (and reactivate) themselves.  Think of it
as a refcounted wrapper for the software pullup control.

A key example of why to use this mechanism involves functions that
require a userspace daemon.  Those functions shuld use this new
mechanism to prevent the gadget from enumerating until those daemons
are activated.  Without this mechanism, hosts would see devices that
malfunction until the relevant daemons start.

Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

drivers/usb/gadget/composite.c
include/linux/usb/composite.h

index 85c876c..f79fdb8 100644 (file)
@@ -128,6 +128,70 @@ done:
 }
 
 /**
+ * usb_function_deactivate - prevent function and gadget enumeration
+ * @function: the function that isn't yet ready to respond
+ *
+ * Blocks response of the gadget driver to host enumeration by
+ * preventing the data line pullup from being activated.  This is
+ * normally called during @bind() processing to change from the
+ * initial "ready to respond" state, or when a required resource
+ * becomes available.
+ *
+ * For example, drivers that serve as a passthrough to a userspace
+ * daemon can block enumeration unless that daemon (such as an OBEX,
+ * MTP, or print server) is ready to handle host requests.
+ *
+ * Not all systems support software control of their USB peripheral
+ * data pullups.
+ *
+ * Returns zero on success, else negative errno.
+ */
+int usb_function_deactivate(struct usb_function *function)
+{
+       struct usb_composite_dev        *cdev = function->config->cdev;
+       int                             status = 0;
+
+       spin_lock(&cdev->lock);
+
+       if (cdev->deactivations == 0)
+               status = usb_gadget_disconnect(cdev->gadget);
+       if (status == 0)
+               cdev->deactivations++;
+
+       spin_unlock(&cdev->lock);
+       return status;
+}
+
+/**
+ * usb_function_activate - allow function and gadget enumeration
+ * @function: function on which usb_function_activate() was called
+ *
+ * Reverses effect of usb_function_deactivate().  If no more functions
+ * are delaying their activation, the gadget driver will respond to
+ * host enumeration procedures.
+ *
+ * Returns zero on success, else negative errno.
+ */
+int usb_function_activate(struct usb_function *function)
+{
+       struct usb_composite_dev        *cdev = function->config->cdev;
+       int                             status = 0;
+
+       spin_lock(&cdev->lock);
+
+       if (WARN_ON(cdev->deactivations == 0))
+               status = -EINVAL;
+       else {
+               cdev->deactivations--;
+               if (cdev->deactivations == 0)
+                       status = usb_gadget_connect(cdev->gadget);
+       }
+
+       spin_unlock(&cdev->lock);
+       return status;
+}
+
+/**
  * usb_interface_id() - allocate an unused interface ID
  * @config: configuration associated with the interface
  * @function: function handling the interface
index c932390..935c380 100644 (file)
@@ -130,6 +130,9 @@ struct usb_function {
 
 int usb_add_function(struct usb_configuration *, struct usb_function *);
 
+int usb_function_deactivate(struct usb_function *);
+int usb_function_activate(struct usb_function *);
+
 int usb_interface_id(struct usb_configuration *, struct usb_function *);
 
 /**
@@ -316,9 +319,13 @@ struct usb_composite_dev {
        struct usb_composite_driver     *driver;
        u8                              next_string_id;
 
-       spinlock_t                      lock;
+       /* the gadget driver won't enable the data pullup
+        * while the deactivation count is nonzero.
+        */
+       unsigned                        deactivations;
 
-       /* REVISIT use and existence of lock ... */
+       /* protects at least deactivation count */
+       spinlock_t                      lock;
 };
 
 extern int usb_string_id(struct usb_composite_dev *c);