Unfortunately, usb_set_configuration() is widely mis-used as a lightweight device reset. That's trouble because setting a configuration must sometimes involve things that don't relate at all to a light reset, and can't be done in contexts like driver probe() calls.
This patch updates most usb_set_configuration() users to use a call that provides more appropriate functionality:
- Adds a new usb_reset_configuration() call, which never needs to change very much usbcore state.
- Uses it to replace most usb_set_configuration() calls, in many serial drivers, hisax, dvb, irda, and so on.
- Modifies usb_reset_device() so it issues the control request directly. It's both more of a reset (hides a USB reset) and less of one (altsettings are unchanged).
- Makes usbfs return the error code instead of discarding it.
Once this goes in, then usb_set_configuration() can be made to work properly (including from sysfs).
Unfortunately I can't test this change (beyond compilation), since I don't have any of these devices. Please verify that this works, and merge if it does.
- Dave
--- 1.33/drivers/usb/core/message.c Fri Aug 1 05:03:12 2003 +++ edited/drivers/usb/core/message.c Mon Aug 11 14:56:25 2003 @@ -964,6 +964,55 @@ } /** + * usb_reset_configuration - lightweight device reset + * @dev: the device whose configuration is being reset + * + * This issues a standard SET_CONFIGURATION request to the device using + * the current configuration. The effect is to reset most USB-related + * state in the device, including interface altsettings (reset to zero), + * endpoint halts (cleared), and data toggle (only for bulk and interrupt + * endpoints). Other usbcore state is unchanged, including bindings of + * usb device drivers to interfaces. + * + * Because this affects multiple interfaces, avoid using this with composite + * (multi-interface) devices. Instead, the driver for each interface may + * use usb_set_interface() on the interfaces it claims. Resetting the whole + * configuration would affect other drivers' interfaces. + * + * Returns zero on success, else a negative error code. + */ +int usb_reset_configuration(struct usb_device *dev) +{ + int i, retval; + struct usb_host_config *config; + + for (i = 1; i < 16; ++i) { + usb_disable_endpoint(dev, i); + usb_disable_endpoint(dev, i + USB_DIR_IN); + } + + config = dev->actconfig; + retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + USB_REQ_SET_CONFIGURATION, 0, + config->desc.bConfigurationValue, 0, + NULL, 0, HZ * USB_CTRL_SET_TIMEOUT); + if (retval < 0) + return retval; + + dev->toggle[0] = dev->toggle[1] = 0; + dev->halted[0] = dev->halted[1] = 0; + + /* re-init hc/hcd interface/endpoint state */ + for (i = 0; i < config->desc.bNumInterfaces; i++) { + struct usb_interface *intf = config->interface[i]; + + intf->act_altsetting = 0; + usb_enable_interface(dev, intf); + } + return 0; +} + +/** * usb_set_configuration - Makes a particular device setting be current * @dev: the device whose configuration is being updated * @configuration: the configuration being chosen. @@ -1136,7 +1185,10 @@ EXPORT_SYMBOL(usb_get_status); EXPORT_SYMBOL(usb_get_string); EXPORT_SYMBOL(usb_string); + +// synchronous calls that also maintain usbcore state EXPORT_SYMBOL(usb_clear_halt); +EXPORT_SYMBOL(usb_reset_configuration); EXPORT_SYMBOL(usb_set_configuration); EXPORT_SYMBOL(usb_set_interface); --- 1.18/drivers/isdn/hisax/st5481_usb.c Sat Aug 9 05:19:09 2003 +++ edited/drivers/isdn/hisax/st5481_usb.c Mon Aug 11 14:56:55 2003 @@ -252,8 +252,8 @@ DBG(1,""); - if ((status = usb_set_configuration (dev,dev->config[0].desc.bConfigurationValue)) < 0) { - WARN("set_configuration failed,status=%d",status); + if ((status = usb_reset_configuration (dev)) < 0) { + WARN("reset_configuration failed,status=%d",status); return status; } --- 1.2/drivers/media/dvb/ttusb-budget/dvb-ttusb-budget.c Wed Aug 6 14:06:39 2003 +++ edited/drivers/media/dvb/ttusb-budget/dvb-ttusb-budget.c Mon Aug 11 14:56:25 2003 @@ -1017,7 +1017,7 @@ static int ttusb_setup_interfaces(struct ttusb *ttusb) { - usb_set_configuration(ttusb->dev, 1); + usb_reset_configuration(ttusb->dev); usb_set_interface(ttusb->dev, 1, 1); ttusb->bulk_out_pipe = usb_sndbulkpipe(ttusb->dev, 1); --- 1.42/drivers/net/irda/irda-usb.c Tue Aug 5 08:25:31 2003 +++ edited/drivers/net/irda/irda-usb.c Mon Aug 11 14:56:25 2003 @@ -1482,9 +1482,9 @@ goto err_out_2; } - /* Is this really necessary? */ - if (usb_set_configuration (dev, dev->config[0].desc.bConfigurationValue) < 0) { - err("set_configuration failed"); + /* Is this really necessary? (no, except maybe for broken devices) */ + if (usb_reset_configuration (dev) < 0) { + err("reset_configuration failed"); ret = -EIO; goto err_out_3; } --- 1.48/drivers/usb/class/cdc-acm.c Tue Jul 29 04:28:54 2003 +++ edited/drivers/usb/class/cdc-acm.c Mon Aug 11 14:56:25 2003 @@ -593,7 +593,14 @@ epwrite = &ifdata->endpoint[0].desc; } - usb_set_configuration(dev, cfacm->desc.bConfigurationValue); + /* FIXME don't scan every config. it's either correct + * when we probe(), or some other task must fix this. + */ + if (dev->actconfig != cfacm) { + err("need inactive config #%d", + cfacm->desc.bConfigurationValue); + return -ENODEV; + } for (minor = 0; minor < ACM_TTY_MINORS && acm_table[minor]; minor++); if (acm_table[minor]) { --- 1.52/drivers/usb/core/devio.c Thu Jul 31 09:22:43 2003 +++ edited/drivers/usb/core/devio.c Mon Aug 11 14:56:25 2003 @@ -762,9 +762,7 @@ if (get_user(u, (unsigned int __user *)arg)) return -EFAULT; - if (usb_set_configuration(ps->dev, u) < 0) - return -EINVAL; - return 0; + return usb_set_configuration(ps->dev, u); } static int proc_submiturb(struct dev_state *ps, void __user *arg) --- 1.76/drivers/usb/core/hub.c Wed Aug 6 04:52:20 2003 +++ edited/drivers/usb/core/hub.c Mon Aug 11 14:56:25 2003 @@ -1335,7 +1335,10 @@ kfree(descriptor); - ret = usb_set_configuration(dev, dev->actconfig->desc.bConfigurationValue); + ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + USB_REQ_SET_CONFIGURATION, 0, + dev->actconfig->desc.bConfigurationValue, 0, + NULL, 0, HZ * USB_CTRL_SET_TIMEOUT); if (ret < 0) { err("failed to set dev %s active configuration (error=%d)", dev->devpath, ret); --- 1.30/drivers/usb/media/dabusb.c Wed Jul 16 13:55:43 2003 +++ edited/drivers/usb/media/dabusb.c Mon Aug 11 14:56:25 2003 @@ -747,8 +747,8 @@ s->usbdev = usbdev; s->devnum = intf->minor; - if (usb_set_configuration (usbdev, usbdev->config[0].desc.bConfigurationValue) < 0) { - err("set_configuration failed"); + if (usb_reset_configuration (usbdev) < 0) { + err("reset_configuration failed"); goto reject; } if (usbdev->descriptor.idProduct == 0x2131) { --- 1.27/drivers/usb/media/stv680.c Thu May 29 13:30:29 2003 +++ edited/drivers/usb/media/stv680.c Mon Aug 11 14:56:25 2003 @@ -225,8 +225,9 @@ static int stv_set_config (struct usb_stv *dev, int configuration, int interface, int alternate) { - if (usb_set_configuration (dev->udev, configuration) < 0) { - PDEBUG (1, "STV(e): FAILED to set configuration %i", configuration); + if (configuration != dev->udev->actconfig->desc.bConfigurationValue + || usb_reset_configuration (dev->udev) < 0) { + PDEBUG (1, "STV(e): FAILED to reset configuration %i", configuration); return -1; } if (usb_set_interface (dev->udev, interface, alternate) < 0) { --- 1.20/drivers/usb/misc/tiglusb.c Tue Jun 17 08:12:48 2003 +++ edited/drivers/usb/misc/tiglusb.c Mon Aug 11 14:56:25 2003 @@ -57,7 +57,7 @@ static inline int clear_device (struct usb_device *dev) { - if (usb_set_configuration (dev, dev->config[0].desc.bConfigurationValue) < 0) { + if (usb_reset_configuration (dev) < 0) { err ("clear_device failed"); return -1; } @@ -343,8 +343,10 @@ && (dev->descriptor.idVendor != 0x451)) return -ENODEV; - if (usb_set_configuration (dev, dev->config[0].desc.bConfigurationValue) < 0) { - err ("tiglusb_probe: set_configuration failed"); + // NOTE: it's already in this config, this shouldn't be needed. + // is this working around some hardware bug? + if (usb_reset_configuration (dev) < 0) { + err ("tiglusb_probe: reset_configuration failed"); return -ENODEV; } --- 1.39/drivers/usb/serial/empeg.c Wed Jun 4 03:57:48 2003 +++ edited/drivers/usb/serial/empeg.c Mon Aug 11 14:56:25 2003 @@ -464,8 +464,13 @@ dbg("%s", __FUNCTION__); - dbg("%s - Set config to 1", __FUNCTION__); - r = usb_set_configuration (serial->dev, 1); + if (serial->dev->actconfig->desc.bConfigurationValue != 1) { + err("active config #%d != 1 ??", + serial->dev->actconfig->desc.bConfigurationValue); + return -ENODEV; + } + dbg("%s - reset config", __FUNCTION__); + r = usb_reset_configuration (serial->dev); /* continue on with initialization */ return r; --- 1.31/drivers/usb/serial/ipaq.c Sat Jul 12 03:25:54 2003 +++ edited/drivers/usb/serial/ipaq.c Mon Aug 11 14:56:25 2003 @@ -555,8 +555,12 @@ static int ipaq_startup(struct usb_serial *serial) { dbg("%s", __FUNCTION__); - usb_set_configuration(serial->dev, 1); - return 0; + if (serial->dev->actconfig->desc.bConfigurationValue != 1) { + err("active config #%d != 1 ??", + serial->dev->actconfig->desc.bConfigurationValue); + return -ENODEV; + } + return usb_reset_configuration (serial->dev); } static void ipaq_shutdown(struct usb_serial *serial) --- 1.66/drivers/usb/serial/visor.c Fri Aug 1 09:26:32 2003 +++ edited/drivers/usb/serial/visor.c Mon Aug 11 14:56:25 2003 @@ -763,8 +763,14 @@ dbg("%s", __FUNCTION__); - dbg("%s - Set config to 1", __FUNCTION__); - usb_set_configuration (serial->dev, 1); + if (serial->dev->actconfig->desc.bConfigurationValue != 1) { + err("active config #%d != 1 ??", + serial->dev->actconfig->desc.bConfigurationValue); + return -ENODEV; + } + dbg("%s - reset config", __FUNCTION__); + retval = usb_reset_configuration (serial->dev); + if (id->driver_info) { startup = (void *)id->driver_info; --- 1.74/drivers/usb/storage/usb.c Sun Jul 6 11:14:28 2003 +++ edited/drivers/usb/storage/usb.c Mon Aug 11 14:56:25 2003 @@ -921,9 +921,14 @@ if (us->protocol == US_PR_EUSB_SDDR09 || us->protocol == US_PR_DPCM_USB) { /* set the configuration -- STALL is an acceptable response here */ - result = usb_set_configuration(us->pusb_dev, 1); + if (us->pusb_dev->actconfig->desc.bConfigurationValue != 1) { + US_DEBUGP("active config #%d != 1 ??\n", us->pusb_dev + ->actconfig->desc.bConfigurationValue); + goto BadDevice; + } + result = usb_reset_configuration(us->pusb_dev); - US_DEBUGP("Result from usb_set_configuration is %d\n", result); + US_DEBUGP("Result of usb_reset_configuration is %d\n", result); if (result == -EPIPE) { US_DEBUGP("-- stall on control interface\n"); } else if (result != 0) { --- 1.84/include/linux/usb.h Thu Jul 31 09:22:43 2003 +++ edited/include/linux/usb.h Mon Aug 11 14:56:25 2003 @@ -869,6 +869,7 @@ /* wrappers that also update important state inside usbcore */ extern int usb_clear_halt(struct usb_device *dev, int pipe); +extern int usb_reset_configuration(struct usb_device *dev); extern int usb_set_configuration(struct usb_device *dev, int configuration); extern int usb_set_interface(struct usb_device *dev, int ifnum, int alternate); --- 1.43/sound/usb/usbaudio.c Fri Aug 1 10:52:20 2003 +++ edited/sound/usb/usbaudio.c Mon Aug 11 14:56:25 2003 @@ -2560,8 +2560,8 @@ err = usb_get_device_descriptor(dev); config = dev->actconfig; if (err < 0) snd_printdd("error usb_get_device_descriptor: %d\n", err); - err = usb_set_configuration(dev, get_cfg_desc(config)->bConfigurationValue); - if (err < 0) snd_printdd("error usb_set_configuration: %d\n", err); + err = usb_reset_configuration(dev); + if (err < 0) snd_printdd("error usb_reset_configuration: %d\n", err); snd_printdd("extigy_boot: new boot length = %d\n", get_cfg_desc(config)->wTotalLength); return -ENODEV; /* quit this anyway */ } @@ -2761,8 +2761,8 @@ * now look for an empty slot and create a new card instance */ /* first, set the current configuration for this device */ - if (usb_set_configuration(dev, get_cfg_desc(config)->bConfigurationValue) < 0) { - snd_printk(KERN_ERR "cannot set configuration (value 0x%x)\n", get_cfg_desc(config)->bConfigurationValue); + if (usb_reset_configuration(dev) < 0) { + snd_printk(KERN_ERR "cannot reset configuration (value 0x%x)\n", get_cfg_desc(config)->bConfigurationValue); goto __error; } for (i = 0; i < SNDRV_CARDS; i++)