diff packages/io/eth/current/src/net/eth_drv.c @ 115:6ed91473a1cd ecos-sw-2000-08-21

Merge from eCos master repository on 2000-08-21-22:40:54-BST
author jlarmour
date Fri, 25 Aug 2000 17:32:38 +0000
parents
children bd1a71e3e476
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/packages/io/eth/current/src/net/eth_drv.c
@@ -0,0 +1,500 @@
+//==========================================================================
+//
+//      ecos/eth_drv.c
+//
+//      Hardware independent ethernet driver
+//
+//==========================================================================
+//####COPYRIGHTBEGIN####
+//                                                                          
+// -------------------------------------------                              
+// The contents of this file are subject to the Red Hat eCos Public License 
+// Version 1.1 (the "License"); you may not use this file except in         
+// compliance with the License.  You may obtain a copy of the License at    
+// http://www.redhat.com/                                                   
+//                                                                          
+// Software distributed under the License is distributed on an "AS IS"      
+// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the 
+// License for the specific language governing rights and limitations under 
+// the License.                                                             
+//                                                                          
+// The Original Code is eCos - Embedded Configurable Operating System,      
+// released September 30, 1998.                                             
+//                                                                          
+// The Initial Developer of the Original Code is Red Hat.                   
+// Portions created by Red Hat are                                          
+// Copyright (C) 1998, 1999, 2000 Red Hat, Inc.                             
+// All Rights Reserved.                                                     
+// -------------------------------------------                              
+//                                                                          
+//####COPYRIGHTEND####
+//####BSDCOPYRIGHTBEGIN####
+//
+// -------------------------------------------
+//
+// Portions of this software may have been derived from OpenBSD or other sources,
+// and are covered by the appropriate copyright disclaimers included herein.
+//
+// -------------------------------------------
+//
+//####BSDCOPYRIGHTEND####
+//==========================================================================
+//#####DESCRIPTIONBEGIN####
+//
+// Author(s):    gthomas
+// Contributors: gthomas
+// Date:         2000-01-10
+// Purpose:      Hardware independent ethernet driver
+// Description:  
+//              
+//
+//####DESCRIPTIONEND####
+//
+//==========================================================================
+
+// High-level ethernet driver
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <sys/ioctl.h>
+#include <sys/mbuf.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/netisr.h>
+
+#ifdef INET
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/in_var.h>
+#include <netinet/ip.h>
+#include <netinet/if_ether.h>
+#endif
+
+#if NBPFILTER > 0
+#include <net/bpf.h>
+#include <net/bpfdesc.h>
+#endif
+
+#include <cyg/infra/cyg_ass.h>
+#include <cyg/hal/drv_api.h>
+#include <pkgconf/hal.h>
+#include <cyg/hal/hal_if.h>
+#include <eth_drv.h>
+
+static int  eth_drv_ioctl(struct ifnet *, u_long, caddr_t);
+static void eth_drv_send(struct ifnet *);
+
+extern int net_debug;  // FIXME
+
+// Interfaces exported to drivers
+
+static void eth_drv_init(struct eth_drv_sc *sc, unsigned char *enaddr);
+static void eth_drv_recv(struct eth_drv_sc *sc, int total_len);
+static void eth_drv_tx_done(struct eth_drv_sc *sc, unsigned long key, int status);
+
+struct eth_drv_funs eth_drv_funs = {eth_drv_init, eth_drv_recv, eth_drv_tx_done};
+
+//
+// This function is called during system initialization to register a
+// network interface with the system.
+//
+static void
+eth_drv_init(struct eth_drv_sc *sc, unsigned char *enaddr)
+{
+    struct ifnet *ifp = &sc->sc_arpcom.ac_if;
+
+    // Set up hardware address
+    bcopy(enaddr, &sc->sc_arpcom.ac_enaddr, ETHER_ADDR_LEN);
+
+    // Initialize ifnet structure
+    bcopy((void *)sc->dev_name, ifp->if_xname, IFNAMSIZ);
+    ifp->if_softc = sc;
+    ifp->if_start = eth_drv_send;
+    ifp->if_ioctl = eth_drv_ioctl;
+    ifp->if_flags =
+        IFF_BROADCAST | IFF_SIMPLEX | IFF_NOTRAILERS | IFF_MULTICAST;
+    sc->state = 0;
+
+    // Attach the interface
+    if_attach(ifp);
+    ether_ifattach(ifp);
+
+#ifdef CYGSEM_HAL_VIRTUAL_VECTOR_DIAG
+// Set up interfaces so debug environment can share this device
+    {
+        void *dbg = CYGACC_CALL_IF_DBG_DATA();
+        if (!dbg) {
+            CYGACC_CALL_IF_DBG_DATA_SET((void *)sc);
+        }
+    }
+#endif
+}
+
+//
+// This [internal] function will be called to stop activity on an interface.
+//
+static void
+eth_drv_stop(struct eth_drv_sc *sc)
+{
+    (sc->funs->stop)(sc);
+    sc->state &= ~ETH_DRV_STATE_ACTIVE;
+}
+
+//
+// This [internal] function will be called to start activity on an interface.
+//
+static void
+eth_drv_start(struct eth_drv_sc *sc)
+{
+    struct ifnet *ifp = &sc->sc_arpcom.ac_if;
+    // Perform any hardware initialization
+    (sc->funs->start)(sc, (unsigned char *)&sc->sc_arpcom.ac_enaddr, 0);
+    // Set 'running' flag, and clear output active flag.
+    ifp->if_flags |= IFF_RUNNING;
+    ifp->if_flags &= ~IFF_OACTIVE;
+    sc->state |= ETH_DRV_STATE_ACTIVE;
+    eth_drv_send(ifp);  // Try and start up transmit
+}
+
+//
+// This function supports "I/O control" operations on an interface.
+//
+static int  
+eth_drv_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
+{
+    struct eth_drv_sc *sc = ifp->if_softc;
+    struct ifaddr *ifa = (struct ifaddr *) data;
+    struct ifreq *ifr = (struct ifreq *)data;
+    int     s, error = 0;
+
+    s = splnet();
+
+    if ((error = ether_ioctl(ifp, &sc->sc_arpcom, cmd, data)) > 0) {
+        splx(s);
+        return error;
+    }
+
+    switch (cmd) {
+
+    case SIOCSIFADDR:
+        ifp->if_flags |= IFF_UP;
+
+        switch (ifa->ifa_addr->sa_family) {
+#ifdef INET
+        case AF_INET:
+            eth_drv_start(sc);
+            arp_ifinit(&sc->sc_arpcom, ifa);
+            break;
+#endif
+        default:
+            eth_drv_start(sc);
+            break;
+        }
+        break;
+
+    case SIOCGIFHWADDR:
+        // Get hardware (MAC) address
+        ifr->ifr_hwaddr.sa_family = AF_INET;
+        bcopy(&sc->sc_arpcom.ac_enaddr, &ifr->ifr_hwaddr.sa_data, ETHER_ADDR_LEN);
+        break;
+
+    case SIOCSIFHWADDR:
+        // Set hardware (MAC) address
+        bcopy(&ifr->ifr_hwaddr.sa_data, &sc->sc_arpcom.ac_enaddr, ETHER_ADDR_LEN);
+        if ((sc->funs->control)(sc, ETH_DRV_SET_MAC_ADDRESS,
+                                &sc->sc_arpcom.ac_enaddr, ETHER_ADDR_LEN)) {
+            error = EINVAL;
+        }
+        break;
+
+    case SIOCSIFFLAGS:
+        if ((ifp->if_flags & IFF_UP) == 0 &&
+            (ifp->if_flags & IFF_RUNNING) != 0) {
+            /*
+             * If interface is marked down and it is running, then
+             * stop it.
+             */
+            eth_drv_stop(sc);
+            ifp->if_flags &= ~IFF_RUNNING;
+        } else
+            if ((ifp->if_flags & IFF_UP) != 0 &&
+                (ifp->if_flags & IFF_RUNNING) == 0) {
+                /*
+                 * If interface is marked up and it is stopped, then
+                 * start it.
+                 */
+                eth_drv_start(sc);
+            } else {
+                /*
+                 * Reset the interface to pick up changes in any other
+                 * flags that affect hardware registers.
+                 */
+                eth_drv_stop(sc);
+                eth_drv_start(sc);
+            }
+        break;
+
+#if 0
+    case SIOCADDMULTI:
+    case SIOCDELMULTI:
+        /* Update our multicast list. */
+        error = (cmd == SIOCADDMULTI) ?
+            ether_addmulti(ifr, &sc->sc_arpcom) :
+                ether_delmulti(ifr, &sc->sc_arpcom);
+
+        if (error == ENETRESET) {
+            /*
+             * Multicast list has changed; set the hardware filter
+             * accordingly.
+             */
+            eth_drv_stop(sc);	/* XXX for ds_setmcaf? */
+            eth_drv_start(sc);
+            error = 0;
+        }
+        break;
+#endif
+
+    default:
+        error = EINVAL;
+        break;
+    }
+
+    splx(s);
+    return (error);
+}
+
+//
+// This routine is called to start transmitting if there is data
+// available.
+//
+static void 
+eth_drv_send(struct ifnet *ifp)
+{
+    struct eth_drv_sc *sc = ifp->if_softc;
+    struct eth_drv_sg sg_list[MAX_ETH_DRV_SG];
+    int sg_len;
+    struct mbuf *m0, *m;
+    int len, total_len;
+    unsigned char *data;
+
+    cyg_scheduler_lock();  // Prevent DSRs from running
+    if ((ifp->if_flags & IFF_RUNNING) != IFF_RUNNING) {
+        cyg_scheduler_unlock();
+        return;
+    }
+
+    while ((sc->funs->can_send)(sc) > 0) {
+        IF_DEQUEUE(&ifp->if_snd, m0);
+        if (m0 == 0) {
+            break;
+        }
+
+        if (net_debug)
+            diag_printf("Sending %d bytes\n", m0->m_pkthdr.len);
+
+        /* We need to use m->m_pkthdr.len, so require the header */
+        if ((m0->m_flags & M_PKTHDR) == 0)
+            panic("eth_drv_send: no header mbuf");
+
+#if NBPFILTER > 0
+        /* Tap off here if there is a BPF listener. */
+        if (ifp->if_bpf)
+            bpf_mtap(ifp->if_bpf, m0);
+#endif
+
+        // Extract data pointers (don't actually move data here)
+        sg_len = 0;  total_len = 0;
+        for (m = m0; m ; m = m->m_next) {
+            data = mtod(m, u_char *);
+            len = m->m_len;
+            total_len += len;
+            sg_list[sg_len].buf = (CYG_ADDRESS)data;
+            sg_list[sg_len].len = len;
+            if ( len )
+                sg_len++;
+            if (net_debug) {
+                diag_printf("xmit %d bytes at %x sg[%d]\n", len, data, sg_len);
+                if ( 1 & net_debug )
+                    diag_dump_buf(data, len);
+            }
+            if ( MAX_ETH_DRV_SG < sg_len ) {
+                diag_printf("too many mbufs to tx, %d > %d\n", sg_len, MAX_ETH_DRV_SG );
+                sg_len = 0;
+                break; // drop it on the floor
+            }
+        }
+
+        // Tell hardware to send this packet
+        if ( sg_len )
+            (sc->funs->send)(sc, sg_list, sg_len, total_len, (unsigned long)m0);
+    }
+
+    cyg_scheduler_unlock();  // Allow DSRs to run
+}
+
+//
+// This function is called from the hardware driver when an output operation
+// has completed - i.e. the packet has been sent.
+//
+static struct mbuf *mbuf_key;
+
+static void
+eth_drv_tx_done(struct eth_drv_sc *sc, unsigned long key, int status)
+{
+    struct ifnet *ifp = &sc->sc_arpcom.ac_if;
+    struct mbuf *m0 = (struct mbuf *)key;
+    // Check for errors here (via 'status')
+    // Done with packet
+    mbuf_key = m0;
+    m_freem(m0);
+    // Start another if possible
+    eth_drv_send(ifp);
+}
+
+//
+// This function is called from a hardware driver to indicate that an input
+// packet has arrived.  The routine will set up appropriate network resources
+// (mbuf's) to hold the data and call back into the driver to retrieve the data.
+//
+static void
+eth_drv_recv(struct eth_drv_sc *sc, int total_len)
+{
+    struct ifnet *ifp = &sc->sc_arpcom.ac_if;
+    struct ether_header _eh, *eh=&_eh;
+    struct mbuf *top, **mp, *m;
+    int i, mlen;
+    unsigned char *data;
+    struct eth_drv_sg sg_list[MAX_ETH_DRV_SG];
+    int sg_len;
+
+    if ((ifp->if_flags & IFF_RUNNING) != IFF_RUNNING) {
+        return;  // Interface not up, ignore this request
+    }
+
+    CYG_ASSERT( 0 != total_len, "total_len is zero!" );
+    CYG_ASSERT( 0 <= total_len, "total_len is negative!" );
+    CYG_ASSERT( sizeof( struct ether_header ) <= total_len,
+                "No ether header here!" );
+
+    if ( total_len < sizeof( struct ether_header ) )
+        // Our arithmetic below would go wrong
+        return;
+
+    /* Pull packet off interface. */
+    MGETHDR(m, M_DONTWAIT, MT_DATA);
+    if (m == 0) {
+        diag_printf("warning: eth_recv out of MBUFs\n");
+    }
+
+    // Set up buffers
+    // Unload ethernet header separately so IP/UDP/TCP headers are aligned
+    sg_list[0].buf = (CYG_ADDRESS)eh;
+    sg_list[0].len = sizeof(*eh);
+    sg_len = 1;
+
+    // Compute total length (minus ethernet header)
+    total_len -= sizeof(*eh);
+
+    top = 0;
+    mlen = MHLEN;
+    mp = &top;
+
+    if (m) {
+        m->m_pkthdr.rcvif = ifp;
+        m->m_pkthdr.len = total_len;
+    } else {
+        sg_list[sg_len].buf = (CYG_ADDRESS)0;
+        sg_list[sg_len].len = min(total_len, MCLBYTES);
+        sg_len++;
+        total_len = 0;
+    }
+
+    while (total_len > 0) {
+        if (top) {
+            MGET(m, M_DONTWAIT, MT_DATA);
+            if (m == 0) {
+                m_freem(top);
+                panic("out of MBUFs [2]");
+            }
+            mlen = MLEN;
+        }
+        if (total_len >= MINCLSIZE) {
+            MCLGET(m, M_DONTWAIT);
+            if ((m->m_flags & M_EXT) == 0) {
+                m_freem(top);
+                diag_printf("warning: eth_recv out of MBUFs\n");
+                sg_list[sg_len].buf = (CYG_ADDRESS)0;
+                sg_list[sg_len].len = min(total_len, MCLBYTES);
+                sg_len++;
+                top = 0;
+                break;
+            }
+            mlen = MCLBYTES;
+        }
+        m->m_len = mlen = min(total_len, mlen);
+        total_len -= mlen;
+        data = mtod(m, caddr_t);
+        sg_list[sg_len].buf = (CYG_ADDRESS)data;
+        sg_list[sg_len].len = mlen;
+        sg_len++;
+        *mp = m;
+        mp = &m->m_next;
+    }
+
+    // Ask hardware to unload buffers
+    (sc->funs->recv)(sc, sg_list, sg_len);
+
+    if (net_debug) {
+        for (i = 0;  i < sg_len;  i++) {
+            if (sg_list[i].buf) {
+                diag_printf("rx %d bytes at %x sg[%d]\n", sg_list[i].len, sg_list[i].buf, i);
+                if ( 1 & net_debug )
+                    diag_dump_buf((void *)sg_list[i].buf, sg_list[i].len);
+            }
+        }
+    }
+
+    m = top;
+    if (m == 0) {
+        ifp->if_ierrors++;
+        return;
+    }
+
+    ifp->if_ipackets++;
+
+
+#if NBPFILTER > 0
+#error FIXME - Need mbuf with ethernet header attached
+    /*
+     * Check if there's a BPF listener on this interface.
+     * If so, hand off the raw packet to bpf.
+     */
+    if (ifp->if_bpf)
+        bpf_mtap(ifp->if_bpf, m);
+#endif
+
+    // Push data into protocol stacks
+    ether_input(ifp, eh, m);
+}
+
+#ifdef CYGPKG_IO_PCMCIA
+// Lookup a 'netdev' entry, assuming that it is an ethernet device.
+cyg_netdevtab_entry_t * 
+eth_drv_netdev(char *name)
+{
+    cyg_netdevtab_entry_t *t;
+    struct eth_drv_sc *sc;
+    for (t = &__NETDEVTAB__[0]; t != &__NETDEVTAB_END__; t++) {
+        sc = (struct eth_drv_sc *)t->device_instance;
+        if (strcmp(sc->dev_name, name) == 0) {
+            return t;
+        }
+    }
+    return (cyg_netdevtab_entry_t *)NULL;
+}
+#endif // CYGPKG_IO_PCMCIA
+
+// EOF eth_drv.c