Newer generations of the ENA hardware require the use of low latency transmit queues, where the submission queues and the initial portion of the transmitted packet are written to on-device memory via BAR2 instead of being read from host memory. Detect support for low latency queues and set the placement policy appropriately. We attempt the use of low latency queues only if the device reports that it supports inline headers, 128-byte entries, and two descriptors prior to the inlined header, on the basis that we don't care about using low latency queues on older versions of the hardware since those versions will support normal host memory submission queues anyway. We reuse the redundant memory allocated for the submission queue as the bounce buffer for constructing the descriptors and inlined packet data, since this avoids needing a separate allocation just for the bounce buffer. We construct a metadata submission queue entry prior to the actual submission queue entry, since experimentation suggests that newer generations of the hardware require this to be present even though it conveys no information beyond its own existence. Signed-off-by: Michael Brown <mcb30@ipxe.org>
1521 lines
38 KiB
C
1521 lines
38 KiB
C
/*
|
|
* Copyright (C) 2018 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 <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <byteswap.h>
|
|
#include <ipxe/netdevice.h>
|
|
#include <ipxe/ethernet.h>
|
|
#include <ipxe/if_ether.h>
|
|
#include <ipxe/iobuf.h>
|
|
#include <ipxe/malloc.h>
|
|
#include <ipxe/pci.h>
|
|
#include <ipxe/pcibridge.h>
|
|
#include <ipxe/version.h>
|
|
#include "ena.h"
|
|
|
|
/** @file
|
|
*
|
|
* Amazon ENA network driver
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* Get direction name (for debugging)
|
|
*
|
|
* @v direction Direction
|
|
* @ret name Direction name
|
|
*/
|
|
static const char * ena_direction ( unsigned int direction ) {
|
|
|
|
switch ( direction ) {
|
|
case ENA_SQ_TX: return "TX";
|
|
case ENA_SQ_RX: return "RX";
|
|
default: return "<UNKNOWN>";
|
|
}
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* Device reset
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Wait for reset operation to be acknowledged
|
|
*
|
|
* @v ena ENA device
|
|
* @v expected Expected reset state
|
|
* @ret rc Return status code
|
|
*/
|
|
static int ena_reset_wait ( struct ena_nic *ena, uint32_t expected ) {
|
|
uint32_t stat;
|
|
unsigned int i;
|
|
|
|
/* Wait for reset to complete */
|
|
for ( i = 0 ; i < ENA_RESET_MAX_WAIT_MS ; i++ ) {
|
|
|
|
/* Check if device is ready */
|
|
stat = readl ( ena->regs + ENA_STAT );
|
|
if ( ( stat & ENA_STAT_RESET ) == expected )
|
|
return 0;
|
|
|
|
/* Delay */
|
|
mdelay ( 1 );
|
|
}
|
|
|
|
DBGC ( ena, "ENA %p timed out waiting for reset status %#08x "
|
|
"(got %#08x)\n", ena, expected, stat );
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/**
|
|
* Reset hardware
|
|
*
|
|
* @v ena ENA device
|
|
* @ret rc Return status code
|
|
*/
|
|
static int ena_reset ( struct ena_nic *ena ) {
|
|
int rc;
|
|
|
|
/* Trigger reset */
|
|
writel ( ENA_CTRL_RESET, ( ena->regs + ENA_CTRL ) );
|
|
|
|
/* Wait for reset to take effect */
|
|
if ( ( rc = ena_reset_wait ( ena, ENA_STAT_RESET ) ) != 0 )
|
|
return rc;
|
|
|
|
/* Clear reset */
|
|
writel ( 0, ( ena->regs + ENA_CTRL ) );
|
|
|
|
/* Wait for reset to clear */
|
|
if ( ( rc = ena_reset_wait ( ena, 0 ) ) != 0 )
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* Admin queue
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Set queue base address
|
|
*
|
|
* @v ena ENA device
|
|
* @v offset Register offset
|
|
* @v address Base address
|
|
*/
|
|
static inline void ena_set_base ( struct ena_nic *ena, unsigned int offset,
|
|
void *base ) {
|
|
physaddr_t phys = virt_to_bus ( base );
|
|
|
|
/* Program base address registers */
|
|
writel ( ( phys & 0xffffffffUL ),
|
|
( ena->regs + offset + ENA_BASE_LO ) );
|
|
if ( sizeof ( phys ) > sizeof ( uint32_t ) ) {
|
|
writel ( ( ( ( uint64_t ) phys ) >> 32 ),
|
|
( ena->regs + offset + ENA_BASE_HI ) );
|
|
} else {
|
|
writel ( 0, ( ena->regs + offset + ENA_BASE_HI ) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set queue capabilities
|
|
*
|
|
* @v ena ENA device
|
|
* @v offset Register offset
|
|
* @v count Number of entries
|
|
* @v size Size of each entry
|
|
*/
|
|
static inline __attribute__ (( always_inline )) void
|
|
ena_set_caps ( struct ena_nic *ena, unsigned int offset, unsigned int count,
|
|
size_t size ) {
|
|
|
|
/* Program capabilities register */
|
|
writel ( ENA_CAPS ( count, size ), ( ena->regs + offset ) );
|
|
}
|
|
|
|
/**
|
|
* Clear queue capabilities
|
|
*
|
|
* @v ena ENA device
|
|
* @v offset Register offset
|
|
*/
|
|
static inline __attribute__ (( always_inline )) void
|
|
ena_clear_caps ( struct ena_nic *ena, unsigned int offset ) {
|
|
|
|
/* Clear capabilities register */
|
|
writel ( 0, ( ena->regs + offset ) );
|
|
}
|
|
|
|
/**
|
|
* Create admin queues
|
|
*
|
|
* @v ena ENA device
|
|
* @ret rc Return status code
|
|
*/
|
|
static int ena_create_admin ( struct ena_nic *ena ) {
|
|
size_t aq_len = ( ENA_AQ_COUNT * sizeof ( ena->aq.req[0] ) );
|
|
size_t acq_len = ( ENA_ACQ_COUNT * sizeof ( ena->acq.rsp[0] ) );
|
|
int rc;
|
|
|
|
/* Allocate admin completion queue */
|
|
ena->acq.rsp = malloc_phys ( acq_len, acq_len );
|
|
if ( ! ena->acq.rsp ) {
|
|
rc = -ENOMEM;
|
|
goto err_alloc_acq;
|
|
}
|
|
memset ( ena->acq.rsp, 0, acq_len );
|
|
|
|
/* Allocate admin queue */
|
|
ena->aq.req = malloc_phys ( aq_len, aq_len );
|
|
if ( ! ena->aq.req ) {
|
|
rc = -ENOMEM;
|
|
goto err_alloc_aq;
|
|
}
|
|
memset ( ena->aq.req, 0, aq_len );
|
|
|
|
/* Program queue addresses and capabilities */
|
|
ena_set_base ( ena, ENA_ACQ_BASE, ena->acq.rsp );
|
|
ena_set_caps ( ena, ENA_ACQ_CAPS, ENA_ACQ_COUNT,
|
|
sizeof ( ena->acq.rsp[0] ) );
|
|
ena_set_base ( ena, ENA_AQ_BASE, ena->aq.req );
|
|
ena_set_caps ( ena, ENA_AQ_CAPS, ENA_AQ_COUNT,
|
|
sizeof ( ena->aq.req[0] ) );
|
|
|
|
DBGC ( ena, "ENA %p AQ [%08lx,%08lx) ACQ [%08lx,%08lx)\n",
|
|
ena, virt_to_phys ( ena->aq.req ),
|
|
( virt_to_phys ( ena->aq.req ) + aq_len ),
|
|
virt_to_phys ( ena->acq.rsp ),
|
|
( virt_to_phys ( ena->acq.rsp ) + acq_len ) );
|
|
return 0;
|
|
|
|
ena_clear_caps ( ena, ENA_AQ_CAPS );
|
|
ena_clear_caps ( ena, ENA_ACQ_CAPS );
|
|
free_phys ( ena->aq.req, aq_len );
|
|
err_alloc_aq:
|
|
free_phys ( ena->acq.rsp, acq_len );
|
|
err_alloc_acq:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Destroy admin queues
|
|
*
|
|
* @v ena ENA device
|
|
*/
|
|
static void ena_destroy_admin ( struct ena_nic *ena ) {
|
|
size_t aq_len = ( ENA_AQ_COUNT * sizeof ( ena->aq.req[0] ) );
|
|
size_t acq_len = ( ENA_ACQ_COUNT * sizeof ( ena->acq.rsp[0] ) );
|
|
|
|
/* Clear queue capabilities */
|
|
ena_clear_caps ( ena, ENA_AQ_CAPS );
|
|
ena_clear_caps ( ena, ENA_ACQ_CAPS );
|
|
wmb();
|
|
|
|
/* Free queues */
|
|
free_phys ( ena->aq.req, aq_len );
|
|
free_phys ( ena->acq.rsp, acq_len );
|
|
DBGC ( ena, "ENA %p AQ and ACQ destroyed\n", ena );
|
|
}
|
|
|
|
/**
|
|
* Get next available admin queue request
|
|
*
|
|
* @v ena ENA device
|
|
* @ret req Admin queue request
|
|
*/
|
|
static union ena_aq_req * ena_admin_req ( struct ena_nic *ena ) {
|
|
union ena_aq_req *req;
|
|
unsigned int index;
|
|
|
|
/* Get next request */
|
|
index = ( ena->aq.prod % ENA_AQ_COUNT );
|
|
req = &ena->aq.req[index];
|
|
|
|
/* Initialise request */
|
|
memset ( ( ( ( void * ) req ) + sizeof ( req->header ) ), 0,
|
|
( sizeof ( *req ) - sizeof ( req->header ) ) );
|
|
req->header.id = ena->aq.prod;
|
|
|
|
/* Increment producer counter */
|
|
ena->aq.prod++;
|
|
|
|
return req;
|
|
}
|
|
|
|
/**
|
|
* Issue admin queue request
|
|
*
|
|
* @v ena ENA device
|
|
* @v req Admin queue request
|
|
* @v rsp Admin queue response to fill in
|
|
* @ret rc Return status code
|
|
*/
|
|
static int ena_admin ( struct ena_nic *ena, union ena_aq_req *req,
|
|
union ena_acq_rsp **rsp ) {
|
|
unsigned int index;
|
|
unsigned int i;
|
|
int rc;
|
|
|
|
/* Locate response */
|
|
index = ( ena->acq.cons % ENA_ACQ_COUNT );
|
|
*rsp = &ena->acq.rsp[index];
|
|
|
|
/* Mark request as ready */
|
|
req->header.flags ^= ENA_AQ_PHASE;
|
|
wmb();
|
|
DBGC2 ( ena, "ENA %p admin request %#x:\n",
|
|
ena, le16_to_cpu ( req->header.id ) );
|
|
DBGC2_HDA ( ena, virt_to_phys ( req ), req, sizeof ( *req ) );
|
|
|
|
/* Ring doorbell */
|
|
writel ( ena->aq.prod, ( ena->regs + ENA_AQ_DB ) );
|
|
|
|
/* Wait for response */
|
|
for ( i = 0 ; i < ENA_ADMIN_MAX_WAIT_MS ; i++ ) {
|
|
|
|
/* Check for response */
|
|
if ( ( (*rsp)->header.flags ^ ena->acq.phase ) & ENA_ACQ_PHASE){
|
|
mdelay ( 1 );
|
|
continue;
|
|
}
|
|
DBGC2 ( ena, "ENA %p admin response %#x:\n",
|
|
ena, le16_to_cpu ( (*rsp)->header.id ) );
|
|
DBGC2_HDA ( ena, virt_to_phys ( *rsp ), *rsp, sizeof ( **rsp ));
|
|
|
|
/* Increment consumer counter */
|
|
ena->acq.cons++;
|
|
if ( ( ena->acq.cons % ENA_ACQ_COUNT ) == 0 )
|
|
ena->acq.phase ^= ENA_ACQ_PHASE;
|
|
|
|
/* Check command identifier */
|
|
if ( (*rsp)->header.id != req->header.id ) {
|
|
DBGC ( ena, "ENA %p admin response %#x mismatch:\n",
|
|
ena, le16_to_cpu ( (*rsp)->header.id ) );
|
|
rc = -EILSEQ;
|
|
goto err;
|
|
}
|
|
|
|
/* Check status */
|
|
if ( (*rsp)->header.status != 0 ) {
|
|
DBGC ( ena, "ENA %p admin response %#x status %d:\n",
|
|
ena, le16_to_cpu ( (*rsp)->header.id ),
|
|
(*rsp)->header.status );
|
|
rc = -EIO;
|
|
goto err;
|
|
}
|
|
|
|
/* Success */
|
|
return 0;
|
|
}
|
|
|
|
rc = -ETIMEDOUT;
|
|
DBGC ( ena, "ENA %p timed out waiting for admin request %#x:\n",
|
|
ena, le16_to_cpu ( req->header.id ) );
|
|
err:
|
|
DBGC_HDA ( ena, virt_to_phys ( req ), req, sizeof ( *req ) );
|
|
DBGC_HDA ( ena, virt_to_phys ( *rsp ), *rsp, sizeof ( **rsp ) );
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Set async event notification queue config
|
|
*
|
|
* @v ena ENA device
|
|
* @v enabled Bitmask of the groups to enable
|
|
* @ret rc Return status code
|
|
*/
|
|
static int ena_set_aenq_config ( struct ena_nic *ena, uint32_t enabled ) {
|
|
union ena_aq_req *req;
|
|
union ena_acq_rsp *rsp;
|
|
union ena_feature *feature;
|
|
int rc;
|
|
|
|
/* Construct request */
|
|
req = ena_admin_req ( ena );
|
|
req->header.opcode = ENA_SET_FEATURE;
|
|
req->set_feature.id = ENA_AENQ_CONFIG;
|
|
feature = &req->set_feature.feature;
|
|
feature->aenq.enabled = cpu_to_le32 ( enabled );
|
|
|
|
/* Issue request */
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Create async event notification queue
|
|
*
|
|
* @v ena ENA device
|
|
* @ret rc Return status code
|
|
*/
|
|
static int ena_create_async ( struct ena_nic *ena ) {
|
|
size_t aenq_len = ( ENA_AENQ_COUNT * sizeof ( ena->aenq.evt[0] ) );
|
|
int rc;
|
|
|
|
/* Allocate async event notification queue */
|
|
ena->aenq.evt = malloc_phys ( aenq_len, aenq_len );
|
|
if ( ! ena->aenq.evt ) {
|
|
rc = -ENOMEM;
|
|
goto err_alloc_aenq;
|
|
}
|
|
memset ( ena->aenq.evt, 0, aenq_len );
|
|
|
|
/* Program queue address and capabilities */
|
|
ena_set_base ( ena, ENA_AENQ_BASE, ena->aenq.evt );
|
|
ena_set_caps ( ena, ENA_AENQ_CAPS, ENA_AENQ_COUNT,
|
|
sizeof ( ena->aenq.evt[0] ) );
|
|
|
|
DBGC ( ena, "ENA %p AENQ [%08lx,%08lx)\n",
|
|
ena, virt_to_phys ( ena->aenq.evt ),
|
|
( virt_to_phys ( ena->aenq.evt ) + aenq_len ) );
|
|
|
|
/* Disable all events */
|
|
if ( ( rc = ena_set_aenq_config ( ena, 0 ) ) != 0 )
|
|
goto err_set_aenq_config;
|
|
|
|
return 0;
|
|
|
|
err_set_aenq_config:
|
|
ena_clear_caps ( ena, ENA_AENQ_CAPS );
|
|
free_phys ( ena->aenq.evt, aenq_len );
|
|
err_alloc_aenq:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Destroy async event notification queue
|
|
*
|
|
* @v ena ENA device
|
|
*/
|
|
static void ena_destroy_async ( struct ena_nic *ena ) {
|
|
size_t aenq_len = ( ENA_AENQ_COUNT * sizeof ( ena->aenq.evt[0] ) );
|
|
|
|
/* Clear queue capabilities */
|
|
ena_clear_caps ( ena, ENA_AENQ_CAPS );
|
|
wmb();
|
|
|
|
/* Free queue */
|
|
free_phys ( ena->aenq.evt, aenq_len );
|
|
DBGC ( ena, "ENA %p AENQ destroyed\n", ena );
|
|
}
|
|
|
|
/**
|
|
* Create submission queue
|
|
*
|
|
* @v ena ENA device
|
|
* @v sq Submission queue
|
|
* @v cq Corresponding completion queue
|
|
* @ret rc Return status code
|
|
*/
|
|
static int ena_create_sq ( struct ena_nic *ena, struct ena_sq *sq,
|
|
struct ena_cq *cq ) {
|
|
union ena_aq_req *req;
|
|
union ena_acq_rsp *rsp;
|
|
unsigned int i;
|
|
size_t llqe;
|
|
int rc;
|
|
|
|
/* Allocate submission queue entries */
|
|
sq->sqe.raw = malloc_phys ( sq->len, ENA_ALIGN );
|
|
if ( ! sq->sqe.raw ) {
|
|
rc = -ENOMEM;
|
|
goto err_alloc;
|
|
}
|
|
memset ( sq->sqe.raw, 0, sq->len );
|
|
|
|
/* Construct request */
|
|
req = ena_admin_req ( ena );
|
|
req->header.opcode = ENA_CREATE_SQ;
|
|
req->create_sq.direction = sq->direction;
|
|
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 ) );
|
|
|
|
/* Issue request */
|
|
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;
|
|
if ( sq->fill > cq->actual )
|
|
sq->fill = cq->actual;
|
|
|
|
/* Initialise buffer ID ring */
|
|
for ( i = 0 ; i < sq->count ; i++ )
|
|
sq->ids[i] = i;
|
|
|
|
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;
|
|
|
|
err_admin:
|
|
free_phys ( sq->sqe.raw, sq->len );
|
|
err_alloc:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Destroy submission queue
|
|
*
|
|
* @v ena ENA device
|
|
* @v sq Submission queue
|
|
* @ret rc Return status code
|
|
*/
|
|
static int ena_destroy_sq ( struct ena_nic *ena, struct ena_sq *sq ) {
|
|
union ena_aq_req *req;
|
|
union ena_acq_rsp *rsp;
|
|
int rc;
|
|
|
|
/* Construct request */
|
|
req = ena_admin_req ( ena );
|
|
req->header.opcode = ENA_DESTROY_SQ;
|
|
req->destroy_sq.id = cpu_to_le16 ( sq->id );
|
|
req->destroy_sq.direction = sq->direction;
|
|
|
|
/* Issue request */
|
|
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 );
|
|
|
|
DBGC ( ena, "ENA %p %s SQ%d destroyed\n",
|
|
ena, ena_direction ( sq->direction ), sq->id );
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Create completion queue
|
|
*
|
|
* @v ena ENA device
|
|
* @v cq Completion queue
|
|
* @ret rc Return status code
|
|
*/
|
|
static int ena_create_cq ( struct ena_nic *ena, struct ena_cq *cq ) {
|
|
union ena_aq_req *req;
|
|
union ena_acq_rsp *rsp;
|
|
int rc;
|
|
|
|
/* Allocate completion queue entries */
|
|
cq->cqe.raw = malloc_phys ( cq->len, ENA_ALIGN );
|
|
if ( ! cq->cqe.raw ) {
|
|
rc = -ENOMEM;
|
|
goto err_alloc;
|
|
}
|
|
memset ( cq->cqe.raw, 0, cq->len );
|
|
|
|
/* Construct request */
|
|
req = ena_admin_req ( ena );
|
|
req->header.opcode = ENA_CREATE_CQ;
|
|
req->create_cq.size = cq->size;
|
|
req->create_cq.count = cpu_to_le16 ( cq->requested );
|
|
req->create_cq.vector = cpu_to_le32 ( ENA_MSIX_NONE );
|
|
req->create_cq.address = cpu_to_le64 ( virt_to_bus ( cq->cqe.raw ) );
|
|
|
|
/* Issue request */
|
|
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 );
|
|
cq->actual = le16_to_cpu ( rsp->create_cq.count );
|
|
cq->doorbell = le32_to_cpu ( rsp->create_cq.doorbell );
|
|
cq->mask = ( cq->actual - 1 );
|
|
if ( cq->actual != cq->requested ) {
|
|
DBGC ( ena, "ENA %p CQ%d requested %d actual %d\n",
|
|
ena, cq->id, cq->requested, cq->actual );
|
|
}
|
|
|
|
/* Reset consumer counter and phase */
|
|
cq->cons = 0;
|
|
cq->phase = ENA_CQE_PHASE;
|
|
|
|
DBGC ( ena, "ENA %p CQ%d at [%08lx,%08lx) db +%04x\n",
|
|
ena, cq->id, virt_to_phys ( cq->cqe.raw ),
|
|
( virt_to_phys ( cq->cqe.raw ) + cq->len ), cq->doorbell );
|
|
return 0;
|
|
|
|
err_admin:
|
|
free_phys ( cq->cqe.raw, cq->len );
|
|
err_alloc:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Destroy completion queue
|
|
*
|
|
* @v ena ENA device
|
|
* @v cq Completion queue
|
|
* @ret rc Return status code
|
|
*/
|
|
static int ena_destroy_cq ( struct ena_nic *ena, struct ena_cq *cq ) {
|
|
union ena_aq_req *req;
|
|
union ena_acq_rsp *rsp;
|
|
int rc;
|
|
|
|
/* Construct request */
|
|
req = ena_admin_req ( ena );
|
|
req->header.opcode = ENA_DESTROY_CQ;
|
|
req->destroy_cq.id = cpu_to_le16 ( cq->id );
|
|
|
|
/* Issue request */
|
|
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 );
|
|
|
|
DBGC ( ena, "ENA %p CQ%d destroyed\n", ena, cq->id );
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Create queue pair
|
|
*
|
|
* @v ena ENA device
|
|
* @v qp Queue pair
|
|
* @ret rc Return status code
|
|
*/
|
|
static int ena_create_qp ( struct ena_nic *ena, struct ena_qp *qp ) {
|
|
int rc;
|
|
|
|
/* Create completion queue */
|
|
if ( ( rc = ena_create_cq ( ena, &qp->cq ) ) != 0 )
|
|
goto err_create_cq;
|
|
|
|
/* Create submission queue */
|
|
if ( ( rc = ena_create_sq ( ena, &qp->sq, &qp->cq ) ) != 0 )
|
|
goto err_create_sq;
|
|
|
|
return 0;
|
|
|
|
ena_destroy_sq ( ena, &qp->sq );
|
|
err_create_sq:
|
|
ena_destroy_cq ( ena, &qp->cq );
|
|
err_create_cq:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Destroy queue pair
|
|
*
|
|
* @v ena ENA device
|
|
* @v qp Queue pair
|
|
* @ret rc Return status code
|
|
*/
|
|
static int ena_destroy_qp ( struct ena_nic *ena, struct ena_qp *qp ) {
|
|
|
|
/* Destroy submission queue */
|
|
ena_destroy_sq ( ena, &qp->sq );
|
|
|
|
/* Destroy completion queue */
|
|
ena_destroy_cq ( ena, &qp->cq );
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Get device attributes
|
|
*
|
|
* @v netdev Network device
|
|
* @ret rc Return status code
|
|
*/
|
|
static int ena_get_device_attributes ( struct net_device *netdev ) {
|
|
struct ena_nic *ena = netdev->priv;
|
|
union ena_aq_req *req;
|
|
union ena_acq_rsp *rsp;
|
|
union ena_feature *feature;
|
|
int rc;
|
|
|
|
/* Construct request */
|
|
req = ena_admin_req ( ena );
|
|
req->header.opcode = ENA_GET_FEATURE;
|
|
req->get_feature.id = ENA_DEVICE_ATTRIBUTES;
|
|
|
|
/* Issue request */
|
|
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 features %#08x\n",
|
|
ena, eth_ntoa ( netdev->hw_addr ), netdev->max_pkt_len,
|
|
ena->features );
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Set host attributes
|
|
*
|
|
* @v ena ENA device
|
|
* @ret rc Return status code
|
|
*/
|
|
static int ena_set_host_attributes ( struct ena_nic *ena ) {
|
|
union ena_aq_req *req;
|
|
union ena_acq_rsp *rsp;
|
|
union ena_feature *feature;
|
|
int rc;
|
|
|
|
/* Construct request */
|
|
req = ena_admin_req ( ena );
|
|
req->header.opcode = ENA_SET_FEATURE;
|
|
req->set_feature.id = ENA_HOST_ATTRIBUTES;
|
|
feature = &req->set_feature.feature;
|
|
feature->host.info = cpu_to_le64 ( virt_to_bus ( ena->info ) );
|
|
|
|
/* Issue request */
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Get statistics (for debugging)
|
|
*
|
|
* @v ena ENA device
|
|
* @ret rc Return status code
|
|
*/
|
|
static int ena_get_stats ( struct ena_nic *ena ) {
|
|
union ena_aq_req *req;
|
|
union ena_acq_rsp *rsp;
|
|
struct ena_get_stats_rsp *stats;
|
|
int rc;
|
|
|
|
/* Do nothing unless debug messages are enabled */
|
|
if ( ! DBG_LOG )
|
|
return 0;
|
|
|
|
/* Construct request */
|
|
req = ena_admin_req ( ena );
|
|
req->header.opcode = ENA_GET_STATS;
|
|
req->get_stats.type = ENA_STATS_TYPE_BASIC;
|
|
req->get_stats.scope = ENA_STATS_SCOPE_ETH;
|
|
req->get_stats.device = ENA_DEVICE_MINE;
|
|
|
|
/* Issue request */
|
|
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;
|
|
DBGC ( ena, "ENA %p TX bytes %#llx packets %#llx\n", ena,
|
|
( ( unsigned long long ) le64_to_cpu ( stats->tx_bytes ) ),
|
|
( ( unsigned long long ) le64_to_cpu ( stats->tx_packets ) ) );
|
|
DBGC ( ena, "ENA %p RX bytes %#llx packets %#llx drops %#llx\n", ena,
|
|
( ( unsigned long long ) le64_to_cpu ( stats->rx_bytes ) ),
|
|
( ( unsigned long long ) le64_to_cpu ( stats->rx_packets ) ),
|
|
( ( unsigned long long ) le64_to_cpu ( stats->rx_drops ) ) );
|
|
|
|
return 0;
|
|
}
|
|
|
|
/******************************************************************************
|
|
*
|
|
* Network device interface
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* Refill receive queue
|
|
*
|
|
* @v netdev Network device
|
|
*/
|
|
static void ena_refill_rx ( struct net_device *netdev ) {
|
|
struct ena_nic *ena = netdev->priv;
|
|
struct io_buffer *iobuf;
|
|
struct ena_rx_sqe *sqe;
|
|
physaddr_t address;
|
|
size_t len = netdev->max_pkt_len;
|
|
unsigned int refilled = 0;
|
|
unsigned int index;
|
|
unsigned int id;
|
|
|
|
/* Refill queue */
|
|
while ( ( ena->rx.sq.prod - ena->rx.cq.cons ) < ena->rx.sq.fill ) {
|
|
|
|
/* Allocate I/O buffer */
|
|
iobuf = alloc_iob ( len );
|
|
if ( ! iobuf ) {
|
|
/* Wait for next refill */
|
|
break;
|
|
}
|
|
|
|
/* Get next submission queue entry and buffer ID */
|
|
index = ( ena->rx.sq.prod % ENA_RX_COUNT );
|
|
sqe = &ena->rx.sq.sqe.rx[index];
|
|
id = ena->rx_ids[index];
|
|
|
|
/* Construct submission queue entry */
|
|
address = virt_to_bus ( iobuf->data );
|
|
sqe->len = cpu_to_le16 ( len );
|
|
sqe->id = cpu_to_le16 ( id );
|
|
sqe->address = cpu_to_le64 ( address );
|
|
wmb();
|
|
sqe->flags = ( ENA_SQE_FIRST | ENA_SQE_LAST | ENA_SQE_CPL |
|
|
ena->rx.sq.phase );
|
|
|
|
/* Increment producer counter */
|
|
ena->rx.sq.prod++;
|
|
if ( ( ena->rx.sq.prod % ENA_RX_COUNT ) == 0 )
|
|
ena->rx.sq.phase ^= ENA_SQE_PHASE;
|
|
|
|
/* Record I/O buffer */
|
|
assert ( ena->rx_iobuf[id] == NULL );
|
|
ena->rx_iobuf[id] = iobuf;
|
|
|
|
DBGC2 ( ena, "ENA %p RX %d at [%08llx,%08llx)\n", ena, id,
|
|
( ( unsigned long long ) address ),
|
|
( ( unsigned long long ) address + len ) );
|
|
refilled++;
|
|
}
|
|
|
|
/* Ring doorbell, if applicable */
|
|
if ( refilled ) {
|
|
wmb();
|
|
writel ( ena->rx.sq.prod, ( ena->regs + ena->rx.sq.doorbell ) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Discard unused receive I/O buffers
|
|
*
|
|
* @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++ ) {
|
|
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 );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Open network device
|
|
*
|
|
* @v netdev Network device
|
|
* @ret rc Return status code
|
|
*/
|
|
static int ena_open ( struct net_device *netdev ) {
|
|
struct ena_nic *ena = netdev->priv;
|
|
int rc;
|
|
|
|
/* Create transmit queue pair */
|
|
if ( ( rc = ena_create_qp ( ena, &ena->tx ) ) != 0 )
|
|
goto err_create_tx;
|
|
|
|
/* Create receive queue pair */
|
|
if ( ( rc = ena_create_qp ( ena, &ena->rx ) ) != 0 )
|
|
goto err_create_rx;
|
|
|
|
/* Refill receive queue */
|
|
ena_refill_rx ( netdev );
|
|
|
|
return 0;
|
|
|
|
ena_destroy_qp ( ena, &ena->rx );
|
|
err_create_rx:
|
|
ena_destroy_qp ( ena, &ena->tx );
|
|
err_create_tx:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Close network device
|
|
*
|
|
* @v netdev Network device
|
|
*/
|
|
static void ena_close ( struct net_device *netdev ) {
|
|
struct ena_nic *ena = netdev->priv;
|
|
|
|
/* Dump statistics (for debugging) */
|
|
ena_get_stats ( ena );
|
|
|
|
/* Destroy receive queue pair */
|
|
ena_destroy_qp ( ena, &ena->rx );
|
|
|
|
/* Discard any unused receive buffers */
|
|
ena_empty_rx ( ena );
|
|
|
|
/* Destroy transmit queue pair */
|
|
ena_destroy_qp ( ena, &ena->tx );
|
|
|
|
/* Cancel any uncompleted transmit buffers */
|
|
ena_cancel_tx ( netdev );
|
|
}
|
|
|
|
/**
|
|
* Transmit packet
|
|
*
|
|
* @v netdev Network device
|
|
* @v iobuf I/O buffer
|
|
* @ret rc Return status code
|
|
*/
|
|
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 */
|
|
if ( ( ena->tx.sq.prod - ena->tx.cq.cons ) >= ena->tx.sq.fill ) {
|
|
DBGC ( ena, "ENA %p out of transmit descriptors\n", ena );
|
|
return -ENOBUFS;
|
|
}
|
|
index = ( ena->tx.sq.prod % ENA_TX_COUNT );
|
|
sqe = &ena->tx.sq.sqe.tx[index];
|
|
id = ena->tx_ids[index];
|
|
|
|
/* 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 = 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++ );
|
|
}
|
|
|
|
/* Increment producer counter */
|
|
ena->tx.sq.prod++;
|
|
if ( ( ena->tx.sq.prod % ENA_TX_COUNT ) == 0 )
|
|
ena->tx.sq.phase ^= ENA_SQE_PHASE;
|
|
|
|
/* Record I/O buffer */
|
|
assert ( ena->tx_iobuf[id] == NULL );
|
|
ena->tx_iobuf[id] = iobuf;
|
|
|
|
/* Ring doorbell */
|
|
writel ( ena->tx.sq.prod, ( ena->regs + ena->tx.sq.doorbell ) );
|
|
|
|
DBGC2 ( ena, "ENA %p TX %d at [%08llx,%08llx)\n", ena, id,
|
|
( ( unsigned long long ) address ),
|
|
( ( unsigned long long ) address + len ) );
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Poll for completed transmissions
|
|
*
|
|
* @v netdev Network device
|
|
*/
|
|
static void ena_poll_tx ( struct net_device *netdev ) {
|
|
struct ena_nic *ena = netdev->priv;
|
|
struct ena_tx_cqe *cqe;
|
|
struct io_buffer *iobuf;
|
|
unsigned int index;
|
|
unsigned int id;
|
|
|
|
/* Check for completed packets */
|
|
while ( ena->tx.cq.cons != ena->tx.sq.prod ) {
|
|
|
|
/* Get next completion queue entry */
|
|
index = ( ena->tx.cq.cons & ena->tx.cq.mask );
|
|
cqe = &ena->tx.cq.cqe.tx[index];
|
|
|
|
/* Stop if completion queue entry is empty */
|
|
if ( ( cqe->flags ^ ena->tx.cq.phase ) & ENA_CQE_PHASE )
|
|
return;
|
|
|
|
/* Increment consumer counter */
|
|
ena->tx.cq.cons++;
|
|
if ( ! ( ena->tx.cq.cons & ena->tx.cq.mask ) )
|
|
ena->tx.cq.phase ^= ENA_CQE_PHASE;
|
|
|
|
/* Identify and free buffer ID */
|
|
id = ENA_TX_CQE_ID ( le16_to_cpu ( cqe->id ) );
|
|
ena->tx_ids[index] = id;
|
|
|
|
/* Identify I/O buffer */
|
|
iobuf = ena->tx_iobuf[id];
|
|
assert ( iobuf != NULL );
|
|
ena->tx_iobuf[id] = NULL;
|
|
|
|
/* Complete transmit */
|
|
DBGC2 ( ena, "ENA %p TX %d complete\n", ena, id );
|
|
netdev_tx_complete ( netdev, iobuf );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Poll for received packets
|
|
*
|
|
* @v netdev Network device
|
|
*/
|
|
static void ena_poll_rx ( struct net_device *netdev ) {
|
|
struct ena_nic *ena = netdev->priv;
|
|
struct ena_rx_cqe *cqe;
|
|
struct io_buffer *iobuf;
|
|
unsigned int index;
|
|
unsigned int id;
|
|
size_t len;
|
|
|
|
/* Check for received packets */
|
|
while ( ena->rx.cq.cons != ena->rx.sq.prod ) {
|
|
|
|
/* Get next completion queue entry */
|
|
index = ( ena->rx.cq.cons & ena->rx.cq.mask );
|
|
cqe = &ena->rx.cq.cqe.rx[index];
|
|
|
|
/* Stop if completion queue entry is empty */
|
|
if ( ( cqe->flags ^ ena->rx.cq.phase ) & ENA_CQE_PHASE )
|
|
return;
|
|
|
|
/* Increment consumer counter */
|
|
ena->rx.cq.cons++;
|
|
if ( ! ( ena->rx.cq.cons & ena->rx.cq.mask ) )
|
|
ena->rx.cq.phase ^= ENA_CQE_PHASE;
|
|
|
|
/* Identify and free buffer ID */
|
|
id = le16_to_cpu ( cqe->id );
|
|
ena->rx_ids[index] = id;
|
|
|
|
/* Populate I/O buffer */
|
|
iobuf = ena->rx_iobuf[id];
|
|
assert ( iobuf != NULL );
|
|
ena->rx_iobuf[id] = NULL;
|
|
len = le16_to_cpu ( cqe->len );
|
|
iob_put ( iobuf, len );
|
|
|
|
/* Hand off to network stack */
|
|
DBGC2 ( ena, "ENA %p RX %d complete (length %zd)\n",
|
|
ena, id, len );
|
|
netdev_rx ( netdev, iobuf );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Poll for completed and received packets
|
|
*
|
|
* @v netdev Network device
|
|
*/
|
|
static void ena_poll ( struct net_device *netdev ) {
|
|
|
|
/* Poll for transmit completions */
|
|
ena_poll_tx ( netdev );
|
|
|
|
/* Poll for receive completions */
|
|
ena_poll_rx ( netdev );
|
|
|
|
/* Refill receive ring */
|
|
ena_refill_rx ( netdev );
|
|
}
|
|
|
|
/** ENA network device operations */
|
|
static struct net_device_operations ena_operations = {
|
|
.open = ena_open,
|
|
.close = ena_close,
|
|
.transmit = ena_transmit,
|
|
.poll = ena_poll,
|
|
};
|
|
|
|
/******************************************************************************
|
|
*
|
|
* PCI interface
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/**
|
|
* 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
|
|
* address to the ENA device. The device is the only device behind
|
|
* 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_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 ) ) {
|
|
DBGC ( ena, "ENA %p at " PCI_FMT " may not be only device "
|
|
"on bus\n", ena, PCI_ARGS ( pci ) );
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/**
|
|
* Probe PCI device
|
|
*
|
|
* @v pci PCI device
|
|
* @ret rc Return status code
|
|
*/
|
|
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 */
|
|
netdev = alloc_etherdev ( sizeof ( *ena ) );
|
|
if ( ! netdev ) {
|
|
rc = -ENOMEM;
|
|
goto err_alloc;
|
|
}
|
|
netdev_init ( netdev, &ena_operations );
|
|
ena = netdev->priv;
|
|
pci_set_drvdata ( pci, netdev );
|
|
netdev->dev = &pci->dev;
|
|
memset ( ena, 0, sizeof ( *ena ) );
|
|
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,
|
|
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,
|
|
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 ( ( rc = ena_membases ( ena, pci, &prefmembase,
|
|
&prefmemsize ) ) != 0 ) {
|
|
goto err_membases;
|
|
}
|
|
|
|
/* Map registers */
|
|
ena->regs = pci_ioremap ( pci, pci->membase, ENA_REGS_SIZE );
|
|
if ( ! ena->regs ) {
|
|
rc = -ENODEV;
|
|
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 */
|
|
info = malloc_phys ( PAGE_SIZE, PAGE_SIZE );
|
|
if ( ! info ) {
|
|
rc = -ENOMEM;
|
|
goto err_info;
|
|
}
|
|
ena->info = info;
|
|
memset ( info, 0, PAGE_SIZE );
|
|
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",
|
|
product_version );
|
|
info->version = cpu_to_le32 ( ENA_HOST_INFO_VERSION_WTF );
|
|
info->spec = cpu_to_le16 ( ENA_HOST_INFO_SPEC_2_0 );
|
|
info->busdevfn = cpu_to_le16 ( pci->busdevfn );
|
|
DBGC2 ( ena, "ENA %p host info:\n", ena );
|
|
DBGC2_HDA ( ena, virt_to_phys ( info ), info, sizeof ( *info ) );
|
|
|
|
/* Reset the NIC */
|
|
if ( ( rc = ena_reset ( ena ) ) != 0 )
|
|
goto err_reset;
|
|
|
|
/* Create admin queues */
|
|
if ( ( rc = ena_create_admin ( ena ) ) != 0 )
|
|
goto err_create_admin;
|
|
|
|
/* Create async event notification queue */
|
|
if ( ( rc = ena_create_async ( ena ) ) != 0 )
|
|
goto err_create_async;
|
|
|
|
/* Set host attributes */
|
|
if ( ( rc = ena_set_host_attributes ( ena ) ) != 0 )
|
|
goto err_set_host_attributes;
|
|
|
|
/* Fetch MAC address */
|
|
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;
|
|
|
|
/* Mark as link up, since we have no way to test link state on
|
|
* this hardware.
|
|
*/
|
|
netdev_link_up ( netdev );
|
|
|
|
return 0;
|
|
|
|
unregister_netdev ( netdev );
|
|
err_register_netdev:
|
|
err_get_device_attributes:
|
|
err_set_host_attributes:
|
|
ena_destroy_async ( ena );
|
|
err_create_async:
|
|
ena_destroy_admin ( ena );
|
|
err_create_admin:
|
|
ena_reset ( ena );
|
|
err_reset:
|
|
free_phys ( ena->info, PAGE_SIZE );
|
|
err_info:
|
|
if ( ena->mem )
|
|
iounmap ( ena->mem );
|
|
err_mem:
|
|
iounmap ( ena->regs );
|
|
err_regs:
|
|
err_membases:
|
|
netdev_nullify ( netdev );
|
|
netdev_put ( netdev );
|
|
err_alloc:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Remove PCI device
|
|
*
|
|
* @v pci PCI device
|
|
*/
|
|
static void ena_remove ( struct pci_device *pci ) {
|
|
struct net_device *netdev = pci_get_drvdata ( pci );
|
|
struct ena_nic *ena = netdev->priv;
|
|
|
|
/* Unregister network device */
|
|
unregister_netdev ( netdev );
|
|
|
|
/* Destroy async event notification queue */
|
|
ena_destroy_async ( ena );
|
|
|
|
/* Destroy admin queues */
|
|
ena_destroy_admin ( ena );
|
|
|
|
/* Reset card */
|
|
ena_reset ( ena );
|
|
|
|
/* Free host info */
|
|
free_phys ( ena->info, PAGE_SIZE );
|
|
|
|
/* Unmap registers and on-device memory */
|
|
if ( ena->mem )
|
|
iounmap ( ena->mem );
|
|
iounmap ( ena->regs );
|
|
|
|
/* Free network device */
|
|
netdev_nullify ( netdev );
|
|
netdev_put ( netdev );
|
|
}
|
|
|
|
/** ENA PCI device IDs */
|
|
static struct pci_device_id ena_nics[] = {
|
|
PCI_ROM ( 0x1d0f, 0xec20, "ena-vf", "ENA VF", 0 ),
|
|
PCI_ROM ( 0x1d0f, 0xec21, "ena-vf-llq", "ENA VF (LLQ)", 0 ),
|
|
};
|
|
|
|
/** ENA PCI driver */
|
|
struct pci_driver ena_driver __pci_driver = {
|
|
.ids = ena_nics,
|
|
.id_count = ( sizeof ( ena_nics ) / sizeof ( ena_nics[0] ) ),
|
|
.probe = ena_probe,
|
|
.remove = ena_remove,
|
|
};
|