Re: [RFC/PATCH 2/3] usb: gadget: introduce UDC Class

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

 



On Fri, 13 May 2011 13:05:34 +0200, Felipe Balbi <balbi@xxxxxx> wrote:
@@ -43,6 +43,12 @@ menuconfig USB_GADGET
if USB_GADGET
+config USB_UDC_CORE
+	bool "USB UDC Core Support"
+	help
+	  This is the new style UDC Core Layer. If your driver has been
+	  converted to this new layer, then you should say Y or M here.
+

How user is supposed to know? Shouldn't the drivers themselves select it
when needed?

 config USB_GADGET_DEBUG
 	boolean "Debugging messages (DEVELOPMENT)"
 	depends on DEBUG_KERNEL
diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
index 1ea15ee..74e8d9f 100644
--- a/drivers/usb/gadget/Makefile
+++ b/drivers/usb/gadget/Makefile
@@ -3,6 +3,7 @@
 #
 ccflags-$(CONFIG_USB_GADGET_DEBUG) := -DDEBUG
+obj-$(CONFIG_USB_UDC_CORE)	+= udc-core.o
 obj-$(CONFIG_USB_DUMMY_HCD)	+= dummy_hcd.o
 obj-$(CONFIG_USB_NET2280)	+= net2280.o
 obj-$(CONFIG_USB_AMD5536UDC)	+= amd5536udc.o
diff --git a/drivers/usb/gadget/udc-core.c b/drivers/usb/gadget/udc-core.c
new file mode 100644
index 0000000..dedf3adb
--- /dev/null
+++ b/drivers/usb/gadget/udc-core.c
@@ -0,0 +1,461 @@
+/**
+ * udc.c - Core UDC Framework
+ *
+ * Copyright (C) 2010 Texas Instruments
+ * Author: Felipe Balbi <balbi@xxxxxx>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2  of
+ * the License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/list.h>
+#include <linux/err.h>
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+
+/**
+ * struct usb_udc - describes one usb device controller
+ * @driver - the gadget driver pointer. For use by the class code
+ * @dev - the child device to the actual controller
+ * @gadget - the gadget. For use by the class code
+ * @list - for use by the udc class driver
+ * @name - a human friendly name representation
+ *
+ * This represents the internal data structure which is used by the UDC-class
+ * to hold information about udc driver and gadget together.
+ */
+struct usb_udc {
+	struct usb_gadget_driver	*driver;
+	struct usb_gadget		*gadget;
+	struct device			dev;
+	struct list_head		list;
+	char				*name;
+};
+
+static struct class *udc_class;
+static struct device_type udc_device_type;
+static LIST_HEAD(udc_list);
+static spinlock_t udc_lock;
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * usb_gadget_start - tells usb device controller to start up
+ * @gadget: The device we want to get started
+ *
+ * This call is issued by the UDC Class driver when it's about
+ * to register a gadget driver to the device controller, before
+ * calling gadget driver's bind() method.
+ *
+ * It allows the controller to be powered off until extrictly
+ * necessary to have it powered on.
+ *
+ * Returns zero on success, else negative errno.
+ */
+static inline int usb_gadget_start(struct usb_gadget *gadget,
+		struct usb_gadget_driver *drv)
+{
+	return gadget->ops->start(gadget, drv);
+}
+
+/**
+ * usb_gadget_stop - tells usb device controller we don't need it anymore
+ * @gadget: The device we want to stop activity
+ *
+ * This call is issued by the UDC Class driver after calling
+ * gadget driver's unbind() method.
+ *
+ * The details are implementation specific, but it can go as
+ * far as powering off UDC completely and disable its data
+ * line pullups.
+ */
+static inline void usb_gadget_stop(struct usb_gadget *gadget,
+		struct usb_gadget_driver *driver)
+{
+	gadget->ops->stop(gadget, driver);
+}
+
+/* ------------------------------------------------------------------------- */
+
+static void usb_udc_release(struct device *dev)
+{
+	struct usb_udc *udc;
+
+	udc = container_of(dev, struct usb_udc, dev);
+	dev_dbg(dev, "releasing '%s'\n", dev_name(dev));
+	kfree(udc);
+}
+
+/**
+ * usb_add_gadget - adds a new gadget to the udc class driver list
+ * @parent: the parent device to this udc. Usually the controller
+ * driver's device.
+ * @gadget: the gadget to be added to the list
+ *
+ * Returns zero on success, negative errno otherwise.
+ */
+int usb_add_gadget(struct device *parent, struct usb_gadget *gadget)
+{
+	struct usb_udc		*udc;
+	unsigned long		flags;
+	int			ret = -ENOMEM;
+
+	udc = kzalloc(sizeof(*udc), GFP_KERNEL);
+	if (!udc)
+		goto err1;
+
+	device_initialize(&udc->dev);
+	udc->dev.release = usb_udc_release;
+	udc->dev.class = udc_class;
+	udc->dev.parent = parent;
+	ret = dev_set_name(&udc->dev, "%s", kobject_name(&parent->kobj));
+	if (ret)
+		goto err2;
+
+	udc->gadget = gadget;
+
+	spin_lock_irqsave(&udc_lock, flags);
+	list_add_tail(&udc->list, &udc_list);
+	spin_unlock_irqrestore(&udc_lock, flags);
+
+	ret = device_add(&udc->dev);
+	if (ret)
+		goto err2;
+
+	return 0;
+
+err2:
+	put_device(&udc->dev);
+
+err1:
+	return ret;
+}
+EXPORT_SYMBOL_GPL(usb_add_gadget);
+
+/**
+ * usb_del_gadget - deletes @udc from udc_list
+ * @udc: the udc to be removed.
+ *
+ * This, will call usb_gadget_unregister_driver() if
+ * the @udc is still busy.
+ */
+void usb_del_gadget(struct usb_gadget *gadget)
+{
+	struct usb_udc		*udc = NULL;
+	unsigned long		flags;
+
+	dev_vdbg(gadget->dev.parent, "unregistering gadget\n");
+
+	spin_lock_irqsave(&udc_lock, flags);
+	list_for_each_entry(udc, &udc_list, list) {
+		if (udc->gadget == gadget)
+			break;
+	}
+
+	if (!udc)
+		return;

Spinlock remains locked.

+	if (udc->gadget != gadget) {
+		dev_err(gadget->dev.parent, "gadget not registered.\n");
+		return;

Spinlock remains locked.

+	}

Also, if you ask me, I'd rewrite the above as:

	spin_lock_irqsave(&udc_lock, flags);
	list_for_each_entry(udc, &udc_list, list) {
		if (udc->gadget == gadget)
			goto found;
	}

	dev_err(gadget->dev.parent, "gadget not registered.\n");
	spin_unlock_irqrestore(&udc_lock, flags);
	return;

found:

+
+	list_del(&udc->list);

Do we need to keep the spinlock after this point?

+
+	if (udc->driver) {
+		spin_unlock_irqrestore(&udc_lock, flags);
+		usb_gadget_unregister_driver(udc->driver);

usb_gadget_unregister_driver() will once again traverse the list
to look for the UDC and it *will* *fail* since we already have
removed the UDC from the list.  We need to have another function
which takes udc and unregisteres the driver.

+		spin_lock_irqsave(&udc_lock, flags);
+	}
+
+	device_unregister(&gadget->dev);
+	spin_unlock_irqrestore(&udc_lock, flags);
+}
+EXPORT_SYMBOL_GPL(usb_del_gadget);
+
+/* ------------------------------------------------------------------------- */
+
+int usb_gadget_register_driver(struct usb_gadget_driver *driver)
+{
+	struct usb_udc		*udc = NULL;
+	unsigned long		flags;
+	int			ret;
+
+	if (!driver || !driver->bind || !driver->setup)
+		return -EINVAL;
+
+	spin_lock_irqsave(&udc_lock, flags);
+	list_for_each_entry(udc, &udc_list, list) {
+		/* For now we take the first one */
+		if (!udc->driver) {
+			pr_debug("using UDC '%s'\n", udc->name);
+			break;
+		}
+		udc = NULL;
+	}
+
+	if (!udc) {
+		pr_debug("couldn't find an available UDC\n");
+		ret = -ENODEV;
+		goto err0;
+	}
+
+	dev_dbg(&udc->dev, "registering UDC driver [%s]\n",
+			driver->function);
+
+	udc->driver = driver;
+	udc->dev.driver = &driver->driver;
+
+	spin_unlock_irqrestore(&udc_lock, flags);
+
+	ret = usb_gadget_start(udc->gadget, driver);
+	if (ret) {
+		dev_err(&udc->dev, "failed to start %s --> %d\n",
+				udc->name, ret);
+		goto err1;
+	}
+
+#if 0
+	/*
+	 * in long term we try to move same/simmilar steps from every gadget's
+	 * ->start() callback into here. ->start() should only change some bits
+	 * in the HW if possible.
+	 */
+	ret = driver->bind(udc->gadget);
+	if (ret) {
+		dev_err(&udc->dev, "bind to driver %s failed --> %d\n",
+				driver->function, ret);
+		goto err2;
+	}
+
+	ret = usb_gadget_connect(udc->gadget);
+	if (ret) {
+		dev_err(&udc->dev, "failed to start %s --> %d\n",
+				udc->name, ret);
+		goto err3;
+	}
+#endif
+
+	kobject_uevent(&udc->dev.kobj, KOBJ_ADD);
+
+	return 0;
+
+#if 0
+err3:
+	driver->unbind(udc->gadget);
+
+err2:
+	usb_gadget_stop(udc->gadget);
+
+#endif
+err1:
+	spin_lock_irqsave(&udc_lock, flags);
+	udc->driver = NULL;
+	udc->dev.driver = NULL;
+err0:
+	spin_unlock_irqrestore(&udc_lock, flags);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(usb_gadget_register_driver);
+
+int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
+{
+	struct usb_udc		*udc = NULL;
+	unsigned long		flags;
+	int			ret = 0;
+
+	if (!driver || !driver->unbind)
+		return -EINVAL;
+
+	spin_lock_irqsave(&udc_lock, flags);
+	list_for_each_entry(udc, &udc_list, list) {
+		if (udc->driver == driver)
+			break;
+	}
+
+	if (!udc || udc->driver != driver) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	spin_unlock_irqrestore(&udc_lock, flags);
+
+	usb_gadget_vbus_draw(udc->gadget, 0);
+
+ dev_dbg(&udc->dev, "unregistering UDC driver [%s]\n", driver->function);
+	kobject_uevent(&udc->dev.kobj, KOBJ_REMOVE);
+
+	driver->unbind(udc->gadget);
+	usb_gadget_stop(udc->gadget, udc->driver);
+	usb_gadget_disconnect(udc->gadget);
+
+	spin_lock_irqsave(&udc_lock, flags);
+	udc->driver = NULL;
+	udc->dev.driver = NULL;
+	spin_unlock_irqrestore(&udc_lock, flags);
+
+out:
+	return ret;
+}
+EXPORT_SYMBOL_GPL(usb_gadget_unregister_driver);

--
Best regards,                                         _     _
.o. | Liege of Serenely Enlightened Majesty of      o' \,=./ `o
..o | Computer Science,  Michal "mina86" Nazarewicz    (o o)
ooo +-----<email/xmpp: mnazarewicz@xxxxxxxxxx>-----ooO--(_)--Ooo--
--
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