diff --git a/sys/dev/ipw/if_ipw.c b/sys/dev/ipw/if_ipw.c index 0be6fca4a004..f72c1c207177 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 . All rights reserved. + * Copyright (c) 2006 Sam Leffler, Errno Consulting + * Copyright (c) 2007 Andrew Thompson * * 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 #include +#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 @@ -1465,6 +1662,17 @@ ipw_tx_start(struct ifnet *ifp, struct mbuf *m0, struct ieee80211_node *ni) 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; @@ -1472,12 +1680,10 @@ ipw_start(struct ifnet *ifp) 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; + struct ipw_softc *sc = arg; + struct ieee80211com *ic = &sc->sc_ic; + struct ifnet *ifp = sc->sc_ifp; - mtx_lock(&sc->sc_mtx); - - 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. */ @@ -1740,18 +2007,190 @@ ipw_load_firmware(struct ipw_softc *sc, const char *fw, int size) return 0; } +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, + ¶ms, 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; + error = ipw_setwepkeys(sc); + if (error != 0) + return error; - 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); + 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 96741d0352c0..b300e13492ca 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 c97d6a8e97bb..f6ec999a5291 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)