NetBSD/sys/arch/macppc/dev/if_gm.c
ozaki-r 9c4cd06355 Introduce softint-based if_input
This change intends to run the whole network stack in softint context
(or normal LWP), not hardware interrupt context. Note that the work is
still incomplete by this change; to that end, we also have to softint-ify
if_link_state_change (and bpf) which can still run in hardware interrupt.

This change softint-ifies at ifp->if_input that is called from
each device driver (and ieee80211_input) to ensure Layer 2 runs
in softint (e.g., ether_input and bridge_input). To this end,
we provide a framework (called percpuq) that utlizes softint(9)
and percpu ifqueues. With this patch, rxintr of most drivers just
queues received packets and schedules a softint, and the softint
dequeues packets and does rest packet processing.

To minimize changes to each driver, percpuq is allocated in struct
ifnet for now and that is initialized by default (in if_attach).
We probably have to move percpuq to softc of each driver, but it's
future work. At this point, only wm(4) has percpuq in its softc
as a reference implementation.

Additional information including performance numbers can be found
in the thread at tech-kern@ and tech-net@:
http://mail-index.netbsd.org/tech-kern/2016/01/14/msg019997.html

Acknowledgment: riastradh@ greatly helped this work.
Thank you very much!
2016-02-09 08:32:07 +00:00

924 lines
21 KiB
C

/* $NetBSD: if_gm.c,v 1.46 2016/02/09 08:32:08 ozaki-r Exp $ */
/*-
* Copyright (c) 2000 Tsubai Masanari. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* 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>
__KERNEL_RCSID(0, "$NetBSD: if_gm.c,v 1.46 2016/02/09 08:32:08 ozaki-r Exp $");
#include "opt_inet.h"
#include <sys/param.h>
#include <sys/device.h>
#include <sys/ioctl.h>
#include <sys/kernel.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/systm.h>
#include <sys/callout.h>
#include <sys/rndsource.h>
#include <uvm/uvm_extern.h>
#include <net/if.h>
#include <net/if_ether.h>
#include <net/if_media.h>
#include <net/bpf.h>
#ifdef INET
#include <netinet/in.h>
#include <netinet/if_inarp.h>
#endif
#include <dev/mii/mii.h>
#include <dev/mii/miivar.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcidevs.h>
#include <dev/ofw/openfirm.h>
#include <macppc/dev/if_gmreg.h>
#include <machine/pio.h>
#define NTXBUF 4
#define NRXBUF 32
struct gmac_softc {
device_t sc_dev;
struct ethercom sc_ethercom;
vaddr_t sc_reg;
struct gmac_dma *sc_txlist;
struct gmac_dma *sc_rxlist;
int sc_txnext;
int sc_rxlast;
void *sc_txbuf[NTXBUF];
void *sc_rxbuf[NRXBUF];
struct mii_data sc_mii;
struct callout sc_tick_ch;
char sc_laddr[6];
krndsource_t sc_rnd_source; /* random source */
};
#define sc_if sc_ethercom.ec_if
int gmac_match(device_t, cfdata_t, void *);
void gmac_attach(device_t, device_t, void *);
static inline u_int gmac_read_reg(struct gmac_softc *, int);
static inline void gmac_write_reg(struct gmac_softc *, int, u_int);
static inline void gmac_start_txdma(struct gmac_softc *);
static inline void gmac_start_rxdma(struct gmac_softc *);
static inline void gmac_stop_txdma(struct gmac_softc *);
static inline void gmac_stop_rxdma(struct gmac_softc *);
int gmac_intr(void *);
void gmac_tint(struct gmac_softc *);
void gmac_rint(struct gmac_softc *);
struct mbuf * gmac_get(struct gmac_softc *, void *, int);
void gmac_start(struct ifnet *);
int gmac_put(struct gmac_softc *, void *, struct mbuf *);
void gmac_stop(struct gmac_softc *);
void gmac_reset(struct gmac_softc *);
void gmac_init(struct gmac_softc *);
void gmac_init_mac(struct gmac_softc *);
void gmac_setladrf(struct gmac_softc *);
int gmac_ioctl(struct ifnet *, u_long, void *);
void gmac_watchdog(struct ifnet *);
int gmac_mii_readreg(device_t, int, int);
void gmac_mii_writereg(device_t, int, int, int);
void gmac_mii_statchg(struct ifnet *);
void gmac_mii_tick(void *);
CFATTACH_DECL_NEW(gm, sizeof(struct gmac_softc),
gmac_match, gmac_attach, NULL, NULL);
int
gmac_match(device_t parent, cfdata_t match, void *aux)
{
struct pci_attach_args *pa = aux;
if (PCI_VENDOR(pa->pa_id) == PCI_VENDOR_APPLE &&
(PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_APPLE_GMAC ||
PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_APPLE_GMAC2 ||
PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_APPLE_GMAC3))
return 1;
return 0;
}
void
gmac_attach(device_t parent, device_t self, void *aux)
{
struct gmac_softc * const sc = device_private(self);
struct pci_attach_args * const pa = aux;
struct ifnet * const ifp = &sc->sc_if;
struct mii_data * const mii = &sc->sc_mii;
pci_intr_handle_t ih;
const char *intrstr = NULL;
const char * const xname = device_xname(self);
int node, i;
char *p;
struct gmac_dma *dp;
u_int32_t reg[10];
u_char laddr[6];
char buf[PCI_INTRSTR_LEN];
sc->sc_dev = self;
node = pcidev_to_ofdev(pa->pa_pc, pa->pa_tag);
if (node == 0) {
printf(": cannot find gmac node\n");
return;
}
OF_getprop(node, "local-mac-address", laddr, sizeof laddr);
OF_getprop(node, "assigned-addresses", reg, sizeof reg);
memcpy(sc->sc_laddr, laddr, sizeof laddr);
sc->sc_reg = reg[2];
if (pci_intr_map(pa, &ih)) {
printf(": unable to map interrupt\n");
return;
}
intrstr = pci_intr_string(pa->pa_pc, ih, buf, sizeof(buf));
if (pci_intr_establish(pa->pa_pc, ih, IPL_NET, gmac_intr, sc) == NULL) {
printf(": unable to establish interrupt");
if (intrstr)
printf(" at %s", intrstr);
printf("\n");
return;
}
/* Setup packet buffers and DMA descriptors. */
p = malloc((NRXBUF + NTXBUF) * 2048 + 3 * 0x800, M_DEVBUF, M_NOWAIT);
if (p == NULL) {
printf(": cannot malloc buffers\n");
return;
}
p = (void *)roundup((vaddr_t)p, 0x800);
memset(p, 0, 2048 * (NRXBUF + NTXBUF) + 2 * 0x800);
sc->sc_rxlist = (void *)p;
p += 0x800;
sc->sc_txlist = (void *)p;
p += 0x800;
dp = sc->sc_rxlist;
for (i = 0; i < NRXBUF; i++) {
sc->sc_rxbuf[i] = p;
dp->address = htole32(vtophys((vaddr_t)p));
dp->cmd = htole32(GMAC_OWN);
dp++;
p += 2048;
}
dp = sc->sc_txlist;
for (i = 0; i < NTXBUF; i++) {
sc->sc_txbuf[i] = p;
dp->address = htole32(vtophys((vaddr_t)p));
dp++;
p += 2048;
}
aprint_normal(": Ethernet address %s\n", ether_sprintf(laddr));
aprint_normal_dev(self, "interrupting at %s\n", intrstr);
callout_init(&sc->sc_tick_ch, 0);
gmac_reset(sc);
gmac_init_mac(sc);
memcpy(ifp->if_xname, xname, IFNAMSIZ);
ifp->if_softc = sc;
ifp->if_ioctl = gmac_ioctl;
ifp->if_start = gmac_start;
ifp->if_watchdog = gmac_watchdog;
ifp->if_flags =
IFF_BROADCAST | IFF_SIMPLEX | IFF_NOTRAILERS | IFF_MULTICAST;
IFQ_SET_READY(&ifp->if_snd);
mii->mii_ifp = ifp;
mii->mii_readreg = gmac_mii_readreg;
mii->mii_writereg = gmac_mii_writereg;
mii->mii_statchg = gmac_mii_statchg;
sc->sc_ethercom.ec_mii = mii;
ifmedia_init(&mii->mii_media, 0, ether_mediachange, ether_mediastatus);
mii_attach(self, mii, 0xffffffff, MII_PHY_ANY, MII_OFFSET_ANY, 0);
/* Choose a default media. */
if (LIST_FIRST(&mii->mii_phys) == NULL) {
ifmedia_add(&mii->mii_media, IFM_ETHER|IFM_NONE, 0, NULL);
ifmedia_set(&mii->mii_media, IFM_ETHER|IFM_NONE);
} else
ifmedia_set(&mii->mii_media, IFM_ETHER|IFM_AUTO);
if_attach(ifp);
ether_ifattach(ifp, laddr);
rnd_attach_source(&sc->sc_rnd_source, xname, RND_TYPE_NET,
RND_FLAG_DEFAULT);
}
u_int
gmac_read_reg(struct gmac_softc *sc, int reg)
{
return in32rb(sc->sc_reg + reg);
}
void
gmac_write_reg(struct gmac_softc *sc, int reg, u_int val)
{
out32rb(sc->sc_reg + reg, val);
}
void
gmac_start_txdma(struct gmac_softc *sc)
{
u_int x;
x = gmac_read_reg(sc, GMAC_TXDMACONFIG);
x |= 1;
gmac_write_reg(sc, GMAC_TXDMACONFIG, x);
x = gmac_read_reg(sc, GMAC_TXMACCONFIG);
x |= 1;
gmac_write_reg(sc, GMAC_TXMACCONFIG, x);
}
void
gmac_start_rxdma(struct gmac_softc *sc)
{
u_int x;
x = gmac_read_reg(sc, GMAC_RXDMACONFIG);
x |= 1;
gmac_write_reg(sc, GMAC_RXDMACONFIG, x);
x = gmac_read_reg(sc, GMAC_RXMACCONFIG);
x |= 1;
gmac_write_reg(sc, GMAC_RXMACCONFIG, x);
}
void
gmac_stop_txdma(struct gmac_softc *sc)
{
u_int x;
x = gmac_read_reg(sc, GMAC_TXDMACONFIG);
x &= ~1;
gmac_write_reg(sc, GMAC_TXDMACONFIG, x);
x = gmac_read_reg(sc, GMAC_TXMACCONFIG);
x &= ~1;
gmac_write_reg(sc, GMAC_TXMACCONFIG, x);
}
void
gmac_stop_rxdma(struct gmac_softc *sc)
{
u_int x;
x = gmac_read_reg(sc, GMAC_RXDMACONFIG);
x &= ~1;
gmac_write_reg(sc, GMAC_RXDMACONFIG, x);
x = gmac_read_reg(sc, GMAC_RXMACCONFIG);
x &= ~1;
gmac_write_reg(sc, GMAC_RXMACCONFIG, x);
}
int
gmac_intr(void *v)
{
struct gmac_softc *sc = v;
u_int status;
status = gmac_read_reg(sc, GMAC_STATUS) & 0xff;
if (status == 0)
return 0;
if (status & GMAC_INT_RXDONE)
gmac_rint(sc);
if (status & GMAC_INT_TXEMPTY)
gmac_tint(sc);
rnd_add_uint32(&sc->sc_rnd_source, status);
return 1;
}
void
gmac_tint(struct gmac_softc *sc)
{
struct ifnet *ifp = &sc->sc_if;
ifp->if_flags &= ~IFF_OACTIVE;
ifp->if_timer = 0;
gmac_start(ifp);
}
void
gmac_rint(struct gmac_softc *sc)
{
struct ifnet *ifp = &sc->sc_if;
volatile struct gmac_dma *dp;
struct mbuf *m;
int i, j, len;
u_int cmd;
for (i = sc->sc_rxlast;; i++) {
if (i == NRXBUF)
i = 0;
dp = &sc->sc_rxlist[i];
cmd = le32toh(dp->cmd);
if (cmd & GMAC_OWN)
break;
len = (cmd >> 16) & GMAC_LEN_MASK;
len -= 4; /* CRC */
if (le32toh(dp->cmd_hi) & 0x40000000) {
ifp->if_ierrors++;
goto next;
}
m = gmac_get(sc, sc->sc_rxbuf[i], len);
if (m == NULL) {
ifp->if_ierrors++;
goto next;
}
/*
* Check if there's a BPF listener on this interface.
* If so, hand off the raw packet to BPF.
*/
bpf_mtap(ifp, m);
if_percpuq_enqueue(ifp->if_percpuq, m);
ifp->if_ipackets++;
next:
dp->cmd_hi = 0;
__asm volatile ("sync");
dp->cmd = htole32(GMAC_OWN);
}
sc->sc_rxlast = i;
/* XXX Make sure free buffers have GMAC_OWN. */
i++;
for (j = 1; j < NRXBUF; j++) {
if (i == NRXBUF)
i = 0;
dp = &sc->sc_rxlist[i++];
dp->cmd = htole32(GMAC_OWN);
}
}
struct mbuf *
gmac_get(struct gmac_softc *sc, void *pkt, int totlen)
{
struct mbuf *m;
struct mbuf *top, **mp;
int len;
MGETHDR(m, M_DONTWAIT, MT_DATA);
if (m == 0)
return 0;
m->m_pkthdr.rcvif = &sc->sc_if;
m->m_pkthdr.len = totlen;
len = MHLEN;
top = 0;
mp = &top;
while (totlen > 0) {
if (top) {
MGET(m, M_DONTWAIT, MT_DATA);
if (m == 0) {
m_freem(top);
return 0;
}
len = MLEN;
}
if (totlen >= MINCLSIZE) {
MCLGET(m, M_DONTWAIT);
if ((m->m_flags & M_EXT) == 0) {
m_free(m);
m_freem(top);
return 0;
}
len = MCLBYTES;
}
m->m_len = len = min(totlen, len);
memcpy(mtod(m, void *), pkt, len);
pkt += len;
totlen -= len;
*mp = m;
mp = &m->m_next;
}
return top;
}
void
gmac_start(struct ifnet *ifp)
{
struct gmac_softc *sc = ifp->if_softc;
struct mbuf *m;
void *buff;
int i, tlen;
volatile struct gmac_dma *dp;
if ((ifp->if_flags & (IFF_RUNNING | IFF_OACTIVE)) != IFF_RUNNING)
return;
for (;;) {
if (ifp->if_flags & IFF_OACTIVE)
break;
IFQ_DEQUEUE(&ifp->if_snd, m);
if (m == 0)
break;
/* 5 seconds to watch for failing to transmit */
ifp->if_timer = 5;
ifp->if_opackets++; /* # of pkts */
i = sc->sc_txnext;
buff = sc->sc_txbuf[i];
tlen = gmac_put(sc, buff, m);
dp = &sc->sc_txlist[i];
dp->cmd_hi = 0;
dp->address_hi = 0;
dp->cmd = htole32(tlen | GMAC_OWN | GMAC_SOP);
i++;
if (i == NTXBUF)
i = 0;
__asm volatile ("sync");
gmac_write_reg(sc, GMAC_TXDMAKICK, i);
sc->sc_txnext = i;
/*
* If BPF is listening on this interface, let it see the
* packet before we commit it to the wire.
*/
bpf_mtap(ifp, m);
m_freem(m);
i++;
if (i == NTXBUF)
i = 0;
if (i == gmac_read_reg(sc, GMAC_TXDMACOMPLETE)) {
ifp->if_flags |= IFF_OACTIVE;
break;
}
}
}
int
gmac_put(struct gmac_softc *sc, void *buff, struct mbuf *m)
{
int len, tlen = 0;
for (; m; m = m->m_next) {
len = m->m_len;
if (len == 0)
continue;
memcpy(buff, mtod(m, void *), len);
buff += len;
tlen += len;
}
if (tlen > 2048)
panic("%s: gmac_put packet overflow", device_xname(sc->sc_dev));
return tlen;
}
void
gmac_reset(struct gmac_softc *sc)
{
int i, s;
s = splnet();
gmac_stop_txdma(sc);
gmac_stop_rxdma(sc);
gmac_write_reg(sc, GMAC_SOFTWARERESET, 3);
for (i = 10; i > 0; i--) {
delay(300000); /* XXX long delay */
if ((gmac_read_reg(sc, GMAC_SOFTWARERESET) & 3) == 0)
break;
}
if (i == 0)
aprint_error_dev(sc->sc_dev, "reset timeout\n");
sc->sc_txnext = 0;
sc->sc_rxlast = 0;
for (i = 0; i < NRXBUF; i++)
sc->sc_rxlist[i].cmd = htole32(GMAC_OWN);
__asm volatile ("sync");
gmac_write_reg(sc, GMAC_TXDMADESCBASEHI, 0);
gmac_write_reg(sc, GMAC_TXDMADESCBASELO,
vtophys((vaddr_t)sc->sc_txlist));
gmac_write_reg(sc, GMAC_RXDMADESCBASEHI, 0);
gmac_write_reg(sc, GMAC_RXDMADESCBASELO,
vtophys((vaddr_t)sc->sc_rxlist));
gmac_write_reg(sc, GMAC_RXDMAKICK, NRXBUF);
splx(s);
}
void
gmac_stop(struct gmac_softc *sc)
{
struct ifnet *ifp = &sc->sc_if;
int s;
s = splnet();
callout_stop(&sc->sc_tick_ch);
mii_down(&sc->sc_mii);
gmac_stop_txdma(sc);
gmac_stop_rxdma(sc);
gmac_write_reg(sc, GMAC_INTMASK, 0xffffffff);
ifp->if_flags &= ~(IFF_UP | IFF_RUNNING);
ifp->if_timer = 0;
splx(s);
}
void
gmac_init_mac(struct gmac_softc *sc)
{
int i, tb;
char *laddr = sc->sc_laddr;
if ((mfpvr() >> 16) == MPC601)
tb = mfrtcl();
else
tb = mftbl();
gmac_write_reg(sc, GMAC_RANDOMSEED, tb);
/* init-mii */
gmac_write_reg(sc, GMAC_DATAPATHMODE, 4);
gmac_mii_writereg(&sc->sc_dev, 0, 0, 0x1000);
gmac_write_reg(sc, GMAC_TXDMACONFIG, 0xffc00);
gmac_write_reg(sc, GMAC_RXDMACONFIG, 0);
gmac_write_reg(sc, GMAC_MACPAUSE, 0x1bf0);
gmac_write_reg(sc, GMAC_INTERPACKETGAP0, 0);
gmac_write_reg(sc, GMAC_INTERPACKETGAP1, 8);
gmac_write_reg(sc, GMAC_INTERPACKETGAP2, 4);
gmac_write_reg(sc, GMAC_MINFRAMESIZE, ETHER_MIN_LEN);
gmac_write_reg(sc, GMAC_MAXFRAMESIZE, ETHER_MAX_LEN);
gmac_write_reg(sc, GMAC_PASIZE, 7);
gmac_write_reg(sc, GMAC_JAMSIZE, 4);
gmac_write_reg(sc, GMAC_ATTEMPTLIMIT,0x10);
gmac_write_reg(sc, GMAC_MACCNTLTYPE, 0x8808);
gmac_write_reg(sc, GMAC_MACADDRESS0, (laddr[4] << 8) | laddr[5]);
gmac_write_reg(sc, GMAC_MACADDRESS1, (laddr[2] << 8) | laddr[3]);
gmac_write_reg(sc, GMAC_MACADDRESS2, (laddr[0] << 8) | laddr[1]);
gmac_write_reg(sc, GMAC_MACADDRESS3, 0);
gmac_write_reg(sc, GMAC_MACADDRESS4, 0);
gmac_write_reg(sc, GMAC_MACADDRESS5, 0);
gmac_write_reg(sc, GMAC_MACADDRESS6, 1);
gmac_write_reg(sc, GMAC_MACADDRESS7, 0xc200);
gmac_write_reg(sc, GMAC_MACADDRESS8, 0x0180);
gmac_write_reg(sc, GMAC_MACADDRFILT0, 0);
gmac_write_reg(sc, GMAC_MACADDRFILT1, 0);
gmac_write_reg(sc, GMAC_MACADDRFILT2, 0);
gmac_write_reg(sc, GMAC_MACADDRFILT2_1MASK, 0);
gmac_write_reg(sc, GMAC_MACADDRFILT0MASK, 0);
for (i = 0; i < 0x6c; i += 4)
gmac_write_reg(sc, GMAC_HASHTABLE0 + i, 0);
gmac_write_reg(sc, GMAC_SLOTTIME, 0x40);
if (IFM_OPTIONS(sc->sc_mii.mii_media_active) & IFM_FDX) {
gmac_write_reg(sc, GMAC_TXMACCONFIG, 6);
gmac_write_reg(sc, GMAC_XIFCONFIG, 1);
} else {
gmac_write_reg(sc, GMAC_TXMACCONFIG, 0);
gmac_write_reg(sc, GMAC_XIFCONFIG, 5);
}
if (0) /* g-bit? */
gmac_write_reg(sc, GMAC_MACCTRLCONFIG, 3);
else
gmac_write_reg(sc, GMAC_MACCTRLCONFIG, 0);
}
void
gmac_setladrf(struct gmac_softc *sc)
{
struct ifnet *ifp = &sc->sc_if;
struct ether_multi *enm;
struct ether_multistep step;
struct ethercom *ec = &sc->sc_ethercom;
u_int32_t crc;
u_int32_t hash[16];
u_int v;
int i;
/* Clear hash table */
for (i = 0; i < 16; i++)
hash[i] = 0;
/* Get current RX configuration */
v = gmac_read_reg(sc, GMAC_RXMACCONFIG);
if ((ifp->if_flags & IFF_PROMISC) != 0) {
/* Turn on promiscuous mode; turn off the hash filter */
v |= GMAC_RXMAC_PR;
v &= ~GMAC_RXMAC_HEN;
ifp->if_flags |= IFF_ALLMULTI;
goto chipit;
}
/* Turn off promiscuous mode; turn on the hash filter */
v &= ~GMAC_RXMAC_PR;
v |= GMAC_RXMAC_HEN;
/*
* Set up multicast address filter by passing all multicast addresses
* through a crc generator, and then using the high order 8 bits as an
* index into the 256 bit logical address filter. The high order bit
* selects the word, while the rest of the bits select the bit within
* the word.
*/
ETHER_FIRST_MULTI(step, ec, enm);
while (enm != NULL) {
if (memcmp(enm->enm_addrlo, enm->enm_addrhi, 6)) {
/*
* We must listen to a range of multicast addresses.
* For now, just accept all multicasts, rather than
* trying to set only those filter bits needed to match
* the range. (At this time, the only use of address
* ranges is for IP multicast routing, for which the
* range is big enough to require all bits set.)
*/
for (i = 0; i < 16; i++)
hash[i] = 0xffff;
ifp->if_flags |= IFF_ALLMULTI;
goto chipit;
}
crc = ether_crc32_le(enm->enm_addrlo, ETHER_ADDR_LEN);
/* Just want the 8 most significant bits. */
crc >>= 24;
/* Set the corresponding bit in the filter. */
hash[crc >> 4] |= 1 << (crc & 0xf);
ETHER_NEXT_MULTI(step, enm);
}
ifp->if_flags &= ~IFF_ALLMULTI;
chipit:
/* Now load the hash table into the chip */
for (i = 0; i < 16; i++)
gmac_write_reg(sc, GMAC_HASHTABLE0 + i * 4, hash[i]);
gmac_write_reg(sc, GMAC_RXMACCONFIG, v);
}
void
gmac_init(struct gmac_softc *sc)
{
struct ifnet *ifp = &sc->sc_if;
gmac_stop_txdma(sc);
gmac_stop_rxdma(sc);
gmac_init_mac(sc);
gmac_setladrf(sc);
gmac_start_txdma(sc);
gmac_start_rxdma(sc);
gmac_write_reg(sc, GMAC_INTMASK, ~(GMAC_INT_TXEMPTY | GMAC_INT_RXDONE));
ifp->if_flags |= IFF_RUNNING;
ifp->if_flags &= ~IFF_OACTIVE;
ifp->if_timer = 0;
callout_reset(&sc->sc_tick_ch, 1, gmac_mii_tick, sc);
gmac_start(ifp);
}
int
gmac_ioctl(struct ifnet *ifp, unsigned long cmd, void *data)
{
struct gmac_softc *sc = ifp->if_softc;
struct ifaddr *ifa = (struct ifaddr *)data;
struct ifreq *ifr = (struct ifreq *)data;
int s, error = 0;
s = splnet();
switch (cmd) {
case SIOCINITIFADDR:
ifp->if_flags |= IFF_UP;
gmac_init(sc);
switch (ifa->ifa_addr->sa_family) {
#ifdef INET
case AF_INET:
arp_ifinit(ifp, ifa);
break;
#endif
default:
break;
}
break;
case SIOCSIFFLAGS:
if ((error = ifioctl_common(ifp, cmd, data)) != 0)
break;
/* XXX see the comment in ed_ioctl() about code re-use */
if ((ifp->if_flags & IFF_UP) == 0 &&
(ifp->if_flags & IFF_RUNNING) != 0) {
/*
* If interface is marked down and it is running, then
* stop it.
*/
gmac_stop(sc);
ifp->if_flags &= ~IFF_RUNNING;
} else if ((ifp->if_flags & IFF_UP) != 0 &&
(ifp->if_flags & IFF_RUNNING) == 0) {
/*
* If interface is marked up and it is stopped, then
* start it.
*/
gmac_init(sc);
} else {
/*
* Reset the interface to pick up changes in any other
* flags that affect hardware registers.
*/
gmac_reset(sc);
gmac_init(sc);
}
#ifdef GMAC_DEBUG
if (ifp->if_flags & IFF_DEBUG)
sc->sc_flags |= GMAC_DEBUGFLAG;
#endif
break;
case SIOCADDMULTI:
case SIOCDELMULTI:
case SIOCGIFMEDIA:
case SIOCSIFMEDIA:
if ((error = ether_ioctl(ifp, cmd, data)) == ENETRESET) {
/*
* Multicast list has changed; set the hardware filter
* accordingly.
*/
if (ifp->if_flags & IFF_RUNNING) {
gmac_init(sc);
/* gmac_setladrf(sc); */
}
error = 0;
}
break;
default:
error = ether_ioctl(ifp, cmd, data);
break;
}
splx(s);
return error;
}
void
gmac_watchdog(struct ifnet *ifp)
{
struct gmac_softc *sc = ifp->if_softc;
printf("%s: device timeout\n", ifp->if_xname);
ifp->if_oerrors++;
gmac_reset(sc);
gmac_init(sc);
}
int
gmac_mii_readreg(device_t self, int phy, int reg)
{
struct gmac_softc *sc = device_private(self);
int i;
gmac_write_reg(sc, GMAC_MIFFRAMEOUTPUT,
0x60020000 | (phy << 23) | (reg << 18));
for (i = 1000; i >= 0; i -= 10) {
if (gmac_read_reg(sc, GMAC_MIFFRAMEOUTPUT) & 0x10000)
break;
delay(10);
}
if (i < 0) {
aprint_error_dev(sc->sc_dev, "gmac_mii_readreg: timeout\n");
return 0;
}
return gmac_read_reg(sc, GMAC_MIFFRAMEOUTPUT) & 0xffff;
}
void
gmac_mii_writereg(device_t self, int phy, int reg, int val)
{
struct gmac_softc *sc = device_private(self);
int i;
gmac_write_reg(sc, GMAC_MIFFRAMEOUTPUT,
0x50020000 | (phy << 23) | (reg << 18) | (val & 0xffff));
for (i = 1000; i >= 0; i -= 10) {
if (gmac_read_reg(sc, GMAC_MIFFRAMEOUTPUT) & 0x10000)
break;
delay(10);
}
if (i < 0)
aprint_error_dev(sc->sc_dev, "gmac_mii_writereg: timeout\n");
}
void
gmac_mii_statchg(struct ifnet *ifp)
{
struct gmac_softc *sc = ifp->if_softc;
gmac_stop_txdma(sc);
gmac_stop_rxdma(sc);
if (IFM_OPTIONS(sc->sc_mii.mii_media_active) & IFM_FDX) {
gmac_write_reg(sc, GMAC_TXMACCONFIG, 6);
gmac_write_reg(sc, GMAC_XIFCONFIG, 1);
} else {
gmac_write_reg(sc, GMAC_TXMACCONFIG, 0);
gmac_write_reg(sc, GMAC_XIFCONFIG, 5);
}
if (0) /* g-bit? */
gmac_write_reg(sc, GMAC_MACCTRLCONFIG, 3);
else
gmac_write_reg(sc, GMAC_MACCTRLCONFIG, 0);
gmac_start_txdma(sc);
gmac_start_rxdma(sc);
}
void
gmac_mii_tick(void *v)
{
struct gmac_softc *sc = v;
int s;
s = splnet();
mii_tick(&sc->sc_mii);
splx(s);
callout_reset(&sc->sc_tick_ch, hz, gmac_mii_tick, sc);
}