summaryrefslogtreecommitdiffstats
path: root/src/drivers
diff options
context:
space:
mode:
authorSimon Rettberg2022-05-11 10:41:01 +0200
committerSimon Rettberg2022-05-11 10:41:01 +0200
commita12e3c379cf2e5946c7316259ef46736cdd5f222 (patch)
tree49638dad528a4490e293ea4a0f87e39ce862a75b /src/drivers
parentLocal UEFI disk boot support (diff)
parent[cloud] Allow aws-import script to run on Python 3.6 (diff)
downloadipxe-a12e3c379cf2e5946c7316259ef46736cdd5f222.tar.gz
ipxe-a12e3c379cf2e5946c7316259ef46736cdd5f222.tar.xz
ipxe-a12e3c379cf2e5946c7316259ef46736cdd5f222.zip
Merge branch 'master' into openslx
Diffstat (limited to 'src/drivers')
-rw-r--r--src/drivers/bus/pci.c5
-rw-r--r--src/drivers/bus/virtio-pci.c45
-rw-r--r--src/drivers/bus/virtio-ring.c4
-rw-r--r--src/drivers/net/efi/nii.c2
-rw-r--r--src/drivers/net/efi/snpnet.c18
-rw-r--r--src/drivers/net/intelx.c1
-rw-r--r--src/drivers/net/virtio-net.c36
-rw-r--r--src/drivers/usb/usbkbd.c27
-rw-r--r--src/drivers/usb/usbkbd.h1
-rw-r--r--src/drivers/usb/xhci.c73
-rw-r--r--src/drivers/usb/xhci.h2
11 files changed, 175 insertions, 39 deletions
diff --git a/src/drivers/bus/pci.c b/src/drivers/bus/pci.c
index 1b7350c8b..5891e42ff 100644
--- a/src/drivers/bus/pci.c
+++ b/src/drivers/bus/pci.c
@@ -121,6 +121,11 @@ static void pci_read_bases ( struct pci_device *pci ) {
unsigned long bar;
int reg;
+ /* Clear any existing base addresses */
+ pci->ioaddr = 0;
+ pci->membase = 0;
+
+ /* Get first memory and I/O BAR addresses */
for ( reg = PCI_BASE_ADDRESS_0; reg <= PCI_BASE_ADDRESS_5; reg += 4 ) {
bar = pci_bar ( pci, reg );
if ( bar & PCI_BASE_ADDRESS_SPACE_IO ) {
diff --git a/src/drivers/bus/virtio-pci.c b/src/drivers/bus/virtio-pci.c
index 5d2d62750..8b34c7277 100644
--- a/src/drivers/bus/virtio-pci.c
+++ b/src/drivers/bus/virtio-pci.c
@@ -17,37 +17,47 @@
#include "ipxe/io.h"
#include "ipxe/iomap.h"
#include "ipxe/pci.h"
+#include "ipxe/dma.h"
#include "ipxe/reboot.h"
#include "ipxe/virtio-pci.h"
#include "ipxe/virtio-ring.h"
-static int vp_alloc_vq(struct vring_virtqueue *vq, u16 num)
+static int vp_alloc_vq(struct vring_virtqueue *vq, u16 num, size_t header_size)
{
- size_t queue_size = PAGE_MASK + vring_size(num);
+ size_t ring_size = PAGE_MASK + vring_size(num);
size_t vdata_size = num * sizeof(void *);
+ size_t queue_size = ring_size + vdata_size + header_size;
- vq->queue = zalloc(queue_size + vdata_size);
+ vq->queue = dma_alloc(vq->dma, &vq->map, queue_size, queue_size);
if (!vq->queue) {
return -ENOMEM;
}
+ memset ( vq->queue, 0, queue_size );
+ vq->queue_size = queue_size;
+
/* vdata immediately follows the ring */
- vq->vdata = (void **)(vq->queue + queue_size);
+ vq->vdata = (void **)(vq->queue + ring_size);
+
+ /* empty header immediately follows vdata */
+ vq->empty_header = (struct virtio_net_hdr_modern *)(vq->queue + ring_size + vdata_size);
return 0;
}
void vp_free_vq(struct vring_virtqueue *vq)
{
- if (vq->queue) {
- free(vq->queue);
+ if (vq->queue && vq->queue_size) {
+ dma_free(&vq->map, vq->queue, vq->queue_size);
vq->queue = NULL;
vq->vdata = NULL;
+ vq->queue_size = 0;
}
}
int vp_find_vq(unsigned int ioaddr, int queue_index,
- struct vring_virtqueue *vq)
+ struct vring_virtqueue *vq, struct dma_device *dma_dev,
+ size_t header_size)
{
struct vring * vr = &vq->vring;
u16 num;
@@ -73,9 +83,10 @@ int vp_find_vq(unsigned int ioaddr, int queue_index,
}
vq->queue_index = queue_index;
+ vq->dma = dma_dev;
/* initialize the queue */
- rc = vp_alloc_vq(vq, num);
+ rc = vp_alloc_vq(vq, num, header_size);
if (rc) {
DBG("VIRTIO-PCI ERROR: failed to allocate queue memory\n");
return rc;
@@ -87,8 +98,7 @@ int vp_find_vq(unsigned int ioaddr, int queue_index,
* NOTE: vr->desc is initialized by vring_init()
*/
- outl((unsigned long)virt_to_phys(vr->desc) >> PAGE_SHIFT,
- ioaddr + VIRTIO_PCI_QUEUE_PFN);
+ outl(dma(&vq->map, vr->desc) >> PAGE_SHIFT, ioaddr + VIRTIO_PCI_QUEUE_PFN);
return num;
}
@@ -348,7 +358,8 @@ void vpm_notify(struct virtio_pci_modern_device *vdev,
}
int vpm_find_vqs(struct virtio_pci_modern_device *vdev,
- unsigned nvqs, struct vring_virtqueue *vqs)
+ unsigned nvqs, struct vring_virtqueue *vqs,
+ struct dma_device *dma_dev, size_t header_size)
{
unsigned i;
struct vring_virtqueue *vq;
@@ -392,11 +403,12 @@ int vpm_find_vqs(struct virtio_pci_modern_device *vdev,
vq = &vqs[i];
vq->queue_index = i;
+ vq->dma = dma_dev;
/* get offset of notification word for this vq */
off = vpm_ioread16(vdev, &vdev->common, COMMON_OFFSET(queue_notify_off));
- err = vp_alloc_vq(vq, size);
+ err = vp_alloc_vq(vq, size, header_size);
if (err) {
DBG("VIRTIO-PCI %p: failed to allocate queue memory\n", vdev);
return err;
@@ -406,13 +418,16 @@ int vpm_find_vqs(struct virtio_pci_modern_device *vdev,
/* activate the queue */
vpm_iowrite16(vdev, &vdev->common, size, COMMON_OFFSET(queue_size));
- vpm_iowrite64(vdev, &vdev->common, virt_to_phys(vq->vring.desc),
+ vpm_iowrite64(vdev, &vdev->common,
+ dma(&vq->map, vq->vring.desc),
COMMON_OFFSET(queue_desc_lo),
COMMON_OFFSET(queue_desc_hi));
- vpm_iowrite64(vdev, &vdev->common, virt_to_phys(vq->vring.avail),
+ vpm_iowrite64(vdev, &vdev->common,
+ dma(&vq->map, vq->vring.avail),
COMMON_OFFSET(queue_avail_lo),
COMMON_OFFSET(queue_avail_hi));
- vpm_iowrite64(vdev, &vdev->common, virt_to_phys(vq->vring.used),
+ vpm_iowrite64(vdev, &vdev->common,
+ dma(&vq->map, vq->vring.used),
COMMON_OFFSET(queue_used_lo),
COMMON_OFFSET(queue_used_hi));
diff --git a/src/drivers/bus/virtio-ring.c b/src/drivers/bus/virtio-ring.c
index 98e787e16..e448c3488 100644
--- a/src/drivers/bus/virtio-ring.c
+++ b/src/drivers/bus/virtio-ring.c
@@ -98,7 +98,7 @@ void vring_add_buf(struct vring_virtqueue *vq,
for (i = head; out; i = vr->desc[i].next, out--) {
vr->desc[i].flags = VRING_DESC_F_NEXT;
- vr->desc[i].addr = (u64)virt_to_phys(list->addr);
+ vr->desc[i].addr = list->addr;
vr->desc[i].len = list->length;
prev = i;
list++;
@@ -106,7 +106,7 @@ void vring_add_buf(struct vring_virtqueue *vq,
for ( ; in; i = vr->desc[i].next, in--) {
vr->desc[i].flags = VRING_DESC_F_NEXT|VRING_DESC_F_WRITE;
- vr->desc[i].addr = (u64)virt_to_phys(list->addr);
+ vr->desc[i].addr = list->addr;
vr->desc[i].len = list->length;
prev = i;
list++;
diff --git a/src/drivers/net/efi/nii.c b/src/drivers/net/efi/nii.c
index b9f34650e..833462e77 100644
--- a/src/drivers/net/efi/nii.c
+++ b/src/drivers/net/efi/nii.c
@@ -576,7 +576,7 @@ static int nii_issue_cpb_db ( struct nii_nic *nii, unsigned int op, void *cpb,
cdb.IFnum = nii->nii->IfNum;
/* Raise task priority level */
- tpl = bs->RaiseTPL ( TPL_CALLBACK );
+ tpl = bs->RaiseTPL ( efi_internal_tpl );
/* Issue command */
DBGC2 ( nii, "NII %s issuing %02x:%04x ifnum %d%s%s\n",
diff --git a/src/drivers/net/efi/snpnet.c b/src/drivers/net/efi/snpnet.c
index fb5240277..69ec6f5e5 100644
--- a/src/drivers/net/efi/snpnet.c
+++ b/src/drivers/net/efi/snpnet.c
@@ -164,6 +164,10 @@ static int snpnet_transmit ( struct net_device *netdev,
EFI_STATUS efirc;
int rc;
+ /* Do nothing if shutdown is in progress */
+ if ( efi_shutdown_in_progress )
+ return -ECANCELED;
+
/* Defer the packet if there is already a transmission in progress */
if ( snp->txbuf ) {
netdev_tx_defer ( netdev, iobuf );
@@ -283,6 +287,10 @@ static void snpnet_poll_rx ( struct net_device *netdev ) {
*/
static void snpnet_poll ( struct net_device *netdev ) {
+ /* Do nothing if shutdown is in progress */
+ if ( efi_shutdown_in_progress )
+ return;
+
/* Process any TX completions */
snpnet_poll_tx ( netdev );
@@ -426,8 +434,9 @@ static void snpnet_close ( struct net_device *netdev ) {
EFI_STATUS efirc;
int rc;
- /* Shut down NIC */
- if ( ( efirc = snp->snp->Shutdown ( snp->snp ) ) != 0 ) {
+ /* Shut down NIC (unless whole system shutdown is in progress) */
+ if ( ( ! efi_shutdown_in_progress ) &&
+ ( ( efirc = snp->snp->Shutdown ( snp->snp ) ) != 0 ) ) {
rc = -EEFI ( efirc );
DBGC ( snp, "SNP %s could not shut down: %s\n",
netdev->name, strerror ( rc ) );
@@ -589,8 +598,9 @@ void snpnet_stop ( struct efi_device *efidev ) {
/* Unregister network device */
unregister_netdev ( netdev );
- /* Stop SNP protocol */
- if ( ( efirc = snp->snp->Stop ( snp->snp ) ) != 0 ) {
+ /* Stop SNP protocol (unless whole system shutdown is in progress) */
+ if ( ( ! efi_shutdown_in_progress ) &&
+ ( ( efirc = snp->snp->Stop ( snp->snp ) ) != 0 ) ) {
rc = -EEFI ( efirc );
DBGC ( device, "SNP %s could not stop: %s\n",
efi_handle_name ( device ), strerror ( rc ) );
diff --git a/src/drivers/net/intelx.c b/src/drivers/net/intelx.c
index ccf6b0648..f4dad8859 100644
--- a/src/drivers/net/intelx.c
+++ b/src/drivers/net/intelx.c
@@ -481,6 +481,7 @@ static struct pci_device_id intelx_nics[] = {
PCI_ROM ( 0x8086, 0x15ab, "x552", "X552", 0 ),
PCI_ROM ( 0x8086, 0x15c8, "x553t", "X553/X557-AT", 0 ),
PCI_ROM ( 0x8086, 0x15ce, "x553-sfp", "X553 (SFP+)", 0 ),
+ PCI_ROM ( 0x8086, 0x15e4, "x553a", "X553", 0 ),
PCI_ROM ( 0x8086, 0x15e5, "x553", "X553", 0 ),
};
diff --git a/src/drivers/net/virtio-net.c b/src/drivers/net/virtio-net.c
index 78ec9ac4e..0c4541924 100644
--- a/src/drivers/net/virtio-net.c
+++ b/src/drivers/net/virtio-net.c
@@ -29,6 +29,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <ipxe/iobuf.h>
#include <ipxe/netdevice.h>
#include <ipxe/pci.h>
+#include <ipxe/dma.h>
#include <ipxe/if_ether.h>
#include <ipxe/ethernet.h>
#include <ipxe/virtio-pci.h>
@@ -99,8 +100,9 @@ struct virtnet_nic {
/** Pending rx packet count */
unsigned int rx_num_iobufs;
- /** Virtio net dummy packet headers */
- struct virtio_net_hdr_modern empty_header[QUEUE_NB];
+ /** DMA device */
+ struct dma_device *dma;
+
};
/** Add an iobuf to a virtqueue
@@ -115,7 +117,7 @@ static void virtnet_enqueue_iob ( struct net_device *netdev,
int vq_idx, struct io_buffer *iobuf ) {
struct virtnet_nic *virtnet = netdev->priv;
struct vring_virtqueue *vq = &virtnet->virtqueue[vq_idx];
- struct virtio_net_hdr_modern *header = &virtnet->empty_header[vq_idx];
+ struct virtio_net_hdr_modern *header = vq->empty_header;
unsigned int out = ( vq_idx == TX_INDEX ) ? 2 : 0;
unsigned int in = ( vq_idx == TX_INDEX ) ? 0 : 2;
size_t header_len = ( virtnet->virtio_version ?
@@ -132,11 +134,11 @@ static void virtnet_enqueue_iob ( struct net_device *netdev,
* to header->flags for received packets. Work around
* this by using separate RX and TX headers.
*/
- .addr = ( char* ) header,
+ .addr = dma ( &vq->map, header ),
.length = header_len,
},
{
- .addr = ( char* ) iobuf->data,
+ .addr = iob_dma ( iobuf ),
.length = iob_len ( iobuf ),
},
};
@@ -161,7 +163,7 @@ static void virtnet_refill_rx_virtqueue ( struct net_device *netdev ) {
struct io_buffer *iobuf;
/* Try to allocate a buffer, stop for now if out of memory */
- iobuf = alloc_iob ( len );
+ iobuf = alloc_rx_iob ( len, virtnet->dma );
if ( ! iobuf )
break;
@@ -215,7 +217,8 @@ static int virtnet_open_legacy ( struct net_device *netdev ) {
/* Initialize rx/tx virtqueues */
for ( i = 0; i < QUEUE_NB; i++ ) {
- if ( vp_find_vq ( ioaddr, i, &virtnet->virtqueue[i] ) == -1 ) {
+ if ( vp_find_vq ( ioaddr, i, &virtnet->virtqueue[i], virtnet->dma,
+ sizeof ( struct virtio_net_hdr_modern ) ) == -1 ) {
DBGC ( virtnet, "VIRTIO-NET %p cannot register queue %d\n",
virtnet, i );
virtnet_free_virtqueues ( netdev );
@@ -280,7 +283,8 @@ static int virtnet_open_modern ( struct net_device *netdev ) {
}
/* Initialize rx/tx virtqueues */
- if ( vpm_find_vqs ( &virtnet->vdev, QUEUE_NB, virtnet->virtqueue ) ) {
+ if ( vpm_find_vqs ( &virtnet->vdev, QUEUE_NB, virtnet->virtqueue,
+ virtnet->dma, sizeof ( struct virtio_net_hdr_modern ) ) ) {
DBGC ( virtnet, "VIRTIO-NET %p cannot register queues\n",
virtnet );
virtnet_free_virtqueues ( netdev );
@@ -335,7 +339,7 @@ static void virtnet_close ( struct net_device *netdev ) {
/* Free rx iobufs */
list_for_each_entry_safe ( iobuf, next_iobuf, &virtnet->rx_iobufs, list ) {
- free_iob ( iobuf );
+ free_rx_iob ( iobuf );
}
INIT_LIST_HEAD ( &virtnet->rx_iobufs );
virtnet->rx_num_iobufs = 0;
@@ -478,6 +482,12 @@ static int virtnet_probe_legacy ( struct pci_device *pci ) {
/* Enable PCI bus master and reset NIC */
adjust_pci_device ( pci );
+
+ /* Configure DMA */
+ virtnet->dma = &pci->dma;
+ dma_set_mask_64bit ( virtnet->dma );
+ netdev->dma = virtnet->dma;
+
vp_reset ( ioaddr );
/* Load MAC address and MTU */
@@ -506,7 +516,7 @@ static int virtnet_probe_legacy ( struct pci_device *pci ) {
return 0;
unregister_netdev ( netdev );
- err_register_netdev:
+err_register_netdev:
vp_reset ( ioaddr );
netdev_nullify ( netdev );
netdev_put ( netdev );
@@ -586,6 +596,11 @@ static int virtnet_probe_modern ( struct pci_device *pci, int *found_dev ) {
/* Enable the PCI device */
adjust_pci_device ( pci );
+ /* Configure DMA */
+ virtnet->dma = &pci->dma;
+ dma_set_mask_64bit ( virtnet->dma );
+ netdev->dma = virtnet->dma;
+
/* Reset the device and set initial status bits */
vpm_reset ( &virtnet->vdev );
vpm_add_status ( &virtnet->vdev, VIRTIO_CONFIG_S_ACKNOWLEDGE );
@@ -633,7 +648,6 @@ err_mac_address:
vpm_reset ( &virtnet->vdev );
netdev_nullify ( netdev );
netdev_put ( netdev );
-
virtio_pci_unmap_capability ( &virtnet->vdev.device );
err_map_device:
virtio_pci_unmap_capability ( &virtnet->vdev.isr );
diff --git a/src/drivers/usb/usbkbd.c b/src/drivers/usb/usbkbd.c
index a8ab6ab76..b284e584f 100644
--- a/src/drivers/usb/usbkbd.c
+++ b/src/drivers/usb/usbkbd.c
@@ -25,10 +25,12 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <stdlib.h>
#include <string.h>
+#include <ctype.h>
#include <errno.h>
#include <assert.h>
#include <ipxe/console.h>
#include <ipxe/keys.h>
+#include <ipxe/keymap.h>
#include <ipxe/usb.h>
#include "usbkbd.h"
@@ -69,10 +71,7 @@ static unsigned int usbkbd_map ( unsigned int keycode, unsigned int modifiers,
} else if ( keycode <= USBKBD_KEY_Z ) {
/* Alphabetic keys */
key = ( keycode - USBKBD_KEY_A + 'a' );
- if ( modifiers & USBKBD_CTRL ) {
- key -= ( 'a' - CTRL_A );
- } else if ( ( modifiers & USBKBD_SHIFT ) ||
- ( leds & USBKBD_LED_CAPS_LOCK ) ) {
+ if ( modifiers & USBKBD_SHIFT ) {
key -= ( 'a' - 'A' );
}
} else if ( keycode <= USBKBD_KEY_0 ) {
@@ -118,10 +117,30 @@ static unsigned int usbkbd_map ( unsigned int keycode, unsigned int modifiers,
};
key = keypad[ keycode - USBKBD_KEY_PAD_1 ];
};
+ } else if ( keycode == USBKBD_KEY_NON_US ) {
+ /* Non-US \ and | */
+ key = ( ( modifiers & USBKBD_SHIFT ) ?
+ ( KEYMAP_PSEUDO | '|' ) : ( KEYMAP_PSEUDO | '\\' ) );
} else {
key = 0;
}
+ /* Remap key if applicable */
+ if ( ( keycode < USBKBD_KEY_CAPS_LOCK ) ||
+ ( keycode == USBKBD_KEY_NON_US ) ) {
+
+ /* Apply modifiers */
+ if ( modifiers & USBKBD_CTRL )
+ key |= KEYMAP_CTRL;
+ if ( modifiers & USBKBD_ALT_RIGHT )
+ key |= KEYMAP_ALTGR;
+ if ( leds & USBKBD_LED_CAPS_LOCK )
+ key |= KEYMAP_CAPSLOCK;
+
+ /* Remap key */
+ key = key_remap ( key );
+ }
+
return key;
}
diff --git a/src/drivers/usb/usbkbd.h b/src/drivers/usb/usbkbd.h
index cedebfe71..1a3fea1ba 100644
--- a/src/drivers/usb/usbkbd.h
+++ b/src/drivers/usb/usbkbd.h
@@ -75,6 +75,7 @@ enum usb_keycode {
USBKBD_KEY_PAD_ENTER = 0x58,
USBKBD_KEY_PAD_1 = 0x59,
USBKBD_KEY_PAD_DOT = 0x63,
+ USBKBD_KEY_NON_US = 0x64,
};
/** USB keyboard LEDs */
diff --git a/src/drivers/usb/xhci.c b/src/drivers/usb/xhci.c
index cc48af033..3247ee69c 100644
--- a/src/drivers/usb/xhci.c
+++ b/src/drivers/usb/xhci.c
@@ -1165,6 +1165,31 @@ static int xhci_reset ( struct xhci_device *xhci ) {
return -ETIMEDOUT;
}
+/**
+ * Mark xHCI device as permanently failed
+ *
+ * @v xhci xHCI device
+ * @ret rc Return status code
+ */
+static int xhci_fail ( struct xhci_device *xhci ) {
+ size_t len;
+ int rc;
+
+ /* Mark command mechanism as permanently failed */
+ xhci->failed = 1;
+
+ /* Reset device */
+ if ( ( rc = xhci_reset ( xhci ) ) != 0 )
+ return rc;
+
+ /* Discard DCBAA entries since DCBAAP has been cleared */
+ assert ( xhci->dcbaa.context != NULL );
+ len = ( ( xhci->slots + 1 ) * sizeof ( xhci->dcbaa.context[0] ) );
+ memset ( xhci->dcbaa.context, 0, len );
+
+ return 0;
+}
+
/******************************************************************************
*
* Transfer request blocks
@@ -1720,6 +1745,10 @@ static void xhci_event_poll ( struct xhci_device *xhci ) {
unsigned int consumed;
unsigned int type;
+ /* Do nothing if device has permanently failed */
+ if ( xhci->failed )
+ return;
+
/* Poll for events */
profile_start ( &xhci_event_profiler );
for ( consumed = 0 ; ; consumed++ ) {
@@ -1778,6 +1807,7 @@ static void xhci_event_poll ( struct xhci_device *xhci ) {
*/
static void xhci_abort ( struct xhci_device *xhci ) {
physaddr_t crp;
+ uint32_t crcr;
/* Abort the command */
DBGC2 ( xhci, "XHCI %s aborting command\n", xhci->name );
@@ -1786,8 +1816,18 @@ static void xhci_abort ( struct xhci_device *xhci ) {
/* Allow time for command to abort */
mdelay ( XHCI_COMMAND_ABORT_DELAY_MS );
- /* Sanity check */
- assert ( ( readl ( xhci->op + XHCI_OP_CRCR ) & XHCI_CRCR_CRR ) == 0 );
+ /* Check for failure to abort */
+ crcr = readl ( xhci->op + XHCI_OP_CRCR );
+ if ( crcr & XHCI_CRCR_CRR ) {
+
+ /* Device has failed to abort a command and is almost
+ * certainly beyond repair. Reset device, abandoning
+ * all state, and mark device as failed to avoid
+ * delays on any future command attempts.
+ */
+ DBGC ( xhci, "XHCI %s failed to abort command\n", xhci->name );
+ xhci_fail ( xhci );
+ }
/* Consume (and ignore) any final command status */
xhci_event_poll ( xhci );
@@ -1813,6 +1853,12 @@ static int xhci_command ( struct xhci_device *xhci, union xhci_trb *trb ) {
unsigned int i;
int rc;
+ /* Immediately fail all commands if command mechanism has failed */
+ if ( xhci->failed ) {
+ rc = -EPIPE;
+ goto err_failed;
+ }
+
/* Sanity check */
if ( xhci->pending ) {
DBGC ( xhci, "XHCI %s command ring busy\n", xhci->name );
@@ -1863,6 +1909,7 @@ static int xhci_command ( struct xhci_device *xhci, union xhci_trb *trb ) {
err_enqueue:
xhci->pending = NULL;
err_pending:
+ err_failed:
return rc;
}
@@ -3412,14 +3459,36 @@ static int xhci_probe ( struct pci_device *pci ) {
static void xhci_remove ( struct pci_device *pci ) {
struct xhci_device *xhci = pci_get_drvdata ( pci );
struct usb_bus *bus = xhci->bus;
+ uint16_t command;
+
+ /* Some systems are observed to disable bus mastering on
+ * Thunderbolt controllers before we get a chance to shut
+ * down. Detect this and avoid attempting any DMA operations,
+ * which are guaranteed to fail and may end up spuriously
+ * completing after the operating system kernel starts up.
+ */
+ pci_read_config_word ( pci, PCI_COMMAND, &command );
+ if ( ! ( command & PCI_COMMAND_MASTER ) ) {
+ DBGC ( xhci, "XHCI %s DMA was disabled\n", xhci->name );
+ xhci_fail ( xhci );
+ }
+ /* Unregister and free USB bus */
unregister_usb_bus ( bus );
free_usb_bus ( bus );
+
+ /* Reset device and undo any PCH-specific fixes */
xhci_reset ( xhci );
if ( xhci->quirks & XHCI_PCH )
xhci_pch_undo ( xhci, pci );
+
+ /* Release ownership back to BIOS */
xhci_legacy_release ( xhci );
+
+ /* Unmap registers */
iounmap ( xhci->regs );
+
+ /* Free device */
free ( xhci );
}
diff --git a/src/drivers/usb/xhci.h b/src/drivers/usb/xhci.h
index 6e02d70e0..a3c8888af 100644
--- a/src/drivers/usb/xhci.h
+++ b/src/drivers/usb/xhci.h
@@ -1115,6 +1115,8 @@ struct xhci_device {
struct xhci_event_ring event;
/** Current command (if any) */
union xhci_trb *pending;
+ /** Command mechanism has permanently failed */
+ int failed;
/** Device slots, indexed by slot ID */
struct xhci_slot **slot;