This patch is an update of one I sent around with 2.5.70, when the kernel wasn't quite ready to do this. It resolves some bugs in the way changing device configurations were reflected in the driver model:
- Moves code around so that usb_set_configuration() updates sysfs to reflect the current configuration. Previously, that only worked right for the initial configuration chosen by khubd.
* The old interfaces are all gone. The code to handle this moved from usb_disconnect() into usb_disable_device(), which is called both on disconnect and set_configuration paths.
* There are new interfaces. The code to handle this moved from usb_new_device() into usb_set_configuration().
Its kerneldoc is updated appropriately. The main point being that calling this in driver probe() methods is even more of a no-no, since it'll self-deadlock.
- Moves usb_new_interface() code around a bit, so that the device is visible in sysfs before usb_set_configuration() is called.
- Makes the bConfigurationValue be writable through sysfs, so device configurations can be easily changed from user mode.
I think this is ready to merge, and that it should be merged unless someone notices a significant problem.
- Dave
--- 1.34/drivers/usb/core/message.c Mon Aug 11 07:56:25 2003 +++ edited/drivers/usb/core/message.c Wed Aug 13 13:50:07 2003 @@ -788,18 +788,39 @@ * @skip_ep0: 0 to disable endpoint 0, 1 to skip it. * * Disables all the device's endpoints, potentially including endpoint 0. - * Deallocates hcd/hardware state for the endpoints ... and nukes all - * pending urbs. + * Deallocates hcd/hardware state for the endpoints (nuking all or most + * pending urbs) and usbcore state for the interfaces, so that usbcore + * must usb_set_configuration() before any interfaces could be used. */ void usb_disable_device(struct usb_device *dev, int skip_ep0) { int i; - dbg("nuking URBs for device %s", dev->dev.bus_id); + dev_dbg(&dev->dev, "%s nuking %s URBs\n", __FUNCTION__, + skip_ep0 ? "non-ep0" : "all"); for (i = skip_ep0; i < 16; ++i) { usb_disable_endpoint(dev, i); usb_disable_endpoint(dev, i + USB_DIR_IN); } + dev->toggle[0] = dev->toggle[1] = 0; + dev->halted[0] = dev->halted[1] = 0; + + /* getting rid of interfaces will disconnect + * any drivers bound to them (a key side effect) + */ + if (dev->actconfig) { + for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) { + struct usb_interface *interface; + + /* remove this interface */ + interface = dev->actconfig->interface[i]; + dev_dbg (&dev->dev, "unregistering interface %s\n", + interface->dev.bus_id); + device_unregister(&interface->dev); + } + dev->actconfig = 0; + } + dev->state = USB_STATE_ADDRESS; } @@ -1019,28 +1040,33 @@ * Context: !in_interrupt () * * This is used to enable non-default device modes. Not all devices - * support this kind of configurability. By default, configuration - * zero is selected after enumeration; many devices only have a single + * support this kind of configurability; many devices only have one * configuration. * - * USB devices may support one or more configurations, which affect + * USB device configuration affect * power consumption and the functionality available. For example, * the default configuration is limited to using 100mA of bus power, * so that when certain device functionality requires more power, - * and the device is bus powered, that functionality will be in some + * and the device is bus powered, that functionality should be in some * non-default device configuration. Other device modes may also be * reflected as configuration options, such as whether two ISDN * channels are presented as independent 64Kb/s interfaces or as one - * bonded 128Kb/s interface. + * bonded 128Kb/s interface; or whether the device uses standard protocols + * (maybe CDC Ethernet) or proprietary ones (maybe RNDIS). * * Note that USB has an additional level of device configurability, * associated with interfaces. That configurability is accessed using * usb_set_interface(). * - * This call is synchronous, and may not be used in an interrupt context. + * This call is synchronous. The calling context must be able to sleep, + * and must not hold the driver model lock for USB; usb device driver + * probe() methods may not use this routine. * * Returns zero on success, or else the status code returned by the - * underlying usb_control_msg() call. + * underlying call that failed. On succesful completion, each interface + * in the original device configuration has been destroied, and each one + * in the new configuration has been probed by all relevant usb device + * drivers currently known to the kernel. */ int usb_set_configuration(struct usb_device *dev, int configuration) { @@ -1058,29 +1084,51 @@ return -EINVAL; } - /* if it's already configured, clear out old state first. */ + /* dev->serialize guards all config changes */ + down(&dev->serialize); + + /* if it's already configured, clear out old state first. + * getting rid of old interfaces means unbinding their drivers. + */ if (dev->state != USB_STATE_ADDRESS) usb_disable_device (dev, 1); // Skip ep0 - dev->toggle[0] = dev->toggle[1] = 0; - dev->halted[0] = dev->halted[1] = 0; - dev->state = USB_STATE_ADDRESS; if ((ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), USB_REQ_SET_CONFIGURATION, 0, configuration, 0, - NULL, 0, HZ * USB_CTRL_SET_TIMEOUT)) < 0) + NULL, 0, HZ * USB_CTRL_SET_TIMEOUT)) < 0) { + up(&dev->serialize); return ret; + } if (configuration) dev->state = USB_STATE_CONFIGURED; dev->actconfig = cp; - /* reset more hc/hcd interface/endpoint state */ - for (i = 0; i < cp->desc.bNumInterfaces; ++i) { + /* re-initialize hc/hcd/usbcore interface/endpoint state. + * this triggers binding of drivers to interfaces; and + * maybe probe() calls will choose different altsettings. + */ + for (i = 0; cp && i < cp->desc.bNumInterfaces; ++i) { struct usb_interface *intf = cp->interface[i]; + struct usb_interface_descriptor *desc; intf->act_altsetting = 0; + desc = &intf->altsetting [0].desc; usb_enable_interface(dev, intf); + + intf->dev.parent = &dev->dev; + intf->dev.driver = NULL; + intf->dev.bus = &usb_bus_type; + intf->dev.dma_mask = dev->dev.dma_mask; + sprintf (&intf->dev.bus_id[0], "%d-%s:%d", + dev->bus->busnum, dev->devpath, + desc->bInterfaceNumber); + dev_dbg (&dev->dev, "registering interface %s\n", + intf->dev.bus_id); + device_add (&intf->dev); + usb_create_driverfs_intf_files (intf); } + up(&dev->serialize); return 0; } --- 1.136/drivers/usb/core/usb.c Mon Aug 11 16:09:09 2003 +++ edited/drivers/usb/core/usb.c Wed Aug 13 13:52:50 2003 @@ -906,21 +906,11 @@ usb_disconnect(child); } - /* deallocate hcd/hardware state ... and nuke all pending urbs */ + /* deallocate hcd/hardware state ... nuking all pending urbs and + * cleaning up all state associated with the current configuration + */ usb_disable_device(dev, 0); - /* disconnect() drivers from interfaces (a key side effect) */ - dev_dbg (&dev->dev, "unregistering interfaces\n"); - if (dev->actconfig) { - for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) { - struct usb_interface *interface; - - /* remove this interface */ - interface = dev->actconfig->interface[i]; - device_unregister(&interface->dev); - } - } - dev_dbg (&dev->dev, "unregistering device\n"); /* Free the device number and remove the /proc/bus/usb entry */ if (dev->devnum > 0) { @@ -1088,27 +1078,12 @@ err = usb_get_configuration(dev); if (err < 0) { - dev_err(&dev->dev, "unable to get device %d configuration (error=%d)\n", - dev->devnum, err); + dev_err(&dev->dev, "can't read configurations, error %d\n", + err); goto fail; } - /* choose and set the configuration here */ - if (dev->descriptor.bNumConfigurations != 1) { - dev_info(&dev->dev, - "configuration #%d chosen from %d choices\n", - dev->config[0].desc.bConfigurationValue, - dev->descriptor.bNumConfigurations); - } - err = usb_set_configuration(dev, dev->config[0].desc.bConfigurationValue); - if (err) { - dev_err(&dev->dev, "failed to set device %d default configuration (error=%d)\n", - dev->devnum, err); - goto fail; - } - - /* USB device state == configured ... tell the world! */ - + /* Tell the world! */ dev_dbg(&dev->dev, "new device strings: Mfr=%d, Product=%d, SerialNumber=%d\n", dev->descriptor.iManufacturer, dev->descriptor.iProduct, dev->descriptor.iSerialNumber); @@ -1121,30 +1096,30 @@ usb_show_string(dev, "SerialNumber", dev->descriptor.iSerialNumber); #endif - /* put into sysfs, with device and config specific files */ + /* put device-specific files into sysfs */ err = device_add (&dev->dev); if (err) goto fail; usb_create_driverfs_dev_files (dev); - /* Register all of the interfaces for this device with the driver core. - * Remember, interfaces get bound to drivers, not devices. */ - for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) { - struct usb_interface *interface = dev->actconfig->interface[i]; - struct usb_interface_descriptor *desc; - - desc = &interface->altsetting [interface->act_altsetting].desc; - interface->dev.parent = &dev->dev; - interface->dev.driver = NULL; - interface->dev.bus = &usb_bus_type; - interface->dev.dma_mask = parent->dma_mask; - sprintf (&interface->dev.bus_id[0], "%d-%s:%d", - dev->bus->busnum, dev->devpath, - desc->bInterfaceNumber); - dev_dbg (&dev->dev, "%s - registering interface %s\n", __FUNCTION__, interface->dev.bus_id); - device_add (&interface->dev); - usb_create_driverfs_intf_files (interface); + /* choose and set the configuration. that registers the interfaces + * with the driver core, and lets usb device drivers bind to them. + */ + if (dev->descriptor.bNumConfigurations != 1) { + dev_info(&dev->dev, + "configuration #%d chosen from %d choices\n", + dev->config[0].desc.bConfigurationValue, + dev->descriptor.bNumConfigurations); + } + err = usb_set_configuration(dev, + dev->config[0].desc.bConfigurationValue); + if (err) { + dev_err(&dev->dev, "can't set config #%d, error %d\n", + dev->config[0].desc.bConfigurationValue, err); + goto fail; } + + /* USB device state == configured ... usable */ /* add a /proc/bus/usb entry */ usbfs_add_device(dev); --- 1.6/drivers/usb/core/driverfs.c Wed Feb 26 03:17:52 2003 +++ edited/drivers/usb/core/driverfs.c Wed Aug 13 13:37:32 2003 @@ -23,21 +23,43 @@ #include "usb.h" /* Active configuration fields */ -#define usb_actconfig_attr(field, format_string) \ +#define usb_actconfig_show(field, format_string) \ static ssize_t \ show_##field (struct device *dev, char *buf) \ { \ struct usb_device *udev; \ \ udev = to_usb_device (dev); \ + if (udev->actconfig) \ return sprintf (buf, format_string, udev->actconfig->desc.field); \ + else return 0; \ } \ + +#define usb_actconfig_attr(field, format_string) \ +usb_actconfig_show(field,format_string) \ static DEVICE_ATTR(field, S_IRUGO, show_##field, NULL); usb_actconfig_attr (bNumInterfaces, "%2d\n") -usb_actconfig_attr (bConfigurationValue, "%2d\n") usb_actconfig_attr (bmAttributes, "%2x\n") usb_actconfig_attr (bMaxPower, "%3dmA\n") + +/* configuration value is always present, and r/w */ +usb_actconfig_show(bConfigurationValue,"%u\n"); + +static ssize_t +set_bConfigurationValue (struct device *dev, const char *buf, size_t count) +{ + struct usb_device *udev = udev = to_usb_device (dev); + int config, value; + + if (sscanf (buf, "%u", &config) != 1 || config > 255) + return -EINVAL; + value = usb_set_configuration (udev, config); + return (value < 0) ? value : count; +} + +static DEVICE_ATTR(bConfigurationValue, S_IRUGO | S_IWUSR, + show_bConfigurationValue, set_bConfigurationValue); /* String fields */ static ssize_t show_product (struct device *dev, char *buf)