Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 127229
b: refs/heads/master
c: dc023dc
h: refs/heads/master
i:
  127227: 46a2035
v: v3
  • Loading branch information
Inaky Perez-Gonzalez authored and Greg Kroah-Hartman committed Jan 7, 2009
1 parent 01a2f94 commit 0e454a8
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 3 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: 9ac39f28b5237a629e41ccfc1f73d3a55723045c
refs/heads/master: dc023dceec861c60bc1d1a17a2c6496ddac26ee7
23 changes: 21 additions & 2 deletions trunk/drivers/usb/core/driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,20 @@ static int usb_unbind_device(struct device *dev)
return 0;
}

/*
* Cancel any pending scheduled resets
*
* [see usb_queue_reset_device()]
*
* Called after unconfiguring / when releasing interfaces. See
* comments in __usb_queue_reset_device() regarding
* udev->reset_running.
*/
static void usb_cancel_queued_reset(struct usb_interface *iface)
{
if (iface->reset_running == 0)
cancel_work_sync(&iface->reset_ws);
}

/* called from driver core with dev locked */
static int usb_probe_interface(struct device *dev)
Expand Down Expand Up @@ -242,6 +256,7 @@ static int usb_probe_interface(struct device *dev)
mark_quiesced(intf);
intf->needs_remote_wakeup = 0;
intf->condition = USB_INTERFACE_UNBOUND;
usb_cancel_queued_reset(intf);
} else
intf->condition = USB_INTERFACE_BOUND;

Expand Down Expand Up @@ -272,6 +287,7 @@ static int usb_unbind_interface(struct device *dev)
usb_disable_interface(udev, intf);

driver->disconnect(intf);
usb_cancel_queued_reset(intf);

/* Reset other interface state.
* We cannot do a Set-Interface if the device is suspended or
Expand Down Expand Up @@ -380,8 +396,10 @@ void usb_driver_release_interface(struct usb_driver *driver,
if (device_is_registered(dev)) {
iface->condition = USB_INTERFACE_UNBINDING;
device_release_driver(dev);
} else {
iface->condition = USB_INTERFACE_UNBOUND;
usb_cancel_queued_reset(iface);
}

dev->driver = NULL;
usb_set_intfdata(iface, NULL);

Expand Down Expand Up @@ -942,7 +960,8 @@ static int usb_suspend_interface(struct usb_device *udev,
if (udev->state == USB_STATE_NOTATTACHED || !is_active(intf))
goto done;

if (intf->condition == USB_INTERFACE_UNBOUND) /* This can't happen */
/* This can happen; see usb_driver_release_interface() */
if (intf->condition == USB_INTERFACE_UNBOUND)
goto done;
driver = to_usb_driver(intf->dev.driver);

Expand Down
43 changes: 43 additions & 0 deletions trunk/drivers/usb/core/hub.c
Original file line number Diff line number Diff line change
Expand Up @@ -3518,3 +3518,46 @@ int usb_reset_device(struct usb_device *udev)
return ret;
}
EXPORT_SYMBOL_GPL(usb_reset_device);


/**
* usb_queue_reset_device - Reset a USB device from an atomic context
* @iface: USB interface belonging to the device to reset
*
* This function can be used to reset a USB device from an atomic
* context, where usb_reset_device() won't work (as it blocks).
*
* Doing a reset via this method is functionally equivalent to calling
* usb_reset_device(), except for the fact that it is delayed to a
* workqueue. This means that any drivers bound to other interfaces
* might be unbound, as well as users from usbfs in user space.
*
* Corner cases:
*
* - Scheduling two resets at the same time from two different drivers
* attached to two different interfaces of the same device is
* possible; depending on how the driver attached to each interface
* handles ->pre_reset(), the second reset might happen or not.
*
* - If a driver is unbound and it had a pending reset, the reset will
* be cancelled.
*
* - This function can be called during .probe() or .disconnect()
* times. On return from .disconnect(), any pending resets will be
* cancelled.
*
* There is no no need to lock/unlock the @reset_ws as schedule_work()
* does its own.
*
* NOTE: We don't do any reference count tracking because it is not
* needed. The lifecycle of the work_struct is tied to the
* usb_interface. Before destroying the interface we cancel the
* work_struct, so the fact that work_struct is queued and or
* running means the interface (and thus, the device) exist and
* are referenced.
*/
void usb_queue_reset_device(struct usb_interface *iface)
{
schedule_work(&iface->reset_ws);
}
EXPORT_SYMBOL_GPL(usb_queue_reset_device);
41 changes: 41 additions & 0 deletions trunk/drivers/usb/core/message.c
Original file line number Diff line number Diff line change
Expand Up @@ -1441,6 +1441,46 @@ static struct usb_interface_assoc_descriptor *find_iad(struct usb_device *dev,
return retval;
}


/*
* Internal function to queue a device reset
*
* This is initialized into the workstruct in 'struct
* usb_device->reset_ws' that is launched by
* message.c:usb_set_configuration() when initializing each 'struct
* usb_interface'.
*
* It is safe to get the USB device without reference counts because
* the life cycle of @iface is bound to the life cycle of @udev. Then,
* this function will be ran only if @iface is alive (and before
* freeing it any scheduled instances of it will have been cancelled).
*
* We need to set a flag (usb_dev->reset_running) because when we call
* the reset, the interfaces might be unbound. The current interface
* cannot try to remove the queued work as it would cause a deadlock
* (you cannot remove your work from within your executing
* workqueue). This flag lets it know, so that
* usb_cancel_queued_reset() doesn't try to do it.
*
* See usb_queue_reset_device() for more details
*/
void __usb_queue_reset_device(struct work_struct *ws)
{
int rc;
struct usb_interface *iface =
container_of(ws, struct usb_interface, reset_ws);
struct usb_device *udev = interface_to_usbdev(iface);

rc = usb_lock_device_for_reset(udev, iface);
if (rc >= 0) {
iface->reset_running = 1;
usb_reset_device(udev);
iface->reset_running = 0;
usb_unlock_device(udev);
}
}


/*
* usb_set_configuration - Makes a particular device setting be current
* @dev: the device whose configuration is being updated
Expand Down Expand Up @@ -1611,6 +1651,7 @@ int usb_set_configuration(struct usb_device *dev, int configuration)
intf->dev.type = &usb_if_device_type;
intf->dev.groups = usb_interface_groups;
intf->dev.dma_mask = dev->dev.dma_mask;
INIT_WORK(&intf->reset_ws, __usb_queue_reset_device);
device_initialize(&intf->dev);
mark_quiesced(intf);
dev_set_name(&intf->dev, "%d-%s:%d.%d",
Expand Down
8 changes: 8 additions & 0 deletions trunk/include/linux/usb.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ enum usb_interface_condition {
* to the sysfs representation for that device.
* @pm_usage_cnt: PM usage counter for this interface; autosuspend is not
* allowed unless the counter is 0.
* @reset_ws: Used for scheduling resets from atomic context.
* @reset_running: set to 1 if the interface is currently running a
* queued reset so that usb_cancel_queued_reset() doesn't try to
* remove from the workqueue when running inside the worker
* thread. See __usb_queue_reset_device().
*
* USB device drivers attach to interfaces on a physical device. Each
* interface encapsulates a single high level function, such as feeding
Expand Down Expand Up @@ -168,10 +173,12 @@ struct usb_interface {
unsigned needs_remote_wakeup:1; /* driver requires remote wakeup */
unsigned needs_altsetting0:1; /* switch to altsetting 0 is pending */
unsigned needs_binding:1; /* needs delayed unbind/rebind */
unsigned reset_running:1;

struct device dev; /* interface specific device info */
struct device *usb_dev;
int pm_usage_cnt; /* usage counter for autosuspend */
struct work_struct reset_ws; /* for resets in atomic context */
};
#define to_usb_interface(d) container_of(d, struct usb_interface, dev)
#define interface_to_usbdev(intf) \
Expand Down Expand Up @@ -507,6 +514,7 @@ extern int usb_lock_device_for_reset(struct usb_device *udev,

/* USB port reset for device reinitialization */
extern int usb_reset_device(struct usb_device *dev);
extern void usb_queue_reset_device(struct usb_interface *dev);

extern struct usb_device *usb_find_device(u16 vendor_id, u16 product_id);

Expand Down

0 comments on commit 0e454a8

Please sign in to comment.