Greg: This patch implements what I described earlier: allocating interfaces dynamically to avoid the problems involved in reusing them. It's a sizeable patch and careful testing is called for.
Several issues of varying importance came up while I was working on this. (1) The locking in devices.c needs to be fixed. The USB subsystem rwsem should be held over a much larger part of the code. That should be pretty easy to do. I don't think it's necessary to use usb_get_dev or to lock usbdev->serialize, but someone should verify this. (2) The exact relationships among usbdev->state, usbdev->actconfig, and the actual state of the physical device aren't clear. Various error scenarios can leave ->state disagreeing with the device or ->state equal to USB_STATE_CONFIGURED even though ->actconfig is NULL. This needs more thought. (3) I decided that it wasn't really necessary to make a deep copy of the altsetting and endpoint structures for the dynamic interfaces (those structures are read-only), so this patch just copies some pointers. Unfortunately this opens up a hole: A user program can pin an interface attribute file and use it to try reading an altsetting structure even after the device has been disconnected and the structure deallocated. I added a test to the attribute read method to prevent dereferencing a NULL pointer, but there remained the possibility of a race between using the pointer and setting it to NULL. So I added another semaphore to handle _that_. All this makes those attribute methods a lot bigger than they used to be. Alternative approaches include: making a deep copy after all, or adding a kref to the altsetting array (a little tricky to do since it is a variable-length array allocated as a single unit, so there's no logical place to store the kref). None of these possibilities is elegant, and I would welcome suggestions for improvements. Alan Stern ===== include/linux/usb.h 1.181 vs edited ===== --- 1.181/include/linux/usb.h Wed Mar 31 17:13:05 2004 +++ edited/include/linux/usb.h Fri Apr 9 12:00:22 2004 @@ -147,11 +147,35 @@ #define USB_MAXINTERFACES 32 /** + * struct usb_interface_cache - long-term representation of a device interface + * @altsetting: array of interface structures, one for each alternate + * setting that may be selected. Each one includes a set of + * endpoint configurations. They will be in no particular order. + * @num_altsetting: number of altsettings defined. + * + * These structures persist for the lifetime of a usb_device, unlike + * struct usb_interface (which persists only as long as its configuration + * is installed). The altsetting arrays can be accessed through these + * structures at any time, permitting comparison of configurations and + * providing support for the /proc/bus/usb/devices pseudo-file. + */ +struct usb_interface_cache { + /* array of alternate settings for this interface, + * stored in no particular order */ + struct usb_host_interface *altsetting; + unsigned num_altsetting; /* number of alternate settings */ +}; + +/** * struct usb_host_config - representation of a device's configuration * @desc: the device's configuration descriptor. - * @interface: array of usb_interface structures, one for each interface - * in the configuration. The number of interfaces is stored in - * desc.bNumInterfaces. + * @interface: array of pointers to usb_interface structures, one for each + * interface in the configuration. The number of interfaces is stored + * in desc.bNumInterfaces. These pointers are valid only while the + * the configuration is active. + * @intf_cache: pointer to array of usb_interface_cache structures, one + * for each interface in the configuration. These structures exist + * for the entire life of the device. * @extra: pointer to buffer containing all extra descriptors associated * with this configuration (those preceding the first interface * descriptor). @@ -184,6 +208,10 @@ /* the interfaces associated with this configuration, * stored in no particular order */ struct usb_interface *interface[USB_MAXINTERFACES]; + + /* Interface information available even when this is not the + * active configuration */ + struct usb_interface_cache *intf_cache; unsigned char *extra; /* Extra descriptors */ int extralen; ===== drivers/usb/core/config.c 1.33 vs edited ===== --- 1.33/drivers/usb/core/config.c Mon Mar 29 11:17:38 2004 +++ edited/drivers/usb/core/config.c Mon Apr 12 09:44:18 2004 @@ -96,19 +96,18 @@ return buffer - buffer0 + i; } -static void usb_free_intf(struct usb_interface *intf) +static void usb_free_intf_cache(struct usb_interface_cache *intfc) { int j; - if (intf->altsetting) { - for (j = 0; j < intf->num_altsetting; j++) { - struct usb_host_interface *alt = &intf->altsetting[j]; + if (intfc->altsetting) { + for (j = 0; j < intfc->num_altsetting; j++) { + struct usb_host_interface *alt = &intfc->altsetting[j]; kfree(alt->endpoint); } - kfree(intf->altsetting); + kfree(intfc->altsetting); } - kfree(intf); } static int usb_parse_interface(struct device *ddev, int cfgno, @@ -117,7 +116,7 @@ unsigned char *buffer0 = buffer; struct usb_interface_descriptor *d; int inum, asnum; - struct usb_interface *interface; + struct usb_interface_cache *intfc; struct usb_host_interface *alt; int i, n; int len, retval; @@ -137,16 +136,16 @@ if (inum >= config->desc.bNumInterfaces) goto skip_to_next_interface_descriptor; - interface = config->interface[inum]; + intfc = &config->intf_cache[inum]; asnum = d->bAlternateSetting; - if (asnum >= interface->num_altsetting) { + if (asnum >= intfc->num_altsetting) { dev_err(ddev, "config %d interface %d has an invalid " "alternate setting number: %d but max is %d\n", - cfgno, inum, asnum, interface->num_altsetting - 1); + cfgno, inum, asnum, intfc->num_altsetting - 1); return -EINVAL; } - alt = &interface->altsetting[asnum]; + alt = &intfc->altsetting[asnum]; if (alt->desc.bLength) { dev_err(ddev, "Duplicate descriptor for config %d " "interface %d altsetting %d\n", cfgno, inum, asnum); @@ -210,7 +209,7 @@ int cfgno; int nintf, nintf_orig; int i, j, n; - struct usb_interface *interface; + struct usb_interface_cache *intfc; unsigned char *buffer2; int size2; struct usb_descriptor_header *header; @@ -238,13 +237,11 @@ config->desc.bNumInterfaces = nintf = USB_MAXINTERFACES; } - for (i = 0; i < nintf; ++i) { - interface = config->interface[i] = - kmalloc(sizeof(struct usb_interface), GFP_KERNEL); - if (!interface) - return -ENOMEM; - memset(interface, 0, sizeof(struct usb_interface)); - } + len = sizeof(*intfc) * nintf; + config->intf_cache = intfc = kmalloc(len, GFP_KERNEL); + if (!intfc) + return -ENOMEM; + memset(intfc, 0, len); /* Go through the descriptors, checking their length and counting the * number of altsettings for each interface */ @@ -278,7 +275,7 @@ return -EINVAL; } if (i < nintf) - ++config->interface[i]->num_altsetting; + ++intfc[i].num_altsetting; } else if (header->bDescriptorType == USB_DT_DEVICE || header->bDescriptorType == USB_DT_CONFIG) { @@ -291,28 +288,26 @@ } /* for ((buffer2 = buffer, size2 = size); ...) */ /* Allocate the altsetting arrays */ - for (i = 0; i < nintf; ++i) { - interface = config->interface[i]; - if (interface->num_altsetting > USB_MAXALTSETTING) { + for (i = 0; i < nintf; (++i, ++intfc)) { + if (intfc->num_altsetting > USB_MAXALTSETTING) { dev_err(ddev, "too many alternate settings for " "config %d interface %d: %d, " "maximum allowed: %d\n", - cfgno, i, interface->num_altsetting, + cfgno, i, intfc->num_altsetting, USB_MAXALTSETTING); return -EINVAL; } - if (interface->num_altsetting == 0) { + if (intfc->num_altsetting == 0) { dev_err(ddev, "config %d has no interface number " "%d\n", cfgno, i); return -EINVAL; } - len = sizeof(*interface->altsetting) * - interface->num_altsetting; - interface->altsetting = kmalloc(len, GFP_KERNEL); - if (!interface->altsetting) + len = sizeof(*intfc->altsetting) * intfc->num_altsetting; + intfc->altsetting = kmalloc(len, GFP_KERNEL); + if (!intfc->altsetting) return -ENOMEM; - memset(interface->altsetting, 0, len); + memset(intfc->altsetting, 0, len); } /* Skip over any Class Specific or Vendor Specific descriptors; @@ -339,10 +334,10 @@ } /* Check for missing altsettings */ - for (i = 0; i < nintf; ++i) { - interface = config->interface[i]; - for (j = 0; j < interface->num_altsetting; ++j) { - if (!interface->altsetting[j].desc.bLength) { + intfc = config->intf_cache; + for (i = 0; i < nintf; (++i, ++intfc)) { + for (j = 0; j < intfc->num_altsetting; ++j) { + if (!intfc->altsetting[j].desc.bLength) { dev_err(ddev, "config %d interface %d has no " "altsetting %d\n", cfgno, i, j); return -EINVAL; @@ -373,11 +368,11 @@ for (c = 0; c < dev->descriptor.bNumConfigurations; c++) { struct usb_host_config *cf = &dev->config[c]; - for (i = 0; i < cf->desc.bNumInterfaces; i++) { - struct usb_interface *ifp = cf->interface[i]; + if (cf->intf_cache) { + for (i = 0; i < cf->desc.bNumInterfaces; i++) + usb_free_intf_cache(&cf->intf_cache[i]); - if (ifp) - usb_free_intf(ifp); + kfree(cf->intf_cache); } } kfree(dev->config); ===== drivers/usb/core/devices.c 1.29 vs edited ===== --- 1.29/drivers/usb/core/devices.c Tue Mar 30 11:50:40 2004 +++ edited/drivers/usb/core/devices.c Mon Apr 12 09:44:18 2004 @@ -232,13 +232,21 @@ return start; } -static char *usb_dump_interface_descriptor(char *start, char *end, const struct usb_interface *iface, int setno) +static char *usb_dump_interface_descriptor(char *start, char *end, + const struct usb_interface_cache *intfc, + const struct usb_interface *iface, + int setno) { - struct usb_interface_descriptor *desc = &iface->altsetting[setno].desc; + struct usb_interface_descriptor *desc = &intfc->altsetting[setno].desc; + char *driver_name = ""; if (start > end) return start; down_read(&usb_bus_type.subsys.rwsem); + if (iface) + driver_name = (iface->dev.driver + ? iface->dev.driver->name + : "(none)"); start += sprintf(start, format_iface, desc->bInterfaceNumber, desc->bAlternateSetting, @@ -247,9 +255,7 @@ class_decode(desc->bInterfaceClass), desc->bInterfaceSubClass, desc->bInterfaceProtocol, - iface->dev.driver - ? iface->dev.driver->name - : "(none)"); + driver_name); up_read(&usb_bus_type.subsys.rwsem); return start; } @@ -258,13 +264,14 @@ int speed, char *start, char *end, + const struct usb_interface_cache *intfc, const struct usb_interface *iface, int setno ) { - struct usb_host_interface *desc = &iface->altsetting[setno]; + struct usb_host_interface *desc = &intfc->altsetting[setno]; int i; - start = usb_dump_interface_descriptor(start, end, iface, setno); + start = usb_dump_interface_descriptor(start, end, intfc, iface, setno); for (i = 0; i < desc->desc.bNumEndpoints; i++) { if (start > end) return start; @@ -303,6 +310,7 @@ ) { int i, j; + struct usb_interface_cache *intfc; struct usb_interface *interface; if (start > end) @@ -310,15 +318,14 @@ if (!config) /* getting these some in 2.3.7; none in 2.3.6 */ return start + sprintf(start, "(null Cfg. desc.)\n"); start = usb_dump_config_descriptor(start, end, &config->desc, active); - for (i = 0; i < config->desc.bNumInterfaces; i++) { + intfc = config->intf_cache; + for (i = 0; i < config->desc.bNumInterfaces; (i++, intfc++)) { interface = config->interface[i]; - if (!interface) - break; - for (j = 0; j < interface->num_altsetting; j++) { + for (j = 0; j < intfc->num_altsetting; j++) { if (start > end) return start; start = usb_dump_interface(speed, - start, end, interface, j); + start, end, intfc, interface, j); } } return start; @@ -395,7 +402,7 @@ return start; start = usb_dump_device_strings (start, end, dev); - + for (i = 0; i < dev->descriptor.bNumConfigurations; i++) { if (start > end) return start; ===== drivers/usb/core/driverfs.c 1.19 vs edited ===== --- 1.19/drivers/usb/core/driverfs.c Wed Apr 7 19:49:16 2004 +++ edited/drivers/usb/core/driverfs.c Mon Apr 12 11:13:51 2004 @@ -22,6 +22,9 @@ #include "usb.h" +DECLARE_MUTEX(interface_attribute_sem); + + /* Active configuration fields */ #define usb_actconfig_show(field, multiplier, format_string) \ static ssize_t show_##field (struct device *dev, char *buf) \ @@ -204,8 +207,15 @@ show_##field (struct device *dev, char *buf) \ { \ struct usb_interface *intf = to_usb_interface (dev); \ + int rc; \ \ - return sprintf (buf, format_string, intf->cur_altsetting->desc.field); \ + down (&interface_attribute_sem); \ + rc = (intf->cur_altsetting \ + ? sprintf (buf, format_string, \ + intf->cur_altsetting->desc.field) \ + : -ENXIO); \ + up (&interface_attribute_sem); \ + return rc; \ } \ static DEVICE_ATTR(field, S_IRUGO, show_##field, NULL); ===== drivers/usb/core/message.c 1.80 vs edited ===== --- 1.80/drivers/usb/core/message.c Tue Apr 6 05:52:25 2004 +++ edited/drivers/usb/core/message.c Mon Apr 12 11:14:34 2004 @@ -796,10 +796,6 @@ } } -static void release_interface(struct device *dev) -{ -} - /* * usb_disable_device - Disable all the endpoints for a USB device * @dev: the device whose endpoints are being disabled @@ -834,7 +830,15 @@ interface = dev->actconfig->interface[i]; dev_dbg (&dev->dev, "unregistering interface %s\n", interface->dev.bus_id); - device_unregister (&interface->dev); + device_del (&interface->dev); + + /* Nobody should use these pointers any more */ + down (&interface_attribute_sem); + interface->altsetting = interface->cur_altsetting = + NULL; + up (&interface_attribute_sem); + dev->actconfig->interface[i] = NULL; + put_device (&interface->dev); } dev->actconfig = 0; if (dev->state == USB_STATE_CONFIGURED) @@ -1071,6 +1075,13 @@ return 0; } +static void release_interface(struct device *dev) +{ + struct usb_interface *intf = to_usb_interface(dev); + + kfree(intf); +} + /* * usb_set_configuration - Makes a particular device setting be current * @dev: the device whose configuration is being updated @@ -1109,19 +1120,18 @@ { int i, ret; struct usb_host_config *cp = NULL; - + struct usb_interface_cache *intfc; + /* dev->serialize guards all config changes */ - for (i=0; i<dev->descriptor.bNumConfigurations; i++) { + for (i = 0; i < dev->descriptor.bNumConfigurations; i++) { if (dev->config[i].desc.bConfigurationValue == configuration) { cp = &dev->config[i]; break; } } - if ((!cp && configuration != 0)) { - ret = -EINVAL; - goto out; - } + if ((!cp && configuration != 0)) + return -EINVAL; /* The USB spec says configuration 0 means unconfigured. * But if a device includes a configuration numbered 0, @@ -1139,72 +1149,84 @@ 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) - goto out; + return ret; dev->actconfig = cp; - if (!cp) + if (!cp) { dev->state = USB_STATE_ADDRESS; - else { - dev->state = USB_STATE_CONFIGURED; + return 0; + } - /* 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; i < cp->desc.bNumInterfaces; ++i) { - struct usb_interface *intf = cp->interface[i]; - struct usb_host_interface *alt; - - alt = usb_altnum_to_altsetting(intf, 0); - - /* No altsetting 0? We'll assume the first altsetting. - * We could use a GetInterface call, but if a device is - * so non-compliant that it doesn't have altsetting 0 - * then I wouldn't trust its reply anyway. - */ - if (!alt) - alt = &intf->altsetting[0]; - - intf->cur_altsetting = alt; - 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; - intf->dev.release = release_interface; - device_initialize (&intf->dev); - sprintf (&intf->dev.bus_id[0], "%d-%s:%d.%d", - dev->bus->busnum, dev->devpath, - configuration, - alt->desc.bInterfaceNumber); + dev->state = USB_STATE_CONFIGURED; + + /* re-initialize hc/hcd/usbcore interface/endpoint state. + * this triggers binding of drivers to interfaces; and + * maybe probe() calls will choose different altsettings. + */ + intfc = cp->intf_cache; + for (i = 0; i < cp->desc.bNumInterfaces; (++i, ++intfc)) { + struct usb_interface *intf; + struct usb_host_interface *alt; + + cp->interface[i] = intf = kmalloc(sizeof(*intf), GFP_KERNEL); + if (!intf) { + dev_err(&dev->dev, "Out of memory"); + for (; i >= 0; --i) { + kfree(cp->interface[i]); + cp->interface[i] = NULL; + } + dev->actconfig = NULL; + return -ENOMEM; } - /* Now that all interfaces are setup, probe() calls - * may claim() any interface that's not yet bound. - * Many class drivers need that: CDC, audio, video, etc. + memset(intf, 0, sizeof(*intf)); + intf->altsetting = intfc->altsetting; + intf->num_altsetting = intfc->num_altsetting; + + alt = usb_altnum_to_altsetting(intf, 0); + + /* No altsetting 0? We'll assume the first altsetting. + * We could use a GetInterface call, but if a device is + * so non-compliant that it doesn't have altsetting 0 + * then I wouldn't trust its reply anyway. */ - for (i = 0; i < cp->desc.bNumInterfaces; ++i) { - struct usb_interface *intf = cp->interface[i]; - struct usb_interface_descriptor *desc; - - desc = &intf->altsetting [0].desc; - dev_dbg (&dev->dev, - "adding %s (config #%d, interface %d)\n", - intf->dev.bus_id, configuration, - desc->bInterfaceNumber); - ret = device_add (&intf->dev); - if (ret != 0) { - dev_err(&dev->dev, - "device_add(%s) --> %d\n", - intf->dev.bus_id, - ret); - continue; - } - usb_create_driverfs_intf_files (intf); + if (!alt) + alt = &intf->altsetting[0]; + + intf->cur_altsetting = alt; + 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; + intf->dev.release = release_interface; + device_initialize (&intf->dev); + sprintf (&intf->dev.bus_id[0], "%d-%s:%d.%d", + dev->bus->busnum, dev->devpath, + configuration, alt->desc.bInterfaceNumber); + } + + /* Now that all interfaces are setup, probe() calls + * may claim() any interface that's not yet bound. + * Many class drivers need that: CDC, audio, video, etc. + */ + for (i = 0; i < cp->desc.bNumInterfaces; ++i) { + struct usb_interface *intf = cp->interface[i]; + struct usb_interface_descriptor *desc; + + desc = &intf->altsetting [0].desc; + dev_dbg (&dev->dev, "adding %s (config #%d, interface %d)\n", + intf->dev.bus_id, configuration, + desc->bInterfaceNumber); + ret = device_add (&intf->dev); + if (ret != 0) { + dev_err(&dev->dev, "device_add(%s) --> %d\n", + intf->dev.bus_id, ret); + continue; } + usb_create_driverfs_intf_files (intf); } -out: return ret; } ===== drivers/usb/core/usb.c 1.260 vs edited ===== --- 1.260/drivers/usb/core/usb.c Wed Mar 31 17:13:05 2004 +++ edited/drivers/usb/core/usb.c Mon Apr 12 09:44:19 2004 @@ -1193,8 +1193,7 @@ /* heuristic: Linux is more likely to have class * drivers, so avoid vendor-specific interfaces. */ - desc = &dev->config[i].interface[0] - ->altsetting->desc; + desc = &dev->config[i].intf_cache[0].altsetting->desc; if (desc->bInterfaceClass == USB_CLASS_VENDOR_SPEC) continue; /* COMM/2/all is CDC ACM, except 0xff is MSFT RNDIS */ ===== drivers/usb/core/usb.h 1.8 vs edited ===== --- 1.8/drivers/usb/core/usb.h Tue Mar 30 04:06:12 2004 +++ edited/drivers/usb/core/usb.h Mon Apr 12 09:48:56 2004 @@ -21,3 +21,6 @@ /* for labeling diagnostics */ extern const char *usbcore_name; + +/* protect interface->cur_altsetting for sysfs attributes */ +extern struct semaphore interface_attribute_sem; ------------------------------------------------------- This SF.Net email is sponsored by: IBM Linux Tutorials Free Linux tutorial presented by Daniel Robbins, President and CEO of GenToo technologies. Learn everything from fundamentals to system administration.http://ads.osdn.com/?ad_id=1470&alloc_id=3638&op=click _______________________________________________ [EMAIL PROTECTED] To unsubscribe, use the last form field at: https://lists.sourceforge.net/lists/listinfo/linux-usb-devel