diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 49cfd7928a1c..333b22c68aa4 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -1493,6 +1493,65 @@ free_interfaces: return 0; } +struct set_config_request { + struct usb_device *udev; + int config; + struct work_struct work; +}; + +/* Worker routine for usb_driver_set_configuration() */ +static void driver_set_config_work(void *_req) +{ + struct set_config_request *req = _req; + + usb_lock_device(req->udev); + usb_set_configuration(req->udev, req->config); + usb_unlock_device(req->udev); + usb_put_dev(req->udev); + kfree(req); +} + +/** + * usb_driver_set_configuration - Provide a way for drivers to change device configurations + * @udev: the device whose configuration is being updated + * @config: the configuration being chosen. + * Context: In process context, must be able to sleep + * + * Device interface drivers are not allowed to change device configurations. + * This is because changing configurations will destroy the interface the + * driver is bound to and create new ones; it would be like a floppy-disk + * driver telling the computer to replace the floppy-disk drive with a + * tape drive! + * + * Still, in certain specialized circumstances the need may arise. This + * routine gets around the normal restrictions by using a work thread to + * submit the change-config request. + * + * Returns 0 if the request was succesfully queued, error code otherwise. + * The caller has no way to know whether the queued request will eventually + * succeed. + */ +int usb_driver_set_configuration(struct usb_device *udev, int config) +{ + struct set_config_request *req; + + req = kmalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + req->udev = udev; + req->config = config; + INIT_WORK(&req->work, driver_set_config_work, req); + + usb_get_dev(udev); + if (!schedule_work(&req->work)) { + usb_put_dev(udev); + kfree(req); + return -EINVAL; + } + return 0; +} +EXPORT_SYMBOL_GPL(usb_driver_set_configuration); + // synchronous request completion model EXPORT_SYMBOL(usb_control_msg); EXPORT_SYMBOL(usb_bulk_msg); diff --git a/include/linux/usb.h b/include/linux/usb.h index 26d8a5f36896..f104efa04d79 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -1099,6 +1099,9 @@ extern int usb_clear_halt(struct usb_device *dev, int pipe); extern int usb_reset_configuration(struct usb_device *dev); extern int usb_set_interface(struct usb_device *dev, int ifnum, int alternate); +/* this request isn't really synchronous, but it belongs with the others */ +extern int usb_driver_set_configuration(struct usb_device *udev, int config); + /* * timeouts, in milliseconds, used for sending/receiving control messages * they typically complete within a few frames (msec) after they're issued