src/sys/dev/fdt/if_mvneta.c

1998 lines
52 KiB
C

/* $OpenBSD: if_mvneta.c,v 1.31 2023/11/10 15:51:19 bluhm Exp $ */
/* $NetBSD: if_mvneta.c,v 1.41 2015/04/15 10:15:40 hsuenaga Exp $ */
/*
* Copyright (c) 2007, 2008, 2013 KIYOHARA Takashi
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "bpfilter.h"
#include "kstat.h"
#include <sys/param.h>
#include <sys/device.h>
#include <sys/systm.h>
#include <sys/endian.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/mutex.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <uvm/uvm_extern.h>
#include <sys/mbuf.h>
#include <sys/kstat.h>
#include <machine/bus.h>
#include <machine/cpufunc.h>
#include <machine/fdt.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_clock.h>
#include <dev/ofw/ofw_misc.h>
#include <dev/ofw/ofw_pinctrl.h>
#include <dev/ofw/fdt.h>
#include <dev/fdt/if_mvnetareg.h>
#ifdef __armv7__
#include <armv7/marvell/mvmbusvar.h>
#endif
#include <net/if.h>
#include <net/if_media.h>
#include <net/if_types.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <dev/mii/mii.h>
#include <dev/mii/miivar.h>
#if NBPFILTER > 0
#include <net/bpf.h>
#endif
#ifdef MVNETA_DEBUG
#define DPRINTF(x) if (mvneta_debug) printf x
#define DPRINTFN(n,x) if (mvneta_debug >= (n)) printf x
int mvneta_debug = MVNETA_DEBUG;
#else
#define DPRINTF(x)
#define DPRINTFN(n,x)
#endif
#define MVNETA_READ(sc, reg) \
bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg))
#define MVNETA_WRITE(sc, reg, val) \
bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
#define MVNETA_READ_FILTER(sc, reg, val, c) \
bus_space_read_region_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val), (c))
#define MVNETA_WRITE_FILTER(sc, reg, val, c) \
bus_space_write_region_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val), (c))
#define MVNETA_LINKUP_READ(sc) \
MVNETA_READ(sc, MVNETA_PS0)
#define MVNETA_IS_LINKUP(sc) (MVNETA_LINKUP_READ(sc) & MVNETA_PS0_LINKUP)
#define MVNETA_TX_RING_CNT 256
#define MVNETA_TX_RING_MSK (MVNETA_TX_RING_CNT - 1)
#define MVNETA_TX_RING_NEXT(x) (((x) + 1) & MVNETA_TX_RING_MSK)
#define MVNETA_TX_QUEUE_CNT 1
#define MVNETA_RX_RING_CNT 256
#define MVNETA_RX_RING_MSK (MVNETA_RX_RING_CNT - 1)
#define MVNETA_RX_RING_NEXT(x) (((x) + 1) & MVNETA_RX_RING_MSK)
#define MVNETA_RX_QUEUE_CNT 1
CTASSERT(MVNETA_TX_RING_CNT > 1 && MVNETA_TX_RING_NEXT(MVNETA_TX_RING_CNT) ==
(MVNETA_TX_RING_CNT + 1) % MVNETA_TX_RING_CNT);
CTASSERT(MVNETA_RX_RING_CNT > 1 && MVNETA_RX_RING_NEXT(MVNETA_RX_RING_CNT) ==
(MVNETA_RX_RING_CNT + 1) % MVNETA_RX_RING_CNT);
#define MVNETA_NTXSEG 30
struct mvneta_dmamem {
bus_dmamap_t mdm_map;
bus_dma_segment_t mdm_seg;
size_t mdm_size;
caddr_t mdm_kva;
};
#define MVNETA_DMA_MAP(_mdm) ((_mdm)->mdm_map)
#define MVNETA_DMA_LEN(_mdm) ((_mdm)->mdm_size)
#define MVNETA_DMA_DVA(_mdm) ((_mdm)->mdm_map->dm_segs[0].ds_addr)
#define MVNETA_DMA_KVA(_mdm) ((void *)(_mdm)->mdm_kva)
struct mvneta_buf {
bus_dmamap_t tb_map;
struct mbuf *tb_m;
};
struct mvneta_softc {
struct device sc_dev;
struct mii_bus *sc_mdio;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_ioh;
bus_dma_tag_t sc_dmat;
void *sc_ih;
uint64_t sc_clk_freq;
struct arpcom sc_ac;
#define sc_enaddr sc_ac.ac_enaddr
struct mii_data sc_mii;
#define sc_media sc_mii.mii_media
struct timeout sc_tick_ch;
struct mvneta_dmamem *sc_txring;
struct mvneta_buf *sc_txbuf;
struct mvneta_tx_desc *sc_txdesc;
unsigned int sc_tx_prod; /* next free tx desc */
unsigned int sc_tx_cons; /* first tx desc sent */
struct mvneta_dmamem *sc_rxring;
struct mvneta_buf *sc_rxbuf;
struct mvneta_rx_desc *sc_rxdesc;
unsigned int sc_rx_prod; /* next rx desc to fill */
unsigned int sc_rx_cons; /* next rx desc recvd */
struct if_rxring sc_rx_ring;
enum {
PHY_MODE_QSGMII,
PHY_MODE_SGMII,
PHY_MODE_RGMII,
PHY_MODE_RGMII_ID,
PHY_MODE_1000BASEX,
PHY_MODE_2500BASEX,
} sc_phy_mode;
int sc_fixed_link;
int sc_inband_status;
int sc_phy;
int sc_phyloc;
int sc_link;
int sc_sfp;
int sc_node;
struct if_device sc_ifd;
#if NKSTAT > 0
struct mutex sc_kstat_lock;
struct timeout sc_kstat_tick;
struct kstat *sc_kstat;
#endif
};
int mvneta_miibus_readreg(struct device *, int, int);
void mvneta_miibus_writereg(struct device *, int, int, int);
void mvneta_miibus_statchg(struct device *);
void mvneta_wininit(struct mvneta_softc *);
/* Gigabit Ethernet Port part functions */
int mvneta_match(struct device *, void *, void *);
void mvneta_attach(struct device *, struct device *, void *);
void mvneta_attach_deferred(struct device *);
void mvneta_tick(void *);
int mvneta_intr(void *);
void mvneta_start(struct ifqueue *);
int mvneta_ioctl(struct ifnet *, u_long, caddr_t);
void mvneta_inband_statchg(struct mvneta_softc *);
void mvneta_port_change(struct mvneta_softc *);
void mvneta_port_up(struct mvneta_softc *);
int mvneta_up(struct mvneta_softc *);
void mvneta_down(struct mvneta_softc *);
void mvneta_watchdog(struct ifnet *);
int mvneta_mediachange(struct ifnet *);
void mvneta_mediastatus(struct ifnet *, struct ifmediareq *);
void mvneta_rx_proc(struct mvneta_softc *);
void mvneta_tx_proc(struct mvneta_softc *);
uint8_t mvneta_crc8(const uint8_t *, size_t);
void mvneta_iff(struct mvneta_softc *);
struct mvneta_dmamem *mvneta_dmamem_alloc(struct mvneta_softc *,
bus_size_t, bus_size_t);
void mvneta_dmamem_free(struct mvneta_softc *, struct mvneta_dmamem *);
void mvneta_fill_rx_ring(struct mvneta_softc *);
#if NKSTAT > 0
void mvneta_kstat_attach(struct mvneta_softc *);
#endif
static struct rwlock mvneta_sff_lock = RWLOCK_INITIALIZER("mvnetasff");
struct cfdriver mvneta_cd = {
NULL, "mvneta", DV_IFNET
};
const struct cfattach mvneta_ca = {
sizeof (struct mvneta_softc), mvneta_match, mvneta_attach,
};
int
mvneta_miibus_readreg(struct device *dev, int phy, int reg)
{
struct mvneta_softc *sc = (struct mvneta_softc *) dev;
return sc->sc_mdio->md_readreg(sc->sc_mdio->md_cookie, phy, reg);
}
void
mvneta_miibus_writereg(struct device *dev, int phy, int reg, int val)
{
struct mvneta_softc *sc = (struct mvneta_softc *) dev;
return sc->sc_mdio->md_writereg(sc->sc_mdio->md_cookie, phy, reg, val);
}
void
mvneta_miibus_statchg(struct device *self)
{
struct mvneta_softc *sc = (struct mvneta_softc *)self;
if (sc->sc_mii.mii_media_status & IFM_ACTIVE) {
uint32_t panc = MVNETA_READ(sc, MVNETA_PANC);
panc &= ~(MVNETA_PANC_SETMIISPEED |
MVNETA_PANC_SETGMIISPEED |
MVNETA_PANC_SETFULLDX);
switch (IFM_SUBTYPE(sc->sc_mii.mii_media_active)) {
case IFM_1000_SX:
case IFM_1000_LX:
case IFM_1000_CX:
case IFM_1000_T:
panc |= MVNETA_PANC_SETGMIISPEED;
break;
case IFM_100_TX:
panc |= MVNETA_PANC_SETMIISPEED;
break;
case IFM_10_T:
break;
}
if ((sc->sc_mii.mii_media_active & IFM_GMASK) == IFM_FDX)
panc |= MVNETA_PANC_SETFULLDX;
MVNETA_WRITE(sc, MVNETA_PANC, panc);
}
mvneta_port_change(sc);
}
void
mvneta_inband_statchg(struct mvneta_softc *sc)
{
uint64_t subtype = IFM_SUBTYPE(sc->sc_mii.mii_media_active);
uint32_t reg;
sc->sc_mii.mii_media_status = IFM_AVALID;
sc->sc_mii.mii_media_active = IFM_ETHER;
reg = MVNETA_READ(sc, MVNETA_PS0);
if (reg & MVNETA_PS0_LINKUP)
sc->sc_mii.mii_media_status |= IFM_ACTIVE;
if (sc->sc_phy_mode == PHY_MODE_2500BASEX)
sc->sc_mii.mii_media_active |= subtype;
else if (sc->sc_phy_mode == PHY_MODE_1000BASEX)
sc->sc_mii.mii_media_active |= subtype;
else if (reg & MVNETA_PS0_GMIISPEED)
sc->sc_mii.mii_media_active |= IFM_1000_T;
else if (reg & MVNETA_PS0_MIISPEED)
sc->sc_mii.mii_media_active |= IFM_100_TX;
else
sc->sc_mii.mii_media_active |= IFM_10_T;
if (reg & MVNETA_PS0_FULLDX)
sc->sc_mii.mii_media_active |= IFM_FDX;
mvneta_port_change(sc);
}
void
mvneta_enaddr_write(struct mvneta_softc *sc)
{
uint32_t maddrh, maddrl;
maddrh = sc->sc_enaddr[0] << 24;
maddrh |= sc->sc_enaddr[1] << 16;
maddrh |= sc->sc_enaddr[2] << 8;
maddrh |= sc->sc_enaddr[3];
maddrl = sc->sc_enaddr[4] << 8;
maddrl |= sc->sc_enaddr[5];
MVNETA_WRITE(sc, MVNETA_MACAH, maddrh);
MVNETA_WRITE(sc, MVNETA_MACAL, maddrl);
}
void
mvneta_wininit(struct mvneta_softc *sc)
{
uint32_t en;
int i;
#ifdef __armv7__
if (mvmbus_dram_info == NULL)
panic("%s: mbus dram information not set up",
sc->sc_dev.dv_xname);
#endif
for (i = 0; i < MVNETA_NWINDOW; i++) {
MVNETA_WRITE(sc, MVNETA_BASEADDR(i), 0);
MVNETA_WRITE(sc, MVNETA_S(i), 0);
if (i < MVNETA_NREMAP)
MVNETA_WRITE(sc, MVNETA_HA(i), 0);
}
en = MVNETA_BARE_EN_MASK;
#ifdef __armv7__
for (i = 0; i < mvmbus_dram_info->numcs; i++) {
struct mbus_dram_window *win = &mvmbus_dram_info->cs[i];
MVNETA_WRITE(sc, MVNETA_BASEADDR(i),
MVNETA_BASEADDR_TARGET(mvmbus_dram_info->targetid) |
MVNETA_BASEADDR_ATTR(win->attr) |
MVNETA_BASEADDR_BASE(win->base));
MVNETA_WRITE(sc, MVNETA_S(i), MVNETA_S_SIZE(win->size));
en &= ~(1 << i);
}
#else
MVNETA_WRITE(sc, MVNETA_S(0), MVNETA_S_SIZE(0));
en &= ~(1 << 0);
#endif
MVNETA_WRITE(sc, MVNETA_BARE, en);
}
#define COMPHY_SIP_POWER_ON 0x82000001
#define COMPHY_SIP_POWER_OFF 0x82000002
#define COMPHY_SPEED(x) ((x) << 2)
#define COMPHY_SPEED_1_25G 0 /* SGMII 1G */
#define COMPHY_SPEED_2_5G 1
#define COMPHY_SPEED_3_125G 2 /* SGMII 2.5G */
#define COMPHY_SPEED_5G 3
#define COMPHY_SPEED_5_15625G 4 /* XFI 5G */
#define COMPHY_SPEED_6G 5
#define COMPHY_SPEED_10_3125G 6 /* XFI 10G */
#define COMPHY_UNIT(x) ((x) << 8)
#define COMPHY_MODE(x) ((x) << 12)
#define COMPHY_MODE_SATA 1
#define COMPHY_MODE_SGMII 2 /* SGMII 1G */
#define COMPHY_MODE_HS_SGMII 3 /* SGMII 2.5G */
#define COMPHY_MODE_USB3H 4
#define COMPHY_MODE_USB3D 5
#define COMPHY_MODE_PCIE 6
#define COMPHY_MODE_RXAUI 7
#define COMPHY_MODE_XFI 8
#define COMPHY_MODE_SFI 9
#define COMPHY_MODE_USB3 10
void
mvneta_comphy_init(struct mvneta_softc *sc)
{
int node, phys[2], lane, unit;
uint32_t mode;
if (OF_getpropintarray(sc->sc_node, "phys", phys, sizeof(phys)) !=
sizeof(phys))
return;
node = OF_getnodebyphandle(phys[0]);
if (!node)
return;
lane = OF_getpropint(node, "reg", 0);
unit = phys[1];
switch (sc->sc_phy_mode) {
case PHY_MODE_1000BASEX:
case PHY_MODE_SGMII:
mode = COMPHY_MODE(COMPHY_MODE_SGMII) |
COMPHY_SPEED(COMPHY_SPEED_1_25G) |
COMPHY_UNIT(unit);
break;
case PHY_MODE_2500BASEX:
mode = COMPHY_MODE(COMPHY_MODE_HS_SGMII) |
COMPHY_SPEED(COMPHY_SPEED_3_125G) |
COMPHY_UNIT(unit);
break;
default:
return;
}
smc_call(COMPHY_SIP_POWER_ON, lane, mode, 0);
}
int
mvneta_match(struct device *parent, void *cfdata, void *aux)
{
struct fdt_attach_args *faa = aux;
return OF_is_compatible(faa->fa_node, "marvell,armada-370-neta") ||
OF_is_compatible(faa->fa_node, "marvell,armada-3700-neta");
}
void
mvneta_attach(struct device *parent, struct device *self, void *aux)
{
struct mvneta_softc *sc = (struct mvneta_softc *) self;
struct fdt_attach_args *faa = aux;
uint32_t ctl0, ctl2, ctl4, panc;
struct ifnet *ifp;
int i, len, node;
char *phy_mode;
char *managed;
sc->sc_iot = faa->fa_iot;
timeout_set(&sc->sc_tick_ch, mvneta_tick, sc);
if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
printf("%s: cannot map registers\n", self->dv_xname);
return;
}
sc->sc_dmat = faa->fa_dmat;
sc->sc_node = faa->fa_node;
clock_enable(faa->fa_node, NULL);
sc->sc_clk_freq = clock_get_frequency_idx(faa->fa_node, 0);
pinctrl_byname(faa->fa_node, "default");
len = OF_getproplen(faa->fa_node, "phy-mode");
if (len <= 0) {
printf(": cannot extract phy-mode\n");
return;
}
phy_mode = malloc(len, M_TEMP, M_WAITOK);
OF_getprop(faa->fa_node, "phy-mode", phy_mode, len);
if (!strncmp(phy_mode, "qsgmii", strlen("qsgmii")))
sc->sc_phy_mode = PHY_MODE_QSGMII;
else if (!strncmp(phy_mode, "sgmii", strlen("sgmii")))
sc->sc_phy_mode = PHY_MODE_SGMII;
else if (!strncmp(phy_mode, "rgmii-id", strlen("rgmii-id")))
sc->sc_phy_mode = PHY_MODE_RGMII_ID;
else if (!strncmp(phy_mode, "rgmii", strlen("rgmii")))
sc->sc_phy_mode = PHY_MODE_RGMII;
else if (!strncmp(phy_mode, "1000base-x", strlen("1000base-x")))
sc->sc_phy_mode = PHY_MODE_1000BASEX;
else if (!strncmp(phy_mode, "2500base-x", strlen("2500base-x")))
sc->sc_phy_mode = PHY_MODE_2500BASEX;
else {
printf(": cannot use phy-mode %s\n", phy_mode);
return;
}
free(phy_mode, M_TEMP, len);
/* TODO: check child's name to be "fixed-link" */
if (OF_getproplen(faa->fa_node, "fixed-link") >= 0 ||
OF_child(faa->fa_node))
sc->sc_fixed_link = 1;
if ((len = OF_getproplen(faa->fa_node, "managed")) >= 0) {
managed = malloc(len, M_TEMP, M_WAITOK);
OF_getprop(faa->fa_node, "managed", managed, len);
if (!strncmp(managed, "in-band-status",
strlen("in-band-status"))) {
sc->sc_fixed_link = 1;
sc->sc_inband_status = 1;
}
free(managed, M_TEMP, len);
}
if (!sc->sc_fixed_link) {
sc->sc_phy = OF_getpropint(faa->fa_node, "phy", 0);
node = OF_getnodebyphandle(sc->sc_phy);
if (!node) {
printf(": cannot find phy in fdt\n");
return;
}
if ((sc->sc_phyloc = OF_getpropint(node, "reg", -1)) == -1) {
printf(": cannot extract phy addr\n");
return;
}
}
mvneta_wininit(sc);
if (OF_getproplen(faa->fa_node, "local-mac-address") ==
ETHER_ADDR_LEN) {
OF_getprop(faa->fa_node, "local-mac-address",
sc->sc_enaddr, ETHER_ADDR_LEN);
mvneta_enaddr_write(sc);
} else {
uint32_t maddrh, maddrl;
maddrh = MVNETA_READ(sc, MVNETA_MACAH);
maddrl = MVNETA_READ(sc, MVNETA_MACAL);
if (maddrh || maddrl) {
sc->sc_enaddr[0] = maddrh >> 24;
sc->sc_enaddr[1] = maddrh >> 16;
sc->sc_enaddr[2] = maddrh >> 8;
sc->sc_enaddr[3] = maddrh >> 0;
sc->sc_enaddr[4] = maddrl >> 8;
sc->sc_enaddr[5] = maddrl >> 0;
} else
ether_fakeaddr(&sc->sc_ac.ac_if);
}
sc->sc_sfp = OF_getpropint(faa->fa_node, "sfp", 0);
printf(": address %s\n", ether_sprintf(sc->sc_enaddr));
/* disable port */
MVNETA_WRITE(sc, MVNETA_PMACC0,
MVNETA_READ(sc, MVNETA_PMACC0) & ~MVNETA_PMACC0_PORTEN);
delay(200);
/* clear all cause registers */
MVNETA_WRITE(sc, MVNETA_PRXTXTIC, 0);
MVNETA_WRITE(sc, MVNETA_PRXTXIC, 0);
MVNETA_WRITE(sc, MVNETA_PMIC, 0);
/* mask all interrupts */
MVNETA_WRITE(sc, MVNETA_PRXTXTIM, MVNETA_PRXTXTI_PMISCICSUMMARY);
MVNETA_WRITE(sc, MVNETA_PRXTXIM, 0);
MVNETA_WRITE(sc, MVNETA_PMIM, MVNETA_PMI_PHYSTATUSCHNG |
MVNETA_PMI_LINKCHANGE | MVNETA_PMI_PSCSYNCCHNG);
MVNETA_WRITE(sc, MVNETA_PIE, 0);
/* enable MBUS Retry bit16 */
MVNETA_WRITE(sc, MVNETA_ERETRY, 0x20);
/* enable access for CPU0 */
MVNETA_WRITE(sc, MVNETA_PCP2Q(0),
MVNETA_PCP2Q_RXQAE_ALL | MVNETA_PCP2Q_TXQAE_ALL);
/* reset RX and TX DMAs */
MVNETA_WRITE(sc, MVNETA_PRXINIT, MVNETA_PRXINIT_RXDMAINIT);
MVNETA_WRITE(sc, MVNETA_PTXINIT, MVNETA_PTXINIT_TXDMAINIT);
/* disable legacy WRR, disable EJP, release from reset */
MVNETA_WRITE(sc, MVNETA_TQC_1, 0);
for (i = 0; i < MVNETA_TX_QUEUE_CNT; i++) {
MVNETA_WRITE(sc, MVNETA_TQTBCOUNT(i), 0);
MVNETA_WRITE(sc, MVNETA_TQTBCONFIG(i), 0);
}
MVNETA_WRITE(sc, MVNETA_PRXINIT, 0);
MVNETA_WRITE(sc, MVNETA_PTXINIT, 0);
/* set port acceleration mode */
MVNETA_WRITE(sc, MVNETA_PACC, MVGVE_PACC_ACCELERATIONMODE_EDM);
MVNETA_WRITE(sc, MVNETA_PXC, MVNETA_PXC_AMNOTXES | MVNETA_PXC_RXCS);
MVNETA_WRITE(sc, MVNETA_PXCX, 0);
MVNETA_WRITE(sc, MVNETA_PMFS, 64);
/* Set SDC register except IPGINT bits */
MVNETA_WRITE(sc, MVNETA_SDC,
MVNETA_SDC_RXBSZ_16_64BITWORDS |
MVNETA_SDC_BLMR | /* Big/Little Endian Receive Mode: No swap */
MVNETA_SDC_BLMT | /* Big/Little Endian Transmit Mode: No swap */
MVNETA_SDC_TXBSZ_16_64BITWORDS);
/* XXX: Disable PHY polling in hardware */
MVNETA_WRITE(sc, MVNETA_EUC,
MVNETA_READ(sc, MVNETA_EUC) & ~MVNETA_EUC_POLLING);
/* clear uni-/multicast tables */
uint32_t dfut[MVNETA_NDFUT], dfsmt[MVNETA_NDFSMT], dfomt[MVNETA_NDFOMT];
memset(dfut, 0, sizeof(dfut));
memset(dfsmt, 0, sizeof(dfut));
memset(dfomt, 0, sizeof(dfut));
MVNETA_WRITE_FILTER(sc, MVNETA_DFUT, dfut, MVNETA_NDFUT);
MVNETA_WRITE_FILTER(sc, MVNETA_DFSMT, dfut, MVNETA_NDFSMT);
MVNETA_WRITE_FILTER(sc, MVNETA_DFOMT, dfut, MVNETA_NDFOMT);
MVNETA_WRITE(sc, MVNETA_PIE,
MVNETA_PIE_RXPKTINTRPTENB_ALL | MVNETA_PIE_TXPKTINTRPTENB_ALL);
MVNETA_WRITE(sc, MVNETA_EUIC, 0);
/* Setup phy. */
ctl0 = MVNETA_READ(sc, MVNETA_PMACC0);
ctl2 = MVNETA_READ(sc, MVNETA_PMACC2);
ctl4 = MVNETA_READ(sc, MVNETA_PMACC4);
panc = MVNETA_READ(sc, MVNETA_PANC);
/* Force link down to change in-band settings. */
panc &= ~MVNETA_PANC_FORCELINKPASS;
panc |= MVNETA_PANC_FORCELINKFAIL;
MVNETA_WRITE(sc, MVNETA_PANC, panc);
mvneta_comphy_init(sc);
ctl0 &= ~MVNETA_PMACC0_PORTTYPE;
ctl2 &= ~(MVNETA_PMACC2_PORTMACRESET | MVNETA_PMACC2_INBANDAN);
ctl4 &= ~(MVNETA_PMACC4_SHORT_PREAMBLE);
panc &= ~(MVNETA_PANC_INBANDANEN | MVNETA_PANC_INBANDRESTARTAN |
MVNETA_PANC_SETMIISPEED | MVNETA_PANC_SETGMIISPEED |
MVNETA_PANC_ANSPEEDEN | MVNETA_PANC_SETFCEN |
MVNETA_PANC_PAUSEADV | MVNETA_PANC_ANFCEN |
MVNETA_PANC_SETFULLDX | MVNETA_PANC_ANDUPLEXEN);
ctl2 |= MVNETA_PMACC2_RGMIIEN;
switch (sc->sc_phy_mode) {
case PHY_MODE_QSGMII:
MVNETA_WRITE(sc, MVNETA_SERDESCFG,
MVNETA_SERDESCFG_QSGMII_PROTO);
ctl2 |= MVNETA_PMACC2_PCSEN;
break;
case PHY_MODE_SGMII:
MVNETA_WRITE(sc, MVNETA_SERDESCFG,
MVNETA_SERDESCFG_SGMII_PROTO);
ctl2 |= MVNETA_PMACC2_PCSEN;
break;
case PHY_MODE_1000BASEX:
MVNETA_WRITE(sc, MVNETA_SERDESCFG,
MVNETA_SERDESCFG_SGMII_PROTO);
ctl2 |= MVNETA_PMACC2_PCSEN;
break;
case PHY_MODE_2500BASEX:
MVNETA_WRITE(sc, MVNETA_SERDESCFG,
MVNETA_SERDESCFG_HSGMII_PROTO);
ctl2 |= MVNETA_PMACC2_PCSEN;
ctl4 |= MVNETA_PMACC4_SHORT_PREAMBLE;
break;
default:
break;
}
/* Use Auto-Negotiation for Inband Status only */
if (sc->sc_inband_status) {
panc &= ~(MVNETA_PANC_FORCELINKFAIL |
MVNETA_PANC_FORCELINKPASS);
/* TODO: read mode from SFP */
if (1) {
/* 802.3z */
ctl0 |= MVNETA_PMACC0_PORTTYPE;
panc |= (MVNETA_PANC_INBANDANEN |
MVNETA_PANC_SETGMIISPEED |
MVNETA_PANC_SETFULLDX);
} else {
/* SGMII */
ctl2 |= MVNETA_PMACC2_INBANDAN;
panc |= (MVNETA_PANC_INBANDANEN |
MVNETA_PANC_ANSPEEDEN |
MVNETA_PANC_ANDUPLEXEN);
}
MVNETA_WRITE(sc, MVNETA_OMSCD,
MVNETA_READ(sc, MVNETA_OMSCD) | MVNETA_OMSCD_1MS_CLOCK_ENABLE);
} else {
MVNETA_WRITE(sc, MVNETA_OMSCD,
MVNETA_READ(sc, MVNETA_OMSCD) & ~MVNETA_OMSCD_1MS_CLOCK_ENABLE);
}
MVNETA_WRITE(sc, MVNETA_PMACC0, ctl0);
MVNETA_WRITE(sc, MVNETA_PMACC2, ctl2);
MVNETA_WRITE(sc, MVNETA_PMACC4, ctl4);
MVNETA_WRITE(sc, MVNETA_PANC, panc);
/* Port reset */
while (MVNETA_READ(sc, MVNETA_PMACC2) & MVNETA_PMACC2_PORTMACRESET)
;
sc->sc_ih = fdt_intr_establish(faa->fa_node, IPL_NET | IPL_MPSAFE,
mvneta_intr, sc, sc->sc_dev.dv_xname);
ifp = &sc->sc_ac.ac_if;
ifp->if_softc = sc;
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
ifp->if_xflags = IFXF_MPSAFE;
ifp->if_qstart = mvneta_start;
ifp->if_ioctl = mvneta_ioctl;
ifp->if_watchdog = mvneta_watchdog;
ifp->if_capabilities = IFCAP_VLAN_MTU;
#if notyet
/*
* We can do IPv4/TCPv4/UDPv4 checksums in hardware.
*/
ifp->if_capabilities |= IFCAP_CSUM_IPv4 | IFCAP_CSUM_TCPv4 |
IFCAP_CSUM_UDPv4;
ifp->if_capabilities |= IFCAP_VLAN_HWTAGGING;
/*
* But, IPv6 packets in the stream can cause incorrect TCPv4 Tx sums.
*/
ifp->if_capabilities &= ~IFCAP_CSUM_TCPv4;
#endif
ifq_init_maxlen(&ifp->if_snd, max(MVNETA_TX_RING_CNT - 1, IFQ_MAXLEN));
strlcpy(ifp->if_xname, sc->sc_dev.dv_xname, sizeof(ifp->if_xname));
/*
* Do MII setup.
*/
sc->sc_mii.mii_ifp = ifp;
sc->sc_mii.mii_readreg = mvneta_miibus_readreg;
sc->sc_mii.mii_writereg = mvneta_miibus_writereg;
sc->sc_mii.mii_statchg = mvneta_miibus_statchg;
ifmedia_init(&sc->sc_mii.mii_media, 0,
mvneta_mediachange, mvneta_mediastatus);
config_defer(self, mvneta_attach_deferred);
}
void
mvneta_attach_deferred(struct device *self)
{
struct mvneta_softc *sc = (struct mvneta_softc *) self;
struct ifnet *ifp = &sc->sc_ac.ac_if;
int mii_flags = 0;
if (!sc->sc_fixed_link) {
sc->sc_mdio = mii_byphandle(sc->sc_phy);
if (sc->sc_mdio == NULL) {
printf("%s: mdio bus not yet attached\n", self->dv_xname);
return;
}
switch (sc->sc_phy_mode) {
case PHY_MODE_1000BASEX:
mii_flags |= MIIF_IS_1000X;
break;
case PHY_MODE_SGMII:
mii_flags |= MIIF_SGMII;
break;
case PHY_MODE_RGMII_ID:
mii_flags |= MIIF_RXID | MIIF_TXID;
break;
default:
break;
}
mii_attach(self, &sc->sc_mii, 0xffffffff, sc->sc_phyloc,
MII_OFFSET_ANY, mii_flags);
if (LIST_FIRST(&sc->sc_mii.mii_phys) == NULL) {
printf("%s: no PHY found!\n", self->dv_xname);
ifmedia_add(&sc->sc_mii.mii_media,
IFM_ETHER|IFM_MANUAL, 0, NULL);
ifmedia_set(&sc->sc_mii.mii_media, IFM_ETHER|IFM_MANUAL);
} else
ifmedia_set(&sc->sc_mii.mii_media, IFM_ETHER|IFM_AUTO);
} else {
ifmedia_add(&sc->sc_mii.mii_media, IFM_ETHER|IFM_AUTO, 0, NULL);
ifmedia_set(&sc->sc_mii.mii_media, IFM_ETHER|IFM_AUTO);
if (sc->sc_inband_status) {
switch (sc->sc_phy_mode) {
case PHY_MODE_1000BASEX:
sc->sc_mii.mii_media_active =
IFM_ETHER|IFM_1000_KX|IFM_FDX;
break;
case PHY_MODE_2500BASEX:
sc->sc_mii.mii_media_active =
IFM_ETHER|IFM_2500_KX|IFM_FDX;
break;
default:
break;
}
mvneta_inband_statchg(sc);
} else {
sc->sc_mii.mii_media_status = IFM_AVALID|IFM_ACTIVE;
sc->sc_mii.mii_media_active = IFM_ETHER|IFM_1000_T|IFM_FDX;
mvneta_miibus_statchg(self);
}
ifp->if_baudrate = ifmedia_baudrate(sc->sc_mii.mii_media_active);
ifp->if_link_state = LINK_STATE_FULL_DUPLEX;
}
/*
* Call MI attach routines.
*/
if_attach(ifp);
ether_ifattach(ifp);
sc->sc_ifd.if_node = sc->sc_node;
sc->sc_ifd.if_ifp = ifp;
if_register(&sc->sc_ifd);
#if NKSTAT > 0
mvneta_kstat_attach(sc);
#endif
}
void
mvneta_tick(void *arg)
{
struct mvneta_softc *sc = arg;
struct mii_data *mii = &sc->sc_mii;
int s;
s = splnet();
mii_tick(mii);
splx(s);
timeout_add_sec(&sc->sc_tick_ch, 1);
}
int
mvneta_intr(void *arg)
{
struct mvneta_softc *sc = arg;
struct ifnet *ifp = &sc->sc_ac.ac_if;
uint32_t ic, misc;
ic = MVNETA_READ(sc, MVNETA_PRXTXTIC);
if (ic & MVNETA_PRXTXTI_PMISCICSUMMARY) {
KERNEL_LOCK();
misc = MVNETA_READ(sc, MVNETA_PMIC);
MVNETA_WRITE(sc, MVNETA_PMIC, 0);
if (sc->sc_inband_status && (misc &
(MVNETA_PMI_PHYSTATUSCHNG |
MVNETA_PMI_LINKCHANGE |
MVNETA_PMI_PSCSYNCCHNG))) {
mvneta_inband_statchg(sc);
}
KERNEL_UNLOCK();
}
if (!ISSET(ifp->if_flags, IFF_RUNNING))
return 1;
if (ic & MVNETA_PRXTXTI_TBTCQ(0))
mvneta_tx_proc(sc);
if (ISSET(ic, MVNETA_PRXTXTI_RBICTAPQ(0) | MVNETA_PRXTXTI_RDTAQ(0)))
mvneta_rx_proc(sc);
return 1;
}
static inline int
mvneta_load_mbuf(struct mvneta_softc *sc, bus_dmamap_t map, struct mbuf *m)
{
int error;
error = bus_dmamap_load_mbuf(sc->sc_dmat, map, m,
BUS_DMA_STREAMING | BUS_DMA_NOWAIT);
switch (error) {
case EFBIG:
error = m_defrag(m, M_DONTWAIT);
if (error != 0)
break;
error = bus_dmamap_load_mbuf(sc->sc_dmat, map, m,
BUS_DMA_STREAMING | BUS_DMA_NOWAIT);
if (error != 0)
break;
/* FALLTHROUGH */
case 0:
return (0);
default:
break;
}
return (error);
}
static inline void
mvneta_encap(struct mvneta_softc *sc, bus_dmamap_t map, struct mbuf *m,
unsigned int prod)
{
struct mvneta_tx_desc *txd;
uint32_t cmdsts;
unsigned int i;
cmdsts = MVNETA_TX_FIRST_DESC | MVNETA_TX_ZERO_PADDING |
MVNETA_TX_L4_CSUM_NOT;
#if notyet
int m_csumflags;
if (m_csumflags & M_CSUM_IPv4)
cmdsts |= MVNETA_TX_GENERATE_IP_CHKSUM;
if (m_csumflags & M_CSUM_TCPv4)
cmdsts |=
MVNETA_TX_GENERATE_L4_CHKSUM | MVNETA_TX_L4_TYPE_TCP;
if (m_csumflags & M_CSUM_UDPv4)
cmdsts |=
MVNETA_TX_GENERATE_L4_CHKSUM | MVNETA_TX_L4_TYPE_UDP;
if (m_csumflags & (M_CSUM_IPv4 | M_CSUM_TCPv4 | M_CSUM_UDPv4)) {
const int iphdr_unitlen = sizeof(struct ip) / sizeof(uint32_t);
cmdsts |= MVNETA_TX_IP_NO_FRAG |
MVNETA_TX_IP_HEADER_LEN(iphdr_unitlen); /* unit is 4B */
}
#endif
for (i = 0; i < map->dm_nsegs; i++) {
txd = &sc->sc_txdesc[prod];
txd->bytecnt = map->dm_segs[i].ds_len;
txd->l4ichk = 0;
txd->cmdsts = cmdsts;
txd->nextdescptr = 0;
txd->bufptr = map->dm_segs[i].ds_addr;
txd->_padding[0] = 0;
txd->_padding[1] = 0;
txd->_padding[2] = 0;
txd->_padding[3] = 0;
prod = MVNETA_TX_RING_NEXT(prod);
cmdsts = 0;
}
txd->cmdsts |= MVNETA_TX_LAST_DESC;
}
static inline void
mvneta_sync_txring(struct mvneta_softc *sc, int ops)
{
bus_dmamap_sync(sc->sc_dmat, MVNETA_DMA_MAP(sc->sc_txring), 0,
MVNETA_DMA_LEN(sc->sc_txring), ops);
}
void
mvneta_start(struct ifqueue *ifq)
{
struct ifnet *ifp = ifq->ifq_if;
struct mvneta_softc *sc = ifp->if_softc;
unsigned int prod, nprod, free, used = 0, nused;
struct mbuf *m;
bus_dmamap_t map;
/* If Link is DOWN, can't start TX */
if (!MVNETA_IS_LINKUP(sc)) {
ifq_purge(ifq);
return;
}
mvneta_sync_txring(sc, BUS_DMASYNC_POSTWRITE);
prod = sc->sc_tx_prod;
free = MVNETA_TX_RING_CNT - (prod - sc->sc_tx_cons);
for (;;) {
if (free < MVNETA_NTXSEG - 1) {
ifq_set_oactive(ifq);
break;
}
m = ifq_dequeue(ifq);
if (m == NULL)
break;
map = sc->sc_txbuf[prod].tb_map;
if (mvneta_load_mbuf(sc, map, m) != 0) {
m_freem(m);
ifp->if_oerrors++; /* XXX atomic */
continue;
}
#if NBPFILTER > 0
if (ifp->if_bpf)
bpf_mtap(ifp->if_bpf, m, BPF_DIRECTION_OUT);
#endif
bus_dmamap_sync(sc->sc_dmat, map, 0, map->dm_mapsize,
BUS_DMASYNC_PREWRITE);
mvneta_encap(sc, map, m, prod);
if (map->dm_nsegs > 1) {
nprod = (prod + (map->dm_nsegs - 1)) %
MVNETA_TX_RING_CNT;
sc->sc_txbuf[prod].tb_map = sc->sc_txbuf[nprod].tb_map;
prod = nprod;
sc->sc_txbuf[prod].tb_map = map;
}
sc->sc_txbuf[prod].tb_m = m;
prod = MVNETA_TX_RING_NEXT(prod);
free -= map->dm_nsegs;
nused = used + map->dm_nsegs;
if (nused > MVNETA_PTXSU_MAX) {
mvneta_sync_txring(sc,
BUS_DMASYNC_PREWRITE|BUS_DMASYNC_POSTWRITE);
MVNETA_WRITE(sc, MVNETA_PTXSU(0),
MVNETA_PTXSU_NOWD(used));
used = map->dm_nsegs;
} else
used = nused;
}
mvneta_sync_txring(sc, BUS_DMASYNC_PREWRITE);
sc->sc_tx_prod = prod;
if (used)
MVNETA_WRITE(sc, MVNETA_PTXSU(0), MVNETA_PTXSU_NOWD(used));
}
int
mvneta_ioctl(struct ifnet *ifp, u_long cmd, caddr_t addr)
{
struct mvneta_softc *sc = ifp->if_softc;
struct ifreq *ifr = (struct ifreq *)addr;
int s, error = 0;
s = splnet();
switch (cmd) {
case SIOCSIFADDR:
ifp->if_flags |= IFF_UP;
/* FALLTHROUGH */
case SIOCSIFFLAGS:
if (ifp->if_flags & IFF_UP) {
if (ifp->if_flags & IFF_RUNNING)
error = ENETRESET;
else
mvneta_up(sc);
} else {
if (ifp->if_flags & IFF_RUNNING)
mvneta_down(sc);
}
break;
case SIOCGIFMEDIA:
case SIOCSIFMEDIA:
DPRINTFN(2, ("mvneta_ioctl MEDIA\n"));
error = ifmedia_ioctl(ifp, ifr, &sc->sc_media, cmd);
break;
case SIOCGIFRXR:
error = if_rxr_ioctl((struct if_rxrinfo *)ifr->ifr_data,
NULL, MCLBYTES, &sc->sc_rx_ring);
break;
case SIOCGIFSFFPAGE:
error = rw_enter(&mvneta_sff_lock, RW_WRITE|RW_INTR);
if (error != 0)
break;
error = sfp_get_sffpage(sc->sc_sfp, (struct if_sffpage *)addr);
rw_exit(&mvneta_sff_lock);
break;
default:
DPRINTFN(2, ("mvneta_ioctl ETHER\n"));
error = ether_ioctl(ifp, &sc->sc_ac, cmd, addr);
break;
}
if (error == ENETRESET) {
if (ifp->if_flags & IFF_RUNNING)
mvneta_iff(sc);
error = 0;
}
splx(s);
return error;
}
void
mvneta_port_change(struct mvneta_softc *sc)
{
if (!!(sc->sc_mii.mii_media_status & IFM_ACTIVE) != sc->sc_link) {
sc->sc_link = !sc->sc_link;
if (sc->sc_link) {
if (!sc->sc_inband_status) {
uint32_t panc = MVNETA_READ(sc, MVNETA_PANC);
panc &= ~MVNETA_PANC_FORCELINKFAIL;
panc |= MVNETA_PANC_FORCELINKPASS;
MVNETA_WRITE(sc, MVNETA_PANC, panc);
}
mvneta_port_up(sc);
} else {
if (!sc->sc_inband_status) {
uint32_t panc = MVNETA_READ(sc, MVNETA_PANC);
panc &= ~MVNETA_PANC_FORCELINKPASS;
panc |= MVNETA_PANC_FORCELINKFAIL;
MVNETA_WRITE(sc, MVNETA_PANC, panc);
}
}
}
}
void
mvneta_port_up(struct mvneta_softc *sc)
{
/* Enable port RX/TX. */
MVNETA_WRITE(sc, MVNETA_RQC, MVNETA_RQC_ENQ(0));
MVNETA_WRITE(sc, MVNETA_TQC, MVNETA_TQC_ENQ(0));
}
int
mvneta_up(struct mvneta_softc *sc)
{
struct ifnet *ifp = &sc->sc_ac.ac_if;
struct mvneta_buf *txb, *rxb;
int i;
DPRINTFN(2, ("mvneta_up\n"));
/* Allocate Tx descriptor ring. */
sc->sc_txring = mvneta_dmamem_alloc(sc,
MVNETA_TX_RING_CNT * sizeof(struct mvneta_tx_desc), 32);
sc->sc_txdesc = MVNETA_DMA_KVA(sc->sc_txring);
sc->sc_txbuf = malloc(sizeof(struct mvneta_buf) * MVNETA_TX_RING_CNT,
M_DEVBUF, M_WAITOK);
for (i = 0; i < MVNETA_TX_RING_CNT; i++) {
txb = &sc->sc_txbuf[i];
bus_dmamap_create(sc->sc_dmat, MCLBYTES, MVNETA_NTXSEG,
MCLBYTES, 0, BUS_DMA_WAITOK, &txb->tb_map);
txb->tb_m = NULL;
}
sc->sc_tx_prod = sc->sc_tx_cons = 0;
/* Allocate Rx descriptor ring. */
sc->sc_rxring = mvneta_dmamem_alloc(sc,
MVNETA_RX_RING_CNT * sizeof(struct mvneta_rx_desc), 32);
sc->sc_rxdesc = MVNETA_DMA_KVA(sc->sc_rxring);
sc->sc_rxbuf = malloc(sizeof(struct mvneta_buf) * MVNETA_RX_RING_CNT,
M_DEVBUF, M_WAITOK);
for (i = 0; i < MVNETA_RX_RING_CNT; i++) {
rxb = &sc->sc_rxbuf[i];
bus_dmamap_create(sc->sc_dmat, MCLBYTES, 1,
MCLBYTES, 0, BUS_DMA_WAITOK, &rxb->tb_map);
rxb->tb_m = NULL;
}
/* Set Rx descriptor ring data. */
MVNETA_WRITE(sc, MVNETA_PRXDQA(0), MVNETA_DMA_DVA(sc->sc_rxring));
MVNETA_WRITE(sc, MVNETA_PRXDQS(0), MVNETA_RX_RING_CNT |
((MCLBYTES >> 3) << 19));
if (sc->sc_clk_freq != 0) {
/*
* Use the Non Occupied Descriptors Threshold to
* interrupt when the descriptors granted by rxr are
* used up, otherwise wait until the RX Interrupt
* Time Threshold is reached.
*/
MVNETA_WRITE(sc, MVNETA_PRXDQTH(0),
MVNETA_PRXDQTH_ODT(MVNETA_RX_RING_CNT) |
MVNETA_PRXDQTH_NODT(2));
MVNETA_WRITE(sc, MVNETA_PRXITTH(0), sc->sc_clk_freq / 4000);
} else {
/* Time based moderation is hard without a clock */
MVNETA_WRITE(sc, MVNETA_PRXDQTH(0), 0);
MVNETA_WRITE(sc, MVNETA_PRXITTH(0), 0);
}
MVNETA_WRITE(sc, MVNETA_PRXC(0), 0);
/* Set Tx queue bandwidth. */
MVNETA_WRITE(sc, MVNETA_TQTBCOUNT(0), 0x03ffffff);
MVNETA_WRITE(sc, MVNETA_TQTBCONFIG(0), 0x03ffffff);
/* Set Tx descriptor ring data. */
MVNETA_WRITE(sc, MVNETA_PTXDQA(0), MVNETA_DMA_DVA(sc->sc_txring));
MVNETA_WRITE(sc, MVNETA_PTXDQS(0),
MVNETA_PTXDQS_DQS(MVNETA_TX_RING_CNT) |
MVNETA_PTXDQS_TBT(MIN(MVNETA_TX_RING_CNT / 2, ifp->if_txmit)));
sc->sc_rx_prod = sc->sc_rx_cons = 0;
if_rxr_init(&sc->sc_rx_ring, 2, MVNETA_RX_RING_CNT);
mvneta_fill_rx_ring(sc);
/* TODO: correct frame size */
MVNETA_WRITE(sc, MVNETA_PMACC0,
(MVNETA_READ(sc, MVNETA_PMACC0) & MVNETA_PMACC0_PORTTYPE) |
MVNETA_PMACC0_FRAMESIZELIMIT(MCLBYTES - MVNETA_HWHEADER_SIZE));
/* set max MTU */
MVNETA_WRITE(sc, MVNETA_TXMTU, MVNETA_TXMTU_MAX);
MVNETA_WRITE(sc, MVNETA_TXTKSIZE, 0xffffffff);
MVNETA_WRITE(sc, MVNETA_TXQTKSIZE(0), 0x7fffffff);
/* enable port */
MVNETA_WRITE(sc, MVNETA_PMACC0,
MVNETA_READ(sc, MVNETA_PMACC0) | MVNETA_PMACC0_PORTEN);
mvneta_enaddr_write(sc);
/* Program promiscuous mode and multicast filters. */
mvneta_iff(sc);
if (!sc->sc_fixed_link)
mii_mediachg(&sc->sc_mii);
if (sc->sc_link)
mvneta_port_up(sc);
/* Enable interrupt masks */
MVNETA_WRITE(sc, MVNETA_PRXTXTIM, MVNETA_PRXTXTI_RBICTAPQ(0) |
MVNETA_PRXTXTI_TBTCQ(0) | MVNETA_PRXTXTI_RDTAQ(0) |
MVNETA_PRXTXTI_PMISCICSUMMARY);
MVNETA_WRITE(sc, MVNETA_PMIM, MVNETA_PMI_PHYSTATUSCHNG |
MVNETA_PMI_LINKCHANGE | MVNETA_PMI_PSCSYNCCHNG);
timeout_add_sec(&sc->sc_tick_ch, 1);
ifp->if_flags |= IFF_RUNNING;
ifq_clr_oactive(&ifp->if_snd);
return 0;
}
void
mvneta_down(struct mvneta_softc *sc)
{
struct ifnet *ifp = &sc->sc_ac.ac_if;
uint32_t reg, txinprog, txfifoemp;
struct mvneta_buf *txb, *rxb;
int i, cnt;
DPRINTFN(2, ("mvneta_down\n"));
timeout_del(&sc->sc_tick_ch);
ifp->if_flags &= ~IFF_RUNNING;
intr_barrier(sc->sc_ih);
/* Stop Rx port activity. Check port Rx activity. */
reg = MVNETA_READ(sc, MVNETA_RQC);
if (reg & MVNETA_RQC_ENQ_MASK)
/* Issue stop command for active channels only */
MVNETA_WRITE(sc, MVNETA_RQC, MVNETA_RQC_DISQ_DISABLE(reg));
/* Stop Tx port activity. Check port Tx activity. */
if (MVNETA_READ(sc, MVNETA_TQC) & MVNETA_TQC_ENQ(0))
MVNETA_WRITE(sc, MVNETA_TQC, MVNETA_TQC_DISQ(0));
txinprog = MVNETA_PS_TXINPROG_(0);
txfifoemp = MVNETA_PS_TXFIFOEMP_(0);
#define RX_DISABLE_TIMEOUT 0x1000000
#define TX_FIFO_EMPTY_TIMEOUT 0x1000000
/* Wait for all Rx activity to terminate. */
cnt = 0;
do {
if (cnt >= RX_DISABLE_TIMEOUT) {
printf("%s: timeout for RX stopped. rqc 0x%x\n",
sc->sc_dev.dv_xname, reg);
break;
}
cnt++;
/*
* Check Receive Queue Command register that all Rx queues
* are stopped
*/
reg = MVNETA_READ(sc, MVNETA_RQC);
} while (reg & 0xff);
/* Double check to verify that TX FIFO is empty */
cnt = 0;
while (1) {
do {
if (cnt >= TX_FIFO_EMPTY_TIMEOUT) {
printf("%s: timeout for TX FIFO empty. status "
"0x%x\n", sc->sc_dev.dv_xname, reg);
break;
}
cnt++;
reg = MVNETA_READ(sc, MVNETA_PS);
} while (!(reg & txfifoemp) || reg & txinprog);
if (cnt >= TX_FIFO_EMPTY_TIMEOUT)
break;
/* Double check */
reg = MVNETA_READ(sc, MVNETA_PS);
if (reg & txfifoemp && !(reg & txinprog))
break;
else
printf("%s: TX FIFO empty double check failed."
" %d loops, status 0x%x\n", sc->sc_dev.dv_xname,
cnt, reg);
}
delay(200);
/* disable port */
MVNETA_WRITE(sc, MVNETA_PMACC0,
MVNETA_READ(sc, MVNETA_PMACC0) & ~MVNETA_PMACC0_PORTEN);
delay(200);
/* mask all interrupts */
MVNETA_WRITE(sc, MVNETA_PRXTXTIM, MVNETA_PRXTXTI_PMISCICSUMMARY);
MVNETA_WRITE(sc, MVNETA_PRXTXIM, 0);
/* clear all cause registers */
MVNETA_WRITE(sc, MVNETA_PRXTXTIC, 0);
MVNETA_WRITE(sc, MVNETA_PRXTXIC, 0);
/* Free RX and TX mbufs still in the queues. */
for (i = 0; i < MVNETA_TX_RING_CNT; i++) {
txb = &sc->sc_txbuf[i];
if (txb->tb_m) {
bus_dmamap_sync(sc->sc_dmat, txb->tb_map, 0,
txb->tb_map->dm_mapsize, BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(sc->sc_dmat, txb->tb_map);
m_freem(txb->tb_m);
}
bus_dmamap_destroy(sc->sc_dmat, txb->tb_map);
}
mvneta_dmamem_free(sc, sc->sc_txring);
free(sc->sc_txbuf, M_DEVBUF, 0);
for (i = 0; i < MVNETA_RX_RING_CNT; i++) {
rxb = &sc->sc_rxbuf[i];
if (rxb->tb_m) {
bus_dmamap_sync(sc->sc_dmat, rxb->tb_map, 0,
rxb->tb_map->dm_mapsize, BUS_DMASYNC_POSTREAD);
bus_dmamap_unload(sc->sc_dmat, rxb->tb_map);
m_freem(rxb->tb_m);
}
bus_dmamap_destroy(sc->sc_dmat, rxb->tb_map);
}
mvneta_dmamem_free(sc, sc->sc_rxring);
free(sc->sc_rxbuf, M_DEVBUF, 0);
/* reset RX and TX DMAs */
MVNETA_WRITE(sc, MVNETA_PRXINIT, MVNETA_PRXINIT_RXDMAINIT);
MVNETA_WRITE(sc, MVNETA_PTXINIT, MVNETA_PTXINIT_TXDMAINIT);
MVNETA_WRITE(sc, MVNETA_PRXINIT, 0);
MVNETA_WRITE(sc, MVNETA_PTXINIT, 0);
ifq_clr_oactive(&ifp->if_snd);
}
void
mvneta_watchdog(struct ifnet *ifp)
{
struct mvneta_softc *sc = ifp->if_softc;
/*
* Reclaim first as there is a possibility of losing Tx completion
* interrupts.
*/
mvneta_tx_proc(sc);
if (sc->sc_tx_prod != sc->sc_tx_cons) {
printf("%s: watchdog timeout\n", sc->sc_dev.dv_xname);
ifp->if_oerrors++;
}
}
/*
* Set media options.
*/
int
mvneta_mediachange(struct ifnet *ifp)
{
struct mvneta_softc *sc = ifp->if_softc;
if (LIST_FIRST(&sc->sc_mii.mii_phys))
mii_mediachg(&sc->sc_mii);
return (0);
}
/*
* Report current media status.
*/
void
mvneta_mediastatus(struct ifnet *ifp, struct ifmediareq *ifmr)
{
struct mvneta_softc *sc = ifp->if_softc;
if (LIST_FIRST(&sc->sc_mii.mii_phys)) {
mii_pollstat(&sc->sc_mii);
ifmr->ifm_active = sc->sc_mii.mii_media_active;
ifmr->ifm_status = sc->sc_mii.mii_media_status;
}
if (sc->sc_fixed_link) {
ifmr->ifm_active = sc->sc_mii.mii_media_active;
ifmr->ifm_status = sc->sc_mii.mii_media_status;
}
}
void
mvneta_rx_proc(struct mvneta_softc *sc)
{
struct ifnet *ifp = &sc->sc_ac.ac_if;
struct mvneta_rx_desc *rxd;
struct mvneta_buf *rxb;
struct mbuf_list ml = MBUF_LIST_INITIALIZER();
struct mbuf *m;
uint32_t rxstat;
unsigned int i, done, cons;
done = MVNETA_PRXS_ODC(MVNETA_READ(sc, MVNETA_PRXS(0)));
if (done == 0)
return;
bus_dmamap_sync(sc->sc_dmat, MVNETA_DMA_MAP(sc->sc_rxring),
0, MVNETA_DMA_LEN(sc->sc_rxring), BUS_DMASYNC_POSTREAD);
cons = sc->sc_rx_cons;
for (i = 0; i < done; i++) {
rxd = &sc->sc_rxdesc[cons];
rxb = &sc->sc_rxbuf[cons];
m = rxb->tb_m;
rxb->tb_m = NULL;
bus_dmamap_sync(sc->sc_dmat, rxb->tb_map, 0,
m->m_pkthdr.len, BUS_DMASYNC_POSTREAD);
bus_dmamap_unload(sc->sc_dmat, rxb->tb_map);
rxstat = rxd->cmdsts;
if (rxstat & MVNETA_ERROR_SUMMARY) {
#if 0
int err = rxstat & MVNETA_RX_ERROR_CODE_MASK;
if (err == MVNETA_RX_CRC_ERROR)
ifp->if_ierrors++;
if (err == MVNETA_RX_OVERRUN_ERROR)
ifp->if_ierrors++;
if (err == MVNETA_RX_MAX_FRAME_LEN_ERROR)
ifp->if_ierrors++;
if (err == MVNETA_RX_RESOURCE_ERROR)
ifp->if_ierrors++;
#else
ifp->if_ierrors++;
#endif
m_freem(m);
} else {
m->m_pkthdr.len = m->m_len = rxd->bytecnt;
m_adj(m, MVNETA_HWHEADER_SIZE);
ml_enqueue(&ml, m);
}
#if notyet
if (rxstat & MVNETA_RX_IP_FRAME_TYPE) {
int flgs = 0;
/* Check IPv4 header checksum */
flgs |= M_CSUM_IPv4;
if (!(rxstat & MVNETA_RX_IP_HEADER_OK))
flgs |= M_CSUM_IPv4_BAD;
else if ((bufsize & MVNETA_RX_IP_FRAGMENT) == 0) {
/*
* Check TCPv4/UDPv4 checksum for
* non-fragmented packet only.
*
* It seemd that sometimes
* MVNETA_RX_L4_CHECKSUM_OK bit was set to 0
* even if the checksum is correct and the
* packet was not fragmented. So we don't set
* M_CSUM_TCP_UDP_BAD even if csum bit is 0.
*/
if (((rxstat & MVNETA_RX_L4_TYPE_MASK) ==
MVNETA_RX_L4_TYPE_TCP) &&
((rxstat & MVNETA_RX_L4_CHECKSUM_OK) != 0))
flgs |= M_CSUM_TCPv4;
else if (((rxstat & MVNETA_RX_L4_TYPE_MASK) ==
MVNETA_RX_L4_TYPE_UDP) &&
((rxstat & MVNETA_RX_L4_CHECKSUM_OK) != 0))
flgs |= M_CSUM_UDPv4;
}
m->m_pkthdr.csum_flags = flgs;
}
#endif
if_rxr_put(&sc->sc_rx_ring, 1);
cons = MVNETA_RX_RING_NEXT(cons);
if (i == MVNETA_PRXSU_MAX) {
MVNETA_WRITE(sc, MVNETA_PRXSU(0),
MVNETA_PRXSU_NOPD(MVNETA_PRXSU_MAX));
/* tweaking the iterator inside the loop is fun */
done -= MVNETA_PRXSU_MAX;
i = 0;
}
}
sc->sc_rx_cons = cons;
bus_dmamap_sync(sc->sc_dmat, MVNETA_DMA_MAP(sc->sc_rxring),
0, MVNETA_DMA_LEN(sc->sc_rxring), BUS_DMASYNC_PREREAD);
if (i > 0) {
MVNETA_WRITE(sc, MVNETA_PRXSU(0),
MVNETA_PRXSU_NOPD(i));
}
if (ifiq_input(&ifp->if_rcv, &ml))
if_rxr_livelocked(&sc->sc_rx_ring);
mvneta_fill_rx_ring(sc);
}
void
mvneta_tx_proc(struct mvneta_softc *sc)
{
struct ifnet *ifp = &sc->sc_ac.ac_if;
struct ifqueue *ifq = &ifp->if_snd;
struct mvneta_tx_desc *txd;
struct mvneta_buf *txb;
unsigned int i, cons, done;
if (!(ifp->if_flags & IFF_RUNNING))
return;
done = MVNETA_PTXS_TBC(MVNETA_READ(sc, MVNETA_PTXS(0)));
if (done == 0)
return;
bus_dmamap_sync(sc->sc_dmat, MVNETA_DMA_MAP(sc->sc_txring), 0,
MVNETA_DMA_LEN(sc->sc_txring),
BUS_DMASYNC_POSTREAD);
cons = sc->sc_tx_cons;
for (i = 0; i < done; i++) {
txd = &sc->sc_txdesc[cons];
txb = &sc->sc_txbuf[cons];
if (txb->tb_m) {
bus_dmamap_sync(sc->sc_dmat, txb->tb_map, 0,
txb->tb_map->dm_mapsize, BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(sc->sc_dmat, txb->tb_map);
m_freem(txb->tb_m);
txb->tb_m = NULL;
}
if (txd->cmdsts & MVNETA_ERROR_SUMMARY) {
int err = txd->cmdsts & MVNETA_TX_ERROR_CODE_MASK;
if (err == MVNETA_TX_LATE_COLLISION_ERROR)
ifp->if_collisions++;
if (err == MVNETA_TX_UNDERRUN_ERROR)
ifp->if_oerrors++;
if (err == MVNETA_TX_EXCESSIVE_COLLISION_ERRO)
ifp->if_collisions++;
}
cons = MVNETA_TX_RING_NEXT(cons);
if (i == MVNETA_PTXSU_MAX) {
MVNETA_WRITE(sc, MVNETA_PTXSU(0),
MVNETA_PTXSU_NORB(MVNETA_PTXSU_MAX));
/* tweaking the iterator inside the loop is fun */
done -= MVNETA_PTXSU_MAX;
i = 0;
}
}
sc->sc_tx_cons = cons;
bus_dmamap_sync(sc->sc_dmat, MVNETA_DMA_MAP(sc->sc_txring), 0,
MVNETA_DMA_LEN(sc->sc_txring),
BUS_DMASYNC_PREREAD);
if (i > 0) {
MVNETA_WRITE(sc, MVNETA_PTXSU(0),
MVNETA_PTXSU_NORB(i));
}
if (ifq_is_oactive(ifq))
ifq_restart(ifq);
}
uint8_t
mvneta_crc8(const uint8_t *data, size_t size)
{
int bit;
uint8_t byte;
uint8_t crc = 0;
const uint8_t poly = 0x07;
while(size--)
for (byte = *data++, bit = NBBY-1; bit >= 0; bit--)
crc = (crc << 1) ^ ((((crc >> 7) ^ (byte >> bit)) & 1) ? poly : 0);
return crc;
}
CTASSERT(MVNETA_NDFSMT == MVNETA_NDFOMT);
void
mvneta_iff(struct mvneta_softc *sc)
{
struct arpcom *ac = &sc->sc_ac;
struct ifnet *ifp = &sc->sc_ac.ac_if;
struct ether_multi *enm;
struct ether_multistep step;
uint32_t dfut[MVNETA_NDFUT], dfsmt[MVNETA_NDFSMT], dfomt[MVNETA_NDFOMT];
uint32_t pxc;
int i;
const uint8_t special[ETHER_ADDR_LEN] = {0x01,0x00,0x5e,0x00,0x00,0x00};
pxc = MVNETA_READ(sc, MVNETA_PXC);
pxc &= ~(MVNETA_PXC_RB | MVNETA_PXC_RBIP | MVNETA_PXC_RBARP | MVNETA_PXC_UPM);
ifp->if_flags &= ~IFF_ALLMULTI;
memset(dfut, 0, sizeof(dfut));
memset(dfsmt, 0, sizeof(dfsmt));
memset(dfomt, 0, sizeof(dfomt));
if (ifp->if_flags & IFF_PROMISC || ac->ac_multirangecnt > 0) {
ifp->if_flags |= IFF_ALLMULTI;
if (ifp->if_flags & IFF_PROMISC)
pxc |= MVNETA_PXC_UPM;
for (i = 0; i < MVNETA_NDFSMT; i++) {
dfsmt[i] = dfomt[i] =
MVNETA_DF(0, MVNETA_DF_QUEUE(0) | MVNETA_DF_PASS) |
MVNETA_DF(1, MVNETA_DF_QUEUE(0) | MVNETA_DF_PASS) |
MVNETA_DF(2, MVNETA_DF_QUEUE(0) | MVNETA_DF_PASS) |
MVNETA_DF(3, MVNETA_DF_QUEUE(0) | MVNETA_DF_PASS);
}
} else {
ETHER_FIRST_MULTI(step, ac, enm);
while (enm != NULL) {
/* chip handles some IPv4 multicast specially */
if (memcmp(enm->enm_addrlo, special, 5) == 0) {
i = enm->enm_addrlo[5];
dfsmt[i>>2] |=
MVNETA_DF(i&3, MVNETA_DF_QUEUE(0) | MVNETA_DF_PASS);
} else {
i = mvneta_crc8(enm->enm_addrlo, ETHER_ADDR_LEN);
dfomt[i>>2] |=
MVNETA_DF(i&3, MVNETA_DF_QUEUE(0) | MVNETA_DF_PASS);
}
ETHER_NEXT_MULTI(step, enm);
}
}
MVNETA_WRITE(sc, MVNETA_PXC, pxc);
/* Set Destination Address Filter Unicast Table */
i = sc->sc_enaddr[5] & 0xf; /* last nibble */
dfut[i>>2] = MVNETA_DF(i&3, MVNETA_DF_QUEUE(0) | MVNETA_DF_PASS);
MVNETA_WRITE_FILTER(sc, MVNETA_DFUT, dfut, MVNETA_NDFUT);
/* Set Destination Address Filter Multicast Tables */
MVNETA_WRITE_FILTER(sc, MVNETA_DFSMT, dfsmt, MVNETA_NDFSMT);
MVNETA_WRITE_FILTER(sc, MVNETA_DFOMT, dfomt, MVNETA_NDFOMT);
}
struct mvneta_dmamem *
mvneta_dmamem_alloc(struct mvneta_softc *sc, bus_size_t size, bus_size_t align)
{
struct mvneta_dmamem *mdm;
int nsegs;
mdm = malloc(sizeof(*mdm), M_DEVBUF, M_WAITOK | M_ZERO);
mdm->mdm_size = size;
if (bus_dmamap_create(sc->sc_dmat, size, 1, size, 0,
BUS_DMA_WAITOK | BUS_DMA_ALLOCNOW, &mdm->mdm_map) != 0)
goto mdmfree;
if (bus_dmamem_alloc(sc->sc_dmat, size, align, 0, &mdm->mdm_seg, 1,
&nsegs, BUS_DMA_WAITOK) != 0)
goto destroy;
if (bus_dmamem_map(sc->sc_dmat, &mdm->mdm_seg, nsegs, size,
&mdm->mdm_kva, BUS_DMA_WAITOK|BUS_DMA_COHERENT) != 0)
goto free;
if (bus_dmamap_load(sc->sc_dmat, mdm->mdm_map, mdm->mdm_kva, size,
NULL, BUS_DMA_WAITOK) != 0)
goto unmap;
bzero(mdm->mdm_kva, size);
return (mdm);
unmap:
bus_dmamem_unmap(sc->sc_dmat, mdm->mdm_kva, size);
free:
bus_dmamem_free(sc->sc_dmat, &mdm->mdm_seg, 1);
destroy:
bus_dmamap_destroy(sc->sc_dmat, mdm->mdm_map);
mdmfree:
free(mdm, M_DEVBUF, 0);
return (NULL);
}
void
mvneta_dmamem_free(struct mvneta_softc *sc, struct mvneta_dmamem *mdm)
{
bus_dmamem_unmap(sc->sc_dmat, mdm->mdm_kva, mdm->mdm_size);
bus_dmamem_free(sc->sc_dmat, &mdm->mdm_seg, 1);
bus_dmamap_destroy(sc->sc_dmat, mdm->mdm_map);
free(mdm, M_DEVBUF, 0);
}
static inline struct mbuf *
mvneta_alloc_mbuf(struct mvneta_softc *sc, bus_dmamap_t map)
{
struct mbuf *m = NULL;
m = MCLGETL(NULL, M_DONTWAIT, MCLBYTES);
if (m == NULL)
return (NULL);
m->m_len = m->m_pkthdr.len = MCLBYTES;
if (bus_dmamap_load_mbuf(sc->sc_dmat, map, m, BUS_DMA_NOWAIT) != 0) {
printf("%s: could not load mbuf DMA map", sc->sc_dev.dv_xname);
m_freem(m);
return (NULL);
}
bus_dmamap_sync(sc->sc_dmat, map, 0,
m->m_pkthdr.len, BUS_DMASYNC_PREREAD);
return (m);
}
void
mvneta_fill_rx_ring(struct mvneta_softc *sc)
{
struct mvneta_rx_desc *rxd;
struct mvneta_buf *rxb;
unsigned int slots, used = 0;
unsigned int prod;
bus_dmamap_sync(sc->sc_dmat, MVNETA_DMA_MAP(sc->sc_rxring),
0, MVNETA_DMA_LEN(sc->sc_rxring), BUS_DMASYNC_POSTWRITE);
prod = sc->sc_rx_prod;
for (slots = if_rxr_get(&sc->sc_rx_ring, MVNETA_PRXSU_MAX);
slots > 0; slots--) {
rxb = &sc->sc_rxbuf[prod];
rxb->tb_m = mvneta_alloc_mbuf(sc, rxb->tb_map);
if (rxb->tb_m == NULL)
break;
rxd = &sc->sc_rxdesc[prod];
rxd->cmdsts = 0;
rxd->bufsize = 0;
rxd->bytecnt = 0;
rxd->bufptr = rxb->tb_map->dm_segs[0].ds_addr;
rxd->nextdescptr = 0;
rxd->_padding[0] = 0;
rxd->_padding[1] = 0;
rxd->_padding[2] = 0;
rxd->_padding[3] = 0;
prod = MVNETA_RX_RING_NEXT(prod);
used++;
}
if_rxr_put(&sc->sc_rx_ring, slots);
sc->sc_rx_prod = prod;
bus_dmamap_sync(sc->sc_dmat, MVNETA_DMA_MAP(sc->sc_rxring),
0, MVNETA_DMA_LEN(sc->sc_rxring), BUS_DMASYNC_PREWRITE);
if (used > 0)
MVNETA_WRITE(sc, MVNETA_PRXSU(0), MVNETA_PRXSU_NOND(used));
}
#if NKSTAT > 0
/* this is used to sort and look up the array of kstats quickly */
enum mvneta_stat {
mvneta_stat_good_octets_received,
mvneta_stat_bad_octets_received,
mvneta_stat_good_frames_received,
mvneta_stat_mac_trans_error,
mvneta_stat_bad_frames_received,
mvneta_stat_broadcast_frames_received,
mvneta_stat_multicast_frames_received,
mvneta_stat_frames_64_octets,
mvneta_stat_frames_65_to_127_octets,
mvneta_stat_frames_128_to_255_octets,
mvneta_stat_frames_256_to_511_octets,
mvneta_stat_frames_512_to_1023_octets,
mvneta_stat_frames_1024_to_max_octets,
mvneta_stat_good_octets_sent,
mvneta_stat_good_frames_sent,
mvneta_stat_excessive_collision,
mvneta_stat_multicast_frames_sent,
mvneta_stat_broadcast_frames_sent,
mvneta_stat_unrecog_mac_control_received,
mvneta_stat_good_fc_received,
mvneta_stat_bad_fc_received,
mvneta_stat_undersize,
mvneta_stat_fc_sent,
mvneta_stat_fragments,
mvneta_stat_oversize,
mvneta_stat_jabber,
mvneta_stat_mac_rcv_error,
mvneta_stat_bad_crc,
mvneta_stat_collisions,
mvneta_stat_late_collisions,
mvneta_stat_port_discard,
mvneta_stat_port_overrun,
mvnet_stat_count
};
struct mvneta_counter {
const char *name;
enum kstat_kv_unit unit;
bus_size_t reg;
};
static const struct mvneta_counter mvneta_counters[] = {
[mvneta_stat_good_octets_received] =
{ "rx good", KSTAT_KV_U_BYTES, 0x0 /* 64bit */ },
[mvneta_stat_bad_octets_received] =
{ "rx bad", KSTAT_KV_U_BYTES, 0x3008 },
[mvneta_stat_good_frames_received] =
{ "rx good", KSTAT_KV_U_PACKETS, 0x3010 },
[mvneta_stat_mac_trans_error] =
{ "tx mac error", KSTAT_KV_U_PACKETS, 0x300c },
[mvneta_stat_bad_frames_received] =
{ "rx bad", KSTAT_KV_U_PACKETS, 0x3014 },
[mvneta_stat_broadcast_frames_received] =
{ "rx bcast", KSTAT_KV_U_PACKETS, 0x3018 },
[mvneta_stat_multicast_frames_received] =
{ "rx mcast", KSTAT_KV_U_PACKETS, 0x301c },
[mvneta_stat_frames_64_octets] =
{ "64B", KSTAT_KV_U_PACKETS, 0x3020 },
[mvneta_stat_frames_65_to_127_octets] =
{ "65-127B", KSTAT_KV_U_PACKETS, 0x3024 },
[mvneta_stat_frames_128_to_255_octets] =
{ "128-255B", KSTAT_KV_U_PACKETS, 0x3028 },
[mvneta_stat_frames_256_to_511_octets] =
{ "256-511B", KSTAT_KV_U_PACKETS, 0x302c },
[mvneta_stat_frames_512_to_1023_octets] =
{ "512-1023B", KSTAT_KV_U_PACKETS, 0x3030 },
[mvneta_stat_frames_1024_to_max_octets] =
{ "1024-maxB", KSTAT_KV_U_PACKETS, 0x3034 },
[mvneta_stat_good_octets_sent] =
{ "tx good", KSTAT_KV_U_BYTES, 0x0 /* 64bit */ },
[mvneta_stat_good_frames_sent] =
{ "tx good", KSTAT_KV_U_PACKETS, 0x3040 },
[mvneta_stat_excessive_collision] =
{ "tx excess coll", KSTAT_KV_U_PACKETS, 0x3044 },
[mvneta_stat_multicast_frames_sent] =
{ "tx mcast", KSTAT_KV_U_PACKETS, 0x3048 },
[mvneta_stat_broadcast_frames_sent] =
{ "tx bcast", KSTAT_KV_U_PACKETS, 0x304c },
[mvneta_stat_unrecog_mac_control_received] =
{ "rx unknown fc", KSTAT_KV_U_PACKETS, 0x3050 },
[mvneta_stat_good_fc_received] =
{ "rx fc good", KSTAT_KV_U_PACKETS, 0x3058 },
[mvneta_stat_bad_fc_received] =
{ "rx fc bad", KSTAT_KV_U_PACKETS, 0x305c },
[mvneta_stat_undersize] =
{ "rx undersize", KSTAT_KV_U_PACKETS, 0x3060 },
[mvneta_stat_fc_sent] =
{ "tx fc", KSTAT_KV_U_PACKETS, 0x3054 },
[mvneta_stat_fragments] =
{ "rx fragments", KSTAT_KV_U_NONE, 0x3064 },
[mvneta_stat_oversize] =
{ "rx oversize", KSTAT_KV_U_PACKETS, 0x3068 },
[mvneta_stat_jabber] =
{ "rx jabber", KSTAT_KV_U_PACKETS, 0x306c },
[mvneta_stat_mac_rcv_error] =
{ "rx mac errors", KSTAT_KV_U_PACKETS, 0x3070 },
[mvneta_stat_bad_crc] =
{ "rx bad crc", KSTAT_KV_U_PACKETS, 0x3074 },
[mvneta_stat_collisions] =
{ "rx colls", KSTAT_KV_U_PACKETS, 0x3078 },
[mvneta_stat_late_collisions] =
{ "rx late colls", KSTAT_KV_U_PACKETS, 0x307c },
[mvneta_stat_port_discard] =
{ "rx discard", KSTAT_KV_U_PACKETS, MVNETA_PXDFC },
[mvneta_stat_port_overrun] =
{ "rx overrun", KSTAT_KV_U_PACKETS, MVNETA_POFC },
};
CTASSERT(nitems(mvneta_counters) == mvnet_stat_count);
int
mvneta_kstat_read(struct kstat *ks)
{
struct mvneta_softc *sc = ks->ks_softc;
struct kstat_kv *kvs = ks->ks_data;
unsigned int i;
uint32_t hi, lo;
for (i = 0; i < nitems(mvneta_counters); i++) {
const struct mvneta_counter *c = &mvneta_counters[i];
if (c->reg == 0)
continue;
kstat_kv_u64(&kvs[i]) += (uint64_t)MVNETA_READ(sc, c->reg);
}
/* handle the exceptions */
lo = MVNETA_READ(sc, 0x3000);
hi = MVNETA_READ(sc, 0x3004);
kstat_kv_u64(&kvs[mvneta_stat_good_octets_received]) +=
(uint64_t)hi << 32 | (uint64_t)lo;
lo = MVNETA_READ(sc, 0x3038);
hi = MVNETA_READ(sc, 0x303c);
kstat_kv_u64(&kvs[mvneta_stat_good_octets_sent]) +=
(uint64_t)hi << 32 | (uint64_t)lo;
nanouptime(&ks->ks_updated);
return (0);
}
void
mvneta_kstat_tick(void *arg)
{
struct mvneta_softc *sc = arg;
timeout_add_sec(&sc->sc_kstat_tick, 37);
if (mtx_enter_try(&sc->sc_kstat_lock)) {
mvneta_kstat_read(sc->sc_kstat);
mtx_leave(&sc->sc_kstat_lock);
}
}
void
mvneta_kstat_attach(struct mvneta_softc *sc)
{
struct kstat *ks;
struct kstat_kv *kvs;
unsigned int i;
mtx_init(&sc->sc_kstat_lock, IPL_SOFTCLOCK);
timeout_set(&sc->sc_kstat_tick, mvneta_kstat_tick, sc);
ks = kstat_create(sc->sc_dev.dv_xname, 0, "mvneta-stats", 0,
KSTAT_T_KV, 0);
if (ks == NULL)
return;
kvs = mallocarray(nitems(mvneta_counters), sizeof(*kvs),
M_DEVBUF, M_WAITOK|M_ZERO);
for (i = 0; i < nitems(mvneta_counters); i++) {
const struct mvneta_counter *c = &mvneta_counters[i];
kstat_kv_unit_init(&kvs[i], c->name,
KSTAT_KV_T_COUNTER64, c->unit);
}
ks->ks_softc = sc;
ks->ks_data = kvs;
ks->ks_datalen = nitems(mvneta_counters) * sizeof(*kvs);
ks->ks_read = mvneta_kstat_read;
kstat_set_mutex(ks, &sc->sc_kstat_lock);
kstat_install(ks);
sc->sc_kstat = ks;
timeout_add_sec(&sc->sc_kstat_tick, 37);
}
#endif