/***************************************************************************** * drivers/net/igc.c * * 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 #include #include #include #include #include #include #include #include #include #include #include "igc.h" /***************************************************************************** * Pre-processor Definitions *****************************************************************************/ /* Packet buffer size */ #define IGC_PKTBUF_SIZE 2048 #define IGC_RCTL_BSIZE IGC_RCTL_BSIZE_2048 /* TX and RX descriptors */ #define IGC_TX_DESC 256 #define IGC_RX_DESC 256 /* 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 * allways possible to allocate the new RX packet in the recevier logic. * It's hard to tell how many spare buffers is needed, for now it's set to 8. */ #define IGC_TX_QUOTA IGC_TX_DESC #define IGC_RX_QUOTA (IGC_RX_DESC + CONFIG_NET_IGC_RXSPARE) /* NOTE: CONFIG_IOB_ALIGNMENT must match system D-CACHE line size */ #if CONFIG_IOB_NBUFFERS < IGC_RX_QUOTA + IGC_TX_QUOTA # error CONFIG_IOB_NBUFFERS must be > (IGC_RX_QUOTA + IGC_TX_QUOTA) #endif #if CONFIG_IOB_BUFSIZE < IGC_PKTBUF_SIZE # error CONFIG_IOB_BUFSIZE must be > IGC_PKTBUF_SIZE #endif /* PCI BARs */ #define IGC_MMIO_BAR 0 #define IGC_FLASH_BAR 1 #define IGC_IO_BAR 2 #define IGC_MSIX_BAR 3 /* Minimum Inter-interrupt Interval in 1 us increments */ #define IGC_INTERRUPT_INTERVAL 100 /* For MSI-X we allocate all interrupts to MSI-X vector 0 */ #define IGC_GPIE_MSIX_SINGLE (IGC_GPIE_NSICR | IGC_GPIE_EIAME | \ IGC_GPIE_PBASUPPORT) #define IGC_MSIX_IMS (IGC_IC_TXDW | IGC_IC_LSC | \ IGC_IC_RXMISS | IGC_IC_RXDW) #define IGC_MSIX_EIMS (IGC_EIMS_NOMSIX_OTHER | \ IGC_EIMS_NOMSIX_RXTX0) #define IGC_MSIX_IVAR0 (IGC_IVAR0_RXQ0_VAL | IGC_IVAR0_TXQ0_VAL) #define IGC_MSIX_IVARMSC (IGC_IVARMSC_OTHER_VAL) /***************************************************************************** * Private Types *****************************************************************************/ /* Extend default PCI devie type */ struct igc_type_s { uint32_t desc_align; /* Descriptor alignment */ uint32_t mta_regs; /* MTA registers */ }; /* IGC private data */ struct igc_driver_s { /* This holds the information visible to the NuttX network */ struct netdev_lowerhalf_s dev; /* Driver state */ bool bifup; /* Packets list */ FAR netpkt_t **tx_pkt; FAR netpkt_t **rx_pkt; /* Descriptors */ FAR struct igc_tx_leg_s *tx; FAR struct igc_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 igc_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 igc_getreg_mem(FAR struct igc_driver_s *priv, unsigned int offset); static void igc_putreg_mem(FAR struct igc_driver_s *priv, unsigned int offset, uint32_t value); #ifdef CONFIG_DEBUG_NET_INFO static void igc_dump_reg(FAR struct igc_driver_s *priv, FAR const char *msg, unsigned int offset); static void igc_dump_mem(FAR struct igc_driver_s *priv, FAR const char *msg); #endif /* Common TX logic */ static int igc_transmit(FAR struct netdev_lowerhalf_s *dev, FAR netpkt_t *pkt); /* Interrupt handling */ static FAR netpkt_t *igc_receive(FAR struct netdev_lowerhalf_s *dev); static void igc_txdone(FAR struct netdev_lowerhalf_s *dev); static void igc_msix_interrupt(FAR struct igc_driver_s *priv); static int igc_interrupt(int irq, FAR void *context, FAR void *arg); /* NuttX callback functions */ static int igc_ifup(FAR struct netdev_lowerhalf_s *dev); static int igc_ifdown(FAR struct netdev_lowerhalf_s *dev); #ifdef CONFIG_NET_MCASTGROUP static uint32_t igc_hashmta(FAR struct igc_driver_s *priv, FAR const uint8_t *mac); static int igc_addmac(FAR struct netdev_lowerhalf_s *dev, FAR const uint8_t *mac); static int igc_rmmac(FAR struct netdev_lowerhalf_s *dev, FAR const uint8_t *mac); #endif /* Initialization */ static void igc_disable(FAR struct igc_driver_s *priv); static void igc_enable(FAR struct igc_driver_s *priv); static int igc_initialize(FAR struct igc_driver_s *priv); static int igc_probe(FAR struct pci_device_s *dev); /***************************************************************************** * Private Data *****************************************************************************/ #ifdef CONFIG_NET_IGC_I225LM /* Intel I225LM */ static const struct igc_type_s g_igc_i225lm = { .desc_align = 128, .mta_regs = 128 }; #endif static const struct pci_device_id_s g_igc_id_table[] = { #ifdef CONFIG_NET_IGC_I225LM { PCI_DEVICE(0x8086, 0x15f2), .driver_data = (uintptr_t)&g_igc_i225lm }, #endif { } }; static struct pci_driver_s g_pci_igc_drv = { .id_table = g_igc_id_table, .probe = igc_probe, }; static const struct netdev_ops_s g_igc_ops = { .ifup = igc_ifup, .ifdown = igc_ifdown, .transmit = igc_transmit, .receive = igc_receive, #ifdef CONFIG_NET_MCASTGROUP .addmac = igc_addmac, .rmmac = igc_rmmac, #endif }; /***************************************************************************** * Private Functions *****************************************************************************/ /***************************************************************************** * Name: igc_getreg_mem *****************************************************************************/ static uint32_t igc_getreg_mem(FAR struct igc_driver_s *priv, unsigned int offset) { uintptr_t addr = priv->base + offset; return *((FAR volatile uint32_t *)addr); } /***************************************************************************** * Name: igc_putreg_mem *****************************************************************************/ static void igc_putreg_mem(FAR struct igc_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: igc_dump_reg *****************************************************************************/ static void igc_dump_reg(FAR struct igc_driver_s *priv, FAR const char *msg, unsigned int offset) { ninfo("\t%s:\t\t0x%" PRIx32 "\n", msg, igc_getreg_mem(priv, offset)); } /***************************************************************************** * Name: igc_dump_mem *****************************************************************************/ static void igc_dump_mem(FAR struct igc_driver_s *priv, FAR const char *msg) { ninfo("Dump: %s\n", msg); ninfo("General registers:\n"); igc_dump_reg(priv, "CTRL", IGC_CTRL); igc_dump_reg(priv, "STATUS", IGC_STATUS); igc_dump_reg(priv, "CTRLEXT", IGC_CTRLEXT); igc_dump_reg(priv, "IPCNFG", IGC_IPCNFG); igc_dump_reg(priv, "PHMP", IGC_PHMP); ninfo("Interrupt registers:\n"); igc_dump_reg(priv, "ICS", IGC_ICS); igc_dump_reg(priv, "IMS", IGC_IMS); igc_dump_reg(priv, "IAM", IGC_IAM); igc_dump_reg(priv, "EICS", IGC_EICS); igc_dump_reg(priv, "EIMS", IGC_EIMS); igc_dump_reg(priv, "EIAM", IGC_EIAM); igc_dump_reg(priv, "EIAC", IGC_EIAC); igc_dump_reg(priv, "IVAR0", IGC_IVAR0); igc_dump_reg(priv, "IVARMSC", IGC_IVARMSC); igc_dump_reg(priv, "GPIE", IGC_GPIE); igc_dump_reg(priv, "PICAUSE", IGC_PICAUSE); igc_dump_reg(priv, "PIENA", IGC_PIENA); igc_dump_reg(priv, "PBACL", IGC_PBACL); igc_dump_reg(priv, "EITR0", IGC_EITR0); ninfo("Receive registers:\n"); igc_dump_reg(priv, "RCTL", IGC_RCTL); igc_dump_reg(priv, "PSRCTL", IGC_PSRCTL); igc_dump_reg(priv, "FCRTL0", IGC_FCRTL0); igc_dump_reg(priv, "FCRTH0", IGC_FCRTH0); igc_dump_reg(priv, "RXPBSIZE", IGC_RXPBSIZE); igc_dump_reg(priv, "FCRTV", IGC_FCRTV); igc_dump_reg(priv, "RDBAL0", IGC_RDBAL0); igc_dump_reg(priv, "RDBAH0", IGC_RDBAH0); igc_dump_reg(priv, "RDLEN0", IGC_RDLEN0); igc_dump_reg(priv, "SRRCTL0", IGC_SRRCTL0); igc_dump_reg(priv, "RDH0", IGC_RDH0); igc_dump_reg(priv, "RDT0", IGC_RDT0); igc_dump_reg(priv, "RXDCTL0", IGC_RXDCTL0); igc_dump_reg(priv, "RXCTL0", IGC_RXCTL0); igc_dump_reg(priv, "RXCSUM", IGC_RXCSUM); igc_dump_reg(priv, "RLPML", IGC_RLPML); igc_dump_reg(priv, "RFCTL", IGC_RFCTL); igc_dump_reg(priv, "MTA", IGC_MTA); igc_dump_reg(priv, "RAL", IGC_RAL); igc_dump_reg(priv, "RAH", IGC_RAH); ninfo("Transmit registers:\n"); igc_dump_reg(priv, "TCTL", IGC_TCTL); igc_dump_reg(priv, "TCTLEXT", IGC_TCTLEXT); igc_dump_reg(priv, "TIPG", IGC_TIPG); igc_dump_reg(priv, "RETXCTL", IGC_RETXCTL); igc_dump_reg(priv, "TXPBSIZE", IGC_TXPBSIZE); igc_dump_reg(priv, "DTXCTL", IGC_DTXCTL); igc_dump_reg(priv, "TDBAL0", IGC_TDBAL0); igc_dump_reg(priv, "TDBAH0", IGC_TDBAH0); igc_dump_reg(priv, "TDLEN0", IGC_TDLEN0); igc_dump_reg(priv, "TDH0", IGC_TDH0); igc_dump_reg(priv, "TDT0", IGC_TDT0); igc_dump_reg(priv, "TXDCTL0", IGC_TXDCTL0); igc_dump_reg(priv, "TXCTL0", IGC_TXCTL0); igc_dump_reg(priv, "TDWBAL0", IGC_TDWBAL0); igc_dump_reg(priv, "TDWBAH0", IGC_TDWBAH0); ninfo("Statistic registers:\n"); igc_dump_reg(priv, "CRCERRS", IGC_CRCERRS); igc_dump_reg(priv, "ALGNERRC", IGC_ALGNERRC); igc_dump_reg(priv, "RXERRC", IGC_RXERRC); igc_dump_reg(priv, "MPC", IGC_MPC); igc_dump_reg(priv, "SCC", IGC_SCC); igc_dump_reg(priv, "ECOL", IGC_ECOL); igc_dump_reg(priv, "MCC", IGC_MCC); igc_dump_reg(priv, "LATECOL", IGC_LATECOL); igc_dump_reg(priv, "COLC", IGC_COLC); igc_dump_reg(priv, "DC", IGC_DC); igc_dump_reg(priv, "TNCRS", IGC_TNCRS); igc_dump_reg(priv, "CEXTERR", IGC_CEXTERR); igc_dump_reg(priv, "RLEC", IGC_RLEC); igc_dump_reg(priv, "XONRXC", IGC_XONRXC); igc_dump_reg(priv, "XONTXC", IGC_XONTXC); igc_dump_reg(priv, "XOFFRXC", IGC_XOFFRXC); igc_dump_reg(priv, "XOFFTXC", IGC_XOFFTXC); igc_dump_reg(priv, "FCRUC", IGC_FCRUC); igc_dump_reg(priv, "PRC64", IGC_PRC64); igc_dump_reg(priv, "PRC127", IGC_PRC127); igc_dump_reg(priv, "PRC255", IGC_PRC255); igc_dump_reg(priv, "PRC511", IGC_PRC511); igc_dump_reg(priv, "PRC1023", IGC_PRC1023); igc_dump_reg(priv, "PRC1522", IGC_PRC1522); igc_dump_reg(priv, "GPRC", IGC_GPRC); igc_dump_reg(priv, "BPRC", IGC_BPRC); igc_dump_reg(priv, "MPRC", IGC_MPRC); igc_dump_reg(priv, "GPTC", IGC_GPTC); igc_dump_reg(priv, "GORCL", IGC_GORCL); igc_dump_reg(priv, "GORCH", IGC_GORCH); igc_dump_reg(priv, "GOTCL", IGC_GOTCL); igc_dump_reg(priv, "GOTCH", IGC_GOTCH); igc_dump_reg(priv, "RNBC", IGC_RNBC); igc_dump_reg(priv, "RUC", IGC_RUC); igc_dump_reg(priv, "RFC", IGC_RFC); igc_dump_reg(priv, "ROC", IGC_ROC); igc_dump_reg(priv, "RJC", IGC_RJC); igc_dump_reg(priv, "MNGPRC", IGC_MNGPRC); igc_dump_reg(priv, "MPDC", IGC_MPDC); igc_dump_reg(priv, "MPTC", IGC_MPTC); igc_dump_reg(priv, "TORL", IGC_TORL); igc_dump_reg(priv, "TORH", IGC_TORH); igc_dump_reg(priv, "TOT", IGC_TOT); igc_dump_reg(priv, "TPR", IGC_TPR); igc_dump_reg(priv, "TPT", IGC_TPT); igc_dump_reg(priv, "PTC64", IGC_PTC64); igc_dump_reg(priv, "PTC127", IGC_PTC127); igc_dump_reg(priv, "PTC255", IGC_PTC255); igc_dump_reg(priv, "PTC511", IGC_PTC511); igc_dump_reg(priv, "PTC1023", IGC_PTC1023); igc_dump_reg(priv, "PTC1522", IGC_PTC1522); igc_dump_reg(priv, "MPTC", IGC_MPTC); igc_dump_reg(priv, "BPTC", IGC_BPTC); igc_dump_reg(priv, "TSCTC", IGC_TSCTC); igc_dump_reg(priv, "TSCTFC", IGC_TSCTFC); igc_dump_reg(priv, "IAC", IGC_IAC); } #endif /***************************************************************************** * Name: igc_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 igc_transmit(FAR struct netdev_lowerhalf_s *dev, FAR netpkt_t *pkt) { FAR struct igc_driver_s *priv = (FAR struct igc_driver_s *)dev; uint64_t pa = 0; int desc = priv->tx_now; size_t len = netpkt_getdatalen(dev, pkt); ninfo("transmit\n"); /* Check the send length */ if (len > IGC_PKTBUF_SIZE) { nerr("net transmit buffer too large\n"); return -EINVAL; } /* Store TX packet reference */ priv->tx_pkt[priv->tx_now] = pkt; /* Prepare next TX descriptor */ priv->tx_now = (priv->tx_now + 1) % IGC_TX_DESC; /* 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 = (IGC_TDESC_CMD_EOP | IGC_TDESC_CMD_IFCS | IGC_TDESC_CMD_RS); priv->tx[desc].cso = 0; priv->tx[desc].status = 0; SP_DSB(); /* Update TX tail */ igc_putreg_mem(priv, IGC_TDT0, priv->tx_now); ninfodumpbuffer("Transmitted:", netpkt_getdata(dev, pkt), len); return OK; } /***************************************************************************** * Name: igc_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 * igc_receive(FAR struct netdev_lowerhalf_s *dev) { FAR struct igc_driver_s *priv = (FAR struct igc_driver_s *)dev; FAR netpkt_t *pkt = NULL; FAR struct igc_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 & IGC_RDESC_STATUS_DD)) { return NULL; } /* Next descriptor */ priv->rx_now = (priv->rx_now + 1) % IGC_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 */ igc_putreg_mem(priv, IGC_RDT0, desc); /* Handle errros */ if (rx->errors) { nerr("RX error reported (%"PRIu8")\n", rx->errors); NETDEV_RXERRORS(&priv->dev); netpkt_free(dev, pkt, NETPKT_RX); return NULL; } return pkt; } /***************************************************************************** * Name: igc_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 igc_txdone(FAR struct netdev_lowerhalf_s *dev) { FAR struct igc_driver_s *priv = (FAR struct igc_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 & IGC_TDESC_STATUS_DD)) { nerr("tx failed: 0x%" PRIx32 "\n", priv->tx[priv->tx_done].status); NETDEV_TXERRORS(priv->dev); } /* Free net packet */ netpkt_free(dev, priv->tx_pkt[priv->tx_done], NETPKT_TX); /* Next descriptor */ priv->tx_done = (priv->tx_done + 1) % IGC_TX_DESC; } netdev_lower_txdone(dev); } /***************************************************************************** * Name: igc_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 igc_msix_interrupt(FAR struct igc_driver_s *priv) { uint32_t icr = 0; uint32_t eicr = 0; uint32_t tmp = 0; /* Get interrupts */ icr = igc_getreg_mem(priv, IGC_ICR); eicr = igc_getreg_mem(priv, IGC_EICR); ninfo("eicr = 0x%" PRIx32 " icr = 0x%" PRIx32 "\n", eicr, icr); if (icr == 0) { /* Ignore spurious interrupts */ return; } /* Receiver Descriptor Write Back */ if (icr & IGC_IC_RXDW) { netdev_lower_rxready(&priv->dev); } /* Link Status Change */ if (icr & IGC_IC_LSC) { tmp = igc_getreg_mem(priv, IGC_STATUS); if (tmp & IGC_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); } } /* Receiver Miss */ if (icr & IGC_IC_RXMISS) { nerr("Receiver Miss\n"); netdev_lower_rxready(&priv->dev); } /* Transmit Descriptor Written Back */ if (icr & IGC_IC_TXDW) { igc_txdone(&priv->dev); } } /***************************************************************************** * Name: igc_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 igc_interrupt(int irq, FAR void *context, FAR void *arg) { FAR struct igc_driver_s *priv = (FAR struct igc_driver_s *)arg; DEBUGASSERT(priv != NULL); ninfo("interrupt!\n"); /* Schedule to perform the interrupt processing on the worker thread. */ igc_msix_interrupt(priv); return OK; } /***************************************************************************** * Name: igc_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 igc_ifup(FAR struct netdev_lowerhalf_s *dev) { FAR struct igc_driver_s *priv = (FAR struct igc_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 /* Enable the Ethernet */ flags = enter_critical_section(); igc_enable(priv); priv->bifup = true; leave_critical_section(flags); return OK; } /***************************************************************************** * Name: igc_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 igc_ifdown(FAR struct netdev_lowerhalf_s *dev) { FAR struct igc_driver_s *priv = (FAR struct igc_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 igc_ifup() always * successfully brings the interface back up. */ igc_disable(priv); /* Mark the device "down" */ priv->bifup = false; leave_critical_section(flags); return OK; } #ifdef CONFIG_NET_MCASTGROUP /***************************************************************************** * Name: igc_hashmta * * Note: This logic is based on freeBSD igc implementation * *****************************************************************************/ static uint32_t igc_hashmta(FAR struct igc_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: igc_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 igc_addmac(FAR struct netdev_lowerhalf_s *dev, FAR const uint8_t *mac) { FAR struct igc_driver_s *priv = (FAR struct igc_driver_s *)dev; uint16_t hash = 0; uint8_t row = 0; uint8_t bit = 0; int i = 0; hash = igc_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--) { igc_putreg_mem(priv, IGC_MTA + (i << 2), priv->mta[i]); } return OK; } /***************************************************************************** * Name: igc_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 igc_rmmac(FAR struct netdev_lowerhalf_s *dev, FAR const uint8_t *mac) { FAR struct igc_driver_s *priv = (FAR struct igc_driver_s *)dev; uint16_t hash = 0; uint8_t row = 0; uint8_t bit = 0; int i = 0; hash = igc_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--) { igc_putreg_mem(priv, IGC_MTA + (i << 2), priv->mta[i]); } return OK; } #endif /* CONFIG_NET_MCASTGROUP */ /***************************************************************************** * Name: igc_disable * * Description: * Reset device to known state. * *****************************************************************************/ static void igc_disable(FAR struct igc_driver_s *priv) { int i = 0; /* Reset Tx tail */ igc_putreg_mem(priv, IGC_TDH0, 0); igc_putreg_mem(priv, IGC_TDT0, 0); /* Reset Rx tail */ igc_putreg_mem(priv, IGC_RDH0, 0); igc_putreg_mem(priv, IGC_RDT0, 0); /* Disable interrupts */ igc_putreg_mem(priv, IGC_EIMC, IGC_MSIX_EIMS); igc_putreg_mem(priv, IGC_IMC, IGC_MSIX_IMS); up_disable_irq(priv->irq); /* Disable Transmiter */ igc_putreg_mem(priv, IGC_TCTL, 0); /* Disable Receiver */ igc_putreg_mem(priv, IGC_RCTL, 0); /* Free RX packets */ for (i = 0; i < IGC_RX_DESC; i += 1) { netpkt_free(&priv->dev, priv->rx_pkt[i], NETPKT_RX); } } /***************************************************************************** * Name: igc_phy_reset * * Description: * Reset PHY * *****************************************************************************/ static void igc_phy_reset(FAR struct igc_driver_s *priv) { uint32_t regval = 0; regval = igc_getreg_mem(priv, IGC_CTRL); igc_putreg_mem(priv, IGC_CTRL, regval | IGC_CTRL_PHYRST); up_udelay(100); igc_putreg_mem(priv, IGC_CTRL, regval); up_udelay(100); } /***************************************************************************** * Name: igc_enable * * Description: * Enable device. * *****************************************************************************/ static void igc_enable(FAR struct igc_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 */ igc_phy_reset(priv); /* Reset Multicast Table Array */ for (i = 0; i < priv->type->mta_regs; i++) { igc_putreg_mem(priv, IGC_MTA + (i << 2), 0); } /* Allocate RX packets */ for (i = 0; i < IGC_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; igc_putreg_mem(priv, IGC_TDBAL0, regval); regval = (uint32_t)(pa >> 32); igc_putreg_mem(priv, IGC_TDBAH0, regval); regval = IGC_TX_DESC * sizeof(struct igc_tx_leg_s); igc_putreg_mem(priv, IGC_TDLEN0, regval); priv->tx_now = 0; /* Reset TX tail */ igc_putreg_mem(priv, IGC_TDH0, 0); igc_putreg_mem(priv, IGC_TDT0, 0); /* Setup RX descriptor */ /* The address passed to the NIC must be physical */ pa = up_addrenv_va_to_pa(priv->rx); regval = (uint32_t)pa; igc_putreg_mem(priv, IGC_RDBAL0, regval); regval = (uint32_t)(pa >> 32); igc_putreg_mem(priv, IGC_RDBAH0, regval); regval = IGC_RX_DESC * sizeof(struct igc_rx_leg_s); igc_putreg_mem(priv, IGC_RDLEN0, regval); priv->rx_now = 0; /* Enable interrupts */ igc_putreg_mem(priv, IGC_EIMS, IGC_MSIX_EIMS); igc_putreg_mem(priv, IGC_IMS, IGC_MSIX_IMS); up_enable_irq(priv->irq); /* Set link up */ igc_putreg_mem(priv, IGC_CTRL, IGC_CTRL_SLU); /* Setup and enable Transmiter */ regval = igc_getreg_mem(priv, IGC_TCTL); regval |= IGC_TCTL_EN | IGC_TCTL_PSP; igc_putreg_mem(priv, IGC_TCTL, regval); /* Setup and enable Receiver */ regval = (IGC_RCTL_EN | IGC_RCTL_MPE | (IGC_RCTL_BSIZE << IGC_RCTL_BSIZE_SHIFT)); #ifdef CONFIG_NET_PROMISCUOUS regval |= IGC_RCTL_UPE | IGC_RCTL_MPE; #endif igc_putreg_mem(priv, IGC_RCTL, regval); /* Enable TX queeu */ regval = igc_getreg_mem(priv, IGC_TXDCTL0); regval |= IGC_TXDCTL_ENABLE; igc_putreg_mem(priv, IGC_TXDCTL0, regval); /* Enable RX queue */ regval = igc_getreg_mem(priv, IGC_RXDCTL0); regval |= IGC_RXDCTL_ENABLE; igc_putreg_mem(priv, IGC_RXDCTL0, regval); /* Reset RX tail - after queue is enabled */ igc_putreg_mem(priv, IGC_RDH0, 0); igc_putreg_mem(priv, IGC_RDT0, IGC_RX_DESC); #ifdef CONFIG_DEBUG_NET_INFO /* Dump memory */ igc_dump_mem(priv, "enabled"); #endif } /***************************************************************************** * Name: igc_initialize * * Description: * Initialize device * *****************************************************************************/ static int igc_initialize(FAR struct igc_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, igc_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 */ igc_putreg_mem(priv, IGC_EIMC, 0xffffffff); igc_putreg_mem(priv, IGC_IMC, 0xffffffff); /* Configure MSI-X */ igc_putreg_mem(priv, IGC_IVAR0, IGC_MSIX_IVAR0); igc_putreg_mem(priv, IGC_IVARMSC, IGC_MSIX_IVARMSC); /* Enable MSI-X Single Vector */ igc_putreg_mem(priv, IGC_GPIE, IGC_GPIE_MSIX_SINGLE); igc_putreg_mem(priv, IGC_EIMS, IGC_MSIX_EIMS); /* Configure Other causes */ igc_putreg_mem(priv, IGC_IMS, IGC_MSIX_IMS); /* Configure Interrupt Throttle */ igc_putreg_mem(priv, IGC_EITR0, (IGC_INTERRUPT_INTERVAL << 2)); /* Get MAC if valid */ regval = igc_getreg_mem(priv, IGC_RAH); if (regval & IGC_RAH_AV) { mac = ((uint64_t)regval & IGC_RAH_RAH_MASK) << 32; mac |= igc_getreg_mem(priv, IGC_RAL); memcpy(&priv->dev.netdev.d_mac.ether, &mac, sizeof(struct ether_addr)); } else { nwarn("Receive Address not vaild!\n"); } return OK; } /***************************************************************************** * Name: igc_probe * * Description: * Initialize device * *****************************************************************************/ static int igc_probe(FAR struct pci_device_s *dev) { FAR const struct igc_type_s *type = NULL; FAR struct igc_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 igc_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, IGC_TX_DESC * sizeof(struct igc_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, IGC_RX_DESC * sizeof(struct igc_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(IGC_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(IGC_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, IGC_MMIO_BAR); if (!priv->base) { pcierr("Not found MMIO control bar\n"); goto errout; } /* Initialize PHYs, Ethernet interface, and setup up Ethernet interrupts */ ret = igc_initialize(priv); if (ret != OK) { nerr("igc_initialize failed %d\n", ret); return ret; } /* Register the network device */ netdev->quota[NETPKT_TX] = IGC_TX_QUOTA; netdev->quota[NETPKT_RX] = IGC_RX_QUOTA; netdev->ops = &g_igc_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_igc_init * * Description: * Register a pci driver * *****************************************************************************/ int pci_igc_init(void) { return pci_register_driver(&g_pci_igc_drv); }