diff options
author | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2018-04-23 09:53:31 +0200 |
---|---|---|
committer | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2018-04-23 15:18:44 +0200 |
commit | 031df3914990db0336a0d386fb53558b05de467e (patch) | |
tree | 4661e22f0cdb3f9d06879f0194b77c75f62bac79 /bsps/arm | |
parent | bsps: Move interrupt controller support to bsps (diff) | |
download | rtems-031df3914990db0336a0d386fb53558b05de467e.tar.bz2 |
bsps: Move legacy network drivers to bsps
This patch is a part of the BSP source reorganization.
Update #3285.
Diffstat (limited to 'bsps/arm')
-rw-r--r-- | bsps/arm/atsam/net/if_atsam.c | 1259 | ||||
-rw-r--r-- | bsps/arm/csb336/net/lan91c11x.c | 260 | ||||
-rw-r--r-- | bsps/arm/csb336/net/lan91c11x.h | 229 | ||||
-rw-r--r-- | bsps/arm/csb336/net/network.c | 708 | ||||
-rw-r--r-- | bsps/arm/csb337/net/network.c | 864 | ||||
-rw-r--r-- | bsps/arm/edb7312/net/network.c | 126 | ||||
-rw-r--r-- | bsps/arm/gumstix/net/rtl8019.c | 1196 | ||||
-rw-r--r-- | bsps/arm/gumstix/net/wd80x3.h | 303 | ||||
-rw-r--r-- | bsps/arm/rtl22xx/net/network.c | 126 | ||||
-rw-r--r-- | bsps/arm/shared/net/lpc-ethernet.c | 1839 |
10 files changed, 6910 insertions, 0 deletions
diff --git a/bsps/arm/atsam/net/if_atsam.c b/bsps/arm/atsam/net/if_atsam.c new file mode 100644 index 0000000000..7e7e0e6faf --- /dev/null +++ b/bsps/arm/atsam/net/if_atsam.c @@ -0,0 +1,1259 @@ +/* + * Copyright (c) 2016 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Dornierstr. 4 + * 82178 Puchheim + * Germany + * <info@embedded-brains.de> + * + * 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. + * + * 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. + */ + +#include <libchip/chip.h> +#include <libchip/include/gmacd.h> +#include <libchip/include/pio.h> + +#define __INSIDE_RTEMS_BSD_TCPIP_STACK__ 1 +#define __BSD_VISIBLE 1 + +#include <bsp.h> +#include <bsp/irq.h> + +#include <stdio.h> + +#include <rtems/error.h> +#include <rtems/rtems_bsdnet.h> +#include <rtems/rtems_mii_ioctl.h> + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/sockio.h> + +#include <net/if.h> +#include <net/if_var.h> +#include <net/if_types.h> + +#include <netinet/in.h> +#include <netinet/if_ether.h> + +#include <dev/mii/mii.h> + +/* + * Number of interfaces supported by the driver + */ +#define NIFACES 1 + +/** Enable/Disable CopyAllFrame */ +#define GMAC_CAF_DISABLE 0 +#define GMAC_CAF_ENABLE 1 + +/** Enable/Disable NoBroadCast */ +#define GMAC_NBC_DISABLE 0 +#define GMAC_NBC_ENABLE 1 + +/** The PIN list of PIO for GMAC */ +#define BOARD_GMAC_PINS \ + { (PIO_PD0A_GTXCK | PIO_PD1A_GTXEN | PIO_PD2A_GTX0 | PIO_PD3A_GTX1 \ + | PIO_PD4A_GRXDV | PIO_PD5A_GRX0 | PIO_PD6A_GRX1 \ + | PIO_PD7A_GRXER \ + | PIO_PD8A_GMDC | PIO_PD9A_GMDIO), PIOD, ID_PIOD, PIO_PERIPH_A, \ + PIO_DEFAULT } +/** The runtime pin configure list for GMAC */ +#define BOARD_GMAC_RUN_PINS BOARD_GMAC_PINS + +/** The PIN list of PIO for GMAC */ +#define BOARD_GMAC_RESET_PIN \ + { PIO_PC10, PIOC, ID_PIOC, \ + PIO_OUTPUT_1, \ + PIO_PULLUP } + +/** Multicast Enable */ +#define GMAC_MC_ENABLE (1u << 6) +#define HASH_INDEX_AMOUNT 6 +#define HASH_ELEMENTS_PER_INDEX 8 +#define MAC_ADDR_MASK 0x0000FFFFFFFFFFFF +#define MAC_IDX_MASK (1u << 0) + +/** Promiscuous Mode Enable */ +#define GMAC_PROM_ENABLE (1u << 4) + +/** RX Defines */ +#define GMAC_RX_BUFFER_SIZE 1536 +#define GMAC_RX_BUF_DESC_ADDR_MASK 0xFFFFFFFC +#define GMAC_RX_SET_OFFSET (1u << 15) +#define GMAC_RX_SET_USED_WRAP ((1u << 1) | (1u << 0)) +#define GMAC_RX_SET_WRAP (1u << 1) +#define GMAC_RX_SET_USED (1u << 0) +/** TX Defines */ +#define GMAC_TX_SET_EOF (1u << 15) +#define GMAC_TX_SET_WRAP (1u << 30) +#define GMAC_TX_SET_USED (1u << 31) + +#define GMAC_DESCRIPTOR_ALIGNMENT 8 + +/** Events */ +#define ATSAMV7_ETH_RX_EVENT_INTERRUPT RTEMS_EVENT_1 +#define ATSAMV7_ETH_TX_EVENT_INTERRUPT RTEMS_EVENT_2 +#define ATSAMV7_ETH_START_TRANSMIT_EVENT RTEMS_EVENT_3 + +#define ATSAMV7_ETH_RX_DATA_OFFSET 2 + +#define WATCHDOG_TIMEOUT 5 + +/** The PINs for GMAC */ +static const Pin gmacPins[] = { BOARD_GMAC_RUN_PINS }; + +static const Pin gmacResetPin = BOARD_GMAC_RESET_PIN; + +typedef struct if_atsam_gmac { + /** The GMAC driver instance */ + sGmacd gGmacd; + uint32_t retries; + uint8_t phy_address; +} if_atsam_gmac; + +typedef struct ring_buffer { + unsigned tx_bd_used; + unsigned tx_bd_free; + size_t length; +} ring_buffer; + +/* + * Per-device data + */ +typedef struct if_atsam_softc { + /* + * Data + */ + struct arpcom arpcom; + if_atsam_gmac Gmac_inst; + struct rtems_mdio_info mdio; + uint8_t GMacAddress[6]; + rtems_id rx_daemon_tid; + rtems_id tx_daemon_tid; + rtems_vector_number interrupt_number; + struct mbuf **rx_mbuf; + struct mbuf **tx_mbuf; + volatile sGmacTxDescriptor *tx_bd_base; + uint32_t anlpar; + size_t rx_bd_fill_idx; + size_t amount_rx_buf; + size_t amount_tx_buf; + ring_buffer tx_ring; + + /* + * Statistics + */ + unsigned rx_overrun_errors; + unsigned rx_interrupts; + unsigned tx_complete_int; + unsigned tx_tur_errors; + unsigned tx_rlex_errors; + unsigned tx_tfc_errors; + unsigned tx_hresp_errors; + unsigned tx_interrupts; +} if_atsam_softc; + +static struct if_atsam_softc if_atsam_softc_inst; + +static struct mbuf *if_atsam_new_mbuf(struct ifnet *ifp) +{ + struct mbuf *m; + + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m != NULL) { + MCLGET(m, M_DONTWAIT); + if ((m->m_flags & M_EXT) != 0) { + m->m_pkthdr.rcvif = ifp; + m->m_data = mtod(m, char *); + rtems_cache_invalidate_multiple_data_lines(mtod(m, void *), + GMAC_RX_BUFFER_SIZE); + } else { + m_free(m); + m = NULL; + } + } + return (m); +} + + +static uint8_t if_atsam_wait_phy(Gmac *pHw, uint32_t retry) +{ + volatile uint32_t retry_count = 0; + + while (!GMAC_IsIdle(pHw)) { + if (retry == 0) { + continue; + } + retry_count++; + + if (retry_count >= retry) { + return (1); + } + rtems_task_wake_after(1); + } + + return (0); +} + + +static uint8_t +if_atsam_write_phy(Gmac *pHw, uint8_t PhyAddress, uint8_t Address, + uint32_t Value, uint32_t retry) +{ + GMAC_PHYMaintain(pHw, PhyAddress, Address, 0, (uint16_t)Value); + if (if_atsam_wait_phy(pHw, retry) == 1) { + return (1); + } + return (0); +} + + +static uint8_t +if_atsam_read_phy(Gmac *pHw, + uint8_t PhyAddress, uint8_t Address, uint32_t *pvalue, uint32_t retry) +{ + GMAC_PHYMaintain(pHw, PhyAddress, Address, 1, 0); + if (if_atsam_wait_phy(pHw, retry) == 1) { + return (1); + } + *pvalue = GMAC_PHYData(pHw); + return (0); +} + + +static void atsamv7_find_valid_phy(if_atsam_gmac *gmac_inst) +{ + Gmac *pHw = gmac_inst->gGmacd.pHw; + uint32_t value = 0; + uint8_t phy_address; + int i; + + if (gmac_inst->phy_address != 0xFF) { + return; + } + + /* Find another one */ + phy_address = 0xFF; + + for (i = 31; i >= 0; --i) { + int rv; + + rv = if_atsam_read_phy(pHw, (uint8_t)i, MII_PHYIDR1, + &value, gmac_inst->retries); + if (rv == 0 && value != 0 && value < 0xffff) { + phy_address = (uint8_t)i; + break; + } + } + + if (phy_address != 0xFF) { + if_atsam_read_phy(pHw, phy_address, MII_PHYIDR1, &value, + gmac_inst->retries); + if_atsam_read_phy(pHw, phy_address, MII_PHYIDR2, &value, + gmac_inst->retries); + gmac_inst->phy_address = phy_address; + } +} + + +static uint8_t if_atsam_reset_phy(if_atsam_gmac *gmac_inst) +{ + uint32_t retry_max; + uint32_t bmcr; + uint8_t phy_address; + uint32_t timeout = 10; + uint8_t ret = 0; + + Gmac *pHw = gmac_inst->gGmacd.pHw; + + phy_address = gmac_inst->phy_address; + retry_max = gmac_inst->retries; + + bmcr = BMCR_RESET; + if_atsam_write_phy(pHw, phy_address, MII_BMCR, bmcr, retry_max); + do { + if_atsam_read_phy(pHw, phy_address, MII_BMCR, &bmcr, + retry_max); + timeout--; + } while ((bmcr & BMCR_RESET) && timeout); + + if (!timeout) { + ret = 1; + } + return (ret); +} + + +static uint8_t +if_atsam_init_phy(if_atsam_gmac *gmac_inst, uint32_t mck, + const Pin *pResetPins, uint32_t nbResetPins, const Pin *pGmacPins, + uint32_t nbGmacPins) +{ + uint8_t rc = 1; + Gmac *pHw = gmac_inst->gGmacd.pHw; + + /* Perform RESET */ + if (pResetPins) { + /* Configure PINS */ + PIO_Configure(pResetPins, nbResetPins); + PIO_Clear(pResetPins); + rtems_task_wake_after(1); + PIO_Set(pResetPins); + } + /* Configure GMAC runtime pins */ + if (rc) { + PIO_Configure(pGmacPins, nbGmacPins); + rc = GMAC_SetMdcClock(pHw, mck); + + if (!rc) { + return (0); + } + if_atsam_reset_phy(gmac_inst); + } + return (rc); +} + +static bool if_atsam_is_valid_phy(int phy) +{ + return phy >= 0 && phy <= 31; +} + +static int if_atsam_mdio_read(int phy, void *arg, unsigned reg, uint32_t *pval) +{ + if_atsam_softc *sc = (if_atsam_softc *)arg; + + if (!if_atsam_is_valid_phy(phy)) { + return (EINVAL); + } + + return (if_atsam_read_phy(sc->Gmac_inst.gGmacd.pHw, + (uint8_t)phy, (uint8_t)reg, pval, sc->Gmac_inst.retries)); +} + + +static int if_atsam_mdio_write(int phy, void *arg, unsigned reg, uint32_t pval) +{ + if_atsam_softc *sc = (if_atsam_softc *)arg; + + if (!if_atsam_is_valid_phy(phy)) { + return (EINVAL); + } + + return if_atsam_write_phy(sc->Gmac_inst.gGmacd.pHw, + (uint8_t)phy, (uint8_t)reg, pval, sc->Gmac_inst.retries); +} + + +/* + * Interrupt Handler for the network driver + */ +static void if_atsam_interrupt_handler(void *arg) +{ + if_atsam_softc *sc = (if_atsam_softc *)arg; + uint32_t irq_status_val; + rtems_event_set rx_event = 0; + rtems_event_set tx_event = 0; + Gmac *pHw = sc->Gmac_inst.gGmacd.pHw; + + /* Get interrupt status */ + irq_status_val = GMAC_GetItStatus(pHw, 0); + + /* Check receive interrupts */ + if ((irq_status_val & GMAC_IER_ROVR) != 0) { + ++sc->rx_overrun_errors; + rx_event = ATSAMV7_ETH_RX_EVENT_INTERRUPT; + } + if ((irq_status_val & GMAC_IER_RCOMP) != 0) { + rx_event = ATSAMV7_ETH_RX_EVENT_INTERRUPT; + } + /* Send events to receive task and switch off rx interrupts */ + if (rx_event != 0) { + ++sc->rx_interrupts; + /* Erase the interrupts for RX completion and errors */ + GMAC_DisableIt(pHw, GMAC_IER_RCOMP | GMAC_IER_ROVR, 0); + (void)rtems_bsdnet_event_send(sc->rx_daemon_tid, rx_event); + } + if ((irq_status_val & GMAC_IER_TUR) != 0) { + ++sc->tx_tur_errors; + tx_event = ATSAMV7_ETH_TX_EVENT_INTERRUPT; + } + if ((irq_status_val & GMAC_IER_RLEX) != 0) { + ++sc->tx_rlex_errors; + tx_event = ATSAMV7_ETH_TX_EVENT_INTERRUPT; + } + if ((irq_status_val & GMAC_IER_TFC) != 0) { + ++sc->tx_tfc_errors; + tx_event = ATSAMV7_ETH_TX_EVENT_INTERRUPT; + } + if ((irq_status_val & GMAC_IER_HRESP) != 0) { + ++sc->tx_hresp_errors; + tx_event = ATSAMV7_ETH_TX_EVENT_INTERRUPT; + } + if ((irq_status_val & GMAC_IER_TCOMP) != 0) { + ++sc->tx_complete_int; + tx_event = ATSAMV7_ETH_TX_EVENT_INTERRUPT; + } + /* Send events to transmit task and switch off tx interrupts */ + if (tx_event != 0) { + ++sc->tx_interrupts; + /* Erase the interrupts for TX completion and errors */ + GMAC_DisableIt(pHw, GMAC_INT_TX_BITS, 0); + (void)rtems_bsdnet_event_send(sc->tx_daemon_tid, tx_event); + } +} +/* + * Receive daemon + */ +static void if_atsam_rx_daemon(void *arg) +{ + if_atsam_softc *sc = (if_atsam_softc *)arg; + rtems_event_set events = 0; + void *rx_bd_base; + struct mbuf *m; + struct mbuf *n; + volatile sGmacRxDescriptor *buffer_desc; + int frame_len; + struct ether_header *eh; + uint32_t tmp_rx_bd_address; + + Gmac *pHw = sc->Gmac_inst.gGmacd.pHw; + + /* Allocate memory space for priority queue descriptor list */ + rx_bd_base = rtems_cache_coherent_allocate(sizeof(sGmacRxDescriptor), + GMAC_DESCRIPTOR_ALIGNMENT, 0); + assert(rx_bd_base != NULL); + + buffer_desc = (sGmacRxDescriptor *)rx_bd_base; + buffer_desc->addr.val = GMAC_RX_SET_USED_WRAP; + buffer_desc->status.val = 0; + + GMAC_SetRxQueue(pHw, (uint32_t)buffer_desc, 1); + GMAC_SetRxQueue(pHw, (uint32_t)buffer_desc, 2); + + /* Allocate memory space for buffer descriptor list */ + rx_bd_base = rtems_cache_coherent_allocate( + sc->amount_rx_buf * sizeof(sGmacRxDescriptor), + GMAC_DESCRIPTOR_ALIGNMENT, 0); + assert(rx_bd_base != NULL); + buffer_desc = (sGmacRxDescriptor *)rx_bd_base; + + /* Create descriptor list and mark as empty */ + for (sc->rx_bd_fill_idx = 0; sc->rx_bd_fill_idx < sc->amount_rx_buf; + ++sc->rx_bd_fill_idx) { + m = if_atsam_new_mbuf(&sc->arpcom.ac_if); + assert(m != NULL); + sc->rx_mbuf[sc->rx_bd_fill_idx] = m; + buffer_desc->addr.val = ((uint32_t)m->m_data) & + GMAC_RX_BUF_DESC_ADDR_MASK; + buffer_desc->status.val = 0; + if (sc->rx_bd_fill_idx == (sc->amount_rx_buf - 1)) { + buffer_desc->addr.bm.bWrap = 1; + } else { + buffer_desc++; + } + } + buffer_desc = (sGmacRxDescriptor *)rx_bd_base; + + /* Set 2 Byte Receive Buffer Offset */ + pHw->GMAC_NCFGR |= GMAC_RX_SET_OFFSET; + + /* Write Buffer Queue Base Address Register */ + GMAC_ReceiveEnable(pHw, 0); + GMAC_SetRxQueue(pHw, (uint32_t)buffer_desc, 0); + + /* Set address for address matching */ + GMAC_SetAddress(pHw, 0, sc->GMacAddress); + + /* Enable Receiving of data */ + GMAC_ReceiveEnable(pHw, 1); + + /* Setup the interrupts for RX completion and errors */ + GMAC_EnableIt(pHw, GMAC_IER_RCOMP | GMAC_IER_ROVR, 0); + + sc->rx_bd_fill_idx = 0; + + while (true) { + /* Wait for events */ + rtems_bsdnet_event_receive(ATSAMV7_ETH_RX_EVENT_INTERRUPT, + RTEMS_EVENT_ANY | RTEMS_WAIT, + RTEMS_NO_TIMEOUT, &events); + + /* + * Check for all packets with a set ownership bit + */ + while (buffer_desc->addr.bm.bOwnership == 1) { + if (buffer_desc->status.bm.bEof == 1) { + m = sc->rx_mbuf[sc->rx_bd_fill_idx]; + + /* New mbuf for desc */ + n = if_atsam_new_mbuf(&sc->arpcom.ac_if); + if (n != NULL) { + frame_len = (int) + (buffer_desc->status.bm.len); + + /* Discard Ethernet header */ + int sz = frame_len - ETHER_HDR_LEN; + + /* Update mbuf */ + eh = (struct ether_header *) + (mtod(m, char *) + 2); + m->m_len = sz; + m->m_pkthdr.len = sz; + m->m_data = (void *)(eh + 1); + ether_input(&sc->arpcom.ac_if, eh, m); + m = n; + } else { + (void)rtems_bsdnet_event_send( + sc->tx_daemon_tid, ATSAMV7_ETH_START_TRANSMIT_EVENT); + } + sc->rx_mbuf[sc->rx_bd_fill_idx] = m; + tmp_rx_bd_address = (uint32_t)m->m_data & + GMAC_RX_BUF_DESC_ADDR_MASK; + + /* Switch pointer to next buffer descriptor */ + if (sc->rx_bd_fill_idx == + (sc->amount_rx_buf - 1)) { + tmp_rx_bd_address |= GMAC_RX_SET_WRAP; + sc->rx_bd_fill_idx = 0; + } else { + ++sc->rx_bd_fill_idx; + } + + /* + * Give ownership to GMAC for further processing + */ + tmp_rx_bd_address &= ~GMAC_RX_SET_USED; + _ARM_Data_synchronization_barrier(); + buffer_desc->addr.val = tmp_rx_bd_address; + + buffer_desc = (sGmacRxDescriptor *)rx_bd_base + + sc->rx_bd_fill_idx; + } + } + /* Setup the interrupts for RX completion and errors */ + GMAC_EnableIt(pHw, GMAC_IER_RCOMP | GMAC_IER_ROVR, 0); + } +} + +/* + * Update of current transmit buffer position. + */ +static void if_atsam_tx_bd_pos_update(size_t *pos, size_t amount_tx_buf) +{ + *pos = (*pos + 1) % amount_tx_buf; +} + +/* + * Is RingBuffer empty + */ +static bool if_atsam_ring_buffer_empty(ring_buffer *ring_buffer) +{ + return (ring_buffer->tx_bd_used == ring_buffer->tx_bd_free); +} + +/* + * Is RingBuffer full + */ +static bool if_atsam_ring_buffer_full(ring_buffer *ring_buffer) +{ + size_t tx_bd_used_next = ring_buffer->tx_bd_used; + + if_atsam_tx_bd_pos_update(&tx_bd_used_next, ring_buffer->length); + return (tx_bd_used_next == ring_buffer->tx_bd_free); +} + +/* + * Cleanup transmit file descriptors by freeing mbufs which are not needed any + * longer due to correct transmission. + */ +static void if_atsam_tx_bd_cleanup(if_atsam_softc *sc) +{ + struct mbuf *m; + volatile sGmacTxDescriptor *cur; + bool eof_needed = false; + + while (!if_atsam_ring_buffer_empty(&sc->tx_ring)){ + cur = sc->tx_bd_base + sc->tx_ring.tx_bd_free; + if (((cur->status.bm.bUsed == 1) && !eof_needed) || eof_needed) { + eof_needed = true; + cur->status.val |= GMAC_TX_SET_USED; + m = sc->tx_mbuf[sc->tx_ring.tx_bd_free]; + m_free(m); + sc->tx_mbuf[sc->tx_ring.tx_bd_free] = 0; + if_atsam_tx_bd_pos_update(&sc->tx_ring.tx_bd_free, + sc->tx_ring.length); + if (cur->status.bm.bLastBuffer) { + eof_needed = false; + } + } else { + break; + } + } +} + +/* + * Prepare Ethernet frame to start transmission. + */ +static bool if_atsam_send_packet(if_atsam_softc *sc, struct mbuf *m) +{ + volatile sGmacTxDescriptor *cur; + volatile sGmacTxDescriptor *start_packet_tx_bd = 0; + int pos = 0; + uint32_t tmp_val = 0; + Gmac *pHw = sc->Gmac_inst.gGmacd.pHw; + bool success; + + if_atsam_tx_bd_cleanup(sc); + /* Wait for interrupt in case no buffer descriptors are available */ + /* Wait for events */ + while (true) { + if (if_atsam_ring_buffer_full(&sc->tx_ring)) { + /* Setup the interrupts for TX completion and errors */ + GMAC_EnableIt(pHw, GMAC_INT_TX_BITS, 0); + success = false; + break; + } + + /* + * Get current mbuf for data fill + */ + cur = &sc->tx_bd_base[sc->tx_ring.tx_bd_used]; + /* Set the transfer data */ + if (m->m_len) { + uintptr_t cache_adjustment = mtod(m, uintptr_t) % 32; + + rtems_cache_flush_multiple_data_lines( + mtod(m, const char *) - cache_adjustment, + (size_t)(m->m_len + cache_adjustment)); + + cur->addr = mtod(m, uint32_t); + tmp_val = (uint32_t)m->m_len | GMAC_TX_SET_USED; + if (sc->tx_ring.tx_bd_used == (sc->tx_ring.length - 1)) { + tmp_val |= GMAC_TX_SET_WRAP; + } + if (pos == 0) { + start_packet_tx_bd = cur; + } + sc->tx_mbuf[sc->tx_ring.tx_bd_used] = m; + m = m->m_next; + if_atsam_tx_bd_pos_update(&sc->tx_ring.tx_bd_used, + sc->tx_ring.length); + } else { + /* Discard empty mbufs */ + m = m_free(m); + } + + /* + * Send out the buffer once the complete mbuf_chain has been + * processed + */ + if (m == NULL) { + tmp_val |= GMAC_TX_SET_EOF; + tmp_val &= ~GMAC_TX_SET_USED; + _ARM_Data_synchronization_barrier(); + cur->status.val = tmp_val; + start_packet_tx_bd->status.val &= ~GMAC_TX_SET_USED; + _ARM_Data_synchronization_barrier(); + GMAC_TransmissionStart(pHw); + success = true; + break; + } else { + if (pos > 0) { + tmp_val &= ~GMAC_TX_SET_USED; + } + pos++; + cur->status.val = tmp_val; + } + } + return success; +} + + +/* + * Transmit daemon + */ +static void if_atsam_tx_daemon(void *arg) +{ + if_atsam_softc *sc = (if_atsam_softc *)arg; + rtems_event_set events = 0; + sGmacTxDescriptor *buffer_desc; + int bd_number; + void *tx_bd_base; + struct mbuf *m; + bool success; + + Gmac *pHw = sc->Gmac_inst.gGmacd.pHw; + struct ifnet *ifp = &sc->arpcom.ac_if; + + GMAC_TransmitEnable(pHw, 0); + + /* Allocate memory space for priority queue descriptor list */ + tx_bd_base = rtems_cache_coherent_allocate(sizeof(sGmacTxDescriptor), + GMAC_DESCRIPTOR_ALIGNMENT, 0); + assert(tx_bd_base != NULL); + + buffer_desc = (sGmacTxDescriptor *)tx_bd_base; + buffer_desc->addr = 0; + buffer_desc->status.val = GMAC_TX_SET_USED | GMAC_TX_SET_WRAP; + + GMAC_SetTxQueue(pHw, (uint32_t)buffer_desc, 1); + GMAC_SetTxQueue(pHw, (uint32_t)buffer_desc, 2); + + /* Allocate memory space for buffer descriptor list */ + tx_bd_base = rtems_cache_coherent_allocate( + sc->amount_tx_buf * sizeof(sGmacTxDescriptor), + GMAC_DESCRIPTOR_ALIGNMENT, 0); + assert(tx_bd_base != NULL); + buffer_desc = (sGmacTxDescriptor *)tx_bd_base; + + /* Create descriptor list and mark as empty */ + for (bd_number = 0; bd_number < sc->amount_tx_buf; bd_number++) { + buffer_desc->addr = 0; + buffer_desc->status.val = GMAC_TX_SET_USED; + if (bd_number == (sc->amount_tx_buf - 1)) { + buffer_desc->status.bm.bWrap = 1; + } else { + buffer_desc++; + } + } + buffer_desc = (sGmacTxDescriptor *)tx_bd_base; + + /* Write Buffer Queue Base Address Register */ + GMAC_SetTxQueue(pHw, (uint32_t)buffer_desc, 0); + + /* Enable Transmission of data */ + GMAC_TransmitEnable(pHw, 1); + + /* Set variables in context */ + sc->tx_bd_base = tx_bd_base; + + while (true) { + /* Wait for events */ + rtems_bsdnet_event_receive(ATSAMV7_ETH_START_TRANSMIT_EVENT | ATSAMV7_ETH_TX_EVENT_INTERRUPT, + RTEMS_EVENT_ANY | RTEMS_WAIT, + RTEMS_NO_TIMEOUT, &events); + //printf("TX Transmit Event received\n"); + + /* + * Send packets till queue is empty + */ + while (true) { + /* + * Get the mbuf chain to transmit + */ + if_atsam_tx_bd_cleanup(sc); + IF_DEQUEUE(&sc->arpcom.ac_if.if_snd, m); + if (!m) { + ifp->if_flags &= ~IFF_OACTIVE; + break; + } + success = if_atsam_send_packet(sc, m); + if (!success){ + break; + } + } + } +} + + +/* + * Send packet (caller provides header). + */ +static void if_atsam_enet_start(struct ifnet *ifp) +{ + if_atsam_softc *sc = (if_atsam_softc *)ifp->if_softc; + + ifp->if_flags |= IFF_OACTIVE; + rtems_bsdnet_event_send(sc->tx_daemon_tid, + ATSAMV7_ETH_START_TRANSMIT_EVENT); +} + + +/* + * Attach a watchdog for autonegotiation to the system + */ +static void if_atsam_interface_watchdog(struct ifnet *ifp) +{ + uint32_t anlpar; + uint8_t speed = GMAC_SPEED_100M; + uint8_t full_duplex = GMAC_DUPLEX_FULL; + + if_atsam_softc *sc = (if_atsam_softc *)ifp->if_softc; + Gmac *pHw = sc->Gmac_inst.gGmacd.pHw; + uint8_t phy = sc->Gmac_inst.phy_address; + uint32_t retries = sc->Gmac_inst.retries; + + if (if_atsam_read_phy(pHw, phy, MII_ANLPAR, &anlpar, retries)) { + anlpar = 0; + } + if (sc->anlpar != anlpar) { + /* Set up the GMAC link speed */ + if (anlpar & ANLPAR_TX_FD) { + /* Set MII for 100BaseTx and Full Duplex */ + speed = GMAC_SPEED_100M; + full_duplex = GMAC_DUPLEX_FULL; + } else if (anlpar & ANLPAR_10_FD) { + /* Set MII for 10BaseTx and Full Duplex */ + speed = GMAC_SPEED_10M; + full_duplex = GMAC_DUPLEX_FULL; + } else if (anlpar & ANLPAR_TX) { + /* Set MII for 100BaseTx and half Duplex */ + speed = GMAC_SPEED_100M; + full_duplex = GMAC_DUPLEX_HALF; + } else if (anlpar & ANLPAR_10) { + /* Set MII for 10BaseTx and half Duplex */ + speed = GMAC_SPEED_10M; + full_duplex = GMAC_DUPLEX_HALF; + } else { + /* Set MII for 100BaseTx and Full Duplex */ + speed = GMAC_SPEED_100M; + full_duplex = GMAC_DUPLEX_FULL; + } + GMAC_SetLinkSpeed(pHw, speed, full_duplex); + sc->anlpar = anlpar; + } + ifp->if_timer = WATCHDOG_TIMEOUT; +} + + +/* + * Sets up the hardware and chooses the interface to be used + */ +static void if_atsam_init(void *arg) +{ + rtems_status_code status; + + if_atsam_softc *sc = (if_atsam_softc *)arg; + struct ifnet *ifp = &sc->arpcom.ac_if; + uint32_t dmac_cfg = 0; + uint32_t gmii_val = 0; + + if (sc->arpcom.ac_if.if_flags & IFF_RUNNING) { + return; + } + sc->arpcom.ac_if.if_flags |= IFF_RUNNING; + sc->interrupt_number = GMAC_IRQn; + + /* Disable Watchdog */ + WDT_Disable(WDT); + + /* Enable Peripheral Clock */ + if ((PMC->PMC_PCSR1 & (1u << 7)) != (1u << 7)) { + PMC->PMC_PCER1 = 1 << 7; + } + /* Setup interrupts */ + NVIC_ClearPendingIRQ(GMAC_IRQn); + NVIC_EnableIRQ(GMAC_IRQn); + + GMACD_Init(&sc->Gmac_inst.gGmacd, GMAC, ID_GMAC, GMAC_CAF_ENABLE, + GMAC_NBC_DISABLE); + + /* Enable MDIO interface */ + GMAC_EnableMdio(sc->Gmac_inst.gGmacd.pHw); + + /* PHY initialize */ + if_atsam_init_phy(&sc->Gmac_inst, BOARD_MCK, &gmacResetPin, 1, + gmacPins, PIO_LISTSIZE(gmacPins)); + /* Find valid Phy */ + atsamv7_find_valid_phy(&sc->Gmac_inst); + + /* Set Link Speed */ + sc->anlpar = 0xFFFFFFFF; + if_atsam_interface_watchdog(ifp); + + /* Enable autonegotation */ + if_atsam_read_phy(sc->Gmac_inst.gGmacd.pHw, sc->Gmac_inst.phy_address, + MII_BMCR, &gmii_val, sc->Gmac_inst.retries); + if_atsam_write_phy(sc->Gmac_inst.gGmacd.pHw, sc->Gmac_inst.phy_address, + MII_BMCR, (gmii_val | BMCR_AUTOEN), sc->Gmac_inst.retries); + + /* Configuration of DMAC */ + dmac_cfg = (GMAC_DCFGR_DRBS(GMAC_RX_BUFFER_SIZE >> 6)) | + GMAC_DCFGR_RXBMS(3) | GMAC_DCFGR_TXPBMS | GMAC_DCFGR_FBLDO_INCR16; + GMAC_SetDMAConfig(sc->Gmac_inst.gGmacd.pHw, dmac_cfg, 0); + + /* Shut down Transmit and Receive */ + GMAC_ReceiveEnable(sc->Gmac_inst.gGmacd.pHw, 0); + GMAC_TransmitEnable(sc->Gmac_inst.gGmacd.pHw, 0); + + GMAC_StatisticsWriteEnable(sc->Gmac_inst.gGmacd.pHw, 1); + + /* + * Allocate mbuf pointers + */ + sc->rx_mbuf = malloc(sc->amount_rx_buf * sizeof *sc->rx_mbuf, + M_MBUF, M_NOWAIT); + sc->tx_mbuf = malloc(sc->amount_tx_buf * sizeof *sc->tx_mbuf, + M_MBUF, M_NOWAIT); + + /* Install interrupt handler */ + status = rtems_interrupt_handler_install(sc->interrupt_number, + "Ethernet", + RTEMS_INTERRUPT_UNIQUE, + if_atsam_interrupt_handler, + sc); + assert(status == RTEMS_SUCCESSFUL); + + /* + * Start driver tasks + */ + sc->rx_daemon_tid = rtems_bsdnet_newproc("SCrx", 4096, + if_atsam_rx_daemon, sc); + sc->tx_daemon_tid = rtems_bsdnet_newproc("SCtx", 4096, + if_atsam_tx_daemon, sc); + + /* Start Watchdog Timer */ + ifp->if_timer = 1; +} + + +/* + * Stop the device + */ +static void if_atsam_stop(struct if_atsam_softc *sc) +{ + struct ifnet *ifp = &sc->arpcom.ac_if; + Gmac *pHw = sc->Gmac_inst.gGmacd.pHw; + + ifp->if_flags &= ~IFF_RUNNING; + + /* Disable MDIO interface and TX/RX */ + pHw->GMAC_NCR &= ~(GMAC_NCR_RXEN | GMAC_NCR_TXEN); + pHw->GMAC_NCR &= ~GMAC_NCR_MPE; +} + + +/* + * Show interface statistics + */ +static void if_atsam_stats(struct if_atsam_softc *sc) +{ + int eno = EIO; + int media = 0; + Gmac *pHw; + + media = (int)IFM_MAKEWORD(0, 0, 0, sc->Gmac_inst.phy_address); + eno = rtems_mii_ioctl(&sc->mdio, sc, SIOCGIFMEDIA, &media); + + rtems_bsdnet_semaphore_release(); + + if (eno == 0) { + rtems_ifmedia2str(media, NULL, 0); + printf("\n"); + } + pHw = sc->Gmac_inst.gGmacd.pHw; + + printf("\n** Context Statistics **\n"); + printf("Rx interrupts: %u\n", sc->rx_interrupts); + printf("Tx interrupts: %u\n", sc->tx_interrupts); + printf("Error Tur Tx interrupts: %u\n\n", sc->tx_tur_errors); + printf("Error Rlex Tx interrupts: %u\n\n", sc->tx_rlex_errors); + printf("Error Tfc Tx interrupts: %u\n\n", sc->tx_tfc_errors); + printf("Error Hresp Tx interrupts: %u\n\n", sc->tx_hresp_errors); + printf("Tx complete interrupts: %u\n\n", sc->tx_complete_int); + printf("\n** Statistics **\n"); + printf("Octets Transmitted Low: %lu\n", pHw->GMAC_OTLO); + printf("Octets Transmitted High: %lu\n", pHw->GMAC_OTHI); + printf("Frames Transmitted: %lu\n", pHw->GMAC_FT); + printf("Broadcast Frames Transmitted: %lu\n", pHw->GMAC_BCFT); + printf("Multicast Frames Transmitted: %lu\n", pHw->GMAC_MFT); + printf("Pause Frames Transmitted: %lu\n", pHw->GMAC_PFT); + printf("64 Byte Frames Transmitted: %lu\n", pHw->GMAC_BFT64); + printf("65 to 127 Byte Frames Transmitted: %lu\n", pHw->GMAC_TBFT127); + printf("128 to 255 Byte Frames Transmitted: %lu\n", pHw->GMAC_TBFR255); + printf("256 to 511 Byte Frames Transmitted: %lu\n", pHw->GMAC_TBFT511); + printf("512 to 1023 Byte Frames Transmitted: %lu\n", + pHw->GMAC_TBFT1023); + printf("1024 to 1518 Byte Frames Transmitted: %lu\n", + pHw->GMAC_TBFT1518); + printf("Greater Than 1518 Byte Frames Transmitted: %lu\n", + pHw->GMAC_GTBFT1518); + printf("Transmit Underruns: %lu\n", pHw->GMAC_TUR); + printf("Single Collision Frames: %lu\n", pHw->GMAC_SCF); + printf("Multiple Collision Frames: %lu\n", pHw->GMAC_MCF); + printf("Excessive Collisions: %lu\n", pHw->GMAC_EC); + printf("Late Collisions: %lu\n", pHw->GMAC_LC); + printf("Deferred Transmission Frames: %lu\n", pHw->GMAC_DTF); + printf("Carrier Sense Errors: %lu\n", pHw->GMAC_CSE); + printf("Octets Received Low: %lu\n", pHw->GMAC_ORLO); + printf("Octets Received High: %lu\n", pHw->GMAC_ORHI); + printf("Frames Received: %lu\n", pHw->GMAC_FR); + printf("Broadcast Frames Received: %lu\n", pHw->GMAC_BCFR); + printf("Multicast Frames Received: %lu\n", pHw->GMAC_MFR); + printf("Pause Frames Received: %lu\n", pHw->GMAC_PFR); + printf("64 Byte Frames Received: %lu\n", pHw->GMAC_BFR64); + printf("65 to 127 Byte Frames Received: %lu\n", pHw->GMAC_TBFR127); + printf("128 to 255 Byte Frames Received: %lu\n", pHw->GMAC_TBFR255); + printf("256 to 511 Byte Frames Received: %lu\n", pHw->GMAC_TBFR511); + printf("512 to 1023 Byte Frames Received: %lu\n", pHw->GMAC_TBFR1023); + printf("1024 to 1518 Byte Frames Received: %lu\n", pHw->GMAC_TBFR1518); + printf("1519 to Maximum Byte Frames Received: %lu\n", + pHw->GMAC_TBFR1518); + printf("Undersize Frames Received: %lu\n", pHw->GMAC_UFR); + printf("Oversize Frames Received: %lu\n", pHw->GMAC_OFR); + printf("Jabbers Received: %lu\n", pHw->GMAC_JR); + printf("Frame Check Sequence Errors: %lu\n", pHw->GMAC_FCSE); + printf("Length Field Frame Errors: %lu\n", pHw->GMAC_LFFE); + printf("Receive Symbol Errors: %lu\n", pHw->GMAC_RSE); + printf("Alignment Errors: %lu\n", pHw->GMAC_AE); + printf("Receive Resource Errors: %lu\n", pHw->GMAC_RRE); + printf("Receive Overrun: %lu\n", pHw->GMAC_ROE); + printf("IP Header Checksum Errors: %lu\n", pHw->GMAC_IHCE); + printf("TCP Checksum Errors: %lu\n", pHw->GMAC_TCE); + printf("UDP Checksum Errors: %lu\n", pHw->GMAC_UCE); + + rtems_bsdnet_semaphore_obtain(); +} + + +/* + * Calculates the index that is to be sent into the hash registers + */ +static void if_atsam_get_hash_index(uint64_t addr, uint32_t *val) +{ + uint64_t tmp_val; + uint8_t i, j; + uint64_t idx; + int offset = 0; + + addr &= MAC_ADDR_MASK; + + for (i = 0; i < HASH_INDEX_AMOUNT; ++i) { + tmp_val = 0; + offset = 0; + for (j = 0; j < HASH_ELEMENTS_PER_INDEX; j++) { + idx = (addr >> (offset + i)) & MAC_IDX_MASK; + tmp_val ^= idx; + offset += HASH_INDEX_AMOUNT; + } + if (tmp_val > 0) { + *val |= (1u << i); + } + } +} + + +/* + * Dis/Enable promiscuous Mode + */ +static void if_atsam_promiscuous_mode(if_atsam_softc *sc, bool enable) +{ + Gmac *pHw = sc->Gmac_inst.gGmacd.pHw; + + if (enable) { + pHw->GMAC_NCFGR |= GMAC_PROM_ENABLE; + } else { + pHw->GMAC_NCFGR &= ~GMAC_PROM_ENABLE; + } +} + + +/* + * Multicast handler + */ +static int +if_atsam_multicast_control(bool add, struct ifreq *ifr, if_atsam_softc *sc) +{ + int eno = 0; + struct arpcom *ac = &sc->arpcom; + Gmac *pHw = sc->Gmac_inst.gGmacd.pHw; + + /* Switch off Multicast Hashing */ + pHw->GMAC_NCFGR &= ~GMAC_MC_ENABLE; + + if (add) { + eno = ether_addmulti(ifr, ac); + } else { + eno = ether_delmulti(ifr, ac); + } + + if (eno == ENETRESET) { + struct ether_multistep step; + struct ether_multi *enm; + + eno = 0; + + pHw->GMAC_HRB = 0; + pHw->GMAC_HRT = 0; + + ETHER_FIRST_MULTI(step, ac, enm); + while (enm != NULL) { + uint64_t addrlo = 0; + uint64_t addrhi = 0; + uint32_t val = 0; + + memcpy(&addrlo, enm->enm_addrlo, ETHER_ADDR_LEN); + memcpy(&addrhi, enm->enm_addrhi, ETHER_ADDR_LEN); + while (addrlo <= addrhi) { + if_atsam_get_hash_index(addrlo, &val); + if (val < 32) { + pHw->GMAC_HRB |= (1u << val); + } else { + pHw->GMAC_HRT |= (1u << (val - 32)); + } + ++addrlo; + } + ETHER_NEXT_MULTI(step, enm); + } + } + /* Switch on Multicast Hashing */ + pHw->GMAC_NCFGR |= GMAC_MC_ENABLE; + return (eno); +} + + +/* + * Driver ioctl handler + */ +static int +if_atsam_ioctl(struct ifnet *ifp, ioctl_command_t command, caddr_t data) +{ + struct if_atsam_softc *sc = (if_atsam_softc *)ifp->if_softc; + struct ifreq *ifr = (struct ifreq *)data; + int rv = 0; + bool prom_enable; + + switch (command) { + case SIOCGIFMEDIA: + case SIOCSIFMEDIA: + rtems_mii_ioctl(&sc->mdio, sc, command, &ifr->ifr_media); + break; + case SIOCGIFADDR: + case SIOCSIFADDR: + ether_ioctl(ifp, command, data); + break; + case SIOCSIFFLAGS: + if (ifp->if_flags & IFF_UP) { + if (ifp->if_flags & IFF_RUNNING) { + /* Don't do anything */ + } else { + if_atsam_init(sc); + } + prom_enable = ((ifp->if_flags & IFF_PROMISC) != 0); + if_atsam_promiscuous_mode(sc, prom_enable); + } else { + if (ifp->if_flags & IFF_RUNNING) { + if_atsam_stop(sc); + } + } + break; + case SIOCADDMULTI: + case SIOCDELMULTI: + if_atsam_multicast_control(command == SIOCADDMULTI, ifr, sc); + break; + case SIO_RTEMS_SHOW_STATS: + if_atsam_stats(sc); + break; + default: + rv = EINVAL; + break; + } + return (rv); +} + + +/* + * Attach an SAMV71 driver to the system + */ +static int if_atsam_driver_attach(struct rtems_bsdnet_ifconfig *config) +{ + if_atsam_softc *sc = &if_atsam_softc_inst; + struct ifnet *ifp = &sc->arpcom.ac_if; + const if_atsam_config *conf = config->drv_ctrl; + int unitNumber; + char *unitName; + + if (conf != NULL) { + sc->Gmac_inst.retries = conf->mdio_retries; + sc->Gmac_inst.phy_address = conf->phy_addr; + } else { + sc->Gmac_inst.retries = 10; + sc->Gmac_inst.phy_address = 0xFF; + } + + /* The MAC address used */ + memcpy(sc->GMacAddress, config->hardware_address, ETHER_ADDR_LEN); + memcpy(sc->arpcom.ac_enaddr, sc->GMacAddress, ETHER_ADDR_LEN); + + /* + * Parse driver name + */ + unitNumber = rtems_bsdnet_parse_driver_name(config, &unitName); + assert(unitNumber == 0); + + assert(ifp->if_softc == NULL); + + /* MDIO */ + sc->mdio.mdio_r = if_atsam_mdio_read; + sc->mdio.mdio_w = if_atsam_mdio_write; + sc->mdio.has_gmii = 1; + + if (config->rbuf_count > 0) { + sc->amount_rx_buf = config->rbuf_count; + } else { + sc->amount_rx_buf = 8; + } + + if (config->xbuf_count > 0) { + sc->amount_tx_buf = config->xbuf_count; + } else { + sc->amount_tx_buf = 64; + } + + sc->tx_ring.tx_bd_used = 0; + sc->tx_ring.tx_bd_free = 0; + sc->tx_ring.length = sc->amount_tx_buf; + + /* + * Set up network interface values + */ + ifp->if_softc = sc; + ifp->if_unit = (short int)unitNumber; + ifp->if_name = unitName; + ifp->if_mtu = ETHERMTU; + ifp->if_init = if_atsam_init; + ifp->if_ioctl = if_atsam_ioctl; + ifp->if_start = if_atsam_enet_start; + ifp->if_output = ether_output; + ifp->if_watchdog = if_atsam_interface_watchdog; + ifp->if_flags = IFF_MULTICAST | IFF_BROADCAST | IFF_SIMPLEX; + ifp->if_snd.ifq_maxlen = ifqmaxlen; + ifp->if_timer = 0; + + /* + * Attach the interface + */ + if_attach(ifp); + ether_ifattach(ifp); + return (1); +} + + +int if_atsam_attach(struct rtems_bsdnet_ifconfig *config, int attaching) +{ + (void)attaching; + return (if_atsam_driver_attach(config)); +} diff --git a/bsps/arm/csb336/net/lan91c11x.c b/bsps/arm/csb336/net/lan91c11x.c new file mode 100644 index 0000000000..37594776d1 --- /dev/null +++ b/bsps/arm/csb336/net/lan91c11x.c @@ -0,0 +1,260 @@ +/** + * @file + * + * @ingroup arm_csb336 + * + * @brief Helper functions for SMSC LAN91C11x + */ + +/* + * Helper functions for SMSC LAN91C11x + * + * Copyright (c) 2004 by Cogent Computer Systems + * Written by Jay Monkman <jtm@lopingdog.com> + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.org/license/LICENSE. + */ + +#define __INSIDE_RTEMS_BSD_TCPIP_STACK__ + +#include <rtems.h> +#include "lan91c11x.h" + +uint16_t lan91c11x_read_reg(int reg) +{ + volatile uint16_t *ptr = (uint16_t *)LAN91C11X_BASE_ADDR; + uint16_t old_bank; + uint16_t val; + rtems_interrupt_level level; + + rtems_interrupt_disable(level); + + /* save the bank register */ + old_bank = ptr[7] & 0x7; + + /* set the bank register */ + ptr[7] = (reg >> 4) & 0x7; + + val = ptr[((reg & 0xf) >> 1)]; + + /* restore the bank register */ + ptr[7] = old_bank; + + rtems_interrupt_enable(level); + return val; +} + +void lan91c11x_write_reg(int reg, uint16_t value) +{ + volatile uint16_t *ptr = (uint16_t *)LAN91C11X_BASE_ADDR; + uint16_t old_bank; + rtems_interrupt_level level; + + rtems_interrupt_disable(level); + + /* save the bank register */ + old_bank = ptr[7] & 0x7; + + /* set the bank register */ + ptr[7] = (reg >> 4) & 0x7; + + ptr[((reg & 0xf) >> 1)] = value; + + /* restore the bank register */ + ptr[7] = old_bank; + + rtems_interrupt_enable(level); +} + +uint16_t lan91c11x_read_reg_fast(int reg) +{ + volatile uint16_t *ptr = (uint16_t *)LAN91C11X_BASE_ADDR; + uint16_t val; + + val = ptr[((reg & 0xf) >> 1)]; + + return val; +} + +void lan91c11x_write_reg_fast(int reg, uint16_t value) +{ + volatile uint16_t *ptr = (uint16_t *)LAN91C11X_BASE_ADDR; + + ptr[((reg & 0xf) >> 1)] = value; +} + + +uint16_t lan91c11x_read_phy_reg(int reg) +{ + int i; + uint16_t mask; + uint16_t bits[64]; + int clk_idx = 0; + int input_idx = 0; + uint16_t phydata; + + /* 32 consecutive ones on MDO to establish sync */ + for (i = 0; i < 32; ++i) { + bits[clk_idx++] = LAN91C11X_MGMT_MDOE | LAN91C11X_MGMT_MDO; + } + + /* Start code <01> */ + bits[clk_idx++] = LAN91C11X_MGMT_MDOE; + bits[clk_idx++] = LAN91C11X_MGMT_MDOE | LAN91C11X_MGMT_MDO; + + /* Read command <10> */ + bits[clk_idx++] = LAN91C11X_MGMT_MDOE | LAN91C11X_MGMT_MDO; + bits[clk_idx++] = LAN91C11X_MGMT_MDOE; + + /* Output the PHY address, msb first - Internal PHY is address 0 */ + for (i = 0; i < 5; ++i) { + bits[clk_idx++] = LAN91C11X_MGMT_MDOE; + } + + /* Output the phy register number, msb first */ + mask = 0x10; + for (i = 0; i < 5; ++i) { + if (reg & mask) { + bits[clk_idx++] = LAN91C11X_MGMT_MDOE | LAN91C11X_MGMT_MDO; + } else { + bits[clk_idx++] = LAN91C11X_MGMT_MDOE; + } + + + /* Shift to next lowest bit */ + mask >>= 1; + } + + /* 1 bit time for turnaround */ + bits[clk_idx++] = 0; + + /* Input starts at this bit time */ + input_idx = clk_idx; + + /* Will input 16 bits */ + for (i = 0; i < 16; ++i) { + bits[clk_idx++] = 0; + } + + /* Final clock bit */ + bits[clk_idx++] = 0; + + /* Turn off all MII Interface bits */ + lan91c11x_write_reg(LAN91C11X_MGMT, + lan91c11x_read_reg(LAN91C11X_MGMT) & 0xfff0); + + /* Clock all 64 cycles */ + for (i = 0; i < sizeof bits; ++i) { + /* Clock Low - output data */ + lan91c11x_write_reg(LAN91C11X_MGMT, bits[i]); + rtems_task_wake_after(1); + + /* Clock Hi - input data */ + lan91c11x_write_reg(LAN91C11X_MGMT, bits[i] | LAN91C11X_MGMT_MCLK); + rtems_task_wake_after(1); + bits[i] |= lan91c11x_read_reg(LAN91C11X_MGMT) & LAN91C11X_MGMT_MDI; + } + + /* Return to idle state */ + /* Set clock to low, data to low, and output tristated */ + lan91c11x_write_reg(LAN91C11X_MGMT, lan91c11x_read_reg(LAN91C11X_MGMT) & 0xfff0); + rtems_task_wake_after(1); + + /* Recover input data */ + phydata = 0; + for (i = 0; i < 16; ++i) { + phydata <<= 1; + + if (bits[input_idx++] & LAN91C11X_MGMT_MDI) { + phydata |= 0x0001; + } + } + + return phydata; +} + + + +void lan91c11x_write_phy_reg(int reg, uint16_t phydata) +{ + int i; + ushort mask; + ushort bits[64]; + int clk_idx = 0; + + /* 32 consecutive ones on MDO to establish sync */ + for (i = 0; i < 32; ++i) { + bits[clk_idx++] = LAN91C11X_MGMT_MDOE | LAN91C11X_MGMT_MDO; + } + + /* Start code <01> */ + bits[clk_idx++] = LAN91C11X_MGMT_MDOE; + bits[clk_idx++] = LAN91C11X_MGMT_MDOE | LAN91C11X_MGMT_MDO; + + /* Write command <01> */ + bits[clk_idx++] = LAN91C11X_MGMT_MDOE; + bits[clk_idx++] = LAN91C11X_MGMT_MDOE | LAN91C11X_MGMT_MDO; + + /* Output the PHY address, msb first - Internal PHY is address 0 */ + for (i = 0; i < 5; ++i) { + bits[clk_idx++] = LAN91C11X_MGMT_MDOE; + } + + /* Output the phy register number, msb first */ + mask = 0x10; + for (i = 0; i < 5; ++i) { + if (reg & mask) { + bits[clk_idx++] = LAN91C11X_MGMT_MDOE | LAN91C11X_MGMT_MDO; + } else { + bits[clk_idx++] = LAN91C11X_MGMT_MDOE; + } + + /* Shift to next lowest bit */ + mask >>= 1; + } + + /* 2 extra bit times for turnaround */ + bits[clk_idx++] = 0; + bits[clk_idx++] = 0; + + /* Write out 16 bits of data, msb first */ + mask = 0x8000; + for (i = 0; i < 16; ++i) { + if (phydata & mask) { + bits[clk_idx++] = LAN91C11X_MGMT_MDOE | LAN91C11X_MGMT_MDO; + } else { + bits[clk_idx++] = LAN91C11X_MGMT_MDOE; + } + + /* Shift to next lowest bit */ + mask >>= 1; + } + + /* Turn off all MII Interface bits */ + lan91c11x_write_reg(LAN91C11X_MGMT, + lan91c11x_read_reg(LAN91C11X_MGMT) & 0xfff0); + + /* Clock all 64 cycles */ + for (i = 0; i < sizeof bits; ++i) { + /* Clock Low - output data */ + lan91c11x_write_reg(LAN91C11X_MGMT, bits[i]); + rtems_task_wake_after(1); + + /* Clock Hi - input data */ + lan91c11x_write_reg(LAN91C11X_MGMT, bits[i] | LAN91C11X_MGMT_MCLK); + rtems_task_wake_after(1); + bits[i] |= lan91c11x_read_reg(LAN91C11X_MGMT) & LAN91C11X_MGMT_MDI; + } + + /* Return to idle state */ + /* Set clock to low, data to low, and output tristated */ + lan91c11x_write_reg(LAN91C11X_MGMT, + lan91c11x_read_reg(LAN91C11X_MGMT) & 0xfff0); + rtems_task_wake_after(1); + +} + + + diff --git a/bsps/arm/csb336/net/lan91c11x.h b/bsps/arm/csb336/net/lan91c11x.h new file mode 100644 index 0000000000..8172874e0c --- /dev/null +++ b/bsps/arm/csb336/net/lan91c11x.h @@ -0,0 +1,229 @@ +/** + * @file + * + * @ingroup arm_csb336 + * + * @brief SMSC LAN91C11x ethernet devices definitions. + */ + +/** + * @defgroup arm_csb336_lan91c11x SMSC LAN91C11x + * + * @ingroup arm_csb336 + * + * @brief SMSC LAN91C11x ethernet devices definitions. + */ + +/* + * Header file for SMSC LAN91C11x ethernet devices + * + * Copyright (c) 2004 by Cogent Computer Systems + * Written by Jay Monkman <jtm@lopingdog.com> + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.org/license/LICENSE. + */ +#ifndef __LAN91C11X_H__ +#define __LAN91C11X_H__ + +#include <rtems.h> +#include <bsp.h> + +uint16_t lan91c11x_read_reg(int); +void lan91c11x_write_reg(int, uint16_t); +uint16_t lan91c11x_read_reg_fast(int); +void lan91c11x_write_reg_fast(int, uint16_t); +void lan91c11x_write_phy_reg(int , uint16_t); +uint16_t lan91c11x_read_phy_reg(int); +void lan91c11x_unlock(void); +void lan91c11x_lock(void); + +#define LAN91C11X_BASE_ADDR 0x12000000 + +#define LAN91C11X_REG(_b_, _r_) ((((_b_) & 0xf) << 4) | ((_r_) & 0xf)) + + +#define LAN91C11X_TCR (LAN91C11X_REG(0, 0x0)) +#define LAN91C11X_EPHSTAT (LAN91C11X_REG(0, 0x2)) +#define LAN91C11X_RCR (LAN91C11X_REG(0, 0x4)) +#define LAN91C11X_CNTR (LAN91C11X_REG(0, 0x6)) +#define LAN91C11X_MIR (LAN91C11X_REG(0, 0x8)) +#define LAN91C11X_RPCR (LAN91C11X_REG(0, 0xa)) +#define LAN91C11X_BANK (LAN91C11X_REG(0, 0xe)) +#define LAN91C11X_CONFIG (LAN91C11X_REG(1, 0x0)) +#define LAN91C11X_BASE (LAN91C11X_REG(1, 0x2)) +#define LAN91C11X_IA0 (LAN91C11X_REG(1, 0x4)) +#define LAN91C11X_IA2 (LAN91C11X_REG(1, 0x6)) +#define LAN91C11X_IA4 (LAN91C11X_REG(1, 0x8)) +#define LAN91C11X_GNRL (LAN91C11X_REG(1, 0xa)) +#define LAN91C11X_CTRL (LAN91C11X_REG(1, 0xc)) +#define LAN91C11X_MMUCMD (LAN91C11X_REG(2, 0x0)) +#define LAN91C11X_PNR (LAN91C11X_REG(2, 0x2)) +#define LAN91C11X_FIFO (LAN91C11X_REG(2, 0x4)) +#define LAN91C11X_PTR (LAN91C11X_REG(2, 0x6)) +#define LAN91C11X_DATA (LAN91C11X_REG(2, 0x8)) +#define LAN91C11X_INT (LAN91C11X_REG(2, 0xc)) +#define LAN91C11X_MT0 (LAN91C11X_REG(3, 0x0)) +#define LAN91C11X_MT2 (LAN91C11X_REG(3, 0x2)) +#define LAN91C11X_MT4 (LAN91C11X_REG(3, 0x4)) +#define LAN91C11X_MT6 (LAN91C11X_REG(3, 0x6)) +#define LAN91C11X_MGMT (LAN91C11X_REG(3, 0x8)) +#define LAN91C11X_REV (LAN91C11X_REG(3, 0xa)) +#define LAN91C11X_ERCV (LAN91C11X_REG(3, 0xc)) + + +#define LAN91C11X_TCR_TXENA (bit(0)) +#define LAN91C11X_TCR_LOOP (bit(1)) +#define LAN91C11X_TCR_FORCOL (bit(2)) +#define LAN91C11X_TCR_PADEN (bit(7)) +#define LAN91C11X_TCR_NOCRC (bit(8)) +#define LAN91C11X_TCR_MONCSN (bit(10)) +#define LAN91C11X_TCR_FDUPLX (bit(11)) +#define LAN91C11X_TCR_STPSQET (bit(12)) +#define LAN91C11X_TCR_EPHLOOP (bit(13)) +#define LAN91C11X_TCR_SWFDUP (bit(15)) + +#define LAN91C11X_EPHSTAT_TXSUC (bit(0)) +#define LAN91C11X_EPHSTAT_SNGLCOL (bit(1)) +#define LAN91C11X_EPHSTAT_MULCOL (bit(2)) +#define LAN91C11X_EPHSTAT_LTXMUL (bit(3)) +#define LAN91C11X_EPHSTAT_16COL (bit(4)) +#define LAN91C11X_EPHSTAT_SQET (bit(5)) +#define LAN91C11X_EPHSTAT_LTXBRD (bit(6)) +#define LAN91C11X_EPHSTAT_TXDFR (bit(7)) +#define LAN91C11X_EPHSTAT_LATCOL (bit(9)) +#define LAN91C11X_EPHSTAT_LOST (bit(10)) +#define LAN91C11X_EPHSTAT_EXCDEF (bit(11)) +#define LAN91C11X_EPHSTAT_CTRROL (bit(12)) +#define LAN91C11X_EPHSTAT_LINK (bit(14)) +#define LAN91C11X_EPHSTAT_TXUNRN (bit(15)) + +#define LAN91C11X_RCR_RXABT (bit(0)) +#define LAN91C11X_RCR_PRMS (bit(1)) +#define LAN91C11X_RCR_ALMUL (bit(2)) +#define LAN91C11X_RCR_RXEN (bit(8)) +#define LAN91C11X_RCR_STRIP (bit(9)) +#define LAN91C11X_RCR_ABTENB (bit(13)) +#define LAN91C11X_RCR_FILT (bit(14)) +#define LAN91C11X_RCR_RST (bit(15)) + +#define LAN91C11X_RPCR_LS0B (bit(2)) +#define LAN91C11X_RPCR_LS1B (bit(3)) +#define LAN91C11X_RPCR_LS2B (bit(4)) +#define LAN91C11X_RPCR_LS0A (bit(5)) +#define LAN91C11X_RPCR_LS1A (bit(6)) +#define LAN91C11X_RPCR_LS2A (bit(7)) +#define LAN91C11X_RPCR_ANEG (bit(11)) +#define LAN91C11X_RPCR_DPLX (bit(12)) +#define LAN91C11X_RPCR_SPEED (bit(13)) + +#define LAN91C11X_CONFIG_EXTPHY (bit(9)) +#define LAN91C11X_CONFIG_GPCTRL (bit(10)) +#define LAN91C11X_CONFIG_NOWAIT (bit(12)) +#define LAN91C11X_CONFIG_PWR (bit(15)) + +#define LAN91C11X_CTRL_STORE (bit(0)) +#define LAN91C11X_CTRL_RELOAD (bit(1)) +#define LAN91C11X_CTRL_EEPROM (bit(2)) +#define LAN91C11X_CTRL_TEEN (bit(5)) +#define LAN91C11X_CTRL_CREN (bit(6)) +#define LAN91C11X_CTRL_LEEN (bit(7)) +#define LAN91C11X_CTRL_AUTO (bit(11)) +#define LAN91C11X_CTRL_RCVBAD (bit(14)) + +#define LAN91C11X_MMUCMD_BUSY (bit(0)) +#define LAN91C11X_MMUCMD_NOOP (0 << 5) +#define LAN91C11X_MMUCMD_ALLOCTX (1 << 5) +#define LAN91C11X_MMUCMD_RESETMMU (2 << 5) +#define LAN91C11X_MMUCMD_REMFRM (3 << 5) +#define LAN91C11X_MMUCMD_REMTOP (4 << 5) +#define LAN91C11X_MMUCMD_RELEASE (5 << 5) +#define LAN91C11X_MMUCMD_ENQUEUE (6 << 5) +#define LAN91C11X_MMUCMD_RESETTX (7 << 5) + +#define LAN91C11X_PTR_MASK (0x7ff) +#define LAN91C11X_PTR_NE (bit(11)) +#define LAN91C11X_PTR_ETEN (bit(12)) +#define LAN91C11X_PTR_READ (bit(13)) +#define LAN91C11X_PTR_AUTOINC (bit(14)) +#define LAN91C11X_PTR_RCV (bit(15)) + +#define LAN91C11X_INT_RX (bit(0)) +#define LAN91C11X_INT_TX (bit(1)) +#define LAN91C11X_INT_TXE (bit(2)) +#define LAN91C11X_INT_ALLOC (bit(3)) +#define LAN91C11X_INT_RXOV (bit(4)) +#define LAN91C11X_INT_EPH (bit(5)) +#define LAN91C11X_INT_ERX (bit(6)) +#define LAN91C11X_INT_MD (bit(7)) +#define LAN91C11X_INT_RXMASK (bit(8)) +#define LAN91C11X_INT_TXMASK (bit(9)) +#define LAN91C11X_INT_TXEMASK (bit(10)) +#define LAN91C11X_INT_ALLOCMASK (bit(11)) +#define LAN91C11X_INT_RXOVMASK (bit(12)) +#define LAN91C11X_INT_EPHMASK (bit(13)) +#define LAN91C11X_INT_ERXMASK (bit(14)) +#define LAN91C11X_INT_MDMASK (bit(15)) + +#define LAN91C11X_MGMT_MDO (bit(0)) +#define LAN91C11X_MGMT_MDI (bit(1)) +#define LAN91C11X_MGMT_MCLK (bit(2)) +#define LAN91C11X_MGMT_MDOE (bit(3)) +#define LAN91C11X_MGMT_MSKCRS100 (bit(14)) + + +#define LAN91C11X_PKT_CTRL_CRC (bit(4)) +#define LAN91C11X_PKT_CTRL_ODD (bit(5)) + + +/* PHY Registers */ +#define PHY_CTRL 0x00 /* PHY Control */ +#define PHY_STAT 0x01 /* PHY Status */ +#define PHY_ID1 0x02 /* PHY Identifier 1 */ +#define PHY_ID2 0x03 /* PHY Identifier 2 */ +#define PHY_AD 0x04 /* PHY Auto-negotiate Control */ +#define PHY_RMT 0x05 /* PHY Auto-neg Remote End Cap Register */ +#define PHY_CFG1 0x10 /* PHY Configuration 1 */ +#define PHY_CFG2 0x11 /* PHY Configuration 2 */ +#define PHY_INT 0x12 /* Status Output (Interrupt Status) */ +#define PHY_MASK 0x13 /* Interrupt Mask */ + +/* PHY Control Register Bit Defines */ +#define PHY_CTRL_RST 0x8000 /* PHY Reset */ +#define PHY_CTRL_LPBK 0x4000 /* PHY Loopback */ +#define PHY_CTRL_SPEED 0x2000 /* 100Mbps, 0=10Mpbs */ +#define PHY_CTRL_ANEGEN 0x1000 /* Enable Auto negotiation */ +#define PHY_CTRL_PDN 0x0800 /* PHY Power Down mode */ +#define PHY_CTRL_MIIDIS 0x0400 /* MII 4 bit interface disabled */ +#define PHY_CTRL_ANEGRST 0x0200 /* Reset Auto negotiate */ +#define PHY_CTRL_DPLX 0x0100 /* Full Duplex, 0=Half Duplex */ +#define PHY_CTRL_COLTST 0x0080 /* MII Colision Test */ + +#define PHY_STAT_CAPT4 0x8000 +#define PHY_STAT_CAPTXF 0x4000 +#define PHY_STAT_CAPTXH 0x2000 +#define PHY_STAT_CAPTF 0x1000 +#define PHY_STAT_CAPTH 0x0800 +#define PHY_STAT_CAPSUPR 0x0040 +#define PHY_STAT_ANEGACK 0x0020 +#define PHY_STAT_REMFLT 0x0010 +#define PHY_STAT_CAPANEG 0x0008 +#define PHY_STAT_LINK 0x0004 +#define PHY_STAT_JAB 0x0002 +#define PHY_STAT_EXREG 0x0001 + +#define PHY_ADV_NP 0x8000 +#define PHY_ADV_ACK 0x4000 +#define PHY_ADV_RF 0x2000 +#define PHY_ADV_T4 0x0200 +#define PHY_ADV_TXFDX 0x0100 +#define PHY_ADV_TXHDX 0x0080 +#define PHY_ADV_10FDX 0x0040 +#define PHY_ADV_10HDX 0x0020 +#define PHY_ADV_CSMA 0x0001 + + + + +#endif /* __LAN91C11X_H__ */ diff --git a/bsps/arm/csb336/net/network.c b/bsps/arm/csb336/net/network.c new file mode 100644 index 0000000000..be3d3f7b3d --- /dev/null +++ b/bsps/arm/csb336/net/network.c @@ -0,0 +1,708 @@ +/* + * MC9323MXL Ethernet driver + * + * Copyright (c) 2004 by Cogent Computer Systems + * Written by Jay Monkman <jtm@lopingdog.com> + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.org/license/LICENSE. + */ + +#define __INSIDE_RTEMS_BSD_TCPIP_STACK__ + +#include <rtems.h> +#include <rtems/rtems_bsdnet.h> +#include <mc9328mxl.h> +#include "lan91c11x.h" + +#include <stdio.h> +#include <string.h> + +#include <errno.h> +#include <rtems/error.h> +#include <rtems/bspIo.h> +#include <assert.h> + +#include <sys/param.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/sockio.h> + +#include <net/if.h> + +#include <netinet/in.h> +#include <netinet/if_ether.h> + +#include <bsp/irq.h> + +/* RTEMS event used by interrupt handler to start receive daemon. */ +#define START_RECEIVE_EVENT RTEMS_EVENT_1 + +/* RTEMS event used to start transmit daemon. */ +#define START_TRANSMIT_EVENT RTEMS_EVENT_2 + +static void enet_isr(void *); +static void enet_isr_on(void); + +typedef struct { + unsigned long rx_packets; /* total packets received */ + unsigned long tx_packets; /* total packets transmitted */ + unsigned long rx_bytes; /* total bytes received */ + unsigned long tx_bytes; /* total bytes transmitted */ + unsigned long interrupts; /* total number of interrupts */ + unsigned long rx_interrupts; /* total number of rx interrupts */ + unsigned long tx_interrupts; /* total number of tx interrupts */ + unsigned long txerr_interrupts; /* total number of tx error interrupts */ + +} eth_stats_t; + +/* + * Hardware-specific storage + */ +typedef struct +{ + /* + * Connection to networking code + * This entry *must* be the first in the sonic_softc structure. + */ + struct arpcom arpcom; + + int accept_bcast; + + /* Tasks waiting for interrupts */ + rtems_id rx_task; + rtems_id tx_task; + + eth_stats_t stats; + +} mc9328mxl_enet_softc_t; + +static mc9328mxl_enet_softc_t softc; + + +/* function prototypes */ +int rtems_mc9328mxl_enet_attach(struct rtems_bsdnet_ifconfig *config, + void *chip); +void mc9328mxl_enet_init(void *arg); +void mc9328mxl_enet_init_hw(mc9328mxl_enet_softc_t *sc); +void mc9328mxl_enet_start(struct ifnet *ifp); +void mc9328mxl_enet_stop (mc9328mxl_enet_softc_t *sc); +void mc9328mxl_enet_tx_task (void *arg); +void mc9328mxl_enet_sendpacket (struct ifnet *ifp, struct mbuf *m); +void mc9328mxl_enet_rx_task(void *arg); +void mc9328mxl_enet_stats(mc9328mxl_enet_softc_t *sc); +static int mc9328mxl_enet_ioctl(struct ifnet *ifp, + ioctl_command_t command, caddr_t data); + + +int rtems_mc9328mxl_enet_attach ( + struct rtems_bsdnet_ifconfig *config, + void *chip /* only one ethernet, so no chip number */ + ) +{ + struct ifnet *ifp; + int mtu; + int unitnumber; + char *unitname; + int tmp; + + /* + * Parse driver name + */ + unitnumber = rtems_bsdnet_parse_driver_name(config, &unitname); + if (unitnumber < 0) { + return 0; + } + + /* + * Is driver free? + */ + if (unitnumber != 0) { + printf ("Bad MC9328MXL unit number.\n"); + return 0; + } + + ifp = &softc.arpcom.ac_if; + if (ifp->if_softc != NULL) { + printf ("Driver already in use.\n"); + return 0; + } + + /* zero out the control structure */ + memset( &softc, 0, sizeof(softc) ); + + + /* set the MAC address */ + tmp = lan91c11x_read_reg(LAN91C11X_IA0); + softc.arpcom.ac_enaddr[0] = tmp & 0xff; + softc.arpcom.ac_enaddr[1] = (tmp >> 8) & 0xff; + + tmp = lan91c11x_read_reg(LAN91C11X_IA2); + softc.arpcom.ac_enaddr[2] = tmp & 0xff; + softc.arpcom.ac_enaddr[3] = (tmp >> 8) & 0xff; + + tmp = lan91c11x_read_reg(LAN91C11X_IA4); + softc.arpcom.ac_enaddr[4] = tmp & 0xff; + softc.arpcom.ac_enaddr[5] = (tmp >> 8) & 0xff; + + if (config->mtu) { + mtu = config->mtu; + } else { + mtu = ETHERMTU; + } + + softc.accept_bcast = !config->ignore_broadcast; + + /* + * Set up network interface values + */ + ifp->if_softc = &softc; + ifp->if_unit = unitnumber; + ifp->if_name = unitname; + ifp->if_mtu = mtu; + ifp->if_init = mc9328mxl_enet_init; + ifp->if_ioctl = mc9328mxl_enet_ioctl; + ifp->if_start = mc9328mxl_enet_start; + ifp->if_output = ether_output; + ifp->if_flags = IFF_BROADCAST; + if (ifp->if_snd.ifq_maxlen == 0) { + ifp->if_snd.ifq_maxlen = ifqmaxlen; + } + + /* Attach the interface */ + if_attach (ifp); + ether_ifattach (ifp); + return 1; +} + +void mc9328mxl_enet_init(void *arg) +{ + mc9328mxl_enet_softc_t *sc = arg; + struct ifnet *ifp = &sc->arpcom.ac_if; + + /* + *This is for stuff that only gets done once (mc9328mxl_enet_init() + * gets called multiple times + */ + if (sc->tx_task == 0) + { + /* Set up ENET hardware */ + mc9328mxl_enet_init_hw(sc); + + /* Start driver tasks */ + sc->rx_task = rtems_bsdnet_newproc("ENrx", + 4096, + mc9328mxl_enet_rx_task, + sc); + sc->tx_task = rtems_bsdnet_newproc("ENtx", + 4096, + mc9328mxl_enet_tx_task, + sc); + } /* if tx_task */ + + + /* Configure for promiscuous if needed */ + if (ifp->if_flags & IFF_PROMISC) { + lan91c11x_write_reg(LAN91C11X_RCR, + (lan91c11x_read_reg(LAN91C11X_RCR) | + LAN91C11X_RCR_PRMS)); + } + + + /* + * Tell the world that we're running. + */ + ifp->if_flags |= IFF_RUNNING; + + /* Enable TX/RX */ + lan91c11x_write_reg(LAN91C11X_TCR, + (lan91c11x_read_reg(LAN91C11X_TCR) | + LAN91C11X_TCR_TXENA)); + + lan91c11x_write_reg(LAN91C11X_RCR, + (lan91c11x_read_reg(LAN91C11X_RCR) | + LAN91C11X_RCR_RXEN)); + + +} /* mc9328mxl_enet_init() */ + +void mc9328mxl_enet_init_hw(mc9328mxl_enet_softc_t *sc) +{ + uint16_t stat; + uint16_t my = 0; + rtems_status_code status = RTEMS_SUCCESSFUL; + + lan91c11x_write_reg(LAN91C11X_RCR, LAN91C11X_RCR_RST); + lan91c11x_write_reg(LAN91C11X_RCR, 0); + rtems_task_wake_after(1); + + /* Reset the PHY */ + lan91c11x_write_phy_reg(PHY_CTRL, PHY_CTRL_RST); + while(lan91c11x_read_phy_reg(PHY_CTRL) & PHY_CTRL_RST) { + rtems_task_wake_after(1); + } + + + stat = lan91c11x_read_phy_reg(PHY_STAT); + + if(stat & PHY_STAT_CAPT4) { + my |= PHY_ADV_T4; + } +/* 100Mbs doesn't work, so we won't advertise it */ + + if(stat & PHY_STAT_CAPTXF) { + my |= PHY_ADV_TXFDX; + } + if(stat & PHY_STAT_CAPTXH) { + my |= PHY_ADV_TXHDX; + } + + if(stat & PHY_STAT_CAPTF) { + my |= PHY_ADV_10FDX; + } + + if(stat & PHY_STAT_CAPTH) { + my |= PHY_ADV_10HDX; + } + + my |= PHY_ADV_CSMA; + + lan91c11x_write_phy_reg(PHY_AD, my); + + + /* Enable Autonegotiation */ +#if 0 + lan91c11x_write_phy_reg(PHY_CTRL, + (PHY_CTRL_ANEGEN | PHY_CTRL_ANEGRST)); +#endif + + /* Enable full duplex, let MAC take care + * of padding and CRC. + */ + lan91c11x_write_reg(LAN91C11X_TCR, + (LAN91C11X_TCR_PADEN | + LAN91C11X_TCR_SWFDUP)); + + /* Disable promisc, don'tstrip CRC */ + lan91c11x_write_reg(LAN91C11X_RCR, 0); + + /* Enable auto-negotiation, LEDA is link, LEDB is traffic */ + lan91c11x_write_reg(LAN91C11X_RPCR, + (LAN91C11X_RPCR_ANEG | + LAN91C11X_RPCR_LS2B)); + + /* Don't add wait states, enable PHY power */ + lan91c11x_write_reg(LAN91C11X_CONFIG, + (LAN91C11X_CONFIG_NOWAIT | + LAN91C11X_CONFIG_PWR)); + + /* Disable error interrupts, enable auto release */ + lan91c11x_write_reg(LAN91C11X_CTRL, LAN91C11X_CTRL_AUTO); + + /* Reset MMU */ + lan91c11x_write_reg(LAN91C11X_MMUCMD, + LAN91C11X_MMUCMD_RESETMMU ); + + + rtems_task_wake_after(100); + /* Enable Autonegotiation */ + lan91c11x_write_phy_reg(PHY_CTRL, 0x3000); + rtems_task_wake_after(100); + + /* Enable Interrupts for RX */ + lan91c11x_write_reg(LAN91C11X_INT, LAN91C11X_INT_RXMASK); + + /* Enable interrupts on GPIO Port A3 */ + /* Make pin 3 an input */ + MC9328MXL_GPIOA_DDIR &= ~bit(3); + + /* Use GPIO function for pin 3 */ + MC9328MXL_GPIOA_GIUS |= bit(3); + + /* Set for active high, level triggered interupt */ + MC9328MXL_GPIOA_ICR1 = ((MC9328MXL_GPIOA_ICR1 & ~(3 << 6)) | + (2 << 6)); + + /* Enable GPIO port A3 interrupt */ + MC9328MXL_GPIOA_IMR |= bit(3); + + /* Install the interrupt handler */ + status = rtems_interrupt_handler_install( + BSP_INT_GPIO_PORTA, + "Network", + RTEMS_INTERRUPT_UNIQUE, + enet_isr, + (void *)BSP_INT_GPIO_PORTA + ); + assert(status == RTEMS_SUCCESSFUL); + enet_isr_on(); + +} /* mc9328mxl_enet_init_hw() */ + +void mc9328mxl_enet_start(struct ifnet *ifp) +{ + mc9328mxl_enet_softc_t *sc = ifp->if_softc; + + rtems_bsdnet_event_send(sc->tx_task, START_TRANSMIT_EVENT); + ifp->if_flags |= IFF_OACTIVE; +} + +void mc9328mxl_enet_stop (mc9328mxl_enet_softc_t *sc) +{ + struct ifnet *ifp = &sc->arpcom.ac_if; + + ifp->if_flags &= ~IFF_RUNNING; + + + /* Stop the transmitter and receiver. */ + lan91c11x_write_reg(LAN91C11X_TCR, + (lan91c11x_read_reg(LAN91C11X_TCR) & + ~LAN91C11X_TCR_TXENA)); + + lan91c11x_write_reg(LAN91C11X_RCR, + (lan91c11x_read_reg(LAN91C11X_RCR) & + ~LAN91C11X_RCR_RXEN)); + +} + +/* + * Driver transmit daemon + */ +void mc9328mxl_enet_tx_task(void *arg) +{ + mc9328mxl_enet_softc_t *sc = (mc9328mxl_enet_softc_t *)arg; + struct ifnet *ifp = &sc->arpcom.ac_if; + struct mbuf *m; + rtems_event_set events; + + for (;;) + { + rtems_bsdnet_event_receive( + START_TRANSMIT_EVENT, + RTEMS_EVENT_ANY | RTEMS_WAIT, + RTEMS_NO_TIMEOUT, + &events); + + /* Send packets till queue is empty */ + for (;;) + { + /* Get the next mbuf chain to transmit. */ + IF_DEQUEUE(&ifp->if_snd, m); + if (!m) { + break; + } + mc9328mxl_enet_sendpacket (ifp, m); + softc.stats.tx_packets++; + + } + ifp->if_flags &= ~IFF_OACTIVE; + } +} + +/* Send packet */ +void mc9328mxl_enet_sendpacket (struct ifnet *ifp, struct mbuf *m) +{ + struct mbuf *l = NULL; + int size = 0; + int tmp; + int i; + int start; + uint16_t d; + + /* How big is the packet ? */ + l = m; + do { + size += l->m_len; + l = l->m_next; + } while (l != NULL); + + /* Allocate a TX buffer */ + lan91c11x_write_reg(LAN91C11X_MMUCMD, + (LAN91C11X_MMUCMD_ALLOCTX | + (size >> 8))); + + /* Wait for the allocation */ + while ((lan91c11x_read_reg(LAN91C11X_INT) & LAN91C11X_INT_ALLOC) == 0) { + continue; + } + + tmp = lan91c11x_read_reg(LAN91C11X_PNR); + lan91c11x_write_reg(LAN91C11X_PNR, ((tmp >> 8) & 0xff)); + + /* Set the data pointer for auto increment */ + lan91c11x_write_reg(LAN91C11X_PTR, LAN91C11X_PTR_AUTOINC); + + /* A delay is needed between pointer and data access ?!? */ + for (i = 0; i < 10; i++) { + continue; + } + + /* Write status word */ + lan91c11x_write_reg(LAN91C11X_DATA, 0); + + /* Write byte count */ + if (size & 1) { + size++; + } + lan91c11x_write_reg(LAN91C11X_DATA, size + 6); + + lan91c11x_lock(); + + /* Copy the mbuf */ + l = m; + start = 0; + d = 0; + while (l != NULL) + { + uint8_t *data; + + data = mtod(l, uint8_t *); + + for (i = start; i < l->m_len; i++) { + if ((i & 1) == 0) { + d = data[i] << 8; + } else { + d = d | data[i]; + lan91c11x_write_reg_fast(LAN91C11X_DATA, htons(d)); + } + } + + /* If everything is 2 byte aligned, i will be even */ + start = (i & 1); + + l = l->m_next; + } + + /* write control byte */ + if (i & 1) { + lan91c11x_write_reg_fast(LAN91C11X_DATA, + htons(LAN91C11X_PKT_CTRL_ODD | d)); + } else { + lan91c11x_write_reg_fast(LAN91C11X_DATA, 0); + } + + lan91c11x_unlock(); + + /* Enable TX interrupts */ + lan91c11x_write_reg(LAN91C11X_INT, + (lan91c11x_read_reg(LAN91C11X_INT) | + LAN91C11X_INT_TXMASK | + LAN91C11X_INT_TXEMASK)); + + /* Enqueue it */ + lan91c11x_write_reg(LAN91C11X_MMUCMD, + LAN91C11X_MMUCMD_ENQUEUE); + + /* free the mbuf chain we just copied */ + m_freem(m); + +} /* mc9328mxl_enet_sendpacket () */ + + +/* reader task */ +void mc9328mxl_enet_rx_task(void *arg) +{ + mc9328mxl_enet_softc_t *sc = (mc9328mxl_enet_softc_t *)arg; + struct ifnet *ifp = &sc->arpcom.ac_if; + struct mbuf *m; + struct ether_header *eh; + rtems_event_set events; + int pktlen; + uint16_t rsw; + uint16_t bc; + uint16_t cbyte; + int i; + uint16_t int_reg; + + /* Input packet handling loop */ + while (1) { + rtems_bsdnet_event_receive( + START_RECEIVE_EVENT, + RTEMS_EVENT_ANY | RTEMS_WAIT, + RTEMS_NO_TIMEOUT, + &events); + + /* Configure for reads from RX data area */ + lan91c11x_write_reg(LAN91C11X_PTR, + (LAN91C11X_PTR_AUTOINC | + LAN91C11X_PTR_RCV | + LAN91C11X_PTR_READ)); + + /* read the receive status word */ + rsw = lan91c11x_read_reg(LAN91C11X_DATA); + /* TBD: Need to check rsw here */ + + /* read the byte count */ + bc = lan91c11x_read_reg(LAN91C11X_DATA); + pktlen = (bc & 0x7ff) - 6; + + /* get an mbuf for this packet */ + MGETHDR(m, M_WAIT, MT_DATA); + + /* now get a cluster pointed to by the mbuf */ + /* since an mbuf by itself is too small */ + MCLGET(m, M_WAIT); + + lan91c11x_lock(); + + /* Copy the received packet into an mbuf */ + for (i = 0; i < (pktlen / 2); i++) { + ((uint16_t*)m->m_ext.ext_buf)[i] = + lan91c11x_read_reg_fast(LAN91C11X_DATA); + } + + cbyte = lan91c11x_read_reg_fast(LAN91C11X_DATA); + if (cbyte & LAN91C11X_PKT_CTRL_ODD) { + ((uint16_t*)m->m_ext.ext_buf)[i] = cbyte; + pktlen++; + } + lan91c11x_unlock(); + + /* Release the packets memory */ + lan91c11x_write_reg(LAN91C11X_MMUCMD, + LAN91C11X_MMUCMD_REMTOP); + + /* set the receiving interface */ + m->m_pkthdr.rcvif = ifp; + m->m_nextpkt = 0; + + /* set the length of the mbuf */ + m->m_len = pktlen - (sizeof(struct ether_header)); + m->m_pkthdr.len = m->m_len; + + /* strip off the ethernet header from the mbuf */ + /* but save the pointer to it */ + eh = mtod (m, struct ether_header *); + m->m_data += sizeof(struct ether_header); + + + softc.stats.rx_packets++; + + /* give all this stuff to the stack */ + ether_input(ifp, eh, m); + + /* renable RX interrupts */ + int_reg = lan91c11x_read_reg(LAN91C11X_INT); + int_reg |= LAN91C11X_INT_RXMASK; + lan91c11x_write_reg(LAN91C11X_INT, int_reg); + + } +} /* mc9328mxl_enet_rx_task */ + + +/* Show interface statistics */ +void mc9328mxl_enet_stats (mc9328mxl_enet_softc_t *sc) +{ + printf (" Total Interrupts:%-8lu", sc->stats.interrupts); + printf (" Rx Interrupts:%-8lu", sc->stats.rx_interrupts); + printf (" Tx Interrupts:%-8lu\n", sc->stats.tx_interrupts); + printf (" Tx Error Interrupts:%-8lu\n", sc->stats.txerr_interrupts); + printf (" Rx Packets:%-8lu", sc->stats.rx_packets); + printf (" Tx Packets:%-8lu\n", sc->stats.tx_packets); +} + + +/* Enables mc9328mxl_enet interrupts. */ +static void enet_isr_on(void) +{ + /* Enable interrupts */ + MC9328MXL_AITC_INTENNUM = MC9328MXL_INT_GPIO_PORTA; + + return; +} + +/* Driver ioctl handler */ +static int +mc9328mxl_enet_ioctl (struct ifnet *ifp, ioctl_command_t command, caddr_t data) +{ + mc9328mxl_enet_softc_t *sc = ifp->if_softc; + int error = 0; + + switch (command) { + case SIOCGIFADDR: + case SIOCSIFADDR: + ether_ioctl (ifp, command, data); + break; + + case SIOCSIFFLAGS: + switch (ifp->if_flags & (IFF_UP | IFF_RUNNING)) + { + case IFF_RUNNING: + mc9328mxl_enet_stop (sc); + break; + + case IFF_UP: + mc9328mxl_enet_init (sc); + break; + + case IFF_UP | IFF_RUNNING: + mc9328mxl_enet_stop (sc); + mc9328mxl_enet_init (sc); + break; + + default: + break; + } /* switch (if_flags) */ + break; + + case SIO_RTEMS_SHOW_STATS: + mc9328mxl_enet_stats (sc); + break; + + /* + * FIXME: All sorts of multicast commands need to be added here! + */ + default: + error = EINVAL; + break; + } /* switch (command) */ + return error; +} + +/* interrupt handler */ +static void enet_isr(void * unused) +{ + uint16_t int_reg; + + softc.stats.interrupts++; + /* get the ISR status and determine RX or TX */ + int_reg = lan91c11x_read_reg(LAN91C11X_INT); + + /* Handle RX interrupts */ + if ((int_reg & LAN91C11X_INT_RX) && (int_reg & LAN91C11X_INT_RXMASK)) { + softc.stats.rx_interrupts++; + + /* Disable the interrupt */ + int_reg &= ~LAN91C11X_INT_RXMASK; + + rtems_bsdnet_event_send (softc.rx_task, START_RECEIVE_EVENT); + } + + /* Handle TX Empty interrupts */ + if ((int_reg & LAN91C11X_INT_TXE) && (int_reg & LAN91C11X_INT_TXEMASK)) { + softc.stats.tx_interrupts++; + + /* Disable the interrupt */ + int_reg &= ~LAN91C11X_INT_TXEMASK; + + /* Acknowledge the interrupt */ + int_reg |= LAN91C11X_INT_TXE; + + rtems_bsdnet_event_send(softc.tx_task, START_TRANSMIT_EVENT); + + } + + /* Handle interrupts for transmit errors */ + if ((int_reg & LAN91C11X_INT_TX) && (int_reg & LAN91C11X_INT_TXMASK)) { + softc.stats.txerr_interrupts++; + printk("Caught TX interrupt - error on transmission\n"); + } + + /* Update the interrupt register on the 91c11x */ + lan91c11x_write_reg(LAN91C11X_INT, int_reg); + + /* clear GPIO Int. status */ + MC9328MXL_GPIOA_ISR |= bit(3); +} + diff --git a/bsps/arm/csb337/net/network.c b/bsps/arm/csb337/net/network.c new file mode 100644 index 0000000000..af7150122d --- /dev/null +++ b/bsps/arm/csb337/net/network.c @@ -0,0 +1,864 @@ +/* + * AT91RM9200 ethernet driver + * + * Copyright (c) 2003 by Cogent Computer Systems + * Written by Mike Kelly <mike@cogcomp.com> + * and Jay Monkman <jtm@lopingdog.com> + * + * + * July 2009: Joel Sherrill merged csb637 PHY differences from + * MicroMonitor 1.17. + */ + +#define __INSIDE_RTEMS_BSD_TCPIP_STACK__ + +#include <rtems.h> +#include <rtems/rtems_bsdnet.h> +#include <rtems/bspIo.h> +#include <at91rm9200.h> +#include <at91rm9200_emac.h> +#include <at91rm9200_gpio.h> +#include <at91rm9200_pmc.h> + +#include <stdio.h> +#include <string.h> + +#include <errno.h> +#include <rtems/error.h> +#include <assert.h> + +#include <sys/param.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/sockio.h> + +#include <net/if.h> + +#include <netinet/in.h> +#include <netinet/if_ether.h> + +#include <bsp/irq.h> +#include <bspopts.h> + +/* enable debugging of the PHY code */ +#define PHY_DBG + +/* enable debugging of the EMAC code */ +/* #define EMAC_DBG */ + +#if csb637 + /* Bit defines for the PHY Status Register #1 (phy address 0x01) */ + /* 1 = PHY able to perform 100BASE-T4 */ + #define PHY_STAT_100BASE_T4 BIT15 + /* 1 = PHY able to perform full-duplex 100BASE-X */ + #define PHY_STAT_100BASE_X_FDX BIT14 + /* 1 = PHY able to perform half-duplex 100BASE-X */ + #define PHY_STAT_100BASE_X_HDX BIT13 + /* 1 = PHY able to operate at 10 Mbps in full-duplex mode */ + #define PHY_STAT_10BASE_FDX BIT12 + /* 1 = PHY able to operate at 10 Mbps in half-duplex mode */ + #define PHY_STAT_10BASE_HDX BIT11 + /* 1 = PHY will accept management frames with preamble suppressed */ + #define PHY_STAT_MF_PREAMBLE BIT6 + /* 1 = Auto-negotiation complete */ + #define PHY_STAT_AUTO_NEG_DONE BIT5 + /* 1 = Remote fault condition detected */ + #define PHY_STAT_REM_FLT BIT4 + /* 1 = PHY is able to perform Auto-Negotiation */ + #define PHY_STAT_AUTO_NEG_ABLE BIT3 + /* 1 = Link is up */ + #define PHY_STAT_LINK_UP BIT2 + /* 1 = Jabber condition detected */ + #define PHY_STAT_JABBER BIT1 + /* 1 = Extended register capabilities */ + #define PHY_STAT_EXT_REG BIT0 + + /* Bit defines for the Auxillary Mode 3 register */ + #define PHY_AUX_MODE2_TRAFFIC_LED BIT6 +#endif + + /* interrupt stuff */ + #define EMAC_INT_PRIORITY 0 /* lowest priority */ + + /* RTEMS event used by interrupt handler to start receive daemon. */ + #define START_RECEIVE_EVENT RTEMS_EVENT_1 + + /* RTEMS event used to start transmit daemon. */ + #define START_TRANSMIT_EVENT RTEMS_EVENT_2 + +static void at91rm9200_emac_isr (void *); +static void at91rm9200_emac_isr_on(void); + +/* use the values defined in linkcmds for our use of SRAM */ +extern void * at91rm9200_emac_rxbuf_hdrs; +extern void * at91rm9200_emac_txbuf; +extern void * at91rm9200_emac_rxbufs; + +/* Set up EMAC hardware */ +/* Number of Receive and Transmit Buffers and Buffer Descriptors */ +#define NUM_RXBDS 8 +#define NUM_TXBDS 1 +#define RX_BUFFER_SIZE 0x600 + +/* use internal SRAM for buffers and descriptors + * also insure that the receive descriptors + * start on a 64byte boundary + * Receive Buffer Descriptor Header + */ + +typedef struct +{ + unsigned long address; + unsigned long status; +} RXBUF_HDR; + +RXBUF_HDR *rxbuf_hdrs; +unsigned char *txbuf; +unsigned char *rxbuf; + +int delay_cnt; + +/* + * Hardware-specific storage + */ +typedef struct +{ + /* + * Connection to networking code + * This entry *must* be the first in the sonic_softc structure. + */ + struct arpcom arpcom; + + /* + * Interrupt vector + */ + rtems_vector_number vector; + + /* + * Indicates configuration + */ + int acceptBroadcast; + + /* + * Task waiting for interrupts + */ + rtems_id rxDaemonTid; + rtems_id txDaemonTid; + + /* + * current receive header + */ + int rx_buf_idx; + + + + /* + * Statistics + */ + unsigned long Interrupts; + unsigned long rxInterrupts; + unsigned long rxMissed; + unsigned long rxGiant; + unsigned long rxNonOctet; + unsigned long rxBadCRC; + unsigned long rxCollision; + + unsigned long txInterrupts; + unsigned long txSingleCollision; + unsigned long txMultipleCollision; + unsigned long txCollision; + unsigned long txDeferred; + unsigned long txUnderrun; + unsigned long txLateCollision; + unsigned long txExcessiveCollision; + unsigned long txExcessiveDeferral; + unsigned long txLostCarrier; + unsigned long txRawWait; +} at91rm9200_emac_softc_t; + +static at91rm9200_emac_softc_t softc; + + +/* The AT91RM9200 ethernet fifos are very undersized. Therefore + * we use the internal SRAM to hold 4 receive packets and one + * transmit packet. Note that the AT91RM9200 can only queue + * one transmit packet at a time. + */ + +/* function prototypes */ +int rtems_at91rm9200_emac_attach (struct rtems_bsdnet_ifconfig *config, + void *chip); +void at91rm9200_emac_init(void *arg); +void at91rm9200_emac_init_hw(at91rm9200_emac_softc_t *sc); +void at91rm9200_emac_start(struct ifnet *ifp); +void at91rm9200_emac_stop (at91rm9200_emac_softc_t *sc); +void at91rm9200_emac_txDaemon (void *arg); +void at91rm9200_emac_sendpacket (struct ifnet *ifp, struct mbuf *m); +void at91rm9200_emac_rxDaemon(void *arg); +void at91rm9200_emac_stats (at91rm9200_emac_softc_t *sc); +static int at91rm9200_emac_ioctl (struct ifnet *ifp, + ioctl_command_t command, + caddr_t data); + +#if csb637 +/* + * phyread(): Read the PHY + */ +static uint32_t phyread(uint8_t reg) +{ + EMAC_REG(EMAC_MAN) = (0x01 << 30 /* Start of Frame Delimiter */ + | 0x02 << 28 /* Operation, 0x01 = Write, 0x02 = Read */ + | 0x00 << 23 /* Phy Number, 0 as we only have one */ + | reg << 18 /* Phy Register */ + | 0x02 << 16); /* must be 0x02 for turn around field */ + + /* wait for phy read to complete (was udelay(5000)) */ + rtems_task_wake_after(1); + + #if defined(EMAC_DBG) + printk( + "EMAC: Phy 0, Reg %d, Read 0x%04lx.\n", + reg, + (EMAC_REG(EMAC_MAN) & 0xffff) + ); + #endif + + return EMAC_REG(EMAC_MAN) & 0xffff; +} +#endif + +/* + * phywrite(): Write the PHY + */ +static void phywrite(uint8_t reg, uint16_t data) +{ + EMAC_REG(EMAC_MAN) = (0x01 << 30 /* Start of Frame Delimiter */ + | 0x01 << 28 /* Operation, 0x01 = Write, 0x02 = Read */ + | 0x00 << 23 /* Phy Number, BCM5221 is address 0 */ + | reg << 18 /* Phy Register */ + | 0x02 << 16 /* must be 0x02 for turn around field */ + | data); + #if defined(EMAC_DBG) + printk("EMAC: Phy 0, Reg %d, Write 0x%04x.\n", reg, data); + #endif + + /* wait for phy write to complete (was udelay(5000)) */ + rtems_task_wake_after(1); +} + + +int rtems_at91rm9200_emac_attach ( + struct rtems_bsdnet_ifconfig *config, + void *chip /* only one ethernet, so no chip number */ + ) +{ + struct ifnet *ifp; + int mtu; + int unitnumber; + char *unitname; + void *p; + + /* an array of receive buffer descriptors -- avoid type punned warning */ + p = (void *)&at91rm9200_emac_rxbuf_hdrs; + rxbuf_hdrs = (RXBUF_HDR *)p; + + /* one transmit buffer, 1536 bytes maximum */ + txbuf = (unsigned char *)&at91rm9200_emac_txbuf; + + /* receive buffers starting address */ + rxbuf = (unsigned char *)&at91rm9200_emac_rxbufs; + /* + * Parse driver name + */ + if ((unitnumber = rtems_bsdnet_parse_driver_name (config, &unitname)) < 0) + return 0; + + /* + * Is driver free? + */ + if (unitnumber != 0) { + printk ("Bad AT91RM9200 EMAC unit number.\n"); + return 0; + } + ifp = &softc.arpcom.ac_if; + if (ifp->if_softc != NULL) { + printk ("Driver already in use.\n"); + return 0; + } + + /* + * zero out the control structure + */ + + memset( &softc, 0, sizeof(softc) ); + + + /* get the MAC address from the chip */ + softc.arpcom.ac_enaddr[0] = (EMAC_REG(EMAC_SA1L) >> 0) & 0xff; + softc.arpcom.ac_enaddr[1] = (EMAC_REG(EMAC_SA1L) >> 8) & 0xff; + softc.arpcom.ac_enaddr[2] = (EMAC_REG(EMAC_SA1L) >> 16) & 0xff; + softc.arpcom.ac_enaddr[3] = (EMAC_REG(EMAC_SA1L) >> 24) & 0xff; + softc.arpcom.ac_enaddr[4] = (EMAC_REG(EMAC_SA1H) >> 0) & 0xff; + softc.arpcom.ac_enaddr[5] = (EMAC_REG(EMAC_SA1H) >> 8) & 0xff; + + #if 0 + printk( "MAC=%02x:%02x:%02x:%02x:%02x:%02x\n", + softc.arpcom.ac_enaddr[0], + softc.arpcom.ac_enaddr[1], + softc.arpcom.ac_enaddr[2], + softc.arpcom.ac_enaddr[3], + softc.arpcom.ac_enaddr[4], + softc.arpcom.ac_enaddr[5] + ); + #endif + + if (config->mtu) { + mtu = config->mtu; + } else { + mtu = ETHERMTU; + } + + softc.acceptBroadcast = !config->ignore_broadcast; + + /* + * Set up network interface values + */ + ifp->if_softc = &softc; + ifp->if_unit = unitnumber; + ifp->if_name = unitname; + ifp->if_mtu = mtu; + ifp->if_init = at91rm9200_emac_init; + ifp->if_ioctl = at91rm9200_emac_ioctl; + ifp->if_start = at91rm9200_emac_start; + ifp->if_output = ether_output; + ifp->if_flags = IFF_BROADCAST; + if (ifp->if_snd.ifq_maxlen == 0) { + ifp->if_snd.ifq_maxlen = ifqmaxlen; + } + + softc.rx_buf_idx = 0; + + /* + * Attach the interface + */ + if_attach (ifp); + ether_ifattach (ifp); + return 1; +} + +void at91rm9200_emac_init(void *arg) +{ + at91rm9200_emac_softc_t *sc = arg; + struct ifnet *ifp = &sc->arpcom.ac_if; + rtems_status_code status = RTEMS_SUCCESSFUL; + + /* + *This is for stuff that only gets done once (at91rm9200_emac_init() + * gets called multiple times + */ + if (sc->txDaemonTid == 0) { + /* Set up EMAC hardware */ + at91rm9200_emac_init_hw(sc); + + /* Start driver tasks */ + sc->rxDaemonTid = rtems_bsdnet_newproc("ENrx", + 4096, + at91rm9200_emac_rxDaemon, + sc); + sc->txDaemonTid = rtems_bsdnet_newproc("ENtx", + 4096, + at91rm9200_emac_txDaemon, + sc); + } /* if txDaemonTid */ + + /* set our priority in the AIC */ + AIC_SMR_REG(AIC_SMR_EMAC) = AIC_SMR_PRIOR(EMAC_INT_PRIORITY); + + /* install the interrupt handler */ + status = rtems_interrupt_handler_install( + AT91RM9200_INT_EMAC, + "Network", + RTEMS_INTERRUPT_UNIQUE, + at91rm9200_emac_isr, + NULL + ); + assert(status == RTEMS_SUCCESSFUL); + at91rm9200_emac_isr_on(); + + /* EMAC doesn't support promiscuous, so ignore requests */ + if (ifp->if_flags & IFF_PROMISC) { + printk ("Warning - AT91RM9200 Ethernet driver" + " doesn't support Promiscuous Mode!\n"); + } + + /* + * Tell the world that we're running. + */ + ifp->if_flags |= IFF_RUNNING; + + /* Enable TX/RX and clear the statistics counters */ + EMAC_REG(EMAC_CTL) = (EMAC_CTL_TE | EMAC_CTL_RE | EMAC_CTL_CSR); + + /* clear any pending interrupts */ + EMAC_REG(EMAC_TSR) = 0xffffffff; + EMAC_REG(EMAC_RSR) = 0xffffffff; + +} /* at91rm9200_emac_init() */ + +void at91rm9200_emac_init_hw(at91rm9200_emac_softc_t *sc) +{ + int i; + + /* Configure shared pins for Ethernet, not GPIO */ + PIOA_REG(PIO_PDR) = ( BIT7 | /* tx clock */ + BIT8 | /* tx enable */ + BIT9 | /* tx data 0 */ + BIT10 | /* tx data 1 */ + BIT11 | /* carrier sense */ + BIT12 | /* rx data 0 */ + BIT13 | /* rx data 1 */ + BIT14 | /* rx error */ + BIT15 | /* MII clock */ + BIT16 ); /* MII data */ + + PIOB_REG(PIO_PDR) = ( BIT12 | /* tx data 2 */ + BIT13 | /* tx data 3 */ + BIT14 | /* tx error */ + BIT15 | /* rx data 2 */ + BIT16 | /* rx data 3 */ + BIT17 | /* rx data valid */ + BIT18 | /* rx collistion */ + BIT19 ); /* rx clock */ + + PIOB_REG(PIO_BSR) = ( BIT12 | /* tx data 2 */ + BIT13 | /* tx data 3 */ + BIT14 | /* tx error */ + BIT15 | /* rx data 2 */ + BIT16 | /* rx data 3 */ + BIT17 | /* rx data valid */ + BIT18 | /* rx collistion */ + BIT19 ); /* rx clock */ + + + /* Enable the clock to the EMAC */ + PMC_REG(PMC_PCER) |= PMC_PCR_PID_EMAC; + + /* initialize our receive buffer descriptors */ + for (i = 0; i < NUM_RXBDS-1; i++) { + rxbuf_hdrs[i].address = (unsigned long)(&rxbuf[i * RX_BUFFER_SIZE]); + rxbuf_hdrs[i].status = 0x00000000; + } + + /* last one needs the wrapbit set as well */ + rxbuf_hdrs[i].address = ((unsigned long)(&rxbuf[i * RX_BUFFER_SIZE]) | + RXBUF_ADD_WRAP); + rxbuf_hdrs[i].status = 0x00000000; + + /* point to our receive buffer queue */ + EMAC_REG(EMAC_RBQP) = (unsigned long)rxbuf_hdrs; + + /* clear any left over status bits */ + EMAC_REG(EMAC_RSR) &= ~(EMAC_RSR_OVR | EMAC_RSR_REC | EMAC_RSR_BNA); + + /* set the MII clock divder to MCK/64 */ + EMAC_REG(EMAC_CFG) &= EMAC_CFG_CLK_MASK; + EMAC_REG(EMAC_CFG) = (EMAC_CFG_CLK_64 | EMAC_CFG_BIG | EMAC_CFG_FD); + + /* enable the MII interface */ + EMAC_REG(EMAC_CTL) = EMAC_CTL_MPE; + + #if csb637 + { + int timeout; + uint32_t emac_link_status; + + #if defined(PHY_DBG) + printk("EMAC: Getting Link Status.\n"); + #endif + /* read the PHY ID registers */ + emac_link_status = phyread(0x02); + emac_link_status = phyread(0x03); + + /* Get the link status - wait for done with a timeout */ + for (timeout = 10000 ; timeout ; ) { + for (i = 0; i < 100; i++) + ; + emac_link_status = phyread(0x01); + if (!(emac_link_status & PHY_STAT_AUTO_NEG_ABLE)) { + #if defined(PHY_DBG) + printk("EMAC: PHY is unable to Auto-Negotatiate!\n"); + #endif + timeout = 0; + break; + } + if (emac_link_status & PHY_STAT_AUTO_NEG_DONE) { + #if defined(PHY_DBG) + printk("EMAC: Auto-Negotiate Complete, Link = "); + #endif + break; + } + timeout-- ; + } + if (!timeout) { + #if defined(PHY_DBG) + printk( + "EMAC: Auto-Negotatiate Failed, Status = 0x%04lx!\n" + "EMAC: Initialization Halted.\n", + emac_link_status + ); + #endif + /* if autonegotiation fails, just force to 10HD... */ + emac_link_status = PHY_STAT_10BASE_HDX; + } + + /* Set SPD and FD based on the return link status */ + if (emac_link_status & (PHY_STAT_100BASE_X_FDX | PHY_STAT_100BASE_X_HDX)){ + EMAC_REG(EMAC_CFG) |= EMAC_CFG_SPD; + #if defined(PHY_DBG) + printk("100MBIT, "); + #endif + } else { + EMAC_REG(EMAC_CFG) &= ~EMAC_CFG_SPD; + #if defined(PHY_DBG) + printk("10MBIT, "); + #endif + } + + if (emac_link_status & (PHY_STAT_100BASE_X_FDX | PHY_STAT_10BASE_FDX)) { + EMAC_REG(EMAC_CFG) |= EMAC_CFG_FD; + #if defined(PHY_DBG) + printk("Full Duplex.\n"); + #endif + } else { + EMAC_REG(EMAC_CFG) &= ~EMAC_CFG_FD; + #if defined(PHY_DBG) + printk("Half Duplex.\n"); + #endif + } + + /* Set PHY LED modes. Traffic Meter Mode for ACTLED + * Set Bit 6 - Traffic Mode on + */ + phywrite(0x1b, PHY_AUX_MODE2_TRAFFIC_LED); + } + #else + /* must be csb337 */ + /* Set PHY LED2 to combined Link/Activity and enable pulse stretching */ + phywrite( 18, 0x0d0a ); + #endif + + #if 0 + EMAC_REG(EMAC_MAN) = (0x01 << 30 | /* Start of Frame Delimiter */ + 0x01 << 28 | /* Operation, 0x01 = Write */ + 0x00 << 23 | /* Phy Number */ + 0x14 << 18 | /* Phy Register */ + 0x02 << 16 | /* must be 0x02 */ + 0x0D0A); /* Write data (0x0000 if read) */ + #endif + +} /* at91rm9200_emac_init_hw() */ + +void at91rm9200_emac_start(struct ifnet *ifp) +{ + at91rm9200_emac_softc_t *sc = ifp->if_softc; + + rtems_bsdnet_event_send(sc->txDaemonTid, START_TRANSMIT_EVENT); + ifp->if_flags |= IFF_OACTIVE; +} + +void at91rm9200_emac_stop (at91rm9200_emac_softc_t *sc) +{ + struct ifnet *ifp = &sc->arpcom.ac_if; + + ifp->if_flags &= ~IFF_RUNNING; + + /* + * Stop the transmitter and receiver. + */ + EMAC_REG(EMAC_CTL) &= ~(EMAC_CTL_TE | EMAC_CTL_RE); +} + +/* + * Driver transmit daemon + */ +void at91rm9200_emac_txDaemon (void *arg) +{ + at91rm9200_emac_softc_t *sc = (at91rm9200_emac_softc_t *)arg; + struct ifnet *ifp = &sc->arpcom.ac_if; + struct mbuf *m; + rtems_event_set events; + + for (;;) + { + /* turn on TX interrupt, then wait for one */ + EMAC_REG(EMAC_IER) = EMAC_INT_TCOM; /* Transmit complete */ + + rtems_bsdnet_event_receive( + START_TRANSMIT_EVENT, + RTEMS_EVENT_ANY | RTEMS_WAIT, + RTEMS_NO_TIMEOUT, + &events); + + /* Send packets till queue is empty */ + for (;;) + { + /* Get the next mbuf chain to transmit. */ + IF_DEQUEUE(&ifp->if_snd, m); + if (!m) + break; + at91rm9200_emac_sendpacket (ifp, m); + } + ifp->if_flags &= ~IFF_OACTIVE; + } +} + +/* Send packet */ +void at91rm9200_emac_sendpacket (struct ifnet *ifp, struct mbuf *m) +{ + struct mbuf *l = NULL; + unsigned int pkt_offset = 0; + delay_cnt = 0; + /* printk("at91rm9200_emac_sendpacket %p\n", m); */ + + /* Wait for EMAC Transmit Queue to become available. */ + while (((EMAC_REG(EMAC_TSR) & EMAC_TSR_COMP) == 0) && + ((EMAC_REG(EMAC_TSR) & EMAC_TSR_TXIDLE) == 0)) + + { + delay_cnt++; +/* sleep(0); make sure we don't hog the cpu */ + continue; + } + + /* copy the mbuf chain into the transmit buffer */ + l = m; + while (l != NULL) { + memcpy(((char *)txbuf + pkt_offset), /* offset into pkt for mbuf */ + (char *)mtod(l, void *), /* cast to void */ + l->m_len); /* length of this mbuf */ + + pkt_offset += l->m_len; /* update offset */ + l = l->m_next; /* get next mbuf, if any */ + } + + /* free the mbuf chain we just copied */ + m_freem(m); + + /* clear any pending status */ + EMAC_REG(EMAC_TSR) = (EMAC_TSR_OVR | EMAC_TSR_COL | EMAC_TSR_RLE + | EMAC_TSR_COMP | EMAC_TSR_UND); + + /* tell the EMAC about our buffer */ + EMAC_REG(EMAC_TAR) = (unsigned long)txbuf; + EMAC_REG(EMAC_TCR) = (unsigned long)pkt_offset; +} /* at91rm9200_emac_sendpacket () */ + + +/* SONIC reader task */ +void at91rm9200_emac_rxDaemon(void *arg) +{ + at91rm9200_emac_softc_t *sc = (at91rm9200_emac_softc_t *)arg; + struct ifnet *ifp = &sc->arpcom.ac_if; + struct mbuf *m; + struct ether_header *eh; + rtems_event_set events; + int pktlen; + + /* Input packet handling loop */ + for (;;) { + /* turn on RX interrupts, then wait for one */ + EMAC_REG(EMAC_IER) = (EMAC_INT_RCOM | /* Receive complete */ + EMAC_INT_RBNA | /* Receive buf not available */ + EMAC_INT_ROVR); /* Receive overrun */ + + rtems_bsdnet_event_receive( + START_RECEIVE_EVENT, + RTEMS_EVENT_ANY | RTEMS_WAIT, + RTEMS_NO_TIMEOUT, + &events); + + if (EMAC_REG(EMAC_RSR) & EMAC_RSR_BNA) { + printk("1: EMAC_BNA\n"); + } + + if (EMAC_REG(EMAC_RSR) & EMAC_RSR_OVR) { + printk("1: EMAC_OVR\n"); + } + + /* clear the receive status as we do not use it anyway */ + EMAC_REG(EMAC_RSR) = (EMAC_RSR_REC | EMAC_RSR_OVR | EMAC_RSR_BNA); + + /* scan the buffer descriptors looking for any with data in them */ + while (rxbuf_hdrs[sc->rx_buf_idx].address & RXBUF_ADD_OWNED) { + pktlen = rxbuf_hdrs[sc->rx_buf_idx].status & RXBUF_STAT_LEN_MASK; + + /* get an mbuf this packet */ + MGETHDR(m, M_WAIT, MT_DATA); + + /* now get a cluster pointed to by the mbuf */ + /* since an mbuf by itself is too small */ + MCLGET(m, M_WAIT); + + /* set the type of mbuf to ifp (ethernet I/F) */ + m->m_pkthdr.rcvif = ifp; + m->m_nextpkt = 0; + + /* copy the packet into the cluster pointed to by the mbuf */ + memcpy((char *)m->m_ext.ext_buf, + (char *)(rxbuf_hdrs[sc->rx_buf_idx].address & 0xfffffffc), + pktlen); + + /* Release the buffer ASAP back to the EMAC */ + rxbuf_hdrs[sc->rx_buf_idx].address &= ~RXBUF_ADD_OWNED; + + /* set the length of the mbuf */ + m->m_len = pktlen - (sizeof(struct ether_header) + 4); + m->m_pkthdr.len = m->m_len; + + /* strip off the ethernet header from the mbuf */ + /* but save the pointer to it */ + eh = mtod (m, struct ether_header *); + m->m_data += sizeof(struct ether_header); + + /* increment the buffer index */ + sc->rx_buf_idx++; + if (sc->rx_buf_idx >= NUM_RXBDS) { + sc->rx_buf_idx = 0; + } + + /* give all this stuff to the stack */ + ether_input(ifp, eh, m); + + } /* while ADD_OWNED = 0 */ + + if (EMAC_REG(EMAC_RSR) & EMAC_RSR_BNA) { + printk("2:EMAC_BNA\n"); + } + if (EMAC_REG(EMAC_RSR) & EMAC_RSR_OVR) { + printk("2:EMAC_OVR\n"); + } + + + } /* for (;;) */ +} /* at91rm9200_emac_rxDaemon() */ + + +/* Show interface statistics */ +void at91rm9200_emac_stats (at91rm9200_emac_softc_t *sc) +{ + printf (" Total Interrupts:%-8lu", sc->Interrupts); + printf (" Rx Interrupts:%-8lu", sc->rxInterrupts); + printf (" Giant:%-8lu", sc->rxGiant); + printf (" Non-octet:%-8lu\n", sc->rxNonOctet); + printf (" Bad CRC:%-8lu", sc->rxBadCRC); + printf (" Collision:%-8lu", sc->rxCollision); + printf (" Missed:%-8lu\n", sc->rxMissed); + + printf ( " Tx Interrupts:%-8lu", sc->txInterrupts); + printf ( " Deferred:%-8lu", sc->txDeferred); + printf (" Lost Carrier:%-8lu\n", sc->txLostCarrier); + printf ( "Single Collisions:%-8lu", sc->txSingleCollision); + printf ( "Multiple Collisions:%-8lu", sc->txMultipleCollision); + printf ("Excessive Collisions:%-8lu\n", sc->txExcessiveCollision); + printf ( " Total Collisions:%-8lu", sc->txCollision); + printf ( " Late Collision:%-8lu", sc->txLateCollision); + printf (" Underrun:%-8lu\n", sc->txUnderrun); + printf ( " Raw output wait:%-8lu\n", sc->txRawWait); +} + + +/* Enables at91rm9200_emac interrupts. */ +static void at91rm9200_emac_isr_on(void) +{ + /* Enable various TX/RX interrupts */ + EMAC_REG(EMAC_IER) = (EMAC_INT_RCOM | /* Receive complete */ + EMAC_INT_RBNA | /* Receive buffer not available */ + EMAC_INT_TCOM | /* Transmit complete */ + EMAC_INT_ROVR | /* Receive overrun */ + EMAC_INT_ABT); /* Abort on DMA transfer */ + + return; +} + +/* Driver ioctl handler */ +static int +at91rm9200_emac_ioctl (struct ifnet *ifp, ioctl_command_t command, caddr_t data) +{ + at91rm9200_emac_softc_t *sc = ifp->if_softc; + int error = 0; + + switch (command) { + case SIOCGIFADDR: + case SIOCSIFADDR: + ether_ioctl (ifp, command, data); + break; + + case SIOCSIFFLAGS: + switch (ifp->if_flags & (IFF_UP | IFF_RUNNING)) + { + case IFF_RUNNING: + at91rm9200_emac_stop (sc); + break; + + case IFF_UP: + at91rm9200_emac_init (sc); + break; + + case IFF_UP | IFF_RUNNING: + at91rm9200_emac_stop (sc); + at91rm9200_emac_init (sc); + break; + + default: + break; + } /* switch (if_flags) */ + break; + + case SIO_RTEMS_SHOW_STATS: + at91rm9200_emac_stats (sc); + break; + + /* + * FIXME: All sorts of multicast commands need to be added here! + */ + default: + error = EINVAL; + break; + } /* switch (command) */ + return error; +} + +/* interrupt handler */ +static void at91rm9200_emac_isr (void * unused) +{ + unsigned long status32; + + /* get the ISR status and determine RX or TX */ + status32 = EMAC_REG(EMAC_ISR); + + if (status32 & EMAC_INT_ABT) { + EMAC_REG(EMAC_IDR) = EMAC_INT_ABT; /* disable it */ + rtems_panic("AT91RM9200 Ethernet MAC has received an Abort.\n"); + } + + if (status32 & (EMAC_INT_RCOM | /* Receive complete */ + EMAC_INT_RBNA | /* Receive buffer not available */ + EMAC_INT_ROVR)) { /* Receive overrun */ + + /* disable the RX interrupts */ + EMAC_REG(EMAC_IDR) = (EMAC_INT_RCOM | /* Receive complete */ + EMAC_INT_RBNA | /* Receive buf not available */ + EMAC_INT_ROVR); /* Receive overrun */ + + rtems_bsdnet_event_send (softc.rxDaemonTid, START_RECEIVE_EVENT); + } + + if (status32 & EMAC_INT_TCOM) { /* Transmit buffer register empty */ + + /* disable the TX interrupts */ + EMAC_REG(EMAC_IDR) = EMAC_INT_TCOM; + + rtems_bsdnet_event_send (softc.txDaemonTid, START_TRANSMIT_EVENT); + } +} + diff --git a/bsps/arm/edb7312/net/network.c b/bsps/arm/edb7312/net/network.c new file mode 100644 index 0000000000..480808aeaa --- /dev/null +++ b/bsps/arm/edb7312/net/network.c @@ -0,0 +1,126 @@ +#define __INSIDE_RTEMS_BSD_TCPIP_STACK__ + +#include <rtems.h> +#include <sys/param.h> +#include <sys/mbuf.h> +#include <bsp/irq.h> +#include <libchip/cs8900.h> +#include <assert.h> + +#define CS8900_BASE 0x20000300 +unsigned int bsp_cs8900_io_base = 0; +unsigned int bsp_cs8900_memory_base = 0; +static void cs8900_isr(void *); + +char g_enetbuf[1520]; + +static void cs8900_isr(void *arg) +{ + cs8900_interrupt(BSP_EINT3, arg); +} + +/* cs8900_io_set_reg - set one of the I/O addressed registers */ +void cs8900_io_set_reg (cs8900_device *cs, unsigned short reg, unsigned short data) +{ + /* works the same for all values of dev */ +/* + printf("cs8900_io_set_reg: reg: %#6x, val %#6x\n", + CS8900_BASE + reg, + data); +*/ + *(unsigned short *)(CS8900_BASE + reg) = data; +} + +/* cs8900_io_get_reg - reads one of the I/O addressed registers */ +unsigned short cs8900_io_get_reg (cs8900_device *cs, unsigned short reg) +{ + unsigned short val; + /* works the same for all values of dev */ + val = *(unsigned short *)(CS8900_BASE + reg); +/* + printf("cs8900_io_get_reg: reg: %#6x, val %#6x\n", reg, val); +*/ + return val; +} + +/* cs8900_mem_set_reg - sets one of the registers mapped through + * PacketPage + */ +void cs8900_mem_set_reg (cs8900_device *cs, unsigned long reg, unsigned short data) +{ + /* works the same for all values of dev */ + cs8900_io_set_reg(cs, CS8900_IO_PACKET_PAGE_PTR, reg); + cs8900_io_set_reg(cs, CS8900_IO_PP_DATA_PORT0, data); +} + +/* cs8900_mem_get_reg - reads one of the registers mapped through + * PacketPage + */ +unsigned short cs8900_mem_get_reg (cs8900_device *cs, unsigned long reg) +{ + /* works the same for all values of dev */ + cs8900_io_set_reg(cs, CS8900_IO_PACKET_PAGE_PTR, reg); + return cs8900_io_get_reg(cs, CS8900_IO_PP_DATA_PORT0); +} + +void cs8900_attach_interrupt (cs8900_device *cs) +{ + rtems_status_code status = RTEMS_SUCCESSFUL; + + status = rtems_interrupt_handler_install( + BSP_EINT3, + "Network", + RTEMS_INTERRUPT_UNIQUE, + cs8900_isr, + cs + ); + assert(status == RTEMS_SUCCESSFUL); +} + +void cs8900_detach_interrupt (cs8900_device *cs) +{ + rtems_status_code status = RTEMS_SUCCESSFUL; + + status = rtems_interrupt_handler_remove( + BSP_EINT3, + cs8900_isr, + cs + ); + assert(status == RTEMS_SUCCESSFUL); +} + +unsigned short cs8900_get_data_block (cs8900_device *cs, unsigned char *data) +{ + int len; + int i; + + len = cs8900_mem_get_reg(cs, CS8900_PP_RxLength); + + for (i = 0; i < ((len + 1) / 2); i++) { + ((short *)data)[i] = cs8900_io_get_reg(cs, + CS8900_IO_RX_TX_DATA_PORT0); + } + return len; +} + +void cs8900_tx_load (cs8900_device *cs, struct mbuf *m) +{ + int len; + unsigned short *data; + int i; + + len = 0; + + do { + memcpy(&g_enetbuf[len], mtod(m, const void *), m->m_len); + len += m->m_len; + m = m->m_next; + } while (m != 0); + + data = (unsigned short *) &g_enetbuf[0]; + for (i = 0; i < ((len + 1) / 2); i++) { + cs8900_io_set_reg(cs, + CS8900_IO_RX_TX_DATA_PORT0, + data[i]); + } +} diff --git a/bsps/arm/gumstix/net/rtl8019.c b/bsps/arm/gumstix/net/rtl8019.c new file mode 100644 index 0000000000..995835c689 --- /dev/null +++ b/bsps/arm/gumstix/net/rtl8019.c @@ -0,0 +1,1196 @@ +/* + *By Yang Xi <hiyangxi@gmail.com>. + *RTL8019 ethernet driver for Gumstix on SKYEYE simulator + *Based on NE2000 driver for pc386 bsp + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.org/license/LICENSE. + */ + +#define __INSIDE_RTEMS_BSD_TCPIP_STACK__ + +#include <bsp.h> +#include <bsp/irq.h> +#include "wd80x3.h" + +#include <stdio.h> +#include <assert.h> +#include <errno.h> + +#include <rtems/error.h> +#include <rtems/rtems_bsdnet.h> + +#include <sys/param.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/sockio.h> + +#include <net/if.h> +#include <net/if_arp.h> + +#include <netinet/in.h> +#include <netinet/if_ether.h> + + +/* Define this to force byte-wide data transfers with the NIC. This + is needed for boards like the TS-1325 386EX PC, which support only + an 8-bit PC/104 bus. Undefine this on a normal PC.*/ + +#define NE2000_BYTE_TRANSFERS + +/* Define this to print debugging messages with printk. */ + +/*#define DEBUG_NE2000 + #define DEBUG_NE*/ + +/* We expect to be able to read a complete packet into an mbuf. */ + +#if (MCLBYTES < 1520) +# error "Driver must have MCLBYTES >= 1520" +#endif + +/* The 8390 macro definitions in wd80x3.h expect RO to be defined. */ +#define RO 0 + +/* Minimum size of Ethernet packet. */ +#define ET_MINLEN 60 + +/* The number of NE2000 devices supported by this driver. */ + +#define NNEDRIVER 1 + +/* RTEMS event number used by the interrupt handler to signal the + driver task. This must not be any of the events used by the + network task synchronization. */ +#define INTERRUPT_EVENT RTEMS_EVENT_1 + +/* RTEMS event number used to start the transmit daemon. This must + not be the same as INTERRUPT_EVENT. */ +#define START_TRANSMIT_EVENT RTEMS_EVENT_2 + +/* Interrupts we want to handle from the device. */ + +#define NE_INTERRUPTS \ + (MSK_PRX | MSK_PTX | MSK_RXE | MSK_TXE | MSK_OVW | MSK_CNT) + +/* The size of a page in device memory. */ + +#define NE_PAGE_SIZE (256) + +/* The first page address in device memory. */ + +#define NE_START_PAGE (0x40) + +/* The last page address, plus 1. */ + +#define NE_STOP_PAGE (0x80) + +/* The number of pages used for a single transmit buffer. This is + 1536 bytes, enough for a full size packet. */ + +#define NE_TX_PAGES (6) + +/* The number of transmit buffers. We use two, so we can load one + packet while the other is being sent. */ + +#define NE_TX_BUFS (2) + +/* We use the first pages in memory as transmit buffers, and the + remaining ones as receive buffers. */ + +#define NE_FIRST_TX_PAGE (NE_START_PAGE) + +#define NE_FIRST_RX_PAGE (NE_FIRST_TX_PAGE + NE_TX_PAGES * NE_TX_BUFS) + +/* Data we store for each NE2000 device. */ + +struct ne_softc { + /* The bsdnet information structure. */ + struct arpcom arpcom; + + /* The interrupt request number. */ + unsigned int irno; + /* The base IO port number. */ + unsigned int port; + + /* Whether we accept broadcasts. */ + int accept_broadcasts; + + /* The thread ID of the transmit task. */ + rtems_id tx_daemon_tid; + /* The thread ID of the receive task. */ + rtems_id rx_daemon_tid; + + /* Whether we use byte-transfers with the device. */ + bool byte_transfers; + + /* The number of memory buffers which the transmit daemon has loaded + with data to be sent, but which have not yet been completely + sent. */ + int inuse; + /* The index of the next available transmit memory buffer. */ + int nextavail; + /* The index of the next transmit buffer to send. */ + int nextsend; + /* Nonzero if the device is currently transmitting a packet. */ + int transmitting; + /* The length of the data stored in each transmit buffer. */ + int sendlen[NE_TX_BUFS]; + + /* Set if we have a packet overrun while receiving. */ + int overrun; + /* Set if we should resend after an overrun. */ + int resend; + + /* Statistics. */ + struct { + /* Number of packets received. */ + unsigned long rx_packets; + /* Number of packets sent. */ + unsigned long tx_packets; + /* Number of interrupts. */ + unsigned long interrupts; + /* Number of receive acknowledgements. */ + unsigned long rx_acks; + /* Number of transmit acknowledgements. */ + unsigned long tx_acks; + /* Number of packet overruns. */ + unsigned long overruns; + /* Number of frame errors. */ + unsigned long rx_frame_errors; + /* Number of CRC errors. */ + unsigned long rx_crc_errors; + /* Number of missed packets. */ + unsigned long rx_missed_errors; + } stats; +}; + +/* The list of NE2000 devices on this system. */ + +static struct ne_softc ne_softc[NNEDRIVER]; + +/* + * receive ring descriptor + * + * The National Semiconductor DS8390 Network interface controller uses + * the following receive ring headers. The way this works is that the + * memory on the interface card is chopped up into 256 bytes blocks. + * A contiguous portion of those blocks are marked for receive packets + * by setting start and end block #'s in the NIC. For each packet that + * is put into the receive ring, one of these headers (4 bytes each) is + * tacked onto the front. The first byte is a copy of the receiver status + * register at the time the packet was received. + */ +struct ne_ring +{ + unsigned char rsr; /* receiver status */ + unsigned char next; /* pointer to next packet */ + unsigned short count; /* bytes in packet (length + 4) */ +}; + +/* Forward declarations to avoid warnings */ + +static void ne_init_irq_handler (struct ne_softc *sc); +static void ne_stop (struct ne_softc *sc); +static void ne_stop_hardware (struct ne_softc *sc); +static void ne_init (void *arg); +static void ne_init_hardware (struct ne_softc *sc); + +static void ne_reset(struct ne_softc *sc); +#ifdef DEBUG_NE +static void ne_dump(struct ne_softc *sc); +#endif + +/* Read data from an NE2000 device. Read LEN bytes at ADDR, storing + them into P. */ + +static void +ne_read_data (struct ne_softc *sc, int addr, int len, unsigned char *p) +{ + unsigned int port = sc->port; + unsigned int dport = port + DATAPORT; + outport_byte (port + CMDR, MSK_PG0 | MSK_RD2 | MSK_STA); + outport_byte (port + RBCR0, len); + outport_byte (port + RBCR1, len >> 8); + outport_byte (port + RSAR0, addr); + outport_byte (port + RSAR1, addr >> 8); + outport_byte (port + CMDR, MSK_PG0 | MSK_RRE | MSK_STA); + + if (sc->byte_transfers) + { + unsigned char d; + while (len > 0) + { + inport_byte(dport, d); + *p++ = d; + len--; + } + } + else /* word transfers */ + { + unsigned short d; + while (len > 1) + { + inport_word(dport, d); + *p++ = d; + *p++ = d >> 8; + len -= 2; + } + if (len) + { + inport_word(dport, d); + *p++ = d; + } + } + + outport_byte (port + ISR, MSK_RDC); +} + +/* Handle the current NE2000 status. This is called when the device + signals an interrupt. It is also called at other times while + NE2000 interrupts have been disabled. */ + +static void +ne_check_status (struct ne_softc *sc, int from_irq_handler) +{ + struct ifnet *ifp = &sc->arpcom.ac_if; + unsigned int port = sc->port; + unsigned char status; + + /* It seems that we need to use a loop here, because if the NE2000 + signals an interrupt because packet transmission is complete, and + then receives a packet while interrupts are disabled, it seems to + sometimes fail to signal the interrupt for the received packet + when interrupts are reenabled. (Based on the behaviour of the + Realtek 8019AS chip). */ + + /* int count = 0; */ + while (1) + { + inport_byte (port + ISR, status); +#ifdef DEBUG_NE2000 + printk ("NE2000 status 0x%x\n", status); +#endif + if (status == 0) + break; + + /* ack */ + outport_byte (port + ISR, status); + + /* Check for incoming packet overwrite. */ + if (status & MSK_OVW) + { + ifp->if_timer = 0; +#ifdef DEBUG_NE + printk("^\n"); +#endif + ++sc->stats.overruns; + ne_reset(sc); + /* Reenable device interrupts. */ + if (from_irq_handler) + outport_byte(port + IMR, NE_INTERRUPTS); + return; + } + + /* Check for transmitted packet. The transmit daemon may now be + able to send another packet to the device. */ + if ((status & (MSK_PTX | MSK_TXE)) != 0) + { + ifp->if_timer = 0; + ++sc->stats.tx_acks; + --sc->inuse; + sc->transmitting = 0; + if (sc->inuse > 0 || (sc->arpcom.ac_if.if_flags & IFF_OACTIVE) != 0) + rtems_bsdnet_event_send (sc->tx_daemon_tid, START_TRANSMIT_EVENT); + } + + /* Check for received packet. */ + if ((status & (MSK_PRX | MSK_RXE)) != 0) + { + ++sc->stats.rx_acks; + rtems_bsdnet_event_send (sc->rx_daemon_tid, INTERRUPT_EVENT); + } + + /* Check for counter change. */ + if ((status & MSK_CNT) != 0) + { + unsigned char add; + inport_byte (port + CNTR0, add); + sc->stats.rx_frame_errors += add; + inport_byte (port + CNTR1, add); + sc->stats.rx_crc_errors += add; + inport_byte (port + CNTR2, add); + sc->stats.rx_missed_errors += add; + } + + break; + } + + outport_byte (port + CMDR, MSK_PG0 | MSK_STA | MSK_RD2); + +} + +/* Handle an NE2000 interrupt. */ + +static void +ne_interrupt_handler (void *arg) +{ + struct ne_softc *sc = arg; + + if (sc == NULL) + return; + + ++sc->stats.interrupts; + +#ifdef DEBUG_NE + printk("!"); +#endif + ne_check_status(sc, 1); +} + +/* Turn NE2000 interrupts on. */ + +static void +ne_interrupt_on (struct ne_softc *sc) +{ +#ifdef DEBUG_NE + printk ("ne_interrupt_on()\n"); +#endif + if (sc != NULL) + outport_byte (sc->port + IMR, NE_INTERRUPTS); +} + +/* Initialize the NE2000 hardware. */ + +static void +ne_init_hardware (struct ne_softc *sc) +{ + unsigned int port = sc->port; + int i; + +#ifdef DEBUG_NE2000 + printk ("ne_init_hardware()\n"); +#endif + + /* Initialize registers. */ + + /* Set interface for page 0, Remote DMA complete, Stopped */ + outport_byte (port + CMDR, MSK_PG0 | MSK_RD2 | MSK_STP); + + /* Set FIFO threshold to 8, No auto-init Remote DMA, byte order=80x86 */ + /* byte-wide DMA xfers */ + if (sc->byte_transfers) + outport_byte (port + DCR, MSK_FT10 | MSK_BMS); + /* word-wide DMA xfers */ + else + outport_byte (port + DCR, MSK_FT10 | MSK_BMS | MSK_WTS); + + /* Clear Remote Byte Count Registers */ + outport_byte (port + RBCR0, 0); + outport_byte (port + RBCR1, 0); + + /* For the moment, don't store incoming packets in memory. */ + outport_byte (port + RCR, MSK_MON); + + /* Place NIC in internal loopback mode */ + outport_byte (port + TCR, MSK_LOOP); + + /* Initialize transmit/receive (ring-buffer) Page Start */ + outport_byte (port + TPSR, NE_FIRST_TX_PAGE); + outport_byte (port + PSTART, NE_FIRST_RX_PAGE); + + /* Initialize Receiver (ring-buffer) Page Stop and Boundary */ + outport_byte (port + PSTOP, NE_STOP_PAGE); + outport_byte (port + BNRY, NE_STOP_PAGE - 1); + + /* Clear all interrupts */ + outport_byte (port + ISR, 0xff); + /* Disable all interrupts */ + outport_byte (port + IMR, 0); + + /* Program Command Register for page 1 */ + outport_byte (port + CMDR, MSK_PG1 | MSK_RD2 | MSK_STP); + + /* Set the Ethernet hardware address. */ + for (i = 0; i < ETHER_ADDR_LEN; ++i) + outport_byte (port + PAR + i, sc->arpcom.ac_enaddr[i]); + + /* Set Current Page pointer to next_packet */ + outport_byte (port + CURR, NE_FIRST_RX_PAGE); + + /* Clear the multicast address. */ + for (i = 0; i < MARsize; ++i) + outport_byte (port + MAR + i, 0); + + /* Set page 0 registers */ + outport_byte (port + CMDR, MSK_PG0 | MSK_RD2 | MSK_STP); + + /* accept broadcast */ + outport_byte (port + RCR, (sc->accept_broadcasts ? MSK_AB : 0)); + + /* Start interface */ + outport_byte (port + CMDR, MSK_PG0 | MSK_RD2 | MSK_STA); + + /* Take interface out of loopback */ + outport_byte (port + TCR, 0); +} + +/* Set up interrupts. +*/ +static void +ne_init_irq_handler(struct ne_softc *sc) +{ + rtems_status_code status = RTEMS_SUCCESSFUL; + +#ifdef DEBUG_NE + printk("ne_init_irq_handler(%d)\n", sc->irno); +#endif + + status = rtems_interrupt_handler_install( + sc->irno, + "RTL8019", + RTEMS_INTERRUPT_UNIQUE, + ne_interrupt_handler, + sc + ); + assert(status == RTEMS_SUCCESSFUL); + ne_interrupt_on(sc); +} + +/* The NE2000 packet receive daemon. This task is started when the + NE2000 driver is initialized. */ + +#ifdef DEBUG_NE +static int ccc = 0; /* experinent! */ +#endif + +static void +ne_rx_daemon (void *arg) +{ + struct ne_softc *sc = (struct ne_softc *) arg; + struct ifnet *ifp = &sc->arpcom.ac_if; + unsigned int port = sc->port; + + while (1) + { + rtems_event_set events; + + /* Wait for the interrupt handler to tell us that there is a + packet ready to receive. */ + rtems_bsdnet_event_receive (INTERRUPT_EVENT, + RTEMS_WAIT | RTEMS_EVENT_ANY, + RTEMS_NO_TIMEOUT, + &events); + + /* Don't let the device interrupt us now. */ + outport_byte (port + IMR, 0); + + while (1) + { + unsigned char startpage, currpage; + unsigned short len; + unsigned char next, cnt1, cnt2; + struct mbuf *m = NULL; + unsigned char *p; + int startaddr; + int toend; + struct ether_header *eh; + struct ne_ring hdr; /* ring buffer header */ + int reset; + + inport_byte (port + BNRY, startpage); + + outport_byte (port + CMDR, MSK_PG1 | MSK_RD2); + inport_byte (port + CURR, currpage); + outport_byte (port + CMDR, MSK_PG0 | MSK_RD2); + + ++startpage; + if (startpage >= NE_STOP_PAGE) + startpage = NE_FIRST_RX_PAGE; + + if (startpage == currpage) + break; + +#ifdef DEBUG_NE2000 + printk ("ne_rx_daemon: start page %x; current page %x\n", + startpage, currpage); +#endif + + reset = 0; + + /* Read the buffer header */ + startaddr = startpage * NE_PAGE_SIZE; + ne_read_data(sc, startaddr, sizeof(hdr), (unsigned char *)&hdr); + next = hdr.next; + + if (next >= NE_STOP_PAGE) + next = NE_FIRST_RX_PAGE; + + /* check packet length */ + len = hdr.count; + if (currpage < startpage) + cnt1 = currpage + (NE_STOP_PAGE - NE_FIRST_RX_PAGE) - startpage; + else + cnt1 = currpage - startpage; + cnt2 = len / NE_PAGE_SIZE; + if (len % NE_PAGE_SIZE) + cnt2++; + if (cnt1 < cnt2) + { +#ifdef DEBUG_NE + printk("(%x<%x:%x)\n", cnt1, cnt2, len); +/* + printk("start page 0x%x; current page 0x%x\n", + startpage, currpage); + printk("cnt1 < cnt2 (0x%x, 0x%x); len 0x%x\n", + cnt1, cnt2, len); +*/ +#endif + reset = 1; + } + if (len > (ETHER_MAX_LEN - ETHER_CRC_LEN + sizeof(struct ne_ring)) || + len < (ETHER_MIN_LEN - ETHER_CRC_LEN + sizeof(struct ne_ring)) || + len > MCLBYTES) + { +#ifdef DEBUG_NE + printk("(%x)", len); + + printk("start page 0x%x; current page 0x%x\n", + startpage, currpage); + printk("len out of range: 0x%x\n", len); + printk("stat: 0x%x, next: 0x%x\n", hdr.rsr, hdr.next); +#endif + reset = 1; + } +#ifdef DEBUG_NE + if (++ccc == 100) + { ccc = 0; reset = 1; + printk("T"); + } +#endif + + /* reset interface */ + if (reset) + { + printk("Reset in RX\n"); + ne_reset(sc); + goto Next; + } + + /* The first four bytes of the length are the buffer header + * Just decrease them by 2 since in ARM, we have to make sure + * 4bytes memory access align on 4bytes + */ + len -= 2; + startaddr += 2; + + MGETHDR (m, M_WAIT, MT_DATA); + MCLGET (m, M_WAIT); + + if (m == NULL) + panic ("ne_rx_daemon"); + + m->m_pkthdr.rcvif = ifp; + m->m_nextpkt = 0; + + p = mtod (m, unsigned char *); + m->m_len = m->m_pkthdr.len = len - (sizeof(struct ether_header) + 2); + + toend = NE_STOP_PAGE * NE_PAGE_SIZE - startaddr; + if (toend < len) + { + ne_read_data (sc, startaddr, toend, p); + p += toend; + len -= toend; + startaddr = NE_FIRST_RX_PAGE * NE_PAGE_SIZE; + } + + if (len > 0) + ne_read_data (sc, startaddr, len, p); + + m->m_data +=2; + eh = mtod(m, struct ether_header *); + m->m_data += sizeof (struct ether_header); + +#ifdef DEBUG_NE + /*printk("[r%d]", hdr.count - sizeof(hdr));*/ + printk("<\n"); +#endif + ether_input (ifp, eh, m); + ++sc->stats.rx_packets; + outport_byte (port + BNRY, next - 1); + } + + if (sc->overrun) { + outport_byte (port + ISR, MSK_OVW); + outport_byte (port + TCR, 0); + if (sc->resend) + outport_byte (port + CMDR, MSK_PG0 | MSK_TXP | MSK_RD2 | MSK_STA); + sc->resend = 0; + sc->overrun = 0; + } + + Next: + /* Reenable device interrupts. */ + outport_byte (port + IMR, NE_INTERRUPTS); + } +} + +/* Load an NE2000 packet onto the device. */ + +static void +ne_loadpacket (struct ne_softc *sc, struct mbuf *m) +{ + unsigned int port = sc->port; + unsigned int dport = port + DATAPORT; + struct mbuf *mhold = m; + int leftover; + unsigned char leftover_data; + int timeout; + int send_cnt = 0; + +#ifdef DEBUG_NE2000 + printk ("Uploading NE2000 packet\n"); +#endif + + /* Reset remote DMA complete flag. */ + outport_byte (port + ISR, MSK_RDC); + + /* Write out the count. */ + outport_byte (port + RBCR0, m->m_pkthdr.len); + outport_byte (port + RBCR1, m->m_pkthdr.len >> 8); + + sc->sendlen[sc->nextavail] = m->m_pkthdr.len; + + /* Tell the device which address we want to write to. */ + outport_byte (port + RSAR0, 0); + outport_byte (port + RSAR1, + NE_FIRST_TX_PAGE + (sc->nextavail * NE_TX_PAGES)); + + /* Set up the write. */ + outport_byte (port + CMDR, MSK_PG0 | MSK_RWR | MSK_STA); + + /* Transfer the mbuf chain to device memory. NE2000 devices require + that the data be transferred as words, so we need to handle odd + length mbufs. Never occurs if we force byte transfers. */ + + leftover = 0; + leftover_data = '\0'; + + for (; m != NULL; m = m->m_next) { + int len; + unsigned char *data; + + len = m->m_len; + if (len == 0) + continue; + + data = mtod (m, unsigned char *); + + if (leftover) { + unsigned char next; + + /* Data left over from previous mbuf in chain. */ + next = *data++; + --len; + outport_word (dport, leftover_data | (next << 8)); + send_cnt += 2; + leftover = 0; + } + + /* If using byte transfers, len always ends up as zero so + there are no leftovers. */ + + if (sc->byte_transfers) + while (len > 0) { + outport_byte (dport, *data++); + len--; + } + else + while (len > 1) { + outport_word (dport, data[0] | (data[1] << 8)); + data += 2; + len -= 2; + send_cnt += 2; + } + + if (len > 0) + { + leftover = 1; + leftover_data = *data++; + } + } + + if (leftover) + { + outport_word (dport, leftover_data); + send_cnt += 2; + } + +#ifdef DEBUG_NE + /* printk("{l%d|%d}", send_cnt, sc->nextavail); */ + printk("v"); +#endif + m_freem (mhold); + + /* Wait for the device to complete accepting the data, with a + limiting counter so that we don't wait too long. */ + for (timeout = 0; timeout < 1000; ++timeout) + { + unsigned char status; + + inport_byte (port + ISR, status); + +#ifdef DEBUG_NE2000 + if ((status &~ MSK_RDC) != 0) + printk ("Status 0x%x while waiting for acknowledgement of uploaded packet\n", + status); +#endif + + if ((status & MSK_RDC) != 0) { + outport_byte (port + ISR, MSK_RDC); + break; + } + } + + if (timeout >= 1000) + printk ("Timed out waiting for acknowledgement of uploaded NE2000 packet\n"); + + ++sc->nextavail; + if (sc->nextavail == NE_TX_BUFS) + sc->nextavail = 0; +} + +/* Tell the NE2000 to transmit a buffer whose contents we have already + loaded onto the device. */ + +static void +ne_transmit (struct ne_softc *sc) +{ + struct ifnet *ifp = &sc->arpcom.ac_if; + unsigned int port = sc->port; + int len; + +#ifdef DEBUG_NE2000 + printk ("Transmitting NE2000 packet\n"); +#endif + + len = sc->sendlen[sc->nextsend]; + if (len < ET_MINLEN) + len = ET_MINLEN; + outport_byte (port + TBCR0, len); + outport_byte (port + TBCR1, len >> 8); + + outport_byte (port + TPSR, NE_FIRST_TX_PAGE + (sc->nextsend * NE_TX_PAGES)); + + outport_byte (port + CMDR, MSK_PG0 | MSK_TXP | MSK_RD2 | MSK_STA); + +#ifdef DEBUG_NE + /* printk("{s%d|%d}", len, sc->nextsend); */ + printk(">"); +#endif + ++sc->nextsend; + if (sc->nextsend == NE_TX_BUFS) + sc->nextsend = 0; + + ++sc->stats.tx_packets; + + /* set watchdog timer */ + ifp->if_timer = 2; +} + +/* The NE2000 packet transmit daemon. This task is started when the + NE2000 driver is initialized. */ + +static void +ne_tx_daemon (void *arg) +{ + struct ne_softc *sc = (struct ne_softc *) arg; + unsigned int port = sc->port; + struct ifnet *ifp = &sc->arpcom.ac_if; + + while (1) { + rtems_event_set events; + + /* Wait for a packet to be ready for sending, or for there to be + room for another packet in the device memory. */ + rtems_bsdnet_event_receive (START_TRANSMIT_EVENT, + RTEMS_EVENT_ANY | RTEMS_WAIT, + RTEMS_NO_TIMEOUT, + &events); + +#ifdef DEBUG_NE2000 + printk ("ne_tx_daemon\n"); +#endif + + /* This daemon handles both uploading data onto the device and + telling the device to transmit data which has been uploaded. + These are separate tasks, because while the device is + transmitting one buffer we will upload another. */ + + /* Don't let the device interrupt us now. */ + outport_byte (port + IMR, 0); + + while (1) { + struct mbuf *m; + + /* If the device is not transmitting a packet, and we have + uploaded a packet, tell the device to transmit it. */ + if (! sc->transmitting && sc->inuse > 0) { + sc->transmitting = 1; + ne_transmit (sc); + } + + /* If we don't have any more buffers to send, quit now. */ + if (ifp->if_snd.ifq_head == NULL) { + ifp->if_flags &= ~IFF_OACTIVE; + break; + } + + /* Allocate a buffer to load data into. If there are none + available, quit until a buffer has been transmitted. */ + if (sc->inuse >= NE_TX_BUFS) + break; + + ++sc->inuse; + + IF_DEQUEUE (&ifp->if_snd, m); + if (m == NULL) + panic ("ne_tx_daemon"); + + ne_loadpacket (sc, m); + + /* Check the device status. It may have finished transmitting + the last packet. */ + ne_check_status(sc, 0); + } + + /* Reenable device interrupts. */ + outport_byte (port + IMR, NE_INTERRUPTS); + } +} + +/* Start sending an NE2000 packet. */ + +static void +ne_start (struct ifnet *ifp) +{ + struct ne_softc *sc = ifp->if_softc; + +#ifdef DEBUG_NE + printk("S"); +#endif + /* Tell the transmit daemon to wake up and send a packet. */ + rtems_bsdnet_event_send (sc->tx_daemon_tid, START_TRANSMIT_EVENT); + ifp->if_flags |= IFF_OACTIVE; +} + +/* Initialize and start and NE2000. */ + +static void +ne_init (void *arg) +{ + struct ne_softc *sc = (struct ne_softc *) arg; + struct ifnet *ifp = &sc->arpcom.ac_if; + +#ifdef DEBUG_NE + printk("ne_init()\n"); + ne_dump(sc); +#endif + + /* only once... */ + if (sc->tx_daemon_tid == 0) + { + sc->inuse = 0; + sc->nextavail = 0; + sc->nextsend = 0; + sc->transmitting = 0; + + ne_init_hardware (sc); + + sc->tx_daemon_tid = rtems_bsdnet_newproc ("SCtx", 4096, ne_tx_daemon, sc); + sc->rx_daemon_tid = rtems_bsdnet_newproc ("SCrx", 4096, ne_rx_daemon, sc); + + /* install rtems irq handler */ + ne_init_irq_handler(sc); + } + + ifp->if_flags |= IFF_RUNNING; +} + +/* Stop an NE2000. */ + +static void +ne_stop (struct ne_softc *sc) +{ + sc->arpcom.ac_if.if_flags &= ~IFF_RUNNING; + + ne_stop_hardware(sc); + + sc->inuse = 0; + sc->nextavail = 0; + sc->nextsend = 0; + sc->transmitting = 0; + sc->overrun = 0; + sc->resend = 0; +} + +static void +ne_stop_hardware (struct ne_softc *sc) +{ + unsigned int port = sc->port; + int i; + + /* Stop everything. */ + outport_byte (port + CMDR, MSK_STP | MSK_RD2); + + /* Wait for the interface to stop, using I as a time limit. */ + for (i = 0; i < 5000; ++i) + { + unsigned char status; + + inport_byte (port + ISR, status); + if ((status & MSK_RST) != 0) + break; + } +} + +/* reinitializing interface +*/ +static void +ne_reset(struct ne_softc *sc) +{ + ne_stop(sc); + ne_init_hardware(sc); + sc->arpcom.ac_if.if_flags |= IFF_RUNNING; + sc->arpcom.ac_if.if_flags &= ~IFF_OACTIVE; +#ifdef DEBUG_NE + printk("*"); +#endif +} + +#ifdef DEBUG_NE +/* show anything about ne +*/ +static void +ne_dump(struct ne_softc *sc) +{ + int i; + printk("\nne configuration:\n"); + printk("ethernet addr:"); + for (i=0; i<ETHER_ADDR_LEN; i++) + printk(" %x", sc->arpcom.ac_enaddr[i]); + printk("\n"); + printk("irq = %d\n", sc->irno); + printk("port = 0x%x\n", sc->port); + printk("accept_broadcasts = %d\n", sc->accept_broadcasts); + printk("byte_transfers = %d\n", sc->byte_transfers); +} +#endif + +/* Show NE2000 interface statistics. */ + +static void +ne_stats (struct ne_softc *sc) +{ + printf (" Received packets: %-8lu", sc->stats.rx_packets); + printf (" Transmitted packets: %-8lu\n", sc->stats.tx_packets); + printf (" Receive acks: %-8lu", sc->stats.rx_acks); + printf (" Transmit acks: %-8lu\n", sc->stats.tx_acks); + printf (" Packet overruns: %-8lu", sc->stats.overruns); + printf (" Frame errors: %-8lu\n", sc->stats.rx_frame_errors); + printf (" CRC errors: %-8lu", sc->stats.rx_crc_errors); + printf (" Missed packets: %-8lu\n", sc->stats.rx_missed_errors); + printf (" Interrupts: %-8lu\n", sc->stats.interrupts); +} + +/* NE2000 driver ioctl handler. */ + +static int +ne_ioctl (struct ifnet *ifp, ioctl_command_t command, caddr_t data) +{ + struct ne_softc *sc = ifp->if_softc; + int error = 0; + + switch (command) { + case SIOCGIFADDR: + case SIOCSIFADDR: + error = ether_ioctl (ifp, command, data); + break; + + case SIOCSIFFLAGS: + switch (ifp->if_flags & (IFF_UP | IFF_RUNNING)) { + case IFF_RUNNING: + ne_stop (sc); + break; + + case IFF_UP: + printk("IFF_UP\n"); + ne_init (sc); + break; + + case IFF_UP | IFF_RUNNING: + ne_stop (sc); + ne_init (sc); + break; + + default: + break; + } + break; + + case SIO_RTEMS_SHOW_STATS: + ne_stats (sc); + break; + + /* FIXME: Multicast commands must be added here. */ + + default: + error = EINVAL; + break; + } + + return error; +} + +/* + * Device timeout/watchdog routine. Entered if the device neglects to + * generate an interrupt after a transmit has been started on it. + */ +static void +ne_watchdog(struct ifnet *ifp) +{ + struct ne_softc *sc = ifp->if_softc; + + printk("ne2000: device timeout\n"); + ifp->if_oerrors++; + + ne_reset(sc); +} + +static void +print_byte(unsigned char b) +{ + printk("%x%x", b >> 4, b & 0x0f); +} + +/* Attach an NE2000 driver to the system. */ + +int +rtems_ne_driver_attach (struct rtems_bsdnet_ifconfig *config, int attach) +{ + int i; + struct ne_softc *sc; + struct ifnet *ifp; + int mtu; + + /* dettach ... */ + if (!attach) + return 0; + + /* Find a free driver. */ + sc = NULL; + for (i = 0; i < NNEDRIVER; ++i) { + sc = &ne_softc[i]; + ifp = &sc->arpcom.ac_if; + if (ifp->if_softc == NULL) + break; + } + + if (sc == NULL) { + printf ("Too many NE2000 drivers.\n"); + return 0; + } + + memset (sc, 0, sizeof *sc); + + /* Check whether we do byte-wide or word-wide transfers. */ + +#ifdef NE2000_BYTE_TRANSFERS + sc->byte_transfers = true; +#else + sc->byte_transfers = false; +#endif + + /* Handle the options passed in by the caller. */ + + if (config->mtu != 0) + mtu = config->mtu; + else + mtu = ETHERMTU; + + /* We use 16 as the default IRQ. */ + sc->irno = XSCALE_IRQ_NETWORK; + + + + /*IO prts are mapped to 0X40000600 */ + sc->port = 0x40000600; + + sc->accept_broadcasts = ! config->ignore_broadcast; + + if (config->hardware_address != NULL) + memcpy (sc->arpcom.ac_enaddr, config->hardware_address, + ETHER_ADDR_LEN); + else + { + unsigned char prom[16]; + int ia; + + /* Read the PROM to get the Ethernet hardware address. */ + + outport_byte (sc->port + CMDR, MSK_PG0 | MSK_RD2 | MSK_STP); + + if (sc->byte_transfers) { + outport_byte (sc->port + DCR, MSK_FT10 | MSK_BMS); + } + else { + outport_byte (sc->port + DCR, MSK_FT10 | MSK_BMS | MSK_WTS); + } + + outport_byte (sc->port + RBCR0, 0); + outport_byte (sc->port + RBCR1, 0); + outport_byte (sc->port + RCR, MSK_MON); + outport_byte (sc->port + TCR, MSK_LOOP); + outport_byte (sc->port + IMR, 0); + outport_byte (sc->port + ISR, 0xff); + + ne_read_data (sc, 0, sizeof prom, prom); + + outport_byte (sc->port + CMDR, MSK_PG0 | MSK_RD2 | MSK_STP); + + for (ia = 0; ia < ETHER_ADDR_LEN; ++ia) + sc->arpcom.ac_enaddr[ia] = prom[ia * 2]; + } + + /* Set up the network interface. */ + + ifp->if_softc = sc; + ifp->if_unit = i + 1; + ifp->if_name = "ne"; + ifp->if_mtu = mtu; + ifp->if_init = ne_init; + ifp->if_ioctl = ne_ioctl; + ifp->if_watchdog = ne_watchdog; + ifp->if_start = ne_start; + ifp->if_output = ether_output; + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX; + if (ifp->if_snd.ifq_maxlen == 0) + ifp->if_snd.ifq_maxlen = ifqmaxlen; + + /* Attach the interface. */ + + if_attach (ifp); + ether_ifattach (ifp); + + printk("network device '%s' <", config->name); + print_byte(sc->arpcom.ac_enaddr[0]); + for (i=1; i<ETHER_ADDR_LEN; i++) + { printk(":"); + print_byte(sc->arpcom.ac_enaddr[i]); + } + printk("> initialized on port 0x%x, irq %d\n", sc->port, sc->irno); + + return 1; +} diff --git a/bsps/arm/gumstix/net/wd80x3.h b/bsps/arm/gumstix/net/wd80x3.h new file mode 100644 index 0000000000..c4a3aba758 --- /dev/null +++ b/bsps/arm/gumstix/net/wd80x3.h @@ -0,0 +1,303 @@ +/** + * @file + * @ingroup gumstix_dp8390 + * @brief DP8390 Ethernet Controller Support + */ + +/* + * Information about the DP8390 Ethernet controller. + */ + +#ifndef __BSP_WD80x3_h +#define __BSP_WD80x3_h + +/* Register descriptions */ + +/** + * @defgroup gumstix_dp8390 DP8390 Support + * @ingroup arm_gumstix + * @brief DP8390 Ethernet Controller Support + * @{ + */ + +/** + * @name Controller DP8390. + * @{ + */ + +/** @brief Port Window. */ +#define DATAPORT 0x10 +/** @brief Issue a read for reset */ +#define RESET 0x1f +/** @brief I/O port definition */ +#define W83CREG 0x00 +#define ADDROM 0x08 + +/** @} */ + +/** + * @name page 0 read or read/write registers + * @{ + */ + +#define CMDR 0x00+RO +/** @brief current local dma addr 0 for read */ +#define CLDA0 0x01+RO +/** @brief current local dma addr 1 for read */ +#define CLDA1 0x02+RO +/** @brief boundary reg for rd and wr */ +#define BNRY 0x03+RO +/** @brief tx status reg for rd */ +#define TSR 0x04+RO +/** @brief number of collision reg for rd */ +#define NCR 0x05+RO +/** @breif FIFO for rd */ +#define FIFO 0x06+RO +/** @brief interrupt status reg for rd and wr */ +#define ISR 0x07+RO +/** @brief current remote dma address 0 for rd */ +#define CRDA0 0x08+RO +/** @brief current remote dma address 1 for rd */ +#define CRDA1 0x09+RO +/** @brief rx status reg for rd */ +#define RSR 0x0C+RO +/** @brief tally cnt 0 for frm alg err for rd */ +#define CNTR0 0x0D+RO +/** @brief tally cnt 1 for crc err for rd */ +#define CNTR1 RO+0x0E +/** @brief tally cnt 2 for missed pkt for rd */ +#define CNTR2 0x0F+RO + +/** @} */ + +/** + * @name page 0 write registers + * @{ + */ + +/** @brief page start register */ +#define PSTART 0x01+RO +/** @brief page stop register */ +#define PSTOP 0x02+RO +/** @breif tx start page start reg */ +#define TPSR 0x04+RO +/** @brief tx byte count 0 reg */ +#define TBCR0 0x05+RO +/** @brief tx byte count 1 reg */ +#define TBCR1 0x06+RO +/** @brief remote start address reg 0 */ +#define RSAR0 0x08+RO +/** @brief remote start address reg 1 */ +#define RSAR1 0x09+RO +/** @brief remote byte count reg 0 */ +#define RBCR0 0x0A+RO +/** @brief remote byte count reg 1 */ +#define RBCR1 0x0B+RO +/** @brief rx configuration reg */ +#define RCR 0x0C+RO +/** @brief tx configuration reg */ +#define TCR 0x0D+RO +/** @brief data configuration reg */ +#define DCR RO+0x0E +/** @brief interrupt mask reg */ +#define IMR 0x0F+RO + +/** @} */ + +/** + * @name page 1 registers + * @{ + */ + +/** @brief physical addr reg base for rd and wr */ +#define PAR 0x01+RO +/** @brief current page reg for rd and wr */ +#define CURR 0x07+RO +/** @brief multicast addr reg base fro rd and WR */ +#define MAR 0x08+RO +/** @brief size of multicast addr space */ +#define MARsize 8 + +/** @} */ + +/** + * @name W83CREG command bits + * @{ + */ + +/** @brief W83CREG masks */ +#define MSK_RESET 0x80 +#define MSK_ENASH 0x40 +/** @brief memory decode bits, corresponding */ +#define MSK_DECOD 0x3F + +/** @} */ + +/** + * @name CMDR command bits + * @{ + */ + +/** @brief stop the chip */ +#define MSK_STP 0x01 +/** @brief start the chip */ +#define MSK_STA 0x02 +/** @brief initial txing of a frm */ +#define MSK_TXP 0x04 +/** @brief remote read */ +#define MSK_RRE 0x08 +/** @brief remote write */ +#define MSK_RWR 0x10 +/** @brief no DMA used */ +#define MSK_RD2 0x20 +/** @brief select register page 0 */ +#define MSK_PG0 0x00 +/** @brief select register page 1 */ +#define MSK_PG1 0x40 +/** @brief select register page 2 */ +#define MSK_PG2 0x80 + +/** @} */ + +/** + * @name ISR and TSR status bits + * @{ + */ + +/* @brief rx with no error */ +#define MSK_PRX 0x01 +/* @brief tx with no error */ +#define MSK_PTX 0x02 +/* @brief rx with error */ +#define MSK_RXE 0x04 +/* @brief tx with error */ +#define MSK_TXE 0x08 +/* @brief overwrite warning */ +#define MSK_OVW 0x10 +/* @brief MSB of one of the tally counters is set */ +#define MSK_CNT 0x20 +/* @brief remote dma completed */ +#define MSK_RDC 0x40 +/* @brief reset state indicator */ +#define MSK_RST 0x80 + +/** @} */ + +/** + * @name DCR command bits + * @{ + */ + +/** @brief word transfer mode selection */ +#define MSK_WTS 0x01 +/** @brief byte order selection */ +#define MSK_BOS 0x02 +/** @brief long addr selection */ +#define MSK_LAS 0x04 +/** @brief burst mode selection */ +#define MSK_BMS 0x08 +/** @brief autoinitialize remote */ +#define MSK_ARM 0x10 +/** @brief burst lrngth selection */ +#define MSK_FT00 0x00 +/** @brief burst lrngth selection */ +#define MSK_FT01 0x20 +/** @brief burst lrngth selection */ +#define MSK_FT10 0x40 +/** @brief burst lrngth selection */ +#define MSK_FT11 0x60 + +/** @} */ + +/** + * @name RCR command bits + * @{ + */ + +/** @brief save error pkts */ +#define MSK_SEP 0x01 +/** @brief accept runt pkt */ +#define MSK_AR 0x02 +/** @brief 8390 RCR */ +#define MSK_AB 0x04 +/** @brief accept multicast */ +#define MSK_AM 0x08 +/** @brief accept all pkt with physical adr */ +#define MSK_PRO 0x10 +/** @brief monitor mode */ +#define MSK_MON 0x20 + +/** @} */ + +/** + * @name TCR command bits + * @{ + */ + +/** @brief inhibit CRC, do not append crc */ +#define MSK_CRC 0x01 +/** @brief set loopback mode */ +#define MSK_LOOP 0x02 +/** @brief Accept broadcasts */ +#define MSK_BCST 0x04 +/** @brief encoded loopback control */ +#define MSK_LB01 0x06 +/** @brief auto tx disable */ +#define MSK_ATD 0x08 +/** @brief collision offset enable */ +#define MSK_OFST 0x10 + +/** @} */ + +/** + * @name receive status bits + * @{ + */ + +/** @brief rx without error */ +#define SMK_PRX 0x01 +/** @brief CRC error */ +#define SMK_CRC 0x02 +/** @brief frame alignment error */ +#define SMK_FAE 0x04 +/** @brief FIFO overrun */ +#define SMK_FO 0x08 +/** @brief missed pkt */ +#define SMK_MPA 0x10 +/** @brief physical/multicase address */ +#define SMK_PHY 0x20 +/** @brief receiver disable. set in monitor mode */ +#define SMK_DIS 0x40 +/** @brief deferring */ +#define SMK_DEF 0x80 + +/** @} */ + +/** + * @name transmit status bits + * @{ + */ + +/** @brief tx without error */ +#define SMK_PTX 0x01 +/** @brief non deferred tx */ +#define SMK_DFR 0x02 +/** @brief tx collided */ +#define SMK_COL 0x04 +/** @brief tx abort because of excessive collisions */ +#define SMK_ABT 0x08 +/** @brief carrier sense lost */ +#define SMK_CRS 0x10 +/** @brief FIFO underrun */ +#define SMK_FU 0x20 +/** @brief collision detect heartbeat */ +#define SMK_CDH 0x40 +/** @brief out of window collision */ +#define SMK_OWC 0x80 + +/** @} */ + +/** @} */ + +#endif +/* end of include */ diff --git a/bsps/arm/rtl22xx/net/network.c b/bsps/arm/rtl22xx/net/network.c new file mode 100644 index 0000000000..b269a6ddfc --- /dev/null +++ b/bsps/arm/rtl22xx/net/network.c @@ -0,0 +1,126 @@ +/*Note: this file is copy from 7312 BSP, and untested yet*/ + +#define __INSIDE_RTEMS_BSD_TCPIP_STACK__ + +#include <rtems.h> +#include <sys/param.h> +#include <sys/mbuf.h> +#include <bsp/irq.h> +#include <libchip/cs8900.h> +#include <assert.h> + +#define CS8900_BASE 0x20000300 +unsigned int bsp_cs8900_io_base = 0; +unsigned int bsp_cs8900_memory_base = 0; +static void cs8900_isr(void *); + +char g_enetbuf[1520]; + +static void cs8900_isr(void *arg) +{ + cs8900_interrupt(LPC22xx_INTERRUPT_EINT2, arg); +} + +/* cs8900_io_set_reg - set one of the I/O addressed registers */ +void cs8900_io_set_reg (cs8900_device *cs, unsigned short reg, unsigned short data) +{ + /* works the same for all values of dev */ +/* + printf("cs8900_io_set_reg: reg: %#6x, val %#6x\n", + CS8900_BASE + reg, + data); +*/ + *(unsigned short *)(CS8900_BASE + reg) = data; +} + +/* cs8900_io_get_reg - reads one of the I/O addressed registers */ +unsigned short cs8900_io_get_reg (cs8900_device *cs, unsigned short reg) +{ + unsigned short val; + /* works the same for all values of dev */ + val = *(unsigned short *)(CS8900_BASE + reg); +/* + printf("cs8900_io_get_reg: reg: %#6x, val %#6x\n", reg, val); +*/ + return val; +} + +/* cs8900_mem_set_reg - sets one of the registers mapped through + * PacketPage + */ +void cs8900_mem_set_reg (cs8900_device *cs, unsigned long reg, unsigned short data) +{ + /* works the same for all values of dev */ + cs8900_io_set_reg(cs, CS8900_IO_PACKET_PAGE_PTR, reg); + cs8900_io_set_reg(cs, CS8900_IO_PP_DATA_PORT0, data); +} + +/* cs8900_mem_get_reg - reads one of the registers mapped through + * PacketPage + */ +unsigned short cs8900_mem_get_reg (cs8900_device *cs, unsigned long reg) +{ + /* works the same for all values of dev */ + cs8900_io_set_reg(cs, CS8900_IO_PACKET_PAGE_PTR, reg); + return cs8900_io_get_reg(cs, CS8900_IO_PP_DATA_PORT0); +} + +void cs8900_attach_interrupt (cs8900_device *cs) +{ + rtems_status_code status = RTEMS_SUCCESSFUL; + status = rtems_interrupt_handler_install( + LPC22xx_INTERRUPT_EINT2, + "Network", + RTEMS_INTERRUPT_UNIQUE, + cs8900_isr, + cs + ); + assert(status == RTEMS_SUCCESSFUL); +} + +void cs8900_detach_interrupt (cs8900_device *cs) +{ + rtems_status_code status = RTEMS_SUCCESSFUL; + status = rtems_interrupt_handler_remove( + LPC22xx_INTERRUPT_EINT2, + cs8900_isr, + cs + ); + assert(status == RTEMS_SUCCESSFUL); +} + +unsigned short cs8900_get_data_block (cs8900_device *cs, unsigned char *data) +{ + int len; + int i; + + len = cs8900_mem_get_reg(cs, CS8900_PP_RxLength); + + for (i = 0; i < ((len + 1) / 2); i++) { + ((short *)data)[i] = cs8900_io_get_reg(cs, + CS8900_IO_RX_TX_DATA_PORT0); + } + return len; +} + +void cs8900_tx_load (cs8900_device *cs, struct mbuf *m) +{ + int len; + unsigned short *data; + int i; + + len = 0; + + do { + memcpy(&g_enetbuf[len], mtod(m, const void *), m->m_len); + len += m->m_len; + m = m->m_next; + } while (m != 0); + + data = (unsigned short *) &g_enetbuf[0]; + for (i = 0; i < ((len + 1) / 2); i++) { + cs8900_io_set_reg(cs, + CS8900_IO_RX_TX_DATA_PORT0, + data[i]); + } +} diff --git a/bsps/arm/shared/net/lpc-ethernet.c b/bsps/arm/shared/net/lpc-ethernet.c new file mode 100644 index 0000000000..fb8f014963 --- /dev/null +++ b/bsps/arm/shared/net/lpc-ethernet.c @@ -0,0 +1,1839 @@ +/** + * @file + * + * @ingroup lpc_eth + * + * @brief Ethernet driver. + */ + +/* + * Copyright (c) 2009-2012 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Obere Lagerstr. 30 + * 82178 Puchheim + * Germany + * <rtems@embedded-brains.de> + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.org/license/LICENSE. + */ + +#define __INSIDE_RTEMS_BSD_TCPIP_STACK__ + +#include <errno.h> +#include <inttypes.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> + +#include <rtems.h> +#include <rtems/rtems_bsdnet.h> +#include <rtems/rtems_mii_ioctl.h> + +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/mbuf.h> + +#include <net/if.h> +#include <net/if_arp.h> +#include <netinet/in.h> +#include <netinet/if_ether.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> + +#include <bsp.h> +#include <bsp/irq.h> +#include <bsp/lpc-ethernet-config.h> +#include <bsp/utility.h> + +#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 + +#define DEFAULT_PHY 0 +#define WATCHDOG_TIMEOUT 5 + +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 + +/* ETH_MAC2 */ + +#define ETH_MAC2_FULL_DUPLEX BSP_BIT32(8) + +/* ETH_SUPP */ + +#define ETH_SUPP_SPEED BSP_BIT32(8) + +/* ETH_MCFG */ + +#define ETH_MCFG_CLOCK_SELECT(val) BSP_FLD32(val, 2, 4) + +#define ETH_MCFG_RESETMIIMGMT BSP_BIT32(15) + +/* ETH_MCMD */ + +#define ETH_MCMD_READ BSP_BIT32(0) +#define ETH_MCMD_SCAN BSP_BIT32(1) + +/* ETH_MADR */ + +#define ETH_MADR_REG(val) BSP_FLD32(val, 0, 4) +#define ETH_MADR_PHY(val) BSP_FLD32(val, 8, 12) + +/* ETH_MIND */ + +#define ETH_MIND_BUSY BSP_BIT32(0) +#define ETH_MIND_SCANNING BSP_BIT32(1) +#define ETH_MIND_NOT_VALID BSP_BIT32(2) +#define ETH_MIND_MII_LINK_FAIL BSP_BIT32(3) + +/* Events */ + +#define LPC_ETH_EVENT_INITIALIZE RTEMS_EVENT_1 + +#define LPC_ETH_EVENT_TXSTART RTEMS_EVENT_2 + +#define LPC_ETH_EVENT_INTERRUPT RTEMS_EVENT_3 + +#define LPC_ETH_EVENT_STOP RTEMS_EVENT_4 + +/* 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_STATE_NOT_INITIALIZED = 0, + LPC_ETH_STATE_DOWN, + LPC_ETH_STATE_UP +} lpc_eth_state; + +typedef struct { + struct arpcom arpcom; + lpc_eth_state state; + struct rtems_mdio_info mdio; + uint32_t anlpar; + 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_drop_errors; + 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; + uint32_t phy_id; + int phy; + rtems_vector_number interrupt_number; + rtems_id control_task; +} lpc_eth_driver_entry; + +static lpc_eth_driver_entry lpc_eth_driver_data; + +static void lpc_eth_control_request_complete(const lpc_eth_driver_entry *e) +{ + rtems_status_code sc = rtems_event_transient_send(e->control_task); + assert(sc == RTEMS_SUCCESSFUL); +} + +static void lpc_eth_control_request( + lpc_eth_driver_entry *e, + rtems_id task, + rtems_event_set event +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + uint32_t nest_count = 0; + + e->control_task = rtems_task_self(); + + sc = rtems_bsdnet_event_send(task, event); + assert(sc == RTEMS_SUCCESSFUL); + + nest_count = rtems_bsdnet_semaphore_release_recursive(); + sc = rtems_event_transient_receive(RTEMS_WAIT, RTEMS_NO_TIMEOUT); + assert(sc == RTEMS_SUCCESSFUL); + rtems_bsdnet_semaphore_obtain_recursive(nest_count); + + e->control_task = 0; +} + +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_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_MULTICAST_HASH + | 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_bsdnet_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_bsdnet_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; + + LPC_ETH_PRINTF("%s\n", __func__); + + /* Main event loop */ + while (true) { + /* Wait for events */ + sc = rtems_bsdnet_event_receive( + LPC_ETH_EVENT_INITIALIZE + | LPC_ETH_EVENT_STOP + | LPC_ETH_EVENT_INTERRUPT, + RTEMS_EVENT_ANY | RTEMS_WAIT, + RTEMS_NO_TIMEOUT, + &events + ); + assert(sc == RTEMS_SUCCESSFUL); + + LPC_ETH_PRINTF("rx: wake up: 0x%08" PRIx32 "\n", events); + + /* Stop receiver? */ + if ((events & LPC_ETH_EVENT_STOP) != 0) { + lpc_eth_control_request_complete(e); + + /* Wait for events */ + continue; + } + + /* 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; + + /* Enable receiver */ + lpc_eth->command |= ETH_CMD_RX_ENABLE; + + /* Enable receive interrupts */ + lpc_eth_enable_receive_interrupts(); + + lpc_eth_control_request_complete(e); + + /* 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 (consume_index != produce_index) { + uint32_t stat = 0; + + /* Fragment status */ + rtems_cache_invalidate_multiple_data_lines( + (void *) &status [consume_index], + sizeof(status [0]) + ); + stat = status [consume_index].info; + + if ( + (stat & ETH_RX_STAT_LAST_FLAG) != 0 + && (stat & LPC_ETH_RX_STAT_ERRORS) == 0 + ) { + /* Received mbuf */ + struct mbuf *m = mbufs [consume_index]; + + if (lpc_eth_add_new_mbuf(ifp, desc, mbufs, consume_index, false)) { + /* 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", consume_index, sz); + + /* Hand over */ + ether_input(ifp, eh, m); + + /* Increment received frames counter */ + ++e->received_frames; + } else { + ++e->receive_drop_errors; + } + } else { + /* 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 and update consume index */ + consume_index = lpc_eth_increment(consume_index, index_max); + lpc_eth->rxconsumeindex = consume_index; + } else { + /* Nothing to do, enable receive interrupts */ + lpc_eth_enable_receive_interrupts(); + break; + } + } + } +} + +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_STOP + | LPC_ETH_EVENT_TXSTART + | LPC_ETH_EVENT_INTERRUPT, + RTEMS_EVENT_ANY | RTEMS_WAIT, + RTEMS_NO_TIMEOUT, + &events + ); + assert(sc == RTEMS_SUCCESSFUL); + + LPC_ETH_PRINTF("tx: wake up: 0x%08" PRIx32 "\n", events); + + /* Stop transmitter? */ + if ((events & LPC_ETH_EVENT_STOP) != 0) { + lpc_eth_control_request_complete(e); + + /* Wait for events */ + continue; + } + + /* 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; + + lpc_eth_control_request_complete(e); + } + + /* 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(); + } + } +} + +static int lpc_eth_mdio_wait_for_not_busy(void) +{ + rtems_interval one_second = rtems_clock_get_ticks_per_second(); + rtems_interval i = 0; + + while ((lpc_eth->mind & ETH_MIND_BUSY) != 0 && i < one_second) { + rtems_task_wake_after(1); + ++i; + } + + LPC_ETH_PRINTK("tx: lpc_eth_mdio_wait %s after %d\n", + i != one_second? "succeed": "timeout", i); + + return i != one_second ? 0 : ETIMEDOUT; +} + +static uint32_t lpc_eth_mdio_read_anlpar(int phy) +{ + uint32_t madr = ETH_MADR_REG(MII_ANLPAR) | ETH_MADR_PHY(phy); + uint32_t anlpar = 0; + int eno = 0; + + if (lpc_eth->madr != madr) { + lpc_eth->madr = madr; + } + + if (lpc_eth->mcmd != ETH_MCMD_READ) { + lpc_eth->mcmd = 0; + lpc_eth->mcmd = ETH_MCMD_READ; + } + + eno = lpc_eth_mdio_wait_for_not_busy(); + if (eno == 0) { + anlpar = lpc_eth->mrdd; + } + + /* Start next read */ + lpc_eth->mcmd = 0; + lpc_eth->mcmd = ETH_MCMD_READ; + + return anlpar; +} + +static int lpc_eth_mdio_read( + int phy, + void *arg RTEMS_UNUSED, + unsigned reg, + uint32_t *val +) +{ + int eno = 0; + + if (0 <= phy && phy <= 31) { + lpc_eth->madr = ETH_MADR_REG(reg) | ETH_MADR_PHY(phy); + lpc_eth->mcmd = 0; + lpc_eth->mcmd = ETH_MCMD_READ; + eno = lpc_eth_mdio_wait_for_not_busy(); + + if (eno == 0) { + *val = lpc_eth->mrdd; + } + } else { + eno = EINVAL; + } + + return eno; +} + +static int lpc_eth_mdio_write( + int phy, + void *arg RTEMS_UNUSED, + unsigned reg, + uint32_t val +) +{ + int eno = 0; + + if (0 <= phy && phy <= 31) { + lpc_eth->madr = ETH_MADR_REG(reg) | ETH_MADR_PHY(phy); + lpc_eth->mwtd = val; + eno = lpc_eth_mdio_wait_for_not_busy(); + } else { + eno = EINVAL; + } + + return eno; +} + +static int lpc_eth_phy_get_id(int phy, uint32_t *id) +{ + uint32_t id1 = 0; + int eno = lpc_eth_mdio_read(phy, NULL, MII_PHYIDR1, &id1); + + if (eno == 0) { + uint32_t id2 = 0; + + eno = lpc_eth_mdio_read(phy, NULL, MII_PHYIDR2, &id2); + if (eno == 0) { + *id = (id1 << 16) | (id2 & 0xfff0); + } + } + + return eno; +} + +#define PHY_KSZ80X1RNL 0x221550 +#define PHY_DP83848 0x20005c90 + +typedef struct { + unsigned reg; + uint32_t set; + uint32_t clear; +} lpc_eth_phy_action; + +static int lpc_eth_phy_set_and_clear( + lpc_eth_driver_entry *e, + const lpc_eth_phy_action *actions, + size_t n +) +{ + int eno = 0; + size_t i; + + for (i = 0; eno == 0 && i < n; ++i) { + const lpc_eth_phy_action *action = &actions [i]; + uint32_t val; + + eno = lpc_eth_mdio_read(e->phy, NULL, action->reg, &val); + if (eno == 0) { + val |= action->set; + val &= ~action->clear; + eno = lpc_eth_mdio_write(e->phy, NULL, action->reg, val); + } + } + + return eno; +} + +static const lpc_eth_phy_action lpc_eth_phy_up_action_default [] = { + { MII_BMCR, 0, BMCR_PDOWN }, + { MII_BMCR, BMCR_RESET, 0 }, + { MII_BMCR, BMCR_AUTOEN, 0 } +}; + +static const lpc_eth_phy_action lpc_eth_phy_up_pre_action_KSZ80X1RNL [] = { + /* Disable slow oscillator mode */ + { 0x11, 0, 0x10 } +}; + +static const lpc_eth_phy_action lpc_eth_phy_up_post_action_KSZ80X1RNL [] = { + /* Enable energy detect power down (EDPD) mode */ + { 0x18, 0x0800, 0 }, + /* Turn PLL of automatically in EDPD mode */ + { 0x10, 0x10, 0 } +}; + +static int lpc_eth_phy_up(lpc_eth_driver_entry *e) +{ + int eno; + int retries = 64; + uint32_t val; + + e->phy = DEFAULT_PHY - 1; + while (true) { + e->phy = (e->phy + 1) % 32; + + --retries; + eno = lpc_eth_phy_get_id(e->phy, &e->phy_id); + if ( + (eno == 0 && e->phy_id != 0xfffffff0 && e->phy_id != 0) + || retries <= 0 + ) { + break; + } + + rtems_task_wake_after(1); + } + + LPC_ETH_PRINTF("lpc_eth_phy_get_id: 0x%08" PRIx32 " from phy %d retries %d\n", + e->phy_id, e->phy, retries); + + if (eno == 0) { + switch (e->phy_id) { + case PHY_KSZ80X1RNL: + eno = lpc_eth_phy_set_and_clear( + e, + &lpc_eth_phy_up_pre_action_KSZ80X1RNL [0], + RTEMS_ARRAY_SIZE(lpc_eth_phy_up_pre_action_KSZ80X1RNL) + ); + break; + case PHY_DP83848: + eno = lpc_eth_mdio_read(e->phy, NULL, 0x17, &val); + LPC_ETH_PRINTF("phy PHY_DP83848 RBR 0x%08" PRIx32 "\n", val); + /* val = 0x21; */ + val = 0x32 ; + eno = lpc_eth_mdio_write(e->phy, NULL, 0x17, val); + break; + case 0: + case 0xfffffff0: + eno = EIO; + e->phy = DEFAULT_PHY; + break; + default: + break; + } + + if (eno == 0) { + eno = lpc_eth_phy_set_and_clear( + e, + &lpc_eth_phy_up_action_default [0], + RTEMS_ARRAY_SIZE(lpc_eth_phy_up_action_default) + ); + } + + if (eno == 0) { + switch (e->phy_id) { + case PHY_KSZ80X1RNL: + eno = lpc_eth_phy_set_and_clear( + e, + &lpc_eth_phy_up_post_action_KSZ80X1RNL [0], + RTEMS_ARRAY_SIZE(lpc_eth_phy_up_post_action_KSZ80X1RNL) + ); + break; + default: + break; + } + } + } else { + e->phy_id = 0; + } + + return eno; +} + +static const lpc_eth_phy_action lpc_eth_phy_down_action_default [] = { + { MII_BMCR, BMCR_PDOWN, 0 } +}; + +static const lpc_eth_phy_action lpc_eth_phy_down_post_action_KSZ80X1RNL [] = { + /* Enable slow oscillator mode */ + { 0x11, 0x10, 0 } +}; + +static void lpc_eth_phy_down(lpc_eth_driver_entry *e) +{ + int eno = lpc_eth_phy_set_and_clear( + e, + &lpc_eth_phy_down_action_default [0], + RTEMS_ARRAY_SIZE(lpc_eth_phy_down_action_default) + ); + + if (eno == 0) { + switch (e->phy_id) { + case PHY_KSZ80X1RNL: + eno = lpc_eth_phy_set_and_clear( + e, + &lpc_eth_phy_down_post_action_KSZ80X1RNL [0], + RTEMS_ARRAY_SIZE(lpc_eth_phy_down_post_action_KSZ80X1RNL) + ); + break; + default: + break; + } + } +} + +static void lpc_eth_soft_reset(void) +{ + lpc_eth->command = 0x38; + lpc_eth->mac1 = 0xcf00; + lpc_eth->mac1 = 0x0; +} + +static int lpc_eth_up_or_down(lpc_eth_driver_entry *e, bool up) +{ + int eno = 0; + rtems_status_code sc = RTEMS_SUCCESSFUL; + struct ifnet *ifp = &e->arpcom.ac_if; + + if (up && e->state == LPC_ETH_STATE_DOWN) { + + lpc_eth_config_module_enable(); + + /* Enable RX/TX reset and disable soft reset */ + lpc_eth->mac1 = 0xf00; + + /* Initialize PHY */ + /* Clock value 10 (divide by 44 ) is safe on LPC178x up to 100 MHz AHB clock */ + lpc_eth->mcfg = ETH_MCFG_CLOCK_SELECT(10) | ETH_MCFG_RESETMIIMGMT; + rtems_task_wake_after(1); + lpc_eth->mcfg = ETH_MCFG_CLOCK_SELECT(10); + rtems_task_wake_after(1); + eno = lpc_eth_phy_up(e); + + if (eno == 0) { + /* + * We must have a valid external clock from the PHY at this point, + * otherwise the system bus hangs and only a watchdog reset helps. + */ + lpc_eth_soft_reset(); + + /* 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 = ETH_SUPP_SPEED; + lpc_eth->test = 0; + #ifdef LPC_ETH_CONFIG_RMII + lpc_eth->command = 0x0600; + #else + lpc_eth->command = 0x0400; + #endif + lpc_eth->intenable = ETH_INT_RX_OVERRUN | ETH_INT_TX_UNDERRUN; + 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; + + /* Initialize tasks */ + lpc_eth_control_request(e, e->receive_task, LPC_ETH_EVENT_INITIALIZE); + lpc_eth_control_request(e, e->transmit_task, LPC_ETH_EVENT_INITIALIZE); + + /* Install interrupt handler */ + sc = rtems_interrupt_handler_install( + e->interrupt_number, + "Ethernet", + RTEMS_INTERRUPT_UNIQUE, + lpc_eth_interrupt_handler, + e + ); + assert(sc == RTEMS_SUCCESSFUL); + + /* Start watchdog timer */ + ifp->if_timer = 1; + + /* Change state */ + e->state = LPC_ETH_STATE_UP; + } + + if (eno != 0) { + ifp->if_flags &= ~IFF_UP; + } + } else if (!up && e->state == LPC_ETH_STATE_UP) { + /* Remove interrupt handler */ + sc = rtems_interrupt_handler_remove( + e->interrupt_number, + lpc_eth_interrupt_handler, + e + ); + assert(sc == RTEMS_SUCCESSFUL); + + /* Stop tasks */ + lpc_eth_control_request(e, e->receive_task, LPC_ETH_EVENT_STOP); + lpc_eth_control_request(e, e->transmit_task, LPC_ETH_EVENT_STOP); + + lpc_eth_soft_reset(); + lpc_eth_phy_down(e); + lpc_eth_config_module_disable(); + + /* Stop watchdog timer */ + ifp->if_timer = 0; + + /* Change state */ + e->state = LPC_ETH_STATE_DOWN; + } + + return eno; +} + +static void lpc_eth_interface_init(void *arg) +{ + /* Nothing to do */ +} + +static void lpc_eth_interface_stats(lpc_eth_driver_entry *e) +{ + int eno = EIO; + int media = 0; + + if (e->state == LPC_ETH_STATE_UP) { + media = IFM_MAKEWORD(0, 0, 0, 0); + eno = rtems_mii_ioctl(&e->mdio, e, SIOCGIFMEDIA, &media); + } + + rtems_bsdnet_semaphore_release(); + + if (eno == 0) { + rtems_ifmedia2str(media, NULL, 0); + printf("\n"); + } + + 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 drop errors: %u\n", e->receive_drop_errors); + 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_multicast_control( + bool add, + struct ifreq *ifr, + struct arpcom *ac +) +{ + int eno = 0; + + if (add) { + eno = ether_addmulti(ifr, ac); + } else { + eno = ether_delmulti(ifr, ac); + } + + if (eno == ENETRESET) { + struct ether_multistep step; + struct ether_multi *enm; + + eno = 0; + + lpc_eth->hashfilterl = 0; + lpc_eth->hashfilterh = 0; + + ETHER_FIRST_MULTI(step, ac, enm); + while (enm != NULL) { + uint64_t addrlo = 0; + uint64_t addrhi = 0; + + memcpy(&addrlo, enm->enm_addrlo, ETHER_ADDR_LEN); + memcpy(&addrhi, enm->enm_addrhi, ETHER_ADDR_LEN); + while (addrlo <= addrhi) { + /* XXX: ether_crc32_le() does not work, why? */ + uint32_t crc = ether_crc32_be((uint8_t *) &addrlo, ETHER_ADDR_LEN); + uint32_t index = (crc >> 23) & 0x3f; + + if (index < 32) { + lpc_eth->hashfilterl |= 1U << index; + } else { + lpc_eth->hashfilterh |= 1U << (index - 32); + } + ++addrlo; + } + ETHER_NEXT_MULTI(step, enm); + } + } + + return eno; +} + +static int lpc_eth_interface_ioctl( + struct ifnet *ifp, + ioctl_command_t cmd, + caddr_t data +) +{ + lpc_eth_driver_entry *e = (lpc_eth_driver_entry *) ifp->if_softc; + struct ifreq *ifr = (struct ifreq *) data; + int eno = 0; + + LPC_ETH_PRINTF("%s\n", __func__); + + switch (cmd) { + case SIOCGIFMEDIA: + case SIOCSIFMEDIA: + rtems_mii_ioctl(&e->mdio, e, cmd, &ifr->ifr_media); + break; + case SIOCGIFADDR: + case SIOCSIFADDR: + ether_ioctl(ifp, cmd, data); + break; + case SIOCSIFFLAGS: + eno = lpc_eth_up_or_down(e, (ifp->if_flags & IFF_UP) != 0); + if (eno == 0 && (ifp->if_flags & IFF_UP) != 0) { + lpc_eth_enable_promiscous_mode((ifp->if_flags & IFF_PROMISC) != 0); + } + break; + case SIOCADDMULTI: + case SIOCDELMULTI: + eno = lpc_eth_multicast_control(cmd == SIOCADDMULTI, ifr, &e->arpcom); + break; + case SIO_RTEMS_SHOW_STATS: + lpc_eth_interface_stats(e); + break; + default: + eno = EINVAL; + break; + } + + return eno; +} + +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; + + if (e->state == LPC_ETH_STATE_UP) { + sc = rtems_bsdnet_event_send(e->transmit_task, LPC_ETH_EVENT_TXSTART); + assert(sc == RTEMS_SUCCESSFUL); + } +} + +static void lpc_eth_interface_watchdog(struct ifnet *ifp) +{ + lpc_eth_driver_entry *e = (lpc_eth_driver_entry *) ifp->if_softc; + + if (e->state == LPC_ETH_STATE_UP) { + uint32_t anlpar = lpc_eth_mdio_read_anlpar(e->phy); + + if (e->anlpar != anlpar) { + bool full_duplex = false; + bool speed = false; + + e->anlpar = anlpar; + + if ((anlpar & ANLPAR_TX_FD) != 0) { + full_duplex = true; + speed = true; + } else if ((anlpar & ANLPAR_T4) != 0) { + speed = true; + } else if ((anlpar & ANLPAR_TX) != 0) { + speed = true; + } else if ((anlpar & ANLPAR_10_FD) != 0) { + full_duplex = true; + } + + if (full_duplex) { + lpc_eth->mac2 |= ETH_MAC2_FULL_DUPLEX; + } else { + lpc_eth->mac2 &= ~ETH_MAC2_FULL_DUPLEX; + } + + if (speed) { + lpc_eth->supp |= ETH_SUPP_SPEED; + } else { + lpc_eth->supp &= ~ETH_SUPP_SPEED; + } + } + + ifp->if_timer = WATCHDOG_TIMEOUT; + } +} + +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) +{ + 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) { + return 0; + } + if (unit_index != 0) { + goto cleanup; + } + if (config->hardware_address == NULL) { + goto cleanup; + } + if (e->state != LPC_ETH_STATE_NOT_INITIALIZED) { + goto cleanup; + } + + /* MDIO */ + e->mdio.mdio_r = lpc_eth_mdio_read; + e->mdio.mdio_w = lpc_eth_mdio_write; + e->mdio.has_gmii = 0; + e->anlpar = 0; + + /* 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; + + /* Remember interrupt number */ + e->interrupt_number = config->irno; + + /* 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) { + goto cleanup; + } + 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 = IFF_MULTICAST | IFF_BROADCAST | IFF_SIMPLEX; + ifp->if_snd.ifq_maxlen = ifqmaxlen; + ifp->if_timer = 0; + + /* Create tasks */ + e->receive_task = rtems_bsdnet_newproc( + "ntrx", + 4096, + lpc_eth_receive_task, + e + ); + e->transmit_task = rtems_bsdnet_newproc( + "nttx", + 4096, + lpc_eth_transmit_task, + e + ); + + /* Change status */ + ifp->if_flags |= IFF_RUNNING; + e->state = LPC_ETH_STATE_DOWN; + + /* 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 RTEMS_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); + } +} |