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

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

 



On Wed, 15 Sep 2010 11:43:56 +0200, Felipe Balbi <balbi@xxxxxx> wrote:
+int usb_add_udc(struct device *parent, struct usb_udc *udc)
+{
+	struct device		*dev;
+	unsigned long		flags;
+	int			ret;
+
+	dev_vdbg(parent, "registering UDC\n");
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev) {
+		ret = -ENOMEM;
+		goto err1;
+	}
+
+	device_initialize(dev);
+
+	dev->class = udc_class;
+	dev->type = &udc_device_type;
+	dev->parent = parent;
+	dev->release = usb_udc_release;
+	dev_set_drvdata(dev, udc);
+	udc->dev = dev;
+	udc->gadget->dev.parent = udc->dev;
+	udc->gadget->dev.release = usb_udc_nop_release;
+	udc->gadget->dev.dma_mask = parent->dma_mask;
+
+	ret = dev_set_name(dev, "%s", udc->name);
+	if (ret)
+		goto err2;
+
+	ret = dev_set_name(&udc->gadget->dev, udc->name);

In theory udc->name can have "%".  If we assume it has no "%" than
dev_set_name() above can be changed as well.

+	if (ret)
+		goto err2;
+
+	ret = device_add(dev);
+	if (ret)
+		goto err2;
+
+	ret = device_register(&udc->gadget->dev);
+	if (ret)
+		goto err3;
+
+
+	spin_lock_irqsave(&udc_lock, flags);
+	list_add_tail(&udc->list, &udc_list);
+	spin_unlock_irqrestore(&udc_lock, flags);
+
+	return 0;
+
+err3:
+	device_unregister(dev);
+
+err2:
+	kfree(dev);
+
+err1:
+	return ret;
+}


+void usb_del_udc(struct usb_udc *udc)
+{
+	unsigned long		flags;
+
+	dev_vdbg(udc->dev->parent, "unregistering UDC\n");
+
+	if (udc->busy)

I'd say "if (WARN_ON(udc->busy))".

+		usb_gadget_unregister_driver(udc->driver);

Maybe add a __usb_gadget_unregister_driver() function which would
take udc as argument and unregister the driver?

Also, note that a concurrent call to usb_gadget_register_driver()
could grab this UDC and register a driver to it.  The list_del() should
be moved above the condition.

+	device_unregister(udc->dev);
+	device_unregister(&udc->gadget->dev);
+
+	spin_lock_irqsave(&udc_lock, flags);
+	list_del(&udc->list);
+	spin_unlock_irqrestore(&udc_lock, flags);
+}

So, after proposed changes, the function would look as follows:

void usb_del_udc(struct usb_udc *udc)
{
	unsigned long		flags;

	dev_vdbg(udc->dev->parent, "unregistering UDC\n");

	spin_lock_irqsave(&udc_lock, flags);
	list_del(&udc->list);
	spin_unlock_irqrestore(&udc_lock, flags);

	if (WARN_ON(udc->busy))
		__usb_gadget_unregister_driver(udc);

	device_unregister(udc->dev);
	device_unregister(&udc->gadget->dev);
}

The __usb_gadget_unregister_driver() would look as follows:

static void __usb_gadget_unregister_driver(struct usb_udc *udc)
{
	(void) 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);
	usb_gadget_disconnect(udc->gadget);

	udc->driver = NULL;
	udc->dev->driver = NULL;
	smb_wb();
	udc->busy = false;
}

Also note, that you should first unbind, stop and disconnect and only then mark UDC
as not busy.  Otherwise, concurrent usb_gadget_register_driver() could take the UDC
while driver is being unregistered.

Alternatively, you could create two lists: of free and busy UDCs and move UDCs from
one list to the other.


+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) {
+		if (!udc->busy) {
+			pr_debug("using UDC '%s'\n", udc->name);

This pr_debug() seems redundant.  Information is printed after the loop
anyway.

+			break;
+		}
+
+		pr_debug("couldn't find an available UDC\n");
+		ret = -ENODEV;
+		spin_unlock_irqrestore(&udc_lock, flags);
+		goto err0;

Matter of taste but I would prefer a simple "return -ENODEV;" rather
than a goto.

+	}
+
+	dev_dbg(udc->dev, "registering UDC driver [%s]\n",
+			driver->function);
+
+	udc->busy = true;
+	udc->driver = driver;
+	udc->dev->driver = &driver->driver;
+
+	spin_unlock_irqrestore(&udc_lock, flags);
+
+	ret = usb_gadget_start(udc->gadget);
+	if (ret) {
+		dev_err(udc->dev, "failed to start %s --> %d\n",
+				udc->name, ret);
+		goto err1;
+	}
+
+	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;
+	}
+
+	kobject_uevent(&udc->dev->kobj, KOBJ_ADD);
+
+	return 0;
+
+err3:
+	driver->unbind(udc->gadget);
+
+err2:
+	usb_gadget_stop(udc->gadget);
+
+err1:
+	udc->busy = false;
+	udc->driver = NULL;
+	udc->dev->driver = NULL;

The above needs some kind of synchronisation, for instance:

	udc->driver = NULL;
	udc->dev->driver = NULL;
	smp_wb();
	udc->busy = false;

+
+err0:
+	return ret;
+}


+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)
+			continue;
+
+		if (udc->busy)
+			break;
+
+		ret = -ENODEV;
+		spin_unlock_irqrestore(&udc_lock, flags);
+		goto out;
+	}

You can get an invalid UDC here if there was no UDC that matched driver.

+
+	udc->busy = false;
+	udc->driver = NULL;
+	udc->dev->driver = NULL;

The same comment as in my __usb_gadget_unregister_driver() above applies.
Concurrent call to usb_gadget_register_driver() could now grab the UDC that
is not yet fully free to use.

+	spin_unlock_irqrestore(&udc_lock, flags);
+
+	(void) 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);
+	usb_gadget_disconnect(udc->gadget);
+
+out:
+	return ret;
+}

With __usb_gadget_unregister_driver(), this would look something like:

int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
{
	struct usb_udc		*udc = NULL;
	unsigned long		flags;
	bool			ret = -ENODEV;

	if (!driver || !driver->unbind)
		return -EINVAL;

	spin_lock_irqsave(&udc_lock, flags);
	list_for_each_entry(udc, &udc_list, list)
		if (udc->busy && udc->driver == driver) {
			ret = 0;
			break;
		}
	spin_unlock_irqrestore(&udc_lock, flags);

	if (!ret)
		__usb_gadget_unregister_driver(udc);
	return ret;
}

At this point, I only wonder if calling usb_gadget_unregister_driver() and usb_del_udc()
won't break something.  But that's a pathological situation anyway so maybe we don't have
to care?


diff --git a/include/linux/usb/udc.h b/include/linux/usb/udc.h
@@ -0,0 +1,54 @@
+/**
+ * struct usb_udc - describes one usb device controller
+ * @gadget - the gadget. Always provide this.
+ * @driver - the gadget driver pointer. Always provide this.

Actually, always set to NULL.  This field is modified only by the
class code, right? UDC driver should not touch it.  Or am I missing
something.

+ * @dev - the child device to the actual controller
+ * @list - for use by the udc class driver
+ * @busy - true when this udc already has @driver and @gadget
+ * @name - a human friendly name representation
+ *
+ * a pointer to this structure should be passed to udc class
+ * driver by controller driver (musb, omap_udc, atmel, etc).
+ * @gadget is expected to be already initialized by that time.
+ */
+struct usb_udc {
+	struct usb_gadget		*gadget;
+	struct usb_gadget_driver	*driver;
+	struct device			*dev;
+
+	struct list_head		list;
+
+	unsigned			busy:1;

I started wondering if this field is really needed.  After all,
this field is set only if driver is not-NULL.

+
+	const char			*name;

Is that required?  Wouldn't dev's or gadget's dev's name be enough?

+};
+
+extern int usb_add_udc(struct device *, struct usb_udc *);
+extern void usb_del_udc(struct usb_udc *);
+
+

Empty lines at the end of file.


--
Best regards,                                        _     _
| Humble Liege of Serenely Enlightened Majesty of  o' \,=./ `o
| Computer Science,  Michał "mina86" Nazarewicz       (o o)
+----[mina86*mina86.com]---[mina86*jabber.org]----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