mirror of https://github.com/ipxe/ipxe.git
2258 lines
55 KiB
C
2258 lines
55 KiB
C
/*
|
|
* Copyright (C) 2014 Michael Brown <mbrown@fensystems.co.uk>.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301, USA.
|
|
*
|
|
* You can also choose to distribute this program under the terms of
|
|
* the Unmodified Binary Distribution Licence (as given in the file
|
|
* COPYING.UBDL), provided that you have satisfied its requirements.
|
|
*/
|
|
|
|
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
#include <byteswap.h>
|
|
#include <ipxe/usb.h>
|
|
#include <ipxe/cdc.h>
|
|
|
|
/** @file
|
|
*
|
|
* Universal Serial Bus (USB)
|
|
*
|
|
*/
|
|
|
|
/** List of USB buses */
|
|
struct list_head usb_buses = LIST_HEAD_INIT ( usb_buses );
|
|
|
|
/** List of changed ports */
|
|
static struct list_head usb_changed = LIST_HEAD_INIT ( usb_changed );
|
|
|
|
/** List of halted endpoints */
|
|
static struct list_head usb_halted = LIST_HEAD_INIT ( usb_halted );
|
|
|
|
/******************************************************************************
|
|
*
|
|
* Utility functions
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Get USB speed name (for debugging)
|
|
*
|
|
* @v speed Speed
|
|
* @ret name Speed name
|
|
*/
|
|
static inline const char * usb_speed_name ( unsigned int speed ) {
|
|
static const char *exponents[4] = { "", "k", "M", "G" };
|
|
static char buf[ 10 /* "xxxxxXbps" + NUL */ ];
|
|
unsigned int mantissa;
|
|
unsigned int exponent;
|
|
|
|
/* Extract mantissa and exponent */
|
|
mantissa = USB_SPEED_MANTISSA ( speed );
|
|
exponent = USB_SPEED_EXPONENT ( speed );
|
|
|
|
/* Name speed */
|
|
switch ( speed ) {
|
|
case USB_SPEED_NONE: return "DETACHED";
|
|
case USB_SPEED_LOW: return "low";
|
|
case USB_SPEED_FULL: return "full";
|
|
case USB_SPEED_HIGH: return "high";
|
|
case USB_SPEED_SUPER: return "super";
|
|
default:
|
|
snprintf ( buf, sizeof ( buf ), "%d%sbps",
|
|
mantissa, exponents[exponent] );
|
|
return buf;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Transcribe USB BCD-coded value (for debugging)
|
|
*
|
|
* @v bcd BCD-coded value
|
|
* @ret string Transcribed value
|
|
*/
|
|
static inline const char * usb_bcd ( uint16_t bcd ) {
|
|
static char buf[ 6 /* "xx.xx" + NUL */ ];
|
|
uint8_t high = ( bcd >> 8 );
|
|
uint8_t low = ( bcd >> 0 );
|
|
|
|
snprintf ( buf, sizeof ( buf ), "%x.%02x", high, low );
|
|
return buf;
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* USB descriptors
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Locate USB interface association descriptor
|
|
*
|
|
* @v config Configuraton descriptor
|
|
* @v first First interface number
|
|
* @ret desc Interface association descriptor, or NULL if not found
|
|
*/
|
|
static struct usb_interface_association_descriptor *
|
|
usb_interface_association_descriptor ( struct usb_configuration_descriptor
|
|
*config,
|
|
unsigned int first ) {
|
|
struct usb_interface_association_descriptor *desc;
|
|
|
|
/* Find a matching interface association descriptor */
|
|
for_each_config_descriptor ( desc, config ) {
|
|
if ( ( desc->header.type ==
|
|
USB_INTERFACE_ASSOCIATION_DESCRIPTOR ) &&
|
|
( desc->first == first ) )
|
|
return desc;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Locate USB interface descriptor
|
|
*
|
|
* @v config Configuraton descriptor
|
|
* @v interface Interface number
|
|
* @v alternate Alternate setting
|
|
* @ret desc Interface descriptor, or NULL if not found
|
|
*/
|
|
struct usb_interface_descriptor *
|
|
usb_interface_descriptor ( struct usb_configuration_descriptor *config,
|
|
unsigned int interface, unsigned int alternate ) {
|
|
struct usb_interface_descriptor *desc;
|
|
|
|
/* Find a matching interface descriptor */
|
|
for_each_config_descriptor ( desc, config ) {
|
|
if ( ( desc->header.type == USB_INTERFACE_DESCRIPTOR ) &&
|
|
( desc->interface == interface ) &&
|
|
( desc->alternate == alternate ) )
|
|
return desc;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Locate USB endpoint descriptor
|
|
*
|
|
* @v config Configuration descriptor
|
|
* @v interface Interface descriptor
|
|
* @v type Endpoint (internal) type
|
|
* @v index Endpoint index
|
|
* @ret desc Descriptor, or NULL if not found
|
|
*/
|
|
struct usb_endpoint_descriptor *
|
|
usb_endpoint_descriptor ( struct usb_configuration_descriptor *config,
|
|
struct usb_interface_descriptor *interface,
|
|
unsigned int type, unsigned int index ) {
|
|
struct usb_endpoint_descriptor *desc;
|
|
unsigned int attributes = ( type & USB_ENDPOINT_ATTR_TYPE_MASK );
|
|
unsigned int direction = ( type & USB_DIR_IN );
|
|
|
|
/* Find a matching endpoint descriptor */
|
|
for_each_interface_descriptor ( desc, config, interface ) {
|
|
if ( ( desc->header.type == USB_ENDPOINT_DESCRIPTOR ) &&
|
|
( ( desc->attributes &
|
|
USB_ENDPOINT_ATTR_TYPE_MASK ) == attributes ) &&
|
|
( ( desc->endpoint & USB_DIR_IN ) == direction ) &&
|
|
( index-- == 0 ) )
|
|
return desc;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Locate USB endpoint companion descriptor
|
|
*
|
|
* @v config Configuration descriptor
|
|
* @v desc Endpoint descriptor
|
|
* @ret descx Companion descriptor, or NULL if not found
|
|
*/
|
|
struct usb_endpoint_companion_descriptor *
|
|
usb_endpoint_companion_descriptor ( struct usb_configuration_descriptor *config,
|
|
struct usb_endpoint_descriptor *desc ) {
|
|
struct usb_endpoint_companion_descriptor *descx;
|
|
|
|
/* Get companion descriptor, if present */
|
|
descx = container_of ( usb_next_descriptor ( &desc->header ),
|
|
struct usb_endpoint_companion_descriptor,
|
|
header );
|
|
return ( ( usb_is_within_config ( config, &descx->header ) &&
|
|
descx->header.type == USB_ENDPOINT_COMPANION_DESCRIPTOR )
|
|
? descx : NULL );
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* USB endpoint
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Get USB endpoint name (for debugging)
|
|
*
|
|
* @v ep USB endpoint
|
|
* @ret name Endpoint name
|
|
*/
|
|
const char * usb_endpoint_name ( struct usb_endpoint *ep ) {
|
|
static char buf[ 9 /* "EPxx OUT" + NUL */ ];
|
|
unsigned int address = ep->address;
|
|
|
|
snprintf ( buf, sizeof ( buf ), "EP%d%s",
|
|
( address & USB_ENDPOINT_MAX ),
|
|
( address ?
|
|
( ( address & USB_ENDPOINT_IN ) ? " IN" : " OUT" ) : "" ));
|
|
return buf;
|
|
}
|
|
|
|
/**
|
|
* Describe USB endpoint from device configuration
|
|
*
|
|
* @v ep USB endpoint
|
|
* @v config Configuration descriptor
|
|
* @v interface Interface descriptor
|
|
* @v type Endpoint (internal) type
|
|
* @v index Endpoint index
|
|
* @ret rc Return status code
|
|
*/
|
|
int usb_endpoint_described ( struct usb_endpoint *ep,
|
|
struct usb_configuration_descriptor *config,
|
|
struct usb_interface_descriptor *interface,
|
|
unsigned int type, unsigned int index ) {
|
|
struct usb_device *usb = ep->usb;
|
|
struct usb_endpoint_descriptor *desc;
|
|
struct usb_endpoint_companion_descriptor *descx;
|
|
unsigned int sizes;
|
|
unsigned int burst;
|
|
unsigned int interval;
|
|
size_t mtu;
|
|
|
|
/* Locate endpoint descriptor */
|
|
desc = usb_endpoint_descriptor ( config, interface, type, index );
|
|
if ( ! desc )
|
|
return -ENOENT;
|
|
|
|
/* Locate companion descriptor, if any */
|
|
descx = usb_endpoint_companion_descriptor ( config, desc );
|
|
|
|
/* Calculate MTU and burst size */
|
|
sizes = le16_to_cpu ( desc->sizes );
|
|
mtu = USB_ENDPOINT_MTU ( sizes );
|
|
burst = ( descx ? descx->burst : USB_ENDPOINT_BURST ( sizes ) );
|
|
|
|
/* Calculate interval */
|
|
if ( ( type & USB_ENDPOINT_ATTR_TYPE_MASK ) ==
|
|
USB_ENDPOINT_ATTR_INTERRUPT ) {
|
|
if ( usb->speed >= USB_SPEED_HIGH ) {
|
|
/* 2^(desc->interval-1) is a microframe count */
|
|
interval = ( 1 << ( desc->interval - 1 ) );
|
|
} else {
|
|
/* desc->interval is a (whole) frame count */
|
|
interval = ( desc->interval << 3 );
|
|
}
|
|
} else {
|
|
/* desc->interval is a microframe count */
|
|
interval = desc->interval;
|
|
}
|
|
|
|
/* Describe endpoint */
|
|
usb_endpoint_describe ( ep, desc->endpoint, desc->attributes,
|
|
mtu, burst, interval );
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Open USB endpoint
|
|
*
|
|
* @v ep USB endpoint
|
|
* @ret rc Return status code
|
|
*/
|
|
int usb_endpoint_open ( struct usb_endpoint *ep ) {
|
|
struct usb_device *usb = ep->usb;
|
|
unsigned int idx = USB_ENDPOINT_IDX ( ep->address );
|
|
int rc;
|
|
|
|
/* Populate host controller operations */
|
|
ep->host = &usb->port->hub->bus->op->endpoint;
|
|
|
|
/* Add to endpoint list */
|
|
if ( usb->ep[idx] != NULL ) {
|
|
DBGC ( usb, "USB %s %s is already open\n",
|
|
usb->name, usb_endpoint_name ( ep ) );
|
|
rc = -EALREADY;
|
|
goto err_already;
|
|
}
|
|
usb->ep[idx] = ep;
|
|
INIT_LIST_HEAD ( &ep->halted );
|
|
|
|
/* Open endpoint */
|
|
if ( ( rc = ep->host->open ( ep ) ) != 0 ) {
|
|
DBGC ( usb, "USB %s %s could not open: %s\n", usb->name,
|
|
usb_endpoint_name ( ep ), strerror ( rc ) );
|
|
goto err_open;
|
|
}
|
|
ep->open = 1;
|
|
|
|
DBGC2 ( usb, "USB %s %s opened with MTU %zd, burst %d, interval %d\n",
|
|
usb->name, usb_endpoint_name ( ep ), ep->mtu, ep->burst,
|
|
ep->interval );
|
|
return 0;
|
|
|
|
ep->open = 0;
|
|
ep->host->close ( ep );
|
|
err_open:
|
|
usb->ep[idx] = NULL;
|
|
err_already:
|
|
if ( ep->max )
|
|
usb_flush ( ep );
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Clear transaction translator (if applicable)
|
|
*
|
|
* @v ep USB endpoint
|
|
* @ret rc Return status code
|
|
*/
|
|
static int usb_endpoint_clear_tt ( struct usb_endpoint *ep ) {
|
|
struct usb_device *usb = ep->usb;
|
|
struct usb_port *tt;
|
|
int rc;
|
|
|
|
/* Do nothing if this is a periodic endpoint */
|
|
if ( ep->attributes & USB_ENDPOINT_ATTR_PERIODIC )
|
|
return 0;
|
|
|
|
/* Do nothing if this endpoint is not behind a transaction translator */
|
|
tt = usb_transaction_translator ( usb );
|
|
if ( ! tt )
|
|
return 0;
|
|
|
|
/* Clear transaction translator buffer */
|
|
if ( ( rc = tt->hub->driver->clear_tt ( tt->hub, tt, ep ) ) != 0 ) {
|
|
DBGC ( usb, "USB %s %s could not clear transaction translator: "
|
|
"%s\n", usb->name, usb_endpoint_name ( ep ),
|
|
strerror ( rc ) );
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Close USB endpoint
|
|
*
|
|
* @v ep USB endpoint
|
|
*/
|
|
void usb_endpoint_close ( struct usb_endpoint *ep ) {
|
|
struct usb_device *usb = ep->usb;
|
|
unsigned int idx = USB_ENDPOINT_IDX ( ep->address );
|
|
|
|
/* Sanity checks */
|
|
assert ( usb->ep[idx] == ep );
|
|
|
|
/* Close endpoint */
|
|
ep->open = 0;
|
|
ep->host->close ( ep );
|
|
assert ( ep->fill == 0 );
|
|
|
|
/* Remove from endpoint list */
|
|
usb->ep[idx] = NULL;
|
|
list_del ( &ep->halted );
|
|
|
|
/* Discard any recycled buffers, if applicable */
|
|
if ( ep->max )
|
|
usb_flush ( ep );
|
|
|
|
/* Clear transaction translator, if applicable */
|
|
usb_endpoint_clear_tt ( ep );
|
|
}
|
|
|
|
/**
|
|
* Reset USB endpoint
|
|
*
|
|
* @v ep USB endpoint
|
|
* @ret rc Return status code
|
|
*/
|
|
static int usb_endpoint_reset ( struct usb_endpoint *ep ) {
|
|
struct usb_device *usb = ep->usb;
|
|
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",
|
|
usb->name, usb_endpoint_name ( ep ), strerror ( rc ) );
|
|
return rc;
|
|
}
|
|
|
|
/* Clear transaction translator, if applicable */
|
|
if ( ( rc = usb_endpoint_clear_tt ( ep ) ) != 0 )
|
|
return rc;
|
|
|
|
/* Clear endpoint halt, if applicable */
|
|
type = ( ep->attributes & USB_ENDPOINT_ATTR_TYPE_MASK );
|
|
if ( ( type != USB_ENDPOINT_ATTR_CONTROL ) &&
|
|
( ( rc = usb_clear_feature ( usb, USB_RECIP_ENDPOINT,
|
|
USB_ENDPOINT_HALT,
|
|
ep->address ) ) != 0 ) ) {
|
|
DBGC ( usb, "USB %s %s could not clear endpoint halt: %s\n",
|
|
usb->name, usb_endpoint_name ( ep ), strerror ( rc ) );
|
|
return rc;
|
|
}
|
|
|
|
/* 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 ) );
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Update endpoint MTU
|
|
*
|
|
* @v ep USB endpoint
|
|
* @v mtu New MTU
|
|
* @ret rc Return status code
|
|
*/
|
|
static int usb_endpoint_mtu ( struct usb_endpoint *ep, size_t mtu ) {
|
|
struct usb_device *usb = ep->usb;
|
|
int rc;
|
|
|
|
/* Update MTU */
|
|
ep->mtu = mtu;
|
|
if ( ( rc = ep->host->mtu ( ep ) ) != 0 ) {
|
|
DBGC ( usb, "USB %s %s could not update MTU: %s\n",
|
|
usb->name, usb_endpoint_name ( ep ), strerror ( rc ) );
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Enqueue USB message transfer
|
|
*
|
|
* @v ep USB endpoint
|
|
* @v request Request
|
|
* @v value Value parameter
|
|
* @v index Index parameter
|
|
* @v iobuf I/O buffer
|
|
* @ret rc Return status code
|
|
*
|
|
* The I/O buffer must have sufficient headroom to contain a setup
|
|
* packet.
|
|
*/
|
|
int usb_message ( struct usb_endpoint *ep, unsigned int request,
|
|
unsigned int value, unsigned int index,
|
|
struct io_buffer *iobuf ) {
|
|
struct usb_device *usb = ep->usb;
|
|
struct usb_port *port = usb->port;
|
|
struct usb_setup_packet *packet;
|
|
size_t len = iob_len ( iobuf );
|
|
int rc;
|
|
|
|
/* Sanity check */
|
|
assert ( iob_headroom ( iobuf ) >= sizeof ( *packet ) );
|
|
|
|
/* Fail immediately if device has been unplugged */
|
|
if ( port->disconnected )
|
|
return -ENODEV;
|
|
|
|
/* Reset endpoint if required */
|
|
if ( ( ! list_empty ( &ep->halted ) ) &&
|
|
( ( rc = usb_endpoint_reset ( ep ) ) != 0 ) )
|
|
return rc;
|
|
|
|
/* Zero input data buffer (if applicable) */
|
|
if ( request & USB_DIR_IN )
|
|
memset ( iobuf->data, 0, len );
|
|
|
|
/* Construct setup packet */
|
|
packet = iob_push ( iobuf, sizeof ( *packet ) );
|
|
packet->request = cpu_to_le16 ( request );
|
|
packet->value = cpu_to_le16 ( value );
|
|
packet->index = cpu_to_le16 ( index );
|
|
packet->len = cpu_to_le16 ( len );
|
|
|
|
/* Enqueue message transfer */
|
|
if ( ( rc = ep->host->message ( ep, iobuf ) ) != 0 ) {
|
|
DBGC ( usb, "USB %s %s could not enqueue message transfer: "
|
|
"%s\n", usb->name, usb_endpoint_name ( ep ),
|
|
strerror ( rc ) );
|
|
return rc;
|
|
}
|
|
|
|
/* Increment fill level */
|
|
ep->fill++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Enqueue USB stream transfer
|
|
*
|
|
* @v ep USB endpoint
|
|
* @v iobuf I/O buffer
|
|
* @v terminate Terminate using a short packet
|
|
* @ret rc Return status code
|
|
*/
|
|
int usb_stream ( struct usb_endpoint *ep, struct io_buffer *iobuf,
|
|
int terminate ) {
|
|
struct usb_device *usb = ep->usb;
|
|
struct usb_port *port = usb->port;
|
|
int zlp;
|
|
int rc;
|
|
|
|
/* Fail immediately if device has been unplugged */
|
|
if ( port->disconnected )
|
|
return -ENODEV;
|
|
|
|
/* Reset endpoint if required */
|
|
if ( ( ! list_empty ( &ep->halted ) ) &&
|
|
( ( rc = usb_endpoint_reset ( ep ) ) != 0 ) )
|
|
return rc;
|
|
|
|
/* Append a zero-length packet if necessary */
|
|
zlp = terminate;
|
|
if ( iob_len ( iobuf ) & ( ep->mtu - 1 ) )
|
|
zlp = 0;
|
|
|
|
/* Enqueue stream transfer */
|
|
if ( ( rc = ep->host->stream ( ep, iobuf, zlp ) ) != 0 ) {
|
|
DBGC ( usb, "USB %s %s could not enqueue stream transfer: %s\n",
|
|
usb->name, usb_endpoint_name ( ep ), strerror ( rc ) );
|
|
return rc;
|
|
}
|
|
|
|
/* Increment fill level */
|
|
ep->fill++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Complete transfer (possibly with error)
|
|
*
|
|
* @v ep USB endpoint
|
|
* @v iobuf I/O buffer
|
|
* @v rc Completion status code
|
|
*/
|
|
void usb_complete_err ( struct usb_endpoint *ep, struct io_buffer *iobuf,
|
|
int rc ) {
|
|
struct usb_device *usb = ep->usb;
|
|
|
|
/* Decrement fill level */
|
|
assert ( ep->fill > 0 );
|
|
ep->fill--;
|
|
|
|
/* Schedule reset, if applicable */
|
|
if ( ( rc != 0 ) && ep->open ) {
|
|
DBGC ( usb, "USB %s %s completion failed: %s\n",
|
|
usb->name, usb_endpoint_name ( ep ), strerror ( rc ) );
|
|
list_del ( &ep->halted );
|
|
list_add_tail ( &ep->halted, &usb_halted );
|
|
}
|
|
|
|
/* Report completion */
|
|
ep->driver->complete ( ep, iobuf, rc );
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* Endpoint refilling
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Prefill endpoint recycled buffer list
|
|
*
|
|
* @v ep USB endpoint
|
|
* @ret rc Return status code
|
|
*/
|
|
int usb_prefill ( struct usb_endpoint *ep ) {
|
|
struct io_buffer *iobuf;
|
|
size_t len = ( ep->len ? ep->len : ep->mtu );
|
|
unsigned int fill;
|
|
int rc;
|
|
|
|
/* Sanity checks */
|
|
assert ( ep->fill == 0 );
|
|
assert ( ep->max > 0 );
|
|
assert ( list_empty ( &ep->recycled ) );
|
|
|
|
/* Fill recycled buffer list */
|
|
for ( fill = 0 ; fill < ep->max ; fill++ ) {
|
|
|
|
/* Allocate I/O buffer */
|
|
iobuf = alloc_iob ( len );
|
|
if ( ! iobuf ) {
|
|
rc = -ENOMEM;
|
|
goto err_alloc;
|
|
}
|
|
|
|
/* Add to recycled buffer list */
|
|
list_add_tail ( &iobuf->list, &ep->recycled );
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_alloc:
|
|
usb_flush ( ep );
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Refill endpoint
|
|
*
|
|
* @v ep USB endpoint
|
|
* @ret rc Return status code
|
|
*/
|
|
int usb_refill ( struct usb_endpoint *ep ) {
|
|
struct io_buffer *iobuf;
|
|
size_t len = ( ep->len ? ep->len : ep->mtu );
|
|
int rc;
|
|
|
|
/* Sanity checks */
|
|
assert ( ep->open );
|
|
assert ( ep->max > 0 );
|
|
|
|
/* Refill endpoint */
|
|
while ( ep->fill < ep->max ) {
|
|
|
|
/* Get or allocate buffer */
|
|
if ( list_empty ( &ep->recycled ) ) {
|
|
/* Recycled buffer list is empty; allocate new buffer */
|
|
iobuf = alloc_iob ( len );
|
|
if ( ! iobuf )
|
|
return -ENOMEM;
|
|
} else {
|
|
/* Get buffer from recycled buffer list */
|
|
iobuf = list_first_entry ( &ep->recycled,
|
|
struct io_buffer, list );
|
|
assert ( iobuf != NULL );
|
|
list_del ( &iobuf->list );
|
|
}
|
|
|
|
/* Reset buffer to maximum size */
|
|
assert ( iob_len ( iobuf ) <= len );
|
|
iob_put ( iobuf, ( len - iob_len ( iobuf ) ) );
|
|
|
|
/* Enqueue buffer */
|
|
if ( ( rc = usb_stream ( ep, iobuf, 0 ) ) != 0 ) {
|
|
list_add ( &iobuf->list, &ep->recycled );
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Discard endpoint recycled buffer list
|
|
*
|
|
* @v ep USB endpoint
|
|
*/
|
|
void usb_flush ( struct usb_endpoint *ep ) {
|
|
struct io_buffer *iobuf;
|
|
struct io_buffer *tmp;
|
|
|
|
/* Sanity checks */
|
|
assert ( ! ep->open );
|
|
assert ( ep->max > 0 );
|
|
|
|
/* Free all I/O buffers */
|
|
list_for_each_entry_safe ( iobuf, tmp, &ep->recycled, list ) {
|
|
list_del ( &iobuf->list );
|
|
free_iob ( iobuf );
|
|
}
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* Control endpoint
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/** USB control transfer pseudo-header */
|
|
struct usb_control_pseudo_header {
|
|
/** Completion status */
|
|
int rc;
|
|
};
|
|
|
|
/**
|
|
* Complete USB control transfer
|
|
*
|
|
* @v ep USB endpoint
|
|
* @v iobuf I/O buffer
|
|
* @v rc Completion status code
|
|
*/
|
|
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;
|
|
|
|
/* Record completion status in buffer */
|
|
pshdr = iob_push ( iobuf, sizeof ( *pshdr ) );
|
|
pshdr->rc = rc;
|
|
|
|
/* Add to list of completed I/O buffers */
|
|
list_add_tail ( &iobuf->list, &usb->complete );
|
|
}
|
|
|
|
/** USB control endpoint driver operations */
|
|
static struct usb_endpoint_driver_operations usb_control_operations = {
|
|
.complete = usb_control_complete,
|
|
};
|
|
|
|
/**
|
|
* Issue USB control transaction
|
|
*
|
|
* @v usb USB device
|
|
* @v request Request
|
|
* @v value Value parameter
|
|
* @v index Index parameter
|
|
* @v data Data buffer (if any)
|
|
* @v len Length of data
|
|
* @ret rc Return status code
|
|
*/
|
|
int usb_control ( struct usb_device *usb, unsigned int request,
|
|
unsigned int value, unsigned int index, void *data,
|
|
size_t len ) {
|
|
struct usb_bus *bus = usb->port->hub->bus;
|
|
struct usb_endpoint *ep = &usb->control;
|
|
struct io_buffer *iobuf;
|
|
struct io_buffer *cmplt;
|
|
union {
|
|
struct usb_setup_packet setup;
|
|
struct usb_control_pseudo_header pshdr;
|
|
} *headroom;
|
|
struct usb_control_pseudo_header *pshdr;
|
|
unsigned int i;
|
|
int rc;
|
|
|
|
/* Allocate I/O buffer */
|
|
iobuf = alloc_iob ( sizeof ( *headroom ) + len );
|
|
if ( ! iobuf ) {
|
|
rc = -ENOMEM;
|
|
goto err_alloc;
|
|
}
|
|
iob_reserve ( iobuf, sizeof ( *headroom ) );
|
|
iob_put ( iobuf, len );
|
|
if ( request & USB_DIR_IN ) {
|
|
memset ( data, 0, len );
|
|
} else {
|
|
memcpy ( iobuf->data, data, len );
|
|
}
|
|
|
|
/* Enqueue message */
|
|
if ( ( rc = usb_message ( ep, request, value, index, iobuf ) ) != 0 )
|
|
goto err_message;
|
|
|
|
/* Wait for completion */
|
|
for ( i = 0 ; i < USB_CONTROL_MAX_WAIT_MS ; i++ ) {
|
|
|
|
/* Poll bus */
|
|
usb_poll ( bus );
|
|
|
|
/* Check for completion */
|
|
while ( ( cmplt = list_first_entry ( &usb->complete,
|
|
struct io_buffer,
|
|
list ) ) ) {
|
|
|
|
/* 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: "
|
|
"%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 ) {
|
|
DBGC ( usb, "USB %s control %04x:%04x:%04x "
|
|
"failed: %s\n", usb->name, request,
|
|
value, index, strerror ( rc ) );
|
|
free_iob ( cmplt );
|
|
return rc;
|
|
}
|
|
|
|
/* Copy completion to data buffer, if applicable */
|
|
assert ( iob_len ( cmplt ) <= len );
|
|
if ( request & USB_DIR_IN )
|
|
memcpy ( data, cmplt->data, iob_len ( cmplt ) );
|
|
free_iob ( cmplt );
|
|
return 0;
|
|
}
|
|
|
|
/* Delay */
|
|
mdelay ( 1 );
|
|
}
|
|
|
|
DBGC ( usb, "USB %s timed out waiting for control %04x:%04x:%04x\n",
|
|
usb->name, request, value, index );
|
|
return -ETIMEDOUT;
|
|
|
|
err_message:
|
|
free_iob ( iobuf );
|
|
err_alloc:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Get USB string descriptor
|
|
*
|
|
* @v usb USB device
|
|
* @v index String index
|
|
* @v language Language ID
|
|
* @v buf Data buffer
|
|
* @v len Length of buffer
|
|
* @ret len String length (excluding NUL), or negative error
|
|
*/
|
|
int usb_get_string_descriptor ( struct usb_device *usb, unsigned int index,
|
|
unsigned int language, char *buf, size_t len ) {
|
|
size_t max = ( len ? ( len - 1 /* NUL */ ) : 0 );
|
|
struct {
|
|
struct usb_descriptor_header header;
|
|
uint16_t character[max];
|
|
} __attribute__ (( packed )) *desc;
|
|
unsigned int actual;
|
|
unsigned int i;
|
|
int rc;
|
|
|
|
/* Allocate buffer for string */
|
|
desc = malloc ( sizeof ( *desc ) );
|
|
if ( ! desc ) {
|
|
rc = -ENOMEM;
|
|
goto err_alloc;
|
|
}
|
|
|
|
/* Get descriptor */
|
|
if ( ( rc = usb_get_descriptor ( usb, 0, USB_STRING_DESCRIPTOR, index,
|
|
language, &desc->header,
|
|
sizeof ( *desc ) ) ) != 0 )
|
|
goto err_get_descriptor;
|
|
|
|
/* Copy to buffer */
|
|
actual = ( ( desc->header.len - sizeof ( desc->header ) ) /
|
|
sizeof ( desc->character[0] ) );
|
|
for ( i = 0 ; ( ( i < actual ) && ( i < max ) ) ; i++ )
|
|
buf[i] = le16_to_cpu ( desc->character[i] );
|
|
if ( len )
|
|
buf[i] = '\0';
|
|
|
|
/* Free buffer */
|
|
free ( desc );
|
|
|
|
return actual;
|
|
|
|
err_get_descriptor:
|
|
free ( desc );
|
|
err_alloc:
|
|
return rc;
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* USB device driver
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Get USB configuration descriptor
|
|
*
|
|
* @v usb USB device
|
|
* @v index Configuration index
|
|
* @ret config Configuration descriptor
|
|
* @ret rc Return status code
|
|
*
|
|
* The configuration descriptor is dynamically allocated and must
|
|
* eventually be freed by the caller.
|
|
*/
|
|
static int
|
|
usb_config_descriptor ( struct usb_device *usb, unsigned int index,
|
|
struct usb_configuration_descriptor **config ) {
|
|
struct usb_configuration_descriptor partial;
|
|
size_t len;
|
|
int rc;
|
|
|
|
/* Read first part of configuration descriptor to get size */
|
|
if ( ( rc = usb_get_config_descriptor ( usb, index, &partial,
|
|
sizeof ( partial ) ) ) != 0 ) {
|
|
DBGC ( usb, "USB %s could not get configuration descriptor %d: "
|
|
"%s\n", usb->name, index, strerror ( rc ) );
|
|
goto err_get_partial;
|
|
}
|
|
len = le16_to_cpu ( partial.len );
|
|
if ( len < sizeof ( partial ) ) {
|
|
DBGC ( usb, "USB %s underlength configuraton descriptor %d\n",
|
|
usb->name, index );
|
|
rc = -EINVAL;
|
|
goto err_partial_len;
|
|
}
|
|
|
|
/* Allocate buffer for whole configuration descriptor */
|
|
*config = malloc ( len );
|
|
if ( ! *config ) {
|
|
rc = -ENOMEM;
|
|
goto err_alloc_config;
|
|
}
|
|
|
|
/* Read whole configuration descriptor */
|
|
if ( ( rc = usb_get_config_descriptor ( usb, index, *config,
|
|
len ) ) != 0 ) {
|
|
DBGC ( usb, "USB %s could not get configuration descriptor %d: "
|
|
"%s\n", usb->name, index, strerror ( rc ) );
|
|
goto err_get_config_descriptor;
|
|
}
|
|
if ( (*config)->len != partial.len ) {
|
|
DBGC ( usb, "USB %s bad configuration descriptor %d length\n",
|
|
usb->name, index );
|
|
rc = -EINVAL;
|
|
goto err_config_len;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_config_len:
|
|
err_get_config_descriptor:
|
|
free ( *config );
|
|
err_alloc_config:
|
|
err_partial_len:
|
|
err_get_partial:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Describe USB function
|
|
*
|
|
* @v usb USB device
|
|
* @v config Configuration descriptor
|
|
* @v first First interface number
|
|
* @v interfaces Interface list to fill in
|
|
* @v desc Function descriptor to fill in
|
|
* @ret rc Return status code
|
|
*/
|
|
static int usb_describe ( struct usb_device *usb,
|
|
struct usb_configuration_descriptor *config,
|
|
unsigned int first, uint8_t *interfaces,
|
|
struct usb_function_descriptor *desc ) {
|
|
struct usb_interface_association_descriptor *association;
|
|
struct usb_interface_descriptor *interface;
|
|
struct cdc_union_descriptor *cdc_union;
|
|
unsigned int i;
|
|
|
|
/* Fill in vendor and product ID */
|
|
memset ( desc, 0, sizeof ( *desc ) );
|
|
desc->vendor = le16_to_cpu ( usb->device.vendor );
|
|
desc->product = le16_to_cpu ( usb->device.product );
|
|
|
|
/* First, look for an interface association descriptor */
|
|
association = usb_interface_association_descriptor ( config, first );
|
|
if ( association ) {
|
|
|
|
/* Sanity check */
|
|
assert ( association->first == first );
|
|
if ( ( first + association->count ) > config->interfaces ) {
|
|
DBGC ( usb, "USB %s has invalid association [%d-%d)\n",
|
|
usb->name, first, ( first + association->count));
|
|
return -ERANGE;
|
|
}
|
|
|
|
/* Describe function */
|
|
memcpy ( &desc->class, &association->class,
|
|
sizeof ( desc->class ) );
|
|
desc->count = association->count;
|
|
for ( i = 0 ; i < association->count ; i++ )
|
|
interfaces[i] = ( first + i );
|
|
return 0;
|
|
}
|
|
|
|
/* Next, look for an interface descriptor */
|
|
interface = usb_interface_descriptor ( config, first, 0 );
|
|
if ( ! interface ) {
|
|
DBGC ( usb, "USB %s has no descriptor for interface %d\n",
|
|
usb->name, first );
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* Describe function */
|
|
memcpy ( &desc->class, &interface->class, sizeof ( desc->class ) );
|
|
desc->count = 1;
|
|
interfaces[0] = first;
|
|
|
|
/* Look for a CDC union descriptor, if applicable */
|
|
if ( ( desc->class.class.class == USB_CLASS_CDC ) &&
|
|
( cdc_union = cdc_union_descriptor ( config, interface ) ) ) {
|
|
|
|
/* Determine interface count */
|
|
desc->count = ( ( cdc_union->header.len -
|
|
offsetof ( typeof ( *cdc_union ),
|
|
interface[0] ) ) /
|
|
sizeof ( cdc_union->interface[0] ) );
|
|
if ( desc->count > config->interfaces ) {
|
|
DBGC ( usb, "USB %s has invalid union functional "
|
|
"descriptor with %d interfaces\n",
|
|
usb->name, desc->count );
|
|
return -ERANGE;
|
|
}
|
|
|
|
/* Describe function */
|
|
for ( i = 0 ; i < desc->count ; i++ ) {
|
|
if ( cdc_union->interface[i] >= config->interfaces ) {
|
|
DBGC ( usb, "USB %s has invalid union "
|
|
"functional descriptor covering "
|
|
"interface %d\n", usb->name,
|
|
cdc_union->interface[i] );
|
|
return -ERANGE;
|
|
}
|
|
interfaces[i] = cdc_union->interface[i];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Update list of used interface
|
|
*
|
|
* @v usb USB device
|
|
* @v count Number of interfaces
|
|
* @v interface List of interfaces
|
|
* @v used List of already-used interfaces
|
|
* @ret rc Return status code
|
|
*/
|
|
static int usb_used ( struct usb_device *usb, unsigned int count,
|
|
uint8_t *interface, uint8_t *used ) {
|
|
unsigned int i;
|
|
|
|
for ( i = 0 ; i < count ; i++ ) {
|
|
if ( used[interface[i]] ) {
|
|
DBGC ( usb, "USB %s interface %d already in use\n",
|
|
usb->name, interface[i] );
|
|
return -EINVAL;
|
|
}
|
|
used[interface[i]] = 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Find USB device driver
|
|
*
|
|
* @v desc Function descriptor
|
|
* @ret id USB device ID, or NULL
|
|
* @ret driver USB device driver, or NULL
|
|
*/
|
|
struct usb_driver * usb_find_driver ( struct usb_function_descriptor *desc,
|
|
struct usb_device_id **id ) {
|
|
struct usb_driver *driver;
|
|
unsigned int i;
|
|
|
|
/* Look for a matching driver */
|
|
for_each_table_entry ( driver, USB_DRIVERS ) {
|
|
for ( i = 0 ; i < driver->id_count ; i++ ) {
|
|
|
|
/* Ignore non-matching driver class */
|
|
if ( ( driver->class.class.scalar ^ desc->class.scalar )
|
|
& driver->class.mask.scalar )
|
|
continue;
|
|
|
|
/* Look for a matching ID */
|
|
*id = &driver->ids[i];
|
|
if ( ( ( (*id)->vendor == desc->vendor ) ||
|
|
( (*id)->vendor == USB_ANY_ID ) ) &&
|
|
( ( (*id)->product == desc->product ) ||
|
|
( (*id)->product == USB_ANY_ID ) ) )
|
|
return driver;
|
|
}
|
|
}
|
|
|
|
/* Not found */
|
|
*id = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Get USB device configuration score
|
|
*
|
|
* @v usb USB device
|
|
* @v config Configuration descriptor
|
|
* @ret score Device configuration score, or negative error
|
|
*/
|
|
static int usb_score ( struct usb_device *usb,
|
|
struct usb_configuration_descriptor *config ) {
|
|
uint8_t used[config->interfaces];
|
|
uint8_t interface[config->interfaces];
|
|
struct usb_function_descriptor desc;
|
|
struct usb_driver *driver;
|
|
struct usb_device_id *id;
|
|
unsigned int first;
|
|
unsigned int score = 0;
|
|
int rc;
|
|
|
|
/* Identify each function in turn */
|
|
memset ( used, 0, sizeof ( used ) );
|
|
for ( first = 0 ; first < config->interfaces ; first++ ) {
|
|
|
|
/* Skip interfaces already used */
|
|
if ( used[first] )
|
|
continue;
|
|
|
|
/* Describe function */
|
|
if ( ( rc = usb_describe ( usb, config, first, interface,
|
|
&desc ) ) != 0 )
|
|
return rc;
|
|
|
|
/* Update used interfaces */
|
|
if ( ( rc = usb_used ( usb, desc.count, interface,
|
|
used ) ) != 0 )
|
|
return rc;
|
|
|
|
/* Look for a driver for this function */
|
|
driver = usb_find_driver ( &desc, &id );
|
|
if ( driver )
|
|
score += driver->score;
|
|
}
|
|
|
|
return score;
|
|
}
|
|
|
|
/**
|
|
* Probe USB device driver
|
|
*
|
|
* @v func USB function
|
|
* @v config Configuration descriptor
|
|
* @ret rc Return status code
|
|
*/
|
|
static int usb_probe ( struct usb_function *func,
|
|
struct usb_configuration_descriptor *config ) {
|
|
struct usb_device *usb = func->usb;
|
|
struct usb_driver *driver;
|
|
struct usb_device_id *id;
|
|
int rc;
|
|
|
|
/* Identify driver */
|
|
driver = usb_find_driver ( &func->desc, &id );
|
|
if ( ! driver ) {
|
|
DBGC ( usb, "USB %s %04x:%04x class %d:%d:%d has no driver\n",
|
|
func->name, func->desc.vendor, func->desc.product,
|
|
func->desc.class.class.class,
|
|
func->desc.class.class.subclass,
|
|
func->desc.class.class.protocol );
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* Record driver */
|
|
func->driver = driver;
|
|
func->id = id;
|
|
func->dev.driver_name = id->name;
|
|
|
|
/* Probe driver */
|
|
if ( ( rc = driver->probe ( func, config ) ) != 0 ) {
|
|
DBGC ( usb, "USB %s failed to probe driver %s: %s\n",
|
|
func->name, id->name, strerror ( rc ) );
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Remove USB device driver
|
|
*
|
|
* @v func USB function
|
|
*/
|
|
static void usb_remove ( struct usb_function *func ) {
|
|
|
|
/* Remove driver */
|
|
func->driver->remove ( func );
|
|
}
|
|
|
|
/**
|
|
* Probe all USB device drivers
|
|
*
|
|
* @v usb USB device
|
|
* @v config Configuration descriptor
|
|
*/
|
|
static void
|
|
usb_probe_all ( struct usb_device *usb,
|
|
struct usb_configuration_descriptor *config ) {
|
|
struct usb_bus *bus = usb->port->hub->bus;
|
|
struct usb_function *func;
|
|
uint8_t used[config->interfaces];
|
|
unsigned int first;
|
|
unsigned int i;
|
|
int rc;
|
|
|
|
/* Identify each function in turn */
|
|
memset ( used, 0, sizeof ( used ) );
|
|
for ( first = 0 ; first < config->interfaces ; first++ ) {
|
|
|
|
/* Skip interfaces already used */
|
|
if ( used[first] )
|
|
continue;
|
|
|
|
/* Allocate and initialise structure */
|
|
func = zalloc ( sizeof ( *func ) +
|
|
( config->interfaces *
|
|
sizeof ( func->interface[0] ) ) );
|
|
if ( ! func )
|
|
goto err_alloc;
|
|
func->name = func->dev.name;
|
|
func->usb = usb;
|
|
func->dev.desc.bus_type = BUS_TYPE_USB;
|
|
func->dev.desc.location = usb->address;
|
|
func->dev.desc.vendor = le16_to_cpu ( usb->device.vendor );
|
|
func->dev.desc.device = le16_to_cpu ( usb->device.product );
|
|
snprintf ( func->dev.name, sizeof ( func->dev.name ),
|
|
"%s-%d.%d", usb->name, config->config, first );
|
|
INIT_LIST_HEAD ( &func->dev.children );
|
|
func->dev.parent = bus->dev;
|
|
list_add_tail ( &func->list, &usb->functions );
|
|
|
|
/* Identify function */
|
|
if ( ( rc = usb_describe ( usb, config, first, func->interface,
|
|
&func->desc ) ) != 0 )
|
|
goto err_describe;
|
|
assert ( func->desc.count <= config->interfaces );
|
|
|
|
/* Mark interfaces as used */
|
|
if ( ( rc = usb_used ( usb, func->desc.count, func->interface,
|
|
used ) ) != 0 )
|
|
goto err_used;
|
|
|
|
/* Probe device driver */
|
|
if ( ( rc = usb_probe ( func, config ) ) != 0 )
|
|
goto err_probe;
|
|
DBGC ( usb, "USB %s %04x:%04x class %d:%d:%d interfaces ",
|
|
func->name, func->desc.vendor, func->desc.product,
|
|
func->desc.class.class.class,
|
|
func->desc.class.class.subclass,
|
|
func->desc.class.class.protocol );
|
|
for ( i = 0 ; i < func->desc.count ; i++ )
|
|
DBGC ( usb, "%s%d", ( i ? "," : "" ),
|
|
func->interface[i] );
|
|
DBGC ( usb, " using driver %s\n", func->dev.driver_name );
|
|
|
|
/* Add to device hierarchy */
|
|
list_add_tail ( &func->dev.siblings, &bus->dev->children );
|
|
|
|
continue;
|
|
|
|
list_del ( &func->dev.siblings );
|
|
usb_remove ( func );
|
|
err_probe:
|
|
err_used:
|
|
err_describe:
|
|
list_del ( &func->list );
|
|
free ( func );
|
|
err_alloc:
|
|
/* Continue registering other functions */
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove all device drivers
|
|
*
|
|
* @v usb USB device
|
|
*/
|
|
static void usb_remove_all ( struct usb_device *usb ) {
|
|
struct usb_function *func;
|
|
struct usb_function *tmp;
|
|
|
|
/* Remove all functions */
|
|
list_for_each_entry_safe ( func, tmp, &usb->functions, list ) {
|
|
|
|
/* Remove device driver */
|
|
usb_remove ( func );
|
|
|
|
/* Remove from device hierarchy */
|
|
assert ( list_empty ( &func->dev.children ) );
|
|
list_del ( &func->dev.siblings );
|
|
|
|
/* Remove from list of functions */
|
|
list_del ( &func->list );
|
|
|
|
/* Free function */
|
|
free ( func );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear USB device configuration
|
|
*
|
|
* @v usb USB device
|
|
*/
|
|
static void usb_deconfigure ( struct usb_device *usb ) {
|
|
unsigned int i;
|
|
|
|
/* Remove device drivers */
|
|
usb_remove_all ( usb );
|
|
|
|
/* Sanity checks */
|
|
for ( i = 0 ; i < ( sizeof ( usb->ep ) / sizeof ( usb->ep[0] ) ) ; i++){
|
|
if ( i != USB_ENDPOINT_IDX ( USB_EP0_ADDRESS ) )
|
|
assert ( usb->ep[i] == NULL );
|
|
}
|
|
|
|
/* Clear device configuration */
|
|
usb_set_configuration ( usb, 0 );
|
|
}
|
|
|
|
/**
|
|
* Choose our preferred USB device configuration
|
|
*
|
|
* @v usb USB device
|
|
* @ret rc Return status code
|
|
*/
|
|
static int usb_autoconfigure ( struct usb_device *usb ) {
|
|
struct usb_configuration_descriptor *config;
|
|
unsigned int preferred = 0;
|
|
unsigned int index;
|
|
int score;
|
|
int best = 0;
|
|
int rc;
|
|
|
|
/* Calculate driver score for each configuration index */
|
|
for ( index = 0 ; index < usb->device.configurations ; index++ ) {
|
|
|
|
/* Read configuration descriptor */
|
|
if ( ( rc = usb_config_descriptor ( usb, index,
|
|
&config ) ) != 0 )
|
|
goto err_config;
|
|
|
|
/* Get score for this configuration */
|
|
score = usb_score ( usb, config );
|
|
if ( score < 0 ) {
|
|
rc = score;
|
|
goto err_score;
|
|
}
|
|
DBGC2 ( usb, "USB %s configuration %d score %d\n",
|
|
usb->name, config->config, score );
|
|
|
|
/* Record as preferred configuration, if applicable */
|
|
if ( score > best ) {
|
|
best = score;
|
|
preferred = index;
|
|
}
|
|
|
|
/* Free configuration descriptor */
|
|
free ( config );
|
|
config = NULL;
|
|
}
|
|
|
|
/* Read preferred configuration descriptor */
|
|
if ( ( rc = usb_config_descriptor ( usb, preferred, &config ) ) != 0 )
|
|
goto err_preferred;
|
|
|
|
/* Set configuration */
|
|
if ( ( rc = usb_set_configuration ( usb, config->config ) ) != 0){
|
|
DBGC ( usb, "USB %s could not set configuration %d: %s\n",
|
|
usb->name, config->config, strerror ( rc ) );
|
|
goto err_set_configuration;
|
|
}
|
|
|
|
/* Probe USB device drivers */
|
|
usb_probe_all ( usb, config );
|
|
|
|
/* Free configuration descriptor */
|
|
free ( config );
|
|
|
|
return 0;
|
|
|
|
usb_remove_all ( usb );
|
|
usb_set_configuration ( usb, 0 );
|
|
err_set_configuration:
|
|
free ( config );
|
|
err_preferred:
|
|
return rc;
|
|
|
|
err_score:
|
|
free ( config );
|
|
err_config:
|
|
return rc;
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* USB device
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Allocate USB device
|
|
*
|
|
* @v port USB port
|
|
* @ret usb USB device, or NULL on allocation failure
|
|
*/
|
|
static struct usb_device * alloc_usb ( struct usb_port *port ) {
|
|
struct usb_hub *hub = port->hub;
|
|
struct usb_bus *bus = hub->bus;
|
|
struct usb_device *usb;
|
|
|
|
/* Allocate and initialise structure */
|
|
usb = zalloc ( sizeof ( *usb ) );
|
|
if ( ! usb )
|
|
return NULL;
|
|
snprintf ( usb->name, sizeof ( usb->name ), "%s%c%d", hub->name,
|
|
( hub->usb ? '.' : '-' ), port->address );
|
|
usb->port = port;
|
|
INIT_LIST_HEAD ( &usb->functions );
|
|
usb->host = &bus->op->device;
|
|
usb_endpoint_init ( &usb->control, usb, &usb_control_operations );
|
|
INIT_LIST_HEAD ( &usb->complete );
|
|
|
|
return usb;
|
|
}
|
|
|
|
/**
|
|
* Register USB device
|
|
*
|
|
* @v usb USB device
|
|
* @ret rc Return status code
|
|
*/
|
|
static int register_usb ( struct usb_device *usb ) {
|
|
struct usb_port *port = usb->port;
|
|
struct usb_hub *hub = port->hub;
|
|
struct usb_bus *bus = hub->bus;
|
|
unsigned int protocol;
|
|
size_t mtu;
|
|
int rc;
|
|
|
|
/* Add to port */
|
|
if ( port->usb != NULL ) {
|
|
DBGC ( hub, "USB hub %s port %d is already registered to %s\n",
|
|
hub->name, port->address, port->usb->name );
|
|
rc = -EALREADY;
|
|
goto err_already;
|
|
}
|
|
port->usb = usb;
|
|
|
|
/* Add to bus device list */
|
|
list_add_tail ( &usb->list, &bus->devices );
|
|
|
|
/* Enable device */
|
|
if ( ( rc = hub->driver->enable ( hub, port ) ) != 0 ) {
|
|
DBGC ( hub, "USB hub %s port %d could not enable: %s\n",
|
|
hub->name, port->address, strerror ( rc ) );
|
|
goto err_enable;
|
|
}
|
|
|
|
/* Allow recovery interval since port may have been reset */
|
|
mdelay ( USB_RESET_RECOVER_DELAY_MS );
|
|
|
|
/* Get device speed */
|
|
if ( ( rc = hub->driver->speed ( hub, port ) ) != 0 ) {
|
|
DBGC ( hub, "USB hub %s port %d could not get speed: %s\n",
|
|
hub->name, port->address, strerror ( rc ) );
|
|
goto err_speed;
|
|
}
|
|
usb->speed = port->speed;
|
|
DBGC2 ( usb, "USB %s attached as %s-speed device\n",
|
|
usb->name, usb_speed_name ( usb->speed ) );
|
|
|
|
/* Open device */
|
|
if ( ( rc = usb->host->open ( usb ) ) != 0 ) {
|
|
DBGC ( usb, "USB %s could not open: %s\n",
|
|
usb->name, strerror ( rc ) );
|
|
goto err_open;
|
|
}
|
|
|
|
/* Describe control endpoint */
|
|
mtu = USB_EP0_DEFAULT_MTU ( usb->speed );
|
|
usb_endpoint_describe ( &usb->control, USB_EP0_ADDRESS,
|
|
USB_EP0_ATTRIBUTES, mtu, USB_EP0_BURST,
|
|
USB_EP0_INTERVAL );
|
|
|
|
/* Open control endpoint */
|
|
if ( ( rc = usb_endpoint_open ( &usb->control ) ) != 0 )
|
|
goto err_open_control;
|
|
assert ( usb_endpoint ( usb, USB_EP0_ADDRESS ) == &usb->control );
|
|
|
|
/* Assign device address */
|
|
if ( ( rc = usb->host->address ( usb ) ) != 0 ) {
|
|
DBGC ( usb, "USB %s could not set address: %s\n",
|
|
usb->name, strerror ( rc ) );
|
|
goto err_address;
|
|
}
|
|
DBGC2 ( usb, "USB %s assigned address %d\n", usb->name, usb->address );
|
|
|
|
/* Allow recovery interval after Set Address command */
|
|
mdelay ( USB_SET_ADDRESS_RECOVER_DELAY_MS );
|
|
|
|
/* Read first part of device descriptor to get EP0 MTU */
|
|
if ( ( rc = usb_get_mtu ( usb, &usb->device ) ) != 0 ) {
|
|
DBGC ( usb, "USB %s could not get MTU: %s\n",
|
|
usb->name, strerror ( rc ) );
|
|
goto err_get_mtu;
|
|
}
|
|
|
|
/* Calculate EP0 MTU */
|
|
protocol = le16_to_cpu ( usb->device.protocol );
|
|
mtu = ( ( protocol < USB_PROTO_3_0 ) ?
|
|
usb->device.mtu : ( 1 << usb->device.mtu ) );
|
|
DBGC2 ( usb, "USB %s has control MTU %zd (guessed %zd)\n",
|
|
usb->name, mtu, usb->control.mtu );
|
|
|
|
/* Update MTU */
|
|
if ( ( rc = usb_endpoint_mtu ( &usb->control, mtu ) ) != 0 )
|
|
goto err_mtu;
|
|
|
|
/* Read whole device descriptor */
|
|
if ( ( rc = usb_get_device_descriptor ( usb, &usb->device ) ) != 0 ) {
|
|
DBGC ( usb, "USB %s could not get device descriptor: %s\n",
|
|
usb->name, strerror ( rc ) );
|
|
goto err_get_device_descriptor;
|
|
}
|
|
DBGC ( usb, "USB %s addr %d %04x:%04x class %d:%d:%d (v%s, %s-speed, "
|
|
"MTU %zd)\n", usb->name, usb->address,
|
|
le16_to_cpu ( usb->device.vendor ),
|
|
le16_to_cpu ( usb->device.product ), usb->device.class.class,
|
|
usb->device.class.subclass, usb->device.class.protocol,
|
|
usb_bcd ( le16_to_cpu ( usb->device.protocol ) ),
|
|
usb_speed_name ( usb->speed ), usb->control.mtu );
|
|
|
|
/* Configure device */
|
|
if ( ( rc = usb_autoconfigure ( usb ) ) != 0 )
|
|
goto err_autoconfigure;
|
|
|
|
return 0;
|
|
|
|
usb_deconfigure ( usb );
|
|
err_autoconfigure:
|
|
err_get_device_descriptor:
|
|
err_mtu:
|
|
err_get_mtu:
|
|
err_address:
|
|
usb_endpoint_close ( &usb->control );
|
|
err_open_control:
|
|
usb->host->close ( usb );
|
|
err_open:
|
|
err_speed:
|
|
hub->driver->disable ( hub, port );
|
|
err_enable:
|
|
list_del ( &usb->list );
|
|
port->usb = NULL;
|
|
err_already:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Unregister USB device
|
|
*
|
|
* @v usb USB device
|
|
*/
|
|
static void unregister_usb ( struct usb_device *usb ) {
|
|
struct usb_port *port = usb->port;
|
|
struct usb_hub *hub = port->hub;
|
|
struct io_buffer *iobuf;
|
|
struct io_buffer *tmp;
|
|
|
|
/* Sanity checks */
|
|
assert ( port->usb == usb );
|
|
|
|
/* Clear device configuration */
|
|
usb_deconfigure ( usb );
|
|
|
|
/* Close control endpoint */
|
|
usb_endpoint_close ( &usb->control );
|
|
|
|
/* Discard any stale control completions */
|
|
list_for_each_entry_safe ( iobuf, tmp, &usb->complete, list ) {
|
|
list_del ( &iobuf->list );
|
|
free_iob ( iobuf );
|
|
}
|
|
|
|
/* Close device */
|
|
usb->host->close ( usb );
|
|
|
|
/* Disable port */
|
|
hub->driver->disable ( hub, port );
|
|
|
|
/* Remove from bus device list */
|
|
list_del ( &usb->list );
|
|
|
|
/* Remove from port */
|
|
port->usb = NULL;
|
|
}
|
|
|
|
/**
|
|
* Free USB device
|
|
*
|
|
* @v usb USB device
|
|
*/
|
|
static void free_usb ( struct usb_device *usb ) {
|
|
unsigned int i;
|
|
|
|
/* Sanity checks */
|
|
for ( i = 0 ; i < ( sizeof ( usb->ep ) / sizeof ( usb->ep[0] ) ) ; i++ )
|
|
assert ( usb->ep[i] == NULL );
|
|
assert ( list_empty ( &usb->functions ) );
|
|
assert ( list_empty ( &usb->complete ) );
|
|
|
|
/* Free device */
|
|
free ( usb );
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* USB device hotplug event handling
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Handle newly attached USB device
|
|
*
|
|
* @v port USB port
|
|
* @ret rc Return status code
|
|
*/
|
|
static int usb_attached ( struct usb_port *port ) {
|
|
struct usb_device *usb;
|
|
int rc;
|
|
|
|
/* Mark port as attached */
|
|
port->attached = 1;
|
|
|
|
/* Sanity checks */
|
|
assert ( port->usb == NULL );
|
|
|
|
/* Allocate USB device */
|
|
usb = alloc_usb ( port );
|
|
if ( ! usb ) {
|
|
rc = -ENOMEM;
|
|
goto err_alloc;
|
|
}
|
|
|
|
/* Register USB device */
|
|
if ( ( rc = register_usb ( usb ) ) != 0 )
|
|
goto err_register;
|
|
|
|
return 0;
|
|
|
|
unregister_usb ( usb );
|
|
err_register:
|
|
free_usb ( usb );
|
|
err_alloc:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Handle newly detached USB device
|
|
*
|
|
* @v port USB port
|
|
*/
|
|
static void usb_detached ( struct usb_port *port ) {
|
|
struct usb_device *usb = port->usb;
|
|
|
|
/* Mark port as detached */
|
|
port->attached = 0;
|
|
|
|
/* Do nothing if we have no USB device */
|
|
if ( ! usb )
|
|
return;
|
|
|
|
/* Unregister USB device */
|
|
unregister_usb ( usb );
|
|
|
|
/* Free USB device */
|
|
free_usb ( usb );
|
|
}
|
|
|
|
/**
|
|
* Handle newly attached or detached USB device
|
|
*
|
|
* @v port USB port
|
|
* @ret rc Return status code
|
|
*/
|
|
static int usb_hotplugged ( struct usb_port *port ) {
|
|
struct usb_hub *hub = port->hub;
|
|
int rc;
|
|
|
|
/* Get current port speed */
|
|
if ( ( rc = hub->driver->speed ( hub, port ) ) != 0 ) {
|
|
DBGC ( hub, "USB hub %s port %d could not get speed: %s\n",
|
|
hub->name, port->address, strerror ( rc ) );
|
|
/* Treat as a disconnection */
|
|
port->disconnected = 1;
|
|
port->speed = USB_SPEED_NONE;
|
|
}
|
|
|
|
/* Detach device, if applicable */
|
|
if ( port->attached && ( port->disconnected || ! port->speed ) )
|
|
usb_detached ( port );
|
|
|
|
/* Clear any recorded disconnections */
|
|
port->disconnected = 0;
|
|
|
|
/* Attach device, if applicable */
|
|
if ( port->speed && ( ! port->attached ) &&
|
|
( ( rc = usb_attached ( port ) ) != 0 ) )
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* USB process
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Report port status change
|
|
*
|
|
* @v port USB port
|
|
*/
|
|
void usb_port_changed ( struct usb_port *port ) {
|
|
|
|
/* Record hub port status change */
|
|
list_del ( &port->changed );
|
|
list_add_tail ( &port->changed, &usb_changed );
|
|
}
|
|
|
|
/**
|
|
* Handle newly attached or detached USB device
|
|
*
|
|
*/
|
|
static void usb_hotplug ( void ) {
|
|
struct usb_port *port;
|
|
|
|
/* Handle any changed ports, allowing for the fact that the
|
|
* port list may change as we perform hotplug actions.
|
|
*/
|
|
while ( ! list_empty ( &usb_changed ) ) {
|
|
|
|
/* Get first changed port */
|
|
port = list_first_entry ( &usb_changed, struct usb_port,
|
|
changed );
|
|
assert ( port != NULL );
|
|
|
|
/* Remove from list of changed ports */
|
|
list_del ( &port->changed );
|
|
INIT_LIST_HEAD ( &port->changed );
|
|
|
|
/* Perform appropriate hotplug action */
|
|
usb_hotplugged ( port );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* USB process
|
|
*
|
|
* @v process USB process
|
|
*/
|
|
static void usb_step ( struct process *process __unused ) {
|
|
struct usb_bus *bus;
|
|
struct usb_endpoint *ep;
|
|
|
|
/* Poll all buses */
|
|
for_each_usb_bus ( 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 ( &usb_halted, struct usb_endpoint,
|
|
halted ) ) != NULL )
|
|
usb_endpoint_reset ( ep );
|
|
|
|
/* Handle any changed ports */
|
|
usb_hotplug();
|
|
}
|
|
|
|
/** USB process */
|
|
PERMANENT_PROCESS ( usb_process, usb_step );
|
|
|
|
/******************************************************************************
|
|
*
|
|
* USB hub
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Allocate USB hub
|
|
*
|
|
* @v bus USB bus
|
|
* @v usb Underlying USB device, if any
|
|
* @v ports Number of ports
|
|
* @v driver Hub driver operations
|
|
* @ret hub USB hub, or NULL on allocation failure
|
|
*/
|
|
struct usb_hub * alloc_usb_hub ( struct usb_bus *bus, struct usb_device *usb,
|
|
unsigned int ports,
|
|
struct usb_hub_driver_operations *driver ) {
|
|
struct usb_hub *hub;
|
|
struct usb_port *port;
|
|
unsigned int i;
|
|
|
|
/* Allocate and initialise structure */
|
|
hub = zalloc ( sizeof ( *hub ) + ( ports * sizeof ( hub->port[0] ) ) );
|
|
if ( ! hub )
|
|
return NULL;
|
|
hub->name = ( usb ? usb->name : bus->name );
|
|
hub->bus = bus;
|
|
hub->usb = usb;
|
|
if ( usb )
|
|
hub->protocol = usb->port->protocol;
|
|
hub->ports = ports;
|
|
hub->driver = driver;
|
|
hub->host = &bus->op->hub;
|
|
|
|
/* Initialise port list */
|
|
for ( i = 1 ; i <= hub->ports ; i++ ) {
|
|
port = usb_port ( hub, i );
|
|
port->hub = hub;
|
|
port->address = i;
|
|
if ( usb )
|
|
port->protocol = usb->port->protocol;
|
|
INIT_LIST_HEAD ( &port->changed );
|
|
}
|
|
|
|
return hub;
|
|
}
|
|
|
|
/**
|
|
* Register USB hub
|
|
*
|
|
* @v hub USB hub
|
|
* @ret rc Return status code
|
|
*/
|
|
int register_usb_hub ( struct usb_hub *hub ) {
|
|
struct usb_bus *bus = hub->bus;
|
|
struct usb_port *port;
|
|
unsigned int i;
|
|
int rc;
|
|
|
|
/* Add to hub list */
|
|
list_add_tail ( &hub->list, &bus->hubs );
|
|
|
|
/* Open hub (host controller) */
|
|
if ( ( rc = hub->host->open ( hub ) ) != 0 ) {
|
|
DBGC ( hub, "USB hub %s could not open: %s\n",
|
|
hub->name, strerror ( rc ) );
|
|
goto err_host_open;
|
|
}
|
|
|
|
/* Open hub (driver) */
|
|
if ( ( rc = hub->driver->open ( hub ) ) != 0 ) {
|
|
DBGC ( hub, "USB hub %s could not open: %s\n",
|
|
hub->name, strerror ( rc ) );
|
|
goto err_driver_open;
|
|
}
|
|
|
|
/* Delay to allow ports to stabilise */
|
|
mdelay ( USB_PORT_DELAY_MS );
|
|
|
|
/* Mark all ports as changed */
|
|
for ( i = 1 ; i <= hub->ports ; i++ ) {
|
|
port = usb_port ( hub, i );
|
|
usb_port_changed ( port );
|
|
}
|
|
|
|
/* Some hubs seem to defer reporting device connections until
|
|
* their interrupt endpoint is polled for the first time.
|
|
* Poll the bus once now in order to pick up any such
|
|
* connections.
|
|
*/
|
|
usb_poll ( bus );
|
|
|
|
return 0;
|
|
|
|
hub->driver->close ( hub );
|
|
err_driver_open:
|
|
hub->host->close ( hub );
|
|
err_host_open:
|
|
list_del ( &hub->list );
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Unregister USB hub
|
|
*
|
|
* @v hub USB hub
|
|
*/
|
|
void unregister_usb_hub ( struct usb_hub *hub ) {
|
|
struct usb_port *port;
|
|
unsigned int i;
|
|
|
|
/* Detach all devices */
|
|
for ( i = 1 ; i <= hub->ports ; i++ ) {
|
|
port = usb_port ( hub, i );
|
|
if ( port->attached )
|
|
usb_detached ( port );
|
|
}
|
|
|
|
/* Close hub (driver) */
|
|
hub->driver->close ( hub );
|
|
|
|
/* Close hub (host controller) */
|
|
hub->host->close ( hub );
|
|
|
|
/* Cancel any pending port status changes */
|
|
for ( i = 1 ; i <= hub->ports ; i++ ) {
|
|
port = usb_port ( hub, i );
|
|
list_del ( &port->changed );
|
|
INIT_LIST_HEAD ( &port->changed );
|
|
}
|
|
|
|
/* Remove from hub list */
|
|
list_del ( &hub->list );
|
|
}
|
|
|
|
/**
|
|
* Free USB hub
|
|
*
|
|
* @v hub USB hub
|
|
*/
|
|
void free_usb_hub ( struct usb_hub *hub ) {
|
|
struct usb_port *port;
|
|
unsigned int i;
|
|
|
|
/* Sanity checks */
|
|
for ( i = 1 ; i <= hub->ports ; i++ ) {
|
|
port = usb_port ( hub, i );
|
|
assert ( ! port->attached );
|
|
assert ( port->usb == NULL );
|
|
assert ( list_empty ( &port->changed ) );
|
|
}
|
|
|
|
/* Free hub */
|
|
free ( hub );
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* USB bus
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Allocate USB bus
|
|
*
|
|
* @v dev Underlying hardware device
|
|
* @v ports Number of root hub ports
|
|
* @v mtu Largest transfer allowed on the bus
|
|
* @v op Host controller operations
|
|
* @ret bus USB bus, or NULL on allocation failure
|
|
*/
|
|
struct usb_bus * alloc_usb_bus ( struct device *dev, unsigned int ports,
|
|
size_t mtu, struct usb_host_operations *op ) {
|
|
struct usb_bus *bus;
|
|
|
|
/* Allocate and initialise structure */
|
|
bus = zalloc ( sizeof ( *bus ) );
|
|
if ( ! bus )
|
|
goto err_alloc_bus;
|
|
bus->name = dev->name;
|
|
bus->dev = dev;
|
|
bus->mtu = mtu;
|
|
bus->op = op;
|
|
INIT_LIST_HEAD ( &bus->devices );
|
|
INIT_LIST_HEAD ( &bus->hubs );
|
|
bus->host = &bus->op->bus;
|
|
|
|
/* Allocate root hub */
|
|
bus->hub = alloc_usb_hub ( bus, NULL, ports, &op->root );
|
|
if ( ! bus->hub )
|
|
goto err_alloc_hub;
|
|
|
|
return bus;
|
|
|
|
free_usb_hub ( bus->hub );
|
|
err_alloc_hub:
|
|
free ( bus );
|
|
err_alloc_bus:
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Register USB bus
|
|
*
|
|
* @v bus USB bus
|
|
* @ret rc Return status code
|
|
*/
|
|
int register_usb_bus ( struct usb_bus *bus ) {
|
|
int rc;
|
|
|
|
/* Sanity checks */
|
|
assert ( bus->hub != NULL );
|
|
|
|
/* Open bus */
|
|
if ( ( rc = bus->host->open ( bus ) ) != 0 )
|
|
goto err_open;
|
|
|
|
/* Add to list of USB buses */
|
|
list_add_tail ( &bus->list, &usb_buses );
|
|
|
|
/* Register root hub */
|
|
if ( ( rc = register_usb_hub ( bus->hub ) ) != 0 )
|
|
goto err_register_hub;
|
|
|
|
/* Attach any devices already present */
|
|
usb_hotplug();
|
|
|
|
return 0;
|
|
|
|
unregister_usb_hub ( bus->hub );
|
|
err_register_hub:
|
|
list_del ( &bus->list );
|
|
bus->host->close ( bus );
|
|
err_open:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Unregister USB bus
|
|
*
|
|
* @v bus USB bus
|
|
*/
|
|
void unregister_usb_bus ( struct usb_bus *bus ) {
|
|
|
|
/* Sanity checks */
|
|
assert ( bus->hub != NULL );
|
|
|
|
/* Unregister root hub */
|
|
unregister_usb_hub ( bus->hub );
|
|
|
|
/* Remove from list of USB buses */
|
|
list_del ( &bus->list );
|
|
|
|
/* Close bus */
|
|
bus->host->close ( bus );
|
|
|
|
/* Sanity checks */
|
|
assert ( list_empty ( &bus->devices ) );
|
|
assert ( list_empty ( &bus->hubs ) );
|
|
}
|
|
|
|
/**
|
|
* Free USB bus
|
|
*
|
|
* @v bus USB bus
|
|
*/
|
|
void free_usb_bus ( struct usb_bus *bus ) {
|
|
struct usb_endpoint *ep;
|
|
struct usb_port *port;
|
|
|
|
/* Sanity checks */
|
|
assert ( list_empty ( &bus->devices ) );
|
|
assert ( list_empty ( &bus->hubs ) );
|
|
list_for_each_entry ( ep, &usb_halted, halted )
|
|
assert ( ep->usb->port->hub->bus != bus );
|
|
list_for_each_entry ( port, &usb_changed, changed )
|
|
assert ( port->hub->bus != bus );
|
|
|
|
/* Free root hub */
|
|
free_usb_hub ( bus->hub );
|
|
|
|
/* Free bus */
|
|
free ( bus );
|
|
}
|
|
|
|
/**
|
|
* Find USB bus by device location
|
|
*
|
|
* @v bus_type Bus type
|
|
* @v location Bus location
|
|
* @ret bus USB bus, or NULL
|
|
*/
|
|
struct usb_bus * find_usb_bus_by_location ( unsigned int bus_type,
|
|
unsigned int location ) {
|
|
struct usb_bus *bus;
|
|
|
|
for_each_usb_bus ( bus ) {
|
|
if ( ( bus->dev->desc.bus_type == bus_type ) &&
|
|
( bus->dev->desc.location == location ) )
|
|
return bus;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* USB address assignment
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Allocate device address
|
|
*
|
|
* @v bus USB bus
|
|
* @ret address Device address, or negative error
|
|
*/
|
|
int usb_alloc_address ( struct usb_bus *bus ) {
|
|
unsigned int address;
|
|
|
|
/* Find first free device address */
|
|
address = ffsll ( ~bus->addresses );
|
|
if ( ! address )
|
|
return -ENOENT;
|
|
|
|
/* Mark address as used */
|
|
bus->addresses |= ( 1ULL << ( address - 1 ) );
|
|
|
|
return address;
|
|
}
|
|
|
|
/**
|
|
* Free device address
|
|
*
|
|
* @v bus USB bus
|
|
* @v address Device address
|
|
*/
|
|
void usb_free_address ( struct usb_bus *bus, unsigned int address ) {
|
|
|
|
/* Sanity check */
|
|
assert ( address > 0 );
|
|
assert ( bus->addresses & ( 1ULL << ( address - 1 ) ) );
|
|
|
|
/* Mark address as free */
|
|
bus->addresses &= ~( 1ULL << ( address - 1 ) );
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* USB bus topology
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Get USB route string
|
|
*
|
|
* @v usb USB device
|
|
* @ret route USB route string
|
|
*/
|
|
unsigned int usb_route_string ( struct usb_device *usb ) {
|
|
struct usb_device *parent;
|
|
unsigned int route;
|
|
|
|
/* Navigate up to root hub, constructing route string as we go */
|
|
for ( route = 0 ; ( parent = usb->port->hub->usb ) ; usb = parent ) {
|
|
route <<= 4;
|
|
route |= ( ( usb->port->address > 0xf ) ?
|
|
0xf : usb->port->address );
|
|
}
|
|
|
|
return route;
|
|
}
|
|
|
|
/**
|
|
* Get USB depth
|
|
*
|
|
* @v usb USB device
|
|
* @ret depth Hub depth
|
|
*/
|
|
unsigned int usb_depth ( struct usb_device *usb ) {
|
|
struct usb_device *parent;
|
|
unsigned int depth;
|
|
|
|
/* Navigate up to root hub, constructing depth as we go */
|
|
for ( depth = 0 ; ( parent = usb->port->hub->usb ) ; usb = parent )
|
|
depth++;
|
|
|
|
return depth;
|
|
}
|
|
|
|
/**
|
|
* Get USB root hub port
|
|
*
|
|
* @v usb USB device
|
|
* @ret port Root hub port
|
|
*/
|
|
struct usb_port * usb_root_hub_port ( struct usb_device *usb ) {
|
|
struct usb_device *parent;
|
|
|
|
/* Navigate up to root hub */
|
|
while ( ( parent = usb->port->hub->usb ) )
|
|
usb = parent;
|
|
|
|
return usb->port;
|
|
}
|
|
|
|
/**
|
|
* Get USB transaction translator
|
|
*
|
|
* @v usb USB device
|
|
* @ret port Transaction translator port, or NULL
|
|
*/
|
|
struct usb_port * usb_transaction_translator ( struct usb_device *usb ) {
|
|
struct usb_device *parent;
|
|
|
|
/* Navigate up to root hub. If we find a low-speed or
|
|
* full-speed device with a higher-speed parent hub, then that
|
|
* device's port is the transaction translator.
|
|
*/
|
|
for ( ; ( parent = usb->port->hub->usb ) ; usb = parent ) {
|
|
if ( ( usb->speed <= USB_SPEED_FULL ) &&
|
|
( parent->speed > USB_SPEED_FULL ) )
|
|
return usb->port;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Drag in objects via register_usb_bus() */
|
|
REQUIRING_SYMBOL ( register_usb_bus );
|
|
|
|
/* Drag in USB configuration */
|
|
REQUIRE_OBJECT ( config_usb );
|
|
|
|
/* Drag in hub driver */
|
|
REQUIRE_OBJECT ( usbhub );
|