Re: [PATCH v4 3/3] usb: gadget: udc: core: Offload usb_udc_vbus_handler processing

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

 



On Mon, May 29, 2023 at 11:48:16PM +0000, Badhri Jagan Sridharan wrote:
> chipidea udc calls usb_udc_vbus_handler from udc_start gadget
> ops causing a deadlock. Avoid this by offloading usb_udc_vbus_handler
> processing.

This is not a good explanation.  In particular, it doesn't explain why 
moving the processing to a workqueue is the proper solution.

You should describe the issue I raised earlier, namely, that 
usb_udc_vbus_handler() has to run in interrupt context but it calls 
usb_udc_connect_control(), which has to run in process context.  And 
explain _why_ these routines have to run in those contexts.

> ---
>  drivers/usb/gadget/udc/core.c | 269 ++++++++++++++++------------------
>  1 file changed, 123 insertions(+), 146 deletions(-)
> 
> diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c
> index 4641153e9706..26380e621e9f 100644
> --- a/drivers/usb/gadget/udc/core.c
> +++ b/drivers/usb/gadget/udc/core.c
> @@ -38,9 +38,10 @@ static const struct bus_type gadget_bus_type;
>   * 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.
>   * @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
> - * called with this lock held.
> + * functions. usb_gadget_pullup_update_locked called with this lock held.
> + * @vbus_events: list head for processing vbus updates on usb_udc_vbus_handler.
> + * @vbus_events_lock: protects vbus_events list
> + * @vbus_work: work item that invokes usb_gadget_pullup_update_locked.
>   *
>   * This represents the internal data structure which is used by the UDC-class
>   * to hold information about udc driver and gadget together.
> @@ -53,6 +54,20 @@ struct usb_udc {
>  	bool				vbus;
>  	bool				started;
>  	struct mutex			connect_lock;
> +	struct list_head		vbus_events;
> +	spinlock_t			vbus_events_lock;
> +	struct work_struct		vbus_work;

Do you really need three new fields here?  Isn't vbus_work sufficient?

> +	bool				unbinding;

Do not include this in the same patch.  The unbinding flag does 
something different from the vbus_work structure, so it belongs in a 
different patch.

> +};
> +
> +/**
> + * struct vbus_event - used to notify vbus updates posted through usb_udc_vbus_handler.
> + * @vbus_on: true when vbus is on. false other wise.
> + * @node: list node for maintaining a list of pending updates to be processed.
> + */
> +struct vbus_event {
> +	bool vbus_on;
> +	struct list_head node;
>  };

Why do we need this?  Why can't the work routine simply set the pullup 
according to the current setting of vbus and the other flags?  That's 
what usb_udc_vbus_handler() does now.

>  
>  static struct class *udc_class;
> @@ -693,40 +708,46 @@ int usb_gadget_vbus_disconnect(struct usb_gadget *gadget)
>  EXPORT_SYMBOL_GPL(usb_gadget_vbus_disconnect);
>  
>  /* Internal version of usb_gadget_connect needs to be called with connect_lock held. */
> -static int usb_gadget_connect_locked(struct usb_gadget *gadget)
> +static int usb_gadget_pullup_update_locked(struct usb_gadget *gadget)
>  	__must_hold(&gadget->udc->connect_lock)
>  {
>  	int ret = 0;
> +	bool connect = !gadget->deactivated && gadget->udc->started && gadget->udc->vbus &&
> +		       !gadget->udc->unbinding;

Since you are wrapping this line anyway, you might as well wrap it 
before column 76.

>  
>  	if (!gadget->ops->pullup) {
>  		ret = -EOPNOTSUPP;
>  		goto out;
>  	}
>  
> -	if (gadget->connected)
> -		goto out;
> -
> -	if (gadget->deactivated || !gadget->udc->started) {
> -		/*
> -		 * If gadget is deactivated we only save new state.
> -		 * Gadget will be connected automatically after activation.
> -		 *
> -		 * udc first needs to be started before gadget can be pulled up.
> -		 */
> -		gadget->connected = true;
> -		goto out;
> +	if (connect != gadget->connected) {
> +		ret = gadget->ops->pullup(gadget, connect);
> +		if (!ret)
> +			gadget->connected = connect;
> +		mutex_lock(&udc_lock);
> +		if (!connect)
> +			gadget->udc->driver->disconnect(gadget);
> +		mutex_unlock(&udc_lock);
>  	}

What will happen if the gadget isn't deactivated, but it is started and 
VBUS power is prevent and the driver isn't unbinding, but the gadget 
driver decides to call usb_gadget_disconnect()?

>  
> -	ret = gadget->ops->pullup(gadget, 1);
> -	if (!ret)
> -		gadget->connected = 1;
> -
>  out:
>  	trace_usb_gadget_connect(gadget, ret);
>  
>  	return ret;
>  }
>  
> +static int usb_gadget_set_vbus(struct usb_gadget *gadget, bool vbus)
> +{
> +	int ret;
> +
> +	mutex_lock(&gadget->udc->connect_lock);
> +	gadget->udc->vbus = vbus;

Why does this have to be here?  What's wrong with setting vbus in 
interrupt context?

> +	ret = usb_gadget_pullup_update_locked(gadget);
> +	mutex_unlock(&gadget->udc->connect_lock);

Sorry, but at this point I'm getting tired of pointing out all the 
problems in this patch, so I'm going to stop here.

How about instead doing something really simple, like just make 
usb_udc_vbus_handler() queue up a work routine that calls 
usb_udc_connect_control()?  Just a minimal change that will be really 
easy to review.

Alan Stern



[Index of Archives]     [Linux Kernel]     [Kernel Development Newbies]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite Hiking]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux