NetBSD-Bugs archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

kern/44624: Improved DM9000 Ethernet Driver



>Number:         44624
>Category:       kern
>Synopsis:       Improved DM9000 Ethernet Driver
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    kern-bug-people
>State:          open
>Class:          change-request
>Submitter-Id:   net
>Arrival-Date:   Tue Feb 22 19:45:00 +0000 2011
>Originator:     Paul Fleischer
>Release:        
>Organization:
>Environment:
>Description:
The current DM9000 ethernet driver is somewhat rough. The attached patch 
improves it in the following areas:

- Proper multicast support
- Carrier detection, and proper media handling
- Restructure code to make it easier to implement 8 and 32-bit transfers to 
DM9000 chip.
- Various cleanups
>How-To-Repeat:

>Fix:
--- dm9000var.h-1.1     2011-02-22 20:36:08.000000000 +0100
+++ dm9000var.h 2011-02-19 19:38:23.000000000 +0100
@@ -63,7 +63,7 @@
 #ifndef _DEV_IC_DM9000VAR_H_
 #define _DEV_IC_DM9000VAR_H_
 
-#include <sys/mutex.h>
+#include <sys/callout.h>
 
 #define DM9000_MODE_8BIT 2
 #define DM9000_MODE_16BIT 0
@@ -75,6 +75,9 @@
        struct ethercom sc_ethercom;    /* Ethernet common data */
        struct ifmedia  sc_media;       /* Media control structures */
 
+       uint            sc_media_active;
+       uint            sc_media_status;
+
        bus_space_tag_t         sc_iot;
        bus_space_handle_t      sc_ioh;
        void            *sc_ih;
@@ -85,7 +88,7 @@
        uint16_t        sc_vendor_id;
        uint16_t        sc_product_id;
 
-       uint16_t        io_mode;
+       uint8_t         sc_data_width;
 
        uint8_t         sc_enaddr[ETHER_ADDR_LEN];
 
@@ -94,6 +97,13 @@
                                           for transmission. */
        uint16_t        txready_length;
 
+       int (*sc_pkt_write)(struct dme_softc*, struct mbuf *);
+       int (*sc_pkt_read)(struct dme_softc*, struct ifnet *, struct mbuf **);
+
+       callout_t       sc_link_callout;
+
+       bool            sc_phy_initialized;
+
 #ifdef DIAGNOSTIC
        bool            sc_inside_interrupt;
 #endif
@@ -104,6 +114,9 @@
 int    dme_detach(struct dme_softc *);
 int    dme_intr(void *);
 
+/* Helper method used by sc_pkt_read */
+struct mbuf* dme_alloc_receive_buffer(struct ifnet *, unsigned int);
+
 /* Inline memory access methods */
 static inline uint8_t
 dme_read(struct dme_softc *sc, int reg)
--- dm9000reg.h-1.1     2011-02-22 20:35:56.000000000 +0100
+++ dm9000reg.h 2011-02-19 19:38:23.000000000 +0100
@@ -195,6 +195,10 @@
 #define DM9000_PHY_PHYID1      0x02
 #define DM9000_PHY_PHYID2      0x03
 #define DM9000_PHY_ANAR        0x04
+#define  DM9000_PHY_ANAR_10_HDX         (1<<5)
+#define  DM9000_PHY_ANAR_10_FDX  (1<<6)
+#define  DM9000_PHY_ANAR_TX_HDX  (1<<7)
+#define  DM9000_PHY_ANAR_TX_FDX  (1<<8)
 #define DM9000_PHY_ANLPAR      0x05
 #define DM9000_PHY_ANER        0x06
 #define DM9000_PHY_DSCR        0x16
--- dm9000.c-1.2        2011-02-22 20:35:41.000000000 +0100
+++ dm9000.c    2011-02-19 19:38:23.000000000 +0100
@@ -89,6 +89,7 @@
 #include <sys/cdefs.h>
 
 #include <sys/param.h>
+#include <sys/kernel.h>
 #include <sys/systm.h>
 #include <sys/mbuf.h>
 #include <sys/syslog.h>
@@ -117,7 +118,7 @@
 
 #if 1
 #undef DM9000_DEBUG
-#undef  DM9000_TX_DEBUG
+#undef DM9000_TX_DEBUG
 #undef DM9000_TX_DATA_DEBUG
 #undef DM9000_RX_DEBUG
 #undef  DM9000_RX_DATA_DEBUG
@@ -159,9 +160,13 @@
 #define TX_DATA_DPRINTF(s) do {} while (/*CONSTCOND*/0)
 #endif
 
-
+/*** Internal PHY functions ***/
 uint16_t dme_phy_read(struct dme_softc *sc, int reg);
-void dme_phy_write(struct dme_softc *sc, int reg, uint16_t value);
+void   dme_phy_write(struct dme_softc *sc, int reg, uint16_t value);
+void   dme_phy_init(struct dme_softc *sc);
+void   dme_phy_reset(struct dme_softc *sc);
+void   dme_phy_update_media(struct dme_softc *sc);
+void   dme_phy_check_link(void *arg);
 
 /*** Methods registered in struct ifnet ***/
 void   dme_start_output(struct ifnet *ifp);
@@ -186,6 +191,17 @@
 /* Software Initialize/Reset of the DM9000 */
 void    dme_reset(struct dme_softc *sc);
 
+/* Configure multicast filter */
+void   dme_set_addr_filter(struct dme_softc *sc);
+
+/* Set media */
+int    dme_set_media(struct dme_softc *sc, int media);
+
+/* Read/write packet data from/to DM9000 IC in various transfer sizes */
+int    dme_pkt_read_2(struct dme_softc *sc, struct ifnet *ifp, struct mbuf 
**outBuf);
+int    dme_pkt_write_2(struct dme_softc *sc, struct mbuf *bufChain);
+/* TODO: Implement 8 and 32 bit read/write functions */
+
 uint16_t
 dme_phy_read(struct dme_softc *sc, int reg)
 {
@@ -199,9 +215,6 @@
        /* Wait until access to PHY has completed */
        while (dme_read(sc, DM9000_EPCR) & DM9000_EPCR_ERRE);
 
-       /* XXX: The delay is probably not necessary as we just busy-waited */
-       delay(200);
-
        /* Reset ERPRR-bit */
        dme_write(sc, DM9000_EPCR, DM9000_EPCR_EPOS_PHY);
 
@@ -228,19 +241,163 @@
        /* Wait until access to PHY has completed */
        while(dme_read(sc, DM9000_EPCR) & DM9000_EPCR_ERRE);
 
-
-       /* XXX: The delay is probably not necessary as we just busy-waited */
-       delay(200);
-
        /* Reset ERPRR-bit */
        dme_write(sc, DM9000_EPCR, DM9000_EPCR_EPOS_PHY);
 }
 
+void
+dme_phy_init(struct dme_softc *sc)
+{
+       u_int ifm_media = sc->sc_media.ifm_media;
+       uint32_t bmcr, anar;
+
+       bmcr = dme_phy_read(sc, DM9000_PHY_BMCR);
+       anar = dme_phy_read(sc, DM9000_PHY_ANAR);
+
+       anar = anar & ~DM9000_PHY_ANAR_10_HDX
+               & ~DM9000_PHY_ANAR_10_FDX
+               & ~DM9000_PHY_ANAR_TX_HDX
+               & ~DM9000_PHY_ANAR_TX_FDX;
+
+       switch (IFM_SUBTYPE(ifm_media)) {
+       case IFM_AUTO:
+               bmcr |= DM9000_PHY_BMCR_AUTO_NEG_EN;
+               anar |= DM9000_PHY_ANAR_10_HDX |
+                       DM9000_PHY_ANAR_10_FDX |
+                       DM9000_PHY_ANAR_TX_HDX |
+                       DM9000_PHY_ANAR_TX_FDX;
+               break;
+       case IFM_10_T:
+               //bmcr &= ~DM9000_PHY_BMCR_AUTO_NEG_EN;
+               bmcr &= ~DM9000_PHY_BMCR_SPEED_SELECT;
+               if (ifm_media & IFM_FDX)
+                       anar |= DM9000_PHY_ANAR_10_FDX;
+               else
+                       anar |= DM9000_PHY_ANAR_10_HDX;
+               break;
+       case IFM_100_TX:
+               //bmcr &= ~DM9000_PHY_BMCR_AUTO_NEG_EN;
+               bmcr |= DM9000_PHY_BMCR_SPEED_SELECT;
+               if (ifm_media & IFM_FDX)
+                       anar |= DM9000_PHY_ANAR_TX_FDX;
+               else
+                       anar |= DM9000_PHY_ANAR_TX_HDX;
+
+               break;
+       }
+
+       if(ifm_media & IFM_FDX) {
+               bmcr |= DM9000_PHY_BMCR_DUPLEX_MODE;
+       } else {
+               bmcr &= ~DM9000_PHY_BMCR_DUPLEX_MODE;
+       }
+
+       dme_phy_write(sc, DM9000_PHY_BMCR, bmcr);
+       dme_phy_write(sc, DM9000_PHY_ANAR, anar);
+}
+
+void
+dme_phy_reset(struct dme_softc *sc)
+{
+       uint32_t reg;
+
+       /* PHY Reset */
+       dme_phy_write(sc, DM9000_PHY_BMCR, DM9000_PHY_BMCR_RESET);
+
+       reg = dme_read(sc, DM9000_GPCR);
+       dme_write(sc, DM9000_GPCR, reg & ~DM9000_GPCR_GPIO0_OUT);
+       reg = dme_read(sc, DM9000_GPR);
+       dme_write(sc, DM9000_GPR, reg | DM9000_GPR_PHY_PWROFF);
+
+       dme_phy_init(sc);
+
+       reg = dme_read(sc, DM9000_GPR);
+       dme_write(sc, DM9000_GPR, reg & ~DM9000_GPR_PHY_PWROFF);
+       reg = dme_read(sc, DM9000_GPCR);
+       dme_write(sc, DM9000_GPCR, reg | DM9000_GPCR_GPIO0_OUT);
+
+       dme_phy_update_media(sc);
+}
+
+void
+dme_phy_update_media(struct dme_softc *sc)
+{
+       u_int ifm_media = sc->sc_media.ifm_media;
+       uint32_t reg;
+
+       if (IFM_SUBTYPE(ifm_media) == IFM_AUTO) {
+               /* If auto-negotiation is used, ensures that it is completed
+                before trying to extract any media information. */
+               reg = dme_phy_read(sc, DM9000_PHY_BMSR);
+               if ((reg & DM9000_PHY_BMSR_AUTO_NEG_AB) == 0) {
+                       /* Auto-negotation not possible, therefore there is no
+                          reason to try obtain any media information. */
+                       return;
+               }
+
+               /* Then loop until the negotiation is completed. */
+               while ((reg & DM9000_PHY_BMSR_AUTO_NEG_COM) == 0) {
+                       /* TODO: Bail out after a finite number of attempts
+                        in case something goes wrong. */
+                       preempt();
+                       reg = dme_phy_read(sc, DM9000_PHY_BMSR);
+               }
+       }
+
+
+       sc->sc_media_active = IFM_ETHER;
+       reg = dme_phy_read(sc, DM9000_PHY_BMCR);
+
+       if (reg & DM9000_PHY_BMCR_SPEED_SELECT) {
+               sc->sc_media_active |= IFM_100_TX;
+       } else {
+               sc->sc_media_active |= IFM_10_T;
+       }
+
+       if (reg & DM9000_PHY_BMCR_DUPLEX_MODE) {
+               sc->sc_media_active |= IFM_FDX;
+       }
+}
+
+void
+dme_phy_check_link(void *arg)
+{
+       struct dme_softc *sc = arg;
+       uint32_t reg;
+
+       reg = dme_read(sc, DM9000_NSR) & DM9000_NSR_LINKST;
+
+       if( reg )
+               reg = IFM_ETHER | IFM_AVALID | IFM_ACTIVE;
+       else {
+               reg = IFM_ETHER | IFM_AVALID;
+               sc->sc_media_active = IFM_NONE;
+       }
+
+       if ( (sc->sc_media_status != reg) && (reg & IFM_ACTIVE)) {
+               dme_phy_reset(sc);
+       }
+
+       sc->sc_media_status = reg;
+
+       callout_schedule(&sc->sc_link_callout, mstohz(2000));
+}
+
+int
+dme_set_media(struct dme_softc *sc, int media)
+{
+       sc->sc_media.ifm_media = media;
+       dme_phy_reset(sc);
+
+       return 0;
+}
+
 int
 dme_attach(struct dme_softc *sc, uint8_t *enaddr)
 {
-       struct ifnet *ifp = &sc->sc_ethercom.ec_if;
-       uint8_t b[2];
+       struct ifnet    *ifp = &sc->sc_ethercom.ec_if;
+       uint8_t         b[2];
+       uint16_t        io_mode;
 
        dme_read_c(sc, DM9000_VID0, b, 2);
 #if BYTE_ORDER == BIG_ENDIAN
@@ -260,16 +417,6 @@
                    sc->sc_product_id);
        }
 
-#if 0
-       {
-               /* Force 10Mbps to test dme_phy_write */
-               uint16_t bmcr;
-               bmcr = dme_phy_read(sc, DM9000_PHY_BMCR);
-               bmcr &= ~DM9000_PHY_BMCR_AUTO_NEG_EN;
-               bmcr &= ~DM9000_PHY_BMCR_SPEED_SELECT; /* select 100Mbps */
-               dme_phy_write(sc, DM9000_PHY_BMCR, bmcr);
-       }
-#endif
        /* Initialize ifnet structure. */
        strlcpy(ifp->if_xname, device_xname(sc->sc_dev), IFNAMSIZ);
        ifp->if_softc = sc;
@@ -278,17 +425,28 @@
        ifp->if_ioctl = dme_ioctl;
        ifp->if_stop = dme_stop;
        ifp->if_watchdog = NULL;        /* no watchdog at this stage */
-       ifp->if_flags = IFF_SIMPLEX | IFF_NOTRAILERS |
-               IFF_BROADCAST; /* No multicast support for now */
+       ifp->if_flags = IFF_SIMPLEX | IFF_NOTRAILERS | IFF_BROADCAST |
+                       IFF_MULTICAST;
        IFQ_SET_READY(&ifp->if_snd);
 
        /* Initialize ifmedia structures. */
        ifmedia_init(&sc->sc_media, 0, dme_mediachange, dme_mediastatus);
-       ifmedia_add(&sc->sc_media, IFM_ETHER|IFM_10_T|IFM_100_TX, 0, NULL);
-       ifmedia_set(&sc->sc_media, IFM_ETHER|IFM_10_T|IFM_100_TX);
+       ifmedia_add(&sc->sc_media, IFM_ETHER|IFM_AUTO, 0, NULL);
+       ifmedia_add(&sc->sc_media, IFM_ETHER|IFM_10_T|IFM_FDX, 0, NULL);
+       ifmedia_add(&sc->sc_media, IFM_ETHER|IFM_10_T, 0, NULL);
+       ifmedia_add(&sc->sc_media, IFM_ETHER|IFM_100_TX|IFM_FDX, 0, NULL);
+       ifmedia_add(&sc->sc_media, IFM_ETHER|IFM_100_TX, 0, NULL);
+
+       ifmedia_set(&sc->sc_media, IFM_ETHER|IFM_AUTO);
 
        if (enaddr != NULL)
                memcpy(sc->sc_enaddr, enaddr, sizeof(sc->sc_enaddr));
+       /* TODO: Support an EEPROM attached to the DM9000 chip */
+
+       callout_init(&sc->sc_link_callout, 0);
+       callout_setfunc(&sc->sc_link_callout, dme_phy_check_link, sc);
+
+       sc->sc_media_status = 0;
 
        /* Configure DM9000 with the MAC address */
        dme_write_c(sc, DM9000_PAB0, sc->sc_enaddr, 6);
@@ -325,28 +483,34 @@
        }
 #endif
 
-       sc->io_mode = (dme_read(sc, DM9000_ISR) &
+       io_mode = (dme_read(sc, DM9000_ISR) &
            DM9000_IOMODE_MASK) >> DM9000_IOMODE_SHIFT;
-       if (sc->io_mode != DM9000_MODE_16BIT )
+       if (io_mode != DM9000_MODE_16BIT )
                panic("DM9000: Only 16-bit mode is supported!\n");
-#ifdef DM9000_DEBUG
-       printf("DM9000 Operation Mode: ");
-       switch( sc->io_mode) {
+
+       DPRINTF(("DM9000 Operation Mode: "));
+       switch( io_mode) {
        case DM9000_MODE_16BIT:
-               printf("16-bit mode");
+               DPRINTF(("16-bit mode"));
+               sc->sc_data_width = 2;
+               sc->sc_pkt_write = dme_pkt_write_2;
+               sc->sc_pkt_read = dme_pkt_read_2;
                break;
        case DM9000_MODE_32BIT:
-               printf("32-bit mode");
+               DPRINTF(("32-bit mode"));
+               sc->sc_data_width = 4;
                break;
        case DM9000_MODE_8BIT:
-               printf("8-bit mode");
+               DPRINTF(("8-bit mode"));
+               sc->sc_data_width = 1;
                break;
-       case 3:
-               printf("Invalid mode");
+       default:
+               DPRINTF(("Invalid mode"));
                break;
        }
-       printf("\n");
-#endif
+       DPRINTF(("\n"));
+
+       callout_schedule(&sc->sc_link_callout, mstohz(2000));
 
        return 0;
 }
@@ -357,6 +521,9 @@
        struct ifnet *ifp = &sc->sc_ethercom.ec_if;
        uint8_t status;
 
+
+       DPRINTF(("dme_intr: Begin\n"));
+
        /* Disable interrupts */
        dme_write(sc, DM9000_IMR, DM9000_IMR_PAR );
 
@@ -412,6 +579,8 @@
        dme_write(sc, DM9000_IMR, DM9000_IMR_PAR | DM9000_IMR_PRM |
                 DM9000_IMR_PTM);
 
+       DPRINTF(("dme_intr: End\n"));
+
        return 1;
 }
 
@@ -422,6 +591,8 @@
 
        sc = ifp->if_softc;
 
+       DPRINTF(("dme_start_output: Begin\n"));
+
        if ((ifp->if_flags & (IFF_RUNNING | IFF_OACTIVE)) != IFF_RUNNING) {
                printf("No output\n");
                return;
@@ -444,15 +615,15 @@
                 */
                ifp->if_flags |= IFF_OACTIVE;
        }
+
+       DPRINTF(("dme_start_output: End\n"));
 }
 
 void
 dme_prepare(struct dme_softc *sc, struct ifnet *ifp)
 {
-       struct mbuf *buf;
        struct mbuf *bufChain;
        uint16_t length;
-       uint8_t *write_ptr;
 
        TX_DPRINTF(("dme_prepare: Entering\n"));
 
@@ -471,106 +642,16 @@
        if (ifp->if_bpf)
                bpf_mtap(ifp, bufChain);
 
-
-       length = 0;
-
-       /* XXX: This support 16-bit I/O mode only. */
-       /* XXX: This code must be factored out, such that architecture
-          dependant versions can be supplied */
-
-       int left_over_count = 0; /* Number of bytes from previous mbuf, which
-                                   need to be written with the next.*/
-       uint16_t left_over_buf = 0;
-
        /* Setup the DM9000 to accept the writes, and then write each buf in
           the chain. */
 
        TX_DATA_DPRINTF(("dme_prepare: Writing data: "));
        bus_space_write_1(sc->sc_iot, sc->sc_ioh, sc->dme_io, DM9000_MWCMD);
-       for (buf = bufChain; buf != NULL; buf = buf->m_next) {
-               int to_write = buf->m_len;
-
-               length += to_write;
-
-               write_ptr = buf->m_data;
-               while (to_write > 0 ||
-                      (buf->m_next == NULL && left_over_count > 0)
-                      ) {
-                       if (left_over_count > 0) {
-                               uint8_t b = 0;
-                               DPRINTF(("dme_prepare: "
-                                       "Writing left over byte\n"));
-
-                               if (to_write > 0) {
-                                       b = *write_ptr;
-                                       to_write--;
-                                       write_ptr++;
-
-                                       DPRINTF(("Took single byte\n"));
-                               } else {
-                                       DPRINTF(("Leftover in last run\n"));
-                                       length++;
-                               }
-
-                               /* Does shift direction depend on endianess? */
-                               left_over_buf = left_over_buf | (b << 8);
-
-                               bus_space_write_2(sc->sc_iot, sc->sc_ioh,
-                                                 sc->dme_data, left_over_buf);
-                               TX_DATA_DPRINTF(("%02X ", left_over_buf));
-                               left_over_count = 0;
-                       } else if ((long)write_ptr % 2 != 0) {
-                               /* Misaligned data */
-                               DPRINTF(("dme_prepare: "
-                                       "Detected misaligned data\n"));
-                               left_over_buf = *write_ptr;
-                               left_over_count = 1;
-                               write_ptr++;
-                               to_write--;
-                       } else {
-                               int i;
-                               uint16_t *dptr = (uint16_t*)write_ptr;
-
-                               /* A block of aligned data. */
-                               for(i = 0; i < to_write/2; i++) {
-                                       /* buf will be half-word aligned
-                                        * all the time
-                                        */
-                                       bus_space_write_2(sc->sc_iot,
-                                           sc->sc_ioh, sc->dme_data, *dptr);
-                                       TX_DATA_DPRINTF(("%02X %02X ",
-                                           *dptr & 0xFF, (*dptr>>8) & 0xFF));
-                                       dptr++;
-                               }
-
-                               write_ptr += i*2;
-                               if (to_write % 2 != 0) {
-                                       DPRINTF(("dme_prepare: "
-                                               "to_write %% 2: %d\n",
-                                               to_write % 2));
-                                       left_over_count = 1;
-                                       /* XXX: Does this depend on
-                                        * the endianess?
-                                        */
-                                       left_over_buf = *write_ptr;
-                                       
-                                       write_ptr++;
-                                       to_write--;
-                                       DPRINTF(("dme_prepare: "
-                                               "to_write (after): %d\n",
-                                               to_write));
-                                       DPRINTF(("dme_prepare: i*2: %d\n",
-                                               i*2));
-                               }
-                               to_write -= i*2;
-                       }
-               } /* while(...) */
-       } /* for(...) */
-
+       length = sc->sc_pkt_write(sc, bufChain);
        TX_DATA_DPRINTF(("\n"));
 
-       if (length % 2 == 1) {
-               panic("dme_prepare: length is not a word-length");
+       if (length % sc->sc_data_width != 0) {
+               panic("dme_prepare: length is not compatible with IO_MODE");
        }
 
        sc->txready_length = length;
@@ -622,6 +703,14 @@
                break;
        default:
                error = ether_ioctl(ifp, cmd, data);
+               if (error == ENETRESET) {
+                       if (ifp->if_flags && IFF_RUNNING) {
+                               /* Address list has changed, reconfigure
+                                  filter */
+                               dme_set_addr_filter(sc);
+                       }
+                       error = 0;
+               }
                break;
        }
 
@@ -647,22 +736,18 @@
 int
 dme_mediachange(struct ifnet *ifp)
 {
-       /* TODO: Make this function do something useful. */
-       return 0;
+       struct dme_softc *sc = ifp->if_softc;
+
+       return dme_set_media(sc, sc->sc_media.ifm_cur->ifm_media);
 }
 
 void
 dme_mediastatus(struct ifnet *ifp, struct ifmediareq *ifmr)
 {
-       /* TODO: Make this function do something useful. */
        struct dme_softc *sc = ifp->if_softc;
-       ifmr->ifm_active = sc->sc_media.ifm_cur->ifm_media;
 
-       if (ifp->if_flags & IFF_UP) {
-               ifmr->ifm_status = IFM_AVALID | IFM_ACTIVE;
-       } else {
-               ifmr->ifm_status = 0;
-       }
+       ifmr->ifm_active = sc->sc_media_active;
+       ifmr->ifm_status = sc->sc_media_status;
 }
 
 void
@@ -704,69 +789,14 @@
                ready = bus_space_read_1(sc->sc_iot, sc->sc_ioh, sc->dme_data);
                ready &= 0x03;  /* we only want bits 1:0 */
                if (ready == 0x01) {
-                       uint8_t rx_status;
-
-                       uint16_t data;
-                       uint16_t frame_length;
-                       uint16_t i;
-                       struct mbuf *m;
-                       uint16_t *buf;
-                       int pad;
-
-                       /* TODO: Add support for 8-bit and
-                        *  32-bit transfer modes.
-                        */
+                       uint8_t         rx_status;
+                       struct mbuf     *m;
 
                        /* Read with address increment. */
                        bus_space_write_1(sc->sc_iot, sc->sc_ioh,
-                           sc->dme_io, DM9000_MRCMD);
-                       data = bus_space_read_2(sc->sc_iot, sc->sc_ioh,
-                           sc->dme_data);
-
-                       rx_status = data & 0xFF;
-                       frame_length = bus_space_read_2(sc->sc_iot,
-                           sc->sc_ioh, sc->dme_data);
-
-                       RX_DPRINTF(("dme_receive: "
-                               "rx_statux: 0x%x, frame_length: %d\n",
-                               rx_status, frame_length));
-
-
-                       MGETHDR(m, M_DONTWAIT, MT_DATA);
-                       m->m_pkthdr.rcvif = ifp;
-                       /* Ensure that we always allocate an even number of
-                        * bytes in order to avoid writing beyond the buffer
-                        */
-                       m->m_pkthdr.len = frame_length + (frame_length % 2);
-                       pad = ALIGN(sizeof(struct ether_header)) -
-                           sizeof(struct ether_header);
-                       /* All our frames have the CRC attached */
-                       m->m_flags |= M_HASFCS;
-                       if (m->m_pkthdr.len + pad > MHLEN )
-                               MCLGET(m, M_DONTWAIT);
-
-                       m->m_data += pad;
-                       m->m_len = frame_length + (frame_length % 2);
-                       buf = mtod(m, uint16_t*);
-
-                       RX_DPRINTF(("dme_receive: "));
-
-                       for(i=0; i< frame_length; i+=2 ) {
-                               data = bus_space_read_2(sc->sc_iot,
-                                   sc->sc_ioh, sc->dme_data);
-                               if ( (frame_length % 2 != 0) &&
-                                   (i == frame_length-1) ) {
-                                       data = data & 0xff;
-                                       RX_DPRINTF((" L "));
-                               }
-                               *buf = data;
-                               buf++;
-                               RX_DATA_DPRINTF(("%02X %02X ", data & 0xff,
-                                           (data>>8) & 0xff));
-                       }
+                                         sc->dme_io, DM9000_MRCMD);
 
-                       RX_DATA_DPRINTF(("\n"));
-                       RX_DPRINTF(("Read %d bytes\n", i));
+                       rx_status = sc->sc_pkt_read(sc, ifp, &m);
 
                        if (rx_status & (DM9000_RSR_CE | DM9000_RSR_PLE)) {
                                /* Error while receiving the packet,
@@ -797,28 +827,46 @@
 {
        uint8_t var;
 
-       /* Enable PHY */
-       var = dme_read(sc, DM9000_GPCR);
-       dme_write(sc, DM9000_GPCR, var | DM9000_GPCR_GPIO0_OUT);
-       var = dme_read(sc, DM9000_GPR);
-       dme_write(sc, DM9000_GPR, var & ~DM9000_GPR_PHY_PWROFF);
+       /* We only re-initialized the PHY in this function the first time it is
+          called. */
+       if( !sc->sc_phy_initialized) {
+               /* PHY Reset */
+               dme_phy_write(sc, DM9000_PHY_BMCR, DM9000_PHY_BMCR_RESET);
+
+               /* PHY Power Down */
+               var = dme_read(sc, DM9000_GPR);
+               dme_write(sc, DM9000_GPR, var | DM9000_GPR_PHY_PWROFF);
+       }
 
-       /* Reset the DM9000 twice, as describe din section 5.2 of the
-        * Application Notes
+       /* Reset the DM9000 twice, as described in section 2 of the Programming
+          Guide.
+          The PHY is initialized and enabled between those two resets.
         */
+
+       /* Software Reset*/
        dme_write(sc, DM9000_NCR,
            DM9000_NCR_RST | DM9000_NCR_LBK_MAC_INTERNAL);
-       
-       delay(20);
-       dme_write(sc, DM9000_NCR, 0x0);
-       dme_write(sc, DM9000_NCR,
-           DM9000_NCR_RST | DM9000_NCR_LBK_MAC_INTERNAL);
-       
        delay(20);
        dme_write(sc, DM9000_NCR, 0x0);
 
+       if( !sc->sc_phy_initialized) {
+               /* PHY Initialization */
+               dme_phy_init(sc);
+
+               /* PHY Enable */
+               var = dme_read(sc, DM9000_GPR);
+               dme_write(sc, DM9000_GPR, var & ~DM9000_GPR_PHY_PWROFF);
+               var = dme_read(sc, DM9000_GPCR);
+               dme_write(sc, DM9000_GPCR, var | DM9000_GPCR_GPIO0_OUT);
+
+               dme_write(sc, DM9000_NCR,
+                         DM9000_NCR_RST | DM9000_NCR_LBK_MAC_INTERNAL);
+               delay(20);
+               dme_write(sc, DM9000_NCR, 0x0);
+       }
+
        /* Select internal PHY, no wakeup event, no collosion mode,
-        * normal loopback mode, and no full duplex mode
+        * normal loopback mode.
         */
        dme_write(sc, DM9000_NCR, DM9000_NCR_LBK_NORMAL );
 
@@ -831,9 +879,243 @@
        dme_write(sc, DM9000_IMR,
            DM9000_IMR_PAR | DM9000_IMR_PRM | DM9000_IMR_PTM);
 
-       /* Enable RX without watchdog */
-       dme_write(sc, DM9000_RCR, DM9000_RCR_RXEN | DM9000_RCR_WTDIS);
+       /* Setup multicast address filter, and enable RX. */
+       dme_set_addr_filter(sc);
+
+       /* Obtain media information from PHY */
+       dme_phy_update_media(sc);
 
        sc->txbusy = 0;
        sc->txready = 0;
+       sc->sc_phy_initialized = 1;
+}
+
+void
+dme_set_addr_filter(struct dme_softc *sc)
+{
+       struct ether_multi      *enm;
+       struct ether_multistep  step;
+       struct ethercom         *ec;
+       struct ifnet            *ifp;
+       uint16_t                af[4];
+       int                     i;
+
+       ec = &sc->sc_ethercom;
+       ifp = &ec->ec_if;
+
+       if (ifp->if_flags & IFF_PROMISC) {
+               dme_write(sc, DM9000_RCR, DM9000_RCR_RXEN  |
+                                         DM9000_RCR_WTDIS |
+                                         DM9000_RCR_PRMSC);
+               ifp->if_flags |= IFF_ALLMULTI;
+               return;
+       }
+
+       af[0] = af[1] = af[2] = af[3] = 0x0000;
+       ifp->if_flags &= ~IFF_ALLMULTI;
+
+       ETHER_FIRST_MULTI(step, ec, enm);
+       while (enm != NULL) {
+               uint16_t hash;
+               if (memcpy(enm->enm_addrlo, enm->enm_addrhi,
+                   sizeof(enm->enm_addrlo))) {
+                       /*
+                        * We must listen to a range of multicast addresses.
+                        * For now, just accept all multicasts, rather than
+                        * trying to set only those filter bits needed to match
+                        * the range.  (At this time, the only use of address
+                        * ranges is for IP multicast routing, for which the
+                        * range is big enough to require all bits set.)
+                        */
+                       ifp->if_flags |= IFF_ALLMULTI;
+                       af[0] = af[1] = af[2] = af[3] = 0xffff;
+                       break;
+               } else {
+                       hash = ether_crc32_le(enm->enm_addrlo, ETHER_ADDR_LEN) 
& 0x3F;
+                       af[(uint16_t)(hash>>4)] |= (uint16_t)(1 << (hash % 16));
+                       ETHER_NEXT_MULTI(step, enm);
+               }
+       }
+
+       /* Write the multicast address filter */
+       for(i=0; i<4; i++) {
+               dme_write(sc, DM9000_MAB0+i*2, af[i] & 0xFF);
+               dme_write(sc, DM9000_MAB0+i*2+1, (af[i] >> 8) & 0xFF);
+       }
+
+       /* Setup RX controls */
+       dme_write(sc, DM9000_RCR, DM9000_RCR_RXEN | DM9000_RCR_WTDIS);
+}
+
+int
+dme_pkt_write_2(struct dme_softc *sc, struct mbuf *bufChain)
+{
+       int left_over_count = 0; /* Number of bytes from previous mbuf, which
+                                   need to be written with the next.*/
+       uint16_t left_over_buf = 0;
+       int length = 0;
+       struct mbuf *buf;
+       uint8_t *write_ptr;
+
+       /* We expect that the DM9000 has been setup to accept writes before
+          this function is called. */
+
+       for (buf = bufChain; buf != NULL; buf = buf->m_next) {
+               int to_write = buf->m_len;
+
+               length += to_write;
+
+               write_ptr = buf->m_data;
+               while (to_write > 0 ||
+                      (buf->m_next == NULL && left_over_count > 0)
+                      ) {
+                       if (left_over_count > 0) {
+                               uint8_t b = 0;
+                               DPRINTF(("dme_pkt_write_16: "
+                                        "Writing left over byte\n"));
+
+                               if (to_write > 0) {
+                                       b = *write_ptr;
+                                       to_write--;
+                                       write_ptr++;
+
+                                       DPRINTF(("Took single byte\n"));
+                               } else {
+                                       DPRINTF(("Leftover in last run\n"));
+                                       length++;
+                               }
+
+                               /* Does shift direction depend on endianess? */
+                               left_over_buf = left_over_buf | (b << 8);
+
+                               bus_space_write_2(sc->sc_iot, sc->sc_ioh,
+                                                 sc->dme_data, left_over_buf);
+                               TX_DATA_DPRINTF(("%02X ", left_over_buf));
+                               left_over_count = 0;
+                       } else if ((long)write_ptr % 2 != 0) {
+                               /* Misaligned data */
+                               DPRINTF(("dme_pkt_write_16: "
+                                        "Detected misaligned data\n"));
+                               left_over_buf = *write_ptr;
+                               left_over_count = 1;
+                               write_ptr++;
+                               to_write--;
+                       } else {
+                               int i;
+                               uint16_t *dptr = (uint16_t*)write_ptr;
+
+                               /* A block of aligned data. */
+                               for(i = 0; i < to_write/2; i++) {
+                                       /* buf will be half-word aligned
+                                        * all the time
+                                        */
+                                       bus_space_write_2(sc->sc_iot,
+                                                         sc->sc_ioh, 
sc->dme_data, *dptr);
+                                       TX_DATA_DPRINTF(("%02X %02X ",
+                                                        *dptr & 0xFF, 
(*dptr>>8) & 0xFF));
+                                       dptr++;
+                               }
+
+                               write_ptr += i*2;
+                               if (to_write % 2 != 0) {
+                                       DPRINTF(("dme_pkt_write_16: "
+                                                "to_write %% 2: %d\n",
+                                                to_write % 2));
+                                       left_over_count = 1;
+                                       /* XXX: Does this depend on
+                                        * the endianess?
+                                        */
+                                       left_over_buf = *write_ptr;
+
+                                       write_ptr++;
+                                       to_write--;
+                                       DPRINTF(("dme_pkt_write_16: "
+                                                "to_write (after): %d\n",
+                                                to_write));
+                                       DPRINTF(("dme_pkt_write_16: i*2: %d\n",
+                                                i*2));
+                               }
+                               to_write -= i*2;
+                       }
+               } /* while(...) */
+       } /* for(...) */
+
+       return length;
+}
+
+int
+dme_pkt_read_2(struct dme_softc *sc, struct ifnet *ifp, struct mbuf **outBuf)
+{
+       uint8_t rx_status;
+       struct mbuf *m;
+       uint16_t data;
+       uint16_t frame_length;
+       uint16_t i;
+       uint16_t *buf;
+
+       data = bus_space_read_2(sc->sc_iot, sc->sc_ioh,
+                               sc->dme_data);
+
+       rx_status = data & 0xFF;
+       frame_length = bus_space_read_2(sc->sc_iot,
+                                       sc->sc_ioh, sc->dme_data);
+       if (frame_length > ETHER_MAX_LEN) {
+               panic("Something is rotten");
+       }
+       RX_DPRINTF(("dme_receive: "
+                   "rx_statux: 0x%x, frame_length: %d\n",
+                   rx_status, frame_length));
+
+
+       m = dme_alloc_receive_buffer(ifp, frame_length);
+
+       buf = mtod(m, uint16_t*);
+
+       RX_DPRINTF(("dme_receive: "));
+
+       for(i=0; i< frame_length; i+=2 ) {
+               data = bus_space_read_2(sc->sc_iot,
+                                       sc->sc_ioh, sc->dme_data);
+               if ( (frame_length % 2 != 0) &&
+                    (i == frame_length-1) ) {
+                       data = data & 0xff;
+                       RX_DPRINTF((" L "));
+               }
+               *buf = data;
+               buf++;
+               RX_DATA_DPRINTF(("%02X %02X ", data & 0xff,
+                                (data>>8) & 0xff));
+       }
+
+       RX_DATA_DPRINTF(("\n"));
+       RX_DPRINTF(("Read %d bytes\n", i));
+
+       *outBuf = m;
+       return rx_status;
+}
+
+struct mbuf*
+dme_alloc_receive_buffer(struct ifnet *ifp, unsigned int frame_length)
+{
+       struct dme_softc *sc = ifp->if_softc;
+       struct mbuf *m;
+       int pad;
+
+       MGETHDR(m, M_DONTWAIT, MT_DATA);
+       m->m_pkthdr.rcvif = ifp;
+       /* Ensure that we always allocate an even number of
+        * bytes in order to avoid writing beyond the buffer
+        */
+       m->m_pkthdr.len = frame_length + (frame_length % sc->sc_data_width);
+       pad = ALIGN(sizeof(struct ether_header)) -
+               sizeof(struct ether_header);
+       /* All our frames have the CRC attached */
+       m->m_flags |= M_HASFCS;
+       if (m->m_pkthdr.len + pad > MHLEN )
+               MCLGET(m, M_DONTWAIT);
+
+       m->m_data += pad;
+       m->m_len = frame_length + (frame_length % sc->sc_data_width);
+
+       return m;
 }



Home | Main Index | Thread Index | Old Index