1
0
mirror of https://git.FreeBSD.org/src.git synced 2024-12-24 11:29:10 +00:00
freebsd/sys/dev/wi/if_wi.c
Maxim Sobolev e50d35e6c6 Add new tunable 'net.link.ifqmaxlen' to set default send interface
queue length. The default value for this parameter is 50, which is
quite low for many of today's uses and the only way to modify this
parameter right now is to edit if_var.h file. Also add read-only
sysctl with the same name, so that it's possible to retrieve the
current value.

MFC after:	1 month
2010-05-03 07:32:50 +00:00

2125 lines
57 KiB
C

/*-
* 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.
*/
/*
* Lucent WaveLAN/IEEE 802.11 PCMCIA driver.
*
* Original FreeBSD driver 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 features, 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 the ISA, PCMCIA and PCI versions of the Lucent
* WaveLan cards (based on the Hermes chipset), as well as the newer
* Prism 2 chipsets with firmware from Intersil and Symbol.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#define WI_HERMES_STATS_WAR /* Work around stats counter bug. */
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/endian.h>
#include <sys/sockio.h>
#include <sys/mbuf.h>
#include <sys/priv.h>
#include <sys/proc.h>
#include <sys/kernel.h>
#include <sys/socket.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/random.h>
#include <sys/syslog.h>
#include <sys/sysctl.h>
#include <machine/bus.h>
#include <machine/resource.h>
#include <machine/atomic.h>
#include <sys/rman.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <net/ethernet.h>
#include <net/if_dl.h>
#include <net/if_llc.h>
#include <net/if_media.h>
#include <net/if_types.h>
#include <net80211/ieee80211_var.h>
#include <net80211/ieee80211_ioctl.h>
#include <net80211/ieee80211_radiotap.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/in_var.h>
#include <netinet/ip.h>
#include <netinet/if_ether.h>
#include <net/bpf.h>
#include <dev/wi/if_wavelan_ieee.h>
#include <dev/wi/if_wireg.h>
#include <dev/wi/if_wivar.h>
static struct ieee80211vap *wi_vap_create(struct ieee80211com *ic,
const char name[IFNAMSIZ], int unit, int opmode, int flags,
const uint8_t bssid[IEEE80211_ADDR_LEN],
const uint8_t mac[IEEE80211_ADDR_LEN]);
static void wi_vap_delete(struct ieee80211vap *vap);
static void wi_stop_locked(struct wi_softc *sc, int disable);
static void wi_start_locked(struct ifnet *);
static void wi_start(struct ifnet *);
static int wi_start_tx(struct ifnet *ifp, struct wi_frame *frmhdr,
struct mbuf *m0);
static int wi_raw_xmit(struct ieee80211_node *, struct mbuf *,
const struct ieee80211_bpf_params *);
static int wi_newstate_sta(struct ieee80211vap *, enum ieee80211_state, int);
static int wi_newstate_hostap(struct ieee80211vap *, enum ieee80211_state,
int);
static void wi_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m,
int subtype, int rssi, int nf);
static int wi_reset(struct wi_softc *);
static void wi_watchdog(void *);
static int wi_ioctl(struct ifnet *, u_long, caddr_t);
static void wi_media_status(struct ifnet *, struct ifmediareq *);
static void wi_rx_intr(struct wi_softc *);
static void wi_tx_intr(struct wi_softc *);
static void wi_tx_ex_intr(struct wi_softc *);
static void wi_info_intr(struct wi_softc *);
static int wi_write_txrate(struct wi_softc *, struct ieee80211vap *);
static int wi_write_wep(struct wi_softc *, struct ieee80211vap *);
static int wi_write_multi(struct wi_softc *);
static void wi_update_mcast(struct ifnet *);
static void wi_update_promisc(struct ifnet *);
static int wi_alloc_fid(struct wi_softc *, int, int *);
static void wi_read_nicid(struct wi_softc *);
static int wi_write_ssid(struct wi_softc *, int, u_int8_t *, int);
static int wi_cmd(struct wi_softc *, int, int, int, int);
static int wi_seek_bap(struct wi_softc *, int, int);
static int wi_read_bap(struct wi_softc *, int, int, void *, int);
static int wi_write_bap(struct wi_softc *, int, int, void *, int);
static int wi_mwrite_bap(struct wi_softc *, int, int, struct mbuf *, int);
static int wi_read_rid(struct wi_softc *, int, void *, int *);
static int wi_write_rid(struct wi_softc *, int, void *, int);
static int wi_write_appie(struct wi_softc *, int, const struct ieee80211_appie *);
static void wi_scan_start(struct ieee80211com *);
static void wi_scan_end(struct ieee80211com *);
static void wi_set_channel(struct ieee80211com *);
static __inline int
wi_write_val(struct wi_softc *sc, int rid, u_int16_t val)
{
val = htole16(val);
return wi_write_rid(sc, rid, &val, sizeof(val));
}
SYSCTL_NODE(_hw, OID_AUTO, wi, CTLFLAG_RD, 0, "Wireless driver parameters");
static struct timeval lasttxerror; /* time of last tx error msg */
static int curtxeps; /* current tx error msgs/sec */
static int wi_txerate = 0; /* tx error rate: max msgs/sec */
SYSCTL_INT(_hw_wi, OID_AUTO, txerate, CTLFLAG_RW, &wi_txerate,
0, "max tx error msgs/sec; 0 to disable msgs");
#define WI_DEBUG
#ifdef WI_DEBUG
static int wi_debug = 0;
SYSCTL_INT(_hw_wi, OID_AUTO, debug, CTLFLAG_RW, &wi_debug,
0, "control debugging printfs");
#define DPRINTF(X) if (wi_debug) printf X
#else
#define DPRINTF(X)
#endif
#define WI_INTRS (WI_EV_RX | WI_EV_ALLOC | WI_EV_INFO)
struct wi_card_ident wi_card_ident[] = {
/* CARD_ID CARD_NAME FIRM_TYPE */
{ WI_NIC_LUCENT_ID, WI_NIC_LUCENT_STR, WI_LUCENT },
{ WI_NIC_SONY_ID, WI_NIC_SONY_STR, WI_LUCENT },
{ WI_NIC_LUCENT_EMB_ID, WI_NIC_LUCENT_EMB_STR, WI_LUCENT },
{ WI_NIC_EVB2_ID, WI_NIC_EVB2_STR, WI_INTERSIL },
{ WI_NIC_HWB3763_ID, WI_NIC_HWB3763_STR, WI_INTERSIL },
{ WI_NIC_HWB3163_ID, WI_NIC_HWB3163_STR, WI_INTERSIL },
{ WI_NIC_HWB3163B_ID, WI_NIC_HWB3163B_STR, WI_INTERSIL },
{ WI_NIC_EVB3_ID, WI_NIC_EVB3_STR, WI_INTERSIL },
{ WI_NIC_HWB1153_ID, WI_NIC_HWB1153_STR, WI_INTERSIL },
{ WI_NIC_P2_SST_ID, WI_NIC_P2_SST_STR, WI_INTERSIL },
{ WI_NIC_EVB2_SST_ID, WI_NIC_EVB2_SST_STR, WI_INTERSIL },
{ WI_NIC_3842_EVA_ID, WI_NIC_3842_EVA_STR, WI_INTERSIL },
{ WI_NIC_3842_PCMCIA_AMD_ID, WI_NIC_3842_PCMCIA_STR, WI_INTERSIL },
{ WI_NIC_3842_PCMCIA_SST_ID, WI_NIC_3842_PCMCIA_STR, WI_INTERSIL },
{ WI_NIC_3842_PCMCIA_ATL_ID, WI_NIC_3842_PCMCIA_STR, WI_INTERSIL },
{ WI_NIC_3842_PCMCIA_ATS_ID, WI_NIC_3842_PCMCIA_STR, WI_INTERSIL },
{ WI_NIC_3842_MINI_AMD_ID, WI_NIC_3842_MINI_STR, WI_INTERSIL },
{ WI_NIC_3842_MINI_SST_ID, WI_NIC_3842_MINI_STR, WI_INTERSIL },
{ WI_NIC_3842_MINI_ATL_ID, WI_NIC_3842_MINI_STR, WI_INTERSIL },
{ WI_NIC_3842_MINI_ATS_ID, WI_NIC_3842_MINI_STR, WI_INTERSIL },
{ WI_NIC_3842_PCI_AMD_ID, WI_NIC_3842_PCI_STR, WI_INTERSIL },
{ WI_NIC_3842_PCI_SST_ID, WI_NIC_3842_PCI_STR, WI_INTERSIL },
{ WI_NIC_3842_PCI_ATS_ID, WI_NIC_3842_PCI_STR, WI_INTERSIL },
{ WI_NIC_3842_PCI_ATL_ID, WI_NIC_3842_PCI_STR, WI_INTERSIL },
{ WI_NIC_P3_PCMCIA_AMD_ID, WI_NIC_P3_PCMCIA_STR, WI_INTERSIL },
{ WI_NIC_P3_PCMCIA_SST_ID, WI_NIC_P3_PCMCIA_STR, WI_INTERSIL },
{ WI_NIC_P3_PCMCIA_ATL_ID, WI_NIC_P3_PCMCIA_STR, WI_INTERSIL },
{ WI_NIC_P3_PCMCIA_ATS_ID, WI_NIC_P3_PCMCIA_STR, WI_INTERSIL },
{ WI_NIC_P3_MINI_AMD_ID, WI_NIC_P3_MINI_STR, WI_INTERSIL },
{ WI_NIC_P3_MINI_SST_ID, WI_NIC_P3_MINI_STR, WI_INTERSIL },
{ WI_NIC_P3_MINI_ATL_ID, WI_NIC_P3_MINI_STR, WI_INTERSIL },
{ WI_NIC_P3_MINI_ATS_ID, WI_NIC_P3_MINI_STR, WI_INTERSIL },
{ 0, NULL, 0 },
};
static char *wi_firmware_names[] = { "none", "Hermes", "Intersil", "Symbol" };
devclass_t wi_devclass;
int
wi_attach(device_t dev)
{
struct wi_softc *sc = device_get_softc(dev);
struct ieee80211com *ic;
struct ifnet *ifp;
int i, nrates, buflen;
u_int16_t val;
u_int8_t ratebuf[2 + IEEE80211_RATE_SIZE];
struct ieee80211_rateset *rs;
struct sysctl_ctx_list *sctx;
struct sysctl_oid *soid;
static const u_int8_t empty_macaddr[IEEE80211_ADDR_LEN] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
int error;
uint8_t macaddr[IEEE80211_ADDR_LEN];
ifp = sc->sc_ifp = if_alloc(IFT_IEEE80211);
if (ifp == NULL) {
device_printf(dev, "can not if_alloc\n");
wi_free(dev);
return ENOSPC;
}
ic = ifp->if_l2com;
sc->sc_firmware_type = WI_NOTYPE;
sc->wi_cmd_count = 500;
/* Reset the NIC. */
if (wi_reset(sc) != 0) {
wi_free(dev);
return ENXIO; /* XXX */
}
/* Read NIC identification */
wi_read_nicid(sc);
switch (sc->sc_firmware_type) {
case WI_LUCENT:
if (sc->sc_sta_firmware_ver < 60006)
goto reject;
break;
case WI_INTERSIL:
if (sc->sc_sta_firmware_ver < 800)
goto reject;
break;
default:
reject:
device_printf(dev, "Sorry, this card is not supported "
"(type %d, firmware ver %d)\n",
sc->sc_firmware_type, sc->sc_sta_firmware_ver);
wi_free(dev);
return EOPNOTSUPP;
}
/* Export info about the device via sysctl */
sctx = device_get_sysctl_ctx(dev);
soid = device_get_sysctl_tree(dev);
SYSCTL_ADD_STRING(sctx, SYSCTL_CHILDREN(soid), OID_AUTO,
"firmware_type", CTLFLAG_RD,
wi_firmware_names[sc->sc_firmware_type], 0,
"Firmware type string");
SYSCTL_ADD_INT(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "sta_version",
CTLFLAG_RD, &sc->sc_sta_firmware_ver, 0,
"Station Firmware version");
if (sc->sc_firmware_type == WI_INTERSIL)
SYSCTL_ADD_INT(sctx, SYSCTL_CHILDREN(soid), OID_AUTO,
"pri_version", CTLFLAG_RD, &sc->sc_pri_firmware_ver, 0,
"Primary Firmware version");
SYSCTL_ADD_XINT(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "nic_id",
CTLFLAG_RD, &sc->sc_nic_id, 0, "NIC id");
SYSCTL_ADD_STRING(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "nic_name",
CTLFLAG_RD, sc->sc_nic_name, 0, "NIC name");
mtx_init(&sc->sc_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK,
MTX_DEF | MTX_RECURSE);
callout_init_mtx(&sc->sc_watchdog, &sc->sc_mtx, 0);
/*
* Read the station address.
* And do it twice. I've seen PRISM-based cards that return
* an error when trying to read it the first time, which causes
* the probe to fail.
*/
buflen = IEEE80211_ADDR_LEN;
error = wi_read_rid(sc, WI_RID_MAC_NODE, macaddr, &buflen);
if (error != 0) {
buflen = IEEE80211_ADDR_LEN;
error = wi_read_rid(sc, WI_RID_MAC_NODE, macaddr, &buflen);
}
if (error || IEEE80211_ADDR_EQ(macaddr, empty_macaddr)) {
if (error != 0)
device_printf(dev, "mac read failed %d\n", error);
else {
device_printf(dev, "mac read failed (all zeros)\n");
error = ENXIO;
}
wi_free(dev);
return (error);
}
ifp->if_softc = sc;
if_initname(ifp, device_get_name(dev), device_get_unit(dev));
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
ifp->if_ioctl = wi_ioctl;
ifp->if_start = wi_start;
ifp->if_init = wi_init;
IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen);
ifp->if_snd.ifq_drv_maxlen = ifqmaxlen;
IFQ_SET_READY(&ifp->if_snd);
ic->ic_ifp = ifp;
ic->ic_phytype = IEEE80211_T_DS;
ic->ic_opmode = IEEE80211_M_STA;
ic->ic_caps = IEEE80211_C_STA
| IEEE80211_C_PMGT
| IEEE80211_C_MONITOR
;
/*
* Query the card for available channels and setup the
* channel table. We assume these are all 11b channels.
*/
buflen = sizeof(val);
if (wi_read_rid(sc, WI_RID_CHANNEL_LIST, &val, &buflen) != 0)
val = htole16(0x1fff); /* assume 1-11 */
KASSERT(val != 0, ("wi_attach: no available channels listed!"));
val <<= 1; /* shift for base 1 indices */
for (i = 1; i < 16; i++) {
struct ieee80211_channel *c;
if (!isset((u_int8_t*)&val, i))
continue;
c = &ic->ic_channels[ic->ic_nchans++];
c->ic_freq = ieee80211_ieee2mhz(i, IEEE80211_CHAN_B);
c->ic_flags = IEEE80211_CHAN_B;
c->ic_ieee = i;
/* XXX txpowers? */
}
/*
* Set flags based on firmware version.
*/
switch (sc->sc_firmware_type) {
case WI_LUCENT:
sc->sc_ntxbuf = 1;
ic->ic_caps |= IEEE80211_C_IBSS;
sc->sc_ibss_port = WI_PORTTYPE_BSS;
sc->sc_monitor_port = WI_PORTTYPE_ADHOC;
sc->sc_min_rssi = WI_LUCENT_MIN_RSSI;
sc->sc_max_rssi = WI_LUCENT_MAX_RSSI;
sc->sc_dbm_offset = WI_LUCENT_DBM_OFFSET;
break;
case WI_INTERSIL:
sc->sc_ntxbuf = WI_NTXBUF;
sc->sc_flags |= WI_FLAGS_HAS_FRAGTHR
| WI_FLAGS_HAS_ROAMING;
/*
* Old firmware are slow, so give peace a chance.
*/
if (sc->sc_sta_firmware_ver < 10000)
sc->wi_cmd_count = 5000;
if (sc->sc_sta_firmware_ver > 10101)
sc->sc_flags |= WI_FLAGS_HAS_DBMADJUST;
ic->ic_caps |= IEEE80211_C_IBSS;
/*
* version 0.8.3 and newer are the only ones that are known
* to currently work. Earlier versions can be made to work,
* at least according to the Linux driver but we require
* monitor mode so this is irrelevant.
*/
ic->ic_caps |= IEEE80211_C_HOSTAP;
if (sc->sc_sta_firmware_ver >= 10603)
sc->sc_flags |= WI_FLAGS_HAS_ENHSECURITY;
if (sc->sc_sta_firmware_ver >= 10700) {
/*
* 1.7.0+ have the necessary support for sta mode WPA.
*/
sc->sc_flags |= WI_FLAGS_HAS_WPASUPPORT;
ic->ic_caps |= IEEE80211_C_WPA;
}
sc->sc_ibss_port = WI_PORTTYPE_IBSS;
sc->sc_monitor_port = WI_PORTTYPE_APSILENT;
sc->sc_min_rssi = WI_PRISM_MIN_RSSI;
sc->sc_max_rssi = WI_PRISM_MAX_RSSI;
sc->sc_dbm_offset = WI_PRISM_DBM_OFFSET;
break;
}
/*
* Find out if we support WEP on this card.
*/
buflen = sizeof(val);
if (wi_read_rid(sc, WI_RID_WEP_AVAIL, &val, &buflen) == 0 &&
val != htole16(0))
ic->ic_cryptocaps |= IEEE80211_CRYPTO_WEP;
/* Find supported rates. */
buflen = sizeof(ratebuf);
rs = &ic->ic_sup_rates[IEEE80211_MODE_11B];
if (wi_read_rid(sc, WI_RID_DATA_RATES, ratebuf, &buflen) == 0) {
nrates = le16toh(*(u_int16_t *)ratebuf);
if (nrates > IEEE80211_RATE_MAXSIZE)
nrates = IEEE80211_RATE_MAXSIZE;
rs->rs_nrates = 0;
for (i = 0; i < nrates; i++)
if (ratebuf[2+i])
rs->rs_rates[rs->rs_nrates++] = ratebuf[2+i];
} else {
/* XXX fallback on error? */
}
buflen = sizeof(val);
if ((sc->sc_flags & WI_FLAGS_HAS_DBMADJUST) &&
wi_read_rid(sc, WI_RID_DBM_ADJUST, &val, &buflen) == 0) {
sc->sc_dbm_offset = le16toh(val);
}
sc->sc_portnum = WI_DEFAULT_PORT;
ieee80211_ifattach(ic, macaddr);
ic->ic_raw_xmit = wi_raw_xmit;
ic->ic_scan_start = wi_scan_start;
ic->ic_scan_end = wi_scan_end;
ic->ic_set_channel = wi_set_channel;
ic->ic_vap_create = wi_vap_create;
ic->ic_vap_delete = wi_vap_delete;
ic->ic_update_mcast = wi_update_mcast;
ic->ic_update_promisc = wi_update_promisc;
ieee80211_radiotap_attach(ic,
&sc->sc_tx_th.wt_ihdr, sizeof(sc->sc_tx_th),
WI_TX_RADIOTAP_PRESENT,
&sc->sc_rx_th.wr_ihdr, sizeof(sc->sc_rx_th),
WI_RX_RADIOTAP_PRESENT);
if (bootverbose)
ieee80211_announce(ic);
error = bus_setup_intr(dev, sc->irq, INTR_TYPE_NET | INTR_MPSAFE,
NULL, wi_intr, sc, &sc->wi_intrhand);
if (error) {
device_printf(dev, "bus_setup_intr() failed! (%d)\n", error);
ieee80211_ifdetach(ic);
if_free(sc->sc_ifp);
wi_free(dev);
return error;
}
return (0);
}
int
wi_detach(device_t dev)
{
struct wi_softc *sc = device_get_softc(dev);
struct ifnet *ifp = sc->sc_ifp;
struct ieee80211com *ic = ifp->if_l2com;
WI_LOCK(sc);
/* check if device was removed */
sc->wi_gone |= !bus_child_present(dev);
wi_stop_locked(sc, 0);
WI_UNLOCK(sc);
ieee80211_ifdetach(ic);
bus_teardown_intr(dev, sc->irq, sc->wi_intrhand);
if_free(sc->sc_ifp);
wi_free(dev);
mtx_destroy(&sc->sc_mtx);
return (0);
}
static struct ieee80211vap *
wi_vap_create(struct ieee80211com *ic,
const char name[IFNAMSIZ], int unit, int opmode, int flags,
const uint8_t bssid[IEEE80211_ADDR_LEN],
const uint8_t mac[IEEE80211_ADDR_LEN])
{
struct wi_softc *sc = ic->ic_ifp->if_softc;
struct wi_vap *wvp;
struct ieee80211vap *vap;
if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */
return NULL;
wvp = (struct wi_vap *) malloc(sizeof(struct wi_vap),
M_80211_VAP, M_NOWAIT | M_ZERO);
if (wvp == NULL)
return NULL;
vap = &wvp->wv_vap;
ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid, mac);
vap->iv_max_aid = WI_MAX_AID;
switch (opmode) {
case IEEE80211_M_STA:
sc->sc_porttype = WI_PORTTYPE_BSS;
wvp->wv_newstate = vap->iv_newstate;
vap->iv_newstate = wi_newstate_sta;
/* need to filter mgt frames to avoid confusing state machine */
wvp->wv_recv_mgmt = vap->iv_recv_mgmt;
vap->iv_recv_mgmt = wi_recv_mgmt;
break;
case IEEE80211_M_IBSS:
sc->sc_porttype = sc->sc_ibss_port;
wvp->wv_newstate = vap->iv_newstate;
vap->iv_newstate = wi_newstate_sta;
break;
case IEEE80211_M_AHDEMO:
sc->sc_porttype = WI_PORTTYPE_ADHOC;
break;
case IEEE80211_M_HOSTAP:
sc->sc_porttype = WI_PORTTYPE_HOSTAP;
wvp->wv_newstate = vap->iv_newstate;
vap->iv_newstate = wi_newstate_hostap;
break;
case IEEE80211_M_MONITOR:
sc->sc_porttype = sc->sc_monitor_port;
break;
default:
break;
}
/* complete setup */
ieee80211_vap_attach(vap, ieee80211_media_change, wi_media_status);
ic->ic_opmode = opmode;
return vap;
}
static void
wi_vap_delete(struct ieee80211vap *vap)
{
struct wi_vap *wvp = WI_VAP(vap);
ieee80211_vap_detach(vap);
free(wvp, M_80211_VAP);
}
int
wi_shutdown(device_t dev)
{
struct wi_softc *sc = device_get_softc(dev);
wi_stop(sc, 1);
return (0);
}
void
wi_intr(void *arg)
{
struct wi_softc *sc = arg;
struct ifnet *ifp = sc->sc_ifp;
u_int16_t status;
WI_LOCK(sc);
if (sc->wi_gone || !sc->sc_enabled || (ifp->if_flags & IFF_UP) == 0) {
CSR_WRITE_2(sc, WI_INT_EN, 0);
CSR_WRITE_2(sc, WI_EVENT_ACK, 0xFFFF);
WI_UNLOCK(sc);
return;
}
/* Disable interrupts. */
CSR_WRITE_2(sc, WI_INT_EN, 0);
status = CSR_READ_2(sc, WI_EVENT_STAT);
if (status & WI_EV_RX)
wi_rx_intr(sc);
if (status & WI_EV_ALLOC)
wi_tx_intr(sc);
if (status & WI_EV_TX_EXC)
wi_tx_ex_intr(sc);
if (status & WI_EV_INFO)
wi_info_intr(sc);
if ((ifp->if_drv_flags & IFF_DRV_OACTIVE) == 0 &&
!IFQ_DRV_IS_EMPTY(&ifp->if_snd))
wi_start_locked(ifp);
/* Re-enable interrupts. */
CSR_WRITE_2(sc, WI_INT_EN, WI_INTRS);
WI_UNLOCK(sc);
return;
}
static void
wi_enable(struct wi_softc *sc)
{
/* Enable interrupts */
CSR_WRITE_2(sc, WI_INT_EN, WI_INTRS);
/* enable port */
wi_cmd(sc, WI_CMD_ENABLE | sc->sc_portnum, 0, 0, 0);
sc->sc_enabled = 1;
}
static int
wi_setup_locked(struct wi_softc *sc, int porttype, int mode,
uint8_t mac[IEEE80211_ADDR_LEN])
{
int i;
wi_reset(sc);
wi_write_val(sc, WI_RID_PORTTYPE, porttype);
wi_write_val(sc, WI_RID_CREATE_IBSS, mode);
wi_write_val(sc, WI_RID_MAX_DATALEN, 2304);
/* XXX IEEE80211_BPF_NOACK wants 0 */
wi_write_val(sc, WI_RID_ALT_RETRY_CNT, 2);
if (sc->sc_flags & WI_FLAGS_HAS_ROAMING)
wi_write_val(sc, WI_RID_ROAMING_MODE, 3); /* NB: disabled */
wi_write_rid(sc, WI_RID_MAC_NODE, mac, IEEE80211_ADDR_LEN);
/* Allocate fids for the card */
sc->sc_buflen = IEEE80211_MAX_LEN + sizeof(struct wi_frame);
for (i = 0; i < sc->sc_ntxbuf; i++) {
int error = wi_alloc_fid(sc, sc->sc_buflen,
&sc->sc_txd[i].d_fid);
if (error) {
device_printf(sc->sc_dev,
"tx buffer allocation failed (error %u)\n",
error);
return error;
}
sc->sc_txd[i].d_len = 0;
}
sc->sc_txcur = sc->sc_txnext = 0;
return 0;
}
static void
wi_init_locked(struct wi_softc *sc)
{
struct ifnet *ifp = sc->sc_ifp;
int wasenabled;
WI_LOCK_ASSERT(sc);
wasenabled = sc->sc_enabled;
if (wasenabled)
wi_stop_locked(sc, 1);
if (wi_setup_locked(sc, sc->sc_porttype, 3, IF_LLADDR(ifp)) != 0) {
if_printf(ifp, "interface not running\n");
wi_stop_locked(sc, 1);
return;
}
ifp->if_drv_flags |= IFF_DRV_RUNNING;
ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
callout_reset(&sc->sc_watchdog, hz, wi_watchdog, sc);
wi_enable(sc); /* Enable desired port */
}
void
wi_init(void *arg)
{
struct wi_softc *sc = arg;
struct ifnet *ifp = sc->sc_ifp;
struct ieee80211com *ic = ifp->if_l2com;
WI_LOCK(sc);
wi_init_locked(sc);
WI_UNLOCK(sc);
if (ifp->if_drv_flags & IFF_DRV_RUNNING)
ieee80211_start_all(ic); /* start all vap's */
}
static void
wi_stop_locked(struct wi_softc *sc, int disable)
{
struct ifnet *ifp = sc->sc_ifp;
WI_LOCK_ASSERT(sc);
if (sc->sc_enabled && !sc->wi_gone) {
CSR_WRITE_2(sc, WI_INT_EN, 0);
wi_cmd(sc, WI_CMD_DISABLE | sc->sc_portnum, 0, 0, 0);
if (disable)
sc->sc_enabled = 0;
} else if (sc->wi_gone && disable) /* gone --> not enabled */
sc->sc_enabled = 0;
callout_stop(&sc->sc_watchdog);
sc->sc_tx_timer = 0;
sc->sc_false_syns = 0;
ifp->if_drv_flags &= ~(IFF_DRV_OACTIVE | IFF_DRV_RUNNING);
}
void
wi_stop(struct wi_softc *sc, int disable)
{
WI_LOCK(sc);
wi_stop_locked(sc, disable);
WI_UNLOCK(sc);
}
static void
wi_set_channel(struct ieee80211com *ic)
{
struct ifnet *ifp = ic->ic_ifp;
struct wi_softc *sc = ifp->if_softc;
DPRINTF(("%s: channel %d, %sscanning\n", __func__,
ieee80211_chan2ieee(ic, ic->ic_curchan),
ic->ic_flags & IEEE80211_F_SCAN ? "" : "!"));
WI_LOCK(sc);
wi_write_val(sc, WI_RID_OWN_CHNL,
ieee80211_chan2ieee(ic, ic->ic_curchan));
WI_UNLOCK(sc);
}
static void
wi_scan_start(struct ieee80211com *ic)
{
struct ifnet *ifp = ic->ic_ifp;
struct wi_softc *sc = ifp->if_softc;
struct ieee80211_scan_state *ss = ic->ic_scan;
DPRINTF(("%s\n", __func__));
WI_LOCK(sc);
/*
* Switch device to monitor mode.
*/
wi_write_val(sc, WI_RID_PORTTYPE, sc->sc_monitor_port);
if (sc->sc_firmware_type == WI_INTERSIL) {
wi_cmd(sc, WI_CMD_DISABLE | WI_PORT0, 0, 0, 0);
wi_cmd(sc, WI_CMD_ENABLE | WI_PORT0, 0, 0, 0);
}
/* force full dwell time to compensate for firmware overhead */
ss->ss_mindwell = ss->ss_maxdwell = msecs_to_ticks(400);
WI_UNLOCK(sc);
}
static void
wi_scan_end(struct ieee80211com *ic)
{
struct ifnet *ifp = ic->ic_ifp;
struct wi_softc *sc = ifp->if_softc;
DPRINTF(("%s: restore port type %d\n", __func__, sc->sc_porttype));
WI_LOCK(sc);
wi_write_val(sc, WI_RID_PORTTYPE, sc->sc_porttype);
if (sc->sc_firmware_type == WI_INTERSIL) {
wi_cmd(sc, WI_CMD_DISABLE | WI_PORT0, 0, 0, 0);
wi_cmd(sc, WI_CMD_ENABLE | WI_PORT0, 0, 0, 0);
}
WI_UNLOCK(sc);
}
static void
wi_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m,
int subtype, int rssi, int nf)
{
struct ieee80211vap *vap = ni->ni_vap;
switch (subtype) {
case IEEE80211_FC0_SUBTYPE_AUTH:
case IEEE80211_FC0_SUBTYPE_ASSOC_RESP:
case IEEE80211_FC0_SUBTYPE_REASSOC_RESP:
/* NB: filter frames that trigger state changes */
return;
}
WI_VAP(vap)->wv_recv_mgmt(ni, m, subtype, rssi, nf);
}
static int
wi_newstate_sta(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
{
struct ieee80211com *ic = vap->iv_ic;
struct ifnet *ifp = ic->ic_ifp;
struct ieee80211_node *bss;
struct wi_softc *sc = ifp->if_softc;
DPRINTF(("%s: %s -> %s\n", __func__,
ieee80211_state_name[vap->iv_state],
ieee80211_state_name[nstate]));
if (nstate == IEEE80211_S_AUTH) {
WI_LOCK(sc);
wi_setup_locked(sc, WI_PORTTYPE_BSS, 3, vap->iv_myaddr);
if (vap->iv_flags & IEEE80211_F_PMGTON) {
wi_write_val(sc, WI_RID_MAX_SLEEP, ic->ic_lintval);
wi_write_val(sc, WI_RID_PM_ENABLED, 1);
}
wi_write_val(sc, WI_RID_RTS_THRESH, vap->iv_rtsthreshold);
if (sc->sc_flags & WI_FLAGS_HAS_FRAGTHR)
wi_write_val(sc, WI_RID_FRAG_THRESH,
vap->iv_fragthreshold);
wi_write_txrate(sc, vap);
bss = vap->iv_bss;
wi_write_ssid(sc, WI_RID_DESIRED_SSID, bss->ni_essid, bss->ni_esslen);
wi_write_val(sc, WI_RID_OWN_CHNL,
ieee80211_chan2ieee(ic, bss->ni_chan));
/* Configure WEP. */
if (ic->ic_cryptocaps & IEEE80211_CRYPTO_WEP)
wi_write_wep(sc, vap);
else
sc->sc_encryption = 0;
if ((sc->sc_flags & WI_FLAGS_HAS_WPASUPPORT) &&
(vap->iv_flags & IEEE80211_F_WPA)) {
wi_write_val(sc, WI_RID_WPA_HANDLING, 1);
if (vap->iv_appie_wpa != NULL)
wi_write_appie(sc, WI_RID_WPA_DATA,
vap->iv_appie_wpa);
}
wi_enable(sc); /* enable port */
/* Lucent firmware does not support the JOIN RID. */
if (sc->sc_firmware_type == WI_INTERSIL) {
struct wi_joinreq join;
memset(&join, 0, sizeof(join));
IEEE80211_ADDR_COPY(&join.wi_bssid, bss->ni_bssid);
join.wi_chan = htole16(
ieee80211_chan2ieee(ic, bss->ni_chan));
wi_write_rid(sc, WI_RID_JOIN_REQ, &join, sizeof(join));
}
WI_UNLOCK(sc);
/*
* NB: don't go through 802.11 layer, it'll send auth frame;
* instead we drive the state machine from the link status
* notification we get on association.
*/
vap->iv_state = nstate;
return (0);
}
return WI_VAP(vap)->wv_newstate(vap, nstate, arg);
}
static int
wi_newstate_hostap(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
{
struct ieee80211com *ic = vap->iv_ic;
struct ifnet *ifp = ic->ic_ifp;
struct ieee80211_node *bss;
struct wi_softc *sc = ifp->if_softc;
int error;
DPRINTF(("%s: %s -> %s\n", __func__,
ieee80211_state_name[vap->iv_state],
ieee80211_state_name[nstate]));
error = WI_VAP(vap)->wv_newstate(vap, nstate, arg);
if (error == 0 && nstate == IEEE80211_S_RUN) {
WI_LOCK(sc);
wi_setup_locked(sc, WI_PORTTYPE_HOSTAP, 0, vap->iv_myaddr);
bss = vap->iv_bss;
wi_write_ssid(sc, WI_RID_OWN_SSID,
bss->ni_essid, bss->ni_esslen);
wi_write_val(sc, WI_RID_OWN_CHNL,
ieee80211_chan2ieee(ic, bss->ni_chan));
wi_write_val(sc, WI_RID_BASIC_RATE, 0x3);
wi_write_val(sc, WI_RID_SUPPORT_RATE, 0xf);
wi_write_txrate(sc, vap);
wi_write_val(sc, WI_RID_OWN_BEACON_INT, bss->ni_intval);
wi_write_val(sc, WI_RID_DTIM_PERIOD, vap->iv_dtim_period);
wi_write_val(sc, WI_RID_RTS_THRESH, vap->iv_rtsthreshold);
if (sc->sc_flags & WI_FLAGS_HAS_FRAGTHR)
wi_write_val(sc, WI_RID_FRAG_THRESH,
vap->iv_fragthreshold);
if ((sc->sc_flags & WI_FLAGS_HAS_ENHSECURITY) &&
(vap->iv_flags & IEEE80211_F_HIDESSID)) {
/*
* bit 0 means hide SSID in beacons,
* bit 1 means don't respond to bcast probe req
*/
wi_write_val(sc, WI_RID_ENH_SECURITY, 0x3);
}
if ((sc->sc_flags & WI_FLAGS_HAS_WPASUPPORT) &&
(vap->iv_flags & IEEE80211_F_WPA) &&
vap->iv_appie_wpa != NULL)
wi_write_appie(sc, WI_RID_WPA_DATA, vap->iv_appie_wpa);
wi_write_val(sc, WI_RID_PROMISC, 0);
/* Configure WEP. */
if (ic->ic_cryptocaps & IEEE80211_CRYPTO_WEP)
wi_write_wep(sc, vap);
else
sc->sc_encryption = 0;
wi_enable(sc); /* enable port */
WI_UNLOCK(sc);
}
return error;
}
static void
wi_start_locked(struct ifnet *ifp)
{
struct wi_softc *sc = ifp->if_softc;
struct ieee80211_node *ni;
struct ieee80211_frame *wh;
struct mbuf *m0;
struct ieee80211_key *k;
struct wi_frame frmhdr;
const struct llc *llc;
int cur;
WI_LOCK_ASSERT(sc);
if (sc->wi_gone)
return;
memset(&frmhdr, 0, sizeof(frmhdr));
cur = sc->sc_txnext;
for (;;) {
IFQ_DRV_DEQUEUE(&ifp->if_snd, m0);
if (m0 == NULL)
break;
if (sc->sc_txd[cur].d_len != 0) {
IFQ_DRV_PREPEND(&ifp->if_snd, m0);
ifp->if_drv_flags |= IFF_DRV_OACTIVE;
break;
}
ni = (struct ieee80211_node *) m0->m_pkthdr.rcvif;
/* reconstruct 802.3 header */
wh = mtod(m0, struct ieee80211_frame *);
switch (wh->i_fc[1]) {
case IEEE80211_FC1_DIR_TODS:
IEEE80211_ADDR_COPY(frmhdr.wi_ehdr.ether_shost,
wh->i_addr2);
IEEE80211_ADDR_COPY(frmhdr.wi_ehdr.ether_dhost,
wh->i_addr3);
break;
case IEEE80211_FC1_DIR_NODS:
IEEE80211_ADDR_COPY(frmhdr.wi_ehdr.ether_shost,
wh->i_addr2);
IEEE80211_ADDR_COPY(frmhdr.wi_ehdr.ether_dhost,
wh->i_addr1);
break;
case IEEE80211_FC1_DIR_FROMDS:
IEEE80211_ADDR_COPY(frmhdr.wi_ehdr.ether_shost,
wh->i_addr3);
IEEE80211_ADDR_COPY(frmhdr.wi_ehdr.ether_dhost,
wh->i_addr1);
break;
}
llc = (const struct llc *)(
mtod(m0, const uint8_t *) + ieee80211_hdrsize(wh));
frmhdr.wi_ehdr.ether_type = llc->llc_snap.ether_type;
frmhdr.wi_tx_ctl = htole16(WI_ENC_TX_802_11|WI_TXCNTL_TX_EX);
if (wh->i_fc[1] & IEEE80211_FC1_WEP) {
k = ieee80211_crypto_encap(ni, m0);
if (k == NULL) {
ieee80211_free_node(ni);
m_freem(m0);
continue;
}
frmhdr.wi_tx_ctl |= htole16(WI_TXCNTL_NOCRYPT);
}
if (ieee80211_radiotap_active_vap(ni->ni_vap)) {
sc->sc_tx_th.wt_rate = ni->ni_txrate;
ieee80211_radiotap_tx(ni->ni_vap, m0);
}
m_copydata(m0, 0, sizeof(struct ieee80211_frame),
(caddr_t)&frmhdr.wi_whdr);
m_adj(m0, sizeof(struct ieee80211_frame));
frmhdr.wi_dat_len = htole16(m0->m_pkthdr.len);
ieee80211_free_node(ni);
if (wi_start_tx(ifp, &frmhdr, m0))
continue;
sc->sc_txnext = cur = (cur + 1) % sc->sc_ntxbuf;
ifp->if_opackets++;
}
}
static void
wi_start(struct ifnet *ifp)
{
struct wi_softc *sc = ifp->if_softc;
WI_LOCK(sc);
wi_start_locked(ifp);
WI_UNLOCK(sc);
}
static int
wi_start_tx(struct ifnet *ifp, struct wi_frame *frmhdr, struct mbuf *m0)
{
struct wi_softc *sc = ifp->if_softc;
int cur = sc->sc_txnext;
int fid, off, error;
fid = sc->sc_txd[cur].d_fid;
off = sizeof(*frmhdr);
error = wi_write_bap(sc, fid, 0, frmhdr, sizeof(*frmhdr)) != 0
|| wi_mwrite_bap(sc, fid, off, m0, m0->m_pkthdr.len) != 0;
m_freem(m0);
if (error) {
ifp->if_oerrors++;
return -1;
}
sc->sc_txd[cur].d_len = off;
if (sc->sc_txcur == cur) {
if (wi_cmd(sc, WI_CMD_TX | WI_RECLAIM, fid, 0, 0)) {
if_printf(ifp, "xmit failed\n");
sc->sc_txd[cur].d_len = 0;
return -1;
}
sc->sc_tx_timer = 5;
}
return 0;
}
static int
wi_raw_xmit(struct ieee80211_node *ni, struct mbuf *m0,
const struct ieee80211_bpf_params *params)
{
struct ieee80211com *ic = ni->ni_ic;
struct ifnet *ifp = ic->ic_ifp;
struct ieee80211vap *vap = ni->ni_vap;
struct wi_softc *sc = ifp->if_softc;
struct ieee80211_key *k;
struct ieee80211_frame *wh;
struct wi_frame frmhdr;
int cur;
int rc = 0;
WI_LOCK(sc);
if (sc->wi_gone) {
rc = ENETDOWN;
goto out;
}
memset(&frmhdr, 0, sizeof(frmhdr));
cur = sc->sc_txnext;
if (sc->sc_txd[cur].d_len != 0) {
ifp->if_drv_flags |= IFF_DRV_OACTIVE;
rc = ENOBUFS;
goto out;
}
m0->m_pkthdr.rcvif = NULL;
m_copydata(m0, 4, ETHER_ADDR_LEN * 2,
(caddr_t)&frmhdr.wi_ehdr);
frmhdr.wi_ehdr.ether_type = 0;
wh = mtod(m0, struct ieee80211_frame *);
frmhdr.wi_tx_ctl = htole16(WI_ENC_TX_802_11|WI_TXCNTL_TX_EX);
if (params && (params->ibp_flags & IEEE80211_BPF_NOACK))
frmhdr.wi_tx_ctl |= htole16(WI_TXCNTL_ALTRTRY);
if ((wh->i_fc[1] & IEEE80211_FC1_WEP) &&
(!params || (params && (params->ibp_flags & IEEE80211_BPF_CRYPTO)))) {
k = ieee80211_crypto_encap(ni, m0);
if (k == NULL) {
rc = ENOMEM;
goto out;
}
frmhdr.wi_tx_ctl |= htole16(WI_TXCNTL_NOCRYPT);
}
if (ieee80211_radiotap_active_vap(vap)) {
sc->sc_tx_th.wt_rate = ni->ni_txrate;
ieee80211_radiotap_tx(vap, m0);
}
m_copydata(m0, 0, sizeof(struct ieee80211_frame),
(caddr_t)&frmhdr.wi_whdr);
m_adj(m0, sizeof(struct ieee80211_frame));
frmhdr.wi_dat_len = htole16(m0->m_pkthdr.len);
if (wi_start_tx(ifp, &frmhdr, m0) < 0) {
m0 = NULL;
rc = EIO;
goto out;
}
m0 = NULL;
sc->sc_txnext = cur = (cur + 1) % sc->sc_ntxbuf;
out:
WI_UNLOCK(sc);
if (m0 != NULL)
m_freem(m0);
ieee80211_free_node(ni);
return rc;
}
static int
wi_reset(struct wi_softc *sc)
{
#define WI_INIT_TRIES 3
int i, error = 0;
for (i = 0; i < WI_INIT_TRIES; i++) {
error = wi_cmd(sc, WI_CMD_INI, 0, 0, 0);
if (error == 0)
break;
DELAY(WI_DELAY * 1000);
}
sc->sc_reset = 1;
if (i == WI_INIT_TRIES) {
if_printf(sc->sc_ifp, "reset failed\n");
return error;
}
CSR_WRITE_2(sc, WI_INT_EN, 0);
CSR_WRITE_2(sc, WI_EVENT_ACK, 0xFFFF);
/* Calibrate timer. */
wi_write_val(sc, WI_RID_TICK_TIME, 8);
return 0;
#undef WI_INIT_TRIES
}
static void
wi_watchdog(void *arg)
{
struct wi_softc *sc = arg;
struct ifnet *ifp = sc->sc_ifp;
WI_LOCK_ASSERT(sc);
if (!sc->sc_enabled)
return;
if (sc->sc_tx_timer && --sc->sc_tx_timer == 0) {
if_printf(ifp, "device timeout\n");
ifp->if_oerrors++;
wi_init_locked(ifp->if_softc);
return;
}
callout_reset(&sc->sc_watchdog, hz, wi_watchdog, sc);
}
static int
wi_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
{
struct wi_softc *sc = ifp->if_softc;
struct ieee80211com *ic = ifp->if_l2com;
struct ifreq *ifr = (struct ifreq *) data;
int error = 0, startall = 0;
switch (cmd) {
case SIOCSIFFLAGS:
WI_LOCK(sc);
/*
* Can't do promisc and hostap at the same time. If all that's
* changing is the promisc flag, try to short-circuit a call to
* wi_init() by just setting PROMISC in the hardware.
*/
if (ifp->if_flags & IFF_UP) {
if (ic->ic_opmode != IEEE80211_M_HOSTAP &&
ifp->if_drv_flags & IFF_DRV_RUNNING) {
if ((ifp->if_flags ^ sc->sc_if_flags) & IFF_PROMISC) {
wi_write_val(sc, WI_RID_PROMISC,
(ifp->if_flags & IFF_PROMISC) != 0);
} else {
wi_init_locked(sc);
startall = 1;
}
} else {
wi_init_locked(sc);
startall = 1;
}
} else {
if (ifp->if_drv_flags & IFF_DRV_RUNNING)
wi_stop_locked(sc, 1);
sc->wi_gone = 0;
}
sc->sc_if_flags = ifp->if_flags;
WI_UNLOCK(sc);
if (startall)
ieee80211_start_all(ic);
break;
case SIOCGIFMEDIA:
error = ifmedia_ioctl(ifp, ifr, &ic->ic_media, cmd);
break;
case SIOCGIFADDR:
error = ether_ioctl(ifp, cmd, data);
break;
default:
error = EINVAL;
break;
}
return error;
}
static void
wi_media_status(struct ifnet *ifp, struct ifmediareq *imr)
{
struct ieee80211vap *vap = ifp->if_softc;
struct ieee80211com *ic = vap->iv_ic;
struct wi_softc *sc = ic->ic_ifp->if_softc;
u_int16_t val;
int rate, len;
len = sizeof(val);
if (sc->sc_enabled &&
wi_read_rid(sc, WI_RID_CUR_TX_RATE, &val, &len) == 0 &&
len == sizeof(val)) {
/* convert to 802.11 rate */
val = le16toh(val);
rate = val * 2;
if (sc->sc_firmware_type == WI_LUCENT) {
if (rate == 10)
rate = 11; /* 5.5Mbps */
} else {
if (rate == 4*2)
rate = 11; /* 5.5Mbps */
else if (rate == 8*2)
rate = 22; /* 11Mbps */
}
vap->iv_bss->ni_txrate = rate;
}
ieee80211_media_status(ifp, imr);
}
static void
wi_sync_bssid(struct wi_softc *sc, u_int8_t new_bssid[IEEE80211_ADDR_LEN])
{
struct ifnet *ifp = sc->sc_ifp;
struct ieee80211com *ic = ifp->if_l2com;
struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
struct ieee80211_node *ni = vap->iv_bss;
if (IEEE80211_ADDR_EQ(new_bssid, ni->ni_bssid))
return;
DPRINTF(("wi_sync_bssid: bssid %s -> ", ether_sprintf(ni->ni_bssid)));
DPRINTF(("%s ?\n", ether_sprintf(new_bssid)));
/* In promiscuous mode, the BSSID field is not a reliable
* indicator of the firmware's BSSID. Damp spurious
* change-of-BSSID indications.
*/
if ((ifp->if_flags & IFF_PROMISC) != 0 &&
!ppsratecheck(&sc->sc_last_syn, &sc->sc_false_syns,
WI_MAX_FALSE_SYNS))
return;
sc->sc_false_syns = MAX(0, sc->sc_false_syns - 1);
#if 0
/*
* XXX hack; we should create a new node with the new bssid
* and replace the existing ic_bss with it but since we don't
* process management frames to collect state we cheat by
* reusing the existing node as we know wi_newstate will be
* called and it will overwrite the node state.
*/
ieee80211_sta_join(ic, ieee80211_ref_node(ni));
#endif
}
static __noinline void
wi_rx_intr(struct wi_softc *sc)
{
struct ifnet *ifp = sc->sc_ifp;
struct ieee80211com *ic = ifp->if_l2com;
struct wi_frame frmhdr;
struct mbuf *m;
struct ieee80211_frame *wh;
struct ieee80211_node *ni;
int fid, len, off;
u_int8_t dir;
u_int16_t status;
int8_t rssi, nf;
fid = CSR_READ_2(sc, WI_RX_FID);
/* First read in the frame header */
if (wi_read_bap(sc, fid, 0, &frmhdr, sizeof(frmhdr))) {
CSR_WRITE_2(sc, WI_EVENT_ACK, WI_EV_RX);
ifp->if_ierrors++;
DPRINTF(("wi_rx_intr: read fid %x failed\n", fid));
return;
}
/*
* Drop undecryptable or packets with receive errors here
*/
status = le16toh(frmhdr.wi_status);
if (status & WI_STAT_ERRSTAT) {
CSR_WRITE_2(sc, WI_EVENT_ACK, WI_EV_RX);
ifp->if_ierrors++;
DPRINTF(("wi_rx_intr: fid %x error status %x\n", fid, status));
return;
}
len = le16toh(frmhdr.wi_dat_len);
off = ALIGN(sizeof(struct ieee80211_frame));
/*
* Sometimes the PRISM2.x returns bogusly large frames. Except
* in monitor mode, just throw them away.
*/
if (off + len > MCLBYTES) {
if (ic->ic_opmode != IEEE80211_M_MONITOR) {
CSR_WRITE_2(sc, WI_EVENT_ACK, WI_EV_RX);
ifp->if_ierrors++;
DPRINTF(("wi_rx_intr: oversized packet\n"));
return;
} else
len = 0;
}
if (off + len > MHLEN)
m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR);
else
m = m_gethdr(M_DONTWAIT, MT_DATA);
if (m == NULL) {
CSR_WRITE_2(sc, WI_EVENT_ACK, WI_EV_RX);
ifp->if_ierrors++;
DPRINTF(("wi_rx_intr: MGET failed\n"));
return;
}
m->m_data += off - sizeof(struct ieee80211_frame);
memcpy(m->m_data, &frmhdr.wi_whdr, sizeof(struct ieee80211_frame));
wi_read_bap(sc, fid, sizeof(frmhdr),
m->m_data + sizeof(struct ieee80211_frame), len);
m->m_pkthdr.len = m->m_len = sizeof(struct ieee80211_frame) + len;
m->m_pkthdr.rcvif = ifp;
CSR_WRITE_2(sc, WI_EVENT_ACK, WI_EV_RX);
rssi = frmhdr.wi_rx_signal;
nf = frmhdr.wi_rx_silence;
if (ieee80211_radiotap_active(ic)) {
struct wi_rx_radiotap_header *tap = &sc->sc_rx_th;
uint32_t rstamp;
rstamp = (le16toh(frmhdr.wi_rx_tstamp0) << 16) |
le16toh(frmhdr.wi_rx_tstamp1);
tap->wr_tsf = htole64((uint64_t)rstamp);
/* XXX replace divide by table */
tap->wr_rate = frmhdr.wi_rx_rate / 5;
tap->wr_flags = 0;
if (frmhdr.wi_status & WI_STAT_PCF)
tap->wr_flags |= IEEE80211_RADIOTAP_F_CFP;
if (m->m_flags & M_WEP)
tap->wr_flags |= IEEE80211_RADIOTAP_F_WEP;
tap->wr_antsignal = rssi;
tap->wr_antnoise = nf;
}
/* synchronize driver's BSSID with firmware's BSSID */
wh = mtod(m, struct ieee80211_frame *);
dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK;
if (ic->ic_opmode == IEEE80211_M_IBSS && dir == IEEE80211_FC1_DIR_NODS)
wi_sync_bssid(sc, wh->i_addr3);
WI_UNLOCK(sc);
ni = ieee80211_find_rxnode(ic, mtod(m, struct ieee80211_frame_min *));
if (ni != NULL) {
(void) ieee80211_input(ni, m, rssi, nf);
ieee80211_free_node(ni);
} else
(void) ieee80211_input_all(ic, m, rssi, nf);
WI_LOCK(sc);
}
static __noinline void
wi_tx_ex_intr(struct wi_softc *sc)
{
struct ifnet *ifp = sc->sc_ifp;
struct wi_frame frmhdr;
int fid;
fid = CSR_READ_2(sc, WI_TX_CMP_FID);
/* Read in the frame header */
if (wi_read_bap(sc, fid, 0, &frmhdr, sizeof(frmhdr)) == 0) {
u_int16_t status = le16toh(frmhdr.wi_status);
/*
* Spontaneous station disconnects appear as xmit
* errors. Don't announce them and/or count them
* as an output error.
*/
if ((status & WI_TXSTAT_DISCONNECT) == 0) {
if (ppsratecheck(&lasttxerror, &curtxeps, wi_txerate)) {
if_printf(ifp, "tx failed");
if (status & WI_TXSTAT_RET_ERR)
printf(", retry limit exceeded");
if (status & WI_TXSTAT_AGED_ERR)
printf(", max transmit lifetime exceeded");
if (status & WI_TXSTAT_DISCONNECT)
printf(", port disconnected");
if (status & WI_TXSTAT_FORM_ERR)
printf(", invalid format (data len %u src %6D)",
le16toh(frmhdr.wi_dat_len),
frmhdr.wi_ehdr.ether_shost, ":");
if (status & ~0xf)
printf(", status=0x%x", status);
printf("\n");
}
ifp->if_oerrors++;
} else {
DPRINTF(("port disconnected\n"));
ifp->if_collisions++; /* XXX */
}
} else
DPRINTF(("wi_tx_ex_intr: read fid %x failed\n", fid));
CSR_WRITE_2(sc, WI_EVENT_ACK, WI_EV_TX_EXC);
}
static __noinline void
wi_tx_intr(struct wi_softc *sc)
{
struct ifnet *ifp = sc->sc_ifp;
int fid, cur;
if (sc->wi_gone)
return;
fid = CSR_READ_2(sc, WI_ALLOC_FID);
CSR_WRITE_2(sc, WI_EVENT_ACK, WI_EV_ALLOC);
cur = sc->sc_txcur;
if (sc->sc_txd[cur].d_fid != fid) {
if_printf(ifp, "bad alloc %x != %x, cur %d nxt %d\n",
fid, sc->sc_txd[cur].d_fid, cur, sc->sc_txnext);
return;
}
sc->sc_tx_timer = 0;
sc->sc_txd[cur].d_len = 0;
sc->sc_txcur = cur = (cur + 1) % sc->sc_ntxbuf;
if (sc->sc_txd[cur].d_len == 0)
ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
else {
if (wi_cmd(sc, WI_CMD_TX | WI_RECLAIM, sc->sc_txd[cur].d_fid,
0, 0)) {
if_printf(ifp, "xmit failed\n");
sc->sc_txd[cur].d_len = 0;
} else {
sc->sc_tx_timer = 5;
}
}
}
static __noinline void
wi_info_intr(struct wi_softc *sc)
{
struct ifnet *ifp = sc->sc_ifp;
struct ieee80211com *ic = ifp->if_l2com;
struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
int i, fid, len, off;
u_int16_t ltbuf[2];
u_int16_t stat;
u_int32_t *ptr;
fid = CSR_READ_2(sc, WI_INFO_FID);
wi_read_bap(sc, fid, 0, ltbuf, sizeof(ltbuf));
switch (le16toh(ltbuf[1])) {
case WI_INFO_LINK_STAT:
wi_read_bap(sc, fid, sizeof(ltbuf), &stat, sizeof(stat));
DPRINTF(("wi_info_intr: LINK_STAT 0x%x\n", le16toh(stat)));
switch (le16toh(stat)) {
case WI_INFO_LINK_STAT_CONNECTED:
if (vap->iv_state == IEEE80211_S_RUN &&
vap->iv_opmode != IEEE80211_M_IBSS)
break;
/* fall thru... */
case WI_INFO_LINK_STAT_AP_CHG:
IEEE80211_LOCK(ic);
vap->iv_bss->ni_associd = 1 | 0xc000; /* NB: anything will do */
ieee80211_new_state(vap, IEEE80211_S_RUN, 0);
IEEE80211_UNLOCK(ic);
break;
case WI_INFO_LINK_STAT_AP_INR:
break;
case WI_INFO_LINK_STAT_DISCONNECTED:
/* we dropped off the net; e.g. due to deauth/disassoc */
IEEE80211_LOCK(ic);
vap->iv_bss->ni_associd = 0;
vap->iv_stats.is_rx_deauth++;
ieee80211_new_state(vap, IEEE80211_S_SCAN, 0);
IEEE80211_UNLOCK(ic);
break;
case WI_INFO_LINK_STAT_AP_OOR:
/* XXX does this need to be per-vap? */
ieee80211_beacon_miss(ic);
break;
case WI_INFO_LINK_STAT_ASSOC_FAILED:
if (vap->iv_opmode == IEEE80211_M_STA)
ieee80211_new_state(vap, IEEE80211_S_SCAN,
IEEE80211_SCAN_FAIL_TIMEOUT);
break;
}
break;
case WI_INFO_COUNTERS:
/* some card versions have a larger stats structure */
len = min(le16toh(ltbuf[0]) - 1, sizeof(sc->sc_stats) / 4);
ptr = (u_int32_t *)&sc->sc_stats;
off = sizeof(ltbuf);
for (i = 0; i < len; i++, off += 2, ptr++) {
wi_read_bap(sc, fid, off, &stat, sizeof(stat));
#ifdef WI_HERMES_STATS_WAR
if (stat & 0xf000)
stat = ~stat;
#endif
*ptr += stat;
}
ifp->if_collisions = sc->sc_stats.wi_tx_single_retries +
sc->sc_stats.wi_tx_multi_retries +
sc->sc_stats.wi_tx_retry_limit;
break;
default:
DPRINTF(("wi_info_intr: got fid %x type %x len %d\n", fid,
le16toh(ltbuf[1]), le16toh(ltbuf[0])));
break;
}
CSR_WRITE_2(sc, WI_EVENT_ACK, WI_EV_INFO);
}
static int
wi_write_multi(struct wi_softc *sc)
{
struct ifnet *ifp = sc->sc_ifp;
int n;
struct ifmultiaddr *ifma;
struct wi_mcast mlist;
if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) {
allmulti:
memset(&mlist, 0, sizeof(mlist));
return wi_write_rid(sc, WI_RID_MCAST_LIST, &mlist,
sizeof(mlist));
}
n = 0;
if_maddr_rlock(ifp);
TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
if (ifma->ifma_addr->sa_family != AF_LINK)
continue;
if (n >= 16)
goto allmulti;
IEEE80211_ADDR_COPY(&mlist.wi_mcast[n],
(LLADDR((struct sockaddr_dl *)ifma->ifma_addr)));
n++;
}
if_maddr_runlock(ifp);
return wi_write_rid(sc, WI_RID_MCAST_LIST, &mlist,
IEEE80211_ADDR_LEN * n);
}
static void
wi_update_mcast(struct ifnet *ifp)
{
wi_write_multi(ifp->if_softc);
}
static void
wi_update_promisc(struct ifnet *ifp)
{
struct wi_softc *sc = ifp->if_softc;
struct ieee80211com *ic = ifp->if_l2com;
WI_LOCK(sc);
/* XXX handle WEP special case handling? */
wi_write_val(sc, WI_RID_PROMISC,
(ic->ic_opmode == IEEE80211_M_MONITOR ||
(ifp->if_flags & IFF_PROMISC)));
WI_UNLOCK(sc);
}
static void
wi_read_nicid(struct wi_softc *sc)
{
struct wi_card_ident *id;
char *p;
int len;
u_int16_t ver[4];
/* getting chip identity */
memset(ver, 0, sizeof(ver));
len = sizeof(ver);
wi_read_rid(sc, WI_RID_CARD_ID, ver, &len);
sc->sc_firmware_type = WI_NOTYPE;
sc->sc_nic_id = le16toh(ver[0]);
for (id = wi_card_ident; id->card_name != NULL; id++) {
if (sc->sc_nic_id == id->card_id) {
sc->sc_nic_name = id->card_name;
sc->sc_firmware_type = id->firm_type;
break;
}
}
if (sc->sc_firmware_type == WI_NOTYPE) {
if (sc->sc_nic_id & 0x8000) {
sc->sc_firmware_type = WI_INTERSIL;
sc->sc_nic_name = "Unknown Prism chip";
} else {
sc->sc_firmware_type = WI_LUCENT;
sc->sc_nic_name = "Unknown Lucent chip";
}
}
if (bootverbose)
device_printf(sc->sc_dev, "using %s\n", sc->sc_nic_name);
/* get primary firmware version (Only Prism chips) */
if (sc->sc_firmware_type != WI_LUCENT) {
memset(ver, 0, sizeof(ver));
len = sizeof(ver);
wi_read_rid(sc, WI_RID_PRI_IDENTITY, ver, &len);
sc->sc_pri_firmware_ver = le16toh(ver[2]) * 10000 +
le16toh(ver[3]) * 100 + le16toh(ver[1]);
}
/* get station firmware version */
memset(ver, 0, sizeof(ver));
len = sizeof(ver);
wi_read_rid(sc, WI_RID_STA_IDENTITY, ver, &len);
sc->sc_sta_firmware_ver = le16toh(ver[2]) * 10000 +
le16toh(ver[3]) * 100 + le16toh(ver[1]);
if (sc->sc_firmware_type == WI_INTERSIL &&
(sc->sc_sta_firmware_ver == 10102 ||
sc->sc_sta_firmware_ver == 20102)) {
char ident[12];
memset(ident, 0, sizeof(ident));
len = sizeof(ident);
/* value should be the format like "V2.00-11" */
if (wi_read_rid(sc, WI_RID_SYMBOL_IDENTITY, ident, &len) == 0 &&
*(p = (char *)ident) >= 'A' &&
p[2] == '.' && p[5] == '-' && p[8] == '\0') {
sc->sc_firmware_type = WI_SYMBOL;
sc->sc_sta_firmware_ver = (p[1] - '0') * 10000 +
(p[3] - '0') * 1000 + (p[4] - '0') * 100 +
(p[6] - '0') * 10 + (p[7] - '0');
}
}
if (bootverbose) {
device_printf(sc->sc_dev, "%s Firmware: ",
wi_firmware_names[sc->sc_firmware_type]);
if (sc->sc_firmware_type != WI_LUCENT) /* XXX */
printf("Primary (%u.%u.%u), ",
sc->sc_pri_firmware_ver / 10000,
(sc->sc_pri_firmware_ver % 10000) / 100,
sc->sc_pri_firmware_ver % 100);
printf("Station (%u.%u.%u)\n",
sc->sc_sta_firmware_ver / 10000,
(sc->sc_sta_firmware_ver % 10000) / 100,
sc->sc_sta_firmware_ver % 100);
}
}
static int
wi_write_ssid(struct wi_softc *sc, int rid, u_int8_t *buf, int buflen)
{
struct wi_ssid ssid;
if (buflen > IEEE80211_NWID_LEN)
return ENOBUFS;
memset(&ssid, 0, sizeof(ssid));
ssid.wi_len = htole16(buflen);
memcpy(ssid.wi_ssid, buf, buflen);
return wi_write_rid(sc, rid, &ssid, sizeof(ssid));
}
static int
wi_write_txrate(struct wi_softc *sc, struct ieee80211vap *vap)
{
static const uint16_t lucent_rates[12] = {
[ 0] = 3, /* auto */
[ 1] = 1, /* 1Mb/s */
[ 2] = 2, /* 2Mb/s */
[ 5] = 4, /* 5.5Mb/s */
[11] = 5 /* 11Mb/s */
};
static const uint16_t intersil_rates[12] = {
[ 0] = 0xf, /* auto */
[ 1] = 0, /* 1Mb/s */
[ 2] = 1, /* 2Mb/s */
[ 5] = 2, /* 5.5Mb/s */
[11] = 3, /* 11Mb/s */
};
const uint16_t *rates = sc->sc_firmware_type == WI_LUCENT ?
lucent_rates : intersil_rates;
struct ieee80211com *ic = vap->iv_ic;
const struct ieee80211_txparam *tp;
tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_bsschan)];
return wi_write_val(sc, WI_RID_TX_RATE,
(tp->ucastrate == IEEE80211_FIXED_RATE_NONE ?
rates[0] : rates[tp->ucastrate / 2]));
}
static int
wi_write_wep(struct wi_softc *sc, struct ieee80211vap *vap)
{
int error = 0;
int i, keylen;
u_int16_t val;
struct wi_key wkey[IEEE80211_WEP_NKID];
switch (sc->sc_firmware_type) {
case WI_LUCENT:
val = (vap->iv_flags & IEEE80211_F_PRIVACY) ? 1 : 0;
error = wi_write_val(sc, WI_RID_ENCRYPTION, val);
if (error)
break;
if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0)
break;
error = wi_write_val(sc, WI_RID_TX_CRYPT_KEY, vap->iv_def_txkey);
if (error)
break;
memset(wkey, 0, sizeof(wkey));
for (i = 0; i < IEEE80211_WEP_NKID; i++) {
keylen = vap->iv_nw_keys[i].wk_keylen;
wkey[i].wi_keylen = htole16(keylen);
memcpy(wkey[i].wi_keydat, vap->iv_nw_keys[i].wk_key,
keylen);
}
error = wi_write_rid(sc, WI_RID_DEFLT_CRYPT_KEYS,
wkey, sizeof(wkey));
sc->sc_encryption = 0;
break;
case WI_INTERSIL:
val = HOST_ENCRYPT | HOST_DECRYPT;
if (vap->iv_flags & IEEE80211_F_PRIVACY) {
/*
* ONLY HWB3163 EVAL-CARD Firmware version
* less than 0.8 variant2
*
* If promiscuous mode disable, Prism2 chip
* does not work with WEP .
* It is under investigation for details.
* (ichiro@netbsd.org)
*/
if (sc->sc_sta_firmware_ver < 802 ) {
/* firm ver < 0.8 variant 2 */
wi_write_val(sc, WI_RID_PROMISC, 1);
}
wi_write_val(sc, WI_RID_CNFAUTHMODE,
vap->iv_bss->ni_authmode);
val |= PRIVACY_INVOKED;
} else {
wi_write_val(sc, WI_RID_CNFAUTHMODE, IEEE80211_AUTH_OPEN);
}
error = wi_write_val(sc, WI_RID_P2_ENCRYPTION, val);
if (error)
break;
sc->sc_encryption = val;
if ((val & PRIVACY_INVOKED) == 0)
break;
error = wi_write_val(sc, WI_RID_P2_TX_CRYPT_KEY, vap->iv_def_txkey);
break;
}
return error;
}
static int
wi_cmd(struct wi_softc *sc, int cmd, int val0, int val1, int val2)
{
int i, s = 0;
if (sc->wi_gone)
return (ENODEV);
/* wait for the busy bit to clear */
for (i = sc->wi_cmd_count; i > 0; i--) { /* 500ms */
if (!(CSR_READ_2(sc, WI_COMMAND) & WI_CMD_BUSY))
break;
DELAY(1*1000); /* 1ms */
}
if (i == 0) {
device_printf(sc->sc_dev, "%s: busy bit won't clear, cmd 0x%x\n",
__func__, cmd);
sc->wi_gone = 1;
return(ETIMEDOUT);
}
CSR_WRITE_2(sc, WI_PARAM0, val0);
CSR_WRITE_2(sc, WI_PARAM1, val1);
CSR_WRITE_2(sc, WI_PARAM2, val2);
CSR_WRITE_2(sc, WI_COMMAND, cmd);
if (cmd == WI_CMD_INI) {
/* XXX: should sleep here. */
DELAY(100*1000); /* 100ms delay for init */
}
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);
if (s & WI_EV_CMD) {
/* Ack the event and read result code. */
s = CSR_READ_2(sc, WI_STATUS);
CSR_WRITE_2(sc, WI_EVENT_ACK, WI_EV_CMD);
if (s & WI_STAT_CMD_RESULT) {
return(EIO);
}
break;
}
DELAY(WI_DELAY);
}
if (i == WI_TIMEOUT) {
device_printf(sc->sc_dev, "%s: timeout on cmd 0x%04x; "
"event status 0x%04x\n", __func__, cmd, s);
if (s == 0xffff)
sc->wi_gone = 1;
return(ETIMEDOUT);
}
return (0);
}
static int
wi_seek_bap(struct wi_softc *sc, int id, int off)
{
int i, status;
CSR_WRITE_2(sc, WI_SEL0, id);
CSR_WRITE_2(sc, WI_OFF0, off);
for (i = 0; ; i++) {
status = CSR_READ_2(sc, WI_OFF0);
if ((status & WI_OFF_BUSY) == 0)
break;
if (i == WI_TIMEOUT) {
device_printf(sc->sc_dev, "%s: timeout, id %x off %x\n",
__func__, id, off);
sc->sc_bap_off = WI_OFF_ERR; /* invalidate */
if (status == 0xffff)
sc->wi_gone = 1;
return ETIMEDOUT;
}
DELAY(1);
}
if (status & WI_OFF_ERR) {
device_printf(sc->sc_dev, "%s: error, id %x off %x\n",
__func__, id, off);
sc->sc_bap_off = WI_OFF_ERR; /* invalidate */
return EIO;
}
sc->sc_bap_id = id;
sc->sc_bap_off = off;
return 0;
}
static int
wi_read_bap(struct wi_softc *sc, int id, int off, void *buf, int buflen)
{
u_int16_t *ptr;
int i, error, cnt;
if (buflen == 0)
return 0;
if (id != sc->sc_bap_id || off != sc->sc_bap_off) {
if ((error = wi_seek_bap(sc, id, off)) != 0)
return error;
}
cnt = (buflen + 1) / 2;
ptr = (u_int16_t *)buf;
for (i = 0; i < cnt; i++)
*ptr++ = CSR_READ_2(sc, WI_DATA0);
sc->sc_bap_off += cnt * 2;
return 0;
}
static int
wi_write_bap(struct wi_softc *sc, int id, int off, void *buf, int buflen)
{
u_int16_t *ptr;
int i, error, cnt;
if (buflen == 0)
return 0;
if (id != sc->sc_bap_id || off != sc->sc_bap_off) {
if ((error = wi_seek_bap(sc, id, off)) != 0)
return error;
}
cnt = (buflen + 1) / 2;
ptr = (u_int16_t *)buf;
for (i = 0; i < cnt; i++)
CSR_WRITE_2(sc, WI_DATA0, ptr[i]);
sc->sc_bap_off += cnt * 2;
return 0;
}
static int
wi_mwrite_bap(struct wi_softc *sc, int id, int off, struct mbuf *m0, int totlen)
{
int error, len;
struct mbuf *m;
for (m = m0; m != NULL && totlen > 0; m = m->m_next) {
if (m->m_len == 0)
continue;
len = min(m->m_len, totlen);
if (((u_long)m->m_data) % 2 != 0 || len % 2 != 0) {
m_copydata(m, 0, totlen, (caddr_t)&sc->sc_txbuf);
return wi_write_bap(sc, id, off, (caddr_t)&sc->sc_txbuf,
totlen);
}
if ((error = wi_write_bap(sc, id, off, m->m_data, len)) != 0)
return error;
off += m->m_len;
totlen -= len;
}
return 0;
}
static int
wi_alloc_fid(struct wi_softc *sc, int len, int *idp)
{
int i;
if (wi_cmd(sc, WI_CMD_ALLOC_MEM, len, 0, 0)) {
device_printf(sc->sc_dev, "%s: failed to allocate %d bytes on NIC\n",
__func__, len);
return ENOMEM;
}
for (i = 0; i < WI_TIMEOUT; i++) {
if (CSR_READ_2(sc, WI_EVENT_STAT) & WI_EV_ALLOC)
break;
DELAY(1);
}
if (i == WI_TIMEOUT) {
device_printf(sc->sc_dev, "%s: timeout in alloc\n", __func__);
return ETIMEDOUT;
}
*idp = CSR_READ_2(sc, WI_ALLOC_FID);
CSR_WRITE_2(sc, WI_EVENT_ACK, WI_EV_ALLOC);
return 0;
}
static int
wi_read_rid(struct wi_softc *sc, int rid, void *buf, int *buflenp)
{
int error, len;
u_int16_t ltbuf[2];
/* Tell the NIC to enter record read mode. */
error = wi_cmd(sc, WI_CMD_ACCESS | WI_ACCESS_READ, rid, 0, 0);
if (error)
return error;
error = wi_read_bap(sc, rid, 0, ltbuf, sizeof(ltbuf));
if (error)
return error;
if (le16toh(ltbuf[1]) != rid) {
device_printf(sc->sc_dev, "record read mismatch, rid=%x, got=%x\n",
rid, le16toh(ltbuf[1]));
return EIO;
}
len = (le16toh(ltbuf[0]) - 1) * 2; /* already got rid */
if (*buflenp < len) {
device_printf(sc->sc_dev, "record buffer is too small, "
"rid=%x, size=%d, len=%d\n",
rid, *buflenp, len);
return ENOSPC;
}
*buflenp = len;
return wi_read_bap(sc, rid, sizeof(ltbuf), buf, len);
}
static int
wi_write_rid(struct wi_softc *sc, int rid, void *buf, int buflen)
{
int error;
u_int16_t ltbuf[2];
ltbuf[0] = htole16((buflen + 1) / 2 + 1); /* includes rid */
ltbuf[1] = htole16(rid);
error = wi_write_bap(sc, rid, 0, ltbuf, sizeof(ltbuf));
if (error) {
device_printf(sc->sc_dev, "%s: bap0 write failure, rid 0x%x\n",
__func__, rid);
return error;
}
error = wi_write_bap(sc, rid, sizeof(ltbuf), buf, buflen);
if (error) {
device_printf(sc->sc_dev, "%s: bap1 write failure, rid 0x%x\n",
__func__, rid);
return error;
}
return wi_cmd(sc, WI_CMD_ACCESS | WI_ACCESS_WRITE, rid, 0, 0);
}
static int
wi_write_appie(struct wi_softc *sc, int rid, const struct ieee80211_appie *ie)
{
/* NB: 42 bytes is probably ok to have on the stack */
char buf[sizeof(uint16_t) + 40];
if (ie->ie_len > 40)
return EINVAL;
/* NB: firmware requires 16-bit ie length before ie data */
*(uint16_t *) buf = htole16(ie->ie_len);
memcpy(buf + sizeof(uint16_t), ie->ie_data, ie->ie_len);
return wi_write_rid(sc, rid, buf, ie->ie_len + sizeof(uint16_t));
}
int
wi_alloc(device_t dev, int rid)
{
struct wi_softc *sc = device_get_softc(dev);
if (sc->wi_bus_type != WI_BUS_PCI_NATIVE) {
sc->iobase_rid = rid;
sc->iobase = bus_alloc_resource(dev, SYS_RES_IOPORT,
&sc->iobase_rid, 0, ~0, (1 << 6),
rman_make_alignment_flags(1 << 6) | RF_ACTIVE);
if (sc->iobase == NULL) {
device_printf(dev, "No I/O space?!\n");
return ENXIO;
}
sc->wi_io_addr = rman_get_start(sc->iobase);
sc->wi_btag = rman_get_bustag(sc->iobase);
sc->wi_bhandle = rman_get_bushandle(sc->iobase);
} else {
sc->mem_rid = rid;
sc->mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
&sc->mem_rid, RF_ACTIVE);
if (sc->mem == NULL) {
device_printf(dev, "No Mem space on prism2.5?\n");
return ENXIO;
}
sc->wi_btag = rman_get_bustag(sc->mem);
sc->wi_bhandle = rman_get_bushandle(sc->mem);
}
sc->irq_rid = 0;
sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irq_rid,
RF_ACTIVE |
((sc->wi_bus_type == WI_BUS_PCCARD) ? 0 : RF_SHAREABLE));
if (sc->irq == NULL) {
wi_free(dev);
device_printf(dev, "No irq?!\n");
return ENXIO;
}
sc->sc_dev = dev;
sc->sc_unit = device_get_unit(dev);
return 0;
}
void
wi_free(device_t dev)
{
struct wi_softc *sc = device_get_softc(dev);
if (sc->iobase != NULL) {
bus_release_resource(dev, SYS_RES_IOPORT, sc->iobase_rid, sc->iobase);
sc->iobase = NULL;
}
if (sc->irq != NULL) {
bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid, sc->irq);
sc->irq = NULL;
}
if (sc->mem != NULL) {
bus_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, sc->mem);
sc->mem = NULL;
}
}