/*-
* Copyright (c) 1999 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Paul Kranenburg.
*
* 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) 1998 Theo de Raadt and Jason L. Wright.
* 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 authors may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS 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.
*/
struct be_softc {
device_t sc_dev;
bus_space_tag_t sc_bustag; /* bus & DMA tags */
bus_dma_tag_t sc_dmatag;
bus_dmamap_t sc_dmamap;
struct ethercom sc_ethercom;
/*struct ifmedia sc_ifmedia; -* interface media */
struct mii_data sc_mii; /* MII media control */
#define sc_media sc_mii.mii_media/* shorthand */
int sc_phys[2]; /* MII instance -> phy */
struct callout sc_tick_ch;
/*
* Some `mii_softc' items we need to emulate MII operation
* for our internal transceiver.
*/
int sc_mii_inst; /* instance of internal phy */
int sc_mii_active; /* currently active medium */
int sc_mii_ticks; /* tick counter */
int sc_mii_flags; /* phy status flags */
#define MIIF_HAVELINK 0x04000000
int sc_intphy_curspeed; /* Established link speed */
/*
* Initialize transceiver and determine which PHY connection to use.
*/
be_mii_sync(sc);
v = bus_space_read_4(sc->sc_bustag, sc->sc_tr, BE_TRI_MGMTPAL);
child = LIST_FIRST(&mii->mii_phys);
if (child == NULL) {
/* No PHY attached */
ifmedia_add(&sc->sc_media,
IFM_MAKEWORD(IFM_ETHER, IFM_NONE, 0, instance),
0, NULL);
ifmedia_set(&sc->sc_media,
IFM_MAKEWORD(IFM_ETHER, IFM_NONE, 0, instance));
} else {
/*
* Note: we support just one PHY on the external
* MII connector.
*/
#ifdef DIAGNOSTIC
if (LIST_NEXT(child, mii_list) != NULL) {
aprint_error_dev(self,
"spurious MII device %s attached\n",
device_xname(child->mii_dev));
}
#endif
if (child->mii_phy != BE_PHY_EXTERNAL ||
child->mii_inst > 0) {
aprint_error_dev(self,
"cannot accommodate MII device %s"
" at phy %d, instance %d\n",
device_xname(child->mii_dev),
child->mii_phy, child->mii_inst);
} else {
sc->sc_phys[instance] = child->mii_phy;
}
/*
* XXX - we can really do the following ONLY if the
* phy indeed has the auto negotiation capability!!
*/
ifmedia_set(&sc->sc_media,
IFM_MAKEWORD(IFM_ETHER, IFM_AUTO, 0, instance));
/* Mark our current media setting */
be_pal_gate(sc, BE_PHY_EXTERNAL);
instance++;
}
}
if ((v & MGMT_PAL_INT_MDIO) != 0) {
/*
* The be internal phy looks vaguely like MII hardware,
* but not enough to be able to use the MII device
* layer. Hence, we have to take care of media selection
* ourselves.
*/
/* Use `ifm_data' to store BMCR bits */
ifmedia_add(&sc->sc_media,
IFM_MAKEWORD(IFM_ETHER, IFM_10_T, 0, instance),
0, NULL);
ifmedia_add(&sc->sc_media,
IFM_MAKEWORD(IFM_ETHER, IFM_100_TX, 0, instance),
BMCR_S100, NULL);
ifmedia_add(&sc->sc_media,
IFM_MAKEWORD(IFM_ETHER, IFM_AUTO, 0, instance),
0, NULL);
printf("on-board transceiver at %s: 10baseT, 100baseTX, auto\n",
device_xname(self));
be_mii_reset(sc, BE_PHY_INTERNAL);
/* Only set default medium here if there's no external PHY */
if (instance == 0) {
be_pal_gate(sc, BE_PHY_INTERNAL);
ifmedia_set(&sc->sc_media,
IFM_MAKEWORD(IFM_ETHER, IFM_AUTO, 0, instance));
} else
be_mii_writereg(self,
BE_PHY_INTERNAL, MII_BMCR, BMCR_ISO);
}
/* Attach the interface. */
if_attach(ifp);
ether_ifattach(ifp, sc->sc_enaddr);
}
/*
* Routine to copy from mbuf chain to transmit buffer in
* network buffer memory.
*/
static inline int
be_put(struct be_softc *sc, int idx, struct mbuf *m)
{
struct mbuf *n;
int len, tlen = 0, boff = 0;
uint8_t *bp;
bp = sc->sc_rb.rb_txbuf + (idx % sc->sc_rb.rb_ntbuf) * BE_PKT_BUF_SZ;
for (; m; m = n) {
len = m->m_len;
if (len == 0) {
n = m_free(m);
continue;
}
memcpy(bp + boff, mtod(m, void *), len);
boff += len;
tlen += len;
n = m_free(m);
}
return tlen;
}
/*
* Pull data off an interface.
* Len is the length of data, with local net header stripped.
* We copy the data into mbufs. When full cluster sized units are present,
* we copy into clusters.
*/
static inline struct mbuf *
be_get(struct be_softc *sc, int idx, int totlen)
{
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
struct mbuf *m;
struct mbuf *top, **mp;
int len, pad, boff = 0;
uint8_t *bp;
bp = sc->sc_rb.rb_rxbuf + (idx % sc->sc_rb.rb_nrbuf) * BE_PKT_BUF_SZ;
pad = ALIGN(sizeof(struct ether_header)) - sizeof(struct ether_header);
m->m_data += pad;
len = MHLEN - pad;
top = NULL;
mp = ⊤
while (totlen > 0) {
if (top) {
MGET(m, M_DONTWAIT, MT_DATA);
if (m == NULL) {
m_freem(top);
return (NULL);
}
len = MLEN;
}
if (top && totlen >= MINCLSIZE) {
MCLGET(m, M_DONTWAIT);
if (m->m_flags & M_EXT)
len = MCLBYTES;
}
m->m_len = len = uimin(totlen, len);
memcpy(mtod(m, void *), bp + boff, len);
boff += len;
totlen -= len;
*mp = m;
mp = &m->m_next;
}
return top;
}
/*
* Pass a packet to the higher levels.
*/
static inline void
be_read(struct be_softc *sc, int idx, int len)
{
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
struct mbuf *m;
if (len <= sizeof(struct ether_header) ||
len > ETHER_MAX_LEN + ETHER_VLAN_ENCAP_LEN) {
#ifdef BEDEBUG
if (sc->sc_debug)
printf("%s: invalid packet size %d; dropping\n",
ifp->if_xname, len);
#endif
if_statinc(ifp, if_ierrors);
return;
}
/*
* Pull packet off interface.
*/
m = be_get(sc, idx, len);
if (m == NULL) {
if_statinc(ifp, if_ierrors);
return;
}
/* Pass the packet up. */
if_percpuq_enqueue(ifp->if_percpuq, m);
}
/*
* Start output on interface.
* We make an assumption here:
* 1) that the current priority is set to splnet _before_ this code
* is called *and* is returned to the appropriate priority after
* return
*/
void
bestart(struct ifnet *ifp)
{
struct be_softc *sc = ifp->if_softc;
struct qec_xd *txd = sc->sc_rb.rb_txd;
struct mbuf *m;
unsigned int bix, len;
unsigned int ntbuf = sc->sc_rb.rb_ntbuf;
if ((ifp->if_flags & IFF_RUNNING) != IFF_RUNNING)
return;
bix = sc->sc_rb.rb_tdhead;
while (sc->sc_rb.rb_td_nbusy < ntbuf) {
IFQ_DEQUEUE(&ifp->if_snd, m);
if (m == 0)
break;
/*
* If BPF is listening on this interface, let it see the
* packet before we commit it to the wire.
*/
bpf_mtap(ifp, m, BPF_D_OUT);
/*
* Copy the mbuf chain into the transmit buffer.
*/
len = be_put(sc, bix, m);
case SIOCSIFFLAGS:
if ((error = ifioctl_common(ifp, cmd, data)) != 0)
break;
/* XXX re-use ether_ioctl() */
switch (ifp->if_flags & (IFF_UP | IFF_RUNNING)) {
case IFF_RUNNING:
/*
* If interface is marked down and it is running, then
* stop it.
*/
bestop(ifp, 0);
ifp->if_flags &= ~IFF_RUNNING;
break;
case IFF_UP:
/*
* If interface is marked up and it is stopped, then
* start it.
*/
beinit(ifp);
break;
default:
/*
* Reset the interface to pick up changes in any other
* flags that affect hardware registers.
*/
bestop(ifp, 0);
beinit(ifp);
break;
}
#ifdef BEDEBUG
if (ifp->if_flags & IFF_DEBUG)
sc->sc_debug = 1;
else
sc->sc_debug = 0;
#endif
break;
default:
if ((error = ether_ioctl(ifp, cmd, data)) == ENETRESET) {
/*
* Multicast list has changed; set the hardware filter
* accordingly.
*/
if (ifp->if_flags & IFF_RUNNING)
error = beinit(ifp);
else
error = 0;
}
break;
}
splx(s);
return error;
}
/* Set max packet length */
v = ETHER_MAX_LEN;
if (sc->sc_ethercom.ec_capenable & ETHERCAP_VLAN_MTU)
v += ETHER_VLAN_ENCAP_LEN;
bus_space_write_4(t, br, BE_BRI_RXMAX, v);
bus_space_write_4(t, br, BE_BRI_TXMAX, v);
ETHER_LOCK(ec);
ETHER_FIRST_MULTI(step, ec, enm);
while (enm != NULL) {
if (memcmp(enm->enm_addrlo, enm->enm_addrhi, ETHER_ADDR_LEN)) {
/*
* 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.)
*/
hash[3] = hash[2] = hash[1] = hash[0] = 0xffff;
ifp->if_flags |= IFF_ALLMULTI;
ETHER_UNLOCK(ec);
goto chipit;
}
crc = ether_crc32_le(enm->enm_addrlo, ETHER_ADDR_LEN);
/* Just want the 6 most significant bits. */
crc >>= 26;
v = bus_space_read_4(t, br, BE_BRI_RXCFG);
v &= ~BE_BR_RXCFG_PMISC;
v |= BE_BR_RXCFG_HENABLE;
bus_space_write_4(t, br, BE_BRI_RXCFG, v);
}
/*
* Set the tcvr to an idle state
*/
void
be_mii_sync(struct be_softc *sc)
{
bus_space_tag_t t = sc->sc_bustag;
bus_space_handle_t tr = sc->sc_tr;
int n = 32;
/*
* Service routine for our pseudo-MII internal transceiver.
*/
int
be_intphy_service(struct be_softc *sc, struct mii_data *mii, int cmd)
{
struct ifmedia_entry *ife = mii->mii_media.ifm_cur;
device_t self = sc->sc_dev;
uint16_t bmcr, bmsr;
int error;
switch (cmd) {
case MII_POLLSTAT:
/*
* If we're not polling our PHY instance, just return.
*/
if (IFM_INST(ife->ifm_media) != sc->sc_mii_inst)
return 0;
break;
case MII_MEDIACHG:
/*
* If the media indicates a different PHY instance,
* isolate ourselves.
*/
if (IFM_INST(ife->ifm_media) != sc->sc_mii_inst) {
be_mii_readreg(self, BE_PHY_INTERNAL, MII_BMCR, &bmcr);
be_mii_writereg(self,
BE_PHY_INTERNAL, MII_BMCR, bmcr | BMCR_ISO);
sc->sc_mii_flags &= ~MIIF_HAVELINK;
sc->sc_intphy_curspeed = 0;
return 0;
}
if ((error = be_mii_reset(sc, BE_PHY_INTERNAL)) != 0)
return error;
/*
* Select the new mode and take out of isolation
*/
if (IFM_SUBTYPE(ife->ifm_media) == IFM_100_TX)
bmcr |= BMCR_S100;
else if (IFM_SUBTYPE(ife->ifm_media) == IFM_10_T)
bmcr &= ~BMCR_S100;
else if (IFM_SUBTYPE(ife->ifm_media) == IFM_AUTO) {
if ((sc->sc_mii_flags & MIIF_HAVELINK) != 0) {
bmcr &= ~BMCR_S100;
bmcr |= sc->sc_intphy_curspeed;
} else {
/* Keep isolated until link is up */
bmcr |= BMCR_ISO;
sc->sc_mii_flags |= MIIF_DOINGAUTO;
}
}
case MII_TICK:
/*
* If we're not currently selected, just return.
*/
if (IFM_INST(ife->ifm_media) != sc->sc_mii_inst)
return 0;
/* Is the interface even up? */
if ((mii->mii_ifp->if_flags & IFF_UP) == 0)
return 0;
/* Only used for automatic media selection */
if (IFM_SUBTYPE(ife->ifm_media) != IFM_AUTO)
break;
/*
* Check link status; if we don't have a link, try another
* speed. We can't detect duplex mode, so half-duplex is
* what we have to settle for.
*/
/* Read twice in case the register is latched */
be_mii_readreg(self, BE_PHY_INTERNAL, MII_BMSR, &bmsr);
be_mii_readreg(self, BE_PHY_INTERNAL, MII_BMSR, &bmsr);
if ((bmsr & BMSR_LINK) != 0) {
/* We have a carrier */
be_mii_readreg(self, BE_PHY_INTERNAL, MII_BMCR, &bmcr);
/* Read twice in case the register is latched */
be_mii_readreg(self, BE_PHY_INTERNAL, MII_BMSR, &bmsr);
be_mii_readreg(self, BE_PHY_INTERNAL, MII_BMSR, &bmsr);
if (bmsr & BMSR_LINK)
media_status |= IFM_ACTIVE;