edc612fe8c
- Don't recurse driver mutex. - Don't hold driver mutex across fubyte/subyte. - Replace fubyte/subyte loops with copyin/copyout calls. - Use relatively sane locking in wl_ioctl(). - Use bus space accessors instead of in*()/out*(). - Use callout(9) instead of timeout(9). - Stop watchdog timer in detach and don't hold mutex across bus_teardown_intr(). - Use device_printf() and if_printf(). - De-spl(). Tested by: no one
2621 lines
74 KiB
C
2621 lines
74 KiB
C
/*-
|
|
* 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 all copyright
|
|
* notices, this list of conditions and the following disclaimer.
|
|
* 2. The names 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.
|
|
*
|
|
*/
|
|
/*
|
|
* if_wl.c - original MACH, then BSDI ISA wavelan driver
|
|
* ported to mach by Anders Klemets
|
|
* to BSDI by Robert Morris
|
|
* to FreeBSD by Jim Binkley
|
|
* to FreeBSD 2.2+ by Michael Smith
|
|
*
|
|
* 2.2 update:
|
|
* Changed interface to match 2.1-2.2 differences.
|
|
* Implement IRQ selection logic in wlprobe()
|
|
* Implement PSA updating.
|
|
* Pruned heading comments for relevance.
|
|
* Ripped out all the 'interface counters' cruft.
|
|
* Cut the missing-interrupt timer back to 100ms.
|
|
* 2.2.1 update:
|
|
* now supports all multicast mode (mrouted will work),
|
|
* but unfortunately must do that by going into promiscuous mode
|
|
* NWID sysctl added so that normally promiscuous mode is NWID-specific
|
|
* but can be made NWID-inspecific
|
|
* 7/14/97 jrb
|
|
*
|
|
* Work done:
|
|
* Ported to FreeBSD, got promiscuous mode working with bpfs,
|
|
* and rewired timer routine. The i82586 will hang occasionally on output
|
|
* and the watchdog timer will kick it if so and log an entry.
|
|
* 2 second timeout there. Apparently the chip loses an interrupt.
|
|
* Code borrowed from if_ie.c for watchdog timer.
|
|
*
|
|
* The wavelan card is a 2mbit radio modem that emulates ethernet;
|
|
* i.e., it uses MAC addresses. This should not be a surprise since
|
|
* it uses an ethernet controller as a major hw item.
|
|
* It can broadcast, unicast or apparently multicast in a base cell
|
|
* using an omni-directional antennae that is
|
|
* about 800 feet around the base cell barring walls and metal.
|
|
* With directional antennae, it can be used point to point over a mile
|
|
* or so apparently (haven't tried that).
|
|
*
|
|
* There are ISA and pcmcia versions (not supported by this code).
|
|
* The ISA card has an Intel 82586 lan controller on it. It consists
|
|
* of 2 pieces of hw, the lan controller (intel) and a radio-modem.
|
|
* The latter has an extra set of controller registers that has nothing
|
|
* to do with the i82586 and allows setting and monitoring of radio
|
|
* signal strength, etc. There is a nvram area called the PSA that
|
|
* contains a number of setup variables including the IRQ and so-called
|
|
* NWID or Network ID. The NWID must be set the same for all radio
|
|
* cards to communicate (unless you are using the ATT/NCR roaming feature
|
|
* with their access points. There is no support for that here. Roaming
|
|
* involves a link-layer beacon sent out from the access points. End
|
|
* stations monitor the signal strength and only use the strongest
|
|
* access point). This driver assumes that the base ISA port, IRQ,
|
|
* and NWID are first set in nvram via the dos-side "instconf.exe" utility
|
|
* supplied with the card. This driver takes the ISA port from
|
|
* the kernel configuration setup, and then determines the IRQ either
|
|
* from the kernel config (if an explicit IRQ is set) or from the
|
|
* PSA on the card if not.
|
|
* The hw also magically just uses the IRQ set in the nvram.
|
|
* The NWID is used magically as well by the radio-modem
|
|
* to determine which packets to keep or throw out.
|
|
*
|
|
* sample config:
|
|
*
|
|
* device wl0 at isa? port 0x300 net irq ?
|
|
*
|
|
* Ifdefs:
|
|
* 1. WLDEBUG. (off) - if turned on enables IFF_DEBUG set via ifconfig debug
|
|
* 2. MULTICAST (on) - turned on and works up to and including mrouted
|
|
* 3. WLCACHE (off) - define to turn on a signal strength
|
|
* (and other metric) cache that is indexed by sender MAC address.
|
|
* Apps can read this out to learn the remote signal strength of a
|
|
* sender. Note that it has a switch so that it only stores
|
|
* broadcast/multicast senders but it could be set to store unicast
|
|
* too only. Size is hardwired in if_wl_wavelan.h
|
|
*
|
|
* one further note: promiscuous mode is a curious thing. In this driver,
|
|
* promiscuous mode apparently CAN catch ALL packets and ignore the NWID
|
|
* setting. This is probably more useful in a sense (for snoopers) if
|
|
* you are interested in all traffic as opposed to if you are interested
|
|
* in just your own. There is a driver specific sysctl to turn promiscuous
|
|
* from just promiscuous to wildly promiscuous...
|
|
*
|
|
* This driver also knows how to load the synthesizers in the 2.4 Gz
|
|
* ISA Half-card, Product number 847647476 (USA/FCC IEEE Channel set).
|
|
* This product consists of a "mothercard" that contains the 82586,
|
|
* NVRAM that holds the PSA, and the ISA-buss interface custom ASIC.
|
|
* The radio transceiver is a "daughtercard" called the WaveMODEM which
|
|
* connects to the mothercard through two single-inline connectors: a
|
|
* 20-pin connector provides DC-power and modem signals, and a 3-pin
|
|
* connector which exports the antenna connection. The code herein
|
|
* loads the receive and transmit synthesizers and the corresponding
|
|
* transmitter output power value from an EEPROM controlled through
|
|
* additional registers via the MMC. The EEPROM address selected
|
|
* are those whose values are preset by the DOS utility programs
|
|
* provided with the product, and this provides compatible operation
|
|
* with the DOS Packet Driver software. A future modification will
|
|
* add the necessary functionality to this driver and to the wlconfig
|
|
* utility to completely replace the DOS Configuration Utilities.
|
|
* The 2.4 Gz WaveMODEM is described in document number 407-024692/E,
|
|
* and is available through Lucent Technologies OEM supply channels.
|
|
* --RAB 1997/06/08.
|
|
*/
|
|
|
|
#define MULTICAST 1
|
|
|
|
/*
|
|
* Olivetti PC586 Mach Ethernet driver v1.0
|
|
* Copyright Ing. C. Olivetti & C. S.p.A. 1988, 1989
|
|
* All rights reserved.
|
|
*
|
|
*/
|
|
/*
|
|
Copyright 1988, 1989 by Olivetti Advanced Technology Center, Inc.,
|
|
Cupertino, California.
|
|
|
|
All Rights Reserved
|
|
|
|
Permission to use, copy, modify, and distribute this software and
|
|
its documentation for any purpose and without fee is hereby
|
|
granted, provided that the above copyright notice appears in all
|
|
copies and that both the copyright notice and this permission notice
|
|
appear in supporting documentation, and that the name of Olivetti
|
|
not be used in advertising or publicity pertaining to distribution
|
|
of the software without specific, written prior permission.
|
|
|
|
OLIVETTI DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE
|
|
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS,
|
|
IN NO EVENT SHALL OLIVETTI BE LIABLE FOR ANY SPECIAL, INDIRECT, OR
|
|
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
LOSS OF USE, DATA OR PROFITS, WHETHER IN ACTION OF CONTRACT,
|
|
NEGLIGENCE, OR OTHER TORTIOUS ACTION, ARISING OUR OF OR IN CONNECTION
|
|
WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
/*
|
|
Copyright 1988, 1989 by Intel Corporation, Santa Clara, California.
|
|
|
|
All Rights Reserved
|
|
|
|
Permission to use, copy, modify, and distribute this software and
|
|
its documentation for any purpose and without fee is hereby
|
|
granted, provided that the above copyright notice appears in all
|
|
copies and that both the copyright notice and this permission notice
|
|
appear in supporting documentation, and that the name of Intel
|
|
not be used in advertising or publicity pertaining to distribution
|
|
of the software without specific, written prior permission.
|
|
|
|
INTEL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE
|
|
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS,
|
|
IN NO EVENT SHALL INTEL BE LIABLE FOR ANY SPECIAL, INDIRECT, OR
|
|
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
LOSS OF USE, DATA OR PROFITS, WHETHER IN ACTION OF CONTRACT,
|
|
NEGLIGENCE, OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
|
|
WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
/*
|
|
* NOTE:
|
|
* by rvb:
|
|
* 1. The best book on the 82586 is:
|
|
* LAN Components User's Manual by Intel
|
|
* The copy I found was dated 1984. This really tells you
|
|
* what the state machines are doing
|
|
* 2. In the current design, we only do one write at a time,
|
|
* though the hardware is capable of chaining and possibly
|
|
* even batching. The problem is that we only make one
|
|
* transmit buffer available in sram space.
|
|
*/
|
|
|
|
#include "opt_wavelan.h"
|
|
#include "opt_inet.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/module.h>
|
|
#include <sys/sockio.h>
|
|
#include <sys/mbuf.h>
|
|
#include <sys/priv.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/syslog.h>
|
|
#include <machine/bus.h>
|
|
#include <machine/resource.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/rman.h>
|
|
|
|
#include <sys/sysctl.h>
|
|
|
|
#include <net/ethernet.h>
|
|
#include <net/if.h>
|
|
#include <net/if_var.h>
|
|
#include <net/if_arp.h>
|
|
#include <net/if_dl.h>
|
|
#include <net/if_types.h>
|
|
|
|
#ifdef INET
|
|
#include <netinet/in.h>
|
|
#include <netinet/in_systm.h>
|
|
#include <netinet/ip.h>
|
|
#include <netinet/if_ether.h>
|
|
#endif
|
|
|
|
#include <net/bpf.h>
|
|
#include <isa/isavar.h>
|
|
|
|
/* was 1000 in original, fed to DELAY(x) */
|
|
#define DELAYCONST 1000
|
|
#include <dev/wl/if_wl_i82586.h> /* Definitions for the Intel chip */
|
|
#include <dev/wl/if_wl.h>
|
|
#include <machine/if_wl_wavelan.h>
|
|
|
|
static char t_packet[ETHERMTU + sizeof(struct ether_header) + sizeof(long)];
|
|
|
|
struct wl_softc {
|
|
device_t dev;
|
|
struct ifnet *ifp;
|
|
u_char psa[0x40];
|
|
u_char nwid[2]; /* current radio modem nwid */
|
|
int flags;
|
|
int tbusy; /* flag to determine if xmit is busy */
|
|
u_short begin_fd;
|
|
u_short end_fd;
|
|
u_short end_rbd;
|
|
u_short hacr; /* latest host adapter CR command */
|
|
short mode;
|
|
u_char chan24; /* 2.4 Gz: channel number/EEPROM Area # */
|
|
u_short freq24; /* 2.4 Gz: resulting frequency */
|
|
int rid_ioport;
|
|
int rid_irq;
|
|
struct resource *res_ioport;
|
|
struct resource *res_irq;
|
|
void *intr_cookie;
|
|
struct mtx wl_mtx;
|
|
struct callout watchdog_timer;
|
|
#ifdef WLCACHE
|
|
int w_sigitems; /* number of cached entries */
|
|
/* array of cache entries */
|
|
struct w_sigcache w_sigcache[ MAXCACHEITEMS ];
|
|
int w_nextcache; /* next free cache entry */
|
|
int w_wrapindex; /* next "free" cache entry */
|
|
#endif
|
|
};
|
|
|
|
#define WL_LOCK(_sc) mtx_lock(&(_sc)->wl_mtx)
|
|
#define WL_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->wl_mtx, MA_OWNED)
|
|
#define WL_UNLOCK(_sc) mtx_unlock(&(_sc)->wl_mtx)
|
|
|
|
static int wlprobe(device_t);
|
|
static int wlattach(device_t);
|
|
static int wldetach(device_t);
|
|
|
|
static device_method_t wl_methods[] = {
|
|
DEVMETHOD(device_probe, wlprobe),
|
|
DEVMETHOD(device_attach, wlattach),
|
|
DEVMETHOD(device_detach, wldetach),
|
|
{ 0, 0}
|
|
};
|
|
|
|
static driver_t wl_driver = {
|
|
"wl",
|
|
wl_methods,
|
|
sizeof (struct wl_softc)
|
|
};
|
|
|
|
devclass_t wl_devclass;
|
|
DRIVER_MODULE(wl, isa, wl_driver, wl_devclass, 0, 0);
|
|
MODULE_DEPEND(wl, isa, 1, 1, 1);
|
|
MODULE_DEPEND(wl, ether, 1, 1, 1);
|
|
|
|
static struct isa_pnp_id wl_ids[] = {
|
|
{0, NULL}
|
|
};
|
|
|
|
/*
|
|
* XXX The Wavelan appears to be prone to dropping stuff if you talk to
|
|
* it too fast. This disgusting hack inserts a delay after each packet
|
|
* is queued which helps avoid this behaviour on fast systems.
|
|
*/
|
|
static int wl_xmit_delay = 250;
|
|
SYSCTL_INT(_machdep, OID_AUTO, wl_xmit_delay, CTLFLAG_RW, &wl_xmit_delay, 0, "");
|
|
|
|
/*
|
|
* not XXX, but ZZZ (bizarre).
|
|
* promiscuous mode can be toggled to ignore NWIDs. By default,
|
|
* it does not. Caution should be exercised about combining
|
|
* this mode with IFF_ALLMULTI which puts this driver in
|
|
* promiscuous mode.
|
|
*/
|
|
static int wl_ignore_nwid = 0;
|
|
SYSCTL_INT(_machdep, OID_AUTO, wl_ignore_nwid, CTLFLAG_RW, &wl_ignore_nwid, 0, "");
|
|
|
|
/*
|
|
* Emit diagnostics about transmission problems
|
|
*/
|
|
static int xmt_watch = 0;
|
|
SYSCTL_INT(_machdep, OID_AUTO, wl_xmit_watch, CTLFLAG_RW, &xmt_watch, 0, "");
|
|
|
|
/*
|
|
* Collect SNR statistics
|
|
*/
|
|
static int gathersnr = 0;
|
|
SYSCTL_INT(_machdep, OID_AUTO, wl_gather_snr, CTLFLAG_RW, &gathersnr, 0, "");
|
|
|
|
static int wl_allocate_resources(device_t device);
|
|
static int wl_deallocate_resources(device_t device);
|
|
static void wlstart(struct ifnet *ifp);
|
|
static void wlstart_locked(struct ifnet *ifp);
|
|
static void wlinit(void *xsc);
|
|
static void wlinit_locked(struct wl_softc *sc);
|
|
static int wlioctl(struct ifnet *ifp, u_long cmd, caddr_t data);
|
|
static void wlwatchdog(void *arg);
|
|
static void wlintr(void *arg);
|
|
static void wlxmt(struct wl_softc *sc, struct mbuf *m);
|
|
static int wldiag(struct wl_softc *sc);
|
|
static int wlconfig(struct wl_softc *sc);
|
|
static int wlcmd(struct wl_softc *sc, char *str);
|
|
static void wlmmcstat(struct wl_softc *sc);
|
|
static u_short wlbldru(struct wl_softc *sc);
|
|
static u_short wlmmcread(struct wl_softc *sc, u_short reg);
|
|
static void wlinitmmc(struct wl_softc *sc);
|
|
static int wlhwrst(struct wl_softc *sc);
|
|
static void wlrustrt(struct wl_softc *sc);
|
|
static void wlbldcu(struct wl_softc *sc);
|
|
static int wlack(struct wl_softc *sc);
|
|
static int wlread(struct wl_softc *sc, u_short fd_p);
|
|
static void getsnr(struct wl_softc *sc);
|
|
static void wlrcv(struct wl_softc *sc);
|
|
static int wlrequeue(struct wl_softc *sc, u_short fd_p);
|
|
static void wlsftwsleaze(u_short *countp, u_char **mb_pp, struct mbuf **tm_pp, struct wl_softc *sc);
|
|
static void wlhdwsleaze(u_short *countp, u_char **mb_pp, struct mbuf **tm_pp, struct wl_softc *sc);
|
|
#ifdef WLDEBUG
|
|
static void wltbd(struct wl_softc *sc);
|
|
#endif
|
|
static void wlgetpsa(struct wl_softc *sc, u_char *buf);
|
|
static void wlsetpsa(struct wl_softc *sc);
|
|
static u_short wlpsacrc(u_char *buf);
|
|
static void wldump(struct wl_softc *sc);
|
|
#ifdef WLCACHE
|
|
static void wl_cache_store(struct wl_softc *, struct ether_header *, struct mbuf *);
|
|
static void wl_cache_zero(struct wl_softc *sc);
|
|
#endif
|
|
|
|
/* array for maping irq numbers to values for the irq parameter register */
|
|
static int irqvals[16] = {
|
|
0, 0, 0, 0x01, 0x02, 0x04, 0, 0x08, 0, 0, 0x10, 0x20, 0x40, 0, 0, 0x80
|
|
};
|
|
|
|
/*
|
|
* wlprobe:
|
|
*
|
|
* This function "probes" or checks for the WaveLAN board on the bus to
|
|
* see if it is there. As far as I can tell, the best break between this
|
|
* routine and the attach code is to simply determine whether the board
|
|
* is configured in properly. Currently my approach to this is to write
|
|
* and read a word from the SRAM on the board being probed. If the word
|
|
* comes back properly then we assume the board is there. The config
|
|
* code expects to see a successful return from the probe routine before
|
|
* attach will be called.
|
|
*
|
|
* input : address device is mapped to, and unit # being checked
|
|
* output : a '1' is returned if the board exists, and a 0 otherwise
|
|
*
|
|
*/
|
|
static int
|
|
wlprobe(device_t device)
|
|
{
|
|
struct wl_softc *sc;
|
|
char *str = "wl%d: board out of range [0..%d]\n";
|
|
u_char inbuf[100];
|
|
unsigned long junk, sirq;
|
|
int error, irq;
|
|
|
|
error = ISA_PNP_PROBE(device_get_parent(device), device, wl_ids);
|
|
if (error == ENXIO || error == 0)
|
|
return (error);
|
|
|
|
sc = device_get_softc(device);
|
|
error = wl_allocate_resources(device);
|
|
if (error)
|
|
goto errexit;
|
|
|
|
/* TBD. not true.
|
|
* regular CMD() will not work, since no softc yet
|
|
*/
|
|
#define PCMD(sc, hacr) WL_WRITE_2((sc), HACR, (hacr))
|
|
|
|
PCMD(sc, HACR_RESET); /* reset the board */
|
|
DELAY(DELAYCONST); /* >> 4 clocks at 6MHz */
|
|
PCMD(sc, HACR_RESET); /* reset the board */
|
|
DELAY(DELAYCONST); /* >> 4 clocks at 6MHz */
|
|
|
|
/* clear reset command and set PIO#1 in autoincrement mode */
|
|
PCMD(sc, HACR_DEFAULT);
|
|
PCMD(sc, HACR_DEFAULT);
|
|
WL_WRITE_2(sc, PIOR1, 0); /* go to beginning of RAM */
|
|
WL_WRITE_MULTI_2(sc, PIOP1, str, strlen(str)/2+1); /* write string */
|
|
|
|
WL_WRITE_2(sc, PIOR1, 0); /* rewind */
|
|
WL_READ_MULTI_2(sc, PIOP1, inbuf, strlen(str)/2+1); /* read result */
|
|
|
|
if (bcmp(str, inbuf, strlen(str))) {
|
|
error = ENXIO;
|
|
goto errexit;
|
|
}
|
|
|
|
sc->chan24 = 0; /* 2.4 Gz: config channel */
|
|
sc->freq24 = 0; /* 2.4 Gz: frequency */
|
|
|
|
/* read the PSA from the board into temporary storage */
|
|
wlgetpsa(sc, inbuf);
|
|
|
|
/* We read the IRQ value from the PSA on the board. */
|
|
for (irq = 15; irq >= 0; irq--)
|
|
if (irqvals[irq] == inbuf[WLPSA_IRQNO])
|
|
break;
|
|
if ((irq == 0) || (irqvals[irq] == 0)){
|
|
device_printf(device, "PSA corrupt (invalid IRQ value)\n");
|
|
} else {
|
|
/*
|
|
* If the IRQ requested by the PSA is already claimed by another
|
|
* device, the board won't work, but the user can still access the
|
|
* driver to change the IRQ.
|
|
*/
|
|
if (bus_get_resource(device, SYS_RES_IRQ, 0, &sirq, &junk))
|
|
goto errexit;
|
|
if (irq != (int)sirq)
|
|
device_printf(device, "board is configured for interrupt %d\n",
|
|
irq);
|
|
}
|
|
wl_deallocate_resources(device);
|
|
return (0);
|
|
|
|
errexit:
|
|
wl_deallocate_resources(device);
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* wlattach:
|
|
*
|
|
* This function attaches a WaveLAN board to the "system". The rest of
|
|
* runtime structures are initialized here (this routine is called after
|
|
* a successful probe of the board). Once the ethernet address is read
|
|
* and stored, the board's ifnet structure is attached and readied.
|
|
*
|
|
* input : isa_dev structure setup in autoconfig
|
|
* output : board structs and ifnet is setup
|
|
*
|
|
*/
|
|
static int
|
|
wlattach(device_t device)
|
|
{
|
|
struct wl_softc *sc;
|
|
int error, i, j;
|
|
struct ifnet *ifp;
|
|
u_char eaddr[6];
|
|
|
|
sc = device_get_softc(device);
|
|
sc->dev = device;
|
|
ifp = sc->ifp = if_alloc(IFT_ETHER);
|
|
if (ifp == NULL) {
|
|
device_printf(device, "can not if_alloc()\n");
|
|
return (ENOSPC);
|
|
}
|
|
|
|
mtx_init(&sc->wl_mtx, device_get_nameunit(device), MTX_NETWORK_LOCK,
|
|
MTX_DEF);
|
|
callout_init_mtx(&sc->watchdog_timer, &sc->wl_mtx, 0);
|
|
|
|
error = wl_allocate_resources(device);
|
|
if (error) {
|
|
wl_deallocate_resources(device);
|
|
return (ENXIO);
|
|
}
|
|
|
|
#ifdef WLDEBUG
|
|
printf("wlattach: base %lx, unit %d\n", rman_get_start(sc->res_ioport),
|
|
device_get_unit(device));
|
|
#endif
|
|
|
|
sc->flags = 0;
|
|
sc->mode = 0;
|
|
sc->hacr = HACR_RESET;
|
|
CMD(sc); /* reset the board */
|
|
DELAY(DELAYCONST); /* >> 4 clocks at 6MHz */
|
|
|
|
/* clear reset command and set PIO#2 in parameter access mode */
|
|
sc->hacr = (HACR_DEFAULT & ~HACR_16BITS);
|
|
CMD(sc);
|
|
|
|
/* Read the PSA from the board for our later reference */
|
|
wlgetpsa(sc, sc->psa);
|
|
|
|
/* fetch NWID */
|
|
sc->nwid[0] = sc->psa[WLPSA_NWID];
|
|
sc->nwid[1] = sc->psa[WLPSA_NWID+1];
|
|
|
|
/* fetch MAC address - decide which one first */
|
|
if (sc->psa[WLPSA_MACSEL] & 1)
|
|
j = WLPSA_LOCALMAC;
|
|
else
|
|
j = WLPSA_UNIMAC;
|
|
for (i=0; i < WAVELAN_ADDR_SIZE; ++i)
|
|
eaddr[i] = sc->psa[j + i];
|
|
|
|
/* enter normal 16 bit mode operation */
|
|
sc->hacr = HACR_DEFAULT;
|
|
CMD(sc);
|
|
|
|
wlinitmmc(sc);
|
|
WL_WRITE_2(sc, PIOR1, OFFSET_SCB + 8); /* address of scb_crcerrs */
|
|
WL_WRITE_2(sc, PIOP1, 0); /* clear scb_crcerrs */
|
|
WL_WRITE_2(sc, PIOP1, 0); /* clear scb_alnerrs */
|
|
WL_WRITE_2(sc, PIOP1, 0); /* clear scb_rscerrs */
|
|
WL_WRITE_2(sc, PIOP1, 0); /* clear scb_ovrnerrs */
|
|
|
|
ifp->if_softc = sc;
|
|
ifp->if_mtu = WAVELAN_MTU;
|
|
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX;
|
|
#ifdef WLDEBUG
|
|
ifp->if_flags |= IFF_DEBUG;
|
|
#endif
|
|
#if MULTICAST
|
|
ifp->if_flags |= IFF_MULTICAST;
|
|
#endif /* MULTICAST */
|
|
if_initname(ifp, device_get_name(device), device_get_unit(device));
|
|
ifp->if_init = wlinit;
|
|
ifp->if_start = wlstart;
|
|
ifp->if_ioctl = wlioctl;
|
|
ifp->if_snd.ifq_maxlen = ifqmaxlen;
|
|
/* no entries
|
|
ifp->if_done
|
|
ifp->if_reset
|
|
*/
|
|
ether_ifattach(ifp, eaddr);
|
|
|
|
if_printf(ifp, "NWID 0x%02x%02x", sc->nwid[0], sc->nwid[1]);
|
|
if (sc->freq24)
|
|
printf(", Freq %d MHz",sc->freq24); /* 2.4 Gz */
|
|
printf("\n"); /* 2.4 Gz */
|
|
|
|
bus_setup_intr(device, sc->res_irq, INTR_TYPE_NET, NULL, wlintr, sc, &sc->intr_cookie);
|
|
|
|
if (bootverbose)
|
|
wldump(sc);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
wldetach(device_t device)
|
|
{
|
|
struct wl_softc *sc = device_get_softc(device);
|
|
struct ifnet *ifp;
|
|
|
|
ifp = sc->ifp;
|
|
ether_ifdetach(ifp);
|
|
|
|
WL_LOCK(sc);
|
|
|
|
/* reset the board */
|
|
sc->hacr = HACR_RESET;
|
|
CMD(sc);
|
|
sc->hacr = HACR_DEFAULT;
|
|
CMD(sc);
|
|
callout_stop(&sc->watchdog_timer);
|
|
WL_UNLOCK(sc);
|
|
callout_drain(&sc->watchdog_timer);
|
|
|
|
if (sc->intr_cookie != NULL) {
|
|
bus_teardown_intr(device, sc->res_irq, sc->intr_cookie);
|
|
sc->intr_cookie = NULL;
|
|
}
|
|
|
|
wl_deallocate_resources(device);
|
|
if_free(ifp);
|
|
mtx_destroy(&sc->wl_mtx);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
wl_allocate_resources(device_t device)
|
|
{
|
|
struct wl_softc *sc = device_get_softc(device);
|
|
int ports = 16; /* Number of ports */
|
|
|
|
sc->res_ioport = bus_alloc_resource(device, SYS_RES_IOPORT,
|
|
&sc->rid_ioport, 0ul, ~0ul, ports, RF_ACTIVE);
|
|
if (sc->res_ioport == NULL)
|
|
goto errexit;
|
|
|
|
sc->res_irq = bus_alloc_resource_any(device, SYS_RES_IRQ,
|
|
&sc->rid_irq, RF_SHAREABLE|RF_ACTIVE);
|
|
if (sc->res_irq == NULL)
|
|
goto errexit;
|
|
return (0);
|
|
|
|
errexit:
|
|
wl_deallocate_resources(device);
|
|
return (ENXIO);
|
|
}
|
|
|
|
static int
|
|
wl_deallocate_resources(device_t device)
|
|
{
|
|
struct wl_softc *sc = device_get_softc(device);
|
|
|
|
if (sc->res_irq != 0) {
|
|
bus_release_resource(device, SYS_RES_IRQ,
|
|
sc->rid_irq, sc->res_irq);
|
|
sc->res_irq = 0;
|
|
}
|
|
if (sc->res_ioport != 0) {
|
|
bus_release_resource(device, SYS_RES_IOPORT,
|
|
sc->rid_ioport, sc->res_ioport);
|
|
sc->res_ioport = 0;
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Print out interesting information about the 82596.
|
|
*/
|
|
static void
|
|
wldump(struct wl_softc *sc)
|
|
{
|
|
int i;
|
|
|
|
printf("hasr %04x\n", WL_READ_2(sc, HASR));
|
|
|
|
printf("scb at %04x:\n ", OFFSET_SCB);
|
|
WL_WRITE_2(sc, PIOR1, OFFSET_SCB);
|
|
for (i = 0; i < 8; i++)
|
|
printf("%04x ", WL_READ_2(sc, PIOP1));
|
|
printf("\n");
|
|
|
|
printf("cu at %04x:\n ", OFFSET_CU);
|
|
WL_WRITE_2(sc, PIOR1, OFFSET_CU);
|
|
for (i = 0; i < 8; i++)
|
|
printf("%04x ", WL_READ_2(sc, PIOP1));
|
|
printf("\n");
|
|
|
|
printf("tbd at %04x:\n ", OFFSET_TBD);
|
|
WL_WRITE_2(sc, PIOR1, OFFSET_TBD);
|
|
for (i = 0; i < 4; i++)
|
|
printf("%04x ", WL_READ_2(sc, PIOP1));
|
|
printf("\n");
|
|
}
|
|
|
|
/* Initialize the Modem Management Controller */
|
|
static void
|
|
wlinitmmc(struct wl_softc *sc)
|
|
{
|
|
int configured;
|
|
int mode = sc->mode;
|
|
int i; /* 2.4 Gz */
|
|
|
|
/* enter 8 bit operation */
|
|
sc->hacr = (HACR_DEFAULT & ~HACR_16BITS);
|
|
CMD(sc);
|
|
|
|
configured = sc->psa[WLPSA_CONFIGURED] & 1;
|
|
|
|
/*
|
|
* Set default modem control parameters. Taken from NCR document
|
|
* 407-0024326 Rev. A
|
|
*/
|
|
MMC_WRITE(MMC_JABBER_ENABLE, 0x01);
|
|
MMC_WRITE(MMC_ANTEN_SEL, 0x02);
|
|
MMC_WRITE(MMC_IFS, 0x20);
|
|
MMC_WRITE(MMC_MOD_DELAY, 0x04);
|
|
MMC_WRITE(MMC_JAM_TIME, 0x38);
|
|
MMC_WRITE(MMC_DECAY_PRM, 0x00); /* obsolete ? */
|
|
MMC_WRITE(MMC_DECAY_UPDAT_PRM, 0x00);
|
|
if (!configured) {
|
|
MMC_WRITE(MMC_LOOPT_SEL, 0x00);
|
|
if (sc->psa[WLPSA_COMPATNO] & 1) {
|
|
MMC_WRITE(MMC_THR_PRE_SET, 0x01); /* 0x04 for AT and 0x01 for MCA */
|
|
} else {
|
|
MMC_WRITE(MMC_THR_PRE_SET, 0x04); /* 0x04 for AT and 0x01 for MCA */
|
|
}
|
|
MMC_WRITE(MMC_QUALITY_THR, 0x03);
|
|
} else {
|
|
/* use configuration defaults from parameter storage area */
|
|
if (sc->psa[WLPSA_NWIDENABLE] & 1) {
|
|
if ((mode & (MOD_PROM | MOD_ENAL)) && wl_ignore_nwid) {
|
|
MMC_WRITE(MMC_LOOPT_SEL, 0x40);
|
|
} else {
|
|
MMC_WRITE(MMC_LOOPT_SEL, 0x00);
|
|
}
|
|
} else {
|
|
MMC_WRITE(MMC_LOOPT_SEL, 0x40); /* disable network id check */
|
|
}
|
|
MMC_WRITE(MMC_THR_PRE_SET, sc->psa[WLPSA_THRESH]);
|
|
MMC_WRITE(MMC_QUALITY_THR, sc->psa[WLPSA_QUALTHRESH]);
|
|
}
|
|
MMC_WRITE(MMC_FREEZE, 0x00);
|
|
MMC_WRITE(MMC_ENCR_ENABLE, 0x00);
|
|
|
|
MMC_WRITE(MMC_NETW_ID_L,sc->nwid[1]); /* set NWID */
|
|
MMC_WRITE(MMC_NETW_ID_H,sc->nwid[0]);
|
|
|
|
/* enter normal 16 bit mode operation */
|
|
sc->hacr = HACR_DEFAULT;
|
|
CMD(sc);
|
|
CMD(sc); /* virtualpc1 needs this! */
|
|
|
|
if (sc->psa[WLPSA_COMPATNO]== /* 2.4 Gz: half-card ver */
|
|
WLPSA_COMPATNO_WL24B) { /* 2.4 Gz */
|
|
i=sc->chan24<<4; /* 2.4 Gz: position ch # */
|
|
MMC_WRITE(MMC_EEADDR,i+0x0f); /* 2.4 Gz: named ch, wc=16 */
|
|
MMC_WRITE(MMC_EECTRL,MMC_EECTRL_DWLD+ /* 2.4 Gz: Download Synths */
|
|
MMC_EECTRL_EEOP_READ); /* 2.4 Gz: Read EEPROM */
|
|
for (i=0; i<1000; ++i) { /* 2.4 Gz: wait for download */
|
|
DELAY(40); /* 2.4 Gz */
|
|
if ((wlmmcread(sc, MMC_EECTRLstat) /* 2.4 Gz: check DWLD and */
|
|
&(MMC_EECTRLstat_DWLD /* 2.4 Gz: EEBUSY */
|
|
+MMC_EECTRLstat_EEBUSY))==0) /* 2.4 Gz: */
|
|
break; /* 2.4 Gz: download finished */
|
|
} /* 2.4 Gz */
|
|
if (i==1000) printf("wl: synth load failed\n"); /* 2.4 Gz */
|
|
MMC_WRITE(MMC_EEADDR,0x61); /* 2.4 Gz: default pwr, wc=2 */
|
|
MMC_WRITE(MMC_EECTRL,MMC_EECTRL_DWLD+ /* 2.4 Gz: Download Xmit Pwr */
|
|
MMC_EECTRL_EEOP_READ); /* 2.4 Gz: Read EEPROM */
|
|
for (i=0; i<1000; ++i) { /* 2.4 Gz: wait for download */
|
|
DELAY(40); /* 2.4 Gz */
|
|
if ((wlmmcread(sc, MMC_EECTRLstat) /* 2.4 Gz: check DWLD and */
|
|
&(MMC_EECTRLstat_DWLD /* 2.4 Gz: EEBUSY */
|
|
+MMC_EECTRLstat_EEBUSY))==0) /* 2.4 Gz: */
|
|
break; /* 2.4 Gz: download finished */
|
|
} /* 2.4 Gz */
|
|
if (i==1000) printf("wl: xmit pwr load failed\n"); /* 2.4 Gz */
|
|
MMC_WRITE(MMC_ANALCTRL, /* 2.4 Gz: EXT ant+polarity */
|
|
MMC_ANALCTRL_ANTPOL + /* 2.4 Gz: */
|
|
MMC_ANALCTRL_EXTANT); /* 2.4 Gz: */
|
|
i=sc->chan24<<4; /* 2.4 Gz: position ch # */
|
|
MMC_WRITE(MMC_EEADDR,i); /* 2.4 Gz: get frequency */
|
|
MMC_WRITE(MMC_EECTRL, /* 2.4 Gz: EEPROM read */
|
|
MMC_EECTRL_EEOP_READ); /* 2.4 Gz: */
|
|
DELAY(40); /* 2.4 Gz */
|
|
i = wlmmcread(sc, MMC_EEDATALrv) /* 2.4 Gz: freq val */
|
|
+ (wlmmcread(sc, MMC_EEDATAHrv)<<8); /* 2.4 Gz */
|
|
sc->freq24 = (i>>6)+2400; /* 2.4 Gz: save real freq */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* wlinit:
|
|
*
|
|
* Another routine that interfaces the "if" layer to this driver.
|
|
* Simply resets the structures that are used by "upper layers".
|
|
* As well as calling wlhwrst that does reset the WaveLAN board.
|
|
*
|
|
* input : softc pointer for this interface
|
|
* output : structures (if structs) and board are reset
|
|
*
|
|
*/
|
|
static void
|
|
wlinit(void *xsc)
|
|
{
|
|
struct wl_softc *sc = xsc;
|
|
|
|
WL_LOCK(sc);
|
|
wlinit_locked(sc);
|
|
WL_UNLOCK(sc);
|
|
}
|
|
|
|
static void
|
|
wlinit_locked(struct wl_softc *sc)
|
|
{
|
|
struct ifnet *ifp = sc->ifp;
|
|
int stat;
|
|
|
|
#ifdef WLDEBUG
|
|
if (sc->ifp->if_flags & IFF_DEBUG)
|
|
if_printf(ifp, "entered wlinit()\n");
|
|
#endif
|
|
WL_LOCK_ASSERT(sc);
|
|
if ((stat = wlhwrst(sc)) == TRUE) {
|
|
sc->ifp->if_drv_flags |= IFF_DRV_RUNNING; /* same as DSF_RUNNING */
|
|
/*
|
|
* OACTIVE is used by upper-level routines
|
|
* and must be set
|
|
*/
|
|
sc->ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; /* same as tbusy below */
|
|
|
|
sc->flags |= DSF_RUNNING;
|
|
sc->tbusy = 0;
|
|
callout_stop(&sc->watchdog_timer);
|
|
|
|
wlstart_locked(ifp);
|
|
} else {
|
|
if_printf(ifp, "init(): trouble resetting board.\n");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* wlhwrst:
|
|
*
|
|
* This routine resets the WaveLAN board that corresponds to the
|
|
* board number passed in.
|
|
*
|
|
* input : board number to do a hardware reset
|
|
* output : board is reset
|
|
*
|
|
*/
|
|
static int
|
|
wlhwrst(struct wl_softc *sc)
|
|
{
|
|
|
|
#ifdef WLDEBUG
|
|
if (sc->ifp->if_flags & IFF_DEBUG)
|
|
if_printf(sc->ifp, "entered wlhwrst()\n");
|
|
#endif
|
|
sc->hacr = HACR_RESET;
|
|
CMD(sc); /* reset the board */
|
|
|
|
/* clear reset command and set PIO#1 in autoincrement mode */
|
|
sc->hacr = HACR_DEFAULT;
|
|
CMD(sc);
|
|
|
|
#ifdef WLDEBUG
|
|
if (sc->ifp->if_flags & IFF_DEBUG)
|
|
wlmmcstat(sc); /* Display MMC registers */
|
|
#endif /* WLDEBUG */
|
|
wlbldcu(sc); /* set up command unit structures */
|
|
|
|
if (wldiag(sc) == 0)
|
|
return(0);
|
|
|
|
if (wlconfig(sc) == 0)
|
|
return(0);
|
|
/*
|
|
* insert code for loopback test here
|
|
*/
|
|
wlrustrt(sc); /* start receive unit */
|
|
|
|
/* enable interrupts */
|
|
sc->hacr = (HACR_DEFAULT | HACR_INTRON);
|
|
CMD(sc);
|
|
|
|
return(1);
|
|
}
|
|
|
|
/*
|
|
* wlbldcu:
|
|
*
|
|
* This function builds up the command unit structures. It inits
|
|
* the scp, iscp, scb, cb, tbd, and tbuf.
|
|
*
|
|
*/
|
|
static void
|
|
wlbldcu(struct wl_softc *sc)
|
|
{
|
|
scp_t scp;
|
|
iscp_t iscp;
|
|
scb_t scb;
|
|
ac_t cb;
|
|
tbd_t tbd;
|
|
int i;
|
|
|
|
bzero(&scp, sizeof(scp));
|
|
scp.scp_sysbus = 0;
|
|
scp.scp_iscp = OFFSET_ISCP;
|
|
scp.scp_iscp_base = 0;
|
|
WL_WRITE_2(sc, PIOR1, OFFSET_SCP);
|
|
WL_WRITE_MULTI_2(sc, PIOP1, &scp, sizeof(scp_t)/2);
|
|
|
|
bzero(&iscp, sizeof(iscp));
|
|
iscp.iscp_busy = 1;
|
|
iscp.iscp_scb_offset = OFFSET_SCB;
|
|
iscp.iscp_scb = 0;
|
|
iscp.iscp_scb_base = 0;
|
|
WL_WRITE_2(sc, PIOR1, OFFSET_ISCP);
|
|
WL_WRITE_MULTI_2(sc, PIOP1, &iscp, sizeof(iscp_t)/2);
|
|
|
|
scb.scb_status = 0;
|
|
scb.scb_command = SCB_RESET;
|
|
scb.scb_cbl_offset = OFFSET_CU;
|
|
scb.scb_rfa_offset = OFFSET_RU;
|
|
scb.scb_crcerrs = 0;
|
|
scb.scb_alnerrs = 0;
|
|
scb.scb_rscerrs = 0;
|
|
scb.scb_ovrnerrs = 0;
|
|
WL_WRITE_2(sc, PIOR1, OFFSET_SCB);
|
|
WL_WRITE_MULTI_2(sc, PIOP1, &scb, sizeof(scb_t)/2);
|
|
|
|
SET_CHAN_ATTN(sc);
|
|
|
|
WL_WRITE_2(sc, PIOR0, OFFSET_ISCP + 0); /* address of iscp_busy */
|
|
for (i = 1000000; WL_READ_2(sc, PIOP0) && (i-- > 0); )
|
|
continue;
|
|
if (i <= 0)
|
|
device_printf(sc->dev, "bldcu(): iscp_busy timeout.\n");
|
|
WL_WRITE_2(sc, PIOR0, OFFSET_SCB + 0); /* address of scb_status */
|
|
for (i = STATUS_TRIES; i-- > 0; ) {
|
|
if (WL_READ_2(sc, PIOP0) == (SCB_SW_CX|SCB_SW_CNA))
|
|
break;
|
|
}
|
|
if (i <= 0)
|
|
device_printf(sc->dev, "bldcu(): not ready after reset.\n");
|
|
wlack(sc);
|
|
|
|
cb.ac_status = 0;
|
|
cb.ac_command = AC_CW_EL; /* NOP */
|
|
cb.ac_link_offset = OFFSET_CU;
|
|
WL_WRITE_2(sc, PIOR1, OFFSET_CU);
|
|
WL_WRITE_MULTI_2(sc, PIOP1, &cb, 6/2);
|
|
|
|
tbd.act_count = 0;
|
|
tbd.next_tbd_offset = I82586NULL;
|
|
tbd.buffer_addr = 0;
|
|
tbd.buffer_base = 0;
|
|
WL_WRITE_2(sc, PIOR1, OFFSET_TBD);
|
|
WL_WRITE_MULTI_2(sc, PIOP1, &tbd, sizeof(tbd_t)/2);
|
|
}
|
|
|
|
/*
|
|
* wlstart:
|
|
*
|
|
* send a packet
|
|
*
|
|
* input : board number
|
|
* output : stuff sent to board if any there
|
|
*
|
|
*/
|
|
static void
|
|
wlstart(struct ifnet *ifp)
|
|
{
|
|
struct wl_softc *sc = ifp->if_softc;
|
|
|
|
WL_LOCK(sc);
|
|
wlstart_locked(ifp);
|
|
WL_UNLOCK(sc);
|
|
}
|
|
|
|
static void
|
|
wlstart_locked(struct ifnet *ifp)
|
|
{
|
|
struct mbuf *m;
|
|
struct wl_softc *sc = ifp->if_softc;
|
|
int scb_status, cu_status, scb_command;
|
|
|
|
WL_LOCK_ASSERT(sc);
|
|
#ifdef WLDEBUG
|
|
if (sc->ifp->if_flags & IFF_DEBUG)
|
|
if_printf(ifp, "entered wlstart()\n");
|
|
#endif
|
|
|
|
WL_WRITE_2(sc, PIOR1, OFFSET_CU);
|
|
cu_status = WL_READ_2(sc, PIOP1);
|
|
WL_WRITE_2(sc, PIOR0,OFFSET_SCB + 0); /* scb_status */
|
|
scb_status = WL_READ_2(sc, PIOP0);
|
|
WL_WRITE_2(sc, PIOR0, OFFSET_SCB + 2);
|
|
scb_command = WL_READ_2(sc, PIOP0);
|
|
|
|
/*
|
|
* don't need OACTIVE check as tbusy here checks to see
|
|
* if we are already busy
|
|
*/
|
|
if (sc->tbusy) {
|
|
if ((scb_status & 0x0700) == SCB_CUS_IDLE &&
|
|
(cu_status & AC_SW_B) == 0){
|
|
sc->tbusy = 0;
|
|
callout_stop(&sc->watchdog_timer);
|
|
sc->ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
|
|
/*
|
|
* This is probably just a race. The xmt'r is just
|
|
* became idle but WE have masked interrupts so ...
|
|
*/
|
|
#ifdef WLDEBUG
|
|
if_printf(ifp, "CU idle, scb %04x %04x cu %04x\n",
|
|
scb_status, scb_command, cu_status);
|
|
#endif
|
|
if (xmt_watch) printf("!!");
|
|
} else {
|
|
return; /* genuinely still busy */
|
|
}
|
|
} else if ((scb_status & 0x0700) == SCB_CUS_ACTV ||
|
|
(cu_status & AC_SW_B)){
|
|
#ifdef WLDEBUG
|
|
if_printf(ifp, "CU unexpectedly busy; scb %04x cu %04x\n",
|
|
scb_status, cu_status);
|
|
#endif
|
|
if (xmt_watch)
|
|
if_printf(ifp, "busy?!\n");
|
|
return; /* hey, why are we busy? */
|
|
}
|
|
|
|
/* get ourselves some data */
|
|
IF_DEQUEUE(&ifp->if_snd, m);
|
|
if (m != NULL) {
|
|
/* let BPF see it before we commit it */
|
|
BPF_MTAP(ifp, m);
|
|
sc->tbusy++;
|
|
/* set the watchdog timer so that if the board
|
|
* fails to interrupt we will restart
|
|
*/
|
|
/* try 10 ms, not very long */
|
|
callout_reset(&sc->watchdog_timer, hz / 100, wlwatchdog, sc);
|
|
sc->ifp->if_drv_flags |= IFF_DRV_OACTIVE;
|
|
if_inc_counter(sc->ifp, IFCOUNTER_OPACKETS, 1);
|
|
wlxmt(sc, m);
|
|
} else {
|
|
sc->ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* wlread:
|
|
*
|
|
* This routine does the actual copy of data (including ethernet header
|
|
* structure) from the WaveLAN to an mbuf chain that will be passed up
|
|
* to the "if" (network interface) layer. NOTE: we currently
|
|
* don't handle trailer protocols, so if that is needed, it will
|
|
* (at least in part) be added here. For simplicities sake, this
|
|
* routine copies the receive buffers from the board into a local (stack)
|
|
* buffer until the frame has been copied from the board. Once in
|
|
* the local buffer, the contents are copied to an mbuf chain that
|
|
* is then enqueued onto the appropriate "if" queue.
|
|
*
|
|
* input : board number, and a frame descriptor address
|
|
* output : the packet is put into an mbuf chain, and passed up
|
|
* assumes : if any errors occur, packet is "dropped on the floor"
|
|
*
|
|
*/
|
|
static int
|
|
wlread(struct wl_softc *sc, u_short fd_p)
|
|
{
|
|
struct ifnet *ifp = sc->ifp;
|
|
fd_t fd;
|
|
struct ether_header *eh;
|
|
struct mbuf *m;
|
|
rbd_t rbd;
|
|
u_char *mb_p;
|
|
u_short mlen, len;
|
|
u_short bytes_in_msg, bytes_in_mbuf, bytes;
|
|
|
|
WL_LOCK_ASSERT(sc);
|
|
|
|
#ifdef WLDEBUG
|
|
if (sc->ifp->if_flags & IFF_DEBUG)
|
|
if_printf(ifp, "entered wlread()\n");
|
|
#endif
|
|
if (!((ifp->if_flags & IFF_UP) && (ifp->if_drv_flags & IFF_DRV_RUNNING))) {
|
|
if_printf(ifp, "read(): board is not running.\n");
|
|
sc->hacr &= ~HACR_INTRON;
|
|
CMD(sc); /* turn off interrupts */
|
|
}
|
|
|
|
/*
|
|
* Collect message size.
|
|
*/
|
|
WL_WRITE_2(sc, PIOR1, fd_p);
|
|
WL_READ_MULTI_2(sc, PIOP1, &fd, sizeof(fd_t)/2);
|
|
if (fd.rbd_offset == I82586NULL) {
|
|
if (wlhwrst(sc) != TRUE) {
|
|
sc->hacr &= ~HACR_INTRON;
|
|
CMD(sc); /* turn off interrupts */
|
|
if_printf(ifp, "read(): hwrst trouble.\n");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
WL_WRITE_2(sc, PIOR1, fd.rbd_offset);
|
|
WL_READ_MULTI_2(sc, PIOP1, &rbd, sizeof(rbd_t)/2);
|
|
bytes_in_msg = rbd.status & RBD_SW_COUNT;
|
|
|
|
/*
|
|
* Allocate a cluster'd mbuf to receive the packet.
|
|
*/
|
|
m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
|
|
if (m == NULL) {
|
|
if (wlhwrst(sc) != TRUE) {
|
|
sc->hacr &= ~HACR_INTRON;
|
|
CMD(sc); /* turn off interrupts */
|
|
if_printf(ifp, "read(): hwrst trouble.\n");
|
|
}
|
|
return 0;
|
|
}
|
|
m->m_pkthdr.len = m->m_len = MCLBYTES;
|
|
m_adj(m, ETHER_ALIGN); /* align IP header */
|
|
|
|
/*
|
|
* Collect the message data.
|
|
*/
|
|
mlen = 0;
|
|
mb_p = mtod(m, u_char *);
|
|
bytes_in_mbuf = m->m_len;
|
|
|
|
/* Put the ethernet header inside the mbuf. */
|
|
bcopy(&fd.destination[0], mb_p, 14);
|
|
mb_p += 14;
|
|
mlen += 14;
|
|
bytes_in_mbuf -= 14;
|
|
|
|
bytes = min(bytes_in_mbuf, bytes_in_msg);
|
|
for (;;) {
|
|
if (bytes & 1) {
|
|
len = bytes + 1;
|
|
} else {
|
|
len = bytes;
|
|
}
|
|
WL_WRITE_2(sc, PIOR1, rbd.buffer_addr);
|
|
WL_READ_MULTI_2(sc, PIOP1, mb_p, len/2);
|
|
mlen += bytes;
|
|
|
|
if (bytes > bytes_in_mbuf) {
|
|
/* XXX something wrong, a packet should fit in 1 cluster */
|
|
m_freem(m);
|
|
if_printf(ifp, "read(): packet too large (%u > %u)\n",
|
|
bytes, bytes_in_mbuf);
|
|
if (wlhwrst(sc) != TRUE) {
|
|
sc->hacr &= ~HACR_INTRON;
|
|
CMD(sc); /* turn off interrupts */
|
|
if_printf(ifp, "read(): hwrst trouble.\n");
|
|
}
|
|
return 0;
|
|
}
|
|
mb_p += bytes;
|
|
bytes_in_mbuf -= bytes;
|
|
bytes_in_msg -= bytes;
|
|
if (bytes_in_msg == 0) {
|
|
if (rbd.status & RBD_SW_EOF || rbd.next_rbd_offset == I82586NULL) {
|
|
break;
|
|
}
|
|
WL_WRITE_2(sc, PIOR1, rbd.next_rbd_offset);
|
|
WL_READ_MULTI_2(sc, PIOP1, &rbd, sizeof(rbd_t)/2);
|
|
bytes_in_msg = rbd.status & RBD_SW_COUNT;
|
|
} else {
|
|
rbd.buffer_addr += bytes;
|
|
}
|
|
|
|
bytes = min(bytes_in_mbuf, bytes_in_msg);
|
|
}
|
|
|
|
m->m_pkthdr.len = m->m_len = mlen;
|
|
m->m_pkthdr.rcvif = ifp;
|
|
|
|
/*
|
|
* If hw is in promiscuous mode (note that I said hardware, not if
|
|
* IFF_PROMISC is set in ifnet flags), then if this is a unicast
|
|
* packet and the MAC dst is not us, drop it. This check in normally
|
|
* inside ether_input(), but IFF_MULTI causes hw promisc without
|
|
* a bpf listener, so this is wrong.
|
|
* Greg Troxel <gdt@ir.bbn.com>, 1998-08-07
|
|
*/
|
|
/*
|
|
* TBD: also discard packets where NWID does not match.
|
|
* However, there does not appear to be a way to read the nwid
|
|
* for a received packet. -gdt 1998-08-07
|
|
*/
|
|
/* XXX verify mbuf length */
|
|
eh = mtod(m, struct ether_header *);
|
|
if (
|
|
#ifdef WL_USE_IFNET_PROMISC_CHECK /* not defined */
|
|
(sc->ifp->if_flags & (IFF_PROMISC|IFF_ALLMULTI))
|
|
#else
|
|
/* hw is in promisc mode if this is true */
|
|
(sc->mode & (MOD_PROM | MOD_ENAL))
|
|
#endif
|
|
&&
|
|
(eh->ether_dhost[0] & 1) == 0 && /* !mcast and !bcast */
|
|
bcmp(eh->ether_dhost, IF_LLADDR(sc->ifp),
|
|
sizeof(eh->ether_dhost)) != 0 ) {
|
|
m_freem(m);
|
|
return 1;
|
|
}
|
|
|
|
#ifdef WLDEBUG
|
|
if (sc->ifp->if_flags & IFF_DEBUG)
|
|
if_printf(ifp, "wlrecv %u bytes\n", mlen);
|
|
#endif
|
|
|
|
#ifdef WLCACHE
|
|
wl_cache_store(sc, eh, m);
|
|
#endif
|
|
|
|
/*
|
|
* received packet is now in a chain of mbuf's. next step is
|
|
* to pass the packet upwards.
|
|
*/
|
|
WL_UNLOCK(sc);
|
|
(*ifp->if_input)(ifp, m);
|
|
WL_LOCK(sc);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* wlioctl:
|
|
*
|
|
* This routine processes an ioctl request from the "if" layer
|
|
* above.
|
|
*
|
|
* input : pointer the appropriate "if" struct, command, and data
|
|
* output : based on command appropriate action is taken on the
|
|
* WaveLAN board(s) or related structures
|
|
* return : error is returned containing exit conditions
|
|
*
|
|
*/
|
|
static int
|
|
wlioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
|
|
{
|
|
struct ifreq *ifr = (struct ifreq *)data;
|
|
struct wl_softc *sc = ifp->if_softc;
|
|
short mode = 0;
|
|
int error = 0;
|
|
struct thread *td = curthread; /* XXX */
|
|
int irq, irqval, i, isroot;
|
|
char psa_buf[0x40];
|
|
char eeprom_buf[0x80];
|
|
#ifdef WLCACHE
|
|
size_t size;
|
|
char * cpt;
|
|
#endif
|
|
|
|
#ifdef WLDEBUG
|
|
if (sc->ifp->if_flags & IFF_DEBUG)
|
|
if_printf(ifp, "entered wlioctl()\n");
|
|
#endif
|
|
switch (cmd) {
|
|
case SIOCSIFFLAGS:
|
|
WL_LOCK(sc);
|
|
if (ifp->if_flags & IFF_ALLMULTI) {
|
|
mode |= MOD_ENAL;
|
|
}
|
|
if (ifp->if_flags & IFF_PROMISC) {
|
|
mode |= MOD_PROM;
|
|
}
|
|
if (ifp->if_flags & IFF_LINK0) {
|
|
mode |= MOD_PROM;
|
|
}
|
|
/*
|
|
* force a complete reset if the recieve multicast/
|
|
* promiscuous mode changes so that these take
|
|
* effect immediately.
|
|
*
|
|
*/
|
|
if (sc->mode != mode) {
|
|
sc->mode = mode;
|
|
if (sc->flags & DSF_RUNNING) {
|
|
sc->flags &= ~DSF_RUNNING;
|
|
wlinit_locked(sc);
|
|
}
|
|
}
|
|
/* if interface is marked DOWN and still running then
|
|
* stop it.
|
|
*/
|
|
if ((ifp->if_flags & IFF_UP) == 0 && sc->flags & DSF_RUNNING) {
|
|
if_printf(ifp, "ioctl(): board is not running\n");
|
|
sc->flags &= ~DSF_RUNNING;
|
|
sc->hacr &= ~HACR_INTRON;
|
|
CMD(sc); /* turn off interrupts */
|
|
}
|
|
/* else if interface is UP and RUNNING, start it
|
|
*/
|
|
else if (ifp->if_flags & IFF_UP && (sc->flags & DSF_RUNNING) == 0) {
|
|
wlinit_locked(sc);
|
|
}
|
|
|
|
/* if WLDEBUG set on interface, then printf rf-modem regs
|
|
*/
|
|
if (ifp->if_flags & IFF_DEBUG)
|
|
wlmmcstat(sc);
|
|
WL_UNLOCK(sc);
|
|
break;
|
|
#if MULTICAST
|
|
case SIOCADDMULTI:
|
|
case SIOCDELMULTI:
|
|
|
|
wlinit(sc);
|
|
break;
|
|
#endif /* MULTICAST */
|
|
|
|
/* DEVICE SPECIFIC */
|
|
|
|
|
|
/* copy the PSA out to the caller */
|
|
case SIOCGWLPSA:
|
|
/* work out if they're root */
|
|
isroot = (priv_check(td, PRIV_NET80211_GETKEY) == 0);
|
|
|
|
bzero(psa_buf, sizeof(psa_buf));
|
|
WL_LOCK(sc);
|
|
for (i = 0; i < 0x40; i++) {
|
|
/* don't hand the DES key out to non-root users */
|
|
if ((i > WLPSA_DESKEY) && (i < (WLPSA_DESKEY + 8)) && !isroot)
|
|
continue;
|
|
psa_buf[i] = sc->psa[i];
|
|
}
|
|
WL_UNLOCK(sc);
|
|
|
|
error = copyout(psa_buf, ifr->ifr_data, sizeof(psa_buf));
|
|
break;
|
|
|
|
|
|
/* copy the PSA in from the caller; we only copy _some_ values */
|
|
case SIOCSWLPSA:
|
|
/* root only */
|
|
if ((error = priv_check(td, PRIV_DRIVER)))
|
|
break;
|
|
|
|
error = copyin(ifr->ifr_data, psa_buf, sizeof(psa_buf));
|
|
if (error)
|
|
break;
|
|
|
|
/* check IRQ value */
|
|
irqval = psa_buf[WLPSA_IRQNO];
|
|
for (irq = 15; irq >= 0; irq--)
|
|
if (irqvals[irq] == irqval)
|
|
break;
|
|
if (irq == 0) /* oops */
|
|
break;
|
|
WL_LOCK(sc);
|
|
/* new IRQ */
|
|
sc->psa[WLPSA_IRQNO] = irqval;
|
|
|
|
/* local MAC */
|
|
for (i = 0; i < 6; i++)
|
|
sc->psa[WLPSA_LOCALMAC + i] = psa_buf[WLPSA_LOCALMAC + i];
|
|
|
|
/* MAC select */
|
|
sc->psa[WLPSA_MACSEL] = psa_buf[WLPSA_MACSEL];
|
|
|
|
/* default nwid */
|
|
sc->psa[WLPSA_NWID] = psa_buf[WLPSA_NWID];
|
|
sc->psa[WLPSA_NWID + 1] = psa_buf[WLPSA_NWID + 1];
|
|
|
|
wlsetpsa(sc); /* update the PSA */
|
|
WL_UNLOCK(sc);
|
|
break;
|
|
|
|
|
|
/* get the current NWID out of the sc since we stored it there */
|
|
case SIOCGWLCNWID:
|
|
WL_LOCK(sc);
|
|
ifr->ifr_data = (caddr_t) (sc->nwid[0] << 8 | sc->nwid[1]);
|
|
WL_UNLOCK(sc);
|
|
break;
|
|
|
|
|
|
/*
|
|
* change the nwid dynamically. This
|
|
* ONLY changes the radio modem and does not
|
|
* change the PSA.
|
|
*
|
|
* 2 steps:
|
|
* 1. save in softc "soft registers"
|
|
* 2. save in radio modem (MMC)
|
|
*/
|
|
case SIOCSWLCNWID:
|
|
/* root only */
|
|
if ((error = priv_check(td, PRIV_DRIVER)))
|
|
break;
|
|
WL_LOCK(sc);
|
|
if (!(ifp->if_flags & IFF_UP)) {
|
|
error = EIO; /* only allowed while up */
|
|
} else {
|
|
/*
|
|
* soft c nwid shadows radio modem setting
|
|
*/
|
|
sc->nwid[0] = (int)ifr->ifr_data >> 8;
|
|
sc->nwid[1] = (int)ifr->ifr_data & 0xff;
|
|
MMC_WRITE(MMC_NETW_ID_L,sc->nwid[1]);
|
|
MMC_WRITE(MMC_NETW_ID_H,sc->nwid[0]);
|
|
}
|
|
WL_UNLOCK(sc);
|
|
break;
|
|
|
|
/* copy the EEPROM in 2.4 Gz WaveMODEM out to the caller */
|
|
case SIOCGWLEEPROM:
|
|
/* root only */
|
|
if ((error = priv_check(td, PRIV_DRIVER)))
|
|
break;
|
|
|
|
bzero(eeprom_buf, sizeof(eeprom_buf));
|
|
WL_LOCK(sc);
|
|
for (i=0x00; i<0x80; ++i) { /* 2.4 Gz: size of EEPROM */
|
|
MMC_WRITE(MMC_EEADDR,i); /* 2.4 Gz: get frequency */
|
|
MMC_WRITE(MMC_EECTRL, /* 2.4 Gz: EEPROM read */
|
|
MMC_EECTRL_EEOP_READ); /* 2.4 Gz: */
|
|
DELAY(40); /* 2.4 Gz */
|
|
eeprom_buf[2 * i] = /* 2.4 Gz: pass low byte of */
|
|
wlmmcread(sc, MMC_EEDATALrv); /* 2.4 Gz: EEPROM word */
|
|
eeprom_buf[2 * i + 1] = /* 2.4 Gz: pass hi byte of */
|
|
wlmmcread(sc, MMC_EEDATALrv); /* 2.4 Gz: EEPROM word */
|
|
}
|
|
WL_UNLOCK(sc);
|
|
error = copyout(ifr->ifr_data, eeprom_buf, sizeof(eeprom_buf));
|
|
break;
|
|
|
|
#ifdef WLCACHE
|
|
/* zero (Delete) the wl cache */
|
|
case SIOCDWLCACHE:
|
|
/* root only */
|
|
if ((error = priv_check(td, PRIV_DRIVER)))
|
|
break;
|
|
WL_LOCK(sc);
|
|
wl_cache_zero(sc);
|
|
WL_UNLOCK(sc);
|
|
break;
|
|
|
|
/* read out the number of used cache elements */
|
|
case SIOCGWLCITEM:
|
|
WL_LOCK(sc);
|
|
ifr->ifr_data = (caddr_t) sc->w_sigitems;
|
|
WL_UNLOCK(sc);
|
|
break;
|
|
|
|
/* read out the wl cache */
|
|
case SIOCGWLCACHE:
|
|
WL_LOCK(sc);
|
|
size = sc->w_sigitems * sizeof(struct w_sigcache);
|
|
cpt = malloc(size, M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
if (cpt == NULL) {
|
|
WL_UNLOCK(sc);
|
|
return (ENOMEM);
|
|
}
|
|
|
|
bcopy(sc->w_sigcache, cpt, size);
|
|
WL_UNLOCK(sc);
|
|
|
|
error = copyout(cpt, ifr->ifr_data, size);
|
|
free(cpt, M_DEVBUF);
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
error = ether_ioctl(ifp, cmd, data);
|
|
break;
|
|
}
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* wlwatchdog():
|
|
*
|
|
* Called if the timer set in wlstart expires before an interrupt is received
|
|
* from the wavelan. It seems to lose interrupts sometimes.
|
|
* The watchdog routine gets called if the transmitter failed to interrupt
|
|
*
|
|
* input : which board is timing out
|
|
* output : board reset
|
|
*
|
|
*/
|
|
static void
|
|
wlwatchdog(void *vsc)
|
|
{
|
|
struct wl_softc *sc = vsc;
|
|
|
|
log(LOG_ERR, "%s: wavelan device timeout on xmit\n", sc->ifp->if_xname);
|
|
if_inc_counter(sc->ifp, IFCOUNTER_OERRORS, 1);
|
|
wlinit_locked(sc);
|
|
}
|
|
|
|
/*
|
|
* wlintr:
|
|
*
|
|
* This function is the interrupt handler for the WaveLAN
|
|
* board. This routine will be called whenever either a packet
|
|
* is received, or a packet has successfully been transfered and
|
|
* the unit is ready to transmit another packet.
|
|
*
|
|
* input : board number that interrupted
|
|
* output : either a packet is received, or a packet is transfered
|
|
*
|
|
*/
|
|
static void
|
|
wlintr(void *arg)
|
|
{
|
|
struct wl_softc *sc = (struct wl_softc *)arg;
|
|
int ac_status;
|
|
u_short int_type, int_type1;
|
|
|
|
WL_LOCK(sc);
|
|
#ifdef WLDEBUG
|
|
if (sc->ifp->if_flags & IFF_DEBUG)
|
|
if_printf(sc->ifp, "wlintr() called\n");
|
|
#endif
|
|
|
|
if ((int_type = WL_READ_2(sc, HASR)) & HASR_MMC_INTR) {
|
|
/* handle interrupt from the modem management controler */
|
|
/* This will clear the interrupt condition */
|
|
(void) wlmmcread(sc, MMC_DCE_STATUS); /* ignored for now */
|
|
}
|
|
|
|
if (!(int_type & HASR_INTR)){ /* return if no interrupt from 82586 */
|
|
/* commented out. jrb. it happens when reinit occurs
|
|
printf("wlintr: int_type %x, dump follows\n", int_type);
|
|
wldump(sc);
|
|
*/
|
|
WL_UNLOCK(sc);
|
|
return;
|
|
}
|
|
|
|
if (gathersnr)
|
|
getsnr(sc);
|
|
for (;;) {
|
|
WL_WRITE_2(sc, PIOR0, OFFSET_SCB + 0); /* get scb status */
|
|
int_type = (WL_READ_2(sc, PIOP0) & SCB_SW_INT);
|
|
if (int_type == 0) /* no interrupts left */
|
|
break;
|
|
|
|
int_type1 = wlack(sc); /* acknowledge interrupt(s) */
|
|
/* make sure no bits disappeared (others may appear) */
|
|
if ((int_type & int_type1) != int_type)
|
|
printf("wlack() int bits disappeared : %04x != int_type %04x\n",
|
|
int_type1, int_type);
|
|
int_type = int_type1; /* go with the new status */
|
|
/*
|
|
* incoming packet
|
|
*/
|
|
if (int_type & SCB_SW_FR) {
|
|
if_inc_counter(sc->ifp, IFCOUNTER_IPACKETS, 1);
|
|
wlrcv(sc);
|
|
}
|
|
/*
|
|
* receiver not ready
|
|
*/
|
|
if (int_type & SCB_SW_RNR) {
|
|
if_inc_counter(sc->ifp, IFCOUNTER_IERRORS, 1);
|
|
#ifdef WLDEBUG
|
|
if (sc->ifp->if_flags & IFF_DEBUG)
|
|
if_printf(sc->ifp, "intr(): receiver overrun! begin_fd = %x\n",
|
|
sc->begin_fd);
|
|
#endif
|
|
wlrustrt(sc);
|
|
}
|
|
/*
|
|
* CU not ready
|
|
*/
|
|
if (int_type & SCB_SW_CNA) {
|
|
/*
|
|
* At present, we don't care about CNA's. We
|
|
* believe they are a side effect of XMT.
|
|
*/
|
|
}
|
|
if (int_type & SCB_SW_CX) {
|
|
/*
|
|
* At present, we only request Interrupt for
|
|
* XMT.
|
|
*/
|
|
WL_WRITE_2(sc, PIOR1, OFFSET_CU); /* get command status */
|
|
ac_status = WL_READ_2(sc, PIOP1);
|
|
|
|
if (xmt_watch) { /* report some anomalies */
|
|
|
|
if (sc->tbusy == 0) {
|
|
if_printf(sc->ifp, "xmt intr but not busy, CU %04x\n",
|
|
ac_status);
|
|
}
|
|
if (ac_status == 0) {
|
|
if_printf(sc->ifp, "xmt intr but ac_status == 0\n");
|
|
}
|
|
if (ac_status & AC_SW_A) {
|
|
if_printf(sc->ifp, "xmt aborted\n");
|
|
}
|
|
#ifdef notdef
|
|
if (ac_status & TC_CARRIER) {
|
|
if_printf(sc->ifp, "no carrier\n");
|
|
}
|
|
#endif /* notdef */
|
|
if (ac_status & TC_CLS) {
|
|
if_printf(sc->ifp, "no CTS\n");
|
|
}
|
|
if (ac_status & TC_DMA) {
|
|
if_printf(sc->ifp, "DMA underrun\n");
|
|
}
|
|
if (ac_status & TC_DEFER) {
|
|
if_printf(sc->ifp, "xmt deferred\n");
|
|
}
|
|
if (ac_status & TC_SQE) {
|
|
if_printf(sc->ifp, "heart beat\n");
|
|
}
|
|
if (ac_status & TC_COLLISION) {
|
|
if_printf(sc->ifp, "too many collisions\n");
|
|
}
|
|
}
|
|
/* if the transmit actually failed, or returned some status */
|
|
if ((!(ac_status & AC_SW_OK)) || (ac_status & 0xfff)) {
|
|
if (ac_status & (TC_COLLISION | TC_CLS | TC_DMA)) {
|
|
if_inc_counter(sc->ifp, IFCOUNTER_OERRORS, 1);
|
|
}
|
|
/* count collisions */
|
|
if_inc_counter(sc->ifp, IFCOUNTER_COLLISIONS, (ac_status & 0xf));
|
|
/* if TC_COLLISION set and collision count zero, 16 collisions */
|
|
if ((ac_status & 0x20) == 0x20) {
|
|
if_inc_counter(sc->ifp, IFCOUNTER_COLLISIONS, 0x10);
|
|
}
|
|
}
|
|
sc->tbusy = 0;
|
|
callout_stop(&sc->watchdog_timer);
|
|
sc->ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
|
|
wlstart_locked(sc->ifp);
|
|
}
|
|
}
|
|
WL_UNLOCK(sc);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* wlrcv:
|
|
*
|
|
* This routine is called by the interrupt handler to initiate a
|
|
* packet transfer from the board to the "if" layer above this
|
|
* driver. This routine checks if a buffer has been successfully
|
|
* received by the WaveLAN. If so, the routine wlread is called
|
|
* to do the actual transfer of the board data (including the
|
|
* ethernet header) into a packet (consisting of an mbuf chain).
|
|
*
|
|
* input : number of the board to check
|
|
* output : if a packet is available, it is "sent up"
|
|
*
|
|
*/
|
|
static void
|
|
wlrcv(struct wl_softc *sc)
|
|
{
|
|
u_short fd_p, status, offset, link_offset;
|
|
|
|
#ifdef WLDEBUG
|
|
if (sc->ifp->if_flags & IFF_DEBUG)
|
|
if_printf(sc->ifp, "entered wlrcv()\n");
|
|
#endif
|
|
for (fd_p = sc->begin_fd; fd_p != I82586NULL; fd_p = sc->begin_fd) {
|
|
|
|
WL_WRITE_2(sc, PIOR0, fd_p + 0); /* address of status */
|
|
status = WL_READ_2(sc, PIOP0);
|
|
WL_WRITE_2(sc, PIOR1, fd_p + 4); /* address of link_offset */
|
|
link_offset = WL_READ_2(sc, PIOP1);
|
|
offset = WL_READ_2(sc, PIOP1); /* rbd_offset */
|
|
if (status == 0xffff || offset == 0xffff /*I82586NULL*/) {
|
|
if (wlhwrst(sc) != TRUE)
|
|
if_printf(sc->ifp, "rcv(): hwrst ffff trouble.\n");
|
|
return;
|
|
} else if (status & AC_SW_C) {
|
|
if (status == (RFD_DONE|RFD_RSC)) {
|
|
/* lost one */
|
|
#ifdef WLDEBUG
|
|
if (sc->ifp->if_flags & IFF_DEBUG)
|
|
if_printf(sc->ifp, "RCV: RSC %x\n", status);
|
|
#endif
|
|
if_inc_counter(sc->ifp, IFCOUNTER_IERRORS, 1);
|
|
} else if (!(status & RFD_OK)) {
|
|
if_printf(sc->ifp, "RCV: !OK %x\n", status);
|
|
if_inc_counter(sc->ifp, IFCOUNTER_IERRORS, 1);
|
|
} else if (status & 0xfff) { /* can't happen */
|
|
if_printf(sc->ifp, "RCV: ERRs %x\n", status);
|
|
if_inc_counter(sc->ifp, IFCOUNTER_IERRORS, 1);
|
|
} else if (!wlread(sc, fd_p))
|
|
return;
|
|
|
|
if (!wlrequeue(sc, fd_p)) {
|
|
/* abort on chain error */
|
|
if (wlhwrst(sc) != TRUE)
|
|
if_printf(sc->ifp, "rcv(): hwrst trouble.\n");
|
|
return;
|
|
}
|
|
sc->begin_fd = link_offset;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* wlrequeue:
|
|
*
|
|
* This routine puts rbd's used in the last receive back onto the
|
|
* free list for the next receive.
|
|
*
|
|
*/
|
|
static int
|
|
wlrequeue(struct wl_softc *sc, u_short fd_p)
|
|
{
|
|
fd_t fd;
|
|
u_short l_rbdp, f_rbdp, rbd_offset;
|
|
|
|
WL_WRITE_2(sc, PIOR0, fd_p + 6);
|
|
rbd_offset = WL_READ_2(sc, PIOP0);
|
|
if ((f_rbdp = rbd_offset) != I82586NULL) {
|
|
l_rbdp = f_rbdp;
|
|
for (;;) {
|
|
WL_WRITE_2(sc, PIOR0, l_rbdp + 0); /* address of status */
|
|
if (WL_READ_2(sc, PIOP0) & RBD_SW_EOF)
|
|
break;
|
|
WL_WRITE_2(sc, PIOP0, 0);
|
|
WL_WRITE_2(sc, PIOR0, l_rbdp + 2); /* next_rbd_offset */
|
|
if ((l_rbdp = WL_READ_2(sc, PIOP0)) == I82586NULL)
|
|
break;
|
|
}
|
|
WL_WRITE_2(sc, PIOP0, 0);
|
|
WL_WRITE_2(sc, PIOR0, l_rbdp + 2); /* next_rbd_offset */
|
|
WL_WRITE_2(sc, PIOP0, I82586NULL);
|
|
WL_WRITE_2(sc, PIOR0, l_rbdp + 8); /* address of size */
|
|
WL_WRITE_2(sc, PIOP0, WL_READ_2(sc, PIOP0) | AC_CW_EL);
|
|
WL_WRITE_2(sc, PIOR0, sc->end_rbd + 2);
|
|
WL_WRITE_2(sc, PIOP0, f_rbdp); /* end_rbd->next_rbd_offset */
|
|
WL_WRITE_2(sc, PIOR0, sc->end_rbd + 8); /* size */
|
|
WL_WRITE_2(sc, PIOP0, WL_READ_2(sc, PIOP0) & ~AC_CW_EL);
|
|
sc->end_rbd = l_rbdp;
|
|
}
|
|
|
|
fd.status = 0;
|
|
fd.command = AC_CW_EL;
|
|
fd.link_offset = I82586NULL;
|
|
fd.rbd_offset = I82586NULL;
|
|
WL_WRITE_2(sc, PIOR1, fd_p);
|
|
WL_WRITE_MULTI_2(sc, PIOP1, &fd, 8/2);
|
|
|
|
WL_WRITE_2(sc, PIOR1, sc->end_fd + 2); /* addr of command */
|
|
WL_WRITE_2(sc, PIOP1, 0); /* command = 0 */
|
|
WL_WRITE_2(sc, PIOP1, fd_p); /* end_fd->link_offset = fd_p */
|
|
sc->end_fd = fd_p;
|
|
|
|
return 1;
|
|
}
|
|
|
|
#ifdef WLDEBUG
|
|
static int xmt_debug = 0;
|
|
#endif /* WLDEBUG */
|
|
|
|
/*
|
|
* wlxmt:
|
|
*
|
|
* This routine fills in the appropriate registers and memory
|
|
* locations on the WaveLAN board and starts the board off on
|
|
* the transmit.
|
|
*
|
|
* input : pointers to board of interest's softc and the mbuf
|
|
* output : board memory and registers are set for xfer and attention
|
|
*
|
|
*/
|
|
static void
|
|
wlxmt(struct wl_softc *sc, struct mbuf *m)
|
|
{
|
|
u_short xmtdata_p = OFFSET_TBUF;
|
|
u_short xmtshort_p;
|
|
struct mbuf *tm_p = m;
|
|
struct ether_header *eh_p = mtod(m, struct ether_header *);
|
|
u_char *mb_p = mtod(m, u_char *) + sizeof(struct ether_header);
|
|
u_short count = m->m_len - sizeof(struct ether_header);
|
|
ac_t cb;
|
|
u_short tbd_p = OFFSET_TBD;
|
|
u_short len, clen = 0;
|
|
int spin;
|
|
|
|
#ifdef WLDEBUG
|
|
if (sc->ifp->if_flags & IFF_DEBUG)
|
|
if_printf(sc->ifp, "entered wlxmt()\n");
|
|
#endif
|
|
|
|
cb.ac_status = 0;
|
|
cb.ac_command = (AC_CW_EL|AC_TRANSMIT|AC_CW_I);
|
|
cb.ac_link_offset = I82586NULL;
|
|
WL_WRITE_2(sc, PIOR1, OFFSET_CU);
|
|
WL_WRITE_MULTI_2(sc, PIOP1, &cb, 6/2);
|
|
WL_WRITE_2(sc, PIOP1, OFFSET_TBD); /* cb.cmd.transmit.tbd_offset */
|
|
WL_WRITE_MULTI_2(sc, PIOP1, eh_p->ether_dhost, WAVELAN_ADDR_SIZE/2);
|
|
WL_WRITE_2(sc, PIOP1, eh_p->ether_type);
|
|
|
|
#ifdef WLDEBUG
|
|
if (sc->ifp->if_flags & IFF_DEBUG) {
|
|
if (xmt_debug) {
|
|
printf("XMT mbuf: L%d @%p ", count, (void *)mb_p);
|
|
printf("ether type %x\n", eh_p->ether_type);
|
|
}
|
|
}
|
|
#endif /* WLDEBUG */
|
|
WL_WRITE_2(sc, PIOR0, OFFSET_TBD);
|
|
WL_WRITE_2(sc, PIOP0, 0); /* act_count */
|
|
WL_WRITE_2(sc, PIOR1, OFFSET_TBD + 4);
|
|
WL_WRITE_2(sc, PIOP1, xmtdata_p); /* buffer_addr */
|
|
WL_WRITE_2(sc, PIOP1, 0); /* buffer_base */
|
|
for (;;) {
|
|
if (count) {
|
|
if (clen + count > WAVELAN_MTU)
|
|
break;
|
|
if (count & 1)
|
|
len = count + 1;
|
|
else
|
|
len = count;
|
|
WL_WRITE_2(sc, PIOR1, xmtdata_p);
|
|
WL_WRITE_MULTI_2(sc, PIOP1, mb_p, len/2);
|
|
clen += count;
|
|
WL_WRITE_2(sc, PIOR0, tbd_p); /* address of act_count */
|
|
WL_WRITE_2(sc, PIOP0, WL_READ_2(sc, PIOP0) + count);
|
|
xmtdata_p += len;
|
|
if ((tm_p = tm_p->m_next) == (struct mbuf *)0)
|
|
break;
|
|
if (count & 1) {
|
|
/* go to the next descriptor */
|
|
WL_WRITE_2(sc, PIOR0, tbd_p + 2);
|
|
tbd_p += sizeof (tbd_t);
|
|
WL_WRITE_2(sc, PIOP0, tbd_p); /* next_tbd_offset */
|
|
WL_WRITE_2(sc, PIOR0, tbd_p);
|
|
WL_WRITE_2(sc, PIOP0, 0); /* act_count */
|
|
WL_WRITE_2(sc, PIOR1, tbd_p + 4);
|
|
WL_WRITE_2(sc, PIOP1, xmtdata_p); /* buffer_addr */
|
|
WL_WRITE_2(sc, PIOP1, 0); /* buffer_base */
|
|
/* at the end -> coallesce remaining mbufs */
|
|
if (tbd_p == OFFSET_TBD + (N_TBD-1) * sizeof (tbd_t)) {
|
|
wlsftwsleaze(&count, &mb_p, &tm_p, sc);
|
|
continue;
|
|
}
|
|
/* next mbuf short -> coallesce as needed */
|
|
if ( (tm_p->m_next == (struct mbuf *) 0) ||
|
|
#define HDW_THRESHOLD 55
|
|
tm_p->m_len > HDW_THRESHOLD)
|
|
/* ok */;
|
|
else {
|
|
wlhdwsleaze(&count, &mb_p, &tm_p, sc);
|
|
continue;
|
|
}
|
|
}
|
|
} else if ((tm_p = tm_p->m_next) == (struct mbuf *)0)
|
|
break;
|
|
count = tm_p->m_len;
|
|
mb_p = mtod(tm_p, u_char *);
|
|
#ifdef WLDEBUG
|
|
if (sc->ifp->if_flags & IFF_DEBUG)
|
|
if (xmt_debug)
|
|
printf("mbuf+ L%d @%p ", count, (void *)mb_p);
|
|
#endif /* WLDEBUG */
|
|
}
|
|
#ifdef WLDEBUG
|
|
if (sc->ifp->if_flags & IFF_DEBUG)
|
|
if (xmt_debug)
|
|
printf("CLEN = %d\n", clen);
|
|
#endif /* WLDEBUG */
|
|
WL_WRITE_2(sc, PIOR0, tbd_p);
|
|
if (clen < ETHERMIN) {
|
|
WL_WRITE_2(sc, PIOP0, WL_READ_2(sc, PIOP0) + ETHERMIN - clen);
|
|
WL_WRITE_2(sc, PIOR1, xmtdata_p);
|
|
for (xmtshort_p = xmtdata_p; clen < ETHERMIN; clen += 2)
|
|
WL_WRITE_2(sc, PIOP1, 0);
|
|
}
|
|
WL_WRITE_2(sc, PIOP0, WL_READ_2(sc, PIOP0) | TBD_SW_EOF);
|
|
WL_WRITE_2(sc, PIOR0, tbd_p + 2);
|
|
WL_WRITE_2(sc, PIOP0, I82586NULL);
|
|
#ifdef WLDEBUG
|
|
if (sc->ifp->if_flags & IFF_DEBUG) {
|
|
if (xmt_debug) {
|
|
wltbd(sc);
|
|
printf("\n");
|
|
}
|
|
}
|
|
#endif /* WLDEBUG */
|
|
|
|
WL_WRITE_2(sc, PIOR0, OFFSET_SCB + 2); /* address of scb_command */
|
|
/*
|
|
* wait for 586 to clear previous command, complain if it takes
|
|
* too long
|
|
*/
|
|
for (spin = 1;;spin = (spin + 1) % 10000) {
|
|
if (WL_READ_2(sc, PIOP0) == 0) { /* it's done, we can go */
|
|
break;
|
|
}
|
|
if ((spin == 0) && xmt_watch) { /* not waking up, and we care */
|
|
if_printf(sc->ifp, "slow accepting xmit\n");
|
|
}
|
|
}
|
|
WL_WRITE_2(sc, PIOP0, SCB_CU_STRT); /* new command */
|
|
SET_CHAN_ATTN(sc);
|
|
|
|
m_freem(m);
|
|
|
|
/* XXX
|
|
* Pause to avoid transmit overrun problems.
|
|
* The required delay tends to vary with platform type, and may be
|
|
* related to interrupt loss.
|
|
*/
|
|
if (wl_xmit_delay) {
|
|
DELAY(wl_xmit_delay);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* wlbldru:
|
|
*
|
|
* This function builds the linear linked lists of fd's and
|
|
* rbd's. Based on page 4-32 of 1986 Intel microcom handbook.
|
|
*
|
|
*/
|
|
static u_short
|
|
wlbldru(struct wl_softc *sc)
|
|
{
|
|
fd_t fd;
|
|
rbd_t rbd;
|
|
u_short fd_p = OFFSET_RU;
|
|
u_short rbd_p = OFFSET_RBD;
|
|
int i;
|
|
|
|
sc->begin_fd = fd_p;
|
|
for (i = 0; i < N_FD; i++) {
|
|
fd.status = 0;
|
|
fd.command = 0;
|
|
fd.link_offset = fd_p + sizeof(fd_t);
|
|
fd.rbd_offset = I82586NULL;
|
|
WL_WRITE_2(sc, PIOR1, fd_p);
|
|
WL_WRITE_MULTI_2(sc, PIOP1, &fd, 8/2);
|
|
fd_p = fd.link_offset;
|
|
}
|
|
fd_p -= sizeof(fd_t);
|
|
sc->end_fd = fd_p;
|
|
WL_WRITE_2(sc, PIOR1, fd_p + 2);
|
|
WL_WRITE_2(sc, PIOP1, AC_CW_EL); /* command */
|
|
WL_WRITE_2(sc, PIOP1, I82586NULL); /* link_offset */
|
|
fd_p = OFFSET_RU;
|
|
|
|
WL_WRITE_2(sc, PIOR0, fd_p + 6); /* address of rbd_offset */
|
|
WL_WRITE_2(sc, PIOP0, rbd_p);
|
|
WL_WRITE_2(sc, PIOR1, rbd_p);
|
|
for (i = 0; i < N_RBD; i++) {
|
|
rbd.status = 0;
|
|
rbd.buffer_addr = rbd_p + sizeof(rbd_t) + 2;
|
|
rbd.buffer_base = 0;
|
|
rbd.size = RCVBUFSIZE;
|
|
if (i != N_RBD-1) {
|
|
rbd_p += sizeof(ru_t);
|
|
rbd.next_rbd_offset = rbd_p;
|
|
} else {
|
|
rbd.next_rbd_offset = I82586NULL;
|
|
rbd.size |= AC_CW_EL;
|
|
sc->end_rbd = rbd_p;
|
|
}
|
|
WL_WRITE_MULTI_2(sc, PIOP1, &rbd, sizeof(rbd_t)/2);
|
|
WL_WRITE_2(sc, PIOR1, rbd_p);
|
|
}
|
|
return sc->begin_fd;
|
|
}
|
|
|
|
/*
|
|
* wlrustrt:
|
|
*
|
|
* This routine starts the receive unit running. First checks if the
|
|
* board is actually ready, then the board is instructed to receive
|
|
* packets again.
|
|
*
|
|
*/
|
|
static void
|
|
wlrustrt(struct wl_softc *sc)
|
|
{
|
|
u_short rfa;
|
|
|
|
#ifdef WLDEBUG
|
|
if (sc->ifp->if_flags & IFF_DEBUG)
|
|
if_printf(sc->ifp, "entered wlrustrt()\n");
|
|
#endif
|
|
WL_WRITE_2(sc, PIOR0, OFFSET_SCB);
|
|
if (WL_READ_2(sc, PIOP0) & SCB_RUS_READY){
|
|
printf("wlrustrt: RUS_READY\n");
|
|
return;
|
|
}
|
|
|
|
WL_WRITE_2(sc, PIOR0, OFFSET_SCB + 2);
|
|
WL_WRITE_2(sc, PIOP0, SCB_RU_STRT); /* command */
|
|
rfa = wlbldru(sc);
|
|
WL_WRITE_2(sc, PIOR0, OFFSET_SCB + 6); /* address of scb_rfa_offset */
|
|
WL_WRITE_2(sc, PIOP0, rfa);
|
|
|
|
SET_CHAN_ATTN(sc);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* wldiag:
|
|
*
|
|
* This routine does a 586 op-code number 7, and obtains the
|
|
* diagnose status for the WaveLAN.
|
|
*
|
|
*/
|
|
static int
|
|
wldiag(struct wl_softc *sc)
|
|
{
|
|
short status;
|
|
|
|
#ifdef WLDEBUG
|
|
if (sc->ifp->if_flags & IFF_DEBUG)
|
|
if_printf(sc->ifp, "entered wldiag()\n");
|
|
#endif
|
|
WL_WRITE_2(sc, PIOR0, OFFSET_SCB);
|
|
status = WL_READ_2(sc, PIOP0);
|
|
if (status & SCB_SW_INT) {
|
|
/* state is 2000 which seems ok
|
|
if_printf(sc->ifp, "diag(): unexpected initial state %\n",
|
|
WL_READ_2(sc, PIOP0));
|
|
*/
|
|
wlack(sc);
|
|
}
|
|
WL_WRITE_2(sc, PIOR1, OFFSET_CU);
|
|
WL_WRITE_2(sc, PIOP1, 0); /* ac_status */
|
|
WL_WRITE_2(sc, PIOP1, AC_DIAGNOSE|AC_CW_EL);/* ac_command */
|
|
if (wlcmd(sc, "diag()") == 0)
|
|
return 0;
|
|
WL_WRITE_2(sc, PIOR0, OFFSET_CU);
|
|
if (WL_READ_2(sc, PIOP0) & 0x0800) {
|
|
if_printf(sc->ifp, "i82586 Self Test failed!\n");
|
|
return 0;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* wlconfig:
|
|
*
|
|
* This routine does a standard config of the WaveLAN board.
|
|
*
|
|
*/
|
|
static int
|
|
wlconfig(struct wl_softc *sc)
|
|
{
|
|
configure_t configure;
|
|
|
|
#if MULTICAST
|
|
struct ifmultiaddr *ifma;
|
|
u_char *addrp;
|
|
int cnt = 0;
|
|
#endif /* MULTICAST */
|
|
|
|
#ifdef WLDEBUG
|
|
if (sc->ifp->if_flags & IFF_DEBUG)
|
|
if_printf(sc->ifp, "entered wlconfig()\n");
|
|
#endif
|
|
WL_WRITE_2(sc, PIOR0, OFFSET_SCB);
|
|
if (WL_READ_2(sc, PIOP0) & SCB_SW_INT) {
|
|
/*
|
|
if_printf(sc->ifp, "config(): unexpected initial state %x\n",
|
|
WL_READ_2(sc, PIOP0));
|
|
*/
|
|
}
|
|
wlack(sc);
|
|
|
|
WL_WRITE_2(sc, PIOR1, OFFSET_CU);
|
|
WL_WRITE_2(sc, PIOP1, 0); /* ac_status */
|
|
WL_WRITE_2(sc, PIOP1, AC_CONFIGURE|AC_CW_EL); /* ac_command */
|
|
|
|
/* jrb hack */
|
|
configure.fifolim_bytecnt = 0x080c;
|
|
configure.addrlen_mode = 0x0600;
|
|
configure.linprio_interframe = 0x2060;
|
|
configure.slot_time = 0xf200;
|
|
configure.hardware = 0x0008; /* tx even w/o CD */
|
|
configure.min_frame_len = 0x0040;
|
|
#if 0
|
|
/* This is the configuration block suggested by Marc Meertens
|
|
* <mmeerten@obelix.utrecht.NCR.COM> in an e-mail message to John
|
|
* Ioannidis on 10 Nov 92.
|
|
*/
|
|
configure.fifolim_bytecnt = 0x040c;
|
|
configure.addrlen_mode = 0x0600;
|
|
configure.linprio_interframe = 0x2060;
|
|
configure.slot_time = 0xf000;
|
|
configure.hardware = 0x0008; /* tx even w/o CD */
|
|
configure.min_frame_len = 0x0040;
|
|
#else
|
|
/*
|
|
* below is the default board configuration from p2-28 from 586 book
|
|
*/
|
|
configure.fifolim_bytecnt = 0x080c;
|
|
configure.addrlen_mode = 0x2600;
|
|
configure.linprio_interframe = 0x7820; /* IFS=120, ACS=2 */
|
|
configure.slot_time = 0xf00c; /* slottime=12 */
|
|
configure.hardware = 0x0008; /* tx even w/o CD */
|
|
configure.min_frame_len = 0x0040;
|
|
#endif
|
|
if (sc->mode & (MOD_PROM | MOD_ENAL))
|
|
configure.hardware |= 1;
|
|
WL_WRITE_2(sc, PIOR1, OFFSET_CU + 6);
|
|
WL_WRITE_MULTI_2(sc, PIOP1, &configure, sizeof(configure_t)/2);
|
|
|
|
if (wlcmd(sc, "config()-configure") == 0)
|
|
return 0;
|
|
#if MULTICAST
|
|
WL_WRITE_2(sc, PIOR1, OFFSET_CU);
|
|
WL_WRITE_2(sc, PIOP1, 0); /* ac_status */
|
|
WL_WRITE_2(sc, PIOP1, AC_MCSETUP|AC_CW_EL); /* ac_command */
|
|
WL_WRITE_2(sc, PIOR1, OFFSET_CU + 8);
|
|
if_maddr_rlock(sc->ifp);
|
|
TAILQ_FOREACH(ifma, &sc->ifp->if_multiaddrs, ifma_link) {
|
|
if (ifma->ifma_addr->sa_family != AF_LINK)
|
|
continue;
|
|
|
|
addrp = LLADDR((struct sockaddr_dl *)ifma->ifma_addr);
|
|
WL_WRITE_2(sc, PIOP1, addrp[0] + (addrp[1] << 8));
|
|
WL_WRITE_2(sc, PIOP1, addrp[2] + (addrp[3] << 8));
|
|
WL_WRITE_2(sc, PIOP1, addrp[4] + (addrp[5] << 8));
|
|
++cnt;
|
|
}
|
|
if_maddr_runlock(sc->ifp);
|
|
WL_WRITE_2(sc, PIOR1, OFFSET_CU + 6); /* mc-cnt */
|
|
WL_WRITE_2(sc, PIOP1, cnt * WAVELAN_ADDR_SIZE);
|
|
if (wlcmd(sc, "config()-mcaddress") == 0)
|
|
return 0;
|
|
#endif /* MULTICAST */
|
|
|
|
WL_WRITE_2(sc, PIOR1, OFFSET_CU);
|
|
WL_WRITE_2(sc, PIOP1, 0); /* ac_status */
|
|
WL_WRITE_2(sc, PIOP1, AC_IASETUP|AC_CW_EL); /* ac_command */
|
|
WL_WRITE_2(sc, PIOR1, OFFSET_CU + 6);
|
|
WL_WRITE_MULTI_2(sc, PIOP1, IF_LLADDR(sc->ifp), WAVELAN_ADDR_SIZE/2);
|
|
|
|
if (wlcmd(sc, "config()-address") == 0)
|
|
return(0);
|
|
|
|
wlinitmmc(sc);
|
|
|
|
return(1);
|
|
}
|
|
|
|
/*
|
|
* wlcmd:
|
|
*
|
|
* Set channel attention bit and busy wait until command has
|
|
* completed. Then acknowledge the command completion.
|
|
*/
|
|
static int
|
|
wlcmd(struct wl_softc *sc, char *str)
|
|
{
|
|
int i;
|
|
|
|
WL_WRITE_2(sc, PIOR0, OFFSET_SCB + 2); /* address of scb_command */
|
|
WL_WRITE_2(sc, PIOP0, SCB_CU_STRT);
|
|
|
|
SET_CHAN_ATTN(sc);
|
|
|
|
WL_WRITE_2(sc, PIOR0, OFFSET_CU);
|
|
for (i = 0; i < 0xffff; i++)
|
|
if (WL_READ_2(sc, PIOP0) & AC_SW_C)
|
|
break;
|
|
if (i == 0xffff || !(WL_READ_2(sc, PIOP0) & AC_SW_OK)) {
|
|
if_printf(sc->ifp, "%s failed; status = %d, inw = %x, outw = %x\n",
|
|
str, WL_READ_2(sc, PIOP0) & AC_SW_OK, WL_READ_2(sc, PIOP0),
|
|
WL_READ_2(sc, PIOR0));
|
|
WL_WRITE_2(sc, PIOR0, OFFSET_SCB);
|
|
printf("scb_status %x\n", WL_READ_2(sc, PIOP0));
|
|
WL_WRITE_2(sc, PIOR0, OFFSET_SCB+2);
|
|
printf("scb_command %x\n", WL_READ_2(sc, PIOP0));
|
|
WL_WRITE_2(sc, PIOR0, OFFSET_SCB+4);
|
|
printf("scb_cbl %x\n", WL_READ_2(sc, PIOP0));
|
|
WL_WRITE_2(sc, PIOR0, OFFSET_CU+2);
|
|
printf("cu_cmd %x\n", WL_READ_2(sc, PIOP0));
|
|
return(0);
|
|
}
|
|
|
|
WL_WRITE_2(sc, PIOR0, OFFSET_SCB);
|
|
if ((WL_READ_2(sc, PIOP0) & SCB_SW_INT) &&
|
|
(WL_READ_2(sc, PIOP0) != SCB_SW_CNA)) {
|
|
/*
|
|
if_printf(sc->ifp, "%s: unexpected final state %x\n",
|
|
str, WL_READ_2(sc, PIOP0));
|
|
*/
|
|
}
|
|
wlack(sc);
|
|
return(TRUE);
|
|
}
|
|
|
|
/*
|
|
* wlack: if the 82596 wants attention because it has finished
|
|
* sending or receiving a packet, acknowledge its desire and
|
|
* return bits indicating the kind of attention. wlack() returns
|
|
* these bits so that the caller can service exactly the
|
|
* conditions that wlack() acknowledged.
|
|
*/
|
|
static int
|
|
wlack(struct wl_softc *sc)
|
|
{
|
|
int i;
|
|
u_short cmd;
|
|
|
|
WL_WRITE_2(sc, PIOR1, OFFSET_SCB);
|
|
if (!(cmd = (WL_READ_2(sc, PIOP1) & SCB_SW_INT)))
|
|
return(0);
|
|
#ifdef WLDEBUG
|
|
if (sc->ifp->if_flags & IFF_DEBUG)
|
|
if_printf(sc->ifp, "doing a wlack()\n");
|
|
#endif
|
|
WL_WRITE_2(sc, PIOP1, cmd);
|
|
SET_CHAN_ATTN(sc);
|
|
WL_WRITE_2(sc, PIOR0, OFFSET_SCB + 2); /* address of scb_command */
|
|
for (i = 1000000; WL_READ_2(sc, PIOP0) && (i-- > 0); )
|
|
continue;
|
|
if (i < 1)
|
|
if_printf(sc->ifp, "wlack(): board not accepting command.\n");
|
|
return(cmd);
|
|
}
|
|
|
|
#ifdef WLDEBUG
|
|
static void
|
|
wltbd(struct wl_softc *sc)
|
|
{
|
|
u_short tbd_p = OFFSET_TBD;
|
|
tbd_t tbd;
|
|
int i = 0;
|
|
int sum = 0;
|
|
|
|
for (;;) {
|
|
WL_WRITE_2(sc, PIOR1, tbd_p);
|
|
WL_READ_MULTI_2(sc, PIOP1, &tbd, sizeof(tbd_t)/2);
|
|
sum += (tbd.act_count & ~TBD_SW_EOF);
|
|
printf("%d: addr %x, count %d (%d), next %x, base %x\n",
|
|
i++, tbd.buffer_addr,
|
|
(tbd.act_count & ~TBD_SW_EOF), sum,
|
|
tbd.next_tbd_offset, tbd.buffer_base);
|
|
if (tbd.act_count & TBD_SW_EOF)
|
|
break;
|
|
tbd_p = tbd.next_tbd_offset;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
wlhdwsleaze(u_short *countp, u_char **mb_pp, struct mbuf **tm_pp, struct wl_softc *sc)
|
|
{
|
|
struct mbuf *tm_p = *tm_pp;
|
|
u_char *mb_p = *mb_pp;
|
|
u_short count = 0;
|
|
u_char *cp;
|
|
int len;
|
|
|
|
/*
|
|
* can we get a run that will be coallesced or
|
|
* that terminates before breaking
|
|
*/
|
|
do {
|
|
count += tm_p->m_len;
|
|
if (tm_p->m_len & 1)
|
|
break;
|
|
} while ((tm_p = tm_p->m_next) != (struct mbuf *)0);
|
|
if ( (tm_p == (struct mbuf *)0) ||
|
|
count > HDW_THRESHOLD) {
|
|
*countp = (*tm_pp)->m_len;
|
|
*mb_pp = mtod((*tm_pp), u_char *);
|
|
return;
|
|
}
|
|
|
|
/* we need to copy */
|
|
tm_p = *tm_pp;
|
|
mb_p = *mb_pp;
|
|
count = 0;
|
|
cp = (u_char *) t_packet;
|
|
for (;;) {
|
|
bcopy(mtod(tm_p, u_char *), cp, len = tm_p->m_len);
|
|
count += len;
|
|
if (count > HDW_THRESHOLD)
|
|
break;
|
|
cp += len;
|
|
if (tm_p->m_next == (struct mbuf *)0)
|
|
break;
|
|
tm_p = tm_p->m_next;
|
|
}
|
|
*countp = count;
|
|
*mb_pp = (u_char *) t_packet;
|
|
*tm_pp = tm_p;
|
|
return;
|
|
}
|
|
|
|
|
|
static void
|
|
wlsftwsleaze(u_short *countp, u_char **mb_pp, struct mbuf **tm_pp, struct wl_softc *sc)
|
|
{
|
|
struct mbuf *tm_p = *tm_pp;
|
|
u_short count = 0;
|
|
u_char *cp = (u_char *) t_packet;
|
|
int len;
|
|
|
|
/* we need to copy */
|
|
for (;;) {
|
|
bcopy(mtod(tm_p, u_char *), cp, len = tm_p->m_len);
|
|
count += len;
|
|
cp += len;
|
|
if (tm_p->m_next == (struct mbuf *)0)
|
|
break;
|
|
tm_p = tm_p->m_next;
|
|
}
|
|
|
|
*countp = count;
|
|
*mb_pp = (u_char *) t_packet;
|
|
*tm_pp = tm_p;
|
|
return;
|
|
}
|
|
|
|
static void
|
|
wlmmcstat(struct wl_softc *sc)
|
|
{
|
|
u_short tmp;
|
|
|
|
device_printf(sc->dev, "DCE_STATUS: 0x%x, ",
|
|
wlmmcread(sc, MMC_DCE_STATUS) & 0x0f);
|
|
tmp = wlmmcread(sc, MMC_CORRECT_NWID_H) << 8;
|
|
tmp |= wlmmcread(sc, MMC_CORRECT_NWID_L);
|
|
printf("Correct NWID's: %d, ", tmp);
|
|
tmp = wlmmcread(sc, MMC_WRONG_NWID_H) << 8;
|
|
tmp |= wlmmcread(sc, MMC_WRONG_NWID_L);
|
|
printf("Wrong NWID's: %d\n", tmp);
|
|
printf("THR_PRE_SET: 0x%x, ", wlmmcread(sc, MMC_THR_PRE_SET));
|
|
printf("SIGNAL_LVL: %d, SILENCE_LVL: %d\n",
|
|
wlmmcread(sc, MMC_SIGNAL_LVL),
|
|
wlmmcread(sc, MMC_SILENCE_LVL));
|
|
printf("SIGN_QUAL: 0x%x, NETW_ID: %x:%x, DES: %d\n",
|
|
wlmmcread(sc, MMC_SIGN_QUAL),
|
|
wlmmcread(sc, MMC_NETW_ID_H),
|
|
wlmmcread(sc, MMC_NETW_ID_L),
|
|
wlmmcread(sc, MMC_DES_AVAIL));
|
|
}
|
|
|
|
static u_short
|
|
wlmmcread(struct wl_softc *sc, u_short reg)
|
|
{
|
|
while (WL_READ_2(sc, HASR) & HASR_MMC_BUSY)
|
|
continue;
|
|
WL_WRITE_2(sc, MMCR,reg << 1);
|
|
while (WL_READ_2(sc, HASR) & HASR_MMC_BUSY)
|
|
continue;
|
|
return (u_short)WL_READ_2(sc, MMCR) >> 8;
|
|
}
|
|
|
|
static void
|
|
getsnr(struct wl_softc *sc)
|
|
{
|
|
MMC_WRITE(MMC_FREEZE,1);
|
|
/*
|
|
* SNR retrieval procedure :
|
|
*
|
|
* read signal level : wlmmcread(sc, MMC_SIGNAL_LVL);
|
|
* read silence level : wlmmcread(sc, MMC_SILENCE_LVL);
|
|
*/
|
|
MMC_WRITE(MMC_FREEZE,0);
|
|
/*
|
|
* SNR is signal:silence ratio.
|
|
*/
|
|
}
|
|
|
|
/*
|
|
** wlgetpsa
|
|
**
|
|
** Reads the psa for the wavelan at (sc) into (buf)
|
|
*/
|
|
static void
|
|
wlgetpsa(struct wl_softc *sc, u_char *buf)
|
|
{
|
|
int i;
|
|
|
|
PCMD(sc, HACR_DEFAULT & ~HACR_16BITS);
|
|
PCMD(sc, HACR_DEFAULT & ~HACR_16BITS);
|
|
|
|
for (i = 0; i < 0x40; i++) {
|
|
WL_WRITE_2(sc, PIOR2, i);
|
|
buf[i] = WL_READ_1(sc, PIOP2);
|
|
}
|
|
PCMD(sc, HACR_DEFAULT);
|
|
PCMD(sc, HACR_DEFAULT);
|
|
}
|
|
|
|
/*
|
|
** wlsetpsa
|
|
**
|
|
** Writes the psa for wavelan (unit) from the softc back to the
|
|
** board. Updates the CRC and sets the CRC OK flag.
|
|
**
|
|
** Do not call this when the board is operating, as it doesn't
|
|
** preserve the hacr.
|
|
*/
|
|
static void
|
|
wlsetpsa(struct wl_softc *sc)
|
|
{
|
|
int i;
|
|
u_short crc;
|
|
|
|
crc = wlpsacrc(sc->psa); /* calculate CRC of PSA */
|
|
sc->psa[WLPSA_CRCLOW] = crc & 0xff;
|
|
sc->psa[WLPSA_CRCHIGH] = (crc >> 8) & 0xff;
|
|
sc->psa[WLPSA_CRCOK] = 0x55; /* default to 'bad' until programming complete */
|
|
|
|
PCMD(sc, HACR_DEFAULT & ~HACR_16BITS);
|
|
PCMD(sc, HACR_DEFAULT & ~HACR_16BITS);
|
|
|
|
for (i = 0; i < 0x40; i++) {
|
|
DELAY(DELAYCONST);
|
|
WL_WRITE_2(sc, PIOR2, i); /* write param memory */
|
|
DELAY(DELAYCONST);
|
|
WL_WRITE_1(sc, PIOP2, sc->psa[i]);
|
|
}
|
|
DELAY(DELAYCONST);
|
|
WL_WRITE_2(sc, PIOR2, WLPSA_CRCOK); /* update CRC flag*/
|
|
DELAY(DELAYCONST);
|
|
sc->psa[WLPSA_CRCOK] = 0xaa; /* OK now */
|
|
WL_WRITE_1(sc, PIOP2, 0xaa); /* all OK */
|
|
DELAY(DELAYCONST);
|
|
|
|
PCMD(sc, HACR_DEFAULT);
|
|
PCMD(sc, HACR_DEFAULT);
|
|
}
|
|
|
|
/*
|
|
** CRC routine provided by Christopher Giordano <cgiordan@gdeb.com>,
|
|
** from original code by Tomi Mikkonen (tomitm@remedy.fi)
|
|
*/
|
|
|
|
static u_int crc16_table[16] = {
|
|
0x0000, 0xCC01, 0xD801, 0x1400,
|
|
0xF001, 0x3C00, 0x2800, 0xE401,
|
|
0xA001, 0x6C00, 0x7800, 0xB401,
|
|
0x5000, 0x9C01, 0x8801, 0x4400
|
|
};
|
|
|
|
static u_short
|
|
wlpsacrc(u_char *buf)
|
|
{
|
|
u_short crc = 0;
|
|
int i, r1;
|
|
|
|
for (i = 0; i < 0x3d; i++, buf++) {
|
|
/* lower 4 bits */
|
|
r1 = crc16_table[crc & 0xF];
|
|
crc = (crc >> 4) & 0x0FFF;
|
|
crc = crc ^ r1 ^ crc16_table[*buf & 0xF];
|
|
|
|
/* upper 4 bits */
|
|
r1 = crc16_table[crc & 0xF];
|
|
crc = (crc >> 4) & 0x0FFF;
|
|
crc = crc ^ r1 ^ crc16_table[(*buf >> 4) & 0xF];
|
|
}
|
|
return(crc);
|
|
}
|
|
#ifdef WLCACHE
|
|
|
|
/*
|
|
* wl_cache_store
|
|
*
|
|
* take input packet and cache various radio hw characteristics
|
|
* indexed by MAC address.
|
|
*
|
|
* Some things to think about:
|
|
* note that no space is malloced.
|
|
* We might hash the mac address if the cache were bigger.
|
|
* It is not clear that the cache is big enough.
|
|
* It is also not clear how big it should be.
|
|
* The cache is IP-specific. We don't care about that as
|
|
* we want it to be IP-specific.
|
|
* The last N recv. packets are saved. This will tend
|
|
* to reward agents and mobile hosts that beacon.
|
|
* That is probably fine for mobile ip.
|
|
*/
|
|
|
|
/* globals for wavelan signal strength cache */
|
|
/* this should go into softc structure above.
|
|
*/
|
|
|
|
/* set true if you want to limit cache items to broadcast/mcast
|
|
* only packets (not unicast)
|
|
*/
|
|
static int wl_cache_mcastonly = 1;
|
|
SYSCTL_INT(_machdep, OID_AUTO, wl_cache_mcastonly, CTLFLAG_RW,
|
|
&wl_cache_mcastonly, 0, "");
|
|
|
|
/* set true if you want to limit cache items to IP packets only
|
|
*/
|
|
static int wl_cache_iponly = 1;
|
|
SYSCTL_INT(_machdep, OID_AUTO, wl_cache_iponly, CTLFLAG_RW,
|
|
&wl_cache_iponly, 0, "");
|
|
|
|
/* zero out the cache
|
|
*/
|
|
static void
|
|
wl_cache_zero(struct wl_softc *sc)
|
|
{
|
|
|
|
bzero(&sc->w_sigcache[0], sizeof(struct w_sigcache) * MAXCACHEITEMS);
|
|
sc->w_sigitems = 0;
|
|
sc->w_nextcache = 0;
|
|
sc->w_wrapindex = 0;
|
|
}
|
|
|
|
/* store hw signal info in cache.
|
|
* index is MAC address, but an ip src gets stored too
|
|
* There are two filters here controllable via sysctl:
|
|
* throw out unicast (on by default, but can be turned off)
|
|
* throw out non-ip (on by default, but can be turned off)
|
|
*/
|
|
static
|
|
void wl_cache_store (struct wl_softc *sc, struct ether_header *eh,
|
|
struct mbuf *m)
|
|
{
|
|
#ifdef INET
|
|
struct ip *ip = NULL; /* Avoid GCC warning */
|
|
int i;
|
|
int signal, silence;
|
|
int w_insertcache; /* computed index for cache entry storage */
|
|
int ipflag = wl_cache_iponly;
|
|
#endif
|
|
|
|
/* filters:
|
|
* 1. ip only
|
|
* 2. configurable filter to throw out unicast packets,
|
|
* keep multicast only.
|
|
*/
|
|
|
|
#ifdef INET
|
|
/* reject if not IP packet
|
|
*/
|
|
if ( wl_cache_iponly && (ntohs(eh->ether_type) != 0x800)) {
|
|
return;
|
|
}
|
|
|
|
/* check if broadcast or multicast packet. we toss
|
|
* unicast packets
|
|
*/
|
|
if (wl_cache_mcastonly && ((eh->ether_dhost[0] & 1) == 0)) {
|
|
return;
|
|
}
|
|
|
|
/* find the ip header. we want to store the ip_src
|
|
* address. use the mtod macro(in mbuf.h)
|
|
* to typecast m to struct ip *
|
|
*/
|
|
if (ipflag) {
|
|
ip = mtod(m, struct ip *);
|
|
}
|
|
|
|
/* do a linear search for a matching MAC address
|
|
* in the cache table
|
|
* . MAC address is 6 bytes,
|
|
* . var w_nextcache holds total number of entries already cached
|
|
*/
|
|
for (i = 0; i < sc->w_nextcache; i++) {
|
|
if (! bcmp(eh->ether_shost, sc->w_sigcache[i].macsrc, 6 )) {
|
|
/* Match!,
|
|
* so we already have this entry,
|
|
* update the data, and LRU age
|
|
*/
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* did we find a matching mac address?
|
|
* if yes, then overwrite a previously existing cache entry
|
|
*/
|
|
if (i < sc->w_nextcache ) {
|
|
w_insertcache = i;
|
|
}
|
|
/* else, have a new address entry,so
|
|
* add this new entry,
|
|
* if table full, then we need to replace entry
|
|
*/
|
|
else {
|
|
|
|
/* check for space in cache table
|
|
* note: w_nextcache also holds number of entries
|
|
* added in the cache table
|
|
*/
|
|
if ( sc->w_nextcache < MAXCACHEITEMS ) {
|
|
w_insertcache = sc->w_nextcache;
|
|
sc->w_nextcache++;
|
|
sc->w_sigitems = sc->w_nextcache;
|
|
}
|
|
/* no space found, so simply wrap with wrap index
|
|
* and "zap" the next entry
|
|
*/
|
|
else {
|
|
if (sc->w_wrapindex == MAXCACHEITEMS) {
|
|
sc->w_wrapindex = 0;
|
|
}
|
|
w_insertcache = sc->w_wrapindex++;
|
|
}
|
|
}
|
|
|
|
/* invariant: w_insertcache now points at some slot
|
|
* in cache.
|
|
*/
|
|
if (w_insertcache < 0 || w_insertcache >= MAXCACHEITEMS) {
|
|
log(LOG_ERR,
|
|
"wl_cache_store, bad index: %d of [0..%d], gross cache error\n",
|
|
w_insertcache, MAXCACHEITEMS);
|
|
return;
|
|
}
|
|
|
|
/* store items in cache
|
|
* .ipsrc
|
|
* .macsrc
|
|
* .signal (0..63) ,silence (0..63) ,quality (0..15)
|
|
*/
|
|
if (ipflag) {
|
|
sc->w_sigcache[w_insertcache].ipsrc = ip->ip_src.s_addr;
|
|
}
|
|
bcopy( eh->ether_shost, sc->w_sigcache[w_insertcache].macsrc, 6);
|
|
signal = sc->w_sigcache[w_insertcache].signal = wlmmcread(sc, MMC_SIGNAL_LVL) & 0x3f;
|
|
silence = sc->w_sigcache[w_insertcache].silence = wlmmcread(sc, MMC_SILENCE_LVL) & 0x3f;
|
|
sc->w_sigcache[w_insertcache].quality = wlmmcread(sc, MMC_SIGN_QUAL) & 0x0f;
|
|
if (signal > 0)
|
|
sc->w_sigcache[w_insertcache].snr =
|
|
signal - silence;
|
|
else
|
|
sc->w_sigcache[w_insertcache].snr = 0;
|
|
#endif /* INET */
|
|
|
|
}
|
|
#endif /* WLCACHE */
|