213eac1f2c
queue length. The default value for this parameter is 50, which is quite low for many of today's uses and the only way to modify this parameter right now is to edit if_var.h file. Also add read-only sysctl with the same name, so that it's possible to retrieve the current value. MFC after: 1 month
1780 lines
47 KiB
C
1780 lines
47 KiB
C
/*-
|
|
* Copyright (c) 2006-2008 Sam Leffler. 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 <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
/*
|
|
* Intel XScale NPE Ethernet driver.
|
|
*
|
|
* This driver handles the two ports present on the IXP425.
|
|
* Packet processing is done by the Network Processing Engines
|
|
* (NPE's) that work together with a MAC and PHY. The MAC
|
|
* is also mapped to the XScale cpu; the PHY is accessed via
|
|
* the MAC. NPE-XScale communication happens through h/w
|
|
* queues managed by the Q Manager block.
|
|
*
|
|
* The code here replaces the ethAcc, ethMii, and ethDB classes
|
|
* in the Intel Access Library (IAL) and the OS-specific driver.
|
|
*
|
|
* XXX add vlan support
|
|
*/
|
|
#ifdef HAVE_KERNEL_OPTION_HEADERS
|
|
#include "opt_device_polling.h"
|
|
#endif
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/mbuf.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/module.h>
|
|
#include <sys/rman.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/sockio.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/endian.h>
|
|
#include <machine/bus.h>
|
|
|
|
#include <net/ethernet.h>
|
|
#include <net/if.h>
|
|
#include <net/if_arp.h>
|
|
#include <net/if_dl.h>
|
|
#include <net/if_media.h>
|
|
#include <net/if_mib.h>
|
|
#include <net/if_types.h>
|
|
|
|
#ifdef INET
|
|
#include <netinet/in.h>
|
|
#include <netinet/in_systm.h>
|
|
#include <netinet/in_var.h>
|
|
#include <netinet/ip.h>
|
|
#endif
|
|
|
|
#include <net/bpf.h>
|
|
#include <net/bpfdesc.h>
|
|
|
|
#include <arm/xscale/ixp425/ixp425reg.h>
|
|
#include <arm/xscale/ixp425/ixp425var.h>
|
|
#include <arm/xscale/ixp425/ixp425_qmgr.h>
|
|
#include <arm/xscale/ixp425/ixp425_npevar.h>
|
|
|
|
#include <dev/mii/mii.h>
|
|
#include <dev/mii/miivar.h>
|
|
#include <arm/xscale/ixp425/if_npereg.h>
|
|
|
|
#include <machine/armreg.h>
|
|
|
|
#include "miibus_if.h"
|
|
|
|
/*
|
|
* XXX: For the main bus dma tag. Can go away if the new method to get the
|
|
* dma tag from the parent got MFC'd into RELENG_6.
|
|
*/
|
|
extern struct ixp425_softc *ixp425_softc;
|
|
|
|
struct npebuf {
|
|
struct npebuf *ix_next; /* chain to next buffer */
|
|
void *ix_m; /* backpointer to mbuf */
|
|
bus_dmamap_t ix_map; /* bus dma map for associated data */
|
|
struct npehwbuf *ix_hw; /* associated h/w block */
|
|
uint32_t ix_neaddr; /* phys address of ix_hw */
|
|
};
|
|
|
|
struct npedma {
|
|
const char* name;
|
|
int nbuf; /* # npebuf's allocated */
|
|
bus_dma_tag_t mtag; /* bus dma tag for mbuf data */
|
|
struct npehwbuf *hwbuf; /* NPE h/w buffers */
|
|
bus_dma_tag_t buf_tag; /* tag+map for NPE buffers */
|
|
bus_dmamap_t buf_map;
|
|
bus_addr_t buf_phys; /* phys addr of buffers */
|
|
struct npebuf *buf; /* s/w buffers (1-1 w/ h/w) */
|
|
};
|
|
|
|
struct npe_softc {
|
|
/* XXX mii requires this be first; do not move! */
|
|
struct ifnet *sc_ifp; /* ifnet pointer */
|
|
struct mtx sc_mtx; /* basically a perimeter lock */
|
|
device_t sc_dev;
|
|
bus_space_tag_t sc_iot;
|
|
bus_space_handle_t sc_ioh; /* MAC register window */
|
|
device_t sc_mii; /* child miibus */
|
|
bus_space_handle_t sc_miih; /* MII register window */
|
|
int sc_npeid;
|
|
struct ixpnpe_softc *sc_npe; /* NPE support */
|
|
int sc_debug; /* DPRINTF* control */
|
|
int sc_tickinterval;
|
|
struct callout tick_ch; /* Tick callout */
|
|
int npe_watchdog_timer;
|
|
struct npedma txdma;
|
|
struct npebuf *tx_free; /* list of free tx buffers */
|
|
struct npedma rxdma;
|
|
bus_addr_t buf_phys; /* XXX for returning a value */
|
|
int rx_qid; /* rx qid */
|
|
int rx_freeqid; /* rx free buffers qid */
|
|
int tx_qid; /* tx qid */
|
|
int tx_doneqid; /* tx completed qid */
|
|
int sc_phy; /* PHY id */
|
|
struct ifmib_iso_8802_3 mibdata;
|
|
bus_dma_tag_t sc_stats_tag; /* bus dma tag for stats block */
|
|
struct npestats *sc_stats;
|
|
bus_dmamap_t sc_stats_map;
|
|
bus_addr_t sc_stats_phys; /* phys addr of sc_stats */
|
|
struct npestats sc_totals; /* accumulated sc_stats */
|
|
};
|
|
|
|
/*
|
|
* Static configuration for IXP425. The tx and
|
|
* rx free Q id's are fixed by the NPE microcode. The
|
|
* rx Q id's are programmed to be separate to simplify
|
|
* multi-port processing. It may be better to handle
|
|
* all traffic through one Q (as done by the Intel drivers).
|
|
*
|
|
* Note that the PHY's are accessible only from MAC B on the
|
|
* IXP425 and from MAC C on other devices. This and other
|
|
* platform-specific assumptions are handled with hints.
|
|
*/
|
|
static const struct {
|
|
uint32_t macbase;
|
|
uint32_t miibase;
|
|
int phy; /* phy id */
|
|
uint8_t rx_qid;
|
|
uint8_t rx_freeqid;
|
|
uint8_t tx_qid;
|
|
uint8_t tx_doneqid;
|
|
} npeconfig[NPE_MAX] = {
|
|
[NPE_A] = {
|
|
.macbase = IXP435_MAC_A_HWBASE,
|
|
.miibase = IXP425_MAC_C_HWBASE,
|
|
.phy = 2,
|
|
.rx_qid = 4,
|
|
.rx_freeqid = 26,
|
|
.tx_qid = 23,
|
|
.tx_doneqid = 31
|
|
},
|
|
[NPE_B] = {
|
|
.macbase = IXP425_MAC_B_HWBASE,
|
|
.miibase = IXP425_MAC_B_HWBASE,
|
|
.phy = 0,
|
|
.rx_qid = 4,
|
|
.rx_freeqid = 27,
|
|
.tx_qid = 24,
|
|
.tx_doneqid = 31
|
|
},
|
|
[NPE_C] = {
|
|
.macbase = IXP425_MAC_C_HWBASE,
|
|
.miibase = IXP425_MAC_B_HWBASE,
|
|
.phy = 1,
|
|
.rx_qid = 12,
|
|
.rx_freeqid = 28,
|
|
.tx_qid = 25,
|
|
.tx_doneqid = 31
|
|
},
|
|
};
|
|
static struct npe_softc *npes[NPE_MAX]; /* NB: indexed by npeid */
|
|
|
|
static __inline uint32_t
|
|
RD4(struct npe_softc *sc, bus_size_t off)
|
|
{
|
|
return bus_space_read_4(sc->sc_iot, sc->sc_ioh, off);
|
|
}
|
|
|
|
static __inline void
|
|
WR4(struct npe_softc *sc, bus_size_t off, uint32_t val)
|
|
{
|
|
bus_space_write_4(sc->sc_iot, sc->sc_ioh, off, val);
|
|
}
|
|
|
|
#define NPE_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
|
|
#define NPE_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
|
|
#define NPE_LOCK_INIT(_sc) \
|
|
mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->sc_dev), \
|
|
MTX_NETWORK_LOCK, MTX_DEF)
|
|
#define NPE_LOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx);
|
|
#define NPE_ASSERT_LOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_OWNED);
|
|
#define NPE_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED);
|
|
|
|
static devclass_t npe_devclass;
|
|
|
|
static int override_npeid(device_t, const char *resname, int *val);
|
|
static int npe_activate(device_t dev);
|
|
static void npe_deactivate(device_t dev);
|
|
static int npe_ifmedia_update(struct ifnet *ifp);
|
|
static void npe_ifmedia_status(struct ifnet *ifp, struct ifmediareq *ifmr);
|
|
static void npe_setmac(struct npe_softc *sc, u_char *eaddr);
|
|
static void npe_getmac(struct npe_softc *sc, u_char *eaddr);
|
|
static void npe_txdone(int qid, void *arg);
|
|
static int npe_rxbuf_init(struct npe_softc *, struct npebuf *,
|
|
struct mbuf *);
|
|
static int npe_rxdone(int qid, void *arg);
|
|
static void npeinit(void *);
|
|
static void npestart_locked(struct ifnet *);
|
|
static void npestart(struct ifnet *);
|
|
static void npestop(struct npe_softc *);
|
|
static void npewatchdog(struct npe_softc *);
|
|
static int npeioctl(struct ifnet * ifp, u_long, caddr_t);
|
|
|
|
static int npe_setrxqosentry(struct npe_softc *, int classix,
|
|
int trafclass, int qid);
|
|
static int npe_setportaddress(struct npe_softc *, const uint8_t mac[]);
|
|
static int npe_setfirewallmode(struct npe_softc *, int onoff);
|
|
static int npe_updatestats(struct npe_softc *);
|
|
#if 0
|
|
static int npe_getstats(struct npe_softc *);
|
|
static uint32_t npe_getimageid(struct npe_softc *);
|
|
static int npe_setloopback(struct npe_softc *, int ena);
|
|
#endif
|
|
|
|
/* NB: all tx done processing goes through one queue */
|
|
static int tx_doneqid = -1;
|
|
|
|
SYSCTL_NODE(_hw, OID_AUTO, npe, CTLFLAG_RD, 0, "IXP4XX NPE driver parameters");
|
|
|
|
static int npe_debug = 0;
|
|
SYSCTL_INT(_hw_npe, OID_AUTO, debug, CTLFLAG_RW, &npe_debug,
|
|
0, "IXP4XX NPE network interface debug msgs");
|
|
TUNABLE_INT("hw.npe.debug", &npe_debug);
|
|
#define DPRINTF(sc, fmt, ...) do { \
|
|
if (sc->sc_debug) device_printf(sc->sc_dev, fmt, __VA_ARGS__); \
|
|
} while (0)
|
|
#define DPRINTFn(n, sc, fmt, ...) do { \
|
|
if (sc->sc_debug >= n) device_printf(sc->sc_dev, fmt, __VA_ARGS__);\
|
|
} while (0)
|
|
static int npe_tickinterval = 3; /* npe_tick frequency (secs) */
|
|
SYSCTL_INT(_hw_npe, OID_AUTO, tickinterval, CTLFLAG_RD, &npe_tickinterval,
|
|
0, "periodic work interval (secs)");
|
|
TUNABLE_INT("hw.npe.tickinterval", &npe_tickinterval);
|
|
|
|
static int npe_rxbuf = 64; /* # rx buffers to allocate */
|
|
SYSCTL_INT(_hw_npe, OID_AUTO, rxbuf, CTLFLAG_RD, &npe_rxbuf,
|
|
0, "rx buffers allocated");
|
|
TUNABLE_INT("hw.npe.rxbuf", &npe_rxbuf);
|
|
static int npe_txbuf = 128; /* # tx buffers to allocate */
|
|
SYSCTL_INT(_hw_npe, OID_AUTO, txbuf, CTLFLAG_RD, &npe_txbuf,
|
|
0, "tx buffers allocated");
|
|
TUNABLE_INT("hw.npe.txbuf", &npe_txbuf);
|
|
|
|
static int
|
|
unit2npeid(int unit)
|
|
{
|
|
static const int npeidmap[2][3] = {
|
|
/* on 425 A is for HSS, B & C are for Ethernet */
|
|
{ NPE_B, NPE_C, -1 }, /* IXP425 */
|
|
/* 435 only has A & C, order C then A */
|
|
{ NPE_C, NPE_A, -1 }, /* IXP435 */
|
|
};
|
|
/* XXX check feature register instead */
|
|
return (unit < 3 ? npeidmap[
|
|
(cpu_id() & CPU_ID_CPU_MASK) == CPU_ID_IXP435][unit] : -1);
|
|
}
|
|
|
|
static int
|
|
npe_probe(device_t dev)
|
|
{
|
|
static const char *desc[NPE_MAX] = {
|
|
[NPE_A] = "IXP NPE-A",
|
|
[NPE_B] = "IXP NPE-B",
|
|
[NPE_C] = "IXP NPE-C"
|
|
};
|
|
int unit = device_get_unit(dev);
|
|
int npeid;
|
|
|
|
if (unit > 2 ||
|
|
(ixp4xx_read_feature_bits() &
|
|
(unit == 0 ? EXP_FCTRL_ETH0 : EXP_FCTRL_ETH1)) == 0)
|
|
return EINVAL;
|
|
|
|
npeid = -1;
|
|
if (!override_npeid(dev, "npeid", &npeid))
|
|
npeid = unit2npeid(unit);
|
|
if (npeid == -1) {
|
|
device_printf(dev, "unit %d not supported\n", unit);
|
|
return EINVAL;
|
|
}
|
|
device_set_desc(dev, desc[npeid]);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
npe_attach(device_t dev)
|
|
{
|
|
struct npe_softc *sc = device_get_softc(dev);
|
|
struct ixp425_softc *sa = device_get_softc(device_get_parent(dev));
|
|
struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(dev);
|
|
struct sysctl_oid *tree = device_get_sysctl_tree(dev);
|
|
struct ifnet *ifp;
|
|
int error;
|
|
u_char eaddr[6];
|
|
|
|
sc->sc_dev = dev;
|
|
sc->sc_iot = sa->sc_iot;
|
|
NPE_LOCK_INIT(sc);
|
|
callout_init_mtx(&sc->tick_ch, &sc->sc_mtx, 0);
|
|
sc->sc_debug = npe_debug;
|
|
sc->sc_tickinterval = npe_tickinterval;
|
|
|
|
ifp = if_alloc(IFT_ETHER);
|
|
if (ifp == NULL) {
|
|
device_printf(dev, "cannot allocate ifnet\n");
|
|
error = EIO; /* XXX */
|
|
goto out;
|
|
}
|
|
/* NB: must be setup prior to invoking mii code */
|
|
sc->sc_ifp = ifp;
|
|
|
|
error = npe_activate(dev);
|
|
if (error) {
|
|
device_printf(dev, "cannot activate npe\n");
|
|
goto out;
|
|
}
|
|
|
|
npe_getmac(sc, eaddr);
|
|
|
|
ifp->if_softc = sc;
|
|
if_initname(ifp, device_get_name(dev), device_get_unit(dev));
|
|
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
|
|
ifp->if_start = npestart;
|
|
ifp->if_ioctl = npeioctl;
|
|
ifp->if_init = npeinit;
|
|
IFQ_SET_MAXLEN(&ifp->if_snd, sc->txdma.nbuf - 1);
|
|
ifp->if_snd.ifq_drv_maxlen = ifqmaxlen;
|
|
IFQ_SET_READY(&ifp->if_snd);
|
|
ifp->if_linkmib = &sc->mibdata;
|
|
ifp->if_linkmiblen = sizeof(sc->mibdata);
|
|
sc->mibdata.dot3Compliance = DOT3COMPLIANCE_STATS;
|
|
/* device supports oversided vlan frames */
|
|
ifp->if_capabilities |= IFCAP_VLAN_MTU;
|
|
ifp->if_capenable = ifp->if_capabilities;
|
|
#ifdef DEVICE_POLLING
|
|
ifp->if_capabilities |= IFCAP_POLLING;
|
|
#endif
|
|
|
|
SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "debug",
|
|
CTLFLAG_RW, &sc->sc_debug, 0, "control debugging printfs");
|
|
SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "tickinterval",
|
|
CTLFLAG_RW, &sc->sc_tickinterval, 0, "periodic work frequency");
|
|
SYSCTL_ADD_STRUCT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "stats",
|
|
CTLFLAG_RD, &sc->sc_totals, npestats, "onboard stats");
|
|
|
|
ether_ifattach(ifp, eaddr);
|
|
return 0;
|
|
out:
|
|
if (ifp != NULL)
|
|
if_free(ifp);
|
|
NPE_LOCK_DESTROY(sc);
|
|
npe_deactivate(dev);
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
npe_detach(device_t dev)
|
|
{
|
|
struct npe_softc *sc = device_get_softc(dev);
|
|
struct ifnet *ifp = sc->sc_ifp;
|
|
|
|
#ifdef DEVICE_POLLING
|
|
if (ifp->if_capenable & IFCAP_POLLING)
|
|
ether_poll_deregister(ifp);
|
|
#endif
|
|
npestop(sc);
|
|
if (ifp != NULL) {
|
|
ether_ifdetach(ifp);
|
|
if_free(ifp);
|
|
}
|
|
NPE_LOCK_DESTROY(sc);
|
|
npe_deactivate(dev);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Compute and install the multicast filter.
|
|
*/
|
|
static void
|
|
npe_setmcast(struct npe_softc *sc)
|
|
{
|
|
struct ifnet *ifp = sc->sc_ifp;
|
|
uint8_t mask[ETHER_ADDR_LEN], addr[ETHER_ADDR_LEN];
|
|
int i;
|
|
|
|
if (ifp->if_flags & IFF_PROMISC) {
|
|
memset(mask, 0, ETHER_ADDR_LEN);
|
|
memset(addr, 0, ETHER_ADDR_LEN);
|
|
} else if (ifp->if_flags & IFF_ALLMULTI) {
|
|
static const uint8_t allmulti[ETHER_ADDR_LEN] =
|
|
{ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
|
memcpy(mask, allmulti, ETHER_ADDR_LEN);
|
|
memcpy(addr, allmulti, ETHER_ADDR_LEN);
|
|
} else {
|
|
uint8_t clr[ETHER_ADDR_LEN], set[ETHER_ADDR_LEN];
|
|
struct ifmultiaddr *ifma;
|
|
const uint8_t *mac;
|
|
|
|
memset(clr, 0, ETHER_ADDR_LEN);
|
|
memset(set, 0xff, ETHER_ADDR_LEN);
|
|
|
|
if_maddr_rlock(ifp);
|
|
TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
|
|
if (ifma->ifma_addr->sa_family != AF_LINK)
|
|
continue;
|
|
mac = LLADDR((struct sockaddr_dl *) ifma->ifma_addr);
|
|
for (i = 0; i < ETHER_ADDR_LEN; i++) {
|
|
clr[i] |= mac[i];
|
|
set[i] &= mac[i];
|
|
}
|
|
}
|
|
if_maddr_runlock(ifp);
|
|
|
|
for (i = 0; i < ETHER_ADDR_LEN; i++) {
|
|
mask[i] = set[i] | ~clr[i];
|
|
addr[i] = set[i];
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Write the mask and address registers.
|
|
*/
|
|
for (i = 0; i < ETHER_ADDR_LEN; i++) {
|
|
WR4(sc, NPE_MAC_ADDR_MASK(i), mask[i]);
|
|
WR4(sc, NPE_MAC_ADDR(i), addr[i]);
|
|
}
|
|
}
|
|
|
|
static void
|
|
npe_getaddr(void *arg, bus_dma_segment_t *segs, int nsegs, int error)
|
|
{
|
|
struct npe_softc *sc;
|
|
|
|
if (error != 0)
|
|
return;
|
|
sc = (struct npe_softc *)arg;
|
|
sc->buf_phys = segs[0].ds_addr;
|
|
}
|
|
|
|
static int
|
|
npe_dma_setup(struct npe_softc *sc, struct npedma *dma,
|
|
const char *name, int nbuf, int maxseg)
|
|
{
|
|
int error, i;
|
|
|
|
memset(dma, 0, sizeof(*dma));
|
|
|
|
dma->name = name;
|
|
dma->nbuf = nbuf;
|
|
|
|
/* DMA tag for mapped mbufs */
|
|
error = bus_dma_tag_create(ixp425_softc->sc_dmat, 1, 0,
|
|
BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL,
|
|
MCLBYTES, maxseg, MCLBYTES, 0,
|
|
busdma_lock_mutex, &sc->sc_mtx, &dma->mtag);
|
|
if (error != 0) {
|
|
device_printf(sc->sc_dev, "unable to create %s mbuf dma tag, "
|
|
"error %u\n", dma->name, error);
|
|
return error;
|
|
}
|
|
|
|
/* DMA tag and map for the NPE buffers */
|
|
error = bus_dma_tag_create(ixp425_softc->sc_dmat, sizeof(uint32_t), 0,
|
|
BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL,
|
|
nbuf * sizeof(struct npehwbuf), 1,
|
|
nbuf * sizeof(struct npehwbuf), 0,
|
|
busdma_lock_mutex, &sc->sc_mtx, &dma->buf_tag);
|
|
if (error != 0) {
|
|
device_printf(sc->sc_dev,
|
|
"unable to create %s npebuf dma tag, error %u\n",
|
|
dma->name, error);
|
|
return error;
|
|
}
|
|
/* XXX COHERENT for now */
|
|
if (bus_dmamem_alloc(dma->buf_tag, (void **)&dma->hwbuf,
|
|
BUS_DMA_NOWAIT | BUS_DMA_ZERO | BUS_DMA_COHERENT,
|
|
&dma->buf_map) != 0) {
|
|
device_printf(sc->sc_dev,
|
|
"unable to allocate memory for %s h/w buffers, error %u\n",
|
|
dma->name, error);
|
|
return error;
|
|
}
|
|
/* XXX M_TEMP */
|
|
dma->buf = malloc(nbuf * sizeof(struct npebuf), M_TEMP, M_NOWAIT | M_ZERO);
|
|
if (dma->buf == NULL) {
|
|
device_printf(sc->sc_dev,
|
|
"unable to allocate memory for %s s/w buffers\n",
|
|
dma->name);
|
|
return error;
|
|
}
|
|
if (bus_dmamap_load(dma->buf_tag, dma->buf_map,
|
|
dma->hwbuf, nbuf*sizeof(struct npehwbuf), npe_getaddr, sc, 0) != 0) {
|
|
device_printf(sc->sc_dev,
|
|
"unable to map memory for %s h/w buffers, error %u\n",
|
|
dma->name, error);
|
|
return error;
|
|
}
|
|
dma->buf_phys = sc->buf_phys;
|
|
for (i = 0; i < dma->nbuf; i++) {
|
|
struct npebuf *npe = &dma->buf[i];
|
|
struct npehwbuf *hw = &dma->hwbuf[i];
|
|
|
|
/* calculate offset to shared area */
|
|
npe->ix_neaddr = dma->buf_phys +
|
|
((uintptr_t)hw - (uintptr_t)dma->hwbuf);
|
|
KASSERT((npe->ix_neaddr & 0x1f) == 0,
|
|
("ixpbuf misaligned, PA 0x%x", npe->ix_neaddr));
|
|
error = bus_dmamap_create(dma->mtag, BUS_DMA_NOWAIT,
|
|
&npe->ix_map);
|
|
if (error != 0) {
|
|
device_printf(sc->sc_dev,
|
|
"unable to create dmamap for %s buffer %u, "
|
|
"error %u\n", dma->name, i, error);
|
|
return error;
|
|
}
|
|
npe->ix_hw = hw;
|
|
}
|
|
bus_dmamap_sync(dma->buf_tag, dma->buf_map, BUS_DMASYNC_PREWRITE);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
npe_dma_destroy(struct npe_softc *sc, struct npedma *dma)
|
|
{
|
|
int i;
|
|
|
|
if (dma->hwbuf != NULL) {
|
|
for (i = 0; i < dma->nbuf; i++) {
|
|
struct npebuf *npe = &dma->buf[i];
|
|
bus_dmamap_destroy(dma->mtag, npe->ix_map);
|
|
}
|
|
bus_dmamap_unload(dma->buf_tag, dma->buf_map);
|
|
bus_dmamem_free(dma->buf_tag, dma->hwbuf, dma->buf_map);
|
|
}
|
|
if (dma->buf != NULL)
|
|
free(dma->buf, M_TEMP);
|
|
if (dma->buf_tag)
|
|
bus_dma_tag_destroy(dma->buf_tag);
|
|
if (dma->mtag)
|
|
bus_dma_tag_destroy(dma->mtag);
|
|
memset(dma, 0, sizeof(*dma));
|
|
}
|
|
|
|
static int
|
|
override_addr(device_t dev, const char *resname, int *base)
|
|
{
|
|
int unit = device_get_unit(dev);
|
|
const char *resval;
|
|
|
|
/* XXX warn for wrong hint type */
|
|
if (resource_string_value("npe", unit, resname, &resval) != 0)
|
|
return 0;
|
|
switch (resval[0]) {
|
|
case 'A':
|
|
*base = IXP435_MAC_A_HWBASE;
|
|
break;
|
|
case 'B':
|
|
*base = IXP425_MAC_B_HWBASE;
|
|
break;
|
|
case 'C':
|
|
*base = IXP425_MAC_C_HWBASE;
|
|
break;
|
|
default:
|
|
device_printf(dev, "Warning, bad value %s for "
|
|
"npe.%d.%s ignored\n", resval, unit, resname);
|
|
return 0;
|
|
}
|
|
if (bootverbose)
|
|
device_printf(dev, "using npe.%d.%s=%s override\n",
|
|
unit, resname, resval);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
override_npeid(device_t dev, const char *resname, int *npeid)
|
|
{
|
|
int unit = device_get_unit(dev);
|
|
const char *resval;
|
|
|
|
/* XXX warn for wrong hint type */
|
|
if (resource_string_value("npe", unit, resname, &resval) != 0)
|
|
return 0;
|
|
switch (resval[0]) {
|
|
case 'A': *npeid = NPE_A; break;
|
|
case 'B': *npeid = NPE_B; break;
|
|
case 'C': *npeid = NPE_C; break;
|
|
default:
|
|
device_printf(dev, "Warning, bad value %s for "
|
|
"npe.%d.%s ignored\n", resval, unit, resname);
|
|
return 0;
|
|
}
|
|
if (bootverbose)
|
|
device_printf(dev, "using npe.%d.%s=%s override\n",
|
|
unit, resname, resval);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
override_unit(device_t dev, const char *resname, int *val, int min, int max)
|
|
{
|
|
int unit = device_get_unit(dev);
|
|
int resval;
|
|
|
|
if (resource_int_value("npe", unit, resname, &resval) != 0)
|
|
return 0;
|
|
if (!(min <= resval && resval <= max)) {
|
|
device_printf(dev, "Warning, bad value %d for npe.%d.%s "
|
|
"ignored (value must be [%d-%d])\n", resval, unit,
|
|
resname, min, max);
|
|
return 0;
|
|
}
|
|
if (bootverbose)
|
|
device_printf(dev, "using npe.%d.%s=%d override\n",
|
|
unit, resname, resval);
|
|
*val = resval;
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
npe_mac_reset(struct npe_softc *sc)
|
|
{
|
|
/*
|
|
* Reset MAC core.
|
|
*/
|
|
WR4(sc, NPE_MAC_CORE_CNTRL, NPE_CORE_RESET);
|
|
DELAY(NPE_MAC_RESET_DELAY);
|
|
/* configure MAC to generate MDC clock */
|
|
WR4(sc, NPE_MAC_CORE_CNTRL, NPE_CORE_MDC_EN);
|
|
}
|
|
|
|
static int
|
|
npe_activate(device_t dev)
|
|
{
|
|
struct npe_softc *sc = device_get_softc(dev);
|
|
int error, i, macbase, miibase;
|
|
|
|
/*
|
|
* Setup NEP ID, MAC, and MII bindings. We allow override
|
|
* via hints to handle unexpected board configs.
|
|
*/
|
|
if (!override_npeid(dev, "npeid", &sc->sc_npeid))
|
|
sc->sc_npeid = unit2npeid(device_get_unit(dev));
|
|
sc->sc_npe = ixpnpe_attach(dev, sc->sc_npeid);
|
|
if (sc->sc_npe == NULL) {
|
|
device_printf(dev, "cannot attach ixpnpe\n");
|
|
return EIO; /* XXX */
|
|
}
|
|
|
|
/* MAC */
|
|
if (!override_addr(dev, "mac", &macbase))
|
|
macbase = npeconfig[sc->sc_npeid].macbase;
|
|
device_printf(sc->sc_dev, "MAC at 0x%x\n", macbase);
|
|
if (bus_space_map(sc->sc_iot, macbase, IXP425_REG_SIZE, 0, &sc->sc_ioh)) {
|
|
device_printf(dev, "cannot map mac registers 0x%x:0x%x\n",
|
|
macbase, IXP425_REG_SIZE);
|
|
return ENOMEM;
|
|
}
|
|
|
|
/* PHY */
|
|
if (!override_unit(dev, "phy", &sc->sc_phy, 0, MII_NPHY-1))
|
|
sc->sc_phy = npeconfig[sc->sc_npeid].phy;
|
|
if (!override_addr(dev, "mii", &miibase))
|
|
miibase = npeconfig[sc->sc_npeid].miibase;
|
|
device_printf(sc->sc_dev, "MII at 0x%x\n", miibase);
|
|
if (miibase != macbase) {
|
|
/*
|
|
* PHY is mapped through a different MAC, setup an
|
|
* additional mapping for frobbing the PHY registers.
|
|
*/
|
|
if (bus_space_map(sc->sc_iot, miibase, IXP425_REG_SIZE, 0, &sc->sc_miih)) {
|
|
device_printf(dev,
|
|
"cannot map MII registers 0x%x:0x%x\n",
|
|
miibase, IXP425_REG_SIZE);
|
|
return ENOMEM;
|
|
}
|
|
} else
|
|
sc->sc_miih = sc->sc_ioh;
|
|
|
|
/*
|
|
* Load NPE firmware and start it running.
|
|
*/
|
|
error = ixpnpe_init(sc->sc_npe);
|
|
if (error != 0) {
|
|
device_printf(dev, "cannot init NPE (error %d)\n", error);
|
|
return error;
|
|
}
|
|
|
|
/* probe for PHY */
|
|
if (mii_phy_probe(dev, &sc->sc_mii, npe_ifmedia_update, npe_ifmedia_status)) {
|
|
device_printf(dev, "cannot find PHY %d.\n", sc->sc_phy);
|
|
return ENXIO;
|
|
}
|
|
|
|
error = npe_dma_setup(sc, &sc->txdma, "tx", npe_txbuf, NPE_MAXSEG);
|
|
if (error != 0)
|
|
return error;
|
|
error = npe_dma_setup(sc, &sc->rxdma, "rx", npe_rxbuf, 1);
|
|
if (error != 0)
|
|
return error;
|
|
|
|
/* setup statistics block */
|
|
error = bus_dma_tag_create(ixp425_softc->sc_dmat, sizeof(uint32_t), 0,
|
|
BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL,
|
|
sizeof(struct npestats), 1, sizeof(struct npestats), 0,
|
|
busdma_lock_mutex, &sc->sc_mtx, &sc->sc_stats_tag);
|
|
if (error != 0) {
|
|
device_printf(sc->sc_dev, "unable to create stats tag, "
|
|
"error %u\n", error);
|
|
return error;
|
|
}
|
|
if (bus_dmamem_alloc(sc->sc_stats_tag, (void **)&sc->sc_stats,
|
|
BUS_DMA_NOWAIT, &sc->sc_stats_map) != 0) {
|
|
device_printf(sc->sc_dev,
|
|
"unable to allocate memory for stats block, error %u\n",
|
|
error);
|
|
return error;
|
|
}
|
|
if (bus_dmamap_load(sc->sc_stats_tag, sc->sc_stats_map,
|
|
sc->sc_stats, sizeof(struct npestats), npe_getaddr, sc, 0) != 0) {
|
|
device_printf(sc->sc_dev,
|
|
"unable to load memory for stats block, error %u\n",
|
|
error);
|
|
return error;
|
|
}
|
|
sc->sc_stats_phys = sc->buf_phys;
|
|
|
|
/*
|
|
* Setup h/w rx/tx queues. There are four q's:
|
|
* rx inbound q of rx'd frames
|
|
* rx_free pool of ixpbuf's for receiving frames
|
|
* tx outbound q of frames to send
|
|
* tx_done q of tx frames that have been processed
|
|
*
|
|
* The NPE handles the actual tx/rx process and the q manager
|
|
* handles the queues. The driver just writes entries to the
|
|
* q manager mailbox's and gets callbacks when there are rx'd
|
|
* frames to process or tx'd frames to reap. These callbacks
|
|
* are controlled by the q configurations; e.g. we get a
|
|
* callback when tx_done has 2 or more frames to process and
|
|
* when the rx q has at least one frame. These setings can
|
|
* changed at the time the q is configured.
|
|
*/
|
|
sc->rx_qid = npeconfig[sc->sc_npeid].rx_qid;
|
|
ixpqmgr_qconfig(sc->rx_qid, npe_rxbuf, 0, 1,
|
|
IX_QMGR_Q_SOURCE_ID_NOT_E, (qconfig_hand_t *)npe_rxdone, sc);
|
|
sc->rx_freeqid = npeconfig[sc->sc_npeid].rx_freeqid;
|
|
ixpqmgr_qconfig(sc->rx_freeqid, npe_rxbuf, 0, npe_rxbuf/2, 0, NULL, sc);
|
|
/*
|
|
* Setup the NPE to direct all traffic to rx_qid.
|
|
* When QoS is enabled in the firmware there are
|
|
* 8 traffic classes; otherwise just 4.
|
|
*/
|
|
for (i = 0; i < 8; i++)
|
|
npe_setrxqosentry(sc, i, 0, sc->rx_qid);
|
|
|
|
/* disable firewall mode just in case (should be off) */
|
|
npe_setfirewallmode(sc, 0);
|
|
|
|
sc->tx_qid = npeconfig[sc->sc_npeid].tx_qid;
|
|
sc->tx_doneqid = npeconfig[sc->sc_npeid].tx_doneqid;
|
|
ixpqmgr_qconfig(sc->tx_qid, npe_txbuf, 0, npe_txbuf, 0, NULL, sc);
|
|
if (tx_doneqid == -1) {
|
|
ixpqmgr_qconfig(sc->tx_doneqid, npe_txbuf, 0, 2,
|
|
IX_QMGR_Q_SOURCE_ID_NOT_E, npe_txdone, sc);
|
|
tx_doneqid = sc->tx_doneqid;
|
|
}
|
|
|
|
KASSERT(npes[sc->sc_npeid] == NULL,
|
|
("npe %u already setup", sc->sc_npeid));
|
|
npes[sc->sc_npeid] = sc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
npe_deactivate(device_t dev)
|
|
{
|
|
struct npe_softc *sc = device_get_softc(dev);
|
|
|
|
npes[sc->sc_npeid] = NULL;
|
|
|
|
/* XXX disable q's */
|
|
if (sc->sc_npe != NULL) {
|
|
ixpnpe_stop(sc->sc_npe);
|
|
ixpnpe_detach(sc->sc_npe);
|
|
}
|
|
if (sc->sc_stats != NULL) {
|
|
bus_dmamap_unload(sc->sc_stats_tag, sc->sc_stats_map);
|
|
bus_dmamem_free(sc->sc_stats_tag, sc->sc_stats,
|
|
sc->sc_stats_map);
|
|
}
|
|
if (sc->sc_stats_tag != NULL)
|
|
bus_dma_tag_destroy(sc->sc_stats_tag);
|
|
npe_dma_destroy(sc, &sc->txdma);
|
|
npe_dma_destroy(sc, &sc->rxdma);
|
|
bus_generic_detach(sc->sc_dev);
|
|
if (sc->sc_mii != NULL)
|
|
device_delete_child(sc->sc_dev, sc->sc_mii);
|
|
}
|
|
|
|
/*
|
|
* Change media according to request.
|
|
*/
|
|
static int
|
|
npe_ifmedia_update(struct ifnet *ifp)
|
|
{
|
|
struct npe_softc *sc = ifp->if_softc;
|
|
struct mii_data *mii;
|
|
|
|
mii = device_get_softc(sc->sc_mii);
|
|
NPE_LOCK(sc);
|
|
mii_mediachg(mii);
|
|
/* XXX push state ourself? */
|
|
NPE_UNLOCK(sc);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Notify the world which media we're using.
|
|
*/
|
|
static void
|
|
npe_ifmedia_status(struct ifnet *ifp, struct ifmediareq *ifmr)
|
|
{
|
|
struct npe_softc *sc = ifp->if_softc;
|
|
struct mii_data *mii;
|
|
|
|
mii = device_get_softc(sc->sc_mii);
|
|
NPE_LOCK(sc);
|
|
mii_pollstat(mii);
|
|
ifmr->ifm_active = mii->mii_media_active;
|
|
ifmr->ifm_status = mii->mii_media_status;
|
|
NPE_UNLOCK(sc);
|
|
}
|
|
|
|
static void
|
|
npe_addstats(struct npe_softc *sc)
|
|
{
|
|
#define NPEADD(x) sc->sc_totals.x += be32toh(ns->x)
|
|
#define MIBADD(x) do { sc->mibdata.x += be32toh(ns->x); NPEADD(x); } while (0)
|
|
struct ifnet *ifp = sc->sc_ifp;
|
|
struct npestats *ns = sc->sc_stats;
|
|
|
|
MIBADD(dot3StatsAlignmentErrors);
|
|
MIBADD(dot3StatsFCSErrors);
|
|
MIBADD(dot3StatsInternalMacReceiveErrors);
|
|
NPEADD(RxOverrunDiscards);
|
|
NPEADD(RxLearnedEntryDiscards);
|
|
NPEADD(RxLargeFramesDiscards);
|
|
NPEADD(RxSTPBlockedDiscards);
|
|
NPEADD(RxVLANTypeFilterDiscards);
|
|
NPEADD(RxVLANIdFilterDiscards);
|
|
NPEADD(RxInvalidSourceDiscards);
|
|
NPEADD(RxBlackListDiscards);
|
|
NPEADD(RxWhiteListDiscards);
|
|
NPEADD(RxUnderflowEntryDiscards);
|
|
MIBADD(dot3StatsSingleCollisionFrames);
|
|
MIBADD(dot3StatsMultipleCollisionFrames);
|
|
MIBADD(dot3StatsDeferredTransmissions);
|
|
MIBADD(dot3StatsLateCollisions);
|
|
MIBADD(dot3StatsExcessiveCollisions);
|
|
MIBADD(dot3StatsInternalMacTransmitErrors);
|
|
MIBADD(dot3StatsCarrierSenseErrors);
|
|
NPEADD(TxLargeFrameDiscards);
|
|
NPEADD(TxVLANIdFilterDiscards);
|
|
|
|
sc->mibdata.dot3StatsFrameTooLongs +=
|
|
be32toh(ns->RxLargeFramesDiscards)
|
|
+ be32toh(ns->TxLargeFrameDiscards);
|
|
sc->mibdata.dot3StatsMissedFrames +=
|
|
be32toh(ns->RxOverrunDiscards)
|
|
+ be32toh(ns->RxUnderflowEntryDiscards);
|
|
|
|
ifp->if_oerrors +=
|
|
be32toh(ns->dot3StatsInternalMacTransmitErrors)
|
|
+ be32toh(ns->dot3StatsCarrierSenseErrors)
|
|
+ be32toh(ns->TxVLANIdFilterDiscards)
|
|
;
|
|
ifp->if_ierrors += be32toh(ns->dot3StatsFCSErrors)
|
|
+ be32toh(ns->dot3StatsInternalMacReceiveErrors)
|
|
+ be32toh(ns->RxOverrunDiscards)
|
|
+ be32toh(ns->RxUnderflowEntryDiscards)
|
|
;
|
|
ifp->if_collisions +=
|
|
be32toh(ns->dot3StatsSingleCollisionFrames)
|
|
+ be32toh(ns->dot3StatsMultipleCollisionFrames)
|
|
;
|
|
#undef NPEADD
|
|
#undef MIBADD
|
|
}
|
|
|
|
static void
|
|
npe_tick(void *xsc)
|
|
{
|
|
#define ACK (NPE_RESETSTATS << NPE_MAC_MSGID_SHL)
|
|
struct npe_softc *sc = xsc;
|
|
struct mii_data *mii = device_get_softc(sc->sc_mii);
|
|
uint32_t msg[2];
|
|
|
|
NPE_ASSERT_LOCKED(sc);
|
|
|
|
/*
|
|
* NB: to avoid sleeping with the softc lock held we
|
|
* split the NPE msg processing into two parts. The
|
|
* request for statistics is sent w/o waiting for a
|
|
* reply and then on the next tick we retrieve the
|
|
* results. This works because npe_tick is the only
|
|
* code that talks via the mailbox's (except at setup).
|
|
* This likely can be handled better.
|
|
*/
|
|
if (ixpnpe_recvmsg_async(sc->sc_npe, msg) == 0 && msg[0] == ACK) {
|
|
bus_dmamap_sync(sc->sc_stats_tag, sc->sc_stats_map,
|
|
BUS_DMASYNC_POSTREAD);
|
|
npe_addstats(sc);
|
|
}
|
|
npe_updatestats(sc);
|
|
mii_tick(mii);
|
|
|
|
npewatchdog(sc);
|
|
|
|
/* schedule next poll */
|
|
callout_reset(&sc->tick_ch, sc->sc_tickinterval * hz, npe_tick, sc);
|
|
#undef ACK
|
|
}
|
|
|
|
static void
|
|
npe_setmac(struct npe_softc *sc, u_char *eaddr)
|
|
{
|
|
WR4(sc, NPE_MAC_UNI_ADDR_1, eaddr[0]);
|
|
WR4(sc, NPE_MAC_UNI_ADDR_2, eaddr[1]);
|
|
WR4(sc, NPE_MAC_UNI_ADDR_3, eaddr[2]);
|
|
WR4(sc, NPE_MAC_UNI_ADDR_4, eaddr[3]);
|
|
WR4(sc, NPE_MAC_UNI_ADDR_5, eaddr[4]);
|
|
WR4(sc, NPE_MAC_UNI_ADDR_6, eaddr[5]);
|
|
}
|
|
|
|
static void
|
|
npe_getmac(struct npe_softc *sc, u_char *eaddr)
|
|
{
|
|
/* NB: the unicast address appears to be loaded from EEPROM on reset */
|
|
eaddr[0] = RD4(sc, NPE_MAC_UNI_ADDR_1) & 0xff;
|
|
eaddr[1] = RD4(sc, NPE_MAC_UNI_ADDR_2) & 0xff;
|
|
eaddr[2] = RD4(sc, NPE_MAC_UNI_ADDR_3) & 0xff;
|
|
eaddr[3] = RD4(sc, NPE_MAC_UNI_ADDR_4) & 0xff;
|
|
eaddr[4] = RD4(sc, NPE_MAC_UNI_ADDR_5) & 0xff;
|
|
eaddr[5] = RD4(sc, NPE_MAC_UNI_ADDR_6) & 0xff;
|
|
}
|
|
|
|
struct txdone {
|
|
struct npebuf *head;
|
|
struct npebuf **tail;
|
|
int count;
|
|
};
|
|
|
|
static __inline void
|
|
npe_txdone_finish(struct npe_softc *sc, const struct txdone *td)
|
|
{
|
|
struct ifnet *ifp = sc->sc_ifp;
|
|
|
|
NPE_LOCK(sc);
|
|
*td->tail = sc->tx_free;
|
|
sc->tx_free = td->head;
|
|
/*
|
|
* We're no longer busy, so clear the busy flag and call the
|
|
* start routine to xmit more packets.
|
|
*/
|
|
ifp->if_opackets += td->count;
|
|
ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
|
|
sc->npe_watchdog_timer = 0;
|
|
npestart_locked(ifp);
|
|
NPE_UNLOCK(sc);
|
|
}
|
|
|
|
/*
|
|
* Q manager callback on tx done queue. Reap mbufs
|
|
* and return tx buffers to the free list. Finally
|
|
* restart output. Note the microcode has only one
|
|
* txdone q wired into it so we must use the NPE ID
|
|
* returned with each npehwbuf to decide where to
|
|
* send buffers.
|
|
*/
|
|
static void
|
|
npe_txdone(int qid, void *arg)
|
|
{
|
|
#define P2V(a, dma) \
|
|
&(dma)->buf[((a) - (dma)->buf_phys) / sizeof(struct npehwbuf)]
|
|
struct npe_softc *sc0 = arg;
|
|
struct npe_softc *sc;
|
|
struct npebuf *npe;
|
|
struct txdone *td, q[NPE_MAX];
|
|
uint32_t entry;
|
|
|
|
q[NPE_A].tail = &q[NPE_A].head; q[NPE_A].count = 0;
|
|
q[NPE_B].tail = &q[NPE_B].head; q[NPE_B].count = 0;
|
|
q[NPE_C].tail = &q[NPE_C].head; q[NPE_C].count = 0;
|
|
/* XXX max # at a time? */
|
|
while (ixpqmgr_qread(qid, &entry) == 0) {
|
|
DPRINTF(sc0, "%s: entry 0x%x NPE %u port %u\n",
|
|
__func__, entry, NPE_QM_Q_NPE(entry), NPE_QM_Q_PORT(entry));
|
|
|
|
sc = npes[NPE_QM_Q_NPE(entry)];
|
|
npe = P2V(NPE_QM_Q_ADDR(entry), &sc->txdma);
|
|
m_freem(npe->ix_m);
|
|
npe->ix_m = NULL;
|
|
|
|
td = &q[NPE_QM_Q_NPE(entry)];
|
|
*td->tail = npe;
|
|
td->tail = &npe->ix_next;
|
|
td->count++;
|
|
}
|
|
|
|
if (q[NPE_A].count)
|
|
npe_txdone_finish(npes[NPE_A], &q[NPE_A]);
|
|
if (q[NPE_B].count)
|
|
npe_txdone_finish(npes[NPE_B], &q[NPE_B]);
|
|
if (q[NPE_C].count)
|
|
npe_txdone_finish(npes[NPE_C], &q[NPE_C]);
|
|
#undef P2V
|
|
}
|
|
|
|
static int
|
|
npe_rxbuf_init(struct npe_softc *sc, struct npebuf *npe, struct mbuf *m)
|
|
{
|
|
bus_dma_segment_t segs[1];
|
|
struct npedma *dma = &sc->rxdma;
|
|
struct npehwbuf *hw;
|
|
int error, nseg;
|
|
|
|
if (m == NULL) {
|
|
m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR);
|
|
if (m == NULL)
|
|
return ENOBUFS;
|
|
}
|
|
KASSERT(m->m_ext.ext_size >= 1536 + ETHER_ALIGN,
|
|
("ext_size %d", m->m_ext.ext_size));
|
|
m->m_pkthdr.len = m->m_len = 1536;
|
|
/* backload payload and align ip hdr */
|
|
m->m_data = m->m_ext.ext_buf + (m->m_ext.ext_size - (1536+ETHER_ALIGN));
|
|
error = bus_dmamap_load_mbuf_sg(dma->mtag, npe->ix_map, m,
|
|
segs, &nseg, 0);
|
|
if (error != 0) {
|
|
m_freem(m);
|
|
return error;
|
|
}
|
|
hw = npe->ix_hw;
|
|
hw->ix_ne[0].data = htobe32(segs[0].ds_addr);
|
|
/* NB: NPE requires length be a multiple of 64 */
|
|
/* NB: buffer length is shifted in word */
|
|
hw->ix_ne[0].len = htobe32(segs[0].ds_len << 16);
|
|
hw->ix_ne[0].next = 0;
|
|
npe->ix_m = m;
|
|
/* Flush the memory in the mbuf */
|
|
bus_dmamap_sync(dma->mtag, npe->ix_map, BUS_DMASYNC_PREREAD);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* RX q processing for a specific NPE. Claim entries
|
|
* from the hardware queue and pass the frames up the
|
|
* stack. Pass the rx buffers to the free list.
|
|
*/
|
|
static int
|
|
npe_rxdone(int qid, void *arg)
|
|
{
|
|
#define P2V(a, dma) \
|
|
&(dma)->buf[((a) - (dma)->buf_phys) / sizeof(struct npehwbuf)]
|
|
struct npe_softc *sc = arg;
|
|
struct npedma *dma = &sc->rxdma;
|
|
uint32_t entry;
|
|
int rx_npkts = 0;
|
|
|
|
while (ixpqmgr_qread(qid, &entry) == 0) {
|
|
struct npebuf *npe = P2V(NPE_QM_Q_ADDR(entry), dma);
|
|
struct mbuf *m;
|
|
|
|
DPRINTF(sc, "%s: entry 0x%x neaddr 0x%x ne_len 0x%x\n",
|
|
__func__, entry, npe->ix_neaddr, npe->ix_hw->ix_ne[0].len);
|
|
/*
|
|
* Allocate a new mbuf to replenish the rx buffer.
|
|
* If doing so fails we drop the rx'd frame so we
|
|
* can reuse the previous mbuf. When we're able to
|
|
* allocate a new mbuf dispatch the mbuf w/ rx'd
|
|
* data up the stack and replace it with the newly
|
|
* allocated one.
|
|
*/
|
|
m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR);
|
|
if (m != NULL) {
|
|
struct mbuf *mrx = npe->ix_m;
|
|
struct npehwbuf *hw = npe->ix_hw;
|
|
struct ifnet *ifp = sc->sc_ifp;
|
|
|
|
/* Flush mbuf memory for rx'd data */
|
|
bus_dmamap_sync(dma->mtag, npe->ix_map,
|
|
BUS_DMASYNC_POSTREAD);
|
|
|
|
/* XXX flush hw buffer; works now 'cuz coherent */
|
|
/* set m_len etc. per rx frame size */
|
|
mrx->m_len = be32toh(hw->ix_ne[0].len) & 0xffff;
|
|
mrx->m_pkthdr.len = mrx->m_len;
|
|
mrx->m_pkthdr.rcvif = ifp;
|
|
|
|
ifp->if_ipackets++;
|
|
ifp->if_input(ifp, mrx);
|
|
rx_npkts++;
|
|
} else {
|
|
/* discard frame and re-use mbuf */
|
|
m = npe->ix_m;
|
|
}
|
|
if (npe_rxbuf_init(sc, npe, m) == 0) {
|
|
/* return npe buf to rx free list */
|
|
ixpqmgr_qwrite(sc->rx_freeqid, npe->ix_neaddr);
|
|
} else {
|
|
/* XXX should not happen */
|
|
}
|
|
}
|
|
return rx_npkts;
|
|
#undef P2V
|
|
}
|
|
|
|
#ifdef DEVICE_POLLING
|
|
static int
|
|
npe_poll(struct ifnet *ifp, enum poll_cmd cmd, int count)
|
|
{
|
|
struct npe_softc *sc = ifp->if_softc;
|
|
int rx_npkts = 0;
|
|
|
|
if (ifp->if_drv_flags & IFF_DRV_RUNNING) {
|
|
rx_npkts = npe_rxdone(sc->rx_qid, sc);
|
|
npe_txdone(sc->tx_doneqid, sc); /* XXX polls both NPE's */
|
|
}
|
|
return rx_npkts;
|
|
}
|
|
#endif /* DEVICE_POLLING */
|
|
|
|
static void
|
|
npe_startxmit(struct npe_softc *sc)
|
|
{
|
|
struct npedma *dma = &sc->txdma;
|
|
int i;
|
|
|
|
NPE_ASSERT_LOCKED(sc);
|
|
sc->tx_free = NULL;
|
|
for (i = 0; i < dma->nbuf; i++) {
|
|
struct npebuf *npe = &dma->buf[i];
|
|
if (npe->ix_m != NULL) {
|
|
/* NB: should not happen */
|
|
device_printf(sc->sc_dev,
|
|
"%s: free mbuf at entry %u\n", __func__, i);
|
|
m_freem(npe->ix_m);
|
|
}
|
|
npe->ix_m = NULL;
|
|
npe->ix_next = sc->tx_free;
|
|
sc->tx_free = npe;
|
|
}
|
|
}
|
|
|
|
static void
|
|
npe_startrecv(struct npe_softc *sc)
|
|
{
|
|
struct npedma *dma = &sc->rxdma;
|
|
struct npebuf *npe;
|
|
int i;
|
|
|
|
NPE_ASSERT_LOCKED(sc);
|
|
for (i = 0; i < dma->nbuf; i++) {
|
|
npe = &dma->buf[i];
|
|
npe_rxbuf_init(sc, npe, npe->ix_m);
|
|
/* set npe buf on rx free list */
|
|
ixpqmgr_qwrite(sc->rx_freeqid, npe->ix_neaddr);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Reset and initialize the chip
|
|
*/
|
|
static void
|
|
npeinit_locked(void *xsc)
|
|
{
|
|
struct npe_softc *sc = xsc;
|
|
struct ifnet *ifp = sc->sc_ifp;
|
|
|
|
NPE_ASSERT_LOCKED(sc);
|
|
if (ifp->if_drv_flags & IFF_DRV_RUNNING) return;/*XXX*/
|
|
|
|
/*
|
|
* Reset MAC core.
|
|
*/
|
|
npe_mac_reset(sc);
|
|
|
|
/* disable transmitter and reciver in the MAC */
|
|
WR4(sc, NPE_MAC_RX_CNTRL1,
|
|
RD4(sc, NPE_MAC_RX_CNTRL1) &~ NPE_RX_CNTRL1_RX_EN);
|
|
WR4(sc, NPE_MAC_TX_CNTRL1,
|
|
RD4(sc, NPE_MAC_TX_CNTRL1) &~ NPE_TX_CNTRL1_TX_EN);
|
|
|
|
/*
|
|
* Set the MAC core registers.
|
|
*/
|
|
WR4(sc, NPE_MAC_INT_CLK_THRESH, 0x1); /* clock ratio: for ipx4xx */
|
|
WR4(sc, NPE_MAC_TX_CNTRL2, 0xf); /* max retries */
|
|
WR4(sc, NPE_MAC_RANDOM_SEED, 0x8); /* LFSR back-off seed */
|
|
/* thresholds determined by NPE firmware FS */
|
|
WR4(sc, NPE_MAC_THRESH_P_EMPTY, 0x12);
|
|
WR4(sc, NPE_MAC_THRESH_P_FULL, 0x30);
|
|
WR4(sc, NPE_MAC_BUF_SIZE_TX, 0x8); /* tx fifo threshold (bytes) */
|
|
WR4(sc, NPE_MAC_TX_DEFER, 0x15); /* for single deferral */
|
|
WR4(sc, NPE_MAC_RX_DEFER, 0x16); /* deferral on inter-frame gap*/
|
|
WR4(sc, NPE_MAC_TX_TWO_DEFER_1, 0x8); /* for 2-part deferral */
|
|
WR4(sc, NPE_MAC_TX_TWO_DEFER_2, 0x7); /* for 2-part deferral */
|
|
WR4(sc, NPE_MAC_SLOT_TIME, 0x80); /* assumes MII mode */
|
|
|
|
WR4(sc, NPE_MAC_TX_CNTRL1,
|
|
NPE_TX_CNTRL1_RETRY /* retry failed xmits */
|
|
| NPE_TX_CNTRL1_FCS_EN /* append FCS */
|
|
| NPE_TX_CNTRL1_2DEFER /* 2-part deferal */
|
|
| NPE_TX_CNTRL1_PAD_EN); /* pad runt frames */
|
|
/* XXX pad strip? */
|
|
/* ena pause frame handling */
|
|
WR4(sc, NPE_MAC_RX_CNTRL1, NPE_RX_CNTRL1_PAUSE_EN);
|
|
WR4(sc, NPE_MAC_RX_CNTRL2, 0);
|
|
|
|
npe_setmac(sc, IF_LLADDR(ifp));
|
|
npe_setportaddress(sc, IF_LLADDR(ifp));
|
|
npe_setmcast(sc);
|
|
|
|
npe_startxmit(sc);
|
|
npe_startrecv(sc);
|
|
|
|
ifp->if_drv_flags |= IFF_DRV_RUNNING;
|
|
ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
|
|
sc->npe_watchdog_timer = 0; /* just in case */
|
|
|
|
/* enable transmitter and reciver in the MAC */
|
|
WR4(sc, NPE_MAC_RX_CNTRL1,
|
|
RD4(sc, NPE_MAC_RX_CNTRL1) | NPE_RX_CNTRL1_RX_EN);
|
|
WR4(sc, NPE_MAC_TX_CNTRL1,
|
|
RD4(sc, NPE_MAC_TX_CNTRL1) | NPE_TX_CNTRL1_TX_EN);
|
|
|
|
callout_reset(&sc->tick_ch, sc->sc_tickinterval * hz, npe_tick, sc);
|
|
}
|
|
|
|
static void
|
|
npeinit(void *xsc)
|
|
{
|
|
struct npe_softc *sc = xsc;
|
|
NPE_LOCK(sc);
|
|
npeinit_locked(sc);
|
|
NPE_UNLOCK(sc);
|
|
}
|
|
|
|
/*
|
|
* Dequeue packets and place on the h/w transmit queue.
|
|
*/
|
|
static void
|
|
npestart_locked(struct ifnet *ifp)
|
|
{
|
|
struct npe_softc *sc = ifp->if_softc;
|
|
struct npebuf *npe;
|
|
struct npehwbuf *hw;
|
|
struct mbuf *m, *n;
|
|
struct npedma *dma = &sc->txdma;
|
|
bus_dma_segment_t segs[NPE_MAXSEG];
|
|
int nseg, len, error, i;
|
|
uint32_t next;
|
|
|
|
NPE_ASSERT_LOCKED(sc);
|
|
/* XXX can this happen? */
|
|
if (ifp->if_drv_flags & IFF_DRV_OACTIVE)
|
|
return;
|
|
|
|
while (sc->tx_free != NULL) {
|
|
IFQ_DRV_DEQUEUE(&ifp->if_snd, m);
|
|
if (m == NULL) {
|
|
/* XXX? */
|
|
ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
|
|
return;
|
|
}
|
|
npe = sc->tx_free;
|
|
error = bus_dmamap_load_mbuf_sg(dma->mtag, npe->ix_map,
|
|
m, segs, &nseg, 0);
|
|
if (error == EFBIG) {
|
|
n = m_collapse(m, M_DONTWAIT, NPE_MAXSEG);
|
|
if (n == NULL) {
|
|
if_printf(ifp, "%s: too many fragments %u\n",
|
|
__func__, nseg);
|
|
m_freem(m);
|
|
return; /* XXX? */
|
|
}
|
|
m = n;
|
|
error = bus_dmamap_load_mbuf_sg(dma->mtag, npe->ix_map,
|
|
m, segs, &nseg, 0);
|
|
}
|
|
if (error != 0 || nseg == 0) {
|
|
if_printf(ifp, "%s: error %u nseg %u\n",
|
|
__func__, error, nseg);
|
|
m_freem(m);
|
|
return; /* XXX? */
|
|
}
|
|
sc->tx_free = npe->ix_next;
|
|
|
|
bus_dmamap_sync(dma->mtag, npe->ix_map, BUS_DMASYNC_PREWRITE);
|
|
|
|
/*
|
|
* Tap off here if there is a bpf listener.
|
|
*/
|
|
BPF_MTAP(ifp, m);
|
|
|
|
npe->ix_m = m;
|
|
hw = npe->ix_hw;
|
|
len = m->m_pkthdr.len;
|
|
next = npe->ix_neaddr + sizeof(hw->ix_ne[0]);
|
|
for (i = 0; i < nseg; i++) {
|
|
hw->ix_ne[i].data = htobe32(segs[i].ds_addr);
|
|
hw->ix_ne[i].len = htobe32((segs[i].ds_len<<16) | len);
|
|
hw->ix_ne[i].next = htobe32(next);
|
|
|
|
len = 0; /* zero for segments > 1 */
|
|
next += sizeof(hw->ix_ne[0]);
|
|
}
|
|
hw->ix_ne[i-1].next = 0; /* zero last in chain */
|
|
/* XXX flush descriptor instead of using uncached memory */
|
|
|
|
DPRINTF(sc, "%s: qwrite(%u, 0x%x) ne_data %x ne_len 0x%x\n",
|
|
__func__, sc->tx_qid, npe->ix_neaddr,
|
|
hw->ix_ne[0].data, hw->ix_ne[0].len);
|
|
/* stick it on the tx q */
|
|
/* XXX add vlan priority */
|
|
ixpqmgr_qwrite(sc->tx_qid, npe->ix_neaddr);
|
|
|
|
sc->npe_watchdog_timer = 5;
|
|
}
|
|
if (sc->tx_free == NULL)
|
|
ifp->if_drv_flags |= IFF_DRV_OACTIVE;
|
|
}
|
|
|
|
void
|
|
npestart(struct ifnet *ifp)
|
|
{
|
|
struct npe_softc *sc = ifp->if_softc;
|
|
NPE_LOCK(sc);
|
|
npestart_locked(ifp);
|
|
NPE_UNLOCK(sc);
|
|
}
|
|
|
|
static void
|
|
npe_stopxmit(struct npe_softc *sc)
|
|
{
|
|
struct npedma *dma = &sc->txdma;
|
|
int i;
|
|
|
|
NPE_ASSERT_LOCKED(sc);
|
|
|
|
/* XXX qmgr */
|
|
for (i = 0; i < dma->nbuf; i++) {
|
|
struct npebuf *npe = &dma->buf[i];
|
|
|
|
if (npe->ix_m != NULL) {
|
|
bus_dmamap_unload(dma->mtag, npe->ix_map);
|
|
m_freem(npe->ix_m);
|
|
npe->ix_m = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
npe_stoprecv(struct npe_softc *sc)
|
|
{
|
|
struct npedma *dma = &sc->rxdma;
|
|
int i;
|
|
|
|
NPE_ASSERT_LOCKED(sc);
|
|
|
|
/* XXX qmgr */
|
|
for (i = 0; i < dma->nbuf; i++) {
|
|
struct npebuf *npe = &dma->buf[i];
|
|
|
|
if (npe->ix_m != NULL) {
|
|
bus_dmamap_unload(dma->mtag, npe->ix_map);
|
|
m_freem(npe->ix_m);
|
|
npe->ix_m = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Turn off interrupts, and stop the nic.
|
|
*/
|
|
void
|
|
npestop(struct npe_softc *sc)
|
|
{
|
|
struct ifnet *ifp = sc->sc_ifp;
|
|
|
|
/* disable transmitter and reciver in the MAC */
|
|
WR4(sc, NPE_MAC_RX_CNTRL1,
|
|
RD4(sc, NPE_MAC_RX_CNTRL1) &~ NPE_RX_CNTRL1_RX_EN);
|
|
WR4(sc, NPE_MAC_TX_CNTRL1,
|
|
RD4(sc, NPE_MAC_TX_CNTRL1) &~ NPE_TX_CNTRL1_TX_EN);
|
|
|
|
sc->npe_watchdog_timer = 0;
|
|
ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE);
|
|
|
|
callout_stop(&sc->tick_ch);
|
|
|
|
npe_stopxmit(sc);
|
|
npe_stoprecv(sc);
|
|
/* XXX go into loopback & drain q's? */
|
|
/* XXX but beware of disabling tx above */
|
|
|
|
/*
|
|
* The MAC core rx/tx disable may leave the MAC hardware in an
|
|
* unpredictable state. A hw reset is executed before resetting
|
|
* all the MAC parameters to a known value.
|
|
*/
|
|
WR4(sc, NPE_MAC_CORE_CNTRL, NPE_CORE_RESET);
|
|
DELAY(NPE_MAC_RESET_DELAY);
|
|
WR4(sc, NPE_MAC_INT_CLK_THRESH, NPE_MAC_INT_CLK_THRESH_DEFAULT);
|
|
WR4(sc, NPE_MAC_CORE_CNTRL, NPE_CORE_MDC_EN);
|
|
}
|
|
|
|
void
|
|
npewatchdog(struct npe_softc *sc)
|
|
{
|
|
NPE_ASSERT_LOCKED(sc);
|
|
|
|
if (sc->npe_watchdog_timer == 0 || --sc->npe_watchdog_timer != 0)
|
|
return;
|
|
|
|
device_printf(sc->sc_dev, "watchdog timeout\n");
|
|
sc->sc_ifp->if_oerrors++;
|
|
|
|
npeinit_locked(sc);
|
|
}
|
|
|
|
static int
|
|
npeioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
|
|
{
|
|
struct npe_softc *sc = ifp->if_softc;
|
|
struct mii_data *mii;
|
|
struct ifreq *ifr = (struct ifreq *)data;
|
|
int error = 0;
|
|
#ifdef DEVICE_POLLING
|
|
int mask;
|
|
#endif
|
|
|
|
switch (cmd) {
|
|
case SIOCSIFFLAGS:
|
|
NPE_LOCK(sc);
|
|
if ((ifp->if_flags & IFF_UP) == 0 &&
|
|
ifp->if_drv_flags & IFF_DRV_RUNNING) {
|
|
ifp->if_drv_flags &= ~IFF_DRV_RUNNING;
|
|
npestop(sc);
|
|
} else {
|
|
/* reinitialize card on any parameter change */
|
|
npeinit_locked(sc);
|
|
}
|
|
NPE_UNLOCK(sc);
|
|
break;
|
|
|
|
case SIOCADDMULTI:
|
|
case SIOCDELMULTI:
|
|
/* update multicast filter list. */
|
|
NPE_LOCK(sc);
|
|
npe_setmcast(sc);
|
|
NPE_UNLOCK(sc);
|
|
error = 0;
|
|
break;
|
|
|
|
case SIOCSIFMEDIA:
|
|
case SIOCGIFMEDIA:
|
|
mii = device_get_softc(sc->sc_mii);
|
|
error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, cmd);
|
|
break;
|
|
|
|
#ifdef DEVICE_POLLING
|
|
case SIOCSIFCAP:
|
|
mask = ifp->if_capenable ^ ifr->ifr_reqcap;
|
|
if (mask & IFCAP_POLLING) {
|
|
if (ifr->ifr_reqcap & IFCAP_POLLING) {
|
|
error = ether_poll_register(npe_poll, ifp);
|
|
if (error)
|
|
return error;
|
|
NPE_LOCK(sc);
|
|
/* disable callbacks XXX txdone is shared */
|
|
ixpqmgr_notify_disable(sc->rx_qid);
|
|
ixpqmgr_notify_disable(sc->tx_doneqid);
|
|
ifp->if_capenable |= IFCAP_POLLING;
|
|
NPE_UNLOCK(sc);
|
|
} else {
|
|
error = ether_poll_deregister(ifp);
|
|
/* NB: always enable qmgr callbacks */
|
|
NPE_LOCK(sc);
|
|
/* enable qmgr callbacks */
|
|
ixpqmgr_notify_enable(sc->rx_qid,
|
|
IX_QMGR_Q_SOURCE_ID_NOT_E);
|
|
ixpqmgr_notify_enable(sc->tx_doneqid,
|
|
IX_QMGR_Q_SOURCE_ID_NOT_E);
|
|
ifp->if_capenable &= ~IFCAP_POLLING;
|
|
NPE_UNLOCK(sc);
|
|
}
|
|
}
|
|
break;
|
|
#endif
|
|
default:
|
|
error = ether_ioctl(ifp, cmd, data);
|
|
break;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Setup a traffic class -> rx queue mapping.
|
|
*/
|
|
static int
|
|
npe_setrxqosentry(struct npe_softc *sc, int classix, int trafclass, int qid)
|
|
{
|
|
uint32_t msg[2];
|
|
|
|
msg[0] = (NPE_SETRXQOSENTRY << 24) | (sc->sc_npeid << 20) | classix;
|
|
msg[1] = (trafclass << 24) | (1 << 23) | (qid << 16) | (qid << 4);
|
|
return ixpnpe_sendandrecvmsg_sync(sc->sc_npe, msg, msg);
|
|
}
|
|
|
|
static int
|
|
npe_setportaddress(struct npe_softc *sc, const uint8_t mac[ETHER_ADDR_LEN])
|
|
{
|
|
uint32_t msg[2];
|
|
|
|
msg[0] = (NPE_SETPORTADDRESS << 24)
|
|
| (sc->sc_npeid << 20)
|
|
| (mac[0] << 8)
|
|
| (mac[1] << 0);
|
|
msg[1] = (mac[2] << 24)
|
|
| (mac[3] << 16)
|
|
| (mac[4] << 8)
|
|
| (mac[5] << 0);
|
|
return ixpnpe_sendandrecvmsg_sync(sc->sc_npe, msg, msg);
|
|
}
|
|
|
|
static int
|
|
npe_setfirewallmode(struct npe_softc *sc, int onoff)
|
|
{
|
|
uint32_t msg[2];
|
|
|
|
/* XXX honor onoff */
|
|
msg[0] = (NPE_SETFIREWALLMODE << 24) | (sc->sc_npeid << 20);
|
|
msg[1] = 0;
|
|
return ixpnpe_sendandrecvmsg_sync(sc->sc_npe, msg, msg);
|
|
}
|
|
|
|
/*
|
|
* Update and reset the statistics in the NPE.
|
|
*/
|
|
static int
|
|
npe_updatestats(struct npe_softc *sc)
|
|
{
|
|
uint32_t msg[2];
|
|
|
|
msg[0] = NPE_RESETSTATS << NPE_MAC_MSGID_SHL;
|
|
msg[1] = sc->sc_stats_phys; /* physical address of stat block */
|
|
return ixpnpe_sendmsg_async(sc->sc_npe, msg);
|
|
}
|
|
|
|
#if 0
|
|
/*
|
|
* Get the current statistics block.
|
|
*/
|
|
static int
|
|
npe_getstats(struct npe_softc *sc)
|
|
{
|
|
uint32_t msg[2];
|
|
|
|
msg[0] = NPE_GETSTATS << NPE_MAC_MSGID_SHL;
|
|
msg[1] = sc->sc_stats_phys; /* physical address of stat block */
|
|
return ixpnpe_sendandrecvmsg(sc->sc_npe, msg, msg);
|
|
}
|
|
|
|
/*
|
|
* Query the image id of the loaded firmware.
|
|
*/
|
|
static uint32_t
|
|
npe_getimageid(struct npe_softc *sc)
|
|
{
|
|
uint32_t msg[2];
|
|
|
|
msg[0] = NPE_GETSTATUS << NPE_MAC_MSGID_SHL;
|
|
msg[1] = 0;
|
|
return ixpnpe_sendandrecvmsg_sync(sc->sc_npe, msg, msg) == 0 ? msg[1] : 0;
|
|
}
|
|
|
|
/*
|
|
* Enable/disable loopback.
|
|
*/
|
|
static int
|
|
npe_setloopback(struct npe_softc *sc, int ena)
|
|
{
|
|
uint32_t msg[2];
|
|
|
|
msg[0] = (NPE_SETLOOPBACK << NPE_MAC_MSGID_SHL) | (ena != 0);
|
|
msg[1] = 0;
|
|
return ixpnpe_sendandrecvmsg_sync(sc->sc_npe, msg, msg);
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
npe_child_detached(device_t dev, device_t child)
|
|
{
|
|
struct npe_softc *sc;
|
|
|
|
sc = device_get_softc(dev);
|
|
if (child == sc->sc_mii)
|
|
sc->sc_mii = NULL;
|
|
}
|
|
|
|
/*
|
|
* MII bus support routines.
|
|
*/
|
|
#define MII_RD4(sc, reg) bus_space_read_4(sc->sc_iot, sc->sc_miih, reg)
|
|
#define MII_WR4(sc, reg, v) \
|
|
bus_space_write_4(sc->sc_iot, sc->sc_miih, reg, v)
|
|
|
|
static uint32_t
|
|
npe_mii_mdio_read(struct npe_softc *sc, int reg)
|
|
{
|
|
uint32_t v;
|
|
|
|
/* NB: registers are known to be sequential */
|
|
v = (MII_RD4(sc, reg+0) & 0xff) << 0;
|
|
v |= (MII_RD4(sc, reg+4) & 0xff) << 8;
|
|
v |= (MII_RD4(sc, reg+8) & 0xff) << 16;
|
|
v |= (MII_RD4(sc, reg+12) & 0xff) << 24;
|
|
return v;
|
|
}
|
|
|
|
static void
|
|
npe_mii_mdio_write(struct npe_softc *sc, int reg, uint32_t cmd)
|
|
{
|
|
/* NB: registers are known to be sequential */
|
|
MII_WR4(sc, reg+0, cmd & 0xff);
|
|
MII_WR4(sc, reg+4, (cmd >> 8) & 0xff);
|
|
MII_WR4(sc, reg+8, (cmd >> 16) & 0xff);
|
|
MII_WR4(sc, reg+12, (cmd >> 24) & 0xff);
|
|
}
|
|
|
|
static int
|
|
npe_mii_mdio_wait(struct npe_softc *sc)
|
|
{
|
|
uint32_t v;
|
|
int i;
|
|
|
|
/* NB: typically this takes 25-30 trips */
|
|
for (i = 0; i < 1000; i++) {
|
|
v = npe_mii_mdio_read(sc, NPE_MAC_MDIO_CMD);
|
|
if ((v & NPE_MII_GO) == 0)
|
|
return 1;
|
|
DELAY(1);
|
|
}
|
|
device_printf(sc->sc_dev, "%s: timeout after ~1ms, cmd 0x%x\n",
|
|
__func__, v);
|
|
return 0; /* NB: timeout */
|
|
}
|
|
|
|
static int
|
|
npe_miibus_readreg(device_t dev, int phy, int reg)
|
|
{
|
|
struct npe_softc *sc = device_get_softc(dev);
|
|
uint32_t v;
|
|
|
|
if (phy != sc->sc_phy) /* XXX no auto-detect */
|
|
return 0xffff;
|
|
v = (phy << NPE_MII_ADDR_SHL) | (reg << NPE_MII_REG_SHL) | NPE_MII_GO;
|
|
npe_mii_mdio_write(sc, NPE_MAC_MDIO_CMD, v);
|
|
if (npe_mii_mdio_wait(sc))
|
|
v = npe_mii_mdio_read(sc, NPE_MAC_MDIO_STS);
|
|
else
|
|
v = 0xffff | NPE_MII_READ_FAIL;
|
|
return (v & NPE_MII_READ_FAIL) ? 0xffff : (v & 0xffff);
|
|
}
|
|
|
|
static int
|
|
npe_miibus_writereg(device_t dev, int phy, int reg, int data)
|
|
{
|
|
struct npe_softc *sc = device_get_softc(dev);
|
|
uint32_t v;
|
|
|
|
if (phy != sc->sc_phy) /* XXX */
|
|
return (0);
|
|
v = (phy << NPE_MII_ADDR_SHL) | (reg << NPE_MII_REG_SHL)
|
|
| data | NPE_MII_WRITE
|
|
| NPE_MII_GO;
|
|
npe_mii_mdio_write(sc, NPE_MAC_MDIO_CMD, v);
|
|
/* XXX complain about timeout */
|
|
(void) npe_mii_mdio_wait(sc);
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
npe_miibus_statchg(device_t dev)
|
|
{
|
|
struct npe_softc *sc = device_get_softc(dev);
|
|
struct mii_data *mii = device_get_softc(sc->sc_mii);
|
|
uint32_t tx1, rx1;
|
|
|
|
/* sync MAC duplex state */
|
|
tx1 = RD4(sc, NPE_MAC_TX_CNTRL1);
|
|
rx1 = RD4(sc, NPE_MAC_RX_CNTRL1);
|
|
if ((mii->mii_media_active & IFM_GMASK) == IFM_FDX) {
|
|
tx1 &= ~NPE_TX_CNTRL1_DUPLEX;
|
|
rx1 |= NPE_RX_CNTRL1_PAUSE_EN;
|
|
} else {
|
|
tx1 |= NPE_TX_CNTRL1_DUPLEX;
|
|
rx1 &= ~NPE_RX_CNTRL1_PAUSE_EN;
|
|
}
|
|
WR4(sc, NPE_MAC_RX_CNTRL1, rx1);
|
|
WR4(sc, NPE_MAC_TX_CNTRL1, tx1);
|
|
}
|
|
|
|
static device_method_t npe_methods[] = {
|
|
/* Device interface */
|
|
DEVMETHOD(device_probe, npe_probe),
|
|
DEVMETHOD(device_attach, npe_attach),
|
|
DEVMETHOD(device_detach, npe_detach),
|
|
|
|
/* Bus interface */
|
|
DEVMETHOD(bus_child_detached, npe_child_detached),
|
|
|
|
/* MII interface */
|
|
DEVMETHOD(miibus_readreg, npe_miibus_readreg),
|
|
DEVMETHOD(miibus_writereg, npe_miibus_writereg),
|
|
DEVMETHOD(miibus_statchg, npe_miibus_statchg),
|
|
|
|
{ 0, 0 }
|
|
};
|
|
|
|
static driver_t npe_driver = {
|
|
"npe",
|
|
npe_methods,
|
|
sizeof(struct npe_softc),
|
|
};
|
|
|
|
DRIVER_MODULE(npe, ixp, npe_driver, npe_devclass, 0, 0);
|
|
DRIVER_MODULE(miibus, npe, miibus_driver, miibus_devclass, 0, 0);
|
|
MODULE_DEPEND(npe, ixpqmgr, 1, 1, 1);
|
|
MODULE_DEPEND(npe, miibus, 1, 1, 1);
|
|
MODULE_DEPEND(npe, ether, 1, 1, 1);
|