root/usr/src/uts/common/io/chxge/pe.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

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

/*
 * This file is part of the Chelsio T1 Ethernet driver.
 *
 * Copyright (C) 2003-2005 Chelsio Communications.  All rights reserved.
 */

/*
 * Solaris Multithreaded STREAMS Chelsio PCI Ethernet Driver.
 * Interface code
 */

#include <sys/types.h>
#include <sys/systm.h>
#include <sys/cmn_err.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/byteorder.h>
#include <sys/atomic.h>
#include <sys/ethernet.h>
#if PE_PROFILING_ENABLED
#include <sys/time.h>
#endif
#include <sys/gld.h>
#include "ostypes.h"
#include "common.h"
#include "oschtoe.h"
#ifdef CONFIG_CHELSIO_T1_1G
#include "fpga_defs.h"
#endif
#include "regs.h"
#ifdef CONFIG_CHELSIO_T1_OFFLOAD
#include "mc3.h"
#include "mc4.h"
#endif
#include "sge.h"
#include "tp.h"
#ifdef CONFIG_CHELSIO_T1_OFFLOAD
#include "ulp.h"
#endif
#include "espi.h"
#include "elmer0.h"
#include "gmac.h"
#include "cphy.h"
#include "suni1x10gexp_regs.h"
#include "ch.h"

#define MLEN(mp) ((mp)->b_wptr - (mp)->b_rptr)

extern uint32_t buffers_in_use[];
extern kmutex_t in_use_l;
extern uint32_t in_use_index;

static void link_start(ch_t *sa, struct pe_port_t *pp);
static ch_esb_t *ch_alloc_small_esbbuf(ch_t *sa, uint32_t i);
static ch_esb_t *ch_alloc_big_esbbuf(ch_t *sa, uint32_t i);
void ch_big_rbuf_recycle(ch_esb_t *rbp);
void ch_small_rbuf_recycle(ch_esb_t *rbp);
static const struct board_info *pe_sa_init(ch_t *sa);
static int ch_set_config_data(ch_t *chp);
void pe_rbuf_pool_free(ch_t *chp);
static void pe_free_driver_resources(ch_t *sa);
static void update_mtu_tab(ch_t *adapter);
static int pe_change_mtu(ch_t *chp);

/*
 * CPL5 Defines (from netinet/cpl5_commands.h)
 */
#define FLITSTOBYTES    8

#define CPL_FORMAT_0_SIZE 8
#define CPL_FORMAT_1_SIZE 16
#define CPL_FORMAT_2_SIZE 24
#define CPL_FORMAT_3_SIZE 32
#define CPL_FORMAT_4_SIZE 40
#define CPL_FORMAT_5_SIZE 48

#define TID_MASK 0xffffff

#define PE_LINK_SPEED_AUTONEG   5

static int pe_small_rbuf_pool_init(ch_t *sa);
static int pe_big_rbuf_pool_init(ch_t *sa);
static int pe_make_fake_arp(ch_t *chp, unsigned char *arpp);
static uint32_t pe_get_ip(unsigned char *arpp);

/*
 * May be set in /etc/system to 0 to use default latency timer for 10G.
 * See PCI register 0xc definition.
 */
int enable_latency_timer = 1;

/*
 * May be set in /etc/system to 0 to disable hardware checksum for
 * TCP and UDP.
 */
int enable_checksum_offload = 1;

/*
 * Multiplier for freelist pool.
 */
int fl_sz_multiplier = 6;

uint_t
pe_intr(ch_t *sa)
{
        mutex_enter(&sa->ch_intr);

        if (sge_data_in(sa->sge)) {
                sa->isr_intr++;
                mutex_exit(&sa->ch_intr);
                return (DDI_INTR_CLAIMED);
        }

        mutex_exit(&sa->ch_intr);

        return (DDI_INTR_UNCLAIMED);
}

/*
 * Each setup struct will call this function to
 * initialize.
 */
void
pe_init(void* xsa)
{
        ch_t *sa = NULL;
        int i = 0;

        sa = (ch_t *)xsa;

        /*
         * Need to count the number of times this routine is called
         * because we only want the resources to be allocated once.
         * The 7500 has four ports and so this routine can be called
         * once for each port.
         */
        if (sa->init_counter == 0) {
                for_each_port(sa, i) {

                        /*
                         * We only want to initialize the line if it is down.
                         */
                        if (sa->port[i].line_up == 0) {
                                link_start(sa, &sa->port[i]);
                                sa->port[i].line_up = 1;
                        }
                }

                (void) t1_init_hw_modules(sa);

                /*
                 * Enable/Disable checksum offloading.
                 */
                if (sa->ch_config.cksum_enabled) {
                        if (sa->config_data.offload_ip_cksum) {
                                /* Notify that HW will do the checksum. */
                                t1_tp_set_ip_checksum_offload(sa->tp, 1);
                        }

                        if (sa->config_data.offload_tcp_cksum) {
                                /* Notify that HW will do the checksum. */
                                t1_tp_set_tcp_checksum_offload(sa->tp, 1);
                        }

                        if (sa->config_data.offload_udp_cksum) {
                                /* Notify that HW will do the checksum. */
                                t1_tp_set_udp_checksum_offload(sa->tp, 1);
                        }
                }

                sa->ch_flags |= PEINITDONE;

                sa->init_counter++;
        }

        /*
         * Enable interrupts after starting the SGE so
         * that the SGE is ready to handle interrupts.
         */
        (void) sge_start(sa->sge);
        t1_interrupts_enable(sa);

        /*
         * set mtu (either 1500 or bigger)
         */
        (void) pe_change_mtu(sa);
#ifdef HOST_PAUSE
        /*
         * get the configured value of the MAC.
         */
        (void) t1_tpi_read(sa, SUNI1x10GEXP_REG_TXXG_CONFIG_1 << 2,
            &sa->txxg_cfg1);
#endif
}

/* ARGSUSED */
static void
link_start(ch_t *sa, struct pe_port_t *p)
{
        struct cmac *mac = p->mac;

        mac->ops->reset(mac);
        if (mac->ops->macaddress_set)
                mac->ops->macaddress_set(mac, p->enaddr);
        (void) t1_link_start(p->phy, mac, &p->link_config);
        mac->ops->enable(mac, MAC_DIRECTION_RX | MAC_DIRECTION_TX);
}

/*
 * turn off interrupts...
 */
void
pe_stop(ch_t *sa)
{
        t1_interrupts_disable(sa);
        (void) sge_stop(sa->sge);

        /*
         * we can still be running an interrupt thread in sge_data_in().
         * If we are, we'll block on the ch_intr lock
         */
        mutex_enter(&sa->ch_intr);
        mutex_exit(&sa->ch_intr);
}

/*
 * output mblk to SGE level and out to the wire.
 */

int
pe_start(ch_t *sa, mblk_t *mp, uint32_t flg)
{
        mblk_t *m0 = mp;
        cmdQ_ce_t cm[16];
        cmdQ_ce_t *cmp;
        cmdQ_ce_t *hmp = &cm[0]; /* head of cm table (may be kmem_alloed) */
        int cm_flg = 0;         /* flag (1 - if kmem-alloced) */
        int nseg = 0;           /* number cmdQ_ce entries created */
        int mseg = 16;          /* maximum entries in hmp arrary */
        int freeme = 0;         /* we have an mblk to free in case of error */
        uint32_t ch_bind_dma_handle(ch_t *, int, caddr_t, cmdQ_ce_t *,
            uint32_t);
#if defined(__sparc)
        uint32_t ch_bind_dvma_handle(ch_t *, int, caddr_t, cmdQ_ce_t *,
            uint32_t);
#endif
        int rv;                 /* return value on error */

#ifdef CONFIG_CHELSIO_T1_OFFLOAD
        if (flg & CH_OFFLOAD) {
                hmp->ce_pa = ((tbuf_t *)mp)->tb_pa;
                hmp->ce_dh = NULL;
                hmp->ce_flg = DH_TOE;
                hmp->ce_len = ((tbuf_t *)mp)->tb_len;
                hmp->ce_mp = mp;

                /* make sure data is flushed to physical memory */
                (void) ddi_dma_sync((ddi_dma_handle_t)((tbuf_t *)mp)->tb_dh,
                    (off_t)0, hmp->ce_len, DDI_DMA_SYNC_FORDEV);

                if (sge_data_out(sa->sge, 0, mp, hmp, 1, flg) == 0) {
                        return (0);
                }

                /*
                 * set a flag so we'll restart upper layer when
                 * resources become available.
                 */
                sa->ch_blked = 1;
                return (1);
        }
#endif  /* CONFIG_CHELSIO_T1_OFFLOAD */

        /* writes from toe will always have CPL header in place */
        if (flg & CH_NO_CPL) {
                struct cpl_tx_pkt *cpl;

                /* PR2928 & PR3309 */
                if (sa->ch_ip == 0) {
                        ushort_t ethertype = ntohs(*(short *)&mp->b_rptr[12]);
                        if (ethertype == ETHERTYPE_ARP) {
                                if (is_T2(sa)) {
                                        /*
                                         * We assume here that the arp will be
                                         * contained in one mblk.
                                         */
                                        if (pe_make_fake_arp(sa, mp->b_rptr)) {
                                                freemsg(mp);
                                                sa->oerr++;
                                                return (0);
                                        }
                                } else {
                                        sa->ch_ip = pe_get_ip(mp->b_rptr);
                                }
                        }
                }

                /*
                 * if space in front of packet big enough for CPL
                 * header, then use it. We'll allocate an mblk
                 * otherwise.
                 */
                if ((mp->b_rptr - mp->b_datap->db_base) >= SZ_CPL_TX_PKT) {

                        mp->b_rptr -= SZ_CPL_TX_PKT;

                } else {

#ifdef SUN_KSTATS
                        sa->sge->intr_cnt.tx_need_cpl_space++;
#endif
                        m0 = allocb(SZ_CPL_TX_PKT, BPRI_HI);
                        if (m0 == NULL) {
                                freemsg(mp);
                                sa->oerr++;
                                return (0);
                        }

                        m0->b_wptr = m0->b_rptr + SZ_CPL_TX_PKT;
                        m0->b_cont = mp;
                        freeme = 1;

                        mp = m0;
                }

                /* fill in cpl header */
                cpl = (struct cpl_tx_pkt *)mp->b_rptr;
                cpl->opcode = CPL_TX_PKT;
                cpl->iff = 0;           /* XXX port 0 needs fixing with NEMO */
                cpl->ip_csum_dis = 1;   /* no IP header cksum */
                cpl->l4_csum_dis =
                    flg & CH_NO_HWCKSUM;        /* CH_NO_HWCKSUM == 1 */
                cpl->vlan_valid = 0;            /* no vlan */
        }

        if (m0->b_cont) {

#ifdef SUN_KSTATS
                        sa->sge->intr_cnt.tx_multi_mblks++;
#endif

                while (mp) {
                        int lseg;       /* added by ch_bind_dma_handle() */
                        int len;

                        len = MLEN(mp);
                        /* skip mlks with no data */
                        if (len == 0) {
                                mp = mp->b_cont;
                                continue;
                        }

                        /*
                         * if we've run out of space on stack, then we
                         * allocate a temporary buffer to hold the
                         * information. This will kill the the performance,
                         * but since it shouldn't really occur, we can live
                         * with it. Since jumbo frames may map multiple
                         * descriptors, we reallocate the hmp[] array before
                         * we reach the end.
                         */
                        if (nseg >= (mseg-4)) {
                                cmdQ_ce_t *buf;
                                int j;

                                buf = kmem_alloc(sizeof (cmdQ_ce_t) * 2 * mseg,
                                    KM_SLEEP);

                                for (j = 0; j < nseg; j++)
                                        buf[j] = hmp[j];

                                if (cm_flg) {
                                        kmem_free(hmp,
                                            mseg * sizeof (cmdQ_ce_t));
                                } else
                                        cm_flg = 1;

                                hmp = buf;
                                mseg = 2*mseg;

                                /*
                                 * We've used up ch table on stack
                                 */
                        }

#if defined(__sparc)
                        if (sa->ch_config.enable_dvma) {
                                lseg = ch_bind_dvma_handle(sa, len,
                                    (void *)mp->b_rptr,
                                    &hmp[nseg], mseg - nseg);
                                if (lseg == 0) {
                                        sa->sge->intr_cnt.tx_no_dvma1++;
                                        if ((lseg = ch_bind_dma_handle(sa, len,
                                            (void *)mp->b_rptr,
                                            &hmp[nseg],
                                            mseg - nseg)) == 0) {
                                                sa->sge->intr_cnt.tx_no_dma1++;

                                                /*
                                                 * ran out of space. Gonna bale
                                                 */
                                                rv = 0;

                                                /*
                                                 * we may have processed
                                                 * previous mblks and have
                                                 * descriptors. If so, we need
                                                 * to free the meta struct
                                                 * entries before freeing
                                                 * the mblk.
                                                 */
                                                if (nseg)
                                                        goto error;
                                                goto error1;
                                        }
                                }
                        } else {
                                lseg = ch_bind_dma_handle(sa, len,
                                    (void *)mp->b_rptr, &hmp[nseg],
                                    mseg - nseg);
                                if (lseg == 0) {
                                        sa->sge->intr_cnt.tx_no_dma1++;

                                        /*
                                         * ran out of space. Gona bale
                                         */
                                        rv = 0;

                                        /*
                                         * we may have processed previous
                                         * mblks and have descriptors. If so,
                                         * we need to free the meta struct
                                         * entries before freeing the mblk.
                                         */
                                        if (nseg)
                                                goto error;
                                        goto error1;
                                }
                        }
#else   /* defined(__sparc) */
                        lseg = ch_bind_dma_handle(sa, len,
                            (void *)mp->b_rptr, &hmp[nseg],
                            mseg - nseg);
                        if (lseg == 0) {
                                sa->sge->intr_cnt.tx_no_dma1++;

                                /*
                                 * ran out of space. Gona bale
                                 */
                                rv = 0;

                                /*
                                 * we may have processed previous mblks and
                                 * have descriptors. If so, we need to free
                                 * the meta struct entries before freeing
                                 * the mblk.
                                 */
                                if (nseg)
                                        goto error;
                                goto error1;
                        }
#endif  /* defined(__sparc) */
                        nseg += lseg;
                        mp = mp->b_cont;
                }

                /*
                 * SHOULD NEVER OCCUR, BUT...
                 * no data if nseg 0 or
                 * nseg 1 and a CPL mblk (CPL mblk only with offload mode)
                 * and no data
                 */
                if ((nseg == 0) || (freeme && (nseg == 1))) {
                        rv = 0;
                        goto error1;
                }

        } else {
                int len;

                /* we assume that we always have data with one packet */
                len = MLEN(mp);

#if defined(__sparc)
                if (sa->ch_config.enable_dvma) {
                        nseg = ch_bind_dvma_handle(sa, len,
                            (void *)mp->b_rptr,
                            &hmp[0], 16);
                        if (nseg == 0) {
                                sa->sge->intr_cnt.tx_no_dvma2++;
                                nseg = ch_bind_dma_handle(sa, len,
                                    (void *)mp->b_rptr,
                                    &hmp[0], 16);
                                if (nseg == 0) {
                                        sa->sge->intr_cnt.tx_no_dma2++;

                                        /*
                                         * ran out of space. Gona bale
                                         */
                                        rv = 0;
                                        goto error1;
                                }
                        }
                } else {
                        nseg = ch_bind_dma_handle(sa, len,
                            (void *)mp->b_rptr, &hmp[0], 16);
                        if (nseg == 0) {
                                sa->sge->intr_cnt.tx_no_dma2++;

                                /*
                                 * ran out of space. Gona bale
                                 */
                                rv = 0;
                                goto error1;
                        }
                }
#else   /* defined(__sparc) */
                nseg = ch_bind_dma_handle(sa, len,
                    (void *)mp->b_rptr, &hmp[0], 16);
                if (nseg == 0) {
                        sa->sge->intr_cnt.tx_no_dma2++;

                        /*
                         * ran out of space. Gona bale
                         */
                        rv = 0;
                        goto error1;
                }
#endif  /* defined(__sparc) */

                /*
                 * dummy arp message to handle PR3309 & PR2928
                 */
                if (flg & CH_ARP)
                        hmp->ce_flg |= DH_ARP;
        }

        if (sge_data_out(sa->sge, 0, m0, hmp, nseg, flg) == 0) {
                if (cm_flg)
                        kmem_free(hmp, mseg * sizeof (cmdQ_ce_t));
                return (0);
        }

        /*
         * set a flag so we'll restart upper layer when
         * resources become available.
         */
        if ((flg & CH_ARP) == 0)
                sa->ch_blked = 1;
        rv = 1;

error:
        /*
         * unmap the physical addresses allocated earlier.
         */
        cmp = hmp;
        for (--nseg; nseg >= 0; nseg--) {
                if (cmp->ce_dh) {
                        if (cmp->ce_flg == DH_DMA)
                                ch_unbind_dma_handle(sa, cmp->ce_dh);
#if defined(__sparc)
                        else
                                ch_unbind_dvma_handle(sa, cmp->ce_dh);
#endif
                }
                cmp++;
        }

error1:

        /* free the temporary array */
        if (cm_flg)
                kmem_free(hmp, mseg * sizeof (cmdQ_ce_t));

        /*
         * if we've allocated an mblk above, then we need to free it
         * before returning. This is safe since we haven't done anything to
         * the original message. The caller, gld, will still have a pointer
         * to the original mblk.
         */
        if (rv == 1) {
                if (freeme) {
                        /* we had to allocate an mblk. Free it. */
                        freeb(m0);
                } else {
                        /* adjust the mblk back to original start */
                        if (flg & CH_NO_CPL)
                                m0->b_rptr += SZ_CPL_TX_PKT;
                }
        } else {
                freemsg(m0);
                sa->oerr++;
        }

        return (rv);
}

/* KLUDGE ALERT. HARD WIRED TO PORT ZERO */
void
pe_set_mac(ch_t *sa, unsigned char *ac_enaddr)
{
        sa->port[0].mac->ops->macaddress_set(sa->port[0].mac, ac_enaddr);
}

/* KLUDGE ALERT. HARD WIRED TO PORT ZERO */
unsigned char *
pe_get_mac(ch_t *sa)
{
        return (sa->port[0].enaddr);
}

/* KLUDGE ALERT. HARD WIRED TO ONE PORT */
void
pe_set_promiscuous(ch_t *sa, int flag)
{
        struct cmac *mac = sa->port[0].mac;
        struct t1_rx_mode rm;

        switch (flag) {
        case 0:         /* turn off promiscuous mode */
                sa->ch_flags &= ~(PEPROMISC|PEALLMULTI);
                break;

        case 1:         /* turn on promiscuous mode */
                sa->ch_flags |= PEPROMISC;
                break;

        case 2:         /* turn on multicast reception */
                sa->ch_flags |= PEALLMULTI;
                break;
        }

        mutex_enter(&sa->ch_mc_lck);
        rm.chp = sa;
        rm.mc = sa->ch_mc;

        mac->ops->set_rx_mode(mac, &rm);
        mutex_exit(&sa->ch_mc_lck);
}

int
pe_set_mc(ch_t *sa, uint8_t *ep, int flg)
{
        struct cmac *mac = sa->port[0].mac;
        struct t1_rx_mode rm;

        if (flg == GLD_MULTI_ENABLE) {
                ch_mc_t *mcp;

                mcp = (ch_mc_t *)kmem_zalloc(sizeof (struct ch_mc),
                    KM_NOSLEEP);
                if (mcp == NULL)
                        return (GLD_NORESOURCES);

                bcopy(ep, &mcp->cmc_mca, 6);

                mutex_enter(&sa->ch_mc_lck);
                mcp->cmc_next = sa->ch_mc;
                sa->ch_mc = mcp;
                sa->ch_mc_cnt++;
                mutex_exit(&sa->ch_mc_lck);

        } else if (flg == GLD_MULTI_DISABLE) {
                ch_mc_t **p = &sa->ch_mc;
                ch_mc_t *q = NULL;

                mutex_enter(&sa->ch_mc_lck);
                p = &sa->ch_mc;
                while (*p) {
                        if (bcmp(ep, (*p)->cmc_mca, 6) == 0) {
                                q = *p;
                                *p = (*p)->cmc_next;
                                kmem_free(q, sizeof (*q));
                                sa->ch_mc_cnt--;
                                break;
                        }

                        p = &(*p)->cmc_next;
                }
                mutex_exit(&sa->ch_mc_lck);

                if (q == NULL)
                        return (GLD_BADARG);
        } else
                return (GLD_BADARG);

        mutex_enter(&sa->ch_mc_lck);
        rm.chp = sa;
        rm.mc = sa->ch_mc;

        mac->ops->set_rx_mode(mac, &rm);
        mutex_exit(&sa->ch_mc_lck);

        return (GLD_SUCCESS);
}

/*
 * return: speed       - bandwidth of interface
 * return: intrcnt     - # interrupts
 * return: norcvbuf    - # recedived packets dropped by driver
 * return: oerrors     - # bad send packets
 * return: ierrors     - # bad receive packets
 * return: underrun    - # bad underrun xmit packets
 * return: overrun     - # bad overrun recv packets
 * return: framing     - # bad aligned recv packets
 * return: crc         - # bad FCS (crc) recv packets
 * return: carrier     - times carrier was lost
 * return: collisions  - # xmit collisions
 * return: xcollisions - # xmit pkts dropped due to collisions
 * return: late        - # late xmit collisions
 * return: defer       - # deferred xmit packets
 * return: xerrs       - # xmit dropped packets
 * return: rerrs       - # recv dropped packets
 * return: toolong     - # recv pkts too long
 * return: runt        - # recv runt pkts
 * return: multixmt    - # multicast pkts xmitted
 * return: multircv    - # multicast pkts recved
 * return: brdcstxmt   - # broadcast pkts xmitted
 * return: brdcstrcv   - # broadcast pkts rcv
 */

int
pe_get_stats(ch_t *sa, uint64_t *speed, uint32_t *intrcnt, uint32_t *norcvbuf,
    uint32_t *oerrors, uint32_t *ierrors, uint32_t *underrun,
    uint32_t *overrun, uint32_t *framing, uint32_t *crc,
    uint32_t *carrier, uint32_t *collisions, uint32_t *xcollisions,
    uint32_t *late, uint32_t *defer, uint32_t *xerrs, uint32_t *rerrs,
    uint32_t *toolong, uint32_t *runt, ulong_t  *multixmt, ulong_t  *multircv,
    ulong_t  *brdcstxmt, ulong_t  *brdcstrcv)
{
        struct pe_port_t *pt;
        int line_speed;
        int line_duplex;
        int line_is_active;
        uint64_t v;
        const struct cmac_statistics *sp;

        pt = &(sa->port[0]);
        (void) pt->phy->ops->get_link_status(pt->phy,
            &line_is_active, &line_speed, &line_duplex, NULL);

        switch (line_speed) {
        case SPEED_10:
                *speed = 10000000;
                break;
        case SPEED_100:
                *speed = 100000000;
                break;
        case SPEED_1000:
                *speed = 1000000000;
                break;
        case SPEED_10000:
                /*
                 * kludge to get 10,000,000,000 constant (and keep
                 * compiler happy).
                 */
                v = 10000000;
                v *= 1000;
                *speed = v;
                break;
        default:
                goto error;
        }

        *intrcnt = sa->isr_intr;
        *norcvbuf = sa->norcvbuf;

        sp = sa->port[0].mac->ops->statistics_update(sa->port[0].mac,
            MAC_STATS_UPDATE_FULL);

        *ierrors = sp->RxOctetsBad;

        /*
         * not sure this is correct. # aborted at driver level +
         * # at hardware level
         */
        *oerrors = sa->oerr + sp->TxFramesAbortedDueToXSCollisions +
            sp->TxUnderrun + sp->TxLengthErrors +
            sp->TxInternalMACXmitError +
            sp->TxFramesWithExcessiveDeferral +
            sp->TxFCSErrors;

        *underrun = sp->TxUnderrun;
        *overrun = sp->RxFrameTooLongErrors;
        *framing = sp->RxAlignErrors;
        *crc = sp->RxFCSErrors;
        *carrier = 0;           /* need to find this */
        *collisions = sp->TxTotalCollisions;
        *xcollisions = sp->TxFramesAbortedDueToXSCollisions;
        *late = sp->TxLateCollisions;
        *defer = sp->TxFramesWithDeferredXmissions;
        *xerrs = sp->TxUnderrun + sp->TxLengthErrors +
            sp->TxInternalMACXmitError + sp->TxFCSErrors;
        *rerrs = sp->RxSymbolErrors + sp->RxSequenceErrors + sp->RxRuntErrors +
            sp->RxJabberErrors + sp->RxInternalMACRcvError +
            sp->RxInRangeLengthErrors + sp->RxOutOfRangeLengthField;
        *toolong = sp->RxFrameTooLongErrors;
        *runt = sp->RxRuntErrors;

        *multixmt = sp->TxMulticastFramesOK;
        *multircv = sp->RxMulticastFramesOK;
        *brdcstxmt = sp->TxBroadcastFramesOK;
        *brdcstrcv = sp->RxBroadcastFramesOK;

        return (0);

error:
        *speed = 0;
        *intrcnt = 0;
        *norcvbuf = 0;
        *norcvbuf = 0;
        *oerrors = 0;
        *ierrors = 0;
        *underrun = 0;
        *overrun = 0;
        *framing = 0;
        *crc = 0;
        *carrier = 0;
        *collisions = 0;
        *xcollisions = 0;
        *late = 0;
        *defer = 0;
        *xerrs = 0;
        *rerrs = 0;
        *toolong = 0;
        *runt = 0;
        *multixmt = 0;
        *multircv = 0;
        *brdcstxmt = 0;
        *brdcstrcv = 0;

        return (1);
}

uint32_t ch_gtm = 0;            /* Default: Global Tunnel Mode off */
uint32_t ch_global_config = 0x07000000; /* Default: errors, warnings, status */
uint32_t ch_is_asic = 0;        /* Default: non-ASIC */
uint32_t ch_link_speed = PE_LINK_SPEED_AUTONEG; /* Default: auto-negoiate */
uint32_t ch_num_of_ports = 1;   /* Default: 1 port */
uint32_t ch_tp_reset_cm = 1;    /* Default: reset CM memory map */
uint32_t ch_phy_tx_fifo = 0;    /* Default: 0 phy tx fifo depth */
uint32_t ch_phy_rx_fifo = 0;    /* Default: 0 phy rx fifo depth */
uint32_t ch_phy_force_master = 1;       /* Default: link always master mode */
uint32_t ch_mc5_rtbl_size = 2048;       /* Default: TCAM routing table size */
uint32_t ch_mc5_dbsvr_size = 128;       /* Default: TCAM server size */
uint32_t ch_mc5_parity = 1;     /* Default: parity error checking */
uint32_t ch_mc5_issue_syn = 0;  /* Default: Allow transaction overlap */
uint32_t ch_packet_tracing = 0;         /* Default: no packet tracing */
uint32_t ch_server_region_len =
        DEFAULT_SERVER_REGION_LEN;
uint32_t ch_rt_region_len =
        DEFAULT_RT_REGION_LEN;
uint32_t ch_offload_ip_cksum = 0;       /* Default: no checksum offloading */
uint32_t ch_offload_udp_cksum = 1;      /* Default: offload UDP ckecksum */
uint32_t ch_offload_tcp_cksum = 1;      /* Default: offload TCP checksum */
uint32_t ch_sge_cmdq_threshold = 0;     /* Default: threshold 0 */
uint32_t ch_sge_flq_threshold = 0;      /* Default: SGE flq threshold */
uint32_t ch_sge_cmdq0_cnt =     /* Default: cmd queue 0 size */
        SGE_CMDQ0_CNT;
uint32_t ch_sge_cmdq1_cnt =     /* Default: cmd queue 1 size */
        SGE_CMDQ0_CNT;
uint32_t ch_sge_flq0_cnt =      /* Default: free list queue-0 length */
        SGE_FLQ0_CNT;
uint32_t ch_sge_flq1_cnt =      /* Default: free list queue-1 length */
        SGE_FLQ0_CNT;
uint32_t ch_sge_respq_cnt =     /* Default: reqsponse queue size */
        SGE_RESPQ_CNT;
uint32_t ch_stats = 1;          /* Default: Automatic Update MAC stats */
uint32_t ch_tx_delay_us = 0;    /* Default: No Msec delay to Tx pkts */
int32_t ch_chip = -1;           /* Default: use hardware lookup tbl */
uint32_t ch_exit_early = 0;     /* Default: complete initialization */
uint32_t ch_rb_num_of_entries = 1000; /* Default: number ring buffer entries */
uint32_t ch_rb_size_of_entries = 64;    /* Default: ring buffer entry size */
uint32_t ch_rb_flag = 1;        /* Default: ring buffer flag */
uint32_t ch_type;
uint64_t ch_cat_opt0 = 0;
uint64_t ch_cat_opt1 = 0;
uint32_t ch_timer_delay = 0;    /* Default: use value from board entry */

int
pe_attach(ch_t *chp)
{
        int return_val = 1;
        const struct board_info *bi;
        uint32_t pcix_cmd;

        (void) ch_set_config_data(chp);

        bi = pe_sa_init(chp);
        if (bi == 0)
                return (1);

        if (t1_init_sw_modules(chp, bi) < 0)
                return (1);

        if (pe_small_rbuf_pool_init(chp) == 0)
                return (1);

        if (pe_big_rbuf_pool_init(chp) == 0)
                return (1);

        /*
         * We gain significaint performance improvements when we
         * increase the PCI's maximum memory read byte count to
         * 2K(HW doesn't support 4K at this time) and set the PCI's
         * maximum outstanding split transactions to 4. We want to do
         * this for 10G. Done by software utility.
         */

        if (board_info(chp)->caps & SUPPORTED_10000baseT_Full) {
                (void) t1_os_pci_read_config_4(chp, A_PCICFG_PCIX_CMD,
                    &pcix_cmd);
                /*
                 * if the burstsize is set, then use it instead of default
                 */
                if (chp->ch_config.burstsize_set) {
                        pcix_cmd &= ~0xc0000;
                        pcix_cmd |= (chp->ch_config.burstsize << 18);
                }
                /*
                 * if the split transaction count is set, then use it.
                 */
                if (chp->ch_config.transaction_cnt_set) {
                        pcix_cmd &= ~ 0x700000;
                        pcix_cmd |= (chp->ch_config.transaction_cnt << 20);
                }

                /*
                 * set ralaxed ordering flag as configured in chxge.conf
                 */
                pcix_cmd |= (chp->ch_config.relaxed_ordering << 17);

                (void) t1_os_pci_write_config_4(chp, A_PCICFG_PCIX_CMD,
                    pcix_cmd);
        }

        /*
         * set the latency time to F8 for 10G cards.
         * Done by software utiltiy.
         */
        if (enable_latency_timer) {
                if (board_info(chp)->caps & SUPPORTED_10000baseT_Full) {
                        (void) t1_os_pci_write_config_4(chp, 0xc, 0xf800);
                }
        }

        /*
         * update mtu table (regs: 0x404 - 0x420) with bigger values than
         * default.
         */
        update_mtu_tab(chp);

        /*
         * Clear all interrupts now.  Don't enable
         * them until later.
         */
        t1_interrupts_clear(chp);

        /*
         * Function succeeded.
         */
        return_val = 0;

        return (return_val);
}

/*
 * DESC: Read variables set in /boot/loader.conf and save
 *       them internally. These internal values are then
 *       used to make decisions at run-time on behavior thus
 *       allowing a certain level of customization.
 * OUT:  p_config - pointer to config structure that
 *                  contains all of the new values.
 * RTN:  0 - Success;
 */
static int
ch_set_config_data(ch_t *chp)
{
        pe_config_data_t *p_config = (pe_config_data_t *)&chp->config_data;

        bzero(p_config, sizeof (pe_config_data_t));

        /*
         * Global Tunnel Mode configuration
         */
        p_config->gtm = ch_gtm;

        p_config->global_config = ch_global_config;

        if (p_config->gtm)
                p_config->global_config |= CFGMD_TUNNEL;

        p_config->tp_reset_cm = ch_tp_reset_cm;
        p_config->is_asic = ch_is_asic;

        /*
         * MC5 configuration.
         */
        p_config->mc5_rtbl_size = ch_mc5_rtbl_size;
        p_config->mc5_dbsvr_size = ch_mc5_dbsvr_size;
        p_config->mc5_parity = ch_mc5_parity;
        p_config->mc5_issue_syn = ch_mc5_issue_syn;

        p_config->offload_ip_cksum = ch_offload_ip_cksum;
        p_config->offload_udp_cksum = ch_offload_udp_cksum;
        p_config->offload_tcp_cksum = ch_offload_tcp_cksum;

        p_config->packet_tracing = ch_packet_tracing;

        p_config->server_region_len = ch_server_region_len;
        p_config->rt_region_len = ch_rt_region_len;

        /*
         * Link configuration.
         *
         * 5-auto-neg 2-1000Gbps; 1-100Gbps; 0-10Gbps
         */
        p_config->link_speed = ch_link_speed;
        p_config->num_of_ports = ch_num_of_ports;

        /*
         * Catp options
         */
        p_config->cat_opt0 = ch_cat_opt0;
        p_config->cat_opt1 = ch_cat_opt1;

        /*
         * SGE configuration.
         */
        p_config->sge_cmdq0_cnt = ch_sge_cmdq0_cnt;
        p_config->sge_cmdq1_cnt = ch_sge_cmdq1_cnt;
        p_config->sge_flq0_cnt = ch_sge_flq0_cnt;
        p_config->sge_flq1_cnt = ch_sge_flq1_cnt;
        p_config->sge_respq_cnt = ch_sge_respq_cnt;

        p_config->phy_rx_fifo = ch_phy_rx_fifo;
        p_config->phy_tx_fifo = ch_phy_tx_fifo;

        p_config->sge_cmdq_threshold = ch_sge_cmdq_threshold;

        p_config->sge_flq_threshold = ch_sge_flq_threshold;

        p_config->phy_force_master = ch_phy_force_master;

        p_config->rb_num_of_entries = ch_rb_num_of_entries;

        p_config->rb_size_of_entries = ch_rb_size_of_entries;

        p_config->rb_flag = ch_rb_flag;

        p_config->exit_early = ch_exit_early;

        p_config->chip = ch_chip;

        p_config->stats = ch_stats;

        p_config->tx_delay_us = ch_tx_delay_us;

        return (0);
}

static const struct board_info *
pe_sa_init(ch_t *sa)
{
        uint16_t device_id;
        uint16_t device_subid;
        const struct board_info *bi;

        sa->config = sa->config_data.global_config;
        device_id = pci_config_get16(sa->ch_hpci, 2);
        device_subid = pci_config_get16(sa->ch_hpci, 0x2e);

        bi = t1_get_board_info_from_ids(device_id, device_subid);
        if (bi == NULL) {
                cmn_err(CE_NOTE,
                    "The adapter with device_id %d %d is not supported.\n",
                    device_id, device_subid);
                return (NULL);
        }

        if (t1_get_board_rev(sa, bi, &sa->params)) {
                cmn_err(CE_NOTE, "unknown device_id %d %d\n",
                    device_id, device_subid);
                return ((const struct board_info *)NULL);
        }

        return (bi);
}

/*
 * allocate pool of small receive buffers (with vaddr & paddr) and
 * receiver buffer control structure (ch_esb_t *rbp).
 * XXX we should allow better tuning of the # of preallocated
 * free buffers against the # of freelist entries.
 */
static int
pe_small_rbuf_pool_init(ch_t *sa)
{
        int i;
        ch_esb_t *rbp;
        extern uint32_t sge_flq0_cnt;
        extern uint32_t sge_flq1_cnt;
        int size;
        uint32_t j;

        if (is_T2(sa))
                size = sge_flq1_cnt * fl_sz_multiplier;
        else
                size = sge_flq0_cnt * fl_sz_multiplier;

        mutex_init(&sa->ch_small_esbl, NULL, MUTEX_DRIVER, sa->ch_icookp);

        mutex_enter(&in_use_l);
        j = in_use_index++;
        if (in_use_index >= SZ_INUSE)
                in_use_index = 0;
        mutex_exit(&in_use_l);

        sa->ch_small_owner = NULL;
        sa->ch_sm_index = j;
        sa->ch_small_esb_free = NULL;
        for (i = 0; i < size; i++) {
                rbp = ch_alloc_small_esbbuf(sa, j);
                if (rbp == NULL)
                        goto error;
                /*
                 * add entry to free list
                 */
                rbp->cs_next = sa->ch_small_esb_free;
                sa->ch_small_esb_free = rbp;

                /*
                 * add entry to owned list
                 */
                rbp->cs_owner = sa->ch_small_owner;
                sa->ch_small_owner = rbp;
        }
        return (1);

error:
        sa->ch_small_owner = NULL;

        /* free whatever we've already allocated */
        pe_rbuf_pool_free(sa);

        return (0);
}

/*
 * allocate pool of receive buffers (with vaddr & paddr) and
 * receiver buffer control structure (ch_esb_t *rbp).
 * XXX we should allow better tuning of the # of preallocated
 * free buffers against the # of freelist entries.
 */
static int
pe_big_rbuf_pool_init(ch_t *sa)
{
        int i;
        ch_esb_t *rbp;
        extern uint32_t sge_flq0_cnt;
        extern uint32_t sge_flq1_cnt;
        int size;
        uint32_t j;

        if (is_T2(sa))
                size = sge_flq0_cnt * fl_sz_multiplier;
        else
                size = sge_flq1_cnt * fl_sz_multiplier;

        mutex_init(&sa->ch_big_esbl, NULL, MUTEX_DRIVER, sa->ch_icookp);

        mutex_enter(&in_use_l);
        j = in_use_index++;
        if (in_use_index >= SZ_INUSE)
                in_use_index = 0;
        mutex_exit(&in_use_l);

        sa->ch_big_owner = NULL;
        sa->ch_big_index = j;
        sa->ch_big_esb_free = NULL;
        for (i = 0; i < size; i++) {
                rbp = ch_alloc_big_esbbuf(sa, j);
                if (rbp == NULL)
                        goto error;
                rbp->cs_next = sa->ch_big_esb_free;
                sa->ch_big_esb_free = rbp;

                /*
                 * add entry to owned list
                 */
                rbp->cs_owner = sa->ch_big_owner;
                sa->ch_big_owner = rbp;
        }
        return (1);

error:
        sa->ch_big_owner = NULL;

        /* free whatever we've already allocated */
        pe_rbuf_pool_free(sa);

        return (0);
}

/*
 * allocate receive buffer structure and dma mapped buffer (SGE_SM_BUF_SZ bytes)
 * note that we will DMA at a 2 byte offset for Solaris when checksum offload
 * is enabled.
 */
static ch_esb_t *
ch_alloc_small_esbbuf(ch_t *sa, uint32_t i)
{
        ch_esb_t *rbp;

        rbp = (ch_esb_t *)kmem_zalloc(sizeof (ch_esb_t), KM_SLEEP);
        if (rbp == NULL) {
                return ((ch_esb_t *)0);
        }

#if BYTE_ORDER == BIG_ENDIAN
        rbp->cs_buf = (caddr_t)ch_alloc_dma_mem(sa, 1, DMA_STREAM|DMA_SMALN,
            SGE_SM_BUF_SZ(sa), &rbp->cs_pa, &rbp->cs_dh, &rbp->cs_ah);
#else
        rbp->cs_buf = (caddr_t)ch_alloc_dma_mem(sa, 0, DMA_STREAM|DMA_SMALN,
            SGE_SM_BUF_SZ(sa), &rbp->cs_pa, &rbp->cs_dh, &rbp->cs_ah);
#endif

        if (rbp->cs_buf == NULL) {
                kmem_free(rbp, sizeof (ch_esb_t));
                return ((ch_esb_t *)0);
        }

        rbp->cs_sa = sa;
        rbp->cs_index = i;

        rbp->cs_frtn.free_func = (void (*)())&ch_small_rbuf_recycle;
        rbp->cs_frtn.free_arg  = (caddr_t)rbp;

        return (rbp);
}

/*
 * allocate receive buffer structure and dma mapped buffer (SGE_BG_BUF_SZ bytes)
 * note that we will DMA at a 2 byte offset for Solaris when checksum offload
 * is enabled.
 */
static ch_esb_t *
ch_alloc_big_esbbuf(ch_t *sa, uint32_t i)
{
        ch_esb_t *rbp;

        rbp = (ch_esb_t *)kmem_zalloc(sizeof (ch_esb_t), KM_SLEEP);
        if (rbp == NULL) {
                return ((ch_esb_t *)0);
        }

#if BYTE_ORDER == BIG_ENDIAN
        rbp->cs_buf = (caddr_t)ch_alloc_dma_mem(sa, 1, DMA_STREAM|DMA_BGALN,
            SGE_BG_BUF_SZ(sa), &rbp->cs_pa, &rbp->cs_dh, &rbp->cs_ah);
#else
        rbp->cs_buf = (caddr_t)ch_alloc_dma_mem(sa, 0, DMA_STREAM|DMA_BGALN,
            SGE_BG_BUF_SZ(sa), &rbp->cs_pa, &rbp->cs_dh, &rbp->cs_ah);
#endif

        if (rbp->cs_buf == NULL) {
                kmem_free(rbp, sizeof (ch_esb_t));
                return ((ch_esb_t *)0);
        }

        rbp->cs_sa = sa;
        rbp->cs_index = i;

        rbp->cs_frtn.free_func = (void (*)())&ch_big_rbuf_recycle;
        rbp->cs_frtn.free_arg  = (caddr_t)rbp;

        return (rbp);
}

/*
 * free entries on the receive buffer list.
 */
void
pe_rbuf_pool_free(ch_t *sa)
{
        ch_esb_t *rbp;

        mutex_enter(&sa->ch_small_esbl);

        /*
         * Now set-up the rest to commit suicide.
         */
        while (sa->ch_small_owner) {
                rbp = sa->ch_small_owner;
                sa->ch_small_owner = rbp->cs_owner;
                rbp->cs_owner = NULL;
                rbp->cs_flag = 1;
        }

        while ((rbp = sa->ch_small_esb_free) != NULL) {
                /* advance head ptr to next entry */
                sa->ch_small_esb_free = rbp->cs_next;
                /* free private buffer allocated in ch_alloc_esbbuf() */
                ch_free_dma_mem(rbp->cs_dh, rbp->cs_ah);
                /* free descripter buffer */
                kmem_free(rbp, sizeof (ch_esb_t));
        }

        mutex_exit(&sa->ch_small_esbl);

        /* destroy ch_esbl lock */
        mutex_destroy(&sa->ch_small_esbl);


        mutex_enter(&sa->ch_big_esbl);

        /*
         * Now set-up the rest to commit suicide.
         */
        while (sa->ch_big_owner) {
                rbp = sa->ch_big_owner;
                sa->ch_big_owner = rbp->cs_owner;
                rbp->cs_owner = NULL;
                rbp->cs_flag = 1;
        }

        while ((rbp = sa->ch_big_esb_free) != NULL) {
                /* advance head ptr to next entry */
                sa->ch_big_esb_free = rbp->cs_next;
                /* free private buffer allocated in ch_alloc_esbbuf() */
                ch_free_dma_mem(rbp->cs_dh, rbp->cs_ah);
                /* free descripter buffer */
                kmem_free(rbp, sizeof (ch_esb_t));
        }

        mutex_exit(&sa->ch_big_esbl);

        /* destroy ch_esbl lock */
        mutex_destroy(&sa->ch_big_esbl);
}

void
ch_small_rbuf_recycle(ch_esb_t *rbp)
{
        ch_t *sa = rbp->cs_sa;

        if (rbp->cs_flag) {
                uint32_t i;
                /*
                 * free private buffer allocated in ch_alloc_esbbuf()
                 */
                ch_free_dma_mem(rbp->cs_dh, rbp->cs_ah);

                i = rbp->cs_index;

                /*
                 * free descripter buffer
                 */
                kmem_free(rbp, sizeof (ch_esb_t));

                /*
                 * decrement count of receive buffers freed by callback
                 * We decrement here so anyone trying to do fini will
                 * only remove the driver once the counts go to 0.
                 */
                atomic_dec_32(&buffers_in_use[i]);

                return;
        }

        mutex_enter(&sa->ch_small_esbl);
        rbp->cs_next = sa->ch_small_esb_free;
        sa->ch_small_esb_free = rbp;
        mutex_exit(&sa->ch_small_esbl);

        /*
         * decrement count of receive buffers freed by callback
         */
        atomic_dec_32(&buffers_in_use[rbp->cs_index]);
}

/*
 * callback function from freeb() when esballoced mblk freed.
 */
void
ch_big_rbuf_recycle(ch_esb_t *rbp)
{
        ch_t *sa = rbp->cs_sa;

        if (rbp->cs_flag) {
                uint32_t i;
                /*
                 * free private buffer allocated in ch_alloc_esbbuf()
                 */
                ch_free_dma_mem(rbp->cs_dh, rbp->cs_ah);

                i = rbp->cs_index;

                /*
                 * free descripter buffer
                 */
                kmem_free(rbp, sizeof (ch_esb_t));

                /*
                 * decrement count of receive buffers freed by callback
                 * We decrement here so anyone trying to do fini will
                 * only remove the driver once the counts go to 0.
                 */
                atomic_dec_32(&buffers_in_use[i]);

                return;
        }

        mutex_enter(&sa->ch_big_esbl);
        rbp->cs_next = sa->ch_big_esb_free;
        sa->ch_big_esb_free = rbp;
        mutex_exit(&sa->ch_big_esbl);

        /*
         * decrement count of receive buffers freed by callback
         */
        atomic_dec_32(&buffers_in_use[rbp->cs_index]);
}

/*
 * get a pre-allocated, pre-mapped receive buffer from free list.
 * (used sge.c)
 */
ch_esb_t *
ch_get_small_rbuf(ch_t *sa)
{
        ch_esb_t *rbp;

        mutex_enter(&sa->ch_small_esbl);
        rbp = sa->ch_small_esb_free;
        if (rbp) {
                sa->ch_small_esb_free = rbp->cs_next;
        }
        mutex_exit(&sa->ch_small_esbl);

        return (rbp);
}

/*
 * get a pre-allocated, pre-mapped receive buffer from free list.
 * (used sge.c)
 */

ch_esb_t *
ch_get_big_rbuf(ch_t *sa)
{
        ch_esb_t *rbp;

        mutex_enter(&sa->ch_big_esbl);
        rbp = sa->ch_big_esb_free;
        if (rbp) {
                sa->ch_big_esb_free = rbp->cs_next;
        }
        mutex_exit(&sa->ch_big_esbl);

        return (rbp);
}

void
pe_detach(ch_t *sa)
{
        (void) sge_stop(sa->sge);

        pe_free_driver_resources(sa);
}

static void
pe_free_driver_resources(ch_t *sa)
{
        if (sa) {
                t1_free_sw_modules(sa);

                /* free pool of receive buffers */
                pe_rbuf_pool_free(sa);
        }
}

/*
 * Processes elmer0 external interrupts in process context.
 */
static void
ext_intr_task(ch_t *adapter)
{
        u32 enable;

        (void) elmer0_ext_intr_handler(adapter);

        /* Now reenable external interrupts */
        t1_write_reg_4(adapter, A_PL_CAUSE, F_PL_INTR_EXT);
        enable = t1_read_reg_4(adapter, A_PL_ENABLE);
        t1_write_reg_4(adapter, A_PL_ENABLE, enable | F_PL_INTR_EXT);
        adapter->slow_intr_mask |= F_PL_INTR_EXT;
}

/*
 * Interrupt-context handler for elmer0 external interrupts.
 */
void
t1_os_elmer0_ext_intr(ch_t *adapter)
{
        u32 enable = t1_read_reg_4(adapter, A_PL_ENABLE);

        adapter->slow_intr_mask &= ~F_PL_INTR_EXT;
        t1_write_reg_4(adapter, A_PL_ENABLE, enable & ~F_PL_INTR_EXT);
#ifdef NOTYET
        schedule_work(&adapter->ext_intr_handler_task);
#else
        ext_intr_task(adapter);
#endif
}

uint8_t *
t1_get_next_mcaddr(struct t1_rx_mode *rmp)
{
        uint8_t *addr = 0;
        if (rmp->mc) {
                addr = rmp->mc->cmc_mca;
                rmp->mc = rmp->mc->cmc_next;
        }
        return (addr);
}

void
pe_dma_handle_init(ch_t *chp, int cnt)
{
        free_dh_t *dhe;
#if defined(__sparc)
        int tcnt = cnt/2;

        for (; cnt; cnt--) {
                dhe = ch_get_dvma_handle(chp);
                if (dhe == NULL)
                        break;
                mutex_enter(&chp->ch_dh_lck);
                dhe->dhe_next = chp->ch_vdh;
                chp->ch_vdh = dhe;
                mutex_exit(&chp->ch_dh_lck);
        }

        cnt += tcnt;
#endif
        while (cnt--) {
                dhe = ch_get_dma_handle(chp);
                if (dhe == NULL)
                        return;
                mutex_enter(&chp->ch_dh_lck);
                dhe->dhe_next = chp->ch_dh;
                chp->ch_dh = dhe;
                mutex_exit(&chp->ch_dh_lck);
        }
}

/*
 * Write new values to the MTU table.  Caller must validate that the new MTUs
 * are in ascending order. params.mtus[] is initialized by init_mtus()
 * called in t1_init_sw_modules().
 */
#define MTUREG(idx) (A_TP_MTU_REG0 + (idx) * 4)

static void
update_mtu_tab(ch_t *adapter)
{
        int i;

        for (i = 0; i < NMTUS; ++i) {
                int mtu = (unsigned int)adapter->params.mtus[i];

                t1_write_reg_4(adapter, MTUREG(i), mtu);
        }
}

static int
pe_change_mtu(ch_t *chp)
{
        struct cmac *mac = chp->port[0].mac;
        int ret;

        if (!mac->ops->set_mtu) {
                return (EOPNOTSUPP);
        }
        if (chp->ch_mtu < 68) {
                return (EINVAL);
        }
        if (ret = mac->ops->set_mtu(mac, chp->ch_mtu)) {
                return (ret);
        }

        return (0);
}

typedef struct fake_arp {
        char fa_dst[6];         /* ethernet header */
        char fa_src[6];         /* ethernet header */
        ushort_t fa_typ;                /* ethernet header */

        ushort_t fa_hrd;                /* arp */
        ushort_t fa_pro;
        char fa_hln;
        char fa_pln;
        ushort_t fa_op;
        char fa_src_mac[6];
        uint_t fa_src_ip;
        char fa_dst_mac[6];
        char fa_dst_ip[4];
} fake_arp_t;

/*
 * PR2928 & PR3309
 * construct packet in mblk and attach it to sge structure.
 */
static int
pe_make_fake_arp(ch_t *chp, unsigned char *arpp)
{
        pesge *sge = chp->sge;
        mblk_t *bp;
        fake_arp_t *fap;
        static char buf[6] = {0, 7, 0x43, 0, 0, 0};
        struct cpl_tx_pkt *cpl;

        bp = allocb(sizeof (struct fake_arp) + SZ_CPL_TX_PKT, BPRI_HI);
        if (bp == NULL) {
                return (1);
        }
        bzero(bp->b_rptr, sizeof (struct fake_arp) + SZ_CPL_TX_PKT);

        /* fill in cpl header */
        cpl = (struct cpl_tx_pkt *)bp->b_rptr;
        cpl->opcode = CPL_TX_PKT;
        cpl->iff = 0;                   /* XXX port 0 needs fixing with NEMO */
        cpl->ip_csum_dis = 1;           /* no IP header cksum */
        cpl->l4_csum_dis = 1;           /* no tcp/udp cksum */
        cpl->vlan_valid = 0;            /* no vlan */

        fap = (fake_arp_t *)&bp->b_rptr[SZ_CPL_TX_PKT];

        bcopy(arpp, fap, sizeof (*fap));        /* copy first arp to mblk */

        bcopy(buf, fap->fa_dst, 6);             /* overwrite dst mac */
        chp->ch_ip = fap->fa_src_ip;            /* not used yet */
        bcopy(buf, fap->fa_dst_mac, 6);         /* overwrite dst mac */

        bp->b_wptr = bp->b_rptr + sizeof (struct fake_arp)+SZ_CPL_TX_PKT;

        sge_add_fake_arp(sge, (void *)bp);

        return (0);
}

/*
 * PR2928 & PR3309
 * free the fake arp's mblk on sge structure.
 */
void
pe_free_fake_arp(void *arp)
{
        mblk_t *bp = (mblk_t *)(arp);

        freemsg(bp);
}

/*
 * extract ip address of nic from first outgoing arp.
 */
static uint32_t
pe_get_ip(unsigned char *arpp)
{
        fake_arp_t fap;

        /*
         * first copy packet to buffer so we know
         * it will be properly aligned.
         */
        bcopy(arpp, &fap, sizeof (fap));        /* copy first arp to buffer */
        return (fap.fa_src_ip);
}

/* ARGSUSED */
void
t1_os_link_changed(ch_t *obj, int port_id, int link_status,
    int speed, int duplex, int fc)
{
        gld_mac_info_t *macinfo = obj->ch_macp;
        if (link_status) {
                gld_linkstate(macinfo, GLD_LINKSTATE_UP);
                /*
                 * Link states should be reported to user
                 * whenever it changes
                 */
                cmn_err(CE_NOTE, "%s: link is up", adapter_name(obj));
        } else {
                gld_linkstate(macinfo, GLD_LINKSTATE_DOWN);
                /*
                 * Link states should be reported to user
                 * whenever it changes
                 */
                cmn_err(CE_NOTE, "%s: link is down", adapter_name(obj));
        }
}