summaryrefslogtreecommitdiffstats
path: root/src/drivers/net/ena.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/drivers/net/ena.c')
-rw-r--r--src/drivers/net/ena.c335
1 files changed, 294 insertions, 41 deletions
diff --git a/src/drivers/net/ena.c b/src/drivers/net/ena.c
index 7ce5b9eb9..56984d142 100644
--- a/src/drivers/net/ena.c
+++ b/src/drivers/net/ena.c
@@ -371,8 +371,11 @@ static int ena_set_aenq_config ( struct ena_nic *ena, uint32_t enabled ) {
feature->aenq.enabled = cpu_to_le32 ( enabled );
/* Issue request */
- if ( ( rc = ena_admin ( ena, req, &rsp ) ) != 0 )
+ if ( ( rc = ena_admin ( ena, req, &rsp ) ) != 0 ) {
+ DBGC ( ena, "ENA %p could not set AENQ configuration: %s\n",
+ ena, strerror ( rc ) );
return rc;
+ }
return 0;
}
@@ -447,6 +450,7 @@ static int ena_create_sq ( struct ena_nic *ena, struct ena_sq *sq,
union ena_aq_req *req;
union ena_acq_rsp *rsp;
unsigned int i;
+ size_t llqe;
int rc;
/* Allocate submission queue entries */
@@ -461,26 +465,39 @@ static int ena_create_sq ( struct ena_nic *ena, struct ena_sq *sq,
req = ena_admin_req ( ena );
req->header.opcode = ENA_CREATE_SQ;
req->create_sq.direction = sq->direction;
- req->create_sq.policy = cpu_to_le16 ( ENA_SQ_HOST_MEMORY |
- ENA_SQ_CONTIGUOUS );
+ req->create_sq.policy = cpu_to_le16 ( sq->policy );
req->create_sq.cq_id = cpu_to_le16 ( cq->id );
req->create_sq.count = cpu_to_le16 ( sq->count );
- req->create_sq.address = cpu_to_le64 ( virt_to_bus ( sq->sqe.raw ) );
+ if ( ! ( sq->policy & ENA_SQ_DEVICE_MEMORY ) ) {
+ req->create_sq.address =
+ cpu_to_le64 ( virt_to_bus ( sq->sqe.raw ) );
+ }
/* Issue request */
- if ( ( rc = ena_admin ( ena, req, &rsp ) ) != 0 )
+ if ( ( rc = ena_admin ( ena, req, &rsp ) ) != 0 ) {
+ DBGC ( ena, "ENA %p could not create %s SQ: %s\n",
+ ena, ena_direction ( sq->direction ), strerror ( rc ) );
goto err_admin;
+ }
/* Parse response */
sq->id = le16_to_cpu ( rsp->create_sq.id );
sq->doorbell = le32_to_cpu ( rsp->create_sq.doorbell );
+ llqe = le32_to_cpu ( rsp->create_sq.llqe );
+ if ( sq->policy & ENA_SQ_DEVICE_MEMORY ) {
+ assert ( ena->mem != NULL );
+ assert ( sq->len >= sizeof ( *sq->sqe.llq ) );
+ sq->llqe = ( ena->mem + llqe );
+ } else {
+ sq->llqe = NULL;
+ }
/* Reset producer counter and phase */
sq->prod = 0;
sq->phase = ENA_SQE_PHASE;
/* Calculate fill level */
- sq->fill = sq->max;
+ sq->fill = sq->count;
if ( sq->fill > cq->actual )
sq->fill = cq->actual;
@@ -488,10 +505,16 @@ static int ena_create_sq ( struct ena_nic *ena, struct ena_sq *sq,
for ( i = 0 ; i < sq->count ; i++ )
sq->ids[i] = i;
- DBGC ( ena, "ENA %p %s SQ%d at [%08lx,%08lx) fill %d db +%04x CQ%d\n",
- ena, ena_direction ( sq->direction ), sq->id,
- virt_to_phys ( sq->sqe.raw ),
- ( virt_to_phys ( sq->sqe.raw ) + sq->len ),
+ DBGC ( ena, "ENA %p %s SQ%d at ",
+ ena, ena_direction ( sq->direction ), sq->id );
+ if ( sq->policy & ENA_SQ_DEVICE_MEMORY ) {
+ DBGC ( ena, "LLQ [+%08zx,+%08zx)", llqe,
+ ( llqe + ( sq->count * sizeof ( sq->sqe.llq[0] ) ) ) );
+ } else {
+ DBGC ( ena, "[%08lx,%08lx)", virt_to_phys ( sq->sqe.raw ),
+ ( virt_to_phys ( sq->sqe.raw ) + sq->len ) );
+ }
+ DBGC ( ena, " fill %d db +%04x CQ%d\n",
sq->fill, sq->doorbell, cq->id );
return 0;
@@ -520,8 +543,12 @@ static int ena_destroy_sq ( struct ena_nic *ena, struct ena_sq *sq ) {
req->destroy_sq.direction = sq->direction;
/* Issue request */
- if ( ( rc = ena_admin ( ena, req, &rsp ) ) != 0 )
+ if ( ( rc = ena_admin ( ena, req, &rsp ) ) != 0 ) {
+ DBGC ( ena, "ENA %p could not destroy %s SQ%d: %s\n",
+ ena, ena_direction ( sq->direction ), sq->id,
+ strerror ( rc ) );
return rc;
+ }
/* Free submission queue entries */
free_phys ( sq->sqe.raw, sq->len );
@@ -560,8 +587,11 @@ static int ena_create_cq ( struct ena_nic *ena, struct ena_cq *cq ) {
req->create_cq.address = cpu_to_le64 ( virt_to_bus ( cq->cqe.raw ) );
/* Issue request */
- if ( ( rc = ena_admin ( ena, req, &rsp ) ) != 0 )
+ if ( ( rc = ena_admin ( ena, req, &rsp ) ) != 0 ) {
+ DBGC ( ena, "ENA %p could not create CQ (broken firmware?): "
+ "%s\n", ena, strerror ( rc ) );
goto err_admin;
+ }
/* Parse response */
cq->id = le16_to_cpu ( rsp->create_cq.id );
@@ -606,8 +636,11 @@ static int ena_destroy_cq ( struct ena_nic *ena, struct ena_cq *cq ) {
req->destroy_cq.id = cpu_to_le16 ( cq->id );
/* Issue request */
- if ( ( rc = ena_admin ( ena, req, &rsp ) ) != 0 )
+ if ( ( rc = ena_admin ( ena, req, &rsp ) ) != 0 ) {
+ DBGC ( ena, "ENA %p could not destroy CQ%d: %s\n",
+ ena, cq->id, strerror ( rc ) );
return rc;
+ }
/* Free completion queue entries */
free_phys ( cq->cqe.raw, cq->len );
@@ -680,17 +713,22 @@ static int ena_get_device_attributes ( struct net_device *netdev ) {
req->get_feature.id = ENA_DEVICE_ATTRIBUTES;
/* Issue request */
- if ( ( rc = ena_admin ( ena, req, &rsp ) ) != 0 )
+ if ( ( rc = ena_admin ( ena, req, &rsp ) ) != 0 ) {
+ DBGC ( ena, "ENA %p could not get device attributes: %s\n",
+ ena, strerror ( rc ) );
return rc;
+ }
/* Parse response */
feature = &rsp->get_feature.feature;
memcpy ( netdev->hw_addr, feature->device.mac, ETH_ALEN );
netdev->max_pkt_len = le32_to_cpu ( feature->device.mtu );
netdev->mtu = ( netdev->max_pkt_len - ETH_HLEN );
+ ena->features = le32_to_cpu ( feature->device.features );
- DBGC ( ena, "ENA %p MAC %s MTU %zd\n",
- ena, eth_ntoa ( netdev->hw_addr ), netdev->max_pkt_len );
+ DBGC ( ena, "ENA %p MAC %s MTU %zd features %#08x\n",
+ ena, eth_ntoa ( netdev->hw_addr ), netdev->max_pkt_len,
+ ena->features );
return 0;
}
@@ -714,8 +752,106 @@ static int ena_set_host_attributes ( struct ena_nic *ena ) {
feature->host.info = cpu_to_le64 ( virt_to_bus ( ena->info ) );
/* Issue request */
- if ( ( rc = ena_admin ( ena, req, &rsp ) ) != 0 )
+ if ( ( rc = ena_admin ( ena, req, &rsp ) ) != 0 ) {
+ DBGC ( ena, "ENA %p could not set host attributes: %s\n",
+ ena, strerror ( rc ) );
return rc;
+ }
+
+ return 0;
+}
+
+/**
+ * Configure low latency queues
+ *
+ * @v ena ENA device
+ * @ret rc Return status code
+ */
+static int ena_llq_config ( struct ena_nic *ena ) {
+ union ena_aq_req *req;
+ union ena_acq_rsp *rsp;
+ union ena_feature *feature;
+ uint16_t header;
+ uint16_t size;
+ uint16_t desc;
+ uint16_t stride;
+ uint16_t mode;
+ int rc;
+
+ /* Construct request */
+ req = ena_admin_req ( ena );
+ req->header.opcode = ENA_GET_FEATURE;
+ req->get_feature.id = ENA_LLQ_CONFIG;
+
+ /* Issue request */
+ if ( ( rc = ena_admin ( ena, req, &rsp ) ) != 0 ) {
+ DBGC ( ena, "ENA %p could not get LLQ configuration: %s\n",
+ ena, strerror ( rc ) );
+ return rc;
+ }
+
+ /* Parse response */
+ feature = &rsp->get_feature.feature;
+ header = le16_to_cpu ( feature->llq.header.supported );
+ size = le16_to_cpu ( feature->llq.size.supported );
+ desc = le16_to_cpu ( feature->llq.desc.supported );
+ stride = le16_to_cpu ( feature->llq.stride.supported );
+ mode = le16_to_cpu ( feature->llq.mode );
+ DBGC ( ena, "ENA %p LLQ supports %02x:%02x:%02x:%02x:%02x with %dx%d "
+ "entries\n", ena, header, size, desc, stride, mode,
+ le32_to_cpu ( feature->llq.queues ),
+ le32_to_cpu ( feature->llq.count ) );
+
+ /* Check for a supported configuration */
+ if ( ! feature->llq.queues ) {
+ DBGC ( ena, "ENA %p LLQ has no queues\n", ena );
+ return -ENOTSUP;
+ }
+ if ( ! ( header & ENA_LLQ_HEADER_INLINE ) ) {
+ DBGC ( ena, "ENA %p LLQ does not support inline headers\n",
+ ena );
+ return -ENOTSUP;
+ }
+ if ( ! ( size & ENA_LLQ_SIZE_128 ) ) {
+ DBGC ( ena, "ENA %p LLQ does not support 128-byte entries\n",
+ ena );
+ return -ENOTSUP;
+ }
+ if ( ! ( desc & ENA_LLQ_DESC_2 ) ) {
+ DBGC ( ena, "ENA %p LLQ does not support two-descriptor "
+ "entries\n", ena );
+ return -ENOTSUP;
+ }
+
+ /* Enable a minimal configuration */
+ header = ENA_LLQ_HEADER_INLINE;
+ size = ENA_LLQ_SIZE_128;
+ desc = ENA_LLQ_DESC_2;
+ stride &= ( -stride ); /* Don't care: use first supported option */
+ DBGC ( ena, "ENA %p LLQ enabling %02x:%02x:%02x:%02x:%02x\n",
+ ena, header, size, desc, stride, mode );
+
+ /* Construct request */
+ req = ena_admin_req ( ena );
+ req->header.opcode = ENA_SET_FEATURE;
+ req->set_feature.id = ENA_LLQ_CONFIG;
+ feature = &req->set_feature.feature;
+ feature->llq.header.enabled = cpu_to_le16 ( header );
+ feature->llq.size.enabled = cpu_to_le16 ( size );
+ feature->llq.desc.enabled = cpu_to_le16 ( desc );
+ feature->llq.stride.enabled = cpu_to_le16 ( stride );
+ feature->llq.mode = cpu_to_le16 ( mode );
+
+ /* Issue request */
+ if ( ( rc = ena_admin ( ena, req, &rsp ) ) != 0 ) {
+ DBGC ( ena, "ENA %p could not set LLQ configuration: %s\n",
+ ena, strerror ( rc ) );
+ return rc;
+ }
+
+ /* Use on-device memory for transmit queue */
+ ena->tx.sq.policy |= ENA_SQ_DEVICE_MEMORY;
+ ena->tx.sq.inlined = sizeof ( ena->tx.sq.sqe.llq->inlined );
return 0;
}
@@ -744,8 +880,11 @@ static int ena_get_stats ( struct ena_nic *ena ) {
req->get_stats.device = ENA_DEVICE_MINE;
/* Issue request */
- if ( ( rc = ena_admin ( ena, req, &rsp ) ) != 0 )
+ if ( ( rc = ena_admin ( ena, req, &rsp ) ) != 0 ) {
+ DBGC ( ena, "ENA %p could not get statistics: %s\n",
+ ena, strerror ( rc ) );
return rc;
+ }
/* Parse response */
stats = &rsp->get_stats;
@@ -834,12 +973,32 @@ static void ena_refill_rx ( struct net_device *netdev ) {
* @v ena ENA device
*/
static void ena_empty_rx ( struct ena_nic *ena ) {
+ struct io_buffer *iobuf;
unsigned int i;
for ( i = 0 ; i < ENA_RX_COUNT ; i++ ) {
- if ( ena->rx_iobuf[i] )
- free_iob ( ena->rx_iobuf[i] );
+ iobuf = ena->rx_iobuf[i];
ena->rx_iobuf[i] = NULL;
+ if ( iobuf )
+ free_iob ( iobuf );
+ }
+}
+
+/**
+ * Cancel uncompleted transmit I/O buffers
+ *
+ * @v netdev Network device
+ */
+static void ena_cancel_tx ( struct net_device *netdev ) {
+ struct ena_nic *ena = netdev->priv;
+ struct io_buffer *iobuf;
+ unsigned int i;
+
+ for ( i = 0 ; i < ENA_TX_COUNT ; i++ ) {
+ iobuf = ena->tx_iobuf[i];
+ ena->tx_iobuf[i] = NULL;
+ if ( iobuf )
+ netdev_tx_complete_err ( netdev, iobuf, -ECANCELED );
}
}
@@ -892,6 +1051,9 @@ static void ena_close ( struct net_device *netdev ) {
/* Destroy transmit queue pair */
ena_destroy_qp ( ena, &ena->tx );
+
+ /* Cancel any uncompleted transmit buffers */
+ ena_cancel_tx ( netdev );
}
/**
@@ -904,9 +1066,15 @@ static void ena_close ( struct net_device *netdev ) {
static int ena_transmit ( struct net_device *netdev, struct io_buffer *iobuf ) {
struct ena_nic *ena = netdev->priv;
struct ena_tx_sqe *sqe;
+ struct ena_tx_llqe *llqe;
+ const uint64_t *src;
+ uint64_t *dest;
physaddr_t address;
unsigned int index;
unsigned int id;
+ unsigned int i;
+ uint8_t flags;
+ size_t inlined;
size_t len;
/* Get next submission queue entry */
@@ -918,17 +1086,51 @@ static int ena_transmit ( struct net_device *netdev, struct io_buffer *iobuf ) {
sqe = &ena->tx.sq.sqe.tx[index];
id = ena->tx_ids[index];
- /* Construct submission queue entry */
+ /* Construct submission queue entry values */
address = virt_to_bus ( iobuf->data );
len = iob_len ( iobuf );
+ inlined = ena->tx.sq.inlined;
+ if ( inlined > len )
+ inlined = len;
+ len -= inlined;
+ address += inlined;
+ flags = ( ENA_SQE_FIRST | ENA_SQE_LAST | ENA_SQE_CPL |
+ ena->tx.sq.phase );
+
+ /* Prepare low-latency queue bounce buffer, if applicable */
+ llqe = ena->tx.sq.sqe.llq;
+ if ( ena->tx.sq.llqe ) {
+
+ /* Construct zero-information metadata queue entry */
+ llqe->meta.meta = ENA_TX_SQE_META;
+ llqe->meta.flags = ( flags & ~( ENA_SQE_LAST | ENA_SQE_CPL ) );
+
+ /* Copy inlined data */
+ memcpy ( llqe->inlined, iobuf->data, inlined );
+
+ /* Place submission queue entry within bounce buffer */
+ sqe = &llqe->sqe;
+ flags &= ~ENA_SQE_FIRST;
+ }
+
+ /* Construct submission queue entry */
sqe->len = cpu_to_le16 ( len );
sqe->id = cpu_to_le16 ( id );
sqe->address = cpu_to_le64 ( address );
+ sqe->inlined = inlined;
wmb();
- sqe->flags = ( ENA_SQE_FIRST | ENA_SQE_LAST | ENA_SQE_CPL |
- ena->tx.sq.phase );
+ sqe->flags = flags;
wmb();
+ /* Copy bounce buffer to on-device memory, if applicable */
+ if ( ena->tx.sq.llqe ) {
+ src = ( ( const void * ) llqe );
+ dest = ( ena->tx.sq.llqe + ( index * sizeof ( *llqe ) ) );
+ for ( i = 0 ; i < ( sizeof ( *llqe ) / sizeof ( *src ) ); i++ )
+ writeq ( *(src++), dest++ );
+ wmb();
+ }
+
/* Increment producer counter */
ena->tx.sq.prod++;
if ( ( ena->tx.sq.prod % ENA_TX_COUNT ) == 0 )
@@ -1070,10 +1272,12 @@ static struct net_device_operations ena_operations = {
*/
/**
- * Assign memory BAR
+ * Assign memory BARs
*
* @v ena ENA device
* @v pci PCI device
+ * @v prefmembase On-device memory base address to fill in
+ * @v prefmemsize On-device memory size to fill in
* @ret rc Return status code
*
* Some BIOSes in AWS EC2 are observed to fail to assign a base
@@ -1081,15 +1285,27 @@ static struct net_device_operations ena_operations = {
* its bridge, and the BIOS does assign a memory window to the bridge.
* We therefore place the device at the start of the memory window.
*/
-static int ena_membase ( struct ena_nic *ena, struct pci_device *pci ) {
+static int ena_membases ( struct ena_nic *ena, struct pci_device *pci,
+ unsigned long *prefmembase,
+ unsigned long *prefmemsize ) {
struct pci_bridge *bridge;
+ /* Get on-device memory base address and size */
+ *prefmembase = pci_bar_start ( pci, ENA_MEM_BAR );
+ *prefmemsize = pci_bar_size ( pci, ENA_MEM_BAR );
+
+ /* Do nothing if addresses are already assigned */
+ if ( pci->membase && ( *prefmembase || ( ! *prefmemsize ) ) )
+ return 0;
+
/* Locate PCI bridge */
bridge = pcibridge_find ( pci );
if ( ! bridge ) {
DBGC ( ena, "ENA %p found no PCI bridge\n", ena );
return -ENOTCONN;
}
+ DBGC ( ena, "ENA %p at " PCI_FMT " claiming bridge " PCI_FMT "\n",
+ ena, PCI_ARGS ( pci ), PCI_ARGS ( bridge->pci ) );
/* Sanity check */
if ( PCI_SLOT ( pci->busdevfn ) || PCI_FUNC ( pci->busdevfn ) ) {
@@ -1098,12 +1314,21 @@ static int ena_membase ( struct ena_nic *ena, struct pci_device *pci ) {
return -ENOTSUP;
}
- /* Place device at start of memory window */
- pci_write_config_dword ( pci, PCI_BASE_ADDRESS_0, bridge->membase );
- pci->membase = bridge->membase;
- DBGC ( ena, "ENA %p at " PCI_FMT " claiming bridge " PCI_FMT " mem "
- "%08x\n", ena, PCI_ARGS ( pci ), PCI_ARGS ( bridge->pci ),
- bridge->membase );
+ /* Place register BAR at start of memory window, if applicable */
+ if ( ! pci->membase ) {
+ pci_bar_set ( pci, ENA_REGS_BAR, bridge->membase );
+ pci->membase = bridge->membase;
+ DBGC ( ena, "ENA %p at " PCI_FMT " claiming mem %08lx\n",
+ ena, PCI_ARGS ( pci ), pci->membase );
+ }
+
+ /* Place memory BAR at start of prefetchable window, if applicable */
+ if ( *prefmemsize && ( ! *prefmembase ) ) {
+ pci_bar_set ( pci, ENA_MEM_BAR, bridge->prefmembase );
+ *prefmembase = bridge->prefmembase;
+ DBGC ( ena, "ENA %p at " PCI_FMT " claiming prefmem %08lx\n",
+ ena, PCI_ARGS ( pci ), *prefmembase );
+ }
return 0;
}
@@ -1118,6 +1343,8 @@ static int ena_probe ( struct pci_device *pci ) {
struct net_device *netdev;
struct ena_nic *ena;
struct ena_host_info *info;
+ unsigned long prefmembase;
+ unsigned long prefmemsize;
int rc;
/* Allocate and initialise net device */
@@ -1134,25 +1361,38 @@ static int ena_probe ( struct pci_device *pci ) {
ena->acq.phase = ENA_ACQ_PHASE;
ena_cq_init ( &ena->tx.cq, ENA_TX_COUNT,
sizeof ( ena->tx.cq.cqe.tx[0] ) );
- ena_sq_init ( &ena->tx.sq, ENA_SQ_TX, ENA_TX_COUNT, ENA_TX_COUNT,
+ ena_sq_init ( &ena->tx.sq, ENA_SQ_TX, ENA_TX_COUNT,
sizeof ( ena->tx.sq.sqe.tx[0] ), ena->tx_ids );
ena_cq_init ( &ena->rx.cq, ENA_RX_COUNT,
sizeof ( ena->rx.cq.cqe.rx[0] ) );
- ena_sq_init ( &ena->rx.sq, ENA_SQ_RX, ENA_RX_COUNT, ENA_RX_FILL,
+ ena_sq_init ( &ena->rx.sq, ENA_SQ_RX, ENA_RX_COUNT,
sizeof ( ena->rx.sq.sqe.rx[0] ), ena->rx_ids );
/* Fix up PCI device */
adjust_pci_device ( pci );
/* Fix up PCI BAR if left unassigned by BIOS */
- if ( ( ! pci->membase ) && ( ( rc = ena_membase ( ena, pci ) ) != 0 ) )
- goto err_membase;
+ if ( ( rc = ena_membases ( ena, pci, &prefmembase,
+ &prefmemsize ) ) != 0 ) {
+ goto err_membases;
+ }
/* Map registers */
- ena->regs = pci_ioremap ( pci, pci->membase, ENA_BAR_SIZE );
+ ena->regs = pci_ioremap ( pci, pci->membase, ENA_REGS_SIZE );
if ( ! ena->regs ) {
rc = -ENODEV;
- goto err_ioremap;
+ goto err_regs;
+ }
+
+ /* Map device memory */
+ if ( prefmemsize ) {
+ ena->mem = pci_ioremap ( pci, prefmembase, prefmemsize );
+ if ( ! ena->mem ) {
+ rc = -ENODEV;
+ goto err_mem;
+ }
+ DBGC ( ena, "ENA %p has %ldkB of on-device memory\n",
+ ena, ( prefmemsize >> 10 ) );
}
/* Allocate and initialise host info */
@@ -1163,7 +1403,7 @@ static int ena_probe ( struct pci_device *pci ) {
}
ena->info = info;
memset ( info, 0, PAGE_SIZE );
- info->type = cpu_to_le32 ( ENA_HOST_INFO_TYPE_LINUX );
+ info->type = cpu_to_le32 ( ENA_HOST_INFO_TYPE_IPXE );
snprintf ( info->dist_str, sizeof ( info->dist_str ), "%s",
( product_name[0] ? product_name : product_short_name ) );
snprintf ( info->kernel_str, sizeof ( info->kernel_str ), "%s",
@@ -1194,6 +1434,12 @@ static int ena_probe ( struct pci_device *pci ) {
if ( ( rc = ena_get_device_attributes ( netdev ) ) != 0 )
goto err_get_device_attributes;
+ /* Attempt to configure low latency queues, if applicable.
+ * Ignore any errors and continue without using LLQs.
+ */
+ if ( ena->mem && ( ena->features & ENA_FEATURE_LLQ ) )
+ ena_llq_config ( ena );
+
/* Register network device */
if ( ( rc = register_netdev ( netdev ) ) != 0 )
goto err_register_netdev;
@@ -1217,9 +1463,12 @@ static int ena_probe ( struct pci_device *pci ) {
err_reset:
free_phys ( ena->info, PAGE_SIZE );
err_info:
+ if ( ena->mem )
+ iounmap ( ena->mem );
+ err_mem:
iounmap ( ena->regs );
- err_ioremap:
- err_membase:
+ err_regs:
+ err_membases:
netdev_nullify ( netdev );
netdev_put ( netdev );
err_alloc:
@@ -1250,8 +1499,12 @@ static void ena_remove ( struct pci_device *pci ) {
/* Free host info */
free_phys ( ena->info, PAGE_SIZE );
- /* Free network device */
+ /* Unmap registers and on-device memory */
+ if ( ena->mem )
+ iounmap ( ena->mem );
iounmap ( ena->regs );
+
+ /* Free network device */
netdev_nullify ( netdev );
netdev_put ( netdev );
}