/********************************************************************** * $Id$ lpc18xx_43xx_emac.c 2011-11-20 *//** * @file lpc18xx_43xx_emac.c * @brief LPC18xx/43xx ethernet driver for LWIP * @version 1.0 * @date 20. Nov. 2011 * @author NXP MCU SW Application Team * * Copyright(C) 2012, NXP Semiconductor * All rights reserved. * *********************************************************************** * Software that is described herein is for illustrative purposes only * which provides customers with programming information regarding the * products. This software is supplied "AS IS" without any warranties. * NXP Semiconductors assumes no responsibility or liability for the * use of the software, conveys no license or title under any patent, * copyright, or mask work right to the product. NXP Semiconductors * reserves the right to make changes in the software without * notification. NXP Semiconductors also make no representation or * warranty that such application will be suitable for the specified * use without further testing or modification. **********************************************************************/ #include "lwip/opt.h" #include "lwip/sys.h" #include "lwip/def.h" #include "lwip/mem.h" #include "lwip/pbuf.h" #include "lwip/stats.h" #include "lwip/snmp.h" #include "netif/etharp.h" #include "netif/ppp_oe.h" #include "boards/board.h" #include "lpc18xx_43xx_mac_regs.h" #include "lpc18xx_43xx_emac.h" #include "lpc_phy.h" // FIXME - still to do // Checksum offloading for packets using 43xx hardware #ifndef LPC_EMAC_RMII #error LPC_EMAC_RMII is not defined! #endif #if LPC_NUM_BUFF_TXDESCS < 2 #error LPC_NUM_BUFF_TXDESCS must be at least 2 #endif #if LPC_NUM_BUFF_RXDESCS < 3 #error LPC_NUM_BUFF_RXDESCS must be at least 3 #endif #ifndef LPC_CHECK_SLOWMEM #error LPC_CHECK_SLOWMEM must be 0 or 1 #endif /** @defgroup lwip18xx_43xx_emac_DRIVER lpc18xx/43xx EMAC driver for LWIP * @ingroup lwip_emac * * @{ */ #if NO_SYS == 0 /** \brief Driver transmit and receive thread priorities * * Thread priorities for receive thread and TX cleanup thread. Alter * to prioritize receive or transmit bandwidth. In a heavily loaded * system or with LWIP_DEBUG enabled, the priorities might be better * the same. */ #define tskTXCLEAN_PRIORITY (TCPIP_THREAD_PRIO - 1) #define tskRECPKT_PRIORITY (TCPIP_THREAD_PRIO - 1) #endif /** \brief Debug output formatter lock define * * When using FreeRTOS and with LWIP_DEBUG enabled, enabling this * define will allow RX debug messages to not interleave with the * TX messages (so they are actually readable). Not enabling this * define when the system is under load will cause the output to * be unreadable. There is a small tradeoff in performance for this * so use it only for debug. */ //#define LOCK_RX_THREAD /* LPC EMAC driver data structure */ struct lpc_enetdata { struct netif *netif; /**< Reference back to LWIP parent netif */ TRAN_DESC_ENH_T ptdesc[LPC_NUM_BUFF_TXDESCS]; /**< TX descriptor list */ REC_DESC_ENH_T prdesc[LPC_NUM_BUFF_RXDESCS]; /**< RX descriptor list */ struct pbuf *txpbufs[LPC_NUM_BUFF_TXDESCS]; /**< Saved pbuf pointers, for free after TX */ volatile u32_t tx_free_descs; /**< Number of free TX descriptors */ u32_t tx_fill_idx; /**< Current free TX descriptor index */ u32_t tx_reclaim_idx; /**< Next incoming TX packet descriptor index */ struct pbuf *rxpbufs[LPC_NUM_BUFF_RXDESCS]; /**< Saved pbuf pointers for RX */ volatile u32_t rx_free_descs; /**< Number of free RX descriptors */ volatile u32_t rx_get_idx; /**< Index to next RX descriptor that id to be received */ u32_t rx_next_idx; /**< Index to next RX descriptor that needs a pbuf */ #if NO_SYS == 0 sys_sem_t RxSem; /**< RX receive thread wakeup semaphore */ sys_sem_t TxCleanSem; /**< TX cleanup thread wakeup semaphore */ sys_mutex_t TXLockMutex; /**< TX critical section mutex */ xSemaphoreHandle xTXDCountSem; /**< TX free buffer counting semaphore */ #endif }; /** \brief LPC EMAC driver work data */ static struct lpc_enetdata lpc_enetdata; #if LPC_CHECK_SLOWMEM == 1 struct lpc_slowmem_array_t { u32_t start; u32_t end; }; const struct lpc_slowmem_array_t slmem[] = LPC_SLOWMEM_ARRAY; #endif /* Write a value via the MII link (non-blocking) */ void lpc_mii_write_noblock(u32_t PhyReg, u32_t Value) { /* Write value at PHY address and register */ LPC_ETHERNET->MAC_MII_ADDR = MAC_MIIA_PA(LPC_PHYDEF_PHYADDR) | MAC_MIIA_GR(PhyReg) | MAC_MIIA_CR(4) | MAC_MIIA_W; LPC_ETHERNET->MAC_MII_DATA = Value; LPC_ETHERNET->MAC_MII_ADDR |= MAC_MIIA_GB; } /* Write a value via the MII link (blocking) */ err_t lpc_mii_write(u32_t PhyReg, u32_t Value) { u32_t mst = 250; err_t sts = ERR_OK; /* Write value at PHY address and register */ lpc_mii_write_noblock(PhyReg, Value); /* Wait for unbusy status */ while (mst > 0) { sts = LPC_ETHERNET->MAC_MII_ADDR & MAC_MIIA_GB; if (sts == 0) mst = 0; else { mst--; msDelay(1); } } if (sts != 0) sts = ERR_TIMEOUT; return sts; } /* Reads current MII link busy status */ u32_t lpc_mii_is_busy(void) { return (LPC_ETHERNET->MAC_MII_ADDR & MAC_MIIA_GB); } /* Starts a read operation via the MII link (non-blocking) */ u32_t lpc_mii_read_data(void) { return LPC_ETHERNET->MAC_MII_DATA; } /* Starts a read operation via the MII link (non-blocking) */ void lpc_mii_read_noblock(u32_t PhyReg) { /* Read value at PHY address and register */ LPC_ETHERNET->MAC_MII_ADDR = MAC_MIIA_PA(LPC_PHYDEF_PHYADDR) | MAC_MIIA_GR(PhyReg) | MAC_MIIA_CR(4); LPC_ETHERNET->MAC_MII_ADDR |= MAC_MIIA_GB; } /* Read a value via the MII link (blocking) */ err_t lpc_mii_read(u32_t PhyReg, u32_t *data) { u32_t mst = 250; err_t sts = ERR_OK; /* Read value at PHY address and register */ lpc_mii_read_noblock(PhyReg); /* Wait for unbusy status */ while (mst > 0) { sts = LPC_ETHERNET->MAC_MII_ADDR & MAC_MIIA_GB; if (sts == 0) { mst = 0; *data = LPC_ETHERNET->MAC_MII_DATA; } else { mst--; msDelay(1); } } if (sts != 0) sts = ERR_TIMEOUT; return sts; } /** \brief Queues a pbuf into a free RX descriptor * * \param[in] lpc_netifdata Pointer to the driver data structure * \param[in] p Pointer to pbuf to queue */ static void lpc_rxqueue_pbuf(struct lpc_enetdata *lpc_netifdata, struct pbuf *p) { u32_t idx = lpc_netifdata->rx_next_idx; /* Save location of pbuf so we know what to pass to LWIP later */ lpc_netifdata->rxpbufs[idx] = p; /* Buffer size and address for pbuf */ lpc_netifdata->prdesc[idx].CTRL = (u32_t) RDES_ENH_BS1(p->len) | RDES_ENH_RCH; if (idx == (LPC_NUM_BUFF_RXDESCS - 1)) lpc_netifdata->prdesc[idx].CTRL |= RDES_ENH_RER; lpc_netifdata->prdesc[idx].B1ADD = (u32_t) p->payload; /* Give descriptor to MAC/DMA */ lpc_netifdata->prdesc[idx].STATUS = RDES_OWN; /* Update free count */ lpc_netifdata->rx_free_descs--; LWIP_DEBUGF(UDP_LPC_EMAC | LWIP_DBG_TRACE, ("lpc_rxqueue_pbuf: Queueing packet %p at index %d, free %d\n", p, idx, lpc_netifdata->rx_free_descs)); /* Update index for next pbuf */ idx++; if (idx >= LPC_NUM_BUFF_RXDESCS) idx = 0; lpc_netifdata->rx_next_idx = idx; } /** \brief Attempt to allocate and requeue a new pbuf for RX * * \param[in] netif Pointer to the netif structure * \returns The number of new descriptors queued */ s32_t lpc_rx_queue(struct netif *netif) { struct lpc_enetdata *lpc_netifdata = netif->state; struct pbuf *p; s32_t queued = 0; /* Attempt to requeue as many packets as possible */ while (lpc_netifdata->rx_free_descs > 0) { /* Allocate a pbuf from the pool. We need to allocate at the maximum size as we don't know the size of the yet to be received packet. */ p = pbuf_alloc(PBUF_RAW, (u16_t) EMAC_ETH_MAX_FLEN, PBUF_RAM); if (p == NULL) { LWIP_DEBUGF(UDP_LPC_EMAC | LWIP_DBG_TRACE, ("lpc_rx_queue: could not allocate RX pbuf index %d, " "free %d)\n", lpc_netifdata->rx_next_idx, lpc_netifdata->rx_free_descs)); return queued; } /* pbufs allocated from the RAM pool should be non-chained (although the hardware will allow chaining) */ LWIP_ASSERT("lpc_rx_queue: pbuf is not contiguous (chained)", pbuf_clen(p) <= 1); /* Queue packet */ lpc_rxqueue_pbuf(lpc_netifdata, p); /* Update queued count */ queued++; } return queued; } /** \brief Sets up the RX descriptor ring buffers * * This function sets up the descriptor list used for receive packets. * * \param[in] lpc_netifdata Pointer to driver data structure * \returns Always returns ERR_OK */ static err_t lpc_rx_setup(struct lpc_enetdata *lpc_netifdata) { s32_t idx; /* Set to start of list */ lpc_netifdata->rx_get_idx = 0; lpc_netifdata->rx_next_idx = 0; lpc_netifdata->rx_free_descs = LPC_NUM_BUFF_RXDESCS; /* Clear initial RX descriptor list */ memset(lpc_netifdata->prdesc, 0, sizeof(lpc_netifdata->prdesc)); /* Setup buffer chaining before allocating pbufs for descriptors just in case memory runs out. */ for (idx = 0; idx < LPC_NUM_BUFF_RXDESCS; idx++) { lpc_netifdata->prdesc[idx].CTRL = RDES_ENH_RCH; lpc_netifdata->prdesc[idx].B2ADD = (u32_t) &lpc_netifdata->prdesc[idx + 1]; } lpc_netifdata->prdesc[LPC_NUM_BUFF_RXDESCS - 1].CTRL = RDES_ENH_RCH | RDES_ENH_RER; lpc_netifdata->prdesc[LPC_NUM_BUFF_RXDESCS - 1].B2ADD = (u32_t) &lpc_netifdata->prdesc[0]; LPC_ETHERNET->DMA_REC_DES_ADDR = (u32_t) lpc_netifdata->prdesc; /* Setup up RX pbuf queue, but post a warning if not enough were queued for all descriptors. */ if (lpc_rx_queue(lpc_netifdata->netif) != LPC_NUM_BUFF_RXDESCS) LWIP_DEBUGF(UDP_LPC_EMAC | LWIP_DBG_TRACE, ("lpc_rx_setup: Warning, not enough memory for RX pbufs\n")); return ERR_OK; } /** \brief Gets data from queue and forwards to LWIP * * \param[in] netif the lwip network interface structure for this lpc_enetif * \return a pbuf filled with the received packet (including MAC header) or * NULL on memory error */ static struct pbuf *lpc_low_level_input(struct netif *netif) { struct lpc_enetdata *lpc_netifdata = netif->state; u32_t status, ridx; int rxerr = 0; struct pbuf *p; #ifdef LOCK_RX_THREAD #if NO_SYS == 0 /* Get exclusive access */ sys_mutex_lock(&lpc_netifdata->TXLockMutex); #endif #endif /* If there are no used descriptors, then this call was not for a received packet, try to setup some descriptors now */ if (lpc_netifdata->rx_free_descs == LPC_NUM_BUFF_RXDESCS) { lpc_rx_queue(netif); #ifdef LOCK_RX_THREAD #if NO_SYS == 0 sys_mutex_unlock(&lpc_netifdata->TXLockMutex); #endif #endif return NULL; } /* Get index for next descriptor with data */ ridx = lpc_netifdata->rx_get_idx; /* Return if descriptor is still owned by DMA */ if (lpc_netifdata->prdesc[ridx].STATUS & RDES_OWN) { #ifdef LOCK_RX_THREAD #if NO_SYS == 0 sys_mutex_unlock(&lpc_netifdata->TXLockMutex); #endif #endif return NULL; } /* Get address of pbuf for this descriptor */ p = lpc_netifdata->rxpbufs[ridx]; /* Get receive packet status */ status = lpc_netifdata->prdesc[ridx].STATUS; /* Check packet for errors */ if (status & RDES_ES) { LINK_STATS_INC(link.drop); /* Error conditions that cause a packet drop */ if (status & ( #if LPC_EMAC_RMII == 0 RDES_CE | RDES_RE | RDES_RWT | RDES_LC | RDES_OE | RDES_SAF | RDES_AFM #else RDES_CE | RDES_DE | RDES_RE | RDES_RWT | RDES_LC | RDES_OE | RDES_SAF | RDES_AFM #endif )) { LINK_STATS_INC(link.err); rxerr = 1; } else /* Length error check needs qualification */ if ((status & (RDES_LE | RDES_FT)) == RDES_LE) { LINK_STATS_INC(link.lenerr); rxerr = 1; } else /* CRC error check needs qualification */ if ((status & (RDES_CE | RDES_LS)) == (RDES_CE | RDES_LS)) { LINK_STATS_INC(link.chkerr); rxerr = 1; } /* Descriptor error check needs qualification */ if ((status & (RDES_DE | RDES_LS)) == (RDES_DE | RDES_LS)) { LINK_STATS_INC(link.err); rxerr = 1; } else /* Dribble bit error only applies in half duplex mode */ if ((status & RDES_DE) && (!(LPC_ETHERNET->MAC_CONFIG & MAC_CFG_DM))) { LINK_STATS_INC(link.err); rxerr = 1; } } /* Increment free descriptor count and next get index */ lpc_netifdata->rx_free_descs++; ridx++; if (ridx >= LPC_NUM_BUFF_RXDESCS) ridx = 0; lpc_netifdata->rx_get_idx = ridx; /* If an error occurred, just re-queue the pbuf */ if (rxerr) { lpc_rxqueue_pbuf(lpc_netifdata, p); p = NULL; LWIP_DEBUGF(UDP_LPC_EMAC | LWIP_DBG_TRACE, ("lpc_low_level_input: RX error condition status 0x%08x\n", status)); } else { /* Attempt to queue a new pbuf for the descriptor */ lpc_rx_queue(netif); /* Get length of received packet */ p->len = p->tot_len = (u16_t) RDES_FLMSK(status); LINK_STATS_INC(link.recv); LWIP_DEBUGF(UDP_LPC_EMAC | LWIP_DBG_TRACE, ("lpc_low_level_input: Packet received, %d bytes, " "status 0x%08x\n", p->len, status)); } /* (Re)start receive polling */ LPC_ETHERNET->DMA_REC_POLL_DEMAND = 1; #ifdef LOCK_RX_THREAD #if NO_SYS == 0 /* Get exclusive access */ sys_mutex_unlock(&lpc_netifdata->TXLockMutex); #endif #endif return p; } /** \brief Attempt to read a packet from the EMAC interface. * * \param[in] netif the lwip network interface structure for this lpc_enetif */ void lpc_enetif_input(struct netif *netif) { struct eth_hdr *ethhdr; struct pbuf *p; /* move received packet into a new pbuf */ p = lpc_low_level_input(netif); if (p == NULL) return; /* points to packet payload, which starts with an Ethernet header */ ethhdr = p->payload; switch (htons(ethhdr->type)) { case ETHTYPE_IP: case ETHTYPE_ARP: #if PPPOE_SUPPORT case ETHTYPE_PPPOEDISC: case ETHTYPE_PPPOE: #endif /* PPPOE_SUPPORT */ /* full packet send to tcpip_thread to process */ if (netif->input(p, netif) != ERR_OK) { LWIP_DEBUGF(NETIF_DEBUG, ("lpc_enetif_input: IP input error\n")); /* Free buffer */ pbuf_free(p); } break; default: /* Return buffer */ pbuf_free(p); break; } } /** \brief Sets up the TX descriptor ring buffers. * * This function sets up the descriptor list used for transmit packets. * * \param[in] lpc_netifdata Pointer to driver data structure */ static err_t lpc_tx_setup(struct lpc_enetdata *lpc_netifdata) { s32_t idx; /* Clear TX descriptors, will be queued with pbufs as needed */ memset(&lpc_netifdata->ptdesc[0], 0, sizeof(lpc_netifdata->ptdesc)); lpc_netifdata->tx_free_descs = LPC_NUM_BUFF_TXDESCS; lpc_netifdata->tx_fill_idx = 0; lpc_netifdata->tx_reclaim_idx = 0; /* Link/wrap descriptors */ for (idx = 0; idx < LPC_NUM_BUFF_TXDESCS; idx++) { lpc_netifdata->ptdesc[idx].CTRLSTAT = TDES_ENH_TCH | TDES_ENH_CIC(3); lpc_netifdata->ptdesc[idx].B2ADD = (u32_t) &lpc_netifdata->ptdesc[idx + 1]; } lpc_netifdata->ptdesc[LPC_NUM_BUFF_TXDESCS - 1].CTRLSTAT = TDES_ENH_TCH | TDES_ENH_TER | TDES_ENH_CIC(3); lpc_netifdata->ptdesc[LPC_NUM_BUFF_TXDESCS - 1].B2ADD = (u32_t) &lpc_netifdata->ptdesc[0]; /* Setup pointer to TX descriptor table */ LPC_ETHERNET->DMA_TRANS_DES_ADDR = (u32_t) lpc_netifdata->ptdesc; return ERR_OK; } /** \brief Call for freeing TX buffers that are complete * * \param[in] netif the lwip network interface structure for this lpc_enetif */ void lpc_tx_reclaim(struct netif *netif) { struct lpc_enetdata *lpc_netifdata = netif->state; s32_t ridx; u32_t status; #if NO_SYS == 0 /* Get exclusive access */ sys_mutex_lock(&lpc_netifdata->TXLockMutex); #endif /* If a descriptor is available and is no longer owned by the hardware, it can be reclaimed */ ridx = lpc_netifdata->tx_reclaim_idx; while ((lpc_netifdata->tx_free_descs < LPC_NUM_BUFF_TXDESCS) && (!(lpc_netifdata->ptdesc[ridx].CTRLSTAT & TDES_OWN))) { /* Peek at the status of the descriptor to determine if the packet is good and any status information. */ status = lpc_netifdata->ptdesc[ridx].CTRLSTAT; LWIP_DEBUGF(UDP_LPC_EMAC | LWIP_DBG_TRACE, ("lpc_tx_reclaim: Reclaiming sent packet %p, index %d\n", lpc_netifdata->txpbufs[ridx], ridx)); /* Check TX error conditions */ if (status & TDES_ES) { LWIP_DEBUGF(UDP_LPC_EMAC | LWIP_DBG_TRACE, ("lpc_tx_reclaim: TX error condition status 0x%x\n", status)); LINK_STATS_INC(link.err); #if LINK_STATS == 1 /* Error conditions that cause a packet drop */ if (status & (TDES_UF | TDES_ED | TDES_EC | TDES_LC)) LINK_STATS_INC(link.drop); #endif } /* Reset control for this descriptor */ if (ridx == (LPC_NUM_BUFF_TXDESCS - 1)) lpc_netifdata->ptdesc[ridx].CTRLSTAT = TDES_ENH_TCH | TDES_ENH_TER; else lpc_netifdata->ptdesc[ridx].CTRLSTAT = TDES_ENH_TCH; /* Free the pbuf associate with this descriptor */ if (lpc_netifdata->txpbufs[ridx]) pbuf_free(lpc_netifdata->txpbufs[ridx]); /* Reclaim this descriptor */ lpc_netifdata->tx_free_descs++; #if NO_SYS == 0 xSemaphoreGive(lpc_netifdata->xTXDCountSem); #endif ridx++; if (ridx >= LPC_NUM_BUFF_TXDESCS) ridx = 0; } lpc_netifdata->tx_reclaim_idx = ridx; #if NO_SYS == 0 /* Restore access */ sys_mutex_unlock(&lpc_netifdata->TXLockMutex); #endif } /** \brief Polls if an available TX descriptor is ready. Can be used to * determine if the low level transmit function will block. * * \param[in] netif the lwip network interface structure for this lpc_enetif * \return 0 if no descriptors are read, or >0 */ s32_t lpc_tx_ready(struct netif *netif) { return ((struct lpc_enetdata *) netif->state)->tx_free_descs; } /** \brief Low level output of a packet. Never call this from an * interrupt context, as it may block until TX descriptors * become available. * * \param[in] netif the lwip network interface structure for this lpc_enetif * \param[in] sendp the MAC packet to send (e.g. IP packet including MAC addresses and type) * \return ERR_OK if the packet could be sent or * an err_t value if the packet couldn't be sent */ static err_t lpc_low_level_output(struct netif *netif, struct pbuf *sendp) { struct lpc_enetdata *lpc_netifdata = netif->state; u32_t idx, fidx, dn, fdn; struct pbuf *p = sendp; #if LPC_CHECK_SLOWMEM == 1 struct pbuf *q, *wp; u8_t *dst; int pcopy = 0; /* Check packet address to determine if it's in slow memory and relocate if necessary */ for(q = p; ((q != NULL) && (pcopy == 0)); q = q->next) { fidx = 0; for (idx = 0; idx < sizeof(slmem); idx += sizeof(struct lpc_slowmem_array_t)) { if ((q->payload >= (void *) slmem[fidx].start) && (q->payload <= (void *) slmem[fidx].end)) { /* Needs copy */ pcopy = 1; } } } if (pcopy) { /* Create a new pbuf with the total pbuf size */ wp = pbuf_alloc(PBUF_RAW, (u16_t) EMAC_ETH_MAX_FLEN, PBUF_RAM); if (!wp) { /* Exit with error */ return ERR_MEM; } /* Copy pbuf */ dst = (u8_t *) wp->payload; wp->tot_len = 0; for(q = p; q != NULL; q = q->next) { MEMCPY(dst, (u8_t *) q->payload, q->len); dst += q->len; wp->tot_len += q->len; } wp->len = wp->tot_len; /* LWIP will free original pbuf on exit of function */ p = sendp = wp; } #endif /* Zero-copy TX buffers may be fragmented across mutliple payload chains. Determine the number of descriptors needed for the transfer. The pbuf chaining can be a mess! */ dn = (u32_t) pbuf_clen(p); /* Wait until enough descriptors are available for the transfer. */ /* THIS WILL BLOCK UNTIL THERE ARE ENOUGH DESCRIPTORS AVAILABLE */ while (dn > lpc_tx_ready(netif)) #if NO_SYS == 0 xSemaphoreTake(lpc_netifdata->xTXDCountSem, 0); #else msDelay(1); #endif /* Get the next free descriptor index */ fidx = idx = lpc_netifdata->tx_fill_idx; #if NO_SYS == 0 /* Get exclusive access */ sys_mutex_lock(&lpc_netifdata->TXLockMutex); #endif /* Fill in the next free descriptor(s) */ while (dn > 0) { dn--; /* Setup packet address and length */ lpc_netifdata->ptdesc[idx].B1ADD = (u32_t) p->payload; lpc_netifdata->ptdesc[idx].BSIZE = (u32_t) TDES_ENH_BS1(p->len); /* Save pointer to pbuf so we can reclain the memory for the pbuf after the buffer has been sent. Only the first pbuf in a chain is saved since the full chain doesn't need to be freed. */ /* For first packet only, first flag */ lpc_netifdata->tx_free_descs--; if (idx == fidx) { lpc_netifdata->ptdesc[idx].CTRLSTAT |= TDES_ENH_FS; #if LPC_CHECK_SLOWMEM == 1 /* If this is a copied pbuf, then avoid getting the extra reference or the TX reclaim will be off by 1 */ if (!pcopy) pbuf_ref(p); #else /* Increment reference count on this packet so LWIP doesn't attempt to free it on return from this call */ pbuf_ref(p); #endif } else lpc_netifdata->ptdesc[idx].CTRLSTAT |= TDES_OWN; /* Save address of pbuf, but make sure it's associated with the first chained pbuf so it gets freed once all pbuf chains are transferred. */ if (!dn) lpc_netifdata->txpbufs[idx] = sendp; else lpc_netifdata->txpbufs[idx] = NULL; /* For last packet only, interrupt and last flag */ if (dn == 0) lpc_netifdata->ptdesc[idx].CTRLSTAT |= TDES_ENH_LS | TDES_ENH_IC; /* FIXME: For now, only IP header checksumming */ lpc_netifdata->ptdesc[idx].CTRLSTAT |= TDES_ENH_CIC(3); LWIP_DEBUGF(UDP_LPC_EMAC | LWIP_DBG_TRACE, ("lpc_low_level_output: pbuf packet %p sent, chain %d," " size %d, index %d, free %d\n", p, dn, p->len, idx, lpc_netifdata->tx_free_descs)); /* Update next available descriptor */ idx++; if (idx >= LPC_NUM_BUFF_TXDESCS) idx = 0; /* Next packet fragment */ p = p->next; } lpc_netifdata->tx_fill_idx = idx; LINK_STATS_INC(link.xmit); /* Give first descriptor to DMA to start transfer */ lpc_netifdata->ptdesc[fidx].CTRLSTAT |= TDES_OWN; /* Tell DMA to poll descriptors to start transfer */ LPC_ETHERNET->DMA_TRANS_POLL_DEMAND = 1; #if NO_SYS == 0 /* Restore access */ sys_mutex_unlock(&lpc_netifdata->TXLockMutex); #endif return ERR_OK; } /** \brief LPC EMAC interrupt handler. * * This function handles the transmit, receive, and error interrupt of * the LPC118xx/43xx. This is meant to be used when NO_SYS=0. */ void ETH_IRQHandler(void) { #if NO_SYS == 1 /* Interrupts are not used without an RTOS */ NVIC_DisableIRQ(ETHERNET_IRQn); #else signed portBASE_TYPE xRecTaskWoken = pdFALSE, XTXTaskWoken = pdFALSE; uint32_t ints; /* Get pending interrupts */ ints = LPC_ETHERNET->DMA_STAT; /* RX group interrupt(s) */ if (ints & (DMA_ST_RI | DMA_ST_OVF | DMA_ST_RU)) { /* Give semaphore to wakeup RX receive task. Note the FreeRTOS method is used instead of the LWIP arch method. */ xSemaphoreGiveFromISR(lpc_enetdata.RxSem, &xRecTaskWoken); } /* TX group interrupt(s) */ if (ints & (DMA_ST_TI | DMA_ST_UNF | DMA_ST_TU)) { /* Give semaphore to wakeup TX cleanup task. Note the FreeRTOS method is used instead of the LWIP arch method. */ xSemaphoreGiveFromISR(lpc_enetdata.TxCleanSem, &XTXTaskWoken); } /* Clear pending interrupts */ LPC_ETHERNET->DMA_STAT = ints; /* Context switch needed? */ portEND_SWITCHING_ISR( xRecTaskWoken || XTXTaskWoken ); #endif } #if NO_SYS == 0 /** \brief Packet reception task * * This task is called when a packet is received. It will * pass the packet to the LWIP core. * * \param[in] pvParameters Pointer to driver data */ static portTASK_FUNCTION( vPacketReceiveTask, pvParameters ) { struct lpc_enetdata *lpc_netifdata = pvParameters; while (1) { /* Wait for receive task to wakeup */ sys_arch_sem_wait(&lpc_netifdata->RxSem, 0); /* Process receive packets */ while (!(lpc_netifdata->prdesc[lpc_netifdata->rx_get_idx].STATUS & RDES_OWN)) lpc_enetif_input(lpc_netifdata->netif); } } /** \brief Transmit cleanup task * * This task is called when a transmit interrupt occurs and * reclaims the pbuf and descriptor used for the packet once * the packet has been transferred. * * \param[in] pvParameters Pointer to driver data */ static portTASK_FUNCTION( vTransmitCleanupTask, pvParameters ) { struct lpc_enetdata *lpc_netifdata = pvParameters; while (1) { /* Wait for transmit cleanup task to wakeup */ sys_arch_sem_wait(&lpc_netifdata->TxCleanSem, 0); /* Free TX pbufs and descriptors that are done */ lpc_tx_reclaim(lpc_netifdata->netif); } } #endif /** \brief Low level init of the MAC and PHY. * * \param[in] netif Pointer to LWIP netif structure * \return ERR_OK or error code */ static err_t low_level_init(struct netif *netif) { struct lpc_enetdata *lpc_netifdata = netif->state; err_t err; s32_t timeout; /* Enable MAC clocking from same source as CPU */ CGU_EntityConnect(CGU_CLKSRC_PLL1, CGU_PERIPHERAL_ETHERNET); /* Slightly different clocking for RMII and MII modes */ CGU_EntityConnect(CGU_CLKSRC_ENET_TX_CLK, CGU_BASE_PHY_TX); #if LPC_EMAC_RMII == 1 /* RMII mode gets PHY RX clock from ENET_REF_CLK (same pin as ENET_TX_CLK on the chip) */ CGU_EntityConnect(CGU_CLKSRC_ENET_TX_CLK, CGU_BASE_PHY_RX); #else /* MII mode gets PHY RX clock from ENET_RX_CLK */ CGU_EntityConnect(CGU_CLKSRC_ENET_RX_CLK, CGU_BASE_PHY_RX); #endif /* Reset ethernet via RGU. This should be 1 clock, but we wait anyways. If the while loop really stalls, something else is wrong. */ LPC_RGU->RESET_CTRL0 = (1 << 22); timeout = 10; while (!(LPC_RGU->RESET_ACTIVE_STATUS0 & (1 << 22))) { msDelay(1); timeout--; if (timeout == 0) return ERR_TIMEOUT; } /* Reset MAC Subsystem internal registers and logic */ LPC_ETHERNET->DMA_BUS_MODE |= DMA_BM_SWR; // timeout = 3; // while (LPC_ETHERNET->DMA_BUS_MODE & DMA_BM_SWR) { // msDelay(1); // timeout--; // if (timeout == 0) // return ERR_TIMEOUT; // } LPC_ETHERNET->DMA_BUS_MODE = DMA_BM_ATDS | DMA_BM_PBL(1) | DMA_BM_RPBL(1); /* Save MAC address */ LPC_ETHERNET->MAC_ADDR0_LOW = ((u32_t) netif->hwaddr[3] << 24) | ((u32_t) netif->hwaddr[2] << 16) | ((u32_t) netif->hwaddr[1] << 8) | (u32_t) netif->hwaddr[0]; LPC_ETHERNET->MAC_ADDR0_HIGH = ((u32_t) netif->hwaddr[5] << 8) | (u32_t) netif->hwaddr[4]; /* Initial MAC configuration for checksum offload, full duplex, 100Mbps, disable receive own in half duplex, inter-frame gap of 64-bits */ LPC_ETHERNET->MAC_CONFIG = MAC_CFG_BL(0) | MAC_CFG_IPC | MAC_CFG_DM | MAC_CFG_DO | MAC_CFG_FES | MAC_CFG_PS | MAC_CFG_IFG(3); /* Setup filter */ #if IP_SOF_BROADCAST_RECV LPC_ETHERNET->MAC_FRAME_FILTER = MAC_FF_PR | MAC_FF_RA; #else LPC_ETHERNET->MAC_FRAME_FILTER = 0; /* Only matching MAC address */ #endif /* Initialize the PHY */ err = lpc_phy_init(netif, LPC_EMAC_RMII); if (err != ERR_OK) return err; /* Setup transmit and receive descriptors */ if (lpc_tx_setup(lpc_netifdata) != ERR_OK) return ERR_BUF; if (lpc_rx_setup(lpc_netifdata) != ERR_OK) return ERR_BUF; /* Flush transmit FIFO */ LPC_ETHERNET->DMA_OP_MODE = DMA_OM_FTF; /* Setup DMA to flush receive FIFOs at 32 bytes, service TX FIFOs at 64 bytes */ LPC_ETHERNET->DMA_OP_MODE |= DMA_OM_RTC(1) | DMA_OM_TTC(0); /* Clear all MAC interrupts */ LPC_ETHERNET->DMA_STAT = DMA_ST_ALL; /* Enable MAC interrupts */ LPC_ETHERNET->DMA_INT_EN = #if NO_SYS == 1 0; #else DMA_IE_TIE | DMA_IE_OVE | DMA_IE_UNE | DMA_IE_RIE | DMA_IE_NIE | DMA_IE_AIE | DMA_IE_TUE | DMA_IE_RUE; #endif /* Enable receive and transmit DMA processes */ LPC_ETHERNET->DMA_OP_MODE |= DMA_OM_ST | DMA_OM_SR; /* Enable packet reception */ LPC_ETHERNET->MAC_CONFIG |= MAC_CFG_RE | MAC_CFG_TE; /* Start receive polling */ LPC_ETHERNET->DMA_REC_POLL_DEMAND = 1; return ERR_OK; } /** * This function provides a method for the PHY to setup the EMAC * for the PHY negotiated duplex mode. * * \param[in] full_duplex 0 = half duplex, 1 = full duplex */ void lpc_emac_set_duplex(int full_duplex) { if (full_duplex) LPC_ETHERNET->MAC_CONFIG |= MAC_CFG_DM; else LPC_ETHERNET->MAC_CONFIG &= ~MAC_CFG_DM; } /** * This function provides a method for the PHY to setup the EMAC * for the PHY negotiated bit rate. * * \param[in] mbs_100 0 = 10mbs mode, 1 = 100mbs mode */ void lpc_emac_set_speed(int mbs_100) { if (mbs_100) LPC_ETHERNET->MAC_CONFIG |= MAC_CFG_FES; else LPC_ETHERNET->MAC_CONFIG &= ~MAC_CFG_FES; } /** * This function is the ethernet packet send function. It calls * etharp_output after checking link status. * * \param[in] netif the lwip network interface structure for this lpc_enetif * \param[in] q Pointer to pbug to send * \param[in] ipaddr IP address * \return ERR_OK or error code */ err_t lpc_etharp_output(struct netif *netif, struct pbuf *q, ip_addr_t *ipaddr) { /* Only send packet is link is up */ if (netif->flags & NETIF_FLAG_LINK_UP) return etharp_output(netif, q, ipaddr); return ERR_CONN; } /** * Should be called at the beginning of the program to set up the * network interface. * * This function should be passed as a parameter to netif_add(). * * \param[in] netif the lwip network interface structure for this lpc_enetif * \return ERR_OK if the loopif is initialized * ERR_MEM if private data couldn't be allocated * any other err_t on error */ void boardGetMACaddr(uint8_t *macaddr); // FIXME ethernet err_t lpc_enetif_init(struct netif *netif) { err_t err; LWIP_ASSERT("netif != NULL", (netif != NULL)); lpc_enetdata.netif = netif; /* set MAC hardware address */ boardGetMACaddr(netif->hwaddr); netif->hwaddr_len = ETHARP_HWADDR_LEN; /* maximum transfer unit */ netif->mtu = 1500; /* device capabilities */ netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_UP | NETIF_FLAG_ETHERNET; /* Initialize the hardware */ netif->state = &lpc_enetdata; err = low_level_init(netif); if (err != ERR_OK) return err; #if LWIP_NETIF_HOSTNAME /* Initialize interface hostname */ netif->hostname = "lwiplpc"; #endif /* LWIP_NETIF_HOSTNAME */ netif->name[0] = 'e'; netif->name[1] = 'n'; netif->output = lpc_etharp_output; netif->linkoutput = lpc_low_level_output; /* For FreeRTOS, start tasks */ #if NO_SYS == 0 lpc_enetdata.xTXDCountSem = xSemaphoreCreateCounting(LPC_NUM_BUFF_TXDESCS, LPC_NUM_BUFF_TXDESCS); LWIP_ASSERT("xTXDCountSem creation error", (lpc_enetdata.xTXDCountSem != NULL)); err = sys_mutex_new(&lpc_enetdata.TXLockMutex); LWIP_ASSERT("TXLockMutex creation error", (err == ERR_OK)); /* Packet receive task */ err = sys_sem_new(&lpc_enetdata.RxSem, 0); LWIP_ASSERT("RxSem creation error", (err == ERR_OK)); sys_thread_new("receive_thread", vPacketReceiveTask, netif->state, DEFAULT_THREAD_STACKSIZE, tskRECPKT_PRIORITY); /* Transmit cleanup task */ err = sys_sem_new(&lpc_enetdata.TxCleanSem, 0); LWIP_ASSERT("TxCleanSem creation error", (err == ERR_OK)); sys_thread_new("txclean_thread", vTransmitCleanupTask, netif->state, DEFAULT_THREAD_STACKSIZE, tskTXCLEAN_PRIORITY); #endif return ERR_OK; } /** * @} */ /* --------------------------------- End Of File ------------------------------ */