]> git.ipfire.org Git - people/ms/u-boot.git/blobdiff - cpu/mpc8260/ether_fcc.c
Patches by Murray Jensen, 17 Jun 2003:
[people/ms/u-boot.git] / cpu / mpc8260 / ether_fcc.c
index bd2fcc927f5f77d7255cb4318ba6bb91403b57c6..45dca732035f02d2f31a74b317f08c48afc7c092 100644 (file)
@@ -391,4 +391,761 @@ int fec_initialize(bd_t *bis)
        return 1;
 }
 
+#ifdef CONFIG_ETHER_LOOPBACK_TEST
+
+#define ELBT_BUFSZ     1024    /* must be multiple of 32 */
+
+#define ELBT_CRCSZ     4
+
+#define ELBT_NRXBD     4       /* must be at least 2 */
+#define ELBT_NTXBD     4
+
+#define ELBT_MAXRXERR  32
+#define ELBT_MAXTXERR  32
+
+#define ELBT_CLSWAIT   1000    /* msec to wait for further input frames */
+
+typedef
+       struct {
+               uint off;
+               char *lab;
+       }
+elbt_prdesc;
+
+typedef
+       struct {
+               uint _l, _f, m, bc, mc, lg, no, sh, cr, ov, cl;
+               uint badsrc, badtyp, badlen, badbit;
+       }
+elbt_rxeacc;
+
+static elbt_prdesc rxeacc_descs[] = {
+       { offsetof(elbt_rxeacc, _l),            "Not Last in Frame"     },
+       { offsetof(elbt_rxeacc, _f),            "Not First in Frame"    },
+       { offsetof(elbt_rxeacc, m),             "Address Miss"          },
+       { offsetof(elbt_rxeacc, bc),            "Broadcast Address"     },
+       { offsetof(elbt_rxeacc, mc),            "Multicast Address"     },
+       { offsetof(elbt_rxeacc, lg),            "Frame Length Violation"},
+       { offsetof(elbt_rxeacc, no),            "Non-Octet Alignment"   },
+       { offsetof(elbt_rxeacc, sh),            "Short Frame"           },
+       { offsetof(elbt_rxeacc, cr),            "CRC Error"             },
+       { offsetof(elbt_rxeacc, ov),            "Overrun"               },
+       { offsetof(elbt_rxeacc, cl),            "Collision"             },
+       { offsetof(elbt_rxeacc, badsrc),        "Bad Src Address"       },
+       { offsetof(elbt_rxeacc, badtyp),        "Bad Frame Type"        },
+       { offsetof(elbt_rxeacc, badlen),        "Bad Frame Length"      },
+       { offsetof(elbt_rxeacc, badbit),        "Data Compare Errors"   },
+};
+static int rxeacc_ndesc = sizeof (rxeacc_descs) / sizeof (rxeacc_descs[0]);
+
+typedef
+       struct {
+               uint def, hb, lc, rl, rc, un, csl;
+       }
+elbt_txeacc;
+
+static elbt_prdesc txeacc_descs[] = {
+       { offsetof(elbt_txeacc, def),           "Defer Indication"      },
+       { offsetof(elbt_txeacc, hb),            "Heartbeat"             },
+       { offsetof(elbt_txeacc, lc),            "Late Collision"        },
+       { offsetof(elbt_txeacc, rl),            "Retransmission Limit"  },
+       { offsetof(elbt_txeacc, rc),            "Retry Count"           },
+       { offsetof(elbt_txeacc, un),            "Underrun"              },
+       { offsetof(elbt_txeacc, csl),           "Carrier Sense Lost"    },
+};
+static int txeacc_ndesc = sizeof (txeacc_descs) / sizeof (txeacc_descs[0]);
+
+typedef
+       struct {
+               uchar rxbufs[ELBT_NRXBD][ELBT_BUFSZ];
+               uchar txbufs[ELBT_NTXBD][ELBT_BUFSZ];
+               cbd_t rxbd[ELBT_NRXBD];
+               cbd_t txbd[ELBT_NTXBD];
+               enum { Idle, Running, Closing, Closed } state;
+               int proff, page, sblock;
+               uint clstime, nsent, ntxerr, nrcvd, nrxerr;
+               ushort rxerrs[ELBT_MAXRXERR], txerrs[ELBT_MAXTXERR];
+               elbt_rxeacc rxeacc;
+               elbt_txeacc txeacc;
+       } __attribute__ ((aligned(8)))
+elbt_chan;
+
+static uchar patbytes[ELBT_NTXBD] = {
+       0xff, 0xaa, 0x55, 0x00
+};
+static uint patwords[ELBT_NTXBD] = {
+       0xffffffff, 0xaaaaaaaa, 0x55555555, 0x00000000
+};
+
+#ifdef __GNUC__
+static elbt_chan elbt_chans[3] __attribute__ ((aligned(8)));
+#else
+#error "elbt_chans must be 64-bit aligned"
+#endif
+
+#define CPM_CR_GRACEFUL_STOP_TX        ((ushort)0x0005)
+
+static elbt_prdesc epram_descs[] = {
+       { offsetof(fcc_enet_t, fen_crcec),      "CRC Errors"            },
+       { offsetof(fcc_enet_t, fen_alec),       "Alignment Errors"      },
+       { offsetof(fcc_enet_t, fen_disfc),      "Discarded Frames"      },
+       { offsetof(fcc_enet_t, fen_octc),       "Octets"                },
+       { offsetof(fcc_enet_t, fen_colc),       "Collisions"            },
+       { offsetof(fcc_enet_t, fen_broc),       "Broadcast Frames"      },
+       { offsetof(fcc_enet_t, fen_mulc),       "Multicast Frames"      },
+       { offsetof(fcc_enet_t, fen_uspc),       "Undersize Frames"      },
+       { offsetof(fcc_enet_t, fen_frgc),       "Fragments"             },
+       { offsetof(fcc_enet_t, fen_ospc),       "Oversize Frames"       },
+       { offsetof(fcc_enet_t, fen_jbrc),       "Jabbers"               },
+       { offsetof(fcc_enet_t, fen_p64c),       "64 Octet Frames"       },
+       { offsetof(fcc_enet_t, fen_p65c),       "65-127 Octet Frames"   },
+       { offsetof(fcc_enet_t, fen_p128c),      "128-255 Octet Frames"  },
+       { offsetof(fcc_enet_t, fen_p256c),      "256-511 Octet Frames"  },
+       { offsetof(fcc_enet_t, fen_p512c),      "512-1023 Octet Frames" },
+       { offsetof(fcc_enet_t, fen_p1024c),     "1024-1518 Octet Frames"},
+};
+static int epram_ndesc = sizeof (epram_descs) / sizeof (epram_descs[0]);
+
+/*
+ * given an elbt_prdesc array and an array of base addresses, print
+ * each prdesc down the screen with the values fetched from each
+ * base address across the screen
+ */
+static void
+print_desc (elbt_prdesc descs[], int ndesc, uchar *bases[], int nbase)
+{
+       elbt_prdesc *dp = descs, *edp = dp + ndesc;
+       int i;
+
+       printf ("%32s", "");
+
+       for (i = 0; i < nbase; i++)
+               printf ("  Channel %d", i);
+
+       puts ("\n");
+
+       while (dp < edp) {
+
+               printf ("%-32s", dp->lab);
+
+               for (i = 0; i < nbase; i++) {
+                       uint val = *(uint *)(bases[i] + dp->off);
+
+                       printf (" %10u", val);
+               }
+
+               puts ("\n");
+
+               dp++;
+       }
+}
+
+/*
+ * return number of bits that are set in a value; value contains
+ * nbits (right-justified) bits.
+ */
+static uint __inline__
+nbs (uint value, uint nbits)
+{
+       uint cnt = 0;
+#if 1
+       uint pos = sizeof (uint) * 8;
+
+       __asm__ __volatile__ ("\
+       mtctr   %2\n\
+1:     rlwnm.  %2,%1,%4,31,31\n\
+       beq     2f\n\
+       addi    %0,%0,1\n\
+2:     subi    %4,%4,1\n\
+       bdnz    1b"
+       : "=r"(cnt)
+       : "r"(value), "r"(nbits), "r"(cnt), "r"(pos)
+       : "ctr", "cc" );
+#else
+       uint mask = 1;
+
+       do {
+               if (value & mask)
+                       cnt++;
+               mask <<= 1;
+       } while (--nbits);
+#endif
+
+       return (cnt);
+}
+
+static ulong
+badbits (uchar *bp, int n, ulong pat)
+{
+       ulong *lp, cnt = 0;
+       int nl;
+
+       while (n > 0 && ((ulong)bp & (sizeof (ulong) - 1)) != 0) {
+               uchar diff;
+
+               diff = *bp++ ^ (uchar)pat;
+
+               if (diff)
+                       cnt += nbs ((ulong)diff, 8);
+
+               n--;
+       }
+
+       lp = (ulong *)bp;
+       nl = n / sizeof (ulong);
+       n -= nl * sizeof (ulong);
+
+       while (nl > 0) {
+               ulong diff;
+
+               diff = *lp++ ^ pat;
+
+               if (diff)
+                       cnt += nbs (diff, 32);
+
+               nl--;
+       }
+
+       bp = (uchar *)lp;
+
+       while (n > 0) {
+               uchar diff;
+
+               diff = *bp++ ^ (uchar)pat;
+
+               if (diff)
+                       cnt += nbs ((ulong)diff, 8);
+
+               n--;
+       }
+
+       return (cnt);
+}
+
+static inline unsigned short
+swap16 (unsigned short x)
+{
+       return (((x & 0xff) << 8) | ((x & 0xff00) >> 8));
+}
+
+void
+eth_loopback_test (void)
+{
+       DECLARE_GLOBAL_DATA_PTR;
+
+       volatile immap_t *immr = (immap_t *)CFG_IMMR;
+       volatile cpm8260_t *cp = &(immr->im_cpm);
+       int c, nclosed;
+       ulong runtime, nmsec;
+       uchar *bases[3];
+
+       puts ("FCC Ethernet External loopback test\n");
+
+       memcpy (NetOurEther, gd->bd->bi_enetaddr, 6);
+
+       /*
+        * global initialisations for all FCC channels
+        */
+
+       /* 28.9 - (1-2): ioports have been set up already */
+
+#if defined(CONFIG_HYMOD)
+       /*
+        * Attention: this is board-specific
+        * - FCC1 Rx-CLK is CLK10
+        * - FCC1 Tx-CLK is CLK11
+        * - FCC2 Rx-CLK is CLK13
+        * - FCC2 Tx-CLK is CLK14
+        * - FCC3 Rx-CLK is CLK15
+        * - FCC3 Tx-CLK is CLK16
+        */
+
+       /* 28.9 - (3): connect FCC's tx and rx clocks */
+       immr->im_cpmux.cmx_uar = 0;
+       immr->im_cpmux.cmx_fcr = CMXFCR_RF1CS_CLK10|CMXFCR_TF1CS_CLK11|\
+           CMXFCR_RF2CS_CLK13|CMXFCR_TF2CS_CLK14|\
+           CMXFCR_RF3CS_CLK15|CMXFCR_TF3CS_CLK16;
+#else
+#error "eth_loopback_test not supported on your board"
+#endif
+
+       puts ("Initialise FCC channels:");
+
+       for (c = 0; c < 3; c++) {
+               elbt_chan *ecp = &elbt_chans[c];
+               volatile fcc_t *fcp = &immr->im_fcc[c];
+               volatile fcc_enet_t *fpp;
+               int i;
+               ulong addr;
+
+               /*
+                * initialise channel data
+                */
+
+               printf (" %d", c);
+
+               memset ((void *)ecp, 0, sizeof (*ecp));
+
+               ecp->state = Idle;
+
+               switch (c) {
+
+               case 0: /* FCC1 */
+                       ecp->proff = PROFF_FCC1;
+                       ecp->page = CPM_CR_FCC1_PAGE;
+                       ecp->sblock = CPM_CR_FCC1_SBLOCK;
+                       break;
+
+               case 1: /* FCC2 */
+                       ecp->proff = PROFF_FCC2;
+                       ecp->page = CPM_CR_FCC2_PAGE;
+                       ecp->sblock = CPM_CR_FCC2_SBLOCK;
+                       break;
+
+               case 2: /* FCC3 */
+                       ecp->proff = PROFF_FCC3;
+                       ecp->page = CPM_CR_FCC3_PAGE;
+                       ecp->sblock = CPM_CR_FCC3_SBLOCK;
+                       break;
+               }
+
+               /*
+                * set up tx buffers and bds
+                */
+
+               for (i = 0; i < ELBT_NTXBD; i++) {
+                       cbd_t *bdp = &ecp->txbd[i];
+                       uchar *bp = &ecp->txbufs[i][0];
+
+                       bdp->cbd_bufaddr = (uint)bp;
+                       /* room for crc */
+                       bdp->cbd_datlen = ELBT_BUFSZ - ELBT_CRCSZ;
+                       bdp->cbd_sc = BD_ENET_TX_READY | BD_ENET_TX_PAD | \
+                               BD_ENET_TX_LAST | BD_ENET_TX_TC;
+
+                       memset ((void *)bp, patbytes[i], ELBT_BUFSZ);
+                       NetSetEther (bp, NetBcastAddr, 0x8000);
+               }
+               ecp->txbd[ELBT_NTXBD - 1].cbd_sc |= BD_ENET_TX_WRAP;
+
+               /*
+                * set up rx buffers and bds
+                */
+
+               for (i = 0; i < ELBT_NRXBD; i++) {
+                   cbd_t *bdp = &ecp->rxbd[i];
+                   uchar *bp = &ecp->rxbufs[i][0];
+
+                   bdp->cbd_bufaddr = (uint)bp;
+                   bdp->cbd_datlen = 0;
+                   bdp->cbd_sc = BD_ENET_RX_EMPTY;
+
+                   memset ((void *)bp, 0, ELBT_BUFSZ);
+               }
+               ecp->rxbd[ELBT_NRXBD - 1].cbd_sc |= BD_ENET_RX_WRAP;
+
+               /*
+                * set up the FCC channel hardware
+                */
+
+               /* 28.9 - (4): GFMR: disable tx/rx, CCITT CRC, Mode Ethernet */
+               fcp->fcc_gfmr = FCC_GFMR_MODE_ENET | FCC_GFMR_TCRC_32;
+
+               /* 28.9 - (5): FPSMR: fd, enet CRC, Promis, RMON, Rx SHort */
+               fcp->fcc_fpsmr = FCC_PSMR_FDE | FCC_PSMR_LPB | \
+                       FCC_PSMR_ENCRC | FCC_PSMR_PRO | \
+                       FCC_PSMR_MON | FCC_PSMR_RSH;
+
+               /* 28.9 - (6): FDSR: Ethernet Syn */
+               fcp->fcc_fdsr = 0xD555;
+
+               /* 29.9 - (7): initialise parameter ram */
+               fpp = (fcc_enet_t *)&(immr->im_dprambase[ecp->proff]);
+
+               /* clear whole struct to make sure all resv fields are zero */
+               memset ((void *)fpp, 0, sizeof (fcc_enet_t));
+
+               /*
+                * common Parameter RAM area
+                *
+                * Allocate space in the reserved FCC area of DPRAM for the
+                * internal buffers.  No one uses this space (yet), so we
+                * can do this.  Later, we will add resource management for
+                * this area.
+                */
+               addr = CPM_FCC_SPECIAL_BASE + (c * 64);
+               fpp->fen_genfcc.fcc_riptr = addr;
+               fpp->fen_genfcc.fcc_tiptr = addr + 32;
+
+               /*
+                * Set maximum bytes per receive buffer.
+                * It must be a multiple of 32.
+                * buffers are in 60x bus memory.
+                */
+               fpp->fen_genfcc.fcc_mrblr = PKT_MAXBLR_SIZE;
+               fpp->fen_genfcc.fcc_rstate = (CPMFCR_GBL | CPMFCR_EB) << 24;
+               fpp->fen_genfcc.fcc_rbase = (unsigned int)(&ecp->rxbd[0]);
+               fpp->fen_genfcc.fcc_tstate = (CPMFCR_GBL | CPMFCR_EB) << 24;
+               fpp->fen_genfcc.fcc_tbase = (unsigned int)(&ecp->txbd[0]);
+
+               /* protocol-specific area */
+               fpp->fen_cmask = 0xdebb20e3;    /* CRC mask */
+               fpp->fen_cpres = 0xffffffff;    /* CRC preset */
+               fpp->fen_retlim = 15;           /* Retry limit threshold */
+               fpp->fen_mflr = PKT_MAXBUF_SIZE;/* max frame length register */
+
+               /*
+                * Set Ethernet station address.
+                *
+                * This is supplied in the board information structure, so we
+                * copy that into the controller.
+                * So, far we have only been given one Ethernet address. We use
+                * the same address for all channels
+                */
+#define ea gd->bd->bi_enetaddr
+               fpp->fen_paddrh = (ea[5] << 8) + ea[4];
+               fpp->fen_paddrm = (ea[3] << 8) + ea[2];
+               fpp->fen_paddrl = (ea[1] << 8) + ea[0];
+#undef ea
+
+               fpp->fen_minflr = PKT_MINBUF_SIZE; /* min frame len register */
+               /*
+                * pad pointer. use tiptr since we don't need
+                * a specific padding char
+                */
+               fpp->fen_padptr = fpp->fen_genfcc.fcc_tiptr;
+               fpp->fen_maxd1 = PKT_MAXDMA_SIZE;       /* max DMA1 length */
+               fpp->fen_maxd2 = PKT_MAXDMA_SIZE;       /* max DMA2 length */
+               fpp->fen_rfthr = 1;
+               fpp->fen_rfcnt = 1;
+
+               /* 28.9 - (8): clear out events in FCCE */
+               fcp->fcc_fcce = ~0x0;
+
+               /* 28.9 - (9): FCCM: mask all events */
+               fcp->fcc_fccm = 0;
+
+               /* 28.9 - (10-12): we don't use ethernet interrupts */
+
+               /* 28.9 - (13)
+                *
+                * Let's re-initialize the channel now.  We have to do it later
+                * than the manual describes because we have just now finished
+                * the BD initialization.
+                */
+               cp->cp_cpcr = mk_cr_cmd (ecp->page, ecp->sblock, \
+                       0x0c, CPM_CR_INIT_TRX) | CPM_CR_FLG;
+               do {
+                       __asm__ __volatile__ ("eieio");
+               } while (cp->cp_cpcr & CPM_CR_FLG);
+       }
+
+       puts (" done\nStarting test... (Ctrl-C to Finish)\n");
+
+       /*
+        * Note: don't want serial output from here until the end of the
+        * test - the delays would probably stuff things up.
+        */
+
+       clear_ctrlc ();
+       runtime = get_timer (0);
+
+       do {
+               nclosed = 0;
+
+               for (c = 0; c < 3; c++) {
+                       volatile fcc_t *fcp = &immr->im_fcc[c];
+                       elbt_chan *ecp = &elbt_chans[c];
+                       int i;
+
+                       switch (ecp->state) {
+
+                       case Idle:
+                               /*
+                                * set the channel Running ...
+                                */
+
+                               /* 28.9 - (14): enable tx/rx in gfmr */
+                               fcp->fcc_gfmr |= FCC_GFMR_ENT | FCC_GFMR_ENR;
+
+                               ecp->state = Running;
+                               break;
+
+                       case Running:
+                               /*
+                                * (while Running only) check for
+                                * termination of the test
+                                */
+
+                               (void)ctrlc ();
+
+                               if (had_ctrlc ()) {
+                                       /*
+                                        * initiate a "graceful stop transmit"
+                                        * on the channel
+                                        */
+                                       cp->cp_cpcr = mk_cr_cmd (ecp->page, \
+                                               ecp->sblock, 0x0c, \
+                                               CPM_CR_GRACEFUL_STOP_TX) | \
+                                               CPM_CR_FLG;
+                                       do {
+                                               __asm__ __volatile__ ("eieio");
+                                       } while (cp->cp_cpcr & CPM_CR_FLG);
+
+                                       ecp->clstime = get_timer (0);
+                                       ecp->state = Closing;
+                               }
+                               /* fall through ... */
+
+                       case Closing:
+                               /*
+                                * (while Running or Closing) poll the channel:
+                                * - check for any non-READY tx buffers and
+                                *   make them ready
+                                * - check for any non-EMPTY rx buffers and
+                                *   check that they were received correctly,
+                                *   adjust counters etc, then make empty
+                                */
+
+                               for (i = 0; i < ELBT_NTXBD; i++) {
+                                       cbd_t *bdp = &ecp->txbd[i];
+                                       ushort sc = bdp->cbd_sc;
+
+                                       if ((sc & BD_ENET_TX_READY) != 0)
+                                               continue;
+
+                                       /*
+                                        * this frame has finished
+                                        * transmitting
+                                        */
+                                       ecp->nsent++;
+
+                                       if (sc & BD_ENET_TX_STATS) {
+                                               ulong n;
+
+                                               /*
+                                                * we had an error on
+                                                * the transmission
+                                                */
+                                               n = ecp->ntxerr++;
+                                               if (n < ELBT_MAXTXERR)
+                                                       ecp->txerrs[n] = sc;
+
+                                               if (sc & BD_ENET_TX_DEF)
+                                                       ecp->txeacc.def++;
+                                               if (sc & BD_ENET_TX_HB)
+                                                       ecp->txeacc.hb++;
+                                               if (sc & BD_ENET_TX_LC)
+                                                       ecp->txeacc.lc++;
+                                               if (sc & BD_ENET_TX_RL)
+                                                       ecp->txeacc.rl++;
+                                               if (sc & BD_ENET_TX_RCMASK)
+                                                       ecp->txeacc.rc++;
+                                               if (sc & BD_ENET_TX_UN)
+                                                       ecp->txeacc.un++;
+                                               if (sc & BD_ENET_TX_CSL)
+                                                       ecp->txeacc.csl++;
+
+                                               bdp->cbd_sc &= \
+                                                       ~BD_ENET_TX_STATS;
+                                       }
+
+                                       if (ecp->state == Closing)
+                                               ecp->clstime = get_timer (0);
+
+                                       /* make it ready again */
+                                       bdp->cbd_sc |= BD_ENET_TX_READY;
+                               }
+
+                               for (i = 0; i < ELBT_NRXBD; i++) {
+                                       cbd_t *bdp = &ecp->rxbd[i];
+                                       ushort sc = bdp->cbd_sc, mask;
+
+                                       if ((sc & BD_ENET_RX_EMPTY) != 0)
+                                               continue;
+
+                                       /* we have a new frame in this buffer */
+                                       ecp->nrcvd++;
+
+                                       mask = BD_ENET_RX_LAST|BD_ENET_RX_FIRST;
+                                       if ((sc & mask) != mask) {
+                                               /* somethings wrong here ... */
+                                               if (!(sc & BD_ENET_RX_LAST))
+                                                       ecp->rxeacc._l++;
+                                               if (!(sc & BD_ENET_RX_FIRST))
+                                                       ecp->rxeacc._f++;
+                                       }
+
+                                       if (sc & BD_ENET_RX_STATS) {
+                                               ulong n;
+
+                                               /*
+                                                * we had some sort of error
+                                                * on the frame
+                                                */
+                                               n = ecp->nrxerr++;
+                                               if (n < ELBT_MAXRXERR)
+                                                       ecp->rxerrs[n] = sc;
+
+                                               if (sc & BD_ENET_RX_MISS)
+                                                       ecp->rxeacc.m++;
+                                               if (sc & BD_ENET_RX_BC)
+                                                       ecp->rxeacc.bc++;
+                                               if (sc & BD_ENET_RX_MC)
+                                                       ecp->rxeacc.mc++;
+                                               if (sc & BD_ENET_RX_LG)
+                                                       ecp->rxeacc.lg++;
+                                               if (sc & BD_ENET_RX_NO)
+                                                       ecp->rxeacc.no++;
+                                               if (sc & BD_ENET_RX_SH)
+                                                       ecp->rxeacc.sh++;
+                                               if (sc & BD_ENET_RX_CR)
+                                                       ecp->rxeacc.cr++;
+                                               if (sc & BD_ENET_RX_OV)
+                                                       ecp->rxeacc.ov++;
+                                               if (sc & BD_ENET_RX_CL)
+                                                       ecp->rxeacc.cl++;
+
+                                               bdp->cbd_sc &= \
+                                                       ~BD_ENET_RX_STATS;
+                                       }
+                                       else {
+                                               ushort datlen = bdp->cbd_datlen;
+                                               Ethernet_t *ehp;
+                                               ushort prot;
+                                               int ours, tb, n, nbytes;
+
+                                               ehp = (Ethernet_t *) \
+                                                       &ecp->rxbufs[i][0];
+
+                                               ours = memcmp (ehp->et_src, \
+                                                       NetOurEther, 6);
+
+                                               prot = swap16 (ehp->et_protlen);
+                                               tb = prot & 0x8000;
+                                               n = prot & 0x7fff;
+
+                                               nbytes = ELBT_BUFSZ - \
+                                                       offsetof (Ethernet_t, \
+                                                               et_dsap) - \
+                                                       ELBT_CRCSZ;
+
+                                               /* check the frame is correct */
+                                               if (datlen != ELBT_BUFSZ)
+                                                       ecp->rxeacc.badlen++;
+                                               else if (!ours)
+                                                       ecp->rxeacc.badsrc++;
+                                               else if (!tb || n >= ELBT_NTXBD)
+                                                       ecp->rxeacc.badtyp++;
+                                               else {
+                                                       ulong patword = \
+                                                               patwords[n];
+                                                       uint nbb;
+
+                                                       nbb = badbits ( \
+                                                               &ehp->et_dsap, \
+                                                               nbytes, \
+                                                               patword);
+
+                                                       ecp->rxeacc.badbit += \
+                                                               nbb;
+                                               }
+                                       }
+
+                                       if (ecp->state == Closing)
+                                           ecp->clstime = get_timer (0);
+
+                                       /* make it empty again */
+                                       bdp->cbd_sc |= BD_ENET_RX_EMPTY;
+                               }
+
+                               if (ecp->state != Closing)
+                                       break;
+
+                               /*
+                                * (while Closing) check to see if
+                                * waited long enough
+                                */
+
+                               if (get_timer (ecp->clstime) >= ELBT_CLSWAIT) {
+                                       /* write GFMR: disable tx/rx */
+                                       fcp->fcc_gfmr &= \
+                                               ~(FCC_GFMR_ENT | FCC_GFMR_ENR);
+                                       ecp->state = Closed;
+                               }
+
+                               break;
+
+                       case Closed:
+                               nclosed++;
+                               break;
+                       }
+               }
+
+       } while (nclosed < 3);
+
+       runtime = get_timer (runtime);
+       if (runtime <= ELBT_CLSWAIT) {
+               printf ("Whoops! somehow elapsed time (%ld) is wrong (<= %d)\n",
+                       runtime, ELBT_CLSWAIT);
+               return;
+       }
+       nmsec = runtime - ELBT_CLSWAIT;
+
+       printf ("Test Finished in %ldms (plus %dms close wait period)!\n\n",
+               nmsec, ELBT_CLSWAIT);
+
+       /*
+        * now print stats
+        */
+
+       for (c = 0; c < 3; c++) {
+               elbt_chan *ecp = &elbt_chans[c];
+               uint rxpps, txpps, nerr;
+
+               rxpps = (ecp->nrcvd * 1000) / nmsec;
+               txpps = (ecp->nsent * 1000) / nmsec;
+
+               printf ("Channel %d: %d rcvd (%d pps, %d rxerrs), "
+                       "%d sent (%d pps, %d txerrs)\n\n", c,
+                       ecp->nrcvd, rxpps, ecp->nrxerr,
+                       ecp->nsent, txpps, ecp->ntxerr);
+
+               if ((nerr = ecp->nrxerr) > 0) {
+                       ulong i;
+
+                       printf ("\tFirst %d rx errs:", nerr);
+                       for (i = 0; i < nerr; i++)
+                               printf (" %04x", ecp->rxerrs[i]);
+                       puts ("\n");
+               }
+
+               if ((nerr = ecp->ntxerr) > 0) {
+                       ulong i;
+
+                       printf ("\tFirst %d tx errs:", nerr);
+                       for (i = 0; i < nerr; i++)
+                               printf (" %04x", ecp->txerrs[i]);
+                       puts ("\n");
+               }
+       }
+
+       puts ("Receive Error Counts:\n");
+       for (c = 0; c < 3; c++)
+               bases[c] = (uchar *)&elbt_chans[c].rxeacc;
+       print_desc (rxeacc_descs, rxeacc_ndesc, bases, 3);
+
+       puts ("\nTransmit Error Counts:\n");
+       for (c = 0; c < 3; c++)
+               bases[c] = (uchar *)&elbt_chans[c].txeacc;
+       print_desc (txeacc_descs, txeacc_ndesc, bases, 3);
+
+       puts ("\nRMON(-like) Counters:\n");
+       for (c = 0; c < 3; c++)
+               bases[c] = (uchar *)&immr->im_dprambase[elbt_chans[c].proff];
+       print_desc (epram_descs, epram_ndesc, bases, 3);
+}
+
+#endif /* CONFIG_ETHER_LOOPBACK_TEST */
+
 #endif /* CONFIG_ETHER_ON_FCC && CFG_CMD_NET && CONFIG_NET_MULTI */