david/ipxe
david
/
ipxe
Archived
1
0
Fork 0

[usb] Detect missed disconnections

The USB core will currently fail to detect disconnections if a new
device has attached by the time the port is examined in
usb_hotplug().

Fix by recording the fact that a disconnection has taken place
whenever the "connection status changed" (CSC) bit is observed to be
set.  (Whether the change represents a disconnection or a
reconnection, it indicates that the port has experienced some time of
being disconnected.)

Note that the time at which a disconnection can be detected varies by
hub type.  In particular: root hubs can observe the CSC bit when
polling, and so will record the disconnection before calling
usb_port_changed(), but USB hubs read the port status (and hence the
CSC bit) only during the call to hub_speed(), long after the call to
usb_port_changed().

Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
Michael Brown 2015-05-06 16:38:28 +01:00
parent b88ab14ba3
commit f6604627ff
5 changed files with 78 additions and 51 deletions

View File

@ -1589,18 +1589,20 @@ static int usb_hotplug ( struct usb_port *port ) {
return rc; return rc;
} }
/* Handle attached/detached device as applicable */ /* Detach device, if applicable */
if ( port->speed && ! port->attached ) { if ( port->attached && ( port->disconnected || ! port->speed ) )
/* Newly attached device */
return usb_attached ( port );
} else if ( port->attached && ! port->speed ) {
/* Newly detached device */
usb_detached ( port ); usb_detached ( port );
return 0;
} else { /* Attach device, if applicable */
/* Ignore */ if ( port->speed && ! port->attached ) {
return 0; if ( ( rc = usb_attached ( port ) ) != 0 )
return rc;
} }
/* Clear any recorded disconnections */
port->disconnected = 0;
return 0;
} }
/****************************************************************************** /******************************************************************************

View File

@ -1498,6 +1498,7 @@ static int ehci_root_speed ( struct usb_hub *hub, struct usb_port *port ) {
unsigned int speed; unsigned int speed;
unsigned int line; unsigned int line;
int ccs; int ccs;
int csc;
int ped; int ped;
/* Read port status */ /* Read port status */
@ -1505,9 +1506,14 @@ static int ehci_root_speed ( struct usb_hub *hub, struct usb_port *port ) {
DBGC2 ( ehci, "EHCI %p port %d status is %08x\n", DBGC2 ( ehci, "EHCI %p port %d status is %08x\n",
ehci, port->address, portsc ); ehci, port->address, portsc );
ccs = ( portsc & EHCI_PORTSC_CCS ); ccs = ( portsc & EHCI_PORTSC_CCS );
csc = ( portsc & EHCI_PORTSC_CSC );
ped = ( portsc & EHCI_PORTSC_PED ); ped = ( portsc & EHCI_PORTSC_PED );
line = EHCI_PORTSC_LINE_STATUS ( portsc ); line = EHCI_PORTSC_LINE_STATUS ( portsc );
/* Record disconnections and clear changes */
port->disconnected |= csc;
writel ( portsc, ehci->op + EHCI_OP_PORTSC ( port->address ) );
/* Determine port speed */ /* Determine port speed */
if ( ! ccs ) { if ( ! ccs ) {
/* Port not connected */ /* Port not connected */
@ -1564,7 +1570,8 @@ static void ehci_root_poll ( struct usb_hub *hub, struct usb_port *port ) {
if ( ! change ) if ( ! change )
return; return;
/* Acknowledge changes */ /* Record disconnections and clear changes */
port->disconnected |= ( portsc & EHCI_PORTSC_CSC );
writel ( portsc, ehci->op + EHCI_OP_PORTSC ( port->address ) ); writel ( portsc, ehci->op + EHCI_OP_PORTSC ( port->address ) );
/* Report port status change */ /* Report port status change */

View File

@ -331,6 +331,9 @@ static int hub_speed ( struct usb_hub *hub, struct usb_port *port ) {
port->speed = USB_SPEED_NONE; port->speed = USB_SPEED_NONE;
} }
/* Record disconnections */
port->disconnected |= ( changed & ( 1 << USB_HUB_PORT_CONNECTION ) );
/* Clear port status change bits */ /* Clear port status change bits */
if ( ( rc = hub_clear_changes ( hubdev, port->address, changed ) ) != 0) if ( ( rc = hub_clear_changes ( hubdev, port->address, changed ) ) != 0)
return rc; return rc;

View File

@ -1532,10 +1532,10 @@ static void xhci_event_free ( struct xhci_device *xhci ) {
* Handle transfer event * Handle transfer event
* *
* @v xhci xHCI device * @v xhci xHCI device
* @v transfer Transfer event TRB * @v trb Transfer event TRB
*/ */
static void xhci_transfer ( struct xhci_device *xhci, static void xhci_transfer ( struct xhci_device *xhci,
struct xhci_trb_transfer *transfer ) { struct xhci_trb_transfer *trb ) {
struct xhci_slot *slot; struct xhci_slot *slot;
struct xhci_endpoint *endpoint; struct xhci_endpoint *endpoint;
struct io_buffer *iobuf; struct io_buffer *iobuf;
@ -1545,20 +1545,20 @@ static void xhci_transfer ( struct xhci_device *xhci,
profile_start ( &xhci_transfer_profiler ); profile_start ( &xhci_transfer_profiler );
/* Identify slot */ /* Identify slot */
if ( ( transfer->slot > xhci->slots ) || if ( ( trb->slot > xhci->slots ) ||
( ( slot = xhci->slot[transfer->slot] ) == NULL ) ) { ( ( slot = xhci->slot[trb->slot] ) == NULL ) ) {
DBGC ( xhci, "XHCI %p transfer event invalid slot %d:\n", DBGC ( xhci, "XHCI %p transfer event invalid slot %d:\n",
xhci, transfer->slot ); xhci, trb->slot );
DBGC_HDA ( xhci, 0, transfer, sizeof ( *transfer ) ); DBGC_HDA ( xhci, 0, trb, sizeof ( *trb ) );
return; return;
} }
/* Identify endpoint */ /* Identify endpoint */
if ( ( transfer->endpoint > XHCI_CTX_END ) || if ( ( trb->endpoint > XHCI_CTX_END ) ||
( ( endpoint = slot->endpoint[transfer->endpoint] ) == NULL ) ) { ( ( endpoint = slot->endpoint[trb->endpoint] ) == NULL ) ) {
DBGC ( xhci, "XHCI %p slot %d transfer event invalid epid " DBGC ( xhci, "XHCI %p slot %d transfer event invalid epid "
"%d:\n", xhci, slot->id, transfer->endpoint ); "%d:\n", xhci, slot->id, trb->endpoint );
DBGC_HDA ( xhci, 0, transfer, sizeof ( *transfer ) ); DBGC_HDA ( xhci, 0, trb, sizeof ( *trb ) );
return; return;
} }
@ -1567,15 +1567,15 @@ static void xhci_transfer ( struct xhci_device *xhci,
assert ( iobuf != NULL ); assert ( iobuf != NULL );
/* Check for errors */ /* Check for errors */
if ( ! ( ( transfer->code == XHCI_CMPLT_SUCCESS ) || if ( ! ( ( trb->code == XHCI_CMPLT_SUCCESS ) ||
( transfer->code == XHCI_CMPLT_SHORT ) ) ) { ( trb->code == XHCI_CMPLT_SHORT ) ) ) {
/* Construct error */ /* Construct error */
rc = -ECODE ( transfer->code ); rc = -ECODE ( trb->code );
DBGC ( xhci, "XHCI %p slot %d ctx %d failed (code %d): %s\n", DBGC ( xhci, "XHCI %p slot %d ctx %d failed (code %d): %s\n",
xhci, slot->id, endpoint->ctx, transfer->code, xhci, slot->id, endpoint->ctx, trb->code,
strerror ( rc ) ); strerror ( rc ) );
DBGC_HDA ( xhci, 0, transfer, sizeof ( *transfer ) ); DBGC_HDA ( xhci, 0, trb, sizeof ( *trb ) );
/* Sanity check */ /* Sanity check */
assert ( ( endpoint->context->state & XHCI_ENDPOINT_STATE_MASK ) assert ( ( endpoint->context->state & XHCI_ENDPOINT_STATE_MASK )
@ -1587,11 +1587,11 @@ static void xhci_transfer ( struct xhci_device *xhci,
} }
/* Record actual transfer size */ /* Record actual transfer size */
iob_unput ( iobuf, le16_to_cpu ( transfer->residual ) ); iob_unput ( iobuf, le16_to_cpu ( trb->residual ) );
/* Sanity check (for successful completions only) */ /* Sanity check (for successful completions only) */
assert ( xhci_ring_consumed ( &endpoint->ring ) == assert ( xhci_ring_consumed ( &endpoint->ring ) ==
le64_to_cpu ( transfer->transfer ) ); le64_to_cpu ( trb->transfer ) );
/* Report completion to USB core */ /* Report completion to USB core */
usb_complete ( endpoint->ep, iobuf ); usb_complete ( endpoint->ep, iobuf );
@ -1602,24 +1602,24 @@ static void xhci_transfer ( struct xhci_device *xhci,
* Handle command completion event * Handle command completion event
* *
* @v xhci xHCI device * @v xhci xHCI device
* @v complete Command completion event * @v trb Command completion event
*/ */
static void xhci_complete ( struct xhci_device *xhci, static void xhci_complete ( struct xhci_device *xhci,
struct xhci_trb_complete *complete ) { struct xhci_trb_complete *trb ) {
int rc; int rc;
/* Ignore "command ring stopped" notifications */ /* Ignore "command ring stopped" notifications */
if ( complete->code == XHCI_CMPLT_CMD_STOPPED ) { if ( trb->code == XHCI_CMPLT_CMD_STOPPED ) {
DBGC2 ( xhci, "XHCI %p command ring stopped\n", xhci ); DBGC2 ( xhci, "XHCI %p command ring stopped\n", xhci );
return; return;
} }
/* Ignore unexpected completions */ /* Ignore unexpected completions */
if ( ! xhci->pending ) { if ( ! xhci->pending ) {
rc = -ECODE ( complete->code ); rc = -ECODE ( trb->code );
DBGC ( xhci, "XHCI %p unexpected completion (code %d): %s\n", DBGC ( xhci, "XHCI %p unexpected completion (code %d): %s\n",
xhci, complete->code, strerror ( rc ) ); xhci, trb->code, strerror ( rc ) );
DBGC_HDA ( xhci, 0, complete, sizeof ( *complete ) ); DBGC_HDA ( xhci, 0, trb, sizeof ( *trb ) );
return; return;
} }
@ -1628,10 +1628,10 @@ static void xhci_complete ( struct xhci_device *xhci,
/* Sanity check */ /* Sanity check */
assert ( xhci_ring_consumed ( &xhci->command ) == assert ( xhci_ring_consumed ( &xhci->command ) ==
le64_to_cpu ( complete->command ) ); le64_to_cpu ( trb->command ) );
/* Record completion */ /* Record completion */
memcpy ( xhci->pending, complete, sizeof ( *xhci->pending ) ); memcpy ( xhci->pending, trb, sizeof ( *xhci->pending ) );
xhci->pending = NULL; xhci->pending = NULL;
} }
@ -1639,38 +1639,40 @@ static void xhci_complete ( struct xhci_device *xhci,
* Handle port status event * Handle port status event
* *
* @v xhci xHCI device * @v xhci xHCI device
* @v port Port status event * @v trb Port status event
*/ */
static void xhci_port_status ( struct xhci_device *xhci, static void xhci_port_status ( struct xhci_device *xhci,
struct xhci_trb_port_status *port ) { struct xhci_trb_port_status *trb ) {
struct usb_port *port = usb_port ( xhci->bus->hub, trb->port );
uint32_t portsc; uint32_t portsc;
/* Sanity check */ /* Sanity check */
assert ( ( port->port > 0 ) && ( port->port <= xhci->ports ) ); assert ( ( trb->port > 0 ) && ( trb->port <= xhci->ports ) );
/* Clear port status change bits */ /* Record disconnections and clear changes */
portsc = readl ( xhci->op + XHCI_OP_PORTSC ( port->port ) ); portsc = readl ( xhci->op + XHCI_OP_PORTSC ( trb->port ) );
port->disconnected |= ( portsc & XHCI_PORTSC_CSC );
portsc &= ( XHCI_PORTSC_PRESERVE | XHCI_PORTSC_CHANGE ); portsc &= ( XHCI_PORTSC_PRESERVE | XHCI_PORTSC_CHANGE );
writel ( portsc, xhci->op + XHCI_OP_PORTSC ( port->port ) ); writel ( portsc, xhci->op + XHCI_OP_PORTSC ( trb->port ) );
/* Report port status change */ /* Report port status change */
usb_port_changed ( usb_port ( xhci->bus->hub, port->port ) ); usb_port_changed ( port );
} }
/** /**
* Handle host controller event * Handle host controller event
* *
* @v xhci xHCI device * @v xhci xHCI device
* @v host Host controller event * @v trb Host controller event
*/ */
static void xhci_host_controller ( struct xhci_device *xhci, static void xhci_host_controller ( struct xhci_device *xhci,
struct xhci_trb_host_controller *host ) { struct xhci_trb_host_controller *trb ) {
int rc; int rc;
/* Construct error */ /* Construct error */
rc = -ECODE ( host->code ); rc = -ECODE ( trb->code );
DBGC ( xhci, "XHCI %p host controller event (code %d): %s\n", DBGC ( xhci, "XHCI %p host controller event (code %d): %s\n",
xhci, host->code, strerror ( rc ) ); xhci, trb->code, strerror ( rc ) );
} }
/** /**
@ -3014,6 +3016,7 @@ static int xhci_root_speed ( struct usb_hub *hub, struct usb_port *port ) {
unsigned int psiv; unsigned int psiv;
int ccs; int ccs;
int ped; int ped;
int csc;
int speed; int speed;
int rc; int rc;
@ -3021,9 +3024,17 @@ static int xhci_root_speed ( struct usb_hub *hub, struct usb_port *port ) {
portsc = readl ( xhci->op + XHCI_OP_PORTSC ( port->address ) ); portsc = readl ( xhci->op + XHCI_OP_PORTSC ( port->address ) );
DBGC2 ( xhci, "XHCI %p port %d status is %08x\n", DBGC2 ( xhci, "XHCI %p port %d status is %08x\n",
xhci, port->address, portsc ); xhci, port->address, portsc );
/* Check whether or not port is connected */
ccs = ( portsc & XHCI_PORTSC_CCS ); ccs = ( portsc & XHCI_PORTSC_CCS );
ped = ( portsc & XHCI_PORTSC_PED );
csc = ( portsc & XHCI_PORTSC_CSC );
psiv = XHCI_PORTSC_PSIV ( portsc );
/* Record disconnections and clear changes */
port->disconnected |= csc;
portsc &= ( XHCI_PORTSC_PRESERVE | XHCI_PORTSC_CHANGE );
writel ( portsc, xhci->op + XHCI_OP_PORTSC ( port->address ) );
/* Port speed is not valid unless port is connected */
if ( ! ccs ) { if ( ! ccs ) {
port->speed = USB_SPEED_NONE; port->speed = USB_SPEED_NONE;
return 0; return 0;
@ -3032,14 +3043,12 @@ static int xhci_root_speed ( struct usb_hub *hub, struct usb_port *port ) {
/* For USB2 ports, the PSIV field is not valid until the port /* For USB2 ports, the PSIV field is not valid until the port
* completes reset and becomes enabled. * completes reset and becomes enabled.
*/ */
ped = ( portsc & XHCI_PORTSC_PED );
if ( ( port->protocol < USB_PROTO_3_0 ) && ! ped ) { if ( ( port->protocol < USB_PROTO_3_0 ) && ! ped ) {
port->speed = USB_SPEED_FULL; port->speed = USB_SPEED_FULL;
return 0; return 0;
} }
/* Get port speed and map to generic USB speed */ /* Get port speed and map to generic USB speed */
psiv = XHCI_PORTSC_PSIV ( portsc );
speed = xhci_port_speed ( xhci, port->address, psiv ); speed = xhci_port_speed ( xhci, port->address, psiv );
if ( speed < 0 ) { if ( speed < 0 ) {
rc = speed; rc = speed;

View File

@ -747,6 +747,12 @@ struct usb_port {
unsigned int protocol; unsigned int protocol;
/** Port speed */ /** Port speed */
unsigned int speed; unsigned int speed;
/** Port disconnection has been detected
*
* This should be set whenever the underlying hardware reports
* a connection status change.
*/
int disconnected;
/** Port has an attached device */ /** Port has an attached device */
int attached; int attached;
/** Currently attached device (if in use) /** Currently attached device (if in use)