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