Re: [PATCH v5 3/3] usb: gadget: udc: core: Prevent UDC from starting when unbound

From: Alan Stern
Date: Wed May 31 2023 - 13:41:52 EST


On Wed, May 31, 2023 at 04:02:03AM +0000, Badhri Jagan Sridharan wrote:
> UDC should neither be started nor pulled up unless the gadget driver is
> bound. The new flag "allow_start" is now set by gadget_bind_driver()
> and cleared by gadget_unbind_driver(). usb_gadget_udc_start_locked()
> now checks whether allow_start is set before starting the UDC by
> invoking the ->udc_start() callback.

"allow_start" isn't quite the right name either, because there is a
short time interval during which the UDC is started but we don't want
to allow it to connect to the host (this occurs in
gadget_unbind_driver() between the disconnect call and the
usb_gadge_udc_stop call). A more accurate name would be "allow_connect"
or "allow_pullup".

> Fixes: fc274c1e9973 ("USB: gadget: Add a new bus for gadgets")
> Cc: stable <stable@xxxxxxxxxx>
> Signed-off-by: Badhri Jagan Sridharan <badhri@xxxxxxxxxx>
> ---
> v5 is the first version in this series.
> ---
> drivers/usb/gadget/udc/core.c | 8 ++++++++
> 1 file changed, 8 insertions(+)
>
> diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c
> index 6ffe5fda8bb7..ac9d6186815d 100644
> --- a/drivers/usb/gadget/udc/core.c
> +++ b/drivers/usb/gadget/udc/core.c
> @@ -37,6 +37,8 @@ static const struct bus_type gadget_bus_type;
> * @vbus: for udcs who care about vbus status, this value is real vbus status;
> * for udcs who do not care about vbus status, this value is always true
> * @started: the UDC's started state. True if the UDC had started.
> + * @allow_start: Indicates whether UDC is allowed to start. Set/cleared by gadget_(un)bind_driver()
> + * after gadget driver is bound or unbound.
> * @connect_lock: protects udc->vbus, udc->started, gadget->connect, gadget->deactivate related
> * functions. usb_gadget_connect_locked, usb_gadget_disconnect_locked,
> * usb_udc_connect_control_locked, usb_gadget_udc_start_locked, usb_gadget_udc_stop_locked are

As before, wrap the comments around column 76.

> @@ -52,6 +54,7 @@ struct usb_udc {
> struct list_head list;
> bool vbus;
> bool started;
> + bool allow_start;
> struct work_struct vbus_work;
> struct mutex connect_lock;
> };
> @@ -1204,6 +1207,9 @@ static inline int usb_gadget_udc_start_locked(struct usb_udc *udc)
> if (udc->started) {
> dev_err(&udc->dev, "UDC had already started\n");
> return -EBUSY;
> + } else if (!udc->allow_start) {
> + dev_err(&udc->dev, "UDC not allowed to start. Is gadget driver bound ?\n");
> + return -EIO;
> }

This isn't the right test or the right place. We want to prevent the
UDC from connecting to the host, not prevent it from starting.

>
> ret = udc->gadget->ops->udc_start(udc->gadget, udc->driver);
> @@ -1590,6 +1596,7 @@ static int gadget_bind_driver(struct device *dev)
> goto err_bind;
>
> mutex_lock(&udc->connect_lock);
> + udc->allow_start = true;
> ret = usb_gadget_udc_start_locked(udc);
> if (ret) {
> mutex_unlock(&udc->connect_lock);
> @@ -1630,6 +1637,7 @@ static void gadget_unbind_driver(struct device *dev)
>
> cancel_work_sync(&udc->vbus_work);
> mutex_lock(&udc->connect_lock);
> + udc->allow_start = false;
> usb_gadget_disconnect_locked(gadget);
> usb_gadget_disable_async_callbacks(udc);
> if (gadget->irq)

Here is where the problem about the vbus work item getting requeued can
be fixed. Clear the allow_connect flag before the call to
cancel_work_sync(). That way, even if the vbus work item gets queued
again after it is cancelled, when it does run it won't do anything.

Although, come to think of it, there is still the problem of making sure
that the work item doesn't run after the udc has been deallocated. It
looks like you will also need to cancel the work item at the end of
usb_del_gadget(). At that point the UDC has already been stopped, so it
won't call usb_hcd_vbus_handler() again.

Alan Stern