2489 lines
55 KiB
C
2489 lines
55 KiB
C
/*-
|
|
* Copyright (c) 1999 The NetBSD Foundation, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to The NetBSD Foundation
|
|
* by Bill Sommerfeld
|
|
*
|
|
* 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 the NetBSD
|
|
* Foundation, Inc. and its contributors.
|
|
* 4. Neither the name of The NetBSD Foundation nor the names of its
|
|
* contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
|
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
/*
|
|
* Driver for AMD 802.11 firmware.
|
|
* Uses am79c930 chip driver to talk to firmware running on the am79c930.
|
|
*
|
|
* More-or-less a generic ethernet-like if driver, with 802.11 gorp added.
|
|
*/
|
|
|
|
/*
|
|
* todo:
|
|
* - don't print "management timeout"/"assoc with" for normal
|
|
* association keepalive goo.
|
|
* - multicast filter.
|
|
* - use cluster mbufs on rx?
|
|
* - fix device reset so it's more likely to work
|
|
* - allow i/o-space-only access to device (over in am79c930.c)
|
|
* - show status goo through ifmedia.
|
|
*
|
|
* more todo:
|
|
* - deal with more 802.11 frames.
|
|
* - send reassoc request
|
|
* - deal with reassoc response
|
|
* - send/deal with disassociation
|
|
* - deal with "full" access points (no room for me).
|
|
* - power save mode
|
|
* - if no traffic, let ourselves gracefully desync?
|
|
*
|
|
* later:
|
|
* - SSID preferences
|
|
* - need ioctls for poking at the MIBs
|
|
* - implement ad-hoc mode (including bss creation).
|
|
* - decide when to do "ad hoc" vs. infrastructure mode (IFF_LINK flags?)
|
|
* (focus on inf. mode since that will be needed for ietf)
|
|
* - deal with DH vs. FH versions of the card
|
|
* - deal with faster cards (2mb/s)
|
|
* - ?WEP goo (mmm, rc4) (it looks not particularly useful).
|
|
* - ifmedia revision.
|
|
* - common 802.11 mibish things.
|
|
* - common 802.11 media layer.
|
|
*/
|
|
|
|
#include "opt_inet.h"
|
|
#include "opt_ns.h"
|
|
#include "bpfilter.h"
|
|
#include "rnd.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/mbuf.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/syslog.h>
|
|
#include <sys/select.h>
|
|
#include <sys/device.h>
|
|
#if NRND > 0
|
|
#include <sys/rnd.h>
|
|
#endif
|
|
|
|
#include <net/if.h>
|
|
#include <net/if_dl.h>
|
|
#include <net/if_ether.h>
|
|
#include <net/if_media.h>
|
|
|
|
#ifdef INET
|
|
#include <netinet/in.h>
|
|
#include <netinet/in_systm.h>
|
|
#include <netinet/in_var.h>
|
|
#include <netinet/ip.h>
|
|
#include <netinet/if_inarp.h>
|
|
#endif
|
|
|
|
#ifdef NS
|
|
#include <netns/ns.h>
|
|
#include <netns/ns_if.h>
|
|
#endif
|
|
|
|
#if NBPFILTER > 0
|
|
#include <net/bpf.h>
|
|
#include <net/bpfdesc.h>
|
|
#endif
|
|
|
|
#include <machine/cpu.h>
|
|
#include <machine/bus.h>
|
|
#include <machine/intr.h>
|
|
|
|
#include <dev/ic/am79c930reg.h>
|
|
#include <dev/ic/am79c930var.h>
|
|
#include <dev/ic/awireg.h>
|
|
#include <dev/ic/awivar.h>
|
|
|
|
void awi_insane __P((struct awi_softc *sc));
|
|
int awi_intlock __P((struct awi_softc *sc));
|
|
void awi_intunlock __P((struct awi_softc *sc));
|
|
void awi_intrinit __P((struct awi_softc *sc));
|
|
u_int8_t awi_read_intst __P((struct awi_softc *sc));
|
|
void awi_stop __P((struct awi_softc *sc));
|
|
void awi_init __P((struct awi_softc *sc));
|
|
void awi_set_mc __P((struct awi_softc *sc));
|
|
void awi_rxint __P((struct awi_softc *));
|
|
void awi_txint __P((struct awi_softc *));
|
|
void awi_tx_packet __P((struct awi_softc *, int, struct mbuf *));
|
|
|
|
void awi_rcv __P((struct awi_softc *, struct mbuf *, u_int32_t, u_int8_t));
|
|
void awi_rcv_mgt __P((struct awi_softc *, struct mbuf *, u_int32_t, u_int8_t));
|
|
void awi_rcv_data __P((struct awi_softc *, struct mbuf *));
|
|
void awi_rcv_ctl __P((struct awi_softc *, struct mbuf *));
|
|
|
|
int awi_enable __P((struct awi_softc *sc));
|
|
void awi_disable __P((struct awi_softc *sc));
|
|
|
|
void awi_zero __P((struct awi_softc *, u_int32_t, u_int32_t));
|
|
|
|
void awi_cmd __P((struct awi_softc *, u_int8_t));
|
|
void awi_cmd_test_if __P((struct awi_softc *));
|
|
void awi_cmd_get_mib __P((struct awi_softc *sc, u_int8_t, u_int8_t, u_int8_t));
|
|
void awi_cmd_txinit __P((struct awi_softc *sc));
|
|
void awi_cmd_scan __P((struct awi_softc *sc));
|
|
void awi_scan_next __P((struct awi_softc *sc));
|
|
void awi_try_sync __P((struct awi_softc *sc));
|
|
void awi_cmd_set_ss __P((struct awi_softc *sc));
|
|
void awi_cmd_set_promisc __P((struct awi_softc *sc));
|
|
void awi_cmd_set_allmulti __P((struct awi_softc *sc));
|
|
void awi_cmd_set_infra __P((struct awi_softc *sc));
|
|
void awi_cmd_set_notap __P((struct awi_softc *sc));
|
|
void awi_cmd_get_myaddr __P((struct awi_softc *sc));
|
|
|
|
|
|
void awi_cmd_scan_done __P((struct awi_softc *sc, u_int8_t));
|
|
void awi_cmd_sync_done __P((struct awi_softc *sc, u_int8_t));
|
|
void awi_cmd_set_ss_done __P((struct awi_softc *sc, u_int8_t));
|
|
void awi_cmd_set_allmulti_done __P((struct awi_softc *sc, u_int8_t));
|
|
void awi_cmd_set_promisc_done __P((struct awi_softc *sc, u_int8_t));
|
|
void awi_cmd_set_infra_done __P((struct awi_softc *sc, u_int8_t));
|
|
void awi_cmd_set_notap_done __P((struct awi_softc *sc, u_int8_t));
|
|
void awi_cmd_get_myaddr_done __P((struct awi_softc *sc, u_int8_t));
|
|
|
|
void awi_reset __P((struct awi_softc *));
|
|
void awi_init_1 __P((struct awi_softc *));
|
|
void awi_init_2 __P((struct awi_softc *, u_int8_t));
|
|
void awi_mibdump __P((struct awi_softc *, u_int8_t));
|
|
void awi_init_read_bufptrs_done __P((struct awi_softc *, u_int8_t));
|
|
void awi_init_4 __P((struct awi_softc *, u_int8_t));
|
|
void awi_init_5 __P((struct awi_softc *, u_int8_t));
|
|
void awi_init_6 __P((struct awi_softc *, u_int8_t));
|
|
void awi_running __P((struct awi_softc *));
|
|
|
|
void awi_init_txdescr __P((struct awi_softc *));
|
|
void awi_init_txd __P((struct awi_softc *, int, int, int, int));
|
|
|
|
void awi_watchdog __P((struct ifnet *));
|
|
void awi_start __P((struct ifnet *));
|
|
int awi_ioctl __P((struct ifnet *, u_long, caddr_t));
|
|
void awi_dump_rxchain __P((struct awi_softc *, char *, u_int32_t *));
|
|
|
|
void awi_send_frame __P((struct awi_softc *, struct mbuf *));
|
|
void awi_send_authreq __P((struct awi_softc *));
|
|
void awi_send_assocreq __P((struct awi_softc *));
|
|
void awi_parse_tlv __P((u_int8_t *base, u_int8_t *end, u_int8_t **vals, u_int8_t *lens, size_t nattr));
|
|
|
|
u_int8_t *awi_add_rates __P((struct awi_softc *, struct mbuf *, u_int8_t *));
|
|
u_int8_t *awi_add_ssid __P((struct awi_softc *, struct mbuf *, u_int8_t *));
|
|
void * awi_init_hdr __P((struct awi_softc *, struct mbuf *, int, int));
|
|
|
|
void awi_hexdump __P((char *tag, u_int8_t *data, int len));
|
|
void awi_card_hexdump __P((struct awi_softc *, char *tag, u_int32_t offset, int len));
|
|
|
|
int awi_drop_output __P((struct ifnet *, struct mbuf *,
|
|
struct sockaddr *, struct rtentry *));
|
|
void awi_drop_input __P((struct ifnet *, struct mbuf *));
|
|
struct mbuf *awi_output_kludge __P((struct awi_softc *, struct mbuf *));
|
|
void awi_set_timer __P((struct awi_softc *));
|
|
void awi_restart_scan __P((struct awi_softc *));
|
|
|
|
static const u_int8_t snap_magic[] = { 0xaa, 0xaa, 3, 0, 0, 0 };
|
|
|
|
/*
|
|
* attach (called by bus-specific front end)
|
|
*
|
|
* look for banner message
|
|
* wait for selftests to complete (up to 2s??? eeee.)
|
|
* (do this with a timeout!!??!!)
|
|
* on timeout completion:
|
|
* issue test_interface command.
|
|
* get_mib command to locate TX buffer.
|
|
* set_mib command to set any non-default variables.
|
|
* init tx first.
|
|
* init rx second with enable receiver command
|
|
*
|
|
* mac mgmt portion executes sync command to start BSS
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* device shutdown routine.
|
|
*/
|
|
|
|
/*
|
|
* device appears to be insane. rather than hanging, whap device upside
|
|
* the head on next timeout.
|
|
*/
|
|
|
|
void
|
|
awi_insane(sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
struct ifnet *ifp = sc->sc_ifp;
|
|
printf("%s: device timeout\n", sc->sc_dev.dv_xname);
|
|
|
|
/* whap device on next timeout. */
|
|
sc->sc_state = AWI_ST_INSANE;
|
|
ifp->if_timer = 1;
|
|
}
|
|
|
|
void
|
|
awi_set_timer (sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
if (sc->sc_tx_timer || sc->sc_scan_timer ||
|
|
sc->sc_mgt_timer || sc->sc_cmd_timer)
|
|
sc->sc_ifp->if_timer = 1;
|
|
}
|
|
|
|
|
|
/*
|
|
* Copy m0 into the given TX descriptor and give the descriptor to the
|
|
* device so it starts transmiting..
|
|
*/
|
|
|
|
void
|
|
awi_tx_packet (sc, txd, m0)
|
|
struct awi_softc *sc;
|
|
int txd;
|
|
struct mbuf *m0;
|
|
{
|
|
u_int32_t frame = sc->sc_txd[txd].frame;
|
|
u_int32_t len = sc->sc_txd[txd].len;
|
|
struct mbuf *m;
|
|
|
|
for (m = m0; m != NULL; m = m->m_next) {
|
|
u_int32_t nmove;
|
|
nmove = min(len, m->m_len);
|
|
awi_write_bytes (sc, frame, m->m_data, nmove);
|
|
if (nmove != m->m_len) {
|
|
printf("%s: large frame truncated\n",
|
|
sc->sc_dev.dv_xname);
|
|
break;
|
|
}
|
|
frame += nmove;
|
|
len -= nmove;
|
|
}
|
|
|
|
awi_init_txd (sc,
|
|
txd,
|
|
AWI_TXD_ST_OWN,
|
|
frame - sc->sc_txd[txd].frame,
|
|
AWI_RATE_1MBIT);
|
|
|
|
#if 0
|
|
awi_card_hexdump (sc, "txd to go", sc->sc_txd[txd].descr,
|
|
AWI_TXD_SIZE);
|
|
#endif
|
|
|
|
}
|
|
|
|
/*
|
|
* XXX KLUDGE XXX
|
|
*
|
|
* Convert ethernet-formatted frame into 802.11 data frame
|
|
* for infrastructure mode.
|
|
*/
|
|
|
|
struct mbuf *
|
|
awi_output_kludge (sc, m0)
|
|
struct awi_softc *sc;
|
|
struct mbuf *m0;
|
|
{
|
|
u_int8_t *framehdr;
|
|
u_int8_t *llchdr;
|
|
u_int8_t dstaddr[ETHER_ADDR_LEN];
|
|
struct awi_mac_header *amhdr;
|
|
u_int16_t etype;
|
|
struct ether_header *eh = mtod(m0, struct ether_header *);
|
|
|
|
#if 0
|
|
awi_hexdump("etherframe", m0->m_data, m0->m_len);
|
|
#endif
|
|
|
|
memcpy(dstaddr, eh->ether_dhost, sizeof(dstaddr));
|
|
etype = eh->ether_type;
|
|
|
|
m_adj(m0, sizeof(struct ether_header));
|
|
|
|
M_PREPEND(m0, sizeof(struct awi_mac_header) + 8, M_DONTWAIT);
|
|
|
|
if (m0 == NULL) {
|
|
printf("oops, prepend failed\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (m0->m_len < 32) {
|
|
printf("oops, prepend only left %d bytes\n", m0->m_len);
|
|
m_freem(m0);
|
|
return NULL;
|
|
}
|
|
framehdr = mtod(m0, u_int8_t *);
|
|
amhdr = mtod(m0, struct awi_mac_header *);
|
|
|
|
amhdr->awi_fc = IEEEWL_FC_VERS |
|
|
IEEEWL_FC_TYPE_DATA<<IEEEWL_FC_TYPE_SHIFT;
|
|
amhdr->awi_f2 = IEEEWL_FC2_TODS;
|
|
|
|
memcpy(amhdr->awi_addr3, dstaddr, ETHER_ADDR_LEN); /* ether DST */
|
|
memcpy(amhdr->awi_addr1, sc->sc_active_bss.bss_id, ETHER_ADDR_LEN);
|
|
memcpy(amhdr->awi_addr2, sc->sc_my_addr, ETHER_ADDR_LEN);
|
|
amhdr->awi_duration = 0;
|
|
amhdr->awi_seqctl = 0;
|
|
llchdr = (u_int8_t *) (amhdr + 1);
|
|
memcpy(llchdr, snap_magic, 6);
|
|
memcpy(llchdr+6, &etype, 2);
|
|
|
|
return m0;
|
|
}
|
|
/*
|
|
* device start routine
|
|
*
|
|
* loop while there are free tx buffer descriptors and mbufs in the queue:
|
|
* -> copy mbufs to tx buffer and free mbufs.
|
|
* -> mark txd as good to go (OWN bit set, all others clear)
|
|
*/
|
|
|
|
void
|
|
awi_start(ifp)
|
|
struct ifnet *ifp;
|
|
{
|
|
struct awi_softc *sc = ifp->if_softc;
|
|
struct mbuf *m0;
|
|
int opending;
|
|
|
|
if ((ifp->if_flags & IFF_RUNNING) == 0) {
|
|
printf("%s: start called while not running\n",
|
|
sc->sc_dev.dv_xname);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* loop through send queue, setting up tx descriptors
|
|
* until we either run out of stuff to send, or descriptors
|
|
* to send them in.
|
|
*/
|
|
opending = sc->sc_txpending;
|
|
|
|
while (sc->sc_txpending < sc->sc_ntxd) {
|
|
/*
|
|
* Grab a packet off the queue.
|
|
*/
|
|
IF_DEQUEUE (&sc->sc_mgtq, m0);
|
|
|
|
if (m0 == NULL) {
|
|
/* XXX defer sending if not synched yet? */
|
|
IF_DEQUEUE (&ifp->if_snd, m0);
|
|
if (m0 == NULL)
|
|
break;
|
|
#if NBPFILTER > 0
|
|
/*
|
|
* Pass packet to bpf if there is a listener.
|
|
*/
|
|
if (ifp->if_bpf)
|
|
bpf_mtap(ifp->if_bpf, m0);
|
|
#endif
|
|
/*
|
|
* We've got an ethernet-format frame.
|
|
* we need to mangle it into 802.11 form..
|
|
*/
|
|
m0 = awi_output_kludge(sc, m0);
|
|
if (m0 == NULL)
|
|
continue;
|
|
}
|
|
|
|
awi_tx_packet(sc, sc->sc_txnext, m0);
|
|
|
|
sc->sc_txpending++;
|
|
sc->sc_txnext = (sc->sc_txnext + 1) % sc->sc_ntxd;
|
|
|
|
m_freem(m0);
|
|
}
|
|
if (sc->sc_txpending >= sc->sc_ntxd) {
|
|
/* no more slots available.. */
|
|
ifp->if_flags |= IFF_OACTIVE;
|
|
}
|
|
if (sc->sc_txpending != opending) {
|
|
/* set watchdog timer in case unit flakes out */
|
|
if (sc->sc_tx_timer == 0)
|
|
sc->sc_tx_timer = 5;
|
|
awi_set_timer(sc);
|
|
}
|
|
}
|
|
|
|
int
|
|
awi_enable(sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
if (sc->sc_enabled == 0) {
|
|
if ((sc->sc_enable != NULL) && ((*sc->sc_enable)(sc) != 0)) {
|
|
printf("%s: device enable failed\n",
|
|
sc->sc_dev.dv_xname);
|
|
return (EIO);
|
|
}
|
|
awi_init(sc);
|
|
}
|
|
sc->sc_enabled = 1;
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
awi_disable(sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
if (sc->sc_enabled != 0 && sc->sc_disable != NULL) {
|
|
(*sc->sc_disable)(sc);
|
|
sc->sc_enabled = 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
int
|
|
awi_intlock(sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
int i, j;
|
|
u_int8_t lockout;
|
|
|
|
DELAY(5);
|
|
for (j=0; j<10; j++) {
|
|
for (i=0; i<AWI_LOCKOUT_SPIN; i++) {
|
|
lockout = awi_read_1(sc, AWI_LOCKOUT_HOST);
|
|
if (!lockout)
|
|
break;
|
|
DELAY(5);
|
|
}
|
|
if (lockout)
|
|
break;
|
|
awi_write_1 (sc, AWI_LOCKOUT_MAC, 1);
|
|
lockout = awi_read_1(sc, AWI_LOCKOUT_HOST);
|
|
|
|
if (!lockout)
|
|
break;
|
|
/* oops, lost the race.. try again */
|
|
awi_write_1 (sc, AWI_LOCKOUT_MAC, 0);
|
|
}
|
|
|
|
if (lockout) {
|
|
awi_insane(sc);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
awi_intunlock(sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
awi_write_1 (sc, AWI_LOCKOUT_MAC, 0);
|
|
}
|
|
|
|
void
|
|
awi_intrinit(sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
u_int8_t intmask;
|
|
|
|
am79c930_gcr_setbits(&sc->sc_chip, AM79C930_GCR_ENECINT);
|
|
|
|
intmask = AWI_INT_GROGGY|AWI_INT_SCAN_CMPLT|
|
|
AWI_INT_TX|AWI_INT_RX|AWI_INT_CMD;
|
|
|
|
intmask = ~intmask;
|
|
|
|
if (!awi_intlock(sc))
|
|
return;
|
|
|
|
awi_write_1(sc, AWI_INTMASK, intmask);
|
|
awi_write_1(sc, AWI_INTMASK2, 0);
|
|
|
|
awi_intunlock(sc);
|
|
}
|
|
|
|
void awi_hexdump (char *tag, u_int8_t *data, int len)
|
|
{
|
|
int i;
|
|
|
|
printf("%s:", tag);
|
|
for (i=0; i<len; i++) {
|
|
printf(" %02x", data[i]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
void awi_card_hexdump (sc, tag, offset, len)
|
|
struct awi_softc *sc;
|
|
char *tag;
|
|
u_int32_t offset;
|
|
int len;
|
|
{
|
|
int i;
|
|
|
|
printf("%s:", tag);
|
|
for (i=0; i<len; i++) {
|
|
printf(" %02x", awi_read_1(sc, offset+i));
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
u_int8_t
|
|
awi_read_intst(sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
u_int8_t state;
|
|
|
|
if (!awi_intlock(sc))
|
|
return 0;
|
|
|
|
/* we have int lock.. */
|
|
|
|
state = awi_read_1 (sc, AWI_INTSTAT);
|
|
awi_write_1(sc, AWI_INTSTAT, 0);
|
|
|
|
awi_intunlock(sc);
|
|
|
|
return state;
|
|
}
|
|
|
|
|
|
void
|
|
awi_parse_tlv (u_int8_t *base, u_int8_t *end, u_int8_t **vals, u_int8_t *lens, size_t nattr)
|
|
{
|
|
u_int8_t tag, len;
|
|
|
|
int i;
|
|
|
|
for (i=0; i<nattr; i++) {
|
|
vals[i] = NULL;
|
|
lens[i] = 0;
|
|
}
|
|
|
|
while (base < end) {
|
|
tag = base[0];
|
|
len = base[1];
|
|
|
|
base += 2;
|
|
|
|
if (tag < nattr) {
|
|
lens[tag] = len;
|
|
vals[tag] = base;
|
|
}
|
|
base += len;
|
|
}
|
|
}
|
|
|
|
void
|
|
awi_send_frame (sc, m)
|
|
struct awi_softc *sc;
|
|
struct mbuf *m;
|
|
{
|
|
IF_ENQUEUE(&sc->sc_mgtq, m);
|
|
|
|
awi_start(sc->sc_ifp);
|
|
}
|
|
|
|
void *
|
|
awi_init_hdr (sc, m, f1, f2)
|
|
struct awi_softc *sc;
|
|
struct mbuf *m;
|
|
int f1;
|
|
int f2;
|
|
{
|
|
struct awi_mac_header *amhp;
|
|
|
|
/*
|
|
* initialize 802.11 mac header in mbuf, return pointer to next byte..
|
|
*/
|
|
|
|
amhp = mtod(m, struct awi_mac_header *);
|
|
|
|
amhp->awi_fc = f1;
|
|
amhp->awi_f2 = f2;
|
|
amhp->awi_duration = 0;
|
|
|
|
memcpy(amhp->awi_addr1, sc->sc_active_bss.bss_id, ETHER_ADDR_LEN);
|
|
memcpy(amhp->awi_addr2, sc->sc_my_addr, ETHER_ADDR_LEN);
|
|
memcpy(amhp->awi_addr3, sc->sc_active_bss.bss_id, ETHER_ADDR_LEN);
|
|
|
|
amhp->awi_seqctl = 0;
|
|
|
|
return amhp+1;
|
|
}
|
|
|
|
|
|
|
|
u_int8_t *
|
|
awi_add_rates (sc, m, ptr)
|
|
struct awi_softc *sc;
|
|
struct mbuf *m;
|
|
u_int8_t *ptr;
|
|
{
|
|
*ptr++ = 1; /* XXX */
|
|
*ptr++ = 1; /* XXX */
|
|
*ptr++ = 0x82; /* XXX */
|
|
return ptr;
|
|
}
|
|
|
|
u_int8_t *
|
|
awi_add_ssid (sc, m, ptr)
|
|
struct awi_softc *sc;
|
|
struct mbuf *m;
|
|
u_int8_t *ptr;
|
|
{
|
|
int len = sc->sc_active_bss.sslen;
|
|
*ptr++ = 0; /* XXX */
|
|
*ptr++ = len;
|
|
memcpy(ptr, sc->sc_active_bss.ssid, len);
|
|
ptr += len;
|
|
return ptr;
|
|
}
|
|
|
|
|
|
|
|
void
|
|
awi_send_authreq (sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
struct mbuf *m;
|
|
struct awi_auth_hdr *amahp;
|
|
u_int8_t *tlvptr;
|
|
|
|
MGETHDR(m, M_DONTWAIT, MT_DATA);
|
|
|
|
/*
|
|
* form an "association request" message.
|
|
*/
|
|
|
|
/*
|
|
auth alg number. 2 bytes. = 0
|
|
auth txn seq number = 2 bytes = 1
|
|
status code = 2 bytes = 0
|
|
challenge text (not present)
|
|
*/
|
|
|
|
if (m == 0)
|
|
return; /* we'll try again later.. */
|
|
|
|
amahp = awi_init_hdr (sc, m,
|
|
(IEEEWL_FC_VERS |
|
|
(IEEEWL_FC_TYPE_MGT << IEEEWL_FC_TYPE_SHIFT) |
|
|
(IEEEWL_SUBTYPE_AUTH << IEEEWL_FC_SUBTYPE_SHIFT)),
|
|
0);
|
|
|
|
amahp->awi_algno[0] = 0;
|
|
amahp->awi_algno[1] = 0;
|
|
amahp->awi_seqno[0] = 1;
|
|
amahp->awi_seqno[1] = 0;
|
|
amahp->awi_status[0] = 0;
|
|
amahp->awi_status[1] = 0;
|
|
|
|
/*
|
|
* form an "authentication" message.
|
|
*/
|
|
|
|
tlvptr = (u_int8_t *)(amahp+1);
|
|
|
|
tlvptr = awi_add_ssid(sc, m, tlvptr);
|
|
tlvptr = awi_add_rates(sc, m, tlvptr);
|
|
|
|
m->m_len = tlvptr - mtod(m, u_int8_t *);
|
|
|
|
awi_send_frame(sc, m);
|
|
|
|
sc->sc_mgt_timer = 2;
|
|
awi_set_timer(sc);
|
|
}
|
|
|
|
void
|
|
awi_send_assocreq (sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
struct mbuf *m;
|
|
struct awi_assoc_hdr *amahp;
|
|
u_int8_t *tlvptr;
|
|
|
|
MGETHDR(m, M_DONTWAIT, MT_DATA);
|
|
|
|
/*
|
|
* form an "association request" message.
|
|
*/
|
|
|
|
if (m == 0)
|
|
return; /* we'll try again later.. */
|
|
|
|
/*
|
|
* cap info (2 bytes)
|
|
* listen interval (2 bytes)
|
|
* ssid (variable)
|
|
* supported rates (variable)
|
|
*/
|
|
|
|
amahp = awi_init_hdr (sc, m,
|
|
IEEEWL_FC_TYPE_MGT, IEEEWL_SUBTYPE_ASSOCREQ);
|
|
|
|
amahp->awi_cap_info[0] = 4; /* XXX magic (CF-pollable) */
|
|
amahp->awi_cap_info[1] = 0;
|
|
amahp->awi_li[0] = 1;
|
|
amahp->awi_li[1] = 0;
|
|
|
|
tlvptr = (u_int8_t *)(amahp+1);
|
|
|
|
tlvptr = awi_add_ssid(sc, m, tlvptr);
|
|
tlvptr = awi_add_rates(sc, m, tlvptr);
|
|
|
|
m->m_len = tlvptr - mtod(m, u_int8_t *);
|
|
|
|
awi_send_frame(sc, m);
|
|
|
|
sc->sc_mgt_timer = 2;
|
|
awi_set_timer(sc);
|
|
}
|
|
|
|
#if 0
|
|
void
|
|
awi_send_reassocreq (sc)
|
|
{
|
|
|
|
/*
|
|
* form an "reassociation request" message.
|
|
*/
|
|
|
|
/* 2 bytes frame control
|
|
00100000 00000000
|
|
2 bytes goo
|
|
00000000 00000000
|
|
address 1: bssid
|
|
address 2: my address
|
|
address 3: bssid
|
|
2 bytes seq/ctl
|
|
00000000 00000000
|
|
|
|
cap info (2 bytes)
|
|
listen interval (2 bytes)
|
|
current ap address (6 bytes)
|
|
ssid (variable)
|
|
supported rates (va
|
|
*/
|
|
}
|
|
|
|
#endif
|
|
|
|
void
|
|
awi_rcv_ctl (sc, m)
|
|
struct awi_softc *sc;
|
|
struct mbuf *m;
|
|
{
|
|
printf("%s: ctl\n", sc->sc_dev.dv_xname);
|
|
}
|
|
|
|
void
|
|
awi_rcv_data (sc, m)
|
|
struct awi_softc *sc;
|
|
struct mbuf *m;
|
|
{
|
|
struct ifnet *ifp = sc->sc_ifp;
|
|
u_int8_t *llc;
|
|
u_int8_t *to, *from;
|
|
struct awi_mac_header *amhp;
|
|
|
|
sc->sc_scan_timer = 2; /* user data is as good
|
|
as a beacon as a keepalive.. */
|
|
|
|
amhp = mtod(m, struct awi_mac_header *);
|
|
|
|
/*
|
|
* we have: 4 bytes useless goo.
|
|
* 3 x 6 bytes MAC addresses.
|
|
* 2 bytes goo.
|
|
* 802.x LLC header, SNAP header, and data.
|
|
*
|
|
* for now, we fake up a "normal" ethernet header and feed
|
|
* this to the appropriate input routine.
|
|
*/
|
|
|
|
llc = (u_int8_t *)(amhp+1);
|
|
|
|
if (amhp->awi_f2 & IEEEWL_FC2_TODS) {
|
|
printf("drop packet to DS\n");
|
|
goto drop;
|
|
}
|
|
|
|
to = amhp->awi_addr1;
|
|
if (amhp->awi_f2 & IEEEWL_FC2_FROMDS)
|
|
from = amhp->awi_addr3;
|
|
else
|
|
from = amhp->awi_addr2;
|
|
if (memcmp (llc, snap_magic, 6) != 0)
|
|
goto drop;
|
|
|
|
/* XXX overwrite llc with "from" address */
|
|
/* XXX overwrite llc-6 with "to" address */
|
|
memcpy(llc, from, ETHER_ADDR_LEN);
|
|
memcpy(llc-6, to, ETHER_ADDR_LEN);
|
|
|
|
m_adj(m, sizeof(struct awi_mac_header) + sizeof(struct awi_llc_header)
|
|
- sizeof(struct ether_header));
|
|
|
|
#if NBPFILTER > 0
|
|
/*
|
|
* Pass packet to bpf if there is a listener.
|
|
*/
|
|
if (ifp->if_bpf)
|
|
bpf_mtap(ifp->if_bpf, m);
|
|
#endif
|
|
|
|
#if __NetBSD_Version__ > 104010000
|
|
m->m_flags |= M_HASFCS;
|
|
(*ifp->if_input)(ifp, m);
|
|
#else
|
|
{
|
|
struct ether_header *eh;
|
|
eh = mtod(m, struct ether_header *);
|
|
m_adj(m, sizeof(*eh));
|
|
m_adj(m, -ETHER_CRC_LEN);
|
|
ether_input(ifp, eh, m);
|
|
}
|
|
#endif
|
|
return;
|
|
drop:
|
|
m_freem(m);
|
|
}
|
|
|
|
void
|
|
awi_rcv_mgt (sc, m, rxts, rssi)
|
|
struct awi_softc *sc;
|
|
struct mbuf *m;
|
|
u_int32_t rxts;
|
|
u_int8_t rssi;
|
|
{
|
|
u_int8_t subtype;
|
|
u_int8_t *framehdr, *mgthdr, *end, *timestamp;
|
|
struct awi_auth_hdr *auhp;
|
|
|
|
#define IEEEWL_MGT_NATTR 10 /* XXX */
|
|
u_int8_t *attr[IEEEWL_MGT_NATTR];
|
|
u_int8_t attrlen[IEEEWL_MGT_NATTR];
|
|
u_int8_t *addr1, *addr2, *addr3;
|
|
u_int8_t *sa, *da, *bss;
|
|
|
|
framehdr = mtod(m, u_int8_t *);
|
|
|
|
/*
|
|
* mgt frame:
|
|
* 2 bytes frame goo
|
|
* 2 bytes duration
|
|
* 6 bytes a1
|
|
* 6 bytes a2
|
|
* 6 bytes a3
|
|
* 2 bytes seq control.
|
|
* --
|
|
* 24 bytes goo.
|
|
*/
|
|
|
|
subtype = (framehdr[IEEEWL_FC] & IEEEWL_FC_SUBTYPE_MASK)
|
|
>> IEEEWL_FC_SUBTYPE_SHIFT;
|
|
|
|
addr1 = framehdr + 4; /* XXX */
|
|
addr2 = addr1+ETHER_ADDR_LEN;
|
|
addr3 = addr2+ETHER_ADDR_LEN;
|
|
|
|
/* XXX look at to/from DS bits here!! */
|
|
da = addr1;
|
|
sa = addr3;
|
|
bss = addr2;
|
|
|
|
framehdr = mtod(m, u_int8_t *);
|
|
end = framehdr + m->m_len;
|
|
end -= 4; /* trim TLV */
|
|
|
|
mgthdr = framehdr + 24; /* XXX magic */
|
|
|
|
switch (subtype) {
|
|
|
|
case IEEEWL_SUBTYPE_ASSOCRESP:
|
|
/*
|
|
* this acknowledges that the AP will be forwarding traffic
|
|
* for us..
|
|
*
|
|
* contains:
|
|
* cap info
|
|
* status code
|
|
* AId
|
|
* supported rates.
|
|
*/
|
|
#if 0
|
|
printf("assoc_resp\n");
|
|
/* XXX should look in "status" of received message.. */
|
|
#endif
|
|
awi_drvstate (sc, AWI_DRV_INFASSOC);
|
|
sc->sc_state = AWI_ST_RUNNING;
|
|
sc->sc_mgt_timer = AWI_ASSOC_REFRESH;
|
|
awi_set_timer(sc);
|
|
if (sc->sc_new_bss) {
|
|
printf("%s: associated with %s, SSID: %s\n",
|
|
sc->sc_dev.dv_xname,
|
|
ether_sprintf(sc->sc_active_bss.bss_id),
|
|
sc->sc_active_bss.ssid);
|
|
sc->sc_new_bss = 0;
|
|
}
|
|
|
|
/* XXX set media status to "i see carrier" */
|
|
break;
|
|
|
|
case IEEEWL_SUBTYPE_REASSOCRESP:
|
|
/*
|
|
* this indicates that we've moved from one AP to another
|
|
* within the same DS.
|
|
*/
|
|
printf("reassoc_resp\n");
|
|
|
|
break;
|
|
|
|
case IEEEWL_SUBTYPE_PROBERESP:
|
|
/*
|
|
* 8 bytes timestamp.
|
|
* 2 bytes beacon intvl.
|
|
* 2 bytes cap info.
|
|
* then tlv data..
|
|
*/
|
|
timestamp = mgthdr;
|
|
|
|
/* now, into the tlv goo.. */
|
|
mgthdr += 12; /* XXX magic */
|
|
awi_parse_tlv (mgthdr, end, attr, attrlen, IEEEWL_MGT_NATTR);
|
|
|
|
if (attr[IEEEWL_MGT_TLV_SSID] &&
|
|
attr[IEEEWL_MGT_TLV_FHPARMS] &&
|
|
attrlen[IEEEWL_MGT_TLV_SSID] < AWI_SSID_LEN) {
|
|
struct awi_bss_binding *bp = NULL;
|
|
int i;
|
|
|
|
for (i=0; i< sc->sc_nbindings; i++) {
|
|
struct awi_bss_binding *bp1 =
|
|
&sc->sc_bindings[i];
|
|
if (memcmp(bp1->bss_id, bss, ETHER_ADDR_LEN) == 0) {
|
|
bp = bp1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bp == NULL && sc->sc_nbindings < NBND) {
|
|
bp = &sc->sc_bindings[sc->sc_nbindings++];
|
|
}
|
|
if (bp != NULL) {
|
|
u_int8_t *fhparms =
|
|
attr[IEEEWL_MGT_TLV_FHPARMS];
|
|
|
|
bp->sslen = attrlen[IEEEWL_MGT_TLV_SSID];
|
|
|
|
memcpy(bp->ssid, attr[IEEEWL_MGT_TLV_SSID],
|
|
bp->sslen);
|
|
bp->ssid[bp->sslen] = 0;
|
|
|
|
memcpy(bp->bss_id, bss, ETHER_ADDR_LEN);
|
|
|
|
/* XXX more magic numbers.. */
|
|
bp->dwell_time = fhparms[0] | (fhparms[1]<<8);
|
|
bp->chanset = fhparms[2];
|
|
bp->pattern = fhparms[3];
|
|
bp->index = fhparms[4];
|
|
bp->rssi = rssi;
|
|
bp->rxtime = rxts;
|
|
memcpy(bp->bss_timestamp, timestamp, 8);
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case IEEEWL_SUBTYPE_BEACON:
|
|
/*
|
|
* Note that AP is still alive so we don't have to go looking
|
|
* for one for a while.
|
|
*
|
|
* XXX Beacons from other AP's should be recorded for
|
|
* potential use if we lose this AP.. (also, may want
|
|
* to notice if rssi of new AP is significantly
|
|
* stronger than old one and jump ship..)
|
|
*/
|
|
if ((sc->sc_state >= AWI_ST_SYNCED) &&
|
|
(memcmp (addr2, sc->sc_active_bss.bss_id,
|
|
ETHER_ADDR_LEN) == 0)) {
|
|
sc->sc_scan_timer = 2;
|
|
awi_set_timer(sc);
|
|
}
|
|
|
|
break;
|
|
|
|
case IEEEWL_SUBTYPE_DISSOC:
|
|
printf("dissoc\n");
|
|
|
|
break;
|
|
|
|
case IEEEWL_SUBTYPE_AUTH:
|
|
/*
|
|
* woohoo! somebody likes us!
|
|
*/
|
|
|
|
auhp = (struct awi_auth_hdr *)mgthdr;
|
|
|
|
if ((auhp->awi_status[0] == 0) && (auhp->awi_status[1] == 0))
|
|
{
|
|
awi_drvstate (sc, AWI_DRV_INFAUTH);
|
|
sc->sc_state = AWI_ST_AUTHED;
|
|
awi_send_assocreq (sc);
|
|
}
|
|
break;
|
|
|
|
case IEEEWL_SUBTYPE_DEAUTH:
|
|
printf("%s: received deauth\n", sc->sc_dev.dv_xname);
|
|
sc->sc_state = AWI_ST_SYNCED;
|
|
sc->sc_new_bss = 1;
|
|
awi_send_authreq(sc);
|
|
break;
|
|
default:
|
|
printf("unk mgt subtype %x\n", subtype);
|
|
break;
|
|
}
|
|
m_freem(m); /* done.. */
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Do 802.11 receive processing. "m" contains a receive frame;
|
|
* rxts is the local receive timestamp
|
|
*/
|
|
|
|
void
|
|
awi_rcv (sc, m, rxts, rssi)
|
|
struct awi_softc *sc;
|
|
struct mbuf *m;
|
|
u_int32_t rxts;
|
|
u_int8_t rssi;
|
|
{
|
|
u_int8_t *framehdr;
|
|
u_int8_t framectl;
|
|
|
|
framehdr = mtod(m, u_int8_t *);
|
|
|
|
/*
|
|
* peek at first byte of frame header.
|
|
* check version subfield (must be zero)
|
|
* check type subfield (00 = mgt, 01 = ctl, 10 = data)
|
|
* check subtype field (next four bits)
|
|
*/
|
|
|
|
/*
|
|
* Not counting WDS mode, the IEEE 802.11 frame header format
|
|
* has *three* MAC addresses.
|
|
* (source, destination, and BSS).
|
|
*
|
|
* The BSS indicates which wireless "cable segment" we're part of;
|
|
* we discover this dynamically..
|
|
*
|
|
* Not content to put them in a fixed order, the exact
|
|
* ordering of these addresses depends on other attribute bits
|
|
* in the frame control word!
|
|
*
|
|
* an alternate presentation which is more self-consistent:
|
|
* address 1 is the "wireless destination" -- either the
|
|
* station address,
|
|
* for wireless->wireless traffic, or the BSS id of an AP.
|
|
*
|
|
* address 2 is the "wireless source" -- either the
|
|
* station address of a wireless node, or the BSS id of an AP.
|
|
*
|
|
* address 3 is the "other address" -- for STA->AP, the
|
|
* eventual destination; for AP->STA, the original source, and
|
|
* for ad-hoc mode, the BSS id..
|
|
*/
|
|
|
|
framectl = framehdr[IEEEWL_FC];
|
|
|
|
if ((framectl & IEEEWL_FC_VERS_MASK) != IEEEWL_FC_VERS) {
|
|
printf("wrong vers. drop");
|
|
goto drop;
|
|
}
|
|
|
|
switch (framectl & IEEEWL_FC_TYPE_MASK) {
|
|
case IEEEWL_FC_TYPE_MGT << IEEEWL_FC_TYPE_SHIFT:
|
|
awi_rcv_mgt (sc, m, rxts, rssi);
|
|
m = 0;
|
|
break;
|
|
|
|
case IEEEWL_FC_TYPE_DATA << IEEEWL_FC_TYPE_SHIFT:
|
|
awi_rcv_data (sc, m);
|
|
m = 0;
|
|
break;
|
|
|
|
case IEEEWL_FC_TYPE_CTL << IEEEWL_FC_TYPE_SHIFT:
|
|
awi_rcv_ctl (sc, m);
|
|
default:
|
|
goto drop;
|
|
}
|
|
|
|
drop:
|
|
if (m) m_freem(m);
|
|
}
|
|
|
|
|
|
void
|
|
awi_dump_rxchain (sc, what, descr)
|
|
struct awi_softc *sc;
|
|
char *what;
|
|
u_int32_t *descr;
|
|
{
|
|
u_int32_t next, cur;
|
|
int i;
|
|
struct ifnet *ifp = sc->sc_ifp;
|
|
u_int8_t *mptr;
|
|
int mleft;
|
|
|
|
struct mbuf *top = NULL, *m = NULL, *m1 = NULL;
|
|
cur = *descr;
|
|
|
|
|
|
if (cur & AWI_RXD_NEXT_LAST)
|
|
return;
|
|
|
|
for (i=0;i<1000;i++) {
|
|
u_int16_t len;
|
|
u_int8_t state, rate, rssi, index;
|
|
u_int32_t frame;
|
|
u_int32_t rxts;
|
|
|
|
top = 0;
|
|
|
|
next = awi_read_4(sc, cur + AWI_RXD_NEXT);
|
|
|
|
if (next & AWI_RXD_NEXT_LAST)
|
|
break;
|
|
|
|
state = awi_read_1(sc, cur + AWI_RXD_HOST_DESC_STATE);
|
|
len = awi_read_2 (sc, cur + AWI_RXD_LEN);
|
|
rate = awi_read_1 (sc, cur + AWI_RXD_RATE);
|
|
rssi = awi_read_1 (sc, cur + AWI_RXD_RSSI);
|
|
index = awi_read_1 (sc, cur + AWI_RXD_INDEX);
|
|
frame = awi_read_4 (sc, cur + AWI_RXD_START_FRAME);
|
|
rxts = awi_read_4 (sc, cur + AWI_RXD_LOCALTIME);
|
|
|
|
/*
|
|
* only the low order bits of "frame" and "next" are valid.
|
|
* (the documentation doesn't mention this).
|
|
*/
|
|
frame &= 0xffff;
|
|
next &= 0xffff;
|
|
|
|
if (state & AWI_RXD_ST_CONSUMED) {
|
|
state |= AWI_RXD_ST_CONSUMED | AWI_RXD_ST_OWN;
|
|
awi_write_1(sc, cur + AWI_RXD_HOST_DESC_STATE, state);
|
|
} else {
|
|
MGETHDR(top, M_DONTWAIT, MT_DATA);
|
|
if (top != 0) {
|
|
m = top;
|
|
m->m_pkthdr.rcvif = ifp;
|
|
m->m_pkthdr.len = 0;
|
|
m->m_len = 0;
|
|
|
|
mleft = MHLEN;
|
|
mptr = mtod(m, u_int8_t *);
|
|
}
|
|
for(;;) {
|
|
if (top != 0) {
|
|
/* copy data into mbuf */
|
|
while (len > 0) {
|
|
int nmove = min (len, mleft);
|
|
|
|
awi_read_bytes (sc, frame, mptr, nmove);
|
|
len -= nmove;
|
|
mleft -= nmove;
|
|
mptr += nmove;
|
|
frame += nmove;
|
|
|
|
top->m_pkthdr.len += nmove;
|
|
m->m_len += nmove;
|
|
|
|
if (mleft == 0) {
|
|
/* Get next mbuf.. */
|
|
MGET(m1, M_DONTWAIT, MT_DATA);
|
|
if (m1 == NULL) {
|
|
panic("awi mget"); /* XXX */
|
|
}
|
|
m->m_next = m1;
|
|
m = m1;
|
|
m->m_len = 0;
|
|
|
|
mleft = MLEN;
|
|
mptr = mtod(m, u_int8_t *);
|
|
}
|
|
}
|
|
}
|
|
state |= AWI_RXD_ST_CONSUMED | AWI_RXD_ST_OWN;
|
|
awi_write_1(sc, cur + AWI_RXD_HOST_DESC_STATE, state);
|
|
|
|
if (state & AWI_RXD_ST_LF)
|
|
break;
|
|
|
|
if (next & AWI_RXD_NEXT_LAST)
|
|
panic("awi oops"); /* XXX */
|
|
|
|
/* XXX deal with dummy frames here?? */
|
|
|
|
cur = next;
|
|
state = awi_read_1(sc, cur + AWI_RXD_HOST_DESC_STATE);
|
|
len = awi_read_2 (sc, cur + AWI_RXD_LEN);
|
|
rate = awi_read_1 (sc, cur + AWI_RXD_RATE);
|
|
rssi = awi_read_1 (sc, cur + AWI_RXD_RSSI);
|
|
index = awi_read_1 (sc, cur + AWI_RXD_INDEX);
|
|
frame = awi_read_4 (sc, cur + AWI_RXD_START_FRAME);
|
|
frame &= 0xffff;
|
|
next &= 0xffff;
|
|
}
|
|
}
|
|
if (top) {
|
|
awi_rcv(sc, top, rxts, rssi);
|
|
top = 0;
|
|
}
|
|
cur = next;
|
|
}
|
|
*descr = cur;
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
awi_rxint (sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
awi_dump_rxchain (sc, "mgt", &sc->sc_rx_mgt_desc);
|
|
awi_dump_rxchain (sc, "data", &sc->sc_rx_data_desc);
|
|
}
|
|
|
|
void
|
|
awi_init_txd (sc, tx, flag, len, rate)
|
|
struct awi_softc *sc;
|
|
int tx;
|
|
int flag;
|
|
int len;
|
|
int rate;
|
|
{
|
|
u_int32_t txdbase = sc->sc_txd[tx].descr;
|
|
u_int32_t framebase = sc->sc_txd[tx].frame;
|
|
u_int32_t nextbase = sc->sc_txd[(tx+1)%sc->sc_ntxd].descr;
|
|
|
|
awi_write_4 (sc, txdbase + AWI_TXD_START, framebase);
|
|
awi_write_4 (sc, txdbase + AWI_TXD_NEXT, nextbase);
|
|
awi_write_4 (sc, txdbase + AWI_TXD_LENGTH, len);
|
|
awi_write_1 (sc, txdbase + AWI_TXD_RATE, rate);
|
|
/* zeroize tail end of txd */
|
|
awi_write_4 (sc, txdbase + AWI_TXD_NDA, 0);
|
|
awi_write_4 (sc, txdbase + AWI_TXD_NRA, 0);
|
|
/* Init state last; firmware keys off of this to know when to start tx */
|
|
awi_write_1 (sc, txdbase + AWI_TXD_STATE, flag);
|
|
}
|
|
|
|
void
|
|
awi_init_txdescr (sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
int i;
|
|
u_int32_t offset = sc->sc_txbase;
|
|
|
|
sc->sc_txfirst = 0;
|
|
sc->sc_txnext = 0;
|
|
|
|
sc->sc_ntxd = sc->sc_txlen / (AWI_FRAME_SIZE + AWI_TXD_SIZE);
|
|
if (sc->sc_ntxd > NTXD) {
|
|
sc->sc_ntxd = NTXD;
|
|
printf("oops, no, only %d\n", sc->sc_ntxd);
|
|
}
|
|
|
|
/* Allocate TXD's */
|
|
for (i=0; i<sc->sc_ntxd; i++) {
|
|
sc->sc_txd[i].descr = offset;
|
|
offset += AWI_TXD_SIZE;
|
|
}
|
|
/* now, allocate buffer space to each txd.. */
|
|
for (i=0; i<sc->sc_ntxd; i++) {
|
|
sc->sc_txd[i].frame = offset;
|
|
sc->sc_txd[i].len = AWI_FRAME_SIZE;
|
|
offset += AWI_FRAME_SIZE;
|
|
|
|
}
|
|
|
|
/* now, initialize the TX descriptors into a circular linked list. */
|
|
|
|
for (i= 0; i<sc->sc_ntxd; i++) {
|
|
awi_init_txd(sc, i, 0, 0, 0);
|
|
}
|
|
}
|
|
|
|
void
|
|
awi_txint (sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
struct ifnet *ifp = sc->sc_ifp;
|
|
int txfirst;
|
|
|
|
sc->sc_tx_timer = 0;
|
|
|
|
txfirst = sc->sc_txfirst;
|
|
while (sc->sc_txpending > 0) {
|
|
u_int8_t flags = awi_read_1 (sc, sc->sc_txd[txfirst].descr +
|
|
AWI_TXD_STATE);
|
|
|
|
if (flags & AWI_TXD_ST_OWN)
|
|
break;
|
|
|
|
if (flags & AWI_TXD_ST_ERROR) {
|
|
/* increment oerrs */;
|
|
}
|
|
|
|
txfirst = (txfirst + 1) % sc->sc_ntxd;
|
|
sc->sc_txpending--;
|
|
}
|
|
|
|
sc->sc_txfirst = txfirst;
|
|
|
|
if (sc->sc_txpending < sc->sc_ntxd)
|
|
ifp->if_flags &= ~IFF_OACTIVE;
|
|
|
|
/*
|
|
* see which descriptors are done..
|
|
*/
|
|
|
|
awi_start(sc->sc_ifp);
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* device interrupt routine.
|
|
*
|
|
* lock out MAC
|
|
* loop:
|
|
* look at intr status, DTRT.
|
|
*
|
|
* on tx done, reclaim free buffers from tx, call start.
|
|
* on rx done, look at rx queue, copy to mbufs, mark as free,
|
|
* hand to ether media layer rx routine.
|
|
* on cmd done, call cmd cmpl continuation.
|
|
*
|
|
*/
|
|
|
|
int
|
|
awi_intr(arg)
|
|
void *arg;
|
|
{
|
|
struct awi_softc *sc = arg;
|
|
int handled = 0;
|
|
|
|
if (sc->sc_state == AWI_ST_OFF) {
|
|
u_int8_t intstate = awi_read_intst (sc);
|
|
return intstate != 0;
|
|
}
|
|
|
|
/* disable power down, (and implicitly ack interrupt) */
|
|
am79c930_gcr_setbits(&sc->sc_chip, AM79C930_GCR_DISPWDN);
|
|
awi_write_1(sc, AWI_DIS_PWRDN, 1);
|
|
|
|
for (;;) {
|
|
u_int8_t intstate = awi_read_intst (sc);
|
|
|
|
if (!intstate)
|
|
break;
|
|
|
|
handled = 1;
|
|
|
|
if (intstate & AWI_INT_RX)
|
|
awi_rxint(sc);
|
|
|
|
if (intstate & AWI_INT_TX)
|
|
awi_txint(sc);
|
|
|
|
if (intstate & AWI_INT_CMD) {
|
|
u_int8_t status;
|
|
|
|
if (!(sc->sc_flags & AWI_FL_CMD_INPROG))
|
|
printf("%s: no command in progress?\n",
|
|
sc->sc_dev.dv_xname);
|
|
status = awi_read_1(sc, AWI_CMD_STATUS);
|
|
awi_write_1 (sc, AWI_CMD, 0);
|
|
sc->sc_cmd_timer = 0;
|
|
sc->sc_flags &= ~AWI_FL_CMD_INPROG;
|
|
|
|
if (sc->sc_completion)
|
|
(*sc->sc_completion)(sc, status);
|
|
}
|
|
if (intstate & AWI_INT_SCAN_CMPLT) {
|
|
if (sc->sc_flags & AWI_FL_CMD_INPROG) {
|
|
panic("i can't take it any more");
|
|
}
|
|
/*
|
|
* scan completion heuristic..
|
|
*/
|
|
if ((sc->sc_nbindings >= NBND)
|
|
|| ((sc->sc_scan_timer == 0) &&
|
|
(sc->sc_nbindings > 0)))
|
|
awi_try_sync(sc);
|
|
else
|
|
awi_scan_next(sc);
|
|
}
|
|
|
|
}
|
|
/* reenable power down */
|
|
am79c930_gcr_clearbits(&sc->sc_chip, AM79C930_GCR_DISPWDN);
|
|
awi_write_1(sc, AWI_DIS_PWRDN, 0);
|
|
|
|
return handled;
|
|
}
|
|
|
|
/*
|
|
* device stop routine
|
|
*/
|
|
|
|
void
|
|
awi_stop(sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
struct ifnet *ifp = sc->sc_ifp;
|
|
|
|
/* Turn off timer.. */
|
|
ifp->if_timer = 0;
|
|
sc->sc_state = AWI_ST_OFF;
|
|
(void) awi_read_intst (sc);
|
|
/*
|
|
* XXX for pcmcia, there's no point in disabling the device,
|
|
* as it's about to be powered off..
|
|
* for non-PCMCIA attachments, we should, however, stop
|
|
* the receiver and transmitter here.
|
|
*/
|
|
}
|
|
|
|
/*
|
|
* Watchdog routine, triggered by timer.
|
|
* This does periodic maintainance-type tasks on the interface.
|
|
*/
|
|
|
|
void
|
|
awi_watchdog(ifp)
|
|
struct ifnet *ifp;
|
|
{
|
|
struct awi_softc *sc = ifp->if_softc;
|
|
u_int8_t test;
|
|
int i;
|
|
|
|
if (sc->sc_state == AWI_ST_OFF)
|
|
/* nothing to do */
|
|
return;
|
|
else if (sc->sc_state == AWI_ST_INSANE) {
|
|
awi_reset(sc);
|
|
return;
|
|
} else if (sc->sc_state == AWI_ST_SELFTEST) {
|
|
/* check for selftest completion.. */
|
|
test = awi_read_1(sc, AWI_SELFTEST);
|
|
if ((test & 0xf0) == 0xf0) { /* XXX magic numbers */
|
|
if (test == AWI_SELFTEST_PASSED) {
|
|
awi_init_1(sc);
|
|
} else {
|
|
printf("%s: selftest failed (code %x)\n",
|
|
sc->sc_dev.dv_xname, test);
|
|
awi_reset(sc);
|
|
}
|
|
}
|
|
sc->sc_selftest_tries++;
|
|
/* still running. try again on next tick */
|
|
if (sc->sc_selftest_tries < 5) {
|
|
ifp->if_timer = 1;
|
|
} else {
|
|
/*
|
|
* XXX should power down card, wait 1s, power it back
|
|
* up again..
|
|
*/
|
|
printf("%s: device failed to complete selftest (code %x)\n",
|
|
sc->sc_dev.dv_xname, test);
|
|
ifp->if_timer = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* command timer: if it goes to zero, device failed to respond.
|
|
* boot to the head.
|
|
*/
|
|
if (sc->sc_cmd_timer) {
|
|
sc->sc_cmd_timer--;
|
|
if (sc->sc_cmd_timer == 0) {
|
|
sc->sc_flags &= ~AWI_FL_CMD_INPROG;
|
|
|
|
printf("%s: timeout waiting for command completion\n",
|
|
sc->sc_dev.dv_xname);
|
|
test = awi_read_1(sc, AWI_CMD_STATUS);
|
|
printf("%s: cmd status: %x\n", sc->sc_dev.dv_xname, test);
|
|
test = awi_read_1(sc, AWI_CMD);
|
|
printf("%s: cmd: %x\n", sc->sc_dev.dv_xname, test);
|
|
awi_card_hexdump(sc, "CSB", AWI_CSB, 16);
|
|
awi_reset(sc);
|
|
return;
|
|
}
|
|
}
|
|
/*
|
|
* Transmit timer. If it goes to zero, device failed to deliver a
|
|
* tx complete interrupt. boot to the head.
|
|
*/
|
|
if (sc->sc_tx_timer) {
|
|
sc->sc_tx_timer--;
|
|
if ((sc->sc_tx_timer == 0) && (sc->sc_txpending)) {
|
|
awi_card_hexdump(sc, "CSB", AWI_CSB, 16);
|
|
printf("%s: transmit timeout\n", sc->sc_dev.dv_xname);
|
|
awi_card_hexdump(sc, "last_txd", AWI_LAST_TXD, 5*4);
|
|
for (i=0; i<sc->sc_ntxd; i++) {
|
|
awi_card_hexdump(sc, "txd",
|
|
sc->sc_txd[i].descr, AWI_TXD_SIZE);
|
|
}
|
|
awi_reset(sc);
|
|
return;
|
|
}
|
|
}
|
|
/*
|
|
* Scan timer.
|
|
* When synched, this is used to notice when we've stopped
|
|
* receiving beacons and should attempt to resynch.
|
|
*
|
|
* When unsynched, this is used to notice if we've received an
|
|
* interesting probe response and should synch up.
|
|
*/
|
|
|
|
if (sc->sc_scan_timer) {
|
|
sc->sc_scan_timer--;
|
|
if (sc->sc_scan_timer == 0) {
|
|
if (sc->sc_state == AWI_ST_SCAN) {
|
|
/*
|
|
* XXX what if device fails to deliver
|
|
* a scan-completion interrupt?
|
|
*/
|
|
} else {
|
|
printf("%s: no recent beacon from %s; rescanning\n",
|
|
sc->sc_dev.dv_xname,
|
|
ether_sprintf(sc->sc_active_bss.bss_id));
|
|
awi_restart_scan(sc);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Management timer. Used to know when to send auth
|
|
* requests and associate requests.
|
|
*/
|
|
if (sc->sc_mgt_timer) {
|
|
sc->sc_mgt_timer--;
|
|
if (sc->sc_mgt_timer == 0) {
|
|
switch (sc->sc_state)
|
|
{
|
|
case AWI_ST_SYNCED:
|
|
case AWI_ST_RUNNING:
|
|
sc->sc_state = AWI_ST_SYNCED;
|
|
awi_send_authreq(sc);
|
|
break;
|
|
case AWI_ST_AUTHED:
|
|
awi_send_assocreq(sc);
|
|
break;
|
|
default:
|
|
printf("weird state for mgt timeout!\n");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
awi_set_timer(sc);
|
|
}
|
|
|
|
void
|
|
awi_set_mc (sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
/* XXX not implemented yet.. */
|
|
}
|
|
|
|
/*
|
|
* init routine
|
|
*/
|
|
|
|
/*
|
|
* ioctl routine
|
|
* SIOCSIFADDR sets IFF_UP
|
|
* SIOCIFMTU
|
|
* SIOCSIFFLAGS
|
|
* SIOCADDMULTI/SIOCDELMULTI
|
|
*/
|
|
|
|
int
|
|
awi_ioctl(ifp, cmd, data)
|
|
register struct ifnet *ifp;
|
|
u_long cmd;
|
|
caddr_t data;
|
|
{
|
|
struct awi_softc *sc = ifp->if_softc;
|
|
struct ifaddr *ifa = (struct ifaddr *)data;
|
|
#if 0
|
|
struct ifreq *ifr = (struct ifreq *)data;
|
|
#endif
|
|
int s, error = 0;
|
|
|
|
s = splnet();
|
|
|
|
switch (cmd) {
|
|
case SIOCSIFADDR:
|
|
if ((error = awi_enable(sc)) != 0)
|
|
break;
|
|
|
|
ifp->if_flags |= IFF_UP;
|
|
|
|
/* XXX other AF support: inet6, NS, ... */
|
|
switch (ifa->ifa_addr->sa_family) {
|
|
#ifdef INET
|
|
case AF_INET:
|
|
arp_ifinit(&sc->sc_ec.ec_if, ifa);
|
|
break;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case SIOCSIFFLAGS:
|
|
if ((ifp->if_flags & IFF_UP) == 0 &&
|
|
(sc->sc_state != AWI_ST_OFF)) {
|
|
/*
|
|
* If interface is marked down and it is enabled, then
|
|
* stop it.
|
|
*/
|
|
ifp->if_flags &= ~IFF_RUNNING;
|
|
awi_stop(sc);
|
|
awi_disable(sc);
|
|
} else if ((ifp->if_flags & IFF_UP) != 0 &&
|
|
(ifp->if_flags & IFF_RUNNING) == 0) {
|
|
/*
|
|
* If interface is marked up and it is stopped, then
|
|
* start it.
|
|
*/
|
|
if ((error = awi_enable(sc)) != 0)
|
|
break;
|
|
} else if ((ifp->if_flags & IFF_UP) != 0) {
|
|
/*
|
|
* Deal with other flags that change hardware
|
|
* state, i.e. IFF_PROMISC.
|
|
*/
|
|
awi_set_mc(sc);
|
|
}
|
|
break;
|
|
default:
|
|
error = EINVAL;
|
|
break;
|
|
|
|
}
|
|
splx(s);
|
|
return error;
|
|
|
|
}
|
|
|
|
int awi_activate (self, act)
|
|
struct device *self;
|
|
enum devact act;
|
|
{
|
|
int s = splnet();
|
|
panic("awi_activate");
|
|
|
|
#if 0
|
|
switch (act) {
|
|
case DVACT_ACTIVATE:
|
|
rv = EOPNOTSUPP;
|
|
break;
|
|
|
|
case DVACT_DEACTIVATE:
|
|
#ifdef notyet
|
|
/* First, kill off the interface. */
|
|
if_detach(sc->sc_ethercom.ec_if);
|
|
#endif
|
|
|
|
/* Now disable the interface. */
|
|
awidisable(sc);
|
|
break;
|
|
}
|
|
#endif
|
|
splx(s);
|
|
|
|
}
|
|
|
|
int
|
|
awi_drop_output (ifp, m0, dst, rt0)
|
|
struct ifnet *ifp;
|
|
struct mbuf *m0;
|
|
struct sockaddr *dst;
|
|
struct rtentry *rt0;
|
|
{
|
|
m_freem(m0);
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
awi_drop_input (ifp, m0)
|
|
struct ifnet *ifp;
|
|
struct mbuf *m0;
|
|
{
|
|
m_freem(m0);
|
|
}
|
|
|
|
int awi_attach (sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
struct ifnet *ifp = &sc->sc_ec.ec_if;
|
|
u_int8_t version[AWI_BANNER_LEN];
|
|
|
|
sc->sc_ifp = ifp;
|
|
|
|
awi_read_bytes (sc, AWI_BANNER, version, AWI_BANNER_LEN);
|
|
printf("%s: firmware %s\n", sc->sc_dev.dv_xname, version);
|
|
|
|
memcpy(ifp->if_xname, sc->sc_dev.dv_xname, IFNAMSIZ);
|
|
ifp->if_softc = sc;
|
|
ifp->if_start = awi_start;
|
|
ifp->if_ioctl = awi_ioctl;
|
|
ifp->if_watchdog = awi_watchdog;
|
|
ifp->if_mtu = ETHERMTU;
|
|
/* XXX simplex may not be correct here.. */
|
|
ifp->if_flags =
|
|
IFF_BROADCAST | IFF_SIMPLEX | IFF_NOTRAILERS | IFF_MULTICAST;
|
|
|
|
sc->sc_mgtq.ifq_maxlen = 5;
|
|
|
|
if_attach(ifp);
|
|
/* Defer ether_ifattach, bpfattach until we get enaddr. */
|
|
ifp->if_output = awi_drop_output;
|
|
#if __NetBSD_Version__ > 104010000
|
|
ifp->if_input = awi_drop_input;
|
|
#endif
|
|
|
|
#if NBPFILTER > 0
|
|
bpfattach(&ifp->if_bpf, ifp, DLT_EN10MB, sizeof(struct ether_header));
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
awi_zero (sc, from, to)
|
|
struct awi_softc *sc;
|
|
u_int32_t from, to;
|
|
{
|
|
u_int32_t i;
|
|
for (i=from; i<to; i++)
|
|
awi_write_1(sc, i, 0);
|
|
}
|
|
|
|
void
|
|
awi_init (sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
struct ifnet *ifp = sc->sc_ifp;
|
|
|
|
sc->sc_scan_duration = 100; /* scan for 100ms */
|
|
|
|
/*
|
|
* Maybe we should randomize these....
|
|
*/
|
|
sc->sc_scan_chanset = IEEEWL_FH_CHANSET_MIN;
|
|
sc->sc_scan_pattern = IEEEWL_FH_PATTERN_MIN;
|
|
|
|
sc->sc_flags &= ~AWI_FL_CMD_INPROG;
|
|
|
|
ifp->if_flags &= ~(IFF_RUNNING|IFF_OACTIVE);
|
|
ifp->if_timer = 0;
|
|
|
|
sc->sc_cmd_timer = 0;
|
|
sc->sc_tx_timer = 0;
|
|
sc->sc_mgt_timer = 0;
|
|
sc->sc_scan_timer = 0;
|
|
|
|
sc->sc_nbindings = 0;
|
|
|
|
/*
|
|
* this reset sequence doesn't seem to always do the trick.
|
|
* hard-power-cycling the card may do it..
|
|
*/
|
|
|
|
/*
|
|
* reset the hardware, just to be sure.
|
|
* (bring out the big hammer here..)
|
|
*/
|
|
/* XXX insert delay here? */
|
|
|
|
am79c930_gcr_setbits (&sc->sc_chip, AM79C930_GCR_CORESET);
|
|
delay(10); /* XXX arbitrary value */
|
|
|
|
/*
|
|
* clear control memory regions (firmware should do this but...)
|
|
*/
|
|
awi_zero(sc, AWI_LAST_TXD, AWI_BUFFERS);
|
|
|
|
awi_drvstate(sc, AWI_DRV_RESET);
|
|
sc->sc_selftest_tries = 0;
|
|
|
|
/*
|
|
* release reset
|
|
*/
|
|
am79c930_gcr_clearbits (&sc->sc_chip, AM79C930_GCR_CORESET);
|
|
delay(10);
|
|
|
|
sc->sc_state = AWI_ST_SELFTEST;
|
|
ifp->if_timer = 1;
|
|
|
|
}
|
|
|
|
void
|
|
awi_cmd (sc, opcode)
|
|
struct awi_softc *sc;
|
|
u_int8_t opcode;
|
|
{
|
|
if (sc->sc_flags & AWI_FL_CMD_INPROG)
|
|
panic("%s: command reentered", sc->sc_dev.dv_xname);
|
|
|
|
sc->sc_flags |= AWI_FL_CMD_INPROG;
|
|
|
|
/* issue test-interface command */
|
|
awi_write_1(sc, AWI_CMD, opcode);
|
|
|
|
awi_write_1(sc, AWI_CMD_STATUS, 0);
|
|
|
|
sc->sc_cmd_timer = 2;
|
|
awi_set_timer(sc);
|
|
}
|
|
|
|
void
|
|
awi_cmd_test_if (sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
awi_cmd (sc, AWI_CMD_NOP);
|
|
}
|
|
|
|
void
|
|
awi_cmd_get_mib (sc, var, offset, len)
|
|
struct awi_softc *sc;
|
|
u_int8_t var;
|
|
u_int8_t offset;
|
|
u_int8_t len;
|
|
{
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_TYPE, var);
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_SIZE, len);
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_INDEX, offset);
|
|
|
|
awi_cmd (sc, AWI_CMD_GET_MIB);
|
|
}
|
|
|
|
void
|
|
awi_cmd_txinit (sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
awi_write_4(sc, AWI_CMD_PARAMS+AWI_CA_TX_DATA, sc->sc_txbase);
|
|
awi_write_4(sc, AWI_CMD_PARAMS+AWI_CA_TX_MGT, 0);
|
|
awi_write_4(sc, AWI_CMD_PARAMS+AWI_CA_TX_BCAST, 0);
|
|
awi_write_4(sc, AWI_CMD_PARAMS+AWI_CA_TX_PS, 0);
|
|
awi_write_4(sc, AWI_CMD_PARAMS+AWI_CA_TX_CF, 0);
|
|
|
|
awi_cmd (sc, AWI_CMD_INIT_TX);
|
|
}
|
|
|
|
int awi_max_chan = -1;
|
|
int awi_min_chan = 1000;
|
|
int awi_max_pattern = -1;
|
|
int awi_min_pattern = 1000;
|
|
|
|
|
|
/*
|
|
* timeout-driven routine: complete device init once device has passed
|
|
* selftest.
|
|
*/
|
|
|
|
void awi_init_1 (sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
struct ifnet *ifp = sc->sc_ifp;
|
|
|
|
awi_intrinit(sc);
|
|
|
|
sc->sc_state = AWI_ST_IFTEST;
|
|
|
|
if (ifp->if_flags & IFF_DEBUG) {
|
|
awi_card_hexdump(sc, "init_1 CSB", AWI_CSB, 16);
|
|
sc->sc_completion = awi_mibdump;
|
|
} else
|
|
sc->sc_completion = awi_init_2;
|
|
|
|
sc->sc_curmib = 0;
|
|
|
|
awi_cmd_test_if (sc);
|
|
}
|
|
|
|
void awi_mibdump (sc, status)
|
|
struct awi_softc *sc;
|
|
u_int8_t status;
|
|
{
|
|
u_int8_t mibblk[256];
|
|
|
|
if (status != AWI_STAT_OK) {
|
|
printf("%s: pre-mibread failed (card unhappy?)\n",
|
|
sc->sc_dev.dv_xname);
|
|
awi_reset(sc);
|
|
return;
|
|
}
|
|
|
|
if (sc->sc_curmib != 0) {
|
|
awi_read_bytes(sc, AWI_CMD_PARAMS+AWI_CA_MIB_DATA,
|
|
mibblk, 72);
|
|
awi_hexdump("mib", mibblk, 72);
|
|
}
|
|
if (sc->sc_curmib > AWI_MIB_LAST) {
|
|
awi_init_2 (sc, status);
|
|
} else {
|
|
sc->sc_completion = awi_mibdump;
|
|
printf("mib %d\n", sc->sc_curmib);
|
|
awi_cmd_get_mib (sc, sc->sc_curmib, 0, 30);
|
|
sc->sc_curmib++;
|
|
/* skip over reserved MIB's.. */
|
|
if ((sc->sc_curmib == 1) || (sc->sc_curmib == 6))
|
|
sc->sc_curmib++;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* called on completion of test-interface command in first-stage init.
|
|
*/
|
|
|
|
void awi_init_2 (sc, status)
|
|
struct awi_softc *sc;
|
|
u_int8_t status;
|
|
{
|
|
/* did it succeed? */
|
|
if (status != AWI_STAT_OK) {
|
|
printf("%s: nop failed (card unhappy?)\n",
|
|
sc->sc_dev.dv_xname);
|
|
awi_reset(sc);
|
|
}
|
|
|
|
sc->sc_state = AWI_ST_MIB_GET;
|
|
sc->sc_completion = awi_init_read_bufptrs_done;
|
|
|
|
awi_cmd_get_mib (sc, AWI_MIB_LOCAL, 0, AWI_MIB_LOCAL_SIZE);
|
|
}
|
|
|
|
void awi_init_read_bufptrs_done (sc, status)
|
|
struct awi_softc *sc;
|
|
u_int8_t status;
|
|
{
|
|
if (status != AWI_STAT_OK) {
|
|
printf("%s: get_mib failed (card unhappy?)\n",
|
|
sc->sc_dev.dv_xname);
|
|
awi_reset(sc);
|
|
}
|
|
|
|
sc->sc_txbase = awi_read_4 (sc,
|
|
AWI_CMD_PARAMS+AWI_CA_MIB_DATA+AWI_MIB_LOCAL_TXB_OFFSET);
|
|
sc->sc_txlen = awi_read_4 (sc,
|
|
AWI_CMD_PARAMS+AWI_CA_MIB_DATA+AWI_MIB_LOCAL_TXB_SIZE);
|
|
sc->sc_rxbase = awi_read_4 (sc,
|
|
AWI_CMD_PARAMS+AWI_CA_MIB_DATA+AWI_MIB_LOCAL_RXB_OFFSET);
|
|
sc->sc_rxlen = awi_read_4 (sc,
|
|
AWI_CMD_PARAMS+AWI_CA_MIB_DATA+AWI_MIB_LOCAL_RXB_SIZE);
|
|
/*
|
|
* XXX consider repartitioning buffer space to allow for
|
|
* more efficient usage.
|
|
* 6144: 3 txds, 1476 waste (current partition)
|
|
* better splits:
|
|
* 4864: 3 txds, 196 waste
|
|
* 6400: 4 txds, 176 waste
|
|
* 7936: 5 txds, 156 waste
|
|
*/
|
|
|
|
#if 0
|
|
printf("tx offset: %x\n", sc->sc_txbase);
|
|
printf("tx size: %x\n", sc->sc_txlen);
|
|
printf("rx offset: %x\n", sc->sc_rxbase);
|
|
printf("rx size: %x\n", sc->sc_rxlen);
|
|
#endif
|
|
|
|
awi_cmd_get_myaddr (sc);
|
|
}
|
|
|
|
void awi_cmd_get_myaddr (sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
sc->sc_completion = awi_cmd_get_myaddr_done;
|
|
|
|
awi_cmd_get_mib (sc, AWI_MIB_MAC_ADDR, 0, ETHER_ADDR_LEN);
|
|
}
|
|
|
|
void awi_cmd_get_myaddr_done (sc, status)
|
|
struct awi_softc *sc;
|
|
u_int8_t status;
|
|
{
|
|
struct ifnet *ifp = sc->sc_ifp;
|
|
u_int8_t newaddr[ETHER_ADDR_LEN];
|
|
|
|
if (status != AWI_STAT_OK) {
|
|
printf("%s: nop failed (card unhappy?)\n",
|
|
sc->sc_dev.dv_xname);
|
|
awi_reset(sc);
|
|
return;
|
|
}
|
|
|
|
awi_read_bytes (sc, AWI_CMD_PARAMS + AWI_CA_MIB_DATA,
|
|
newaddr, sizeof(newaddr));
|
|
|
|
if (memcmp (newaddr, sc->sc_my_addr, sizeof(newaddr)) != 0) {
|
|
printf("%s: 802.11 address %s\n", sc->sc_dev.dv_xname,
|
|
ether_sprintf(newaddr));
|
|
memcpy(sc->sc_my_addr, newaddr, sizeof(newaddr));
|
|
}
|
|
ether_ifattach(ifp, sc->sc_my_addr);
|
|
ifp->if_hdrlen = 32;
|
|
|
|
sc->sc_state = AWI_ST_MIB_SET;
|
|
awi_cmd_set_notap(sc);
|
|
}
|
|
|
|
void awi_cmd_set_notap (sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_TYPE, AWI_MIB_LOCAL);
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_SIZE, 1);
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_INDEX,
|
|
AWI_MIB_LOCAL_ACTING_AS_AP);
|
|
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_DATA, 0);
|
|
sc->sc_completion = awi_cmd_set_notap_done;
|
|
awi_cmd (sc, AWI_CMD_SET_MIB);
|
|
}
|
|
|
|
void awi_cmd_set_notap_done (sc, status)
|
|
struct awi_softc *sc;
|
|
u_int8_t status;
|
|
{
|
|
if (status != AWI_STAT_OK) {
|
|
int erroffset = awi_read_1 (sc, AWI_ERROR_OFFSET);
|
|
printf("%s: set_infra failed (card unhappy?); erroffset %d\n",
|
|
sc->sc_dev.dv_xname,
|
|
erroffset);
|
|
awi_reset(sc);
|
|
return;
|
|
}
|
|
awi_cmd_set_infra (sc);
|
|
}
|
|
|
|
void awi_cmd_set_infra (sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_TYPE, AWI_MIB_LOCAL);
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_SIZE, 1);
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_INDEX,
|
|
AWI_MIB_LOCAL_INFRA_MODE);
|
|
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_DATA, 1);
|
|
sc->sc_completion = awi_cmd_set_infra_done;
|
|
awi_cmd (sc, AWI_CMD_SET_MIB);
|
|
}
|
|
|
|
void awi_cmd_set_infra_done (sc, status)
|
|
struct awi_softc *sc;
|
|
u_int8_t status;
|
|
{
|
|
#if 0
|
|
printf("set_infra done\n");
|
|
#endif
|
|
if (status != AWI_STAT_OK) {
|
|
int erroffset = awi_read_1 (sc, AWI_ERROR_OFFSET);
|
|
printf("%s: set_infra failed (card unhappy?); erroffset %d\n",
|
|
sc->sc_dev.dv_xname,
|
|
erroffset);
|
|
awi_reset(sc);
|
|
return;
|
|
}
|
|
#if 0
|
|
printf("%s: set_infra done\n", sc->sc_dev.dv_xname);
|
|
#endif
|
|
awi_cmd_set_allmulti (sc);
|
|
}
|
|
|
|
void awi_cmd_set_allmulti (sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_TYPE, AWI_MIB_LOCAL);
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_SIZE, 1);
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_INDEX,
|
|
AWI_MIB_LOCAL_FILTMULTI);
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_DATA, 0);
|
|
sc->sc_completion = awi_cmd_set_allmulti_done;
|
|
awi_cmd (sc, AWI_CMD_SET_MIB);
|
|
}
|
|
|
|
void awi_cmd_set_allmulti_done (sc, status)
|
|
struct awi_softc *sc;
|
|
u_int8_t status;
|
|
{
|
|
if (status != AWI_STAT_OK) {
|
|
int erroffset = awi_read_1 (sc, AWI_ERROR_OFFSET);
|
|
printf("%s: set_almulti_done failed (card unhappy?); erroffset %d\n",
|
|
sc->sc_dev.dv_xname,
|
|
erroffset);
|
|
awi_reset(sc);
|
|
return;
|
|
}
|
|
awi_cmd_set_promisc (sc);
|
|
}
|
|
|
|
void awi_cmd_set_promisc (sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_TYPE, AWI_MIB_MAC);
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_SIZE, 1);
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_INDEX,
|
|
AWI_MIB_MAC_PROMISC);
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_DATA, 0); /* XXX */
|
|
sc->sc_completion = awi_cmd_set_promisc_done;
|
|
awi_cmd (sc, AWI_CMD_SET_MIB);
|
|
}
|
|
|
|
void awi_cmd_set_promisc_done (sc, status)
|
|
struct awi_softc *sc;
|
|
u_int8_t status;
|
|
{
|
|
#if 0
|
|
printf("set promisc_done\n");
|
|
#endif
|
|
|
|
if (status != AWI_STAT_OK) {
|
|
int erroffset = awi_read_1 (sc, AWI_ERROR_OFFSET);
|
|
printf("%s: set_promisc_done failed (card unhappy?); erroffset %d\n",
|
|
sc->sc_dev.dv_xname,
|
|
erroffset);
|
|
awi_reset(sc);
|
|
return;
|
|
}
|
|
#if 0
|
|
printf("%s: set_promisc done\n", sc->sc_dev.dv_xname);
|
|
#endif
|
|
|
|
awi_init_txdescr(sc);
|
|
|
|
sc->sc_state = AWI_ST_TXINIT;
|
|
sc->sc_completion = awi_init_4;
|
|
awi_cmd_txinit(sc);
|
|
}
|
|
|
|
void
|
|
awi_init_4 (sc, status)
|
|
struct awi_softc *sc;
|
|
u_int8_t status;
|
|
{
|
|
#if 0
|
|
printf("%s: awi_init_4, st %x\n", sc->sc_dev.dv_xname, status);
|
|
awi_card_hexdump(sc, "init_4 CSB", AWI_CSB, 16);
|
|
#endif
|
|
|
|
if (status != AWI_STAT_OK) {
|
|
int erroffset = awi_read_1 (sc, AWI_ERROR_OFFSET);
|
|
printf("%s: init_tx failed (card unhappy?); erroffset %d\n",
|
|
sc->sc_dev.dv_xname,
|
|
erroffset);
|
|
awi_reset(sc);
|
|
return;
|
|
}
|
|
|
|
sc->sc_state = AWI_ST_RXINIT;
|
|
sc->sc_completion = awi_init_5;
|
|
|
|
awi_cmd (sc, AWI_CMD_INIT_RX);
|
|
}
|
|
|
|
void awi_init_5 (sc, status)
|
|
struct awi_softc *sc;
|
|
u_int8_t status;
|
|
{
|
|
#if 0
|
|
struct ifnet *ifp = sc->sc_ifp;
|
|
#endif
|
|
|
|
#if 0
|
|
printf("%s: awi_init_5, st %x\n", sc->sc_dev.dv_xname, status);
|
|
awi_card_hexdump(sc, "init_5 CSB", AWI_CSB, 16);
|
|
#endif
|
|
|
|
if (status != AWI_STAT_OK) {
|
|
printf("%s: init_rx failed (card unhappy?)\n",
|
|
sc->sc_dev.dv_xname);
|
|
awi_reset(sc);
|
|
return;
|
|
}
|
|
|
|
sc->sc_rx_data_desc = awi_read_4(sc, AWI_CMD_PARAMS+AWI_CA_IRX_DATA_DESC);
|
|
sc->sc_rx_mgt_desc = awi_read_4(sc, AWI_CMD_PARAMS+AWI_CA_IRX_PS_DESC);
|
|
|
|
#if 0
|
|
printf("%s: data desc %x, mgt desc %x\n", sc->sc_dev.dv_xname,
|
|
sc->sc_rx_data_desc, sc->sc_rx_mgt_desc);
|
|
#endif
|
|
awi_restart_scan(sc);
|
|
}
|
|
|
|
void awi_restart_scan (sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
sc->sc_scan_timer = 1;
|
|
sc->sc_mgt_timer = 0;
|
|
awi_set_timer(sc);
|
|
|
|
sc->sc_nbindings = 0;
|
|
sc->sc_state = AWI_ST_SCAN;
|
|
awi_drvstate (sc, AWI_DRV_INFSC);
|
|
awi_cmd_scan (sc);
|
|
}
|
|
|
|
void
|
|
awi_cmd_scan (sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
|
|
awi_write_2 (sc, AWI_CMD_PARAMS+AWI_CA_SCAN_DURATION,
|
|
sc->sc_scan_duration);
|
|
awi_write_1 (sc, AWI_CMD_PARAMS+AWI_CA_SCAN_SET,
|
|
sc->sc_scan_chanset);
|
|
awi_write_1 (sc, AWI_CMD_PARAMS+AWI_CA_SCAN_PATTERN,
|
|
sc->sc_scan_pattern);
|
|
awi_write_1 (sc, AWI_CMD_PARAMS+AWI_CA_SCAN_IDX, 1);
|
|
awi_write_1 (sc, AWI_CMD_PARAMS+AWI_CA_SCAN_SUSP, 0);
|
|
|
|
sc->sc_completion = awi_cmd_scan_done;
|
|
awi_cmd (sc, AWI_CMD_SCAN);
|
|
}
|
|
|
|
void
|
|
awi_cmd_scan_done (sc, status)
|
|
struct awi_softc *sc;
|
|
u_int8_t status;
|
|
{
|
|
#if 0
|
|
int erroffset;
|
|
#endif
|
|
if (status == AWI_STAT_OK) {
|
|
if (sc->sc_scan_chanset > awi_max_chan)
|
|
awi_max_chan = sc->sc_scan_chanset;
|
|
if (sc->sc_scan_chanset < awi_min_chan)
|
|
awi_min_chan = sc->sc_scan_chanset;
|
|
if (sc->sc_scan_pattern > awi_max_pattern)
|
|
awi_max_pattern = sc->sc_scan_pattern;
|
|
if (sc->sc_scan_pattern < awi_min_pattern)
|
|
awi_min_pattern = sc->sc_scan_pattern;
|
|
|
|
return;
|
|
}
|
|
#if 0
|
|
erroffset = awi_read_1 (sc, AWI_ERROR_OFFSET);
|
|
printf("%s: scan failed; erroffset %d\n", sc->sc_dev.dv_xname,
|
|
erroffset);
|
|
#endif
|
|
/* wait for response or scan timeout.. */
|
|
}
|
|
|
|
void
|
|
awi_scan_next (sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
sc->sc_scan_pattern++;
|
|
if (sc->sc_scan_pattern > IEEEWL_FH_PATTERN_MAX) {
|
|
sc->sc_scan_pattern = IEEEWL_FH_PATTERN_MIN;
|
|
|
|
sc->sc_scan_chanset++;
|
|
if (sc->sc_scan_chanset > IEEEWL_FH_CHANSET_MAX)
|
|
sc->sc_scan_chanset = IEEEWL_FH_CHANSET_MIN;
|
|
}
|
|
#if 0
|
|
printf("scan: pattern %x chanset %x\n", sc->sc_scan_pattern,
|
|
sc->sc_scan_chanset);
|
|
#endif
|
|
|
|
awi_cmd_scan(sc);
|
|
}
|
|
|
|
void
|
|
awi_try_sync (sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
int max_rssi = 0, best = 0;
|
|
int i;
|
|
struct awi_bss_binding *bp = NULL;
|
|
|
|
/* pick one with best rssi */
|
|
for (i=0; i<sc->sc_nbindings; i++) {
|
|
bp = &sc->sc_bindings[i];
|
|
|
|
if (bp->rssi > max_rssi) {
|
|
max_rssi = bp->rssi;
|
|
best = i;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
printf("best: %d\n", best);
|
|
#endif
|
|
if (bp == NULL) {
|
|
return;
|
|
}
|
|
sc->sc_scan_timer = 2;
|
|
|
|
bp = &sc->sc_bindings[best];
|
|
memcpy(&sc->sc_active_bss, bp, sizeof(*bp));
|
|
sc->sc_new_bss = 1;
|
|
|
|
awi_write_1 (sc, AWI_CMD_PARAMS+AWI_CA_SYNC_SET, bp->chanset);
|
|
awi_write_1 (sc, AWI_CMD_PARAMS+AWI_CA_SYNC_PATTERN, bp->pattern);
|
|
awi_write_1 (sc, AWI_CMD_PARAMS+AWI_CA_SYNC_IDX, bp->index);
|
|
awi_write_1 (sc, AWI_CMD_PARAMS+AWI_CA_SYNC_STARTBSS, 0);
|
|
|
|
awi_write_2 (sc, AWI_CMD_PARAMS+AWI_CA_SYNC_DWELL, bp->dwell_time);
|
|
awi_write_2 (sc, AWI_CMD_PARAMS+AWI_CA_SYNC_MBZ, 0);
|
|
|
|
awi_write_bytes (sc, AWI_CMD_PARAMS+AWI_CA_SYNC_TIMESTAMP,
|
|
bp->bss_timestamp, 8);
|
|
awi_write_4 (sc, AWI_CMD_PARAMS+AWI_CA_SYNC_REFTIME, bp->rxtime);
|
|
|
|
sc->sc_completion = awi_cmd_sync_done;
|
|
|
|
awi_cmd (sc, AWI_CMD_SYNC);
|
|
|
|
}
|
|
|
|
void
|
|
awi_cmd_sync_done (sc, status)
|
|
struct awi_softc *sc;
|
|
u_int8_t status;
|
|
{
|
|
if (status != AWI_STAT_OK) {
|
|
int erroffset = awi_read_1 (sc, AWI_ERROR_OFFSET);
|
|
printf("%s: sync_done failed (card unhappy?); erroffset %d\n",
|
|
sc->sc_dev.dv_xname,
|
|
erroffset);
|
|
awi_reset(sc);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* at this point, the card should be synchronized with the AP
|
|
* we heard from. tell the card what BSS and ESS it's running in..
|
|
*/
|
|
|
|
awi_drvstate (sc, AWI_DRV_INFSY);
|
|
#if 0
|
|
printf("%s: sync done, setting bss/iss parameters\n",
|
|
sc->sc_dev.dv_xname);
|
|
awi_hexdump ("bss", sc->sc_active_bss.bss_id, ETHER_ADDR_LEN);
|
|
printf("ssid: %s\n", sc->sc_active_bss.ssid);
|
|
#endif
|
|
awi_cmd_set_ss (sc);
|
|
}
|
|
|
|
|
|
void awi_cmd_set_ss (sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_TYPE, AWI_MIB_MAC_MGT);
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_SIZE,
|
|
ETHER_ADDR_LEN + AWI_MIB_MGT_ESS_SIZE);
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_INDEX,
|
|
AWI_MIB_MGT_BSS_ID);
|
|
|
|
awi_write_bytes(sc, AWI_CMD_PARAMS+AWI_CA_MIB_DATA,
|
|
sc->sc_active_bss.bss_id, ETHER_ADDR_LEN);
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_DATA+ETHER_ADDR_LEN,
|
|
0); /* XXX */
|
|
awi_write_1(sc, AWI_CMD_PARAMS+AWI_CA_MIB_DATA+ETHER_ADDR_LEN+1,
|
|
sc->sc_active_bss.sslen);
|
|
awi_write_bytes(sc, AWI_CMD_PARAMS+AWI_CA_MIB_DATA+8,
|
|
sc->sc_active_bss.ssid, AWI_MIB_MGT_ESS_SIZE-2);
|
|
|
|
sc->sc_completion = awi_cmd_set_ss_done;
|
|
awi_cmd (sc, AWI_CMD_SET_MIB);
|
|
}
|
|
|
|
void awi_cmd_set_ss_done (sc, status)
|
|
struct awi_softc *sc;
|
|
u_int8_t status;
|
|
{
|
|
if (status != AWI_STAT_OK) {
|
|
int erroffset = awi_read_1 (sc, AWI_ERROR_OFFSET);
|
|
printf("%s: set_ss_done failed (card unhappy?); erroffset %d\n",
|
|
sc->sc_dev.dv_xname,
|
|
erroffset);
|
|
awi_reset(sc);
|
|
return;
|
|
}
|
|
#if 0
|
|
printf("%s: set_ss done\n", sc->sc_dev.dv_xname);
|
|
#endif
|
|
|
|
awi_running (sc);
|
|
|
|
/*
|
|
* now, we *should* be getting broadcast frames..
|
|
*/
|
|
sc->sc_state = AWI_ST_SYNCED;
|
|
awi_send_authreq (sc);
|
|
|
|
}
|
|
|
|
void awi_running (sc)
|
|
struct awi_softc *sc;
|
|
|
|
{
|
|
struct ifnet *ifp = sc->sc_ifp;
|
|
|
|
/*
|
|
* Who knows what it is to be running?
|
|
* Only he who is running knows..
|
|
*/
|
|
ifp->if_flags |= IFF_RUNNING;
|
|
awi_start(ifp);
|
|
}
|
|
|
|
|
|
void awi_reset (sc)
|
|
struct awi_softc *sc;
|
|
{
|
|
printf("%s: reset\n", sc->sc_dev.dv_xname);
|
|
|
|
}
|