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

Reply via email to