diff options
author | Michael Brown | 2015-03-23 16:05:28 +0100 |
---|---|---|
committer | Michael Brown | 2015-03-23 16:21:35 +0100 |
commit | de6f4e3ede895b45325c5d062d25a94d1dd37e2e (patch) | |
tree | 220b52023811b1c21c8c59855027e2f43a200a6f /src/drivers/bus | |
parent | [xhci] Ring doorbell as part of endpoint reset (diff) | |
download | ipxe-de6f4e3ede895b45325c5d062d25a94d1dd37e2e.tar.gz ipxe-de6f4e3ede895b45325c5d062d25a94d1dd37e2e.tar.xz ipxe-de6f4e3ede895b45325c5d062d25a94d1dd37e2e.zip |
[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>
Diffstat (limited to 'src/drivers/bus')
-rw-r--r-- | src/drivers/bus/usb.c | 92 |
1 files changed, 64 insertions, 28 deletions
diff --git a/src/drivers/bus/usb.c b/src/drivers/bus/usb.c index 54a115ef..a0334243 100644 --- a/src/drivers/bus/usb.c +++ b/src/drivers/bus/usb.c @@ -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 ) { @@ -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 ) @@ -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", @@ -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 ) ); @@ -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) */ @@ -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 */ @@ -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 */ @@ -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 * @@ -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 */ @@ -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 ); @@ -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 ) @@ -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 ); } @@ -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 ); } /** @@ -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 ); @@ -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; @@ -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 */ @@ -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 */ @@ -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; |