After a restless night, I've come up with some interesting ideas... First, let's acknowledge that in 2.6 we must require drivers not to interact with a device after their disconnect() has returned. Also, let's acknowledge that in the absence of significant changes to the SCSI core, if usb-storage has a device-reset pending then its disconnect() routine _can't_ return until the reset has finished. My scheme takes these things into account.
A key ingredient is to recognize that suspend/resume/reset don't need to lock the parent hub. They're only going to make use of one port, and it will suffice for the hub driver to serialize access to the port. In fact, since suspend/resume/reset all lock their device before doing anything, there's only going to be 1 contender for the port (apart from khubd itself). For now, let's assume that simultaneous actions on multiple ports won't cause problems. We can go back and change that if necessary. However, I can make a good argument that most or all of the port-related operations on a hub are standalone sorts of things, with no context to preserve. So a hub shouldn't have any difficulty doing one operation on one port, then another operation on another port. Serializing access to a port should be done without using a semaphore or other simple synchronization primitive, because a process waiting to use a port must be forced to abort with an error if the port's child device is disconnected. That's necessary to avoid deadlock with disconnect: khubd other thread ----- ------------ acquire port lock device wait for port process disconnect notification on the port mark device as STATE_NOTATTACHED wake up port waiters call usb_disconnect(): try to lock device wake up, see device is gone fail port acquisition unlock device proceed with disconnect Port acquisition should also fail if the hub's state changes from USB_STATE_CONFIGURED. Using this approach, there's no need for a thread holding a child's lock to acquire the parent's lock -- no bottom-up locking. Serializing access to a port can be done fairly easily: Each hub can have a 32-bit bitmask indicating which ports are in use. There can be a waitqueue of processes waiting for an in-use port; since there's not liable to be much contention a single global waitqueue should suffice for all devices. Access to the bitmasks and the waitqueue is protected by a spinlock. Next, let's consider the 3 paths for binding: (A) New device, khubd calls usb_set_configuration(), interfaces are probed. (B) Sysfs calls usb_set_configuration(), interfaces are unbound and then probed. (C) New driver is registered, driver model core probes all matching unbound interfaces. Unbinding is similar. Notice that during (A) the device's hub port has already been acquired, but during (B) and (C) it hasn't. Also, during (A) and (B) the device has been locked, but not during (C). For things to work properly, however, the device must be locked during binding. This is very difficult in (C) because any device might be probed. Solution: We'll effectively lock _all_ devices when (C) occurs. We do this by introducing a new global rwsem, driver_reg_rwsem, and a corresponding flag, driver_reg_locked. Instead of locking udev->serialize directly, we'll use two new subroutines in usb.c: void usb_lock_device(struct usb_device *udev) { down_read(&driver_reg_rwsem); down(&udev->serialize); } void usb_unlock_device(struct usb_device *udev) { up(&udev->serialize); up_read(&driver_reg_rwsem); } The usb_register() and usb_deregister() routines will include code like this: down_write(&driver_reg_rwsem); driver_reg_locked = 1; // Do the regular work driver_reg_locked = 0; up_write(&driver_reg_rwsem); This will have the desired effect. Not only that, we can tell from within a routine like usb_reset_device() whether or not case (C) occurred. It turns out we will also need to distinguish cases (A) and (B). To do that, we introduce new fields into struct usb_device and struct usb_interface (taking a suggestion of Dave's). The usb_device field can take on the values USB_DEVICE_ATTACHING, USB_DEVICE_ATTACHED, USB_DEVICE_DETACHING and the usb_interface field can take on the values USB_INTERFACE_UNBOUND, USB_INTERFACE_PROBING, USB_INTERFACE_BOUND, USB_INTEFACE_UNBINDING Now code in usbcore can tell exactly which path has been followed. Device resets are the hardest part to get right, because of the requirement that they not be allowed to block disconnect(). The key here is to change usb_reset_device's API: along with the usb_device the caller must also pass its usb_interface pointer. Then a polling loop much like what I discussed before can be used to acquire the device lock; if it fails whenever the interface is not PROBING or BOUND that will prevent any problems. In addition, usb_reset_device() must be aware of the context in which it is called. If the device is already locked (as it would be during probe()) we should avoid trying to lock it again. If the device's hub's port is already acquired (as it would be in (A)) we should avoid trying to acquire it again. But now all the information needed to make these determinations is available. This means the device resets will work even during probe(), with no other modifications needed. There's a lot of details involved, but this is the basic outline. In brief: acquire ports, not hubs; lock all devices during driver reg/dereg; store information to indicate the binding/unbinding pathway. Alan Stern ------------------------------------------------------- This SF.Net email is sponsored by Sleepycat Software Learn developer strategies Cisco, Motorola, Ericsson & Lucent use to deliver higher performing products faster, at low TCO. http://www.sleepycat.com/telcomwpreg.php?From=osdnemail3 _______________________________________________ [EMAIL PROTECTED] To unsubscribe, use the last form field at: https://lists.sourceforge.net/lists/listinfo/linux-usb-devel