blob: 8585dd83b3e533774baf002247bef9a99db97505 [file] [log] [blame]
/*****************************************************************************
* drivers/net/igb.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*****************************************************************************/
/*****************************************************************************
* Included Files
*****************************************************************************/
#include <nuttx/config.h>
#include <assert.h>
#include <debug.h>
#include <errno.h>
#include <nuttx/arch.h>
#include <nuttx/kmalloc.h>
#include <nuttx/wqueue.h>
#include <nuttx/addrenv.h>
#include <nuttx/spinlock.h>
#include <nuttx/net/netdev_lowerhalf.h>
#include <nuttx/pci/pci.h>
#include <nuttx/net/igb.h>
#include <arch/barriers.h>
#include "igb.h"
/*****************************************************************************
* Pre-processor Definitions
*****************************************************************************/
#if CONFIG_NET_IGB_TXDESC % 8 != 0
# error CONFIG_NET_IGB_TXDESC must be multiple of 8
#endif
#if CONFIG_NET_IGB_RXDESC % 8 != 0
# error CONFIG_NET_IGB_RXDESC must be multiple of 8
#endif
/* Packet buffer size */
#define IGB_PKTBUF_SIZE 2048
#define IGB_RCTL_BSIZE IGB_RCTL_BSIZE_2048
/* TX and RX descriptors */
#define IGB_TX_DESC CONFIG_NET_IGB_TXDESC
#define IGB_RX_DESC CONFIG_NET_IGB_RXDESC
/* After RX packet is done, we provide free netpkt to the RX descriptor ring.
* The upper-half network logic is responsible for freeing the RX packets
* so we need some additional spare netpkt buffers to assure that it's
* always possible to allocate the new RX packet in the receiver logic.
* It's hard to tell how many spare buffers is needed, for now it's set to 8.
*/
#define IGB_TX_QUOTA IGB_TX_DESC
#define IGB_RX_QUOTA (IGB_RX_DESC + CONFIG_NET_IGB_RXSPARE)
/* NOTE: CONFIG_IOB_ALIGNMENT must match system D-CACHE line size */
#if CONFIG_IOB_NBUFFERS < IGB_RX_QUOTA + IGB_TX_QUOTA
# error CONFIG_IOB_NBUFFERS must be > (IGB_RX_QUOTA + IGB_TX_QUOTA)
#endif
#if CONFIG_IOB_BUFSIZE < IGB_PKTBUF_SIZE
# error CONFIG_IOB_BUFSIZE must be > IGB_PKTBUF_SIZE
#endif
/* PCI BARs */
#define IGB_MMIO_BAR 0
#define IGB_FLASH_BAR 1
#define IGB_IO_BAR 2
#define IGB_MSIX_BAR 3
/* For MSI-X we allocate all interrupts to MSI-X vector 0 */
#define IGB_GPIE_MSIX_SINGLE (IGB_GPIE_NSICR | IGB_GPIE_EIAME | \
IGB_GPIE_PBASUPPORT)
#define IGB_MSIX_IMS (IGB_IC_TXDW | IGB_IC_LSC | \
IGB_IC_RXMISS | IGB_IC_RXDW)
#define IGB_MSIX_EIMS (IGB_EIMS_NOMSIX_OTHER | \
IGB_EIMS_NOMSIX_RXTX0)
#define IGB_MSIX_IVAR0 (IGB_IVAR0_RXQ0_VAL | IGB_IVAR0_TXQ0_VAL)
#define IGB_MSIX_IVARMSC (IGB_IVARMSC_OTHER_VAL)
/*****************************************************************************
* Private Types
*****************************************************************************/
/* Extend default PCI devie type */
struct igb_type_s
{
uint32_t desc_align; /* Descriptor alignment */
uint32_t mta_regs; /* MTA registers */
};
/* IGB private data */
struct igb_driver_s
{
/* This holds the information visible to the NuttX network */
struct netdev_lowerhalf_s dev;
struct work_s work;
/* Packets list */
FAR netpkt_t **tx_pkt;
FAR netpkt_t **rx_pkt;
/* Descriptors */
FAR struct igb_tx_leg_s *tx;
FAR struct igb_rx_leg_s *rx;
size_t tx_now;
size_t tx_done;
size_t rx_now;
/* PCI data */
FAR struct pci_device_s *pcidev;
FAR const struct igb_type_s *type;
int irq;
uint64_t base;
#ifdef CONFIG_NET_MCASTGROUP
/* MTA shadow */
FAR uint32_t *mta;
#endif
};
/*****************************************************************************
* Private Functions Definitions
*****************************************************************************/
/* Helpers */
static uint32_t igb_getreg_mem(FAR struct igb_driver_s *priv,
unsigned int offset);
static void igb_putreg_mem(FAR struct igb_driver_s *priv,
unsigned int offset,
uint32_t value);
#ifdef CONFIG_DEBUG_NET_INFO
static void igb_dump_reg(FAR struct igb_driver_s *priv,
FAR const char *msg, unsigned int offset);
static void igb_dump_mem(FAR struct igb_driver_s *priv, FAR const char *msg);
#endif
/* Rings management */
static void igb_txclean(FAR struct igb_driver_s *priv);
static void igb_rxclean(FAR struct igb_driver_s *priv);
/* Common TX logic */
static int igb_transmit(FAR struct netdev_lowerhalf_s *dev,
FAR netpkt_t *pkt);
/* Interrupt handling */
static FAR netpkt_t *igb_receive(FAR struct netdev_lowerhalf_s *dev);
static void igb_txdone(FAR struct netdev_lowerhalf_s *dev);
static void igb_msix_interrupt(FAR struct igb_driver_s *priv);
static int igb_interrupt(int irq, FAR void *context, FAR void *arg);
/* NuttX callback functions */
static int igb_ifup(FAR struct netdev_lowerhalf_s *dev);
static int igb_ifdown(FAR struct netdev_lowerhalf_s *dev);
#ifdef CONFIG_NET_MCASTGROUP
static uint32_t igb_hashmta(FAR struct igb_driver_s *priv,
FAR const uint8_t *mac);
static int igb_addmac(FAR struct netdev_lowerhalf_s *dev,
FAR const uint8_t *mac);
static int igb_rmmac(FAR struct netdev_lowerhalf_s *dev,
FAR const uint8_t *mac);
#endif
/* Initialization */
static void igb_disable(FAR struct igb_driver_s *priv);
static void igb_enable(FAR struct igb_driver_s *priv);
static int igb_initialize(FAR struct igb_driver_s *priv);
static int igb_probe(FAR struct pci_device_s *dev);
/*****************************************************************************
* Private Data
*****************************************************************************/
/* Intel 82576 (QEMU -device igb) */
static const struct igb_type_s g_igb_82576 =
{
.desc_align = 128,
.mta_regs = 128
};
/* Intel I211 */
static const struct igb_type_s g_igb_i211 =
{
.desc_align = 128,
.mta_regs = 128
};
static const struct pci_device_id_s g_igb_id_table[] =
{
{
PCI_DEVICE(0x8086, 0x10c9),
.driver_data = (uintptr_t)&g_igb_82576
},
{
PCI_DEVICE(0x8086, 0x1539),
.driver_data = (uintptr_t)&g_igb_i211
},
{
PCI_DEVICE(0x8086, 0x1533),
.driver_data = (uintptr_t)&g_igb_i211
},
{ }
};
static struct pci_driver_s g_pci_igb_drv =
{
.id_table = g_igb_id_table,
.probe = igb_probe,
};
static const struct netdev_ops_s g_igb_ops =
{
.ifup = igb_ifup,
.ifdown = igb_ifdown,
.transmit = igb_transmit,
.receive = igb_receive,
#ifdef CONFIG_NET_MCASTGROUP
.addmac = igb_addmac,
.rmmac = igb_rmmac,
#endif
};
/*****************************************************************************
* Private Functions
*****************************************************************************/
/*****************************************************************************
* Name: igb_getreg_mem
*****************************************************************************/
static uint32_t igb_getreg_mem(FAR struct igb_driver_s *priv,
unsigned int offset)
{
uintptr_t addr = priv->base + offset;
return *((FAR volatile uint32_t *)addr);
}
/*****************************************************************************
* Name: igb_putreg_mem
*****************************************************************************/
static void igb_putreg_mem(FAR struct igb_driver_s *priv,
unsigned int offset,
uint32_t value)
{
uintptr_t addr = priv->base + offset;
*((FAR volatile uint32_t *)addr) = value;
}
#ifdef CONFIG_DEBUG_NET_INFO
/*****************************************************************************
* Name: igb_dump_reg
*****************************************************************************/
static void igb_dump_reg(FAR struct igb_driver_s *priv,
FAR const char *msg, unsigned int offset)
{
ninfo("\t%s:\t\t0x%" PRIx32 "\n", msg, igb_getreg_mem(priv, offset));
}
/*****************************************************************************
* Name: igb_dump_mem
*****************************************************************************/
static void igb_dump_mem(FAR struct igb_driver_s *priv, FAR const char *msg)
{
ninfo("\nDump: %s\n", msg);
ninfo("General registers:\n");
igb_dump_reg(priv, "CTRL", IGB_CTRL);
igb_dump_reg(priv, "STATUS", IGB_STATUS);
igb_dump_reg(priv, "CTRLEXT", IGB_CTRLEXT);
igb_dump_reg(priv, "MDIC", IGB_MDIC);
igb_dump_reg(priv, "SERDESCTL", IGB_SERDESCTL);
igb_dump_reg(priv, "FCAL", IGB_FCAL);
igb_dump_reg(priv, "FCAH", IGB_FCAH);
igb_dump_reg(priv, "FCT", IGB_FCT);
igb_dump_reg(priv, "CONNSW", IGB_CONNSW);
igb_dump_reg(priv, "VET", IGB_VET);
igb_dump_reg(priv, "FCTTV", IGB_FCTTV);
ninfo("Interrupt registers:\n");
igb_dump_reg(priv, "ICS", IGB_ICS);
igb_dump_reg(priv, "IMS", IGB_IMS);
igb_dump_reg(priv, "IAM", IGB_IAM);
igb_dump_reg(priv, "EICS", IGB_EICS);
igb_dump_reg(priv, "EIMS", IGB_EIMS);
igb_dump_reg(priv, "EIAM", IGB_EIAM);
igb_dump_reg(priv, "EIAC", IGB_EIAC);
igb_dump_reg(priv, "IVAR0", IGB_IVAR0);
igb_dump_reg(priv, "IVARMSC", IGB_IVARMSC);
igb_dump_reg(priv, "EITR0", IGB_EITR0);
igb_dump_reg(priv, "GPIE", IGB_GPIE);
igb_dump_reg(priv, "PBACL", IGB_PBACL);
ninfo("Receive registers:\n");
igb_dump_reg(priv, "RCTL", IGB_RCTL);
igb_dump_reg(priv, "PSRCTL", IGB_PSRCTL);
igb_dump_reg(priv, "FCRTL0", IGB_FCRTL0);
igb_dump_reg(priv, "FCRTH0", IGB_FCRTH0);
igb_dump_reg(priv, "RXPBSIZE", IGB_RXPBSIZE);
igb_dump_reg(priv, "FCRTV", IGB_FCRTV);
igb_dump_reg(priv, "RDBAL0", IGB_RDBAL0);
igb_dump_reg(priv, "RDBAH0", IGB_RDBAH0);
igb_dump_reg(priv, "RDLEN0", IGB_RDLEN0);
igb_dump_reg(priv, "SRRCTL0", IGB_SRRCTL0);
igb_dump_reg(priv, "RDH0", IGB_RDH0);
igb_dump_reg(priv, "RDT0", IGB_RDT0);
igb_dump_reg(priv, "RXDCTL0", IGB_RXDCTL0);
igb_dump_reg(priv, "RXCTL0", IGB_RXCTL0);
igb_dump_reg(priv, "RXCSUM", IGB_RXCSUM);
igb_dump_reg(priv, "RLPML", IGB_RLPML);
igb_dump_reg(priv, "RFCTL", IGB_RFCTL);
igb_dump_reg(priv, "MTA", IGB_MTA);
igb_dump_reg(priv, "RAL", IGB_RAL);
igb_dump_reg(priv, "RAH", IGB_RAH);
ninfo("Transmit registers:\n");
igb_dump_reg(priv, "TXPBSIZE", IGB_TXPBSIZE);
igb_dump_reg(priv, "PBTWAC", IGB_PBTWAC);
igb_dump_reg(priv, "TCTL", IGB_TCTL);
igb_dump_reg(priv, "TCTLEXT", IGB_TCTLEXT);
igb_dump_reg(priv, "TIPG", IGB_TIPG);
igb_dump_reg(priv, "RETXCTL", IGB_RETXCTL);
igb_dump_reg(priv, "DTXCTL", IGB_DTXCTL);
igb_dump_reg(priv, "TDBAL0", IGB_TDBAL0);
igb_dump_reg(priv, "TDBAH0", IGB_TDBAH0);
igb_dump_reg(priv, "TDLEN0", IGB_TDLEN0);
igb_dump_reg(priv, "TDH0", IGB_TDH0);
igb_dump_reg(priv, "TDT0", IGB_TDT0);
igb_dump_reg(priv, "TXDCTL0", IGB_TXDCTL0);
igb_dump_reg(priv, "TXCTL0", IGB_TXCTL0);
igb_dump_reg(priv, "TDWBAL0", IGB_TDWBAL0);
igb_dump_reg(priv, "TDWBAH0", IGB_TDWBAH0);
ninfo("Statistic registers:\n");
igb_dump_reg(priv, "CRCERRS", IGB_CRCERRS);
igb_dump_reg(priv, "ALGNERRC", IGB_ALGNERRC);
igb_dump_reg(priv, "RXERRC", IGB_RXERRC);
igb_dump_reg(priv, "MPC", IGB_MPC);
igb_dump_reg(priv, "SCC", IGB_SCC);
igb_dump_reg(priv, "ECOL", IGB_ECOL);
igb_dump_reg(priv, "MCC", IGB_MCC);
igb_dump_reg(priv, "LATECOL", IGB_LATECOL);
igb_dump_reg(priv, "COLC", IGB_COLC);
igb_dump_reg(priv, "DC", IGB_DC);
igb_dump_reg(priv, "TNCRS", IGB_TNCRS);
igb_dump_reg(priv, "RLEC", IGB_RLEC);
igb_dump_reg(priv, "XONRXC", IGB_XONRXC);
igb_dump_reg(priv, "XONTXC", IGB_XONTXC);
igb_dump_reg(priv, "XOFFRXC", IGB_XOFFRXC);
igb_dump_reg(priv, "XOFFTXC", IGB_XOFFTXC);
igb_dump_reg(priv, "FCRUC", IGB_FCRUC);
igb_dump_reg(priv, "PRC64", IGB_PRC64);
igb_dump_reg(priv, "PRC127", IGB_PRC127);
igb_dump_reg(priv, "PRC255", IGB_PRC255);
igb_dump_reg(priv, "PRC511", IGB_PRC511);
igb_dump_reg(priv, "PRC1023", IGB_PRC1023);
igb_dump_reg(priv, "PRC1522", IGB_PRC1522);
igb_dump_reg(priv, "GPRC", IGB_GPRC);
igb_dump_reg(priv, "BPRC", IGB_BPRC);
igb_dump_reg(priv, "MPRC", IGB_MPRC);
igb_dump_reg(priv, "GPTC", IGB_GPTC);
igb_dump_reg(priv, "GORCL", IGB_GORCL);
igb_dump_reg(priv, "GORCH", IGB_GORCH);
igb_dump_reg(priv, "GOTCL", IGB_GOTCL);
igb_dump_reg(priv, "GOTCH", IGB_GOTCH);
igb_dump_reg(priv, "RNBC", IGB_RNBC);
igb_dump_reg(priv, "RUC", IGB_RUC);
igb_dump_reg(priv, "RFC", IGB_RFC);
igb_dump_reg(priv, "ROC", IGB_ROC);
igb_dump_reg(priv, "RJC", IGB_RJC);
igb_dump_reg(priv, "MNGPRC", IGB_MNGPRC);
igb_dump_reg(priv, "MPDC", IGB_MPDC);
igb_dump_reg(priv, "TORL", IGB_TORL);
igb_dump_reg(priv, "TORH", IGB_TORH);
igb_dump_reg(priv, "TPR", IGB_TPR);
igb_dump_reg(priv, "TPT", IGB_TPT);
igb_dump_reg(priv, "PTC64", IGB_PTC64);
igb_dump_reg(priv, "PTC127", IGB_PTC127);
igb_dump_reg(priv, "PTC255", IGB_PTC255);
igb_dump_reg(priv, "PTC511", IGB_PTC511);
igb_dump_reg(priv, "PTC1023", IGB_PTC1023);
igb_dump_reg(priv, "PTC1522", IGB_PTC1522);
igb_dump_reg(priv, "BPTC", IGB_BPTC);
ninfo("Diagnostic registers:\n");
igb_dump_reg(priv, "RDFT", IGB_RDFT);
igb_dump_reg(priv, "RDFHS", IGB_RDFHS);
igb_dump_reg(priv, "RDFTS", IGB_RDFTS);
igb_dump_reg(priv, "RDFPC", IGB_RDFPC);
igb_dump_reg(priv, "RPBECCSTS", IGB_RPBECCSTS);
igb_dump_reg(priv, "TPBECCSTS", IGB_TPBECCSTS);
igb_dump_reg(priv, "FCSTS0", IGB_FCSTS0);
igb_dump_reg(priv, "RDHESTS", IGB_RDHESTS);
igb_dump_reg(priv, "TDHESTS", IGB_TDHESTS);
igb_dump_reg(priv, "TDFH", IGB_TDFH);
igb_dump_reg(priv, "TDFT", IGB_TDFT);
igb_dump_reg(priv, "TDFHS", IGB_TDFHS);
igb_dump_reg(priv, "TDFTS", IGB_TDFTS);
igb_dump_reg(priv, "TDFPC", IGB_TDFPC);
igb_dump_reg(priv, "TDHMP", IGB_TDHMP);
igb_dump_reg(priv, "CIRC", IGB_CIRC);
igb_dump_reg(priv, "TXBDC", IGB_TXBDC);
igb_dump_reg(priv, "TXIDLE", IGB_TXIDLE);
igb_dump_reg(priv, "RXBDC", IGB_RXBDC);
igb_dump_reg(priv, "RXIDLE", IGB_RXIDLE);
}
#endif
/*****************************************************************************
* Name: igb_txclean
*
* Description:
* Clean transmission ring
*
* Input Parameters:
* priv - Reference to the driver state structure
*
* Returned Value:
* None
*
* Assumption:
* This function can be called only after card reset and when TX is disabled
*
*****************************************************************************/
static void igb_txclean(FAR struct igb_driver_s *priv)
{
FAR struct netdev_lowerhalf_s *netdev = &priv->dev;
/* Reset ring */
igb_putreg_mem(priv, IGB_TDH0, 0);
igb_putreg_mem(priv, IGB_TDT0, 0);
/* Free any pending TX */
while (priv->tx_now != priv->tx_done)
{
/* Free net packet */
netpkt_free(netdev, priv->tx_pkt[priv->tx_done], NETPKT_TX);
/* Next descriptor */
priv->tx_done = (priv->tx_done + 1) % IGB_TX_DESC;
}
priv->tx_now = 0;
priv->tx_done = 0;
}
/*****************************************************************************
* Name: igb_rxclean
*
* Description:
* Clean receive ring
*
* Input Parameters:
* priv - Reference to the driver state structure
*
* Returned Value:
* None
*
* Assumption:
* This function can be called only after card reset and when RX is disabled
*
*****************************************************************************/
static void igb_rxclean(FAR struct igb_driver_s *priv)
{
priv->rx_now = 0;
igb_putreg_mem(priv, IGB_RDH0, 0);
igb_putreg_mem(priv, IGB_RDT0, IGB_RX_DESC - 1);
}
/*****************************************************************************
* Name: igb_transmit
*
* Description:
* Start hardware transmission. Called either from the txdone interrupt
* handling or from watchdog based polling.
*
* Input Parameters:
* priv - Reference to the driver state structure
*
* Returned Value:
* None
*
* Assumptions:
* The network is locked.
*
*****************************************************************************/
static int igb_transmit(FAR struct netdev_lowerhalf_s *dev,
FAR netpkt_t *pkt)
{
FAR struct igb_driver_s *priv = (FAR struct igb_driver_s *)dev;
uint64_t pa = 0;
int desc = priv->tx_now;
size_t len = netpkt_getdatalen(dev, pkt);
size_t tx_next = (priv->tx_now + 1) % IGB_TX_DESC;
ninfo("transmit\n");
/* Check the send length */
if (len > IGB_PKTBUF_SIZE)
{
nerr("net transmit buffer too large\n");
return -EINVAL;
}
if (!IFF_IS_RUNNING(dev->netdev.d_flags))
{
return -ENETDOWN;
}
/* Drop packet if ring full */
if (tx_next == priv->tx_done)
{
return -ENOMEM;
}
/* Store TX packet reference */
priv->tx_pkt[priv->tx_now] = pkt;
/* Prepare next TX descriptor */
priv->tx_now = tx_next;
/* Setup TX descriptor */
pa = up_addrenv_va_to_pa(netpkt_getdata(dev, pkt));
priv->tx[desc].addr = pa;
priv->tx[desc].len = len;
priv->tx[desc].cmd = (IGB_TDESC_CMD_EOP | IGB_TDESC_CMD_IFCS |
IGB_TDESC_CMD_RS);
priv->tx[desc].cso = 0;
priv->tx[desc].status = 0;
UP_DSB();
/* Update TX tail */
igb_putreg_mem(priv, IGB_TDT0, priv->tx_now);
ninfodumpbuffer("Transmitted:", netpkt_getdata(dev, pkt), len);
return OK;
}
/*****************************************************************************
* Name: igb_receive
*
* Description:
* An interrupt was received indicating the availability of a new RX packet
*
* Input Parameters:
* priv - Reference to the driver state structure
*
* Returned Value:
* None
*
* Assumptions:
* The network is locked.
*
*****************************************************************************/
static FAR netpkt_t *igb_receive(FAR struct netdev_lowerhalf_s *dev)
{
FAR struct igb_driver_s *priv = (FAR struct igb_driver_s *)dev;
FAR netpkt_t *pkt = NULL;
FAR struct igb_rx_leg_s *rx = NULL;
int desc = 0;
desc = priv->rx_now;
/* Get RX descriptor and RX packet */
rx = &priv->rx[desc];
pkt = priv->rx_pkt[desc];
/* Check if descriptor done */
if (!(rx->status & IGB_RDESC_STATUS_DD))
{
return NULL;
}
/* Next descriptor */
priv->rx_now = (priv->rx_now + 1) % IGB_RX_DESC;
/* Allocate new rx packet */
priv->rx_pkt[desc] = netpkt_alloc(dev, NETPKT_RX);
if (priv->rx_pkt[desc] == NULL)
{
nerr("alloc pkt_new failed\n");
PANIC();
}
/* Set packet length */
netpkt_setdatalen(dev, pkt, rx->len);
/* Store new packet in RX descriptor ring */
rx->addr = up_addrenv_va_to_pa(
netpkt_getdata(dev, priv->rx_pkt[desc]));
rx->len = 0;
rx->status = 0;
/* Update RX tail */
igb_putreg_mem(priv, IGB_RDT0, desc);
/* Handle errors */
if (rx->errors)
{
nerr("RX error reported (%"PRIu8")\n", rx->errors);
NETDEV_RXERRORS(&priv->dev.netdev);
netpkt_free(dev, pkt, NETPKT_RX);
return NULL;
}
return pkt;
}
/*****************************************************************************
* Name: igb_txdone
*
* Description:
* An interrupt was received indicating that the last TX packet(s) is done
*
* Input Parameters:
* priv - Reference to the driver state structure
*
* Returned Value:
* None
*
* Assumptions:
* The network is locked.
*
*****************************************************************************/
static void igb_txdone(FAR struct netdev_lowerhalf_s *dev)
{
FAR struct igb_driver_s *priv = (FAR struct igb_driver_s *)dev;
while (priv->tx_now != priv->tx_done)
{
if (priv->tx[priv->tx_done].status == 0)
{
break;
}
if (!(priv->tx[priv->tx_done].status & IGB_TDESC_STATUS_DD))
{
nerr("tx failed: 0x%" PRIx32 "\n", priv->tx[priv->tx_done].status);
NETDEV_TXERRORS(&priv->dev.netdev);
}
/* Free net packet */
netpkt_free(dev, priv->tx_pkt[priv->tx_done], NETPKT_TX);
/* Next descriptor */
priv->tx_done = (priv->tx_done + 1) % IGB_TX_DESC;
}
netdev_lower_txdone(dev);
}
/*****************************************************************************
* Name: igb_link_work
*
* Description:
* Handle link status change.
*
* Input Parameters:
* arg - Reference to the lover half driver structure (cast to void *)
*
* Returned Value:
* None
*
*****************************************************************************/
static void igb_link_work(FAR void *arg)
{
FAR struct igb_driver_s *priv = arg;
uint32_t tmp;
tmp = igb_getreg_mem(priv, IGB_STATUS);
if (tmp & IGB_STATUS_LU)
{
ninfo("Link up, status = 0x%x\n", tmp);
netdev_lower_carrier_on(&priv->dev);
}
else
{
ninfo("Link down\n");
netdev_lower_carrier_off(&priv->dev);
}
}
/*****************************************************************************
* Name: igb_misx_interrupt
*
* Description:
* Perform MSI-X interrupt work
*
* Input Parameters:
* arg - The argument passed when work_queue() was called.
*
* Returned Value:
* OK on success
*
* Assumptions:
* Runs on a worker thread.
*
*****************************************************************************/
static void igb_msix_interrupt(FAR struct igb_driver_s *priv)
{
uint32_t icr = 0;
uint32_t eicr = 0;
/* Get interrupts */
icr = igb_getreg_mem(priv, IGB_ICR);
eicr = igb_getreg_mem(priv, IGB_EICR);
ninfo("eicr = 0x%" PRIx32 " icr = 0x%" PRIx32 "\n", eicr, icr);
if (icr == 0)
{
/* Ignore spurious interrupts */
return;
}
/* Receiver Descriptor Write Back */
if (icr & IGB_IC_RXDW)
{
netdev_lower_rxready(&priv->dev);
}
/* Link Status Change */
if (icr & IGB_IC_LSC)
{
if (work_available(&priv->work))
{
/* Schedule to work queue because netdev_lower_carrier_xxx API
* can't be used in interrupt context
*/
work_queue(LPWORK, &priv->work, igb_link_work, priv, 0);
}
}
/* Receiver Miss */
if (icr & IGB_IC_RXMISS)
{
nerr("Receiver Miss\n");
netdev_lower_rxready(&priv->dev);
}
/* Transmit Descriptor Written Back */
if (icr & IGB_IC_TXDW)
{
igb_txdone(&priv->dev);
}
}
/*****************************************************************************
* Name: igb_interrupt
*
* Description:
* Hardware interrupt handler
*
* Input Parameters:
* irq - Number of the IRQ that generated the interrupt
* context - Interrupt register state save info (architecture-specific)
*
* Returned Value:
* OK on success
*
* Assumptions:
* Runs in the context of a the Ethernet interrupt handler. Local
* interrupts are disabled by the interrupt logic.
*
*****************************************************************************/
static int igb_interrupt(int irq, FAR void *context, FAR void *arg)
{
FAR struct igb_driver_s *priv = (FAR struct igb_driver_s *)arg;
DEBUGASSERT(priv != NULL);
ninfo("interrupt!\n");
/* Schedule to perform the interrupt processing on the worker thread. */
igb_msix_interrupt(priv);
return OK;
}
/*****************************************************************************
* Name: igb_ifup
*
* Description:
* NuttX Callback: Bring up the Ethernet interface when an IP address is
* provided
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
*
* Returned Value:
* None
*
* Assumptions:
* The network is locked.
*
*****************************************************************************/
static int igb_ifup(FAR struct netdev_lowerhalf_s *dev)
{
FAR struct igb_driver_s *priv = (FAR struct igb_driver_s *)dev;
irqstate_t flags;
#ifdef CONFIG_NET_IPv4
ninfo("Bringing up: %u.%u.%u.%u\n",
ip4_addr1(dev->netdev.d_ipaddr), ip4_addr2(dev->netdev.d_ipaddr),
ip4_addr3(dev->netdev.d_ipaddr), ip4_addr4(dev->netdev.d_ipaddr));
#endif
#ifdef CONFIG_NET_IPv6
ninfo("Bringing up: %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
dev->netdev.d_ipv6addr[0], dev->netdev.d_ipv6addr[1],
dev->netdev.d_ipv6addr[2], dev->netdev.d_ipv6addr[3],
dev->netdev.d_ipv6addr[4], dev->netdev.d_ipv6addr[5],
dev->netdev.d_ipv6addr[6], dev->netdev.d_ipv6addr[7]);
#endif
flags = enter_critical_section();
/* Enable the Ethernet */
igb_enable(priv);
leave_critical_section(flags);
/* Update link status in case link status interrupt is missing */
igb_link_work(priv);
return OK;
}
/*****************************************************************************
* Name: igb_ifdown
*
* Description:
* NuttX Callback: Stop the interface.
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
*
* Returned Value:
* None
*
* Assumptions:
* The network is locked.
*
*****************************************************************************/
static int igb_ifdown(FAR struct netdev_lowerhalf_s *dev)
{
FAR struct igb_driver_s *priv = (FAR struct igb_driver_s *)dev;
irqstate_t flags;
flags = enter_critical_section();
/* Put the EMAC in its reset, non-operational state. This should be
* a known configuration that will guarantee the igb_ifup() always
* successfully brings the interface back up.
*/
igb_disable(priv);
leave_critical_section(flags);
return OK;
}
#ifdef CONFIG_NET_MCASTGROUP
/*****************************************************************************
* Name: igb_hashmta
*
* Note: This logic is based on freeBSD igb implementation
*
*****************************************************************************/
static uint32_t igb_hashmta(FAR struct igb_driver_s *priv,
FAR const uint8_t *mac)
{
uint32_t hash_mask = 0;
uint8_t bit_shift = 0;
/* Register count multiplied by bits per register */
hash_mask = (priv->type->mta_regs * 32) - 1;
/* For a mc_filter_type of 0, bit_shift is the number of left-shifts
* where 0xFF would still fall within the hash mask.
*/
while (hash_mask >> bit_shift != 0xff)
{
bit_shift++;
}
/* bit_shift += 0 because we have MO set to 0 */
return hash_mask & ((mac[4] >> (8 - bit_shift)) | (mac[5] << bit_shift));
}
/*****************************************************************************
* Name: igb_addmac
*
* Description:
* NuttX Callback: Add the specified MAC address to the hardware multicast
* address filtering
*
* Parameters:
* dev - Reference to the NuttX driver state structure
* mac - The MAC address to be added
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
*****************************************************************************/
static int igb_addmac(FAR struct netdev_lowerhalf_s *dev,
FAR const uint8_t *mac)
{
FAR struct igb_driver_s *priv = (FAR struct igb_driver_s *)dev;
uint16_t hash = 0;
uint8_t row = 0;
uint8_t bit = 0;
int i = 0;
hash = igb_hashmta(priv, mac);
bit = hash & 31;
row = (hash >> 5) & (priv->type->mta_regs - 1);
/* Bits 4:0 indicate bit in row word */
priv->mta[row] |= (1 << bit);
/* Replace the entire MTA */
for (i = priv->type->mta_regs - 1; i >= 0; i--)
{
igb_putreg_mem(priv, IGB_MTA + (i << 2), priv->mta[i]);
}
return OK;
}
/*****************************************************************************
* Name: igb_rmmac
*
* Description:
* NuttX Callback: Remove the specified MAC address from the hardware
* multicast address filtering
*
* Parameters:
* dev - Reference to the NuttX driver state structure
* mac - The MAC address to be removed
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
*****************************************************************************/
static int igb_rmmac(FAR struct netdev_lowerhalf_s *dev,
FAR const uint8_t *mac)
{
FAR struct igb_driver_s *priv = (FAR struct igb_driver_s *)dev;
uint16_t hash = 0;
uint8_t row = 0;
uint8_t bit = 0;
int i = 0;
hash = igb_hashmta(priv, mac);
bit = hash & 31;
row = (hash >> 5) & (priv->type->mta_regs - 1);
/* Bits 4:0 indicate bit in row word */
priv->mta[row] &= ~(1 << bit);
/* Replace the entire MTA */
for (i = priv->type->mta_regs - 1; i >= 0; i--)
{
igb_putreg_mem(priv, IGB_MTA + (i << 2), priv->mta[i]);
}
return OK;
}
#endif /* CONFIG_NET_MCASTGROUP */
/*****************************************************************************
* Name: igb_disable
*
* Description:
* Reset device to known state.
*
*****************************************************************************/
static void igb_disable(FAR struct igb_driver_s *priv)
{
uint32_t regval;
int i = 0;
/* Disable interrupts */
igb_putreg_mem(priv, IGB_EIMC, IGB_MSIX_EIMS);
igb_putreg_mem(priv, IGB_IMC, IGB_MSIX_IMS);
up_disable_irq(priv->irq);
/* Disable Transmitter */
regval = igb_getreg_mem(priv, IGB_TCTL);
regval &= ~IGB_TCTL_EN;
igb_putreg_mem(priv, IGB_TCTL, regval);
/* Disable Receiver */
igb_putreg_mem(priv, IGB_RCTL, 0);
/* We have to reset device, otherwise writing to RDH and THD corrupts
* the device state.
*/
igb_putreg_mem(priv, IGB_CTRL, IGB_CTRL_RST);
/* Reset Tx tail */
igb_txclean(priv);
/* Reset Rx tail */
igb_rxclean(priv);
/* Free RX packets */
for (i = 0; i < IGB_RX_DESC; i += 1)
{
netpkt_free(&priv->dev, priv->rx_pkt[i], NETPKT_RX);
}
}
/*****************************************************************************
* Name: igb_phy_reset
*
* Description:
* Reset PHY
*
*****************************************************************************/
static void igb_phy_reset(FAR struct igb_driver_s *priv)
{
uint32_t regval = 0;
regval = igb_getreg_mem(priv, IGB_CTRL);
igb_putreg_mem(priv, IGB_CTRL, regval | IGB_CTRL_PHYRST);
up_udelay(100);
igb_putreg_mem(priv, IGB_CTRL, regval);
up_udelay(100);
}
/*****************************************************************************
* Name: igb_enable
*
* Description:
* Enable device.
*
*****************************************************************************/
static void igb_enable(FAR struct igb_driver_s *priv)
{
FAR struct netdev_lowerhalf_s *dev = (FAR struct netdev_lowerhalf_s *)priv;
uint64_t pa = 0;
uint32_t regval = 0;
int i = 0;
/* Reset PHY */
igb_phy_reset(priv);
/* Reset Multicast Table Array */
for (i = 0; i < priv->type->mta_regs; i++)
{
igb_putreg_mem(priv, IGB_MTA + (i << 2), 0);
}
/* Allocate RX packets */
for (i = 0; i < IGB_RX_DESC; i += 1)
{
priv->rx_pkt[i] = netpkt_alloc(dev, NETPKT_RX);
if (priv->rx_pkt[i] == NULL)
{
nerr("alloc rx_pkt failed\n");
PANIC();
}
/* Configure RX descriptor */
priv->rx[i].addr = up_addrenv_va_to_pa(
netpkt_getdata(dev, priv->rx_pkt[i]));
priv->rx[i].len = 0;
priv->rx[i].status = 0;
}
/* Setup TX descriptor */
/* The address passed to the NIC must be physical */
pa = up_addrenv_va_to_pa(priv->tx);
regval = (uint32_t)pa;
igb_putreg_mem(priv, IGB_TDBAL0, regval);
regval = (uint32_t)(pa >> 32);
igb_putreg_mem(priv, IGB_TDBAH0, regval);
regval = IGB_TX_DESC * sizeof(struct igb_tx_leg_s);
igb_putreg_mem(priv, IGB_TDLEN0, regval);
/* Reset TX tail */
igb_txclean(priv);
/* Setup RX descriptor */
/* The address passed to the NIC must be physical */
pa = up_addrenv_va_to_pa(priv->rx);
regval = (uint32_t)pa;
igb_putreg_mem(priv, IGB_RDBAL0, regval);
regval = (uint32_t)(pa >> 32);
igb_putreg_mem(priv, IGB_RDBAH0, regval);
regval = IGB_RX_DESC * sizeof(struct igb_rx_leg_s);
igb_putreg_mem(priv, IGB_RDLEN0, regval);
/* Enable interrupts */
igb_putreg_mem(priv, IGB_EIMS, IGB_MSIX_EIMS);
igb_putreg_mem(priv, IGB_IMS, IGB_MSIX_IMS);
up_enable_irq(priv->irq);
/* Set link up */
igb_putreg_mem(priv, IGB_CTRL, IGB_CTRL_SLU);
/* Setup and enable Transmitter */
regval = igb_getreg_mem(priv, IGB_TCTL);
regval |= IGB_TCTL_EN | IGB_TCTL_PSP;
igb_putreg_mem(priv, IGB_TCTL, regval);
/* Setup and enable Receiver */
regval = (IGB_RCTL_EN | IGB_RCTL_MPE |
(IGB_RCTL_BSIZE << IGB_RCTL_BSIZE_SHIFT));
#ifdef CONFIG_NET_PROMISCUOUS
regval |= IGB_RCTL_UPE | IGB_RCTL_MPE;
#endif
igb_putreg_mem(priv, IGB_RCTL, regval);
/* Enable TX queeu */
regval = igb_getreg_mem(priv, IGB_TXDCTL0);
regval |= IGB_TXDCTL_ENABLE;
igb_putreg_mem(priv, IGB_TXDCTL0, regval);
/* Enable RX queue */
regval = igb_getreg_mem(priv, IGB_RXDCTL0);
regval |= IGB_RXDCTL_ENABLE;
igb_putreg_mem(priv, IGB_RXDCTL0, regval);
/* Reset RX tail - after queue is enabled */
igb_rxclean(priv);
#ifdef CONFIG_DEBUG_NET_INFO
/* Dump memory */
igb_dump_mem(priv, "enabled");
#endif
}
/*****************************************************************************
* Name: igb_initialize
*
* Description:
* Initialize device
*
*****************************************************************************/
static int igb_initialize(FAR struct igb_driver_s *priv)
{
uint32_t regval = 0;
uint64_t mac = 0;
int ret = OK;
/* Allocate MSI */
ret = pci_alloc_irq(priv->pcidev, &priv->irq, 1);
if (ret != 1)
{
nerr("Failed to allocate MSI %d\n", ret);
return ret;
}
/* Attach IRQ */
irq_attach(priv->irq, igb_interrupt, priv);
/* Connect MSI */
ret = pci_connect_irq(priv->pcidev, &priv->irq, 1);
if (ret != OK)
{
nerr("Failed to connect MSI %d\n", ret);
pci_release_irq(priv->pcidev, &priv->irq, 1);
return -ENOTSUP;
}
/* Clear previous Extended Interrupt Mask */
igb_putreg_mem(priv, IGB_EIMC, 0xffffffff);
igb_putreg_mem(priv, IGB_IMC, 0xffffffff);
/* Configure MSI-X */
igb_putreg_mem(priv, IGB_IVAR0, IGB_MSIX_IVAR0);
igb_putreg_mem(priv, IGB_IVARMSC, IGB_MSIX_IVARMSC);
/* Enable MSI-X Single Vector */
igb_putreg_mem(priv, IGB_GPIE, IGB_GPIE_MSIX_SINGLE);
igb_putreg_mem(priv, IGB_EIMS, IGB_MSIX_EIMS);
/* Configure Other causes */
igb_putreg_mem(priv, IGB_IMS, IGB_MSIX_IMS);
/* Configure Interrupt Throttle */
igb_putreg_mem(priv, IGB_EITR0, (CONFIG_NET_IGB_INT_INTERVAL << 2));
/* Get MAC if valid */
regval = igb_getreg_mem(priv, IGB_RAH);
if (regval & IGB_RAH_AV)
{
mac = ((uint64_t)regval & IGB_RAH_RAH_MASK) << 32;
mac |= igb_getreg_mem(priv, IGB_RAL);
memcpy(&priv->dev.netdev.d_mac.ether, &mac, sizeof(struct ether_addr));
}
else
{
nwarn("Receive Address not valid!\n");
}
return OK;
}
/*****************************************************************************
* Name: igb_probe
*
* Description:
* Initialize device
*
*****************************************************************************/
static int igb_probe(FAR struct pci_device_s *dev)
{
FAR const struct igb_type_s *type = NULL;
FAR struct igb_driver_s *priv = NULL;
FAR struct netdev_lowerhalf_s *netdev = NULL;
int ret = -ENOMEM;
/* Get type data associated with this PCI device card */
type = (FAR const struct igb_type_s *)dev->id->driver_data;
/* Not found private data */
if (type == NULL)
{
return -ENODEV;
}
/* Allocate the interface structure */
priv = kmm_zalloc(sizeof(*priv));
if (priv == NULL)
{
return ret;
}
priv->pcidev = dev;
/* Allocate TX descriptors */
priv->tx = kmm_memalign(type->desc_align,
IGB_TX_DESC * sizeof(struct igb_tx_leg_s));
if (priv->tx == NULL)
{
nerr("alloc tx failed %d\n", errno);
goto errout;
}
/* Allocate RX descriptors */
priv->rx = kmm_memalign(type->desc_align,
IGB_RX_DESC * sizeof(struct igb_rx_leg_s));
if (priv->rx == NULL)
{
nerr("alloc rx failed %d\n", errno);
goto errout;
}
/* Allocate TX packet pointer array */
priv->tx_pkt = kmm_zalloc(IGB_TX_DESC * sizeof(netpkt_t *));
if (priv->tx_pkt == NULL)
{
nerr("alloc tx_pkt failed\n");
goto errout;
}
/* Allocate RX packet pointer array */
priv->rx_pkt = kmm_zalloc(IGB_RX_DESC * sizeof(netpkt_t *));
if (priv->rx_pkt == NULL)
{
nerr("alloc rx_pkt failed\n");
goto errout;
}
#ifdef CONFIG_NET_MCASTGROUP
/* Allocate MTA shadow */
priv->mta = kmm_zalloc(type->mta_regs);
if (priv->mta == NULL)
{
nerr("alloc mta failed\n");
goto errout;
}
#endif
/* Get devices */
netdev = &priv->dev;
priv->type = type;
pci_set_master(dev);
pciinfo("Enabled bus mastering\n");
pci_enable_device(dev);
pciinfo("Enabled memory resources\n");
/* If the BAR is MMIO then it must be mapped */
priv->base = (uintptr_t)pci_map_bar(dev, IGB_MMIO_BAR);
if (!priv->base)
{
pcierr("Not found MMIO control bar\n");
goto errout;
}
/* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */
ret = igb_initialize(priv);
if (ret != OK)
{
nerr("igb_initialize failed %d\n", ret);
return ret;
}
/* Register the network device */
netdev->quota[NETPKT_TX] = IGB_TX_QUOTA;
netdev->quota[NETPKT_RX] = IGB_RX_QUOTA;
netdev->ops = &g_igb_ops;
return netdev_lower_register(netdev, NET_LL_ETHERNET);
errout:
kmm_free(priv->tx);
kmm_free(priv->rx);
#ifdef CONFIG_NET_MCASTGROUP
kmm_free(priv->mta);
#endif
kmm_free(priv);
return ret;
}
/*****************************************************************************
* Public Functions
*****************************************************************************/
/*****************************************************************************
* Name: pci_igb_init
*
* Description:
* Register a pci driver
*
*****************************************************************************/
int pci_igb_init(void)
{
return pci_register_driver(&g_pci_igb_drv);
}