/*
* Copyright (C) 2016 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 <string.h>
#include <strings.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <assert.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/pciea.h>
#include <ipxe/umalloc.h>
#include "thunderx.h"
#include "thunderxcfg.h"
/** @file
*
* Cavium ThunderX Ethernet driver
*
*/
/** List of BGX Ethernet interfaces */
static LIST_HEAD ( txnic_bgxs );
/** List of physical functions */
static LIST_HEAD ( txnic_pfs );
/** Debug colour for physical function and BGX messages */
#define TXNICCOL(x) ( &txnic_pfs + (x)->node )
/** Board configuration protocol */
static EFI_THUNDER_CONFIG_PROTOCOL *txcfg;
EFI_REQUEST_PROTOCOL ( EFI_THUNDER_CONFIG_PROTOCOL, &txcfg );
/******************************************************************************
*
* Diagnostics
*
******************************************************************************
*/
/**
* Show virtual NIC diagnostics (for debugging)
*
* @v vnic Virtual NIC
*/
static __attribute__ (( unused )) void txnic_diag ( struct txnic *vnic ) {
DBGC ( vnic, "TXNIC %s SQ %05zx(%05llx)/%05zx(%05llx) %08llx\n",
vnic->name,
( ( vnic->sq.prod % TXNIC_SQES ) * TXNIC_SQ_STRIDE ),
readq ( vnic->regs + TXNIC_QS_SQ_TAIL(0) ),
( ( vnic->sq.cons % TXNIC_SQES ) * TXNIC_SQ_STRIDE ),
readq ( vnic->regs + TXNIC_QS_SQ_HEAD(0) ),
readq ( vnic->regs + TXNIC_QS_SQ_STATUS(0) ) );
DBGC ( vnic, "TXNIC %s RQ %05zx(%05llx)/%05zx(%05llx) %016llx\n",
vnic->name,
( ( vnic->rq.prod % TXNIC_RQES ) * TXNIC_RQ_STRIDE ),
readq ( vnic->regs + TXNIC_QS_RBDR_TAIL(0) ),
( ( vnic->rq.cons % TXNIC_RQES ) * TXNIC_RQ_STRIDE ),
readq ( vnic->regs + TXNIC_QS_RBDR_HEAD(0) ),
readq ( vnic->regs + TXNIC_QS_RBDR_STATUS0(0) ) );
DBGC ( vnic, "TXNIC %s CQ xxxxx(%05llx)/%05x(%05llx) %08llx:%08llx\n",
vnic->name, readq ( vnic->regs + TXNIC_QS_CQ_TAIL(0) ),
( ( vnic->cq.cons % TXNIC_CQES ) * TXNIC_CQ_STRIDE ),
readq ( vnic->regs + TXNIC_QS_CQ_HEAD(0) ),
readq ( vnic->regs + TXNIC_QS_CQ_STATUS(0) ),
readq ( vnic->regs + TXNIC_QS_CQ_STATUS2(0) ) );
}
/******************************************************************************
*
* Send queue
*
******************************************************************************
*/
/**
* Create send queue
*
* @v vnic Virtual NIC
* @ret rc Return status code
*/
static int txnic_create_sq ( struct txnic *vnic ) {
/* Reset send queue */
vnic->sq.prod = 0;
vnic->sq.cons = 0;
writeq ( TXNIC_QS_SQ_CFG_RESET, ( vnic->regs + TXNIC_QS_SQ_CFG(0) ) );
/* Configure and enable send queue */
writeq ( user_to_phys ( vnic->sq.sqe, 0 ),
( vnic->regs + TXNIC_QS_SQ_BASE(0) ) );
writeq ( ( TXNIC_QS_SQ_CFG_ENA | TXNIC_QS_SQ_CFG_QSIZE_1K ),
( vnic->regs + TXNIC_QS_SQ_CFG(0) ) );
DBGC ( vnic, "TXNIC %s SQ at [%08lx,%08lx)\n",
vnic->name, user_to_phys ( vnic->sq.sqe, 0 ),
user_to_phys ( vnic->sq.sqe, TXNIC_SQ_SIZE ) );
return 0;
}
/**
* Disable send queue
*
* @v vnic Virtual NIC
* @ret rc Return status code
*/
static int txnic_disable_sq ( struct txnic *vnic ) {
uint64_t status;
unsigned int i;
/* Disable send queue */
writeq ( 0, ( vnic->regs + TXNIC_QS_SQ_CFG(0) ) );
/* Wait for send queue to be stopped */
for ( i = 0 ; i < TXNIC_SQ_STOP_MAX_WAIT_MS ; i++ ) {
/* Check if send queue is stopped */
status = readq ( vnic->regs + TXNIC_QS_SQ_STATUS(0) );
if ( status & TXNIC_QS_SQ_STATUS_STOPPED )
return 0;
/* Delay */
mdelay ( 1 );
}
DBGC ( vnic, "TXNIC %s SQ disable timed out\n", vnic->name );
return -ETIMEDOUT;
}
/**
* Destroy send queue
*
* @v vnic Virtual NIC
*/
static void txnic_destroy_sq ( struct txnic *vnic ) {
int rc;
/* Disable send queue */
if ( ( rc = txnic_disable_sq ( vnic ) ) != 0 ) {
/* Nothing else we can do */
return;
}
/* Reset send queue */
writeq ( TXNIC_QS_SQ_CFG_RESET, ( vnic->regs + TXNIC_QS_SQ_CFG(0) ) );
}
/**
* Send packet
*
* @v vnic Virtual NIC
* @v iobuf I/O buffer
* @ret rc Return status code
*/
static int txnic_send ( struct txnic *vnic, struct io_buffer *iobuf ) {
struct txnic_sqe sqe;
unsigned int sq_idx;
size_t offset;
size_t len;
/* Get next send queue entry */
if ( ( vnic->sq.prod - vnic->sq.cons ) >= TXNIC_SQ_FILL ) {
DBGC ( vnic, "TXNIC %s out of send queue entries\n",
vnic->name );
return -ENOBUFS;
}
sq_idx = ( vnic->sq.prod++ % TXNIC_SQES );
offset = ( sq_idx * TXNIC_SQ_STRIDE );
/* Populate send descriptor */
len = iob_len ( iobuf );
memset ( &sqe, 0, sizeof ( sqe ) );
sqe.hdr.total = cpu_to_le32 ( ( len >= ETH_ZLEN ) ? len : ETH_ZLEN );
sqe.hdr.subdcnt = ( TXNIC_SQE_SUBDESCS - 1 );
sqe.hdr.flags = TXNIC_SEND_HDR_FLAGS;
sqe.gather.size = cpu_to_le16 ( len );
sqe.gather.flags = TXNIC_SEND_GATHER_FLAGS;
sqe.gather.addr = cpu_to_le64 ( virt_to_bus ( iobuf->data ) );
DBGC2 ( vnic, "TXNIC %s SQE %#03x is [%08lx,%08lx)\n",
vnic->name, sq_idx, virt_to_bus ( iobuf->data ),
( virt_to_bus ( iobuf->data ) + len ) );
/* Copy send descriptor to ring */
copy_to_user ( vnic->sq.sqe, offset, &sqe, sizeof ( sqe ) );
/* Ring doorbell */
wmb();
writeq ( TXNIC_SQE_SUBDESCS, ( vnic->regs + TXNIC_QS_SQ_DOOR(0) ) );
return 0;
}
/**
* Complete send queue entry
*
* @v vnic Virtual NIC
* @v cqe Send completion queue entry
*/
static void txnic_complete_sqe ( struct txnic *vnic,
struct txnic_cqe_send *cqe ) {
struct net_device *netdev = vnic->netdev;
unsigned int sq_idx;
unsigned int status;
/* Parse completion */
sq_idx = ( le16_to_cpu ( cqe->sqe_ptr ) / TXNIC_SQE_SUBDESCS );
status = cqe->send_status;
/* Sanity check */
assert ( sq_idx == ( vnic->sq.cons % TXNIC_SQES ) );
/* Free send queue entry */
vnic->sq.cons++;
/* Complete transmission */
if ( status ) {
DBGC ( vnic, "TXNIC %s SQE %#03x complete (status %#02x)\n",
vnic->name, sq_idx, status );
netdev_tx_complete_next_err ( netdev, -EIO );
} else {
DBGC2 ( vnic, "TXNIC %s SQE %#03x complete\n",
vnic->name, sq_idx );
netdev_tx_complete_next ( netdev );
}
}
/******************************************************************************
*
* Receive queue
*
******************************************************************************
*/
/**
* Create receive queue
*
* @v vnic Virtual NIC
* @ret rc Return status code
*/
static int txnic_create_rq ( struct txnic *vnic ) {
/* Reset receive buffer descriptor ring */
vnic->rq.prod = 0;
vnic->rq.cons = 0;
writeq ( TXNIC_QS_RBDR_CFG_RESET,
( vnic->regs + TXNIC_QS_RBDR_CFG(0) ) );
/* Configure and enable receive buffer descriptor ring */
writeq ( user_to_phys ( vnic->rq.rqe, 0 ),
( vnic->regs + TXNIC_QS_RBDR_BASE(0) ) );
writeq ( ( TXNIC_QS_RBDR_CFG_ENA | TXNIC_QS_RBDR_CFG_QSIZE_8K |
TXNIC_QS_RBDR_CFG_LINES ( TXNIC_RQE_SIZE /
TXNIC_LINE_SIZE ) ),
( vnic->regs + TXNIC_QS_RBDR_CFG(0) ) );
/* Enable receive queue */
writeq ( TXNIC_QS_RQ_CFG_ENA, ( vnic->regs + TXNIC_QS_RQ_CFG(0) ) );
DBGC ( vnic, "TXNIC %s RQ at [%08lx,%08lx)\n",
vnic->name, user_to_phys ( vnic->rq.rqe, 0 ),
user_to_phys ( vnic->rq.rqe, TXNIC_RQ_SIZE ) );
return 0;
}
/**
* Disable receive queue
*
* @v vnic Virtual NIC
* @ret rc Return status code
*/
static int txnic_disable_rq ( struct txnic *vnic ) {
uint64_t cfg;
unsigned int i;
/* Disable receive queue */
writeq ( 0, ( vnic->regs + TXNIC_QS_RQ_CFG(0) ) );
/* Wait for receive queue to be disabled */
for ( i = 0 ; i < TXNIC_RQ_DISABLE_MAX_WAIT_MS ; i++ ) {
/* Check if receive queue is disabled */
cfg = readq ( vnic->regs + TXNIC_QS_RQ_CFG(0) );
if ( ! ( cfg & TXNIC_QS_RQ_CFG_ENA ) )
return 0;
/* Delay */
mdelay ( 1 );
}
DBGC ( vnic, "TXNIC %s RQ disable timed out\n", vnic->name );
return -ETIMEDOUT;
}
/**
* Destroy receive queue
*
* @v vnic Virtual NIC
*/
static void txnic_destroy_rq ( struct txnic *vnic ) {
unsigned int i;
int rc;
/* Disable receive queue */
if ( ( rc = txnic_disable_rq ( vnic ) ) != 0 ) {
/* Leak memory; there's nothing else we can do */
return;
}
/* Disable receive buffer descriptor ring */
writeq ( 0, ( vnic->regs + TXNIC_QS_RBDR_CFG(0) ) );
/* Reset receive buffer descriptor ring */
writeq ( TXNIC_QS_RBDR_CFG_RESET,
( vnic->regs + TXNIC_QS_RBDR_CFG(0) ) );
/* Free any unused I/O buffers */
for ( i = 0 ; i < TXNIC_RQ_FILL ; i++ ) {
if ( vnic->rq.iobuf[i] )
free_iob ( vnic->rq.iobuf[i] );
vnic->rq.iobuf[i] = NULL;
}
}
/**
* Refill receive queue
*
* @v vnic Virtual NIC
*/
static void txnic_refill_rq ( struct txnic *vnic ) {
struct io_buffer *iobuf;
struct txnic_rqe rqe;
unsigned int rq_idx;
unsigned int rq_iobuf_idx;
unsigned int refilled = 0;
size_t offset;
/* Refill ring */
while ( ( vnic->rq.prod - vnic->rq.cons ) < TXNIC_RQ_FILL ) {
/* Allocate I/O buffer */
iobuf = alloc_iob ( TXNIC_RQE_SIZE );
if ( ! iobuf ) {
/* Wait for next refill */
break;
}
/* Get next receive descriptor */
rq_idx = ( vnic->rq.prod++ % TXNIC_RQES );
offset = ( rq_idx * TXNIC_RQ_STRIDE );
/* Populate receive descriptor */
rqe.rbdre.addr = cpu_to_le64 ( virt_to_bus ( iobuf->data ) );
DBGC2 ( vnic, "TXNIC %s RQE %#03x is [%08lx,%08lx)\n",
vnic->name, rq_idx, virt_to_bus ( iobuf->data ),
( virt_to_bus ( iobuf->data ) + TXNIC_RQE_SIZE ) );
/* Copy receive descriptor to ring */
copy_to_user ( vnic->rq.rqe, offset, &rqe, sizeof ( rqe ) );
refilled++;
/* Record I/O buffer */
rq_iobuf_idx = ( rq_idx % TXNIC_RQ_FILL );
assert ( vnic->rq.iobuf[rq_iobuf_idx] == NULL );
vnic->rq.iobuf[rq_iobuf_idx] = iobuf;
}
/* Ring doorbell */
wmb();
writeq ( refilled, ( vnic->regs + TXNIC_QS_RBDR_DOOR(0) ) );
}
/**
* Complete receive queue entry
*
* @v vnic Virtual NIC
* @v cqe Receive completion queue entry
*/
static void txnic_complete_rqe ( struct txnic *vnic,
struct txnic_cqe_rx *cqe ) {
struct net_device *netdev = vnic->netdev;
struct io_buffer *iobuf;
unsigned int errop;
unsigned int rq_idx;
unsigned int rq_iobuf_idx;
size_t apad_len;
size_t len;
/* Parse completion */
errop = cqe->errop;
apad_len = TXNIC_CQE_RX_APAD_LEN ( cqe->apad );
len = le16_to_cpu ( cqe->len );
/* Get next receive I/O buffer */
rq_idx = ( vnic->rq.cons++ % TXNIC_RQES );
rq_iobuf_idx = ( rq_idx % TXNIC_RQ_FILL );
iobuf = vnic->rq.iobuf[rq_iobuf_idx];
vnic->rq.iobuf[rq_iobuf_idx] = NULL;
/* Populate I/O buffer */
iob_reserve ( iobuf, apad_len );
iob_put ( iobuf, len );
/* Hand off to network stack */
if ( errop ) {
DBGC ( vnic, "TXNIC %s RQE %#03x error (length %zd, errop "
"%#02x)\n", vnic->name, rq_idx, len, errop );
netdev_rx_err ( netdev, iobuf, -EIO );
} else {
DBGC2 ( vnic, "TXNIC %s RQE %#03x complete (length %zd)\n",
vnic->name, rq_idx, len );
netdev_rx ( netdev, iobuf );
}
}
/******************************************************************************
*
* Completion queue
*
******************************************************************************
*/
/**
* Create completion queue
*
* @v vnic Virtual NIC
* @ret rc Return status code
*/
static int txnic_create_cq ( struct txnic *vnic ) {
/* Reset completion queue */
vnic->cq.cons = 0;
writeq ( TXNIC_QS_CQ_CFG_RESET, ( vnic->regs + TXNIC_QS_CQ_CFG(0) ) );
/* Configure and enable completion queue */
writeq ( user_to_phys ( vnic->cq.cqe, 0 ),
( vnic->regs + TXNIC_QS_CQ_BASE(0) ) );
writeq ( ( TXNIC_QS_CQ_CFG_ENA | TXNIC_QS_CQ_CFG_QSIZE_256 ),
( vnic->regs + TXNIC_QS_CQ_CFG(0) ) );
DBGC ( vnic, "TXNIC %s CQ at [%08lx,%08lx)\n",
vnic->name, user_to_phys ( vnic->cq.cqe, 0 ),
user_to_phys ( vnic->cq.cqe, TXNIC_CQ_SIZE ) );
return 0;
}
/**
* Disable completion queue
*
* @v vnic Virtual NIC
* @ret rc Return status code
*/
static int txnic_disable_cq ( struct txnic *vnic ) {
uint64_t cfg;
unsigned int i;
/* Disable completion queue */
writeq ( 0, ( vnic->regs + TXNIC_QS_CQ_CFG(0) ) );
/* Wait for completion queue to be disabled */
for ( i = 0 ; i < TXNIC_CQ_DISABLE_MAX_WAIT_MS ; i++ ) {
/* Check if completion queue is disabled */
cfg = readq ( vnic->regs + TXNIC_QS_CQ_CFG(0) );
if ( ! ( cfg & TXNIC_QS_CQ_CFG_ENA ) )
return 0;
/* Delay */
mdelay ( 1 );
}
DBGC ( vnic, "TXNIC %s CQ disable timed out\n", vnic->name );
return -ETIMEDOUT;
}
/**
* Destroy completion queue
*
* @v vnic Virtual NIC
*/
static void txnic_destroy_cq ( struct txnic *vnic ) {
int rc;
/* Disable completion queue */
if ( ( rc = txnic_disable_cq ( vnic ) ) != 0 ) {
/* Leak memory; there's nothing else we can do */
return;
}
/* Reset completion queue */
writeq ( TXNIC_QS_CQ_CFG_RESET, ( vnic->regs + TXNIC_QS_CQ_CFG(0) ) );
}
/**
* Poll completion queue
*
* @v vnic Virtual NIC
*/
static void txnic_poll_cq ( struct txnic *vnic ) {
union txnic_cqe cqe;
uint64_t status;
size_t offset;
unsigned int qcount;
unsigned int cq_idx;
unsigned int i;
/* Get number of completions */
status = readq ( vnic->regs + TXNIC_QS_CQ_STATUS(0) );
qcount = TXNIC_QS_CQ_STATUS_QCOUNT ( status );
if ( ! qcount )
return;
/* Process completion queue entries */
for ( i = 0 ; i < qcount ; i++ ) {
/* Get completion queue entry */
cq_idx = ( vnic->cq.cons++ % TXNIC_CQES );
offset = ( cq_idx * TXNIC_CQ_STRIDE );
copy_from_user ( &cqe, vnic->cq.cqe, offset, sizeof ( cqe ) );
/* Process completion queue entry */
switch ( cqe.common.cqe_type ) {
case TXNIC_CQE_TYPE_SEND:
txnic_complete_sqe ( vnic, &cqe.send );
break;
case TXNIC_CQE_TYPE_RX:
txnic_complete_rqe ( vnic, &cqe.rx );
break;
default:
DBGC ( vnic, "TXNIC %s unknown completion type %d\n",
vnic->name, cqe.common.cqe_type );
DBGC_HDA ( vnic, user_to_phys ( vnic->cq.cqe, offset ),
&cqe, sizeof ( cqe ) );
break;
}
}
/* Ring doorbell */
writeq ( qcount, ( vnic->regs + TXNIC_QS_CQ_DOOR(0) ) );
}
/******************************************************************************
*
* Virtual NIC
*
******************************************************************************
*/
/**
* Open virtual NIC
*
* @v vnic Virtual NIC
* @ret rc Return status code
*/
static int txnic_open ( struct txnic *vnic ) {
int rc;
/* Create completion queue */
if ( ( rc = txnic_create_cq ( vnic ) ) != 0 )
goto err_create_cq;
/* Create send queue */
if ( ( rc = txnic_create_sq ( vnic ) ) != 0 )
goto err_create_sq;
/* Create receive queue */
if ( ( rc = txnic_create_rq ( vnic ) ) != 0 )
goto err_create_rq;
/* Refill receive queue */
txnic_refill_rq ( vnic );
return 0;
txnic_destroy_rq ( vnic );
err_create_rq:
txnic_destroy_sq ( vnic );
err_create_sq:
txnic_destroy_cq ( vnic );
err_create_cq:
return rc;
}
/**
* Close virtual NIC
*
* @v vnic Virtual NIC
*/
static void txnic_close ( struct txnic *vnic ) {
/* Destroy receive queue */
txnic_destroy_rq ( vnic );
/* Destroy send queue */
txnic_destroy_sq ( vnic );
/* Destroy completion queue */
txnic_destroy_cq ( vnic );
}
/**
* Poll virtual NIC
*
* @v vnic Virtual NIC
*/
static void txnic_poll ( struct txnic *vnic ) {
/* Poll completion queue */
txnic_poll_cq ( vnic );
/* Refill receive queue */
txnic_refill_rq ( vnic );
}
/**
* Allocate virtual NIC
*
* @v dev Underlying device
* @v membase Register base address
* @ret vnic Virtual NIC, or NULL on failure
*/
static struct txnic * txnic_alloc ( struct device *dev,
unsigned long membase ) {
struct net_device *netdev;
struct txnic *vnic;
/* Allocate network device */
netdev = alloc_etherdev ( sizeof ( *vnic ) );
if ( ! netdev )
goto err_alloc_netdev;
netdev->dev = dev;
vnic = netdev->priv;
vnic->netdev = netdev;
vnic->name = dev->name;
/* Allow caller to reuse netdev->priv. (The generic virtual
* NIC code never assumes that netdev->priv==vnic.)
*/
netdev->priv = NULL;
/* Allocate completion queue */
vnic->cq.cqe = umalloc ( TXNIC_CQ_SIZE );
if ( ! vnic->cq.cqe )
goto err_alloc_cq;
/* Allocate send queue */
vnic->sq.sqe = umalloc ( TXNIC_SQ_SIZE );
if ( ! vnic->sq.sqe )
goto err_alloc_sq;
/* Allocate receive queue */
vnic->rq.rqe = umalloc ( TXNIC_RQ_SIZE );
if ( ! vnic->rq.rqe )
goto err_alloc_rq;
/* Map registers */
vnic->regs = ioremap ( membase, TXNIC_VF_BAR_SIZE );
if ( ! vnic->regs )
goto err_ioremap;
return vnic;
iounmap ( vnic->regs );
err_ioremap:
ufree ( vnic->rq.rqe );
err_alloc_rq:
ufree ( vnic->sq.sqe );
err_alloc_sq:
ufree ( vnic->cq.cqe );
err_alloc_cq:
netdev_nullify ( netdev );
netdev_put ( netdev );
err_alloc_netdev:
return NULL;
}
/**
* Free virtual NIC
*
* @v vnic Virtual NIC
*/
static void txnic_free ( struct txnic *vnic ) {
struct net_device *netdev = vnic->netdev;
/* Unmap registers */
iounmap ( vnic->regs );
/* Free receive queue */
ufree ( vnic->rq.rqe );
/* Free send queue */
ufree ( vnic->sq.sqe );
/* Free completion queue */
ufree ( vnic->cq.cqe );
/* Free network device */
netdev_nullify ( netdev );
netdev_put ( netdev );
}
/******************************************************************************
*
* Logical MAC virtual NICs
*
******************************************************************************
*/
/**
* Show LMAC diagnostics (for debugging)
*
* @v lmac Logical MAC
*/
static __attribute__ (( unused )) void
txnic_lmac_diag ( struct txnic_lmac *lmac ) {
struct txnic *vnic = lmac->vnic;
uint64_t status1;
uint64_t status2;
uint64_t br_status1;
uint64_t br_status2;
uint64_t br_algn_status;
uint64_t br_pmd_status;
uint64_t an_status;
/* Read status (clearing latching bits) */
writeq ( BGX_SPU_STATUS1_RCV_LNK, ( lmac->regs + BGX_SPU_STATUS1 ) );
writeq ( BGX_SPU_STATUS2_RCVFLT, ( lmac->regs + BGX_SPU_STATUS2 ) );
status1 = readq ( lmac->regs + BGX_SPU_STATUS1 );
status2 = readq ( lmac->regs + BGX_SPU_STATUS2 );
DBGC ( vnic, "TXNIC %s SPU %02llx:%04llx%s%s%s\n",
vnic->name, status1, status2,
( ( status1 & BGX_SPU_STATUS1_FLT ) ? " FLT" : "" ),
( ( status1 & BGX_SPU_STATUS1_RCV_LNK ) ? " RCV_LNK" : "" ),
( ( status2 & BGX_SPU_STATUS2_RCVFLT ) ? " RCVFLT" : "" ) );
/* Read BASE-R status (clearing latching bits) */
writeq ( ( BGX_SPU_BR_STATUS2_LATCHED_LOCK |
BGX_SPU_BR_STATUS2_LATCHED_BER ),
( lmac->regs + BGX_SPU_BR_STATUS2 ) );
br_status1 = readq ( lmac->regs + BGX_SPU_BR_STATUS1 );
br_status2 = readq ( lmac->regs + BGX_SPU_BR_STATUS2 );
DBGC ( vnic, "TXNIC %s BR %04llx:%04llx%s%s%s%s%s\n",
vnic->name, br_status2, br_status2,
( ( br_status1 & BGX_SPU_BR_STATUS1_RCV_LNK ) ? " RCV_LNK" : ""),
( ( br_status1 & BGX_SPU_BR_STATUS1_HI_BER ) ? " HI_BER" : "" ),
( ( br_status1 & BGX_SPU_BR_STATUS1_BLK_LOCK ) ?
" BLK_LOCK" : "" ),
( ( br_status2 & BGX_SPU_BR_STATUS2_LATCHED_LOCK ) ?
" LATCHED_LOCK" : "" ),
( ( br_status2 & BGX_SPU_BR_STATUS2_LATCHED_BER ) ?
" LATCHED_BER" : "" ) );
/* Read BASE-R alignment status */
br_algn_status = readq ( lmac->regs + BGX_SPU_BR_ALGN_STATUS );
DBGC ( vnic, "TXNIC %s BR ALGN %016llx%s\n", vnic->name, br_algn_status,
( ( br_algn_status & BGX_SPU_BR_ALGN_STATUS_ALIGND ) ?
" ALIGND" : "" ) );
/* Read BASE-R link training status */
br_pmd_status = readq ( lmac->regs + BGX_SPU_BR_PMD_STATUS );
DBGC ( vnic, "TXNIC %s BR PMD %04llx\n", vnic->name, br_pmd_status );
/* Read autonegotiation status (clearing latching bits) */
writeq ( ( BGX_SPU_AN_STATUS_PAGE_RX | BGX_SPU_AN_STATUS_LINK_STATUS ),
( lmac->regs + BGX_SPU_AN_STATUS ) );
an_status = readq ( lmac->regs + BGX_SPU_AN_STATUS );
DBGC ( vnic, "TXNIC %s BR AN %04llx%s%s%s%s%s\n", vnic->name, an_status,
( ( an_status & BGX_SPU_AN_STATUS_XNP_STAT ) ? " XNP_STAT" : ""),
( ( an_status & BGX_SPU_AN_STATUS_PAGE_RX ) ? " PAGE_RX" : "" ),
( ( an_status & BGX_SPU_AN_STATUS_AN_COMPLETE ) ?
" AN_COMPLETE" : "" ),
( ( an_status & BGX_SPU_AN_STATUS_LINK_STATUS ) ?
" LINK_STATUS" : "" ),
( ( an_status & BGX_SPU_AN_STATUS_LP_AN_ABLE ) ?
" LP_AN_ABLE" : "" ) );
/* Read transmit statistics */
DBGC ( vnic, "TXNIC %s TXF xc %#llx xd %#llx mc %#llx sc %#llx ok "
"%#llx bc %#llx mc %#llx un %#llx pa %#llx\n", vnic->name,
readq ( lmac->regs + BGX_CMR_TX_STAT0 ),
readq ( lmac->regs + BGX_CMR_TX_STAT1 ),
readq ( lmac->regs + BGX_CMR_TX_STAT2 ),
readq ( lmac->regs + BGX_CMR_TX_STAT3 ),
readq ( lmac->regs + BGX_CMR_TX_STAT5 ),
readq ( lmac->regs + BGX_CMR_TX_STAT14 ),
readq ( lmac->regs + BGX_CMR_TX_STAT15 ),
readq ( lmac->regs + BGX_CMR_TX_STAT16 ),
readq ( lmac->regs + BGX_CMR_TX_STAT17 ) );
DBGC ( vnic, "TXNIC %s TXB ok %#llx hist %#llx:%#llx:%#llx:%#llx:"
"%#llx:%#llx:%#llx:%#llx\n", vnic->name,
readq ( lmac->regs + BGX_CMR_TX_STAT4 ),
readq ( lmac->regs + BGX_CMR_TX_STAT6 ),
readq ( lmac->regs + BGX_CMR_TX_STAT7 ),
readq ( lmac->regs + BGX_CMR_TX_STAT8 ),
readq ( lmac->regs + BGX_CMR_TX_STAT9 ),
readq ( lmac->regs + BGX_CMR_TX_STAT10 ),
readq ( lmac->regs + BGX_CMR_TX_STAT11 ),
readq ( lmac->regs + BGX_CMR_TX_STAT12 ),
readq ( lmac->regs + BGX_CMR_TX_STAT13 ) );
/* Read receive statistics */
DBGC ( vnic, "TXNIC %s RXF ok %#llx pa %#llx nm %#llx ov %#llx er "
"%#llx nc %#llx\n", vnic->name,
readq ( lmac->regs + BGX_CMR_RX_STAT0 ),
readq ( lmac->regs + BGX_CMR_RX_STAT2 ),
readq ( lmac->regs + BGX_CMR_RX_STAT4 ),
readq ( lmac->regs + BGX_CMR_RX_STAT6 ),
readq ( lmac->regs + BGX_CMR_RX_STAT8 ),
readq ( lmac->regs + BGX_CMR_RX_STAT9 ) );
DBGC ( vnic, "TXNIC %s RXB ok %#llx pa %#llx nm %#llx ov %#llx nc "
"%#llx\n", vnic->name,
readq ( lmac->regs + BGX_CMR_RX_STAT1 ),
readq ( lmac->regs + BGX_CMR_RX_STAT3 ),
readq ( lmac->regs + BGX_CMR_RX_STAT5 ),
readq ( lmac->regs + BGX_CMR_RX_STAT7 ),
readq ( lmac->regs + BGX_CMR_RX_STAT10 ) );
}
/**
* Update LMAC link state
*
* @v lmac Logical MAC
*/
static void txnic_lmac_update_link ( struct txnic_lmac *lmac ) {
struct txnic *vnic = lmac->vnic;
struct net_device *netdev = vnic->netdev;
uint64_t status1;
/* Read status (clearing latching bits) */
writeq ( BGX_SPU_STATUS1_RCV_LNK, ( lmac->regs + BGX_SPU_STATUS1 ) );
status1 = readq ( lmac->regs + BGX_SPU_STATUS1 );
/* Report link status */
if ( status1 & BGX_SPU_STATUS1_RCV_LNK ) {
netdev_link_up ( netdev );
} else {
netdev_link_down ( netdev );
}
}
/**
* Poll LMAC link state
*
* @v lmac Logical MAC
*/
static void txnic_lmac_poll_link ( struct txnic_lmac *lmac ) {
struct txnic *vnic = lmac->vnic;
uint64_t intr;
/* Get interrupt status */
intr = readq ( lmac->regs + BGX_SPU_INT );
if ( ! intr )
return;
DBGC ( vnic, "TXNIC %s INT %04llx%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
vnic->name, intr,
( ( intr & BGX_SPU_INT_TRAINING_FAIL ) ? " TRAINING_FAIL" : "" ),
( ( intr & BGX_SPU_INT_TRAINING_DONE ) ? " TRAINING_DONE" : "" ),
( ( intr & BGX_SPU_INT_AN_COMPLETE ) ? " AN_COMPLETE" : "" ),
( ( intr & BGX_SPU_INT_AN_LINK_GOOD ) ? " AN_LINK_GOOD" : "" ),
( ( intr & BGX_SPU_INT_AN_PAGE_RX ) ? " AN_PAGE_RX" : "" ),
( ( intr & BGX_SPU_INT_FEC_UNCORR ) ? " FEC_UNCORR" : "" ),
( ( intr & BGX_SPU_INT_FEC_CORR ) ? " FEC_CORR" : "" ),
( ( intr & BGX_SPU_INT_BIP_ERR ) ? " BIP_ERR" : "" ),
( ( intr & BGX_SPU_INT_DBG_SYNC ) ? " DBG_SYNC" : "" ),
( ( intr & BGX_SPU_INT_ALGNLOS ) ? " ALGNLOS" : "" ),
( ( intr & BGX_SPU_INT_SYNLOS ) ? " SYNLOS" : "" ),
( ( intr & BGX_SPU_INT_BITLCKLS ) ? " BITLCKLS" : "" ),
( ( intr & BGX_SPU_INT_ERR_BLK ) ? " ERR_BLK" : "" ),
( ( intr & BGX_SPU_INT_RX_LINK_DOWN ) ? " RX_LINK_DOWN" : "" ),
( ( intr & BGX_SPU_INT_RX_LINK_UP ) ? " RX_LINK_UP" : "" ) );
/* Clear interrupt status */
writeq ( intr, ( lmac->regs + BGX_SPU_INT ) );
/* Update link state */
txnic_lmac_update_link ( lmac );
}
/**
* Reset LMAC
*
* @v lmac Logical MAC
*/
static void txnic_lmac_reset ( struct txnic_lmac *lmac ) {
struct txnic_bgx *bgx = lmac->bgx;
struct txnic_pf *pf = bgx->pf;
void *qsregs = ( pf->regs + TXNIC_PF_QS ( lmac->idx ) );
/* There is no reset available for the physical function
* aspects of a virtual NIC; we have to explicitly reload a
* sensible set of default values.
*/
writeq ( 0, ( qsregs + TXNIC_PF_QS_CFG ) );
writeq ( 0, ( qsregs + TXNIC_PF_QS_RQ_CFG(0) ) );
writeq ( 0, ( qsregs + TXNIC_PF_QS_RQ_DROP_CFG(0) ) );
writeq ( 0, ( qsregs + TXNIC_PF_QS_RQ_BP_CFG(0) ) );
writeq ( 0, ( qsregs + TXNIC_PF_QS_SQ_CFG(0) ) );
}
/**
* Open network device
*
* @v netdev Network device
* @ret rc Return status code
*/
static int txnic_lmac_open ( struct net_device *netdev ) {
struct txnic_lmac *lmac = netdev->priv;
struct txnic_bgx *bgx = lmac->bgx;
struct txnic_pf *pf = bgx->pf;
struct txnic *vnic = lmac->vnic;
unsigned int vnic_idx = lmac->idx;
unsigned int chan_idx = TXNIC_CHAN_IDX ( vnic_idx );
unsigned int tl4_idx = TXNIC_TL4_IDX ( vnic_idx );
unsigned int tl3_idx = TXNIC_TL3_IDX ( vnic_idx );
unsigned int tl2_idx = TXNIC_TL2_IDX ( vnic_idx );
void *lmregs = ( pf->regs + TXNIC_PF_LMAC ( vnic_idx ) );
void *chregs = ( pf->regs + TXNIC_PF_CHAN ( chan_idx ) );
void *qsregs = ( pf->regs + TXNIC_PF_QS ( vnic_idx ) );
size_t max_pkt_size;
int rc;
/* Configure channel/match parse indices */
writeq ( ( TXNIC_PF_MPI_CFG_VNIC ( vnic_idx ) |
TXNIC_PF_MPI_CFG_RSSI_BASE ( vnic_idx ) ),
( TXNIC_PF_MPI_CFG ( vnic_idx ) + pf->regs ) );
writeq ( ( TXNIC_PF_RSSI_RQ_RQ_QS ( vnic_idx ) ),
( TXNIC_PF_RSSI_RQ ( vnic_idx ) + pf->regs ) );
/* Configure LMAC */
max_pkt_size = ( netdev->max_pkt_len + 4 /* possible VLAN */ );
writeq ( ( TXNIC_PF_LMAC_CFG_ADJUST_DEFAULT |
TXNIC_PF_LMAC_CFG_MIN_PKT_SIZE ( ETH_ZLEN ) ),
( TXNIC_PF_LMAC_CFG + lmregs ) );
writeq ( ( TXNIC_PF_LMAC_CFG2_MAX_PKT_SIZE ( max_pkt_size ) ),
( TXNIC_PF_LMAC_CFG2 + lmregs ) );
writeq ( ( TXNIC_PF_LMAC_CREDIT_CC_UNIT_CNT_DEFAULT |
TXNIC_PF_LMAC_CREDIT_CC_PACKET_CNT_DEFAULT |
TXNIC_PF_LMAC_CREDIT_CC_ENABLE ),
( TXNIC_PF_LMAC_CREDIT + lmregs ) );
/* Configure channels */
writeq ( ( TXNIC_PF_CHAN_TX_CFG_BP_ENA ),
( TXNIC_PF_CHAN_TX_CFG + chregs ) );
writeq ( ( TXNIC_PF_CHAN_RX_CFG_CPI_BASE ( vnic_idx ) ),
( TXNIC_PF_CHAN_RX_CFG + chregs ) );
writeq ( ( TXNIC_PF_CHAN_RX_BP_CFG_ENA |
TXNIC_PF_CHAN_RX_BP_CFG_BPID ( vnic_idx ) ),
( TXNIC_PF_CHAN_RX_BP_CFG + chregs ) );
/* Configure traffic limiters */
writeq ( ( TXNIC_PF_TL2_CFG_RR_QUANTUM_DEFAULT ),
( TXNIC_PF_TL2_CFG ( tl2_idx ) + pf->regs ) );
writeq ( ( TXNIC_PF_TL3_CFG_RR_QUANTUM_DEFAULT ),
( TXNIC_PF_TL3_CFG ( tl3_idx ) + pf->regs ) );
writeq ( ( TXNIC_PF_TL3_CHAN_CHAN ( chan_idx ) ),
( TXNIC_PF_TL3_CHAN ( tl3_idx ) + pf->regs ) );
writeq ( ( TXNIC_PF_TL4_CFG_SQ_QS ( vnic_idx ) |
TXNIC_PF_TL4_CFG_RR_QUANTUM_DEFAULT ),
( TXNIC_PF_TL4_CFG ( tl4_idx ) + pf->regs ) );
/* Configure send queue */
writeq ( ( TXNIC_PF_QS_SQ_CFG_CQ_QS ( vnic_idx ) ),
( TXNIC_PF_QS_SQ_CFG(0) + qsregs ) );
writeq ( ( TXNIC_PF_QS_SQ_CFG2_TL4 ( tl4_idx ) ),
( TXNIC_PF_QS_SQ_CFG2(0) + qsregs ) );
/* Configure receive queue */
writeq ( ( TXNIC_PF_QS_RQ_CFG_CACHING_ALL |
TXNIC_PF_QS_RQ_CFG_CQ_QS ( vnic_idx ) |
TXNIC_PF_QS_RQ_CFG_RBDR_CONT_QS ( vnic_idx ) |
TXNIC_PF_QS_RQ_CFG_RBDR_STRT_QS ( vnic_idx ) ),
( TXNIC_PF_QS_RQ_CFG(0) + qsregs ) );
writeq ( ( TXNIC_PF_QS_RQ_BP_CFG_RBDR_BP_ENA |
TXNIC_PF_QS_RQ_BP_CFG_CQ_BP_ENA |
TXNIC_PF_QS_RQ_BP_CFG_BPID ( vnic_idx ) ),
( TXNIC_PF_QS_RQ_BP_CFG(0) + qsregs ) );
/* Enable queue set */
writeq ( ( TXNIC_PF_QS_CFG_ENA | TXNIC_PF_QS_CFG_VNIC ( vnic_idx ) ),
( TXNIC_PF_QS_CFG + qsregs ) );
/* Open virtual NIC */
if ( ( rc = txnic_open ( vnic ) ) != 0 )
goto err_open;
/* Update link state */
txnic_lmac_update_link ( lmac );
return 0;
txnic_close ( vnic );
err_open:
writeq ( 0, ( qsregs + TXNIC_PF_QS_CFG ) );
return rc;
}
/**
* Close network device
*
* @v netdev Network device
*/
static void txnic_lmac_close ( struct net_device *netdev ) {
struct txnic_lmac *lmac = netdev->priv;
struct txnic_bgx *bgx = lmac->bgx;
struct txnic_pf *pf = bgx->pf;
struct txnic *vnic = lmac->vnic;
void *qsregs = ( pf->regs + TXNIC_PF_QS ( lmac->idx ) );
/* Close virtual NIC */
txnic_close ( vnic );
/* Disable queue set */
writeq ( 0, ( qsregs + TXNIC_PF_QS_CFG ) );
}
/**
* Transmit packet
*
* @v netdev Network device
* @v iobuf I/O buffer
* @ret rc Return status code
*/
static int txnic_lmac_transmit ( struct net_device *netdev,
struct io_buffer *iobuf ) {
struct txnic_lmac *lmac = netdev->priv;
struct txnic *vnic = lmac->vnic;
return txnic_send ( vnic, iobuf );
}
/**
* Poll network device
*
* @v netdev Network device
*/
static void txnic_lmac_poll ( struct net_device *netdev ) {
struct txnic_lmac *lmac = netdev->priv;
struct txnic *vnic = lmac->vnic;
/* Poll virtual NIC */
txnic_poll ( vnic );
/* Poll link state */
txnic_lmac_poll_link ( lmac );
}
/** Network device operations */
static struct net_device_operations txnic_lmac_operations = {
.open = txnic_lmac_open,
.close = txnic_lmac_close,
.transmit = txnic_lmac_transmit,
.poll = txnic_lmac_poll,
};
/**
* Probe logical MAC virtual NIC
*
* @v lmac Logical MAC
* @ret rc Return status code
*/
static int txnic_lmac_probe ( struct txnic_lmac *lmac ) {
struct txnic_bgx *bgx = lmac->bgx;
struct txnic_pf *pf = bgx->pf;
struct txnic *vnic;
struct net_device *netdev;
unsigned long membase;
int rc;
/* Sanity check */
assert ( lmac->vnic == NULL );
/* Calculate register base address */
membase = ( pf->vf_membase + ( lmac->idx * pf->vf_stride ) );
/* Allocate and initialise network device */
vnic = txnic_alloc ( &bgx->pci->dev, membase );
if ( ! vnic ) {
rc = -ENOMEM;
goto err_alloc;
}
netdev = vnic->netdev;
netdev_init ( netdev, &txnic_lmac_operations );
netdev->priv = lmac;
lmac->vnic = vnic;
/* Reset device */
txnic_lmac_reset ( lmac );
/* Set MAC address */
memcpy ( netdev->hw_addr, lmac->mac.raw, ETH_ALEN );
/* Register network device */
if ( ( rc = register_netdev ( netdev ) ) != 0 )
goto err_register;
vnic->name = netdev->name;
DBGC ( TXNICCOL ( pf ), "TXNIC %d/%d/%d is %s (%s)\n", pf->node,
bgx->idx, lmac->idx, vnic->name, eth_ntoa ( lmac->mac.raw ) );
/* Update link state */
txnic_lmac_update_link ( lmac );
return 0;
unregister_netdev ( netdev );
err_register:
txnic_lmac_reset ( lmac );
txnic_free ( vnic );
lmac->vnic = NULL;
err_alloc:
return rc;
}
/**
* Remove logical MAC virtual NIC
*
* @v lmac Logical MAC
*/
static void txnic_lmac_remove ( struct txnic_lmac *lmac ) {
uint64_t config;
/* Sanity check */
assert ( lmac->vnic != NULL );
/* Disable packet receive and transmit */
config = readq ( lmac->regs + BGX_CMR_CONFIG );
config &= ~( BGX_CMR_CONFIG_DATA_PKT_TX_EN |
BGX_CMR_CONFIG_DATA_PKT_RX_EN );
writeq ( config, ( lmac->regs + BGX_CMR_CONFIG ) );
/* Unregister network device */
unregister_netdev ( lmac->vnic->netdev );
/* Reset device */
txnic_lmac_reset ( lmac );
/* Free virtual NIC */
txnic_free ( lmac->vnic );
lmac->vnic = NULL;
}
/**
* Probe all LMACs on a BGX Ethernet interface
*
* @v pf Physical function
* @v bgx BGX Ethernet interface
* @ret rc Return status code
*/
static int txnic_lmac_probe_all ( struct txnic_pf *pf, struct txnic_bgx *bgx ) {
unsigned int bgx_idx;
int lmac_idx;
int count;
int rc;
/* Sanity checks */
bgx_idx = bgx->idx;
assert ( pf->node == bgx->node );
assert ( pf->bgx[bgx_idx] == NULL );
assert ( bgx->pf == NULL );
/* Associate BGX with physical function */
pf->bgx[bgx_idx] = bgx;
bgx->pf = pf;
/* Probe all LMACs */
count = bgx->count;
for ( lmac_idx = 0 ; lmac_idx < count ; lmac_idx++ ) {
if ( ( rc = txnic_lmac_probe ( &bgx->lmac[lmac_idx] ) ) != 0 )
goto err_probe;
}
return 0;
lmac_idx = count;
err_probe:
for ( lmac_idx-- ; lmac_idx >= 0 ; lmac_idx-- )
txnic_lmac_remove ( &bgx->lmac[lmac_idx] );
pf->bgx[bgx_idx] = NULL;
bgx->pf = NULL;
return rc;
}
/**
* Remove all LMACs on a BGX Ethernet interface
*
* @v pf Physical function
* @v bgx BGX Ethernet interface
*/
static void txnic_lmac_remove_all ( struct txnic_pf *pf,
struct txnic_bgx *bgx ) {
unsigned int lmac_idx;
/* Sanity checks */
assert ( pf->bgx[bgx->idx] == bgx );
assert ( bgx->pf == pf );
/* Remove all LMACs */
for ( lmac_idx = 0 ; lmac_idx < bgx->count ; lmac_idx++ )
txnic_lmac_remove ( &bgx->lmac[lmac_idx] );
/* Disassociate BGX from physical function */
pf->bgx[bgx->idx] = NULL;
bgx->pf = NULL;
}
/******************************************************************************
*
* NIC physical function interface
*
******************************************************************************
*/
/**
* Probe PCI device
*
* @v pci PCI device
* @ret rc Return status code
*/
static int txnic_pf_probe ( struct pci_device *pci ) {
struct txnic_pf *pf;
struct txnic_bgx *bgx;
unsigned long membase;
unsigned int i;
int rc;
/* Allocate and initialise structure */
pf = zalloc ( sizeof ( *pf ) );
if ( ! pf ) {
rc = -ENOMEM;
goto err_alloc;
}
pf->pci = pci;
pci_set_drvdata ( pci, pf );
/* Get base addresses */
membase = pciea_bar_start ( pci, PCIEA_BEI_BAR_0 );
pf->vf_membase = pciea_bar_start ( pci, PCIEA_BEI_VF_BAR_0 );
pf->vf_stride = pciea_bar_size ( pci, PCIEA_BEI_VF_BAR_0 );
/* Calculate node ID */
pf->node = txnic_address_node ( membase );
DBGC ( TXNICCOL ( pf ), "TXNIC %d/*/* PF %s at %#lx (VF %#lx+%#lx)\n",
pf->node, pci->dev.name, membase, pf->vf_membase, pf->vf_stride);
/* Fix up PCI device */
adjust_pci_device ( pci );
/* Map registers */
pf->regs = ioremap ( membase, TXNIC_PF_BAR_SIZE );
if ( ! pf->regs ) {
rc = -ENODEV;
goto err_ioremap;
}
/* Configure physical function */
writeq ( TXNIC_PF_CFG_ENA, ( pf->regs + TXNIC_PF_CFG ) );
writeq ( ( TXNIC_PF_BP_CFG_BP_POLL_ENA |
TXNIC_PF_BP_CFG_BP_POLL_DLY_DEFAULT ),
( pf->regs + TXNIC_PF_BP_CFG ) );
for ( i = 0 ; i < TXNIC_NUM_BGX ; i++ ) {
writeq ( ( TXNIC_PF_INTF_SEND_CFG_BLOCK_BGX |
TXNIC_PF_INTF_SEND_CFG_BLOCK ( i ) ),
( pf->regs + TXNIC_PF_INTF_SEND_CFG ( i ) ) );
writeq ( ( TXNIC_PF_INTF_BP_CFG_BP_ENA |
TXNIC_PF_INTF_BP_CFG_BP_ID_BGX |
TXNIC_PF_INTF_BP_CFG_BP_ID ( i ) ),
( pf->regs + TXNIC_PF_INTF_BP_CFG ( i ) ) );
}
writeq ( ( TXNIC_PF_PKIND_CFG_LENERR_EN |
TXNIC_PF_PKIND_CFG_MAXLEN_DISABLE |
TXNIC_PF_PKIND_CFG_MINLEN_DISABLE ),
( pf->regs + TXNIC_PF_PKIND_CFG(0) ) );
/* Add to list of physical functions */
list_add_tail ( &pf->list, &txnic_pfs );
/* Probe all LMACs, if applicable */
list_for_each_entry ( bgx, &txnic_bgxs, list ) {
if ( bgx->node != pf->node )
continue;
if ( ( rc = txnic_lmac_probe_all ( pf, bgx ) ) != 0 )
goto err_probe;
}
return 0;
err_probe:
for ( i = 0 ; i < TXNIC_NUM_BGX ; i++ ) {
if ( pf->bgx[i] )
txnic_lmac_remove_all ( pf, pf->bgx[i] );
}
list_del ( &pf->list );
writeq ( 0, ( pf->regs + TXNIC_PF_CFG ) );
iounmap ( pf->regs );
err_ioremap:
free ( pf );
err_alloc:
return rc;
}
/**
* Remove PCI device
*
* @v pci PCI device
*/
static void txnic_pf_remove ( struct pci_device *pci ) {
struct txnic_pf *pf = pci_get_drvdata ( pci );
unsigned int i;
/* Remove all LMACs, if applicable */
for ( i = 0 ; i < TXNIC_NUM_BGX ; i++ ) {
if ( pf->bgx[i] )
txnic_lmac_remove_all ( pf, pf->bgx[i] );
}
/* Remove from list of physical functions */
list_del ( &pf->list );
/* Unmap registers */
iounmap ( pf->regs );
/* Free physical function */
free ( pf );
}
/** NIC physical function PCI device IDs */
static struct pci_device_id txnic_pf_ids[] = {
PCI_ROM ( 0x177d, 0xa01e, "thunder-pf", "ThunderX NIC PF", 0 ),
};
/** NIC physical function PCI driver */
struct pci_driver txnic_pf_driver __pci_driver = {
.ids = txnic_pf_ids,
.id_count = ( sizeof ( txnic_pf_ids ) / sizeof ( txnic_pf_ids[0] ) ),
.probe = txnic_pf_probe,
.remove = txnic_pf_remove,
};
/******************************************************************************
*
* BGX interface
*
******************************************************************************
*/
/** LMAC types */
static struct txnic_lmac_type txnic_lmac_types[] = {
[TXNIC_LMAC_XAUI] = {
.name = "XAUI",
.count = 1,
.lane_to_sds = 0xe4,
},
[TXNIC_LMAC_RXAUI] = {
.name = "RXAUI",
.count = 2,
.lane_to_sds = 0x0e04,
},
[TXNIC_LMAC_10G_R] = {
.name = "10GBASE-R",
.count = 4,
.lane_to_sds = 0x00000000,
},
[TXNIC_LMAC_40G_R] = {
.name = "40GBASE-R",
.count = 1,
.lane_to_sds = 0xe4,
},
};
/**
* Detect BGX Ethernet interface LMAC type
*
* @v bgx BGX Ethernet interface
* @ret type LMAC type, or negative error
*/
static int txnic_bgx_detect ( struct txnic_bgx *bgx ) {
uint64_t config;
uint64_t br_pmd_control;
uint64_t rx_lmacs;
unsigned int type;
/* We assume that the early (pre-UEFI) firmware will have
* configured at least the LMAC 0 type and use of link
* training, and may have overridden the number of LMACs.
*/
/* Determine type from LMAC 0 */
config = readq ( bgx->regs + BGX_CMR_CONFIG );
type = BGX_CMR_CONFIG_LMAC_TYPE_GET ( config );
if ( ( type >= ( sizeof ( txnic_lmac_types ) /
sizeof ( txnic_lmac_types[0] ) ) ) ||
( txnic_lmac_types[type].count == 0 ) ) {
DBGC ( TXNICCOL ( bgx ), "TXNIC %d/%d/* BGX unknown type %d\n",
bgx->node, bgx->idx, type );
return -ENOTTY;
}
bgx->type = &txnic_lmac_types[type];
/* Check whether link training is required */
br_pmd_control = readq ( bgx->regs + BGX_SPU_BR_PMD_CONTROL );
bgx->training =
( !! ( br_pmd_control & BGX_SPU_BR_PMD_CONTROL_TRAIN_EN ) );
/* Determine number of LMACs */
rx_lmacs = readq ( bgx->regs + BGX_CMR_RX_LMACS );
bgx->count = BGX_CMR_RX_LMACS_LMACS_GET ( rx_lmacs );
if ( ( bgx->count == TXNIC_NUM_LMAC ) &&
( bgx->type->count != TXNIC_NUM_LMAC ) ) {
DBGC ( TXNICCOL ( bgx ), "TXNIC %d/%d/* assuming %d LMACs\n",
bgx->node, bgx->idx, bgx->type->count );
bgx->count = bgx->type->count;
}
return type;
}
/**
* Initialise BGX Ethernet interface
*
* @v bgx BGX Ethernet interface
* @v type LMAC type
*/
static void txnic_bgx_init ( struct txnic_bgx *bgx, unsigned int type ) {
uint64_t global_config;
uint32_t lane_to_sds;
unsigned int i;
/* Set number of LMACs */
writeq ( BGX_CMR_RX_LMACS_LMACS_SET ( bgx->count ),
( bgx->regs + BGX_CMR_RX_LMACS ) );
writeq ( BGX_CMR_TX_LMACS_LMACS_SET ( bgx->count ),
( bgx->regs + BGX_CMR_TX_LMACS ) );
/* Set LMAC types and lane mappings, and disable all LMACs */
lane_to_sds = bgx->type->lane_to_sds;
for ( i = 0 ; i < bgx->count ; i++ ) {
writeq ( ( BGX_CMR_CONFIG_LMAC_TYPE_SET ( type ) |
BGX_CMR_CONFIG_LANE_TO_SDS ( lane_to_sds ) ),
( bgx->regs + BGX_LMAC ( i ) + BGX_CMR_CONFIG ) );
lane_to_sds >>= 8;
}
/* Reset all MAC address filtering */
for ( i = 0 ; i < TXNIC_NUM_DMAC ; i++ )
writeq ( 0, ( bgx->regs + BGX_CMR_RX_DMAC_CAM ( i ) ) );
/* Reset NCSI steering */
for ( i = 0 ; i < TXNIC_NUM_STEERING ; i++ )
writeq ( 0, ( bgx->regs + BGX_CMR_RX_STEERING ( i ) ) );
/* Enable backpressure to all channels */
writeq ( BGX_CMR_CHAN_MSK_AND_ALL ( bgx->count ),
( bgx->regs + BGX_CMR_CHAN_MSK_AND ) );
/* Strip FCS */
global_config = readq ( bgx->regs + BGX_CMR_GLOBAL_CONFIG );
global_config |= BGX_CMR_GLOBAL_CONFIG_FCS_STRIP;
writeq ( global_config, ( bgx->regs + BGX_CMR_GLOBAL_CONFIG ) );
}
/**
* Get MAC address
*
* @v lmac Logical MAC
*/
static void txnic_bgx_mac ( struct txnic_lmac *lmac ) {
struct txnic_bgx *bgx = lmac->bgx;
unsigned int lmac_idx = TXNIC_LMAC_IDX ( lmac->idx );
uint64_t mac;
EFI_STATUS efirc;
int rc;
/* Extract MAC from Board Configuration protocol, if available */
if ( txcfg ) {
if ( ( efirc = txcfg->GetLmacProp ( txcfg, bgx->node, bgx->idx,
lmac_idx, MAC_ADDRESS,
sizeof ( mac ),
&mac ) ) == 0 ) {
lmac->mac.be64 = cpu_to_be64 ( mac );
} else {
rc = -EEFI ( efirc );
DBGC ( TXNICCOL ( bgx ), "TXNIC %d/%d/%d could not get "
"MAC address: %s\n", bgx->node, bgx->idx,
lmac->idx, strerror ( rc ) );
}
} else {
DBGC ( TXNICCOL ( bgx ), "TXNIC %d/%d/%d has no board "
"configuration protocol\n", bgx->node, bgx->idx,
lmac->idx );
}
/* Use random MAC address if none available */
if ( ! lmac->mac.be64 ) {
DBGC ( TXNICCOL ( bgx ), "TXNIC %d/%d/%d has no MAC address\n",
bgx->node, bgx->idx, lmac->idx );
eth_random_addr ( lmac->mac.raw );
}
}
/**
* Initialise Super PHY Unit (SPU)
*
* @v lmac Logical MAC
*/
static void txnic_bgx_spu_init ( struct txnic_lmac *lmac ) {
struct txnic_bgx *bgx = lmac->bgx;
/* Reset PHY */
writeq ( BGX_SPU_CONTROL1_RESET, ( lmac->regs + BGX_SPU_CONTROL1 ) );
mdelay ( BGX_SPU_RESET_DELAY_MS );
/* Power down PHY */
writeq ( BGX_SPU_CONTROL1_LO_PWR, ( lmac->regs + BGX_SPU_CONTROL1 ) );
/* Configure training, if applicable */
if ( bgx->training ) {
writeq ( 0, ( lmac->regs + BGX_SPU_BR_PMD_LP_CUP ) );
writeq ( 0, ( lmac->regs + BGX_SPU_BR_PMD_LD_CUP ) );
writeq ( 0, ( lmac->regs + BGX_SPU_BR_PMD_LD_REP ) );
writeq ( BGX_SPU_BR_PMD_CONTROL_TRAIN_EN,
( lmac->regs + BGX_SPU_BR_PMD_CONTROL ) );
}
/* Disable forward error correction */
writeq ( 0, ( lmac->regs + BGX_SPU_FEC_CONTROL ) );
/* Disable autonegotiation */
writeq ( 0, ( lmac->regs + BGX_SPU_AN_CONTROL ) );
/* Power up PHY */
writeq ( 0, ( lmac->regs + BGX_SPU_CONTROL1 ) );
}
/**
* Initialise LMAC
*
* @v bgx BGX Ethernet interface
* @v lmac_idx LMAC index
*/
static void txnic_bgx_lmac_init ( struct txnic_bgx *bgx,
unsigned int lmac_idx ) {
struct txnic_lmac *lmac = &bgx->lmac[lmac_idx];
uint64_t config;
/* Record associated BGX */
lmac->bgx = bgx;
/* Set register base address (already mapped) */
lmac->regs = ( bgx->regs + BGX_LMAC ( lmac_idx ) );
/* Calculate virtual NIC index */
lmac->idx = TXNIC_VNIC_IDX ( bgx->idx, lmac_idx );
/* Set MAC address */
txnic_bgx_mac ( lmac );
/* Initialise PHY */
txnic_bgx_spu_init ( lmac );
/* Accept all multicasts and broadcasts */
writeq ( ( BGX_CMR_RX_DMAC_CTL_MCST_MODE_ACCEPT |
BGX_CMR_RX_DMAC_CTL_BCST_ACCEPT ),
( lmac->regs + BGX_CMR_RX_DMAC_CTL ) );
/* Enable LMAC */
config = readq ( lmac->regs + BGX_CMR_CONFIG );
config |= ( BGX_CMR_CONFIG_ENABLE |
BGX_CMR_CONFIG_DATA_PKT_RX_EN |
BGX_CMR_CONFIG_DATA_PKT_TX_EN );
writeq ( config, ( lmac->regs + BGX_CMR_CONFIG ) );
}
/**
* Probe PCI device
*
* @v pci PCI device
* @ret rc Return status code
*/
static int txnic_bgx_probe ( struct pci_device *pci ) {
struct txnic_bgx *bgx;
struct txnic_pf *pf;
unsigned long membase;
unsigned int i;
int type;
int rc;
/* Allocate and initialise structure */
bgx = zalloc ( sizeof ( *bgx ) );
if ( ! bgx ) {
rc = -ENOMEM;
goto err_alloc;
}
bgx->pci = pci;
pci_set_drvdata ( pci, bgx );
/* Get base address */
membase = pciea_bar_start ( pci, PCIEA_BEI_BAR_0 );
/* Calculate node ID and index */
bgx->node = txnic_address_node ( membase );
bgx->idx = txnic_address_bgx ( membase );
/* Fix up PCI device */
adjust_pci_device ( pci );
/* Map registers */
bgx->regs = ioremap ( membase, TXNIC_BGX_BAR_SIZE );
if ( ! bgx->regs ) {
rc = -ENODEV;
goto err_ioremap;
}
/* Detect LMAC type */
if ( ( type = txnic_bgx_detect ( bgx ) ) < 0 ) {
rc = type;
goto err_detect;
}
DBGC ( TXNICCOL ( bgx ), "TXNIC %d/%d/* BGX %s at %#lx %dx %s%s\n",
bgx->node, bgx->idx, pci->dev.name, membase, bgx->count,
bgx->type->name, ( bgx->training ? "(training)" : "" ) );
/* Initialise interface */
txnic_bgx_init ( bgx, type );
/* Initialise all LMACs */
for ( i = 0 ; i < bgx->count ; i++ )
txnic_bgx_lmac_init ( bgx, i );
/* Add to list of BGX devices */
list_add_tail ( &bgx->list, &txnic_bgxs );
/* Probe all LMACs, if applicable */
list_for_each_entry ( pf, &txnic_pfs, list ) {
if ( pf->node != bgx->node )
continue;
if ( ( rc = txnic_lmac_probe_all ( pf, bgx ) ) != 0 )
goto err_probe;
}
return 0;
if ( bgx->pf )
txnic_lmac_remove_all ( bgx->pf, bgx );
list_del ( &bgx->list );
err_probe:
err_detect:
iounmap ( bgx->regs );
err_ioremap:
free ( bgx );
err_alloc:
return rc;
}
/**
* Remove PCI device
*
* @v pci PCI device
*/
static void txnic_bgx_remove ( struct pci_device *pci ) {
struct txnic_bgx *bgx = pci_get_drvdata ( pci );
/* Remove all LMACs, if applicable */
if ( bgx->pf )
txnic_lmac_remove_all ( bgx->pf, bgx );
/* Remove from list of BGX devices */
list_del ( &bgx->list );
/* Unmap registers */
iounmap ( bgx->regs );
/* Free BGX device */
free ( bgx );
}
/** BGX PCI device IDs */
static struct pci_device_id txnic_bgx_ids[] = {
PCI_ROM ( 0x177d, 0xa026, "thunder-bgx", "ThunderX BGX", 0 ),
};
/** BGX PCI driver */
struct pci_driver txnic_bgx_driver __pci_driver = {
.ids = txnic_bgx_ids,
.id_count = ( sizeof ( txnic_bgx_ids ) / sizeof ( txnic_bgx_ids[0] ) ),
.probe = txnic_bgx_probe,
.remove = txnic_bgx_remove,
};