root/sys/arch/mac68k/nubus/if_netdock_nubus.c
/*      $NetBSD: if_netdock_nubus.c,v 1.35 2022/09/18 02:45:38 thorpej Exp $    */

/*
 * Copyright (C) 2000,2002 Daishi Kato <daishi@axlight.com>
 * 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 Daishi Kato
 * 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.
 */

/*
 * Asante NetDock (for Duo series) driver
 * the chip inside is not known
 */

/*
 * The author would like to thank Takeo Kuwata <tkuwata@mac.com> for
 * his help in stabilizing this driver.
 */

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

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: if_netdock_nubus.c,v 1.35 2022/09/18 02:45:38 thorpej Exp $");

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

#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_ether.h>

#include "opt_inet.h"
#ifdef INET
#include <netinet/in.h>
#include <netinet/if_inarp.h>
#endif

#include <net/bpf.h>

#include <machine/bus.h>
#include <machine/viareg.h>
#include <mac68k/nubus/nubus.h>

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

#define NETDOCK_DEBUG

#define NETDOCK_NUBUS_CATEGORY  0x0020
#define NETDOCK_NUBUS_TYPE      0x0003
#define NETDOCK_NUBUS_DRSW      0x0103
#define NETDOCK_NUBUS_DRHW      0x0100

#define ETHERMICRODOCK_NUBUS_CATEGORY   0x0020
#define ETHERMICRODOCK_NUBUS_TYPE       0x0003
#define ETHERMICRODOCK_NUBUS_DRSW       0x0102
#define ETHERMICRODOCK_NUBUS_DRHW       0x0100

#define REG_ISR         0x000c
#define REG_000E        0x000e
#define REG_0000        0x0000
#define REG_0002        0x0002
#define REG_0004        0x0004
#define REG_0006        0x0006
#define REG_DATA        0x0008
#define REG_EFD00       0xefd00

#define ISR_ALL         0x3300
#define ISR_TX          0x0200
#define ISR_RX          0x0100
#define ISR_READY       0x0800
#define ISR_BIT_0C      0x1000
#define ISR_BIT_0D      0x2000
#define ISR_MASK        0x0033
#define ISR_BIT_03      0x0008

#define REG_0002_BIT_04 0x0010
#define REG_0000_BIT_08 0x0100
#define REG_0004_BIT_0F 0x8000
#define REG_0004_BIT_07 0x0080
#define REG_DATA_BIT_02 0x0004
#define REG_DATA_BIT_03 0x0008
#define REG_DATA_BIT_04 0x0010
#define REG_DATA_BIT_05 0x0020
#define REG_DATA_BIT_08 0x0100
#define REG_DATA_BIT_07 0x0080
#define REG_DATA_BIT_0F 0x8000

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

typedef struct netdock_softc {
        device_t        sc_dev;
        struct ethercom sc_ethercom;
#define sc_if           sc_ethercom.ec_if

        bus_space_tag_t         sc_regt;
        bus_space_handle_t      sc_regh;

        u_int8_t        sc_enaddr[ETHER_ADDR_LEN];

} netdock_softc_t;

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

static int      netdock_nubus_match(device_t, cfdata_t, void *);
static void     netdock_nubus_attach(device_t, device_t, void *);
static int      netdock_nb_get_enaddr(bus_space_tag_t, bus_space_handle_t,
                        struct nubus_attach_args *, u_int8_t *);
#ifdef NETDOCK_DEBUG_DRIVER
static void     netdock_print_driver(bus_space_tag_t, bus_space_handle_t,
                        struct nubus_attach_args *);
#endif

int     netdock_setup(struct netdock_softc *, u_int8_t *);
void    netdock_intr(void *);

static void     netdock_watchdog(struct ifnet *);
static int      netdock_init(struct netdock_softc *);
static int      netdock_stop(struct netdock_softc *);
static int      netdock_ioctl(struct ifnet *, u_long, void *);
static void     netdock_start(struct ifnet *);
static void     netdock_reset(struct netdock_softc *);
static void     netdock_txint(struct netdock_softc *);
static void     netdock_rxint(struct netdock_softc *);

static u_int    netdock_put(struct netdock_softc *, struct mbuf *);
static int      netdock_read(struct netdock_softc *, int);
static struct mbuf *netdock_get(struct netdock_softc *, int);

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

#define NIC_GET_1(sc, o)        (bus_space_read_1((sc)->sc_regt,        \
                                        (sc)->sc_regh, (o)))
#define NIC_PUT_1(sc, o, val)   (bus_space_write_1((sc)->sc_regt,       \
                                        (sc)->sc_regh, (o), (val)))
#define NIC_GET_2(sc, o)        (bus_space_read_2((sc)->sc_regt,        \
                                        (sc)->sc_regh, (o)))
#define NIC_PUT_2(sc, o, val)   (bus_space_write_2((sc)->sc_regt,       \
                                        (sc)->sc_regh, (o), (val)))
#define NIC_GET_4(sc, o)        (bus_space_read_4((sc)->sc_regt,        \
                                        (sc)->sc_regh, (o)))
#define NIC_PUT_4(sc, o, val)   (bus_space_write_4((sc)->sc_regt,       \
                                        (sc)->sc_regh, (o), (val)))

#define NIC_BSET(sc, o, b)                                              \
        __asm volatile("bset %0,%1" : : "di" ((u_short)(b)),    \
                "g" (*(u_int8_t *)((sc)->sc_regh.base + (o))))
#define NIC_BCLR(sc, o, b)                                              \
        __asm volatile("bclr %0,%1" : : "di" ((u_short)(b)),    \
                "g" (*(u_int8_t *)((sc)->sc_regh.base + (o))))
#define NIC_ANDW(sc, o, b)                                              \
        __asm volatile("andw %0,%1" : : "di" ((u_short)(b)),    \
                "g" (*(u_int8_t *)((sc)->sc_regh.base + (o))))
#define NIC_ORW(sc, o, b)                                               \
        __asm volatile("orw %0,%1" : : "di" ((u_short)(b)),     \
                "g" (*(u_int8_t *)((sc)->sc_regh.base + (o))))


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

CFATTACH_DECL_NEW(netdock_nubus, sizeof(struct netdock_softc),
    netdock_nubus_match, netdock_nubus_attach, NULL, NULL);

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

static int
netdock_nubus_match(device_t parent, cfdata_t cf, void *aux)
{
        struct nubus_attach_args *na = (struct nubus_attach_args *)aux;
        bus_space_handle_t bsh;
        int rv;

        if (bus_space_map(na->na_tag, NUBUS_SLOT2PA(na->slot),
            NBMEMSIZE, 0, &bsh))
                return (0);

        rv = 0;

        if (na->category == NETDOCK_NUBUS_CATEGORY &&
            na->type == NETDOCK_NUBUS_TYPE &&
            na->drsw == NETDOCK_NUBUS_DRSW) {
                /* assuming this IS Asante NetDock */
                rv = 1;
        }

        if (na->category == ETHERMICRODOCK_NUBUS_CATEGORY &&
            na->type == ETHERMICRODOCK_NUBUS_TYPE &&
            na->drsw == ETHERMICRODOCK_NUBUS_DRSW) {
                /* assuming this IS Newer EtherMicroDock */
                rv = 1;
        }

        bus_space_unmap(na->na_tag, bsh, NBMEMSIZE);

        return rv;
}

static void
netdock_nubus_attach(device_t parent, device_t self, void *aux)
{
        struct netdock_softc *sc = device_private(self);
        struct nubus_attach_args *na = (struct nubus_attach_args *)aux;
        bus_space_tag_t bst;
        bus_space_handle_t bsh;
        u_int8_t enaddr[ETHER_ADDR_LEN];
        const char *cardtype;

        bst = na->na_tag;
        if (bus_space_map(bst, NUBUS_SLOT2PA(na->slot), NBMEMSIZE, 0, &bsh)) {
                printf(": failed to map memory space.\n");
                return;
        }

        sc->sc_regt = bst;
        sc->sc_dev = self;
        cardtype = nubus_get_card_name(bst, bsh, na->fmt);

#ifdef NETDOCK_DEBUG_DRIVER
        netdock_print_driver(bst, bsh, na);
#endif

        if (netdock_nb_get_enaddr(bst, bsh, na, enaddr)) {
                printf(": can't find MAC address.\n");
                bus_space_unmap(bst, bsh, NBMEMSIZE);
                return;
        }

        if (bus_space_subregion(bst, bsh, 0xe00300, 0xf0000, &sc->sc_regh)) {
                printf(": failed to map register space.\n");
                bus_space_unmap(bst, bsh, NBMEMSIZE);
                return;
        }

        printf(": %s\n", cardtype);

        if (netdock_setup(sc, enaddr)) {
                bus_space_unmap(bst, bsh, NBMEMSIZE);
                return;
        }

        add_nubus_intr(na->slot, netdock_intr, (void *)sc);

        return;
}

static int
netdock_nb_get_enaddr(bus_space_tag_t bst, bus_space_handle_t bsh,
    struct nubus_attach_args *na, u_int8_t *ep)
{
        nubus_dir dir;
        nubus_dirent dirent;

        /*
         * these hardwired resource IDs are only for NetDock
         */
        nubus_get_main_dir(na->fmt, &dir);
        if (nubus_find_rsrc(bst, bsh, na->fmt, &dir, 0x81, &dirent) <= 0)
                return 1;
        nubus_get_dir_from_rsrc(na->fmt, &dirent, &dir);
        if (nubus_find_rsrc(bst, bsh, na->fmt, &dir, 0x80, &dirent) <= 0)
                return 1;
        if (nubus_get_ind_data(bst, bsh, na->fmt, &dirent,
                ep, ETHER_ADDR_LEN) <= 0)
                return 1;

        return 0;
}

#ifdef NETDOCK_DEBUG_DRIVER
static void
netdock_print_driver(bus_space_tag_t bst, bus_space_handle_t bsh,
    struct nubus_attach_args *na)
{
#define HEADSIZE        (8+4)
#define CODESIZE        (6759-4)
        unsigned char mydata[HEADSIZE + CODESIZE];
        nubus_dir dir;
        nubus_dirent dirent;
        int i, rv;

        nubus_get_main_dir(na->fmt, &dir);
        rv = nubus_find_rsrc(bst, bsh, na->fmt, &dir, 0x81, &dirent);
        if (rv <= 0) {
                printf(": can't find sResource.\n");
                return;
        }
        nubus_get_dir_from_rsrc(na->fmt, &dirent, &dir);
        if (nubus_find_rsrc(bst, bsh, na->fmt, &dir, NUBUS_RSRC_DRVRDIR,
            &dirent) <= 0) {
                printf(": can't find sResource.\n");
                return;
        }
        if (nubus_get_ind_data(bst, bsh, na->fmt,
                &dirent, mydata, HEADSIZE+CODESIZE) <= 0) {
                printf(": can't find indirect data.\n");
                return;
        }
        printf("\n########## begin driver dir");
        for (i = 0; i < HEADSIZE; i++) {
                if (i % 16 == 0)
                        printf("\n%02x:",i);
                printf(" %02x", mydata[i]);
        }
        printf("\n########## begin driver code");
        for (i = 0; i < CODESIZE; i++) {
                if (i % 16 == 0)
                        printf("\n%02x:",i);
                printf(" %02x", mydata[i + HEADSIZE]);
        }
#if 0
        printf("\n########## begin driver code (partial)\n");
#define PARTSIZE 256
#define OFFSET 0x1568
        for (i = OFFSET; i < OFFSET + PARTSIZE; i++) {
                if ((i - OFFSET) % 16 == 0)
                        printf("\n%02x:",i);
                printf(" %02x", mydata[i + HEADSIZE]);
        }
#endif
        printf("\n########## end\n");
}
#endif


int
netdock_setup(struct netdock_softc *sc, u_int8_t *lladdr)
{
        struct ifnet *ifp = &sc->sc_if;

        memcpy(sc->sc_enaddr, lladdr, ETHER_ADDR_LEN);
        printf("%s: Ethernet address %s\n",
            device_xname(sc->sc_dev), ether_sprintf(lladdr));

        memcpy(ifp->if_xname, device_xname(sc->sc_dev), IFNAMSIZ);
        ifp->if_softc = sc;
        ifp->if_ioctl = netdock_ioctl;
        ifp->if_start = netdock_start;
        ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
        ifp->if_watchdog = netdock_watchdog;

        if_attach(ifp);
        ether_ifattach(ifp, lladdr);

        return (0);
}

static int
netdock_ioctl(struct ifnet *ifp, u_long cmd, void *data)
{
        struct ifaddr *ifa;
        struct netdock_softc *sc = ifp->if_softc;
        int s = splnet();
        int err = 0;
        u_short temp;

        switch (cmd) {
        case SIOCINITIFADDR:
                ifa = (struct ifaddr *)data;
                ifp->if_flags |= IFF_UP;
                (void)netdock_init(sc);
                switch (ifa->ifa_addr->sa_family) {
#ifdef INET
                case AF_INET:
                        arp_ifinit(ifp, ifa);
                        break;
#endif
                default:
                        break;
                }
                break;

        case SIOCSIFFLAGS:
                if ((err = ifioctl_common(ifp, cmd, data)) != 0)
                        break;
                /* XXX see the comment in ed_ioctl() about code re-use */
                if ((ifp->if_flags & IFF_UP) == 0 &&
                    (ifp->if_flags & IFF_RUNNING) != 0) {
                        netdock_stop(sc);
                        ifp->if_flags &= ~IFF_RUNNING;
                } else if ((ifp->if_flags & IFF_UP) != 0 &&
                    (ifp->if_flags & IFF_RUNNING) == 0) {
                        (void)netdock_init(sc);
                } else {
                        temp = ifp->if_flags & IFF_UP;
                        netdock_reset(sc);
                        ifp->if_flags |= temp;
                        netdock_start(ifp);
                }
                break;

        case SIOCADDMULTI:
        case SIOCDELMULTI:
                if ((err = ether_ioctl(ifp, cmd, data)) == ENETRESET) {
                        if (ifp->if_flags & IFF_RUNNING) {
                                temp = ifp->if_flags & IFF_UP;
                                netdock_reset(sc);
                                ifp->if_flags |= temp;
                        }
                        err = 0;
                }
                break;
        default:
                err = ether_ioctl(ifp, cmd, data);
                break;
        }
        splx(s);
        return (err);
}

static void
netdock_start(struct ifnet *ifp)
{
        struct netdock_softc *sc = ifp->if_softc;
        struct mbuf *m;

        if ((ifp->if_flags & IFF_RUNNING) == 0)
                return;

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

                KASSERT(m->m_flags & M_PKTHDR);

                bpf_mtap(ifp, m, BPF_D_OUT);

                if ((netdock_put(sc, m)) == 0) {
                        IF_PREPEND(&ifp->if_snd, m);
                        return;
                }

                if_statinc(ifp, if_opackets);
        }
}

static void
netdock_reset(struct netdock_softc *sc)
{

        netdock_stop(sc);
        netdock_init(sc);
}

static int
netdock_init(struct netdock_softc *sc)
{
        int s;
        int saveisr;
        int savetmp;

        if (sc->sc_if.if_flags & IFF_RUNNING)
                return (0);

        s = splnet();

        /* 0606 */
        NIC_PUT_2(sc, REG_000E, 0x0200);
        NIC_PUT_2(sc, REG_ISR , 0);
        NIC_PUT_2(sc, REG_000E, 0);

        NIC_PUT_2(sc, REG_0000, 0x8104);
        NIC_PUT_2(sc, REG_0004, 0x0043);
        NIC_PUT_2(sc, REG_000E, 0x0100);
        NIC_PUT_2(sc, REG_0002, 0x6618);
        NIC_PUT_2(sc, REG_0000, 0x8010);

        NIC_PUT_1(sc, REG_0004 + 0, sc->sc_enaddr[0]);
        NIC_PUT_1(sc, REG_0004 + 1, sc->sc_enaddr[1]);
        NIC_PUT_1(sc, REG_0004 + 2, sc->sc_enaddr[2]);
        NIC_PUT_1(sc, REG_0004 + 3, sc->sc_enaddr[3]);
        NIC_PUT_1(sc, REG_0004 + 4, sc->sc_enaddr[4]);
        NIC_PUT_1(sc, REG_0004 + 5, sc->sc_enaddr[5]);

        NIC_PUT_2(sc, REG_ISR , 0x2008);
        NIC_PUT_2(sc, REG_000E, 0x0200);
        NIC_PUT_2(sc, REG_0000, 0x4000);
        NIC_PUT_2(sc, REG_ISR, ISR_MASK);


        /* 1320 */
        saveisr = NIC_GET_2(sc, REG_ISR);
        NIC_PUT_2(sc, REG_ISR , 0);
        savetmp = NIC_GET_2(sc, REG_000E);
        NIC_PUT_2(sc, REG_000E, 0x0100);
        NIC_ANDW(sc, REG_ISR, ~ISR_BIT_03);
        NIC_PUT_2(sc, REG_000E, savetmp);

        /* 1382 */
        savetmp = NIC_GET_2(sc, REG_000E);
        NIC_PUT_2(sc, REG_000E, 0x0100);
        NIC_ORW(sc, REG_ISR, ISR_BIT_03);
        NIC_PUT_2(sc, REG_000E, savetmp);
        NIC_PUT_2(sc, REG_ISR , saveisr);


        sc->sc_if.if_flags |= IFF_RUNNING;

        splx(s);
        return (0);
}

static int
netdock_stop(struct netdock_softc *sc)
{
        int s = splnet();

        sc->sc_if.if_timer = 0;
        sc->sc_if.if_flags &= ~(IFF_RUNNING | IFF_UP);

        splx(s);
        return (0);
}

static void
netdock_watchdog(struct ifnet *ifp)
{
        struct netdock_softc *sc = ifp->if_softc;
        u_short tmp;

        printf("netdock_watchdog: resetting chip\n");
        tmp = ifp->if_flags & IFF_UP;
        netdock_reset(sc);
        ifp->if_flags |= tmp;
}

static u_int
netdock_put(struct netdock_softc *sc, struct mbuf *m0)
{
        struct mbuf *m;
        u_int totlen = 0;
        u_int tmplen;
        int isr;
        int timeout;
        int tmp;
        u_int i;

        for (m = m0; m; m = m->m_next)
                totlen += m->m_len;

        if (totlen >= ETHER_MAX_LEN)
                panic("%s: netdock_put: packet overflow",
                    device_xname(sc->sc_dev));

        totlen += 6;
        tmplen = totlen;
        tmplen &= 0xff00;
        tmplen |= 0x2000;
        NIC_PUT_2(sc, REG_0000, tmplen);

        timeout = 0x3000;
        while ((((isr = NIC_GET_2(sc, REG_ISR)) & ISR_READY) == 0) &&
            timeout--) {
                if (isr & ISR_TX)
                        netdock_txint(sc);
        }
        if (timeout == 0)
                return (0);

        tmp = NIC_GET_2(sc, REG_0002);
        tmp <<= 8;
        NIC_PUT_2(sc, REG_0002, tmp);
        NIC_PUT_2(sc, REG_0006, 0x240);
        NIC_GET_2(sc, REG_ISR);
        tmplen = ((totlen << 8) & 0xfe00) | ((totlen >> 8) & 0x00ff);
        NIC_PUT_2(sc, REG_DATA, tmplen);

        for (m = m0; m; m = m->m_next) {
                u_char *data = mtod(m, u_char *);
                int len = m->m_len;
                int len4 = len >> 2;
                u_int32_t *data4 = (u_int32_t *)data;
                for (i = 0; i < len4; i++)
                        NIC_PUT_4(sc, REG_DATA, data4[i]);
                for (i = len4 << 2; i < len; i++)
                        NIC_PUT_1(sc, REG_DATA, data[i]);
        }

        if (totlen & 0x01)
                NIC_PUT_2(sc, REG_DATA, 0x2020);
        else
                NIC_PUT_2(sc, REG_DATA, 0);

        NIC_PUT_2(sc, REG_0000, 0xc000);

        m_freem(m0);
        /* sc->sc_if.if_timer = 5; */
        return (totlen);
}

void
netdock_intr(void *arg)
{
        struct netdock_softc *sc = (struct netdock_softc *)arg;
        int isr;
        int tmp;

        NIC_PUT_2(sc, REG_ISR, 0);
        while ((isr = (NIC_GET_2(sc, REG_ISR) & ISR_ALL)) != 0) {
                if (isr & ISR_TX)
                        netdock_txint(sc);

                if (isr & ISR_RX)
                        netdock_rxint(sc);

                if (isr & (ISR_BIT_0C | ISR_BIT_0D)) {
                        if (isr & ISR_BIT_0C) {
                                NIC_PUT_2(sc, REG_000E, 0);
                                NIC_BSET(sc, REG_0004, 0x08);
                                NIC_PUT_2(sc, REG_000E, 0x0200);
                        }
                        if (isr & ISR_BIT_0D) {
                                NIC_PUT_2(sc, REG_000E, 0);
                                tmp = NIC_GET_2(sc, REG_0002);
                                if (tmp & REG_0002_BIT_04)
                                        NIC_GET_2(sc, REG_0006);
                                tmp = NIC_GET_2(sc, REG_0000);
                                if (tmp & REG_0000_BIT_08)
                                        NIC_BSET(sc, REG_0000, 0x08);
                                NIC_PUT_2(sc, REG_000E, 0x0200);
                        }
                        NIC_PUT_2(sc, REG_ISR, isr);
                }
        }
        NIC_PUT_2(sc, REG_ISR, ISR_MASK);
}

static void
netdock_txint(struct netdock_softc *sc)
{
        struct ifnet *ifp = &sc->sc_if;
        int savereg0002;
        int reg0004;
        int regdata;

        ifp->if_timer = 0;

        savereg0002 = NIC_GET_2(sc, REG_0002);

        while (((reg0004 = NIC_GET_2(sc, REG_0004)) & REG_0004_BIT_0F) == 0) {
                NIC_PUT_2(sc, REG_0002, reg0004);
                NIC_PUT_2(sc, REG_0006, 0x0060);
                NIC_GET_2(sc, REG_ISR);
                regdata = NIC_GET_2(sc, REG_DATA);
                if ((regdata & REG_DATA_BIT_08) == 0) {
                        /* if_statinc(ifp, if_collisions); */
                        if (regdata & REG_DATA_BIT_07)
                        /* if_statinc(ifp, if_oerrors); */
                        NIC_PUT_2(sc, REG_000E, 0);
                        NIC_ORW(sc, REG_0000, 0x0100);
                        NIC_PUT_2(sc, REG_000E, 0x0200);
                }
                NIC_GET_2(sc ,REG_DATA);

                if (regdata & REG_DATA_BIT_0F)
                        NIC_GET_4(sc, REG_EFD00);
                NIC_PUT_2(sc, REG_0000, 0xa000);
                NIC_PUT_2(sc, REG_ISR, ISR_TX);
        }

        NIC_PUT_2(sc, REG_0002, savereg0002);
        NIC_PUT_2(sc, REG_000E, 0);
        NIC_GET_2(sc, REG_0006);
        NIC_PUT_2(sc, REG_000E, 0x0200);
        NIC_PUT_2(sc, REG_0006, 0);
}

static void
netdock_rxint(struct netdock_softc *sc)
{
        struct ifnet *ifp = &sc->sc_if;
        int regdata1;
        int regdata2;
        u_int len;
        int timeout;

        while ((NIC_GET_2(sc, REG_0004) & REG_0004_BIT_07) == 0) {
                NIC_GET_2(sc, REG_ISR);
                NIC_PUT_2(sc, REG_0006, 0x00e0);
                NIC_GET_2(sc, REG_ISR);
                regdata1 = NIC_GET_2(sc, REG_DATA);
                regdata2 = NIC_GET_2(sc, REG_DATA);
                len = ((regdata2 << 8) & 0x0700) | ((regdata2 >> 8) & 0x00ff);

#if 0
                printf("netdock_rxint: r1=0x%04x, r2=0x%04x, len=%d\n",
                       regdata1, regdata2, len);
#endif

                if ((regdata1 & REG_DATA_BIT_04) == 0)
                        len -= 2;

                /* CRC is included with the packet; trim it off. */
                len -= ETHER_CRC_LEN;

                if ((regdata1 & 0x00ac) == 0) {
                        if (netdock_read(sc, len) == 0)
                                if_statinc(ifp, if_ierrors);
                } else {
                        if_statinc(ifp, if_ierrors);

                        if (regdata1 & REG_DATA_BIT_02)
                                NIC_GET_4(sc, REG_EFD00);
                        if (regdata1 & REG_DATA_BIT_03)
                                ;
                        if (regdata1 & REG_DATA_BIT_05)
                                NIC_GET_4(sc, REG_EFD00);
                        if (regdata1 & REG_DATA_BIT_07)
                                ;
                }

                timeout = 0x14;
                while ((NIC_GET_2(sc, REG_0000) & REG_0000_BIT_08) && timeout--)
                        ;
                if (timeout == 0)
                        ;

                NIC_PUT_2(sc, REG_0000, 0x8000);
        }
        NIC_PUT_2(sc, REG_0006, 0);
}

static int
netdock_read(struct netdock_softc *sc, int len)
{
        struct ifnet *ifp = &sc->sc_if;
        struct mbuf *m;

        m = netdock_get(sc, len);
        if (m == 0)
                return (0);

        if_percpuq_enqueue(ifp->if_percpuq, m);

        return (1);
}

static struct mbuf *
netdock_get(struct netdock_softc *sc, int datalen)
{
        struct mbuf *m, *top, **mp;
        u_char *data;
        int i;
        int len;
        int len4;
        u_int32_t *data4;

        MGETHDR(m, M_DONTWAIT, MT_DATA);
        if (m == NULL)
                return (NULL);
        m_set_rcvif(m, &sc->sc_if);
        m->m_pkthdr.len = datalen;
        len = MHLEN;
        top = NULL;
        mp = &top;

        while (datalen > 0) {
                if (top) {
                        MGET(m, M_DONTWAIT, MT_DATA);
                        if (m == 0) {
                                m_freem(top);
                                return (NULL);
                        }
                        len = MLEN;
                }
                if (datalen >= MINCLSIZE) {
                        MCLGET(m, M_DONTWAIT);
                        if ((m->m_flags & M_EXT) == 0) {
                                if (top)
                                        m_freem(top);
                                else
                                        m_freem(m);
                                return (NULL);
                        }
                        len = MCLBYTES;
                }

                if (mp == &top) {
                        char *newdata = (char *)
                            ALIGN(m->m_data + sizeof(struct ether_header)) -
                            sizeof(struct ether_header);
                        len -= newdata - m->m_data;
                        m->m_data = newdata;
                }

                m->m_len = len = uimin(datalen, len);

                data = mtod(m, u_char *);
                len4 = len >> 2;
                data4 = (u_int32_t *)data;
                for (i = 0; i < len4; i++)
                        data4[i] = NIC_GET_4(sc, REG_DATA);
                for (i = len4 << 2; i < len; i++)
                        data[i] = NIC_GET_1(sc, REG_DATA);

                datalen -= len;
                *mp = m;
                mp = &m->m_next;
        }

        return (top);
}