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 ) {
/* Remove from endpoint list */
usb->ep[idx] = NULL;
+ list_del ( &ep->halted );
/* Discard any recycled buffers, if applicable */
if ( ep->max )
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",
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 ) );
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) */
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 */
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 */
******************************************************************************
*/
+/** USB control transfer pseudo-header */
+struct usb_control_pseudo_header {
+ /** Completion status */
+ int rc;
+};
+
/**
* Complete USB control transfer
*
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 */
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 );
/* 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 )
return 0;
}
- /* Fail immediately if endpoint is in an error state */
- if ( ep->rc )
- return ep->rc;
-
/* Delay */
mdelay ( 1 );
}
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 );
}
/**
* @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 );
port->address = i;
if ( usb )
port->protocol = usb->port->protocol;
- INIT_LIST_HEAD ( &port->list );
+ INIT_LIST_HEAD ( &port->changed );
}
return 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 */
port = usb_port ( hub, i );
assert ( ! port->attached );
assert ( port->usb == NULL );
- assert ( list_empty ( &port->list ) );
+ assert ( list_empty ( &port->changed ) );
}
/* Free hub */
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;