root/sys/dev/ic/hd64570.c
/*      $NetBSD: hd64570.c,v 1.62 2024/09/14 21:22:37 andvar Exp $      */

/*
 * Copyright (c) 1999 Christian E. Hopps
 * Copyright (c) 1998 Vixie Enterprises
 * 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. Neither the name of Vixie Enterprises nor the names
 *    of its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY VIXIE ENTERPRISES AND
 * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL VIXIE ENTERPRISES OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * This software has been written for Vixie Enterprises by Michael Graff
 * <explorer@flame.org>.  To learn more about Vixie Enterprises, see
 * ``http://www.vix.com''.
 */

/*
 * TODO:
 *
 *      o  teach the receive logic about errors, and about long frames that
 *         span more than one input buffer.  (Right now, receive/transmit is
 *         limited to one descriptor's buffer space, which is MTU + 4 bytes.
 *         This is currently 1504, which is large enough to hold the HDLC
 *         header and the packet itself.  Packets which are too long are
 *         silently dropped on transmit and silently dropped on receive.
 *      o  write code to handle the msci interrupts, needed only for CD
 *         and CTS changes.
 *      o  consider switching back to a "queue tx with DMA active" model which
 *         should help sustain outgoing traffic
 *      o  through clever use of bus_dma*() functions, it should be possible
 *         to map the mbuf's data area directly into a descriptor transmit
 *         buffer, removing the need to allocate extra memory.  If, however,
 *         we run out of descriptors for this, we will need to then allocate
 *         one large mbuf, copy the fragmented chain into it, and put it onto
 *         a single descriptor.
 *      o  use bus_dmamap_sync() with the right offset and lengths, rather
 *         than cheating and always sync'ing the whole region.
 *
 *      o  perhaps allow rx and tx to be in more than one page
 *         if not using DMA.  currently the assumption is that
 *         rx uses a page and tx uses a page.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: hd64570.c,v 1.62 2024/09/14 21:22:37 andvar Exp $");

#include "opt_inet.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/kernel.h>

#include <net/if.h>
#include <net/if_types.h>

#if defined(INET) || defined(INET6)
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/in_var.h>
#include <netinet/ip.h>
#ifdef INET6
#include <netinet6/in6_var.h>
#endif
#endif

#include <net/bpf.h>

#include <sys/cpu.h>
#include <sys/bus.h>
#include <sys/intr.h>

#include <dev/pci/pcivar.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcidevs.h>

#include <dev/ic/hd64570reg.h>
#include <dev/ic/hd64570var.h>

#define SCA_DEBUG_RX            0x0001
#define SCA_DEBUG_TX            0x0002
#define SCA_DEBUG_CISCO         0x0004
#define SCA_DEBUG_DMA           0x0008
#define SCA_DEBUG_RXPKT         0x0010
#define SCA_DEBUG_TXPKT         0x0020
#define SCA_DEBUG_INTR          0x0040
#define SCA_DEBUG_CLOCK         0x0080

#if 0
#define SCA_DEBUG_LEVEL ( 0xFFFF )
#else
#define SCA_DEBUG_LEVEL 0
#endif

u_int32_t sca_debug = SCA_DEBUG_LEVEL;

#if SCA_DEBUG_LEVEL > 0
#define SCA_DPRINTF(l, x) do { \
        if ((l) & sca_debug) \
                printf x;\
        } while (0)
#else
#define SCA_DPRINTF(l, x)
#endif

#if 0
#define SCA_USE_FASTQ           /* use a split queue, one for fast traffic */
#endif

static inline void msci_write_1(sca_port_t *, u_int, u_int8_t);
static inline u_int8_t msci_read_1(sca_port_t *, u_int);

static inline void dmac_write_1(sca_port_t *, u_int, u_int8_t);
static inline void dmac_write_2(sca_port_t *, u_int, u_int16_t);
static inline u_int8_t dmac_read_1(sca_port_t *, u_int);
static inline u_int16_t dmac_read_2(sca_port_t *, u_int);

static  void sca_msci_init(struct sca_softc *, sca_port_t *);
static  void sca_dmac_init(struct sca_softc *, sca_port_t *);
static void sca_dmac_rxinit(sca_port_t *);

static  int sca_dmac_intr(sca_port_t *, u_int8_t);
static  int sca_msci_intr(sca_port_t *, u_int8_t);

static  void sca_get_packets(sca_port_t *);
static  int sca_frame_avail(sca_port_t *);
static  void sca_frame_process(sca_port_t *);
static  void sca_frame_read_done(sca_port_t *);

static  void sca_port_starttx(sca_port_t *);

static  void sca_port_up(sca_port_t *);
static  void sca_port_down(sca_port_t *);

static  int sca_output(struct ifnet *, struct mbuf *, const struct sockaddr *,
                            const struct rtentry *);
static  int sca_ioctl(struct ifnet *, u_long, void *);
static  void sca_start(struct ifnet *);
static  void sca_watchdog(struct ifnet *);

static struct mbuf *sca_mbuf_alloc(struct sca_softc *, void *, u_int);

#if SCA_DEBUG_LEVEL > 0
static  void sca_frame_print(sca_port_t *, sca_desc_t *, u_int8_t *);
#endif


#define sca_read_1(sc, reg)             (sc)->sc_read_1(sc, reg)
#define sca_read_2(sc, reg)             (sc)->sc_read_2(sc, reg)
#define sca_write_1(sc, reg, val)       (sc)->sc_write_1(sc, reg, val)
#define sca_write_2(sc, reg, val)       (sc)->sc_write_2(sc, reg, val)

#define sca_page_addr(sc, addr) ((bus_addr_t)(u_long)(addr) & (sc)->scu_pagemask)

static inline void
msci_write_1(sca_port_t *scp, u_int reg, u_int8_t val)
{
        sca_write_1(scp->sca, scp->msci_off + reg, val);
}

static inline u_int8_t
msci_read_1(sca_port_t *scp, u_int reg)
{
        return sca_read_1(scp->sca, scp->msci_off + reg);
}

static inline void
dmac_write_1(sca_port_t *scp, u_int reg, u_int8_t val)
{
        sca_write_1(scp->sca, scp->dmac_off + reg, val);
}

static inline void
dmac_write_2(sca_port_t *scp, u_int reg, u_int16_t val)
{
        sca_write_2(scp->sca, scp->dmac_off + reg, val);
}

static inline u_int8_t
dmac_read_1(sca_port_t *scp, u_int reg)
{
        return sca_read_1(scp->sca, scp->dmac_off + reg);
}

static inline u_int16_t
dmac_read_2(sca_port_t *scp, u_int reg)
{
        return sca_read_2(scp->sca, scp->dmac_off + reg);
}

#if SCA_DEBUG_LEVEL > 0
/*
 * read the chain pointer
 */
static inline u_int16_t
sca_desc_read_chainp(struct sca_softc *sc, struct sca_desc *dp)
{
        if (sc->sc_usedma)
                return ((dp)->sd_chainp);
        return (bus_space_read_2(sc->scu_memt, sc->scu_memh,
            sca_page_addr(sc, dp) + offsetof(struct sca_desc, sd_chainp)));
}
#endif

/*
 * write the chain pointer
 */
static inline void
sca_desc_write_chainp(struct sca_softc *sc, struct sca_desc *dp, u_int16_t cp)
{
        if (sc->sc_usedma)
                (dp)->sd_chainp = cp;
        else
                bus_space_write_2(sc->scu_memt, sc->scu_memh,
                    sca_page_addr(sc, dp)
                    + offsetof(struct sca_desc, sd_chainp), cp);
}

#if SCA_DEBUG_LEVEL > 0
/*
 * read the buffer pointer
 */
static inline u_int32_t
sca_desc_read_bufp(struct sca_softc *sc, struct sca_desc *dp)
{
        u_int32_t address;

        if (sc->sc_usedma)
                address = dp->sd_bufp | dp->sd_hbufp << 16;
        else {
                address = bus_space_read_2(sc->scu_memt, sc->scu_memh,
                    sca_page_addr(sc, dp) + offsetof(struct sca_desc, sd_bufp));
                address |= bus_space_read_1(sc->scu_memt, sc->scu_memh,
                    sca_page_addr(sc, dp)
                    + offsetof(struct sca_desc, sd_hbufp)) << 16;
        }
        return (address);
}
#endif

/*
 * write the buffer pointer
 */
static inline void
sca_desc_write_bufp(struct sca_softc *sc, struct sca_desc *dp, u_int32_t bufp)
{
        if (sc->sc_usedma) {
                dp->sd_bufp = bufp & 0xFFFF;
                dp->sd_hbufp = (bufp & 0x00FF0000) >> 16;
        } else {
                bus_space_write_2(sc->scu_memt, sc->scu_memh,
                    sca_page_addr(sc, dp) + offsetof(struct sca_desc, sd_bufp),
                    bufp & 0xFFFF);
                bus_space_write_1(sc->scu_memt, sc->scu_memh,
                    sca_page_addr(sc, dp) + offsetof(struct sca_desc, sd_hbufp),
                    (bufp & 0x00FF0000) >> 16);
        }
}

/*
 * read the buffer length
 */
static inline u_int16_t
sca_desc_read_buflen(struct sca_softc *sc, struct sca_desc *dp)
{
        if (sc->sc_usedma)
                return ((dp)->sd_buflen);
        return (bus_space_read_2(sc->scu_memt, sc->scu_memh,
            sca_page_addr(sc, dp) + offsetof(struct sca_desc, sd_buflen)));
}

/*
 * write the buffer length
 */
static inline void
sca_desc_write_buflen(struct sca_softc *sc, struct sca_desc *dp, u_int16_t len)
{
        if (sc->sc_usedma)
                (dp)->sd_buflen = len;
        else
                bus_space_write_2(sc->scu_memt, sc->scu_memh,
                    sca_page_addr(sc, dp)
                    + offsetof(struct sca_desc, sd_buflen), len);
}

/*
 * read the descriptor status
 */
static inline u_int8_t
sca_desc_read_stat(struct sca_softc *sc, struct sca_desc *dp)
{
        if (sc->sc_usedma)
                return ((dp)->sd_stat);
        return (bus_space_read_1(sc->scu_memt, sc->scu_memh,
            sca_page_addr(sc, dp) + offsetof(struct sca_desc, sd_stat)));
}

/*
 * write the descriptor status
 */
static inline void
sca_desc_write_stat(struct sca_softc *sc, struct sca_desc *dp, u_int8_t stat)
{
        if (sc->sc_usedma)
                (dp)->sd_stat = stat;
        else
                bus_space_write_1(sc->scu_memt, sc->scu_memh,
                    sca_page_addr(sc, dp) + offsetof(struct sca_desc, sd_stat),
                    stat);
}

void
sca_init(struct sca_softc *sc)
{
        /*
         * Do a little sanity check:  check number of ports.
         */
        if (sc->sc_numports < 1 || sc->sc_numports > 2)
                panic("sca can\'t handle more than 2 or less than 1 ports");

        /*
         * disable DMA and MSCI interrupts
         */
        sca_write_1(sc, SCA_DMER, 0);
        sca_write_1(sc, SCA_IER0, 0);
        sca_write_1(sc, SCA_IER1, 0);
        sca_write_1(sc, SCA_IER2, 0);

        /*
         * configure interrupt system
         */
        sca_write_1(sc, SCA_ITCR,
            SCA_ITCR_INTR_PRI_MSCI | SCA_ITCR_ACK_NONE | SCA_ITCR_VOUT_IVR);
#if 0
        /* these are for the interrupt ack cycle which we don't use */
        sca_write_1(sc, SCA_IVR, 0x40);
        sca_write_1(sc, SCA_IMVR, 0x40);
#endif

        /*
         * set wait control register to zero wait states
         */
        sca_write_1(sc, SCA_PABR0, 0);
        sca_write_1(sc, SCA_PABR1, 0);
        sca_write_1(sc, SCA_WCRL, 0);
        sca_write_1(sc, SCA_WCRM, 0);
        sca_write_1(sc, SCA_WCRH, 0);

        /*
         * disable DMA and reset status
         */
        sca_write_1(sc, SCA_PCR, SCA_PCR_PR2);

        /*
         * disable transmit DMA for all channels
         */
        sca_write_1(sc, SCA_DSR0 + SCA_DMAC_OFF_0, 0);
        sca_write_1(sc, SCA_DCR0 + SCA_DMAC_OFF_0, SCA_DCR_ABRT);
        sca_write_1(sc, SCA_DSR1 + SCA_DMAC_OFF_0, 0);
        sca_write_1(sc, SCA_DCR1 + SCA_DMAC_OFF_0, SCA_DCR_ABRT);
        sca_write_1(sc, SCA_DSR0 + SCA_DMAC_OFF_1, 0);
        sca_write_1(sc, SCA_DCR0 + SCA_DMAC_OFF_1, SCA_DCR_ABRT);
        sca_write_1(sc, SCA_DSR1 + SCA_DMAC_OFF_1, 0);
        sca_write_1(sc, SCA_DCR1 + SCA_DMAC_OFF_1, SCA_DCR_ABRT);

        /*
         * enable DMA based on channel enable flags for each channel
         */
        sca_write_1(sc, SCA_DMER, SCA_DMER_EN);

        /*
         * Should check to see if the chip is responding, but for now
         * assume it is.
         */
}

/*
 * initialize the port and attach it to the networking layer
 */
void
sca_port_attach(struct sca_softc *sc, u_int port)
{
        struct timeval now;
        sca_port_t *scp = &sc->sc_ports[port];
        struct ifnet *ifp;
        static u_int ntwo_unit = 0;

        scp->sca = sc;  /* point back to the parent */

        scp->sp_port = port;

        if (port == 0) {
                scp->msci_off = SCA_MSCI_OFF_0;
                scp->dmac_off = SCA_DMAC_OFF_0;
                if(sc->sc_parent != NULL)
                        ntwo_unit = device_unit(sc->sc_parent) * 2 + 0;
                else
                        ntwo_unit = 0;  /* XXX */
        } else {
                scp->msci_off = SCA_MSCI_OFF_1;
                scp->dmac_off = SCA_DMAC_OFF_1;
                if(sc->sc_parent != NULL)
                        ntwo_unit = device_unit(sc->sc_parent) * 2 + 1;
                else
                        ntwo_unit = 1;  /* XXX */
        }

        sca_msci_init(sc, scp);
        sca_dmac_init(sc, scp);

        /*
         * attach to the network layer
         */
        ifp = &scp->sp_if;
        snprintf(ifp->if_xname, sizeof(ifp->if_xname), "ntwo%d", ntwo_unit);
        ifp->if_softc = scp;
        ifp->if_mtu = SCA_MTU;
        ifp->if_flags = IFF_POINTOPOINT | IFF_MULTICAST;
        ifp->if_type = IFT_PTPSERIAL;
        ifp->if_hdrlen = HDLC_HDRLEN;
        ifp->if_ioctl = sca_ioctl;
        ifp->if_output = sca_output;
        ifp->if_watchdog = sca_watchdog;
        ifp->if_snd.ifq_maxlen = IFQ_MAXLEN;
        scp->linkq.ifq_maxlen = 5; /* if we exceed this we are hosed already */
#ifdef SCA_USE_FASTQ
        scp->fastq.ifq_maxlen = IFQ_MAXLEN;
#endif
        IFQ_SET_READY(&ifp->if_snd);
        if_attach(ifp);
        if_deferred_start_init(ifp, NULL);
        if_alloc_sadl(ifp);
        bpf_attach(ifp, DLT_HDLC, HDLC_HDRLEN);
        bpf_mtap_softint_init(ifp);

        if (sc->sc_parent == NULL)
                printf("%s: port %d\n", ifp->if_xname, port);
        else
                printf("%s at %s port %d\n",
                       ifp->if_xname, device_xname(sc->sc_parent), port);

        /*
         * reset the last seen times on the cisco keepalive protocol
         */
        getmicrotime(&now);
        scp->cka_lasttx = now.tv_usec;
        scp->cka_lastrx = 0;
}

#if 0
/*
 * returns log2(div), sets 'tmc' for the required freq 'hz'
 */
static u_int8_t
sca_msci_get_baud_rate_values(u_int32_t hz, u_int8_t *tmcp)
{
        u_int32_t tmc, div;
        u_int32_t clock;

        /* clock hz = (chipclock / tmc) / 2^(div); */
        /*
         * TD == tmc * 2^(n)
         *
         * note:
         * 1 <= TD <= 256               TD is inc of 1
         * 2 <= TD <= 512               TD is inc of 2
         * 4 <= TD <= 1024              TD is inc of 4
         * ...
         * 512 <= TD <= 256*512         TD is inc of 512
         *
         * so note there are overlaps.  We lose prec
         * as div increases so we wish to minize div.
         *
         * basically we want to do
         *
         * tmc = chip / hz, but have tmc <= 256
         */

        /* assume system clock is 9.8304MHz or 9830400Hz */
        clock = clock = 9830400 >> 1;

        /* round down */
        div = 0;
        while ((tmc = clock / hz) > 256 || (tmc == 256 && (clock / tmc) > hz)) {
                clock >>= 1;
                div++;
        }
        if (clock / tmc > hz)
                tmc++;
        if (!tmc)
                tmc = 1;

        if (div > SCA_RXS_DIV_512) {
                /* set to maximums */
                div = SCA_RXS_DIV_512;
                tmc = 0;
        }

        *tmcp = (tmc & 0xFF);   /* 0 == 256 */
        return (div & 0xFF);
}
#endif

/*
 * initialize the port's MSCI
 */
static void
sca_msci_init(struct sca_softc *sc, sca_port_t *scp)
{
        /* reset the channel */
        msci_write_1(scp, SCA_CMD0, SCA_CMD_RESET);

        msci_write_1(scp, SCA_MD00,
                     (  SCA_MD0_CRC_1
                      | SCA_MD0_CRC_CCITT
                      | SCA_MD0_CRC_ENABLE
                      | SCA_MD0_MODE_HDLC));
#if 0
        /* immediately send receive reset so the above takes */
        msci_write_1(scp, SCA_CMD0, SCA_CMD_RXRESET);
#endif

        msci_write_1(scp, SCA_MD10, SCA_MD1_NOADDRCHK);
        msci_write_1(scp, SCA_MD20,
                     (SCA_MD2_DUPLEX | SCA_MD2_ADPLLx8 | SCA_MD2_NRZ));

        /* be safe and do it again */
        msci_write_1(scp, SCA_CMD0, SCA_CMD_RXRESET);

        /* setup underrun and idle control, and initial RTS state */
        msci_write_1(scp, SCA_CTL0,
             (SCA_CTL_IDLC_PATTERN
             | SCA_CTL_UDRNC_AFTER_FCS
             | SCA_CTL_RTS_LOW));

        /* reset the transmitter */
        msci_write_1(scp, SCA_CMD0, SCA_CMD_TXRESET);

        /*
         * set the clock sources
         */
        msci_write_1(scp, SCA_RXS0, scp->sp_rxs);
        msci_write_1(scp, SCA_TXS0, scp->sp_txs);
        msci_write_1(scp, SCA_TMC0, scp->sp_tmc);

        /* set external clock generate as requested */
        sc->sc_clock_callback(sc->sc_aux, scp->sp_port, scp->sp_eclock);

        /*
         * XXX don't pay attention to CTS or CD changes right now.  I can't
         * simulate one, and the transmitter will try to transmit even if
         * CD isn't there anyway, so nothing bad SHOULD happen.
         */
#if 0
        msci_write_1(scp, SCA_IE00, 0);
        msci_write_1(scp, SCA_IE10, 0); /* 0x0c == CD and CTS changes only */
#else
        /* this would deliver transmitter underrun to ST1/ISR1 */
        msci_write_1(scp, SCA_IE10, SCA_ST1_UDRN);
        msci_write_1(scp, SCA_IE00, SCA_ST0_TXINT);
#endif
        msci_write_1(scp, SCA_IE20, 0);

        msci_write_1(scp, SCA_FIE0, 0);

        msci_write_1(scp, SCA_SA00, 0);
        msci_write_1(scp, SCA_SA10, 0);

        msci_write_1(scp, SCA_IDL0, 0x7e);

        msci_write_1(scp, SCA_RRC0, 0x0e);
        /* msci_write_1(scp, SCA_TRC00, 0x10); */
        /*
         * the correct values here are important for avoiding underruns
         * for any value less than or equal to TRC0 txrdy is activated
         * which will start the dmac transfer to the fifo.
         * for buffer size >= TRC1 + 1 txrdy is cleared which will stop DMA.
         *
         * thus if we are using a very fast clock that empties the fifo
         * quickly, delays in the dmac starting to fill the fifo can
         * lead to underruns so we want a fairly full fifo to still
         * cause the dmac to start.  for cards with on board ram this
         * has no effect on system performance.  For cards that DMA
         * to/from system memory it will cause more, shorter,
         * bus accesses rather than fewer longer ones.
         */
        msci_write_1(scp, SCA_TRC00, 0x00);
        msci_write_1(scp, SCA_TRC10, 0x1f);
}

/*
 * Take the memory for the port and construct two circular linked lists of
 * descriptors (one tx, one rx) and set the pointers in these descriptors
 * to point to the buffer space for this port.
 */
static void
sca_dmac_init(struct sca_softc *sc, sca_port_t *scp)
{
        sca_desc_t *desc;
        u_int32_t desc_p;
        u_int32_t buf_p;
        int i;

        if (sc->sc_usedma)
                bus_dmamap_sync(sc->scu_dmat, sc->scu_dmam, 0, sc->scu_allocsize,
                    BUS_DMASYNC_PREWRITE);
        else {
                /*
                 * XXX assumes that all tx desc and bufs in same page
                 */
                sc->scu_page_on(sc);
                sc->scu_set_page(sc, scp->sp_txdesc_p);
        }

        desc = scp->sp_txdesc;
        desc_p = scp->sp_txdesc_p;
        buf_p = scp->sp_txbuf_p;
        scp->sp_txcur = 0;
        scp->sp_txinuse = 0;

#ifdef DEBUG
        /* make sure that we won't wrap */
        if ((desc_p & 0xffff0000) !=
            ((desc_p + sizeof(*desc) * scp->sp_ntxdesc) & 0xffff0000))
                panic("sca: tx descriptors cross architecural boundary");
        if ((buf_p & 0xff000000) !=
            ((buf_p + SCA_BSIZE * scp->sp_ntxdesc) & 0xff000000))
                panic("sca: tx buffers cross architecural boundary");
#endif

        for (i = 0 ; i < scp->sp_ntxdesc ; i++) {
                /*
                 * desc_p points to the physical address of the NEXT desc
                 */
                desc_p += sizeof(sca_desc_t);

                sca_desc_write_chainp(sc, desc, desc_p & 0x0000ffff);
                sca_desc_write_bufp(sc, desc, buf_p);
                sca_desc_write_buflen(sc, desc, SCA_BSIZE);
                sca_desc_write_stat(sc, desc, 0);

                desc++;  /* point to the next descriptor */
                buf_p += SCA_BSIZE;
        }

        /*
         * "heal" the circular list by making the last entry point to the
         * first.
         */
        sca_desc_write_chainp(sc, desc - 1, scp->sp_txdesc_p & 0x0000ffff);

        /*
         * Now, initialize the transmit DMA logic
         *
         * CPB == chain pointer base address
         */
        dmac_write_1(scp, SCA_DSR1, 0);
        dmac_write_1(scp, SCA_DCR1, SCA_DCR_ABRT);
        dmac_write_1(scp, SCA_DMR1, SCA_DMR_TMOD | SCA_DMR_NF);
        /* XXX1
        dmac_write_1(scp, SCA_DIR1,
                     (SCA_DIR_EOT | SCA_DIR_BOF | SCA_DIR_COF));
         */
        dmac_write_1(scp, SCA_DIR1,
                     (SCA_DIR_EOM | SCA_DIR_EOT | SCA_DIR_BOF | SCA_DIR_COF));
        dmac_write_1(scp, SCA_CPB1,
                     (u_int8_t)((scp->sp_txdesc_p & 0x00ff0000) >> 16));

        /*
         * now, do the same thing for receive descriptors
         *
         * XXX assumes that all rx desc and bufs in same page
         */
        if (!sc->sc_usedma)
                sc->scu_set_page(sc, scp->sp_rxdesc_p);

        desc = scp->sp_rxdesc;
        desc_p = scp->sp_rxdesc_p;
        buf_p = scp->sp_rxbuf_p;

#ifdef DEBUG
        /* make sure that we won't wrap */
        if ((desc_p & 0xffff0000) !=
            ((desc_p + sizeof(*desc) * scp->sp_nrxdesc) & 0xffff0000))
                panic("sca: rx descriptors cross architecural boundary");
        if ((buf_p & 0xff000000) !=
            ((buf_p + SCA_BSIZE * scp->sp_nrxdesc) & 0xff000000))
                panic("sca: rx buffers cross architecural boundary");
#endif

        for (i = 0 ; i < scp->sp_nrxdesc; i++) {
                /*
                 * desc_p points to the physical address of the NEXT desc
                 */
                desc_p += sizeof(sca_desc_t);

                sca_desc_write_chainp(sc, desc, desc_p & 0x0000ffff);
                sca_desc_write_bufp(sc, desc, buf_p);
                /* sca_desc_write_buflen(sc, desc, SCA_BSIZE); */
                sca_desc_write_buflen(sc, desc, 0);
                sca_desc_write_stat(sc, desc, 0);

                desc++;  /* point to the next descriptor */
                buf_p += SCA_BSIZE;
        }

        /*
         * "heal" the circular list by making the last entry point to the
         * first.
         */
        sca_desc_write_chainp(sc, desc - 1, scp->sp_rxdesc_p & 0x0000ffff);

        sca_dmac_rxinit(scp);

        if (sc->sc_usedma)
                bus_dmamap_sync(sc->scu_dmat, sc->scu_dmam,
                    0, sc->scu_allocsize, BUS_DMASYNC_POSTWRITE);
        else
                sc->scu_page_off(sc);
}

/*
 * reset and reinitialize the receive DMA logic
 */
static void
sca_dmac_rxinit(sca_port_t *scp)
{
        /*
         * ... and the receive DMA logic ...
         */
        dmac_write_1(scp, SCA_DSR0, 0);  /* disable DMA */
        dmac_write_1(scp, SCA_DCR0, SCA_DCR_ABRT);

        dmac_write_1(scp, SCA_DMR0, SCA_DMR_TMOD | SCA_DMR_NF);
        dmac_write_2(scp, SCA_BFLL0, SCA_BSIZE);

        /* reset descriptors to initial state */
        scp->sp_rxstart = 0;
        scp->sp_rxend = scp->sp_nrxdesc - 1;

        /*
         * CPB == chain pointer base
         * CDA == current descriptor address
         * EDA == error descriptor address (overwrite position)
         *      because cda can't be eda when starting we always
         *      have a single buffer gap between cda and eda
         */
        dmac_write_1(scp, SCA_CPB0,
            (u_int8_t)((scp->sp_rxdesc_p & 0x00ff0000) >> 16));
        dmac_write_2(scp, SCA_CDAL0, (u_int16_t)(scp->sp_rxdesc_p & 0xffff));
        dmac_write_2(scp, SCA_EDAL0, (u_int16_t)
            (scp->sp_rxdesc_p + (sizeof(sca_desc_t) * scp->sp_rxend)));

        /*
         * enable receiver DMA
         */
        dmac_write_1(scp, SCA_DIR0,
                     (SCA_DIR_EOT | SCA_DIR_EOM | SCA_DIR_BOF | SCA_DIR_COF));
        dmac_write_1(scp, SCA_DSR0, SCA_DSR_DE);
}

/*
 * Queue the packet for our start routine to transmit
 */
static int
sca_output(
    struct ifnet *ifp,
    struct mbuf *m,
    const struct sockaddr *dst,
    const struct rtentry *rt0)
{
        struct hdlc_header *hdlc;
        struct ifqueue *ifq = NULL;
        int s, error, len;
        short mflags;

        error = 0;

        if ((ifp->if_flags & IFF_UP) != IFF_UP) {
                error = ENETDOWN;
                goto bad;
        }

        /*
         * If the queueing discipline needs packet classification,
         * do it before prepending link headers.
         */
        IFQ_CLASSIFY(&ifp->if_snd, m, dst->sa_family);

        /*
         * determine address family, and priority for this packet
         */
        switch (dst->sa_family) {
#ifdef INET
        case AF_INET:
#ifdef SCA_USE_FASTQ
                if ((mtod(m, struct ip *)->ip_tos & IPTOS_LOWDELAY)
                    == IPTOS_LOWDELAY)
                        ifq = &((sca_port_t *)ifp->if_softc)->fastq;
#endif
                /*
                 * Add cisco serial line header. If there is no
                 * space in the first mbuf, allocate another.
                 */
                M_PREPEND(m, sizeof(struct hdlc_header), M_DONTWAIT);
                if (m == 0)
                        return (ENOBUFS);
                hdlc = mtod(m, struct hdlc_header *);
                hdlc->h_proto = htons(HDLC_PROTOCOL_IP);
                break;
#endif
#ifdef INET6
        case AF_INET6:
                /*
                 * Add cisco serial line header. If there is no
                 * space in the first mbuf, allocate another.
                 */
                M_PREPEND(m, sizeof(struct hdlc_header), M_DONTWAIT);
                if (m == 0)
                        return (ENOBUFS);
                hdlc = mtod(m, struct hdlc_header *);
                hdlc->h_proto = htons(HDLC_PROTOCOL_IPV6);
                break;
#endif
        default:
                printf("%s: address family %d unsupported\n",
                       ifp->if_xname, dst->sa_family);
                error = EAFNOSUPPORT;
                goto bad;
        }

        /* finish */
        if ((m->m_flags & (M_BCAST | M_MCAST)) != 0)
                hdlc->h_addr = CISCO_MULTICAST;
        else
                hdlc->h_addr = CISCO_UNICAST;
        hdlc->h_resv = 0;

        /*
         * queue the packet.  If interactive, use the fast queue.
         */
        mflags = m->m_flags;
        len = m->m_pkthdr.len;
        s = splnet();
        if (ifq != NULL) {
                if (IF_QFULL(ifq)) {
                        IF_DROP(ifq);
                        m_freem(m);
                        error = ENOBUFS;
                } else
                        IF_ENQUEUE(ifq, m);
        } else
                IFQ_ENQUEUE(&ifp->if_snd, m, error);
        net_stat_ref_t nsr = IF_STAT_GETREF(ifp);
        if (error != 0) {
                if_statinc_ref(ifp, nsr, if_oerrors);
                if_statinc_ref(ifp, nsr, if_collisions);
                IF_STAT_PUTREF(ifp);
                splx(s);
                return (error);
        }
        if_statadd_ref(ifp, nsr, if_obytes, len);
        if (mflags & M_MCAST)
                if_statinc_ref(ifp, nsr, if_omcasts);
        IF_STAT_PUTREF(ifp);

        sca_start(ifp);
        splx(s);

        return (error);

 bad:
        m_freem(m);
        return (error);
}

static int
sca_ioctl(struct ifnet *ifp, u_long cmd, void *data)
{
        struct ifreq *ifr;
        struct ifaddr *ifa;
        int error;
        int s;

        s = splnet();

        ifr = (struct ifreq *)data;
        ifa = (struct ifaddr *)data;
        error = 0;

        switch (cmd) {
        case SIOCINITIFADDR:
                switch(ifa->ifa_addr->sa_family) {
#ifdef INET
                case AF_INET:
#endif
#ifdef INET6
                case AF_INET6:
#endif
#if defined(INET) || defined(INET6)
                        ifp->if_flags |= IFF_UP;
                        sca_port_up(ifp->if_softc);
                        break;
#endif
                default:
                        error = EAFNOSUPPORT;
                        break;
                }
                break;

        case SIOCSIFDSTADDR:
#ifdef INET
                if (ifa->ifa_addr->sa_family == AF_INET)
                        break;
#endif
#ifdef INET6
                if (ifa->ifa_addr->sa_family == AF_INET6)
                        break;
#endif
                error = EAFNOSUPPORT;
                break;

        case SIOCADDMULTI:
        case SIOCDELMULTI:
                /* XXX need multicast group management code */
                if (ifr == 0) {
                        error = EAFNOSUPPORT;           /* XXX */
                        break;
                }
                switch (ifreq_getaddr(cmd, ifr)->sa_family) {
#ifdef INET
                case AF_INET:
                        break;
#endif
#ifdef INET6
                case AF_INET6:
                        break;
#endif
                default:
                        error = EAFNOSUPPORT;
                        break;
                }
                break;

        case SIOCSIFFLAGS:
                if ((error = ifioctl_common(ifp, cmd, data)) != 0)
                        break;
                if (ifr->ifr_flags & IFF_UP) {
                        ifp->if_flags |= IFF_UP;
                        sca_port_up(ifp->if_softc);
                } else {
                        ifp->if_flags &= ~IFF_UP;
                        sca_port_down(ifp->if_softc);
                }

                break;

        default:
                error = ifioctl_common(ifp, cmd, data);
        }

        splx(s);
        return error;
}

/*
 * start packet transmission on the interface
 *
 * MUST BE CALLED AT splnet()
 */
static void
sca_start(struct ifnet *ifp)
{
        sca_port_t *scp = ifp->if_softc;
        struct sca_softc *sc = scp->sca;
        struct mbuf *m, *mb_head;
        sca_desc_t *desc;
        u_int8_t *buf, stat;
        u_int32_t buf_p;
        int nexttx;
        int trigger_xmit;
        u_int len;

        SCA_DPRINTF(SCA_DEBUG_TX, ("TX: enter start\n"));

        /*
         * can't queue when we are full or transmitter is busy
         */
#ifdef oldcode
        if ((scp->sp_txinuse >= (scp->sp_ntxdesc - 1))
            || ((ifp->if_flags & IFF_OACTIVE) == IFF_OACTIVE))
                return;
#else
        if (scp->sp_txinuse
            || ((ifp->if_flags & IFF_OACTIVE) == IFF_OACTIVE))
                return;
#endif
        SCA_DPRINTF(SCA_DEBUG_TX, ("TX: txinuse %d\n", scp->sp_txinuse));

        /*
         * XXX assume that all tx desc and bufs in same page
         */
        if (sc->sc_usedma)
                bus_dmamap_sync(sc->scu_dmat, sc->scu_dmam,
                    0, sc->scu_allocsize,
                    BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
        else {
                sc->scu_page_on(sc);
                sc->scu_set_page(sc, scp->sp_txdesc_p);
        }

        trigger_xmit = 0;

 txloop:
        IF_DEQUEUE(&scp->linkq, mb_head);
        if (mb_head == NULL)
#ifdef SCA_USE_FASTQ
                IF_DEQUEUE(&scp->fastq, mb_head);
        if (mb_head == NULL)
#endif
                IFQ_DEQUEUE(&ifp->if_snd, mb_head);
        if (mb_head == NULL)
                goto start_xmit;

        SCA_DPRINTF(SCA_DEBUG_TX, ("TX: got mbuf\n"));
#ifdef oldcode
        if (scp->txinuse != 0) {
                /* Kill EOT interrupts on the previous descriptor. */
                desc = &scp->sp_txdesc[scp->txcur];
                stat = sca_desc_read_stat(sc, desc);
                sca_desc_write_stat(sc, desc, stat & ~SCA_DESC_EOT);

                /* Figure out what the next free descriptor is. */
                nexttx = (scp->sp_txcur + 1) % scp->sp_ntxdesc;
        } else
                nexttx = 0;
#endif  /* oldcode */

        if (scp->sp_txinuse)
                nexttx = (scp->sp_txcur + 1) % scp->sp_ntxdesc;
        else
                nexttx = 0;

        SCA_DPRINTF(SCA_DEBUG_TX, ("TX: nexttx %d\n", nexttx));

        buf = scp->sp_txbuf + SCA_BSIZE * nexttx;
        buf_p = scp->sp_txbuf_p + SCA_BSIZE * nexttx;

        /* XXX hoping we can delay the desc write till after we don't drop. */
        desc = &scp->sp_txdesc[nexttx];

        /* XXX isn't this set already?? */
        sca_desc_write_bufp(sc, desc, buf_p);
        len = 0;

        SCA_DPRINTF(SCA_DEBUG_TX, ("TX: buf %x buf_p %x\n", (u_int)buf, buf_p));

#if 0   /* uncomment this for a core in cc1 */
X
#endif
        /*
         * Run through the chain, copying data into the descriptor as we
         * go.  If it won't fit in one transmission block, drop the packet.
         * No, this isn't nice, but most of the time it _will_ fit.
         */
        for (m = mb_head ; m != NULL ; m = m->m_next) {
                if (m->m_len != 0) {
                        len += m->m_len;
                        if (len > SCA_BSIZE) {
                                m_freem(mb_head);
                                goto txloop;
                        }
                        SCA_DPRINTF(SCA_DEBUG_TX,
                            ("TX: about to mbuf len %d\n", m->m_len));

                        if (sc->sc_usedma)
                                memcpy(buf, mtod(m, u_int8_t *), m->m_len);
                        else
                                bus_space_write_region_1(sc->scu_memt,
                                    sc->scu_memh, sca_page_addr(sc, buf_p),
                                    mtod(m, u_int8_t *), m->m_len);
                        buf += m->m_len;
                        buf_p += m->m_len;
                }
        }

        /* set the buffer, the length, and mark end of frame and end of xfer */
        sca_desc_write_buflen(sc, desc, len);
        sca_desc_write_stat(sc, desc, SCA_DESC_EOM);

        if_statinc(ifp, if_opackets);

        /*
         * Pass packet to bpf if there is a listener.
         */
        bpf_mtap(ifp, mb_head, BPF_D_OUT);

        m_freem(mb_head);

        scp->sp_txcur = nexttx;
        scp->sp_txinuse++;
        trigger_xmit = 1;

        SCA_DPRINTF(SCA_DEBUG_TX,
            ("TX: inuse %d index %d\n", scp->sp_txinuse, scp->sp_txcur));

        /*
         * XXX so didn't this used to limit us to 1?! - multi may be untested
         * sp_ntxdesc used to be hard coded to 2 with claim of a too hard
         * to find bug
         */
#ifdef oldcode
        if (scp->sp_txinuse < (scp->sp_ntxdesc - 1))
#endif
        if (scp->sp_txinuse < scp->sp_ntxdesc)
                goto txloop;

 start_xmit:
        SCA_DPRINTF(SCA_DEBUG_TX, ("TX: trigger_xmit %d\n", trigger_xmit));

        if (trigger_xmit != 0) {
                /* set EOT on final descriptor */
                desc = &scp->sp_txdesc[scp->sp_txcur];
                stat = sca_desc_read_stat(sc, desc);
                sca_desc_write_stat(sc, desc, stat | SCA_DESC_EOT);
        }

        if (sc->sc_usedma)
                bus_dmamap_sync(sc->scu_dmat, sc->scu_dmam, 0,
                    sc->scu_allocsize,
                    BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);

        if (trigger_xmit != 0)
                sca_port_starttx(scp);

        if (!sc->sc_usedma)
                sc->scu_page_off(sc);
}

static void
sca_watchdog(struct ifnet *ifp)
{
}

int
sca_hardintr(struct sca_softc *sc)
{
        u_int8_t isr0, isr1, isr2;
        int     ret;

        ret = 0;  /* non-zero means we processed at least one interrupt */

        SCA_DPRINTF(SCA_DEBUG_INTR, ("sca_hardintr entered\n"));

        while (1) {
                /*
                 * read SCA interrupts
                 */
                isr0 = sca_read_1(sc, SCA_ISR0);
                isr1 = sca_read_1(sc, SCA_ISR1);
                isr2 = sca_read_1(sc, SCA_ISR2);

                if (isr0 == 0 && isr1 == 0 && isr2 == 0)
                        break;

                SCA_DPRINTF(SCA_DEBUG_INTR,
                            ("isr0 = %02x, isr1 = %02x, isr2 = %02x\n",
                             isr0, isr1, isr2));

                /*
                 * check DMAC interrupt
                 */
                if (isr1 & 0x0f)
                        ret += sca_dmac_intr(&sc->sc_ports[0],
                                             isr1 & 0x0f);

                if (isr1 & 0xf0)
                        ret += sca_dmac_intr(&sc->sc_ports[1],
                             (isr1 & 0xf0) >> 4);

                /*
                 * msci interrupts
                 */
                if (isr0 & 0x0f)
                        ret += sca_msci_intr(&sc->sc_ports[0], isr0 & 0x0f);

                if (isr0 & 0xf0)
                        ret += sca_msci_intr(&sc->sc_ports[1],
                            (isr0 & 0xf0) >> 4);

#if 0 /* We don't GET timer interrupts, we have them disabled (msci IE20) */
                if (isr2)
                        ret += sca_timer_intr(sc, isr2);
#endif
        }

        return (ret);
}

static int
sca_dmac_intr(sca_port_t *scp, u_int8_t isr)
{
        u_int8_t         dsr;
        int              ret;

        ret = 0;

        /*
         * Check transmit channel
         */
        if (isr & (SCA_ISR1_DMAC_TX0A | SCA_ISR1_DMAC_TX0B)) {
                SCA_DPRINTF(SCA_DEBUG_INTR,
                    ("TX INTERRUPT port %d\n", scp->sp_port));

                dsr = 1;
                while (dsr != 0) {
                        ret++;
                        /*
                         * reset interrupt
                         */
                        dsr = dmac_read_1(scp, SCA_DSR1);
                        dmac_write_1(scp, SCA_DSR1,
                                     dsr | SCA_DSR_DEWD);

                        /*
                         * filter out the bits we don't care about
                         */
                        dsr &= ( SCA_DSR_COF | SCA_DSR_BOF | SCA_DSR_EOT);
                        if (dsr == 0)
                                break;

                        /*
                         * check for counter overflow
                         */
                        if (dsr & SCA_DSR_COF) {
                                printf("%s: TXDMA counter overflow\n",
                                       scp->sp_if.if_xname);

                                scp->sp_if.if_flags &= ~IFF_OACTIVE;
                                scp->sp_txcur = 0;
                                scp->sp_txinuse = 0;
                        }

                        /*
                         * check for buffer overflow
                         */
                        if (dsr & SCA_DSR_BOF) {
                                printf("%s: TXDMA buffer overflow, cda 0x%04x, eda 0x%04x, cpb 0x%02x\n",
                                       scp->sp_if.if_xname,
                                       dmac_read_2(scp, SCA_CDAL1),
                                       dmac_read_2(scp, SCA_EDAL1),
                                       dmac_read_1(scp, SCA_CPB1));

                                /*
                                 * Yikes.  Arrange for a full
                                 * transmitter restart.
                                 */
                                scp->sp_if.if_flags &= ~IFF_OACTIVE;
                                scp->sp_txcur = 0;
                                scp->sp_txinuse = 0;
                        }

                        /*
                         * check for end of transfer, which is not
                         * an error. It means that all data queued
                         * was transmitted, and we mark ourself as
                         * not in use and stop the watchdog timer.
                         */
                        if (dsr & SCA_DSR_EOT) {
                                SCA_DPRINTF(SCA_DEBUG_TX,
                            ("Transmit completed. cda %x eda %x dsr %x\n",
                                    dmac_read_2(scp, SCA_CDAL1),
                                    dmac_read_2(scp, SCA_EDAL1),
                                    dsr));

                                scp->sp_if.if_flags &= ~IFF_OACTIVE;
                                scp->sp_txcur = 0;
                                scp->sp_txinuse = 0;

                                /*
                                 * check for more packets
                                 */
                                if_schedule_deferred_start(&scp->sp_if);
                        }
                }
        }
        /*
         * receive channel check
         */
        if (isr & (SCA_ISR1_DMAC_RX0A | SCA_ISR1_DMAC_RX0B)) {
                SCA_DPRINTF(SCA_DEBUG_INTR, ("RX INTERRUPT port %d\n",
                    (scp == &scp->sca->sc_ports[0] ? 0 : 1)));

                dsr = 1;
                while (dsr != 0) {
                        ret++;

                        dsr = dmac_read_1(scp, SCA_DSR0);
                        dmac_write_1(scp, SCA_DSR0, dsr | SCA_DSR_DEWD);

                        /*
                         * filter out the bits we don't care about
                         */
                        dsr &= (SCA_DSR_EOM | SCA_DSR_COF
                                | SCA_DSR_BOF | SCA_DSR_EOT);
                        if (dsr == 0)
                                break;

                        /*
                         * End of frame
                         */
                        if (dsr & SCA_DSR_EOM) {
                                SCA_DPRINTF(SCA_DEBUG_RX, ("Got a frame!\n"));

                                sca_get_packets(scp);
                        }

                        /*
                         * check for counter overflow
                         */
                        if (dsr & SCA_DSR_COF) {
                                printf("%s: RXDMA counter overflow\n",
                                       scp->sp_if.if_xname);

                                sca_dmac_rxinit(scp);
                        }

                        /*
                         * check for end of transfer, which means we
                         * ran out of descriptors to receive into.
                         * This means the line is much faster than
                         * we can handle.
                         */
                        if (dsr & (SCA_DSR_BOF | SCA_DSR_EOT)) {
                                printf("%s: RXDMA buffer overflow\n",
                                       scp->sp_if.if_xname);

                                sca_dmac_rxinit(scp);
                        }
                }
        }

        return ret;
}

static int
sca_msci_intr(sca_port_t *scp, u_int8_t isr)
{
        u_int8_t st1, trc0;

        /* get and clear the specific interrupt -- should act on it :)*/
        if ((st1 = msci_read_1(scp, SCA_ST10))) {
                /* clear the interrupt */
                msci_write_1(scp, SCA_ST10, st1);

                if (st1 & SCA_ST1_UDRN) {
                        /* underrun -- try to increase ready control */
                        trc0 = msci_read_1(scp, SCA_TRC00);
                        if (trc0 == 0x1f)
                                printf("TX: underrun - fifo depth maxed\n");
                        else {
                                if ((trc0 += 2) > 0x1f)
                                        trc0 = 0x1f;
                                SCA_DPRINTF(SCA_DEBUG_TX,
                                   ("TX: udrn - incr fifo to %d\n", trc0));
                                msci_write_1(scp, SCA_TRC00, trc0);
                        }
                }
        }
        return (0);
}

static void
sca_get_packets(sca_port_t *scp)
{
        struct sca_softc *sc;

        SCA_DPRINTF(SCA_DEBUG_RX, ("RX: sca_get_packets\n"));

        sc = scp->sca;
        if (sc->sc_usedma)
                bus_dmamap_sync(sc->scu_dmat, sc->scu_dmam,
                    0, sc->scu_allocsize,
                    BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
        else {
                /*
                 * XXX this code is unable to deal with rx stuff
                 * in more than 1 page
                 */
                sc->scu_page_on(sc);
                sc->scu_set_page(sc, scp->sp_rxdesc_p);
        }

        /* process as many frames as are available */
        while (sca_frame_avail(scp)) {
                sca_frame_process(scp);
                sca_frame_read_done(scp);
        }

        if (sc->sc_usedma)
                bus_dmamap_sync(sc->scu_dmat, sc->scu_dmam,
                    0, sc->scu_allocsize,
                    BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
        else
                sc->scu_page_off(sc);
}

/*
 * Starting with the first descriptor we wanted to read into, up to but
 * not including the current SCA read descriptor, look for a packet.
 *
 * must be called at splnet()
 */
static int
sca_frame_avail(sca_port_t *scp)
{
        u_int16_t cda;
        u_int32_t desc_p;       /* physical address (lower 16 bits) */
        sca_desc_t *desc;
        u_int8_t rxstat;
        int cdaidx, toolong;

        /*
         * Read the current descriptor from the SCA.
         */
        cda = dmac_read_2(scp, SCA_CDAL0);

        /*
         * calculate the index of the current descriptor
         */
        desc_p = (scp->sp_rxdesc_p & 0xFFFF);
        desc_p = cda - desc_p;
        cdaidx = desc_p / sizeof(sca_desc_t);

        SCA_DPRINTF(SCA_DEBUG_RX,
            ("RX: cda %x desc_p %x cdaidx %u, nrxdesc %d rxstart %d\n",
            cda, desc_p, cdaidx, scp->sp_nrxdesc, scp->sp_rxstart));

        /* note confusion */
        if (cdaidx >= scp->sp_nrxdesc)
                panic("current descriptor index out of range");

        /* see if we have a valid frame available */
        toolong = 0;
        for (; scp->sp_rxstart != cdaidx; sca_frame_read_done(scp)) {
                /*
                 * We might have a valid descriptor.  Set up a pointer
                 * to the kva address for it so we can more easily examine
                 * the contents.
                 */
                desc = &scp->sp_rxdesc[scp->sp_rxstart];
                rxstat = sca_desc_read_stat(scp->sca, desc);

                SCA_DPRINTF(SCA_DEBUG_RX, ("port %d RX: idx %d rxstat %x\n",
                    scp->sp_port, scp->sp_rxstart, rxstat));

                SCA_DPRINTF(SCA_DEBUG_RX, ("port %d RX: buflen %d\n",
                    scp->sp_port, sca_desc_read_buflen(scp->sca, desc)));

                /*
                 * check for errors
                 */
                if (rxstat & SCA_DESC_ERRORS) {
                        /*
                         * consider an error condition the end
                         * of a frame
                         */
                        if_statinc(&scp->sp_if, if_ierrors);
                        toolong = 0;
                        continue;
                }

                /*
                 * if we aren't skipping overlong frames
                 * we are done, otherwise reset and look for
                 * another good frame
                 */
                if (rxstat & SCA_DESC_EOM) {
                        if (!toolong)
                                return (1);
                        toolong = 0;
                } else if (!toolong) {
                        /*
                         * we currently don't deal with frames
                         * larger than a single buffer (fixed MTU)
                         */
                        if_statinc(&scp->sp_if, if_ierrors);
                        toolong = 1;
                }
                SCA_DPRINTF(SCA_DEBUG_RX, ("RX: idx %d no EOM\n",
                    scp->sp_rxstart));
        }

        SCA_DPRINTF(SCA_DEBUG_RX, ("RX: returning none\n"));
        return 0;
}

/*
 * Pass the packet up to the kernel if it is a packet we want to pay
 * attention to.
 *
 * MUST BE CALLED AT splnet()
 */
static void
sca_frame_process(sca_port_t *scp)
{
        pktqueue_t *pktq = NULL;
        struct hdlc_header *hdlc;
        struct cisco_pkt *cisco;
        sca_desc_t *desc;
        struct mbuf *m;
        u_int8_t *bufp;
        u_int16_t len;
        u_int32_t t;

        t = time_uptime * 1000;
        desc = &scp->sp_rxdesc[scp->sp_rxstart];
        bufp = scp->sp_rxbuf + SCA_BSIZE * scp->sp_rxstart;
        len = sca_desc_read_buflen(scp->sca, desc);

        SCA_DPRINTF(SCA_DEBUG_RX,
            ("RX: desc %lx bufp %lx len %d\n", (bus_addr_t)desc,
            (bus_addr_t)bufp, len));

#if SCA_DEBUG_LEVEL > 0
        if (sca_debug & SCA_DEBUG_RXPKT)
                sca_frame_print(scp, desc, bufp);
#endif
        /*
         * skip packets that are too short
         */
        if (len < sizeof(struct hdlc_header)) {
                if_statinc(&scp->sp_if, if_ierrors);
                return;
        }

        m = sca_mbuf_alloc(scp->sca, bufp, len);
        if (m == NULL) {
                SCA_DPRINTF(SCA_DEBUG_RX, ("RX: no mbuf!\n"));
                return;
        }

        /*
         * read and then strip off the HDLC information
         */
        m = m_pullup(m, sizeof(struct hdlc_header));
        if (m == NULL) {
                SCA_DPRINTF(SCA_DEBUG_RX, ("RX: no m_pullup!\n"));
                return;
        }

        bpf_mtap_softint(&scp->sp_if, m);

        if_statinc(&scp->sp_if, if_ipackets);

        hdlc = mtod(m, struct hdlc_header *);
        switch (ntohs(hdlc->h_proto)) {
#ifdef INET
        case HDLC_PROTOCOL_IP:
                SCA_DPRINTF(SCA_DEBUG_RX, ("Received IP packet\n"));
                m_set_rcvif(m, &scp->sp_if);
                m->m_pkthdr.len -= sizeof(struct hdlc_header);
                m->m_data += sizeof(struct hdlc_header);
                m->m_len -= sizeof(struct hdlc_header);
                pktq = ip_pktq;
                break;
#endif  /* INET */
#ifdef INET6
        case HDLC_PROTOCOL_IPV6:
                SCA_DPRINTF(SCA_DEBUG_RX, ("Received IP packet\n"));
                m_set_rcvif(m, &scp->sp_if);
                m->m_pkthdr.len -= sizeof(struct hdlc_header);
                m->m_data += sizeof(struct hdlc_header);
                m->m_len -= sizeof(struct hdlc_header);
                pktq = ip6_pktq;
                break;
#endif  /* INET6 */
        case CISCO_KEEPALIVE:
                SCA_DPRINTF(SCA_DEBUG_CISCO,
                            ("Received CISCO keepalive packet\n"));

                if (len < CISCO_PKT_LEN) {
                        SCA_DPRINTF(SCA_DEBUG_CISCO,
                                    ("short CISCO packet %d, wanted %d\n",
                                     len, CISCO_PKT_LEN));
                        if_statinc(&scp->sp_if, if_ierrors);
                        goto dropit;
                }

                m = m_pullup(m, sizeof(struct cisco_pkt));
                if (m == NULL) {
                        SCA_DPRINTF(SCA_DEBUG_RX, ("RX: no m_pullup!\n"));
                        return;
                }

                cisco = (struct cisco_pkt *)
                    (mtod(m, u_int8_t *) + HDLC_HDRLEN);
                m_set_rcvif(m, &scp->sp_if);

                switch (ntohl(cisco->type)) {
                case CISCO_ADDR_REQ:
                        printf("Got CISCO addr_req, ignoring\n");
                        if_statinc(&scp->sp_if, if_ierrors);
                        goto dropit;

                case CISCO_ADDR_REPLY:
                        printf("Got CISCO addr_reply, ignoring\n");
                        if_statinc(&scp->sp_if, if_ierrors);
                        goto dropit;

                case CISCO_KEEPALIVE_REQ:

                        SCA_DPRINTF(SCA_DEBUG_CISCO,
                                    ("Received KA, mseq %d,"
                                     " yseq %d, rel 0x%04x, t0"
                                     " %04x, t1 %04x\n",
                                     ntohl(cisco->par1), ntohl(cisco->par2),
                                     ntohs(cisco->rel), ntohs(cisco->time0),
                                     ntohs(cisco->time1)));

                        scp->cka_lastrx = ntohl(cisco->par1);
                        scp->cka_lasttx++;

                        /*
                         * schedule the transmit right here.
                         */
                        cisco->par2 = cisco->par1;
                        cisco->par1 = htonl(scp->cka_lasttx);
                        cisco->time0 = htons((u_int16_t)(t >> 16));
                        cisco->time1 = htons((u_int16_t)(t & 0x0000ffff));

                        if (IF_QFULL(&scp->linkq)) {
                                IF_DROP(&scp->linkq);
                                goto dropit;
                        }
                        IF_ENQUEUE(&scp->linkq, m);

                        sca_start(&scp->sp_if);

                        /* since start may have reset this fix */
                        if (!scp->sca->sc_usedma) {
                                scp->sca->scu_set_page(scp->sca,
                                    scp->sp_rxdesc_p);
                                scp->sca->scu_page_on(scp->sca);
                        }
                        return;
                default:
                        SCA_DPRINTF(SCA_DEBUG_CISCO,
                                    ("Unknown CISCO keepalive protocol 0x%04x\n",
                                     ntohl(cisco->type)));

                        if_statinc(&scp->sp_if, if_noproto);
                        goto dropit;
                }
                return;
        default:
                SCA_DPRINTF(SCA_DEBUG_RX,
                            ("Unknown/unexpected ethertype 0x%04x\n",
                             ntohs(hdlc->h_proto)));
                if_statinc(&scp->sp_if, if_noproto);
                goto dropit;
        }

        /* Queue the packet */
        KASSERT(pktq != NULL);
        if (__predict_false(!pktq_enqueue(pktq, m, 0))) {
                if_statinc(&scp->sp_if, if_iqdrops);
                goto dropit;
        }
        return;
dropit:
        m_freem(m);
        return;
}

#if SCA_DEBUG_LEVEL > 0
/*
 * do a hex dump of the packet received into descriptor "desc" with
 * data buffer "p"
 */
static void
sca_frame_print(sca_port_t *scp, sca_desc_t *desc, u_int8_t *p)
{
        int i;
        int nothing_yet = 1;
        struct sca_softc *sc;
        u_int len;

        sc = scp->sca;
        printf("desc va %p: chainp 0x%x bufp 0x%0x stat 0x%0x len %d\n",
               desc,
               sca_desc_read_chainp(sc, desc),
               sca_desc_read_bufp(sc, desc),
               sca_desc_read_stat(sc, desc),
               (len = sca_desc_read_buflen(sc, desc)));

        for (i = 0 ; i < len && i < 256; i++) {
                if (nothing_yet == 1 &&
                    (sc->sc_usedma ? *p
                        : bus_space_read_1(sc->scu_memt, sc->scu_memh,
                    sca_page_addr(sc, p))) == 0) {
                        p++;
                        continue;
                }
                nothing_yet = 0;
                if (i % 16 == 0)
                        printf("\n");
                printf("%02x ",
                    (sc->sc_usedma ? *p
                    : bus_space_read_1(sc->scu_memt, sc->scu_memh,
                    sca_page_addr(sc, p))));
                p++;
        }

        if (i % 16 != 1)
                printf("\n");
}
#endif

/*
 * adjust things because we have just read the current starting
 * frame
 *
 * must be called at splnet()
 */
static void
sca_frame_read_done(sca_port_t *scp)
{
        u_int16_t edesc_p;

        /* update where our indices are */
        scp->sp_rxend = scp->sp_rxstart;
        scp->sp_rxstart = (scp->sp_rxstart + 1) % scp->sp_nrxdesc;

        /* update the error [end] descriptor */
        edesc_p = (u_int16_t)scp->sp_rxdesc_p +
            (sizeof(sca_desc_t) * scp->sp_rxend);
        dmac_write_2(scp, SCA_EDAL0, edesc_p);
}

/*
 * set a port to the "up" state
 */
static void
sca_port_up(sca_port_t *scp)
{
        struct sca_softc *sc = scp->sca;
        struct timeval now;
#if 0
        u_int8_t ier0, ier1;
#endif

        /*
         * reset things
         */
#if 0
        msci_write_1(scp, SCA_CMD0, SCA_CMD_TXRESET);
        msci_write_1(scp, SCA_CMD0, SCA_CMD_RXRESET);
#endif
        /*
         * clear in-use flag
         */
        scp->sp_if.if_flags &= ~IFF_OACTIVE;
        scp->sp_if.if_flags |= IFF_RUNNING;

        /*
         * raise DTR
         */
        sc->sc_dtr_callback(sc->sc_aux, scp->sp_port, 1);

        /*
         * raise RTS
         */
        msci_write_1(scp, SCA_CTL0,
             (msci_read_1(scp, SCA_CTL0) & ~SCA_CTL_RTS_MASK)
             | SCA_CTL_RTS_HIGH);

#if 0
        /*
         * enable interrupts (no timer IER2)
         */
        ier0 = SCA_IER0_MSCI_RXRDY0 | SCA_IER0_MSCI_TXRDY0
            | SCA_IER0_MSCI_RXINT0 | SCA_IER0_MSCI_TXINT0;
        ier1 = SCA_IER1_DMAC_RX0A | SCA_IER1_DMAC_RX0B
            | SCA_IER1_DMAC_TX0A | SCA_IER1_DMAC_TX0B;
        if (scp->sp_port == 1) {
                ier0 <<= 4;
                ier1 <<= 4;
        }
        sca_write_1(sc, SCA_IER0, sca_read_1(sc, SCA_IER0) | ier0);
        sca_write_1(sc, SCA_IER1, sca_read_1(sc, SCA_IER1) | ier1);
#else
        if (scp->sp_port == 0) {
                sca_write_1(sc, SCA_IER0, sca_read_1(sc, SCA_IER0) | 0x0f);
                sca_write_1(sc, SCA_IER1, sca_read_1(sc, SCA_IER1) | 0x0f);
        } else {
                sca_write_1(sc, SCA_IER0, sca_read_1(sc, SCA_IER0) | 0xf0);
                sca_write_1(sc, SCA_IER1, sca_read_1(sc, SCA_IER1) | 0xf0);
        }
#endif

        /*
         * enable transmit and receive
         */
        msci_write_1(scp, SCA_CMD0, SCA_CMD_TXENABLE);
        msci_write_1(scp, SCA_CMD0, SCA_CMD_RXENABLE);

        /*
         * reset internal state
         */
        scp->sp_txinuse = 0;
        scp->sp_txcur = 0;
        getmicrotime(&now);
        scp->cka_lasttx = now.tv_usec;
        scp->cka_lastrx = 0;
}

/*
 * set a port to the "down" state
 */
static void
sca_port_down(sca_port_t *scp)
{
        struct sca_softc *sc = scp->sca;
#if 0
        u_int8_t ier0, ier1;
#endif

        /*
         * lower DTR
         */
        sc->sc_dtr_callback(sc->sc_aux, scp->sp_port, 0);

        /*
         * lower RTS
         */
        msci_write_1(scp, SCA_CTL0,
             (msci_read_1(scp, SCA_CTL0) & ~SCA_CTL_RTS_MASK)
             | SCA_CTL_RTS_LOW);

        /*
         * disable interrupts
         */
#if 0
        ier0 = SCA_IER0_MSCI_RXRDY0 | SCA_IER0_MSCI_TXRDY0
            | SCA_IER0_MSCI_RXINT0 | SCA_IER0_MSCI_TXINT0;
        ier1 = SCA_IER1_DMAC_RX0A | SCA_IER1_DMAC_RX0B
            | SCA_IER1_DMAC_TX0A | SCA_IER1_DMAC_TX0B;
        if (scp->sp_port == 1) {
                ier0 <<= 4;
                ier1 <<= 4;
        }
        sca_write_1(sc, SCA_IER0, sca_read_1(sc, SCA_IER0) & ~ier0);
        sca_write_1(sc, SCA_IER1, sca_read_1(sc, SCA_IER1) & ~ier1);
#else
        if (scp->sp_port == 0) {
                sca_write_1(sc, SCA_IER0, sca_read_1(sc, SCA_IER0) & 0xf0);
                sca_write_1(sc, SCA_IER1, sca_read_1(sc, SCA_IER1) & 0xf0);
        } else {
                sca_write_1(sc, SCA_IER0, sca_read_1(sc, SCA_IER0) & 0x0f);
                sca_write_1(sc, SCA_IER1, sca_read_1(sc, SCA_IER1) & 0x0f);
        }
#endif

        /*
         * disable transmit and receive
         */
        msci_write_1(scp, SCA_CMD0, SCA_CMD_RXDISABLE);
        msci_write_1(scp, SCA_CMD0, SCA_CMD_TXDISABLE);

        /*
         * no, we're not in use anymore
         */
        scp->sp_if.if_flags &= ~(IFF_OACTIVE|IFF_RUNNING);
}

/*
 * disable all DMA and interrupts for all ports at once.
 */
void
sca_shutdown(struct sca_softc *sca)
{
        /*
         * disable DMA and interrupts
         */
        sca_write_1(sca, SCA_DMER, 0);
        sca_write_1(sca, SCA_IER0, 0);
        sca_write_1(sca, SCA_IER1, 0);
}

/*
 * If there are packets to transmit, start the transmit DMA logic.
 */
static void
sca_port_starttx(sca_port_t *scp)
{
        u_int32_t       startdesc_p, enddesc_p;
        int enddesc;

        SCA_DPRINTF(SCA_DEBUG_TX, ("TX: starttx\n"));

        if (((scp->sp_if.if_flags & IFF_OACTIVE) == IFF_OACTIVE)
            || scp->sp_txinuse == 0)
                return;

        SCA_DPRINTF(SCA_DEBUG_TX, ("TX: setting oactive\n"));

        scp->sp_if.if_flags |= IFF_OACTIVE;

        /*
         * We have something to do, since we have at least one packet
         * waiting, and we are not already marked as active.
         */
        enddesc = (scp->sp_txcur + 1) % scp->sp_ntxdesc;
        startdesc_p = scp->sp_txdesc_p;
        enddesc_p = scp->sp_txdesc_p + sizeof(sca_desc_t) * enddesc;

        SCA_DPRINTF(SCA_DEBUG_TX, ("TX: start %x end %x\n",
            startdesc_p, enddesc_p));

        dmac_write_2(scp, SCA_EDAL1, (u_int16_t)(enddesc_p & 0x0000ffff));
        dmac_write_2(scp, SCA_CDAL1,
                     (u_int16_t)(startdesc_p & 0x0000ffff));

        /*
         * enable the DMA
         */
        dmac_write_1(scp, SCA_DSR1, SCA_DSR_DE);
}

/*
 * allocate an mbuf at least long enough to hold "len" bytes.
 * If "p" is non-NULL, copy "len" bytes from it into the new mbuf,
 * otherwise let the caller handle copying the data in.
 */
static struct mbuf *
sca_mbuf_alloc(struct sca_softc *sc, void *p, u_int len)
{
        struct mbuf *m;

        /*
         * allocate an mbuf and copy the important bits of data
         * into it.  If the packet won't fit in the header,
         * allocate a cluster for it and store it there.
         */
        MGETHDR(m, M_DONTWAIT, MT_DATA);
        if (m == NULL)
                return NULL;
        if (len > MHLEN) {
                if (len > MCLBYTES) {
                        m_freem(m);
                        return NULL;
                }
                MCLGET(m, M_DONTWAIT);
                if ((m->m_flags & M_EXT) == 0) {
                        m_freem(m);
                        return NULL;
                }
        }
        if (p != NULL) {
                /* XXX do we need to sync here? */
                if (sc->sc_usedma)
                        memcpy(mtod(m, void *), p, len);
                else
                        bus_space_read_region_1(sc->scu_memt, sc->scu_memh,
                            sca_page_addr(sc, p), mtod(m, u_int8_t *), len);
        }
        m->m_len = len;
        m->m_pkthdr.len = len;

        return (m);
}

/*
 * get the base clock
 */
void
sca_get_base_clock(struct sca_softc *sc)
{
        struct timeval btv, ctv, dtv;
        u_int64_t bcnt;
        u_int32_t cnt;
        u_int16_t subcnt;

        /* disable the timer, set prescale to 0 */
        sca_write_1(sc, SCA_TCSR0, 0);
        sca_write_1(sc, SCA_TEPR0, 0);

        /* reset the counter */
        (void)sca_read_1(sc, SCA_TCSR0);
        subcnt = sca_read_2(sc, SCA_TCNTL0);

        /* count to max */
        sca_write_2(sc, SCA_TCONRL0, 0xffff);

        cnt = 0;
        microtime(&btv);
        /* start the timer -- no interrupt enable */
        sca_write_1(sc, SCA_TCSR0, SCA_TCSR_TME);
        for (;;) {
                microtime(&ctv);

                /* end around 3/4 of a second */
                timersub(&ctv, &btv, &dtv);
                if (dtv.tv_usec >= 750000)
                        break;

                /* spin */
                while (!(sca_read_1(sc, SCA_TCSR0) & SCA_TCSR_CMF))
                        ;
                /* reset the timer */
                (void)sca_read_2(sc, SCA_TCNTL0);
                cnt++;
        }

        /* stop the timer */
        sca_write_1(sc, SCA_TCSR0, 0);

        subcnt = sca_read_2(sc, SCA_TCNTL0);
        /* add the slop in and get the total timer ticks */
        cnt = (cnt << 16) | subcnt;

        /* cnt is 1/8 the actual time */
        bcnt = cnt * 8;
        /* make it proportional to 3/4 of a second */
        bcnt *= (u_int64_t)750000;
        bcnt /= (u_int64_t)dtv.tv_usec;
        cnt = bcnt;

        /* make it Hz */
        cnt *= 4;
        cnt /= 3;

        SCA_DPRINTF(SCA_DEBUG_CLOCK,
            ("sca: unadjusted base %lu Hz\n", (u_long)cnt));

        /*
         * round to the nearest 200 -- this allows for +-3 ticks error
         */
        sc->sc_baseclock = ((cnt + 100) / 200) * 200;
}

/*
 * print the information about the clock on the ports
 */
void
sca_print_clock_info(struct sca_softc *sc)
{
        struct sca_port *scp;
        u_int32_t mhz, div;
        int i;

        printf("%s: base clock %d Hz\n", device_xname(sc->sc_parent),
            sc->sc_baseclock);

        /* print the information about the port clock selection */
        for (i = 0; i < sc->sc_numports; i++) {
                scp = &sc->sc_ports[i];
                mhz = sc->sc_baseclock / (scp->sp_tmc ? scp->sp_tmc : 256);
                div = scp->sp_rxs & SCA_RXS_DIV_MASK;

                printf("%s: rx clock: ", scp->sp_if.if_xname);
                switch (scp->sp_rxs & SCA_RXS_CLK_MASK) {
                case SCA_RXS_CLK_LINE:
                        printf("line");
                        break;
                case SCA_RXS_CLK_LINE_SN:
                        printf("line with noise suppression");
                        break;
                case SCA_RXS_CLK_INTERNAL:
                        printf("internal %d Hz", (mhz >> div));
                        break;
                case SCA_RXS_CLK_ADPLL_OUT:
                        printf("adpll using internal %d Hz", (mhz >> div));
                        break;
                case SCA_RXS_CLK_ADPLL_IN:
                        printf("adpll using line clock");
                        break;
                }
                printf("  tx clock: ");
                div = scp->sp_txs & SCA_TXS_DIV_MASK;
                switch (scp->sp_txs & SCA_TXS_CLK_MASK) {
                case SCA_TXS_CLK_LINE:
                        printf("line\n");
                        break;
                case SCA_TXS_CLK_INTERNAL:
                        printf("internal %d Hz\n", (mhz >> div));
                        break;
                case SCA_TXS_CLK_RXCLK:
                        printf("rxclock\n");
                        break;
                }
                if (scp->sp_eclock)
                        printf("%s: outputting line clock\n",
                            scp->sp_if.if_xname);
        }
}