Skip to content

Commit

Permalink
[usb] Reset endpoints without waiting for a new transfer to be enqueued
Browse files Browse the repository at this point in the history
The current endpoint reset logic defers the reset until the caller
attempts to enqueue a new transfer to that endpoint.  This is
insufficient when dealing with endpoints behind a transaction
translator, since the transaction translator is a resource shared
between multiple endpoints.

We cannot reset the endpoint as part of the completion handling, since
that would introduce recursive calls to usb_poll().  Instead, we
add the endpoint to a list of halted endpoints, and perform the reset
on the next call to usb_step().

Signed-off-by: Michael Brown <mcb30@ipxe.org>
  • Loading branch information
mcb30 committed Mar 23, 2015
1 parent 661189e commit de6f4e3
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 31 deletions.
92 changes: 64 additions & 28 deletions src/drivers/bus/usb.c
Expand Up @@ -296,9 +296,7 @@ int usb_endpoint_open ( struct usb_endpoint *ep ) {
goto err_already;
}
usb->ep[idx] = ep;

/* Clear any stale error status */
ep->rc = 0;
INIT_LIST_HEAD ( &ep->halted );

/* Open endpoint */
if ( ( rc = ep->host->open ( ep ) ) != 0 ) {
Expand Down Expand Up @@ -342,6 +340,7 @@ void usb_endpoint_close ( struct usb_endpoint *ep ) {

/* Remove from endpoint list */
usb->ep[idx] = NULL;
list_del ( &ep->halted );

/* Discard any recycled buffers, if applicable */
if ( ep->max )
Expand All @@ -359,6 +358,9 @@ static int usb_endpoint_reset ( struct usb_endpoint *ep ) {
unsigned int type;
int rc;

/* Sanity check */
assert ( ! list_empty ( &ep->halted ) );

/* Reset endpoint */
if ( ( rc = ep->host->reset ( ep ) ) != 0 ) {
DBGC ( usb, "USB %s %s could not reset: %s\n",
Expand All @@ -379,8 +381,9 @@ static int usb_endpoint_reset ( struct usb_endpoint *ep ) {
return rc;
}

/* Clear recorded error */
ep->rc = 0;
/* Remove from list of halted endpoints */
list_del ( &ep->halted );
INIT_LIST_HEAD ( &ep->halted );

DBGC ( usb, "USB %s %s reset\n",
usb->name, usb_endpoint_name ( ep->address ) );
Expand Down Expand Up @@ -434,7 +437,8 @@ int usb_message ( struct usb_endpoint *ep, unsigned int request,
return -ENODEV;

/* Reset endpoint if required */
if ( ( ep->rc != 0 ) && ( ( rc = usb_endpoint_reset ( ep ) ) != 0 ) )
if ( ( ! list_empty ( &ep->halted ) ) &&
( ( rc = usb_endpoint_reset ( ep ) ) != 0 ) )
return rc;

/* Zero input data buffer (if applicable) */
Expand Down Expand Up @@ -480,7 +484,8 @@ int usb_stream ( struct usb_endpoint *ep, struct io_buffer *iobuf,
return -ENODEV;

/* Reset endpoint if required */
if ( ( ep->rc != 0 ) && ( ( rc = usb_endpoint_reset ( ep ) ) != 0 ) )
if ( ( ! list_empty ( &ep->halted ) ) &&
( ( rc = usb_endpoint_reset ( ep ) ) != 0 ) )
return rc;

/* Enqueue stream transfer */
Expand All @@ -507,17 +512,19 @@ int usb_stream ( struct usb_endpoint *ep, struct io_buffer *iobuf,
void usb_complete_err ( struct usb_endpoint *ep, struct io_buffer *iobuf,
int rc ) {
struct usb_device *usb = ep->usb;
struct usb_bus *bus = usb->port->hub->bus;

/* Decrement fill level */
assert ( ep->fill > 0 );
ep->fill--;

/* Record error (if any) */
ep->rc = rc;
/* Schedule reset, if applicable */
if ( ( rc != 0 ) && ep->open ) {
DBGC ( usb, "USB %s %s completion failed: %s\n",
usb->name, usb_endpoint_name ( ep->address ),
strerror ( rc ) );
list_del ( &ep->halted );
list_add_tail ( &ep->halted, &bus->halted );
}

/* Report completion */
Expand Down Expand Up @@ -642,6 +649,12 @@ void usb_flush ( struct usb_endpoint *ep ) {
******************************************************************************
*/

/** USB control transfer pseudo-header */
struct usb_control_pseudo_header {
/** Completion status */
int rc;
};

/**
* Complete USB control transfer
*
Expand All @@ -652,13 +665,14 @@ void usb_flush ( struct usb_endpoint *ep ) {
static void usb_control_complete ( struct usb_endpoint *ep,
struct io_buffer *iobuf, int rc ) {
struct usb_device *usb = ep->usb;
struct usb_control_pseudo_header *pshdr;

/* Check for failures */
/* Record completion status in buffer */
pshdr = iob_push ( iobuf, sizeof ( *pshdr ) );
pshdr->rc = rc;
if ( rc != 0 ) {
DBGC ( usb, "USB %s control transaction failed: %s\n",
usb->name, strerror ( rc ) );
free_iob ( iobuf );
return;
}

/* Add to list of completed I/O buffers */
Expand Down Expand Up @@ -686,17 +700,19 @@ int usb_control ( struct usb_device *usb, unsigned int request,
size_t len ) {
struct usb_bus *bus = usb->port->hub->bus;
struct usb_endpoint *ep = &usb->control;
struct usb_control_pseudo_header *pshdr;
struct io_buffer *iobuf;
struct io_buffer *cmplt;
unsigned int i;
int rc;

/* Allocate I/O buffer */
iobuf = alloc_iob ( len );
iobuf = alloc_iob ( sizeof ( *pshdr ) + len );
if ( ! iobuf ) {
rc = -ENOMEM;
goto err_alloc;
}
iob_reserve ( iobuf, sizeof ( *pshdr ) );
iob_put ( iobuf, len );
if ( request & USB_DIR_IN ) {
memset ( data, 0, len );
Expand All @@ -722,16 +738,27 @@ int usb_control ( struct usb_device *usb, unsigned int request,
/* Remove from completion list */
list_del ( &cmplt->list );

/* Extract and strip completion status */
pshdr = cmplt->data;
iob_pull ( cmplt, sizeof ( *pshdr ) );
rc = pshdr->rc;

/* Discard stale completions */
if ( cmplt != iobuf ) {
DBGC ( usb, "USB %s stale control "
"completion:\n", usb->name );
DBGC ( usb, "USB %s stale control completion: "
"%s\n", usb->name, strerror ( rc ) );
DBGC_HDA ( usb, 0, cmplt->data,
iob_len ( cmplt ) );
free_iob ( cmplt );
continue;
}

/* Fail immediately if completion was in error */
if ( rc != 0 ) {
free_iob ( cmplt );
return rc;
}

/* Copy completion to data buffer, if applicable */
assert ( iob_len ( cmplt ) <= len );
if ( request & USB_DIR_IN )
Expand All @@ -740,10 +767,6 @@ int usb_control ( struct usb_device *usb, unsigned int request,
return 0;
}

/* Fail immediately if endpoint is in an error state */
if ( ep->rc )
return ep->rc;

/* Delay */
mdelay ( 1 );
}
Expand Down Expand Up @@ -1549,8 +1572,8 @@ void usb_port_changed ( struct usb_port *port ) {
struct usb_bus *bus = hub->bus;

/* Record hub port status change */
list_del ( &port->list );
list_add_tail ( &port->list, &bus->changed );
list_del ( &port->changed );
list_add_tail ( &port->changed, &bus->changed );
}

/**
Expand All @@ -1559,23 +1582,35 @@ void usb_port_changed ( struct usb_port *port ) {
* @v bus USB bus
*/
static void usb_step ( struct usb_bus *bus ) {
struct usb_endpoint *ep;
struct usb_port *port;

/* Poll bus */
usb_poll ( bus );

/* Attempt to reset first halted endpoint in list, if any. We
* do not attempt to process the complete list, since this
* would require extra code to allow for the facts that the
* halted endpoint list may change as we do so, and that
* resetting an endpoint may fail.
*/
if ( ( ep = list_first_entry ( &bus->halted, struct usb_endpoint,
halted ) ) != NULL )
usb_endpoint_reset ( ep );

/* Handle any changed ports, allowing for the fact that the
* port list may change as we perform hotplug actions.
*/
while ( ! list_empty ( &bus->changed ) ) {

/* Get first changed port */
port = list_first_entry ( &bus->changed, struct usb_port, list);
port = list_first_entry ( &bus->changed, struct usb_port,
changed );
assert ( port != NULL );

/* Remove from list of changed ports */
list_del ( &port->list );
INIT_LIST_HEAD ( &port->list );
list_del ( &port->changed );
INIT_LIST_HEAD ( &port->changed );

/* Perform appropriate hotplug action */
usb_hotplug ( port );
Expand Down Expand Up @@ -1628,7 +1663,7 @@ struct usb_hub * alloc_usb_hub ( struct usb_bus *bus, struct usb_device *usb,
port->address = i;
if ( usb )
port->protocol = usb->port->protocol;
INIT_LIST_HEAD ( &port->list );
INIT_LIST_HEAD ( &port->changed );
}

return hub;
Expand Down Expand Up @@ -1702,8 +1737,8 @@ void unregister_usb_hub ( struct usb_hub *hub ) {
/* Cancel any pending port status changes */
for ( i = 1 ; i <= hub->ports ; i++ ) {
port = usb_port ( hub, i );
list_del ( &port->list );
INIT_LIST_HEAD ( &port->list );
list_del ( &port->changed );
INIT_LIST_HEAD ( &port->changed );
}

/* Remove from hub list */
Expand All @@ -1724,7 +1759,7 @@ void free_usb_hub ( struct usb_hub *hub ) {
port = usb_port ( hub, i );
assert ( ! port->attached );
assert ( port->usb == NULL );
assert ( list_empty ( &port->list ) );
assert ( list_empty ( &port->changed ) );
}

/* Free hub */
Expand Down Expand Up @@ -1762,6 +1797,7 @@ struct usb_bus * alloc_usb_bus ( struct device *dev, unsigned int ports,
INIT_LIST_HEAD ( &bus->devices );
INIT_LIST_HEAD ( &bus->hubs );
INIT_LIST_HEAD ( &bus->changed );
INIT_LIST_HEAD ( &bus->halted );
process_init_stopped ( &bus->process, &usb_process_desc, NULL );
bus->host = &bus->op->bus;

Expand Down
9 changes: 6 additions & 3 deletions src/include/ipxe/usb.h
Expand Up @@ -380,11 +380,12 @@ struct usb_endpoint {

/** Endpoint is open */
int open;
/** Current failure state (if any) */
int rc;
/** Buffer fill level */
unsigned int fill;

/** List of halted endpoints */
struct list_head halted;

/** Host controller operations */
struct usb_endpoint_host_operations *host;
/** Host controller private data */
Expand Down Expand Up @@ -754,7 +755,7 @@ struct usb_port {
*/
struct usb_device *usb;
/** List of changed ports */
struct list_head list;
struct list_head changed;
};

/** A USB hub */
Expand Down Expand Up @@ -888,6 +889,8 @@ struct usb_bus {
struct list_head hubs;
/** List of changed ports */
struct list_head changed;
/** List of halted endpoints */
struct list_head halted;
/** Process */
struct process process;

Expand Down

0 comments on commit de6f4e3

Please sign in to comment.