david/ipxe
david
/
ipxe
Archived
1
0
Fork 0

[usb] Reset endpoints without waiting for a new transfer to be enqueued

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>
This commit is contained in:
Michael Brown 2015-03-23 15:05:28 +00:00
parent 661189eede
commit de6f4e3ede
2 changed files with 70 additions and 31 deletions

View File

@ -296,9 +296,7 @@ int usb_endpoint_open ( struct usb_endpoint *ep ) {
goto err_already; goto err_already;
} }
usb->ep[idx] = ep; usb->ep[idx] = ep;
INIT_LIST_HEAD ( &ep->halted );
/* Clear any stale error status */
ep->rc = 0;
/* Open endpoint */ /* Open endpoint */
if ( ( rc = ep->host->open ( ep ) ) != 0 ) { if ( ( rc = ep->host->open ( ep ) ) != 0 ) {
@ -342,6 +340,7 @@ void usb_endpoint_close ( struct usb_endpoint *ep ) {
/* Remove from endpoint list */ /* Remove from endpoint list */
usb->ep[idx] = NULL; usb->ep[idx] = NULL;
list_del ( &ep->halted );
/* Discard any recycled buffers, if applicable */ /* Discard any recycled buffers, if applicable */
if ( ep->max ) if ( ep->max )
@ -359,6 +358,9 @@ static int usb_endpoint_reset ( struct usb_endpoint *ep ) {
unsigned int type; unsigned int type;
int rc; int rc;
/* Sanity check */
assert ( ! list_empty ( &ep->halted ) );
/* Reset endpoint */ /* Reset endpoint */
if ( ( rc = ep->host->reset ( ep ) ) != 0 ) { if ( ( rc = ep->host->reset ( ep ) ) != 0 ) {
DBGC ( usb, "USB %s %s could not reset: %s\n", DBGC ( usb, "USB %s %s could not reset: %s\n",
@ -379,8 +381,9 @@ static int usb_endpoint_reset ( struct usb_endpoint *ep ) {
return rc; return rc;
} }
/* Clear recorded error */ /* Remove from list of halted endpoints */
ep->rc = 0; list_del ( &ep->halted );
INIT_LIST_HEAD ( &ep->halted );
DBGC ( usb, "USB %s %s reset\n", DBGC ( usb, "USB %s %s reset\n",
usb->name, usb_endpoint_name ( ep->address ) ); usb->name, usb_endpoint_name ( ep->address ) );
@ -434,7 +437,8 @@ int usb_message ( struct usb_endpoint *ep, unsigned int request,
return -ENODEV; return -ENODEV;
/* Reset endpoint if required */ /* 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; return rc;
/* Zero input data buffer (if applicable) */ /* Zero input data buffer (if applicable) */
@ -480,7 +484,8 @@ int usb_stream ( struct usb_endpoint *ep, struct io_buffer *iobuf,
return -ENODEV; return -ENODEV;
/* Reset endpoint if required */ /* 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; return rc;
/* Enqueue stream transfer */ /* Enqueue stream transfer */
@ -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, void usb_complete_err ( struct usb_endpoint *ep, struct io_buffer *iobuf,
int rc ) { int rc ) {
struct usb_device *usb = ep->usb; struct usb_device *usb = ep->usb;
struct usb_bus *bus = usb->port->hub->bus;
/* Decrement fill level */ /* Decrement fill level */
assert ( ep->fill > 0 ); assert ( ep->fill > 0 );
ep->fill--; ep->fill--;
/* Record error (if any) */ /* Schedule reset, if applicable */
ep->rc = rc;
if ( ( rc != 0 ) && ep->open ) { if ( ( rc != 0 ) && ep->open ) {
DBGC ( usb, "USB %s %s completion failed: %s\n", DBGC ( usb, "USB %s %s completion failed: %s\n",
usb->name, usb_endpoint_name ( ep->address ), usb->name, usb_endpoint_name ( ep->address ),
strerror ( rc ) ); strerror ( rc ) );
list_del ( &ep->halted );
list_add_tail ( &ep->halted, &bus->halted );
} }
/* Report completion */ /* Report completion */
@ -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 * Complete USB control transfer
* *
@ -652,13 +665,14 @@ void usb_flush ( struct usb_endpoint *ep ) {
static void usb_control_complete ( struct usb_endpoint *ep, static void usb_control_complete ( struct usb_endpoint *ep,
struct io_buffer *iobuf, int rc ) { struct io_buffer *iobuf, int rc ) {
struct usb_device *usb = ep->usb; 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 ) { if ( rc != 0 ) {
DBGC ( usb, "USB %s control transaction failed: %s\n", DBGC ( usb, "USB %s control transaction failed: %s\n",
usb->name, strerror ( rc ) ); usb->name, strerror ( rc ) );
free_iob ( iobuf );
return;
} }
/* Add to list of completed I/O buffers */ /* Add to list of completed I/O buffers */
@ -686,17 +700,19 @@ int usb_control ( struct usb_device *usb, unsigned int request,
size_t len ) { size_t len ) {
struct usb_bus *bus = usb->port->hub->bus; struct usb_bus *bus = usb->port->hub->bus;
struct usb_endpoint *ep = &usb->control; struct usb_endpoint *ep = &usb->control;
struct usb_control_pseudo_header *pshdr;
struct io_buffer *iobuf; struct io_buffer *iobuf;
struct io_buffer *cmplt; struct io_buffer *cmplt;
unsigned int i; unsigned int i;
int rc; int rc;
/* Allocate I/O buffer */ /* Allocate I/O buffer */
iobuf = alloc_iob ( len ); iobuf = alloc_iob ( sizeof ( *pshdr ) + len );
if ( ! iobuf ) { if ( ! iobuf ) {
rc = -ENOMEM; rc = -ENOMEM;
goto err_alloc; goto err_alloc;
} }
iob_reserve ( iobuf, sizeof ( *pshdr ) );
iob_put ( iobuf, len ); iob_put ( iobuf, len );
if ( request & USB_DIR_IN ) { if ( request & USB_DIR_IN ) {
memset ( data, 0, len ); memset ( data, 0, len );
@ -722,16 +738,27 @@ int usb_control ( struct usb_device *usb, unsigned int request,
/* Remove from completion list */ /* Remove from completion list */
list_del ( &cmplt->list ); list_del ( &cmplt->list );
/* Extract and strip completion status */
pshdr = cmplt->data;
iob_pull ( cmplt, sizeof ( *pshdr ) );
rc = pshdr->rc;
/* Discard stale completions */ /* Discard stale completions */
if ( cmplt != iobuf ) { if ( cmplt != iobuf ) {
DBGC ( usb, "USB %s stale control " DBGC ( usb, "USB %s stale control completion: "
"completion:\n", usb->name ); "%s\n", usb->name, strerror ( rc ) );
DBGC_HDA ( usb, 0, cmplt->data, DBGC_HDA ( usb, 0, cmplt->data,
iob_len ( cmplt ) ); iob_len ( cmplt ) );
free_iob ( cmplt ); free_iob ( cmplt );
continue; continue;
} }
/* Fail immediately if completion was in error */
if ( rc != 0 ) {
free_iob ( cmplt );
return rc;
}
/* Copy completion to data buffer, if applicable */ /* Copy completion to data buffer, if applicable */
assert ( iob_len ( cmplt ) <= len ); assert ( iob_len ( cmplt ) <= len );
if ( request & USB_DIR_IN ) if ( request & USB_DIR_IN )
@ -740,10 +767,6 @@ int usb_control ( struct usb_device *usb, unsigned int request,
return 0; return 0;
} }
/* Fail immediately if endpoint is in an error state */
if ( ep->rc )
return ep->rc;
/* Delay */ /* Delay */
mdelay ( 1 ); mdelay ( 1 );
} }
@ -1549,8 +1572,8 @@ void usb_port_changed ( struct usb_port *port ) {
struct usb_bus *bus = hub->bus; struct usb_bus *bus = hub->bus;
/* Record hub port status change */ /* Record hub port status change */
list_del ( &port->list ); list_del ( &port->changed );
list_add_tail ( &port->list, &bus->changed ); list_add_tail ( &port->changed, &bus->changed );
} }
/** /**
@ -1559,23 +1582,35 @@ void usb_port_changed ( struct usb_port *port ) {
* @v bus USB bus * @v bus USB bus
*/ */
static void usb_step ( struct usb_bus *bus ) { static void usb_step ( struct usb_bus *bus ) {
struct usb_endpoint *ep;
struct usb_port *port; struct usb_port *port;
/* Poll bus */ /* Poll bus */
usb_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 /* Handle any changed ports, allowing for the fact that the
* port list may change as we perform hotplug actions. * port list may change as we perform hotplug actions.
*/ */
while ( ! list_empty ( &bus->changed ) ) { while ( ! list_empty ( &bus->changed ) ) {
/* Get first changed port */ /* 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 ); assert ( port != NULL );
/* Remove from list of changed ports */ /* Remove from list of changed ports */
list_del ( &port->list ); list_del ( &port->changed );
INIT_LIST_HEAD ( &port->list ); INIT_LIST_HEAD ( &port->changed );
/* Perform appropriate hotplug action */ /* Perform appropriate hotplug action */
usb_hotplug ( port ); usb_hotplug ( port );
@ -1628,7 +1663,7 @@ struct usb_hub * alloc_usb_hub ( struct usb_bus *bus, struct usb_device *usb,
port->address = i; port->address = i;
if ( usb ) if ( usb )
port->protocol = usb->port->protocol; port->protocol = usb->port->protocol;
INIT_LIST_HEAD ( &port->list ); INIT_LIST_HEAD ( &port->changed );
} }
return hub; return hub;
@ -1702,8 +1737,8 @@ void unregister_usb_hub ( struct usb_hub *hub ) {
/* Cancel any pending port status changes */ /* Cancel any pending port status changes */
for ( i = 1 ; i <= hub->ports ; i++ ) { for ( i = 1 ; i <= hub->ports ; i++ ) {
port = usb_port ( hub, i ); port = usb_port ( hub, i );
list_del ( &port->list ); list_del ( &port->changed );
INIT_LIST_HEAD ( &port->list ); INIT_LIST_HEAD ( &port->changed );
} }
/* Remove from hub list */ /* Remove from hub list */
@ -1724,7 +1759,7 @@ void free_usb_hub ( struct usb_hub *hub ) {
port = usb_port ( hub, i ); port = usb_port ( hub, i );
assert ( ! port->attached ); assert ( ! port->attached );
assert ( port->usb == NULL ); assert ( port->usb == NULL );
assert ( list_empty ( &port->list ) ); assert ( list_empty ( &port->changed ) );
} }
/* Free hub */ /* Free hub */
@ -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->devices );
INIT_LIST_HEAD ( &bus->hubs ); INIT_LIST_HEAD ( &bus->hubs );
INIT_LIST_HEAD ( &bus->changed ); INIT_LIST_HEAD ( &bus->changed );
INIT_LIST_HEAD ( &bus->halted );
process_init_stopped ( &bus->process, &usb_process_desc, NULL ); process_init_stopped ( &bus->process, &usb_process_desc, NULL );
bus->host = &bus->op->bus; bus->host = &bus->op->bus;

View File

@ -380,11 +380,12 @@ struct usb_endpoint {
/** Endpoint is open */ /** Endpoint is open */
int open; int open;
/** Current failure state (if any) */
int rc;
/** Buffer fill level */ /** Buffer fill level */
unsigned int fill; unsigned int fill;
/** List of halted endpoints */
struct list_head halted;
/** Host controller operations */ /** Host controller operations */
struct usb_endpoint_host_operations *host; struct usb_endpoint_host_operations *host;
/** Host controller private data */ /** Host controller private data */
@ -754,7 +755,7 @@ struct usb_port {
*/ */
struct usb_device *usb; struct usb_device *usb;
/** List of changed ports */ /** List of changed ports */
struct list_head list; struct list_head changed;
}; };
/** A USB hub */ /** A USB hub */
@ -888,6 +889,8 @@ struct usb_bus {
struct list_head hubs; struct list_head hubs;
/** List of changed ports */ /** List of changed ports */
struct list_head changed; struct list_head changed;
/** List of halted endpoints */
struct list_head halted;
/** Process */ /** Process */
struct process process; struct process process;