summaryrefslogtreecommitdiffstats
path: root/sys/dev/ipw
diff options
context:
space:
mode:
authorthompsa <thompsa@FreeBSD.org>2007-10-12 05:23:00 +0000
committerthompsa <thompsa@FreeBSD.org>2007-10-12 05:23:00 +0000
commita7a6cc62cbb4f888b36f438e41119d295a856e55 (patch)
tree0fc6812db765f9b2f15140dfde14ea64fb1fc6e4 /sys/dev/ipw
parent58809b0788815a16cbc3fbef8276ecca2f0bf19e (diff)
downloadFreeBSD-src-a7a6cc62cbb4f888b36f438e41119d295a856e55.zip
FreeBSD-src-a7a6cc62cbb4f888b36f438e41119d295a856e55.tar.gz
Update ipw to work with the new net80211 stack, plus other driver improvements.
- Add proper scanning support rather than letting the firmware grab the first access point - Overhaul state changes - Use macros for locking and provide _locked() versions of some functions - Increase debugging output - Use a callout rather than the old watchdog interface - Improve style, function names and defines - Add WPA (TKIP) support Based heavily on a patchset provided by Sam Leffler.
Diffstat (limited to 'sys/dev/ipw')
-rw-r--r--sys/dev/ipw/if_ipw.c1041
-rw-r--r--sys/dev/ipw/if_ipwreg.h19
-rw-r--r--sys/dev/ipw/if_ipwvar.h27
3 files changed, 868 insertions, 219 deletions
diff --git a/sys/dev/ipw/if_ipw.c b/sys/dev/ipw/if_ipw.c
index 0be6fca..f72c1c2 100644
--- a/sys/dev/ipw/if_ipw.c
+++ b/sys/dev/ipw/if_ipw.c
@@ -3,6 +3,8 @@
/*-
* Copyright (c) 2004-2006
* Damien Bergamini <damien.bergamini@free.fr>. All rights reserved.
+ * Copyright (c) 2006 Sam Leffler, Errno Consulting
+ * Copyright (c) 2007 Andrew Thompson <thompsa@FreeBSD.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -78,6 +80,7 @@ __FBSDID("$FreeBSD$");
#include <dev/ipw/if_ipwreg.h>
#include <dev/ipw/if_ipwvar.h>
+#define IPW_DEBUG
#ifdef IPW_DEBUG
#define DPRINTF(x) do { if (ipw_debug > 0) printf x; } while (0)
#define DPRINTFN(n, x) do { if (ipw_debug >= (n)) printf x; } while (0)
@@ -110,39 +113,58 @@ static int ipw_media_change(struct ifnet *);
static void ipw_media_status(struct ifnet *, struct ifmediareq *);
static int ipw_newstate(struct ieee80211com *, enum ieee80211_state, int);
static uint16_t ipw_read_prom_word(struct ipw_softc *, uint8_t);
-static void ipw_command_intr(struct ipw_softc *, struct ipw_soft_buf *);
-static void ipw_newstate_intr(struct ipw_softc *, struct ipw_soft_buf *);
-static void ipw_data_intr(struct ipw_softc *, struct ipw_status *,
+static void ipw_rx_cmd_intr(struct ipw_softc *, struct ipw_soft_buf *);
+static void ipw_rx_newstate_intr(struct ipw_softc *, struct ipw_soft_buf *);
+static void ipw_rx_data_intr(struct ipw_softc *, struct ipw_status *,
struct ipw_soft_bd *, struct ipw_soft_buf *);
static void ipw_rx_intr(struct ipw_softc *);
static void ipw_release_sbd(struct ipw_softc *, struct ipw_soft_bd *);
static void ipw_tx_intr(struct ipw_softc *);
static void ipw_intr(void *);
static void ipw_dma_map_addr(void *, bus_dma_segment_t *, int, int);
+static const char * ipw_cmdname(int);
static int ipw_cmd(struct ipw_softc *, uint32_t, void *, uint32_t);
static int ipw_tx_start(struct ifnet *, struct mbuf *,
struct ieee80211_node *);
static void ipw_start(struct ifnet *);
-static void ipw_watchdog(struct ifnet *);
+static void ipw_start_locked(struct ifnet *);
+static void ipw_watchdog(void *);
static int ipw_ioctl(struct ifnet *, u_long, caddr_t);
static void ipw_stop_master(struct ipw_softc *);
+static int ipw_enable(struct ipw_softc *);
+static int ipw_disable(struct ipw_softc *);
static int ipw_reset(struct ipw_softc *);
static int ipw_load_ucode(struct ipw_softc *, const char *, int);
static int ipw_load_firmware(struct ipw_softc *, const char *, int);
static int ipw_config(struct ipw_softc *);
+static void ipw_assoc_task(void *, int);
+static int ipw_auth_and_assoc(struct ipw_softc *);
+static void ipw_disassoc_task(void *, int);
+static int ipw_disassociate(struct ipw_softc *);
static void ipw_init_task(void *, int);
static void ipw_init(void *);
+static void ipw_init_locked(struct ipw_softc *, int);
static void ipw_stop(void *);
+static void ipw_stop_locked(struct ipw_softc *);
static int ipw_sysctl_stats(SYSCTL_HANDLER_ARGS);
static int ipw_sysctl_radio(SYSCTL_HANDLER_ARGS);
static uint32_t ipw_read_table1(struct ipw_softc *, uint32_t);
static void ipw_write_table1(struct ipw_softc *, uint32_t, uint32_t);
+#if 0
static int ipw_read_table2(struct ipw_softc *, uint32_t, void *,
uint32_t *);
static void ipw_read_mem_1(struct ipw_softc *, bus_size_t, uint8_t *,
bus_size_t);
+#endif
static void ipw_write_mem_1(struct ipw_softc *, bus_size_t,
const uint8_t *, bus_size_t);
+static void ipw_scan_task(void *, int);
+static int ipw_scan(struct ipw_softc *);
+static void ipw_scan_start(struct ieee80211com *);
+static void ipw_scan_end(struct ieee80211com *);
+static void ipw_set_channel(struct ieee80211com *);
+static void ipw_scan_curchan(struct ieee80211com *, unsigned long maxdwell);
+static void ipw_scan_mindwell(struct ieee80211com *);
static int ipw_probe(device_t);
static int ipw_attach(device_t);
@@ -172,6 +194,7 @@ static driver_t ipw_driver = {
static devclass_t ipw_devclass;
DRIVER_MODULE(ipw, pci, ipw_driver, ipw_devclass, 0, 0);
+DRIVER_MODULE(ipw, cardbus, ipw_driver, ipw_devclass, 0, 0);
static int
ipw_probe(device_t dev)
@@ -207,6 +230,10 @@ ipw_attach(device_t dev)
MTX_DEF | MTX_RECURSE);
TASK_INIT(&sc->sc_init_task, 0, ipw_init_task, sc);
+ TASK_INIT(&sc->sc_scan_task, 0, ipw_scan_task, sc);
+ TASK_INIT(&sc->sc_assoc_task, 0, ipw_assoc_task, sc);
+ TASK_INIT(&sc->sc_disassoc_task, 0, ipw_disassoc_task, sc);
+ callout_init_mtx(&sc->sc_wdtimer, &sc->sc_mtx, 0);
if (pci_get_powerstate(dev) != PCI_POWERSTATE_D0) {
device_printf(dev, "chip is in D%d power mode "
@@ -260,7 +287,6 @@ ipw_attach(device_t dev)
ifp->if_init = ipw_init;
ifp->if_ioctl = ipw_ioctl;
ifp->if_start = ipw_start;
- ifp->if_watchdog = ipw_watchdog;
IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN);
ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN;
IFQ_SET_READY(&ifp->if_snd);
@@ -271,11 +297,12 @@ ipw_attach(device_t dev)
ic->ic_state = IEEE80211_S_INIT;
/* set device capabilities */
- ic->ic_caps =
- IEEE80211_C_IBSS | /* IBSS mode supported */
- IEEE80211_C_MONITOR | /* monitor mode supported */
- IEEE80211_C_TXPMGT | /* tx power management */
- IEEE80211_C_SHPREAMBLE; /* short preamble supported */
+ ic->ic_caps = IEEE80211_C_IBSS /* IBSS mode supported */
+ | IEEE80211_C_MONITOR /* monitor mode supported */
+ | IEEE80211_C_PMGT /* power save supported */
+ | IEEE80211_C_SHPREAMBLE /* short preamble supported */
+ | IEEE80211_C_WPA /* 802.11i supported */
+ ;
/* read MAC address from EEPROM */
val = ipw_read_prom_word(sc, IPW_EEPROM_MAC + 0);
@@ -311,8 +338,14 @@ ipw_attach(device_t dev)
ic->ic_newstate = ipw_newstate;
ieee80211_media_init(ic, ipw_media_change, ipw_media_status);
+ ic->ic_scan_start = ipw_scan_start;
+ ic->ic_scan_end = ipw_scan_end;
+ ic->ic_set_channel = ipw_set_channel;
+ ic->ic_scan_curchan = ipw_scan_curchan;
+ ic->ic_scan_mindwell = ipw_scan_mindwell;
+
bpfattach2(ifp, DLT_IEEE802_11_RADIO,
- sizeof (struct ieee80211_frame) + sizeof (sc->sc_txtap),
+ sizeof (struct ieee80211_frame) + sizeof (sc->sc_txtap),
&sc->sc_drvbpf);
sc->sc_rxtap_len = sizeof sc->sc_rxtap;
@@ -370,6 +403,11 @@ ipw_detach(device_t dev)
struct ifnet *ifp = ic->ic_ifp;
ipw_stop(sc);
+ callout_drain(&sc->sc_wdtimer);
+ taskqueue_drain(taskqueue_fast, &sc->sc_init_task);
+ taskqueue_drain(taskqueue_fast, &sc->sc_scan_task);
+ taskqueue_drain(taskqueue_fast, &sc->sc_assoc_task);
+ taskqueue_drain(taskqueue_fast, &sc->sc_disassoc_task);
if (ifp != NULL) {
bpfdetach(ifp);
@@ -711,18 +749,19 @@ ipw_resume(device_t dev)
{
struct ipw_softc *sc = device_get_softc(dev);
struct ifnet *ifp = sc->sc_ic.ic_ifp;
+ IPW_LOCK_DECL;
- mtx_lock(&sc->sc_mtx);
+ IPW_LOCK(sc);
pci_write_config(dev, 0x41, 0, 1);
if (ifp->if_flags & IFF_UP) {
- ifp->if_init(ifp->if_softc);
+ ipw_init_locked(sc, 0);
if (ifp->if_drv_flags & IFF_DRV_RUNNING)
- ifp->if_start(ifp);
+ ipw_start_locked(ifp);
}
- mtx_unlock(&sc->sc_mtx);
+ IPW_UNLOCK(sc);
return 0;
}
@@ -732,20 +771,30 @@ ipw_media_change(struct ifnet *ifp)
{
struct ipw_softc *sc = ifp->if_softc;
int error;
+ IPW_LOCK_DECL;
- mtx_lock(&sc->sc_mtx);
-
+ IPW_LOCK(sc);
error = ieee80211_media_change(ifp);
- if (error != ENETRESET) {
- mtx_unlock(&sc->sc_mtx);
- return error;
+ if (error == ENETRESET) {
+ if ((ifp->if_flags & IFF_UP) &&
+ (ifp->if_drv_flags & IFF_DRV_RUNNING))
+ ipw_init_locked(sc, 0);
+ error = 0;
}
+ IPW_UNLOCK(sc);
- if ((ifp->if_flags & IFF_UP) && (ifp->if_drv_flags & IFF_DRV_RUNNING))
- ipw_init(sc);
-
- mtx_unlock(&sc->sc_mtx);
+ return (error);
+}
+static int
+ipw_cvtrate(int ipwrate)
+{
+ switch (ipwrate) {
+ case IPW_RATE_DS1: return 2;
+ case IPW_RATE_DS2: return 4;
+ case IPW_RATE_DS5: return 11;
+ case IPW_RATE_DS11: return 22;
+ }
return 0;
}
@@ -756,20 +805,9 @@ ipw_media_change(struct ifnet *ifp)
static void
ipw_media_status(struct ifnet *ifp, struct ifmediareq *imr)
{
-#define N(a) (sizeof (a) / sizeof (a[0]))
struct ipw_softc *sc = ifp->if_softc;
struct ieee80211com *ic = &sc->sc_ic;
- static const struct {
- uint32_t val;
- int rate;
- } rates[] = {
- { IPW_RATE_DS1, 2 },
- { IPW_RATE_DS2, 4 },
- { IPW_RATE_DS5, 11 },
- { IPW_RATE_DS11, 22 },
- };
- uint32_t val;
- int rate, i;
+ int rate;
imr->ifm_status = IFM_AVALID;
imr->ifm_active = IFM_IEEE80211;
@@ -777,14 +815,9 @@ ipw_media_status(struct ifnet *ifp, struct ifmediareq *imr)
imr->ifm_status |= IFM_ACTIVE;
/* read current transmission rate from adapter */
- val = ipw_read_table1(sc, IPW_INFO_CURRENT_TX_RATE) & 0xf;
-
- /* convert ipw rate to 802.11 rate */
- for (i = 0; i < N(rates) && rates[i].val != val; i++);
- rate = (i < N(rates)) ? rates[i].rate : 0;
-
- imr->ifm_active |= IFM_IEEE80211_11B;
+ rate = ipw_cvtrate(ipw_read_table1(sc, IPW_INFO_CURRENT_TX_RATE) & 0xf);
imr->ifm_active |= ieee80211_rate2media(ic, rate, IEEE80211_MODE_11B);
+
switch (ic->ic_opmode) {
case IEEE80211_M_STA:
break;
@@ -803,7 +836,6 @@ ipw_media_status(struct ifnet *ifp, struct ifmediareq *imr)
/* should not get there */
break;
}
-#undef N
}
static int
@@ -811,41 +843,52 @@ ipw_newstate(struct ieee80211com *ic, enum ieee80211_state nstate, int arg)
{
struct ifnet *ifp = ic->ic_ifp;
struct ipw_softc *sc = ifp->if_softc;
- uint8_t macaddr[IEEE80211_ADDR_LEN];
- uint32_t len;
+
+ DPRINTF(("%s: %s -> %s flags 0x%x\n", __func__,
+ ieee80211_state_name[ic->ic_state],
+ ieee80211_state_name[nstate], sc->flags));
switch (nstate) {
case IEEE80211_S_RUN:
- DELAY(200); /* firmware needs a short delay here */
-
- len = IEEE80211_ADDR_LEN;
- ipw_read_table2(sc, IPW_INFO_CURRENT_BSSID, macaddr, &len);
-
-#if 0
- ni = ieee80211_find_node(&ic->ic_scan, macaddr);
- if (ni == NULL)
- break;
-
- ieee80211_ref_node(ni);
- ieee80211_sta_join(ic, ni);
- ieee80211_node_authorize(ni);
-
- if (ic->ic_opmode == IEEE80211_M_STA)
- ieee80211_notify_node_join(ic, ni, 1);
-#endif
+ if (ic->ic_opmode == IEEE80211_M_IBSS) {
+ /*
+ * XXX when joining an ibss network we are called
+ * with a SCAN -> RUN transition on scan complete.
+ * Use that to call ipw_auth_and_assoc. On completing
+ * the join we are then called again with an
+ * AUTH -> RUN transition and we want to do nothing.
+ * This is all totally bogus and needs to be redone.
+ */
+ if (ic->ic_state == IEEE80211_S_SCAN)
+ taskqueue_enqueue_fast(taskqueue_fast,
+ &sc->sc_assoc_task);
+ }
break;
case IEEE80211_S_INIT:
- case IEEE80211_S_SCAN:
+ if (sc->flags & IPW_FLAG_ASSOCIATED)
+ taskqueue_enqueue_fast(taskqueue_fast,
+ &sc->sc_disassoc_task);
+ break;
+
case IEEE80211_S_AUTH:
+ taskqueue_enqueue_fast(taskqueue_fast, &sc->sc_assoc_task);
+ break;
+
case IEEE80211_S_ASSOC:
+ /*
+ * If we are not transitioning from AUTH the resend the
+ * association request.
+ */
+ if (ic->ic_state != IEEE80211_S_AUTH)
+ taskqueue_enqueue_fast(taskqueue_fast,
+ &sc->sc_assoc_task);
+ break;
+
default:
break;
}
-
- ic->ic_state = nstate;
-
- return 0;
+ return (*sc->sc_newstate)(ic, nstate, arg);
}
/*
@@ -904,7 +947,7 @@ ipw_read_prom_word(struct ipw_softc *sc, uint8_t addr)
}
static void
-ipw_command_intr(struct ipw_softc *sc, struct ipw_soft_buf *sbuf)
+ipw_rx_cmd_intr(struct ipw_softc *sc, struct ipw_soft_buf *sbuf)
{
struct ipw_cmd *cmd;
@@ -912,16 +955,19 @@ ipw_command_intr(struct ipw_softc *sc, struct ipw_soft_buf *sbuf)
cmd = mtod(sbuf->m, struct ipw_cmd *);
- DPRINTFN(2, ("cmd ack'ed (%u, %u, %u, %u, %u)\n", le32toh(cmd->type),
+ DPRINTFN(9, ("cmd ack'ed %s(%u, %u, %u, %u, %u)\n",
+ ipw_cmdname(le32toh(cmd->type)), le32toh(cmd->type),
le32toh(cmd->subtype), le32toh(cmd->seq), le32toh(cmd->len),
le32toh(cmd->status)));
+ sc->flags &= ~IPW_FLAG_BUSY;
wakeup(sc);
}
static void
-ipw_newstate_intr(struct ipw_softc *sc, struct ipw_soft_buf *sbuf)
+ipw_rx_newstate_intr(struct ipw_softc *sc, struct ipw_soft_buf *sbuf)
{
+#define IEEESTATE(ic) ieee80211_state_name[ic->ic_state]
struct ieee80211com *ic = &sc->sc_ic;
uint32_t state;
@@ -929,35 +975,94 @@ ipw_newstate_intr(struct ipw_softc *sc, struct ipw_soft_buf *sbuf)
state = le32toh(*mtod(sbuf->m, uint32_t *));
- DPRINTFN(2, ("entering state %u\n", state));
-
switch (state) {
case IPW_STATE_ASSOCIATED:
- ieee80211_new_state(ic, IEEE80211_S_RUN, -1);
+ DPRINTFN(2, ("Association succeeded (%s flags 0x%x)\n",
+ IEEESTATE(ic), sc->flags));
+ sc->flags |= IPW_FLAG_ASSOCIATED;
+ /* XXX suppress state change in case the fw auto-associates */
+ if (ic->ic_state != IEEE80211_S_ASSOC) {
+ DPRINTF(("Unexpected association (state %u)\n",
+ ic->ic_state));
+ } else
+ ieee80211_new_state(ic, IEEE80211_S_RUN, -1);
break;
case IPW_STATE_SCANNING:
- /* don't leave run state on background scan */
- if (ic->ic_state != IEEE80211_S_RUN)
- ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
-
- ic->ic_flags |= IEEE80211_F_SCAN;
+ DPRINTFN(3, ("Scanning (%s flags 0x%x)\n",
+ IEEESTATE(ic), sc->flags));
+ /*
+ * NB: Check driver state for association on assoc
+ * loss as the firmware will immediately start to
+ * scan and we would treat it as a beacon miss if
+ * we checked the 802.11 layer state.
+ */
+ if (sc->flags & IPW_FLAG_ASSOCIATED)
+ ieee80211_beacon_miss(ic);
break;
case IPW_STATE_SCAN_COMPLETE:
- ieee80211_notify_scan_done(ic);
- ic->ic_flags &= ~IEEE80211_F_SCAN;
+ /*
+ * XXX For some reason scan requests generate scan
+ * started + scan done events before any traffic is
+ * received (e.g. probe response frames). We work
+ * around this by marking the HACK flag and skipping
+ * the first scan complete event.
+ */
+ if (sc->flags & IPW_FLAG_HACK) {
+ sc->flags &= ~IPW_FLAG_HACK;
+ break;
+ }
+ DPRINTFN(3, ("Scan complete (%s flags 0x%x)\n",
+ IEEESTATE(ic), sc->flags));
+ if (sc->flags & IPW_FLAG_SCANNING) {
+ ieee80211_scan_done(ic);
+ sc->flags &= ~IPW_FLAG_SCANNING;
+ sc->sc_scan_timer = 0;
+ }
break;
case IPW_STATE_ASSOCIATION_LOST:
- ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
+ DPRINTFN(2, ("Association lost (%s flags 0x%x)\n",
+ IEEESTATE(ic), sc->flags));
+ sc->flags &= ~IPW_FLAG_ASSOCIATED;
+ if (ic->ic_state == IEEE80211_S_RUN)
+ ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
+ break;
+
+ case IPW_STATE_DISABLED:
+ DPRINTFN(2, ("Firmware disabled (%s flags 0x%x)\n",
+ IEEESTATE(ic), sc->flags));
break;
case IPW_STATE_RADIO_DISABLED:
+ DPRINTFN(2, ("Radio off (%s flags 0x%x)\n",
+ IEEESTATE(ic), sc->flags));
ic->ic_ifp->if_flags &= ~IFF_UP;
- ipw_stop(sc);
+ ipw_stop_locked(sc);
+ break;
+
+ default:
+ DPRINTFN(2, ("%s: unhandled state %u %s flags 0x%x\n",
+ __func__, state, IEEESTATE(ic), sc->flags));
break;
}
+#undef IEEESTATE
+}
+
+/*
+ * Set driver state for current channel.
+ */
+static void
+ipw_setcurchan(struct ipw_softc *sc, struct ieee80211_channel *chan)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+
+ ic->ic_curchan = chan;
+ sc->sc_rxtap.wr_chan_freq = sc->sc_txtap.wt_chan_freq =
+ htole16(ic->ic_curchan->ic_freq);
+ sc->sc_rxtap.wr_chan_flags = sc->sc_txtap.wt_chan_flags =
+ htole16(ic->ic_curchan->ic_flags);
}
/*
@@ -965,8 +1070,10 @@ ipw_newstate_intr(struct ipw_softc *sc, struct ipw_soft_buf *sbuf)
* probe responses. Only used during AP detection.
*/
static void
-ipw_fix_channel(struct ieee80211com *ic, struct mbuf *m)
+ipw_fix_channel(struct ipw_softc *sc, struct mbuf *m)
{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211_channel *c;
struct ieee80211_frame *wh;
uint8_t subtype;
uint8_t *frm, *efrm;
@@ -991,16 +1098,22 @@ ipw_fix_channel(struct ieee80211com *ic, struct mbuf *m)
#if IEEE80211_CHAN_MAX < 255
if (frm[2] <= IEEE80211_CHAN_MAX)
#endif
- ic->ic_bsschan = ieee80211_find_channel(ic,
+ {
+ DPRINTF(("Fixing channel to %d\n", frm[2]));
+ c = ieee80211_find_channel(ic,
ieee80211_ieee2mhz(frm[2], 0),
- IEEE80211_MODE_AUTO);
+ IEEE80211_CHAN_B);
+ if (c == NULL)
+ c = &ic->ic_channels[0];
+ ipw_setcurchan(sc, c);
+ }
frm += frm[1] + 2;
}
}
static void
-ipw_data_intr(struct ipw_softc *sc, struct ipw_status *status,
+ipw_rx_data_intr(struct ipw_softc *sc, struct ipw_status *status,
struct ipw_soft_bd *sbd, struct ipw_soft_buf *sbuf)
{
struct ieee80211com *ic = &sc->sc_ic;
@@ -1010,6 +1123,7 @@ ipw_data_intr(struct ipw_softc *sc, struct ipw_status *status,
struct ieee80211_node *ni;
bus_addr_t physaddr;
int error;
+ IPW_LOCK_DECL;
DPRINTFN(5, ("received frame len=%u, rssi=%u\n", le32toh(status->len),
status->rssi));
@@ -1067,18 +1181,18 @@ ipw_data_intr(struct ipw_softc *sc, struct ipw_status *status,
struct ipw_rx_radiotap_header *tap = &sc->sc_rxtap;
tap->wr_flags = 0;
- tap->wr_antsignal = status->rssi;
+ tap->wr_antsignal = status->rssi + IPW_RSSI_TO_DBM;
tap->wr_chan_freq = htole16(ic->ic_curchan->ic_freq);
tap->wr_chan_flags = htole16(ic->ic_curchan->ic_flags);
bpf_mtap2(sc->sc_drvbpf, tap, sc->sc_rxtap_len, m);
}
- if (ic->ic_state == IEEE80211_S_SCAN)
- ipw_fix_channel(ic, m);
+ if (sc->flags & IPW_FLAG_SCANNING)
+ ipw_fix_channel(sc, m);
wh = mtod(m, struct ieee80211_frame *);
- mtx_unlock(&sc->sc_mtx);
+ IPW_UNLOCK(sc);
ni = ieee80211_find_rxnode(ic, (struct ieee80211_frame_min *)wh);
/* send the frame to the 802.11 layer */
@@ -1086,7 +1200,7 @@ ipw_data_intr(struct ipw_softc *sc, struct ipw_status *status,
/* node is no longer needed */
ieee80211_free_node(ni);
- mtx_lock(&sc->sc_mtx);
+ IPW_LOCK(sc);
bus_dmamap_sync(sc->rbd_dmat, sc->rbd_map, BUS_DMASYNC_PREWRITE);
}
@@ -1094,6 +1208,7 @@ ipw_data_intr(struct ipw_softc *sc, struct ipw_status *status,
static void
ipw_rx_intr(struct ipw_softc *sc)
{
+ struct ieee80211com *ic = &sc->sc_ic;
struct ipw_status *status;
struct ipw_soft_bd *sbd;
struct ipw_soft_buf *sbuf;
@@ -1113,24 +1228,30 @@ ipw_rx_intr(struct ipw_softc *sc)
switch (le16toh(status->code) & 0xf) {
case IPW_STATUS_CODE_COMMAND:
- ipw_command_intr(sc, sbuf);
+ ipw_rx_cmd_intr(sc, sbuf);
break;
case IPW_STATUS_CODE_NEWSTATE:
- ipw_newstate_intr(sc, sbuf);
+ ipw_rx_newstate_intr(sc, sbuf);
break;
case IPW_STATUS_CODE_DATA_802_3:
case IPW_STATUS_CODE_DATA_802_11:
- ipw_data_intr(sc, status, sbd, sbuf);
+ ipw_rx_data_intr(sc, status, sbd, sbuf);
break;
case IPW_STATUS_CODE_NOTIFICATION:
- DPRINTFN(2, ("received notification\n"));
+ DPRINTFN(2, ("notification status, len %u flags 0x%x\n",
+ le32toh(status->len), status->flags));
+ if (ic->ic_state == IEEE80211_S_AUTH) {
+ /* XXX assume auth notification */
+ ieee80211_node_authorize(ic->ic_bss);
+ ieee80211_new_state(ic, IEEE80211_S_ASSOC, -1);
+ }
break;
default:
- device_printf(sc->sc_dev, "unknown status code %u\n",
+ device_printf(sc->sc_dev, "unexpected status code %u\n",
le16toh(status->code));
}
@@ -1213,7 +1334,7 @@ ipw_tx_intr(struct ipw_softc *sc)
sc->txold = (r == 0) ? IPW_NTBD - 1 : r - 1;
ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
- ipw_start(ifp);
+ ipw_start_locked(ifp);
}
static void
@@ -1221,11 +1342,12 @@ ipw_intr(void *arg)
{
struct ipw_softc *sc = arg;
uint32_t r;
+ IPW_LOCK_DECL;
- mtx_lock(&sc->sc_mtx);
+ IPW_LOCK(sc);
if ((r = CSR_READ_4(sc, IPW_CSR_INTR)) == 0 || r == 0xffffffff) {
- mtx_unlock(&sc->sc_mtx);
+ IPW_UNLOCK(sc);
return;
}
@@ -1253,7 +1375,7 @@ ipw_intr(void *arg)
/* re-enable interrupts */
CSR_WRITE_4(sc, IPW_CSR_INTR_MASK, IPW_INTR_MASK);
- mtx_unlock(&sc->sc_mtx);
+ IPW_UNLOCK(sc);
}
static void
@@ -1267,6 +1389,54 @@ ipw_dma_map_addr(void *arg, bus_dma_segment_t *segs, int nseg, int error)
*(bus_addr_t *)arg = segs[0].ds_addr;
}
+static const char *
+ipw_cmdname(int cmd)
+{
+#define N(a) (sizeof(a) / sizeof(a[0]))
+ static const struct {
+ int cmd;
+ const char *name;
+ } cmds[] = {
+ { IPW_CMD_ADD_MULTICAST, "ADD_MULTICAST" },
+ { IPW_CMD_BROADCAST_SCAN, "BROADCAST_SCAN" },
+ { IPW_CMD_DISABLE, "DISABLE" },
+ { IPW_CMD_DISABLE_PHY, "DISABLE_PHY" },
+ { IPW_CMD_ENABLE, "ENABLE" },
+ { IPW_CMD_PREPARE_POWER_DOWN, "PREPARE_POWER_DOWN" },
+ { IPW_CMD_SET_BASIC_TX_RATES, "SET_BASIC_TX_RATES" },
+ { IPW_CMD_SET_BEACON_INTERVAL, "SET_BEACON_INTERVAL" },
+ { IPW_CMD_SET_CHANNEL, "SET_CHANNEL" },
+ { IPW_CMD_SET_CONFIGURATION, "SET_CONFIGURATION" },
+ { IPW_CMD_SET_DESIRED_BSSID, "SET_DESIRED_BSSID" },
+ { IPW_CMD_SET_ESSID, "SET_ESSID" },
+ { IPW_CMD_SET_FRAG_THRESHOLD, "SET_FRAG_THRESHOLD" },
+ { IPW_CMD_SET_MAC_ADDRESS, "SET_MAC_ADDRESS" },
+ { IPW_CMD_SET_MANDATORY_BSSID, "SET_MANDATORY_BSSID" },
+ { IPW_CMD_SET_MODE, "SET_MODE" },
+ { IPW_CMD_SET_MSDU_TX_RATES, "SET_MSDU_TX_RATES" },
+ { IPW_CMD_SET_POWER_MODE, "SET_POWER_MODE" },
+ { IPW_CMD_SET_RTS_THRESHOLD, "SET_RTS_THRESHOLD" },
+ { IPW_CMD_SET_SCAN_OPTIONS, "SET_SCAN_OPTIONS" },
+ { IPW_CMD_SET_SECURITY_INFO, "SET_SECURITY_INFO" },
+ { IPW_CMD_SET_TX_POWER_INDEX, "SET_TX_POWER_INDEX" },
+ { IPW_CMD_SET_TX_RATES, "SET_TX_RATES" },
+ { IPW_CMD_SET_WEP_FLAGS, "SET_WEP_FLAGS" },
+ { IPW_CMD_SET_WEP_KEY, "SET_WEP_KEY" },
+ { IPW_CMD_SET_WEP_KEY_INDEX, "SET_WEP_KEY_INDEX" },
+ { IPW_CMD_SET_WPA_IE, "SET_WPA_IE" },
+
+ };
+ static char buf[12];
+ int i;
+
+ for (i = 0; i < N(cmds); i++)
+ if (cmds[i].cmd == cmd)
+ return cmds[i].name;
+ snprintf(buf, sizeof(buf), "%u", cmd);
+ return buf;
+#undef N
+}
+
/*
* Send a command to the firmware and wait for the acknowledgement.
*/
@@ -1277,12 +1447,20 @@ ipw_cmd(struct ipw_softc *sc, uint32_t type, void *data, uint32_t len)
bus_addr_t physaddr;
int error;
+ if (sc->flags & IPW_FLAG_BUSY) {
+ device_printf(sc->sc_dev, "%s: %s not sent, busy\n",
+ __func__, ipw_cmdname(type));
+ return EAGAIN;
+ }
+ sc->flags |= IPW_FLAG_BUSY;
+
sbd = &sc->stbd_list[sc->txcur];
error = bus_dmamap_load(sc->cmd_dmat, sc->cmd_map, &sc->cmd,
sizeof (struct ipw_cmd), ipw_dma_map_addr, &physaddr, 0);
if (error != 0) {
device_printf(sc->sc_dev, "could not map command DMA memory\n");
+ sc->flags &= ~IPW_FLAG_BUSY;
return error;
}
@@ -1302,7 +1480,19 @@ ipw_cmd(struct ipw_softc *sc, uint32_t type, void *data, uint32_t len)
bus_dmamap_sync(sc->cmd_dmat, sc->cmd_map, BUS_DMASYNC_PREWRITE);
bus_dmamap_sync(sc->tbd_dmat, sc->tbd_map, BUS_DMASYNC_PREWRITE);
- DPRINTFN(2, ("sending command (%u, %u, %u, %u)\n", type, 0, 0, len));
+#ifdef IPW_DEBUG
+ if (ipw_debug >= 4) {
+ printf("sending %s(%u, %u, %u, %u)", ipw_cmdname(type), type,
+ 0, 0, len);
+ /* Print the data buffer in the higher debug level */
+ if (ipw_debug >= 9 && len > 0) {
+ printf(" data: 0x");
+ for (int i = 1; i <= len; i++)
+ printf("%1D", (char *)data + len - i, "");
+ }
+ printf("\n");
+ }
+#endif
/* kick firmware */
sc->txfree--;
@@ -1310,7 +1500,14 @@ ipw_cmd(struct ipw_softc *sc, uint32_t type, void *data, uint32_t len)
CSR_WRITE_4(sc, IPW_CSR_TX_WRITE, sc->txcur);
/* wait at most one second for command to complete */
- return msleep(sc, &sc->sc_mtx, 0, "ipwcmd", hz);
+ error = msleep(sc, &sc->sc_mtx, 0, "ipwcmd", hz);
+ if (error != 0) {
+ device_printf(sc->sc_dev, "%s: %s failed, timeout (error %u)\n",
+ __func__, ipw_cmdname(type), error);
+ sc->flags &= ~IPW_FLAG_BUSY;
+ return (error);
+ }
+ return (0);
}
static int
@@ -1467,17 +1664,26 @@ static void
ipw_start(struct ifnet *ifp)
{
struct ipw_softc *sc = ifp->if_softc;
+ IPW_LOCK_DECL;
+
+ IPW_LOCK(sc);
+ ipw_start_locked(ifp);
+ IPW_UNLOCK(sc);
+}
+
+static void
+ipw_start_locked(struct ifnet *ifp)
+{
+ struct ipw_softc *sc = ifp->if_softc;
struct ieee80211com *ic = &sc->sc_ic;
struct mbuf *m0;
struct ether_header *eh;
struct ieee80211_node *ni;
- mtx_lock(&sc->sc_mtx);
+ IPW_LOCK_ASSERT(sc);
- if (ic->ic_state != IEEE80211_S_RUN) {
- mtx_unlock(&sc->sc_mtx);
+ if (ic->ic_state != IEEE80211_S_RUN)
return;
- }
for (;;) {
IFQ_DRV_DEQUEUE(&ifp->if_snd, m0);
@@ -1519,20 +1725,17 @@ ipw_start(struct ifnet *ifp)
/* start watchdog timer */
sc->sc_tx_timer = 5;
- ifp->if_timer = 1;
}
-
- mtx_unlock(&sc->sc_mtx);
}
static void
-ipw_watchdog(struct ifnet *ifp)
+ipw_watchdog(void *arg)
{
- struct ipw_softc *sc = ifp->if_softc;
-
- mtx_lock(&sc->sc_mtx);
+ struct ipw_softc *sc = arg;
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ifnet *ifp = sc->sc_ifp;
- ifp->if_timer = 0;
+ IPW_LOCK_ASSERT(sc);
if (sc->sc_tx_timer > 0) {
if (--sc->sc_tx_timer == 0) {
@@ -1540,13 +1743,20 @@ ipw_watchdog(struct ifnet *ifp)
ifp->if_oerrors++;
taskqueue_enqueue_fast(taskqueue_fast,
&sc->sc_init_task);
- mtx_unlock(&sc->sc_mtx);
- return;
}
- ifp->if_timer = 1;
}
-
- mtx_unlock(&sc->sc_mtx);
+ if (sc->sc_scan_timer > 0) {
+ if (--sc->sc_scan_timer == 0) {
+ DPRINTFN(3, ("Scan timeout\n"));
+ /* End the scan */
+ if (sc->flags & IPW_FLAG_SCANNING) {
+ ieee80211_scan_done(ic);
+ sc->flags &= ~IPW_FLAG_SCANNING;
+ }
+ }
+ }
+ if (ifp->if_drv_flags & IFF_DRV_RUNNING)
+ callout_reset(&sc->sc_wdtimer, hz, ipw_watchdog, sc);
}
static int
@@ -1555,17 +1765,18 @@ ipw_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
struct ipw_softc *sc = ifp->if_softc;
struct ieee80211com *ic = &sc->sc_ic;
int error = 0;
+ IPW_LOCK_DECL;
- mtx_lock(&sc->sc_mtx);
+ IPW_LOCK(sc);
switch (cmd) {
case SIOCSIFFLAGS:
if (ifp->if_flags & IFF_UP) {
if (!(ifp->if_drv_flags & IFF_DRV_RUNNING))
- ipw_init(sc);
+ ipw_init_locked(sc, 0);
} else {
if (ifp->if_drv_flags & IFF_DRV_RUNNING)
- ipw_stop(sc);
+ ipw_stop_locked(sc);
}
break;
@@ -1575,12 +1786,13 @@ ipw_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
if (error == ENETRESET) {
if ((ifp->if_flags & IFF_UP) &&
- (ifp->if_drv_flags & IFF_DRV_RUNNING))
- ipw_init(sc);
+ (ifp->if_drv_flags & IFF_DRV_RUNNING) &&
+ (ic->ic_roaming != IEEE80211_ROAMING_MANUAL))
+ ipw_init_locked(sc, 0);
error = 0;
}
- mtx_unlock(&sc->sc_mtx);
+ IPW_UNLOCK(sc);
return error;
}
@@ -1606,7 +1818,8 @@ ipw_stop_master(struct ipw_softc *sc)
tmp = CSR_READ_4(sc, IPW_CSR_RST);
CSR_WRITE_4(sc, IPW_CSR_RST, tmp | IPW_RST_PRINCETON_RESET);
- sc->flags &= ~IPW_FLAG_FW_INITED;
+ /* Clear all flags except the following */
+ sc->flags &= IPW_FLAG_HAS_RADIO_SWITCH;
}
static int
@@ -1641,6 +1854,60 @@ ipw_reset(struct ipw_softc *sc)
return 0;
}
+static int
+ipw_waitfordisable(struct ipw_softc *sc, int waitfor)
+{
+ int ms = hz < 1000 ? 1 : hz/10;
+ int i, error;
+
+ for (i = 0; i < 100; i++) {
+ if (ipw_read_table1(sc, IPW_INFO_CARD_DISABLED) == waitfor)
+ return 0;
+ error = msleep(sc, &sc->sc_mtx, PCATCH, __func__, ms);
+ if (error == 0 || error != EWOULDBLOCK)
+ return 0;
+ }
+ DPRINTF(("%s: timeout waiting for %s\n",
+ __func__, waitfor ? "disable" : "enable"));
+ return ETIMEDOUT;
+}
+
+static int
+ipw_enable(struct ipw_softc *sc)
+{
+ int error;
+
+ if ((sc->flags & IPW_FLAG_ENABLED) == 0) {
+ DPRINTF(("Enable adapter\n"));
+ error = ipw_cmd(sc, IPW_CMD_ENABLE, NULL, 0);
+ if (error != 0)
+ return error;
+ error = ipw_waitfordisable(sc, 0);
+ if (error != 0)
+ return error;
+ sc->flags |= IPW_FLAG_ENABLED;
+ }
+ return 0;
+}
+
+static int
+ipw_disable(struct ipw_softc *sc)
+{
+ int error;
+
+ if (sc->flags & IPW_FLAG_ENABLED) {
+ DPRINTF(("Disable adapter\n"));
+ error = ipw_cmd(sc, IPW_CMD_DISABLE, NULL, 0);
+ if (error != 0)
+ return error;
+ error = ipw_waitfordisable(sc, 1);
+ if (error != 0)
+ return error;
+ sc->flags &= ~IPW_FLAG_ENABLED;
+ }
+ return 0;
+}
+
/*
* Upload the microcode to the device.
*/
@@ -1741,17 +2008,189 @@ ipw_load_firmware(struct ipw_softc *sc, const char *fw, int size)
}
static int
+ipw_setwepkeys(struct ipw_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ipw_wep_key wepkey;
+ struct ieee80211_key *wk;
+ int error, i;
+
+ for (i = 0; i < IEEE80211_WEP_NKID; i++) {
+ wk = &ic->ic_crypto.cs_nw_keys[i];
+
+ if (wk->wk_cipher == NULL ||
+ wk->wk_cipher->ic_cipher != IEEE80211_CIPHER_WEP)
+ continue;
+
+ wepkey.idx = i;
+ wepkey.len = wk->wk_keylen;
+ memset(wepkey.key, 0, sizeof wepkey.key);
+ memcpy(wepkey.key, wk->wk_key, wk->wk_keylen);
+ DPRINTF(("Setting wep key index %u len %u\n", wepkey.idx,
+ wepkey.len));
+ error = ipw_cmd(sc, IPW_CMD_SET_WEP_KEY, &wepkey,
+ sizeof wepkey);
+ if (error != 0)
+ return error;
+ }
+ return 0;
+}
+
+static int
+ipw_setwpaie(struct ipw_softc *sc, const void *ie, int ielen)
+{
+ struct ipw_wpa_ie wpaie;
+
+ memset(&wpaie, 0, sizeof(wpaie));
+ wpaie.len = htole32(ielen);
+ /* XXX verify length */
+ memcpy(&wpaie.ie, ie, ielen);
+ DPRINTF(("Setting WPA IE\n"));
+ return ipw_cmd(sc, IPW_CMD_SET_WPA_IE, &wpaie, sizeof(wpaie));
+}
+
+static int
+ipw_setbssid(struct ipw_softc *sc, uint8_t *bssid)
+{
+ static const uint8_t zerobssid[IEEE80211_ADDR_LEN];
+
+ if (bssid == NULL || bcmp(bssid, zerobssid, IEEE80211_ADDR_LEN) == 0) {
+ DPRINTF(("Setting mandatory BSSID to null\n"));
+ return ipw_cmd(sc, IPW_CMD_SET_MANDATORY_BSSID, NULL, 0);
+ } else {
+ DPRINTF(("Setting mandatory BSSID to %6D\n", bssid, ":"));
+ return ipw_cmd(sc, IPW_CMD_SET_MANDATORY_BSSID,
+ bssid, IEEE80211_ADDR_LEN);
+ }
+}
+
+static int
+ipw_setssid(struct ipw_softc *sc, void *ssid, size_t ssidlen)
+{
+ if (ssidlen == 0) {
+ /*
+ * A bug in the firmware breaks the ``don't associate''
+ * bit in the scan options command. To compensate for
+ * this install a bogus ssid when no ssid is specified
+ * so the firmware won't try to associate.
+ */
+ DPRINTF(("Setting bogus ESSID to WAR firmware bug\n"));
+ return ipw_cmd(sc, IPW_CMD_SET_ESSID,
+ "\x18\x19\x20\x21\x22\x23\x24\x25\x26\x27"
+ "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31"
+ "\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b"
+ "\x3c\x3d", IEEE80211_NWID_LEN);
+ } else {
+#ifdef IPW_DEBUG
+ if (ipw_debug > 0) {
+ printf("Setting ESSID to ");
+ ieee80211_print_essid(ssid, ssidlen);
+ printf("\n");
+ }
+#endif
+ return ipw_cmd(sc, IPW_CMD_SET_ESSID, ssid, ssidlen);
+ }
+}
+
+static int
+ipw_setscanopts(struct ipw_softc *sc, uint32_t chanmask, uint32_t flags)
+{
+ struct ipw_scan_options opts;
+
+ DPRINTF(("Scan options: mask 0x%x flags 0x%x\n", chanmask, flags));
+ opts.channels = htole32(chanmask);
+ opts.flags = htole32(flags);
+ return ipw_cmd(sc, IPW_CMD_SET_SCAN_OPTIONS, &opts, sizeof(opts));
+}
+
+/*
+ * Handler for sc_scan_task. This is a simple wrapper around ipw_scan().
+ */
+static void
+ipw_scan_task(void *context, int pending)
+{
+ struct ipw_softc *sc = context;
+ IPW_LOCK_DECL;
+
+ IPW_LOCK(sc);
+ ipw_scan(sc);
+ IPW_UNLOCK(sc);
+}
+
+static int
+ipw_scan(struct ipw_softc *sc)
+{
+ uint32_t params;
+ int error;
+
+ DPRINTF(("%s: flags 0x%x\n", __func__, sc->flags));
+
+ if (sc->flags & IPW_FLAG_SCANNING)
+ return (EBUSY);
+ sc->flags |= IPW_FLAG_SCANNING | IPW_FLAG_HACK;
+
+ /* NB: IPW_SCAN_DO_NOT_ASSOCIATE does not work (we set it anyway) */
+ error = ipw_setscanopts(sc, 0x3fff, IPW_SCAN_DO_NOT_ASSOCIATE);
+ if (error != 0)
+ goto done;
+
+ /*
+ * Setup null/bogus ssid so firmware doesn't use any previous
+ * ssid to try and associate. This is because the ``don't
+ * associate'' option bit is broken (sigh).
+ */
+ error = ipw_setssid(sc, NULL, 0);
+ if (error != 0)
+ goto done;
+
+ /*
+ * NB: the adapter may be disabled on association lost;
+ * if so just re-enable it to kick off scanning.
+ */
+ DPRINTF(("Starting scan\n"));
+ sc->sc_scan_timer = 3;
+ if (sc->flags & IPW_FLAG_ENABLED) {
+ params = 0; /* XXX? */
+ error = ipw_cmd(sc, IPW_CMD_BROADCAST_SCAN,
+ &params, sizeof(params));
+ } else
+ error = ipw_enable(sc);
+done:
+ if (error != 0) {
+ DPRINTF(("Scan failed\n"));
+ sc->flags &= ~(IPW_FLAG_SCANNING | IPW_FLAG_HACK);
+ }
+ return (error);
+}
+
+static int
+ipw_setchannel(struct ipw_softc *sc, struct ieee80211_channel *chan)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint32_t data;
+ int error;
+
+ data = htole32(ieee80211_chan2ieee(ic, chan));
+ DPRINTF(("Setting channel to %u\n", le32toh(data)));
+ error = ipw_cmd(sc, IPW_CMD_SET_CHANNEL, &data, sizeof data);
+ if (error == 0)
+ ipw_setcurchan(sc, chan);
+ return error;
+}
+
+static int
ipw_config(struct ipw_softc *sc)
{
struct ieee80211com *ic = &sc->sc_ic;
struct ifnet *ifp = ic->ic_ifp;
struct ipw_security security;
- struct ieee80211_key *k;
- struct ipw_wep_key wepkey;
- struct ipw_scan_options options;
struct ipw_configuration config;
uint32_t data;
- int error, i;
+ int error;
+
+ error = ipw_disable(sc);
+ if (error != 0)
+ return error;
switch (ic->ic_opmode) {
case IEEE80211_M_STA:
@@ -1774,17 +2213,13 @@ ipw_config(struct ipw_softc *sc)
if (ic->ic_opmode == IEEE80211_M_IBSS ||
ic->ic_opmode == IEEE80211_M_MONITOR) {
- data = htole32(ieee80211_chan2ieee(ic, ic->ic_curchan));
- DPRINTF(("Setting channel to %u\n", le32toh(data)));
- error = ipw_cmd(sc, IPW_CMD_SET_CHANNEL, &data, sizeof data);
+ error = ipw_setchannel(sc, ic->ic_curchan);
if (error != 0)
return error;
}
- if (ic->ic_opmode == IEEE80211_M_MONITOR) {
- DPRINTF(("Enabling adapter\n"));
- return ipw_cmd(sc, IPW_CMD_ENABLE, NULL, 0);
- }
+ if (ic->ic_opmode == IEEE80211_M_MONITOR)
+ return ipw_enable(sc);
IEEE80211_ADDR_COPY(ic->ic_myaddr, IF_LLADDR(ifp));
DPRINTF(("Setting MAC address to %6D\n", ic->ic_myaddr, ":"));
@@ -1812,6 +2247,12 @@ ipw_config(struct ipw_softc *sc)
if (error != 0)
return error;
+ /* NB: use the same rate set */
+ DPRINTF(("Setting msdu tx rates to 0x%x\n", le32toh(data)));
+ error = ipw_cmd(sc, IPW_CMD_SET_MSDU_TX_RATES, &data, sizeof data);
+ if (error != 0)
+ return error;
+
data = htole32(0xf); /* 1, 2, 5.5, 11 */
DPRINTF(("Setting tx rates to 0x%x\n", le32toh(data)));
error = ipw_cmd(sc, IPW_CMD_SET_TX_RATES, &data, sizeof data);
@@ -1845,22 +2286,11 @@ ipw_config(struct ipw_softc *sc)
if (error != 0)
return error;
-#ifdef IPW_DEBUG
- if (ipw_debug > 0) {
- printf("Setting ESSID to ");
- ieee80211_print_essid(ic->ic_des_ssid[0].ssid,
- ic->ic_des_ssid[0].len);
- printf("\n");
- }
-#endif
- error = ipw_cmd(sc, IPW_CMD_SET_ESSID, ic->ic_des_ssid[0].ssid,
- ic->ic_des_ssid[0].len);
+ error = ipw_setssid(sc, ic->ic_des_ssid[0].ssid, ic->ic_des_ssid[0].len);
if (error != 0)
return error;
- /* no mandatory BSSID */
- DPRINTF(("Setting mandatory BSSID to null\n"));
- error = ipw_cmd(sc, IPW_CMD_SET_MANDATORY_BSSID, NULL, 0);
+ error = ipw_setbssid(sc, NULL);
if (error != 0)
return error;
@@ -1878,35 +2308,25 @@ ipw_config(struct ipw_softc *sc)
IPW_AUTH_SHARED : IPW_AUTH_OPEN;
security.ciphers = htole32(IPW_CIPHER_NONE);
DPRINTF(("Setting authmode to %u\n", security.authmode));
- error = ipw_cmd(sc, IPW_CMD_SET_SECURITY_INFORMATION, &security,
+ error = ipw_cmd(sc, IPW_CMD_SET_SECURITY_INFO, &security,
sizeof security);
if (error != 0)
return error;
if (ic->ic_flags & IEEE80211_F_PRIVACY) {
- k = ic->ic_crypto.cs_nw_keys;
- for (i = 0; i < IEEE80211_WEP_NKID; i++, k++) {
- if (k->wk_keylen == 0)
- continue;
-
- wepkey.idx = i;
- wepkey.len = k->wk_keylen;
- memset(wepkey.key, 0, sizeof wepkey.key);
- memcpy(wepkey.key, k->wk_key, k->wk_keylen);
- DPRINTF(("Setting wep key index %u len %u\n",
- wepkey.idx, wepkey.len));
- error = ipw_cmd(sc, IPW_CMD_SET_WEP_KEY, &wepkey,
- sizeof wepkey);
+ error = ipw_setwepkeys(sc);
+ if (error != 0)
+ return error;
+
+ if (ic->ic_crypto.cs_def_txkey != IEEE80211_KEYIX_NONE) {
+ data = htole32(ic->ic_crypto.cs_def_txkey);
+ DPRINTF(("Setting wep tx key index to %u\n",
+ le32toh(data)));
+ error = ipw_cmd(sc, IPW_CMD_SET_WEP_KEY_INDEX, &data,
+ sizeof data);
if (error != 0)
return error;
}
-
- data = htole32(ic->ic_crypto.cs_def_txkey);
- DPRINTF(("Setting wep tx key index to %u\n", le32toh(data)));
- error = ipw_cmd(sc, IPW_CMD_SET_WEP_KEY_INDEX, &data,
- sizeof data);
- if (error != 0)
- return error;
}
data = htole32((ic->ic_flags & IEEE80211_F_PRIVACY) ? IPW_WEPON : 0);
@@ -1915,16 +2335,11 @@ ipw_config(struct ipw_softc *sc)
if (error != 0)
return error;
-#if 0
- struct ipw_wpa_ie ie;
-
- memset(&ie, 0, sizeof ie);
- ie.len = htole32(sizeof (struct ieee80211_ie_wpa));
- DPRINTF(("Setting wpa ie\n"));
- error = ipw_cmd(sc, IPW_CMD_SET_WPA_IE, &ie, sizeof ie);
- if (error != 0)
- return error;
-#endif
+ if (ic->ic_opt_ie != NULL) {
+ error = ipw_setwpaie(sc, ic->ic_opt_ie, ic->ic_opt_ie_len);
+ if (error != 0)
+ return error;
+ }
if (ic->ic_opmode == IEEE80211_M_IBSS) {
data = htole32(ic->ic_bintval);
@@ -1935,16 +2350,136 @@ ipw_config(struct ipw_softc *sc)
return error;
}
- options.flags = 0;
- options.channels = htole32(0x3fff); /* scan channels 1-14 */
- DPRINTF(("Setting scan options to 0x%x\n", le32toh(options.flags)));
- error = ipw_cmd(sc, IPW_CMD_SET_SCAN_OPTIONS, &options, sizeof options);
+ error = ipw_setscanopts(sc, 0x3fff, 0);
if (error != 0)
return error;
- /* finally, enable adapter (start scanning for an access point) */
- DPRINTF(("Enabling adapter\n"));
- return ipw_cmd(sc, IPW_CMD_ENABLE, NULL, 0);
+ return (ipw_enable(sc));
+}
+
+/*
+ * Handler for sc_assoc_task. This is a simple wrapper around
+ * ipw_auth_and_assoc().
+ */
+static void
+ipw_assoc_task(void *context, int pending)
+{
+ struct ipw_softc *sc = context;
+ IPW_LOCK_DECL;
+
+ IPW_LOCK(sc);
+ ipw_auth_and_assoc(sc);
+ IPW_UNLOCK(sc);
+}
+
+static int
+ipw_auth_and_assoc(struct ipw_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211_node *ni = ic->ic_bss;
+ struct ipw_security security;
+ uint32_t data;
+ int error;
+
+ error = ipw_disable(sc);
+ if (error != 0)
+ return (error);
+
+ memset(&security, 0, sizeof security);
+ security.authmode = (ni->ni_authmode == IEEE80211_AUTH_SHARED) ?
+ IPW_AUTH_SHARED : IPW_AUTH_OPEN;
+ security.ciphers = htole32(IPW_CIPHER_NONE);
+ DPRINTF(("Setting authmode to %u\n", security.authmode));
+ error = ipw_cmd(sc, IPW_CMD_SET_SECURITY_INFO, &security,
+ sizeof security);
+ if (error != 0)
+ return (error);
+
+ if (ic->ic_flags & IEEE80211_F_PRIVACY) {
+ error = ipw_setwepkeys(sc);
+ if (error != 0)
+ return error;
+
+ if (ic->ic_crypto.cs_def_txkey != IEEE80211_KEYIX_NONE) {
+ data = htole32(ic->ic_crypto.cs_def_txkey);
+ DPRINTF(("Setting wep tx key index to %u\n",
+ le32toh(data)));
+ error = ipw_cmd(sc, IPW_CMD_SET_WEP_KEY_INDEX, &data,
+ sizeof data);
+ if (error != 0)
+ return error;
+ }
+ }
+
+ data = htole32((ic->ic_flags & IEEE80211_F_PRIVACY) ? IPW_WEPON : 0);
+ DPRINTF(("Setting wep flags to 0x%x\n", le32toh(data)));
+ error = ipw_cmd(sc, IPW_CMD_SET_WEP_FLAGS, &data, sizeof data);
+ if (error != 0)
+ return error;
+
+ error = ipw_setssid(sc, ni->ni_essid, ni->ni_esslen);
+ if (error != 0)
+ return (error);
+
+ error = ipw_setbssid(sc, ni->ni_bssid);
+ if (error != 0)
+ return (error);
+
+ if (ic->ic_opt_ie != NULL) {
+ error = ipw_setwpaie(sc, ic->ic_opt_ie, ic->ic_opt_ie_len);
+ if (error != 0)
+ return error;
+ }
+ if (ic->ic_opmode == IEEE80211_M_IBSS) {
+ error = ipw_setchannel(sc, ni->ni_chan);
+ if (error != 0)
+ return (error);
+ }
+
+ /* lock scan to ap's channel and enable associate */
+ error = ipw_setscanopts(sc,
+ 1<<(ieee80211_chan2ieee(ic, ni->ni_chan)-1), 0);
+
+ return ipw_enable(sc); /* finally, enable adapter */
+}
+
+/*
+ * Handler for sc_disassoc_task. This is a simple wrapper around
+ * ipw_disassociate().
+ */
+static void
+ipw_disassoc_task(void *context, int pending)
+{
+ struct ipw_softc *sc = context;
+ IPW_LOCK_DECL;
+
+ IPW_LOCK(sc);
+ ipw_disassociate(sc);
+ IPW_UNLOCK(sc);
+}
+
+static int
+ipw_disassociate(struct ipw_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211_node *ni = ic->ic_bss;
+
+ DPRINTF(("Disassociate from %6D\n", ni->ni_bssid, ":"));
+
+ /*
+ * NB: don't try to do this if ipw_stop_master has
+ * shutdown the firmware and disabled interrupts.
+ */
+ if (!(sc->flags & IPW_FLAG_FW_INITED))
+ return (0);
+
+ sc->flags &= ~IPW_FLAG_ASSOCIATED;
+ /*
+ * NB: firmware currently ignores bssid parameter, but
+ * supply it in case this changes (follow linux driver).
+ */
+ return ipw_cmd(sc, IPW_CMD_DISASSOCIATE,
+ ni->ni_bssid, IEEE80211_ADDR_LEN);
}
/*
@@ -1961,35 +2496,38 @@ static void
ipw_init(void *priv)
{
struct ipw_softc *sc = priv;
+ IPW_LOCK_DECL;
+
+ IPW_LOCK(sc);
+ ipw_init_locked(sc, 0);
+ IPW_UNLOCK(sc);
+}
+
+static void
+ipw_init_locked(struct ipw_softc *sc, int force)
+{
struct ieee80211com *ic = &sc->sc_ic;
struct ifnet *ifp = ic->ic_ifp;
const struct firmware *fp;
const struct ipw_firmware_hdr *hdr;
const char *imagename, *fw;
- int owned;
+ IPW_LOCK_DECL;
- /*
- * ipw_init() is exposed through ifp->if_init so it might be called
- * without the driver's lock held. Since msleep() doesn't like being
- * called on a recursed mutex, we acquire the driver's lock only if
- * we're not already holding it.
- */
- if (!(owned = mtx_owned(&sc->sc_mtx)))
- mtx_lock(&sc->sc_mtx);
+ IPW_LOCK_ASSERT(sc);
+
+ DPRINTF(("%s: state %s flags 0x%x\n", __func__,
+ ieee80211_state_name[ic->ic_state], sc->flags));
/*
* Avoid re-entrant calls. We need to release the mutex in ipw_init()
* when loading the firmware and we don't want to be called during this
* operation.
*/
- if (sc->flags & IPW_FLAG_INIT_LOCKED) {
- if (!owned)
- mtx_unlock(&sc->sc_mtx);
+ if (sc->flags & IPW_FLAG_INIT_LOCKED)
return;
- }
sc->flags |= IPW_FLAG_INIT_LOCKED;
- ipw_stop(sc);
+ ipw_stop_locked(sc);
if (ipw_reset(sc) != 0) {
device_printf(sc->sc_dev, "could not reset adapter\n");
@@ -2016,11 +2554,11 @@ ipw_init(void *priv)
*/
if (sc->sc_firmware == NULL || strcmp(sc->sc_firmware->name,
imagename) != 0) {
- mtx_unlock(&sc->sc_mtx);
+ IPW_UNLOCK(sc);
if (sc->sc_firmware != NULL)
firmware_put(sc->sc_firmware, FIRMWARE_UNLOAD);
sc->sc_firmware = firmware_get(imagename);
- mtx_lock(&sc->sc_mtx);
+ IPW_LOCK(sc);
}
if (sc->sc_firmware == NULL) {
@@ -2045,6 +2583,7 @@ ipw_init(void *priv)
goto fail2;
}
+ DPRINTF(("Loading firmware image '%s'\n", imagename));
fw = (const char *)fp->data + sizeof *hdr + le32toh(hdr->mainsz);
if (ipw_load_ucode(sc, fw, le32toh(hdr->ucodesz)) != 0) {
device_printf(sc->sc_dev, "could not load microcode\n");
@@ -2092,37 +2631,55 @@ ipw_init(void *priv)
goto fail1;
}
+ if (ic->ic_opmode != IEEE80211_M_MONITOR) {
+ /*
+ * NB: When restarting the adapter clock the state
+ * machine regardless of the roaming mode; otherwise
+ * we need to notify user apps so they can manually
+ * get us going again.
+ */
+ if (ic->ic_roaming != IEEE80211_ROAMING_MANUAL || force)
+ ieee80211_new_state(ic, IEEE80211_S_SCAN, 0);
+ } else
+ ieee80211_new_state(ic, IEEE80211_S_RUN, -1);
+
+ callout_reset(&sc->sc_wdtimer, hz, ipw_watchdog, sc);
ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
ifp->if_drv_flags |= IFF_DRV_RUNNING;
sc->flags &=~ IPW_FLAG_INIT_LOCKED;
-
- if (!owned)
- mtx_unlock(&sc->sc_mtx);
-
return;
fail2: firmware_put(fp, FIRMWARE_UNLOAD);
sc->sc_firmware = NULL;
fail1: ifp->if_flags &= ~IFF_UP;
- ipw_stop(sc);
+ ipw_stop_locked(sc);
sc->flags &=~ IPW_FLAG_INIT_LOCKED;
- if (!owned)
- mtx_unlock(&sc->sc_mtx);
}
static void
ipw_stop(void *priv)
{
struct ipw_softc *sc = priv;
+ IPW_LOCK_DECL;
+
+ IPW_LOCK(sc);
+ ipw_stop_locked(sc);
+ IPW_UNLOCK(sc);
+}
+
+static void
+ipw_stop_locked(struct ipw_softc *sc)
+{
struct ieee80211com *ic = &sc->sc_ic;
struct ifnet *ifp = ic->ic_ifp;
int i;
- mtx_lock(&sc->sc_mtx);
+ IPW_LOCK_ASSERT(sc);
ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
+ callout_stop(&sc->sc_wdtimer);
ipw_stop_master(sc);
CSR_WRITE_4(sc, IPW_CSR_RST, IPW_RST_SW_RESET);
@@ -2134,10 +2691,7 @@ ipw_stop(void *priv)
ipw_release_sbd(sc, &sc->stbd_list[i]);
sc->sc_tx_timer = 0;
- ifp->if_timer = 0;
ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE);
-
- mtx_unlock(&sc->sc_mtx);
}
static int
@@ -2184,6 +2738,7 @@ ipw_write_table1(struct ipw_softc *sc, uint32_t off, uint32_t info)
MEM_WRITE_4(sc, MEM_READ_4(sc, sc->table1_base + off), info);
}
+#if 0
static int
ipw_read_table2(struct ipw_softc *sc, uint32_t off, void *buf, uint32_t *len)
{
@@ -2219,6 +2774,7 @@ ipw_read_mem_1(struct ipw_softc *sc, bus_size_t offset, uint8_t *datap,
*datap = CSR_READ_1(sc, IPW_CSR_INDIRECT_DATA + (offset & 3));
}
}
+#endif
static void
ipw_write_mem_1(struct ipw_softc *sc, bus_size_t offset, const uint8_t *datap,
@@ -2229,3 +2785,56 @@ ipw_write_mem_1(struct ipw_softc *sc, bus_size_t offset, const uint8_t *datap,
CSR_WRITE_1(sc, IPW_CSR_INDIRECT_DATA + (offset & 3), *datap);
}
}
+
+static void
+ipw_scan_start(struct ieee80211com *ic)
+{
+ struct ifnet *ifp = ic->ic_ifp;
+ struct ipw_softc *sc = ifp->if_softc;
+ IPW_LOCK_DECL;
+
+ IPW_LOCK(sc);
+ if (!(sc->flags & IPW_FLAG_SCANNING))
+ taskqueue_enqueue_fast(taskqueue_fast, &sc->sc_scan_task);
+ IPW_UNLOCK(sc);
+}
+
+static void
+ipw_set_channel(struct ieee80211com *ic)
+{
+ struct ifnet *ifp = ic->ic_ifp;
+ struct ipw_softc *sc = ifp->if_softc;
+ IPW_LOCK_DECL;
+
+ IPW_LOCK(sc);
+ if (ic->ic_opmode == IEEE80211_M_MONITOR) {
+ ipw_disable(sc);
+ ipw_setchannel(sc, ic->ic_curchan);
+ ipw_enable(sc);
+ }
+ IPW_UNLOCK(sc);
+}
+
+static void
+ipw_scan_curchan(struct ieee80211com *ic, unsigned long maxdwell)
+{
+ /* NB: all channels are scanned at once */
+}
+
+static void
+ipw_scan_mindwell(struct ieee80211com *ic)
+{
+ /* NB: don't try to abort scan; wait for firmware to finish */
+}
+
+static void
+ipw_scan_end(struct ieee80211com *ic)
+{
+ struct ifnet *ifp = ic->ic_ifp;
+ struct ipw_softc *sc = ifp->if_softc;
+ IPW_LOCK_DECL;
+
+ IPW_LOCK(sc);
+ sc->flags &= ~IPW_FLAG_SCANNING;
+ IPW_UNLOCK(sc);
+}
diff --git a/sys/dev/ipw/if_ipwreg.h b/sys/dev/ipw/if_ipwreg.h
index 96741d0..b300e13 100644
--- a/sys/dev/ipw/if_ipwreg.h
+++ b/sys/dev/ipw/if_ipwreg.h
@@ -88,11 +88,18 @@
#define IPW_IO_LED_OFF 0x00002000
#define IPW_IO_RADIO_DISABLED 0x00010000
+/* state codes sent by fw on IPW_STATUS_CODE_NEWSTATE interrupt */
+#define IPW_STATE_INITIALIZED 0x0001
+#define IPW_STATE_CC_FOUND 0x0002 /* 802.11d cc received */
#define IPW_STATE_ASSOCIATED 0x0004
#define IPW_STATE_ASSOCIATION_LOST 0x0008
+#define IPW_STATE_ASSOCIATION_CHANGED 0x0010 /* assoc params changed? */
#define IPW_STATE_SCAN_COMPLETE 0x0020
+#define IPW_STATE_PS_ENTER 0x0040 /* entered power-save mode */
+#define IPW_STATE_PS_EXIT 0x0080 /* exited power-save mode */
#define IPW_STATE_RADIO_DISABLED 0x0100
#define IPW_STATE_DISABLED 0x0200
+#define IPW_STATE_POWER_DOWN 0x0400 /* ??? */
#define IPW_STATE_SCANNING 0x0800
/* table1 offsets */
@@ -146,7 +153,9 @@ struct ipw_status {
uint8_t flags;
#define IPW_STATUS_FLAG_DECRYPTED 0x01
#define IPW_STATUS_FLAG_WEP_ENCRYPTED 0x02
+#define IPW_STATUS_FLAG_CRC_ERROR 0x04
uint8_t rssi; /* received signal strength indicator */
+#define IPW_RSSI_TO_DBM (-98) /* XXX fixed nf to convert dBm */
} __packed;
/* data header */
@@ -190,9 +199,14 @@ struct ipw_cmd {
#define IPW_CMD_DISABLE 44
#define IPW_CMD_SET_DESIRED_BSSID 45
#define IPW_CMD_SET_SCAN_OPTIONS 46
+#define IPW_CMD_SET_SCAN_DWELL_TIME 47
+#define IPW_CMD_SET_SHORT_RETRY 51
+#define IPW_CMD_SET_LONG_RETRY 52
#define IPW_CMD_PREPARE_POWER_DOWN 58
#define IPW_CMD_DISABLE_PHY 61
-#define IPW_CMD_SET_SECURITY_INFORMATION 67
+#define IPW_CMD_SET_MSDU_TX_RATES 62
+#define IPW_CMD_SET_SECURITY_INFO 67
+#define IPW_CMD_DISASSOCIATE 68
#define IPW_CMD_SET_WPA_IE 69
uint32_t subtype;
uint32_t seq;
@@ -204,7 +218,7 @@ struct ipw_cmd {
/* possible values for command IPW_CMD_SET_POWER_MODE */
#define IPW_POWER_MODE_CAM 0
-#define IPW_POWER_AUTOMATIC 6
+#define IPW_POWER_MODE_AUTO 6
/* possible values for command IPW_CMD_SET_MODE */
#define IPW_MODE_BSS 0
@@ -241,6 +255,7 @@ struct ipw_security {
struct ipw_scan_options {
uint32_t flags;
#define IPW_SCAN_DO_NOT_ASSOCIATE 0x00000001
+#define IPW_SCAN_MIXED_CELL 0x00000002
#define IPW_SCAN_PASSIVE 0x00000008
uint32_t channels;
} __packed;
diff --git a/sys/dev/ipw/if_ipwvar.h b/sys/dev/ipw/if_ipwvar.h
index c97d6a8..f6ec999 100644
--- a/sys/dev/ipw/if_ipwvar.h
+++ b/sys/dev/ipw/if_ipwvar.h
@@ -85,12 +85,21 @@ struct ipw_softc {
struct mtx sc_mtx;
struct task sc_init_task;
+ struct task sc_scan_task;
+ struct task sc_chan_task;
+ struct task sc_assoc_task;
+ struct task sc_disassoc_task;
+ struct callout sc_wdtimer; /* watchdog timer */
uint32_t flags;
#define IPW_FLAG_FW_INITED (1 << 0)
#define IPW_FLAG_INIT_LOCKED (1 << 1)
#define IPW_FLAG_HAS_RADIO_SWITCH (1 << 2)
-#define IPW_FLAG_FW_WARNED (1 << 3)
+#define IPW_FLAG_HACK (1 << 3)
+#define IPW_FLAG_SCANNING (1 << 4)
+#define IPW_FLAG_ENABLED (1 << 5)
+#define IPW_FLAG_BUSY (1 << 6)
+#define IPW_FLAG_ASSOCIATED (1 << 7)
int irq_rid;
int mem_rid;
@@ -102,6 +111,7 @@ struct ipw_softc {
const struct firmware *sc_firmware;
int sc_tx_timer;
+ int sc_scan_timer;
bus_dma_tag_t tbd_dmat;
bus_dma_tag_t rbd_dmat;
@@ -160,3 +170,18 @@ struct ipw_softc {
#define sc_txtap sc_txtapu.th
int sc_txtap_len;
};
+
+/*
+ * NB.: This models the only instance of async locking in ipw_init_locked
+ * and must be kept in sync.
+ */
+#define IPW_LOCK_DECL int __waslocked = 0
+#define IPW_LOCK(sc) do { \
+ if (!(__waslocked = mtx_owned(&(sc)->sc_mtx))) \
+ mtx_lock(&sc->sc_mtx); \
+} while (0)
+#define IPW_UNLOCK(sc) do { \
+ if (!__waslocked) \
+ mtx_unlock(&sc->sc_mtx); \
+} while (0)
+#define IPW_LOCK_ASSERT(sc) mtx_assert(&(sc)->sc_mtx, MA_OWNED)
OpenPOWER on IntegriCloud