Logo Search packages:      
Sourcecode: kfreebsd-6 version File versions  Download package

midway.c

/*    $NetBSD: midway.c,v 1.30 1997/09/29 17:40:38 chuck Exp $    */
/*    (sync'd to midway.c 1.68)     */

/*-
 * Copyright (c) 1996 Charles D. Cranor and Washington University.
 * All rights reserved.
 *
 * 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Charles D. Cranor and
 *    Washington University.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 <sys/cdefs.h>
__FBSDID("$FreeBSD: src/sys/dev/en/midway.c,v 1.65.2.2 2007/01/19 23:01:28 jhb Exp $");

/*
 *
 * m i d w a y . c   e n i 1 5 5   d r i v e r 
 *
 * author: Chuck Cranor <chuck@ccrc.wustl.edu>
 * started: spring, 1996 (written from scratch).
 *
 * notes from the author:
 *   Extra special thanks go to Werner Almesberger, EPFL LRC.   Werner's
 *   ENI driver was especially useful in figuring out how this card works.
 *   I would also like to thank Werner for promptly answering email and being
 *   generally helpful.
 */

#define     EN_DIAG
#define EN_DDBHOOK      1     /* compile in ddb functions */

/*
 * Note on EN_ENIDMAFIX: the byte aligner on the ENI version of the card
 * appears to be broken.   it works just fine if there is no load... however
 * when the card is loaded the data get corrupted.   to see this, one only
 * has to use "telnet" over ATM.   do the following command in "telnet":
 *    cat /usr/share/misc/termcap
 * "telnet" seems to generate lots of 1023 byte mbufs (which make great
 * use of the byte aligner).   watch "netstat -s" for checksum errors.
 * 
 * I further tested this by adding a function that compared the transmit 
 * data on the card's SRAM with the data in the mbuf chain _after_ the 
 * "transmit DMA complete" interrupt.   using the "telnet" test I got data
 * mismatches where the byte-aligned data should have been.   using ddb
 * and en_dumpmem() I verified that the DTQs fed into the card were 
 * absolutely correct.   thus, we are forced to concluded that the ENI
 * hardware is buggy.   note that the Adaptec version of the card works
 * just fine with byte DMA.
 *
 * bottom line: we set EN_ENIDMAFIX to 1 to avoid byte DMAs on the ENI
 * card.
 */

#if defined(DIAGNOSTIC) && !defined(EN_DIAG)
#define EN_DIAG               /* link in with master DIAG option */
#endif

#define EN_COUNT(X) (X)++

#ifdef EN_DEBUG

#undef      EN_DDBHOOK
#define     EN_DDBHOOK  1

/*
 * This macro removes almost all the EN_DEBUG conditionals in the code that make
 * to code a good deal less readable.
 */
#define DBG(SC, FL, PRINT) do {                                   \
      if ((SC)->debug & DBG_##FL) {                         \
            if_printf((SC)->ifp, "%s: "#FL": ", __func__);  \
            printf PRINT;                                   \
            printf("\n");                                   \
      }                                               \
    } while (0)

enum {
      DBG_INIT    = 0x0001,   /* debug attach/detach */
      DBG_TX            = 0x0002,   /* debug transmitting */
      DBG_SERV    = 0x0004,   /* debug service interrupts */
      DBG_IOCTL   = 0x0008,   /* debug ioctls */
      DBG_VC            = 0x0010,   /* debug VC handling */
      DBG_INTR    = 0x0020,   /* debug interrupts */
      DBG_DMA           = 0x0040,   /* debug DMA probing */
      DBG_IPACKETS      = 0x0080,   /* print input packets */
      DBG_REG           = 0x0100,   /* print all register access */
      DBG_LOCK    = 0x0200,   /* debug locking */
};

#else /* EN_DEBUG */

#define DBG(SC, FL, PRINT) do { } while (0)

#endif /* EN_DEBUG */

#include "opt_inet.h"
#include "opt_natm.h"
#include "opt_ddb.h"

#ifdef DDB
#undef      EN_DDBHOOK
#define     EN_DDBHOOK  1
#endif

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kdb.h>
#include <sys/queue.h>
#include <sys/sockio.h>
#include <sys/socket.h>
#include <sys/mbuf.h>
#include <sys/endian.h>
#include <sys/stdint.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/condvar.h>
#include <vm/uma.h>

#include <net/if.h>
#include <net/if_media.h>
#include <net/if_atm.h>

#if defined(INET) || defined(INET6)
#include <netinet/in.h>
#include <netinet/if_atm.h>
#endif

#ifdef NATM
#include <netnatm/natm.h>
#endif

#include <sys/bus.h>
#include <machine/bus.h>
#include <sys/rman.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/malloc.h>
#include <machine/resource.h>
#include <dev/utopia/utopia.h>
#include <dev/en/midwayreg.h>
#include <dev/en/midwayvar.h>

#include <net/bpf.h>

/*
 * params
 */
#ifndef EN_TXHIWAT
#define EN_TXHIWAT      (64 * 1024) /* max 64 KB waiting to be DMAd out */
#endif

SYSCTL_DECL(_hw_atm);

/*
 * dma tables
 *
 * The plan is indexed by the number of words to transfer.
 * The maximum index is 15 for 60 words.
 */
struct en_dmatab {
      uint8_t bcode;          /* code */
      uint8_t divshift; /* byte divisor */
};

static const struct en_dmatab en_dmaplan[] = {
  { 0, 0 },       /* 0 */           { MIDDMA_WORD, 2},      /* 1 */
  { MIDDMA_2WORD, 3},   /* 2 */           { MIDDMA_WORD, 2},      /* 3 */
  { MIDDMA_4WORD, 4},   /* 4 */           { MIDDMA_WORD, 2},      /* 5 */
  { MIDDMA_2WORD, 3},   /* 6 */           { MIDDMA_WORD, 2},      /* 7 */
  { MIDDMA_8WORD, 5},   /* 8 */           { MIDDMA_WORD, 2},      /* 9 */
  { MIDDMA_2WORD, 3},   /* 10 */    { MIDDMA_WORD, 2},      /* 11 */
  { MIDDMA_4WORD, 4},   /* 12 */    { MIDDMA_WORD, 2},      /* 13 */
  { MIDDMA_2WORD, 3},   /* 14 */    { MIDDMA_WORD, 2},      /* 15 */
  { MIDDMA_16WORD,6},   /* 16 */
};

/*
 * prototypes
 */
#ifdef EN_DDBHOOK
int en_dump(int unit, int level);
int en_dumpmem(int,int,int);
#endif
static void en_close_finish(struct en_softc *sc, struct en_vcc *vc);

#define EN_LOCK(SC)     do {                    \
      DBG(SC, LOCK, ("ENLOCK %d\n", __LINE__)); \
      mtx_lock(&sc->en_mtx);                    \
    } while (0)
#define EN_UNLOCK(SC)   do {                    \
      DBG(SC, LOCK, ("ENUNLOCK %d\n", __LINE__));     \
      mtx_unlock(&sc->en_mtx);                  \
    } while (0)
#define EN_CHECKLOCK(sc)      mtx_assert(&sc->en_mtx, MA_OWNED)

/*
 * While a transmit mbuf is waiting to get transmit DMA resources we
 * need to keep some information with it. We don't want to allocate
 * additional memory for this so we stuff it into free fields in the
 * mbuf packet header. Neither the checksum fields nor the rcvif field are used
 * so use these.
 */
#define TX_AAL5         0x1   /* transmit AAL5 PDU */
#define TX_HAS_TBD      0x2   /* TBD did fit into mbuf */
#define TX_HAS_PAD      0x4   /* padding did fit into mbuf */
#define TX_HAS_PDU      0x8   /* PDU trailer did fit into mbuf */

#define MBUF_SET_TX(M, VCI, FLAGS, DATALEN, PAD, MAP) do {        \
      (M)->m_pkthdr.csum_data = (VCI) | ((FLAGS) << MID_VCI_BITS);      \
      (M)->m_pkthdr.csum_flags = ((DATALEN) & 0xffff) |           \
          ((PAD & 0x3f) << 16);                             \
      (M)->m_pkthdr.rcvif = (void *)(MAP);                        \
    } while (0)

#define MBUF_GET_TX(M, VCI, FLAGS, DATALEN, PAD, MAP) do {        \
      (VCI) = (M)->m_pkthdr.csum_data & ((1 << MID_VCI_BITS) - 1);      \
      (FLAGS) = ((M)->m_pkthdr.csum_data >> MID_VCI_BITS) & 0xf;  \
      (DATALEN) = (M)->m_pkthdr.csum_flags & 0xffff;              \
      (PAD) = ((M)->m_pkthdr.csum_flags >> 16) & 0x3f;            \
      (MAP) = (void *)((M)->m_pkthdr.rcvif);                      \
    } while (0)


#define EN_WRAPADD(START, STOP, CUR, VAL) do {              \
      (CUR) = (CUR) + (VAL);                          \
      if ((CUR) >= (STOP))                            \
            (CUR) = (START) + ((CUR) - (STOP));       \
    } while (0)

#define WORD_IDX(START, X) (((X) - (START)) / sizeof(uint32_t))

#define SETQ_END(SC, VAL) ((SC)->is_adaptec ?               \
      ((VAL) | (MID_DMA_END >> 4)) :                        \
      ((VAL) | (MID_DMA_END)))

/*
 * The dtq and drq members are set for each END entry in the corresponding
 * card queue entry. It is used to find out, when a buffer has been
 * finished DMAing and can be freed.
 *
 * We store sc->dtq and sc->drq data in the following format...
 * the 0x80000 ensures we != 0
 */
#define EN_DQ_MK(SLOT, LEN)   (((SLOT) << 20) | (LEN) | (0x80000))
#define EN_DQ_SLOT(X)         ((X) >> 20)
#define EN_DQ_LEN(X)          ((X) & 0x3ffff)

/*
 * Variables
 */
static uma_zone_t en_vcc_zone;

/***********************************************************************/

/*
 * en_read{x}: read a word from the card. These are the only functions
 * that read from the card.
 */
static __inline uint32_t
en_readx(struct en_softc *sc, uint32_t r)
{
      uint32_t v;

#ifdef EN_DIAG
      if (r > MID_MAXOFF || (r % 4))
            panic("en_read out of range, r=0x%x", r);
#endif
      v = bus_space_read_4(sc->en_memt, sc->en_base, r);
      return (v);
}

static __inline uint32_t
en_read(struct en_softc *sc, uint32_t r)
{
      uint32_t v;

#ifdef EN_DIAG
      if (r > MID_MAXOFF || (r % 4))
            panic("en_read out of range, r=0x%x", r);
#endif
      v = bus_space_read_4(sc->en_memt, sc->en_base, r);
      DBG(sc, REG, ("en_read(%#x) -> %08x", r, v));
      return (v);
}

/*
 * en_write: write a word to the card. This is the only function that
 * writes to the card.
 */
static __inline void
en_write(struct en_softc *sc, uint32_t r, uint32_t v)
{
#ifdef EN_DIAG
      if (r > MID_MAXOFF || (r % 4))
            panic("en_write out of range, r=0x%x", r);
#endif
      DBG(sc, REG, ("en_write(%#x) <- %08x", r, v));
      bus_space_write_4(sc->en_memt, sc->en_base, r, v);
}

/*
 * en_k2sz: convert KBytes to a size parameter (a log2)
 */
static __inline int
en_k2sz(int k)
{
      switch(k) {
        case 1:   return (0);
        case 2:   return (1);
        case 4:   return (2);
        case 8:   return (3);
        case 16:  return (4);
        case 32:  return (5);
        case 64:  return (6);
        case 128: return (7);
        default:
            panic("en_k2sz");
      }
      return (0);
}
#define en_log2(X) en_k2sz(X)

/*
 * en_b2sz: convert a DMA burst code to its byte size
 */
static __inline int
en_b2sz(int b)
{
      switch (b) {
        case MIDDMA_WORD:   return (1*4);
        case MIDDMA_2WMAYBE:
        case MIDDMA_2WORD:  return (2*4);
        case MIDDMA_4WMAYBE:
        case MIDDMA_4WORD:  return (4*4);
        case MIDDMA_8WMAYBE:
        case MIDDMA_8WORD:  return (8*4);
        case MIDDMA_16WMAYBE:
        case MIDDMA_16WORD: return (16*4);
        default:
            panic("en_b2sz");
      }
      return (0);
}

/*
 * en_sz2b: convert a burst size (bytes) to DMA burst code
 */
static __inline int
en_sz2b(int sz)
{
      switch (sz) {
        case 1*4:  return (MIDDMA_WORD);
        case 2*4:  return (MIDDMA_2WORD);
        case 4*4:  return (MIDDMA_4WORD);
        case 8*4:  return (MIDDMA_8WORD);
        case 16*4: return (MIDDMA_16WORD);
        default:
            panic("en_sz2b");
      }
      return(0);
}

#ifdef EN_DEBUG
/*
 * Dump a packet
 */
static void
en_dump_packet(struct en_softc *sc, struct mbuf *m)
{
      int plen = m->m_pkthdr.len;
      u_int pos = 0;
      u_int totlen = 0;
      int len;
      u_char *ptr;

      if_printf(sc->ifp, "packet len=%d", plen);
      while (m != NULL) {
            totlen += m->m_len;
            ptr = mtod(m, u_char *);
            for (len = 0; len < m->m_len; len++, pos++, ptr++) {
                  if (pos % 16 == 8)
                        printf(" ");
                  if (pos % 16 == 0)
                        printf("\n");
                  printf(" %02x", *ptr);
            }
            m = m->m_next;
      }
      printf("\n");
      if (totlen != plen)
            printf("sum of m_len=%u\n", totlen);
}
#endif

/*********************************************************************/
/*
 * DMA maps
 */

/*
 * Map constructor for a MAP.
 *
 * This is called each time when a map is allocated
 * from the pool and about to be returned to the user. Here we actually
 * allocate the map if there isn't one. The problem is that we may fail
 * to allocate the DMA map yet have no means to signal this error. Therefor
 * when allocating a map, the call must check that there is a map. An
 * additional problem is, that i386 maps will be NULL, yet are ok and must
 * be freed so let's use a flag to signal allocation.
 *
 * Caveat: we have no way to know that we are called from an interrupt context
 * here. We rely on the fact, that bus_dmamap_create uses M_NOWAIT in all
 * its allocations.
 *
 * LOCK: any, not needed
 */
static int
en_map_ctor(void *mem, int size, void *arg, int flags)
{
      struct en_softc *sc = arg;
      struct en_map *map = mem;
      int err;

      err = bus_dmamap_create(sc->txtag, 0, &map->map);
      if (err != 0) {
            if_printf(sc->ifp, "cannot create DMA map %d\n", err);
            return (err);
      }
      map->flags = ENMAP_ALLOC;
      map->sc = sc;
      return (0);
}

/*
 * Map destructor.
 *
 * Called when a map is disposed into the zone. If the map is loaded, unload
 * it.
 *
 * LOCK: any, not needed
 */
static void
en_map_dtor(void *mem, int size, void *arg)
{
      struct en_map *map = mem;

      if (map->flags & ENMAP_LOADED) {
            bus_dmamap_unload(map->sc->txtag, map->map);
            map->flags &= ~ENMAP_LOADED;
      }
}

/*
 * Map finializer.
 *
 * This is called each time a map is returned from the zone to the system.
 * Get rid of the dmamap here.
 *
 * LOCK: any, not needed
 */
static void
en_map_fini(void *mem, int size)
{
      struct en_map *map = mem;

      bus_dmamap_destroy(map->sc->txtag, map->map);
}

/*********************************************************************/
/*
 * Transmission
 */

/*
 * Argument structure to load a transmit DMA map
 */
struct txarg {
      struct en_softc *sc;
      struct mbuf *m;
      u_int vci;
      u_int chan;       /* transmit channel */
      u_int datalen;          /* length of user data */
      u_int flags;
      u_int wait;       /* return: out of resources */
};

/*
 * TX DMA map loader helper. This function is the callback when the map
 * is loaded. It should fill the DMA segment descriptors into the hardware.
 *
 * LOCK: locked, needed
 */
static void
en_txdma_load(void *uarg, bus_dma_segment_t *segs, int nseg, bus_size_t mapsize,
    int error)
{
      struct txarg *tx = uarg;
      struct en_softc *sc = tx->sc;
      struct en_txslot *slot = &sc->txslot[tx->chan];
      uint32_t cur;           /* on-card buffer position (bytes offset) */
      uint32_t dtq;           /* on-card queue position (byte offset) */
      uint32_t last_dtq;      /* last DTQ we have written */
      uint32_t tmp;
      u_int free;       /* free queue entries on card */
      u_int needalign, cnt;
      bus_size_t rest;  /* remaining bytes in current segment */
      bus_addr_t addr;
      bus_dma_segment_t *s;
      uint32_t count, bcode;
      int i;

      if (error != 0)
            return;

      cur = slot->cur;
      dtq = sc->dtq_us;
      free = sc->dtq_free;

      last_dtq = 0;           /* make gcc happy */

      /*
       * Local macro to add an entry to the transmit DMA area. If there
       * are no entries left, return. Save the byte offset of the entry
       * in last_dtq for later use.
       */
#define PUT_DTQ_ENTRY(ENI, BCODE, COUNT, ADDR)                    \
      if (free == 0) {                                \
            EN_COUNT(sc->stats.txdtqout);                   \
            tx->wait = 1;                                   \
            return;                                         \
      }                                               \
      last_dtq = dtq;                                       \
      en_write(sc, dtq + 0, (ENI || !sc->is_adaptec) ?            \
          MID_MK_TXQ_ENI(COUNT, tx->chan, 0, BCODE) :             \
          MID_MK_TXQ_ADP(COUNT, tx->chan, 0, BCODE));             \
      en_write(sc, dtq + 4, ADDR);                          \
                                                      \
      EN_WRAPADD(MID_DTQOFF, MID_DTQEND, dtq, 8);                 \
      free--;

      /*
       * Local macro to generate a DMA entry to DMA cnt bytes. Updates
       * the current buffer byte offset accordingly.
       */
#define DO_DTQ(TYPE) do {                                   \
      rest -= cnt;                                          \
      EN_WRAPADD(slot->start, slot->stop, cur, cnt);              \
      DBG(sc, TX, ("tx%d: "TYPE" %u bytes, %ju left, cur %#x",    \
          tx->chan, cnt, (uintmax_t)rest, cur));                  \
                                                      \
      PUT_DTQ_ENTRY(1, bcode, count, addr);                       \
                                                      \
      addr += cnt;                                          \
    } while (0)

      if (!(tx->flags & TX_HAS_TBD)) {
            /*
             * Prepend the TBD - it did not fit into the first mbuf
             */
            tmp = MID_TBD_MK1((tx->flags & TX_AAL5) ?
                MID_TBD_AAL5 : MID_TBD_NOAAL5,
                sc->vccs[tx->vci]->txspeed,
                tx->m->m_pkthdr.len / MID_ATMDATASZ);
            en_write(sc, cur, tmp);
            EN_WRAPADD(slot->start, slot->stop, cur, 4);

            tmp = MID_TBD_MK2(tx->vci, 0, 0);
            en_write(sc, cur, tmp);
            EN_WRAPADD(slot->start, slot->stop, cur, 4);

            /* update DMA address */
            PUT_DTQ_ENTRY(0, MIDDMA_JK, WORD_IDX(slot->start, cur), 0);
      }

      for (i = 0, s = segs; i < nseg; i++, s++) {
            rest = s->ds_len;
            addr = s->ds_addr;

            if (sc->is_adaptec) {
                  /* adaptec card - simple */

                  /* advance the on-card buffer pointer */
                  EN_WRAPADD(slot->start, slot->stop, cur, rest);
                  DBG(sc, TX, ("tx%d: adp %ju bytes %#jx (cur now 0x%x)",
                      tx->chan, (uintmax_t)rest, (uintmax_t)addr, cur));

                  PUT_DTQ_ENTRY(0, 0, rest, addr);

                  continue;
            }

            /*
             * do we need to do a DMA op to align to the maximum
             * burst? Note, that we are alway 32-bit aligned.
             */
            if (sc->alburst &&
                (needalign = (addr & sc->bestburstmask)) != 0) {
                  /* compute number of bytes, words and code */
                  cnt = sc->bestburstlen - needalign;
                  if (cnt > rest)
                        cnt = rest;
                  count = cnt / sizeof(uint32_t);
                  if (sc->noalbursts) {
                        bcode = MIDDMA_WORD;
                  } else {
                        bcode = en_dmaplan[count].bcode;
                        count = cnt >> en_dmaplan[count].divshift;
                  }
                  DO_DTQ("al_dma");
            }

            /* do we need to do a max-sized burst? */
            if (rest >= sc->bestburstlen) {
                  count = rest >> sc->bestburstshift;
                  cnt = count << sc->bestburstshift;
                  bcode = sc->bestburstcode;
                  DO_DTQ("best_dma");
            }

            /* do we need to do a cleanup burst? */
            if (rest != 0) {
                  cnt = rest;
                  count = rest / sizeof(uint32_t);
                  if (sc->noalbursts) {
                        bcode = MIDDMA_WORD;
                  } else {
                        bcode = en_dmaplan[count].bcode;
                        count = cnt >> en_dmaplan[count].divshift;
                  }
                  DO_DTQ("clean_dma");
            }
      }

      KASSERT (tx->flags & TX_HAS_PAD, ("PDU not padded"));

      if ((tx->flags & TX_AAL5) && !(tx->flags & TX_HAS_PDU)) {
            /*
             * Append the AAL5 PDU trailer
             */
            tmp = MID_PDU_MK1(0, 0, tx->datalen);
            en_write(sc, cur, tmp);
            EN_WRAPADD(slot->start, slot->stop, cur, 4);

            en_write(sc, cur, 0);
            EN_WRAPADD(slot->start, slot->stop, cur, 4);

            /* update DMA address */
            PUT_DTQ_ENTRY(0, MIDDMA_JK, WORD_IDX(slot->start, cur), 0);
      }

      /* record the end for the interrupt routine */
      sc->dtq[MID_DTQ_A2REG(last_dtq)] =
          EN_DQ_MK(tx->chan, tx->m->m_pkthdr.len);

      /* set the end flag in the last descriptor */
      en_write(sc, last_dtq + 0, SETQ_END(sc, en_read(sc, last_dtq + 0)));

#undef PUT_DTQ_ENTRY
#undef DO_DTQ

      /* commit */
      slot->cur = cur;
      sc->dtq_free = free;
      sc->dtq_us = dtq;

      /* tell card */
      en_write(sc, MID_DMA_WRTX, MID_DTQ_A2REG(sc->dtq_us));
}

/*
 * en_txdma: start transmit DMA on the given channel, if possible
 *
 * This is called from two places: when we got new packets from the upper
 * layer or when we found that buffer space has freed up during interrupt
 * processing.
 *
 * LOCK: locked, needed
 */
static void
en_txdma(struct en_softc *sc, struct en_txslot *slot)
{
      struct en_map *map;
      struct mbuf *lastm;
      struct txarg tx;
      u_int pad;
      int error;

      DBG(sc, TX, ("tx%td: starting ...", slot - sc->txslot));
  again:
      bzero(&tx, sizeof(tx));
      tx.chan = slot - sc->txslot;
      tx.sc = sc;

      /*
       * get an mbuf waiting for DMA
       */
      _IF_DEQUEUE(&slot->q, tx.m);
      if (tx.m == NULL) {
            DBG(sc, TX, ("tx%td: ...done!", slot - sc->txslot));
            return;
      }
      MBUF_GET_TX(tx.m, tx.vci, tx.flags, tx.datalen, pad, map);

      /*
       * note: don't use the entire buffer space.  if WRTX becomes equal
       * to RDTX, the transmitter stops assuming the buffer is empty!  --kjc
       */
      if (tx.m->m_pkthdr.len >= slot->bfree) {
            EN_COUNT(sc->stats.txoutspace);
            DBG(sc, TX, ("tx%td: out of transmit space", slot - sc->txslot));
            goto waitres;
      }
  
      lastm = NULL;
      if (!(tx.flags & TX_HAS_PAD)) {
            if (pad != 0) {
                  /* Append the padding buffer */
                  (void)m_length(tx.m, &lastm);
                  lastm->m_next = sc->padbuf;
                  sc->padbuf->m_len = pad;
            }
            tx.flags |= TX_HAS_PAD;
      }

      /*
       * Try to load that map
       */
      error = bus_dmamap_load_mbuf(sc->txtag, map->map, tx.m,
          en_txdma_load, &tx, BUS_DMA_NOWAIT);

      if (lastm != NULL)
            lastm->m_next = NULL;

      if (error != 0) {
            if_printf(sc->ifp, "loading TX map failed %d\n",
                error);
            goto dequeue_drop;
      }
      map->flags |= ENMAP_LOADED;
      if (tx.wait) {
            /* probably not enough space */
            bus_dmamap_unload(map->sc->txtag, map->map);
            map->flags &= ~ENMAP_LOADED;

            sc->need_dtqs = 1;
            DBG(sc, TX, ("tx%td: out of transmit DTQs", slot - sc->txslot));
            goto waitres;
      }

      EN_COUNT(sc->stats.launch);
      sc->ifp->if_opackets++;

      sc->vccs[tx.vci]->opackets++;
      sc->vccs[tx.vci]->obytes += tx.datalen;

#ifdef ENABLE_BPF
      if (bpf_peers_present(sc->ifp->if_bpf)) {
            /*
             * adjust the top of the mbuf to skip the TBD if present
             * before passing the packet to bpf.
             * Also remove padding and the PDU trailer. Assume both of
             * them to be in the same mbuf. pktlen, m_len and m_data
             * are not needed anymore so we can change them.
             */
            if (tx.flags & TX_HAS_TBD) {
                  tx.m->m_data += MID_TBD_SIZE;
                  tx.m->m_len -= MID_TBD_SIZE;
            }
            tx.m->m_pkthdr.len = m_length(tx.m, &lastm);
            if (tx.m->m_pkthdr.len > tx.datalen) {
                  lastm->m_len -= tx.m->m_pkthdr.len - tx.datalen;
                  tx.m->m_pkthdr.len = tx.datalen;
            }

            bpf_mtap(sc->ifp->if_bpf, tx.m);
      }
#endif

      /*
       * do some housekeeping and get the next packet
       */
      slot->bfree -= tx.m->m_pkthdr.len;
      _IF_ENQUEUE(&slot->indma, tx.m);

      goto again;

      /*
       * error handling. This is jumped to when we just want to drop
       * the packet. Must be unlocked here.
       */
  dequeue_drop:
      if (map != NULL)
            uma_zfree(sc->map_zone, map);

      slot->mbsize -= tx.m->m_pkthdr.len;

      m_freem(tx.m);

      goto again;

  waitres:
      _IF_PREPEND(&slot->q, tx.m);
}

/*
 * Create a copy of a single mbuf. It can have either internal or
 * external data, it may have a packet header. External data is really
 * copied, so the new buffer is writeable.
 *
 * LOCK: any, not needed
 */
static struct mbuf *
copy_mbuf(struct mbuf *m)
{
      struct mbuf *new;

      MGET(new, M_TRYWAIT, MT_DATA);
      if (new == NULL)
            return (NULL);

      if (m->m_flags & M_PKTHDR) {
            M_MOVE_PKTHDR(new, m);
            if (m->m_len > MHLEN) {
                  MCLGET(new, M_TRYWAIT);
                  if ((m->m_flags & M_EXT) == 0) {
                        m_free(new);
                        return (NULL);
                  }
            }
      } else {
            if (m->m_len > MLEN) {
                  MCLGET(new, M_TRYWAIT);
                  if ((m->m_flags & M_EXT) == 0) {
                        m_free(new);
                        return (NULL);
                  }
            }
      }

      bcopy(m->m_data, new->m_data, m->m_len);
      new->m_len = m->m_len;
      new->m_flags &= ~M_RDONLY;

      return (new);
}

/*
 * This function is called when we have an ENI adapter. It fixes the
 * mbuf chain, so that all addresses and lengths are 4 byte aligned.
 * The overall length is already padded to multiple of cells plus the
 * TBD so this must always succeed. The routine can fail, when it
 * needs to copy an mbuf (this may happen if an mbuf is readonly).
 *
 * We assume here, that aligning the virtual addresses to 4 bytes also
 * aligns the physical addresses.
 *
 * LOCK: locked, needed
 */
static struct mbuf *
en_fix_mchain(struct en_softc *sc, struct mbuf *m0, u_int *pad)
{
      struct mbuf **prev = &m0;
      struct mbuf *m = m0;
      struct mbuf *new;
      u_char *d;
      int off;

      while (m != NULL) {
            d = mtod(m, u_char *);
            if ((off = (uintptr_t)d % sizeof(uint32_t)) != 0) {
                  EN_COUNT(sc->stats.mfixaddr);
                  if (M_WRITABLE(m)) {
                        bcopy(d, d - off, m->m_len);
                        m->m_data -= off;
                  } else {
                        if ((new = copy_mbuf(m)) == NULL) {
                              EN_COUNT(sc->stats.mfixfail);
                              m_freem(m0);
                              return (NULL);
                        }
                        new->m_next = m_free(m);
                        *prev = m = new;
                  }
            }

            if ((off = m->m_len % sizeof(uint32_t)) != 0) {
                  EN_COUNT(sc->stats.mfixlen);
                  if (!M_WRITABLE(m)) {
                        if ((new = copy_mbuf(m)) == NULL) {
                              EN_COUNT(sc->stats.mfixfail);
                              m_freem(m0);
                              return (NULL);
                        }
                        new->m_next = m_free(m);
                        *prev = m = new;
                  }
                  d = mtod(m, u_char *) + m->m_len;
                  off = 4 - off;
                  while (off) {
                        while (m->m_next && m->m_next->m_len == 0)
                              m->m_next = m_free(m->m_next);

                        if (m->m_next == NULL) {
                              *d++ = 0;
                              KASSERT(*pad > 0, ("no padding space"));
                              (*pad)--;
                        } else {
                              *d++ = *mtod(m->m_next, u_char *);
                              m->m_next->m_len--;
                              m->m_next->m_data++;
                        }
                        m->m_len++;
                        off--;
                  }
            }

            prev = &m->m_next;
            m = m->m_next;
      }

      return (m0);
}

/*
 * en_start: start transmitting the next packet that needs to go out
 * if there is one. We take off all packets from the interface's queue and
 * put them into the channels queue.
 *
 * Here we also prepend the transmit packet descriptor and append the padding
 * and (for aal5) the PDU trailer. This is different from the original driver:
 * we assume, that allocating one or two additional mbufs is actually cheaper
 * than all this algorithmic fiddling we would need otherwise.
 *
 * While the packet is on the channels wait queue we use the csum_* fields
 * in the packet header to hold the original datalen, the AAL5 flag and the
 * VCI. The packet length field in the header holds the needed buffer space.
 * This may actually be more than the length of the current mbuf chain (when
 * one or more of TBD, padding and PDU do not fit).
 *
 * LOCK: unlocked, needed
 */
static void
en_start(struct ifnet *ifp)
{
      struct en_softc *sc = (struct en_softc *)ifp->if_softc;
      struct mbuf *m, *lastm;
      struct atm_pseudohdr *ap;
      u_int pad;        /* 0-bytes to pad at PDU end */
      u_int datalen;          /* length of user data */
      u_int vci;        /* the VCI we are transmitting on */
      u_int flags;
      uint32_t tbd[2];
      uint32_t pdu[2];
      struct en_vcc *vc;
      struct en_map *map;
      struct en_txslot *tx;

      while (1) {
            IF_DEQUEUE(&ifp->if_snd, m);
            if (m == NULL)
                  return;

            flags = 0;

            ap = mtod(m, struct atm_pseudohdr *);
            vci = ATM_PH_VCI(ap);

            if (ATM_PH_VPI(ap) != 0 || vci >= MID_N_VC ||
                (vc = sc->vccs[vci]) == NULL ||
                (vc->vflags & VCC_CLOSE_RX)) {
                  DBG(sc, TX, ("output vpi=%u, vci=%u -- drop",
                      ATM_PH_VPI(ap), vci));
                  m_freem(m);
                  continue;
            }
            if (vc->vcc.aal == ATMIO_AAL_5)
                  flags |= TX_AAL5;
            m_adj(m, sizeof(struct atm_pseudohdr));

            /*
             * (re-)calculate size of packet (in bytes)
             */
            m->m_pkthdr.len = datalen = m_length(m, &lastm);

            /*
             * computing how much padding we need on the end of the mbuf,
             * then see if we can put the TBD at the front of the mbuf
             * where the link header goes (well behaved protocols will
             * reserve room for us). Last, check if room for PDU tail.
             */
            if (flags & TX_AAL5)
                  m->m_pkthdr.len += MID_PDU_SIZE;
            m->m_pkthdr.len = roundup(m->m_pkthdr.len, MID_ATMDATASZ);
            pad = m->m_pkthdr.len - datalen;
            if (flags & TX_AAL5)
                  pad -= MID_PDU_SIZE;
            m->m_pkthdr.len += MID_TBD_SIZE;

            DBG(sc, TX, ("txvci%d: buflen=%u datalen=%u lead=%d trail=%d",
                vci, m->m_pkthdr.len, datalen, (int)M_LEADINGSPACE(m),
                (int)M_TRAILINGSPACE(lastm)));

            /*
             * From here on we need access to sc
             */
            EN_LOCK(sc);

            /*
             * Allocate a map. We do this here rather then in en_txdma,
             * because en_txdma is also called from the interrupt handler
             * and we are going to have a locking problem then. We must
             * use NOWAIT here, because the ip_output path holds various
             * locks.
             */
            map = uma_zalloc_arg(sc->map_zone, sc, M_NOWAIT);
            if (map == NULL) {
                  /* drop that packet */
                  EN_COUNT(sc->stats.txnomap);
                  EN_UNLOCK(sc);
                  m_freem(m);
                  continue;
            }

            if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) {
                  EN_UNLOCK(sc);
                  uma_zfree(sc->map_zone, map);
                  m_freem(m);
                  continue;
            }

            /*
             * Look, whether we can prepend the TBD (8 byte)
             */
            if (M_WRITABLE(m) && M_LEADINGSPACE(m) >= MID_TBD_SIZE) {
                  tbd[0] = htobe32(MID_TBD_MK1((flags & TX_AAL5) ?
                      MID_TBD_AAL5 : MID_TBD_NOAAL5,
                      vc->txspeed, m->m_pkthdr.len / MID_ATMDATASZ));
                  tbd[1] = htobe32(MID_TBD_MK2(vci, 0, 0));

                  m->m_data -= MID_TBD_SIZE;
                  bcopy(tbd, m->m_data, MID_TBD_SIZE);
                  m->m_len += MID_TBD_SIZE;
                  flags |= TX_HAS_TBD;
            }

            /*
             * Check whether the padding fits (must be writeable -
             * we pad with zero).
             */
            if (M_WRITABLE(lastm) && M_TRAILINGSPACE(lastm) >= pad) {
                  bzero(lastm->m_data + lastm->m_len, pad);
                  lastm->m_len += pad;
                  flags |= TX_HAS_PAD;

                  if ((flags & TX_AAL5) &&
                      M_TRAILINGSPACE(lastm) > MID_PDU_SIZE) {
                        pdu[0] = htobe32(MID_PDU_MK1(0, 0, datalen));
                        pdu[1] = 0;
                        bcopy(pdu, lastm->m_data + lastm->m_len,
                            MID_PDU_SIZE);
                        lastm->m_len += MID_PDU_SIZE;
                        flags |= TX_HAS_PDU;
                  }
            }

            if (!sc->is_adaptec &&
                (m = en_fix_mchain(sc, m, &pad)) == NULL) {
                  EN_UNLOCK(sc);
                  uma_zfree(sc->map_zone, map);
                  continue;
            }

            /*
             * get assigned channel (will be zero unless txspeed is set)
             */
            tx = vc->txslot;

            if (m->m_pkthdr.len > EN_TXSZ * 1024) {
                  DBG(sc, TX, ("tx%zu: packet larger than xmit buffer "
                      "(%d > %d)\n", tx - sc->txslot, m->m_pkthdr.len,
                      EN_TXSZ * 1024));
                  EN_UNLOCK(sc);
                  m_freem(m);
                  uma_zfree(sc->map_zone, map);
                  continue;
            }

            if (tx->mbsize > EN_TXHIWAT) {
                  EN_COUNT(sc->stats.txmbovr);
                  DBG(sc, TX, ("tx%d: buffer space shortage",
                      tx - sc->txslot));
                  EN_UNLOCK(sc);
                  m_freem(m);
                  uma_zfree(sc->map_zone, map);
                  continue;
            }

            /* commit */
            tx->mbsize += m->m_pkthdr.len;

            DBG(sc, TX, ("tx%zu: VCI=%d, speed=0x%x, buflen=%d, mbsize=%d",
                tx - sc->txslot, vci, sc->vccs[vci]->txspeed,
                m->m_pkthdr.len, tx->mbsize));

            MBUF_SET_TX(m, vci, flags, datalen, pad, map);

            _IF_ENQUEUE(&tx->q, m);

            en_txdma(sc, tx);

            EN_UNLOCK(sc);
      }
}

/*********************************************************************/
/*
 * VCs
 */

/*
 * en_loadvc: load a vc tab entry from a slot
 *
 * LOCK: locked, needed
 */
static void
en_loadvc(struct en_softc *sc, struct en_vcc *vc)
{
      uint32_t reg = en_read(sc, MID_VC(vc->vcc.vci));

      reg = MIDV_SETMODE(reg, MIDV_TRASH);
      en_write(sc, MID_VC(vc->vcc.vci), reg);
      DELAY(27);

      /* no need to set CRC */

      /* read pointer = 0, desc. start = 0 */
      en_write(sc, MID_DST_RP(vc->vcc.vci), 0);
      /* write pointer = 0 */
      en_write(sc, MID_WP_ST_CNT(vc->vcc.vci), 0);
      /* set mode, size, loc */
      en_write(sc, MID_VC(vc->vcc.vci), vc->rxslot->mode);

      vc->rxslot->cur = vc->rxslot->start;

      DBG(sc, VC, ("rx%d: assigned to VCI %d", vc->rxslot - sc->rxslot,
          vc->vcc.vci));
}

/*
 * Open the given vcc.
 *
 * LOCK: unlocked, needed
 */
static int
en_open_vcc(struct en_softc *sc, struct atmio_openvcc *op)
{
      uint32_t oldmode, newmode;
      struct en_rxslot *slot;
      struct en_vcc *vc;
      int error = 0;

      DBG(sc, IOCTL, ("enable vpi=%d, vci=%d, flags=%#x",
          op->param.vpi, op->param.vci, op->param.flags));

      if (op->param.vpi != 0 || op->param.vci >= MID_N_VC)
            return (EINVAL);

      vc = uma_zalloc(en_vcc_zone, M_NOWAIT | M_ZERO);
      if (vc == NULL)
            return (ENOMEM);

      EN_LOCK(sc);

      if (sc->vccs[op->param.vci] != NULL) {
            error = EBUSY;
            goto done;
      }

      /* find a free receive slot */
      for (slot = sc->rxslot; slot < &sc->rxslot[sc->en_nrx]; slot++)
            if (slot->vcc == NULL)
                  break;
      if (slot == &sc->rxslot[sc->en_nrx]) {
            error = ENOSPC;
            goto done;
      }

      vc->rxslot = slot;
      vc->rxhand = op->rxhand;
      vc->vcc = op->param;

      oldmode = slot->mode;
      newmode = (op->param.aal == ATMIO_AAL_5) ? MIDV_AAL5 : MIDV_NOAAL;
      slot->mode = MIDV_SETMODE(oldmode, newmode);
      slot->vcc = vc;

      KASSERT (_IF_QLEN(&slot->indma) == 0 && _IF_QLEN(&slot->q) == 0,
          ("en_rxctl: left over mbufs on enable slot=%tu",
          vc->rxslot - sc->rxslot));

      vc->txspeed = 0;
      vc->txslot = sc->txslot;
      vc->txslot->nref++;     /* bump reference count */

      en_loadvc(sc, vc);      /* does debug printf for us */

      /* don't free below */
      sc->vccs[vc->vcc.vci] = vc;
      vc = NULL;
      sc->vccs_open++;

  done:
      if (vc != NULL)
            uma_zfree(en_vcc_zone, vc);

      EN_UNLOCK(sc);
      return (error);
}

/*
 * Close finished
 */
static void
en_close_finish(struct en_softc *sc, struct en_vcc *vc)
{

      if (vc->rxslot != NULL)
            vc->rxslot->vcc = NULL;

      DBG(sc, VC, ("vci: %u free (%p)", vc->vcc.vci, vc));

      sc->vccs[vc->vcc.vci] = NULL;
      uma_zfree(en_vcc_zone, vc);
      sc->vccs_open--;
}

/*
 * LOCK: unlocked, needed
 */
static int
en_close_vcc(struct en_softc *sc, struct atmio_closevcc *cl)
{
      uint32_t oldmode, newmode;
      struct en_vcc *vc;
      int error = 0;

      DBG(sc, IOCTL, ("disable vpi=%d, vci=%d", cl->vpi, cl->vci));

      if (cl->vpi != 0 || cl->vci >= MID_N_VC)
            return (EINVAL);

      EN_LOCK(sc);
      if ((vc = sc->vccs[cl->vci]) == NULL) {
            error = ENOTCONN;
            goto done;
      }

      /*
       * turn off VCI
       */
      if (vc->rxslot == NULL) {
            error = ENOTCONN;
            goto done;
      }
      if (vc->vflags & VCC_DRAIN) {
            error = EINVAL;
            goto done;
      }

      oldmode = en_read(sc, MID_VC(cl->vci));
      newmode = MIDV_SETMODE(oldmode, MIDV_TRASH) & ~MIDV_INSERVICE;
      en_write(sc, MID_VC(cl->vci), (newmode | (oldmode & MIDV_INSERVICE)));

      /* halt in tracks, be careful to preserve inservice bit */
      DELAY(27);
      vc->rxslot->mode = newmode;

      vc->txslot->nref--;

      /* if stuff is still going on we are going to have to drain it out */
      if (_IF_QLEN(&vc->rxslot->indma) == 0 &&
          _IF_QLEN(&vc->rxslot->q) == 0 &&
          (vc->vflags & VCC_SWSL) == 0) {
            en_close_finish(sc, vc);
            goto done;
      }

      vc->vflags |= VCC_DRAIN;
      DBG(sc, IOCTL, ("VCI %u now draining", cl->vci));

      if (vc->vcc.flags & ATMIO_FLAG_ASYNC)
            goto done;

      vc->vflags |= VCC_CLOSE_RX;
      while ((sc->ifp->if_drv_flags & IFF_DRV_RUNNING) &&
          (vc->vflags & VCC_DRAIN))
            cv_wait(&sc->cv_close, &sc->en_mtx);

      en_close_finish(sc, vc);
      if (!(sc->ifp->if_drv_flags & IFF_DRV_RUNNING)) {
            error = EIO;
            goto done;
      }


  done:
      EN_UNLOCK(sc);
      return (error);
}

/*********************************************************************/
/*
 * starting/stopping the card
 */

/*
 * en_reset_ul: reset the board, throw away work in progress.
 * must en_init to recover.
 *
 * LOCK: locked, needed
 */
static void
en_reset_ul(struct en_softc *sc)
{
      struct en_map *map;
      struct mbuf *m;
      struct en_rxslot *rx;
      int lcv;

      if_printf(sc->ifp, "reset\n");
      sc->ifp->if_drv_flags &= ~IFF_DRV_RUNNING;

      if (sc->en_busreset)
            sc->en_busreset(sc);
      en_write(sc, MID_RESID, 0x0); /* reset hardware */

      /*
       * recv: dump any mbufs we are dma'ing into, if DRAINing, then a reset
       * will free us! Don't release the rxslot from the channel.
       */
      for (lcv = 0 ; lcv < MID_N_VC ; lcv++) {
            if (sc->vccs[lcv] == NULL)
                  continue;
            rx = sc->vccs[lcv]->rxslot;

            for (;;) {
                  _IF_DEQUEUE(&rx->indma, m);
                  if (m == NULL)
                        break;
                  map = (void *)m->m_pkthdr.rcvif;
                  uma_zfree(sc->map_zone, map);
                  m_freem(m);
            }
            for (;;) {
                  _IF_DEQUEUE(&rx->q, m);
                  if (m == NULL)
                        break;
                  m_freem(m);
            }
            sc->vccs[lcv]->vflags = 0;
      }

      /*
       * xmit: dump everything
       */
      for (lcv = 0 ; lcv < EN_NTX ; lcv++) {
            for (;;) {
                  _IF_DEQUEUE(&sc->txslot[lcv].indma, m);
                  if (m == NULL)
                        break;
                  map = (void *)m->m_pkthdr.rcvif;
                  uma_zfree(sc->map_zone, map);
                  m_freem(m);
            }
            for (;;) {
                  _IF_DEQUEUE(&sc->txslot[lcv].q, m);
                  if (m == NULL)
                        break;
                  map = (void *)m->m_pkthdr.rcvif;
                  uma_zfree(sc->map_zone, map);
                  m_freem(m);
            }
            sc->txslot[lcv].mbsize = 0;
      }

      /*
       * Unstop all waiters
       */
      cv_broadcast(&sc->cv_close);
}

/*
 * en_reset: reset the board, throw away work in progress.
 * must en_init to recover.
 *
 * LOCK: unlocked, needed
 *
 * Use en_reset_ul if you alreay have the lock
 */
void
en_reset(struct en_softc *sc)
{
      EN_LOCK(sc);
      en_reset_ul(sc);
      EN_UNLOCK(sc);
}


/*
 * en_init: init board and sync the card with the data in the softc.
 *
 * LOCK: locked, needed
 */
static void
en_init(struct en_softc *sc)
{
      int vc, slot;
      uint32_t loc;

      if ((sc->ifp->if_flags & IFF_UP) == 0) {
            DBG(sc, INIT, ("going down"));
            en_reset(sc);                       /* to be safe */
            return;
      }

      DBG(sc, INIT, ("going up"));
      sc->ifp->if_drv_flags |= IFF_DRV_RUNNING; /* enable */

      if (sc->en_busreset)
            sc->en_busreset(sc);
      en_write(sc, MID_RESID, 0x0);       /* reset */

      /* zero memory */
      bus_space_set_region_4(sc->en_memt, sc->en_base,
          MID_RAMOFF, 0, sc->en_obmemsz / 4);

      /*
       * init obmem data structures: vc tab, dma q's, slist.
       *
       * note that we set drq_free/dtq_free to one less than the total number
       * of DTQ/DRQs present.   we do this because the card uses the condition
       * (drq_chip == drq_us) to mean "list is empty"... but if you allow the
       * circular list to be completely full then (drq_chip == drq_us) [i.e.
       * the drq_us pointer will wrap all the way around].   by restricting
       * the number of active requests to (N - 1) we prevent the list from
       * becoming completely full.    note that the card will sometimes give
       * us an interrupt for a DTQ/DRQ we have already processes... this helps
       * keep that interrupt from messing us up.
       */
      bzero(&sc->drq, sizeof(sc->drq));
      sc->drq_free = MID_DRQ_N - 1;
      sc->drq_chip = MID_DRQ_REG2A(en_read(sc, MID_DMA_RDRX));
      en_write(sc, MID_DMA_WRRX, MID_DRQ_A2REG(sc->drq_chip)); 
      sc->drq_us = sc->drq_chip;

      bzero(&sc->dtq, sizeof(sc->dtq));
      sc->dtq_free = MID_DTQ_N - 1;
      sc->dtq_chip = MID_DTQ_REG2A(en_read(sc, MID_DMA_RDTX));
      en_write(sc, MID_DMA_WRTX, MID_DRQ_A2REG(sc->dtq_chip)); 
      sc->dtq_us = sc->dtq_chip;

      sc->hwslistp = MID_SL_REG2A(en_read(sc, MID_SERV_WRITE));
      sc->swsl_size = sc->swsl_head = sc->swsl_tail = 0;

      DBG(sc, INIT, ("drq free/chip: %d/0x%x, dtq free/chip: %d/0x%x, "
          "hwslist: 0x%x", sc->drq_free, sc->drq_chip, sc->dtq_free,
          sc->dtq_chip, sc->hwslistp));

      for (slot = 0 ; slot < EN_NTX ; slot++) {
            sc->txslot[slot].bfree = EN_TXSZ * 1024;
            en_write(sc, MIDX_READPTR(slot), 0);
            en_write(sc, MIDX_DESCSTART(slot), 0);
            loc = sc->txslot[slot].cur = sc->txslot[slot].start;
            loc = loc - MID_RAMOFF;
            /* mask, cvt to words */
            loc = (loc & ~((EN_TXSZ * 1024) - 1)) >> 2;
            /* top 11 bits */
            loc = loc >> MIDV_LOCTOPSHFT;
            en_write(sc, MIDX_PLACE(slot), MIDX_MKPLACE(en_k2sz(EN_TXSZ),
                loc));
            DBG(sc, INIT, ("tx%d: place 0x%x", slot,
                (u_int)en_read(sc, MIDX_PLACE(slot))));
      }

      for (vc = 0; vc < MID_N_VC; vc++) 
            if (sc->vccs[vc] != NULL)
                  en_loadvc(sc, sc->vccs[vc]);

      /*
       * enable!
       */
      en_write(sc, MID_INTENA, MID_INT_TX | MID_INT_DMA_OVR | MID_INT_IDENT |
          MID_INT_LERR | MID_INT_DMA_ERR | MID_INT_DMA_RX | MID_INT_DMA_TX |
          MID_INT_SERVICE | MID_INT_SUNI | MID_INT_STATS);
      en_write(sc, MID_MAST_CSR, MID_SETIPL(sc->ipl) | MID_MCSR_ENDMA |
          MID_MCSR_ENTX | MID_MCSR_ENRX);
}

/*********************************************************************/
/*
 * Ioctls
 */
/*
 * en_ioctl: handle ioctl requests
 *
 * NOTE: if you add an ioctl to set txspeed, you should choose a new
 * TX channel/slot.   Choose the one with the lowest sc->txslot[slot].nref
 * value, subtract one from sc->txslot[0].nref, add one to the
 * sc->txslot[slot].nref, set sc->txvc2slot[vci] = slot, and then set
 * txspeed[vci].
 *
 * LOCK: unlocked, needed
 */
static int
en_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
{
      struct en_softc *sc = (struct en_softc *)ifp->if_softc;
      struct ifaddr *ifa = (struct ifaddr *)data;
      struct ifreq *ifr = (struct ifreq *)data;
      struct atmio_vcctable *vtab;
      int error = 0;

      switch (cmd) {

        case SIOCSIFADDR: 
            EN_LOCK(sc);
            ifp->if_flags |= IFF_UP;
#if defined(INET) || defined(INET6)
            if (ifa->ifa_addr->sa_family == AF_INET
                || ifa->ifa_addr->sa_family == AF_INET6) {
                  if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) {
                        en_reset_ul(sc);
                        en_init(sc);
                  }
                  ifa->ifa_rtrequest = atm_rtrequest; /* ??? */
                  EN_UNLOCK(sc);
                  break;
            }
#endif /* INET */
            if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) {
                  en_reset_ul(sc);
                  en_init(sc);
            }
            EN_UNLOCK(sc);
            break;

      case SIOCSIFFLAGS: 
            EN_LOCK(sc);
            if (ifp->if_flags & IFF_UP) {
                  if (!(ifp->if_drv_flags & IFF_DRV_RUNNING))
                        en_init(sc);
            } else {
                  if (ifp->if_drv_flags & IFF_DRV_RUNNING)
                        en_reset_ul(sc);
            }
            EN_UNLOCK(sc);
            break;

        case SIOCSIFMTU:
            /*
             * Set the interface MTU.
             */
            if (ifr->ifr_mtu > ATMMTU) {
                  error = EINVAL;
                  break;
            }
            ifp->if_mtu = ifr->ifr_mtu;
            break;

        case SIOCSIFMEDIA:
        case SIOCGIFMEDIA:
            error = ifmedia_ioctl(ifp, ifr, &sc->media, cmd);
            break;

        case SIOCATMOPENVCC:        /* kernel internal use */
            error = en_open_vcc(sc, (struct atmio_openvcc *)data);
            break;

        case SIOCATMCLOSEVCC:       /* kernel internal use */
            error = en_close_vcc(sc, (struct atmio_closevcc *)data);
            break;

        case SIOCATMGETVCCS:  /* internal netgraph use */
            vtab = atm_getvccs((struct atmio_vcc **)sc->vccs,
                MID_N_VC, sc->vccs_open, &sc->en_mtx, 0);
            if (vtab == NULL) {
                  error = ENOMEM;
                  break;
            }
            *(void **)data = vtab;
            break;

        case SIOCATMGVCCS:    /* return vcc table */
            vtab = atm_getvccs((struct atmio_vcc **)sc->vccs,
                MID_N_VC, sc->vccs_open, &sc->en_mtx, 1);
            error = copyout(vtab, ifr->ifr_data, sizeof(*vtab) +
                vtab->count * sizeof(vtab->vccs[0]));
            free(vtab, M_DEVBUF);
            break;

        default: 
            error = EINVAL;
            break;
      }
      return (error);
}

/*********************************************************************/
/*
 * Sysctl's
 */

/*
 * Sysctl handler for internal statistics
 *
 * LOCK: unlocked, needed
 */
static int
en_sysctl_istats(SYSCTL_HANDLER_ARGS)
{
      struct en_softc *sc = arg1;
      uint32_t *ret;
      int error;

      ret = malloc(sizeof(sc->stats), M_TEMP, M_WAITOK);

      EN_LOCK(sc);
      bcopy(&sc->stats, ret, sizeof(sc->stats));
      EN_UNLOCK(sc);

      error = SYSCTL_OUT(req, ret, sizeof(sc->stats));
      free(ret, M_TEMP);

      return (error);
}

/*********************************************************************/
/*
 * Interrupts
 */

/*
 * Transmit interrupt handler
 *
 * check for tx complete, if detected then this means that some space
 * has come free on the card.   we must account for it and arrange to
 * kick the channel to life (in case it is stalled waiting on the card).
 *
 * LOCK: locked, needed
 */
static uint32_t
en_intr_tx(struct en_softc *sc, uint32_t reg)
{
      uint32_t kick;
      uint32_t mask;
      uint32_t val;
      int chan;

      kick = 0;         /* bitmask of channels to kick */

      for (mask = 1, chan = 0; chan < EN_NTX; chan++, mask *= 2) {
            if (!(reg & MID_TXCHAN(chan)))
                  continue;

            kick = kick | mask;

            /* current read pointer */
            val = en_read(sc, MIDX_READPTR(chan));
            /* as offset */
            val = (val * sizeof(uint32_t)) + sc->txslot[chan].start;
            if (val > sc->txslot[chan].cur)
                  sc->txslot[chan].bfree = val - sc->txslot[chan].cur;
            else
                  sc->txslot[chan].bfree = (val + (EN_TXSZ * 1024)) -
                      sc->txslot[chan].cur;
            DBG(sc, INTR, ("tx%d: transmit done. %d bytes now free in "
                "buffer", chan, sc->txslot[chan].bfree));
      }
      return (kick);
}

/*
 * TX DMA interrupt
 *
 * check for TX DMA complete, if detected then this means
 * that some DTQs are now free.   it also means some indma
 * mbufs can be freed. if we needed DTQs, kick all channels.
 *
 * LOCK: locked, needed
 */
static uint32_t
en_intr_tx_dma(struct en_softc *sc)
{
      uint32_t kick = 0;
      uint32_t val;
      uint32_t idx;
      uint32_t slot;
      uint32_t dtq;
      struct en_map *map;
      struct mbuf *m;

      val = en_read(sc, MID_DMA_RDTX);    /* chip's current location */
      idx = MID_DTQ_A2REG(sc->dtq_chip);  /* where we last saw chip */

      if (sc->need_dtqs) {
            kick = MID_NTX_CH - 1;  /* assume power of 2, kick all! */
            sc->need_dtqs = 0;      /* recalculated in "kick" loop below */
            DBG(sc, INTR, ("cleared need DTQ condition"));
      }

      while (idx != val) {
            sc->dtq_free++;
            if ((dtq = sc->dtq[idx]) != 0) {
                  /* don't forget to zero it out when done */
                  sc->dtq[idx] = 0;
                  slot = EN_DQ_SLOT(dtq);

                  _IF_DEQUEUE(&sc->txslot[slot].indma, m);
                  if (m == NULL)
                        panic("enintr: dtqsync");
                  map = (void *)m->m_pkthdr.rcvif;
                  uma_zfree(sc->map_zone, map);
                  m_freem(m);

                  sc->txslot[slot].mbsize -= EN_DQ_LEN(dtq);
                  DBG(sc, INTR, ("tx%d: free %d dma bytes, mbsize now "
                      "%d", slot, EN_DQ_LEN(dtq), 
                      sc->txslot[slot].mbsize));
            }
            EN_WRAPADD(0, MID_DTQ_N, idx, 1);
      }
      sc->dtq_chip = MID_DTQ_REG2A(val);  /* sync softc */

      return (kick);
}

/*
 * Service interrupt
 *
 * LOCK: locked, needed
 */
static int
en_intr_service(struct en_softc *sc)
{
      uint32_t chip;
      uint32_t vci;
      int need_softserv = 0;
      struct en_vcc *vc;

      chip = MID_SL_REG2A(en_read(sc, MID_SERV_WRITE));

      while (sc->hwslistp != chip) {
            /* fetch and remove it from hardware service list */
            vci = en_read(sc, sc->hwslistp);
            EN_WRAPADD(MID_SLOFF, MID_SLEND, sc->hwslistp, 4);

            if ((vc = sc->vccs[vci]) == NULL ||
                (vc->vcc.flags & ATMIO_FLAG_NORX)) {
                  DBG(sc, INTR, ("unexpected rx interrupt VCI %d", vci));
                  en_write(sc, MID_VC(vci), MIDV_TRASH);  /* rx off */
                  continue;
            }

            /* remove from hwsl */
            en_write(sc, MID_VC(vci), vc->rxslot->mode);
            EN_COUNT(sc->stats.hwpull);

            DBG(sc, INTR, ("pulled VCI %d off hwslist", vci));

            /* add it to the software service list (if needed) */
            if ((vc->vflags & VCC_SWSL) == 0) {
                  EN_COUNT(sc->stats.swadd);
                  need_softserv = 1;
                  vc->vflags |= VCC_SWSL;
                  sc->swslist[sc->swsl_tail] = vci;
                  EN_WRAPADD(0, MID_SL_N, sc->swsl_tail, 1);
                  sc->swsl_size++;
                  DBG(sc, INTR, ("added VCI %d to swslist", vci));
            }
      }
      return (need_softserv);
}

/*
 * Handle a receive DMA completion
 */
static void
en_rx_drain(struct en_softc *sc, u_int drq)
{
      struct en_rxslot *slot;
      struct en_vcc *vc;
      struct mbuf *m;
      struct atm_pseudohdr ah;

      slot = &sc->rxslot[EN_DQ_SLOT(drq)];

      m = NULL;   /* assume "JK" trash DMA */
      if (EN_DQ_LEN(drq) != 0) {
            _IF_DEQUEUE(&slot->indma, m);
            KASSERT(m != NULL, ("drqsync: %s: lost mbuf in slot %zu!",
                sc->ifp->if_xname, slot - sc->rxslot));
            uma_zfree(sc->map_zone, (struct en_map *)m->m_pkthdr.rcvif);
      }
      if ((vc = slot->vcc) == NULL) {
            /* ups */
            if (m != NULL)
                  m_freem(m);
            return;
      }

      /* do something with this mbuf */
      if (vc->vflags & VCC_DRAIN) {
            /* drain? */
            if (m != NULL)
                  m_freem(m);
            if (_IF_QLEN(&slot->indma) == 0 && _IF_QLEN(&slot->q) == 0 &&
                (en_read(sc, MID_VC(vc->vcc.vci)) & MIDV_INSERVICE) == 0 &&
                (vc->vflags & VCC_SWSL) == 0) {
                  vc->vflags &= ~VCC_CLOSE_RX;
                  if (vc->vcc.flags & ATMIO_FLAG_ASYNC)
                        en_close_finish(sc, vc);
                  else
                        cv_signal(&sc->cv_close);
            }
            return;
      }

      if (m != NULL) {
            ATM_PH_FLAGS(&ah) = vc->vcc.flags;
            ATM_PH_VPI(&ah) = 0;
            ATM_PH_SETVCI(&ah, vc->vcc.vci);

            DBG(sc, INTR, ("rx%zu: rxvci%d: atm_input, mbuf %p, len %d, "
                "hand %p", slot - sc->rxslot, vc->vcc.vci, m,
                EN_DQ_LEN(drq), vc->rxhand));

            m->m_pkthdr.rcvif = sc->ifp;
            sc->ifp->if_ipackets++;

            vc->ipackets++;
            vc->ibytes += m->m_pkthdr.len;

#ifdef EN_DEBUG
            if (sc->debug & DBG_IPACKETS)
                  en_dump_packet(sc, m);
#endif
#ifdef ENABLE_BPF
            BPF_MTAP(sc->ifp, m);
#endif
            atm_input(sc->ifp, &ah, m, vc->rxhand);
      }
}

/*
 * check for RX DMA complete, and pass the data "upstairs"
 *
 * LOCK: locked, needed
 */
static int
en_intr_rx_dma(struct en_softc *sc)
{
      uint32_t val;
      uint32_t idx;
      uint32_t drq;

      val = en_read(sc, MID_DMA_RDRX);    /* chip's current location */
      idx = MID_DRQ_A2REG(sc->drq_chip);  /* where we last saw chip */

      while (idx != val) {
            sc->drq_free++;
            if ((drq = sc->drq[idx]) != 0) {
                  /* don't forget to zero it out when done */
                  sc->drq[idx] = 0;
                  en_rx_drain(sc, drq);
            }
            EN_WRAPADD(0, MID_DRQ_N, idx, 1);
      }
      sc->drq_chip = MID_DRQ_REG2A(val);  /* sync softc */

      if (sc->need_drqs) {
            /* true if we had a DRQ shortage */
            sc->need_drqs = 0;
            DBG(sc, INTR, ("cleared need DRQ condition"));
            return (1);
      } else
            return (0);
}

/*
 * en_mget: get an mbuf chain that can hold totlen bytes and return it
 * (for recv). For the actual allocation totlen is rounded up to a multiple
 * of 4. We also ensure, that each mbuf has a multiple of 4 bytes.
 *
 * After this call the sum of all the m_len's in the chain will be totlen.
 * This is called at interrupt time, so we can't wait here.
 *
 * LOCK: any, not needed
 */
static struct mbuf *
en_mget(struct en_softc *sc, u_int pktlen)
{
      struct mbuf *m, *tmp;
      u_int totlen, pad;

      totlen = roundup(pktlen, sizeof(uint32_t));
      pad = totlen - pktlen;

      /*
       * First get an mbuf with header. Keep space for a couple of
       * words at the begin.
       */
      /* called from interrupt context */
      MGETHDR(m, M_DONTWAIT, MT_DATA);
      if (m == NULL)
            return (NULL);

      m->m_pkthdr.rcvif = NULL;
      m->m_pkthdr.len = pktlen;
      m->m_len = EN_RX1BUF;
      MH_ALIGN(m, EN_RX1BUF);
      if (m->m_len >= totlen) {
            m->m_len = totlen;

      } else {
            totlen -= m->m_len;

            /* called from interrupt context */
            tmp = m_getm(m, totlen, M_DONTWAIT, MT_DATA);
            if (tmp == NULL) {
                  m_free(m);
                  return (NULL);
            }
            tmp = m->m_next;
            /* m_getm could do this for us */
            while (tmp != NULL) {
                  tmp->m_len = min(MCLBYTES, totlen);
                  totlen -= tmp->m_len;
                  tmp = tmp->m_next;
            }
      }

      return (m);
}

/*
 * Argument for RX DMAMAP loader.
 */
struct rxarg {
      struct en_softc *sc;
      struct mbuf *m;
      u_int pre_skip;         /* number of bytes to skip at begin */
      u_int post_skip;  /* number of bytes to skip at end */
      struct en_vcc *vc;      /* vc we are receiving on */
      int wait;         /* wait for DRQ entries */
};

/*
 * Copy the segment table to the buffer for later use. And compute the
 * number of dma queue entries we need.
 *
 * LOCK: locked, needed
 */
static void
en_rxdma_load(void *uarg, bus_dma_segment_t *segs, int nseg,
    bus_size_t mapsize, int error)
{
      struct rxarg *rx = uarg;
      struct en_softc *sc = rx->sc;
      struct en_rxslot *slot = rx->vc->rxslot;
      u_int       free;       /* number of free DRQ entries */
      uint32_t    cur;        /* current buffer offset */
      uint32_t    drq;        /* DRQ entry pointer */
      uint32_t    last_drq;   /* where we have written last */
      u_int       needalign, cnt, count, bcode;
      bus_addr_t  addr;
      bus_size_t  rest;
      int         i;

      if (error != 0)
            return;
      if (nseg > EN_MAX_DMASEG)
            panic("too many DMA segments");

      rx->wait = 0;

      free = sc->drq_free;
      drq = sc->drq_us;
      cur = slot->cur;

      last_drq = 0;

      /*
       * Local macro to add an entry to the receive DMA area. If there
       * are no entries left, return. Save the byte offset of the entry
       * in last_drq for later use.
       */
#define PUT_DRQ_ENTRY(ENI, BCODE, COUNT, ADDR)                    \
      if (free == 0) {                                \
            EN_COUNT(sc->stats.rxdrqout);                   \
            rx->wait = 1;                                   \
            return;                                         \
      }                                               \
      last_drq = drq;                                       \
      en_write(sc, drq + 0, (ENI || !sc->is_adaptec) ?            \
          MID_MK_RXQ_ENI(COUNT, rx->vc->vcc.vci, 0, BCODE) :            \
          MID_MK_RXQ_ADP(COUNT, rx->vc->vcc.vci, 0, BCODE));            \
      en_write(sc, drq + 4, ADDR);                          \
                                                      \
      EN_WRAPADD(MID_DRQOFF, MID_DRQEND, drq, 8);                 \
      free--;

      /*
       * Local macro to generate a DMA entry to DMA cnt bytes. Updates
       * the current buffer byte offset accordingly.
       */
#define DO_DRQ(TYPE) do {                                   \
      rest -= cnt;                                          \
      EN_WRAPADD(slot->start, slot->stop, cur, cnt);              \
      DBG(sc, SERV, ("rx%td: "TYPE" %u bytes, %ju left, cur %#x", \
          slot - sc->rxslot, cnt, (uintmax_t)rest, cur));         \
                                                      \
      PUT_DRQ_ENTRY(1, bcode, count, addr);                       \
                                                      \
      addr += cnt;                                          \
    } while (0)

      /*
       * Skip the RBD at the beginning
       */
      if (rx->pre_skip > 0) {
            /* update DMA address */
            EN_WRAPADD(slot->start, slot->stop, cur, rx->pre_skip);

            PUT_DRQ_ENTRY(0, MIDDMA_JK, WORD_IDX(slot->start, cur), 0);
      }

      for (i = 0; i < nseg; i++, segs++) {
            addr = segs->ds_addr;
            rest = segs->ds_len;

            if (sc->is_adaptec) {
                  /* adaptec card - simple */

                  /* advance the on-card buffer pointer */
                  EN_WRAPADD(slot->start, slot->stop, cur, rest);
                  DBG(sc, SERV, ("rx%td: adp %ju bytes %#jx "
                      "(cur now 0x%x)", slot - sc->rxslot,
                      (uintmax_t)rest, (uintmax_t)addr, cur));

                  PUT_DRQ_ENTRY(0, 0, rest, addr);

                  continue;
            }

            /*
             * do we need to do a DMA op to align to the maximum
             * burst? Note, that we are alway 32-bit aligned.
             */
            if (sc->alburst &&
                (needalign = (addr & sc->bestburstmask)) != 0) {
                  /* compute number of bytes, words and code */
                  cnt = sc->bestburstlen - needalign;
                  if (cnt > rest)
                        cnt = rest;
                  count = cnt / sizeof(uint32_t);
                  if (sc->noalbursts) {
                        bcode = MIDDMA_WORD;
                  } else {
                        bcode = en_dmaplan[count].bcode;
                        count = cnt >> en_dmaplan[count].divshift;
                  }
                  DO_DRQ("al_dma");
            }

            /* do we need to do a max-sized burst? */
            if (rest >= sc->bestburstlen) {
                  count = rest >> sc->bestburstshift;
                  cnt = count << sc->bestburstshift;
                  bcode = sc->bestburstcode;
                  DO_DRQ("best_dma");
            }

            /* do we need to do a cleanup burst? */
            if (rest != 0) {
                  cnt = rest;
                  count = rest / sizeof(uint32_t);
                  if (sc->noalbursts) {
                        bcode = MIDDMA_WORD;
                  } else {
                        bcode = en_dmaplan[count].bcode;
                        count = cnt >> en_dmaplan[count].divshift;
                  }
                  DO_DRQ("clean_dma");
            }
      }

      /*
       * Skip stuff at the end
       */
      if (rx->post_skip > 0) {
            /* update DMA address */
            EN_WRAPADD(slot->start, slot->stop, cur, rx->post_skip);

            PUT_DRQ_ENTRY(0, MIDDMA_JK, WORD_IDX(slot->start, cur), 0);
      }

      /* record the end for the interrupt routine */
      sc->drq[MID_DRQ_A2REG(last_drq)] =
          EN_DQ_MK(slot - sc->rxslot, rx->m->m_pkthdr.len);

      /* set the end flag in the last descriptor */
      en_write(sc, last_drq + 0, SETQ_END(sc, en_read(sc, last_drq + 0)));

#undef PUT_DRQ_ENTRY
#undef DO_DRQ

      /* commit */
      slot->cur = cur;
      sc->drq_free = free;
      sc->drq_us = drq;

      /* signal to card */
      en_write(sc, MID_DMA_WRRX, MID_DRQ_A2REG(sc->drq_us));
}

/*
 * en_service: handle a service interrupt
 *
 * Q: why do we need a software service list?
 *
 * A: if we remove a VCI from the hardware list and we find that we are
 *    out of DRQs we must defer processing until some DRQs become free.
 *    so we must remember to look at this RX VCI/slot later, but we can't
 *    put it back on the hardware service list (since that isn't allowed).
 *    so we instead save it on the software service list.   it would be nice 
 *    if we could peek at the VCI on top of the hwservice list without removing
 *    it, however this leads to a race condition: if we peek at it and
 *    decide we are done with it new data could come in before we have a 
 *    chance to remove it from the hwslist.   by the time we get it out of
 *    the list the interrupt for the new data will be lost.   oops!
 *
 * LOCK: locked, needed
 */
static void
en_service(struct en_softc *sc)
{
      struct mbuf *m, *lastm;
      struct en_map     *map;
      struct rxarg      rx;
      uint32_t    cur;
      uint32_t    dstart;           /* data start (as reported by card) */
      uint32_t    rbd;        /* receive buffer descriptor */
      uint32_t    pdu;        /* AAL5 trailer */
      int         mlen;
      int         error;
      struct en_rxslot *slot;
      struct en_vcc *vc;

      rx.sc = sc;

  next_vci:
      if (sc->swsl_size == 0) {
            DBG(sc, SERV, ("en_service done"));
            return;
      }

      /*
       * get vcc to service
       */
      rx.vc = vc = sc->vccs[sc->swslist[sc->swsl_head]];
      slot = vc->rxslot;
      KASSERT (slot->vcc->rxslot == slot, ("en_service: rx slot/vci sync"));

      /*
       * determine our mode and if we've got any work to do
       */
      DBG(sc, SERV, ("rx%td: service vci=%d start/stop/cur=0x%x 0x%x "
          "0x%x", slot - sc->rxslot, vc->vcc.vci, slot->start,
          slot->stop, slot->cur));

  same_vci:
      cur = slot->cur;

      dstart = MIDV_DSTART(en_read(sc, MID_DST_RP(vc->vcc.vci)));
      dstart = (dstart * sizeof(uint32_t)) + slot->start;

      /* check to see if there is any data at all */
      if (dstart == cur) {
            EN_WRAPADD(0, MID_SL_N, sc->swsl_head, 1); 
            /* remove from swslist */
            vc->vflags &= ~VCC_SWSL;
            sc->swsl_size--;
            DBG(sc, SERV, ("rx%td: remove vci %d from swslist",
                slot - sc->rxslot, vc->vcc.vci));
            goto next_vci;
      }

      /*
       * figure out how many bytes we need
       * [mlen = # bytes to go in mbufs]
       */
      rbd = en_read(sc, cur);
      if (MID_RBD_ID(rbd) != MID_RBD_STDID) 
            panic("en_service: id mismatch");

      if (rbd & MID_RBD_T) {
            mlen = 0;         /* we've got trash */
            rx.pre_skip = MID_RBD_SIZE;
            rx.post_skip = 0;
            EN_COUNT(sc->stats.ttrash);
            DBG(sc, SERV, ("RX overflow lost %d cells!", MID_RBD_CNT(rbd)));

      } else if (vc->vcc.aal != ATMIO_AAL_5) {
            /* 1 cell (ick!) */
            mlen = MID_CHDR_SIZE + MID_ATMDATASZ;
            rx.pre_skip = MID_RBD_SIZE;
            rx.post_skip = 0;

      } else {
            rx.pre_skip = MID_RBD_SIZE;

            /* get PDU trailer in correct byte order */
            pdu = cur + MID_RBD_CNT(rbd) * MID_ATMDATASZ +
                MID_RBD_SIZE - MID_PDU_SIZE;
            if (pdu >= slot->stop)
                  pdu -= EN_RXSZ * 1024;
            pdu = en_read(sc, pdu);

            if (MID_RBD_CNT(rbd) * MID_ATMDATASZ <
                MID_PDU_LEN(pdu)) {
                  if_printf(sc->ifp, "invalid AAL5 length\n");
                  rx.post_skip = MID_RBD_CNT(rbd) * MID_ATMDATASZ;
                  mlen = 0;
                  sc->ifp->if_ierrors++;

            } else if (rbd & MID_RBD_CRCERR) {
                  if_printf(sc->ifp, "CRC error\n");
                  rx.post_skip = MID_RBD_CNT(rbd) * MID_ATMDATASZ;
                  mlen = 0;
                  sc->ifp->if_ierrors++;

            } else {
                  mlen = MID_PDU_LEN(pdu);
                  rx.post_skip = MID_RBD_CNT(rbd) * MID_ATMDATASZ - mlen;
            }
      }

      /*
       * now allocate mbufs for mlen bytes of data, if out of mbufs, trash all
       *
       * notes:
       *  1. it is possible that we've already allocated an mbuf for this pkt
       *     but ran out of DRQs, in which case we saved the allocated mbuf
       *     on "q".
       *  2. if we save an buf in "q" we store the "cur" (pointer) in the
       *     buf as an identity (that we can check later).
       *  3. after this block of code, if m is still NULL then we ran out of
       *     mbufs
       */
      _IF_DEQUEUE(&slot->q, m);
      if (m != NULL) {
            if (m->m_pkthdr.csum_data != cur) {
                  /* wasn't ours */
                  DBG(sc, SERV, ("rx%td: q'ed buf %p not ours",
                      slot - sc->rxslot, m));
                  _IF_PREPEND(&slot->q, m);
                  m = NULL;
                  EN_COUNT(sc->stats.rxqnotus);
            } else {
                  EN_COUNT(sc->stats.rxqus);
                  DBG(sc, SERV, ("rx%td: recovered q'ed buf %p",
                      slot - sc->rxslot, m));
            }
      }
      if (mlen == 0 && m != NULL) {
            /* should not happen */
            m_freem(m);
            m = NULL;
      }

      if (mlen != 0 && m == NULL) {
            m = en_mget(sc, mlen);
            if (m == NULL) {
                  rx.post_skip += mlen;
                  mlen = 0;
                  EN_COUNT(sc->stats.rxmbufout);
                  DBG(sc, SERV, ("rx%td: out of mbufs",
                      slot - sc->rxslot));
            } else
                  rx.post_skip -= roundup(mlen, sizeof(uint32_t)) - mlen;

            DBG(sc, SERV, ("rx%td: allocate buf %p, mlen=%d",
                slot - sc->rxslot, m, mlen));
      }

      DBG(sc, SERV, ("rx%td: VCI %d, rbuf %p, mlen %d, skip %u/%u",
          slot - sc->rxslot, vc->vcc.vci, m, mlen, rx.pre_skip,
          rx.post_skip));

      if (m != NULL) {
            /* M_NOWAIT - called from interrupt context */
            map = uma_zalloc_arg(sc->map_zone, sc, M_NOWAIT);
            if (map == NULL) {
                  rx.post_skip += mlen;
                  m_freem(m);
                  DBG(sc, SERV, ("rx%td: out of maps",
                      slot - sc->rxslot));
                  goto skip;
            }
            rx.m = m;
            error = bus_dmamap_load_mbuf(sc->txtag, map->map, m,
                en_rxdma_load, &rx, BUS_DMA_NOWAIT);

            if (error != 0) {
                  if_printf(sc->ifp, "loading RX map failed "
                      "%d\n", error);
                  uma_zfree(sc->map_zone, map);
                  m_freem(m);
                  rx.post_skip += mlen;
                  goto skip;

            }
            map->flags |= ENMAP_LOADED;

            if (rx.wait) {
                  /* out of DRQs - wait */
                  uma_zfree(sc->map_zone, map);

                  m->m_pkthdr.csum_data = cur;
                  _IF_ENQUEUE(&slot->q, m);
                  EN_COUNT(sc->stats.rxdrqout);

                  sc->need_drqs = 1;      /* flag condition */
                  return;

            }
            (void)m_length(m, &lastm);
            lastm->m_len -= roundup(mlen, sizeof(uint32_t)) - mlen;

            m->m_pkthdr.rcvif = (void *)map;
            _IF_ENQUEUE(&slot->indma, m);

            /* get next packet in this slot */
            goto same_vci;
      }
  skip:
      /*
       * Here we end if we should drop the packet from the receive buffer.
       * The number of bytes to drop is in fill. We can do this with on
       * JK entry. If we don't even have that one - wait.
       */
      if (sc->drq_free == 0) {
            sc->need_drqs = 1;      /* flag condition */
            return;
      }
      rx.post_skip += rx.pre_skip;
      DBG(sc, SERV, ("rx%td: skipping %u", slot - sc->rxslot, rx.post_skip));

      /* advance buffer address */
      EN_WRAPADD(slot->start, slot->stop, cur, rx.post_skip);

      /* write DRQ entry */
      if (sc->is_adaptec)
            en_write(sc, sc->drq_us,
                MID_MK_RXQ_ADP(WORD_IDX(slot->start, cur),
                vc->vcc.vci, MID_DMA_END, MIDDMA_JK));
      else
            en_write(sc, sc->drq_us,
                MID_MK_RXQ_ENI(WORD_IDX(slot->start, cur),
                vc->vcc.vci, MID_DMA_END, MIDDMA_JK));
      en_write(sc, sc->drq_us + 4, 0);
      EN_WRAPADD(MID_DRQOFF, MID_DRQEND, sc->drq_us, 8);
      sc->drq_free--;

      /* signal to RX interrupt */
      sc->drq[MID_DRQ_A2REG(sc->drq_us)] = EN_DQ_MK(slot - sc->rxslot, 0);
      slot->cur = cur;

      /* signal to card */
      en_write(sc, MID_DMA_WRRX, MID_DRQ_A2REG(sc->drq_us));

      goto same_vci;
}

/*
 * interrupt handler
 *
 * LOCK: unlocked, needed
 */
void
en_intr(void *arg)
{
      struct en_softc *sc = arg;
      uint32_t reg, kick, mask;
      int lcv, need_softserv;

      EN_LOCK(sc);

      reg = en_read(sc, MID_INTACK);
      DBG(sc, INTR, ("interrupt=0x%b", reg, MID_INTBITS));

      if ((reg & MID_INT_ANY) == 0) {
            EN_UNLOCK(sc);
            return;
      }

      /*
       * unexpected errors that need a reset
       */
      if ((reg & (MID_INT_IDENT | MID_INT_LERR | MID_INT_DMA_ERR)) != 0) {
            if_printf(sc->ifp, "unexpected interrupt=0x%b, "
                "resetting\n", reg, MID_INTBITS);
#ifdef EN_DEBUG
            kdb_enter("en: unexpected error");
            sc->ifp->if_drv_flags &= ~IFF_DRV_RUNNING; /* FREEZE! */
#else
            en_reset_ul(sc);
            en_init(sc);
#endif
            EN_UNLOCK(sc);
            return;
      }

      if (reg & MID_INT_SUNI)
            utopia_intr(&sc->utopia);

      kick = 0;
      if (reg & MID_INT_TX)
            kick |= en_intr_tx(sc, reg);

      if (reg & MID_INT_DMA_TX)
            kick |= en_intr_tx_dma(sc);

      /*
       * kick xmit channels as needed.
       */
      if (kick) {
            DBG(sc, INTR, ("tx kick mask = 0x%x", kick));
            for (mask = 1, lcv = 0 ; lcv < EN_NTX ; lcv++, mask = mask * 2)
                  if ((kick & mask) && _IF_QLEN(&sc->txslot[lcv].q) != 0)
                        en_txdma(sc, &sc->txslot[lcv]);
      }

      need_softserv = 0;
      if (reg & MID_INT_DMA_RX)
            need_softserv |= en_intr_rx_dma(sc);

      if (reg & MID_INT_SERVICE)
            need_softserv |= en_intr_service(sc);

      if (need_softserv)
            en_service(sc);

      /*
       * keep our stats
       */
      if (reg & MID_INT_DMA_OVR) {
            EN_COUNT(sc->stats.dmaovr);
            DBG(sc, INTR, ("MID_INT_DMA_OVR"));
      }
      reg = en_read(sc, MID_STAT);
      sc->stats.otrash += MID_OTRASH(reg);
      sc->stats.vtrash += MID_VTRASH(reg);

      EN_UNLOCK(sc);
}

/*
 * Read at most n SUNI regs starting at reg into val
 */
static int
en_utopia_readregs(struct ifatm *ifatm, u_int reg, uint8_t *val, u_int *n)
{
      struct en_softc *sc = ifatm->ifp->if_softc;
      u_int i;

      EN_CHECKLOCK(sc);
      if (reg >= MID_NSUNI)
            return (EINVAL);
      if (reg + *n > MID_NSUNI)
            *n = MID_NSUNI - reg;

      for (i = 0; i < *n; i++)
            val[i] = en_read(sc, MID_SUNIOFF + 4 * (reg + i));

      return (0);
}

/*
 * change the bits given by mask to them in val in register reg
 */
static int
en_utopia_writereg(struct ifatm *ifatm, u_int reg, u_int mask, u_int val)
{
      struct en_softc *sc = ifatm->ifp->if_softc;
      uint32_t regval;

      EN_CHECKLOCK(sc);
      if (reg >= MID_NSUNI)
            return (EINVAL);
      regval = en_read(sc, MID_SUNIOFF + 4 * reg);
      regval = (regval & ~mask) | (val & mask);
      en_write(sc, MID_SUNIOFF + 4 * reg, regval);
      return (0);
}

static const struct utopia_methods en_utopia_methods = {
      en_utopia_readregs,
      en_utopia_writereg
};

/*********************************************************************/
/*
 * Probing the DMA brokeness of the card
 */

/*
 * Physical address load helper function for DMA probe
 *
 * LOCK: unlocked, not needed
 */
static void
en_dmaprobe_load(void *uarg, bus_dma_segment_t *segs, int nseg, int error)
{
      if (error == 0)
            *(bus_addr_t *)uarg = segs[0].ds_addr;
}

/*
 * en_dmaprobe: helper function for en_attach.
 *
 * see how the card handles DMA by running a few DMA tests.   we need
 * to figure out the largest number of bytes we can DMA in one burst
 * ("bestburstlen"), and if the starting address for a burst needs to
 * be aligned on any sort of boundary or not ("alburst").
 *
 * Things turn out more complex than that, because on my (harti) brand
 * new motherboard (2.4GHz) we can do 64byte aligned DMAs, but everything
 * we more than 4 bytes fails (with an RX DMA timeout) for physical
 * addresses that end with 0xc. Therefor we search not only the largest
 * burst that is supported (hopefully 64) but also check what is the largerst
 * unaligned supported size. If that appears to be lesser than 4 words,
 * set the noalbursts flag. That will be set only if also alburst is set.
 */

/*
 * en_dmaprobe_doit: do actual testing for the DMA test.
 * Cycle through all bursts sizes from 8 up to 64 and try whether it works.
 * Return the largest one that works.
 *
 * LOCK: unlocked, not needed
 */
static int
en_dmaprobe_doit(struct en_softc *sc, uint8_t *sp, bus_addr_t psp)
{
      uint8_t *dp = sp + MIDDMA_MAXBURST;
      bus_addr_t pdp = psp + MIDDMA_MAXBURST;
      int lcv, retval = 4, cnt;
      uint32_t reg, bcode, midvloc;

      if (sc->en_busreset)
            sc->en_busreset(sc);
      en_write(sc, MID_RESID, 0x0); /* reset card before touching RAM */

      /*
       * set up a 1k buffer at MID_BUFOFF
       */
      midvloc = ((MID_BUFOFF - MID_RAMOFF) / sizeof(uint32_t))
          >> MIDV_LOCTOPSHFT;
      en_write(sc, MIDX_PLACE(0), MIDX_MKPLACE(en_k2sz(1), midvloc));
      en_write(sc, MID_VC(0), (midvloc << MIDV_LOCSHIFT) 
          | (en_k2sz(1) << MIDV_SZSHIFT) | MIDV_TRASH);
      en_write(sc, MID_DST_RP(0), 0);
      en_write(sc, MID_WP_ST_CNT(0), 0);

      /* set up sample data */
      for (lcv = 0 ; lcv < MIDDMA_MAXBURST; lcv++)
            sp[lcv] = lcv + 1;

      /* enable DMA (only) */
      en_write(sc, MID_MAST_CSR, MID_MCSR_ENDMA);

      sc->drq_chip = MID_DRQ_REG2A(en_read(sc, MID_DMA_RDRX));
      sc->dtq_chip = MID_DTQ_REG2A(en_read(sc, MID_DMA_RDTX));

      /*
       * try it now . . .  DMA it out, then DMA it back in and compare
       *
       * note: in order to get the dma stuff to reverse directions it wants
       * the "end" flag set!   since we are not dma'ing valid data we may
       * get an ident mismatch interrupt (which we will ignore).
       */
      DBG(sc, DMA, ("test sp=%p/%#lx, dp=%p/%#lx", 
          sp, (u_long)psp, dp, (u_long)pdp));
      for (lcv = 8 ; lcv <= MIDDMA_MAXBURST ; lcv = lcv * 2) {
            DBG(sc, DMA, ("test lcv=%d", lcv));

            /* zero SRAM and dest buffer */
            bus_space_set_region_4(sc->en_memt, sc->en_base,
                MID_BUFOFF, 0, 1024 / 4);
            bzero(dp, MIDDMA_MAXBURST);

            bcode = en_sz2b(lcv);

            /* build lcv-byte-DMA x NBURSTS */
            if (sc->is_adaptec)
                  en_write(sc, sc->dtq_chip,
                      MID_MK_TXQ_ADP(lcv, 0, MID_DMA_END, 0));
            else
                  en_write(sc, sc->dtq_chip,
                      MID_MK_TXQ_ENI(1, 0, MID_DMA_END, bcode));
            en_write(sc, sc->dtq_chip + 4, psp);
            EN_WRAPADD(MID_DTQOFF, MID_DTQEND, sc->dtq_chip, 8);
            en_write(sc, MID_DMA_WRTX, MID_DTQ_A2REG(sc->dtq_chip));

            cnt = 1000;
            while ((reg = en_readx(sc, MID_DMA_RDTX)) !=
                MID_DTQ_A2REG(sc->dtq_chip)) {
                  DELAY(1);
                  if (--cnt == 0) {
                        DBG(sc, DMA, ("unexpected timeout in tx "
                            "DMA test\n  alignment=0x%lx, burst size=%d"
                            ", dma addr reg=%#x, rdtx=%#x, stat=%#x\n",
                            (u_long)sp & 63, lcv,
                            en_read(sc, MID_DMA_ADDR), reg,
                            en_read(sc, MID_INTSTAT)));
                        return (retval);
                  }
            }

            reg = en_read(sc, MID_INTACK); 
            if ((reg & MID_INT_DMA_TX) != MID_INT_DMA_TX) {
                  DBG(sc, DMA, ("unexpected status in tx DMA test: %#x\n",
                      reg));
                  return (retval);
            }
            /* re-enable DMA (only) */
            en_write(sc, MID_MAST_CSR, MID_MCSR_ENDMA);

            /* "return to sender..."  address is known ... */

            /* build lcv-byte-DMA x NBURSTS */
            if (sc->is_adaptec)
                  en_write(sc, sc->drq_chip,
                      MID_MK_RXQ_ADP(lcv, 0, MID_DMA_END, 0));
            else
                  en_write(sc, sc->drq_chip,
                      MID_MK_RXQ_ENI(1, 0, MID_DMA_END, bcode));
            en_write(sc, sc->drq_chip + 4, pdp);
            EN_WRAPADD(MID_DRQOFF, MID_DRQEND, sc->drq_chip, 8);
            en_write(sc, MID_DMA_WRRX, MID_DRQ_A2REG(sc->drq_chip));
            cnt = 1000;
            while ((reg = en_readx(sc, MID_DMA_RDRX)) !=
                MID_DRQ_A2REG(sc->drq_chip)) {
                  DELAY(1);
                  cnt--;
                  if (--cnt == 0) {
                        DBG(sc, DMA, ("unexpected timeout in rx "
                            "DMA test, rdrx=%#x\n", reg));
                        return (retval);
                  }
            }
            reg = en_read(sc, MID_INTACK); 
            if ((reg & MID_INT_DMA_RX) != MID_INT_DMA_RX) {
                  DBG(sc, DMA, ("unexpected status in rx DMA "
                      "test: 0x%x\n", reg));
                  return (retval);
            }
            if (bcmp(sp, dp, lcv)) {
                  DBG(sc, DMA, ("DMA test failed! lcv=%d, sp=%p, "
                      "dp=%p", lcv, sp, dp));
                  return (retval);
            }

            retval = lcv;
      }
      return (retval);  /* studly 64 byte DMA present!  oh baby!! */
}

/*
 * Find the best DMA parameters
 *
 * LOCK: unlocked, not needed
 */
static void
en_dmaprobe(struct en_softc *sc)
{
      bus_dma_tag_t tag;
      bus_dmamap_t map;
      int err;
      void *buffer;
      int bestalgn, lcv, try, bestnoalgn;
      bus_addr_t phys;
      uint8_t *addr;

      sc->alburst = 0;
      sc->noalbursts = 0;

      /*
       * Allocate some DMA-able memory.
       * We need 3 times the max burst size aligned to the max burst size.
       */
      err = bus_dma_tag_create(NULL, MIDDMA_MAXBURST, 0,
          BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL,
          3 * MIDDMA_MAXBURST, 1, 3 * MIDDMA_MAXBURST, 0,
          NULL, NULL, &tag);
      if (err)
            panic("%s: cannot create test DMA tag %d", __func__, err);

      err = bus_dmamem_alloc(tag, &buffer, 0, &map);
      if (err)
            panic("%s: cannot allocate test DMA memory %d", __func__, err);

      err = bus_dmamap_load(tag, map, buffer, 3 * MIDDMA_MAXBURST,
          en_dmaprobe_load, &phys, BUS_DMA_NOWAIT);
      if (err)
            panic("%s: cannot load test DMA map %d", __func__, err);
      addr = buffer;
      DBG(sc, DMA, ("phys=%#lx addr=%p", (u_long)phys, addr));

      /*
       * Now get the best burst size of the aligned case.
       */
      bestalgn = bestnoalgn = en_dmaprobe_doit(sc, addr, phys);

      /*
       * Now try unaligned. 
       */
      for (lcv = 4; lcv < MIDDMA_MAXBURST; lcv += 4) {
            try = en_dmaprobe_doit(sc, addr + lcv, phys + lcv);

            if (try < bestnoalgn)
                  bestnoalgn = try;
      }

      if (bestnoalgn < bestalgn) {
            sc->alburst = 1;
            if (bestnoalgn < 32)
                  sc->noalbursts = 1;
      }

      sc->bestburstlen = bestalgn;
      sc->bestburstshift = en_log2(bestalgn);
      sc->bestburstmask = sc->bestburstlen - 1; /* must be power of 2 */
      sc->bestburstcode = en_sz2b(bestalgn);

      /*
       * Reset the chip before freeing the buffer. It may still be trying
       * to DMA.
       */
      if (sc->en_busreset)
            sc->en_busreset(sc);
      en_write(sc, MID_RESID, 0x0); /* reset card before touching RAM */

      DELAY(10000);                 /* may still do DMA */

      /*
       * Free the DMA stuff
       */
      bus_dmamap_unload(tag, map);
      bus_dmamem_free(tag, buffer, map);
      bus_dma_tag_destroy(tag);
}

/*********************************************************************/
/*
 * Attach/detach.
 */

/*
 * Attach to the card.
 *
 * LOCK: unlocked, not needed (but initialized)
 */
int
en_attach(struct en_softc *sc)
{
      struct ifnet *ifp = sc->ifp;
      int sz;
      uint32_t reg, lcv, check, ptr, sav, midvloc;

#ifdef EN_DEBUG
      sc->debug = EN_DEBUG;
#endif

      /*
       * Probe card to determine memory size.
       *
       * The stupid ENI card always reports to PCI that it needs 4MB of
       * space (2MB regs and 2MB RAM). If it has less than 2MB RAM the
       * addresses wrap in the RAM address space (i.e. on a 512KB card
       * addresses 0x3ffffc, 0x37fffc, and 0x2ffffc are aliases for
       * 0x27fffc  [note that RAM starts at offset 0x200000]).
       */

      /* reset card before touching RAM */
      if (sc->en_busreset)
            sc->en_busreset(sc);
      en_write(sc, MID_RESID, 0x0);

      for (lcv = MID_PROBEOFF; lcv <= MID_MAXOFF ; lcv += MID_PROBSIZE) {
            en_write(sc, lcv, lcv); /* data[address] = address */
            for (check = MID_PROBEOFF; check < lcv ;check += MID_PROBSIZE) {
                  reg = en_read(sc, check);
                  if (reg != check)
                        /* found an alias! - quit */
                        goto done_probe;
            }
      }
  done_probe:
      lcv -= MID_PROBSIZE;                /* take one step back */
      sc->en_obmemsz = (lcv + 4) - MID_RAMOFF;

      /*
       * determine the largest DMA burst supported
       */
      en_dmaprobe(sc);

      /*
       * "hello world"
       */

      /* reset */
      if (sc->en_busreset)
            sc->en_busreset(sc);
      en_write(sc, MID_RESID, 0x0);       /* reset */

      /* zero memory */
      bus_space_set_region_4(sc->en_memt, sc->en_base,
          MID_RAMOFF, 0, sc->en_obmemsz / 4);

      reg = en_read(sc, MID_RESID);

      if_printf(sc->ifp, "ATM midway v%d, board IDs %d.%d, %s%s%s, "
          "%ldKB on-board RAM\n", MID_VER(reg), MID_MID(reg), MID_DID(reg), 
          (MID_IS_SABRE(reg)) ? "sabre controller, " : "",
          (MID_IS_SUNI(reg)) ? "SUNI" : "Utopia",
          (!MID_IS_SUNI(reg) && MID_IS_UPIPE(reg)) ? " (pipelined)" : "",
          (long)sc->en_obmemsz / 1024);

      /*
       * fill in common ATM interface stuff
       */
      IFP2IFATM(sc->ifp)->mib.hw_version = (MID_VER(reg) << 16) |
          (MID_MID(reg) << 8) | MID_DID(reg);
      if (MID_DID(reg) & 0x4)
            IFP2IFATM(sc->ifp)->mib.media = IFM_ATM_UTP_155;
      else
            IFP2IFATM(sc->ifp)->mib.media = IFM_ATM_MM_155;

      IFP2IFATM(sc->ifp)->mib.pcr = ATM_RATE_155M;
      IFP2IFATM(sc->ifp)->mib.vpi_bits = 0;
      IFP2IFATM(sc->ifp)->mib.vci_bits = MID_VCI_BITS;
      IFP2IFATM(sc->ifp)->mib.max_vccs = MID_N_VC;
      IFP2IFATM(sc->ifp)->mib.max_vpcs = 0;

      if (sc->is_adaptec) {
            IFP2IFATM(sc->ifp)->mib.device = ATM_DEVICE_ADP155P;
            if (sc->bestburstlen == 64 && sc->alburst == 0)
                  if_printf(sc->ifp,
                      "passed 64 byte DMA test\n");
            else
                  if_printf(sc->ifp, "FAILED DMA TEST: "
                      "burst=%d, alburst=%d\n", sc->bestburstlen,
                      sc->alburst);
      } else {
            IFP2IFATM(sc->ifp)->mib.device = ATM_DEVICE_ENI155P;
            if_printf(sc->ifp, "maximum DMA burst length = %d "
                "bytes%s\n", sc->bestburstlen, sc->alburst ?
                sc->noalbursts ?  " (no large bursts)" : " (must align)" :
                "");
      }

      /*
       * link into network subsystem and prepare card
       */
      sc->ifp->if_softc = sc;
      ifp->if_flags = IFF_SIMPLEX;
      ifp->if_ioctl = en_ioctl;
      ifp->if_start = en_start;

      mtx_init(&sc->en_mtx, device_get_nameunit(sc->dev),
          MTX_NETWORK_LOCK, MTX_DEF);
      cv_init(&sc->cv_close, "VC close");

      /*
       * Make the sysctl tree
       */
      sysctl_ctx_init(&sc->sysctl_ctx);

      if ((sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx,
          SYSCTL_STATIC_CHILDREN(_hw_atm), OID_AUTO,
          device_get_nameunit(sc->dev), CTLFLAG_RD, 0, "")) == NULL)
            goto fail;

      if (SYSCTL_ADD_PROC(&sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree),
          OID_AUTO, "istats", CTLFLAG_RD, sc, 0, en_sysctl_istats,
          "S", "internal statistics") == NULL)
            goto fail;

#ifdef EN_DEBUG
      if (SYSCTL_ADD_UINT(&sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree),
          OID_AUTO, "debug", CTLFLAG_RW , &sc->debug, 0, "") == NULL)
            goto fail;
#endif

      IFP2IFATM(sc->ifp)->phy = &sc->utopia;
      utopia_attach(&sc->utopia, IFP2IFATM(sc->ifp), &sc->media, &sc->en_mtx,
          &sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree),
          &en_utopia_methods);
      utopia_init_media(&sc->utopia);

      MGET(sc->padbuf, M_TRYWAIT, MT_DATA);
      if (sc->padbuf == NULL)
            goto fail;
      bzero(sc->padbuf->m_data, MLEN);

      if (bus_dma_tag_create(NULL, 1, 0,
          BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL,
          EN_TXSZ * 1024, EN_MAX_DMASEG, EN_TXSZ * 1024, 0,
          NULL, NULL, &sc->txtag))
            goto fail;

      sc->map_zone = uma_zcreate("en dma maps", sizeof(struct en_map),
          en_map_ctor, en_map_dtor, NULL, en_map_fini, UMA_ALIGN_PTR,
          UMA_ZONE_ZINIT);
      if (sc->map_zone == NULL)
            goto fail;
      uma_zone_set_max(sc->map_zone, EN_MAX_MAPS);

      /*
       * init softc
       */
      sc->vccs = malloc(MID_N_VC * sizeof(sc->vccs[0]),
          M_DEVBUF, M_ZERO | M_WAITOK);

      sz = sc->en_obmemsz - (MID_BUFOFF - MID_RAMOFF);
      ptr = sav = MID_BUFOFF;
      ptr = roundup(ptr, EN_TXSZ * 1024); /* align */
      sz = sz - (ptr - sav);
      if (EN_TXSZ*1024 * EN_NTX > sz) {
            if_printf(sc->ifp, "EN_NTX/EN_TXSZ too big\n");
            goto fail;
      }
      for (lcv = 0 ;lcv < EN_NTX ;lcv++) {
            sc->txslot[lcv].mbsize = 0;
            sc->txslot[lcv].start = ptr;
            ptr += (EN_TXSZ * 1024);
            sz -= (EN_TXSZ * 1024);
            sc->txslot[lcv].stop = ptr;
            sc->txslot[lcv].nref = 0;
            DBG(sc, INIT, ("tx%d: start 0x%x, stop 0x%x", lcv,
                sc->txslot[lcv].start, sc->txslot[lcv].stop));
      }

      sav = ptr;
      ptr = roundup(ptr, EN_RXSZ * 1024); /* align */
      sz = sz - (ptr - sav);
      sc->en_nrx = sz / (EN_RXSZ * 1024);
      if (sc->en_nrx <= 0) {
            if_printf(sc->ifp, "EN_NTX/EN_TXSZ/EN_RXSZ too big\n");
            goto fail;
      }

      /* 
       * ensure that there is always one VC slot on the service list free
       * so that we can tell the difference between a full and empty list.
       */
      if (sc->en_nrx >= MID_N_VC)
            sc->en_nrx = MID_N_VC - 1;

      for (lcv = 0 ; lcv < sc->en_nrx ; lcv++) {
            sc->rxslot[lcv].vcc = NULL;
            midvloc = sc->rxslot[lcv].start = ptr;
            ptr += (EN_RXSZ * 1024);
            sz -= (EN_RXSZ * 1024);
            sc->rxslot[lcv].stop = ptr;
            midvloc = midvloc - MID_RAMOFF;
            /* mask, cvt to words */
            midvloc = (midvloc & ~((EN_RXSZ*1024) - 1)) >> 2;
            /* we only want the top 11 bits */
            midvloc = midvloc >> MIDV_LOCTOPSHFT;
            midvloc = (midvloc & MIDV_LOCMASK) << MIDV_LOCSHIFT;
            sc->rxslot[lcv].mode = midvloc | 
                (en_k2sz(EN_RXSZ) << MIDV_SZSHIFT) | MIDV_TRASH;

            DBG(sc, INIT, ("rx%d: start 0x%x, stop 0x%x, mode 0x%x", lcv,
                sc->rxslot[lcv].start, sc->rxslot[lcv].stop,
                sc->rxslot[lcv].mode));
      }

      if_printf(sc->ifp, "%d %dKB receive buffers, %d %dKB transmit "
          "buffers\n", sc->en_nrx, EN_RXSZ, EN_NTX, EN_TXSZ);
      if_printf(sc->ifp, "end station identifier (mac address) "
          "%6D\n", IFP2IFATM(sc->ifp)->mib.esi, ":");

      /*
       * Start SUNI stuff. This will call our readregs/writeregs
       * functions and these assume the lock to be held so we must get it
       * here.
       */
      EN_LOCK(sc);
      utopia_start(&sc->utopia);
      utopia_reset(&sc->utopia);
      EN_UNLOCK(sc);

      /*
       * final commit
       */
      atm_ifattach(ifp); 

#ifdef ENABLE_BPF
      bpfattach(ifp, DLT_ATM_RFC1483, sizeof(struct atmllc));
#endif

      return (0);

 fail:
      en_destroy(sc);
      return (-1);
}

/*
 * Free all internal resources. No access to bus resources here.
 * No locking required here (interrupt is already disabled).
 *
 * LOCK: unlocked, needed (but destroyed)
 */
void
en_destroy(struct en_softc *sc)
{
      u_int i;

      if (sc->utopia.state & UTP_ST_ATTACHED) {
            /* these assume the lock to be held */
            EN_LOCK(sc);
            utopia_stop(&sc->utopia);
            utopia_detach(&sc->utopia);
            EN_UNLOCK(sc);
      }

      if (sc->vccs != NULL) {
            /* get rid of sticky VCCs */
            for (i = 0; i < MID_N_VC; i++)
                  if (sc->vccs[i] != NULL)
                        uma_zfree(en_vcc_zone, sc->vccs[i]);
            free(sc->vccs, M_DEVBUF);
      }

      if (sc->padbuf != NULL)
            m_free(sc->padbuf);

      /*
       * Destroy the map zone before the tag (the fini function will
       * destroy the DMA maps using the tag)
       */
      if (sc->map_zone != NULL)
            uma_zdestroy(sc->map_zone);

      if (sc->txtag != NULL)
            bus_dma_tag_destroy(sc->txtag);

      (void)sysctl_ctx_free(&sc->sysctl_ctx);

      cv_destroy(&sc->cv_close);
      mtx_destroy(&sc->en_mtx);
}

/*
 * Module loaded/unloaded
 */
int
en_modevent(module_t mod __unused, int event, void *arg __unused)
{

      switch (event) {

        case MOD_LOAD:
            en_vcc_zone = uma_zcreate("EN vccs", sizeof(struct en_vcc),
                NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0);
            if (en_vcc_zone == NULL)
                  return (ENOMEM);
            break;

        case MOD_UNLOAD:
            uma_zdestroy(en_vcc_zone);
            break;
      }
      return (0);
}

/*********************************************************************/
/*
 * Debugging support
 */

#ifdef EN_DDBHOOK
/*
 * functions we can call from ddb
 */

/*
 * en_dump: dump the state
 */
#define END_SWSL  0x00000040        /* swsl state */
#define END_DRQ         0x00000020        /* drq state */
#define END_DTQ         0x00000010        /* dtq state */
#define END_RX          0x00000008        /* rx state */
#define END_TX          0x00000004        /* tx state */
#define END_MREGS 0x00000002        /* registers */
#define END_STATS 0x00000001        /* dump stats */

#define END_BITS "\20\7SWSL\6DRQ\5DTQ\4RX\3TX\2MREGS\1STATS"

static void
en_dump_stats(const struct en_stats *s)
{
      printf("en_stats:\n");
      printf("\t%d/%d mfix (%d failed)\n", s->mfixaddr, s->mfixlen,
          s->mfixfail);
      printf("\t%d rx dma overflow interrupts\n", s->dmaovr);
      printf("\t%d times out of TX space and stalled\n", s->txoutspace);
      printf("\t%d times out of DTQs\n", s->txdtqout);
      printf("\t%d times launched a packet\n", s->launch);
      printf("\t%d times pulled the hw service list\n", s->hwpull);
      printf("\t%d times pushed a vci on the sw service list\n", s->swadd);
      printf("\t%d times RX pulled an mbuf from Q that wasn't ours\n",
          s->rxqnotus);
      printf("\t%d times RX pulled a good mbuf from Q\n", s->rxqus);
      printf("\t%d times ran out of DRQs\n", s->rxdrqout);
      printf("\t%d transmit packets dropped due to mbsize\n", s->txmbovr);
      printf("\t%d cells trashed due to turned off rxvc\n", s->vtrash);
      printf("\t%d cells trashed due to totally full buffer\n", s->otrash);
      printf("\t%d cells trashed due almost full buffer\n", s->ttrash);
      printf("\t%d rx mbuf allocation failures\n", s->rxmbufout);
      printf("\t%d times out of tx maps\n", s->txnomap);
#ifdef NATM
#ifdef NATM_STAT
      printf("\tnatmintr so_rcv: ok/drop cnt: %d/%d, ok/drop bytes: %d/%d\n",
          natm_sookcnt, natm_sodropcnt, natm_sookbytes, natm_sodropbytes);
#endif
#endif
}

static void
en_dump_mregs(struct en_softc *sc)
{
      u_int cnt;

      printf("mregs:\n");
      printf("resid = 0x%x\n", en_read(sc, MID_RESID));
      printf("interrupt status = 0x%b\n",
          (int)en_read(sc, MID_INTSTAT), MID_INTBITS);
      printf("interrupt enable = 0x%b\n", 
           (int)en_read(sc, MID_INTENA), MID_INTBITS);
      printf("mcsr = 0x%b\n", (int)en_read(sc, MID_MAST_CSR), MID_MCSRBITS);
      printf("serv_write = [chip=%u] [us=%u]\n", en_read(sc, MID_SERV_WRITE),
           MID_SL_A2REG(sc->hwslistp));
      printf("dma addr = 0x%x\n", en_read(sc, MID_DMA_ADDR));
      printf("DRQ: chip[rd=0x%x,wr=0x%x], sc[chip=0x%x,us=0x%x]\n",
          MID_DRQ_REG2A(en_read(sc, MID_DMA_RDRX)), 
          MID_DRQ_REG2A(en_read(sc, MID_DMA_WRRX)), sc->drq_chip, sc->drq_us);
      printf("DTQ: chip[rd=0x%x,wr=0x%x], sc[chip=0x%x,us=0x%x]\n",
          MID_DTQ_REG2A(en_read(sc, MID_DMA_RDTX)), 
          MID_DTQ_REG2A(en_read(sc, MID_DMA_WRTX)), sc->dtq_chip, sc->dtq_us);

      printf("  unusal txspeeds:");
      for (cnt = 0 ; cnt < MID_N_VC ; cnt++)
            if (sc->vccs[cnt]->txspeed)
                  printf(" vci%d=0x%x", cnt, sc->vccs[cnt]->txspeed);
      printf("\n");

      printf("  rxvc slot mappings:");
      for (cnt = 0 ; cnt < MID_N_VC ; cnt++)
            if (sc->vccs[cnt]->rxslot != NULL)
                  printf("  %d->%zu", cnt,
                      sc->vccs[cnt]->rxslot - sc->rxslot);
      printf("\n");
}

static void
en_dump_tx(struct en_softc *sc)
{
      u_int slot;

      printf("tx:\n");
      for (slot = 0 ; slot < EN_NTX; slot++) {
            printf("tx%d: start/stop/cur=0x%x/0x%x/0x%x [%d]  ", slot,
                sc->txslot[slot].start, sc->txslot[slot].stop,
                sc->txslot[slot].cur,
                (sc->txslot[slot].cur - sc->txslot[slot].start) / 4);
            printf("mbsize=%d, bfree=%d\n", sc->txslot[slot].mbsize,
                sc->txslot[slot].bfree);
            printf("txhw: base_address=0x%x, size=%u, read=%u, "
                "descstart=%u\n",
                (u_int)MIDX_BASE(en_read(sc, MIDX_PLACE(slot))), 
                MIDX_SZ(en_read(sc, MIDX_PLACE(slot))),
                en_read(sc, MIDX_READPTR(slot)),
                en_read(sc, MIDX_DESCSTART(slot)));
      }
}

static void
en_dump_rx(struct en_softc *sc)
{
      struct en_rxslot *slot;

      printf("  recv slots:\n");
      for (slot = sc->rxslot ; slot < &sc->rxslot[sc->en_nrx]; slot++) {
            printf("rx%zu: start/stop/cur=0x%x/0x%x/0x%x mode=0x%x ",
                slot - sc->rxslot, slot->start, slot->stop, slot->cur,
                slot->mode);
            if (slot->vcc != NULL) {
                  printf("vci=%u\n", slot->vcc->vcc.vci);
                  printf("RXHW: mode=0x%x, DST_RP=0x%x, WP_ST_CNT=0x%x\n",
                      en_read(sc, MID_VC(slot->vcc->vcc.vci)),
                      en_read(sc, MID_DST_RP(slot->vcc->vcc.vci)),
                      en_read(sc, MID_WP_ST_CNT(slot->vcc->vcc.vci)));
            }
      }
}

/*
 * This is only correct for non-adaptec adapters
 */
static void
en_dump_dtqs(struct en_softc *sc)
{
      uint32_t ptr, reg;

      printf("  dtq [need_dtqs=%d,dtq_free=%d]:\n", sc->need_dtqs,
          sc->dtq_free);
      ptr = sc->dtq_chip;
      while (ptr != sc->dtq_us) {
            reg = en_read(sc, ptr);
            printf("\t0x%x=[%#x cnt=%d, chan=%d, end=%d, type=%d @ 0x%x]\n", 
                sc->dtq[MID_DTQ_A2REG(ptr)], reg, MID_DMA_CNT(reg),
                MID_DMA_TXCHAN(reg), (reg & MID_DMA_END) != 0,
                MID_DMA_TYPE(reg), en_read(sc, ptr + 4));
            EN_WRAPADD(MID_DTQOFF, MID_DTQEND, ptr, 8);
      }
}

static void
en_dump_drqs(struct en_softc *sc)
{
      uint32_t ptr, reg;

      printf("  drq [need_drqs=%d,drq_free=%d]:\n", sc->need_drqs,
          sc->drq_free);
      ptr = sc->drq_chip;
      while (ptr != sc->drq_us) {
            reg = en_read(sc, ptr);
            printf("\t0x%x=[cnt=%d, chan=%d, end=%d, type=%d @ 0x%x]\n", 
                sc->drq[MID_DRQ_A2REG(ptr)], MID_DMA_CNT(reg),
                MID_DMA_RXVCI(reg), (reg & MID_DMA_END) != 0,
                MID_DMA_TYPE(reg), en_read(sc, ptr + 4));
            EN_WRAPADD(MID_DRQOFF, MID_DRQEND, ptr, 8);
      }
}

/* Do not staticize - meant for calling from DDB! */
int
en_dump(int unit, int level)
{
      struct en_softc *sc;
      int lcv, cnt;
      devclass_t dc;
      int maxunit;

      dc = devclass_find("en");
      if (dc == NULL) {
            printf("%s: can't find devclass!\n", __func__);
            return (0);
      }
      maxunit = devclass_get_maxunit(dc);
      for (lcv = 0 ; lcv < maxunit ; lcv++) {
            sc = devclass_get_softc(dc, lcv);
            if (sc == NULL)
                  continue;
            if (unit != -1 && unit != lcv)
                  continue;

            if_printf(sc->ifp, "dumping device at level 0x%b\n",
                level, END_BITS);

            if (sc->dtq_us == 0) {
                  printf("<hasn't been en_init'd yet>\n");
                  continue;
            }

            if (level & END_STATS)
                  en_dump_stats(&sc->stats);
            if (level & END_MREGS)
                  en_dump_mregs(sc);
            if (level & END_TX)
                  en_dump_tx(sc);
            if (level & END_RX)
                  en_dump_rx(sc);
            if (level & END_DTQ)
                  en_dump_dtqs(sc);
            if (level & END_DRQ)
                  en_dump_drqs(sc);

            if (level & END_SWSL) {
                  printf(" swslist [size=%d]: ", sc->swsl_size);
                  for (cnt = sc->swsl_head ; cnt != sc->swsl_tail ; 
                      cnt = (cnt + 1) % MID_SL_N)
                        printf("0x%x ", sc->swslist[cnt]);
                  printf("\n");
            }
      }
      return (0);
}

/*
 * en_dumpmem: dump the memory
 *
 * Do not staticize - meant for calling from DDB!
 */
int
en_dumpmem(int unit, int addr, int len)
{
      struct en_softc *sc;
      uint32_t reg;
      devclass_t dc;

      dc = devclass_find("en");
      if (dc == NULL) {
            printf("%s: can't find devclass\n", __func__);
            return (0);
      }
      sc = devclass_get_softc(dc, unit);
      if (sc == NULL) {
            printf("%s: invalid unit number: %d\n", __func__, unit);
            return (0);
      }

      addr = addr & ~3;
      if (addr < MID_RAMOFF || addr + len * 4 > MID_MAXOFF || len <= 0) {
            printf("invalid addr/len number: %d, %d\n", addr, len);
            return (0);
      }
      printf("dumping %d words starting at offset 0x%x\n", len, addr);
      while (len--) {
            reg = en_read(sc, addr);
            printf("mem[0x%x] = 0x%x\n", addr, reg);
            addr += 4;
      }
      return (0);
}
#endif

Generated by  Doxygen 1.6.0   Back to index