ChangeSet 1.1608.24.34, 2004/03/03 11:16:57-08:00, stern@rowland.harvard.edu [PATCH] USB: Don't add/del interfaces, register/unregister them On Fri, 27 Feb 2004, Greg KH wrote: > On Wed, Feb 25, 2004 at 10:05:37AM -0500, Alan Stern wrote: > > > Why would anyone want to do this, you ask? Well the USB subsystem does it > > already. Each USB device can have several configurations, only one of > > which is active at any time. Corresponding to each configuration is a set > > of struct devices, and they (together with their embedded kobjects) are > > allocated and initialized when the USB device is first detected. The > > struct devices are add()'ed and del()'ed as configurations are activated > > and deactivated, leading to just the sort of call sequence shown above. > > Then we need to fix this. The driver model does not support repeated device_add(), device_del(), device_add(), device_del(), ... calls for the same device. But that's what happens to an interface's embedded struct device when we change configurations. Accordingly, this patch changes the device_add()/device_del() calls for interfaces to device_register()/device_unregister(). When the interface is unregistered the new code waits for the release method to run, so that it will be safe to re-register the interface should the former configuration be reinstated. Greg, please check this out to make sure I haven't made some dumb mistake. It works on my system and it fixes a memory leak in the USB system. drivers/usb/core/config.c | 9 ++------- drivers/usb/core/message.c | 16 ++++++++++++++-- include/linux/usb.h | 3 +++ 3 files changed, 19 insertions(+), 9 deletions(-) diff -Nru a/drivers/usb/core/config.c b/drivers/usb/core/config.c --- a/drivers/usb/core/config.c Tue Mar 16 15:04:45 2004 +++ b/drivers/usb/core/config.c Tue Mar 16 15:04:45 2004 @@ -72,13 +72,10 @@ return buffer - buffer0; } -static void usb_release_intf(struct device *dev) +static void usb_free_intf(struct usb_interface *intf) { - struct usb_interface *intf; int j; - intf = to_usb_interface(dev); - if (intf->altsetting) { for (j = 0; j < intf->num_altsetting; j++) { struct usb_host_interface *as = &intf->altsetting[j]; @@ -235,8 +232,6 @@ return -ENOMEM; } memset(interface, 0, sizeof(struct usb_interface)); - interface->dev.release = usb_release_intf; - device_initialize(&interface->dev); } /* Go through the descriptors, checking their length and counting the @@ -374,7 +369,7 @@ struct usb_interface *ifp = cf->interface[i]; if (ifp) - put_device(&ifp->dev); + usb_free_intf(ifp); } } kfree(dev->config); diff -Nru a/drivers/usb/core/message.c b/drivers/usb/core/message.c --- a/drivers/usb/core/message.c Tue Mar 16 15:04:45 2004 +++ b/drivers/usb/core/message.c Tue Mar 16 15:04:45 2004 @@ -793,6 +793,13 @@ } } +static void release_interface(struct device *dev) +{ + struct usb_interface *interface = to_usb_interface(dev); + + complete(interface->released); +} + /* * usb_disable_device - Disable all the endpoints for a USB device * @dev: the device whose endpoints are being disabled @@ -822,12 +829,16 @@ if (dev->actconfig) { for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) { struct usb_interface *interface; + struct completion intf_completion; /* remove this interface */ interface = dev->actconfig->interface[i]; dev_dbg (&dev->dev, "unregistering interface %s\n", interface->dev.bus_id); - device_del(&interface->dev); + init_completion (&intf_completion); + interface->released = &intf_completion; + device_unregister (&interface->dev); + wait_for_completion (&intf_completion); } dev->actconfig = 0; if (dev->state == USB_STATE_CONFIGURED) @@ -1145,6 +1156,7 @@ intf->dev.driver = NULL; intf->dev.bus = &usb_bus_type; intf->dev.dma_mask = dev->dev.dma_mask; + intf->dev.release = release_interface; sprintf (&intf->dev.bus_id[0], "%d-%s:%d.%d", dev->bus->busnum, dev->devpath, configuration, @@ -1153,7 +1165,7 @@ "registering %s (config #%d, interface %d)\n", intf->dev.bus_id, configuration, desc->bInterfaceNumber); - device_add (&intf->dev); + device_register (&intf->dev); usb_create_driverfs_intf_files (intf); } } diff -Nru a/include/linux/usb.h b/include/linux/usb.h --- a/include/linux/usb.h Tue Mar 16 15:04:45 2004 +++ b/include/linux/usb.h Tue Mar 16 15:04:45 2004 @@ -89,6 +89,8 @@ * number from the USB core by calling usb_register_dev(). * @dev: driver model's view of this device * @class_dev: driver model's class view of this device. + * @released: wait for the interface to be released when changing + * configurations. * * USB device drivers attach to interfaces on a physical device. Each * interface encapsulates a single high level function, such as feeding @@ -122,6 +124,7 @@ int minor; /* minor number this interface is bound to */ struct device dev; /* interface specific device info */ struct class_device *class_dev; + struct completion *released; /* wait for release */ }; #define to_usb_interface(d) container_of(d, struct usb_interface, dev) #define interface_to_usbdev(intf) \