summaryrefslogtreecommitdiffstats
path: root/sys/i386/isa/if_wi.c
diff options
context:
space:
mode:
authorwpaul <wpaul@FreeBSD.org>1999-05-05 07:11:38 +0000
committerwpaul <wpaul@FreeBSD.org>1999-05-05 07:11:38 +0000
commit0a863b6b19e475dde7eed64b31bb1b05a7d9e66c (patch)
tree0ff140a02f8ced8a34a7d609149d847f7c6be9dc /sys/i386/isa/if_wi.c
parentace20ddfff413899387036af903fd35b8baefbea (diff)
downloadFreeBSD-src-0a863b6b19e475dde7eed64b31bb1b05a7d9e66c.zip
FreeBSD-src-0a863b6b19e475dde7eed64b31bb1b05a7d9e66c.tar.gz
Add device driver support for the Lucent WaveLAN/IEEE 802.11 wireless
network adapters. These are all PCMCIA devices (the ISA version is a PCMCIA to ISA bridge with a PCMCIA card plugged into it). Also add a wicontrol utility to read and write some of the card's parameters. Note: I do not have access to a WavePOINT access point, so I have only been able to test this driver in ad-hoc (point to point) mode. The wicontrol utility allows programming the desired service set name (SSID) and enabling BSS mode, but I can't tell for sure if it works (I know the card switches modes, but I can't verify that it joins a service set correctly). This driver was written using information gleaned from the Lucent HCF Light library, which is an API library designed to simplify driver development for devices based on the Lucent Hermes chip. Unfortunately, the HCF Light is missing certain features (like 802.11 frame encapsulation!) which are available only in the proprietary complete HCF code, which is not available to the public. This driver uses none of the HCF Light code: it's very ugly and contaminated by the GPL. IP and ARP packets are encapsulated as 802.11 frames, everything else is encapsulated as 802.3. (It would be easier to just get the Hermes programming manual, but that's not publically available either. For those who are wondering, the Linux WaveLAN/IEEE driver uses the proprietary HCF code, which is provided in object code form only. So much for supporting open source sofware.) Multicast filter support is implemented, however it appears that the filter doesn't work: programming in one IP mutlicast group enables them all.
Diffstat (limited to 'sys/i386/isa/if_wi.c')
-rw-r--r--sys/i386/isa/if_wi.c1315
1 files changed, 1315 insertions, 0 deletions
diff --git a/sys/i386/isa/if_wi.c b/sys/i386/isa/if_wi.c
new file mode 100644
index 0000000..59c73f0
--- /dev/null
+++ b/sys/i386/isa/if_wi.c
@@ -0,0 +1,1315 @@
+/*
+ * Copyright (c) 1997, 1998, 1999
+ * Bill Paul <wpaul@ctr.columbia.edu>. 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Bill Paul.
+ * 4. Neither the name of the author nor the names of any co-contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD
+ * 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.
+ *
+ * $Id: if_wi.c,v 1.48 1999/05/05 00:32:13 wpaul Exp $
+ */
+
+/*
+ * Lucent WaveLAN/IEEE 802.11 PCMCIA driver for FreeBSD.
+ *
+ * Written by Bill Paul <wpaul@ctr.columbia.edu>
+ * Electrical Engineering Department
+ * Columbia University, New York City
+ */
+
+/*
+ * The WaveLAN IEEE adapter is the second generation of the WaveLAN
+ * from Lucent. Unlike the older cards, the new ones are programmed
+ * entirely via a firmware-driven controller called the Hermes.
+ * Unfortunately, Lucent will not release the Hermes programming manual
+ * without an NDA (if at all). What they do release is an API library
+ * called the HCF (Hardware Control Functions) which is supposed to
+ * do the device-specific operations of a device driver for you. The
+ * publically available version of the HCF library (the 'HCF Light') is
+ * a) extremely gross, b) lacks certain fearures, particularly support
+ * for 802.11 frames, and c) is contaminated by the GNU Public License.
+ *
+ * This driver does not use the HCF or HCF Light at all. Instead, it
+ * programs the Hermes controller directly, using information gleaned
+ * from the HCF Light code and corresponding documentation.
+ *
+ * This driver supports both the PCMCIA and ISA versions of the
+ * WaveLAN/IEEE cards. Note however that the ISA card isn't really
+ * anything of the sort: it's actually a PCMCIA bridge adapter
+ * that fits into an ISA slot, into which a PCMCIA WaveLAN card is
+ * inserted. Consequently, you need to use the pccard support for
+ * both the ISA and PCMCIA adapters.
+ */
+
+#define WI_HERMES_AUTOINC_WAR /* Work around data write autoinc bug. */
+#define WI_HERMES_STATS_WAR /* Work around stats counter bug. */
+
+#include "bpfilter.h"
+#include "card.h"
+#include "wi.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/sockio.h>
+#include <sys/mbuf.h>
+#include <sys/malloc.h>
+#include <sys/kernel.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <net/ethernet.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#ifdef INET
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/in_var.h>
+#include <netinet/ip.h>
+#include <netinet/if_ether.h>
+#endif
+
+#if NBPFILTER > 0
+#include <net/bpf.h>
+#endif
+
+#include <machine/clock.h>
+#include <machine/md_var.h>
+#include <machine/bus_pio.h>
+#include <machine/bus.h>
+
+#include <i386/isa/isa_device.h>
+#include <i386/isa/icu.h>
+#include <i386/isa/if_wireg.h>
+#include <machine/if_wavelan_ieee.h>
+
+#if NCARD > 0
+#include <sys/select.h>
+#include <pccard/cardinfo.h>
+#include <pccard/slot.h>
+#endif
+
+#if !defined(lint)
+static const char rcsid[] =
+ "$Id: if_wi.c,v 1.48 1999/05/05 00:32:13 wpaul Exp $";
+#endif
+
+static struct wi_softc wi_softc[NWI];
+
+#ifdef foo
+static u_int8_t wi_mcast_addr[6] = { 0x00, 0x60, 0x1D, 0x00, 0x01, 0x00 };
+#endif
+
+static int wi_probe __P((struct isa_device *));
+static int wi_attach __P((struct isa_device *));
+#ifdef PCCARD_MODULE
+static ointhand2_t wi_intr;
+#endif
+static void wi_reset __P((struct wi_softc *));
+static int wi_ioctl __P((struct ifnet *, u_long, caddr_t));
+static void wi_init __P((void *));
+static void wi_start __P((struct ifnet *));
+static void wi_stop __P((struct wi_softc *));
+static void wi_watchdog __P((struct ifnet *));
+static void wi_shutdown __P((int, void *));
+static void wi_rxeof __P((struct wi_softc *));
+static void wi_txeof __P((struct wi_softc *, int));
+static void wi_update_stats __P((struct wi_softc *));
+static void wi_setmulti __P((struct wi_softc *));
+
+static int wi_cmd __P((struct wi_softc *, int, int));
+static int wi_read_record __P((struct wi_softc *, struct wi_ltv_gen *));
+static int wi_write_record __P((struct wi_softc *, struct wi_ltv_gen *));
+static int wi_read_data __P((struct wi_softc *, int,
+ int, caddr_t, int));
+static int wi_write_data __P((struct wi_softc *, int,
+ int, caddr_t, int));
+static int wi_seek __P((struct wi_softc *, int, int, int));
+static int wi_alloc_nicmem __P((struct wi_softc *, int, int *));
+static void wi_inquire __P((void *));
+static void wi_setdef __P((struct wi_softc *, struct wi_req *));
+static int wi_mgmt_xmit __P((struct wi_softc *, caddr_t, int));
+
+struct isa_driver widriver = {
+ wi_probe,
+ wi_attach,
+ "wi",
+ 1
+};
+
+#if NCARD > 0
+static int wi_pccard_init __P((struct pccard_devinfo *));
+static void wi_pccard_unload __P((struct pccard_devinfo *));
+static int wi_pccard_intr __P((struct pccard_devinfo *));
+
+#ifdef PCCARD_MODULE
+PCCARD_MODULE(wi, wi_pccard_init, wi_pccard_unload,
+ wi_pccard_intr, 0, net_imask);
+#else
+static struct pccard_device wi_info = {
+ "wi",
+ wi_pccard_init,
+ wi_pccard_unload,
+ wi_pccard_intr,
+ 0, /* Attributes - presently unused */
+ &net_imask /* Interrupt mask for device */
+ /* XXX - Should this also include net_imask? */
+};
+
+DATA_SET(pccarddrv_set, wi_info);
+#endif
+
+/* Initialize the PCCARD. */
+static int wi_pccard_init(sc_p)
+ struct pccard_devinfo *sc_p;
+{
+ struct wi_softc *sc;
+ int i;
+ u_int32_t irq;
+
+ if (sc_p->isahd.id_unit >= NWI)
+ return(ENODEV);
+
+ sc = &wi_softc[sc_p->isahd.id_unit];
+ sc->wi_gone = 0;
+ sc->wi_unit = sc_p->isahd.id_unit;
+ sc->wi_bhandle = sc_p->isahd.id_iobase;
+ sc->wi_btag = I386_BUS_SPACE_IO;
+
+ /* Make sure interrupts are disabled. */
+ CSR_WRITE_2(sc, WI_INT_EN, 0);
+ CSR_WRITE_2(sc, WI_EVENT_ACK, 0xFFFF);
+
+ /* Grr. IRQ is encoded as a bitmask. */
+ irq = sc_p->isahd.id_irq;
+ for (i = 0; i < 32; i++) {
+ if (irq & 0x1)
+ break;
+ irq >>= 1;
+ }
+
+ /*
+ * Print a nice probe message to let the operator
+ * know something interesting is happening.
+ */
+ printf("wi%d: <WaveLAN/IEEE 802.11> at 0x%x-0x%x irq %d on isa\n",
+ sc_p->isahd.id_unit, sc_p->isahd.id_iobase,
+ sc_p->isahd.id_iobase + WI_IOSIZ - 1, i);
+
+ if (wi_attach(&sc_p->isahd))
+ return(ENXIO);
+
+ return(0);
+}
+
+static void wi_pccard_unload(sc_p)
+ struct pccard_devinfo *sc_p;
+{
+ struct wi_softc *sc;
+ struct ifnet *ifp;
+
+ sc = &wi_softc[sc_p->isahd.id_unit];
+ ifp = &sc->arpcom.ac_if;
+
+ if (sc->wi_gone) {
+ printf("wi%d: already unloaded\n", sc_p->isahd.id_unit);
+ return;
+ }
+
+ ifp->if_flags &= ~IFF_RUNNING;
+ if_down(ifp);
+ sc->wi_gone = 1;
+ printf("wi%d: unloaded\n", sc_p->isahd.id_unit);
+
+ return;
+}
+
+static int wi_pccard_intr(sc_p)
+ struct pccard_devinfo *sc_p;
+{
+ wi_intr(sc_p->isahd.id_unit);
+ return(1);
+}
+#endif
+
+static int wi_probe(isa_dev)
+ struct isa_device *isa_dev;
+{
+ /*
+ * The ISA WaveLAN/IEEE card is actually not an ISA card:
+ * it's a PCMCIA card plugged into a PCMCIA expander card
+ * that fits into an ISA slot. Consequently, we will always
+ * be using the pccard support to probe and attach these
+ * devices, so we can never actually probe one from here.
+ */
+ return(0);
+}
+
+static int wi_attach(isa_dev)
+ struct isa_device *isa_dev;
+{
+ struct wi_softc *sc;
+ struct wi_ltv_macaddr mac;
+ struct ifnet *ifp;
+ char ifname[IFNAMSIZ];
+
+#ifdef PCCARD_MODULE
+ isa_dev->id_ointr = wi_intr;
+#endif
+ sc = &wi_softc[isa_dev->id_unit];
+ ifp = &sc->arpcom.ac_if;
+
+ /* Reset the NIC. */
+ wi_reset(sc);
+
+ /* Read the station address. */
+ mac.wi_type = WI_RID_MAC_NODE;
+ mac.wi_len = 4;
+ wi_read_record(sc, (struct wi_ltv_gen *)&mac);
+ bcopy((char *)&mac.wi_mac_addr,
+ (char *)&sc->arpcom.ac_enaddr, ETHER_ADDR_LEN);
+
+ printf("wi%d: Ethernet address: %6D\n", sc->wi_unit,
+ sc->arpcom.ac_enaddr, ":");
+
+ ifp->if_softc = sc;
+ ifp->if_unit = sc->wi_unit;
+ ifp->if_name = "wi";
+ ifp->if_mtu = ETHERMTU;
+ ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
+ ifp->if_ioctl = wi_ioctl;
+ ifp->if_output = ether_output;
+ ifp->if_start = wi_start;
+ ifp->if_watchdog = wi_watchdog;
+ ifp->if_init = wi_init;
+ ifp->if_baudrate = 10000000;
+ ifp->if_snd.ifq_maxlen = IFQ_MAXLEN;
+
+ bzero(sc->wi_node_name, sizeof(sc->wi_node_name));
+ bcopy(WI_DEFAULT_NODENAME, sc->wi_node_name,
+ sizeof(WI_DEFAULT_NODENAME) - 1);
+
+ bzero(sc->wi_net_name, sizeof(sc->wi_net_name));
+ bcopy(WI_DEFAULT_NETNAME, sc->wi_net_name,
+ sizeof(WI_DEFAULT_NETNAME) - 1);
+
+ bzero(sc->wi_ibss_name, sizeof(sc->wi_ibss_name));
+ bcopy(WI_DEFAULT_IBSS, sc->wi_ibss_name,
+ sizeof(WI_DEFAULT_IBSS) - 1);
+
+ sc->wi_portnum = WI_DEFAULT_PORT;
+ sc->wi_ptype = WI_PORTTYPE_ADHOC;
+ sc->wi_ap_density = WI_DEFAULT_AP_DENSITY;
+ sc->wi_rts_thresh = WI_DEFAULT_RTS_THRESH;
+ sc->wi_tx_rate = WI_DEFAULT_TX_RATE;
+ sc->wi_max_data_len = WI_DEFAULT_DATALEN;
+ sc->wi_create_ibss = WI_DEFAULT_CREATE_IBSS;
+
+ bzero((char *)&sc->wi_stats, sizeof(sc->wi_stats));
+
+ wi_init(sc);
+ wi_stop(sc);
+
+ /*
+ * If this logical interface has already been attached,
+ * don't attach it again or chaos will ensue.
+ */
+ sprintf(ifname, "wi%d", sc->wi_unit);
+
+ if (ifunit(ifname) == NULL) {
+ callout_handle_init(&sc->wi_stat_ch);
+ /*
+ * Call MI attach routines.
+ */
+ if_attach(ifp);
+ ether_ifattach(ifp);
+
+#if NBPFILTER > 0
+ bpfattach(ifp, DLT_EN10MB, sizeof(struct ether_header));
+#endif
+
+ at_shutdown(wi_shutdown, sc, SHUTDOWN_POST_SYNC);
+ }
+
+ return(0);
+}
+
+static void wi_rxeof(sc)
+ struct wi_softc *sc;
+{
+ struct ifnet *ifp;
+ struct ether_header *eh;
+ struct wi_frame rx_frame;
+ struct mbuf *m;
+ int id;
+
+ ifp = &sc->arpcom.ac_if;
+
+ id = CSR_READ_2(sc, WI_RX_FID);
+
+ /* First read in the frame header */
+ if (wi_read_data(sc, id, 0, (caddr_t)&rx_frame, sizeof(rx_frame))) {
+ ifp->if_ierrors++;
+ return;
+ }
+
+ if (rx_frame.wi_status & WI_STAT_ERRSTAT) {
+ ifp->if_ierrors++;
+ return;
+ }
+
+ MGETHDR(m, M_DONTWAIT, MT_DATA);
+ if (m == NULL) {
+ ifp->if_ierrors++;
+ return;
+ }
+ MCLGET(m, M_DONTWAIT);
+ if (!(m->m_flags & M_EXT)) {
+ m_freem(m);
+ ifp->if_ierrors++;
+ return;
+ }
+
+ eh = mtod(m, struct ether_header *);
+ m->m_pkthdr.rcvif = ifp;
+
+ if (rx_frame.wi_status == WI_STAT_1042 ||
+ rx_frame.wi_status == WI_STAT_TUNNEL ||
+ rx_frame.wi_status == WI_STAT_WMP_MSG) {
+ m->m_pkthdr.len = m->m_len =
+ rx_frame.wi_dat_len + WI_SNAPHDR_LEN;
+
+ bcopy((char *)&rx_frame.wi_addr1,
+ (char *)&eh->ether_dhost, ETHER_ADDR_LEN);
+ bcopy((char *)&rx_frame.wi_addr2,
+ (char *)&eh->ether_shost, ETHER_ADDR_LEN);
+ bcopy((char *)&rx_frame.wi_type,
+ (char *)&eh->ether_type, sizeof(u_int16_t));
+
+ if (wi_read_data(sc, id, WI_802_11_OFFSET,
+ mtod(m, caddr_t) + sizeof(struct ether_header),
+ m->m_len + 2)) {
+ m_freem(m);
+ ifp->if_ierrors++;
+ return;
+ }
+ } else {
+ m->m_pkthdr.len = m->m_len =
+ rx_frame.wi_dat_len + sizeof(struct ether_header);
+
+ if (wi_read_data(sc, id, WI_802_3_OFFSET,
+ mtod(m, caddr_t), m->m_len + 2)) {
+ m_freem(m);
+ ifp->if_ierrors++;
+ return;
+ }
+ }
+
+ ifp->if_ipackets++;
+
+#if NBPFILTER > 0
+ /* Handle BPF listeners. */
+ if (ifp->if_bpf) {
+ bpf_mtap(ifp, m);
+ if (ifp->if_flags & IFF_PROMISC &&
+ (bcmp(eh->ether_dhost, sc->arpcom.ac_enaddr,
+ ETHER_ADDR_LEN) && (eh->ether_dhost[0] & 1) == 0)) {
+ m_freem(m);
+ return;
+ }
+ }
+#endif
+
+ /* Receive packet. */
+ m_adj(m, sizeof(struct ether_header));
+ ether_input(ifp, eh, m);
+
+ return;
+}
+
+static void wi_txeof(sc, status)
+ struct wi_softc *sc;
+ int status;
+{
+ struct ifnet *ifp;
+
+ ifp = &sc->arpcom.ac_if;
+
+ ifp->if_timer = 0;
+ ifp->if_flags &= ~IFF_OACTIVE;
+
+ if (status & WI_EV_TX_EXC)
+ ifp->if_oerrors++;
+ else
+ ifp->if_opackets++;
+
+ return;
+}
+
+void wi_inquire(xsc)
+ void *xsc;
+{
+ struct wi_softc *sc;
+ struct ifnet *ifp;
+
+ sc = xsc;
+ ifp = &sc->arpcom.ac_if;
+
+ sc->wi_stat_ch = timeout(wi_inquire, sc, hz * 60);
+
+ /* Don't do this while we're transmitting */
+ if (ifp->if_flags & IFF_OACTIVE)
+ return;
+
+ wi_cmd(sc, WI_CMD_INQUIRE, WI_INFO_COUNTERS);
+
+ return;
+}
+
+void wi_update_stats(sc)
+ struct wi_softc *sc;
+{
+ struct wi_ltv_gen gen;
+ u_int16_t id;
+ struct ifnet *ifp;
+ u_int32_t *ptr;
+ int i;
+ u_int16_t t;
+
+ ifp = &sc->arpcom.ac_if;
+
+ id = CSR_READ_2(sc, WI_INFO_FID);
+
+ wi_read_data(sc, id, 0, (char *)&gen, 4);
+
+ if (gen.wi_type != WI_INFO_COUNTERS ||
+ gen.wi_len > (sizeof(sc->wi_stats) / 4) + 1)
+ return;
+
+ ptr = (u_int32_t *)&sc->wi_stats;
+
+ for (i = 0; i < gen.wi_len - 1; i++) {
+ t = CSR_READ_2(sc, WI_DATA1);
+#ifdef WI_HERMES_STATS_WAR
+ if (t > 0xF000)
+ t = ~t & 0xFFFF;
+#endif
+ ptr[i] += t;
+ }
+
+ ifp->if_collisions = sc->wi_stats.wi_tx_single_retries +
+ sc->wi_stats.wi_tx_multi_retries +
+ sc->wi_stats.wi_tx_retry_limit;
+
+ return;
+}
+
+void wi_intr(unit)
+ int unit;
+{
+ struct wi_softc *sc;
+ struct ifnet *ifp;
+ u_int16_t status;
+
+ sc = &wi_softc[unit];
+ ifp = &sc->arpcom.ac_if;
+
+ if (!(ifp->if_flags & IFF_UP)) {
+ CSR_WRITE_2(sc, WI_EVENT_ACK, 0xFFFF);
+ CSR_WRITE_2(sc, WI_INT_EN, 0);
+ return;
+ }
+
+ /* Disable interrupts. */
+ CSR_WRITE_2(sc, WI_INT_EN, 0);
+
+ status = CSR_READ_2(sc, WI_EVENT_STAT);
+ CSR_WRITE_2(sc, WI_EVENT_ACK, ~WI_INTRS);
+
+ if (status & WI_EV_RX) {
+ wi_rxeof(sc);
+ CSR_WRITE_2(sc, WI_EVENT_ACK, WI_EV_RX);
+ }
+
+ if (status & WI_EV_TX) {
+ wi_txeof(sc, status);
+ CSR_WRITE_2(sc, WI_EVENT_ACK, WI_EV_TX);
+ }
+
+ if (status & WI_EV_ALLOC) {
+ int id;
+ id = CSR_READ_2(sc, WI_ALLOC_FID);
+ CSR_WRITE_2(sc, WI_EVENT_ACK, WI_EV_ALLOC);
+ if (id == sc->wi_tx_data_id)
+ wi_txeof(sc, status);
+ }
+
+ if (status & WI_EV_INFO) {
+ wi_update_stats(sc);
+ CSR_WRITE_2(sc, WI_EVENT_ACK, WI_EV_INFO);
+ }
+
+ if (status & WI_EV_TX_EXC) {
+ wi_txeof(sc, status);
+ CSR_WRITE_2(sc, WI_EVENT_ACK, WI_EV_TX_EXC);
+ }
+
+ if (status & WI_EV_INFO_DROP) {
+ CSR_WRITE_2(sc, WI_EVENT_ACK, WI_EV_INFO_DROP);
+ }
+
+ /* Re-enable interrupts. */
+ CSR_WRITE_2(sc, WI_INT_EN, WI_INTRS);
+
+ if (ifp->if_snd.ifq_head != NULL)
+ wi_start(ifp);
+
+ return;
+}
+
+static int wi_cmd(sc, cmd, val)
+ struct wi_softc *sc;
+ int cmd;
+ int val;
+{
+ int i, s = 0;
+
+ CSR_WRITE_2(sc, WI_PARAM0, val);
+ CSR_WRITE_2(sc, WI_COMMAND, cmd);
+
+ for (i = 0; i < WI_TIMEOUT; i++) {
+ /*
+ * Wait for 'command complete' bit to be
+ * set in the event status register.
+ */
+ s = CSR_READ_2(sc, WI_EVENT_STAT) & WI_EV_CMD;
+ if (s) {
+ /* Ack the event and read result code. */
+ s = CSR_READ_2(sc, WI_STATUS);
+ CSR_WRITE_2(sc, WI_EVENT_ACK, WI_EV_CMD);
+#ifdef foo
+ if ((s & WI_CMD_CODE_MASK) != (cmd & WI_CMD_CODE_MASK))
+ return(EIO);
+#endif
+ if (s & WI_STAT_CMD_RESULT)
+ return(EIO);
+ break;
+ }
+ }
+
+ if (i == WI_TIMEOUT)
+ return(ETIMEDOUT);
+
+ return(0);
+}
+
+static void wi_reset(sc)
+ struct wi_softc *sc;
+{
+ if (wi_cmd(sc, WI_CMD_INI, 0))
+ printf("wi%d: init failed\n", sc->wi_unit);
+ CSR_WRITE_2(sc, WI_INT_EN, 0);
+ CSR_WRITE_2(sc, WI_EVENT_ACK, 0xFFFF);
+
+ /* Calibrate timer. */
+ WI_SETVAL(WI_RID_TICK_TIME, 8);
+
+ return;
+}
+
+/*
+ * Read an LTV record from the NIC.
+ */
+static int wi_read_record(sc, ltv)
+ struct wi_softc *sc;
+ struct wi_ltv_gen *ltv;
+{
+ u_int16_t *ptr;
+ int i, len, code;
+
+ /* Tell the NIC to enter record read mode. */
+ if (wi_cmd(sc, WI_CMD_ACCESS|WI_ACCESS_READ, ltv->wi_type))
+ return(EIO);
+
+ /* Select the record we want to read. */
+ CSR_WRITE_2(sc, WI_SEL1, ltv->wi_type);
+
+ /* Specify offset -- we always read the whole record. */
+ CSR_WRITE_2(sc, WI_OFF1, 0);
+
+ /* Wait for NIC to acknowledge */
+ for (i = 0; i < WI_TIMEOUT; i++) {
+ if (!(CSR_READ_2(sc, WI_OFF1) & (WI_OFF_BUSY|WI_OFF_ERR)))
+ break;
+ }
+
+ if (i == WI_TIMEOUT)
+ return(ETIMEDOUT);
+
+ /*
+ * Read the length and record type and make sure they
+ * match what we expect (this verifies that we have enough
+ * room to hold all of the returned data.
+ */
+ len = CSR_READ_2(sc, WI_DATA1);
+ if (len > ltv->wi_len)
+ return(ENOSPC);
+ code = CSR_READ_2(sc, WI_DATA1);
+ if (code != ltv->wi_type)
+ return(EIO);
+
+ ltv->wi_len = len;
+ ltv->wi_type = code;
+
+ /* Now read the data. */
+ ptr = &ltv->wi_val;
+ for (i = 0; i < ltv->wi_len - 1; i++)
+ ptr[i] = CSR_READ_2(sc, WI_DATA1);
+
+ return(0);
+}
+
+/*
+ * Same as read, except we inject data instead of reading it.
+ */
+static int wi_write_record(sc, ltv)
+ struct wi_softc *sc;
+ struct wi_ltv_gen *ltv;
+{
+ u_int16_t *ptr;
+ int i;
+
+ CSR_WRITE_2(sc, WI_SEL1, ltv->wi_type);
+ CSR_WRITE_2(sc, WI_OFF1, 0);
+
+ for (i = 0; i < WI_TIMEOUT; i++) {
+ if (!(CSR_READ_2(sc, WI_OFF1) & (WI_OFF_BUSY|WI_OFF_ERR)))
+ break;
+ }
+
+ if (i == WI_TIMEOUT)
+ return(ETIMEDOUT);
+
+ CSR_WRITE_2(sc, WI_DATA1, ltv->wi_len);
+ CSR_WRITE_2(sc, WI_DATA1, ltv->wi_type);
+
+ ptr = &ltv->wi_val;
+ for (i = 0; i < ltv->wi_len - 1; i++)
+ CSR_WRITE_2(sc, WI_DATA1, ptr[i]);
+
+ if (wi_cmd(sc, WI_CMD_ACCESS|WI_ACCESS_WRITE, ltv->wi_type))
+ return(EIO);
+
+ return(0);
+}
+
+static int wi_seek(sc, id, off, chan)
+ struct wi_softc *sc;
+ int id, off, chan;
+{
+ int i;
+ int selreg, offreg;
+
+ switch (chan) {
+ case WI_BAP0:
+ selreg = WI_SEL0;
+ offreg = WI_OFF0;
+ break;
+ case WI_BAP1:
+ selreg = WI_SEL1;
+ offreg = WI_OFF1;
+ break;
+ default:
+ printf("wi%d: invalid data path: %x\n", sc->wi_unit, chan);
+ return(EIO);
+ }
+
+ CSR_WRITE_2(sc, selreg, id);
+ CSR_WRITE_2(sc, offreg, off);
+
+ for (i = 0; i < WI_TIMEOUT; i++) {
+ if (!(CSR_READ_2(sc, offreg) & (WI_OFF_BUSY|WI_OFF_ERR)))
+ break;
+ }
+
+ if (i == WI_TIMEOUT)
+ return(ETIMEDOUT);
+
+ return(0);
+}
+
+static int wi_read_data(sc, id, off, buf, len)
+ struct wi_softc *sc;
+ int id, off;
+ caddr_t buf;
+ int len;
+{
+ int i;
+ u_int16_t *ptr;
+
+ if (wi_seek(sc, id, off, WI_BAP1))
+ return(EIO);
+
+ ptr = (u_int16_t *)buf;
+ for (i = 0; i < len / 2; i++)
+ ptr[i] = CSR_READ_2(sc, WI_DATA1);
+
+ return(0);
+}
+
+/*
+ * According to the comments in the HCF Light code, there is a bug in
+ * the Hermes (or possibly in certain Hermes firmware revisions) where
+ * the chip's internal autoincrement counter gets thrown off during
+ * data writes: the autoincrement is missed, causing one data word to
+ * be overwritten and subsequent words to be written to the wrong memory
+ * locations. The end result is that we could end up transmitting bogus
+ * frames without realizing it. The workaround for this is to write a
+ * couple of extra guard words after the end of the transfer, then
+ * attempt to read then back. If we fail to locate the guard words where
+ * we expect them, we preform the transfer over again.
+ */
+static int wi_write_data(sc, id, off, buf, len)
+ struct wi_softc *sc;
+ int id, off;
+ caddr_t buf;
+ int len;
+{
+ int i;
+ u_int16_t *ptr;
+
+#ifdef WI_HERMES_AUTOINC_WAR
+again:
+#endif
+
+ if (wi_seek(sc, id, off, WI_BAP0))
+ return(EIO);
+
+ ptr = (u_int16_t *)buf;
+ for (i = 0; i < (len / 2); i++)
+ CSR_WRITE_2(sc, WI_DATA0, ptr[i]);
+
+#ifdef WI_HERMES_AUTOINC_WAR
+ CSR_WRITE_2(sc, WI_DATA0, 0x1234);
+ CSR_WRITE_2(sc, WI_DATA0, 0x5678);
+
+ if (wi_seek(sc, id, off + len, WI_BAP0))
+ return(EIO);
+
+ if (CSR_READ_2(sc, WI_DATA0) != 0x1234 ||
+ CSR_READ_2(sc, WI_DATA0) != 0x5678)
+ goto again;
+#endif
+
+ return(0);
+}
+
+/*
+ * Allocate a region of memory inside the NIC and zero
+ * it out.
+ */
+static int wi_alloc_nicmem(sc, len, id)
+ struct wi_softc *sc;
+ int len;
+ int *id;
+{
+ int i;
+
+ if (wi_cmd(sc, WI_CMD_ALLOC_MEM, len)) {
+ printf("wi%d: failed to allocate %d bytes on NIC\n",
+ sc->wi_unit, len);
+ return(ENOMEM);
+ }
+
+ for (i = 0; i < WI_TIMEOUT; i++) {
+ if (CSR_READ_2(sc, WI_EVENT_STAT) & WI_EV_ALLOC)
+ break;
+ }
+
+ if (i == WI_TIMEOUT)
+ return(ETIMEDOUT);
+
+ CSR_WRITE_2(sc, WI_EVENT_ACK, WI_EV_ALLOC);
+ *id = CSR_READ_2(sc, WI_ALLOC_FID);
+
+ wi_seek(sc, *id, 0, WI_BAP0);
+
+ for (i = 0; i < len / 2; i++)
+ CSR_WRITE_2(sc, WI_DATA0, 0);
+
+ return(0);
+}
+
+static void wi_setmulti(sc)
+ struct wi_softc *sc;
+{
+ struct ifnet *ifp;
+ int i = 0;
+ struct ifmultiaddr *ifma;
+ struct wi_ltv_mcast mcast;
+
+ ifp = &sc->arpcom.ac_if;
+
+ bzero((char *)&mcast, sizeof(mcast));
+
+ mcast.wi_type = WI_RID_MCAST;
+ mcast.wi_len = (3 * 16) + 1;
+
+ if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) {
+ wi_write_record(sc, (struct wi_ltv_gen *)&mcast);
+ return;
+ }
+
+ for (ifma = ifp->if_multiaddrs.lh_first; ifma != NULL;
+ ifma = ifma->ifma_link.le_next) {
+ if (ifma->ifma_addr->sa_family != AF_LINK)
+ continue;
+ if (i < 16) {
+ bcopy(LLADDR((struct sockaddr_dl *)ifma->ifma_addr),
+ (char *)&mcast.wi_mcast[i], ETHER_ADDR_LEN);
+ i++;
+ } else {
+ bzero((char *)&mcast, sizeof(mcast));
+ break;
+ }
+ }
+
+ mcast.wi_len = (i * 3) + 1;
+ wi_write_record(sc, (struct wi_ltv_gen *)&mcast);
+
+ return;
+}
+
+static void wi_setdef(sc, wreq)
+ struct wi_softc *sc;
+ struct wi_req *wreq;
+{
+ struct sockaddr_dl *sdl;
+ struct ifaddr *ifa;
+ struct ifnet *ifp;
+
+ ifp = &sc->arpcom.ac_if;
+
+ switch(wreq->wi_type) {
+ case WI_RID_MAC_NODE:
+ ifa = ifnet_addrs[ifp->if_index - 1];
+ sdl = (struct sockaddr_dl *)ifa->ifa_addr;
+ bcopy((char *)&wreq->wi_val, (char *)&sc->arpcom.ac_enaddr,
+ ETHER_ADDR_LEN);
+ bcopy((char *)&wreq->wi_val, LLADDR(sdl), ETHER_ADDR_LEN);
+ break;
+ case WI_RID_PORTTYPE:
+ sc->wi_ptype = wreq->wi_val[0];
+ break;
+ case WI_RID_TX_RATE:
+ sc->wi_tx_rate = wreq->wi_val[0];
+ break;
+ case WI_RID_MAX_DATALEN:
+ sc->wi_max_data_len = wreq->wi_val[0];
+ break;
+ case WI_RID_RTS_THRESH:
+ sc->wi_rts_thresh = wreq->wi_val[0];
+ break;
+ case WI_RID_SYSTEM_SCALE:
+ sc->wi_ap_density = wreq->wi_val[0];
+ break;
+ case WI_RID_CREATE_IBSS:
+ sc->wi_create_ibss = wreq->wi_val[0];
+ break;
+ case WI_RID_NODENAME:
+ bzero(sc->wi_node_name, sizeof(sc->wi_node_name));
+ bcopy((char *)&wreq->wi_val[1], sc->wi_node_name, 30);
+ break;
+ case WI_RID_DESIRED_SSID:
+ bzero(sc->wi_net_name, sizeof(sc->wi_net_name));
+ bcopy((char *)&wreq->wi_val[1], sc->wi_net_name, 30);
+ break;
+ case WI_RID_OWN_SSID:
+ bzero(sc->wi_ibss_name, sizeof(sc->wi_ibss_name));
+ bcopy((char *)&wreq->wi_val[1], sc->wi_ibss_name, 30);
+ break;
+ default:
+ break;
+ }
+
+ return;
+}
+
+static int wi_ioctl(ifp, command, data)
+ struct ifnet *ifp;
+ u_long command;
+ caddr_t data;
+{
+ int s, error = 0;
+ struct wi_softc *sc;
+ struct wi_req wreq;
+ struct ifreq *ifr;
+
+ s = splimp();
+
+ sc = ifp->if_softc;
+ ifr = (struct ifreq *)data;
+
+ if (sc->wi_gone)
+ return(ENODEV);
+
+ switch(command) {
+ case SIOCSIFADDR:
+ case SIOCGIFADDR:
+ case SIOCSIFMTU:
+ error = ether_ioctl(ifp, command, data);
+ break;
+ case SIOCSIFFLAGS:
+ if (ifp->if_flags & IFF_UP) {
+ if (ifp->if_flags & IFF_RUNNING &&
+ ifp->if_flags & IFF_PROMISC &&
+ !(sc->wi_if_flags & IFF_PROMISC)) {
+ WI_SETVAL(WI_RID_PROMISC, 1);
+ } else if (ifp->if_flags & IFF_RUNNING &&
+ !(ifp->if_flags & IFF_PROMISC) &&
+ sc->wi_if_flags & IFF_PROMISC) {
+ WI_SETVAL(WI_RID_PROMISC, 0);
+ } else
+ wi_init(sc);
+ } else {
+ if (ifp->if_flags & IFF_RUNNING) {
+ wi_stop(sc);
+ }
+ }
+ sc->wi_if_flags = ifp->if_flags;
+ error = 0;
+ break;
+ case SIOCADDMULTI:
+ case SIOCDELMULTI:
+ wi_setmulti(sc);
+ error = 0;
+ break;
+ case SIOCGWAVELAN:
+ error = copyin(ifr->ifr_data, &wreq, sizeof(wreq));
+ if (error)
+ break;
+ if (wreq.wi_type == WI_RID_IFACE_STATS) {
+ bcopy((char *)&sc->wi_stats, (char *)&wreq.wi_val,
+ sizeof(sc->wi_stats));
+ wreq.wi_len = (sizeof(sc->wi_stats) / 2) + 1;
+ } else {
+ if (wi_read_record(sc, (struct wi_ltv_gen *)&wreq)) {
+ error = EINVAL;
+ break;
+ }
+ }
+ error = copyout(&wreq, ifr->ifr_data, sizeof(wreq));
+ break;
+ case SIOCSWAVELAN:
+ error = copyin(ifr->ifr_data, &wreq, sizeof(wreq));
+ if (error)
+ break;
+ if (wreq.wi_type == WI_RID_IFACE_STATS) {
+ error = EINVAL;
+ break;
+ } else if (wreq.wi_type == WI_RID_MGMT_XMIT) {
+ error = wi_mgmt_xmit(sc, (caddr_t)&wreq.wi_val,
+ wreq.wi_len);
+ } else {
+ error = wi_write_record(sc, (struct wi_ltv_gen *)&wreq);
+ if (!error)
+ wi_setdef(sc, &wreq);
+ }
+ break;
+ default:
+ error = EINVAL;
+ break;
+ }
+
+ splx(s);
+
+ return(error);
+}
+
+static void wi_init(xsc)
+ void *xsc;
+{
+ struct wi_softc *sc = xsc;
+ struct ifnet *ifp = &sc->arpcom.ac_if;
+ int s;
+ struct wi_ltv_macaddr mac;
+ int id = 0;
+
+ if (sc->wi_gone)
+ return;
+
+ s = splimp();
+
+ if (ifp->if_flags & IFF_RUNNING)
+ wi_stop(sc);
+
+ wi_reset(sc);
+
+ /* Program max data length. */
+ WI_SETVAL(WI_RID_MAX_DATALEN, sc->wi_max_data_len);
+
+ /* Enable/disable IBSS ctration. */
+ WI_SETVAL(WI_RID_CREATE_IBSS, sc->wi_create_ibss);
+
+ /* Set the port type. */
+ WI_SETVAL(WI_RID_PORTTYPE, sc->wi_ptype);
+
+ /* Program the RTS/CTS threshold. */
+ WI_SETVAL(WI_RID_RTS_THRESH, sc->wi_rts_thresh);
+
+ /* Program the TX rate */
+ WI_SETVAL(WI_RID_TX_RATE, sc->wi_tx_rate);
+
+ /* Access point density */
+ WI_SETVAL(WI_RID_SYSTEM_SCALE, sc->wi_ap_density);
+
+ /* Specify the IBSS name */
+ WI_SETSTR(WI_RID_OWN_SSID, sc->wi_ibss_name);
+
+ /* Specify the network name */
+ WI_SETSTR(WI_RID_DESIRED_SSID, sc->wi_net_name);
+
+ /* Program the nodename. */
+ WI_SETSTR(WI_RID_NODENAME, sc->wi_node_name);
+
+ /* Set our MAC address. */
+ mac.wi_len = 4;
+ mac.wi_type = WI_RID_MAC_NODE;
+ bcopy((char *)&sc->arpcom.ac_enaddr,
+ (char *)&mac.wi_mac_addr, ETHER_ADDR_LEN);
+ wi_write_record(sc, (struct wi_ltv_gen *)&mac);
+
+ /* Initialize promisc mode. */
+ if (ifp->if_flags & IFF_PROMISC) {
+ WI_SETVAL(WI_RID_PROMISC, 1);
+ } else {
+ WI_SETVAL(WI_RID_PROMISC, 0);
+ }
+
+ /* Set multicast filter. */
+ wi_setmulti(sc);
+
+ /* Enable desired port */
+ wi_cmd(sc, WI_CMD_ENABLE|sc->wi_portnum, 0);
+
+ if (wi_alloc_nicmem(sc, 1518 + sizeof(struct wi_frame) + 8, &id))
+ printf("wi%d: mem allocation failed...\n", sc->wi_unit);
+ sc->wi_tx_data_id = id;
+
+ if (wi_alloc_nicmem(sc, 1518 + sizeof(struct wi_frame) + 8, &id))
+ printf("wi%d: mem allocation failed...\n", sc->wi_unit);
+ sc->wi_tx_mgmt_id = id;
+
+ /* enable interrupts */
+ CSR_WRITE_2(sc, WI_INT_EN, WI_INTRS);
+
+ splx(s);
+
+ ifp->if_flags |= IFF_RUNNING;
+ ifp->if_flags &= ~IFF_OACTIVE;
+
+ sc->wi_stat_ch = timeout(wi_inquire, sc, hz * 60);
+
+ return;
+}
+
+static void wi_start(ifp)
+ struct ifnet *ifp;
+{
+ struct wi_softc *sc;
+ struct mbuf *m0;
+ struct wi_frame tx_frame;
+ struct ether_header *eh;
+ int id;
+
+ sc = ifp->if_softc;
+
+ if (sc->wi_gone)
+ return;
+
+ if (ifp->if_flags & IFF_OACTIVE)
+ return;
+
+ IF_DEQUEUE(&ifp->if_snd, m0);
+ if (m0 == NULL)
+ return;
+
+ bzero((char *)&tx_frame, sizeof(tx_frame));
+ id = sc->wi_tx_data_id;
+ eh = mtod(m0, struct ether_header *);
+
+ /*
+ * Use RFC1042 encoding for IP and ARP datagrames,
+ * 802.3 for anything else.
+ */
+ if (ntohs(eh->ether_type) == ETHERTYPE_IP ||
+ ntohs(eh->ether_type) == ETHERTYPE_ARP ||
+ ntohs(eh->ether_type) == ETHERTYPE_REVARP) {
+ bcopy((char *)&eh->ether_dhost,
+ (char *)&tx_frame.wi_addr1, ETHER_ADDR_LEN);
+ bcopy((char *)&eh->ether_shost,
+ (char *)&tx_frame.wi_addr2, ETHER_ADDR_LEN);
+ bcopy((char *)&eh->ether_dhost,
+ (char *)&tx_frame.wi_dst_addr, ETHER_ADDR_LEN);
+ bcopy((char *)&eh->ether_shost,
+ (char *)&tx_frame.wi_src_addr, ETHER_ADDR_LEN);
+
+ tx_frame.wi_dat_len = m0->m_pkthdr.len - WI_SNAPHDR_LEN;
+ tx_frame.wi_frame_ctl = WI_FTYPE_DATA;
+ tx_frame.wi_dat[0] = htons(WI_SNAP_WORD0);
+ tx_frame.wi_dat[1] = htons(WI_SNAP_WORD1);
+ tx_frame.wi_len = htons(m0->m_pkthdr.len - WI_SNAPHDR_LEN);
+ tx_frame.wi_type = eh->ether_type;
+
+ m_copydata(m0, sizeof(struct ether_header),
+ m0->m_pkthdr.len - sizeof(struct ether_header),
+ (caddr_t)&sc->wi_txbuf);
+
+ wi_write_data(sc, id, 0, (caddr_t)&tx_frame,
+ sizeof(struct wi_frame));
+ wi_write_data(sc, id, WI_802_11_OFFSET, (caddr_t)&sc->wi_txbuf,
+ (m0->m_pkthdr.len - sizeof(struct ether_header)) + 2);
+ } else {
+ tx_frame.wi_dat_len = m0->m_pkthdr.len;
+
+ m_copydata(m0, 0, m0->m_pkthdr.len, (caddr_t)&sc->wi_txbuf);
+
+ wi_write_data(sc, id, 0, (caddr_t)&tx_frame,
+ sizeof(struct wi_frame));
+ wi_write_data(sc, id, WI_802_3_OFFSET, (caddr_t)&sc->wi_txbuf,
+ m0->m_pkthdr.len + 2);
+ }
+
+#if NBPFILTER > 0
+ /*
+ * If there's a BPF listner, bounce a copy of
+ * this frame to him.
+ */
+ if (ifp->if_bpf)
+ bpf_mtap(ifp, m0);
+#endif
+
+ m_freem(m0);
+
+ if (wi_cmd(sc, WI_CMD_TX|WI_RECLAIM, id))
+ printf("wi%d: xmit failed\n", sc->wi_unit);
+
+ ifp->if_flags |= IFF_OACTIVE;
+
+ /*
+ * Set a timeout in case the chip goes out to lunch.
+ */
+ ifp->if_timer = 5;
+
+ return;
+}
+
+static int wi_mgmt_xmit(sc, data, len)
+ struct wi_softc *sc;
+ caddr_t data;
+ int len;
+{
+ struct wi_frame tx_frame;
+ int id;
+ struct wi_80211_hdr *hdr;
+ caddr_t dptr;
+
+ if (sc->wi_gone)
+ return(ENODEV);
+
+ hdr = (struct wi_80211_hdr *)data;
+ dptr = data + sizeof(struct wi_80211_hdr);
+
+ bzero((char *)&tx_frame, sizeof(tx_frame));
+ id = sc->wi_tx_mgmt_id;
+
+ bcopy((char *)hdr, (char *)&tx_frame.wi_frame_ctl,
+ sizeof(struct wi_80211_hdr));
+
+ tx_frame.wi_dat_len = len - WI_SNAPHDR_LEN;
+ tx_frame.wi_len = htons(len - WI_SNAPHDR_LEN);
+
+ wi_write_data(sc, id, 0, (caddr_t)&tx_frame, sizeof(struct wi_frame));
+ wi_write_data(sc, id, WI_802_11_OFFSET_RAW, dptr,
+ (len - sizeof(struct wi_80211_hdr)) + 2);
+
+ if (wi_cmd(sc, WI_CMD_TX|WI_RECLAIM, id)) {
+ printf("wi%d: xmit failed\n", sc->wi_unit);
+ return(EIO);
+ }
+
+ return(0);
+}
+
+static void wi_stop(sc)
+ struct wi_softc *sc;
+{
+ struct ifnet *ifp;
+
+ if (sc->wi_gone)
+ return;
+
+ ifp = &sc->arpcom.ac_if;
+
+ CSR_WRITE_2(sc, WI_INT_EN, 0);
+ wi_cmd(sc, WI_CMD_DISABLE|sc->wi_portnum, 0);
+
+ untimeout(wi_inquire, sc, sc->wi_stat_ch);
+
+ ifp->if_flags &= ~(IFF_RUNNING|IFF_OACTIVE);
+
+ return;
+}
+
+static void wi_watchdog(ifp)
+ struct ifnet *ifp;
+{
+ struct wi_softc *sc;
+
+ sc = ifp->if_softc;
+
+ printf("wi%d: device timeout\n", sc->wi_unit);
+
+ wi_init(sc);
+
+ ifp->if_oerrors++;
+
+ return;
+}
+
+static void wi_shutdown(howto, arg)
+ int howto;
+ void *arg;
+{
+ struct wi_softc *sc;
+
+ sc = arg;
+ wi_stop(sc);
+
+ return;
+}
OpenPOWER on IntegriCloud