# This is a BitKeeper generated patch for the following project: # Project Name: Linux kernel tree # This patch format is intended for GNU patch command version 2.5 or higher. # This patch includes the following deltas: # ChangeSet 1.739 -> 1.740 # drivers/usb/core/drivers.c 1.8 -> 1.9 # drivers/usb/class/printer.c 1.28 -> 1.29 # include/linux/usb.h 1.40 -> 1.41 # drivers/usb/core/devices.c 1.15 -> 1.16 # drivers/usb/core/hub.c 1.29 -> 1.30 # drivers/usb/core/devio.c 1.28 -> 1.29 # drivers/usb/core/usb.c 1.66 -> 1.67 # drivers/usb/media/pwc-if.c 1.22 -> 1.23 # drivers/usb/core/file.c 1.1 -> 1.2 # # The following is the BitKeeper ChangeSet Log # -------------------------------------------- # 02/07/18 oliver@neukum.name 1.740 # [PATCH] USB: lots of locking and other SMP race fixes # # This is a merge of a bunch of SMP and locking fixes for the USB code # that Oliver has sent me (greg k-h) over the past few weeks. # -------------------------------------------- # diff -Nru a/drivers/usb/class/printer.c b/drivers/usb/class/printer.c --- a/drivers/usb/class/printer.c Fri Jul 19 11:04:14 2002 +++ b/drivers/usb/class/printer.c Fri Jul 19 11:04:14 2002 @@ -655,7 +655,10 @@ (count - writecount) : USBLP_BUF_SIZE; if (copy_from_user(usblp->writeurb->transfer_buffer, buffer + writecount, - usblp->writeurb->transfer_buffer_length)) return -EFAULT; + usblp->writeurb->transfer_buffer_length)) { + up(&usblp->sem); + return writecount ? writecount : -EFAULT; + } usblp->writeurb->dev = usblp->dev; usblp->wcomplete = 0; diff -Nru a/drivers/usb/core/devices.c b/drivers/usb/core/devices.c --- a/drivers/usb/core/devices.c Fri Jul 19 11:04:14 2002 +++ b/drivers/usb/core/devices.c Fri Jul 19 11:04:14 2002 @@ -152,8 +152,8 @@ void usbdevfs_conn_disc_event(void) { - wake_up(&deviceconndiscwq); conndiscevcnt++; + wake_up(&deviceconndiscwq); } static const char *class_decode(const int class) @@ -239,6 +239,7 @@ if (start > end) return start; + lock_kernel(); /* driver might be unloaded */ start += sprintf(start, format_iface, desc->bInterfaceNumber, desc->bAlternateSetting, @@ -248,6 +249,7 @@ desc->bInterfaceSubClass, desc->bInterfaceProtocol, iface->driver ? iface->driver->name : "(none)"); + unlock_kernel(); return start; } @@ -597,6 +599,13 @@ unlock_kernel(); return POLLIN; } + + /* we may have dropped BKL - need to check for having lost the race */ + if (file->private_data) { + kfree(st); + goto lost_race; + } + /* * need to prevent the module from being unloaded, since * proc_unregister does not call the release method and @@ -606,6 +615,7 @@ file->private_data = st; mask = POLLIN; } +lost_race: if (file->f_mode & FMODE_READ) poll_wait(file, &deviceconndiscwq, wait); if (st->lastev != conndiscevcnt) diff -Nru a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c --- a/drivers/usb/core/devio.c Fri Jul 19 11:04:14 2002 +++ b/drivers/usb/core/devio.c Fri Jul 19 11:04:14 2002 @@ -361,14 +361,14 @@ if (intf >= 8*sizeof(ps->ifclaimed)) return -EINVAL; err = -EINVAL; - lock_kernel(); dev = ps->dev; + down(&dev->serialize); if (dev && test_and_clear_bit(intf, &ps->ifclaimed)) { iface = &dev->actconfig->interface[intf]; usb_driver_release_interface(&usbdevfs_driver, iface); err = 0; } - unlock_kernel(); + up(&dev->serialize); return err; } @@ -722,14 +722,11 @@ if (test_bit(i, &ps->ifclaimed)) continue; - if (intf->driver) { - const struct usb_device_id *id; - down(&intf->driver->serialize); - intf->driver->disconnect(ps->dev, intf->private_data); - id = usb_match_id(ps->dev,intf,intf->driver->id_table); - intf->driver->probe(ps->dev, i, id); - up(&intf->driver->serialize); + lock_kernel(); + if (intf->driver && ps->dev) { + usb_bind_driver(intf->driver,ps->dev, i); } + unlock_kernel(); } return 0; @@ -1092,16 +1089,17 @@ /* disconnect kernel driver from interface, leaving it unbound. */ case USBDEVFS_DISCONNECT: + /* this function is voodoo. without locking it is a maybe thing */ + lock_kernel(); driver = ifp->driver; if (driver) { - down (&driver->serialize); dbg ("disconnect '%s' from dev %d interface %d", driver->name, ps->dev->devnum, ctrl.ifno); - driver->disconnect (ps->dev, ifp->private_data); + usb_unbind_driver(ps->dev, ifp); usb_driver_release_interface (driver, ifp); - up (&driver->serialize); } else retval = -EINVAL; + unlock_kernel(); break; /* let kernel drivers try to (re)bind to the interface */ @@ -1111,18 +1109,28 @@ /* talk directly to the interface's driver */ default: + lock_kernel(); /* against module unload */ driver = ifp->driver; - if (driver == 0 || driver->ioctl == 0) - retval = -ENOSYS; - else { - if (ifp->driver->owner) + if (driver == 0 || driver->ioctl == 0) { + unlock_kernel(); + retval = -ENOSYS; + } else { + if (ifp->driver->owner) { __MOD_INC_USE_COUNT(ifp->driver->owner); + unlock_kernel(); + } /* ifno might usefully be passed ... */ retval = driver->ioctl (ps->dev, ctrl.ioctl_code, buf); /* size = min_t(int, size, retval)? */ - if (ifp->driver->owner) + if (ifp->driver->owner) { __MOD_DEC_USE_COUNT(ifp->driver->owner); + } else { + unlock_kernel(); + } } + + if (retval == -ENOIOCTLCMD) + retval = -ENOTTY; } /* cleanup and return */ @@ -1139,7 +1147,7 @@ static int usbdev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { struct dev_state *ps = (struct dev_state *)file->private_data; - int ret = -ENOIOCTLCMD; + int ret = -ENOTTY; if (!(file->f_mode & FMODE_WRITE)) return -EPERM; diff -Nru a/drivers/usb/core/drivers.c b/drivers/usb/core/drivers.c --- a/drivers/usb/core/drivers.c Fri Jul 19 11:04:14 2002 +++ b/drivers/usb/core/drivers.c Fri Jul 19 11:04:14 2002 @@ -66,6 +66,7 @@ start = page; end = page + (PAGE_SIZE - 100); pos = *ppos; + lock_kernel(); /* else drivers might be unloaded */ for (; tmp != &usb_driver_list; tmp = tmp->next) { struct usb_driver *driver = list_entry(tmp, struct usb_driver, driver_list); int minor = driver->fops ? driver->minor : -1; @@ -80,6 +81,7 @@ break; } } + unlock_kernel(); if (start == page) start += sprintf(start, "(none)\n"); len = start - page; diff -Nru a/drivers/usb/core/file.c b/drivers/usb/core/file.c --- a/drivers/usb/core/file.c Fri Jul 19 11:04:14 2002 +++ b/drivers/usb/core/file.c Fri Jul 19 11:04:14 2002 @@ -44,10 +44,13 @@ spin_lock (&minor_lock); c = usb_minors[minor]; - spin_unlock (&minor_lock); - if (!c || !(new_fops = fops_get(c))) + if (!c || !(new_fops = fops_get(c))) { + spin_unlock(&minor_lock); return err; + } + spin_unlock(&minor_lock); + old_fops = file->f_op; file->f_op = new_fops; /* Curiouser and curiouser... NULL ->open() as "no device" ? */ diff -Nru a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c --- a/drivers/usb/core/hub.c Fri Jul 19 11:04:14 2002 +++ b/drivers/usb/core/hub.c Fri Jul 19 11:04:14 2002 @@ -1046,8 +1046,6 @@ static int usb_hub_thread(void *__hub) { - lock_kernel(); - /* * This thread doesn't need any user-level access, * so get rid of all our resources @@ -1067,8 +1065,6 @@ } while (!signal_pending(current)); dbg("usb_hub_thread exiting"); - - unlock_kernel(); complete_and_exit(&khubd_exited, 0); } diff -Nru a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c --- a/drivers/usb/core/usb.c Fri Jul 19 11:04:14 2002 +++ b/drivers/usb/core/usb.c Fri Jul 19 11:04:14 2002 @@ -32,6 +32,7 @@ #include #include #include +#include #ifdef CONFIG_USB_DEBUG #define DEBUG @@ -117,6 +118,108 @@ up (&usb_bus_list_lock); } +/** + * usb_unbind_driver - disconnects a driver from a device + * @device: usb device to be disconnected + * @intf: interface of the device to be disconnected + * Context: BKL held + * + * Handles module usage count correctly + */ + +void usb_unbind_driver(struct usb_device *device, struct usb_interface *intf) +{ + struct usb_driver *driver; + void *priv; + int m; + + + driver = intf->driver; + priv = intf->private_data; + + if (!driver) + return; + + /* as soon as we increase the module use count we drop the BKL + before that we must not sleep */ + if (driver->owner) { + m = try_inc_mod_count(driver->owner); + if (m == 0) { + err("Dieing driver still bound to device.\n"); + return; + } + unlock_kernel(); + } + down(&driver->serialize); /* if we sleep here on an umanaged driver + the holder of the lock guards against + module unload */ + + driver->disconnect(device, priv); + + up(&driver->serialize); + if (driver->owner) { + lock_kernel(); + __MOD_DEC_USE_COUNT(driver->owner); + } +} + +/** + * usb_bind_driver - connect a driver to a device's interface + * @driver: device driver to be bound to a devices interface + * @dev: device to be bound + * @ifnum: index number of the interface to be used + * + * Does a save binding of a driver to a device's interface + * Returns a pointer to the drivers private description of the binding + */ + +void *usb_bind_driver(struct usb_driver *driver, struct usb_device *dev, unsigned int ifnum) +{ + int i,m; + void *private = NULL; + const struct usb_device_id *id; + struct usb_interface *interface; + + if (driver->owner) { + m = try_inc_mod_count(driver->owner); + if (m == 0) + return NULL; /* this horse is dead - don't ride*/ + unlock_kernel(); + } + + interface = &dev->actconfig->interface[ifnum]; + + id = driver->id_table; + /* new style driver? */ + if (id) { + for (i = 0; i < interface->num_altsetting; i++) { + interface->act_altsetting = i; + id = usb_match_id(dev, interface, id); + if (id) { + down(&driver->serialize); + private = driver->probe(dev,ifnum,id); + up(&driver->serialize); + if (private != NULL) + break; + } + } + + /* if driver not bound, leave defaults unchanged */ + if (private == NULL) + interface->act_altsetting = 0; + } else { /* "old style" driver */ + down(&driver->serialize); + private = driver->probe(dev, ifnum, NULL); + up(&driver->serialize); + } + if (driver->owner) { + lock_kernel(); + __MOD_DEC_USE_COUNT(driver->owner); + } + + return private; +} + /* * This function is part of a depth-first search down the device tree, * removing any instances of a device driver. @@ -136,18 +239,12 @@ if (!dev->actconfig) return; - + for (i = 0; i < dev->actconfig->bNumInterfaces; i++) { struct usb_interface *interface = &dev->actconfig->interface[i]; - + if (interface->driver == driver) { - if (driver->owner) - __MOD_INC_USE_COUNT(driver->owner); - down(&driver->serialize); - driver->disconnect(dev, interface->private_data); - up(&driver->serialize); - if (driver->owner) - __MOD_DEC_USE_COUNT(driver->owner); + usb_unbind_driver(dev, interface); /* if driver->disconnect didn't release the interface */ if (interface->driver) usb_driver_release_interface(driver, interface); @@ -163,7 +260,7 @@ /** * usb_deregister - unregister a USB driver * @driver: USB operations of the driver to unregister - * Context: !in_interrupt () + * Context: !in_interrupt (), must be called with BKL held * * Unlinks the specified driver from the internal USB driver list. * @@ -528,9 +625,7 @@ struct list_head *tmp; struct usb_interface *interface; void *private; - const struct usb_device_id *id; struct usb_driver *driver; - int i; if ((!dev) || (ifnum >= dev->actconfig->bNumInterfaces)) { err("bad find_interface_driver params"); @@ -545,37 +640,12 @@ goto out_err; private = NULL; + lock_kernel(); for (tmp = usb_driver_list.next; tmp != &usb_driver_list;) { driver = list_entry(tmp, struct usb_driver, driver_list); tmp = tmp->next; - if (driver->owner) - __MOD_INC_USE_COUNT(driver->owner); - id = driver->id_table; - /* new style driver? */ - if (id) { - for (i = 0; i < interface->num_altsetting; i++) { - interface->act_altsetting = i; - id = usb_match_id(dev, interface, id); - if (id) { - down(&driver->serialize); - private = driver->probe(dev,ifnum,id); - up(&driver->serialize); - if (private != NULL) - break; - } - } - - /* if driver not bound, leave defaults unchanged */ - if (private == NULL) - interface->act_altsetting = 0; - } else { /* "old style" driver */ - down(&driver->serialize); - private = driver->probe(dev, ifnum, NULL); - up(&driver->serialize); - } - if (driver->owner) - __MOD_DEC_USE_COUNT(driver->owner); + private = usb_bind_driver(driver, dev, ifnum); /* probe() may have changed the config on us */ interface = dev->actconfig->interface + ifnum; @@ -583,9 +653,11 @@ if (private) { usb_driver_claim_interface(driver, interface, private); up(&dev->serialize); + unlock_kernel(); return 0; } } + unlock_kernel(); out_err: up(&dev->serialize); @@ -1121,27 +1193,22 @@ info("USB disconnect on device %d", dev->devnum); + lock_kernel(); if (dev->actconfig) { for (i = 0; i < dev->actconfig->bNumInterfaces; i++) { struct usb_interface *interface = &dev->actconfig->interface[i]; struct usb_driver *driver = interface->driver; if (driver) { - if (driver->owner) - __MOD_INC_USE_COUNT(driver->owner); - down(&driver->serialize); - driver->disconnect(dev, interface->private_data); - up(&driver->serialize); + usb_unbind_driver(dev, interface); /* if driver->disconnect didn't release the interface */ if (interface->driver) usb_driver_release_interface(driver, interface); - /* we don't need the driver any longer */ - if (driver->owner) - __MOD_DEC_USE_COUNT(driver->owner); } /* remove our device node for this interface */ put_device(&interface->dev); } } + unlock_kernel(); /* Free up all the children.. */ for (i = 0; i < USB_MAXCHILDREN; i++) { @@ -1475,6 +1542,8 @@ EXPORT_SYMBOL(usb_reset_device); EXPORT_SYMBOL(usb_connect); EXPORT_SYMBOL(usb_disconnect); +EXPORT_SYMBOL(usb_bind_driver); +EXPORT_SYMBOL(usb_unbind_driver); EXPORT_SYMBOL(__usb_get_extra_descriptor); diff -Nru a/drivers/usb/media/pwc-if.c b/drivers/usb/media/pwc-if.c --- a/drivers/usb/media/pwc-if.c Fri Jul 19 11:04:14 2002 +++ b/drivers/usb/media/pwc-if.c Fri Jul 19 11:04:14 2002 @@ -1756,40 +1756,40 @@ pdev = (struct pwc_device *)ptr; if (pdev == NULL) { Err("pwc_disconnect() Called without private pointer.\n"); - return; + goto out_err; } if (pdev->udev == NULL) { Err("pwc_disconnect() already called for %p\n", pdev); - return; + goto out_err; } if (pdev->udev != udev) { Err("pwc_disconnect() Woops: pointer mismatch udev/pdev.\n"); - return; + goto out_err; } #ifdef PWC_MAGIC if (pdev->magic != PWC_MAGIC) { Err("pwc_disconnect() Magic number failed. Consult your scrolls and try again.\n"); - return; + goto out_err; } -#endif - +#endif + pdev->unplugged = 1; if (pdev->vdev != NULL) { - video_unregister_device(pdev->vdev); + video_unregister_device(pdev->vdev); if (pdev->vopen) { Info("Disconnected while device/video is open!\n"); - + /* Wake up any processes that might be waiting for a frame, let them return an error condition */ wake_up(&pdev->frameq); - + /* Wait until we get a 'go' from _close(). This used to have a gigantic race condition, since we kfree() - stuff here, but we have to wait until close() - is finished. + stuff here, but we have to wait until close() + is finished. */ - + Trace(TRACE_PROBE, "Sleeping on remove_ok.\n"); add_wait_queue(&pdev->remove_ok, &wait); set_current_state(TASK_UNINTERRUPTIBLE); @@ -1815,6 +1815,7 @@ device_hint[hint].pdev = NULL; pdev->udev = NULL; +out_err: unlock_kernel(); kfree(pdev); } diff -Nru a/include/linux/usb.h b/include/linux/usb.h --- a/include/linux/usb.h Fri Jul 19 11:04:14 2002 +++ b/include/linux/usb.h Fri Jul 19 11:04:14 2002 @@ -431,6 +431,10 @@ /* for when layers above USB add new non-USB drivers */ extern void usb_scan_devices(void); +/* for probe/disconnect with correct module usage counting */ +void *usb_bind_driver(struct usb_driver *driver, struct usb_device *dev, unsigned int ifnum); +void usb_unbind_driver(struct usb_device *device, struct usb_interface *intf); + /* mostly for devices emulating SCSI over USB */ extern int usb_reset_device(struct usb_device *dev);