root/usr/src/uts/common/io/pcn/pcn.c
/*
 * Copyright (c) 2011 Jason King.
 * Copyright (c) 2000 Berkeley Software Design, Inc.
 * Copyright (c) 1997, 1998, 1999, 2000
 *      Bill Paul <wpaul@osd.bsdi.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 Bill Paul.
 * 4. Neither the name of the author nor the names of any co-contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

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

#include "pcn.h"
#include "pcnimpl.h"

#define ETHERVLANMTU    (ETHERMAX + 4)

#define CSR_WRITE_4(pcnp, reg, val) \
        ddi_put32(pcnp->pcn_regshandle, (uint32_t *)(pcnp->pcn_regs + reg), val)

#define CSR_WRITE_2(pcnp, reg, val) \
        ddi_put16(pcnp->pcn_regshandle, (uint16_t *)(pcnp->pcn_regs + reg), val)

#define CSR_READ_4(pcnp, reg) \
        ddi_get32(pcnp->pcn_regshandle, (uint32_t *)(pcnp->pcn_regs + reg))

#define CSR_READ_2(pcnp, reg) \
        ddi_get16(pcnp->pcn_regshandle, (uint16_t *)(pcnp->pcn_regs + reg))

#define PCN_CSR_SETBIT(pcnp, reg, x) \
        pcn_csr_write(pcnp, reg, pcn_csr_read(pcnp, reg) | (x))

#define PCN_CSR_CLRBIT(pcnp, reg, x) \
        pcn_csr_write(pcnp, reg, pcn_csr_read(pcnp, reg) & ~(x))

#define PCN_BCR_SETBIT(pncp, reg, x) \
        pcn_bcr_write(pcnp, reg, pcn_bcr_read(pcnp, reg) | (x))

#define PCN_BCR_CLRBIT(pcnp, reg, x) \
        pcn_bcr_write(pcnp, reg, pcn_bcr_read(pcnp, reg) & ~(x))

static int      pcn_attach(dev_info_t *, ddi_attach_cmd_t);
static int      pcn_detach(dev_info_t *, ddi_detach_cmd_t);
static int      pcn_ddi_resume(dev_info_t *);
static int      pcn_quiesce(dev_info_t *);

static void     pcn_teardown(pcn_t *);

static int      pcn_m_unicast(void *, const uint8_t *);
static int      pcn_m_multicast(void *, boolean_t, const uint8_t *);
static int      pcn_m_promisc(void *, boolean_t);
static mblk_t   *pcn_m_tx(void *, mblk_t *);
static void     pcn_m_ioctl(void *, queue_t *, mblk_t *);
static int      pcn_m_stat(void *, uint_t, uint64_t *);
static int      pcn_m_start(void *);
static void     pcn_m_stop(void *);
static int      pcn_m_getprop(void *, const char *, mac_prop_id_t, uint_t,
    void *);
static int      pcn_m_setprop(void *, const char *, mac_prop_id_t, uint_t,
    const void *);
static void     pcn_m_propinfo(void *, const char *, mac_prop_id_t,
    mac_prop_info_handle_t);
static int      pcn_watchdog(pcn_t *);

static unsigned pcn_intr(caddr_t);

static uint16_t pcn_mii_read(void *, uint8_t, uint8_t);
static void     pcn_mii_write(void *, uint8_t, uint8_t, uint16_t);
static void     pcn_mii_notify(void *, link_state_t);

static uint32_t pcn_csr_read(pcn_t *, uint32_t);
static uint16_t pcn_csr_read16(pcn_t *, uint32_t);
static void     pcn_csr_write(pcn_t *, uint32_t, uint32_t);

static uint32_t pcn_bcr_read(pcn_t *, uint32_t);
static uint16_t pcn_bcr_read16(pcn_t *, uint32_t);
static void     pcn_bcr_write(pcn_t *, uint32_t, uint32_t);

static boolean_t        pcn_send(pcn_t *, mblk_t *);

static pcn_buf_t        *pcn_allocbuf(pcn_t *);
static void             pcn_destroybuf(pcn_buf_t *);
static int              pcn_allocrxring(pcn_t *);
static int              pcn_alloctxring(pcn_t *);
static void             pcn_freetxring(pcn_t *);
static void             pcn_freerxring(pcn_t *);
static void             pcn_resetrings(pcn_t *);
static int              pcn_initialize(pcn_t *, boolean_t);
static mblk_t           *pcn_receive(pcn_t *);
static void             pcn_resetall(pcn_t *);
static void             pcn_startall(pcn_t *);
static void             pcn_stopall(pcn_t *);
static void             pcn_reclaim(pcn_t *);
static void             pcn_getfactaddr(pcn_t *);
static int              pcn_set_chipid(pcn_t *, uint32_t);
static const pcn_type_t *pcn_match(uint16_t, uint16_t);
static void             pcn_start_timer(pcn_t *);
static void             pcn_stop_timer(pcn_t *);

static void             pcn_error(dev_info_t *, char *, ...);

void *pcn_ssp = NULL;

static uchar_t pcn_broadcast[ETHERADDRL] = {
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};

static const pcn_type_t pcn_devs[] = {
        { PCN_VENDORID, PCN_DEVICEID_PCNET, "AMD PCnet/PCI 10/100BaseTX" },
        { PCN_VENDORID, PCN_DEVICEID_HOME, "AMD PCnet/Home HomePNA" },
        { 0, 0, NULL }
};

static mii_ops_t pcn_mii_ops = {
        MII_OPS_VERSION,
        pcn_mii_read,
        pcn_mii_write,
        pcn_mii_notify,
        NULL
};

static mac_callbacks_t pcn_m_callbacks = {
        MC_IOCTL | MC_SETPROP | MC_GETPROP | MC_PROPINFO,
        pcn_m_stat,
        pcn_m_start,
        pcn_m_stop,
        pcn_m_promisc,
        pcn_m_multicast,
        pcn_m_unicast,
        pcn_m_tx,
        NULL,
        pcn_m_ioctl,
        NULL,           /* mc_getcapab */
        NULL,           /* mc_open */
        NULL,           /* mc_close */
        pcn_m_setprop,
        pcn_m_getprop,
        pcn_m_propinfo
};

DDI_DEFINE_STREAM_OPS(pcn_devops, nulldev, nulldev, pcn_attach, pcn_detach,
    nodev, NULL, D_MP, NULL, pcn_quiesce);

static struct modldrv pcn_modldrv = {
        &mod_driverops,
        "AMD PCnet",
        &pcn_devops
};

static struct modlinkage pcn_modlinkage = {
        MODREV_1,
        { &pcn_modldrv, NULL }
};

static ddi_device_acc_attr_t pcn_devattr = {
        DDI_DEVICE_ATTR_V0,
        DDI_STRUCTURE_LE_ACC,
        DDI_STRICTORDER_ACC
};

static ddi_device_acc_attr_t pcn_bufattr = {
        DDI_DEVICE_ATTR_V0,
        DDI_NEVERSWAP_ACC,
        DDI_STRICTORDER_ACC
};

static ddi_dma_attr_t pcn_dma_attr = {
        DMA_ATTR_V0,            /* dm_attr_version */
        0,                      /* dma_attr_addr_lo */
        0xFFFFFFFFU,            /* dma_attr_addr_hi */
        0x7FFFFFFFU,            /* dma_attr_count_max */
        4,                      /* dma_attr_align */
        0x3F,                   /* dma_attr_burstsizes */
        1,                      /* dma_attr_minxfer */
        0xFFFFFFFFU,            /* dma_attr_maxxfer */
        0xFFFFFFFFU,            /* dma_attr_seg */
        1,                      /* dma_attr_sgllen */
        1,                      /* dma_attr_granular */
        0                       /* dma_attr_flags */
};

static ddi_dma_attr_t pcn_dmadesc_attr = {
        DMA_ATTR_V0,            /* dm_attr_version */
        0,                      /* dma_attr_addr_lo */
        0xFFFFFFFFU,            /* dma_attr_addr_hi */
        0x7FFFFFFFU,            /* dma_attr_count_max */
        16,                     /* dma_attr_align */
        0x3F,                   /* dma_attr_burstsizes */
        1,                      /* dma_attr_minxfer */
        0xFFFFFFFFU,            /* dma_attr_maxxfer */
        0xFFFFFFFFU,            /* dma_attr_seg */
        1,                      /* dma_attr_sgllen */
        1,                      /* dma_attr_granular */
        0                       /* dma_attr_flags */
};

/*
 * DDI entry points
 */
int
_init(void)
{
        int     rc;

        if ((rc = ddi_soft_state_init(&pcn_ssp, sizeof (pcn_t), 1)) != 0)
                return (rc);

        mac_init_ops(&pcn_devops, "pcn");
        if ((rc = mod_install(&pcn_modlinkage)) != DDI_SUCCESS) {
                mac_fini_ops(&pcn_devops);
                ddi_soft_state_fini(&pcn_ssp);
        }
        return (rc);
}

int
_fini(void)
{
        int     rc;

        if ((rc = mod_remove(&pcn_modlinkage)) == DDI_SUCCESS) {
                mac_fini_ops(&pcn_devops);
                ddi_soft_state_fini(&pcn_ssp);
        }
        return (rc);
}

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

int
pcn_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
        pcn_t                   *pcnp;
        mac_register_t          *macp;
        const pcn_type_t        *pcn_type;
        int                     instance = ddi_get_instance(dip);
        int                     rc;
        ddi_acc_handle_t        pci;
        uint16_t                venid;
        uint16_t                devid;
        uint16_t                svid;
        uint16_t                ssid;

        switch (cmd) {
        case DDI_RESUME:
                return (pcn_ddi_resume(dip));

        case DDI_ATTACH:
                break;

        default:
                return (DDI_FAILURE);
        }

        if (ddi_slaveonly(dip) == DDI_SUCCESS) {
                pcn_error(dip, "slot does not support PCI bus-master");
                return (DDI_FAILURE);
        }

        if (ddi_intr_hilevel(dip, 0) != 0) {
                pcn_error(dip, "hilevel interrupts not supported");
                return (DDI_FAILURE);
        }

        if (pci_config_setup(dip, &pci) != DDI_SUCCESS) {
                pcn_error(dip, "unable to setup PCI config handle");
                return (DDI_FAILURE);
        }

        venid = pci_config_get16(pci, PCI_CONF_VENID);
        devid = pci_config_get16(pci, PCI_CONF_DEVID);
        svid = pci_config_get16(pci, PCI_CONF_SUBVENID);
        ssid = pci_config_get16(pci, PCI_CONF_SUBSYSID);

        if ((pcn_type = pcn_match(venid, devid)) == NULL) {
                pci_config_teardown(&pci);
                pcn_error(dip, "Unable to identify PCI card");
                return (DDI_FAILURE);
        }

        if (ddi_prop_update_string(DDI_DEV_T_NONE, dip, "model",
            pcn_type->pcn_name) != DDI_PROP_SUCCESS) {
                pci_config_teardown(&pci);
                pcn_error(dip, "Unable to create model property");
                return (DDI_FAILURE);
        }

        if (ddi_soft_state_zalloc(pcn_ssp, instance) != DDI_SUCCESS) {
                pcn_error(dip, "Unable to allocate soft state");
                pci_config_teardown(&pci);
                return (DDI_FAILURE);
        }

        pcnp = ddi_get_soft_state(pcn_ssp, instance);
        pcnp->pcn_dip = dip;
        pcnp->pcn_instance = instance;
        pcnp->pcn_extphyaddr = -1;

        if (ddi_get_iblock_cookie(dip, 0, &pcnp->pcn_icookie) != DDI_SUCCESS) {
                pcn_error(pcnp->pcn_dip, "ddi_get_iblock_cookie failed");
                ddi_soft_state_free(pcn_ssp, instance);
                pci_config_teardown(&pci);
                return (DDI_FAILURE);
        }


        mutex_init(&pcnp->pcn_xmtlock, NULL, MUTEX_DRIVER, pcnp->pcn_icookie);
        mutex_init(&pcnp->pcn_intrlock, NULL, MUTEX_DRIVER, pcnp->pcn_icookie);
        mutex_init(&pcnp->pcn_reglock, NULL, MUTEX_DRIVER, pcnp->pcn_icookie);

        /*
         * Enable bus master, IO space, and memory space accesses
         */
        pci_config_put16(pci, PCI_CONF_COMM,
            pci_config_get16(pci, PCI_CONF_COMM) | PCI_COMM_ME | PCI_COMM_MAE);

        pci_config_teardown(&pci);

        if (ddi_regs_map_setup(dip, 1, (caddr_t *)&pcnp->pcn_regs, 0, 0,
            &pcn_devattr, &pcnp->pcn_regshandle)) {
                pcn_error(dip, "ddi_regs_map_setup failed");
                goto fail;
        }

        if (pcn_set_chipid(pcnp, (uint32_t)ssid << 16 | (uint32_t)svid) !=
            DDI_SUCCESS) {
                goto fail;
        }

        if ((pcnp->pcn_mii = mii_alloc(pcnp, dip, &pcn_mii_ops)) == NULL)
                goto fail;

        /* XXX: need to set based on device */
        mii_set_pauseable(pcnp->pcn_mii, B_FALSE, B_FALSE);

        if ((pcn_allocrxring(pcnp) != DDI_SUCCESS) ||
            (pcn_alloctxring(pcnp) != DDI_SUCCESS)) {
                pcn_error(dip, "unable to allocate DMA resources");
                goto fail;
        }

        pcnp->pcn_promisc = B_FALSE;

        mutex_enter(&pcnp->pcn_intrlock);
        mutex_enter(&pcnp->pcn_xmtlock);
        rc = pcn_initialize(pcnp, B_TRUE);
        mutex_exit(&pcnp->pcn_xmtlock);
        mutex_exit(&pcnp->pcn_intrlock);
        if (rc != DDI_SUCCESS)
                goto fail;

        if (ddi_add_intr(dip, 0, NULL, NULL, pcn_intr, (caddr_t)pcnp) !=
            DDI_SUCCESS) {
                pcn_error(dip, "unable to add interrupt");
                goto fail;
        }

        pcnp->pcn_flags |= PCN_INTR_ENABLED;

        if ((macp = mac_alloc(MAC_VERSION)) == NULL) {
                pcn_error(pcnp->pcn_dip, "mac_alloc failed");
                goto fail;
        }

        macp->m_type_ident = MAC_PLUGIN_IDENT_ETHER;
        macp->m_driver = pcnp;
        macp->m_dip = dip;
        macp->m_src_addr = pcnp->pcn_addr;
        macp->m_callbacks = &pcn_m_callbacks;
        macp->m_min_sdu = 0;
        macp->m_max_sdu = ETHERMTU;
        macp->m_margin = VLAN_TAGSZ;

        if (mac_register(macp, &pcnp->pcn_mh) == DDI_SUCCESS) {
                mac_free(macp);
                return (DDI_SUCCESS);
        }

        mac_free(macp);

        return (DDI_SUCCESS);

fail:
        pcn_teardown(pcnp);
        return (DDI_FAILURE);
}

int
pcn_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
        pcn_t   *pcnp;

        pcnp = ddi_get_soft_state(pcn_ssp, ddi_get_instance(dip));

        if (pcnp == NULL) {
                pcn_error(dip, "no soft state in detach!");
                return (DDI_FAILURE);
        }

        switch (cmd) {
        case DDI_DETACH:
                if (mac_unregister(pcnp->pcn_mh) != 0)
                        return (DDI_FAILURE);

                mutex_enter(&pcnp->pcn_intrlock);
                mutex_enter(&pcnp->pcn_xmtlock);
                pcnp->pcn_flags &= ~PCN_RUNNING;
                pcn_stopall(pcnp);
                mutex_exit(&pcnp->pcn_xmtlock);
                mutex_exit(&pcnp->pcn_intrlock);

                pcn_teardown(pcnp);
                return (DDI_SUCCESS);

        case DDI_SUSPEND:
                mii_suspend(pcnp->pcn_mii);

                mutex_enter(&pcnp->pcn_intrlock);
                mutex_enter(&pcnp->pcn_xmtlock);
                pcnp->pcn_flags |= PCN_SUSPENDED;
                pcn_stopall(pcnp);
                mutex_exit(&pcnp->pcn_xmtlock);
                mutex_exit(&pcnp->pcn_intrlock);
                return (DDI_SUCCESS);

        default:
                return (DDI_FAILURE);
        }
}

int
pcn_ddi_resume(dev_info_t *dip)
{
        pcn_t   *pcnp;

        if ((pcnp = ddi_get_soft_state(pcn_ssp, ddi_get_instance(dip))) == NULL)
                return (DDI_FAILURE);

        mutex_enter(&pcnp->pcn_intrlock);
        mutex_enter(&pcnp->pcn_xmtlock);

        pcnp->pcn_flags &= ~PCN_SUSPENDED;

        if (!pcn_initialize(pcnp, B_FALSE)) {
                pcn_error(pcnp->pcn_dip, "unable to resume chip");
                pcnp->pcn_flags |= PCN_SUSPENDED;
                mutex_exit(&pcnp->pcn_intrlock);
                mutex_exit(&pcnp->pcn_xmtlock);
                return (DDI_SUCCESS);
        }

        if (IS_RUNNING(pcnp))
                pcn_startall(pcnp);

        mutex_exit(&pcnp->pcn_xmtlock);
        mutex_exit(&pcnp->pcn_intrlock);

        mii_resume(pcnp->pcn_mii);

        return (DDI_SUCCESS);
}

int
pcn_quiesce(dev_info_t *dip)
{
        pcn_t   *pcnp;

        if ((pcnp = ddi_get_soft_state(pcn_ssp, ddi_get_instance(dip))) == NULL)
                return (DDI_FAILURE);

        /* don't want to take the chance of blocking */
        CSR_WRITE_4(pcnp, PCN_IO32_RAP, PCN_CSR_EXTCTL1);
        CSR_WRITE_4(pcnp, PCN_IO32_RDP, CSR_READ_4(pcnp, PCN_IO32_RDP) &
            ~(PCN_EXTCTL1_SINTEN));

        CSR_WRITE_4(pcnp, PCN_IO32_RAP, PCN_CSR_CSR);
        CSR_WRITE_4(pcnp, PCN_IO32_RDP,
            (CSR_READ_4(pcnp, PCN_IO32_RDP) & ~(PCN_CSR_INTEN)) |
            PCN_CSR_STOP);

        return (DDI_SUCCESS);
}

static void
pcn_teardown(pcn_t *pcnp)
{
        ASSERT(!(pcnp->pcn_flags & PCN_RUNNING));

        if (pcnp->pcn_mii != NULL) {
                mii_free(pcnp->pcn_mii);
                pcnp->pcn_mii = NULL;
        }

        if (pcnp->pcn_flags & PCN_INTR_ENABLED)
                ddi_remove_intr(pcnp->pcn_dip, 0, pcnp->pcn_icookie);

        /* These will exit gracefully if not yet allocated */
        pcn_freerxring(pcnp);
        pcn_freetxring(pcnp);

        if (pcnp->pcn_regshandle != NULL)
                ddi_regs_map_free(&pcnp->pcn_regshandle);


        mutex_destroy(&pcnp->pcn_xmtlock);
        mutex_destroy(&pcnp->pcn_intrlock);
        mutex_destroy(&pcnp->pcn_reglock);

        ddi_soft_state_free(pcn_ssp, ddi_get_instance(pcnp->pcn_dip));
}

/*
 * Drains any FIFOs in the card, then pauses it
 */
static void
pcn_suspend(pcn_t *pcnp)
{
        uint32_t val;
        int i;

        PCN_CSR_SETBIT(pcnp, PCN_CSR_EXTCTL1, PCN_EXTCTL1_SPND);
        for (i = 0; i < 5000; i++) {
                if ((val = pcn_csr_read(pcnp, PCN_CSR_EXTCTL1)) &
                    PCN_EXTCTL1_SPND)
                        return;
                drv_usecwait(1000);
        }

        pcn_error(pcnp->pcn_dip, "Unable to suspend, EXTCTL1 was 0x%b", val,
            PCN_EXTCTL1_STR);
}

static void
pcn_resume(pcn_t *pcnp)
{
        PCN_CSR_CLRBIT(pcnp, PCN_CSR_EXTCTL1, PCN_EXTCTL1_SPND);
}

static int
pcn_m_multicast(void *arg, boolean_t add, const uint8_t *macaddr)
{
        pcn_t           *pcnp = (pcn_t *)arg;
        int             index;
        uint32_t        crc;
        uint16_t        bit;
        uint16_t        newval, oldval;

        /*
         * PCNet uses the upper 6 bits of the CRC of the macaddr
         * to index into a 64bit mask
         */
        CRC32(crc, macaddr, ETHERADDRL, -1U, crc32_table);
        crc >>= 26;
        index = crc / 16;
        bit = (1U << (crc % 16));

        mutex_enter(&pcnp->pcn_intrlock);
        mutex_enter(&pcnp->pcn_xmtlock);
        newval = oldval = pcnp->pcn_mctab[index];

        if (add) {
                pcnp->pcn_mccount[crc]++;
                if (pcnp->pcn_mccount[crc] == 1)
                        newval |= bit;
        } else {
                pcnp->pcn_mccount[crc]--;
                if (pcnp->pcn_mccount[crc] == 0)
                        newval &= ~bit;
        }
        if (newval != oldval) {
                pcnp->pcn_mctab[index] = newval;
                pcn_suspend(pcnp);
                pcn_csr_write(pcnp, PCN_CSR_MAR0 + index, newval);
                pcn_resume(pcnp);
        }

        mutex_exit(&pcnp->pcn_xmtlock);
        mutex_exit(&pcnp->pcn_intrlock);

        return (0);
}

static int
pcn_m_promisc(void *arg, boolean_t on)
{
        pcn_t           *pcnp = (pcn_t *)arg;

        mutex_enter(&pcnp->pcn_intrlock);
        mutex_enter(&pcnp->pcn_xmtlock);

        pcnp->pcn_promisc = on;

        if (IS_RUNNING(pcnp))
                pcn_suspend(pcnp);

        /* set promiscuous mode */
        if (pcnp->pcn_promisc)
                PCN_CSR_SETBIT(pcnp, PCN_CSR_MODE, PCN_MODE_PROMISC);
        else
                PCN_CSR_CLRBIT(pcnp, PCN_CSR_MODE, PCN_MODE_PROMISC);

        if (IS_RUNNING(pcnp))
                pcn_resume(pcnp);

        mutex_exit(&pcnp->pcn_xmtlock);
        mutex_exit(&pcnp->pcn_intrlock);

        return (0);
}

static int
pcn_m_unicast(void *arg, const uint8_t *macaddr)
{
        pcn_t   *pcnp = (pcn_t *)arg;
        int i;
        uint16_t addr[3];

        bcopy(macaddr, addr, sizeof (addr));

        mutex_enter(&pcnp->pcn_intrlock);
        mutex_enter(&pcnp->pcn_xmtlock);

        if (IS_RUNNING(pcnp))
                pcn_suspend(pcnp);

        for (i = 0; i < 3; i++)
                pcn_csr_write(pcnp, PCN_CSR_PAR0 + i, addr[i]);

        bcopy(macaddr, pcnp->pcn_addr, ETHERADDRL);

        if (IS_RUNNING(pcnp))
                pcn_resume(pcnp);

        mutex_exit(&pcnp->pcn_xmtlock);
        mutex_exit(&pcnp->pcn_intrlock);

        return (0);
}

static mblk_t *
pcn_m_tx(void *arg, mblk_t *mp)
{
        pcn_t   *pcnp = (pcn_t *)arg;
        mblk_t  *nmp;

        mutex_enter(&pcnp->pcn_xmtlock);

        if (pcnp->pcn_flags & PCN_SUSPENDED) {
                while ((nmp = mp) != NULL) {
                        pcnp->pcn_carrier_errors++;
                        mp = mp->b_next;
                        freemsg(nmp);
                }
                mutex_exit(&pcnp->pcn_xmtlock);
                return (NULL);
        }

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

                if (!pcn_send(pcnp, mp)) {
                        mp->b_next = nmp;
                        break;
                }
                mp = nmp;
        }
        mutex_exit(&pcnp->pcn_xmtlock);

        return (mp);
}

static boolean_t
pcn_send(pcn_t *pcnp, mblk_t *mp)
{
        size_t          len;
        pcn_buf_t       *txb;
        pcn_tx_desc_t   *tmd;
        int             txsend;

        ASSERT(mutex_owned(&pcnp->pcn_xmtlock));
        ASSERT(mp != NULL);

        len = msgsize(mp);
        if (len > ETHERVLANMTU) {
                pcnp->pcn_macxmt_errors++;
                freemsg(mp);
                return (B_TRUE);
        }

        if (pcnp->pcn_txavail < PCN_TXRECLAIM)
                pcn_reclaim(pcnp);

        if (pcnp->pcn_txavail == 0) {
                pcnp->pcn_wantw = B_TRUE;

                /* enable tx interrupt */
                PCN_CSR_SETBIT(pcnp, PCN_CSR_EXTCTL1, PCN_EXTCTL1_LTINTEN);
                return (B_FALSE);
        }

        txsend = pcnp->pcn_txsend;

        /*
         * We copy the packet to a single buffer.  NetBSD sources suggest
         * that if multiple segements are ever used, VMware has a bug that will
         * only allow 8 segments to be used, while the physical chips allow 16
         */
        txb = pcnp->pcn_txbufs[txsend];
        mcopymsg(mp, txb->pb_buf);      /* frees mp! */

        pcnp->pcn_opackets++;
        pcnp->pcn_obytes += len;
        if (txb->pb_buf[0] & 0x1) {
                if (bcmp(txb->pb_buf, pcn_broadcast, ETHERADDRL) != 0)
                        pcnp->pcn_multixmt++;
                else
                        pcnp->pcn_brdcstxmt++;
        }

        tmd = &pcnp->pcn_txdescp[txsend];

        SYNCBUF(txb, len, DDI_DMA_SYNC_FORDEV);
        tmd->pcn_txstat = 0;
        tmd->pcn_tbaddr = txb->pb_paddr;

        /* PCNet wants the 2's complement of the length of the buffer */
        tmd->pcn_txctl = (~(len) + 1) & PCN_TXCTL_BUFSZ;
        tmd->pcn_txctl |= PCN_TXCTL_MBO;
        tmd->pcn_txctl |= PCN_TXCTL_STP | PCN_TXCTL_ENP | PCN_TXCTL_ADD_FCS |
            PCN_TXCTL_OWN | PCN_TXCTL_MORE_LTINT;

        SYNCTXDESC(pcnp, txsend, DDI_DMA_SYNC_FORDEV);

        pcnp->pcn_txavail--;
        pcnp->pcn_txsend = (txsend + 1) % PCN_TXRING;
        pcnp->pcn_txstall_time = gethrtime() + (5 * 1000000000ULL);

        pcn_csr_write(pcnp, PCN_CSR_CSR, PCN_CSR_TX|PCN_CSR_INTEN);

        return (B_TRUE);
}

static void
pcn_reclaim(pcn_t *pcnp)
{
        pcn_tx_desc_t   *tmdp;

        while (pcnp->pcn_txavail != PCN_TXRING) {
                int index = pcnp->pcn_txreclaim;

                tmdp = &pcnp->pcn_txdescp[index];

                /* sync before reading */
                SYNCTXDESC(pcnp, index, DDI_DMA_SYNC_FORKERNEL);

                /* check if chip is still working on it */
                if (tmdp->pcn_txctl & PCN_TXCTL_OWN)
                        break;

                pcnp->pcn_txavail++;
                pcnp->pcn_txreclaim = (index + 1) % PCN_TXRING;
        }

        if (pcnp->pcn_txavail >= PCN_TXRESCHED) {
                if (pcnp->pcn_wantw) {
                        pcnp->pcn_wantw = B_FALSE;

                        /* Disable TX interrupt */
                        PCN_CSR_CLRBIT(pcnp, PCN_CSR_EXTCTL1,
                            PCN_EXTCTL1_LTINTEN);

                        mac_tx_update(pcnp->pcn_mh);
                }
        }
}

static unsigned
pcn_intr(caddr_t arg1)
{
        pcn_t           *pcnp = (void *)arg1;
        mblk_t          *mp = NULL;
        uint32_t        status, status2;
        boolean_t       do_reset = B_FALSE;

        mutex_enter(&pcnp->pcn_intrlock);

        if (IS_SUSPENDED(pcnp)) {
                mutex_exit(&pcnp->pcn_intrlock);
                return (DDI_INTR_UNCLAIMED);
        }

        while ((status = pcn_csr_read(pcnp, PCN_CSR_CSR)) & PCN_CSR_INTR) {
                pcn_csr_write(pcnp, PCN_CSR_CSR, status);

                status2 = pcn_csr_read(pcnp, PCN_CSR_EXTCTL2);

                if (status & PCN_CSR_TINT) {
                        mutex_enter(&pcnp->pcn_xmtlock);
                        pcn_reclaim(pcnp);
                        mutex_exit(&pcnp->pcn_xmtlock);
                }

                if (status & PCN_CSR_RINT)
                        mp = pcn_receive(pcnp);

                if (status & PCN_CSR_ERR) {
                        do_reset = B_TRUE;
                        break;
                }

                /* timer interrupt */
                if (status2 & PCN_EXTCTL2_STINT) {
                        /* ack it */
                        PCN_CSR_SETBIT(pcnp, PCN_CSR_EXTCTL2,
                            PCN_EXTCTL2_STINT);

                        if (pcn_watchdog(pcnp) != DDI_SUCCESS) {
                                do_reset = B_TRUE;
                                break;
                        }
                }
        }

        if (do_reset) {
                mutex_enter(&pcnp->pcn_xmtlock);
                pcn_resetall(pcnp);
                mutex_exit(&pcnp->pcn_xmtlock);
                mutex_exit(&pcnp->pcn_intrlock);

                mii_reset(pcnp->pcn_mii);
        } else {
                mutex_exit(&pcnp->pcn_intrlock);
        }

        if (mp)
                mac_rx(pcnp->pcn_mh, NULL, mp);

        return (DDI_INTR_CLAIMED);
}

static mblk_t *
pcn_receive(pcn_t *pcnp)
{
        uint32_t        len;
        pcn_buf_t       *rxb;
        pcn_rx_desc_t   *rmd;
        mblk_t          *mpchain, **mpp, *mp;
        int             head, cnt;

        mpchain = NULL;
        mpp = &mpchain;
        head = pcnp->pcn_rxhead;

        for (cnt = 0; cnt < PCN_RXRING; cnt++) {
                rmd = &pcnp->pcn_rxdescp[head];
                rxb = pcnp->pcn_rxbufs[head];

                SYNCRXDESC(pcnp, head, DDI_DMA_SYNC_FORKERNEL);
                if (rmd->pcn_rxstat & PCN_RXSTAT_OWN)
                        break;

                len = rmd->pcn_rxlen - ETHERFCSL;

                if (rmd->pcn_rxstat & PCN_RXSTAT_ERR) {
                        pcnp->pcn_errrcv++;

                        if (rmd->pcn_rxstat & PCN_RXSTAT_FRAM)
                                pcnp->pcn_align_errors++;
                        if (rmd->pcn_rxstat & PCN_RXSTAT_OFLOW)
                                pcnp->pcn_overflow++;
                        if (rmd->pcn_rxstat & PCN_RXSTAT_CRC)
                                pcnp->pcn_fcs_errors++;
                } else if (len > ETHERVLANMTU) {
                        pcnp->pcn_errrcv++;
                        pcnp->pcn_toolong_errors++;
                } else {
                        mp = allocb(len + PCN_HEADROOM, 0);
                        if (mp == NULL) {
                                pcnp->pcn_errrcv++;
                                pcnp->pcn_norcvbuf++;
                                goto skip;
                        }

                        SYNCBUF(rxb, len, DDI_DMA_SYNC_FORKERNEL);
                        mp->b_rptr += PCN_HEADROOM;
                        mp->b_wptr = mp->b_rptr + len;
                        bcopy((char *)rxb->pb_buf, mp->b_rptr, len);

                        pcnp->pcn_ipackets++;
                        pcnp->pcn_rbytes++;

                        if (rmd->pcn_rxstat & PCN_RXSTAT_LAFM|PCN_RXSTAT_BAM) {
                                if (rmd->pcn_rxstat & PCN_RXSTAT_BAM)
                                        pcnp->pcn_brdcstrcv++;
                                else
                                        pcnp->pcn_multircv++;
                        }
                        *mpp = mp;
                        mpp = &mp->b_next;
                }

skip:
                rmd->pcn_rxstat = PCN_RXSTAT_OWN;
                SYNCRXDESC(pcnp, head, DDI_DMA_SYNC_FORDEV);

                head = (head + 1) % PCN_RXRING;
        }

        pcnp->pcn_rxhead = head;
        return (mpchain);
}

static void
pcn_m_ioctl(void *arg, queue_t *wq, mblk_t *mp)
{
        pcn_t *pcnp = (pcn_t *)arg;

        if (mii_m_loop_ioctl(pcnp->pcn_mii, wq, mp))
                return;

        miocnak(wq, mp, 0, EINVAL);
}

static int
pcn_m_start(void *arg)
{
        pcn_t   *pcnp = (pcn_t *)arg;

        mutex_enter(&pcnp->pcn_intrlock);
        mutex_enter(&pcnp->pcn_xmtlock);

        pcn_startall(pcnp);
        pcnp->pcn_flags |= PCN_RUNNING;

        mutex_exit(&pcnp->pcn_xmtlock);
        mutex_exit(&pcnp->pcn_intrlock);

        mii_start(pcnp->pcn_mii);

        return (0);
}

static void
pcn_m_stop(void *arg)
{
        pcn_t   *pcnp = (pcn_t *)arg;

        mii_stop(pcnp->pcn_mii);

        mutex_enter(&pcnp->pcn_intrlock);
        mutex_enter(&pcnp->pcn_xmtlock);

        pcn_stopall(pcnp);
        pcnp->pcn_flags &= ~PCN_RUNNING;

        mutex_exit(&pcnp->pcn_xmtlock);
        mutex_exit(&pcnp->pcn_intrlock);
}

static int
pcn_initialize(pcn_t *pcnp, boolean_t getfact)
{
        int i;
        uint16_t addr[3];

        bcopy(pcnp->pcn_addr, addr, sizeof (addr));

        /*
         * Issue a reset by reading from the RESET register.
         * Note that we don't know if the chip is operating in
         * 16-bit or 32-bit mode at this point, so we attempt
         * to reset the chip both ways.  If one fails, the other
         * will succeed.
         */
        (void) CSR_READ_2(pcnp, PCN_IO16_RESET);
        (void) CSR_READ_4(pcnp, PCN_IO32_RESET);

        drv_usecwait(1000);

        /* Select 32-bit (DWIO) mode */
        CSR_WRITE_4(pcnp, PCN_IO32_RDP, 0);

        /* The timer is not affected by a reset, so explicitly disable */
        pcn_stop_timer(pcnp);

        /* Enable fast suspend */
        pcn_csr_write(pcnp, PCN_CSR_EXTCTL2, PCN_EXTCTL2_FASTSPNDE);

        /* Select Style 3 descriptors */
        pcn_bcr_write(pcnp, PCN_BCR_SSTYLE, PCN_SWSTYLE_PCNETPCI);

        /* Set MAC address */
        if (getfact)
                pcn_getfactaddr(pcnp);

        pcn_csr_write(pcnp, PCN_CSR_PAR0, addr[0]);
        pcn_csr_write(pcnp, PCN_CSR_PAR1, addr[1]);
        pcn_csr_write(pcnp, PCN_CSR_PAR2, addr[2]);

        /* Clear PCN_MISC_ASEL so we can set the port via PCN_CSR_MODE. */
        PCN_BCR_CLRBIT(pcnp, PCN_BCR_MISCCFG, PCN_MISC_ASEL);

        /*
         * XXX: need to find a way to determine when 10bt media is
         * selected for non Am79C978, and set to PCN_PORT_10BASET
         * instead of PCN_PORT_MII
         */
        pcn_csr_write(pcnp, PCN_CSR_MODE, PCN_PORT_MII);

        /* Reenable auto negotiation for external phy */
        PCN_BCR_SETBIT(pcnp, PCN_BCR_MIICTL, PCN_MIICTL_XPHYANE);

        if (pcnp->pcn_promisc)
                PCN_CSR_SETBIT(pcnp, PCN_CSR_MODE, PCN_MODE_PROMISC);

        /* Initalize mcast addr filter */
        for (i = 0; i < 4; i++)
                pcn_csr_write(pcnp, PCN_CSR_MAR0 + i, pcnp->pcn_mctab[i]);

        pcn_resetrings(pcnp);

        /* We're not using the initialization block. */
        pcn_csr_write(pcnp, PCN_CSR_IAB1, 0);

        /*
         * Enable burst read and write.  Also set the no underflow
         * bit.  This will avoid transmit underruns in ceratin
         * conditions while still providing decent performance.
         */
        PCN_BCR_SETBIT(pcnp, PCN_BCR_BUSCTL, PCN_BUSCTL_NOUFLOW |
            PCN_BUSCTL_BREAD | PCN_BUSCTL_BWRITE);

        /* Enable graceful recovery from underflow. */
        PCN_CSR_SETBIT(pcnp, PCN_CSR_IMR, PCN_IMR_DXSUFLO);

        /* Enable auto-padding of short TX frames. */
        PCN_CSR_SETBIT(pcnp, PCN_CSR_TFEAT, PCN_TFEAT_PAD_TX);

        if (pcnp->pcn_type == Am79C978)
                pcn_bcr_write(pcnp, PCN_BCR_PHYSEL,
                    PCN_PHYSEL_PCNET|PCN_PHY_HOMEPNA);

        return (DDI_SUCCESS);
}

static void
pcn_resetall(pcn_t *pcnp)
{
        pcn_stopall(pcnp);
        pcn_startall(pcnp);
}

static void
pcn_startall(pcn_t *pcnp)
{
        ASSERT(mutex_owned(&pcnp->pcn_intrlock));
        ASSERT(mutex_owned(&pcnp->pcn_xmtlock));

        (void) pcn_initialize(pcnp, B_FALSE);

        /* Start chip and enable interrupts */
        PCN_CSR_SETBIT(pcnp, PCN_CSR_CSR, PCN_CSR_START|PCN_CSR_INTEN);

        pcn_start_timer(pcnp);

        if (IS_RUNNING(pcnp))
                mac_tx_update(pcnp->pcn_mh);
}

static void
pcn_stopall(pcn_t *pcnp)
{
        ASSERT(mutex_owned(&pcnp->pcn_intrlock));
        ASSERT(mutex_owned(&pcnp->pcn_xmtlock));

        pcn_stop_timer(pcnp);
        PCN_CSR_SETBIT(pcnp, PCN_CSR_CSR, PCN_CSR_STOP);
}

/*
 * The soft timer is not affected by a soft reset (according to the datasheet)
 * so it must always be explicitly enabled and disabled
 */
static void
pcn_start_timer(pcn_t *pcnp)
{
        PCN_CSR_SETBIT(pcnp, PCN_CSR_EXTCTL1, PCN_EXTCTL1_SINTEN);

        /*
         * The frequency this fires varies based on the particular
         * model, this value is largely arbitrary. It just needs to
         * fire often enough to detect a stall
         */
        pcn_bcr_write(pcnp, PCN_BCR_TIMER, 0xa000);
}


static void
pcn_stop_timer(pcn_t *pcnp)
{
        PCN_CSR_CLRBIT(pcnp, PCN_CSR_EXTCTL1, PCN_EXTCTL1_SINTEN);
}

static int
pcn_m_stat(void *arg, uint_t stat, uint64_t *val)
{
        pcn_t   *pcnp = (pcn_t *)arg;

        if (mii_m_getstat(pcnp->pcn_mii, stat, val) == 0)
                return (0);

        switch (stat) {
        case MAC_STAT_MULTIRCV:
                *val = pcnp->pcn_multircv;
                break;

        case MAC_STAT_BRDCSTRCV:
                *val = pcnp->pcn_brdcstrcv;
                break;

        case MAC_STAT_MULTIXMT:
                *val = pcnp->pcn_multixmt;
                break;

        case MAC_STAT_BRDCSTXMT:
                *val = pcnp->pcn_brdcstxmt;
                break;

        case MAC_STAT_IPACKETS:
                *val = pcnp->pcn_ipackets;
                break;

        case MAC_STAT_RBYTES:
                *val = pcnp->pcn_rbytes;
                break;

        case MAC_STAT_OPACKETS:
                *val = pcnp->pcn_opackets;
                break;

        case MAC_STAT_OBYTES:
                *val = pcnp->pcn_obytes;
                break;

        case MAC_STAT_NORCVBUF:
                *val = pcnp->pcn_norcvbuf;
                break;

        case MAC_STAT_NOXMTBUF:
                *val = 0;
                break;

        case MAC_STAT_COLLISIONS:
                *val = pcnp->pcn_collisions;
                break;

        case MAC_STAT_IERRORS:
                *val = pcnp->pcn_errrcv;
                break;

        case MAC_STAT_OERRORS:
                *val = pcnp->pcn_errxmt;
                break;

        case ETHER_STAT_ALIGN_ERRORS:
                *val = pcnp->pcn_align_errors;
                break;

        case ETHER_STAT_FCS_ERRORS:
                *val = pcnp->pcn_fcs_errors;
                break;

        case ETHER_STAT_SQE_ERRORS:
                *val = pcnp->pcn_sqe_errors;
                break;

        case ETHER_STAT_DEFER_XMTS:
                *val = pcnp->pcn_defer_xmts;
                break;

        case ETHER_STAT_FIRST_COLLISIONS:
                *val = pcnp->pcn_first_collisions;
                break;

        case ETHER_STAT_MULTI_COLLISIONS:
                *val = pcnp->pcn_multi_collisions;
                break;

        case ETHER_STAT_TX_LATE_COLLISIONS:
                *val = pcnp->pcn_tx_late_collisions;
                break;

        case ETHER_STAT_EX_COLLISIONS:
                *val = pcnp->pcn_ex_collisions;
                break;

        case ETHER_STAT_MACXMT_ERRORS:
                *val = pcnp->pcn_macxmt_errors;
                break;

        case ETHER_STAT_CARRIER_ERRORS:
                *val = pcnp->pcn_carrier_errors;
                break;

        case ETHER_STAT_TOOLONG_ERRORS:
                *val = pcnp->pcn_toolong_errors;
                break;

        case ETHER_STAT_MACRCV_ERRORS:
                *val = pcnp->pcn_macrcv_errors;
                break;

        case MAC_STAT_OVERFLOWS:
                *val = pcnp->pcn_overflow;
                break;

        case MAC_STAT_UNDERFLOWS:
                *val = pcnp->pcn_underflow;
                break;

        case ETHER_STAT_TOOSHORT_ERRORS:
                *val = pcnp->pcn_runt;
                break;

        case ETHER_STAT_JABBER_ERRORS:
                *val = pcnp->pcn_jabber;
                break;

        default:
                return (ENOTSUP);
        }
        return (0);
}

static int
pcn_m_getprop(void *arg, const char *name, mac_prop_id_t num, uint_t sz,
    void *val)
{
        pcn_t   *pcnp = (pcn_t *)arg;

        return (mii_m_getprop(pcnp->pcn_mii, name, num, sz, val));
}

static int
pcn_m_setprop(void *arg, const char *name, mac_prop_id_t num, uint_t sz,
    const void *val)
{
        pcn_t   *pcnp = (pcn_t *)arg;

        return (mii_m_setprop(pcnp->pcn_mii, name, num, sz, val));
}

static void
pcn_m_propinfo(void *arg, const char *name, mac_prop_id_t num,
    mac_prop_info_handle_t prh)
{
        pcn_t   *pcnp = arg;

        mii_m_propinfo(pcnp->pcn_mii, name, num, prh);
}

static int
pcn_watchdog(pcn_t *pcnp)
{
        if ((pcnp->pcn_txstall_time != 0) &&
            (gethrtime() > pcnp->pcn_txstall_time) &&
            (pcnp->pcn_txavail != PCN_TXRING)) {
                pcnp->pcn_txstall_time = 0;
                pcn_error(pcnp->pcn_dip, "TX stall detected!");
                return (DDI_FAILURE);
        } else {
                return (DDI_SUCCESS);
        }
}

static uint16_t
pcn_mii_read(void *arg, uint8_t phy, uint8_t reg)
{
        pcn_t           *pcnp = (pcn_t *)arg;
        uint16_t        val;

        /*
         * At least Am79C971 with DP83840A wedge when isolating the
         * external PHY so we can't allow multiple external PHYs.
         * There are cards that use Am79C971 with both the internal
         * and an external PHY though.
         * For internal PHYs it doesn't really matter whether we can
         * isolate the remaining internal and the external ones in
         * the PHY drivers as the internal PHYs have to be enabled
         * individually in PCN_BCR_PHYSEL, PCN_CSR_MODE, etc.
         * With Am79C97{3,5,8} we don't support switching beetween
         * the internal and external PHYs, yet, so we can't allow
         * multiple PHYs with these either.
         * Am79C97{2,6} actually only support external PHYs (not
         * connectable internal ones respond at the usual addresses,
         * which don't hurt if we let them show up on the bus) and
         * isolating them works.
         */
        if (((pcnp->pcn_type == Am79C971 && phy != PCN_PHYAD_10BT) ||
            pcnp->pcn_type == Am79C973 || pcnp->pcn_type == Am79C975 ||
            pcnp->pcn_type == Am79C978) && pcnp->pcn_extphyaddr != -1 &&
            phy != pcnp->pcn_extphyaddr) {
                return (0);
        }

        val = ((uint16_t)phy << 5) | reg;
        pcn_bcr_write(pcnp, PCN_BCR_MIIADDR, phy << 5 | reg);
        val = pcn_bcr_read(pcnp, PCN_BCR_MIIDATA) & 0xFFFF;
        if (val == 0xFFFF) {
                return (0);
        }

        if (((pcnp->pcn_type == Am79C971 && phy != PCN_PHYAD_10BT) ||
            pcnp->pcn_type == Am79C973 || pcnp->pcn_type == Am79C975 ||
            pcnp->pcn_type == Am79C978) && pcnp->pcn_extphyaddr == -1)
                pcnp->pcn_extphyaddr = phy;

        return (val);
}

static void
pcn_mii_write(void *arg, uint8_t phy, uint8_t reg, uint16_t val)
{
        pcn_t           *pcnp = (pcn_t *)arg;

        pcn_bcr_write(pcnp, PCN_BCR_MIIADDR, reg | (phy << 5));
        pcn_bcr_write(pcnp, PCN_BCR_MIIDATA, val);
}

static void
pcn_mii_notify(void *arg, link_state_t link)
{
        pcn_t           *pcnp = (pcn_t *)arg;

        mac_link_update(pcnp->pcn_mh, link);
}

static const pcn_type_t *
pcn_match(uint16_t vid, uint16_t did)
{
        const pcn_type_t        *t;

        t = pcn_devs;
        while (t->pcn_name != NULL) {
                if ((vid == t->pcn_vid) && (did == t->pcn_did))
                        return (t);
                t++;
        }
        return (NULL);
}

static void
pcn_getfactaddr(pcn_t *pcnp)
{
        uint32_t addr[2];

        addr[0] = CSR_READ_4(pcnp, PCN_IO32_APROM00);
        addr[1] = CSR_READ_4(pcnp, PCN_IO32_APROM01);

        bcopy(&addr[0], &pcnp->pcn_addr[0], sizeof (pcnp->pcn_addr));
}

static uint32_t
pcn_csr_read(pcn_t *pcnp, uint32_t reg)
{
        uint32_t val;

        mutex_enter(&pcnp->pcn_reglock);
        CSR_WRITE_4(pcnp, PCN_IO32_RAP, reg);
        val = CSR_READ_4(pcnp, PCN_IO32_RDP);
        mutex_exit(&pcnp->pcn_reglock);
        return (val);
}

static uint16_t
pcn_csr_read16(pcn_t *pcnp, uint32_t reg)
{
        uint16_t val;

        mutex_enter(&pcnp->pcn_reglock);
        CSR_WRITE_2(pcnp, PCN_IO16_RAP, reg);
        val = CSR_READ_2(pcnp, PCN_IO16_RDP);
        mutex_exit(&pcnp->pcn_reglock);
        return (val);
}

static void
pcn_csr_write(pcn_t *pcnp, uint32_t reg, uint32_t val)
{
        mutex_enter(&pcnp->pcn_reglock);
        CSR_WRITE_4(pcnp, PCN_IO32_RAP, reg);
        CSR_WRITE_4(pcnp, PCN_IO32_RDP, val);
        mutex_exit(&pcnp->pcn_reglock);
}

static uint32_t
pcn_bcr_read(pcn_t *pcnp, uint32_t reg)
{
        uint32_t val;

        mutex_enter(&pcnp->pcn_reglock);
        CSR_WRITE_4(pcnp, PCN_IO32_RAP, reg);
        val = CSR_READ_4(pcnp, PCN_IO32_BDP);
        mutex_exit(&pcnp->pcn_reglock);
        return (val);
}

static uint16_t
pcn_bcr_read16(pcn_t *pcnp, uint32_t reg)
{
        uint16_t val;

        mutex_enter(&pcnp->pcn_reglock);
        CSR_WRITE_2(pcnp, PCN_IO16_RAP, reg);
        val = CSR_READ_2(pcnp, PCN_IO16_BDP);
        mutex_exit(&pcnp->pcn_reglock);
        return (val);
}

static void
pcn_bcr_write(pcn_t *pcnp, uint32_t reg, uint32_t val)
{
        mutex_enter(&pcnp->pcn_reglock);
        CSR_WRITE_4(pcnp, PCN_IO32_RAP, reg);
        CSR_WRITE_4(pcnp, PCN_IO32_BDP, val);
        mutex_exit(&pcnp->pcn_reglock);
}

static void
pcn_resetrings(pcn_t *pcnp)
{
        int i;
        uint16_t bufsz = ((~(PCN_BUFSZ) + 1) & PCN_RXLEN_BUFSZ) | PCN_RXLEN_MBO;

        pcnp->pcn_rxhead = 0;
        pcnp->pcn_txreclaim = 0;
        pcnp->pcn_txsend = 0;
        pcnp->pcn_txavail = PCN_TXRING;

        /* reset rx descriptor values */
        for (i = 0; i < PCN_RXRING; i++) {
                pcn_rx_desc_t   *rmd = &pcnp->pcn_rxdescp[i];
                pcn_buf_t       *rxb = pcnp->pcn_rxbufs[i];

                rmd->pcn_rxlen = rmd->pcn_rsvd0 = 0;
                rmd->pcn_rbaddr = rxb->pb_paddr;
                rmd->pcn_bufsz = bufsz;
                rmd->pcn_rxstat = PCN_RXSTAT_OWN;
        }
        (void) ddi_dma_sync(pcnp->pcn_rxdesc_dmah, 0,
            PCN_RXRING * sizeof (pcn_rx_desc_t), DDI_DMA_SYNC_FORDEV);

        /* reset tx descriptor values */
        for (i = 0; i < PCN_TXRING; i++) {
                pcn_tx_desc_t   *txd = &pcnp->pcn_txdescp[i];
                pcn_buf_t       *txb = pcnp->pcn_txbufs[i];

                txd->pcn_txstat = txd->pcn_txctl = txd->pcn_uspace = 0;
                txd->pcn_tbaddr = txb->pb_paddr;
        }
        (void) ddi_dma_sync(pcnp->pcn_txdesc_dmah, 0,
            PCN_TXRING * sizeof (pcn_tx_desc_t), DDI_DMA_SYNC_FORDEV);

        /* set addresses of decriptors */
        pcn_csr_write(pcnp, PCN_CSR_RXADDR0, pcnp->pcn_rxdesc_paddr & 0xFFFF);
        pcn_csr_write(pcnp, PCN_CSR_RXADDR1,
            (pcnp->pcn_rxdesc_paddr >> 16) & 0xFFFF);

        pcn_csr_write(pcnp, PCN_CSR_TXADDR0, pcnp->pcn_txdesc_paddr & 0xFFFF);
        pcn_csr_write(pcnp, PCN_CSR_TXADDR1,
            (pcnp->pcn_txdesc_paddr >> 16) & 0xFFFF);

        /* set the ring sizes */
        pcn_csr_write(pcnp, PCN_CSR_RXRINGLEN, (~PCN_RXRING) + 1);
        pcn_csr_write(pcnp, PCN_CSR_TXRINGLEN, (~PCN_TXRING) + 1);
}

static void
pcn_destroybuf(pcn_buf_t *buf)
{
        if (buf == NULL)
                return;

        if (buf->pb_paddr)
                (void) ddi_dma_unbind_handle(buf->pb_dmah);
        if (buf->pb_acch)
                ddi_dma_mem_free(&buf->pb_acch);
        if (buf->pb_dmah)
                ddi_dma_free_handle(&buf->pb_dmah);
        kmem_free(buf, sizeof (*buf));
}

static pcn_buf_t *
pcn_allocbuf(pcn_t *pcnp)
{
        pcn_buf_t               *buf;
        size_t                  len;
        unsigned                ccnt;
        ddi_dma_cookie_t        dmac;

        buf = kmem_zalloc(sizeof (*buf), KM_SLEEP);

        if (ddi_dma_alloc_handle(pcnp->pcn_dip, &pcn_dma_attr, DDI_DMA_SLEEP,
            NULL, &buf->pb_dmah) != DDI_SUCCESS) {
                kmem_free(buf, sizeof (*buf));
                return (NULL);
        }

        if (ddi_dma_mem_alloc(buf->pb_dmah, PCN_BUFSZ, &pcn_bufattr,
            DDI_DMA_STREAMING, DDI_DMA_SLEEP, NULL, &buf->pb_buf, &len,
            &buf->pb_acch) != DDI_SUCCESS) {
                pcn_destroybuf(buf);
                return (NULL);
        }

        if (ddi_dma_addr_bind_handle(buf->pb_dmah, NULL, buf->pb_buf, len,
            DDI_DMA_READ | DDI_DMA_STREAMING, DDI_DMA_SLEEP, NULL, &dmac,
            &ccnt) != DDI_DMA_MAPPED) {
                pcn_destroybuf(buf);
                return (NULL);
        }
        buf->pb_paddr = dmac.dmac_address;

        return (buf);
}

static int
pcn_alloctxring(pcn_t *pcnp)
{
        int                     rval;
        int                     i;
        size_t                  size;
        size_t                  len;
        ddi_dma_cookie_t        dmac;
        unsigned                ncookies;
        caddr_t                 kaddr;

        size = PCN_TXRING * sizeof (pcn_tx_desc_t);

        rval = ddi_dma_alloc_handle(pcnp->pcn_dip, &pcn_dma_attr, DDI_DMA_SLEEP,
            NULL, &pcnp->pcn_txdesc_dmah);
        if (rval != DDI_SUCCESS) {
                pcn_error(pcnp->pcn_dip, "unable to allocate DMA handle for tx "
                    "descriptors");
                return (DDI_FAILURE);
        }

        rval = ddi_dma_mem_alloc(pcnp->pcn_txdesc_dmah, size, &pcn_devattr,
            DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &kaddr, &len,
            &pcnp->pcn_txdesc_acch);
        if (rval != DDI_SUCCESS) {
                pcn_error(pcnp->pcn_dip, "unable to allocate DMA memory for tx "
                    "descriptors");
                return (DDI_FAILURE);
        }

        rval = ddi_dma_addr_bind_handle(pcnp->pcn_txdesc_dmah, NULL, kaddr,
            size, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &dmac,
            &ncookies);
        if (rval != DDI_DMA_MAPPED) {
                pcn_error(pcnp->pcn_dip, "unable to bind DMA for tx "
                    "descriptors");
                return (DDI_FAILURE);
        }

        ASSERT(ncookies == 1);

        pcnp->pcn_txdesc_paddr = dmac.dmac_address;
        pcnp->pcn_txdescp = (void *)kaddr;

        pcnp->pcn_txbufs = kmem_zalloc(PCN_TXRING * sizeof (pcn_buf_t *),
            KM_SLEEP);

        for (i = 0; i < PCN_TXRING; i++) {
                pcn_buf_t *txb = pcn_allocbuf(pcnp);
                if (txb == NULL)
                        return (DDI_FAILURE);
                pcnp->pcn_txbufs[i] = txb;
        }

        return (DDI_SUCCESS);
}

static int
pcn_allocrxring(pcn_t *pcnp)
{
        int                     rval;
        int                     i;
        size_t                  len;
        size_t                  size;
        ddi_dma_cookie_t        dmac;
        unsigned                ncookies;
        caddr_t                 kaddr;

        size = PCN_RXRING * sizeof (pcn_rx_desc_t);

        rval = ddi_dma_alloc_handle(pcnp->pcn_dip, &pcn_dmadesc_attr,
            DDI_DMA_SLEEP, NULL, &pcnp->pcn_rxdesc_dmah);
        if (rval != DDI_SUCCESS) {
                pcn_error(pcnp->pcn_dip, "unable to allocate DMA handle for rx "
                    "descriptors");
                return (DDI_FAILURE);
        }

        rval = ddi_dma_mem_alloc(pcnp->pcn_rxdesc_dmah, size, &pcn_devattr,
            DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &kaddr, &len,
            &pcnp->pcn_rxdesc_acch);
        if (rval != DDI_SUCCESS) {
                pcn_error(pcnp->pcn_dip, "unable to allocate DMA memory for rx "
                    "descriptors");
                return (DDI_FAILURE);
        }

        rval = ddi_dma_addr_bind_handle(pcnp->pcn_rxdesc_dmah, NULL, kaddr,
            size, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &dmac,
            &ncookies);
        if (rval != DDI_DMA_MAPPED) {
                pcn_error(pcnp->pcn_dip, "unable to bind DMA for rx "
                    "descriptors");
                return (DDI_FAILURE);
        }

        ASSERT(ncookies == 1);

        pcnp->pcn_rxdesc_paddr = dmac.dmac_address;
        pcnp->pcn_rxdescp = (void *)kaddr;

        pcnp->pcn_rxbufs = kmem_zalloc(PCN_RXRING * sizeof (pcn_buf_t *),
            KM_SLEEP);

        for (i = 0; i < PCN_RXRING; i++) {
                pcn_buf_t *rxb = pcn_allocbuf(pcnp);
                if (rxb == NULL)
                        return (DDI_FAILURE);
                pcnp->pcn_rxbufs[i] = rxb;
        }

        return (DDI_SUCCESS);
}

static void
pcn_freetxring(pcn_t *pcnp)
{
        int     i;

        if (pcnp->pcn_txbufs) {
                for (i = 0; i < PCN_TXRING; i++)
                        pcn_destroybuf(pcnp->pcn_txbufs[i]);

                kmem_free(pcnp->pcn_txbufs, PCN_TXRING * sizeof (pcn_buf_t *));
        }

        if (pcnp->pcn_txdesc_paddr)
                (void) ddi_dma_unbind_handle(pcnp->pcn_txdesc_dmah);
        if (pcnp->pcn_txdesc_acch)
                ddi_dma_mem_free(&pcnp->pcn_txdesc_acch);
        if (pcnp->pcn_txdesc_dmah)
                ddi_dma_free_handle(&pcnp->pcn_txdesc_dmah);
}

static void
pcn_freerxring(pcn_t *pcnp)
{
        int     i;

        if (pcnp->pcn_rxbufs) {
                for (i = 0; i < PCN_RXRING; i++)
                        pcn_destroybuf(pcnp->pcn_rxbufs[i]);

                kmem_free(pcnp->pcn_rxbufs, PCN_RXRING * sizeof (pcn_buf_t *));
        }

        if (pcnp->pcn_rxdesc_paddr)
                (void) ddi_dma_unbind_handle(pcnp->pcn_rxdesc_dmah);
        if (pcnp->pcn_rxdesc_acch)
                ddi_dma_mem_free(&pcnp->pcn_rxdesc_acch);
        if (pcnp->pcn_rxdesc_dmah)
                ddi_dma_free_handle(&pcnp->pcn_rxdesc_dmah);
}

static int
pcn_set_chipid(pcn_t *pcnp, uint32_t conf_id)
{
        char *name = NULL;
        uint32_t chipid;

        /*
         * Note: we can *NOT* put the chip into 32-bit mode yet. If a
         * lance ethernet device is present and pcn tries to attach, it can
         * hang the device (requiring a hardware reset), since they only work
         * in 16-bit mode.
         *
         * The solution is check using 16-bit operations first, and determine
         * if 32-bit mode operations are supported.
         *
         * The safest way to do this is to read the PCI subsystem ID from
         * BCR23/24 and compare that with the value read from PCI config
         * space.
         */
        chipid = pcn_bcr_read16(pcnp, PCN_BCR_PCISUBSYSID);
        chipid <<= 16;
        chipid |= pcn_bcr_read16(pcnp, PCN_BCR_PCISUBVENID);

        /*
         * The test for 0x10001000 is a hack to pacify VMware, who's
         * pseudo-PCnet interface is broken. Reading the subsystem register
         * from PCI config space yields 0x00000000 while reading the same value
         * from I/O space yields 0x10001000. It's not supposed to be that way.
         */
        if (chipid == conf_id || chipid == 0x10001000) {
                /* We're in 16-bit mode. */
                chipid = pcn_csr_read16(pcnp, PCN_CSR_CHIPID1);
                chipid <<= 16;
                chipid |= pcn_csr_read16(pcnp, PCN_CSR_CHIPID0);
        } else {
                chipid = pcn_csr_read(pcnp, PCN_CSR_CHIPID1);
                chipid <<= 16;
                chipid |= pcn_csr_read(pcnp, PCN_CSR_CHIPID0);
        }

        chipid = CHIPID_PARTID(chipid);

        /* Set default value and override as needed */
        switch (chipid) {
        case Am79C970:
                name = "Am79C970 PCnet-PCI";
                pcn_error(pcnp->pcn_dip, "Unsupported chip: %s", name);
                return (DDI_FAILURE);
        case Am79C970A:
                name = "Am79C970A PCnet-PCI II";
                pcn_error(pcnp->pcn_dip, "Unsupported chip: %s", name);
                return (DDI_FAILURE);
        case Am79C971:
                name = "Am79C971 PCnet-FAST";
                break;
        case Am79C972:
                name = "Am79C972 PCnet-FAST+";
                break;
        case Am79C973:
                name = "Am79C973 PCnet-FAST III";
                break;
        case Am79C975:
                name = "Am79C975 PCnet-FAST III";
                break;
        case Am79C976:
                name = "Am79C976";
                break;
        case Am79C978:
                name = "Am79C978";
                break;
        default:
                name = "Unknown";
                pcn_error(pcnp->pcn_dip, "Unknown chip id 0x%x", chipid);
        }

        if (ddi_prop_update_string(DDI_DEV_T_NONE, pcnp->pcn_dip, "chipid",
            name) != DDI_SUCCESS) {
                pcn_error(pcnp->pcn_dip, "Unable to set chipid property");
                return (DDI_FAILURE);
        }

        return (DDI_SUCCESS);
}

static void
pcn_error(dev_info_t *dip, char *fmt, ...)
{
        va_list ap;
        char    buf[256];

        va_start(ap, fmt);
        (void) vsnprintf(buf, sizeof (buf), fmt, ap);
        va_end(ap);

        if (dip)
                cmn_err(CE_WARN, "%s%d: %s", ddi_driver_name(dip),
                    ddi_get_instance(dip), buf);
        else
                cmn_err(CE_WARN, "pcn: %s", buf);
}