/** * @file * * @ingroup lpc_eth * * @brief Ethernet driver. */ /* * Copyright (c) 2009 * embedded brains GmbH * Obere Lagerstr. 30 * D-82178 Puchheim * Germany * * * The license and distribution terms for this file may be * found in the file LICENSE in this distribution or at * http://www.rtems.com/license/LICENSE. */ #define __INSIDE_RTEMS_BSD_TCPIP_STACK__ 1 #define __BSD_VISIBLE 1 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if MCLBYTES > (2 * 1024) #error "MCLBYTES to large" #endif #ifdef LPC_ETH_CONFIG_USE_TRANSMIT_DMA #define LPC_ETH_CONFIG_TX_BUF_SIZE sizeof(struct mbuf *) #else #define LPC_ETH_CONFIG_TX_BUF_SIZE 1518U #endif typedef struct { uint32_t start; uint32_t control; } lpc_eth_transfer_descriptor; typedef struct { uint32_t info; uint32_t hash_crc; } lpc_eth_receive_status; typedef struct { uint32_t mac1; uint32_t mac2; uint32_t ipgt; uint32_t ipgr; uint32_t clrt; uint32_t maxf; uint32_t supp; uint32_t test; uint32_t mcfg; uint32_t mcmd; uint32_t madr; uint32_t mwtd; uint32_t mrdd; uint32_t mind; uint32_t reserved_0 [2]; uint32_t sa0; uint32_t sa1; uint32_t sa2; uint32_t reserved_1 [45]; uint32_t command; uint32_t status; uint32_t rxdescriptor; uint32_t rxstatus; uint32_t rxdescriptornum; uint32_t rxproduceindex; uint32_t rxconsumeindex; uint32_t txdescriptor; uint32_t txstatus; uint32_t txdescriptornum; uint32_t txproduceindex; uint32_t txconsumeindex; uint32_t reserved_2 [10]; uint32_t tsv0; uint32_t tsv1; uint32_t rsv; uint32_t reserved_3 [3]; uint32_t flowcontrolcnt; uint32_t flowcontrolsts; uint32_t reserved_4 [34]; uint32_t rxfilterctrl; uint32_t rxfilterwolsts; uint32_t rxfilterwolclr; uint32_t reserved_5 [1]; uint32_t hashfilterl; uint32_t hashfilterh; uint32_t reserved_6 [882]; uint32_t intstatus; uint32_t intenable; uint32_t intclear; uint32_t intset; uint32_t reserved_7 [1]; uint32_t powerdown; } lpc_eth_controller; static volatile lpc_eth_controller *const lpc_eth = (volatile lpc_eth_controller *) LPC_ETH_CONFIG_REG_BASE; /* ETH_RX_CTRL */ #define ETH_RX_CTRL_SIZE_MASK 0x000007ffU #define ETH_RX_CTRL_INTERRUPT 0x80000000U /* ETH_RX_STAT */ #define ETH_RX_STAT_RXSIZE_MASK 0x000007ffU #define ETH_RX_STAT_BYTES 0x00000100U #define ETH_RX_STAT_CONTROL_FRAME 0x00040000U #define ETH_RX_STAT_VLAN 0x00080000U #define ETH_RX_STAT_FAIL_FILTER 0x00100000U #define ETH_RX_STAT_MULTICAST 0x00200000U #define ETH_RX_STAT_BROADCAST 0x00400000U #define ETH_RX_STAT_CRC_ERROR 0x00800000U #define ETH_RX_STAT_SYMBOL_ERROR 0x01000000U #define ETH_RX_STAT_LENGTH_ERROR 0x02000000U #define ETH_RX_STAT_RANGE_ERROR 0x04000000U #define ETH_RX_STAT_ALIGNMENT_ERROR 0x08000000U #define ETH_RX_STAT_OVERRUN 0x10000000U #define ETH_RX_STAT_NO_DESCRIPTOR 0x20000000U #define ETH_RX_STAT_LAST_FLAG 0x40000000U #define ETH_RX_STAT_ERROR 0x80000000U /* ETH_TX_CTRL */ #define ETH_TX_CTRL_SIZE_MASK 0x7ffU #define ETH_TX_CTRL_SIZE_SHIFT 0 #define ETH_TX_CTRL_OVERRIDE 0x04000000U #define ETH_TX_CTRL_HUGE 0x08000000U #define ETH_TX_CTRL_PAD 0x10000000U #define ETH_TX_CTRL_CRC 0x20000000U #define ETH_TX_CTRL_LAST 0x40000000U #define ETH_TX_CTRL_INTERRUPT 0x80000000U /* ETH_TX_STAT */ #define ETH_TX_STAT_COLLISION_COUNT_MASK 0x01e00000U #define ETH_TX_STAT_DEFER 0x02000000U #define ETH_TX_STAT_EXCESSIVE_DEFER 0x04000000U #define ETH_TX_STAT_EXCESSIVE_COLLISION 0x08000000U #define ETH_TX_STAT_LATE_COLLISION 0x10000000U #define ETH_TX_STAT_UNDERRUN 0x20000000U #define ETH_TX_STAT_NO_DESCRIPTOR 0x40000000U #define ETH_TX_STAT_ERROR 0x80000000U /* ETH_INT */ #define ETH_INT_RX_OVERRUN 0x00000001U #define ETH_INT_RX_ERROR 0x00000002U #define ETH_INT_RX_FINISHED 0x00000004U #define ETH_INT_RX_DONE 0x00000008U #define ETH_INT_TX_UNDERRUN 0x00000010U #define ETH_INT_TX_ERROR 0x00000020U #define ETH_INT_TX_FINISHED 0x00000040U #define ETH_INT_TX_DONE 0x00000080U #define ETH_INT_SOFT 0x00001000U #define ETH_INT_WAKEUP 0x00002000U /* ETH_RX_FIL_CTRL */ #define ETH_RX_FIL_CTRL_ACCEPT_UNICAST 0x00000001U #define ETH_RX_FIL_CTRL_ACCEPT_BROADCAST 0x00000002U #define ETH_RX_FIL_CTRL_ACCEPT_MULTICAST 0x00000004U #define ETH_RX_FIL_CTRL_ACCEPT_UNICAST_HASH 0x00000008U #define ETH_RX_FIL_CTRL_ACCEPT_MULTICAST_HASH 0x00000010U #define ETH_RX_FIL_CTRL_ACCEPT_PERFECT 0x00000020U #define ETH_RX_FIL_CTRL_MAGIC_PACKET_WOL 0x00001000U #define ETH_RX_FIL_CTRL_RX_FILTER_WOL 0x00002000U /* ETH_CMD */ #define ETH_CMD_RX_ENABLE 0x00000001U #define ETH_CMD_TX_ENABLE 0x00000002U #define ETH_CMD_REG_RESET 0x00000008U #define ETH_CMD_TX_RESET 0x00000010U #define ETH_CMD_RX_RESET 0x00000020U #define ETH_CMD_PASS_RUNT_FRAME 0x00000040U #define ETH_CMD_PASS_RX_FILTER 0X00000080U #define ETH_CMD_TX_FLOW_CONTROL 0x00000100U #define ETH_CMD_RMII 0x00000200U #define ETH_CMD_FULL_DUPLEX 0x00000400U /* ETH_STAT */ #define ETH_STAT_RX_ACTIVE 0x00000001U #define ETH_STAT_TX_ACTIVE 0x00000002U /* Events */ #define LPC_ETH_EVENT_INITIALIZE RTEMS_EVENT_1 #define LPC_ETH_EVENT_START RTEMS_EVENT_2 #define LPC_ETH_EVENT_INTERRUPT RTEMS_EVENT_3 /* Status */ #define LPC_ETH_INTERRUPT_RECEIVE \ (ETH_INT_RX_ERROR | ETH_INT_RX_FINISHED | ETH_INT_RX_DONE) #define LPC_ETH_INTERRUPT_TRANSMIT \ (ETH_INT_TX_DONE | ETH_INT_TX_FINISHED | ETH_INT_TX_ERROR) #define LPC_ETH_RX_STAT_ERRORS \ (ETH_RX_STAT_CRC_ERROR \ | ETH_RX_STAT_SYMBOL_ERROR \ | ETH_RX_STAT_LENGTH_ERROR \ | ETH_RX_STAT_ALIGNMENT_ERROR \ | ETH_RX_STAT_OVERRUN \ | ETH_RX_STAT_NO_DESCRIPTOR) #define LPC_ETH_LAST_FRAGMENT_FLAGS \ (ETH_TX_CTRL_OVERRIDE \ | ETH_TX_CTRL_PAD \ | ETH_TX_CTRL_CRC \ | ETH_TX_CTRL_INTERRUPT \ | ETH_TX_CTRL_LAST) /* Debug */ #ifdef DEBUG #define LPC_ETH_PRINTF(...) printf(__VA_ARGS__) #define LPC_ETH_PRINTK(...) printk(__VA_ARGS__) #else #define LPC_ETH_PRINTF(...) #define LPC_ETH_PRINTK(...) #endif typedef enum { LPC_ETH_NOT_INITIALIZED, LPC_ETH_INITIALIZED, LPC_ETH_STARTED, LPC_ETH_RUNNING } lpc_eth_state; typedef struct { struct arpcom arpcom; struct rtems_mdio_info mdio_info; lpc_eth_state state; rtems_id receive_task; rtems_id transmit_task; unsigned rx_unit_count; unsigned tx_unit_count; volatile lpc_eth_transfer_descriptor *rx_desc_table; volatile lpc_eth_receive_status *rx_status_table; struct mbuf **rx_mbuf_table; volatile lpc_eth_transfer_descriptor *tx_desc_table; volatile uint32_t *tx_status_table; void *tx_buf_table; unsigned received_frames; unsigned receive_interrupts; unsigned transmitted_frames; unsigned transmit_interrupts; unsigned receive_overrun_errors; unsigned receive_fragment_errors; unsigned receive_crc_errors; unsigned receive_symbol_errors; unsigned receive_length_errors; unsigned receive_alignment_errors; unsigned receive_no_descriptor_errors; unsigned receive_fatal_errors; unsigned transmit_underrun_errors; unsigned transmit_late_collision_errors; unsigned transmit_excessive_collision_errors; unsigned transmit_excessive_defer_errors; unsigned transmit_no_descriptor_errors; unsigned transmit_overflow_errors; unsigned transmit_fatal_errors; } lpc_eth_driver_entry; static lpc_eth_driver_entry lpc_eth_driver_data = { .state = LPC_ETH_NOT_INITIALIZED, .receive_task = RTEMS_ID_NONE, .transmit_task = RTEMS_ID_NONE }; static inline uint32_t lpc_eth_increment( uint32_t value, uint32_t cycle ) { if (value < cycle) { return ++value; } else { return 0; } } static void lpc_eth_enable_promiscous_mode(bool enable) { if (enable) { lpc_eth->rxfilterctrl = ETH_RX_FIL_CTRL_ACCEPT_PERFECT | ETH_RX_FIL_CTRL_ACCEPT_UNICAST | ETH_RX_FIL_CTRL_ACCEPT_MULTICAST | ETH_RX_FIL_CTRL_ACCEPT_BROADCAST; } else { lpc_eth->rxfilterctrl = ETH_RX_FIL_CTRL_ACCEPT_PERFECT | ETH_RX_FIL_CTRL_ACCEPT_BROADCAST; } } static void lpc_eth_interrupt_handler(void *arg) { lpc_eth_driver_entry *e = (lpc_eth_driver_entry *) arg; rtems_event_set re = 0; rtems_event_set te = 0; uint32_t ie = 0; /* Get interrupt status */ uint32_t im = lpc_eth->intenable; uint32_t is = lpc_eth->intstatus & im; /* Check receive interrupts */ if ((is & ETH_INT_RX_OVERRUN) != 0) { re = LPC_ETH_EVENT_INITIALIZE; ++e->receive_fatal_errors; } else if ((is & LPC_ETH_INTERRUPT_RECEIVE) != 0) { re = LPC_ETH_EVENT_INTERRUPT; ie |= LPC_ETH_INTERRUPT_RECEIVE; } /* Send events to receive task */ if (re != 0) { ++e->receive_interrupts; (void) rtems_event_send(e->receive_task, re); } /* Check transmit interrupts */ if ((is & ETH_INT_TX_UNDERRUN) != 0) { te = LPC_ETH_EVENT_INITIALIZE; ++e->transmit_fatal_errors; } else if ((is & LPC_ETH_INTERRUPT_TRANSMIT) != 0) { te = LPC_ETH_EVENT_INTERRUPT; ie |= LPC_ETH_INTERRUPT_TRANSMIT; } /* Send events to transmit task */ if (te != 0) { ++e->transmit_interrupts; (void) rtems_event_send(e->transmit_task, te); } LPC_ETH_PRINTK("interrupt: rx = 0x%08x, tx = 0x%08x\n", re, te); /* Update interrupt mask */ lpc_eth->intenable = im & ~ie; /* Clear interrupts */ lpc_eth->intclear = is; } static void lpc_eth_enable_receive_interrupts(void) { rtems_interrupt_level level; rtems_interrupt_disable(level); lpc_eth->intenable |= LPC_ETH_INTERRUPT_RECEIVE; rtems_interrupt_enable(level); } static void lpc_eth_disable_receive_interrupts(void) { rtems_interrupt_level level; rtems_interrupt_disable(level); lpc_eth->intenable &= ~LPC_ETH_INTERRUPT_RECEIVE; rtems_interrupt_enable(level); } static void lpc_eth_enable_transmit_interrupts(void) { rtems_interrupt_level level; rtems_interrupt_disable(level); lpc_eth->intenable |= LPC_ETH_INTERRUPT_TRANSMIT; rtems_interrupt_enable(level); } static void lpc_eth_disable_transmit_interrupts(void) { rtems_interrupt_level level; rtems_interrupt_disable(level); lpc_eth->intenable &= ~LPC_ETH_INTERRUPT_TRANSMIT; rtems_interrupt_enable(level); } #define LPC_ETH_RX_DATA_OFFSET 2 static struct mbuf *lpc_eth_new_mbuf(struct ifnet *ifp, bool wait) { struct mbuf *m = NULL; int mw = wait ? M_WAIT : M_DONTWAIT; MGETHDR(m, mw, MT_DATA); if (m != NULL) { MCLGET(m, mw); if ((m->m_flags & M_EXT) != 0) { /* Set receive interface */ m->m_pkthdr.rcvif = ifp; /* Adjust by two bytes for proper IP header alignment */ m->m_data = mtod(m, char *) + LPC_ETH_RX_DATA_OFFSET; return m; } else { m_free(m); } } return NULL; } static bool lpc_eth_add_new_mbuf( struct ifnet *ifp, volatile lpc_eth_transfer_descriptor *desc, struct mbuf **mbufs, uint32_t i, bool wait ) { /* New mbuf */ struct mbuf *m = lpc_eth_new_mbuf(ifp, wait); /* Check mbuf */ if (m != NULL) { /* Cache invalidate */ rtems_cache_invalidate_multiple_data_lines( mtod(m, void *), MCLBYTES - LPC_ETH_RX_DATA_OFFSET ); /* Add mbuf to queue */ desc [i].start = mtod(m, uint32_t); desc [i].control = (MCLBYTES - LPC_ETH_RX_DATA_OFFSET - 1) | ETH_RX_CTRL_INTERRUPT; /* Cache flush of descriptor */ rtems_cache_flush_multiple_data_lines((void *)&desc [i], sizeof(desc [0])); /* Add mbuf to table */ mbufs [i] = m; return true; } else { return false; } } static void lpc_eth_receive_task(void *arg) { rtems_status_code sc = RTEMS_SUCCESSFUL; rtems_event_set events = 0; lpc_eth_driver_entry *const e = (lpc_eth_driver_entry *) arg; struct ifnet *const ifp = &e->arpcom.ac_if; volatile lpc_eth_transfer_descriptor *const desc = e->rx_desc_table; volatile lpc_eth_receive_status *const status = e->rx_status_table; struct mbuf **const mbufs = e->rx_mbuf_table; uint32_t const index_max = e->rx_unit_count - 1; uint32_t produce_index = 0; uint32_t consume_index = 0; uint32_t receive_index = 0; LPC_ETH_PRINTF("%s\n", __func__); /* Main event loop */ while (true) { bool wait_for_mbuf = false; /* Wait for events */ sc = rtems_bsdnet_event_receive( LPC_ETH_EVENT_INITIALIZE | LPC_ETH_EVENT_INTERRUPT, RTEMS_EVENT_ANY | RTEMS_WAIT, RTEMS_NO_TIMEOUT, &events ); RTEMS_CLEANUP_SC(sc, cleanup, "wait for events"); LPC_ETH_PRINTF("rx: wake up: 0x%08" PRIx32 "\n", events); /* Initialize receiver? */ if ((events & LPC_ETH_EVENT_INITIALIZE) != 0) { /* Disable receive interrupts */ lpc_eth_disable_receive_interrupts(); /* Disable receiver */ lpc_eth->command &= ~ETH_CMD_RX_ENABLE; /* Wait for inactive status */ while ((lpc_eth->status & ETH_STAT_RX_ACTIVE) != 0) { /* Wait */ } /* Reset */ lpc_eth->command |= ETH_CMD_RX_RESET; /* Clear receive interrupts */ lpc_eth->intclear = LPC_ETH_INTERRUPT_RECEIVE; /* Move existing mbufs to the front */ consume_index = 0; for (produce_index = 0; produce_index <= index_max; ++produce_index) { if (mbufs [produce_index] != NULL) { mbufs [consume_index] = mbufs [produce_index]; ++consume_index; } } /* Fill receive queue */ for ( produce_index = consume_index; produce_index <= index_max; ++produce_index ) { lpc_eth_add_new_mbuf(ifp, desc, mbufs, produce_index, true); } /* Receive descriptor table */ lpc_eth->rxdescriptornum = index_max; lpc_eth->rxdescriptor = (uint32_t) desc; lpc_eth->rxstatus = (uint32_t) status; /* Initialize indices */ produce_index = lpc_eth->rxproduceindex; consume_index = lpc_eth->rxconsumeindex; receive_index = consume_index; /* Enable receiver */ lpc_eth->command |= ETH_CMD_RX_ENABLE; /* Enable receive interrupts */ lpc_eth_enable_receive_interrupts(); /* Wait for events */ continue; } while (true) { /* Clear receive interrupt status */ lpc_eth->intclear = LPC_ETH_INTERRUPT_RECEIVE; /* Get current produce index */ produce_index = lpc_eth->rxproduceindex; if (receive_index != produce_index) { uint32_t stat = 0; /* Fragment mbuf */ struct mbuf *m = mbufs [receive_index]; /* Fragment status */ rtems_cache_invalidate_multiple_data_lines ((void *)&status [receive_index], sizeof(status [0]) ); stat = status [receive_index].info; /* Remove mbuf from table */ mbufs [receive_index] = NULL; if ( (stat & ETH_RX_STAT_LAST_FLAG) != 0 && (stat & LPC_ETH_RX_STAT_ERRORS) == 0 ) { /* Ethernet header */ struct ether_header *eh = mtod(m, struct ether_header *); /* Discard Ethernet header and CRC */ int sz = (int) (stat & ETH_RX_STAT_RXSIZE_MASK) + 1 - ETHER_HDR_LEN - ETHER_CRC_LEN; /* Update mbuf */ m->m_len = sz; m->m_pkthdr.len = sz; m->m_data = mtod(m, char *) + ETHER_HDR_LEN; LPC_ETH_PRINTF("rx: %02" PRIu32 ": %u\n", receive_index, sz); /* Hand over */ ether_input(ifp, eh, m); /* Increment received frames counter */ ++e->received_frames; } else { /* Release mbuf */ m_free(m); /* Update error counters */ if ((stat & ETH_RX_STAT_OVERRUN) != 0) { ++e->receive_overrun_errors; } if ((stat & ETH_RX_STAT_LAST_FLAG) == 0) { ++e->receive_fragment_errors; } if ((stat & ETH_RX_STAT_CRC_ERROR) != 0) { ++e->receive_crc_errors; } if ((stat & ETH_RX_STAT_SYMBOL_ERROR) != 0) { ++e->receive_symbol_errors; } if ((stat & ETH_RX_STAT_LENGTH_ERROR) != 0) { ++e->receive_length_errors; } if ((stat & ETH_RX_STAT_ALIGNMENT_ERROR) != 0) { ++e->receive_alignment_errors; } if ((stat & ETH_RX_STAT_NO_DESCRIPTOR) != 0) { ++e->receive_no_descriptor_errors; } } /* Increment receive index */ receive_index = lpc_eth_increment(receive_index, index_max); } else { /* Nothing to do, enable receive interrupts */ lpc_eth_enable_receive_interrupts(); break; } } /* Wait for mbuf? */ wait_for_mbuf = lpc_eth_increment(produce_index, index_max) == consume_index; /* Fill queue with new mbufs */ while (consume_index != produce_index) { /* Add new mbuf to queue */ if ( !lpc_eth_add_new_mbuf(ifp, desc, mbufs, consume_index, wait_for_mbuf) ) { break; } /* We wait for at most one mbuf */ wait_for_mbuf = false; /* Increment consume index */ consume_index = lpc_eth_increment(consume_index, index_max); /* Update consume indices */ lpc_eth->rxconsumeindex = consume_index; } } cleanup: /* Clear task ID */ e->receive_task = RTEMS_ID_NONE; /* Release network semaphore */ rtems_bsdnet_semaphore_release(); /* Terminate self */ (void) rtems_task_delete(RTEMS_SELF); } static struct mbuf *lpc_eth_next_fragment( struct ifnet *ifp, struct mbuf *m, uint32_t *ctrl ) { struct mbuf *n = NULL; int size = 0; while (true) { if (m == NULL) { /* Dequeue first fragment of the next frame */ IF_DEQUEUE(&ifp->if_snd, m); /* Empty queue? */ if (m == NULL) { return m; } } /* Get fragment size */ size = m->m_len; if (size > 0) { /* Now we have a not empty fragment */ break; } else { /* Discard empty fragments */ m = m_free(m); } } /* Set fragment size */ *ctrl = (uint32_t) (size - 1); /* Discard empty successive fragments */ n = m->m_next; while (n != NULL && n->m_len <= 0) { n = m_free(n); } m->m_next = n; /* Is our fragment the last in the frame? */ if (n == NULL) { *ctrl |= LPC_ETH_LAST_FRAGMENT_FLAGS; } return m; } static void lpc_eth_transmit_task(void *arg) { rtems_status_code sc = RTEMS_SUCCESSFUL; rtems_event_set events = 0; lpc_eth_driver_entry *e = (lpc_eth_driver_entry *) arg; struct ifnet *ifp = &e->arpcom.ac_if; volatile lpc_eth_transfer_descriptor *const desc = e->tx_desc_table; volatile uint32_t *const status = e->tx_status_table; #ifdef LPC_ETH_CONFIG_USE_TRANSMIT_DMA struct mbuf **const mbufs = e->tx_buf_table; #else char *const buf = e->tx_buf_table; #endif struct mbuf *m = NULL; uint32_t const index_max = e->tx_unit_count - 1; uint32_t produce_index = 0; uint32_t consume_index = 0; uint32_t ctrl = 0; #ifndef LPC_ETH_CONFIG_USE_TRANSMIT_DMA uint32_t frame_length = 0; char *frame_buffer = NULL; #endif LPC_ETH_PRINTF("%s\n", __func__); #ifndef LPC_ETH_CONFIG_USE_TRANSMIT_DMA /* Initialize descriptor table */ for (produce_index = 0; produce_index <= index_max; ++produce_index) { desc [produce_index].start = (uint32_t) (buf + produce_index * LPC_ETH_CONFIG_TX_BUF_SIZE); } #endif /* Main event loop */ while (true) { /* Wait for events */ sc = rtems_bsdnet_event_receive( LPC_ETH_EVENT_INITIALIZE | LPC_ETH_EVENT_START | LPC_ETH_EVENT_INTERRUPT, RTEMS_EVENT_ANY | RTEMS_WAIT, RTEMS_NO_TIMEOUT, &events ); RTEMS_CLEANUP_SC(sc, cleanup, "wait for events"); LPC_ETH_PRINTF("tx: wake up: 0x%08" PRIx32 "\n", events); /* Initialize transmitter? */ if ((events & LPC_ETH_EVENT_INITIALIZE) != 0) { /* Disable transmit interrupts */ lpc_eth_disable_transmit_interrupts(); /* Disable transmitter */ lpc_eth->command &= ~ETH_CMD_TX_ENABLE; /* Wait for inactive status */ while ((lpc_eth->status & ETH_STAT_TX_ACTIVE) != 0) { /* Wait */ } /* Reset */ lpc_eth->command |= ETH_CMD_TX_RESET; /* Clear transmit interrupts */ lpc_eth->intclear = LPC_ETH_INTERRUPT_TRANSMIT; /* Transmit descriptors */ lpc_eth->txdescriptornum = index_max; lpc_eth->txdescriptor = (uint32_t) desc; lpc_eth->txstatus = (uint32_t) status; #ifdef LPC_ETH_CONFIG_USE_TRANSMIT_DMA /* Discard outstanding fragments (= data loss) */ for (produce_index = 0; produce_index <= index_max; ++produce_index) { struct mbuf *victim = mbufs [produce_index]; if (victim != NULL) { m_free(victim); mbufs [produce_index] = NULL; } } #endif /* Initialize indices */ produce_index = lpc_eth->txproduceindex; consume_index = lpc_eth->txconsumeindex; #ifndef LPC_ETH_CONFIG_USE_TRANSMIT_DMA /* Fresh frame length and buffer start */ frame_length = 0; frame_buffer = (char *) desc [produce_index].start; #endif /* Enable transmitter */ lpc_eth->command |= ETH_CMD_TX_ENABLE; } /* Free consumed fragments */ while (true) { /* Save last known consume index */ uint32_t c = consume_index; /* Clear transmit interrupt status */ lpc_eth->intclear = LPC_ETH_INTERRUPT_TRANSMIT; /* Get new consume index */ consume_index = lpc_eth->txconsumeindex; /* Nothing consumed in the meantime? */ if (c == consume_index) { break; } while (c != consume_index) { uint32_t s = status [c]; /* Update error counters */ if ((s & (ETH_TX_STAT_ERROR | ETH_TX_STAT_NO_DESCRIPTOR)) != 0) { if ((s & ETH_TX_STAT_UNDERRUN) != 0) { ++e->transmit_underrun_errors; } if ((s & ETH_TX_STAT_LATE_COLLISION) != 0) { ++e->transmit_late_collision_errors; } if ((s & ETH_TX_STAT_EXCESSIVE_COLLISION) != 0) { ++e->transmit_excessive_collision_errors; } if ((s & ETH_TX_STAT_EXCESSIVE_DEFER) != 0) { ++e->transmit_excessive_defer_errors; } if ((s & ETH_TX_STAT_NO_DESCRIPTOR) != 0) { ++e->transmit_no_descriptor_errors; } } #ifdef LPC_ETH_CONFIG_USE_TRANSMIT_DMA /* Release mbuf */ m_free(mbufs [c]); mbufs [c] = NULL; #endif /* Next consume index */ c = lpc_eth_increment(c, index_max); } } /* Transmit new fragments */ while (true) { /* Compute next produce index */ uint32_t p = lpc_eth_increment(produce_index, index_max); /* Get next fragment and control value */ m = lpc_eth_next_fragment(ifp, m, &ctrl); /* Queue full? */ if (p == consume_index) { LPC_ETH_PRINTF("tx: full queue: 0x%08x\n", m); /* The queue is full, wait for transmit interrupt */ break; } /* New fragment? */ if (m != NULL) { #ifdef LPC_ETH_CONFIG_USE_TRANSMIT_DMA /* Set the transfer data */ rtems_cache_flush_multiple_data_lines( mtod(m, const void *), (size_t) m->m_len ); desc [produce_index].start = mtod(m, uint32_t); desc [produce_index].control = ctrl; rtems_cache_flush_multiple_data_lines ((void *)&desc [produce_index], sizeof(desc [0]) ); mbufs [produce_index] = m; LPC_ETH_PRINTF( "tx: %02" PRIu32 ": %u %s\n", produce_index, m->m_len, (ctrl & ETH_TX_CTRL_LAST) != 0 ? "L" : "" ); /* Next produce index */ produce_index = p; /* Last fragment of a frame? */ if ((ctrl & ETH_TX_CTRL_LAST) != 0) { /* Update the produce index */ lpc_eth->txproduceindex = produce_index; /* Increment transmitted frames counter */ ++e->transmitted_frames; } /* Next fragment of the frame */ m = m->m_next; #else size_t fragment_length = (size_t) m->m_len; void *fragment_start = mtod(m, void *); uint32_t new_frame_length = frame_length + fragment_length; /* Check buffer size */ if (new_frame_length > LPC_ETH_CONFIG_TX_BUF_SIZE) { LPC_ETH_PRINTF("tx: overflow\n"); /* Discard overflow data */ new_frame_length = LPC_ETH_CONFIG_TX_BUF_SIZE; fragment_length = new_frame_length - frame_length; /* Finalize frame */ ctrl |= LPC_ETH_LAST_FRAGMENT_FLAGS; /* Update error counter */ ++e->transmit_overflow_errors; } LPC_ETH_PRINTF( "tx: copy: %" PRIu32 "%s%s\n", fragment_length, (m->m_flags & M_EXT) != 0 ? ", E" : "", (m->m_flags & M_PKTHDR) != 0 ? ", H" : "" ); /* Copy fragment to buffer in Ethernet RAM */ memcpy(frame_buffer, fragment_start, fragment_length); if ((ctrl & ETH_TX_CTRL_LAST) != 0) { /* Finalize descriptor */ desc [produce_index].control = (ctrl & ~ETH_TX_CTRL_SIZE_MASK) | (new_frame_length - 1); LPC_ETH_PRINTF( "tx: %02" PRIu32 ": %" PRIu32 "\n", produce_index, new_frame_length ); /* Cache flush of data */ rtems_cache_flush_multiple_data_lines( (const void *) desc [produce_index].start, new_frame_length ); /* Cache flush of descriptor */ rtems_cache_flush_multiple_data_lines ((void *)&desc [produce_index], sizeof(desc [0]) ); /* Next produce index */ produce_index = p; /* Update the produce index */ lpc_eth->txproduceindex = produce_index; /* Fresh frame length and buffer start */ frame_length = 0; frame_buffer = (char *) desc [produce_index].start; /* Increment transmitted frames counter */ ++e->transmitted_frames; } else { /* New frame length */ frame_length = new_frame_length; /* Update current frame buffer start */ frame_buffer += fragment_length; } /* Free mbuf and get next */ m = m_free(m); #endif } else { /* Nothing to transmit */ break; } } /* No more fragments? */ if (m == NULL) { /* Interface is now inactive */ ifp->if_flags &= ~IFF_OACTIVE; } else { LPC_ETH_PRINTF("tx: enable interrupts\n"); /* Enable transmit interrupts */ lpc_eth_enable_transmit_interrupts(); } } cleanup: /* Clear task ID */ e->transmit_task = RTEMS_ID_NONE; /* Release network semaphore */ rtems_bsdnet_semaphore_release(); /* Terminate self */ (void) rtems_task_delete(RTEMS_SELF); } static void lpc_eth_interface_init(void *arg) { rtems_status_code sc = RTEMS_SUCCESSFUL; lpc_eth_driver_entry *e = (lpc_eth_driver_entry *) arg; struct ifnet *ifp = &e->arpcom.ac_if; LPC_ETH_PRINTF("%s\n", __func__); if (e->state == LPC_ETH_INITIALIZED) { lpc_eth_config_module_enable(); /* Soft reset */ /* Do soft reset */ lpc_eth->command = 0x38; lpc_eth->mac1 = 0xcf00; lpc_eth->mac1 = 0x0; /* Initialize PHY */ /* TODO */ /* Reinitialize registers */ lpc_eth->mac2 = 0x31; lpc_eth->ipgt = 0x15; lpc_eth->ipgr = 0x12; lpc_eth->clrt = 0x370f; lpc_eth->maxf = 0x0600; lpc_eth->supp = 0x0100; lpc_eth->test = 0; #ifdef LPC_ETH_CONFIG_RMII lpc_eth->command = 0x0600; #else lpc_eth->command = 0x0400; #endif lpc_eth->intenable = 0; lpc_eth->intclear = 0x30ff; lpc_eth->powerdown = 0; /* MAC address */ lpc_eth->sa0 = ((uint32_t) e->arpcom.ac_enaddr [5] << 8) | (uint32_t) e->arpcom.ac_enaddr [4]; lpc_eth->sa1 = ((uint32_t) e->arpcom.ac_enaddr [3] << 8) | (uint32_t) e->arpcom.ac_enaddr [2]; lpc_eth->sa2 = ((uint32_t) e->arpcom.ac_enaddr [1] << 8) | (uint32_t) e->arpcom.ac_enaddr [0]; /* Enable receiver */ lpc_eth->mac1 = 0x03; /* Start receive task */ if (e->receive_task == RTEMS_ID_NONE) { e->receive_task = rtems_bsdnet_newproc( "ntrx", 4096, lpc_eth_receive_task, e ); sc = rtems_event_send(e->receive_task, LPC_ETH_EVENT_INITIALIZE); RTEMS_SYSLOG_ERROR_SC(sc, "send receive initialize event"); } /* Start transmit task */ if (e->transmit_task == RTEMS_ID_NONE) { e->transmit_task = rtems_bsdnet_newproc( "nttx", 4096, lpc_eth_transmit_task, e ); sc = rtems_event_send(e->transmit_task, LPC_ETH_EVENT_INITIALIZE); RTEMS_SYSLOG_ERROR_SC(sc, "send transmit initialize event"); } /* Change state */ if ( e->receive_task != RTEMS_ID_NONE && e->transmit_task != RTEMS_ID_NONE ) { e->state = LPC_ETH_STARTED; } } if (e->state == LPC_ETH_STARTED) { /* Enable fatal interrupts */ lpc_eth->intenable = ETH_INT_RX_OVERRUN | ETH_INT_TX_UNDERRUN; /* Enable promiscous mode */ lpc_eth_enable_promiscous_mode((ifp->if_flags & IFF_PROMISC) != 0); /* Start watchdog timer */ ifp->if_timer = 1; /* Set interface to running state */ ifp->if_flags |= IFF_RUNNING; /* Change state */ e->state = LPC_ETH_RUNNING; } } static void lpc_eth_interface_stats(const lpc_eth_driver_entry *e) { rtems_bsdnet_semaphore_release(); printf("received frames: %u\n", e->received_frames); printf("receive interrupts: %u\n", e->receive_interrupts); printf("transmitted frames: %u\n", e->transmitted_frames); printf("transmit interrupts: %u\n", e->transmit_interrupts); printf("receive overrun errors: %u\n", e->receive_overrun_errors); printf("receive fragment errors: %u\n", e->receive_fragment_errors); printf("receive CRC errors: %u\n", e->receive_crc_errors); printf("receive symbol errors: %u\n", e->receive_symbol_errors); printf("receive length errors: %u\n", e->receive_length_errors); printf("receive alignment errors: %u\n", e->receive_alignment_errors); printf("receive no descriptor errors: %u\n", e->receive_no_descriptor_errors); printf("receive fatal errors: %u\n", e->receive_fatal_errors); printf("transmit underrun errors: %u\n", e->transmit_underrun_errors); printf("transmit late collision errors: %u\n", e->transmit_late_collision_errors); printf("transmit excessive collision errors: %u\n", e->transmit_excessive_collision_errors); printf("transmit excessive defer errors: %u\n", e->transmit_excessive_defer_errors); printf("transmit no descriptor errors: %u\n", e->transmit_no_descriptor_errors); printf("transmit overflow errors: %u\n", e->transmit_overflow_errors); printf("transmit fatal errors: %u\n", e->transmit_fatal_errors); rtems_bsdnet_semaphore_obtain(); } static int lpc_eth_interface_ioctl( struct ifnet *ifp, ioctl_command_t command, caddr_t data ) { lpc_eth_driver_entry *e = (lpc_eth_driver_entry *) ifp->if_softc; int rv = 0; LPC_ETH_PRINTF("%s\n", __func__); switch (command) { case SIOCGIFMEDIA: case SIOCSIFMEDIA: rtems_mii_ioctl(&e->mdio_info, e, (int) command, (int *) data); break; case SIOCGIFADDR: case SIOCSIFADDR: ether_ioctl(ifp, command, data); break; case SIOCSIFFLAGS: if (ifp->if_flags & IFF_RUNNING) { /* TODO: off */ } if (ifp->if_flags & IFF_UP) { ifp->if_flags |= IFF_RUNNING; /* TODO: init */ } break; case SIO_RTEMS_SHOW_STATS: lpc_eth_interface_stats(e); break; default: rv = EINVAL; break; } return rv; } static void lpc_eth_interface_start(struct ifnet *ifp) { rtems_status_code sc = RTEMS_SUCCESSFUL; lpc_eth_driver_entry *e = (lpc_eth_driver_entry *) ifp->if_softc; ifp->if_flags |= IFF_OACTIVE; sc = rtems_event_send(e->transmit_task, LPC_ETH_EVENT_START); RTEMS_SYSLOG_ERROR_SC(sc, "send transmit start event"); } static void lpc_eth_interface_watchdog(struct ifnet *ifp __attribute__((unused))) { LPC_ETH_PRINTF("%s\n", __func__); } static unsigned lpc_eth_fixup_unit_count(int count, int default_value, int max) { if (count <= 0) { count = default_value; } else if (count > max) { count = max; } return LPC_ETH_CONFIG_UNIT_MULTIPLE + (((unsigned) count - 1U) & ~(LPC_ETH_CONFIG_UNIT_MULTIPLE - 1U)); } static int lpc_eth_attach(struct rtems_bsdnet_ifconfig *config) { rtems_status_code sc = RTEMS_SUCCESSFUL; lpc_eth_driver_entry *e = &lpc_eth_driver_data; struct ifnet *ifp = &e->arpcom.ac_if; char *unit_name = NULL; int unit_index = rtems_bsdnet_parse_driver_name(config, &unit_name); size_t table_area_size = 0; char *table_area = NULL; char *table_location = NULL; /* Check parameter */ if (unit_index < 0) { RTEMS_SYSLOG_ERROR("parse error for interface name\n"); return 0; } if (unit_index != 0) { RTEMS_DO_CLEANUP(cleanup, "unexpected unit number"); } if (config->hardware_address == NULL) { RTEMS_DO_CLEANUP(cleanup, "MAC address missing"); } if (e->state != LPC_ETH_NOT_INITIALIZED) { RTEMS_DO_CLEANUP(cleanup, "already attached"); } /* Interrupt number */ config->irno = LPC_ETH_CONFIG_INTERRUPT; /* Device control */ config->drv_ctrl = e; /* Receive unit count */ e->rx_unit_count = lpc_eth_fixup_unit_count( config->rbuf_count, LPC_ETH_CONFIG_RX_UNIT_COUNT_DEFAULT, LPC_ETH_CONFIG_RX_UNIT_COUNT_MAX ); config->rbuf_count = (int) e->rx_unit_count; /* Transmit unit count */ e->tx_unit_count = lpc_eth_fixup_unit_count( config->xbuf_count, LPC_ETH_CONFIG_TX_UNIT_COUNT_DEFAULT, LPC_ETH_CONFIG_TX_UNIT_COUNT_MAX ); config->xbuf_count = (int) e->tx_unit_count; /* Disable interrupts */ lpc_eth->intenable = 0; /* Install interrupt handler */ sc = rtems_interrupt_handler_install( config->irno, "Ethernet", RTEMS_INTERRUPT_UNIQUE, lpc_eth_interrupt_handler, e ); RTEMS_CLEANUP_SC(sc, cleanup, "install interrupt handler"); /* Copy MAC address */ memcpy(e->arpcom.ac_enaddr, config->hardware_address, ETHER_ADDR_LEN); /* Allocate and clear table area */ table_area_size = e->rx_unit_count * (sizeof(lpc_eth_transfer_descriptor) + sizeof(lpc_eth_receive_status) + sizeof(struct mbuf *)) + e->tx_unit_count * (sizeof(lpc_eth_transfer_descriptor) + sizeof(uint32_t) + LPC_ETH_CONFIG_TX_BUF_SIZE); table_area = lpc_eth_config_alloc_table_area(table_area_size); if (table_area == NULL) { RTEMS_DO_CLEANUP(cleanup, "no memory for table area"); } memset(table_area, 0, table_area_size); table_location = table_area; /* * The receive status table must be the first one since it has the strictest * alignment requirements. */ e->rx_status_table = (volatile lpc_eth_receive_status *) table_location; table_location += e->rx_unit_count * sizeof(e->rx_status_table [0]); e->rx_desc_table = (volatile lpc_eth_transfer_descriptor *) table_location; table_location += e->rx_unit_count * sizeof(e->rx_desc_table [0]); e->rx_mbuf_table = (struct mbuf **) table_location; table_location += e->rx_unit_count * sizeof(e->rx_mbuf_table [0]); e->tx_desc_table = (volatile lpc_eth_transfer_descriptor *) table_location; table_location += e->tx_unit_count * sizeof(e->tx_desc_table [0]); e->tx_status_table = (volatile uint32_t *) table_location; table_location += e->tx_unit_count * sizeof(e->tx_status_table [0]); e->tx_buf_table = table_location; /* Set interface data */ ifp->if_softc = e; ifp->if_unit = (short) unit_index; ifp->if_name = unit_name; ifp->if_mtu = (config->mtu > 0) ? (u_long) config->mtu : ETHERMTU; ifp->if_init = lpc_eth_interface_init; ifp->if_ioctl = lpc_eth_interface_ioctl; ifp->if_start = lpc_eth_interface_start; ifp->if_output = ether_output; ifp->if_watchdog = lpc_eth_interface_watchdog; ifp->if_flags = config->ignore_broadcast ? 0 : IFF_BROADCAST; ifp->if_snd.ifq_maxlen = ifqmaxlen; ifp->if_timer = 0; /* Change status */ e->state = LPC_ETH_INITIALIZED; /* Attach the interface */ if_attach(ifp); ether_ifattach(ifp); return 1; cleanup: lpc_eth_config_free_table_area(table_area); /* FIXME: Type */ free(unit_name, (int) 0xdeadbeef); return 0; } static int lpc_eth_detach( struct rtems_bsdnet_ifconfig *config __attribute__((unused)) ) { /* FIXME: Detach the interface from the upper layers? */ /* Module soft reset */ lpc_eth->command = 0x38; lpc_eth->mac1 = 0xcf00; /* FIXME: More cleanup */ return 0; } int lpc_eth_attach_detach( struct rtems_bsdnet_ifconfig *config, int attaching ) { /* FIXME: Return value */ if (attaching) { return lpc_eth_attach(config); } else { return lpc_eth_detach(config); } }