mxfe.c revision afdda45f890ee5dfc86e5131a30b11b354d51633
/*
* Solaris driver for ethernet cards based on the Macronix 98715
*
* Copyright (c) 2007 by Garrett D'Amore <garrett@damore.org>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the 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 THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <sys/varargs.h>
#include <sys/types.h>
#include <sys/modctl.h>
#include <sys/conf.h>
#include <sys/devops.h>
#include <sys/stream.h>
#include <sys/strsun.h>
#include <sys/cmn_err.h>
#include <sys/dlpi.h>
#include <sys/ethernet.h>
#include <sys/kmem.h>
#include <sys/time.h>
#include <sys/miiregs.h>
#include <sys/strsun.h>
#include <sys/mac.h>
#include <sys/mac_ether.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/vlan.h>
#include "mxfe.h"
#include "mxfeimpl.h"
/*
* Driver globals.
*/
/* patchable debug flag ... must not be static! */
#ifdef DEBUG
unsigned mxfe_debug = DWARN;
#endif
/* table of supported devices */
static mxfe_card_t mxfe_cards[] = {
/*
* Lite-On products
*/
{ 0x11ad, 0xc115, 0, 0, "Lite-On LC82C115", MXFE_PNICII },
/*
* Macronix chips
*/
{ 0x10d9, 0x0531, 0x25, 0xff, "Macronix MX98715AEC", MXFE_98715AEC },
{ 0x10d9, 0x0531, 0x20, 0xff, "Macronix MX98715A", MXFE_98715A },
{ 0x10d9, 0x0531, 0x60, 0xff, "Macronix MX98715B", MXFE_98715B },
{ 0x10d9, 0x0531, 0x30, 0xff, "Macronix MX98725", MXFE_98725 },
{ 0x10d9, 0x0531, 0x00, 0xff, "Macronix MX98715", MXFE_98715 },
{ 0x10d9, 0x0512, 0, 0, "Macronix MX98713", MXFE_98713 },
/*
* Compex (relabeled Macronix products)
*/
{ 0x11fc, 0x9881, 0x00, 0x00, "Compex 9881", MXFE_98713 },
{ 0x11fc, 0x9881, 0x10, 0xff, "Compex 9881A", MXFE_98713A },
/*
* Models listed here
*/
{ 0x11ad, 0xc001, 0, 0, "Linksys LNE100TX", MXFE_PNICII },
{ 0x2646, 0x000b, 0, 0, "Kingston KNE111TX", MXFE_PNICII },
{ 0x1154, 0x0308, 0, 0, "Buffalo LGY-PCI-TXL", MXFE_98715AEC },
};
#define ETHERVLANMTU (ETHERMAX + 4)
/*
* Function prototypes
*/
static int mxfe_attach(dev_info_t *, ddi_attach_cmd_t);
static int mxfe_detach(dev_info_t *, ddi_detach_cmd_t);
static int mxfe_resume(dev_info_t *);
static int mxfe_m_unicst(void *, const uint8_t *);
static int mxfe_m_multicst(void *, boolean_t, const uint8_t *);
static int mxfe_m_promisc(void *, boolean_t);
static mblk_t *mxfe_m_tx(void *, mblk_t *);
static int mxfe_m_stat(void *, uint_t, uint64_t *);
static int mxfe_m_start(void *);
static void mxfe_m_stop(void *);
static int mxfe_m_getprop(void *, const char *, mac_prop_id_t, uint_t,
uint_t, void *, uint_t *);
static int mxfe_m_setprop(void *, const char *, mac_prop_id_t, uint_t,
const void *);
static unsigned mxfe_intr(caddr_t);
static void mxfe_startmac(mxfe_t *);
static void mxfe_stopmac(mxfe_t *);
static void mxfe_resetrings(mxfe_t *);
static boolean_t mxfe_initialize(mxfe_t *);
static void mxfe_startall(mxfe_t *);
static void mxfe_stopall(mxfe_t *);
static void mxfe_resetall(mxfe_t *);
static mxfe_txbuf_t *mxfe_alloctxbuf(mxfe_t *);
static void mxfe_destroytxbuf(mxfe_txbuf_t *);
static mxfe_rxbuf_t *mxfe_allocrxbuf(mxfe_t *);
static void mxfe_destroyrxbuf(mxfe_rxbuf_t *);
static void mxfe_send_setup(mxfe_t *);
static boolean_t mxfe_send(mxfe_t *, mblk_t *);
static int mxfe_allocrxring(mxfe_t *);
static void mxfe_freerxring(mxfe_t *);
static int mxfe_alloctxring(mxfe_t *);
static void mxfe_freetxring(mxfe_t *);
static void mxfe_error(dev_info_t *, char *, ...);
static uint8_t mxfe_sromwidth(mxfe_t *);
static uint16_t mxfe_readsromword(mxfe_t *, unsigned);
static void mxfe_readsrom(mxfe_t *, unsigned, unsigned, void *);
static void mxfe_getfactaddr(mxfe_t *, uchar_t *);
static uint8_t mxfe_miireadbit(mxfe_t *);
static void mxfe_miiwritebit(mxfe_t *, uint8_t);
static void mxfe_miitristate(mxfe_t *);
static uint16_t mxfe_miiread(mxfe_t *, int, int);
static void mxfe_miiwrite(mxfe_t *, int, int, uint16_t);
static uint16_t mxfe_miireadgeneral(mxfe_t *, int, int);
static void mxfe_miiwritegeneral(mxfe_t *, int, int, uint16_t);
static uint16_t mxfe_miiread98713(mxfe_t *, int, int);
static void mxfe_miiwrite98713(mxfe_t *, int, int, uint16_t);
static void mxfe_startphy(mxfe_t *);
static void mxfe_stopphy(mxfe_t *);
static void mxfe_startphymii(mxfe_t *);
static void mxfe_startphynway(mxfe_t *);
static void mxfe_startnway(mxfe_t *);
static void mxfe_reportlink(mxfe_t *);
static void mxfe_checklink(mxfe_t *);
static void mxfe_checklinkmii(mxfe_t *);
static void mxfe_checklinknway(mxfe_t *);
static void mxfe_disableinterrupts(mxfe_t *);
static void mxfe_enableinterrupts(mxfe_t *);
static void mxfe_reclaim(mxfe_t *);
static mblk_t *mxfe_receive(mxfe_t *);
#ifdef DEBUG
static void mxfe_dprintf(mxfe_t *, const char *, int, char *, ...);
#endif
#define KIOIP KSTAT_INTR_PTR(mxfep->mxfe_intrstat)
static mac_callbacks_t mxfe_m_callbacks = {
MC_SETPROP | MC_GETPROP,
mxfe_m_stat,
mxfe_m_start,
mxfe_m_stop,
mxfe_m_promisc,
mxfe_m_multicst,
mxfe_m_unicst,
mxfe_m_tx,
NULL, /* mc_resources */
NULL, /* mc_ioctl */
NULL, /* mc_getcapab */
NULL, /* mc_open */
NULL, /* mc_close */
mxfe_m_setprop,
mxfe_m_getprop
};
/*
* Stream information
*/
DDI_DEFINE_STREAM_OPS(mxfe_devops, nulldev, nulldev, mxfe_attach, mxfe_detach,
nodev, NULL, D_MP, NULL, ddi_quiesce_not_supported);
/*
* Module linkage information.
*/
static struct modldrv mxfe_modldrv = {
&mod_driverops, /* drv_modops */
"Macronix Fast Ethernet", /* drv_linkinfo */
&mxfe_devops /* drv_dev_ops */
};
static struct modlinkage mxfe_modlinkage = {
MODREV_1, /* ml_rev */
{ &mxfe_modldrv, NULL } /* ml_linkage */
};
/*
* Device attributes.
*/
static ddi_device_acc_attr_t mxfe_devattr = {
DDI_DEVICE_ATTR_V0,
DDI_STRUCTURE_LE_ACC,
DDI_STRICTORDER_ACC
};
static ddi_device_acc_attr_t mxfe_bufattr = {
DDI_DEVICE_ATTR_V0,
DDI_NEVERSWAP_ACC,
DDI_STRICTORDER_ACC
};
static ddi_dma_attr_t mxfe_dma_attr = {
DMA_ATTR_V0, /* dma_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 */
};
/*
* Tx buffers can be arbitrarily aligned. Additionally, they can
* cross a page boundary, so we use the two buffer addresses of the
* chip to provide a two-entry scatter-gather list.
*/
static ddi_dma_attr_t mxfe_dma_txattr = {
DMA_ATTR_V0, /* dma_attr_version */
0, /* dma_attr_addr_lo */
0xFFFFFFFFU, /* dma_attr_addr_hi */
0x7FFFFFFFU, /* dma_attr_count_max */
1, /* dma_attr_align */
0x3F, /* dma_attr_burstsizes */
1, /* dma_attr_minxfer */
0xFFFFFFFFU, /* dma_attr_maxxfer */
0xFFFFFFFFU, /* dma_attr_seg */
2, /* dma_attr_sgllen */
1, /* dma_attr_granular */
0 /* dma_attr_flags */
};
/*
* Ethernet addresses.
*/
static uchar_t mxfe_broadcast[ETHERADDRL] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
/*
* DDI entry points.
*/
int
_init(void)
{
int rv;
mac_init_ops(&mxfe_devops, "mxfe");
if ((rv = mod_install(&mxfe_modlinkage)) != DDI_SUCCESS) {
mac_fini_ops(&mxfe_devops);
}
return (rv);
}
int
_fini(void)
{
int rv;
if ((rv = mod_remove(&mxfe_modlinkage)) == DDI_SUCCESS) {
mac_fini_ops(&mxfe_devops);
}
return (rv);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&mxfe_modlinkage, modinfop));
}
int
mxfe_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
mxfe_t *mxfep;
mac_register_t *macp;
int inst = ddi_get_instance(dip);
ddi_acc_handle_t pci;
uint16_t venid;
uint16_t devid;
uint16_t revid;
uint16_t svid;
uint16_t ssid;
uint16_t cachesize;
mxfe_card_t *cardp;
int i;
switch (cmd) {
case DDI_RESUME:
return (mxfe_resume(dip));
case DDI_ATTACH:
break;
default:
return (DDI_FAILURE);
}
/* this card is a bus master, reject any slave-only slot */
if (ddi_slaveonly(dip) == DDI_SUCCESS) {
mxfe_error(dip, "slot does not support PCI bus-master");
return (DDI_FAILURE);
}
/* PCI devices shouldn't generate hilevel interrupts */
if (ddi_intr_hilevel(dip, 0) != 0) {
mxfe_error(dip, "hilevel interrupts not supported");
return (DDI_FAILURE);
}
if (pci_config_setup(dip, &pci) != DDI_SUCCESS) {
mxfe_error(dip, "unable to setup PCI config handle");
return (DDI_FAILURE);
}
venid = pci_config_get16(pci, PCI_VID);
devid = pci_config_get16(pci, PCI_DID);
revid = pci_config_get16(pci, PCI_RID);
svid = pci_config_get16(pci, PCI_SVID);
ssid = pci_config_get16(pci, PCI_SSID);
/*
* the last entry in the card table matches every possible
* card, so the for-loop always terminates properly.
*/
cardp = NULL;
for (i = 0; i < (sizeof (mxfe_cards) / sizeof (mxfe_card_t)); i++) {
if ((venid == mxfe_cards[i].card_venid) &&
(devid == mxfe_cards[i].card_devid) &&
((revid & mxfe_cards[i].card_revmask) ==
mxfe_cards[i].card_revid)) {
cardp = &mxfe_cards[i];
}
if ((svid == mxfe_cards[i].card_venid) &&
(ssid == mxfe_cards[i].card_devid) &&
((revid & mxfe_cards[i].card_revmask) ==
mxfe_cards[i].card_revid)) {
cardp = &mxfe_cards[i];
break;
}
}
if (cardp == NULL) {
pci_config_teardown(&pci);
mxfe_error(dip, "Unable to identify PCI card");
return (DDI_FAILURE);
}
if (ddi_prop_update_string(DDI_DEV_T_NONE, dip, "model",
cardp->card_cardname) != DDI_PROP_SUCCESS) {
pci_config_teardown(&pci);
mxfe_error(dip, "Unable to create model property");
return (DDI_FAILURE);
}
/*
* Grab the PCI cachesize -- we use this to program the
* cache-optimization bus access bits.
*/
cachesize = pci_config_get8(pci, PCI_CLS);
/* this cannot fail */
mxfep = kmem_zalloc(sizeof (mxfe_t), KM_SLEEP);
ddi_set_driver_private(dip, mxfep);
/* get the interrupt block cookie */
if (ddi_get_iblock_cookie(dip, 0, &mxfep->mxfe_icookie)
!= DDI_SUCCESS) {
mxfe_error(dip, "ddi_get_iblock_cookie failed");
pci_config_teardown(&pci);
kmem_free(mxfep, sizeof (mxfe_t));
return (DDI_FAILURE);
}
mxfep->mxfe_dip = dip;
mxfep->mxfe_cardp = cardp;
mxfep->mxfe_phyaddr = -1;
mxfep->mxfe_cachesize = cachesize;
/* default properties */
mxfep->mxfe_adv_aneg = ddi_prop_get_int(DDI_DEV_T_ANY, dip, 0,
"adv_autoneg_cap", 1);
mxfep->mxfe_adv_100T4 = ddi_prop_get_int(DDI_DEV_T_ANY, dip, 0,
"adv_100T4_cap", 1);
mxfep->mxfe_adv_100fdx = ddi_prop_get_int(DDI_DEV_T_ANY, dip, 0,
"adv_100fdx_cap", 1);
mxfep->mxfe_adv_100hdx = ddi_prop_get_int(DDI_DEV_T_ANY, dip, 0,
"adv_100hdx_cap", 1);
mxfep->mxfe_adv_10fdx = ddi_prop_get_int(DDI_DEV_T_ANY, dip, 0,
"adv_10fdx_cap", 1);
mxfep->mxfe_adv_10hdx = ddi_prop_get_int(DDI_DEV_T_ANY, dip, 0,
"adv_10hdx_cap", 1);
DBG(DPCI, "PCI vendor id = %x", venid);
DBG(DPCI, "PCI device id = %x", devid);
DBG(DPCI, "PCI revision id = %x", revid);
DBG(DPCI, "PCI cachesize = %d", cachesize);
DBG(DPCI, "PCI COMM = %x", pci_config_get8(pci, PCI_CMD));
DBG(DPCI, "PCI STAT = %x", pci_config_get8(pci, PCI_STAT));
mutex_init(&mxfep->mxfe_xmtlock, NULL, MUTEX_DRIVER,
mxfep->mxfe_icookie);
mutex_init(&mxfep->mxfe_intrlock, NULL, MUTEX_DRIVER,
mxfep->mxfe_icookie);
/*
* Enable bus master, IO space, and memory space accesses.
*/
pci_config_put16(pci, PCI_CMD,
pci_config_get16(pci, PCI_CMD) |
PCI_CMD_BME | PCI_CMD_MAE | PCI_CMD_MWIE);
/* we're done with this now, drop it */
pci_config_teardown(&pci);
/*
* Initialize interrupt kstat. This should not normally fail, since
* we don't use a persistent stat. We do it this way to avoid having
* to test for it at run time on the hot path.
*/
mxfep->mxfe_intrstat = kstat_create("mxfe", inst, "intr", "controller",
KSTAT_TYPE_INTR, 1, 0);
if (mxfep->mxfe_intrstat == NULL) {
mxfe_error(dip, "kstat_create failed");
goto failed;
}
kstat_install(mxfep->mxfe_intrstat);
/*
* Map in the device registers.
*/
if (ddi_regs_map_setup(dip, 1, (caddr_t *)&mxfep->mxfe_regs,
0, 0, &mxfe_devattr, &mxfep->mxfe_regshandle)) {
mxfe_error(dip, "ddi_regs_map_setup failed");
goto failed;
}
/*
* Allocate DMA resources (descriptor rings and buffers).
*/
if ((mxfe_allocrxring(mxfep) != DDI_SUCCESS) ||
(mxfe_alloctxring(mxfep) != DDI_SUCCESS)) {
mxfe_error(dip, "unable to allocate DMA resources");
goto failed;
}
/* Initialize the chip. */
mutex_enter(&mxfep->mxfe_intrlock);
mutex_enter(&mxfep->mxfe_xmtlock);
if (!mxfe_initialize(mxfep)) {
mutex_exit(&mxfep->mxfe_xmtlock);
mutex_exit(&mxfep->mxfe_intrlock);
goto failed;
}
mutex_exit(&mxfep->mxfe_xmtlock);
mutex_exit(&mxfep->mxfe_intrlock);
/* Determine the number of address bits to our EEPROM. */
mxfep->mxfe_sromwidth = mxfe_sromwidth(mxfep);
/*
* Get the factory ethernet address. This becomes the current
* ethernet address (it can be overridden later via ifconfig).
*/
mxfe_getfactaddr(mxfep, mxfep->mxfe_curraddr);
mxfep->mxfe_promisc = B_FALSE;
/*
* Establish interrupt handler.
*/
if (ddi_add_intr(dip, 0, NULL, NULL, mxfe_intr, (caddr_t)mxfep) !=
DDI_SUCCESS) {
mxfe_error(dip, "unable to add interrupt");
goto failed;
}
/* TODO: do the power management stuff */
if ((macp = mac_alloc(MAC_VERSION)) == NULL) {
mxfe_error(dip, "mac_alloc failed");
goto failed;
}
macp->m_type_ident = MAC_PLUGIN_IDENT_ETHER;
macp->m_driver = mxfep;
macp->m_dip = dip;
macp->m_src_addr = mxfep->mxfe_curraddr;
macp->m_callbacks = &mxfe_m_callbacks;
macp->m_min_sdu = 0;
macp->m_max_sdu = ETHERMTU;
macp->m_margin = VLAN_TAGSZ;
if (mac_register(macp, &mxfep->mxfe_mh) == DDI_SUCCESS) {
mac_free(macp);
return (DDI_SUCCESS);
}
/* failed to register with MAC */
mac_free(macp);
failed:
if (mxfep->mxfe_icookie != NULL) {
ddi_remove_intr(dip, 0, mxfep->mxfe_icookie);
}
if (mxfep->mxfe_intrstat) {
kstat_delete(mxfep->mxfe_intrstat);
}
mutex_destroy(&mxfep->mxfe_intrlock);
mutex_destroy(&mxfep->mxfe_xmtlock);
mxfe_freerxring(mxfep);
mxfe_freetxring(mxfep);
if (mxfep->mxfe_regshandle != NULL) {
ddi_regs_map_free(&mxfep->mxfe_regshandle);
}
kmem_free(mxfep, sizeof (mxfe_t));
return (DDI_FAILURE);
}
int
mxfe_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
mxfe_t *mxfep;
mxfep = ddi_get_driver_private(dip);
if (mxfep == NULL) {
mxfe_error(dip, "no soft state in detach!");
return (DDI_FAILURE);
}
switch (cmd) {
case DDI_DETACH:
if (mac_unregister(mxfep->mxfe_mh) != 0) {
return (DDI_FAILURE);
}
/* make sure hardware is quiesced */
mutex_enter(&mxfep->mxfe_intrlock);
mutex_enter(&mxfep->mxfe_xmtlock);
mxfep->mxfe_flags &= ~MXFE_RUNNING;
mxfe_stopall(mxfep);
mutex_exit(&mxfep->mxfe_xmtlock);
mutex_exit(&mxfep->mxfe_intrlock);
/* clean up and shut down device */
ddi_remove_intr(dip, 0, mxfep->mxfe_icookie);
/* clean up kstats */
kstat_delete(mxfep->mxfe_intrstat);
ddi_prop_remove_all(dip);
/* free up any left over buffers or DMA resources */
mxfe_freerxring(mxfep);
mxfe_freetxring(mxfep);
ddi_regs_map_free(&mxfep->mxfe_regshandle);
mutex_destroy(&mxfep->mxfe_intrlock);
mutex_destroy(&mxfep->mxfe_xmtlock);
kmem_free(mxfep, sizeof (mxfe_t));
return (DDI_SUCCESS);
case DDI_SUSPEND:
/* quiesce the hardware */
mutex_enter(&mxfep->mxfe_intrlock);
mutex_enter(&mxfep->mxfe_xmtlock);
mxfep->mxfe_flags |= MXFE_SUSPENDED;
mxfe_stopall(mxfep);
mutex_exit(&mxfep->mxfe_xmtlock);
mutex_exit(&mxfep->mxfe_intrlock);
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
int
mxfe_resume(dev_info_t *dip)
{
mxfe_t *mxfep;
if ((mxfep = ddi_get_driver_private(dip)) == NULL) {
return (DDI_FAILURE);
}
mutex_enter(&mxfep->mxfe_intrlock);
mutex_enter(&mxfep->mxfe_xmtlock);
mxfep->mxfe_flags &= ~MXFE_SUSPENDED;
/* re-initialize chip */
if (!mxfe_initialize(mxfep)) {
mxfe_error(mxfep->mxfe_dip, "unable to resume chip!");
mxfep->mxfe_flags |= MXFE_SUSPENDED;
mutex_exit(&mxfep->mxfe_intrlock);
mutex_exit(&mxfep->mxfe_xmtlock);
return (DDI_SUCCESS);
}
/* start the chip */
if (mxfep->mxfe_flags & MXFE_RUNNING) {
mxfe_startall(mxfep);
}
/* drop locks */
mutex_exit(&mxfep->mxfe_xmtlock);
mutex_exit(&mxfep->mxfe_intrlock);
return (DDI_SUCCESS);
}
/*ARGSUSED*/
int
mxfe_m_multicst(void *arg, boolean_t add, const uint8_t *macaddr)
{
/* we already receive all multicast frames */
return (0);
}
int
mxfe_m_promisc(void *arg, boolean_t on)
{
mxfe_t *mxfep = arg;
/* exclusive access to the card while we reprogram it */
mutex_enter(&mxfep->mxfe_intrlock);
mutex_enter(&mxfep->mxfe_xmtlock);
/* save current promiscuous mode state for replay in resume */
mxfep->mxfe_promisc = on;
if ((mxfep->mxfe_flags & (MXFE_RUNNING|MXFE_SUSPENDED)) ==
MXFE_RUNNING) {
if (on)
SETBIT(mxfep, CSR_NAR, NAR_RX_PROMISC);
else
CLRBIT(mxfep, CSR_NAR, NAR_RX_PROMISC);
}
mutex_exit(&mxfep->mxfe_xmtlock);
mutex_exit(&mxfep->mxfe_intrlock);
return (0);
}
int
mxfe_m_unicst(void *arg, const uint8_t *macaddr)
{
mxfe_t *mxfep = arg;
mutex_enter(&mxfep->mxfe_intrlock);
mutex_enter(&mxfep->mxfe_xmtlock);
bcopy(macaddr, mxfep->mxfe_curraddr, ETHERADDRL);
mxfe_resetall(mxfep);
mutex_exit(&mxfep->mxfe_intrlock);
mutex_exit(&mxfep->mxfe_xmtlock);
return (0);
}
mblk_t *
mxfe_m_tx(void *arg, mblk_t *mp)
{
mxfe_t *mxfep = arg;
mblk_t *nmp;
mutex_enter(&mxfep->mxfe_xmtlock);
if (mxfep->mxfe_flags & MXFE_SUSPENDED) {
mutex_exit(&mxfep->mxfe_xmtlock);
return (mp);
}
while (mp != NULL) {
nmp = mp->b_next;
mp->b_next = NULL;
if (!mxfe_send(mxfep, mp)) {
mp->b_next = nmp;
break;
}
mp = nmp;
}
mutex_exit(&mxfep->mxfe_xmtlock);
return (mp);
}
/*
* Hardware management.
*/
boolean_t
mxfe_initialize(mxfe_t *mxfep)
{
int i;
unsigned val;
uint32_t par, nar;
ASSERT(mutex_owned(&mxfep->mxfe_intrlock));
ASSERT(mutex_owned(&mxfep->mxfe_xmtlock));
DBG(DCHATTY, "resetting!");
SETBIT(mxfep, CSR_PAR, PAR_RESET);
for (i = 1; i < 10; i++) {
drv_usecwait(5);
val = GETCSR(mxfep, CSR_PAR);
if (!(val & PAR_RESET)) {
break;
}
}
if (i == 10) {
mxfe_error(mxfep->mxfe_dip, "timed out waiting for reset!");
return (B_FALSE);
}
/* initialize busctl register */
par = PAR_BAR | PAR_MRME | PAR_MRLE | PAR_MWIE;
/* set the cache alignment if its supported */
switch (mxfep->mxfe_cachesize) {
case 8:
par |= PAR_CALIGN_8;
break;
case 16:
par |= PAR_CALIGN_16;
break;
case 32:
par |= PAR_CALIGN_32;
break;
default:
par &= ~(PAR_MWIE | PAR_MRME | PAR_MRLE);
}
/* leave the burst length at zero, indicating infinite burst */
PUTCSR(mxfep, CSR_PAR, par);
mxfe_resetrings(mxfep);
/* clear the lost packet counter (cleared on read) */
(void) GETCSR(mxfep, CSR_LPC);
/* a few other NAR bits */
nar = GETCSR(mxfep, CSR_NAR);
nar &= ~NAR_RX_HO; /* disable hash only filtering */
nar |= NAR_RX_HP; /* hash perfect forwarding */
nar |= NAR_RX_MULTI; /* receive all multicast */
nar |= NAR_SF; /* store-and-forward */
if (mxfep->mxfe_promisc) {
nar |= NAR_RX_PROMISC;
} else {
nar &= ~NAR_RX_PROMISC;
}
PUTCSR(mxfep, CSR_NAR, nar);
mxfe_send_setup(mxfep);
return (B_TRUE);
}
/*
* Serial EEPROM access - inspired by the FreeBSD implementation.
*/
uint8_t
mxfe_sromwidth(mxfe_t *mxfep)
{
int i;
int eeread;
uint8_t addrlen = 8;
eeread = SPR_SROM_READ | SPR_SROM_SEL | SPR_SROM_CHIP;
PUTCSR(mxfep, CSR_SPR, eeread & ~SPR_SROM_CHIP);
drv_usecwait(1);
PUTCSR(mxfep, CSR_SPR, eeread);
/* command bits first */
for (i = 4; i != 0; i >>= 1) {
unsigned val = (SROM_READCMD & i) ? SPR_SROM_DIN : 0;
PUTCSR(mxfep, CSR_SPR, eeread | val);
drv_usecwait(1);
PUTCSR(mxfep, CSR_SPR, eeread | val | SPR_SROM_CLOCK);
drv_usecwait(1);
}
PUTCSR(mxfep, CSR_SPR, eeread);
for (addrlen = 1; addrlen <= 12; addrlen++) {
PUTCSR(mxfep, CSR_SPR, eeread | SPR_SROM_CLOCK);
drv_usecwait(1);
if (!(GETCSR(mxfep, CSR_SPR) & SPR_SROM_DOUT)) {
PUTCSR(mxfep, CSR_SPR, eeread);
drv_usecwait(1);
break;
}
PUTCSR(mxfep, CSR_SPR, eeread);
drv_usecwait(1);
}
/* turn off accesses to the EEPROM */
PUTCSR(mxfep, CSR_SPR, eeread &~ SPR_SROM_CHIP);
DBG(DSROM, "detected srom width = %d bits", addrlen);
return ((addrlen < 4 || addrlen > 12) ? 6 : addrlen);
}
/*
* The words in EEPROM are stored in little endian order. We
* shift bits out in big endian order, though. This requires
* a byte swap on some platforms.
*/
uint16_t
mxfe_readsromword(mxfe_t *mxfep, unsigned romaddr)
{
int i;
uint16_t word = 0;
uint16_t retval;
int eeread;
uint8_t addrlen;
int readcmd;
uchar_t *ptr;
eeread = SPR_SROM_READ | SPR_SROM_SEL | SPR_SROM_CHIP;
addrlen = mxfep->mxfe_sromwidth;
readcmd = (SROM_READCMD << addrlen) | romaddr;
if (romaddr >= (1 << addrlen)) {
/* too big to fit! */
return (0);
}
PUTCSR(mxfep, CSR_SPR, eeread & ~SPR_SROM_CHIP);
PUTCSR(mxfep, CSR_SPR, eeread);
/* command and address bits */
for (i = 4 + addrlen; i >= 0; i--) {
short val = (readcmd & (1 << i)) ? SPR_SROM_DIN : 0;
PUTCSR(mxfep, CSR_SPR, eeread | val);
drv_usecwait(1);
PUTCSR(mxfep, CSR_SPR, eeread | val | SPR_SROM_CLOCK);
drv_usecwait(1);
}
PUTCSR(mxfep, CSR_SPR, eeread);
for (i = 0; i < 16; i++) {
PUTCSR(mxfep, CSR_SPR, eeread | SPR_SROM_CLOCK);
drv_usecwait(1);
word <<= 1;
if (GETCSR(mxfep, CSR_SPR) & SPR_SROM_DOUT) {
word |= 1;
}
PUTCSR(mxfep, CSR_SPR, eeread);
drv_usecwait(1);
}
/* turn off accesses to the EEPROM */
PUTCSR(mxfep, CSR_SPR, eeread &~ SPR_SROM_CHIP);
/*
* Fix up the endianness thing. Note that the values
* are stored in little endian format on the SROM.
*/
DBG(DSROM, "got value %d from SROM (before swap)", word);
ptr = (uchar_t *)&word;
retval = (ptr[1] << 8) | ptr[0];
return (retval);
}
void
mxfe_readsrom(mxfe_t *mxfep, unsigned romaddr, unsigned len, void *dest)
{
char *ptr = dest;
int i;
uint16_t word;
for (i = 0; i < len; i++) {
word = mxfe_readsromword(mxfep, romaddr + i);
bcopy(&word, ptr, 2);
ptr += 2;
DBG(DSROM, "word at %d is 0x%x", romaddr + i, word);
}
}
void
mxfe_getfactaddr(mxfe_t *mxfep, uchar_t *eaddr)
{
uint16_t word;
uchar_t *ptr;
/* first read to get the location of mac address in srom */
word = mxfe_readsromword(mxfep, SROM_ENADDR / 2);
ptr = (uchar_t *)&word;
word = (ptr[1] << 8) | ptr[0];
/* then read the actual mac address */
mxfe_readsrom(mxfep, word / 2, ETHERADDRL / 2, eaddr);
DBG(DMACID,
"factory ethernet address = %02x:%02x:%02x:%02x:%02x:%02x",
eaddr[0], eaddr[1], eaddr[2], eaddr[3], eaddr[4], eaddr[5]);
}
void
mxfe_startphy(mxfe_t *mxfep)
{
switch (MXFE_MODEL(mxfep)) {
case MXFE_98713A:
mxfe_startphymii(mxfep);
break;
default:
mxfe_startphynway(mxfep);
break;
}
}
void
mxfe_stopphy(mxfe_t *mxfep)
{
uint32_t nar;
int i;
/* stop the phy timer */
PUTCSR(mxfep, CSR_TIMER, 0);
switch (MXFE_MODEL(mxfep)) {
case MXFE_98713A:
for (i = 0; i < 32; i++) {
mxfe_miiwrite(mxfep, mxfep->mxfe_phyaddr, MII_CONTROL,
MII_CONTROL_PWRDN | MII_CONTROL_ISOLATE);
}
break;
default:
DBG(DPHY, "resetting SIA");
PUTCSR(mxfep, CSR_SIA, SIA_RESET);
drv_usecwait(500);
CLRBIT(mxfep, CSR_TCTL, TCTL_PWR | TCTL_ANE);
nar = GETCSR(mxfep, CSR_NAR);
nar &= ~(NAR_PORTSEL | NAR_PCS | NAR_SCR | NAR_FDX);
nar |= NAR_SPEED;
PUTCSR(mxfep, CSR_NAR, nar);
break;
}
/*
* mark the link state unknown
*/
if (!mxfep->mxfe_resetting) {
mxfep->mxfe_linkup = LINK_STATE_UNKNOWN;
mxfep->mxfe_ifspeed = 0;
mxfep->mxfe_duplex = LINK_DUPLEX_UNKNOWN;
if (mxfep->mxfe_flags & MXFE_RUNNING)
mxfe_reportlink(mxfep);
}
}
/*
* NWay support.
*/
void
mxfe_startnway(mxfe_t *mxfep)
{
unsigned nar;
unsigned tctl;
unsigned restart;
/* this should not happen in a healthy system */
if (mxfep->mxfe_nwaystate != MXFE_NOLINK) {
DBG(DWARN, "link start called out of state (%x)",
mxfep->mxfe_nwaystate);
return;
}
if (mxfep->mxfe_adv_aneg == 0) {
/* not done for forced mode */
return;
}
nar = GETCSR(mxfep, CSR_NAR);
restart = nar & (NAR_TX_ENABLE | NAR_RX_ENABLE);
nar &= ~restart;
if (restart != 0)
mxfe_stopmac(mxfep);
nar |= NAR_SCR | NAR_PCS | NAR_HBD;
nar &= ~(NAR_FDX);
tctl = GETCSR(mxfep, CSR_TCTL);
tctl &= ~(TCTL_100FDX | TCTL_100HDX | TCTL_HDX);
if (mxfep->mxfe_adv_100fdx) {
tctl |= TCTL_100FDX;
}
if (mxfep->mxfe_adv_100hdx) {
tctl |= TCTL_100HDX;
}
if (mxfep->mxfe_adv_10fdx) {
nar |= NAR_FDX;
}
if (mxfep->mxfe_adv_10hdx) {
tctl |= TCTL_HDX;
}
tctl |= TCTL_PWR | TCTL_ANE | TCTL_LTE | TCTL_RSQ;
/* possibly we should add in support for PAUSE frames */
DBG(DPHY, "writing nar = 0x%x", nar);
PUTCSR(mxfep, CSR_NAR, nar);
DBG(DPHY, "writing tctl = 0x%x", tctl);
PUTCSR(mxfep, CSR_TCTL, tctl);
/* restart autonegotation */
DBG(DPHY, "writing tstat = 0x%x", TSTAT_ANS_START);
PUTCSR(mxfep, CSR_TSTAT, TSTAT_ANS_START);
/* restart tx/rx processes... */
if (restart != 0)
mxfe_startmac(mxfep);
/* Macronix initializations from Bolo Tsai */
PUTCSR(mxfep, CSR_MXMAGIC, 0x0b2c0000);
PUTCSR(mxfep, CSR_ACOMP, 0x11000);
mxfep->mxfe_nwaystate = MXFE_NWAYCHECK;
}
void
mxfe_checklinknway(mxfe_t *mxfep)
{
unsigned tstat;
uint16_t lpar;
DBG(DPHY, "NWay check, state %x", mxfep->mxfe_nwaystate);
tstat = GETCSR(mxfep, CSR_TSTAT);
lpar = TSTAT_LPAR(tstat);
mxfep->mxfe_anlpar = lpar;
if (tstat & TSTAT_LPN) {
mxfep->mxfe_aner |= MII_AN_EXP_LPCANAN;
} else {
mxfep->mxfe_aner &= ~(MII_AN_EXP_LPCANAN);
}
DBG(DPHY, "tstat(CSR12) = 0x%x", tstat);
DBG(DPHY, "ANEG state = 0x%x", (tstat & TSTAT_ANS) >> 12);
if ((tstat & TSTAT_ANS) != TSTAT_ANS_OK) {
/* autoneg did not complete */
mxfep->mxfe_bmsr &= ~MII_STATUS_ANDONE;
} else {
mxfep->mxfe_bmsr |= ~MII_STATUS_ANDONE;
}
if ((tstat & TSTAT_100F) && (tstat & TSTAT_10F)) {
mxfep->mxfe_linkup = LINK_STATE_DOWN;
mxfep->mxfe_ifspeed = 0;
mxfep->mxfe_duplex = LINK_DUPLEX_UNKNOWN;
mxfep->mxfe_nwaystate = MXFE_NOLINK;
mxfe_reportlink(mxfep);
mxfe_startnway(mxfep);
return;
}
/*
* if the link is newly up, then we might need to set various
* mode bits, or negotiate for parameters, etc.
*/
if (mxfep->mxfe_adv_aneg) {
uint16_t anlpar;
mxfep->mxfe_linkup = LINK_STATE_UP;
anlpar = mxfep->mxfe_anlpar;
if (tstat & TSTAT_LPN) {
/* partner has NWay */
if ((anlpar & MII_ABILITY_100BASE_TX_FD) &&
mxfep->mxfe_adv_100fdx) {
mxfep->mxfe_ifspeed = 100000000;
mxfep->mxfe_duplex = LINK_DUPLEX_FULL;
} else if ((anlpar & MII_ABILITY_100BASE_TX) &&
mxfep->mxfe_adv_100hdx) {
mxfep->mxfe_ifspeed = 100000000;
mxfep->mxfe_duplex = LINK_DUPLEX_HALF;
} else if ((anlpar & MII_ABILITY_10BASE_T_FD) &&
mxfep->mxfe_adv_10fdx) {
mxfep->mxfe_ifspeed = 10000000;
mxfep->mxfe_duplex = LINK_DUPLEX_FULL;
} else if ((anlpar & MII_ABILITY_10BASE_T) &&
mxfep->mxfe_adv_10hdx) {
mxfep->mxfe_ifspeed = 10000000;
mxfep->mxfe_duplex = LINK_DUPLEX_HALF;
} else {
mxfep->mxfe_ifspeed = 0;
}
} else {
/* link partner does not have NWay */
/* just assume half duplex, since we can't detect */
mxfep->mxfe_duplex = LINK_DUPLEX_HALF;
if (!(tstat & TSTAT_100F)) {
DBG(DPHY, "Partner doesn't have NWAY");
mxfep->mxfe_ifspeed = 100000000;
} else {
mxfep->mxfe_ifspeed = 10000000;
}
}
} else {
/* forced modes */
mxfep->mxfe_linkup = LINK_STATE_UP;
if (mxfep->mxfe_adv_100fdx) {
mxfep->mxfe_ifspeed = 100000000;
mxfep->mxfe_duplex = LINK_DUPLEX_FULL;
} else if (mxfep->mxfe_adv_100hdx) {
mxfep->mxfe_ifspeed = 100000000;
mxfep->mxfe_duplex = LINK_DUPLEX_HALF;
} else if (mxfep->mxfe_adv_10fdx) {
mxfep->mxfe_ifspeed = 10000000;
mxfep->mxfe_duplex = LINK_DUPLEX_FULL;
} else if (mxfep->mxfe_adv_10hdx) {
mxfep->mxfe_ifspeed = 10000000;
mxfep->mxfe_duplex = LINK_DUPLEX_HALF;
} else {
mxfep->mxfe_ifspeed = 0;
}
}
mxfe_reportlink(mxfep);
mxfep->mxfe_nwaystate = MXFE_GOODLINK;
}
void
mxfe_startphynway(mxfe_t *mxfep)
{
/* take NWay and PHY out of reset */
PUTCSR(mxfep, CSR_SIA, SIA_NRESET);
drv_usecwait(500);
mxfep->mxfe_nwaystate = MXFE_NOLINK;
mxfep->mxfe_bmsr = MII_STATUS_CANAUTONEG |
MII_STATUS_100_BASEX_FD | MII_STATUS_100_BASEX |
MII_STATUS_10_FD | MII_STATUS_10;
mxfep->mxfe_cap_aneg =
mxfep->mxfe_cap_100fdx = mxfep->mxfe_cap_100hdx =
mxfep->mxfe_cap_10fdx = mxfep->mxfe_cap_10hdx = 1;
/* lie about the transceiver... its not really 802.3u compliant */
mxfep->mxfe_phyaddr = 0;
mxfep->mxfe_phyinuse = XCVR_100X;
mxfep->mxfe_phyid = 0;
/* 100-T4 not supported with NWay */
mxfep->mxfe_adv_100T4 = 0;
mxfep->mxfe_cap_100T4 = 0;
/* make sure at least one valid mode is selected */
if ((!mxfep->mxfe_adv_100fdx) &&
(!mxfep->mxfe_adv_100hdx) &&
(!mxfep->mxfe_adv_10fdx) &&
(!mxfep->mxfe_adv_10hdx)) {
mxfe_error(mxfep->mxfe_dip, "No valid link mode selected.");
mxfe_error(mxfep->mxfe_dip, "Powering down PHY.");
mxfe_stopphy(mxfep);
mxfep->mxfe_linkup = LINK_STATE_DOWN;
if (mxfep->mxfe_flags & MXFE_RUNNING)
mxfe_reportlink(mxfep);
return;
}
if (mxfep->mxfe_adv_aneg == 0) {
/* forced mode */
unsigned nar;
unsigned tctl;
nar = GETCSR(mxfep, CSR_NAR);
tctl = GETCSR(mxfep, CSR_TCTL);
ASSERT((nar & (NAR_TX_ENABLE | NAR_RX_ENABLE)) == 0);
nar &= ~(NAR_FDX | NAR_PORTSEL | NAR_SCR | NAR_SPEED);
tctl &= ~TCTL_ANE;
if (mxfep->mxfe_adv_100fdx) {
nar |= NAR_PORTSEL | NAR_PCS | NAR_SCR | NAR_FDX;
} else if (mxfep->mxfe_adv_100hdx) {
nar |= NAR_PORTSEL | NAR_PCS | NAR_SCR;
} else if (mxfep->mxfe_adv_10fdx) {
nar |= NAR_FDX | NAR_SPEED;
} else { /* mxfep->mxfe_adv_10hdx */
nar |= NAR_SPEED;
}
PUTCSR(mxfep, CSR_NAR, nar);
PUTCSR(mxfep, CSR_TCTL, tctl);
/* Macronix initializations from Bolo Tsai */
PUTCSR(mxfep, CSR_MXMAGIC, 0x0b2c0000);
PUTCSR(mxfep, CSR_ACOMP, 0x11000);
} else {
mxfe_startnway(mxfep);
}
PUTCSR(mxfep, CSR_TIMER, TIMER_LOOP |
(MXFE_LINKTIMER * 1000 / TIMER_USEC));
}
/*
* MII management.
*/
void
mxfe_startphymii(mxfe_t *mxfep)
{
unsigned phyaddr;
unsigned bmcr;
unsigned bmsr;
unsigned anar;
unsigned phyidr1;
unsigned phyidr2;
int retries;
int cnt;
mxfep->mxfe_phyaddr = -1;
/* search for first PHY we can find */
for (phyaddr = 0; phyaddr < 32; phyaddr++) {
bmsr = mxfe_miiread(mxfep, phyaddr, MII_STATUS);
if ((bmsr != 0) && (bmsr != 0xffff)) {
mxfep->mxfe_phyaddr = phyaddr;
break;
}
}
phyidr1 = mxfe_miiread(mxfep, phyaddr, MII_PHYIDH);
phyidr2 = mxfe_miiread(mxfep, phyaddr, MII_PHYIDL);
mxfep->mxfe_phyid = (phyidr1 << 16) | (phyidr2);
/*
* Generally, all Macronix based devices use an internal
* 100BASE-TX internal transceiver. If we ever run into a
* variation on this, then the following logic will need to be
* enhanced.
*
* One could question the value of the XCVR_INUSE field in the
* MII statistics.
*/
if (bmsr & MII_STATUS_100_BASE_T4) {
mxfep->mxfe_phyinuse = XCVR_100T4;
} else {
mxfep->mxfe_phyinuse = XCVR_100X;
}
/* assume we support everything to start */
mxfep->mxfe_cap_aneg = mxfep->mxfe_cap_100T4 =
mxfep->mxfe_cap_100fdx = mxfep->mxfe_cap_100hdx =
mxfep->mxfe_cap_10fdx = mxfep->mxfe_cap_10hdx = 1;
DBG(DPHY, "phy at %d: %x,%x", phyaddr, phyidr1, phyidr2);
DBG(DPHY, "bmsr = %x", mxfe_miiread(mxfep,
mxfep->mxfe_phyaddr, MII_STATUS));
DBG(DPHY, "anar = %x", mxfe_miiread(mxfep,
mxfep->mxfe_phyaddr, MII_AN_ADVERT));
DBG(DPHY, "anlpar = %x", mxfe_miiread(mxfep,
mxfep->mxfe_phyaddr, MII_AN_LPABLE));
DBG(DPHY, "aner = %x", mxfe_miiread(mxfep,
mxfep->mxfe_phyaddr, MII_AN_EXPANSION));
DBG(DPHY, "resetting phy");
/* we reset the phy block */
mxfe_miiwrite(mxfep, phyaddr, MII_CONTROL, MII_CONTROL_RESET);
/*
* wait for it to complete -- 500usec is still to short to
* bother getting the system clock involved.
*/
drv_usecwait(500);
for (retries = 0; retries < 10; retries++) {
if (mxfe_miiread(mxfep, phyaddr, MII_CONTROL) &
MII_CONTROL_RESET) {
drv_usecwait(500);
continue;
}
break;
}
if (retries == 100) {
mxfe_error(mxfep->mxfe_dip, "timeout waiting on phy to reset");
return;
}
DBG(DPHY, "phy reset complete");
bmsr = mxfe_miiread(mxfep, phyaddr, MII_STATUS);
bmcr = mxfe_miiread(mxfep, phyaddr, MII_CONTROL);
anar = mxfe_miiread(mxfep, phyaddr, MII_AN_ADVERT);
anar &= ~(MII_ABILITY_100BASE_T4 |
MII_ABILITY_100BASE_TX_FD | MII_ABILITY_100BASE_TX |
MII_ABILITY_10BASE_T_FD | MII_ABILITY_10BASE_T);
/* disable modes not supported in hardware */
if (!(bmsr & MII_STATUS_100_BASE_T4)) {
mxfep->mxfe_adv_100T4 = 0;
mxfep->mxfe_cap_100T4 = 0;
}
if (!(bmsr & MII_STATUS_100_BASEX_FD)) {
mxfep->mxfe_adv_100fdx = 0;
mxfep->mxfe_cap_100fdx = 0;
}
if (!(bmsr & MII_STATUS_100_BASEX)) {
mxfep->mxfe_adv_100hdx = 0;
mxfep->mxfe_cap_100hdx = 0;
}
if (!(bmsr & MII_STATUS_10_FD)) {
mxfep->mxfe_adv_10fdx = 0;
mxfep->mxfe_cap_10fdx = 0;
}
if (!(bmsr & MII_STATUS_10)) {
mxfep->mxfe_adv_10hdx = 0;
mxfep->mxfe_cap_10hdx = 0;
}
if (!(bmsr & MII_STATUS_CANAUTONEG)) {
mxfep->mxfe_adv_aneg = 0;
mxfep->mxfe_cap_aneg = 0;
}
cnt = 0;
if (mxfep->mxfe_adv_100T4) {
anar |= MII_ABILITY_100BASE_T4;
cnt++;
}
if (mxfep->mxfe_adv_100fdx) {
anar |= MII_ABILITY_100BASE_TX_FD;
cnt++;
}
if (mxfep->mxfe_adv_100hdx) {
anar |= MII_ABILITY_100BASE_TX;
cnt++;
}
if (mxfep->mxfe_adv_10fdx) {
anar |= MII_ABILITY_10BASE_T_FD;
cnt++;
}
if (mxfep->mxfe_adv_10hdx) {
anar |= MII_ABILITY_10BASE_T;
cnt++;
}
/*
* Make certain at least one valid link mode is selected.
*/
if (!cnt) {
mxfe_error(mxfep->mxfe_dip, "No valid link mode selected.");
mxfe_error(mxfep->mxfe_dip, "Powering down PHY.");
mxfe_stopphy(mxfep);
mxfep->mxfe_linkup = LINK_STATE_DOWN;
if (mxfep->mxfe_flags & MXFE_RUNNING)
mxfe_reportlink(mxfep);
return;
}
if ((mxfep->mxfe_adv_aneg) && (bmsr & MII_STATUS_CANAUTONEG)) {
DBG(DPHY, "using autoneg mode");
bmcr = (MII_CONTROL_ANE | MII_CONTROL_RSAN);
} else {
DBG(DPHY, "using forced mode");
if (mxfep->mxfe_adv_100fdx) {
bmcr = (MII_CONTROL_100MB | MII_CONTROL_FDUPLEX);
} else if (mxfep->mxfe_adv_100hdx) {
bmcr = MII_CONTROL_100MB;
} else if (mxfep->mxfe_adv_10fdx) {
bmcr = MII_CONTROL_FDUPLEX;
} else {
/* 10HDX */
bmcr = 0;
}
}
DBG(DPHY, "programming anar to 0x%x", anar);
mxfe_miiwrite(mxfep, phyaddr, MII_AN_ADVERT, anar);
DBG(DPHY, "programming bmcr to 0x%x", bmcr);
mxfe_miiwrite(mxfep, phyaddr, MII_CONTROL, bmcr);
/*
* schedule a query of the link status
*/
PUTCSR(mxfep, CSR_TIMER, TIMER_LOOP |
(MXFE_LINKTIMER * 1000 / TIMER_USEC));
}
void
mxfe_reportlink(mxfe_t *mxfep)
{
int changed = 0;
if (mxfep->mxfe_ifspeed != mxfep->mxfe_lastifspeed) {
mxfep->mxfe_lastifspeed = mxfep->mxfe_ifspeed;
changed++;
}
if (mxfep->mxfe_duplex != mxfep->mxfe_lastduplex) {
mxfep->mxfe_lastduplex = mxfep->mxfe_duplex;
changed++;
}
if (mxfep->mxfe_linkup != mxfep->mxfe_lastlinkup) {
mxfep->mxfe_lastlinkup = mxfep->mxfe_linkup;
changed++;
}
if (changed)
mac_link_update(mxfep->mxfe_mh, mxfep->mxfe_linkup);
}
void
mxfe_checklink(mxfe_t *mxfep)
{
if ((mxfep->mxfe_flags & MXFE_RUNNING) == 0)
return;
if ((mxfep->mxfe_txstall_time != 0) &&
(gethrtime() > mxfep->mxfe_txstall_time) &&
(mxfep->mxfe_txavail != MXFE_TXRING)) {
mxfep->mxfe_txstall_time = 0;
mxfe_error(mxfep->mxfe_dip, "TX stall detected!");
mxfe_resetall(mxfep);
return;
}
switch (MXFE_MODEL(mxfep)) {
case MXFE_98713A:
mxfe_checklinkmii(mxfep);
break;
default:
mxfe_checklinknway(mxfep);
}
}
void
mxfe_checklinkmii(mxfe_t *mxfep)
{
/* read MII state registers */
uint16_t bmsr;
uint16_t bmcr;
uint16_t anar;
uint16_t anlpar;
uint16_t aner;
/* read this twice, to clear latched link state */
bmsr = mxfe_miiread(mxfep, mxfep->mxfe_phyaddr, MII_STATUS);
bmsr = mxfe_miiread(mxfep, mxfep->mxfe_phyaddr, MII_STATUS);
bmcr = mxfe_miiread(mxfep, mxfep->mxfe_phyaddr, MII_CONTROL);
anar = mxfe_miiread(mxfep, mxfep->mxfe_phyaddr, MII_AN_ADVERT);
anlpar = mxfe_miiread(mxfep, mxfep->mxfe_phyaddr, MII_AN_LPABLE);
aner = mxfe_miiread(mxfep, mxfep->mxfe_phyaddr, MII_AN_EXPANSION);
mxfep->mxfe_bmsr = bmsr;
mxfep->mxfe_anlpar = anlpar;
mxfep->mxfe_aner = aner;
if (bmsr & MII_STATUS_REMFAULT) {
mxfe_error(mxfep->mxfe_dip, "Remote fault detected.");
}
if (bmsr & MII_STATUS_JABBERING) {
mxfe_error(mxfep->mxfe_dip, "Jabber condition detected.");
}
if ((bmsr & MII_STATUS_LINKUP) == 0) {
/* no link */
mxfep->mxfe_ifspeed = 0;
mxfep->mxfe_duplex = LINK_DUPLEX_UNKNOWN;
mxfep->mxfe_linkup = LINK_STATE_DOWN;
mxfe_reportlink(mxfep);
return;
}
DBG(DCHATTY, "link up!");
mxfep->mxfe_linkup = LINK_STATE_UP;
if (!(bmcr & MII_CONTROL_ANE)) {
/* forced mode */
if (bmcr & MII_CONTROL_100MB) {
mxfep->mxfe_ifspeed = 100000000;
} else {
mxfep->mxfe_ifspeed = 10000000;
}
if (bmcr & MII_CONTROL_FDUPLEX) {
mxfep->mxfe_duplex = LINK_DUPLEX_FULL;
} else {
mxfep->mxfe_duplex = LINK_DUPLEX_HALF;
}
} else if ((!(bmsr & MII_STATUS_CANAUTONEG)) ||
(!(bmsr & MII_STATUS_ANDONE))) {
mxfep->mxfe_ifspeed = 0;
mxfep->mxfe_duplex = LINK_DUPLEX_UNKNOWN;
} else if (anar & anlpar & MII_ABILITY_100BASE_TX_FD) {
mxfep->mxfe_ifspeed = 100000000;
mxfep->mxfe_duplex = LINK_DUPLEX_FULL;
} else if (anar & anlpar & MII_ABILITY_100BASE_T4) {
mxfep->mxfe_ifspeed = 100000000;
mxfep->mxfe_duplex = LINK_DUPLEX_HALF;
} else if (anar & anlpar & MII_ABILITY_100BASE_TX) {
mxfep->mxfe_ifspeed = 100000000;
mxfep->mxfe_duplex = LINK_DUPLEX_HALF;
} else if (anar & anlpar & MII_ABILITY_10BASE_T_FD) {
mxfep->mxfe_ifspeed = 10000000;
mxfep->mxfe_duplex = LINK_DUPLEX_FULL;
} else if (anar & anlpar & MII_ABILITY_10BASE_T) {
mxfep->mxfe_ifspeed = 10000000;
mxfep->mxfe_duplex = LINK_DUPLEX_HALF;
} else {
mxfep->mxfe_ifspeed = 0;
mxfep->mxfe_duplex = LINK_DUPLEX_UNKNOWN;
}
mxfe_reportlink(mxfep);
}
void
mxfe_miitristate(mxfe_t *mxfep)
{
unsigned val = SPR_SROM_WRITE | SPR_MII_CTRL;
PUTCSR(mxfep, CSR_SPR, val);
drv_usecwait(1);
PUTCSR(mxfep, CSR_SPR, val | SPR_MII_CLOCK);
drv_usecwait(1);
}
void
mxfe_miiwritebit(mxfe_t *mxfep, uint8_t bit)
{
unsigned val = bit ? SPR_MII_DOUT : 0;
PUTCSR(mxfep, CSR_SPR, val);
drv_usecwait(1);
PUTCSR(mxfep, CSR_SPR, val | SPR_MII_CLOCK);
drv_usecwait(1);
}
uint8_t
mxfe_miireadbit(mxfe_t *mxfep)
{
unsigned val = SPR_MII_CTRL | SPR_SROM_READ;
uint8_t bit;
PUTCSR(mxfep, CSR_SPR, val);
drv_usecwait(1);
bit = (GETCSR(mxfep, CSR_SPR) & SPR_MII_DIN) ? 1 : 0;
PUTCSR(mxfep, CSR_SPR, val | SPR_MII_CLOCK);
drv_usecwait(1);
return (bit);
}
uint16_t
mxfe_miiread(mxfe_t *mxfep, int phy, int reg)
{
switch (MXFE_MODEL(mxfep)) {
case MXFE_98713A:
return (mxfe_miiread98713(mxfep, phy, reg));
default:
return (0xffff);
}
}
uint16_t
mxfe_miireadgeneral(mxfe_t *mxfep, int phy, int reg)
{
uint16_t value = 0;
int i;
/* send the 32 bit preamble */
for (i = 0; i < 32; i++) {
mxfe_miiwritebit(mxfep, 1);
}
/* send the start code - 01b */
mxfe_miiwritebit(mxfep, 0);
mxfe_miiwritebit(mxfep, 1);
/* send the opcode for read, - 10b */
mxfe_miiwritebit(mxfep, 1);
mxfe_miiwritebit(mxfep, 0);
/* next we send the 5 bit phy address */
for (i = 0x10; i > 0; i >>= 1) {
mxfe_miiwritebit(mxfep, (phy & i) ? 1 : 0);
}
/* the 5 bit register address goes next */
for (i = 0x10; i > 0; i >>= 1) {
mxfe_miiwritebit(mxfep, (reg & i) ? 1 : 0);
}
/* turnaround - tristate followed by logic 0 */
mxfe_miitristate(mxfep);
mxfe_miiwritebit(mxfep, 0);
/* read the 16 bit register value */
for (i = 0x8000; i > 0; i >>= 1) {
value <<= 1;
value |= mxfe_miireadbit(mxfep);
}
mxfe_miitristate(mxfep);
return (value);
}
uint16_t
mxfe_miiread98713(mxfe_t *mxfep, int phy, int reg)
{
unsigned nar;
uint16_t retval;
/*
* like an ordinary MII, but we have to turn off portsel while
* we read it.
*/
nar = GETCSR(mxfep, CSR_NAR);
PUTCSR(mxfep, CSR_NAR, nar & ~NAR_PORTSEL);
retval = mxfe_miireadgeneral(mxfep, phy, reg);
PUTCSR(mxfep, CSR_NAR, nar);
return (retval);
}
void
mxfe_miiwrite(mxfe_t *mxfep, int phy, int reg, uint16_t val)
{
switch (MXFE_MODEL(mxfep)) {
case MXFE_98713A:
mxfe_miiwrite98713(mxfep, phy, reg, val);
break;
default:
break;
}
}
void
mxfe_miiwritegeneral(mxfe_t *mxfep, int phy, int reg, uint16_t val)
{
int i;
/* send the 32 bit preamble */
for (i = 0; i < 32; i++) {
mxfe_miiwritebit(mxfep, 1);
}
/* send the start code - 01b */
mxfe_miiwritebit(mxfep, 0);
mxfe_miiwritebit(mxfep, 1);
/* send the opcode for write, - 01b */
mxfe_miiwritebit(mxfep, 0);
mxfe_miiwritebit(mxfep, 1);
/* next we send the 5 bit phy address */
for (i = 0x10; i > 0; i >>= 1) {
mxfe_miiwritebit(mxfep, (phy & i) ? 1 : 0);
}
/* the 5 bit register address goes next */
for (i = 0x10; i > 0; i >>= 1) {
mxfe_miiwritebit(mxfep, (reg & i) ? 1 : 0);
}
/* turnaround - tristate followed by logic 0 */
mxfe_miitristate(mxfep);
mxfe_miiwritebit(mxfep, 0);
/* now write out our data (16 bits) */
for (i = 0x8000; i > 0; i >>= 1) {
mxfe_miiwritebit(mxfep, (val & i) ? 1 : 0);
}
/* idle mode */
mxfe_miitristate(mxfep);
}
void
mxfe_miiwrite98713(mxfe_t *mxfep, int phy, int reg, uint16_t val)
{
unsigned nar;
/*
* like an ordinary MII, but we have to turn off portsel while
* we read it.
*/
nar = GETCSR(mxfep, CSR_NAR);
PUTCSR(mxfep, CSR_NAR, nar & ~NAR_PORTSEL);
mxfe_miiwritegeneral(mxfep, phy, reg, val);
PUTCSR(mxfep, CSR_NAR, nar);
}
int
mxfe_m_start(void *arg)
{
mxfe_t *mxfep = arg;
/* grab exclusive access to the card */
mutex_enter(&mxfep->mxfe_intrlock);
mutex_enter(&mxfep->mxfe_xmtlock);
mxfe_startall(mxfep);
mxfep->mxfe_flags |= MXFE_RUNNING;
mutex_exit(&mxfep->mxfe_xmtlock);
mutex_exit(&mxfep->mxfe_intrlock);
return (0);
}
void
mxfe_m_stop(void *arg)
{
mxfe_t *mxfep = arg;
/* exclusive access to the hardware! */
mutex_enter(&mxfep->mxfe_intrlock);
mutex_enter(&mxfep->mxfe_xmtlock);
mxfe_stopall(mxfep);
mxfep->mxfe_flags &= ~MXFE_RUNNING;
mutex_exit(&mxfep->mxfe_xmtlock);
mutex_exit(&mxfep->mxfe_intrlock);
}
void
mxfe_startmac(mxfe_t *mxfep)
{
/* verify exclusive access to the card */
ASSERT(mutex_owned(&mxfep->mxfe_intrlock));
ASSERT(mutex_owned(&mxfep->mxfe_xmtlock));
/* start the card */
SETBIT(mxfep, CSR_NAR, NAR_TX_ENABLE | NAR_RX_ENABLE);
if (mxfep->mxfe_txavail != MXFE_TXRING)
PUTCSR(mxfep, CSR_TDR, 0);
/* tell the mac that we are ready to go! */
if (mxfep->mxfe_flags & MXFE_RUNNING)
mac_tx_update(mxfep->mxfe_mh);
}
void
mxfe_stopmac(mxfe_t *mxfep)
{
int i;
/* exclusive access to the hardware! */
ASSERT(mutex_owned(&mxfep->mxfe_intrlock));
ASSERT(mutex_owned(&mxfep->mxfe_xmtlock));
CLRBIT(mxfep, CSR_NAR, NAR_TX_ENABLE | NAR_RX_ENABLE);
/*
* A 1518 byte frame at 10Mbps takes about 1.2 msec to drain.
* We just add up to the nearest msec (2), which should be
* plenty to complete.
*
* Note that some chips never seem to indicate the transition to
* the stopped state properly. Experience shows that we can safely
* proceed anyway, after waiting the requisite timeout.
*/
for (i = 2000; i != 0; i -= 10) {
if ((GETCSR(mxfep, CSR_SR) & (SR_TX_STATE | SR_RX_STATE)) == 0)
break;
drv_usecwait(10);
}
/* prevent an interrupt */
PUTCSR(mxfep, CSR_SR, INT_RXSTOPPED | INT_TXSTOPPED);
}
void
mxfe_resetrings(mxfe_t *mxfep)
{
int i;
/* now we need to reset the pointers... */
PUTCSR(mxfep, CSR_RDB, 0);
PUTCSR(mxfep, CSR_TDB, 0);
/* reset the descriptor ring pointers */
mxfep->mxfe_rxhead = 0;
mxfep->mxfe_txreclaim = 0;
mxfep->mxfe_txsend = 0;
mxfep->mxfe_txavail = MXFE_TXRING;
/* set up transmit descriptor ring */
for (i = 0; i < MXFE_TXRING; i++) {
mxfe_desc_t *tmdp = &mxfep->mxfe_txdescp[i];
unsigned control = 0;
if (i == (MXFE_TXRING - 1)) {
control |= TXCTL_ENDRING;
}
PUTTXDESC(mxfep, tmdp->desc_status, 0);
PUTTXDESC(mxfep, tmdp->desc_control, control);
PUTTXDESC(mxfep, tmdp->desc_buffer1, 0);
PUTTXDESC(mxfep, tmdp->desc_buffer2, 0);
SYNCTXDESC(mxfep, i, DDI_DMA_SYNC_FORDEV);
}
PUTCSR(mxfep, CSR_TDB, mxfep->mxfe_txdesc_paddr);
/* make the receive buffers available */
for (i = 0; i < MXFE_RXRING; i++) {
mxfe_rxbuf_t *rxb = mxfep->mxfe_rxbufs[i];
mxfe_desc_t *rmdp = &mxfep->mxfe_rxdescp[i];
unsigned control;
control = MXFE_BUFSZ & RXCTL_BUFLEN1;
if (i == (MXFE_RXRING - 1)) {
control |= RXCTL_ENDRING;
}
PUTRXDESC(mxfep, rmdp->desc_buffer1, rxb->rxb_paddr);
PUTRXDESC(mxfep, rmdp->desc_buffer2, 0);
PUTRXDESC(mxfep, rmdp->desc_control, control);
PUTRXDESC(mxfep, rmdp->desc_status, RXSTAT_OWN);
SYNCRXDESC(mxfep, i, DDI_DMA_SYNC_FORDEV);
}
PUTCSR(mxfep, CSR_RDB, mxfep->mxfe_rxdesc_paddr);
}
void
mxfe_stopall(mxfe_t *mxfep)
{
mxfe_disableinterrupts(mxfep);
mxfe_stopmac(mxfep);
/* stop the phy */
mxfe_stopphy(mxfep);
}
void
mxfe_startall(mxfe_t *mxfep)
{
ASSERT(mutex_owned(&mxfep->mxfe_intrlock));
ASSERT(mutex_owned(&mxfep->mxfe_xmtlock));
/* make sure interrupts are disabled to begin */
mxfe_disableinterrupts(mxfep);
/* initialize the chip */
(void) mxfe_initialize(mxfep);
/* now we can enable interrupts */
mxfe_enableinterrupts(mxfep);
/* start up the phy */
mxfe_startphy(mxfep);
/* start up the mac */
mxfe_startmac(mxfep);
}
void
mxfe_resetall(mxfe_t *mxfep)
{
mxfep->mxfe_resetting = B_TRUE;
mxfe_stopall(mxfep);
mxfep->mxfe_resetting = B_FALSE;
mxfe_startall(mxfep);
}
mxfe_txbuf_t *
mxfe_alloctxbuf(mxfe_t *mxfep)
{
ddi_dma_cookie_t dmac;
unsigned ncookies;
mxfe_txbuf_t *txb;
size_t len;
txb = kmem_zalloc(sizeof (*txb), KM_SLEEP);
if (ddi_dma_alloc_handle(mxfep->mxfe_dip, &mxfe_dma_txattr,
DDI_DMA_SLEEP, NULL, &txb->txb_dmah) != DDI_SUCCESS) {
return (NULL);
}
if (ddi_dma_mem_alloc(txb->txb_dmah, MXFE_BUFSZ, &mxfe_bufattr,
DDI_DMA_STREAMING, DDI_DMA_SLEEP, NULL, &txb->txb_buf,
&len, &txb->txb_acch) != DDI_SUCCESS) {
return (NULL);
}
if (ddi_dma_addr_bind_handle(txb->txb_dmah, NULL, txb->txb_buf,
len, DDI_DMA_WRITE | DDI_DMA_STREAMING, DDI_DMA_SLEEP, NULL,
&dmac, &ncookies) != DDI_DMA_MAPPED) {
return (NULL);
}
txb->txb_paddr = dmac.dmac_address;
return (txb);
}
void
mxfe_destroytxbuf(mxfe_txbuf_t *txb)
{
if (txb != NULL) {
if (txb->txb_paddr)
(void) ddi_dma_unbind_handle(txb->txb_dmah);
if (txb->txb_acch)
ddi_dma_mem_free(&txb->txb_acch);
if (txb->txb_dmah)
ddi_dma_free_handle(&txb->txb_dmah);
kmem_free(txb, sizeof (*txb));
}
}
mxfe_rxbuf_t *
mxfe_allocrxbuf(mxfe_t *mxfep)
{
mxfe_rxbuf_t *rxb;
size_t len;
unsigned ccnt;
ddi_dma_cookie_t dmac;
rxb = kmem_zalloc(sizeof (*rxb), KM_SLEEP);
if (ddi_dma_alloc_handle(mxfep->mxfe_dip, &mxfe_dma_attr,
DDI_DMA_SLEEP, NULL, &rxb->rxb_dmah) != DDI_SUCCESS) {
kmem_free(rxb, sizeof (*rxb));
return (NULL);
}
if (ddi_dma_mem_alloc(rxb->rxb_dmah, MXFE_BUFSZ, &mxfe_bufattr,
DDI_DMA_STREAMING, DDI_DMA_SLEEP, NULL,
&rxb->rxb_buf, &len, &rxb->rxb_acch) != DDI_SUCCESS) {
ddi_dma_free_handle(&rxb->rxb_dmah);
kmem_free(rxb, sizeof (*rxb));
return (NULL);
}
if (ddi_dma_addr_bind_handle(rxb->rxb_dmah, NULL, rxb->rxb_buf, len,
DDI_DMA_READ | DDI_DMA_STREAMING, DDI_DMA_SLEEP, NULL, &dmac,
&ccnt) != DDI_DMA_MAPPED) {
ddi_dma_mem_free(&rxb->rxb_acch);
ddi_dma_free_handle(&rxb->rxb_dmah);
kmem_free(rxb, sizeof (*rxb));
return (NULL);
}
rxb->rxb_paddr = dmac.dmac_address;
return (rxb);
}
void
mxfe_destroyrxbuf(mxfe_rxbuf_t *rxb)
{
if (rxb != NULL) {
(void) ddi_dma_unbind_handle(rxb->rxb_dmah);
ddi_dma_mem_free(&rxb->rxb_acch);
ddi_dma_free_handle(&rxb->rxb_dmah);
kmem_free(rxb, sizeof (*rxb));
}
}
/*
* Allocate receive resources.
*/
int
mxfe_allocrxring(mxfe_t *mxfep)
{
int rval;
int i;
size_t size;
size_t len;
ddi_dma_cookie_t dmac;
unsigned ncookies;
caddr_t kaddr;
size = MXFE_RXRING * sizeof (mxfe_desc_t);
rval = ddi_dma_alloc_handle(mxfep->mxfe_dip, &mxfe_dma_attr,
DDI_DMA_SLEEP, NULL, &mxfep->mxfe_rxdesc_dmah);
if (rval != DDI_SUCCESS) {
mxfe_error(mxfep->mxfe_dip,
"unable to allocate DMA handle for rx descriptors");
return (DDI_FAILURE);
}
rval = ddi_dma_mem_alloc(mxfep->mxfe_rxdesc_dmah, size, &mxfe_devattr,
DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &kaddr, &len,
&mxfep->mxfe_rxdesc_acch);
if (rval != DDI_SUCCESS) {
mxfe_error(mxfep->mxfe_dip,
"unable to allocate DMA memory for rx descriptors");
return (DDI_FAILURE);
}
rval = ddi_dma_addr_bind_handle(mxfep->mxfe_rxdesc_dmah, NULL, kaddr,
size, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL,
&dmac, &ncookies);
if (rval != DDI_DMA_MAPPED) {
mxfe_error(mxfep->mxfe_dip,
"unable to bind DMA for rx descriptors");
return (DDI_FAILURE);
}
/* because of mxfe_dma_attr */
ASSERT(ncookies == 1);
/* we take the 32-bit physical address out of the cookie */
mxfep->mxfe_rxdesc_paddr = dmac.dmac_address;
mxfep->mxfe_rxdescp = (void *)kaddr;
/* allocate buffer pointers (not the buffers themselves, yet) */
mxfep->mxfe_rxbufs = kmem_zalloc(MXFE_RXRING * sizeof (mxfe_rxbuf_t *),
KM_SLEEP);
/* now allocate rx buffers */
for (i = 0; i < MXFE_RXRING; i++) {
mxfe_rxbuf_t *rxb = mxfe_allocrxbuf(mxfep);
if (rxb == NULL)
return (DDI_FAILURE);
mxfep->mxfe_rxbufs[i] = rxb;
}
return (DDI_SUCCESS);
}
/*
* Allocate transmit resources.
*/
int
mxfe_alloctxring(mxfe_t *mxfep)
{
int rval;
int i;
size_t size;
size_t len;
ddi_dma_cookie_t dmac;
unsigned ncookies;
caddr_t kaddr;
size = MXFE_TXRING * sizeof (mxfe_desc_t);
rval = ddi_dma_alloc_handle(mxfep->mxfe_dip, &mxfe_dma_attr,
DDI_DMA_SLEEP, NULL, &mxfep->mxfe_txdesc_dmah);
if (rval != DDI_SUCCESS) {
mxfe_error(mxfep->mxfe_dip,
"unable to allocate DMA handle for tx descriptors");
return (DDI_FAILURE);
}
rval = ddi_dma_mem_alloc(mxfep->mxfe_txdesc_dmah, size, &mxfe_devattr,
DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &kaddr, &len,
&mxfep->mxfe_txdesc_acch);
if (rval != DDI_SUCCESS) {
mxfe_error(mxfep->mxfe_dip,
"unable to allocate DMA memory for tx descriptors");
return (DDI_FAILURE);
}
rval = ddi_dma_addr_bind_handle(mxfep->mxfe_txdesc_dmah, NULL, kaddr,
size, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL,
&dmac, &ncookies);
if (rval != DDI_DMA_MAPPED) {
mxfe_error(mxfep->mxfe_dip,
"unable to bind DMA for tx descriptors");
return (DDI_FAILURE);
}
/* because of mxfe_dma_attr */
ASSERT(ncookies == 1);
/* we take the 32-bit physical address out of the cookie */
mxfep->mxfe_txdesc_paddr = dmac.dmac_address;
mxfep->mxfe_txdescp = (void *)kaddr;
/* allocate buffer pointers (not the buffers themselves, yet) */
mxfep->mxfe_txbufs = kmem_zalloc(MXFE_TXRING * sizeof (mxfe_txbuf_t *),
KM_SLEEP);
/* now allocate tx buffers */
for (i = 0; i < MXFE_TXRING; i++) {
mxfe_txbuf_t *txb = mxfe_alloctxbuf(mxfep);
if (txb == NULL)
return (DDI_FAILURE);
/* stick it in the stack */
mxfep->mxfe_txbufs[i] = txb;
}
return (DDI_SUCCESS);
}
void
mxfe_freerxring(mxfe_t *mxfep)
{
int i;
for (i = 0; i < MXFE_RXRING; i++) {
mxfe_destroyrxbuf(mxfep->mxfe_rxbufs[i]);
}
if (mxfep->mxfe_rxbufs) {
kmem_free(mxfep->mxfe_rxbufs,
MXFE_RXRING * sizeof (mxfe_rxbuf_t *));
}
if (mxfep->mxfe_rxdesc_paddr)
(void) ddi_dma_unbind_handle(mxfep->mxfe_rxdesc_dmah);
if (mxfep->mxfe_rxdesc_acch)
ddi_dma_mem_free(&mxfep->mxfe_rxdesc_acch);
if (mxfep->mxfe_rxdesc_dmah)
ddi_dma_free_handle(&mxfep->mxfe_rxdesc_dmah);
}
void
mxfe_freetxring(mxfe_t *mxfep)
{
int i;
for (i = 0; i < MXFE_TXRING; i++) {
mxfe_destroytxbuf(mxfep->mxfe_txbufs[i]);
}
if (mxfep->mxfe_txbufs) {
kmem_free(mxfep->mxfe_txbufs,
MXFE_TXRING * sizeof (mxfe_txbuf_t *));
}
if (mxfep->mxfe_txdesc_paddr)
(void) ddi_dma_unbind_handle(mxfep->mxfe_txdesc_dmah);
if (mxfep->mxfe_txdesc_acch)
ddi_dma_mem_free(&mxfep->mxfe_txdesc_acch);
if (mxfep->mxfe_txdesc_dmah)
ddi_dma_free_handle(&mxfep->mxfe_txdesc_dmah);
}
/*
* Interrupt service routine.
*/
unsigned
mxfe_intr(caddr_t arg)
{
mxfe_t *mxfep = (void *)arg;
uint32_t status;
mblk_t *mp = NULL;
mutex_enter(&mxfep->mxfe_intrlock);
if (mxfep->mxfe_flags & MXFE_SUSPENDED) {
/* we cannot receive interrupts! */
mutex_exit(&mxfep->mxfe_intrlock);
return (DDI_INTR_UNCLAIMED);
}
/* check interrupt status bits, did we interrupt? */
status = GETCSR(mxfep, CSR_SR) & INT_ALL;
if (status == 0) {
KIOIP->intrs[KSTAT_INTR_SPURIOUS]++;
mutex_exit(&mxfep->mxfe_intrlock);
return (DDI_INTR_UNCLAIMED);
}
/* ack the interrupt */
PUTCSR(mxfep, CSR_SR, status);
KIOIP->intrs[KSTAT_INTR_HARD]++;
if (!(mxfep->mxfe_flags & MXFE_RUNNING)) {
/* not running, don't touch anything */
mutex_exit(&mxfep->mxfe_intrlock);
return (DDI_INTR_CLAIMED);
}
if (status & INT_RXOK) {
/* receive packets */
mp = mxfe_receive(mxfep);
}
if (status & INT_TXOK) {
/* transmit completed */
mutex_enter(&mxfep->mxfe_xmtlock);
mxfe_reclaim(mxfep);
mutex_exit(&mxfep->mxfe_xmtlock);
}
if (((status & (INT_TIMER|INT_ANEG)) != 0) ||
((mxfep->mxfe_linkup == LINK_STATE_UP) &&
((status & (INT_10LINK|INT_100LINK)) != 0))) {
/* rescan the link */
mutex_enter(&mxfep->mxfe_xmtlock);
mxfe_checklink(mxfep);
mutex_exit(&mxfep->mxfe_xmtlock);
}
if (status & (INT_RXSTOPPED|INT_TXSTOPPED|INT_RXNOBUF|
INT_RXJABBER|INT_TXJABBER|INT_TXUNDERFLOW)) {
if (status & (INT_RXJABBER | INT_TXJABBER)) {
mxfep->mxfe_jabber++;
}
DBG(DWARN, "resetting mac, status %x", status);
mutex_enter(&mxfep->mxfe_xmtlock);
mxfe_resetall(mxfep);
mutex_exit(&mxfep->mxfe_xmtlock);
}
if (status & INT_BUSERR) {
switch (status & SR_BERR_TYPE) {
case SR_BERR_PARITY:
mxfe_error(mxfep->mxfe_dip, "PCI parity error");
break;
case SR_BERR_TARGET_ABORT:
mxfe_error(mxfep->mxfe_dip, "PCI target abort");
break;
case SR_BERR_MASTER_ABORT:
mxfe_error(mxfep->mxfe_dip, "PCI master abort");
break;
default:
mxfe_error(mxfep->mxfe_dip, "Unknown PCI error");
break;
}
/* reset the chip in an attempt to fix things */
mutex_enter(&mxfep->mxfe_xmtlock);
mxfe_resetall(mxfep);
mutex_exit(&mxfep->mxfe_xmtlock);
}
mutex_exit(&mxfep->mxfe_intrlock);
/*
* Send up packets. We do this outside of the intrlock.
*/
if (mp) {
mac_rx(mxfep->mxfe_mh, NULL, mp);
}
return (DDI_INTR_CLAIMED);
}
void
mxfe_enableinterrupts(mxfe_t *mxfep)
{
unsigned mask = INT_WANTED;
if (mxfep->mxfe_wantw)
mask |= INT_TXOK;
if (MXFE_MODEL(mxfep) != MXFE_98713A)
mask |= INT_LINKSTATUS;
DBG(DINTR, "setting int mask to 0x%x", mask);
PUTCSR(mxfep, CSR_IER, mask);
}
void
mxfe_disableinterrupts(mxfe_t *mxfep)
{
/* disable further interrupts */
PUTCSR(mxfep, CSR_IER, 0);
/* clear any pending interrupts */
PUTCSR(mxfep, CSR_SR, INT_ALL);
}
void
mxfe_send_setup(mxfe_t *mxfep)
{
mxfe_txbuf_t *txb;
mxfe_desc_t *tmdp;
ASSERT(mutex_owned(&mxfep->mxfe_xmtlock));
/* setup frame -- must be at head of list -- guaranteed by caller! */
ASSERT(mxfep->mxfe_txsend == 0);
txb = mxfep->mxfe_txbufs[0];
tmdp = &mxfep->mxfe_txdescp[0];
bzero(txb->txb_buf, MXFE_SETUP_LEN);
/* program the unicast address */
txb->txb_buf[156] = mxfep->mxfe_curraddr[0];
txb->txb_buf[157] = mxfep->mxfe_curraddr[1];
txb->txb_buf[160] = mxfep->mxfe_curraddr[2];
txb->txb_buf[161] = mxfep->mxfe_curraddr[3];
txb->txb_buf[164] = mxfep->mxfe_curraddr[4];
txb->txb_buf[165] = mxfep->mxfe_curraddr[5];
/* make sure that the hardware can see it */
SYNCTXBUF(txb, MXFE_SETUP_LEN, DDI_DMA_SYNC_FORDEV);
PUTTXDESC(mxfep, tmdp->desc_control,
TXCTL_FIRST | TXCTL_LAST | TXCTL_INTCMPLTE | TXCTL_HASHPERF |
TXCTL_SETUP | MXFE_SETUP_LEN);
PUTTXDESC(mxfep, tmdp->desc_buffer1, txb->txb_paddr);
PUTTXDESC(mxfep, tmdp->desc_buffer2, 0);
PUTTXDESC(mxfep, tmdp->desc_status, TXSTAT_OWN);
/* sync the descriptor out to the device */
SYNCTXDESC(mxfep, 0, DDI_DMA_SYNC_FORDEV);
/*
* wake up the chip ... inside the lock to protect against DR suspend,
* etc.
*/
PUTCSR(mxfep, CSR_TDR, 0);
mxfep->mxfe_txsend++;
mxfep->mxfe_txavail--;
/*
* Program promiscuous mode.
*/
if (mxfep->mxfe_promisc) {
SETBIT(mxfep, CSR_NAR, NAR_RX_PROMISC);
} else {
CLRBIT(mxfep, CSR_NAR, NAR_RX_PROMISC);
}
}
boolean_t
mxfe_send(mxfe_t *mxfep, mblk_t *mp)
{
size_t len;
mxfe_txbuf_t *txb;
mxfe_desc_t *tmd;
uint32_t control;
int txsend;
ASSERT(mutex_owned(&mxfep->mxfe_xmtlock));
ASSERT(mp != NULL);
len = msgsize(mp);
if (len > ETHERVLANMTU) {
DBG(DXMIT, "frame too long: %d", len);
mxfep->mxfe_macxmt_errors++;
freemsg(mp);
return (B_TRUE);
}
if (mxfep->mxfe_txavail < MXFE_TXRECLAIM)
mxfe_reclaim(mxfep);
if (mxfep->mxfe_txavail == 0) {
/* no more tmds */
mxfep->mxfe_wantw = B_TRUE;
/* enable TX interrupt */
mxfe_enableinterrupts(mxfep);
return (B_FALSE);
}
txsend = mxfep->mxfe_txsend;
/*
* For simplicity, we just do a copy into a preallocated
* DMA buffer.
*/
txb = mxfep->mxfe_txbufs[txsend];
mcopymsg(mp, txb->txb_buf); /* frees mp! */
/*
* Statistics.
*/
mxfep->mxfe_opackets++;
mxfep->mxfe_obytes += len;
if (txb->txb_buf[0] & 0x1) {
if (bcmp(txb->txb_buf, mxfe_broadcast, ETHERADDRL) != 0)
mxfep->mxfe_multixmt++;
else
mxfep->mxfe_brdcstxmt++;
}
/* note len is already known to be a small unsigned */
control = len | TXCTL_FIRST | TXCTL_LAST | TXCTL_INTCMPLTE;
if (txsend == (MXFE_TXRING - 1))
control |= TXCTL_ENDRING;
tmd = &mxfep->mxfe_txdescp[txsend];
SYNCTXBUF(txb, len, DDI_DMA_SYNC_FORDEV);
PUTTXDESC(mxfep, tmd->desc_control, control);
PUTTXDESC(mxfep, tmd->desc_buffer1, txb->txb_paddr);
PUTTXDESC(mxfep, tmd->desc_buffer2, 0);
PUTTXDESC(mxfep, tmd->desc_status, TXSTAT_OWN);
/* sync the descriptor out to the device */
SYNCTXDESC(mxfep, txsend, DDI_DMA_SYNC_FORDEV);
/*
* Note the new values of txavail and txsend.
*/
mxfep->mxfe_txavail--;
mxfep->mxfe_txsend = (txsend + 1) % MXFE_TXRING;
/*
* It should never, ever take more than 5 seconds to drain
* the ring. If it happens, then we are stuck!
*/
mxfep->mxfe_txstall_time = gethrtime() + (5 * 1000000000ULL);
/*
* wake up the chip ... inside the lock to protect against DR suspend,
* etc.
*/
PUTCSR(mxfep, CSR_TDR, 0);
return (B_TRUE);
}
/*
* Reclaim buffers that have completed transmission.
*/
void
mxfe_reclaim(mxfe_t *mxfep)
{
mxfe_desc_t *tmdp;
while (mxfep->mxfe_txavail != MXFE_TXRING) {
uint32_t status;
uint32_t control;
int index = mxfep->mxfe_txreclaim;
tmdp = &mxfep->mxfe_txdescp[index];
/* sync it before we read it */
SYNCTXDESC(mxfep, index, DDI_DMA_SYNC_FORKERNEL);
control = GETTXDESC(mxfep, tmdp->desc_control);
status = GETTXDESC(mxfep, tmdp->desc_status);
if (status & TXSTAT_OWN) {
/* chip is still working on it, we're done */
break;
}
mxfep->mxfe_txavail++;
mxfep->mxfe_txreclaim = (index + 1) % MXFE_TXRING;
/* in the most common successful case, all bits are clear */
if (status == 0)
continue;
if (((control & TXCTL_SETUP) != 0) ||
((control & TXCTL_LAST) == 0)) {
/* no interesting statistics here */
continue;
}
if (status & TXSTAT_TXERR) {
mxfep->mxfe_errxmt++;
if (status & TXSTAT_JABBER) {
/* transmit jabber timeout */
mxfep->mxfe_macxmt_errors++;
}
if (status & (TXSTAT_CARRLOST | TXSTAT_NOCARR)) {
mxfep->mxfe_carrier_errors++;
}
if (status & TXSTAT_UFLOW) {
mxfep->mxfe_underflow++;
}
if (status & TXSTAT_LATECOL) {
mxfep->mxfe_tx_late_collisions++;
}
if (status & TXSTAT_EXCOLL) {
mxfep->mxfe_ex_collisions++;
mxfep->mxfe_collisions += 16;
}
}
if (status & TXSTAT_DEFER) {
mxfep->mxfe_defer_xmts++;
}
/* collision counting */
if (TXCOLLCNT(status) == 1) {
mxfep->mxfe_collisions++;
mxfep->mxfe_first_collisions++;
} else if (TXCOLLCNT(status)) {
mxfep->mxfe_collisions += TXCOLLCNT(status);
mxfep->mxfe_multi_collisions += TXCOLLCNT(status);
}
}
if (mxfep->mxfe_txavail >= MXFE_TXRESCHED) {
if (mxfep->mxfe_wantw) {
/*
* we were able to reclaim some packets, so
* disable tx interrupts
*/
mxfep->mxfe_wantw = B_FALSE;
mxfe_enableinterrupts(mxfep);
mac_tx_update(mxfep->mxfe_mh);
}
}
}
mblk_t *
mxfe_receive(mxfe_t *mxfep)
{
unsigned len;
mxfe_rxbuf_t *rxb;
mxfe_desc_t *rmd;
uint32_t status;
mblk_t *mpchain, **mpp, *mp;
int head, cnt;
mpchain = NULL;
mpp = &mpchain;
head = mxfep->mxfe_rxhead;
/* limit the number of packets we process to a ring size */
for (cnt = 0; cnt < MXFE_RXRING; cnt++) {
DBG(DRECV, "receive at index %d", head);
rmd = &mxfep->mxfe_rxdescp[head];
rxb = mxfep->mxfe_rxbufs[head];
SYNCRXDESC(mxfep, head, DDI_DMA_SYNC_FORKERNEL);
status = GETRXDESC(mxfep, rmd->desc_status);
if (status & RXSTAT_OWN) {
/* chip is still chewing on it */
break;
}
/* discard the ethernet frame checksum */
len = RXLENGTH(status) - ETHERFCSL;
DBG(DRECV, "recv length %d, status %x", len, status);
if ((status & (RXSTAT_ERRS | RXSTAT_FIRST | RXSTAT_LAST)) !=
(RXSTAT_FIRST | RXSTAT_LAST)) {
mxfep->mxfe_errrcv++;
/*
* Abnormal status bits detected, analyze further.
*/
if ((status & (RXSTAT_LAST|RXSTAT_FIRST)) !=
(RXSTAT_LAST|RXSTAT_FIRST)) {
DBG(DRECV, "rx packet overspill");
if (status & RXSTAT_FIRST) {
mxfep->mxfe_toolong_errors++;
}
} else if (status & RXSTAT_DESCERR) {
mxfep->mxfe_macrcv_errors++;
} else if (status & RXSTAT_RUNT) {
mxfep->mxfe_runt++;
} else if (status & RXSTAT_COLLSEEN) {
/* this should really be rx_late_collisions */
mxfep->mxfe_macrcv_errors++;
} else if (status & RXSTAT_DRIBBLE) {
mxfep->mxfe_align_errors++;
} else if (status & RXSTAT_CRCERR) {
mxfep->mxfe_fcs_errors++;
} else if (status & RXSTAT_OFLOW) {
mxfep->mxfe_overflow++;
}
}
else if (len > ETHERVLANMTU) {
mxfep->mxfe_errrcv++;
mxfep->mxfe_toolong_errors++;
}
/*
* At this point, the chip thinks the packet is OK.
*/
else {
mp = allocb(len + MXFE_HEADROOM, 0);
if (mp == NULL) {
mxfep->mxfe_errrcv++;
mxfep->mxfe_norcvbuf++;
goto skip;
}
/* sync the buffer before we look at it */
SYNCRXBUF(rxb, len, DDI_DMA_SYNC_FORKERNEL);
mp->b_rptr += MXFE_HEADROOM;
mp->b_wptr = mp->b_rptr + len;
bcopy((char *)rxb->rxb_buf, mp->b_rptr, len);
mxfep->mxfe_ipackets++;
mxfep->mxfe_rbytes += len;
if (status & RXSTAT_GROUP) {
if (bcmp(mp->b_rptr, mxfe_broadcast,
ETHERADDRL) == 0)
mxfep->mxfe_brdcstrcv++;
else
mxfep->mxfe_multircv++;
}
*mpp = mp;
mpp = &mp->b_next;
}
skip:
/* return ring entry to the hardware */
PUTRXDESC(mxfep, rmd->desc_status, RXSTAT_OWN);
SYNCRXDESC(mxfep, head, DDI_DMA_SYNC_FORDEV);
/* advance to next RMD */
head = (head + 1) % MXFE_RXRING;
}
mxfep->mxfe_rxhead = head;
return (mpchain);
}
int
mxfe_m_stat(void *arg, uint_t stat, uint64_t *val)
{
mxfe_t *mxfep = arg;
mutex_enter(&mxfep->mxfe_xmtlock);
if ((mxfep->mxfe_flags & (MXFE_RUNNING|MXFE_SUSPENDED)) == MXFE_RUNNING)
mxfe_reclaim(mxfep);
mutex_exit(&mxfep->mxfe_xmtlock);
switch (stat) {
case MAC_STAT_IFSPEED:
*val = mxfep->mxfe_ifspeed;
break;
case MAC_STAT_MULTIRCV:
*val = mxfep->mxfe_multircv;
break;
case MAC_STAT_BRDCSTRCV:
*val = mxfep->mxfe_brdcstrcv;
break;
case MAC_STAT_MULTIXMT:
*val = mxfep->mxfe_multixmt;
break;
case MAC_STAT_BRDCSTXMT:
*val = mxfep->mxfe_brdcstxmt;
break;
case MAC_STAT_IPACKETS:
*val = mxfep->mxfe_ipackets;
break;
case MAC_STAT_RBYTES:
*val = mxfep->mxfe_rbytes;
break;
case MAC_STAT_OPACKETS:
*val = mxfep->mxfe_opackets;
break;
case MAC_STAT_OBYTES:
*val = mxfep->mxfe_obytes;
break;
case MAC_STAT_NORCVBUF:
*val = mxfep->mxfe_norcvbuf;
break;
case MAC_STAT_NOXMTBUF:
*val = mxfep->mxfe_noxmtbuf;
break;
case MAC_STAT_COLLISIONS:
*val = mxfep->mxfe_collisions;
break;
case MAC_STAT_IERRORS:
*val = mxfep->mxfe_errrcv;
break;
case MAC_STAT_OERRORS:
*val = mxfep->mxfe_errxmt;
break;
case ETHER_STAT_LINK_DUPLEX:
*val = mxfep->mxfe_duplex;
break;
case ETHER_STAT_ALIGN_ERRORS:
*val = mxfep->mxfe_align_errors;
break;
case ETHER_STAT_FCS_ERRORS:
*val = mxfep->mxfe_fcs_errors;
break;
case ETHER_STAT_SQE_ERRORS:
*val = mxfep->mxfe_sqe_errors;
break;
case ETHER_STAT_DEFER_XMTS:
*val = mxfep->mxfe_defer_xmts;
break;
case ETHER_STAT_FIRST_COLLISIONS:
*val = mxfep->mxfe_first_collisions;
break;
case ETHER_STAT_MULTI_COLLISIONS:
*val = mxfep->mxfe_multi_collisions;
break;
case ETHER_STAT_TX_LATE_COLLISIONS:
*val = mxfep->mxfe_tx_late_collisions;
break;
case ETHER_STAT_EX_COLLISIONS:
*val = mxfep->mxfe_ex_collisions;
break;
case ETHER_STAT_MACXMT_ERRORS:
*val = mxfep->mxfe_macxmt_errors;
break;
case ETHER_STAT_CARRIER_ERRORS:
*val = mxfep->mxfe_carrier_errors;
break;
case ETHER_STAT_TOOLONG_ERRORS:
*val = mxfep->mxfe_toolong_errors;
break;
case ETHER_STAT_MACRCV_ERRORS:
*val = mxfep->mxfe_macrcv_errors;
break;
case MAC_STAT_OVERFLOWS:
*val = mxfep->mxfe_overflow;
break;
case MAC_STAT_UNDERFLOWS:
*val = mxfep->mxfe_underflow;
break;
case ETHER_STAT_TOOSHORT_ERRORS:
*val = mxfep->mxfe_runt;
break;
case ETHER_STAT_JABBER_ERRORS:
*val = mxfep->mxfe_jabber;
break;
case ETHER_STAT_ADV_CAP_100T4:
*val = mxfep->mxfe_adv_100T4;
break;
case ETHER_STAT_LP_CAP_100T4:
*val = (mxfep->mxfe_anlpar & MII_ABILITY_100BASE_T4) ? 1 : 0;
break;
case ETHER_STAT_CAP_100T4:
*val = mxfep->mxfe_cap_100T4;
break;
case ETHER_STAT_CAP_100FDX:
*val = mxfep->mxfe_cap_100fdx;
break;
case ETHER_STAT_CAP_100HDX:
*val = mxfep->mxfe_cap_100hdx;
break;
case ETHER_STAT_CAP_10FDX:
*val = mxfep->mxfe_cap_10fdx;
break;
case ETHER_STAT_CAP_10HDX:
*val = mxfep->mxfe_cap_10hdx;
break;
case ETHER_STAT_CAP_AUTONEG:
*val = mxfep->mxfe_cap_aneg;
break;
case ETHER_STAT_LINK_AUTONEG:
*val = ((mxfep->mxfe_adv_aneg != 0) &&
((mxfep->mxfe_aner & MII_AN_EXP_LPCANAN) != 0));
break;
case ETHER_STAT_ADV_CAP_100FDX:
*val = mxfep->mxfe_adv_100fdx;
break;
case ETHER_STAT_ADV_CAP_100HDX:
*val = mxfep->mxfe_adv_100hdx;
break;
case ETHER_STAT_ADV_CAP_10FDX:
*val = mxfep->mxfe_adv_10fdx;
break;
case ETHER_STAT_ADV_CAP_10HDX:
*val = mxfep->mxfe_adv_10hdx;
break;
case ETHER_STAT_ADV_CAP_AUTONEG:
*val = mxfep->mxfe_adv_aneg;
break;
case ETHER_STAT_LP_CAP_100FDX:
*val = (mxfep->mxfe_anlpar & MII_ABILITY_100BASE_TX_FD) ? 1 : 0;
break;
case ETHER_STAT_LP_CAP_100HDX:
*val = (mxfep->mxfe_anlpar & MII_ABILITY_100BASE_TX) ? 1 : 0;
break;
case ETHER_STAT_LP_CAP_10FDX:
*val = (mxfep->mxfe_anlpar & MII_ABILITY_10BASE_T_FD) ? 1 : 0;
break;
case ETHER_STAT_LP_CAP_10HDX:
*val = (mxfep->mxfe_anlpar & MII_ABILITY_10BASE_T) ? 1 : 0;
break;
case ETHER_STAT_LP_CAP_AUTONEG:
*val = (mxfep->mxfe_aner & MII_AN_EXP_LPCANAN) ? 1 : 0;
break;
case ETHER_STAT_XCVR_ADDR:
*val = mxfep->mxfe_phyaddr;
break;
case ETHER_STAT_XCVR_ID:
*val = mxfep->mxfe_phyid;
break;
case ETHER_STAT_XCVR_INUSE:
*val = mxfep->mxfe_phyinuse;
break;
default:
return (ENOTSUP);
}
return (0);
}
/*ARGSUSED*/
int
mxfe_m_getprop(void *arg, const char *name, mac_prop_id_t num, uint_t flags,
uint_t sz, void *val, uint_t *perm)
{
mxfe_t *mxfep = arg;
int err = 0;
boolean_t dfl = flags & MAC_PROP_DEFAULT;
if (sz == 0)
return (EINVAL);
*perm = MAC_PROP_PERM_RW;
switch (num) {
case MAC_PROP_DUPLEX:
*perm = MAC_PROP_PERM_READ;
if (sz >= sizeof (link_duplex_t)) {
bcopy(&mxfep->mxfe_duplex, val,
sizeof (link_duplex_t));
} else {
err = EINVAL;
}
break;
case MAC_PROP_SPEED:
*perm = MAC_PROP_PERM_READ;
if (sz >= sizeof (uint64_t)) {
bcopy(&mxfep->mxfe_ifspeed, val, sizeof (uint64_t));
} else {
err = EINVAL;
}
break;
case MAC_PROP_AUTONEG:
*(uint8_t *)val =
dfl ? mxfep->mxfe_cap_aneg : mxfep->mxfe_adv_aneg;
break;
case MAC_PROP_ADV_100FDX_CAP:
*perm = MAC_PROP_PERM_READ;
*(uint8_t *)val =
dfl ? mxfep->mxfe_cap_100fdx : mxfep->mxfe_adv_100fdx;
break;
case MAC_PROP_EN_100FDX_CAP:
*(uint8_t *)val =
dfl ? mxfep->mxfe_cap_100fdx : mxfep->mxfe_adv_100fdx;
break;
case MAC_PROP_ADV_100HDX_CAP:
*perm = MAC_PROP_PERM_READ;
*(uint8_t *)val =
dfl ? mxfep->mxfe_cap_100hdx : mxfep->mxfe_adv_100hdx;
break;
case MAC_PROP_EN_100HDX_CAP:
*(uint8_t *)val =
dfl ? mxfep->mxfe_cap_100hdx : mxfep->mxfe_adv_100hdx;
break;
case MAC_PROP_ADV_10FDX_CAP:
*perm = MAC_PROP_PERM_READ;
*(uint8_t *)val =
dfl ? mxfep->mxfe_cap_10fdx : mxfep->mxfe_adv_10fdx;
break;
case MAC_PROP_EN_10FDX_CAP:
*(uint8_t *)val =
dfl ? mxfep->mxfe_cap_10fdx : mxfep->mxfe_adv_10fdx;
break;
case MAC_PROP_ADV_10HDX_CAP:
*perm = MAC_PROP_PERM_READ;
*(uint8_t *)val =
dfl ? mxfep->mxfe_cap_10hdx : mxfep->mxfe_adv_10hdx;
break;
case MAC_PROP_EN_10HDX_CAP:
*(uint8_t *)val =
dfl ? mxfep->mxfe_cap_10hdx : mxfep->mxfe_adv_10hdx;
break;
case MAC_PROP_ADV_100T4_CAP:
*perm = MAC_PROP_PERM_READ;
*(uint8_t *)val =
dfl ? mxfep->mxfe_cap_100T4 : mxfep->mxfe_adv_100T4;
break;
case MAC_PROP_EN_100T4_CAP:
*(uint8_t *)val =
dfl ? mxfep->mxfe_cap_100T4 : mxfep->mxfe_adv_100T4;
break;
default:
err = ENOTSUP;
}
return (err);
}
/*ARGSUSED*/
int
mxfe_m_setprop(void *arg, const char *name, mac_prop_id_t num, uint_t sz,
const void *val)
{
mxfe_t *mxfep = arg;
uint8_t *advp;
uint8_t *capp;
switch (num) {
case MAC_PROP_EN_100FDX_CAP:
advp = &mxfep->mxfe_adv_100fdx;
capp = &mxfep->mxfe_cap_100fdx;
break;
case MAC_PROP_EN_100HDX_CAP:
advp = &mxfep->mxfe_adv_100hdx;
capp = &mxfep->mxfe_cap_100hdx;
break;
case MAC_PROP_EN_10FDX_CAP:
advp = &mxfep->mxfe_adv_10fdx;
capp = &mxfep->mxfe_cap_10fdx;
break;
case MAC_PROP_EN_10HDX_CAP:
advp = &mxfep->mxfe_adv_10hdx;
capp = &mxfep->mxfe_cap_10hdx;
break;
case MAC_PROP_EN_100T4_CAP:
advp = &mxfep->mxfe_adv_100T4;
capp = &mxfep->mxfe_cap_100T4;
break;
case MAC_PROP_AUTONEG:
advp = &mxfep->mxfe_adv_aneg;
capp = &mxfep->mxfe_cap_aneg;
break;
default:
return (ENOTSUP);
}
if (*capp == 0) /* ensure phy can support value */
return (ENOTSUP);
mutex_enter(&mxfep->mxfe_intrlock);
mutex_enter(&mxfep->mxfe_xmtlock);
if (*advp != *(const uint8_t *)val) {
*advp = *(const uint8_t *)val;
if ((mxfep->mxfe_flags & (MXFE_RUNNING|MXFE_SUSPENDED)) ==
MXFE_RUNNING) {
/*
* This re-initializes the phy, but it also
* restarts transmit and receive rings.
* Needless to say, changing the link
* parameters is destructive to traffic in
* progress.
*/
mxfe_resetall(mxfep);
}
}
mutex_exit(&mxfep->mxfe_xmtlock);
mutex_exit(&mxfep->mxfe_intrlock);
return (0);
}
/*
* Debugging and error reporting.
*/
void
mxfe_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, "mxfe: %s", buf);
}
}
#ifdef DEBUG
void
mxfe_dprintf(mxfe_t *mxfep, const char *func, int level, char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (mxfe_debug & level) {
char tag[64];
char buf[256];
if (mxfep && mxfep->mxfe_dip) {
(void) snprintf(tag, sizeof (tag),
"%s%d", ddi_driver_name(mxfep->mxfe_dip),
ddi_get_instance(mxfep->mxfe_dip));
} else {
(void) snprintf(tag, sizeof (tag), "mxfe");
}
(void) snprintf(buf, sizeof (buf), "%s: %s: %s\n", tag,
func, fmt);
vcmn_err(CE_CONT, buf, ap);
}
va_end(ap);
}
#endif