[PATCH] USB dummy_hcd: Centralize link state computations

This patch adds to the dummy_hcd driver a new routine for keeping track of
all changes in the state of the emulated USB link.  The logic is now kept
in one spot instead of spread around, and it's easier to verify and
update the code.  The behavior of the port features has been corrected in
a few respects as well (for instance, if the POWER feature is clear then
none of the other features can be set).

Also added is support for the (relatively new) _connect() and
_disconnect() calls of the Gadget API.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
Alan Stern 2005-05-03 16:24:04 -04:00 committed by Greg Kroah-Hartman
parent d9b762510c
commit f1c39fad7d

View File

@ -163,12 +163,16 @@ struct dummy {
struct dummy_request fifo_req;
u8 fifo_buf [FIFO_SIZE];
u16 devstatus;
unsigned pullup:1;
unsigned active:1;
unsigned old_active:1;
/*
* MASTER/HOST side support
*/
struct timer_list timer;
u32 port_status;
u32 old_status;
unsigned resuming:1;
unsigned long re_timeout;
@ -215,6 +219,98 @@ static struct dummy *the_controller;
/*-------------------------------------------------------------------------*/
/* SLAVE/GADGET SIDE UTILITY ROUTINES */
/* called with spinlock held */
static void nuke (struct dummy *dum, struct dummy_ep *ep)
{
while (!list_empty (&ep->queue)) {
struct dummy_request *req;
req = list_entry (ep->queue.next, struct dummy_request, queue);
list_del_init (&req->queue);
req->req.status = -ESHUTDOWN;
spin_unlock (&dum->lock);
req->req.complete (&ep->ep, &req->req);
spin_lock (&dum->lock);
}
}
/* caller must hold lock */
static void
stop_activity (struct dummy *dum)
{
struct dummy_ep *ep;
/* prevent any more requests */
dum->address = 0;
/* The timer is left running so that outstanding URBs can fail */
/* nuke any pending requests first, so driver i/o is quiesced */
list_for_each_entry (ep, &dum->gadget.ep_list, ep.ep_list)
nuke (dum, ep);
/* driver now does any non-usb quiescing necessary */
}
/* caller must hold lock */
static void
set_link_state (struct dummy *dum)
{
dum->active = 0;
if ((dum->port_status & USB_PORT_STAT_POWER) == 0)
dum->port_status = 0;
else if (!dum->pullup) {
dum->port_status &= ~(USB_PORT_STAT_CONNECTION |
USB_PORT_STAT_ENABLE |
USB_PORT_STAT_LOW_SPEED |
USB_PORT_STAT_HIGH_SPEED |
USB_PORT_STAT_SUSPEND);
if ((dum->old_status & USB_PORT_STAT_CONNECTION) != 0)
dum->port_status |= (USB_PORT_STAT_C_CONNECTION << 16);
} else {
dum->port_status |= USB_PORT_STAT_CONNECTION;
if ((dum->old_status & USB_PORT_STAT_CONNECTION) == 0)
dum->port_status |= (USB_PORT_STAT_C_CONNECTION << 16);
if ((dum->port_status & USB_PORT_STAT_ENABLE) == 0)
dum->port_status &= ~USB_PORT_STAT_SUSPEND;
else if ((dum->port_status & USB_PORT_STAT_SUSPEND) == 0)
dum->active = 1;
}
if ((dum->port_status & USB_PORT_STAT_ENABLE) == 0 || dum->active)
dum->resuming = 0;
if ((dum->port_status & USB_PORT_STAT_CONNECTION) == 0 ||
(dum->port_status & USB_PORT_STAT_RESET) != 0) {
if ((dum->old_status & USB_PORT_STAT_CONNECTION) != 0 &&
(dum->old_status & USB_PORT_STAT_RESET) == 0 &&
dum->driver) {
stop_activity (dum);
spin_unlock (&dum->lock);
dum->driver->disconnect (&dum->gadget);
spin_lock (&dum->lock);
}
} else if (dum->active != dum->old_active) {
if (dum->old_active && dum->driver->suspend) {
spin_unlock (&dum->lock);
dum->driver->suspend (&dum->gadget);
spin_lock (&dum->lock);
} else if (!dum->old_active && dum->driver->resume) {
spin_unlock (&dum->lock);
dum->driver->resume (&dum->gadget);
spin_lock (&dum->lock);
}
}
dum->old_status = dum->port_status;
dum->old_active = dum->active;
}
/*-------------------------------------------------------------------------*/
/* SLAVE/GADGET SIDE DRIVER
*
* This only tracks gadget state. All the work is done when the host
@ -339,22 +435,6 @@ done:
return retval;
}
/* called with spinlock held */
static void nuke (struct dummy *dum, struct dummy_ep *ep)
{
while (!list_empty (&ep->queue)) {
struct dummy_request *req;
req = list_entry (ep->queue.next, struct dummy_request, queue);
list_del_init (&req->queue);
req->req.status = -ESHUTDOWN;
spin_unlock (&dum->lock);
req->req.complete (&ep->ep, &req->req);
spin_lock (&dum->lock);
}
}
static int dummy_disable (struct usb_ep *_ep)
{
struct dummy_ep *ep;
@ -603,7 +683,7 @@ static int dummy_wakeup (struct usb_gadget *_gadget)
/* hub notices our request, issues downstream resume, etc */
dum->resuming = 1;
dum->port_status |= (USB_PORT_STAT_C_SUSPEND << 16);
dum->re_timeout = jiffies + msecs_to_jiffies(20);
return 0;
}
@ -619,10 +699,24 @@ static int dummy_set_selfpowered (struct usb_gadget *_gadget, int value)
return 0;
}
static int dummy_pullup (struct usb_gadget *_gadget, int value)
{
struct dummy *dum;
unsigned long flags;
dum = gadget_to_dummy (_gadget);
spin_lock_irqsave (&dum->lock, flags);
dum->pullup = (value != 0);
set_link_state (dum);
spin_unlock_irqrestore (&dum->lock, flags);
return 0;
}
static const struct usb_gadget_ops dummy_ops = {
.get_frame = dummy_g_get_frame,
.wakeup = dummy_wakeup,
.set_selfpowered = dummy_set_selfpowered,
.pullup = dummy_pullup,
};
/*-------------------------------------------------------------------------*/
@ -675,7 +769,6 @@ usb_gadget_register_driver (struct usb_gadget_driver *driver)
*/
dum->devstatus = 0;
dum->resuming = 0;
INIT_LIST_HEAD (&dum->gadget.ep_list);
for (i = 0; i < DUMMY_ENDPOINTS; i++) {
@ -714,35 +807,14 @@ usb_gadget_register_driver (struct usb_gadget_driver *driver)
device_bind_driver (&dum->gadget.dev);
/* khubd will enumerate this in a while */
dum->port_status |= USB_PORT_STAT_CONNECTION
| (USB_PORT_STAT_C_CONNECTION << 16);
spin_lock_irq (&dum->lock);
dum->pullup = 1;
set_link_state (dum);
spin_unlock_irq (&dum->lock);
return 0;
}
EXPORT_SYMBOL (usb_gadget_register_driver);
/* caller must hold lock */
static void
stop_activity (struct dummy *dum, struct usb_gadget_driver *driver)
{
struct dummy_ep *ep;
/* prevent any more requests */
dum->address = 0;
/* The timer is left running so that outstanding URBs can fail */
/* nuke any pending requests first, so driver i/o is quiesced */
list_for_each_entry (ep, &dum->gadget.ep_list, ep.ep_list)
nuke (dum, ep);
/* driver now does any non-usb quiescing necessary */
if (driver) {
spin_unlock (&dum->lock);
driver->disconnect (&dum->gadget);
spin_lock (&dum->lock);
}
}
int
usb_gadget_unregister_driver (struct usb_gadget_driver *driver)
{
@ -758,10 +830,8 @@ usb_gadget_unregister_driver (struct usb_gadget_driver *driver)
driver->driver.name);
spin_lock_irqsave (&dum->lock, flags);
stop_activity (dum, driver);
dum->port_status &= ~(USB_PORT_STAT_CONNECTION | USB_PORT_STAT_ENABLE |
USB_PORT_STAT_LOW_SPEED | USB_PORT_STAT_HIGH_SPEED);
dum->port_status |= (USB_PORT_STAT_C_CONNECTION << 16);
dum->pullup = 0;
set_link_state (dum);
spin_unlock_irqrestore (&dum->lock, flags);
driver->unbind (&dum->gadget);
@ -770,6 +840,11 @@ usb_gadget_unregister_driver (struct usb_gadget_driver *driver)
device_release_driver (&dum->gadget.dev);
driver_unregister (&driver->driver);
spin_lock_irqsave (&dum->lock, flags);
dum->pullup = 0;
set_link_state (dum);
spin_unlock_irqrestore (&dum->lock, flags);
return 0;
}
EXPORT_SYMBOL (usb_gadget_unregister_driver);
@ -1432,6 +1507,13 @@ static int dummy_hub_status (struct usb_hcd *hcd, char *buf)
dum = hcd_to_dummy (hcd);
spin_lock_irqsave (&dum->lock, flags);
if (dum->resuming && time_after_eq (jiffies, dum->re_timeout)) {
dum->port_status |= (USB_PORT_STAT_C_SUSPEND << 16);
dum->port_status &= ~USB_PORT_STAT_SUSPEND;
set_link_state (dum);
}
if (!(dum->port_status & PORT_C_MASK))
retval = 0;
else {
@ -1480,16 +1562,16 @@ static int dummy_hub_control (
/* 20msec resume signaling */
dum->resuming = 1;
dum->re_timeout = jiffies +
msecs_to_jiffies(20);
msecs_to_jiffies(20);
}
break;
case USB_PORT_FEAT_POWER:
dum->port_status = 0;
dum->resuming = 0;
stop_activity(dum, dum->driver);
break;
if (dum->port_status & USB_PORT_STAT_POWER)
dev_dbg (dummy_dev(dum), "power-off\n");
/* FALLS THROUGH */
default:
dum->port_status &= ~(1 << wValue);
set_link_state (dum);
}
break;
case GetHubDescriptor:
@ -1505,23 +1587,16 @@ static int dummy_hub_control (
/* whoever resets or resumes must GetPortStatus to
* complete it!!
*/
if (dum->resuming && time_after (jiffies, dum->re_timeout)) {
if (dum->resuming &&
time_after_eq (jiffies, dum->re_timeout)) {
dum->port_status |= (USB_PORT_STAT_C_SUSPEND << 16);
dum->port_status &= ~USB_PORT_STAT_SUSPEND;
dum->resuming = 0;
dum->re_timeout = 0;
if (dum->driver && dum->driver->resume) {
spin_unlock (&dum->lock);
dum->driver->resume (&dum->gadget);
spin_lock (&dum->lock);
}
}
if ((dum->port_status & USB_PORT_STAT_RESET) != 0
&& time_after (jiffies, dum->re_timeout)) {
if ((dum->port_status & USB_PORT_STAT_RESET) != 0 &&
time_after_eq (jiffies, dum->re_timeout)) {
dum->port_status |= (USB_PORT_STAT_C_RESET << 16);
dum->port_status &= ~USB_PORT_STAT_RESET;
dum->re_timeout = 0;
if (dum->driver) {
if (dum->pullup) {
dum->port_status |= USB_PORT_STAT_ENABLE;
/* give it the best speed we agree on */
dum->gadget.speed = dum->driver->speed;
@ -1542,6 +1617,7 @@ static int dummy_hub_control (
}
}
}
set_link_state (dum);
((u16 *) buf)[0] = cpu_to_le16 (dum->port_status);
((u16 *) buf)[1] = cpu_to_le16 (dum->port_status >> 16);
break;
@ -1551,42 +1627,36 @@ static int dummy_hub_control (
case SetPortFeature:
switch (wValue) {
case USB_PORT_FEAT_SUSPEND:
if ((dum->port_status & USB_PORT_STAT_SUSPEND)
== 0) {
if (dum->active) {
dum->port_status |= USB_PORT_STAT_SUSPEND;
if (dum->driver && dum->driver->suspend) {
spin_unlock (&dum->lock);
dum->driver->suspend (&dum->gadget);
spin_lock (&dum->lock);
/* HNP would happen here; for now we
* assume b_bus_req is always true.
*/
if (((1 << USB_DEVICE_B_HNP_ENABLE)
& dum->devstatus) != 0)
dev_dbg (dummy_dev(dum),
/* HNP would happen here; for now we
* assume b_bus_req is always true.
*/
set_link_state (dum);
if (((1 << USB_DEVICE_B_HNP_ENABLE)
& dum->devstatus) != 0)
dev_dbg (dummy_dev(dum),
"no HNP yet!\n");
}
}
break;
case USB_PORT_FEAT_POWER:
dum->port_status |= USB_PORT_STAT_POWER;
set_link_state (dum);
break;
case USB_PORT_FEAT_RESET:
/* if it's already running, disconnect first */
if (dum->port_status & USB_PORT_STAT_ENABLE) {
dum->port_status &= ~(USB_PORT_STAT_ENABLE
| USB_PORT_STAT_LOW_SPEED
| USB_PORT_STAT_HIGH_SPEED);
if (dum->driver) {
dev_dbg (udc_dev(dum),
"disconnect\n");
stop_activity (dum, dum->driver);
}
/* FIXME test that code path! */
}
/* if it's already enabled, disable */
dum->port_status &= ~(USB_PORT_STAT_ENABLE
| USB_PORT_STAT_LOW_SPEED
| USB_PORT_STAT_HIGH_SPEED);
/* 50msec reset signaling */
dum->re_timeout = jiffies + msecs_to_jiffies(50);
/* FALLTHROUGH */
/* FALLS THROUGH */
default:
dum->port_status |= (1 << wValue);
if ((dum->port_status & USB_PORT_STAT_POWER) != 0) {
dum->port_status |= (1 << wValue);
set_link_state (dum);
}
}
break;