/*-
* Copyright (c) 1994, 1995, 1998 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Ignatios Souvatzis.
*
* 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.
*/
/*
* Chip core driver for the SMC90c26 / SMC90c56 (and SMC90c66 in '56
* compatibility mode) boards
*/
/* for watchdog timer. This should be more than enough. */
#define ARCTIMEOUT (5*IFNET_SLOWHZ)
/*
* This currently uses 2 bufs for tx, 2 for rx
*
* New rx protocol:
*
* rx has a fillcount variable. If fillcount > (NRXBUF-1),
* rx can be switched off from rx hard int.
* Else rx is restarted on the other receiver.
* rx soft int counts down. if it is == (NRXBUF-1), it restarts
* the receiver.
* To ensure packet ordering (we need that for 1201 later), we have a counter
* which is incremented modulo 256 on each receive and a per buffer
* variable, which is set to the counter on filling. The soft int can
* compare both values to determine the older packet.
*
* Transmit direction:
*
* bah_start checks tx_fillcount
* case 2: return
*
* else fill tx_act ^ 1 && inc tx_fillcount
*
* check tx_fillcount again.
* case 2: set IFF_OACTIVE to stop arc_output from filling us.
* case 1: start tx
*
* tint clears IFF_OCATIVE, decrements and checks tx_fillcount
* case 1: start tx on tx_act ^ 1, softcall bah_start
* case 0: softcall bah_start
*
* #define fill(i) get mbuf && copy mbuf to chip(i)
*/
if ((ifp->if_flags & IFF_RUNNING) == 0) {
s = splnet();
ifp->if_flags |= IFF_RUNNING;
bah_reset(sc);
bah_start(ifp);
splx(s);
}
}
/*
* Reset the interface...
*
* this assumes that it is called inside a critical section...
*
*/
void
bah_reset(struct bah_softc *sc)
{
struct ifnet *ifp;
uint8_t linkaddress;
/*
* Start output on interface. Get another datagram to send
* off the interface queue, and copy it to the
* interface before starting the output
*
* this assumes that it is called inside a critical section...
* XXX hm... does it still?
*
*/
void
bah_start(struct ifnet *ifp)
{
struct bah_softc *sc = ifp->if_softc;
struct mbuf *m,*mp;
/*
* If bpf is listening on this interface, let it
* see the packet before we commit it to the wire
*
* (can't give the copy in A2060 card RAM to bpf, because
* that RAM is just accessed as on every other byte)
*/
bpf_mtap(ifp, m, BPF_D_OUT);
#ifdef BAH_DEBUG
if (m->m_len < ARC_HDRLEN)
m = m_pullup(m, ARC_HDRLEN);/* gcc does structure padding */
printf("%s: start: filling %d from %u to %u type %u\n",
device_xname(sc->sc_dev), buffer, mtod(m, u_char *)[0],
mtod(m, u_char *)[1], mtod(m, u_char *)[2]);
#else
if (m->m_len < 2)
m = m_pullup(m, 2);
#endif
bah_ram_ptr = buffer*512;
if (m == 0)
return;
/* write the addresses to RAM and throw them away */
/*
* Hardware does this: Yet Another Microsecond Saved.
* (btw, timing code says usually 2 microseconds)
* PUTMEM(bah_ram_ptr + 0, mtod(m, u_char *)[0]);
*/
if (++sc->sc_tx_fillcount > 1) {
/*
* We are filled up to the rim. No more bufs for the moment,
* please.
*/
ifp->if_flags |= IFF_OACTIVE;
} else {
#ifdef BAH_DEBUG
printf("%s: start: starting transmitter on buffer %d\n",
device_xname(sc->sc_dev), buffer);
#endif
/* Transmitter was off, start it */
sc->sc_tx_act = buffer;
/*
* We still can accept another buf, so don't:
* ifp->if_flags |= IFF_OACTIVE;
*/
sc->sc_intmask |= BAH_TA;
PUTREG(BAHCMD, BAH_TX(buffer));
PUTREG(BAHSTAT, sc->sc_intmask);
/*
* After 10 times reading the docs, I realized
* that in the case the receiver NAKs the buffer request,
* the hardware retries till shutdown.
* This is integrated now in the code above.
*/
return;
}
/*
* Arcnet interface receiver soft interrupt:
* get the stuff out of any filled buffer we find.
*/
void
bah_srint(void *vsc)
{
struct bah_softc *sc = (struct bah_softc *)vsc;
int buffer, len, len1, amount, offset, s, type;
int bah_ram_ptr;
struct mbuf *m, *dst, *head;
struct arc_header *ah;
struct ifnet *ifp;
if (m == 0) {
/*
* in case s.th. goes wrong with mem, drop it
* to make sure the receiver can be started again
* count it as input error (we dont have any other
* detectable)
*/
if_statinc(ifp, if_ierrors);
goto cleanup;
}
m_set_rcvif(m, ifp);
/*
* Align so that IP packet will be longword aligned. Here we
* assume that m_data of new packet is longword aligned.
* When implementing PHDS, we might have to change it to 2,
* (2*sizeof(ulong) - ARC_HDRNEWLEN)), packet type dependent.
*/
bah_ram_ptr = buffer*512;
offset = GETMEM(bah_ram_ptr + 2);
if (offset)
len = 256 - offset;
else {
offset = GETMEM(bah_ram_ptr + 3);
len = 512 - offset;
}
if (len+2 >= MINCLSIZE)
MCLGET(m, M_DONTWAIT);
if (m == 0) {
if_statinc(ifp, if_ierrors);
goto cleanup;
}
type = GETMEM(bah_ram_ptr + offset);
m->m_data += 1 + arc_isphds(type);
#if defined(BAH_DEBUG) && (BAH_DEBUG > 1)
printf("%s: tint: starting tx on buffer %d, status 0x%02x\n",
device_xname(sc->sc_dev), buffer, GETREG(BAHSTAT));
#endif
} else {
/* have to disable TX interrupt */
sc->sc_intmask &= ~BAH_TA;
PUTREG(BAHSTAT, sc->sc_intmask);
/* ... and watchdog timer */
ifp->if_timer = 0;
#ifdef BAH_DEBUG
printf("%s: tint: no more buffers to send, status 0x%02x\n",
device_xname(sc->sc_dev), GETREG(BAHSTAT));
#endif
}
/* XXXX TODO */
#ifdef BAHSOFTCOPY
/* schedule soft int to fill a new buffer for us */
softint_schedule(sc->sc_txcookie);
#else
if_schedule_deferred_start(ifp);
#endif
}
if (maskedisr & BAH_POR) {
/*
* XXX We should never see this. Don't bother to store
* the address.
* sc->sc_arccom.ac_anaddr = GETMEM(BAHMACOFF);
*/
PUTREG(BAHCMD, BAH_CLR(CLR_POR));
log(LOG_WARNING,
"%s: intr: got spurious power on reset int\n",
device_xname(sc->sc_dev));
}
if (maskedisr & BAH_RECON) {
/*
* we dont need to:
* PUTREG(BAHCMD, BAH_CONF(CONF_LONG));
*/
PUTREG(BAHCMD, BAH_CLR(CLR_RECONFIG));
if_statinc(&sc->sc_arccom.ac_if, if_collisions);
/*
* If less than 2 seconds per reconfig:
* If ARC_EXCESSIVE_RECONFIGS
* since last burst, complain and set threshold for
* warnings to ARC_EXCESSIVE_RECONS_REWARN.
*
* This allows for, e.g., new stations on the cable, or
* cable switching as long as it is over after
* (normally) 16 seconds.
*
* XXX TODO: check timeout bits in status word and
* double time if necessary.
*/
/*
* Start receiver on other receive
* buffer. This also clears the RI
* interrupt flag.
*/
PUTREG(BAHCMD, BAH_RXBC(buffer));
/* in RX intr, so mask is ok for RX */
#ifdef BAHSOFTCOPY
/*
* this one starts a soft int to copy out
* of the hw
*/
softint_schedule(sc->sc_rxcookie);
#else
/* this one does the copy here */
bah_srint(sc);
#endif
}
}
if (maskedisr & BAH_TA) {
bah_tint(sc, isr);
}
isr = GETREG(BAHSTAT);
maskedisr = isr & sc->sc_intmask;
} while (maskedisr);
/*
* Process an ioctl request.
* This code needs some work - it looks pretty ugly.
*/
int
bah_ioctl(struct ifnet *ifp, u_long cmd, void *data)
{
struct bah_softc *sc;
struct ifaddr *ifa;
struct ifreq *ifr;
int s, error;
error = 0;
sc = ifp->if_softc;
ifa = (struct ifaddr *)data;
ifr = (struct ifreq *)data;
s = splnet();
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.
*/
bah_stop(sc);
ifp->if_flags &= ~IFF_RUNNING;
break;
case IFF_UP:
/*
* If interface is marked up and it is stopped, then
* start it.
*/
bah_init(sc);
break;
}
break;
case SIOCADDMULTI:
case SIOCDELMULTI:
switch (ifreq_getaddr(cmd, ifr)->sa_family) {
case AF_INET:
case AF_INET6:
error = 0;
break;
default:
error = EAFNOSUPPORT;
break;
}
break;
default:
error = ether_ioctl(ifp, cmd, data);
}
splx(s);
return (error);
}
/*
* watchdog routine for transmitter.
*
* We need this, because else a receiver whose hardware is alive, but whose
* software has not enabled the Receiver, would make our hardware wait forever
* Discovered this after 20 times reading the docs.
*
* Only thing we do is disable transmitter. We'll get a transmit timeout,
* and the int handler will have to decide not to retransmit (in case
* retransmission is implemented).
*
* This one assumes being called inside splnet()
*/