This patch pushes two USB requests - USB_REQ_SET_INTERFACE - USB_REQ_SET_CONFIGURATION to be processed in process context. Both may perform some kind initialisation which is better solved in process context than in atomic. USB_REQ_SET_CONFIGURATION with config 0 also triggers disabling / resetting which causes the dequeue of all requests. Having them in process context will allow the UDC sleep and synchronize before it will return. Signed-off-by: Sebastian Andrzej Siewior <bigeasy@xxxxxxxxxxxxx> --- drivers/usb/gadget/composite.c | 115 +++++++++++++++++++++++++++++----------- include/linux/usb/composite.h | 2 + 2 files changed, 86 insertions(+), 31 deletions(-) diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index a95de6a..c30cf2a 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -673,23 +673,12 @@ static int set_config(struct usb_composite_dev *cdev, reset_config(cdev); goto done; } - - if (result == USB_GADGET_DELAYED_STATUS) { - DBG(cdev, - "%s: interface %d (%s) requested delayed status\n", - __func__, tmp, f->name); - cdev->delayed_status++; - DBG(cdev, "delayed_status count %d\n", - cdev->delayed_status); - } } /* when we return, be sure our power usage is valid */ power = c->bMaxPower ? (2 * c->bMaxPower) : CONFIG_USB_GADGET_VBUS_DRAW; done: usb_gadget_vbus_draw(gadget, power); - if (result >= 0 && cdev->delayed_status) - result = USB_GADGET_DELAYED_STATUS; return result; } @@ -1011,6 +1000,85 @@ static void composite_setup_complete(struct usb_ep *ep, struct usb_request *req) req->status, req->actual, req->length); } + +static void composite_delayed_setup(struct work_struct *wq) +{ + struct usb_composite_dev *cdev = container_of(wq, + struct usb_composite_dev, wq); + const struct usb_ctrlrequest *ctrl = &cdev->ctrl; + struct usb_function *f = NULL; + int value = -EINVAL; + u16 w_index; + u16 w_value; + u16 w_length; + u8 intf; + + w_index = le16_to_cpu(ctrl->wIndex); + w_value = le16_to_cpu(ctrl->wValue); + w_length = le16_to_cpu(ctrl->wLength); + intf = w_index & 0xFF; + + switch (ctrl->bRequest) { + case USB_REQ_SET_INTERFACE: + if (!cdev->config) + break; + f = cdev->config->interface[intf]; + if (!f) + break; + if (w_value && !f->set_alt) + break; + + value = f->set_alt(f, w_index, w_value); + break; + case USB_REQ_SET_CONFIGURATION: + if (gadget_is_otg(cdev->gadget)) { + if (cdev->gadget->a_hnp_support) + DBG(cdev, "HNP available\n"); + else if (cdev->gadget->a_alt_hnp_support) + DBG(cdev, "HNP on another port\n"); + else + VDBG(cdev, "HNP inactive\n"); + } + spin_lock_irq(&cdev->lock); + value = set_config(cdev, ctrl, w_value); + spin_unlock_irq(&cdev->lock); + } + + if (value == USB_GADGET_DELAYED_STATUS) { + /* delayed status requested again */ + DBG(cdev, + "%s: interface %d (%s) requested delayed status (again)\n", + __func__, intf, f ? f->name : NULL); + DBG(cdev, "delayed_status count %d\n", + cdev->delayed_status); + } else if (value < 0) { + /* an error occured, stall the endpoint */ + usb_ep_set_halt(cdev->gadget->ep0); + } else { + /* everything went according to the plan, resume */ + usb_composite_setup_continue(cdev); + } +} + +static int composite_delay_setup(struct usb_gadget *gadget, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = get_gadget_data(gadget); + u16 w_length = le16_to_cpu(ctrl->wLength); + u16 w_index = le16_to_cpu(ctrl->wIndex); + + if (w_length) { + WARN_ON(1); + return 0; + } + DBG(cdev, "interface %d requested delayed status\n", + w_index & 0xff); + cdev->delayed_status++; + cdev->ctrl = *ctrl; + queue_work(system_nrt_wq, &cdev->wq); + return USB_GADGET_DELAYED_STATUS; +} + /* * The setup() callback implements all the ep0 functionality that's * not handled lower down, in hardware or the hardware driver(like @@ -1103,17 +1171,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) case USB_REQ_SET_CONFIGURATION: if (ctrl->bRequestType != 0) goto unknown; - if (gadget_is_otg(gadget)) { - if (gadget->a_hnp_support) - DBG(cdev, "HNP available\n"); - else if (gadget->a_alt_hnp_support) - DBG(cdev, "HNP on another port\n"); - else - VDBG(cdev, "HNP inactive\n"); - } - spin_lock(&cdev->lock); - value = set_config(cdev, ctrl, w_value); - spin_unlock(&cdev->lock); + value = composite_delay_setup(gadget, ctrl); break; case USB_REQ_GET_CONFIGURATION: if (ctrl->bRequestType != USB_DIR_IN) @@ -1138,15 +1196,8 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) break; if (w_value && !f->set_alt) break; - value = f->set_alt(f, w_index, w_value); - if (value == USB_GADGET_DELAYED_STATUS) { - DBG(cdev, - "%s: interface %d (%s) requested delayed status\n", - __func__, intf, f->name); - cdev->delayed_status++; - DBG(cdev, "delayed_status count %d\n", - cdev->delayed_status); - } + + value = composite_delay_setup(gadget, ctrl); break; case USB_REQ_GET_INTERFACE: if (ctrl->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE)) @@ -1325,6 +1376,7 @@ composite_unbind(struct usb_gadget *gadget) * so there's no i/o concurrency that could affect the * state protected by cdev->lock. */ + flush_work(&cdev->wq); WARN_ON(cdev->config); while (!list_empty(&cdev->configs)) { @@ -1388,6 +1440,7 @@ static int composite_bind(struct usb_gadget *gadget) return status; spin_lock_init(&cdev->lock); + INIT_WORK(&cdev->wq, composite_delayed_setup); cdev->gadget = gadget; set_gadget_data(gadget, cdev); INIT_LIST_HEAD(&cdev->configs); diff --git a/include/linux/usb/composite.h b/include/linux/usb/composite.h index a316fba..be4c1b2 100644 --- a/include/linux/usb/composite.h +++ b/include/linux/usb/composite.h @@ -359,6 +359,8 @@ struct usb_composite_dev { * data/status stages till delayed_status is zero. */ int delayed_status; + struct work_struct wq; + struct usb_ctrlrequest ctrl; /* protects deactivations and delayed_status counts*/ spinlock_t lock; -- 1.7.8.3 -- 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