/* * Copyright (C) 2012 by Ole Reinhardt (ole.reinhardt@embedded-it.de) * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * For additional information see http://www.ethernut.de/ */ /* * \verbatim * $Id: $ * \endverbatim */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(MCU_LPC176x) #include #include #include #elif defined(MCU_LPC177x_8x) #include #include #include #elif defined(MCU_LPC407x_8x) #include #include #include #else #warning "Unknown LPC familiy" #endif #include #include #include #include #include #include #include /* WARNING: Variadic macros are C99 and may fail with C89 compilers. */ #ifdef NUTDEBUG #include #include #define EMPRINTF(args,...) printf(args,##__VA_ARGS__); #else #define EMPRINTF(args,...) #endif #define NUT_THREAD_NICLINKSTACK 512 #ifndef NUT_THREAD_NICRXSTACK #define NUT_THREAD_NICRXSTACK 768 #endif #ifndef EMAC_LINK_LOOPS #define EMAC_LINK_LOOPS 1000 #endif /*! * \brief PHY address. * * Any other than 0 seems to create problems with Atmel's evaluation kits. */ #ifndef NIC_PHY_ADDR #define NIC_PHY_ADDR 0 #endif #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif #define min(a,b) ((a>b)?b:a) /* MII Mgmt Configuration register - Clock divider setting */ const uint8_t emac_clkdiv[] = { 4, 6, 8, 10, 14, 20, 28, 36, 40, 44, 48, 52, 56, 60, 64 }; typedef struct _BufDescriptor { unsigned int addr; unsigned int stat; } BufDescriptor; /*! * \brief Network interface controller information structure. */ struct _EMACINFO { #ifdef NUT_PERFMON #warning not yet implemented uint32_t ni_rx_packets; /*!< Number of packets received. */ uint32_t ni_tx_packets; /*!< Number of packets sent. */ uint32_t ni_overruns; /*!< Number of packet overruns. */ uint32_t ni_rx_frame_errors; /*!< Number of frame errors. */ uint32_t ni_rx_crc_errors; /*!< Number of CRC errors. */ uint32_t ni_rx_missed_errors; /*!< Number of missed packets. */ #endif HANDLE volatile ni_rx_rdy; /*!< Receiver event queue. */ HANDLE volatile ni_tx_rdy; /*!< Transmitter event queue. */ HANDLE ni_mutex; /*!< Access mutex semaphore. */ volatile int ni_insane; /*!< Set by error detection. */ }; /*! * \brief Network interface controller information type. */ typedef struct _EMACINFO EMACINFO; /*! * \addtogroup xgNutArchCm3Lpc17xxEmac */ /*@{*/ /*! * \brief Read contents of PHY register. * * \param reg PHY register number. * * \return Contents of the specified register. */ static uint16_t phy_inw(uint8_t reg) { unsigned int tout; LPC_EMAC->MADR = (NIC_PHY_ADDR << 8) | reg; LPC_EMAC->MCMD = EMAC_MCMD_READ; /* Wait until operation completed */ tout = 0; for (tout = 0; tout < EMAC_MII_RD_TOUT; tout++) { if ((LPC_EMAC->MIND & EMAC_MIND_BUSY) == 0) { break; } } LPC_EMAC->MCMD = 0; return (LPC_EMAC->MRDD); } /*! * \brief Write value to PHY register. * * \param reg PHY register number. * \param val Value to write. */ static void phy_outw(uint8_t reg, uint16_t val) { unsigned int tout; LPC_EMAC->MADR = (NIC_PHY_ADDR << 8) | reg; LPC_EMAC->MWTD = val; /* Wait utill operation completed */ tout = 0; for (tout = 0; tout < EMAC_MII_WR_TOUT; tout++) { if ((LPC_EMAC->MIND & EMAC_MIND_BUSY) == 0) { break; } } } /*! * \brief Set the emac duplex mode. * * \param full 1: full duplex, 0: half duplex * * \return none; */ static void Lpc17xxEmacSetDuplex(int full) { if(full) { LPC_EMAC->MAC2 |= EMAC_MAC2_FULL_DUP; LPC_EMAC->Command |= EMAC_CR_FULL_DUP; LPC_EMAC->IPGT = EMAC_IPGT_FULL_DUP; } else { LPC_EMAC->IPGT = EMAC_IPGT_HALF_DUP; } } /*! * \brief Set the emac speed. * * \param full 1: 100MBit, 0: 10 MBit * * \return none; */ static void Lpc17xxEmacSetSpeed(int enable_100mBit) { if(enable_100mBit) { LPC_EMAC->SUPP = EMAC_SUPP_SPEED; } else { LPC_EMAC->SUPP = 0; } } /*! * \brief Set the emac station address. * * \param mac MAC address (station address) * * \return none; */ static void Lpc17xxEmacSetMACAddr(const uint8_t * mac) { /* Set the Ethernet MAC Address registers */ LPC_EMAC->SA0 = ((uint32_t)mac[5] << 8) | (uint32_t)mac[4]; LPC_EMAC->SA1 = ((uint32_t)mac[3] << 8) | (uint32_t)mac[2]; LPC_EMAC->SA2 = ((uint32_t)mac[1] << 8) | (uint32_t)mac[0]; } /*! \fn Lpc17xxEmacWaitLinkThread(void *arg) * \brief Wait for link establishment and configure the NIC * accordingly. */ THREAD(Lpc17xxEmacWaitLinkThread, arg) { uint32_t phyval; int link_wait; for (;;) { /* Restart autonegotiation */ phyval = 1; NutPhyCtl(PHY_CTL_AUTONEG_RE, &phyval); /* Wait for auto negotiation completed and link established. */ for (link_wait = EMAC_LINK_LOOPS;; link_wait--) { phyval = 0; NutPhyCtl(PHY_GET_STATUS, &phyval); if((phyval & PHY_STATUS_HAS_LINK) && (phyval & PHY_STATUS_AUTONEG_OK)) { /* Check link state and configure EMAC accordingly */ if (phyval & PHY_STATUS_100M) { Lpc17xxEmacSetSpeed(TRUE); EMPRINTF("EMAC: Got link: 100 MBit "); } else { Lpc17xxEmacSetSpeed(FALSE); EMPRINTF("Link: Got link: 10 MBit "); } if (phyval & PHY_STATUS_FULLDUPLEX) { Lpc17xxEmacSetDuplex(TRUE); EMPRINTF("Full Duplex\n"); } else { Lpc17xxEmacSetDuplex(FALSE); EMPRINTF("Half Duplex\n"); } NutThreadExit(); break; } if (link_wait == 0) { EMPRINTF("NO LINK!\n"); } NutSleep(10); } } } /*! * \brief Reset the Ethernet controller. * * \return 0 on success, -1 on error */ static int Lpc17xxEmacReset(NUTDEVICE *dev) { int rc = 0; uint32_t phyval; int idx; int32_t tmp; IFNET *ifn = (IFNET *) dev->dev_icb; EMPRINTF("Lpc17xxEmacReset\n"); /* Reset all EMAC internal modules */ LPC_EMAC->MAC1 = EMAC_MAC1_RES_TX | EMAC_MAC1_RES_MCS_TX | EMAC_MAC1_RES_RX | EMAC_MAC1_RES_MCS_RX | EMAC_MAC1_SIM_RES | EMAC_MAC1_SOFT_RES; LPC_EMAC->Command = EMAC_CR_REG_RES | EMAC_CR_TX_RES | EMAC_CR_RX_RES | EMAC_CR_PASS_RUNT_FRM; /* A short delay after reset. */ NutSleep(10); /* Initialize MAC control registers. */ LPC_EMAC->MAC1 = EMAC_MAC1_PASS_ALL; LPC_EMAC->MAC2 = EMAC_MAC2_CRC_EN | EMAC_MAC2_PAD_EN; /* Set maximum frame size. */ LPC_EMAC->MAXF = ifn->if_mtu + ETHER_HDR_LEN + ETHER_HDR_LEN; /* Find the clock that close to desired target clock */ tmp = NutArchClockGet(NUT_HWCLK_CPU) / EMAC_MCFG_MII_MAXCLK; for (idx = 0; idx < sizeof (emac_clkdiv); idx++) { if (emac_clkdiv[idx] >= tmp) { break; } } if(idx >= sizeof (emac_clkdiv)) { return -1; } idx++; /* Write to MAC configuration register and reset */ LPC_EMAC->MCFG = EMAC_MCFG_CLK_SEL(idx) | EMAC_MCFG_RES_MII; /* release reset */ LPC_EMAC->MCFG &= ~(EMAC_MCFG_RES_MII); LPC_EMAC->CLRT = EMAC_CLRT_DEF; LPC_EMAC->IPGR = EMAC_IPGR_P2_DEF; #ifdef PHY_MODE_RMII /* Enable Reduced MII interface. */ LPC_EMAC->Command = EMAC_CR_RMII | EMAC_CR_PASS_RUNT_FRM; #endif NutSleep(10); LPC_EMAC->SUPP = 0; /* Wait for PHY ready. */ NutSleep(255); /* Register PHY PHY driver memory is only allocated one, but as NutRegisterPhy reads the PHY ID we need to have the (R)MII hardware initialised first */ rc = NutRegisterPhy(1, phy_outw, phy_inw); #ifndef PHY_MODE_RMII /* Clear MII isolate. */ phyval = 0; NutPhyCtl(PHY_CTL_ISOLATE, &phyval); #endif /* Restart autonegotiation */ phyval = 1; NutPhyCtl(PHY_CTL_AUTONEG_RE, &phyval); if (NutThreadCreate("emaclink", Lpc17xxEmacWaitLinkThread, NULL, (NUT_THREAD_NICLINKSTACK * NUT_THREAD_STACK_MULT) + NUT_THREAD_STACK_ADD) == NULL) { EMPRINTF("Lpc17xxEmacReset() Could not create link establishment thread\n"); rc = -1; } EMPRINTF("Lpc17xxEmacReset() DONE\n"); return rc; } /*! * \brief Initialise the Emac RX descriptors. * * RX descriptors are located in the extra 16K internal SRAM of the CPU * and will be used for DMA operations. * * \param nb Network buffer structure containing the packet to be sent. * The structure must have been allocated by a previous * call NutNetBufAlloc(). This routine will automatically * release the buffer in case of an error. * * \return none */ static void Lpc17xxEmacRxDescriptorInit(void) { unsigned int i; for (i = 0; i < EMAC_NUM_RX_FRAG; i++) { RX_DESC_PACKET(i) = RX_BUF(i); RX_DESC_CTRL(i) = EMAC_RCTRL_INT | (EMAC_ETH_RX_FRAG_SIZE-1); RX_STAT_INFO(i) = 0; RX_STAT_HASHCRC(i) = 0; } /* Set EMAC Receive Descriptor Registers. */ LPC_EMAC->RxDescriptor = RX_DESC_BASE; LPC_EMAC->RxStatus = RX_STAT_BASE; LPC_EMAC->RxDescriptorNumber = EMAC_NUM_RX_FRAG-1; /* Rx Descriptors Point to 0 */ LPC_EMAC->RxConsumeIndex = 0; } /*! * \brief Initialise the Emac TX descriptors. * * RX descriptors are located in the extra 16K internal SRAM of the CPU * and will be used for DMA operations. * * \param nb Network buffer structure containing the packet to be sent. * The structure must have been allocated by a previous * call NutNetBufAlloc(). This routine will automatically * release the buffer in case of an error. * * \return none */ static void Lpc17xxEmacTxDescriptorInit (void) { unsigned int i; for (i = 0; i < EMAC_NUM_TX_FRAG; i++) { TX_DESC_PACKET(i) = TX_BUF(i); TX_DESC_CTRL(i) = 0; TX_STAT_INFO(i) = 0; } /* Set EMAC Transmit Descriptor Registers. */ LPC_EMAC->TxDescriptor = TX_DESC_BASE; LPC_EMAC->TxStatus = TX_STAT_BASE; LPC_EMAC->TxDescriptorNumber = EMAC_NUM_TX_FRAG-1; /* Tx Descriptors Point to 0 */ LPC_EMAC->TxProduceIndex = 0; } /* * NIC interrupt entry. */ static void Lpc17xxEmacInterrupt(void *arg) { uint32_t isr; EMACINFO *ni = (EMACINFO *) ((NUTDEVICE *) arg)->dev_dcb; /* EMAC Ethernet Controller Interrupt function. */ /* Get EMAC interrupt status */ isr = (LPC_EMAC->IntStatus & LPC_EMAC->IntEnable); if (isr != 0) { /* Clear interrupt status */ LPC_EMAC->IntClear = isr; if (isr & EMAC_INT_RX_OVERRUN) { /* An RX overrun error is fatal. We have to soft-reset the RX Datapath */ LPC_EMAC->Command |= EMAC_CR_RX_RES; /* Anyway post the RX queue so the receiver thread can read the just received frames */ NutEventPostFromIrq(&ni->ni_rx_rdy); } else if (isr & (EMAC_INT_RX_DONE | EMAC_INT_RX_ERR)) { LPC_EMAC->IntEnable &= ~(EMAC_INT_RX_ERR | EMAC_INT_RX_DONE); NutEventPostFromIrq(&ni->ni_rx_rdy); } if (isr & EMAC_INT_TX_UNDERRUN) { /* An TX underrun error is fatal. We have to soft-reset the TX Datapath */ LPC_EMAC->Command |= EMAC_CR_TX_RES; /* Anyway post the TX queue so the transmitter can rwrite new frames */ NutEventPostFromIrq(&ni->ni_tx_rdy); } else if (isr & (EMAC_INT_TX_DONE | EMAC_INT_TX_ERR)) { NutEventPostFromIrq(&ni->ni_tx_rdy); } } } /*! * \brief Fetch the next packet out of the receive buffers. * * \return 0 on success, -1 otherwise. */ static int Lpc17xxEmacGetPacket(NUTDEVICE *dev, NETBUF ** nbp) { int rc = -1; uint32_t idx; uint32_t produce_idx; uint32_t new_consume_idx; int32_t rxlen; register uint32_t status; EMACINFO *ni = (EMACINFO *) dev->dev_dcb; *nbp = NULL; produce_idx = LPC_EMAC->RxProduceIndex; idx = LPC_EMAC->RxConsumeIndex; rxlen = 0; if (idx != produce_idx) { int32_t size = 0; status = RX_STAT_INFO(idx); while (status && idx != produce_idx) { size += (status & EMAC_RINFO_SIZE) + 1; if (++idx == EMAC_NUM_RX_FRAG) { idx = 0; } new_consume_idx = idx; if (status & EMAC_RINFO_LAST_FLAG) { rxlen = size; break; } status = RX_STAT_INFO(idx); } } /* Check if there was any data received */ if(rxlen > 0) { idx = LPC_EMAC->RxConsumeIndex; /* substract 4 bytes CTC. */ rxlen -= 4; /* "status" represents the status flag of the last frame fragment */ /* Check if the frame was correctly received */ if((status & EMAC_RINFO_ERR_MASK) == 0) { /* * Receiving long packets is unexpected. Let's declare the * chip insane. Short packets will be handled by the caller. */ if (rxlen < 0) { ni->ni_insane = 1; } else { *nbp = NutNetBufAlloc(0, NBAF_DATALINK + (2 & (NUTMEM_ALIGNMENT - 1)), (uint16_t)rxlen); if (*nbp != NULL) { uint8_t *bp = (uint8_t *) (* nbp)->nb_dl.vp; unsigned int len; idx = LPC_EMAC->RxConsumeIndex; while (rxlen) { if (rxlen > EMAC_ETH_RX_FRAG_SIZE) { len = EMAC_ETH_RX_FRAG_SIZE; } else { len = rxlen; } /* Copy received data from current frame fragment */ memcpy(bp, (void*)RX_DESC_PACKET(idx), len); /* Clear status info and crc hash from current frame fragment */ RX_STAT_INFO(idx) = 0; RX_STAT_HASHCRC(idx) = 0; if (++idx == EMAC_NUM_RX_FRAG) { idx = 0; } rxlen -= len; bp += len; } rc = 0; } } } else { /* Silently discard the frame from EMAC buffer, update the Rx consume index */ } /* Release frame from EMAC buffer, update the Rx consume index */ LPC_EMAC->RxConsumeIndex = new_consume_idx; } return rc; } /*! * \brief Load a packet into the nic's transmit ring buffer. * * \param nb Network buffer structure containing the packet to be sent. * The structure must have been allocated by a previous * call NutNetBufAlloc(). This routine will automatically * release the buffer in case of an error. * * \return 0 on success, -1 in case of any errors. Errors * will automatically release the network buffer * structure. */ static inline int Lpc17xxEmacPutPacket(NUTDEVICE *dev, NETBUF * nb) { int rc = -1; uint16_t sz; uint8_t *buf; uint32_t idx; IFNET *ifn = (IFNET *) dev->dev_icb; EMACINFO *ni = (EMACINFO *) dev->dev_dcb; /* * Calculate the number of bytes to be send. Do not send packets * larger than the Ethernet maximum transfer unit. The MTU * consist of 1500 data bytes plus the 14 byte Ethernet header * plus 4 bytes CRC. We check the data bytes only. */ if ((sz = nb->nb_nw.sz + nb->nb_tp.sz + nb->nb_ap.sz) > ifn->if_mtu) { return -1; } sz += nb->nb_dl.sz; /* Disable EMAC interrupts. */ NutIrqDisable(&sig_EMAC); /* TODO: Check for link. */ if (ni->ni_insane == 0) { int32_t txlen = sz; int32_t offs = 0; /* Allocate a descriptors for sending frame and get the coressponding buffer address NIC interrupts must be disabled. */ idx = LPC_EMAC->TxProduceIndex; while (txlen > 0) { /* Get the current transmit fragment data pointer */ buf = (uint8_t*)TX_DESC_PACKET(idx); if (txlen > EMAC_ETH_TX_FRAG_SIZE) { /* This is not the last fragment, it is completely filled */ TX_DESC_CTRL(idx) = ((EMAC_ETH_TX_FRAG_SIZE - 1) & EMAC_TCTRL_SIZE) | EMAC_TCTRL_INT; } else { /* Round up the len to the next even number and mark this as the last fragment */ TX_DESC_CTRL(idx) = ((txlen - 1) & EMAC_TCTRL_SIZE) | (EMAC_TCTRL_INT | EMAC_TCTRL_LAST); } if (txlen == sz) { /* This is the first fragment, copy the header bytes and then the application data */ memcpy(buf, nb->nb_dl.vp, nb->nb_dl.sz); buf += nb->nb_dl.sz; txlen -= nb->nb_dl.sz; memcpy(buf, nb->nb_nw.vp, nb->nb_nw.sz); buf += nb->nb_nw.sz; txlen -= nb->nb_nw.sz; memcpy(buf, nb->nb_tp.vp, nb->nb_tp.sz); buf += nb->nb_tp.sz; txlen -= nb->nb_tp.sz; offs = min (EMAC_ETH_TX_FRAG_SIZE - nb->nb_dl.sz - nb->nb_nw.sz - nb->nb_tp.sz, nb->nb_ap.sz); memcpy(buf, nb->nb_ap.vp, offs); buf += offs; txlen -= offs; } else { register int len = min(EMAC_ETH_TX_FRAG_SIZE, nb->nb_ap.sz - offs); /* Continue copying the application data */ memcpy(buf, (uint8_t *)nb->nb_ap.vp + offs, len); offs += len; txlen -= len; } /* Increment produce index counter, taking wrap around into account */ if (++idx == LPC_EMAC->TxDescriptorNumber + 1) { idx = 0; } } /* Update tx produce index Increase the TxProduceIndex (after writting to the Transmit buffer to enable the Transmit buffer) */ LPC_EMAC->TxProduceIndex = idx; rc = 0; #ifdef NUT_PERFMON ni->ni_tx_packets++; #endif } /* Enable EMAC interrupts. */ NutIrqEnable(&sig_EMAC); return rc; } /*! * \brief Fire up the network interface. * * NIC interrupts must be disabled when calling this function. * * \param mac Six byte unique MAC address. */ static int Lpc17xxEmacStart(NUTDEVICE *dev) { IFNET *ifn = (IFNET *) dev->dev_icb; /* Set local MAC address. */ Lpc17xxEmacSetMACAddr(ifn->if_mac); /* Initialize the rx and tx buffer descriptors */ Lpc17xxEmacRxDescriptorInit(); Lpc17xxEmacTxDescriptorInit(); /* Set Receive Filter register: enable broadcast and multicast */ LPC_EMAC->RxFilterCtrl = EMAC_RFC_BCAST_EN | EMAC_RFC_PERFECT_EN; /* Reset all interrupts, Interrupts will be enabled in the RX-Thread */ LPC_EMAC->IntClear = 0xFFFF; /* Enable Transmitter and receiver */ LPC_EMAC->Command |= EMAC_CR_TX_EN | EMAC_CR_RX_EN; LPC_EMAC->MAC1 |= EMAC_MAC1_REC_EN; EMPRINTF("Lpc17xxEmacStart() DONE\n"); return 0; } /*! \fn EmacRxThread(void *arg) * \brief NIC receiver thread. * */ THREAD(Lpc17xxEmacRxThread, arg) { NUTDEVICE *dev = arg; IFNET *ifn = (IFNET *) dev->dev_icb; EMACINFO *ni = (EMACINFO *) dev->dev_dcb; NETBUF *nb; EMPRINTF("Lpc17xxEmacRxThread() INIT\n"); /* * This is a temporary hack. Due to a change in initialization, * we may not have got a MAC address yet. Wait until a valid one * has been set. */ while (!ETHER_IS_UNICAST(ifn->if_mac)) { NutSleep(10); } /* * Do not continue unless we managed to start the NIC. We are * trapped here if the Ethernet link cannot be established. * This happens, for example, if no Ethernet cable is plugged * in. */ Lpc17xxEmacStart(dev); /* Initialize the access mutex. */ NutEventPost(&ni->ni_mutex); /* Run at high priority. */ NutThreadSetPriority(9); /* Enable receive and transmit interrupts. */ LPC_EMAC->IntEnable |= EMAC_INT_RX_OVERRUN | EMAC_INT_RX_ERR | EMAC_INT_RX_DONE | EMAC_INT_TX_UNDERRUN | EMAC_INT_TX_ERR | EMAC_INT_TX_DONE; NutIrqEnable(&sig_EMAC); for (;;) { /* * Wait for the arrival of new packets or poll the receiver every * 2 seconds. The timeout can be modified if there are interrupt problems. */ NutEventWait(&ni->ni_rx_rdy, 2000); /* * Fetch all packets from the NIC's internal buffer and pass * them to the registered handler. */ while (Lpc17xxEmacGetPacket(dev, &nb) == 0) { /* Discard short packets. */ if (nb->nb_dl.sz < 60) { NutNetBufFree(nb); } else { (*ifn->if_recv) (dev, nb); } } LPC_EMAC->IntEnable |= EMAC_INT_RX_OVERRUN | EMAC_INT_RX_ERR | EMAC_INT_RX_FIN | EMAC_INT_RX_DONE; /* We got a weird chip, try to restart it. */ while (ni->ni_insane) { if (Lpc17xxEmacReset(dev)) { NutSleep(500); } else { Lpc17xxEmacStart(dev); ni->ni_insane = 0; NutIrqEnable(&sig_EMAC); } } } } /*! * \brief Send Ethernet packet. * * \param dev Identifies the device to use. * \param nb Network buffer structure containing the packet to be sent. * The structure must have been allocated by a previous * call NutNetBufAlloc(). * * \return 0 on success, -1 in case of any errors. */ int Lpc17xxEmacOutput(NUTDEVICE * dev, NETBUF * nb) { static uint32_t mx_wait = 5000; uint32_t consume_idx; uint32_t produce_idx; uint32_t num_fragments; int rc = -1; EMACINFO *ni = (EMACINFO *) dev->dev_dcb; size_t space; size_t pkg_size; /* * After initialization we are waiting for a long time to give * the PHY a chance to establish an Ethernet link. */ while (rc) { /* Check if we stay in an error condition */ if (ni->ni_insane) { break; } if (NutEventWait(&ni->ni_mutex, mx_wait)) { break; } /* Check for packet queue space. */ consume_idx = LPC_EMAC->TxConsumeIndex; produce_idx = LPC_EMAC->TxProduceIndex; num_fragments = LPC_EMAC->TxDescriptorNumber + 1; /* empty */ if (consume_idx == produce_idx) { space = num_fragments * EMAC_ETH_TX_FRAG_SIZE; } else /* full */ if ((consume_idx == 0) && (produce_idx == num_fragments - 1)) { space = 0; } else /* Wrap-around */ if (consume_idx == produce_idx + 1) { space = 0; } else { /* Calc free space */ if (consume_idx > produce_idx) { space = consume_idx - produce_idx; } else { space = num_fragments - (produce_idx - consume_idx); } space *= EMAC_ETH_TX_FRAG_SIZE; } pkg_size = nb->nb_nw.sz + nb->nb_tp.sz + nb->nb_ap.sz + nb->nb_dl.sz; if (space < pkg_size) { if (NutEventWait(&ni->ni_tx_rdy, 500)) { /* No queue space. Release the lock and give up. */ NutEventPost(&ni->ni_mutex); break; } } else if (Lpc17xxEmacPutPacket(dev, nb) == 0) { /* Ethernet works. Set a long waiting time in case we temporarly lose the link next time. */ rc = 0; } NutEventPost(&ni->ni_mutex); } /* * Probably no Ethernet link. Significantly reduce the waiting * time, so following transmission will soon return an error. */ if (rc) { mx_wait = 500; } else { /* Ethernet works. Set a long waiting time in case we temporarily lose the link next time. */ mx_wait = 5000; } return rc; } /*! * \brief Calculate CRC of a number of bytes (e.g. of an ethernet frame) * * \param data Pointer to the first byte of the data buffer * \param len length of the data * * \return Calculated CRC (32 bit) */ static int32_t Lpc17xxEmacCalcCrc(uint8_t *data, int32_t len) { int i, j; char byte; int crc; int q0, q1, q2, q3; crc = 0xFFFFFFFF; for (i = 0; i < len; i++) { byte = *(data++); for (j = 0; j < 2; j++) { if (((crc >> 28) ^ (byte >> 3)) & 0x00000001) { q3 = 0x04C11DB7; } else { q3 = 0x00000000; } if (((crc >> 29) ^ (byte >> 2)) & 0x00000001) { q2 = 0x09823B6E; } else { q2 = 0x00000000; } if (((crc >> 30) ^ (byte >> 1)) & 0x00000001) { q1 = 0x130476DC; } else { q1 = 0x00000000; } if (((crc >> 31) ^ (byte >> 0)) & 0x00000001) { q0 = 0x2608EDB8; } else { q0 = 0x00000000; } crc = (crc << 4) ^ q3 ^ q2 ^ q1 ^ q0; byte >>= 4; } } return crc; } /*! * \brief Update the multicast hash. * * This function must be called after the multicast list changed. * * \param mclst Pointer to the first entry of the linked list of * multicast entries. */ static void Lpc17xxEmacHashUpdate(MCASTENTRY *mclst) { uint32_t idx; uint32_t hash[2] = { 0, 0 }; /* Determine the hash bit for each entry. */ while (mclst) { /* Calc CRC of the hash entry. Bits 23-28 are used as hash index */ idx = (Lpc17xxEmacCalcCrc(mclst->mca_ha, 6) >> 23) & 0x3F; /* Set the bit given by the 6 bit index. */ hash[idx > 31] |= 1 << (idx & 31); mclst = mclst->mca_next; } /* Set result in the hash register. */ LPC_EMAC->HashFilterL = hash[0]; LPC_EMAC->HashFilterH = hash[1]; /* Enable or disable multicast hash. */ if (hash[0] || hash[1]) { LPC_EMAC->RxFilterCtrl |= EMAC_RFC_MCAST_HASH_EN; } else { LPC_EMAC->RxFilterCtrl &= ~EMAC_RFC_MCAST_HASH_EN; } } /*! * \brief Get multicast entry of a given IP address. * * \todo This function should be shared by all Ethernet drivers. * * \param ifn Pointer to the network interface structure. * \param ip IP address of the entry to retrieve. * * \return Pointer to the entry or NULL if none exists. */ static MCASTENTRY *McastIpEntry(IFNET *ifn, uint32_t ip) { MCASTENTRY *mca = ifn->if_mcast; while (mca) { if (ip == mca->mca_ip) { break; } mca = mca->mca_next; } return mca; } /*! * \brief Add a given IP address to the multicast list. * * \todo This function should be shared by all Ethernet drivers. * * \param ifn Pointer to the network interface structure. * \param ip IP address of the new entry. * * \return Pointer to the requested entry, either a new one or * an already existing entry. In case of a failure, NULL * is returned. */ static MCASTENTRY *McastAddEntry(IFNET *ifn, uint32_t ip) { MCASTENTRY *mca; mca = McastIpEntry(ifn, ip); if (mca == NULL) { mca = malloc(sizeof(MCASTENTRY)); if (mca) { mca->mca_ip = ip; /* Set the IANA OUI. */ mca->mca_ha[0] = 0x01; mca->mca_ha[1] = 0x00; mca->mca_ha[2] = 0x5e; /* Map the lower 23 bits of the IP address to the MAC address. Note that Nut/Net IP addresses are in network byte order. */ mca->mca_ha[3] = (ip >> 8) & 0x7f; mca->mca_ha[4] = (ip >> 16) & 0xff; mca->mca_ha[5] = (ip >> 24) & 0xff; /* Add the new entry to the front of the list. */ mca->mca_next = ifn->if_mcast; ifn->if_mcast = mca; /* Update the EMAC's multicast hash. */ Lpc17xxEmacHashUpdate(mca); } } return mca; } /*! * \brief Remove multicast entry of a given IP address. * * \param ifn Pointer to the network interface structure. * \param ip IP address of the entry to remove. */ static void McastDelEntry(IFNET *ifn, uint32_t ip) { MCASTENTRY *mca = ifn->if_mcast; MCASTENTRY **lnk = &ifn->if_mcast; while (mca) { if (mca->mca_ip == ip) { *lnk = mca->mca_next; free(mca); break; } lnk = &mca->mca_next; mca = *lnk; } /* Update the EMAC's multicast hash. */ Lpc17xxEmacHashUpdate(mca); } /*! * \brief Perform Ethernet control functions. * * \param dev Identifies the device that receives the device-control * function. * \param req Requested control function. May be set to one of the * following constants: * - SIOCSIFADDR sets interface MAC address passed in buffer. * - SIOCGIFADDR copies current interface MAC address to buffer. * - SIOCADDMULTI adds multicast entry for IP in buffer. * - SIOCDELMULTI removes multicast entry of IP in buffer. * \param conf Points to a buffer that contains any data required for * the given control function or receives data from that * function. * \return 0 on success, -1 otherwise. * */ static int Lpc17xxEmacIoCtl(NUTDEVICE * dev, int req, void *conf) { int rc = 0; IFNET *ifn = (IFNET *) dev->dev_icb; uint32_t ip; switch (req) { case SIOCSIFADDR: /* Set interface hardware address. */ memcpy(ifn->if_mac, conf, sizeof(ifn->if_mac)); break; case SIOCGIFADDR: /* Get interface hardware address. */ memcpy(conf, ifn->if_mac, sizeof(ifn->if_mac)); break; case SIOCADDMULTI: /* Add multicast address. */ memcpy(&ip, conf, sizeof(ip)); if (McastAddEntry(ifn, ip) == NULL) { rc = -1; } break; case SIOCDELMULTI: /* Delete multicast address. */ memcpy(&ip, conf, sizeof(ip)); McastDelEntry(ifn, ip); break; default: rc = -1; break; } return rc; } /*! * \brief Initialize Ethernet hardware. * * Applications should do not directly call this function. It is * automatically executed during during device registration by * NutRegisterDevice(). * * \param dev Identifies the device to initialize. */ int Lpc17xxEmacInit(NUTDEVICE * dev) { EMACINFO *ni = (EMACINFO *) dev->dev_dcb; EMPRINTF("Lpc17xxEmacInit()\n"); SysCtlPeripheralClkEnable(CLKPWR_PCONP_PCENET); /* Configure P1 Ethernet pins for RMII interface. */ /* on rev. 'A' and later, P1.6 should NOT be set. */ GpioPinConfigSet(NUTGPIO_PORT1, 0, GPIO_CFG_PERIPHERAL1); /* ETH_TXD0 */ GpioPinConfigSet(NUTGPIO_PORT1, 1, GPIO_CFG_PERIPHERAL1); /* ETH_TXD1 */ GpioPinConfigSet(NUTGPIO_PORT1, 4, GPIO_CFG_PERIPHERAL1); /* ETH_TXEN */ GpioPinConfigSet(NUTGPIO_PORT1, 8, GPIO_CFG_PERIPHERAL1); /* ETH_CRS */ GpioPinConfigSet(NUTGPIO_PORT1, 9, GPIO_CFG_PERIPHERAL1); /* ETH_RXD0 */ GpioPinConfigSet(NUTGPIO_PORT1, 10, GPIO_CFG_PERIPHERAL1); /* ETH_RXD1 */ GpioPinConfigSet(NUTGPIO_PORT1, 14, GPIO_CFG_PERIPHERAL1); /* ETH_RXER */ GpioPinConfigSet(NUTGPIO_PORT1, 15, GPIO_CFG_PERIPHERAL1); /* ETH_RXCLK */ GpioPinConfigSet(NUTGPIO_PORT1, 16, GPIO_CFG_PERIPHERAL1); /* ETH_MDC */ GpioPinConfigSet(NUTGPIO_PORT1, 17, GPIO_CFG_PERIPHERAL1); /* ETH_MDIO */ #ifndef PHY_MODE_RMII /* Configure further I/O pins for MII interface */ GpioPinConfigSet(NUTGPIO_PORT1, 2, GPIO_CFG_PERIPHERAL1); /* ETH_TXD2 */ GpioPinConfigSet(NUTGPIO_PORT1, 3, GPIO_CFG_PERIPHERAL1); /* ETH_TXD3 */ GpioPinConfigSet(NUTGPIO_PORT1, 5, GPIO_CFG_PERIPHERAL1); /* ETH_TXER */ GpioPinConfigSet(NUTGPIO_PORT1, 6, GPIO_CFG_PERIPHERAL1); /* ETH_TXCLK */ GpioPinConfigSet(NUTGPIO_PORT1, 7, GPIO_CFG_PERIPHERAL1); /* ETH_COL */ GpioPinConfigSet(NUTGPIO_PORT1, 11, GPIO_CFG_PERIPHERAL1); /* ETH_RXD2 */ GpioPinConfigSet(NUTGPIO_PORT1, 12, GPIO_CFG_PERIPHERAL1); /* ETH_RXD3 */ GpioPinConfigSet(NUTGPIO_PORT1, 13, GPIO_CFG_PERIPHERAL1); /* ETH_RXDV */ #endif /* Reset the controller. */ if (Lpc17xxEmacReset(dev) == -1) { return -1; } /* Clear EMACINFO structure. */ memset(ni, 0, sizeof(EMACINFO)); /* Register interrupt handler. */ if (NutRegisterIrqHandler(&sig_EMAC, Lpc17xxEmacInterrupt, dev)) { EMPRINTF("EMAC: Registering IRQ failed\n"); return -1; } /* Start the receiver thread. */ if (NutThreadCreate("emacrx", Lpc17xxEmacRxThread, dev, (NUT_THREAD_NICRXSTACK * NUT_THREAD_STACK_MULT) + NUT_THREAD_STACK_ADD) == NULL) { EMPRINTF("EMAC: Registering RX Thread failed\n"); return -1; } EMPRINTF("Lpc17xxEmacInit() DONE\n"); return 0; } static EMACINFO dcb_eth0; /*! * \brief Network interface information structure. * * Used to call. */ static IFNET ifn_eth0 = { IFT_ETHER, /*!< \brief Interface type, if_type. */ 0, /*!< \brief Interface flags, if_flags. */ {0, 0, 0, 0, 0, 0}, /*!< \brief Hardware net address, if_mac. */ 0, /*!< \brief IP address, if_local_ip. */ 0, /*!< \brief Remote IP address for point to point, if_remote_ip. */ 0, /*!< \brief IP network mask, if_mask. */ ETHERMTU, /*!< \brief Maximum size of a transmission unit, if_mtu. */ 0, /*!< \brief Packet identifier, if_pkt_id. */ 0, /*!< \brief Linked list of arp entries, arpTable. */ 0, /*!< \brief Linked list of multicast address entries, if_mcast. */ NutEtherInput, /*!< \brief Routine to pass received data to, if_recv(). */ Lpc17xxEmacOutput, /*!< \brief Driver output routine, if_send(). */ NutEtherOutput, /*!< \brief Media output routine, if_output(). */ NULL /*!< \brief Interface specific control function, if_ioctl(). */ #ifdef NUT_PERFMON , 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 #endif }; /*! * \brief Device information structure. * * A pointer to this structure must be passed to NutRegisterDevice() * to bind this Ethernet device driver to the Nut/OS kernel. * An application may then call NutNetIfConfig() with the name \em eth0 * of this driver to initialize the network interface. * */ NUTDEVICE devLpc17xxEmac = { 0, /*!< \brief Pointer to next device. */ {'e', 't', 'h', '0', 0, 0, 0, 0, 0}, /*!< \brief Unique device name. */ IFTYP_NET, /*!< \brief Type of device. */ 0, /*!< \brief Base address. */ 0, /*!< \brief First interrupt number. */ &ifn_eth0, /*!< \brief Interface control block. */ &dcb_eth0, /*!< \brief Driver control block. */ Lpc17xxEmacInit, /*!< \brief Driver initialization routine. */ Lpc17xxEmacIoCtl, /*!< \brief Driver specific control function. */ 0, /*!< \brief Read from device. */ 0, /*!< \brief Write to device. */ #ifdef __HARVARD_ARCH__ 0, /*!< \brief Write from program space data to device. */ #endif 0, /*!< \brief Open a device or file. */ 0, /*!< \brief Close a device or file. */ 0, /*!< \brief Request file size. */ 0, /*!< \brief Select function, optional, not yet implemented */ }; /*@}*/