summaryrefslogtreecommitdiffstats
path: root/src/drivers/usb
diff options
context:
space:
mode:
authorMichael Brown2015-05-06 17:38:28 +0200
committerMichael Brown2015-05-08 15:57:14 +0200
commitf6604627ff71d42bb63a3d81c2986a9d296d55cb (patch)
tree8eb038959b4e3429c4c15e9a1a7a5bd73e68c2b2 /src/drivers/usb
parent[pci] Provide PCI_CLASS() to calculate a scalar PCI class value (diff)
downloadipxe-f6604627ff71d42bb63a3d81c2986a9d296d55cb.tar.gz
ipxe-f6604627ff71d42bb63a3d81c2986a9d296d55cb.tar.xz
ipxe-f6604627ff71d42bb63a3d81c2986a9d296d55cb.zip
[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>
Diffstat (limited to 'src/drivers/usb')
-rw-r--r--src/drivers/usb/ehci.c9
-rw-r--r--src/drivers/usb/usbhub.c3
-rw-r--r--src/drivers/usb/xhci.c89
3 files changed, 60 insertions, 41 deletions
diff --git a/src/drivers/usb/ehci.c b/src/drivers/usb/ehci.c
index 653e72c1..d64024da 100644
--- a/src/drivers/usb/ehci.c
+++ b/src/drivers/usb/ehci.c
@@ -1498,6 +1498,7 @@ static int ehci_root_speed ( struct usb_hub *hub, struct usb_port *port ) {
unsigned int speed;
unsigned int line;
int ccs;
+ int csc;
int ped;
/* 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",
ehci, port->address, portsc );
ccs = ( portsc & EHCI_PORTSC_CCS );
+ csc = ( portsc & EHCI_PORTSC_CSC );
ped = ( portsc & EHCI_PORTSC_PED );
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 */
if ( ! ccs ) {
/* Port not connected */
@@ -1564,7 +1570,8 @@ static void ehci_root_poll ( struct usb_hub *hub, struct usb_port *port ) {
if ( ! change )
return;
- /* Acknowledge changes */
+ /* Record disconnections and clear changes */
+ port->disconnected |= ( portsc & EHCI_PORTSC_CSC );
writel ( portsc, ehci->op + EHCI_OP_PORTSC ( port->address ) );
/* Report port status change */
diff --git a/src/drivers/usb/usbhub.c b/src/drivers/usb/usbhub.c
index 6d0cdba4..8b5fa9c4 100644
--- a/src/drivers/usb/usbhub.c
+++ b/src/drivers/usb/usbhub.c
@@ -331,6 +331,9 @@ static int hub_speed ( struct usb_hub *hub, struct usb_port *port ) {
port->speed = USB_SPEED_NONE;
}
+ /* Record disconnections */
+ port->disconnected |= ( changed & ( 1 << USB_HUB_PORT_CONNECTION ) );
+
/* Clear port status change bits */
if ( ( rc = hub_clear_changes ( hubdev, port->address, changed ) ) != 0)
return rc;
diff --git a/src/drivers/usb/xhci.c b/src/drivers/usb/xhci.c
index 307a9912..8e5d6e60 100644
--- a/src/drivers/usb/xhci.c
+++ b/src/drivers/usb/xhci.c
@@ -1532,10 +1532,10 @@ static void xhci_event_free ( struct xhci_device *xhci ) {
* Handle transfer event
*
* @v xhci xHCI device
- * @v transfer Transfer event TRB
+ * @v trb Transfer event TRB
*/
static void xhci_transfer ( struct xhci_device *xhci,
- struct xhci_trb_transfer *transfer ) {
+ struct xhci_trb_transfer *trb ) {
struct xhci_slot *slot;
struct xhci_endpoint *endpoint;
struct io_buffer *iobuf;
@@ -1545,20 +1545,20 @@ static void xhci_transfer ( struct xhci_device *xhci,
profile_start ( &xhci_transfer_profiler );
/* Identify slot */
- if ( ( transfer->slot > xhci->slots ) ||
- ( ( slot = xhci->slot[transfer->slot] ) == NULL ) ) {
+ if ( ( trb->slot > xhci->slots ) ||
+ ( ( slot = xhci->slot[trb->slot] ) == NULL ) ) {
DBGC ( xhci, "XHCI %p transfer event invalid slot %d:\n",
- xhci, transfer->slot );
- DBGC_HDA ( xhci, 0, transfer, sizeof ( *transfer ) );
+ xhci, trb->slot );
+ DBGC_HDA ( xhci, 0, trb, sizeof ( *trb ) );
return;
}
/* Identify endpoint */
- if ( ( transfer->endpoint > XHCI_CTX_END ) ||
- ( ( endpoint = slot->endpoint[transfer->endpoint] ) == NULL ) ) {
+ if ( ( trb->endpoint > XHCI_CTX_END ) ||
+ ( ( endpoint = slot->endpoint[trb->endpoint] ) == NULL ) ) {
DBGC ( xhci, "XHCI %p slot %d transfer event invalid epid "
- "%d:\n", xhci, slot->id, transfer->endpoint );
- DBGC_HDA ( xhci, 0, transfer, sizeof ( *transfer ) );
+ "%d:\n", xhci, slot->id, trb->endpoint );
+ DBGC_HDA ( xhci, 0, trb, sizeof ( *trb ) );
return;
}
@@ -1567,15 +1567,15 @@ static void xhci_transfer ( struct xhci_device *xhci,
assert ( iobuf != NULL );
/* Check for errors */
- if ( ! ( ( transfer->code == XHCI_CMPLT_SUCCESS ) ||
- ( transfer->code == XHCI_CMPLT_SHORT ) ) ) {
+ if ( ! ( ( trb->code == XHCI_CMPLT_SUCCESS ) ||
+ ( trb->code == XHCI_CMPLT_SHORT ) ) ) {
/* Construct error */
- rc = -ECODE ( transfer->code );
+ rc = -ECODE ( trb->code );
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 ) );
- DBGC_HDA ( xhci, 0, transfer, sizeof ( *transfer ) );
+ DBGC_HDA ( xhci, 0, trb, sizeof ( *trb ) );
/* Sanity check */
assert ( ( endpoint->context->state & XHCI_ENDPOINT_STATE_MASK )
@@ -1587,11 +1587,11 @@ static void xhci_transfer ( struct xhci_device *xhci,
}
/* 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) */
assert ( xhci_ring_consumed ( &endpoint->ring ) ==
- le64_to_cpu ( transfer->transfer ) );
+ le64_to_cpu ( trb->transfer ) );
/* Report completion to USB core */
usb_complete ( endpoint->ep, iobuf );
@@ -1602,24 +1602,24 @@ static void xhci_transfer ( struct xhci_device *xhci,
* Handle command completion event
*
* @v xhci xHCI device
- * @v complete Command completion event
+ * @v trb Command completion event
*/
static void xhci_complete ( struct xhci_device *xhci,
- struct xhci_trb_complete *complete ) {
+ struct xhci_trb_complete *trb ) {
int rc;
/* 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 );
return;
}
/* Ignore unexpected completions */
if ( ! xhci->pending ) {
- rc = -ECODE ( complete->code );
+ rc = -ECODE ( trb->code );
DBGC ( xhci, "XHCI %p unexpected completion (code %d): %s\n",
- xhci, complete->code, strerror ( rc ) );
- DBGC_HDA ( xhci, 0, complete, sizeof ( *complete ) );
+ xhci, trb->code, strerror ( rc ) );
+ DBGC_HDA ( xhci, 0, trb, sizeof ( *trb ) );
return;
}
@@ -1628,10 +1628,10 @@ static void xhci_complete ( struct xhci_device *xhci,
/* Sanity check */
assert ( xhci_ring_consumed ( &xhci->command ) ==
- le64_to_cpu ( complete->command ) );
+ le64_to_cpu ( trb->command ) );
/* Record completion */
- memcpy ( xhci->pending, complete, sizeof ( *xhci->pending ) );
+ memcpy ( xhci->pending, trb, sizeof ( *xhci->pending ) );
xhci->pending = NULL;
}
@@ -1639,38 +1639,40 @@ static void xhci_complete ( struct xhci_device *xhci,
* Handle port status event
*
* @v xhci xHCI device
- * @v port Port status event
+ * @v trb Port status event
*/
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;
/* Sanity check */
- assert ( ( port->port > 0 ) && ( port->port <= xhci->ports ) );
+ assert ( ( trb->port > 0 ) && ( trb->port <= xhci->ports ) );
- /* Clear port status change bits */
- portsc = readl ( xhci->op + XHCI_OP_PORTSC ( port->port ) );
+ /* Record disconnections and clear changes */
+ portsc = readl ( xhci->op + XHCI_OP_PORTSC ( trb->port ) );
+ port->disconnected |= ( portsc & XHCI_PORTSC_CSC );
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 */
- usb_port_changed ( usb_port ( xhci->bus->hub, port->port ) );
+ usb_port_changed ( port );
}
/**
* Handle host controller event
*
* @v xhci xHCI device
- * @v host Host controller event
+ * @v trb Host controller event
*/
static void xhci_host_controller ( struct xhci_device *xhci,
- struct xhci_trb_host_controller *host ) {
+ struct xhci_trb_host_controller *trb ) {
int rc;
/* Construct error */
- rc = -ECODE ( host->code );
+ rc = -ECODE ( trb->code );
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;
int ccs;
int ped;
+ int csc;
int speed;
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 ) );
DBGC2 ( xhci, "XHCI %p port %d status is %08x\n",
xhci, port->address, portsc );
-
- /* Check whether or not port is connected */
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 ) {
port->speed = USB_SPEED_NONE;
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
* completes reset and becomes enabled.
*/
- ped = ( portsc & XHCI_PORTSC_PED );
if ( ( port->protocol < USB_PROTO_3_0 ) && ! ped ) {
port->speed = USB_SPEED_FULL;
return 0;
}
/* Get port speed and map to generic USB speed */
- psiv = XHCI_PORTSC_PSIV ( portsc );
speed = xhci_port_speed ( xhci, port->address, psiv );
if ( speed < 0 ) {
rc = speed;