Masa Murayama wrote:
> My pc doesn't hang after I modified re.
> The configuration of my pc is core2quad intel cpu with 2GB memory.
> It doesn't hang with both of four core configuration and sigle core
> configuration.
> I did bi-directional netperf test. It ran without hang.
> 
> Would you give me the latest re.c ?

Certainly! I've attached the updated file.

Steve
/*
 * Copyright (c) 2009 Steven Stallion.  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 the copyright owner nor the names of any
 *      contributors may be used to endorse or promote products derived
 *      from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 THE COPYRIGHT OWNER 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.
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/types.h>
#include <sys/errno.h>
#include <sys/cmn_err.h>
#include <sys/varargs.h>
#include <sys/kmem.h>
#include <sys/conf.h>
#include <sys/devops.h>
#include <sys/modctl.h>
#include <sys/ddi.h>
#include <sys/ddi_intr.h>
#include <sys/sunddi.h>
#include <sys/kstat.h>
#include <sys/stream.h>
#include <sys/strsun.h>
#include <sys/pci.h>
#include <sys/ethernet.h>
#include <sys/crc32.h>
#include <sys/vlan.h>
#include <sys/mac.h>
#include <sys/mac_ether.h>
#include <sys/mac_provider.h>

#include "re.h"

/* Loadable module entry points */
static int      re_attach(dev_info_t *, ddi_attach_cmd_t);
static int      re_detach(dev_info_t *, ddi_detach_cmd_t);
static int      re_quiesce(dev_info_t *);

/* MAC entry points */
static int      re_m_stat(void *, uint_t, uint64_t *);
static int      re_m_start(void *);
static void     re_m_stop(void *);
static int      re_m_promisc(void *, boolean_t);
static int      re_m_multicst(void *, boolean_t, const uint8_t *);
static int      re_m_unicst(void *, const uint8_t *);
static mblk_t   *re_m_tx(void *, mblk_t *);
static int      re_m_setprop(void *, const char *, mac_prop_id_t, uint_t,
    const void *);
static int      re_m_getprop(void *, const char *, mac_prop_id_t, uint_t,
    uint_t, void *, uint_t *);

/* ISR/periodic entry points */
static uint_t   re_intr(caddr_t, caddr_t);
static void     re_portmon(void *);

/* Additional kstat entry points */
static int      re_regs_update(kstat_t *ksp, int rw);

/* Support functions */
static int              re_reset(re_t *);
static int              re_init(re_t *);
static inline void      re_init_dcr(re_t *);
static inline void      re_init_ring(re_t *);
static inline void      re_init_mac(re_t *);
static inline void      re_init_phy(re_t *);
static int              re_start(re_t *);
static void             re_stop(re_t *);
static void             re_restart(re_t *);
static int              re_suspend(re_t *);
static int              re_resume(re_t *);

static mblk_t           *re_recv(re_t *);
static inline boolean_t re_recv_next(re_t *, re_hdr_t *);
static void             re_recv_err(re_t *);

static int              re_send(re_t *, mblk_t *);
static inline void      re_send_pkt(re_t *);
static void             re_send_err(re_t *);

static inline void      re_intr_clear(re_t *);
static void             re_intr_enable(re_t *);
static void             re_intr_disable(re_t *);

static link_state_t     re_link_test(re_t *);

static inline int       re_gethdr(re_t *, re_hdr_t *);
static void             re_getmode(re_t *, re_mode_t *);
static void             re_getaddr(re_t *, uint8_t *);
static void             re_setaddr(re_t *, uint8_t *);
static void             re_setmcast(re_t *, uint8_t *);
static inline void      re_setpage(re_t *, uint8_t);

static void             re_read_prom(re_t *, uint8_t *, uint8_t, uint8_t);

static inline void      re_rdma_clear(re_t *);
static void             re_rdma_abort(re_t *);
static void             re_rdma_init(re_t *, uint8_t, uint16_t, uint16_t);
static void             re_rdma_get(re_t *, uint8_t *, uint16_t, uint16_t);
static int              re_rdma_put(re_t *, uint8_t *, uint16_t, uint16_t);

static void             re_dprintf(dev_info_t *, int, const char *, ...);

#ifdef DEBUG
#define re_debug(dip, ...) \
        re_dprintf((dip), CE_NOTE, __VA_ARGS__)
#else
#define re_debug(dip, ...)      /*EMPTY*/
#endif

#define re_error(dip, ...) \
        re_dprintf((dip), CE_WARN, __VA_ARGS__)

extern struct mod_ops mod_driverops;

DDI_DEFINE_STREAM_OPS(re_ops, nulldev, nulldev, re_attach, re_detach,
    nodev, NULL, D_MP, NULL, re_quiesce);

static struct modldrv modldrv = {
        &mod_driverops,         /* drv_modops */
        "Realtek Ethernet",     /* drv_linkinfo */
        &re_ops                 /* drv_dev_ops */
};

static struct modlinkage modlinkage = {
        MODREV_1,               /* ml_rev */
        { &modldrv, NULL }      /* ml_linkage */
};

static ddi_device_acc_attr_t re_acc_attr = {
        DDI_DEVICE_ATTR_V0,     /* devacc_attr_version */
        DDI_NEVERSWAP_ACC,      /* devacc_attr_endian_flags */
        DDI_STRICTORDER_ACC     /* devacc_attr_dataorder */
};

static mac_callbacks_t re_m_callbacks = {
        MC_SETPROP | MC_GETPROP,        /* mc_callbacks */
        re_m_stat,                      /* mc_getstat */
        re_m_start,                     /* mc_start */
        re_m_stop,                      /* mc_stop */
        re_m_promisc,                   /* mc_setpromisc */
        re_m_multicst,                  /* mc_multicst */
        re_m_unicst,                    /* mc_unicst */
        re_m_tx,                        /* mc_tx */
        NULL,                           /* mc_ioctl */
        NULL,                           /* mc_getcapab */
        NULL,                           /* mc_open */
        NULL,                           /* mc_close */
        re_m_setprop,                   /* mc_setprop */
        re_m_getprop                    /* mc_getprop */
};

static uint8_t re_broadcast[] = {
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};

static uint8_t re_mcast_promisc[] = {
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};

static re_regs_kstat_t re_regs_kstats[] = {
        { "reg_cr",     REG_PAGE_0,     CR_REG          },
        { "reg_clda0",  REG_PAGE_0,     CLDA0_REG       },
        { "reg_clda1",  REG_PAGE_0,     CLDA1_REG       },
        { "reg_bnry",   REG_PAGE_0,     BNRY_REG        },
        { "reg_tsr",    REG_PAGE_0,     TSR_REG         },
        { "reg_fifo",   REG_PAGE_0,     FIFO_REG        },
        { "reg_isr",    REG_PAGE_0,     ISR_REG         },
        { "reg_crda0",  REG_PAGE_0,     CRDA0_REG       },
        { "reg_crda1",  REG_PAGE_0,     CRDA1_REG       },
        { "reg_rsr",    REG_PAGE_0,     RSR_REG         },
        { "reg_curr",   REG_PAGE_1,     CURR_REG        },
        { "reg_pstart", REG_PAGE_2,     PSTART_REG      },
        { "reg_pstop",  REG_PAGE_2,     PSTOP_REG       },
        { "reg_tpsr",   REG_PAGE_2,     TPSR_REG        },
        { "reg_rcr",    REG_PAGE_2,     RCR_REG         },
        { "reg_tcr",    REG_PAGE_2,     TCR_REG         },
        { "reg_dcr",    REG_PAGE_2,     DCR_REG         },
        { "reg_imr",    REG_PAGE_2,     IMR_REG         }
};

static size_t re_regs_kstats_len =
    sizeof (re_regs_kstats) / sizeof (re_regs_kstat_t);

/*
 * Loadable module entry points.
 */
int
_init(void)
{
        int error;

        mac_init_ops(&re_ops, "re");

        if ((error = mod_install(&modlinkage)) != DDI_SUCCESS) {
                mac_fini_ops(&re_ops);
        }
        return (error);
}

int
_fini(void)
{
        int error;

        if ((error = mod_remove(&modlinkage)) == DDI_SUCCESS) {
                mac_fini_ops(&re_ops);
        }
        return (error);
}

int
_info(struct modinfo *modinfop)
{
        return (mod_info(&modlinkage, modinfop));
}

int
re_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
        ddi_acc_handle_t pci;
        int types;
        int count;
        int actual;
        re_t *rep;
        mac_register_t *macp;

        switch (cmd) {
        case DDI_ATTACH:
                break;

        case DDI_RESUME:
                rep = ddi_get_driver_private(dip);
                return (re_resume(rep));

        default:
                return (DDI_FAILURE);
        }

        /*
         * PCI configuration.
         */
        if (pci_config_setup(dip, &pci) != DDI_SUCCESS) {
                re_error(dip, "unable to allocate bus resources!");
                return (DDI_FAILURE);
        }

        pci_config_put16(pci, PCI_CONF_COMM,
            pci_config_get16(pci, PCI_CONF_COMM) | PCI_COMM_IO);

        pci_config_teardown(&pci);

        if (ddi_intr_get_supported_types(dip, &types)
            != DDI_SUCCESS || !(types & DDI_INTR_TYPE_FIXED)) {
                re_error(dip, "fixed interrupts not supported!");
                return (DDI_FAILURE);
        }

        if (ddi_intr_get_nintrs(dip, DDI_INTR_TYPE_FIXED, &count)
            != DDI_SUCCESS || count != 1) {
                re_error(dip, "no fixed interrupts available!");
                return (DDI_FAILURE);
        }

        /*
         * Initialize soft state.
         */
        rep = kmem_zalloc(sizeof (re_t), KM_SLEEP);
        ddi_set_driver_private(dip, rep);

        rep->re_dip = dip;

        rep->re_htable = kmem_zalloc(sizeof (ddi_intr_handle_t), KM_SLEEP);

        if (ddi_intr_alloc(dip, rep->re_htable, DDI_INTR_TYPE_FIXED, 0,
            count, &actual, 0) != DDI_SUCCESS || actual != count) {
                re_error(dip, "unable to allocate interrupt!");
                goto failure;
        }

        if (ddi_intr_get_pri(rep->re_htable[0], &rep->re_intr_pri)
            != DDI_SUCCESS) {
                re_error(dip, "unable to get interrupt priority!");
                goto failure;
        }

        if (rep->re_intr_pri >= ddi_intr_get_hilevel_pri()) {
                re_error(dip, "high-level interrupts not supported!");
                goto failure;
        }

        mutex_init(&rep->re_txlock, NULL, MUTEX_DRIVER,
            DDI_INTR_PRI(rep->re_intr_pri));

        /*
         * Map device registers.
         */
        if (ddi_regs_map_setup(dip, 1, &rep->re_regs, 0, 0,
            &re_acc_attr, &rep->re_regs_acch) != DDI_SUCCESS) {
                re_error(dip, "unable to setup register map!");
                goto failure;
        }

        /*
         * Initialize additional kstats.
         */
        if ((rep->re_regs_ksp = kstat_create("re", ddi_get_instance(dip),
            "regs", "misc", KSTAT_TYPE_NAMED, re_regs_kstats_len, 0)) == NULL) {
                re_error(dip, "unable to create regs kstat!");
                goto failure;
        }

        rep->re_regs_ksp->ks_private = rep;
        rep->re_regs_ksp->ks_update = re_regs_update;

        /* Initialize named kstats */
        for (int i = 0; i < re_regs_kstats_len; ++i) {
                kstat_named_init((kstat_named_t *)rep->re_regs_ksp->ks_data + i,
                    re_regs_kstats[i].ks_name, KSTAT_DATA_ULONG);
        }

        kstat_install(rep->re_regs_ksp);

        /*
         * Default properties.
         */
        rep->re_legacy_mode = ddi_prop_get_int(DDI_DEV_T_ANY,
            rep->re_dip, 0, "legacy-mode", 0);

        rep->re_adv_pause_cap = ddi_prop_get_int(DDI_DEV_T_ANY,
            rep->re_dip, 0, "adv_pause_cap", 1);

        rep->re_adv_10fdx_cap = ddi_prop_get_int(DDI_DEV_T_ANY,
            rep->re_dip, 0, "adv_10fdx_cap", 1);

        rep->re_adv_10hdx_cap = ddi_prop_get_int(DDI_DEV_T_ANY,
            rep->re_dip, 0, "adv_10hdx_cap", 1);

        /*
         * Initialize device.
         */
        mutex_enter(&rep->re_txlock);

        if (re_reset(rep) != DDI_SUCCESS) {
                mutex_exit(&rep->re_txlock);
                goto failure;
        }

        /* Determine operating mode */
        re_getmode(rep, &rep->re_mode);

        /* Use station address as default */
        re_getaddr(rep, rep->re_curraddr);

        mutex_exit(&rep->re_txlock);

        /*
         * Enable interrupt handler.
         */
        if (ddi_intr_add_handler(rep->re_htable[0], re_intr, rep, NULL)
            != DDI_SUCCESS) {
                re_error(dip, "unable to add interrupt handler!");
                goto failure;
        }

        if (ddi_intr_enable(rep->re_htable[0]) != DDI_SUCCESS) {
                re_error(dip, "unable to enable interrupt handler!");
                goto failure;
        }

        /*
         * Register with MAC.
         */
        if ((macp = mac_alloc(MAC_VERSION)) == NULL) {
                re_error(dip, "unable to allocate mac resources!");
                goto failure;
        }

        macp->m_type_ident = MAC_PLUGIN_IDENT_ETHER;
        macp->m_driver = rep;
        macp->m_dip = dip;
        macp->m_src_addr = rep->re_curraddr;
        macp->m_callbacks = &re_m_callbacks;
        macp->m_min_sdu = 0;
        macp->m_max_sdu = ETHERMTU;
        macp->m_margin = VLAN_TAGSZ;

        if (mac_register(macp, &rep->re_mh) != 0) {
                re_error(dip, "unable to register with mac!");
                goto failure;
        }

        mac_free(macp);

        return (DDI_SUCCESS);

failure:
        if (macp != NULL) {
                mac_free(macp);
        }

        if (rep->re_htable != NULL) {
                if (rep->re_htable[0] != NULL) {
                        (void) ddi_intr_disable(rep->re_htable[0]);
                        (void) ddi_intr_remove_handler(rep->re_htable[0]);
                        (void) ddi_intr_free(rep->re_htable[0]);
                }
                kmem_free(rep->re_htable, sizeof (ddi_intr_handle_t));
        }

        mutex_destroy(&rep->re_txlock);

        if (rep->re_regs_ksp != NULL) {
                kstat_delete(rep->re_regs_ksp);
        }

        if (rep->re_regs_acch != NULL) {
                ddi_regs_map_free(&rep->re_regs_acch);
        }

        kmem_free(rep, sizeof (re_t));

        return (DDI_FAILURE);
}

int
re_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
        re_t *rep = ddi_get_driver_private(dip);

        switch (cmd) {
        case DDI_DETACH:
                break;

        case DDI_SUSPEND:
                return (re_suspend(rep));

        default:
                return (DDI_FAILURE);
        }

        if (mac_unregister(rep->re_mh) != 0) {
                re_error(dip, "unable to unregister from mac!");
                return (DDI_FAILURE);
        }

        (void) ddi_intr_disable(rep->re_htable[0]);
        (void) ddi_intr_remove_handler(rep->re_htable[0]);
        (void) ddi_intr_free(rep->re_htable[0]);

        kmem_free(rep->re_htable, sizeof (ddi_intr_handle_t));

        mutex_destroy(&rep->re_txlock);

        kstat_delete(rep->re_regs_ksp);

        ddi_regs_map_free(&rep->re_regs_acch);

        kmem_free(rep, sizeof (re_t));

        return (DDI_SUCCESS);
}

int
re_quiesce(dev_info_t *dip)
{
        re_t *rep = ddi_get_driver_private(dip);

        /* Hard reset device */
        ddi_put8(rep->re_regs_acch, RESET8(rep),
            ddi_get8(rep->re_regs_acch, RESET8(rep)));

        return (DDI_SUCCESS);
}

/*
 * MAC entry points.
 */
int
re_m_stat(void *arg, uint_t stat, uint64_t *val)
{
        re_t *rep = arg;

        switch (stat) {
        case MAC_STAT_IFSPEED:
                *val = IFSPEED(rep->re_speed);
                break;

        case MAC_STAT_MULTIRCV:
                *val = rep->re_multircv;
                break;

        case MAC_STAT_BRDCSTRCV:
                *val = rep->re_brdcstrcv;
                break;

        case MAC_STAT_MULTIXMT:
                *val = rep->re_multixmt;
                break;

        case MAC_STAT_BRDCSTXMT:
                *val = rep->re_brdcstxmt;
                break;

        case MAC_STAT_NORCVBUF:
                *val = rep->re_norcvbuf;
                break;

        case MAC_STAT_IERRORS:
                *val = rep->re_ierrors;
                break;

        case MAC_STAT_NOXMTBUF:
                *val = rep->re_noxmtbuf;
                break;

        case MAC_STAT_OERRORS:
                *val = rep->re_oerrors;
                break;

        case MAC_STAT_COLLISIONS:
                *val = rep->re_collisions;
                break;

        case MAC_STAT_RBYTES:
                *val = rep->re_rbytes;
                break;

        case MAC_STAT_IPACKETS:
                *val = rep->re_ipackets;
                break;

        case MAC_STAT_OBYTES:
                *val = rep->re_obytes;
                break;

        case MAC_STAT_OPACKETS:
                *val = rep->re_opackets;
                break;

        case MAC_STAT_UNDERFLOWS:
                *val = rep->re_uflo;
                break;

        case MAC_STAT_OVERFLOWS:
                *val = rep->re_oflo;
                break;

        case ETHER_STAT_ALIGN_ERRORS:
                *val = rep->re_align_errors;
                break;

        case ETHER_STAT_FCS_ERRORS:
                *val = rep->re_fcs_errors;
                break;

        case ETHER_STAT_FIRST_COLLISIONS:
                *val = rep->re_first_collisions;
                break;

        case ETHER_STAT_SQE_ERRORS:
                *val = rep->re_sqe_errors;
                break;

        case ETHER_STAT_TX_LATE_COLLISIONS:
                *val = rep->re_tx_late_collisions;
                break;

        case ETHER_STAT_EX_COLLISIONS:
                *val = rep->re_ex_collisions;
                break;

        case ETHER_STAT_CARRIER_ERRORS:
                *val = rep->re_carrier_errors;
                break;

        case ETHER_STAT_CAP_10FDX:
                *val = rep->re_10fdx_cap;
                break;

        case ETHER_STAT_CAP_10HDX:
                *val = rep->re_10hdx_cap;
                break;

        case ETHER_STAT_CAP_PAUSE:
                *val = rep->re_pause_cap;
                break;

        case ETHER_STAT_ADV_CAP_10FDX:
                *val = rep->re_adv_10fdx_cap;
                break;

        case ETHER_STAT_ADV_CAP_10HDX:
                *val = rep->re_adv_10hdx_cap;
                break;

        case ETHER_STAT_ADV_CAP_PAUSE:
                *val = rep->re_adv_pause_cap;
                break;

        case ETHER_STAT_LINK_PAUSE:
                *val = rep->re_flowctrl;
                break;

        case ETHER_STAT_LINK_DUPLEX:
                *val = rep->re_duplex;
                break;

        case ETHER_STAT_TOOLONG_ERRORS:
                *val = rep->re_toolong_errors;
                break;

        case ETHER_STAT_TOOSHORT_ERRORS:
                *val = rep->re_runt_errors;
                break;

        case ETHER_STAT_JABBER_ERRORS:
                *val = rep->re_jabber_errors;
                break;

        default:
                return (ENOTSUP);
        }

        return (0);
}

int
re_m_start(void *arg)
{
        re_t *rep = arg;
        int error;

        mutex_enter(&rep->re_txlock);

        if ((error = re_start(rep)) != DDI_SUCCESS) {
                return (error);
        }

        rep->re_portmon = ddi_periodic_add(re_portmon,
            rep, PORTMON_INTERVAL, DDI_IPL_0);

        rep->re_state = RE_STATE_UP;

        mutex_exit(&rep->re_txlock);

        return (0);
}

void
re_m_stop(void *arg)
{
        re_t *rep = arg;

        mutex_enter(&rep->re_txlock);

        ddi_periodic_delete(rep->re_portmon);

        re_stop(rep);

        mac_link_update(rep->re_mh, LINK_STATE_UNKNOWN);

        rep->re_state = RE_STATE_DOWN;

        mutex_exit(&rep->re_txlock);
}

int
re_m_promisc(void *arg, boolean_t on)
{
        re_t *rep = arg;

        mutex_enter(&rep->re_txlock);

        rep->re_promisc = on;

        if (rep->re_state & RE_STATE_SUSPENDED) {
                mutex_exit(&rep->re_txlock);
                return (0);
        }

        if (rep->re_state & RE_STATE_UP) {
                /*
                 * The device must be restarted for changes
                 * to take effect. This will destroy remaining
                 * data in the rx and tx buffers.
                 */
                re_restart(rep);
        }

        mutex_exit(&rep->re_txlock);

        return (0);
}

int
re_m_multicst(void *arg, boolean_t add, const uint8_t *macaddr)
{
        re_t *rep = arg;
        uint32_t crc;
        int index;
        uint8_t bit;
        uint8_t *val;

        mutex_enter(&rep->re_txlock);

        CRC32(crc, macaddr, ETHERADDRL, -1U, crc32_table);
        crc >>= MCHASH_LATCH;

        index = crc / MCHASHSZ;
        bit = BIT(crc % MCHASHSZ);

        val = &rep->re_mcast[index];

        if (add) {
                *val |= bit;
        } else {
                *val ^= bit;
        }

        if (rep->re_promisc || rep->re_state & RE_STATE_SUSPENDED) {
                mutex_exit(&rep->re_txlock);
                return (0);
        }

        if (rep->re_state & RE_STATE_UP) {
                /*
                 * The device must be restarted for changes
                 * to take effect. This will destroy remaining
                 * data in the rx and tx buffers.
                 */
                re_restart(rep);
        }

        mutex_exit(&rep->re_txlock);

        return (0);
}

int
re_m_unicst(void *arg, const uint8_t *macaddr)
{
        re_t *rep = arg;

        mutex_enter(&rep->re_txlock);

        bcopy(macaddr, rep->re_curraddr, ETHERADDRL);

        if (rep->re_state & RE_STATE_SUSPENDED) {
                mutex_exit(&rep->re_txlock);
                return (0);
        }

        if (rep->re_state & RE_STATE_UP) {
                /*
                 * The device must be restarted for changes
                 * to take effect. This will destroy remaining
                 * data in the rx and tx buffers.
                 */
                re_restart(rep);
        }

        mutex_exit(&rep->re_txlock);

        return (0);
}

mblk_t *
re_m_tx(void *arg, mblk_t *mp)
{
        re_t *rep = arg;
        mblk_t *next;

        mutex_enter(&rep->re_txlock);

        while (mp != NULL) {
                next = mp->b_next;
                mp->b_next = NULL;

                if (re_send(rep, mp) != DDI_SUCCESS) {
                        rep->re_tx_update = B_TRUE;
                        mp->b_next = next;
                        break;
                }
                mp = next;
        }

        mutex_exit(&rep->re_txlock);

        return (mp);
}

/*ARGSUSED*/
int
re_m_setprop(void *barg, const char *pr_name, mac_prop_id_t pr_num,
    uint_t pr_valsize, const void *pr_val)
{
        re_t *rep = barg;
        int error = 0;

        mutex_enter(&rep->re_txlock);

        switch (pr_num) {
        case MAC_PROP_FLOWCTRL: {
                link_flowctrl_t val;

                if (!rep->re_pause_cap || !rep->re_adv_10fdx_cap) {
                        error = ENOTSUP;
                        break;
                }

                if (pr_valsize < sizeof (link_flowctrl_t)) {
                        error = EINVAL;
                        break;
                }

                bcopy(pr_val, &val, sizeof (link_flowctrl_t));

                switch (val) {
                case LINK_FLOWCTRL_BI:
                        rep->re_adv_pause_cap = 1;
                        break;

                case LINK_FLOWCTRL_NONE:
                        rep->re_adv_pause_cap = 0;
                        break;

                default:
                        error = ENOTSUP;
                }

                if (error == 0) {
                        rep->re_flowctrl = val;
                }
                break;
        }

        case MAC_PROP_EN_10FDX_CAP:
                if (!rep->re_10fdx_cap) {
                        error = ENOTSUP;
                        break;
                }

                rep->re_adv_10fdx_cap = *(uint8_t *)pr_val;

                if (!rep->re_adv_10fdx_cap) {
                        rep->re_adv_pause_cap = 0;
                }
                break;

        case MAC_PROP_EN_10HDX_CAP:
                if (!rep->re_10hdx_cap) {
                        error = ENOTSUP;
                        break;
                }

                rep->re_adv_10hdx_cap = *(uint8_t *)pr_val;
                break;

        default:
                error = ENOTSUP;
        }

        if (error == 0 && rep->re_state == RE_STATE_UP) {
                /*
                 * The device must be restarted for changes
                 * to take effect. This will destroy remaining
                 * data in the rx and tx buffers.
                 */
                re_restart(rep);
        }

        mutex_exit(&rep->re_txlock);

        return (error);
}

/*ARGSUSED*/
int
re_m_getprop(void *barg, const char *pr_name, mac_prop_id_t pr_num,
    uint_t pr_flags, uint_t pr_valsize, void *pr_val, uint_t *perm)
{
        re_t *rep = barg;
        boolean_t is_default;

        if (pr_valsize == 0) {
                return (EINVAL);
        }

        *perm = MAC_PROP_PERM_RW;

        is_default = pr_flags & MAC_PROP_DEFAULT;

        switch (pr_num) {
        case MAC_PROP_DUPLEX: {
                link_duplex_t val;

                *perm = MAC_PROP_PERM_READ;

                if (pr_valsize < sizeof (link_duplex_t)) {
                        return (EINVAL);
                }

                if (is_default) {
                        val = (rep->re_10fdx_cap ?
                            LINK_DUPLEX_FULL : LINK_DUPLEX_HALF);
                } else {
                        val = rep->re_duplex;
                }

                bcopy(&val, pr_val, sizeof (link_duplex_t));
                break;
        }

        case MAC_PROP_SPEED: {
                uint64_t val = IFSPEED(rep->re_speed);

                *perm = MAC_PROP_PERM_READ;

                if (pr_valsize < sizeof (uint64_t)) {
                        return (EINVAL);
                }

                bcopy(&val, pr_val, sizeof (uint64_t));
                break;
        }

        case MAC_PROP_MTU: {
                uint32_t val = ETHERMTU;

                *perm = MAC_PROP_PERM_READ;

                if (pr_valsize < sizeof (uint32_t)) {
                        return (EINVAL);
                }

                bcopy(&val, pr_val, sizeof (uint32_t));
                break;
        }

        case MAC_PROP_FLOWCTRL: {
                link_flowctrl_t val;

                if (pr_valsize < sizeof (link_flowctrl_t)) {
                        return (EINVAL);
                }

                if (is_default) {
                        val = (rep->re_pause_cap ?
                            LINK_FLOWCTRL_BI : LINK_FLOWCTRL_NONE);
                } else {
                        val = rep->re_flowctrl;
                }

                bcopy(&val, pr_val, sizeof (link_flowctrl_t));
                break;
        }

        case MAC_PROP_ADV_10FDX_CAP:
                *perm = MAC_PROP_PERM_READ;
                /*FALLTHRU*/

        case MAC_PROP_EN_10FDX_CAP:
                *(uint8_t *)pr_val = (is_default ?
                    rep->re_10fdx_cap : rep->re_adv_10fdx_cap);
                break;

        case MAC_PROP_ADV_10HDX_CAP:
                *perm = MAC_PROP_PERM_READ;
                /*FALLTHRU*/

        case MAC_PROP_EN_10HDX_CAP:
                *(uint8_t *)pr_val = (is_default ?
                    rep->re_10hdx_cap : rep->re_adv_10hdx_cap);
                break;

        default:
                return (ENOTSUP);
        }

        return (0);
}

/*
 * ISR/periodic entry points.
 */
/*ARGSUSED*/
uint_t
re_intr(caddr_t arg1, caddr_t arg2)
{
        re_t *rep = (void *)arg1;
        uint8_t status;
        mblk_t *mp = NULL;

        mutex_enter(&rep->re_txlock);

        if (rep->re_state != RE_STATE_UP) {
                mutex_exit(&rep->re_txlock);
                return (DDI_INTR_UNCLAIMED);
        }

        re_setpage(rep, REG_PAGE_0);

        if ((status = REG_READ(rep, ISR_REG)) == 0) {
                mutex_exit(&rep->re_txlock);
                return (DDI_INTR_UNCLAIMED);
        }

        /* Clear interrupt status */
        REG_WRITE(rep, ISR_REG, status);

        if (status & ISR_OVW) {
                re_error(rep->re_dip, "rx overflow; resetting!");
                re_restart(rep);

        } else if (status & (ISR_PTX | ISR_TXE)) {
                rep->re_tx_busy = B_FALSE;

                if (rep->re_tx_update) {
                        mac_tx_update(rep->re_mh);
                        rep->re_tx_update = B_FALSE;
                }

                if (status & ISR_TXE) {
                        re_send_err(rep);
                }

        } else if (status & ISR_PRX) {
                mp = re_recv(rep);

        } if (status & ISR_RXE) {
                re_recv_err(rep);
        }

        mutex_exit(&rep->re_txlock);

        /* Never submit chain within txlock! */
        if (mp != NULL) {
                mac_rx(rep->re_mh, NULL, mp);
        }

        return (DDI_INTR_CLAIMED);
}

void
re_portmon(void *arg)
{
        re_t *rep = arg;
        link_state_t state;

        mutex_enter(&rep->re_txlock);

        state = re_link_test(rep);

        if (rep->re_link_state != state) {
                mac_link_update(rep->re_mh,
                    (rep->re_link_state = state));
        }

        mutex_exit(&rep->re_txlock);
}

/*
 * Additional kstat entry points.
 */
int
re_regs_update(kstat_t *ksp, int rw)
{
        re_t *rep = ksp->ks_private;

        if (rw == KSTAT_WRITE) {
                return (EACCES);
        }

        mutex_enter(&rep->re_txlock);

        /*
         * Reading from these registers should never
         * produce side-effects.
         */
        for (int i = 0; i < re_regs_kstats_len; ++i) {
                re_regs_kstat_t *rksp = &re_regs_kstats[i];
                kstat_named_t *knp = (kstat_named_t *)ksp->ks_data + i;

                re_setpage(rep, rksp->ks_reg_page);

                knp->value.ul = REG_READ(rep, rksp->ks_reg);
        }

        mutex_exit(&rep->re_txlock);

        return (0);
}

/*
 * Support functions.
 */
int
re_reset(re_t *rep)
{
        ASSERT(mutex_owned(&rep->re_txlock));

        /* Reset soft state */
        rep->re_duplex = LINK_DUPLEX_UNKNOWN;
        rep->re_flowctrl = LINK_FLOWCTRL_NONE;
        rep->re_speed = RE_SPEED_UNKNOWN;

        rep->re_tx_busy = B_FALSE;
        rep->re_tx_update = B_FALSE;

        /* Soft reset device */
        REG_WRITE(rep, CR_REG, CR_RD_ABRT | CR_STP);

        /* Wait for device to settle */
        for (int i = 0; i < ISR_DELAY_CYCLES; ++i) {
                if (REG_READ(rep, ISR_REG) & ISR_RST) {
                        rep->re_init = B_FALSE;
                        return (DDI_SUCCESS);
                }
                drv_usecwait(ISR_DELAY);
        }
        re_error(rep->re_dip, "reset timed out!");

        return (DDI_FAILURE);
}

int
re_init(re_t *rep)
{
        int error;

        ASSERT(mutex_owned(&rep->re_txlock));

        if ((error = re_reset(rep)) != DDI_SUCCESS) {
                return (error);
        }

        re_init_dcr(rep);

        re_rdma_clear(rep);

        re_init_mac(rep);

        re_init_ring(rep);

        re_intr_enable(rep);

        re_setaddr(rep, rep->re_curraddr);

        if (rep->re_promisc) {
                re_setmcast(rep, re_mcast_promisc);
        } else {
                re_setmcast(rep, rep->re_mcast);
        }

        re_init_phy(rep);

        rep->re_init = B_TRUE;

        return (DDI_SUCCESS);
}

inline void
re_init_dcr(re_t *rep)
{
        ASSERT(mutex_owned(&rep->re_txlock));

        REG_WRITE(rep, DCR_REG, DCR_WTS | DCR_LS | DCR_FT_8);
}

inline void
re_init_ring(re_t *rep)
{
        ASSERT(mutex_owned(&rep->re_txlock));

        rep->re_rx_next = PSTART + 1;

        REG_WRITE(rep, PSTART_REG, PSTART);
        REG_WRITE(rep, PSTOP_REG, PSTOP);

        REG_WRITE(rep, BNRY_REG, PSTART);

        re_setpage(rep, REG_PAGE_1);

        REG_WRITE(rep, CURR_REG, rep->re_rx_next);
}

inline void
re_init_mac(re_t *rep)
{
        ASSERT(mutex_owned(&rep->re_txlock));

        REG_WRITE(rep, RCR_REG, RCR_AB | RCR_AM | RCR_AR |
            (rep->re_promisc ? RCR_PRO : 0));

        /* Put into loopback */
        REG_WRITE(rep, TCR_REG, TCR_LB_1);
}

inline void
re_init_phy(re_t *rep)
{
        ASSERT(mutex_owned(&rep->re_txlock));

        /* Assume all modes supported */
        rep->re_pause_cap = 1;
        rep->re_10fdx_cap = 1;
        rep->re_10hdx_cap = 1;

        /* 10Mbps only device */
        rep->re_speed = RE_SPEED_10;

        re_setpage(rep, REG_PAGE_3);

        /* Enable PHY auto-detection */
        REG_WRITE_BITS(rep, CONFIG2_REG, CONFIG2_PL, CONFIG2_PL_AUTO);

        if (rep->re_mode & RE_MODE_RTL8029AS) {
                REG_WRITE(rep, CR9346_REG, CR9346_EEM_ENABLE);

                if (rep->re_adv_10fdx_cap) {
                        REG_WRITE_BIT(rep, CONFIG3_REG, CONFIG3_FUDUP);
                        rep->re_duplex = LINK_DUPLEX_FULL;

                        if (rep->re_adv_pause_cap) {
                                REG_WRITE_BIT(rep, CONFIG2_REG, CONFIG2_FCE);
                                rep->re_flowctrl = LINK_FLOWCTRL_BI;
                                goto done;
                        }

                } else {
                        REG_CLEAR_BIT(rep, CONFIG3_REG, CONFIG3_FUDUP);
                        rep->re_duplex = LINK_DUPLEX_HALF;
                }

                REG_CLEAR_BIT(rep, CONFIG2_REG, CONFIG2_FCE);
                rep->re_flowctrl = LINK_FLOWCTRL_NONE;

        } else { /* RE_MODE_LEGACY */
                rep->re_duplex = LINK_DUPLEX_HALF;
                rep->re_flowctrl = LINK_FLOWCTRL_NONE;

                /* Disable unsupported modes */
                rep->re_pause_cap = rep->re_adv_pause_cap = 0;
                rep->re_10fdx_cap = rep->re_adv_10fdx_cap = 0;
        }
done:
        REG_WRITE(rep, CR_REG, CR_RD_ABRT | CR_STA);
}

int
re_start(re_t *rep)
{
        int error;

        ASSERT(mutex_owned(&rep->re_txlock));

        if ((error = re_init(rep)) != DDI_SUCCESS) {
                return (error);
        }

        /* Take out of loopback */
        REG_WRITE(rep, TCR_REG, TCR_LB_0);

        return (DDI_SUCCESS);
}

void
re_stop(re_t *rep)
{
        ASSERT(mutex_owned(&rep->re_txlock));

        re_intr_disable(rep);

        (void) re_reset(rep);
}

void
re_restart(re_t *rep)
{
        ASSERT(mutex_owned(&rep->re_txlock));

        re_stop(rep);

        (void) re_start(rep);
}

int
re_suspend(re_t *rep)
{
        mutex_enter(&rep->re_txlock);

        if (rep->re_state & RE_STATE_UP) {
                re_stop(rep);
        }

        rep->re_state |= RE_STATE_SUSPENDED;

        mutex_exit(&rep->re_txlock);

        return (DDI_SUCCESS);
}

int
re_resume(re_t *rep)
{
        mutex_enter(&rep->re_txlock);

        rep->re_state ^= RE_STATE_SUSPENDED;

        if (rep->re_state & RE_STATE_UP) {
                re_start(rep);
        }

        mutex_exit(&rep->re_txlock);

        return (DDI_SUCCESS);
}

mblk_t *
re_recv(re_t *rep)
{
        mblk_t *chain = NULL;
        mblk_t **mpp = &chain;

        ASSERT(mutex_owned(&rep->re_txlock));

        re_hdr_t hdr;
        do {
                uint16_t len;
                mblk_t *mp;

                if (re_gethdr(rep, &hdr) != DDI_SUCCESS) {
                        re_debug(rep->re_dip, "spurious rx interrupt!");
                        return (chain);
                }

                len = hdr.h_len - ETHERFCSL;

                if (len < ETHERMIN) {
                        rep->re_ierrors++;
                        rep->re_runt_errors++;
                        continue;
                }

                if (len > ETHERMAX + VLAN_TAGSZ) {
                        rep->re_ierrors++;
                        rep->re_toolong_errors++;
                        continue;
                }

                /* Align on word boundary */
                mp = allocb(PAD32(len), 0);
                if (mp == NULL) {
                        rep->re_ierrors++;
                        rep->re_norcvbuf++;
                        continue;
                }

                mp->b_wptr = mp->b_rptr + len;

                re_rdma_get(rep, mp->b_rptr, hdr.h_data, len);

                rep->re_ipackets++;
                rep->re_rbytes += len;

                if (hdr.h_status & RSR_PHY) {
                        if (bcmp(mp->b_rptr, re_broadcast, ETHERADDRL) == 0) {
                                rep->re_brdcstrcv++;
                        } else {
                                rep->re_multircv++;
                        }
                }

                *mpp = mp;
                mpp = &mp->b_next;

        } while (re_recv_next(rep, &hdr));

        return (chain);
}

inline boolean_t
re_recv_next(re_t *rep, re_hdr_t *hp)
{
        uint8_t bnry;

        ASSERT(mutex_owned(&rep->re_txlock));

        /* Update next packet pointer */
        rep->re_rx_next = hp->h_next;

        /* Update boundary pointer */
        bnry = rep->re_rx_next - 1;
        if (bnry < PSTART) {
                bnry = PSTOP - 1;
        }
        REG_WRITE(rep, BNRY_REG, bnry);

        re_setpage(rep, REG_PAGE_1);

        /* Are more packets are available? */
        return (REG_READ(rep, CURR_REG) != rep->re_rx_next);
}

void
re_recv_err(re_t *rep)
{
        uint8_t status;

        ASSERT(mutex_owned(&rep->re_txlock));

        re_setpage(rep, REG_PAGE_0);

        if ((status = REG_READ(rep, RSR_REG)) == 0) {
                return;
        }

        rep->re_ierrors++;

        if (status & RSR_CRC) {
                rep->re_fcs_errors +=
                    REG_READ(rep, CNTR1_REG);
        }

        if (status & RSR_FAE) {
                rep->re_align_errors +=
                    REG_READ(rep, CNTR0_REG);
        }

        if (status & RSR_FO) {
                rep->re_oflo++;
        }

        if (status & RSR_MPA) {
                rep->re_norcvbuf +=
                    REG_READ(rep, CNTR2_REG);
        }

        if (status & RSR_DFR) {
                rep->re_jabber_errors++;
        }
}

int
re_send(re_t *rep, mblk_t *mp)
{
        uint16_t len;
        int error;

        ASSERT(mutex_owned(&rep->re_txlock));

        len = msgsize(mp);

        if (len > ETHERMAX + VLAN_TAGSZ) {
                rep->re_oerrors++;
                rep->re_toolong_errors++;
                freemsg(mp);
                return (DDI_SUCCESS);
        }

        if (rep->re_tx_busy) {
                rep->re_noxmtbuf++;
                return (DDI_FAILURE);
        }

        mcopymsg(mp, rep->re_tx_buf);

        /*
         * Packets must contain at least ETHERMIN octets.
         * Padded octets are zeroed out prior to issuing
         * the remote write.
         */
        rep->re_tx_len = max(ETHERMIN, len);

        if (rep->re_tx_len > len) {
                bzero(rep->re_tx_buf + len, rep->re_tx_len - len);
        }

        if ((error = re_rdma_put(rep, rep->re_tx_buf, BTOP(TXPSTART),
            rep->re_tx_len)) != DDI_SUCCESS) {
                return (error);
        }

        rep->re_opackets++;
        rep->re_obytes += rep->re_tx_len;

        if (*rep->re_tx_buf & 0x01) {
                if (bcmp(rep->re_tx_buf, re_broadcast, ETHERADDRL) == 0) {
                        rep->re_brdcstxmt++;
                } else {
                        rep->re_multixmt++;
                }
        }

        /* Attempt to send immediately */
        re_send_pkt(rep);

        return (DDI_SUCCESS);
}

inline void
re_send_pkt(re_t *rep)
{
        ASSERT(mutex_owned(&rep->re_txlock));

        rep->re_tx_busy = B_TRUE;

        REG_WRITE(rep, TPSR_REG, TXPSTART);

        REG_WRITE(rep, TBCR0_REG, rep->re_tx_len);
        REG_WRITE(rep, TBCR1_REG, rep->re_tx_len >> 8);

        REG_WRITE(rep, CR_REG, CR_TXP | CR_STA);
}

void
re_send_err(re_t *rep)
{
        uint8_t status;

        ASSERT(mutex_owned(&rep->re_txlock));

        re_setpage(rep, REG_PAGE_0);

        if ((status = REG_READ(rep, TSR_REG)) == 0) {
                return;
        }

        rep->re_oerrors++;

        if (status & TSR_COL) {
                uint8_t ncr = REG_READ(rep, NCR_REG);

                rep->re_collisions += ncr;

                if (ncr == 1) {
                        rep->re_first_collisions++;
                }
        }

        if (status & TSR_ABT) {
                rep->re_ex_collisions++;
        }

        if (status & TSR_CRS) {
                rep->re_carrier_errors++;
        }

        if (status & TSR_FU) {
                rep->re_uflo++;
        }

        if (status & TSR_CDH) {
                rep->re_sqe_errors++;
        }

        if (status & TSR_OWC) {
                rep->re_tx_late_collisions++;
        }
}

inline void
re_intr_clear(re_t *rep)
{
        ASSERT(mutex_owned(&rep->re_txlock));

        REG_WRITE(rep, ISR_REG, 0xff);
}

void
re_intr_enable(re_t *rep)
{
        ASSERT(mutex_owned(&rep->re_txlock));

        re_setpage(rep, REG_PAGE_0);

        re_intr_clear(rep);

        REG_WRITE(rep, IMR_REG, IMR_OVWE |
            IMR_PRXE | IMR_RXEE | IMR_PTXE | IMR_TXEE);
}

void
re_intr_disable(re_t *rep)
{
        ASSERT(mutex_owned(&rep->re_txlock));

        re_setpage(rep, REG_PAGE_0);

        REG_CLEAR(rep, IMR_REG);

        re_intr_clear(rep);
}

link_state_t
re_link_test(re_t *rep)
{
        ASSERT(mutex_owned(&rep->re_txlock));

        re_setpage(rep, REG_PAGE_3);

        /*
         * If PHY auto-detection is enabled, the BNC bit
         * indicates 10BASE-T link state. If 10BASE2 media
         * is used, LINK_STATE_DOWN will always be reported.
         */
        return (REG_READ(rep, CONFIG0_REG) & CONFIG0_BNC ?
            LINK_STATE_DOWN : LINK_STATE_UP);
}

inline int
re_gethdr(re_t *rep, re_hdr_t *hp)
{
        uint32_t val;

        ASSERT(mutex_owned(&rep->re_txlock));

        re_setpage(rep, REG_PAGE_1);

        /* Is next packet available? */
        if (REG_READ(rep, CURR_REG) == rep->re_rx_next) {
                return (DDI_FAILURE);
        }

        hp->h_base = BTOP(rep->re_rx_next);
        hp->h_data = hp->h_base + RXHDRSZ;

        /*
         * The rx header comprises the first 4 bytes of
         * the initial rx buffer page.
         */
        re_rdma_init(rep, CR_RD_RR, hp->h_base, RXHDRSZ);

        val = ddi_get32(rep->re_regs_acch, DATA32(rep));

        hp->h_status = (uint8_t)val;
        hp->h_next = (uint8_t)(val >> 8);
        hp->h_len = (uint16_t)(val >> 16);

        return (DDI_SUCCESS);
}

void
re_getmode(re_t *rep, re_mode_t *mode)
{
        ASSERT(mutex_owned(&rep->re_txlock));

        if (!rep->re_legacy_mode) {
                re_setpage(rep, REG_PAGE_3);

                if (REG_READ(rep, ASID0_REG) == 0x29 &&
                    REG_READ(rep, ASID1_REG) == 0x80) {
                        *mode = RE_MODE_RTL8029AS;
                        return;
                }
        }

        *mode = RE_MODE_LEGACY;
        re_debug(rep->re_dip, "found legacy mode device");
}

void
re_getaddr(re_t *rep, uint8_t *macaddr)
{
        ASSERT(mutex_owned(&rep->re_txlock));

        re_read_prom(rep, macaddr, PROM_ETHER, ETHERADDRL);

        re_debug(rep->re_dip,
            "station address is %02x:%02x:%02x:%02x:%02x:%02x",
            macaddr[0], macaddr[1], macaddr[2], macaddr[3],
            macaddr[4], macaddr[5]);
}

void
re_setaddr(re_t *rep, uint8_t *macaddr)
{
        ASSERT(mutex_owned(&rep->re_txlock));

        re_setpage(rep, REG_PAGE_1);

        REG_WRITE(rep, PAR0_REG, macaddr[0]);
        REG_WRITE(rep, PAR1_REG, macaddr[1]);
        REG_WRITE(rep, PAR2_REG, macaddr[2]);
        REG_WRITE(rep, PAR3_REG, macaddr[3]);
        REG_WRITE(rep, PAR4_REG, macaddr[4]);
        REG_WRITE(rep, PAR5_REG, macaddr[5]);
}

void
re_setmcast(re_t *rep, uint8_t *mcast)
{
        ASSERT(mutex_owned(&rep->re_txlock));

        re_setpage(rep, REG_PAGE_1);

        REG_WRITE(rep, MAR0_REG, mcast[0]);
        REG_WRITE(rep, MAR1_REG, mcast[1]);
        REG_WRITE(rep, MAR2_REG, mcast[2]);
        REG_WRITE(rep, MAR3_REG, mcast[3]);
        REG_WRITE(rep, MAR4_REG, mcast[4]);
        REG_WRITE(rep, MAR5_REG, mcast[5]);
        REG_WRITE(rep, MAR6_REG, mcast[6]);
        REG_WRITE(rep, MAR7_REG, mcast[7]);
}

inline void
re_setpage(re_t *rep, uint8_t reg_page)
{
        ASSERT(mutex_owned(&rep->re_txlock));

        REG_WRITE(rep, CR_REG, reg_page | CR_RD_ABRT |
            (rep->re_init ? CR_STA : CR_STP));
}

void
re_read_prom(re_t *rep, uint8_t *buf, uint8_t off, uint8_t len)
{
        ASSERT(mutex_owned(&rep->re_txlock));

        re_rdma_abort(rep);

        re_init_dcr(rep);

        re_intr_disable(rep);

        /*
         * The PROM should always be read using byte-wide
         * access regardless of the DCR configuration.
         */
        re_rdma_init(rep, CR_RD_RR, off, len);

        ddi_rep_get8(rep->re_regs_acch, buf, DATA8(rep),
            len, DDI_DEV_NO_AUTOINCR);
}

inline void
re_rdma_clear(re_t *rep)
{
        ASSERT(mutex_owned(&rep->re_txlock));

        REG_CLEAR(rep, RBCR0_REG);
        REG_CLEAR(rep, RBCR1_REG);
}

void
re_rdma_abort(re_t *rep)
{
        ASSERT(mutex_owned(&rep->re_txlock));

        re_setpage(rep, REG_PAGE_0);

        REG_WRITE(rep, CR_REG, CR_RD_ABRT |
            (rep->re_init ? CR_STA : CR_STP));
}

void
re_rdma_init(re_t *rep, uint8_t cmd, uint16_t addr, uint16_t len)
{
        ASSERT(mutex_owned(&rep->re_txlock));

        re_rdma_abort(rep);

        REG_WRITE(rep, RBCR0_REG, len);
        REG_WRITE(rep, RBCR1_REG, len >> 8);

        REG_WRITE(rep, RSAR0_REG, addr);
        REG_WRITE(rep, RSAR1_REG, addr >> 8);

        REG_WRITE(rep, CR_REG, cmd |
            (rep->re_init ? CR_STA : CR_STP));
}

void
re_rdma_get(re_t *rep, uint8_t *buf, uint16_t addr, uint16_t len)
{
        ASSERT(mutex_owned(&rep->re_txlock));

        re_rdma_init(rep, CR_RD_RR, addr, len);

        ddi_rep_get32(rep->re_regs_acch, (uint32_t *)buf, DATA32(rep),
            PAD32(len) / sizeof (uint32_t), DDI_DEV_NO_AUTOINCR);
}

int
re_rdma_put(re_t *rep, uint8_t *buf, uint16_t addr, uint16_t len)
{
        ASSERT(mutex_owned(&rep->re_txlock));

        re_rdma_init(rep, CR_RD_RW, addr, len);

        ddi_rep_put32(rep->re_regs_acch, (uint32_t *)buf, DATA32(rep),
            PAD32(len) / sizeof (uint32_t), DDI_DEV_NO_AUTOINCR);

        /*
         * Remote writes occur in bursts to the FIFO. To
         * avoid potential corruption, interrupt status
         * is polled for completion before returning.
         */
        for (int i = 0; i < ISR_DELAY_CYCLES; ++i) {
                if (REG_READ(rep, ISR_REG) & ISR_RDC) {
                        /* Clear RDMA complete status */
                        REG_WRITE(rep, ISR_REG, ISR_RDC);
                        return (DDI_SUCCESS);
                }
                drv_usecwait(ISR_DELAY);
        }
        re_error(rep->re_dip, "write timed out!");

        return (DDI_FAILURE);
}

void
re_dprintf(dev_info_t *dip, int level, const char *format, ...)
{
        va_list ap;
        char buf[255];

        va_start(ap, format);

        (void) vsnprintf(buf, sizeof (buf), format, ap);

        cmn_err(level, "!%s%d %s", ddi_driver_name(dip),
            ddi_get_instance(dip), buf);

        va_end(ap);
}
_______________________________________________
driver-discuss mailing list
[email protected]
http://mail.opensolaris.org/mailman/listinfo/driver-discuss

Reply via email to