[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 коммит произвёл Greg Kroah-Hartman
Родитель d9b762510c
Коммит f1c39fad7d
1 изменённых файлов: 162 добавлений и 92 удалений

Просмотреть файл

@ -163,12 +163,16 @@ struct dummy {
struct dummy_request fifo_req; struct dummy_request fifo_req;
u8 fifo_buf [FIFO_SIZE]; u8 fifo_buf [FIFO_SIZE];
u16 devstatus; u16 devstatus;
unsigned pullup:1;
unsigned active:1;
unsigned old_active:1;
/* /*
* MASTER/HOST side support * MASTER/HOST side support
*/ */
struct timer_list timer; struct timer_list timer;
u32 port_status; u32 port_status;
u32 old_status;
unsigned resuming:1; unsigned resuming:1;
unsigned long re_timeout; 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 /* SLAVE/GADGET SIDE DRIVER
* *
* This only tracks gadget state. All the work is done when the host * This only tracks gadget state. All the work is done when the host
@ -339,22 +435,6 @@ done:
return retval; 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) static int dummy_disable (struct usb_ep *_ep)
{ {
struct dummy_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 */ /* hub notices our request, issues downstream resume, etc */
dum->resuming = 1; dum->resuming = 1;
dum->port_status |= (USB_PORT_STAT_C_SUSPEND << 16); dum->re_timeout = jiffies + msecs_to_jiffies(20);
return 0; return 0;
} }
@ -619,10 +699,24 @@ static int dummy_set_selfpowered (struct usb_gadget *_gadget, int value)
return 0; 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 = { static const struct usb_gadget_ops dummy_ops = {
.get_frame = dummy_g_get_frame, .get_frame = dummy_g_get_frame,
.wakeup = dummy_wakeup, .wakeup = dummy_wakeup,
.set_selfpowered = dummy_set_selfpowered, .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->devstatus = 0;
dum->resuming = 0;
INIT_LIST_HEAD (&dum->gadget.ep_list); INIT_LIST_HEAD (&dum->gadget.ep_list);
for (i = 0; i < DUMMY_ENDPOINTS; i++) { 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); device_bind_driver (&dum->gadget.dev);
/* khubd will enumerate this in a while */ /* khubd will enumerate this in a while */
dum->port_status |= USB_PORT_STAT_CONNECTION spin_lock_irq (&dum->lock);
| (USB_PORT_STAT_C_CONNECTION << 16); dum->pullup = 1;
set_link_state (dum);
spin_unlock_irq (&dum->lock);
return 0; return 0;
} }
EXPORT_SYMBOL (usb_gadget_register_driver); 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 int
usb_gadget_unregister_driver (struct usb_gadget_driver *driver) 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); driver->driver.name);
spin_lock_irqsave (&dum->lock, flags); spin_lock_irqsave (&dum->lock, flags);
stop_activity (dum, driver); dum->pullup = 0;
dum->port_status &= ~(USB_PORT_STAT_CONNECTION | USB_PORT_STAT_ENABLE | set_link_state (dum);
USB_PORT_STAT_LOW_SPEED | USB_PORT_STAT_HIGH_SPEED);
dum->port_status |= (USB_PORT_STAT_C_CONNECTION << 16);
spin_unlock_irqrestore (&dum->lock, flags); spin_unlock_irqrestore (&dum->lock, flags);
driver->unbind (&dum->gadget); driver->unbind (&dum->gadget);
@ -770,6 +840,11 @@ usb_gadget_unregister_driver (struct usb_gadget_driver *driver)
device_release_driver (&dum->gadget.dev); device_release_driver (&dum->gadget.dev);
driver_unregister (&driver->driver); 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; return 0;
} }
EXPORT_SYMBOL (usb_gadget_unregister_driver); 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); dum = hcd_to_dummy (hcd);
spin_lock_irqsave (&dum->lock, flags); 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)) if (!(dum->port_status & PORT_C_MASK))
retval = 0; retval = 0;
else { else {
@ -1480,16 +1562,16 @@ static int dummy_hub_control (
/* 20msec resume signaling */ /* 20msec resume signaling */
dum->resuming = 1; dum->resuming = 1;
dum->re_timeout = jiffies + dum->re_timeout = jiffies +
msecs_to_jiffies(20); msecs_to_jiffies(20);
} }
break; break;
case USB_PORT_FEAT_POWER: case USB_PORT_FEAT_POWER:
dum->port_status = 0; if (dum->port_status & USB_PORT_STAT_POWER)
dum->resuming = 0; dev_dbg (dummy_dev(dum), "power-off\n");
stop_activity(dum, dum->driver); /* FALLS THROUGH */
break;
default: default:
dum->port_status &= ~(1 << wValue); dum->port_status &= ~(1 << wValue);
set_link_state (dum);
} }
break; break;
case GetHubDescriptor: case GetHubDescriptor:
@ -1505,23 +1587,16 @@ static int dummy_hub_control (
/* whoever resets or resumes must GetPortStatus to /* whoever resets or resumes must GetPortStatus to
* complete it!! * 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_C_SUSPEND << 16);
dum->port_status &= ~USB_PORT_STAT_SUSPEND; 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 if ((dum->port_status & USB_PORT_STAT_RESET) != 0 &&
&& time_after (jiffies, dum->re_timeout)) { time_after_eq (jiffies, dum->re_timeout)) {
dum->port_status |= (USB_PORT_STAT_C_RESET << 16); dum->port_status |= (USB_PORT_STAT_C_RESET << 16);
dum->port_status &= ~USB_PORT_STAT_RESET; dum->port_status &= ~USB_PORT_STAT_RESET;
dum->re_timeout = 0; if (dum->pullup) {
if (dum->driver) {
dum->port_status |= USB_PORT_STAT_ENABLE; dum->port_status |= USB_PORT_STAT_ENABLE;
/* give it the best speed we agree on */ /* give it the best speed we agree on */
dum->gadget.speed = dum->driver->speed; 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)[0] = cpu_to_le16 (dum->port_status);
((u16 *) buf)[1] = cpu_to_le16 (dum->port_status >> 16); ((u16 *) buf)[1] = cpu_to_le16 (dum->port_status >> 16);
break; break;
@ -1551,42 +1627,36 @@ static int dummy_hub_control (
case SetPortFeature: case SetPortFeature:
switch (wValue) { switch (wValue) {
case USB_PORT_FEAT_SUSPEND: case USB_PORT_FEAT_SUSPEND:
if ((dum->port_status & USB_PORT_STAT_SUSPEND) if (dum->active) {
== 0) {
dum->port_status |= USB_PORT_STAT_SUSPEND; dum->port_status |= USB_PORT_STAT_SUSPEND;
if (dum->driver && dum->driver->suspend) {
spin_unlock (&dum->lock); /* HNP would happen here; for now we
dum->driver->suspend (&dum->gadget); * assume b_bus_req is always true.
spin_lock (&dum->lock); */
/* HNP would happen here; for now we set_link_state (dum);
* assume b_bus_req is always true. if (((1 << USB_DEVICE_B_HNP_ENABLE)
*/ & dum->devstatus) != 0)
if (((1 << USB_DEVICE_B_HNP_ENABLE) dev_dbg (dummy_dev(dum),
& dum->devstatus) != 0)
dev_dbg (dummy_dev(dum),
"no HNP yet!\n"); "no HNP yet!\n");
}
} }
break; break;
case USB_PORT_FEAT_POWER:
dum->port_status |= USB_PORT_STAT_POWER;
set_link_state (dum);
break;
case USB_PORT_FEAT_RESET: case USB_PORT_FEAT_RESET:
/* if it's already running, disconnect first */ /* if it's already enabled, disable */
if (dum->port_status & USB_PORT_STAT_ENABLE) { dum->port_status &= ~(USB_PORT_STAT_ENABLE
dum->port_status &= ~(USB_PORT_STAT_ENABLE | USB_PORT_STAT_LOW_SPEED
| USB_PORT_STAT_LOW_SPEED | USB_PORT_STAT_HIGH_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! */
}
/* 50msec reset signaling */ /* 50msec reset signaling */
dum->re_timeout = jiffies + msecs_to_jiffies(50); dum->re_timeout = jiffies + msecs_to_jiffies(50);
/* FALLTHROUGH */ /* FALLS THROUGH */
default: 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; break;