/* * Copyright (C) 2016 Michael Brown . * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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, };