Index: if_ipw.c =================================================================== RCS file: /home/ncvs/src/sys/dev/ipw/if_ipw.c,v retrieving revision 1.29 diff -u -p -r1.29 if_ipw.c --- if_ipw.c 5 Jul 2007 15:06:49 -0000 1.29 +++ if_ipw.c 13 Jul 2007 12:42:38 -0000 @@ -1,8 +1,7 @@ -/* $FreeBSD: src/sys/dev/ipw/if_ipw.c,v 1.29 2007/07/05 15:06:49 avatar Exp $ */ - /*- * Copyright (c) 2004-2006 * Damien Bergamini . All rights reserved. + * Copyright (c) 2006 Sam Leffler, Errno Consulting * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -78,10 +77,11 @@ __FBSDID("$FreeBSD: src/sys/dev/ipw/if_i #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) -int ipw_debug = 0; +int ipw_debug = 40; SYSCTL_INT(_debug, OID_AUTO, ipw, CTLFLAG_RW, &ipw_debug, 0, "ipw debug level"); #else #define DPRINTF(x) @@ -109,40 +109,66 @@ static void ipw_release(struct ipw_softc 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 const char *ipw_cmdname(int); static void ipw_intr(void *); static void ipw_dma_map_addr(void *, bus_dma_segment_t *, int, int); -static int ipw_cmd(struct ipw_softc *, uint32_t, void *, uint32_t); +static int ipw_cmd(struct ipw_softc *, uint32_t, const void *, uint32_t); static int ipw_tx_start(struct ifnet *, struct mbuf *, struct ieee80211_node *); +static void ipw_start_locked(struct ifnet *); static void ipw_start(struct ifnet *); -static void ipw_watchdog(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_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_enable(struct ipw_softc *); +static int ipw_disable(struct ipw_softc *); static int ipw_config(struct ipw_softc *); -static void ipw_init_task(void *, int); +static void ipw_restart(void *, int); +static int ipw_scan(struct ipw_softc *); +static void ipw_assoc_lost(void *, int); +static void ipw_assoc(struct ieee80211com *); +static void ipw_disassoc(struct ieee80211com *); +static int ipw_auth_and_assoc(struct ipw_softc *); +static int ipw_disassociate(struct ipw_softc *); +static void ipw_ops(void *, int); static void ipw_init(void *); +static void ipw_init_locked(struct ipw_softc *, int); +static void ipw_stop_locked(struct ipw_softc *); static void ipw_stop(void *); static int ipw_sysctl_stats(SYSCTL_HANDLER_ARGS); +static int ipw_getrfkill(struct ipw_softc *); +static void ipw_radio_on(void *, int); +static void ipw_radio_off(void *, int); static int ipw_sysctl_radio(SYSCTL_HANDLER_ARGS); +static int ipw_get_firmware(struct ipw_softc *); +static void ipw_put_firmware(struct ipw_softc *); +static int ipw_load_ucode(struct ipw_softc *, const struct ipw_fw *); +static int ipw_load_firmware(struct ipw_softc *, const struct ipw_fw *); 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 uint16_t ipw_read_prom_word(struct ipw_softc *, uint8_t); +static void ipw_sysctlattach(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 +198,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) @@ -205,8 +232,25 @@ ipw_attach(device_t dev) mtx_init(&sc->sc_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, MTX_DEF | MTX_RECURSE); + IPW_CMD_LOCK_INIT(sc); - TASK_INIT(&sc->sc_init_task, 0, ipw_init_task, sc); +#if __FreeBSD_version >= 700000 + sc->sc_tq = taskqueue_create("ipw_taskq", M_NOWAIT, + taskqueue_thread_enqueue, &sc->sc_tq); + taskqueue_start_threads(&sc->sc_tq, 1, PI_NET, "%s taskq", + device_get_nameunit(dev)); +#else + sc->sc_tq = taskqueue_create("ipw_taskq", M_NOWAIT, + taskqueue_thread_enqueue, &sc->sc_tq, &sc->sc_tqproc); + taskqueue_start_threads(&sc->sc_tq, 1, PI_NET, "%s taskq", + device_get_nameunit(dev)); +#endif + TASK_INIT(&sc->sc_radiontask, 0, ipw_radio_on, sc); + TASK_INIT(&sc->sc_radiofftask, 0, ipw_radio_off, sc); + TASK_INIT(&sc->sc_assoclosttask,0, ipw_assoc_lost, sc); + TASK_INIT(&sc->sc_restarttask, 0, ipw_restart, sc); + TASK_INIT(&sc->sc_opstask, 0, ipw_ops, 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 +304,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 +314,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); @@ -291,6 +335,7 @@ ipw_attach(device_t dev) /* set supported .11b channels (read from EEPROM) */ if ((val = ipw_read_prom_word(sc, IPW_EEPROM_CHANNEL_LIST)) == 0) val = 0x7ff; /* default to channels 1-11 */ + sc->chanmask = val; val <<= 1; for (i = 1; i < 16; i++) { if (val & (1 << i)) { @@ -300,10 +345,11 @@ ipw_attach(device_t dev) c->ic_ieee = i; } } + DPRINTF(("%s: added %d channels\n", __func__, ic->ic_nchans)); /* check support for radio transmitter switch in EEPROM */ if (!(ipw_read_prom_word(sc, IPW_EEPROM_RADIO) & 8)) - sc->flags |= IPW_FLAG_HAS_RADIO_SWITCH; + sc->flags |= IPW_FLAG_HAS_RFSWITCH; ieee80211_ifattach(ic); /* override state transition machine */ @@ -311,8 +357,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; @@ -323,25 +375,7 @@ ipw_attach(device_t dev) sc->sc_txtap.wt_ihdr.it_len = htole16(sc->sc_txtap_len); sc->sc_txtap.wt_ihdr.it_present = htole32(IPW_TX_RADIOTAP_PRESENT); - /* - * Add a few sysctl knobs. - */ - sc->dwelltime = 100; - - SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), - SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "radio", - CTLTYPE_INT | CTLFLAG_RD, sc, 0, ipw_sysctl_radio, "I", - "radio transmitter switch state (0=off, 1=on)"); - - SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), - SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "stats", - CTLTYPE_OPAQUE | CTLFLAG_RD, sc, 0, ipw_sysctl_stats, "S", - "statistics"); - - SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), - SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "dwell", - CTLFLAG_RW, &sc->dwelltime, 0, - "channel dwell time (ms) for AP/station scanning"); + ipw_sysctlattach(sc); /* * Hook our interrupt after all initialization is complete. @@ -370,6 +404,8 @@ ipw_detach(device_t dev) struct ifnet *ifp = ic->ic_ifp; ipw_stop(sc); + callout_drain(&sc->sc_wdtimer); + ipw_put_firmware(sc); if (ifp != NULL) { bpfdetach(ifp); @@ -389,12 +425,10 @@ ipw_detach(device_t dev) if (ifp != NULL) if_free(ifp); - if (sc->sc_firmware != NULL) { - firmware_put(sc->sc_firmware, FIRMWARE_UNLOAD); - sc->sc_firmware = NULL; - } + taskqueue_free(sc->sc_tq); mtx_destroy(&sc->sc_mtx); + IPW_CMD_LOCK_DESTROY(sc); return 0; } @@ -692,6 +726,7 @@ ipw_shutdown(device_t dev) struct ipw_softc *sc = device_get_softc(dev); ipw_stop(sc); + ipw_put_firmware(sc); /* ??? XXX */ return 0; } @@ -711,18 +746,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 +768,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 +802,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,33 +812,13 @@ ipw_media_status(struct ifnet *ifp, stru 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; - case IEEE80211_M_IBSS: + if (ic->ic_opmode == IEEE80211_M_IBSS) imr->ifm_active |= IFM_IEEE80211_IBSS; - break; - - case IEEE80211_M_MONITOR: + else if (ic->ic_opmode == IEEE80211_M_MONITOR) imr->ifm_active |= IFM_IEEE80211_MONITOR; - break; - - case IEEE80211_M_AHDEMO: - case IEEE80211_M_HOSTAP: - case IEEE80211_M_WDS: - /* should not get there */ - break; - } -#undef N } static int @@ -811,99 +826,55 @@ ipw_newstate(struct ieee80211com *ic, en { struct ifnet *ifp = ic->ic_ifp; struct ipw_softc *sc = ifp->if_softc; - uint8_t macaddr[IEEE80211_ADDR_LEN]; - uint32_t len; - 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; + DPRINTF(("%s: %s -> %s flags 0x%x\n", __func__, + ieee80211_state_name[ic->ic_state], + ieee80211_state_name[nstate], sc->flags)); - ieee80211_ref_node(ni); - ieee80211_sta_join(ic, ni); - ieee80211_node_authorize(ni); + switch (nstate) { + case IEEE80211_S_AUTH: + ipw_assoc(ic); + break; - if (ic->ic_opmode == IEEE80211_M_STA) - ieee80211_notify_node_join(ic, ni, 1); -#endif + case IEEE80211_S_RUN: + 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) + ipw_assoc(ic); + } + /* XXX way wrong */ + return sc->sc_newstate(ic, nstate, + IEEE80211_FC0_SUBTYPE_ASSOC_RESP); break; - case IEEE80211_S_INIT: - case IEEE80211_S_SCAN: - case IEEE80211_S_AUTH: case IEEE80211_S_ASSOC: break; - } - - ic->ic_state = nstate; - - return 0; -} - -/* - * Read 16 bits at address 'addr' from the serial EEPROM. - */ -static uint16_t -ipw_read_prom_word(struct ipw_softc *sc, uint8_t addr) -{ - uint32_t tmp; - uint16_t val; - int n; - - /* clock C once before the first command */ - IPW_EEPROM_CTL(sc, 0); - IPW_EEPROM_CTL(sc, IPW_EEPROM_S); - IPW_EEPROM_CTL(sc, IPW_EEPROM_S | IPW_EEPROM_C); - IPW_EEPROM_CTL(sc, IPW_EEPROM_S); - - /* write start bit (1) */ - IPW_EEPROM_CTL(sc, IPW_EEPROM_S | IPW_EEPROM_D); - IPW_EEPROM_CTL(sc, IPW_EEPROM_S | IPW_EEPROM_D | IPW_EEPROM_C); - - /* write READ opcode (10) */ - IPW_EEPROM_CTL(sc, IPW_EEPROM_S | IPW_EEPROM_D); - IPW_EEPROM_CTL(sc, IPW_EEPROM_S | IPW_EEPROM_D | IPW_EEPROM_C); - IPW_EEPROM_CTL(sc, IPW_EEPROM_S); - IPW_EEPROM_CTL(sc, IPW_EEPROM_S | IPW_EEPROM_C); - - /* write address A7-A0 */ - for (n = 7; n >= 0; n--) { - IPW_EEPROM_CTL(sc, IPW_EEPROM_S | - (((addr >> n) & 1) << IPW_EEPROM_SHIFT_D)); - IPW_EEPROM_CTL(sc, IPW_EEPROM_S | - (((addr >> n) & 1) << IPW_EEPROM_SHIFT_D) | IPW_EEPROM_C); - } - IPW_EEPROM_CTL(sc, IPW_EEPROM_S); + case IEEE80211_S_INIT: + /* + * NB: don't try to do this if ipw_stop_master has + * shutdown the firmware and disabled interrupts. + */ + if (ic->ic_state == IEEE80211_S_RUN && + (sc->flags & IPW_FLAG_FW_INITED)) + ipw_disassoc(ic); + break; - /* read data Q15-Q0 */ - val = 0; - for (n = 15; n >= 0; n--) { - IPW_EEPROM_CTL(sc, IPW_EEPROM_S | IPW_EEPROM_C); - IPW_EEPROM_CTL(sc, IPW_EEPROM_S); - tmp = MEM_READ_4(sc, IPW_MEM_EEPROM_CTL); - val |= ((tmp & IPW_EEPROM_Q) >> IPW_EEPROM_SHIFT_Q) << n; + default: + break; } - - IPW_EEPROM_CTL(sc, 0); - - /* clear Chip Select and clock C */ - IPW_EEPROM_CTL(sc, IPW_EEPROM_S); - IPW_EEPROM_CTL(sc, 0); - IPW_EEPROM_CTL(sc, IPW_EEPROM_C); - - return le16toh(val); + return sc->sc_newstate(ic, nstate, arg); } 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; @@ -911,16 +882,18 @@ ipw_command_intr(struct ipw_softc *sc, s cmd = mtod(sbuf->m, struct ipw_cmd *); - DPRINTFN(2, ("cmd ack'ed (%u, %u, %u, %u, %u)\n", le32toh(cmd->type), - le32toh(cmd->subtype), le32toh(cmd->seq), le32toh(cmd->len), - le32toh(cmd->status))); + DPRINTFN(9, ("%s(%s subtype %u seq %u len %u status %u)\n", + __func__, ipw_cmdname(le32toh(cmd->type)), le32toh(cmd->subtype), + le32toh(cmd->seq), le32toh(cmd->len), le32toh(cmd->status))); - wakeup(sc); + sc->flags &= ~IPW_FLAG_BUSY; + wakeup_one(&sc->cmd); } 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; @@ -928,44 +901,109 @@ ipw_newstate_intr(struct ipw_softc *sc, state = le32toh(*mtod(sbuf->m, uint32_t *)); - DPRINTFN(2, ("entering state %u\n", state)); - switch (state) { + case IPW_STATE_INITIALIZED: + case IPW_STATE_DISABLED: + break; + 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; + IPW_STATE_END(sc, IPW_FW_ASSOCIATING); + /* 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); + 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); - ic->ic_flags |= IEEE80211_F_SCAN; + sc->sc_state_timer = 3; /* fw is still alive */ break; case IPW_STATE_SCAN_COMPLETE: - ieee80211_notify_scan_done(ic); - ic->ic_flags &= ~IEEE80211_F_SCAN; + DPRINTFN(3, ("Scan complete (%s flags 0x%x)\n", + IEEESTATE(ic), sc->flags)); + /* + * 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. This works ok + * because the adapter scans only 2.4G channels so + * doing an extra pass doesn't take long. + if (sc->flags & IPW_FLAG_HACK) { + sc->flags &= ~IPW_FLAG_HACK; + break; + } + */ + + /* Only update the scan module if we were actaully scanning */ + if (sc->fw_state == IPW_FW_SCANNING) { + sc->flags &= ~IPW_FLAG_SCANNING; + IPW_STATE_END(sc, IPW_FW_SCANNING); + ieee80211_scan_done(ic); + } 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; + taskqueue_enqueue(sc->sc_tq, &sc->sc_assoclosttask); break; - case IPW_STATE_RADIO_DISABLED: - ic->ic_ifp->if_flags &= ~IFF_UP; - ipw_stop(sc); + case IPW_STATE_RADIO_OFF: + DPRINTFN(2, ("Radio off (%s flags 0x%x)\n", + IEEESTATE(ic), sc->flags)); + taskqueue_enqueue(sc->sc_tq, &sc->sc_radiofftask); + break; + default: + DPRINTFN(2, ("%s: 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); } /* * XXX: Hack to set the current channel to the value advertised in beacons or * probe responses. Only used during AP detection. + * XXX value comes from DSPARMS ie which is wrong for off-channel rx's */ 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; @@ -986,20 +1024,25 @@ ipw_fix_channel(struct ieee80211com *ic, frm += 12; /* skip tstamp, bintval and capinfo fields */ while (frm < efrm) { - if (*frm == IEEE80211_ELEMID_DSPARMS) + if (*frm == IEEE80211_ELEMID_DSPARMS #if IEEE80211_CHAN_MAX < 255 - if (frm[2] <= IEEE80211_CHAN_MAX) + && frm[2] <= IEEE80211_CHAN_MAX #endif - ic->ic_bsschan = ieee80211_find_channel(ic, + ) { + 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; @@ -1008,15 +1051,25 @@ ipw_data_intr(struct ipw_softc *sc, stru struct ieee80211_frame *wh; struct ieee80211_node *ni; bus_addr_t physaddr; - int error; + int error, framelen; + IPW_LOCK_DECL; + + framelen = le32toh(status->len); + if (framelen < IEEE80211_MIN_LEN || framelen > MCLBYTES) { + /* + * XXX >MCLBYTES is bogus as it means the h/w dma'd + * out of bounds; need to figure out how to limit + * frame size in the firmware + */ + /* XXX stat */ + DPRINTF(("drop rx frame len=%u rssi=%u\n", + framelen, status->rssi)); + return; + } DPRINTFN(5, ("received frame len=%u, rssi=%u\n", le32toh(status->len), status->rssi)); - if (le32toh(status->len) < sizeof (struct ieee80211_frame_min) || - le32toh(status->len) > MCLBYTES) - return; - /* * Try to allocate a new mbuf for this ring element and load it before * processing the current mbuf. If the ring element cannot be loaded, @@ -1062,22 +1115,21 @@ ipw_data_intr(struct ipw_softc *sc, stru m->m_pkthdr.rcvif = ifp; m->m_pkthdr.len = m->m_len = le32toh(status->len); - if (bpf_peers_present(sc->sc_drvbpf)) { + /* XXX */ + if (sc->flags & IPW_FLAG_SCANNING) + ipw_fix_channel(sc, m); + + if (sc->sc_drvbpf != NULL) { struct ipw_rx_radiotap_header *tap = &sc->sc_rxtap; tap->wr_flags = 0; - tap->wr_antsignal = status->rssi; - tap->wr_chan_freq = htole16(ic->ic_curchan->ic_freq); - tap->wr_chan_flags = htole16(ic->ic_curchan->ic_flags); + tap->wr_antsignal = status->rssi + IPW_RSSI_TO_DBM; bpf_mtap2(sc->sc_drvbpf, tap, sc->sc_rxtap_len, m); } - if (ic->ic_state == IEEE80211_S_SCAN) - ipw_fix_channel(ic, 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 */ @@ -1085,7 +1137,7 @@ ipw_data_intr(struct ipw_softc *sc, stru /* 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); } @@ -1093,6 +1145,7 @@ ipw_data_intr(struct ipw_softc *sc, stru 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; @@ -1112,31 +1165,33 @@ 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)); + break; } - - /* firmware was killed, stop processing received frames */ - if (!(sc->flags & IPW_FLAG_FW_INITED)) - return; - sbd->bd->flags = 0; } @@ -1174,12 +1229,8 @@ ipw_release_sbd(struct ipw_softc *sc, st bus_dmamap_unload(sc->txbuf_dmat, sbuf->map); SLIST_INSERT_HEAD(&sc->free_sbuf, sbuf, next); - if (sbuf->m->m_flags & M_TXCB) - ieee80211_process_callback(sbuf->ni, sbuf->m, 0/*XXX*/); m_freem(sbuf->m); ieee80211_free_node(sbuf->ni); - - sc->sc_tx_timer = 0; break; } @@ -1211,8 +1262,10 @@ ipw_tx_intr(struct ipw_softc *sc) /* remember what the firmware has processed */ sc->txold = (r == 0) ? IPW_NTBD - 1 : r - 1; + sc->sc_tx_timer = 0; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; - ipw_start(ifp); + + ipw_start_locked(ifp); } static void @@ -1222,36 +1275,39 @@ ipw_intr(void *arg) uint32_t r; mtx_lock(&sc->sc_mtx); - - if ((r = CSR_READ_4(sc, IPW_CSR_INTR)) == 0 || r == 0xffffffff) { - mtx_unlock(&sc->sc_mtx); - return; - } - - /* disable interrupts */ - CSR_WRITE_4(sc, IPW_CSR_INTR_MASK, 0); - - /* acknowledge all interrupts */ - CSR_WRITE_4(sc, IPW_CSR_INTR, r); - - if (r & (IPW_INTR_FATAL_ERROR | IPW_INTR_PARITY_ERROR)) { - device_printf(sc->sc_dev, "firmware error\n"); - taskqueue_enqueue_fast(taskqueue_fast, &sc->sc_init_task); - r = 0; /* don't process more interrupts */ + r = CSR_READ_4(sc, IPW_CSR_INTR); + if (r != 0 && r != 0xffffffff) { + /* acknowledge all interrupts */ + CSR_WRITE_4(sc, IPW_CSR_INTR, r); + + DPRINTFN(9, ("%s: 0x%x\n", __func__, r)); + + if (r & IPW_INTR_FATAL_ERROR) { + device_printf(sc->sc_dev, "firmware error\n"); + taskqueue_enqueue(sc->sc_tq, &sc->sc_restarttask); + r = 0; /* NB: don't do anything else */ + } + if (r & IPW_INTR_FW_INIT_DONE) { + wakeup_one(sc); + r &= ~IPW_INTR_FW_INIT_DONE; + } + if (r & IPW_INTR_RX_TRANSFER) { + ipw_rx_intr(sc); + r &= ~IPW_INTR_RX_TRANSFER; + } + if (r & IPW_INTR_TX_TRANSFER) { + ipw_tx_intr(sc); + r &= ~IPW_INTR_TX_TRANSFER; + } + if (r & IPW_INTR_PARITY_ERROR) { + /* XXX rate limit */ + device_printf(sc->sc_dev, "parity error\n"); + r &= ~IPW_INTR_PARITY_ERROR; + } + if (r != 0) + device_printf(sc->sc_dev, + "%s: 0x%x unserviced\n", __func__, r); } - - if (r & IPW_INTR_FW_INIT_DONE) - wakeup(sc); - - if (r & IPW_INTR_RX_TRANSFER) - ipw_rx_intr(sc); - - if (r & IPW_INTR_TX_TRANSFER) - ipw_tx_intr(sc); - - /* re-enable interrupts */ - CSR_WRITE_4(sc, IPW_CSR_INTR_MASK, IPW_INTR_MASK); - mtx_unlock(&sc->sc_mtx); } @@ -1266,26 +1322,87 @@ ipw_dma_map_addr(void *arg, bus_dma_segm *(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_DISASSOCIATE, "DISASSOCIATE" }, + { 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_LONG_RETRY, "SET_LONG_RETRY" }, + { 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_DWELL_TIME, "SET_SCAN_DWELL_TIME" }, + { IPW_CMD_SET_SCAN_OPTIONS, "SET_SCAN_OPTIONS" }, + { IPW_CMD_SET_SECURITY_INFO, "SET_SECURITY_INFO" }, + { IPW_CMD_SET_SHORT_RETRY, "SET_SHORT_RETRY" }, + { 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. */ static int -ipw_cmd(struct ipw_softc *sc, uint32_t type, void *data, uint32_t len) +ipw_cmd(struct ipw_softc *sc, uint32_t cmd, const void *data, uint32_t len) { struct ipw_soft_bd *sbd; 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(cmd)); + 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"); + device_printf(sc->sc_dev, + "%s: %s not sent, could not map memory (error %u)\n", + __func__, ipw_cmdname(cmd), error); return error; } - sc->cmd.type = htole32(type); + sc->cmd.type = htole32(cmd); sc->cmd.subtype = 0; sc->cmd.len = htole32(len); sc->cmd.seq = 0; @@ -1301,7 +1418,8 @@ ipw_cmd(struct ipw_softc *sc, uint32_t t 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)); + DPRINTFN(4, ("sending %s(%u, %u, %u, %u)\n", + ipw_cmdname(cmd), cmd, 0, 0, len)); /* kick firmware */ sc->txfree--; @@ -1309,7 +1427,13 @@ ipw_cmd(struct ipw_softc *sc, uint32_t t 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->cmd, &sc->sc_mtx, 0, "ipwcmd", hz); + if (error != 0) { + device_printf(sc->sc_dev, "%s: %s failed, timeout (error %u)\n", + __func__, ipw_cmdname(cmd), error); + return error; + } + return 0; } static int @@ -1325,11 +1449,15 @@ ipw_tx_start(struct ifnet *ifp, struct m struct mbuf *mnew; bus_dma_segment_t segs[IPW_MAX_NSEG]; bus_addr_t physaddr; - int nsegs, error, i; + int nsegs, error, i, iswep, hdrlen, ismcast; wh = mtod(m0, struct ieee80211_frame *); + /* NB: only data frames use this path */ + iswep = wh->i_fc[1] & IEEE80211_FC1_WEP; + hdrlen = ieee80211_hdrsize(wh); + ismcast = IEEE80211_IS_MULTICAST(wh->i_addr1); - if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + if (iswep) { k = ieee80211_crypto_encap(ic, ni, m0); if (k == NULL) { m_freem(m0); @@ -1340,12 +1468,10 @@ ipw_tx_start(struct ifnet *ifp, struct m wh = mtod(m0, struct ieee80211_frame *); } - if (bpf_peers_present(sc->sc_drvbpf)) { + if (sc->sc_drvbpf != NULL) { struct ipw_tx_radiotap_header *tap = &sc->sc_txtap; tap->wt_flags = 0; - tap->wt_chan_freq = htole16(ic->ic_curchan->ic_freq); - tap->wt_chan_flags = htole16(ic->ic_curchan->ic_flags); bpf_mtap2(sc->sc_drvbpf, tap, sc->sc_txtap_len, m0); } @@ -1356,7 +1482,8 @@ ipw_tx_start(struct ifnet *ifp, struct m shdr->hdr.type = htole32(IPW_HDR_TYPE_SEND); shdr->hdr.subtype = 0; - shdr->hdr.encrypted = (wh->i_fc[1] & IEEE80211_FC1_WEP) ? 1 : 0; + shdr->hdr.encrypted = iswep ? 1 : 0; + /* NB: always do crypto in the host */ shdr->hdr.encrypt = 0; shdr->hdr.keyidx = 0; shdr->hdr.keysz = 0; @@ -1367,8 +1494,7 @@ ipw_tx_start(struct ifnet *ifp, struct m else IEEE80211_ADDR_COPY(shdr->hdr.dst_addr, wh->i_addr1); - /* trim IEEE802.11 header */ - m_adj(m0, sizeof (struct ieee80211_frame)); + m_adj(m0, hdrlen); /* strip IEEE802.11 header */ error = bus_dmamap_load_mbuf_sg(sc->txbuf_dmat, sbuf->map, m0, segs, &nsegs, 0); @@ -1419,7 +1545,7 @@ ipw_tx_start(struct ifnet *ifp, struct m sbd->bd->flags = IPW_BD_FLAG_TX_FRAME_802_3 | IPW_BD_FLAG_TX_NOT_LAST_FRAGMENT; - DPRINTFN(5, ("sending tx hdr (%u, %u, %u, %u, %6D, %6D)\n", + DPRINTFN(5, ("%s: tx hdr (%u, %u, %u, %u, %6D, %6D)\n", __func__, shdr->hdr.type, shdr->hdr.subtype, shdr->hdr.encrypted, shdr->hdr.encrypt, shdr->hdr.src_addr, ":", shdr->hdr.dst_addr, ":")); @@ -1446,7 +1572,8 @@ ipw_tx_start(struct ifnet *ifp, struct m sbd->bd->flags |= IPW_BD_FLAG_TX_NOT_LAST_FRAGMENT; } - DPRINTFN(5, ("sending fragment (%d, %d)\n", i, segs[i].ds_len)); + DPRINTFN(5, ("%s: fragment (%d, %d)\n", __func__, + i, segs[i].ds_len)); sc->txfree--; sc->txcur = (sc->txcur + 1) % IPW_NTBD; @@ -1463,7 +1590,7 @@ ipw_tx_start(struct ifnet *ifp, struct m } static void -ipw_start(struct ifnet *ifp) +ipw_start_locked(struct ifnet *ifp) { struct ipw_softc *sc = ifp->if_softc; struct ieee80211com *ic = &sc->sc_ic; @@ -1471,43 +1598,53 @@ ipw_start(struct ifnet *ifp) struct ether_header *eh; struct ieee80211_node *ni; - mtx_lock(&sc->sc_mtx); - - 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); - if (m0 == NULL) - break; + IF_DEQUEUE(&ic->ic_mgtq, m0); + if (m0 == NULL) { + IFQ_DRV_DEQUEUE(&ifp->if_snd, m0); + if (m0 == NULL) + break; + + if (sc->txfree < 1 + IPW_MAX_NSEG) { + IFQ_DRV_PREPEND(&ifp->if_snd, m0); + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + break; + } - if (sc->txfree < 1 + IPW_MAX_NSEG) { - IFQ_DRV_PREPEND(&ifp->if_snd, m0); - ifp->if_drv_flags |= IFF_DRV_OACTIVE; - break; - } + if (m0->m_len < sizeof (struct ether_header) && + (m0 = m_pullup(m0, sizeof (struct ether_header))) == NULL) + continue; - if (m0->m_len < sizeof (struct ether_header) && - (m0 = m_pullup(m0, sizeof (struct ether_header))) == NULL) - continue; + eh = mtod(m0, struct ether_header *); + ni = ieee80211_find_txnode(ic, eh->ether_dhost); + if (ni == NULL) { + m_freem(m0); + continue; + } + BPF_MTAP(ifp, m0); - eh = mtod(m0, struct ether_header *); - ni = ieee80211_find_txnode(ic, eh->ether_dhost); - if (ni == NULL) { + m0 = ieee80211_encap(ic, m0, ni); + if (m0 == NULL) { + ieee80211_free_node(ni); + continue; + } + } else { + ni = (struct ieee80211_node *) m0->m_pkthdr.rcvif; + m0->m_pkthdr.rcvif = NULL; + /* XXX no way to send mgt frames(yet), discard */ m_freem(m0); - continue; - } - BPF_MTAP(ifp, m0); - - m0 = ieee80211_encap(ic, m0, ni); - if (m0 == NULL) { ieee80211_free_node(ni); continue; } +#if __FreeBSD_version >= 700017 if (bpf_peers_present(ic->ic_rawbpf)) +#else + if (ic->ic_rawbpf != NULL) +#endif bpf_mtap(ic->ic_rawbpf, m0); if (ipw_tx_start(ifp, m0, ni) != 0) { @@ -1518,34 +1655,58 @@ 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_start(struct ifnet *ifp) { struct ipw_softc *sc = ifp->if_softc; + IPW_LOCK_DECL; - mtx_lock(&sc->sc_mtx); + IPW_LOCK(sc); + ipw_start_locked(ifp); + IPW_UNLOCK(sc); +} + +static void +ipw_watchdog(void *arg) +{ + struct ipw_softc *sc = arg; + 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) { if_printf(ifp, "device timeout\n"); ifp->if_oerrors++; - taskqueue_enqueue_fast(taskqueue_fast, - &sc->sc_init_task); - mtx_unlock(&sc->sc_mtx); - return; + taskqueue_enqueue(sc->sc_tq, &sc->sc_restarttask); } - ifp->if_timer = 1; } - - mtx_unlock(&sc->sc_mtx); + if (sc->sc_rfkill_timer > 0) { + if (--sc->sc_rfkill_timer == 0) { + /* + * Check for a change in rfkill state. We get an + * interrupt when a radio is disabled but not when + * it is enabled so we must poll for the latter. + */ + if (!ipw_getrfkill(sc)) + taskqueue_enqueue(sc->sc_tq, &sc->sc_radiontask); + else + sc->sc_rfkill_timer = 2; + } + } + if (sc->sc_state_timer > 0) { + if (--sc->sc_state_timer == 0) { + if (sc->flags & IPW_FLAG_SCANNING) { + if_printf(ifp, "scan stuck\n"); + taskqueue_enqueue(sc->sc_tq, &sc->sc_restarttask); + } + } + } + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + callout_reset(&sc->sc_wdtimer, hz, ipw_watchdog, sc); } static int @@ -1554,32 +1715,44 @@ ipw_ioctl(struct ifnet *ifp, u_long cmd, 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); + else { + /* + * If device was stopped due to rfkill then + * marked down we'll have the polling thread + * running; stop it explicitly. + */ + sc->sc_rfkill_timer = 0; + } + ipw_put_firmware(sc); } break; default: error = ieee80211_ioctl(ic, cmd, data); + break; } 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; } @@ -1605,7 +1778,7 @@ 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; + sc->flags &= ~(IPW_FLAG_FW_INITED | IPW_FLAG_BUSY | IPW_FLAG_ENABLED); } static int @@ -1640,103 +1813,174 @@ ipw_reset(struct ipw_softc *sc) return 0; } -/* - * Upload the microcode to the device. - */ static int -ipw_load_ucode(struct ipw_softc *sc, const char *uc, int size) +ipw_waitfordisable(struct ipw_softc *sc, int waitfor) { - int ntries; + int ms = hz < 1000 ? 1 : hz/10; + int i, error; - MEM_WRITE_4(sc, 0x3000e0, 0x80000000); - CSR_WRITE_4(sc, IPW_CSR_RST, 0); + 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; +} - MEM_WRITE_2(sc, 0x220000, 0x0703); - MEM_WRITE_2(sc, 0x220000, 0x0707); +static int +ipw_enable(struct ipw_softc *sc) +{ + int error; - MEM_WRITE_1(sc, 0x210014, 0x72); - MEM_WRITE_1(sc, 0x210014, 0x72); + 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; +} - MEM_WRITE_1(sc, 0x210000, 0x40); - MEM_WRITE_1(sc, 0x210000, 0x00); - MEM_WRITE_1(sc, 0x210000, 0x40); +static int +ipw_disable(struct ipw_softc *sc) +{ + int error; - MEM_WRITE_MULTI_1(sc, 0x210010, uc, size); + 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; +} - MEM_WRITE_1(sc, 0x210000, 0x00); - MEM_WRITE_1(sc, 0x210000, 0x00); - MEM_WRITE_1(sc, 0x210000, 0x80); +static int +ipw_setpowermode(struct ipw_softc *sc) +{ + struct ieee80211com *ic = &sc->sc_ic; + uint32_t data; - MEM_WRITE_2(sc, 0x220000, 0x0703); - MEM_WRITE_2(sc, 0x220000, 0x0707); + if (ic->ic_flags & IEEE80211_F_PMGTON) { + /* XXX set more fine-grained operation */ + data = htole32(IPW_POWER_MODE_AUTO); + } else + data = htole32(IPW_POWER_MODE_CAM); - MEM_WRITE_1(sc, 0x210014, 0x72); - MEM_WRITE_1(sc, 0x210014, 0x72); + DPRINTF(("Setting power mode to %u\n", le32toh(data))); + return ipw_cmd(sc, IPW_CMD_SET_POWER_MODE, &data, sizeof data); +} - MEM_WRITE_1(sc, 0x210000, 0x00); - MEM_WRITE_1(sc, 0x210000, 0x80); +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 (ntries = 0; ntries < 10; ntries++) { - if (MEM_READ_1(sc, 0x210000) & 1) - break; - DELAY(10); - } - if (ntries == 10) { - device_printf(sc->sc_dev, - "timeout waiting for ucode to initialize\n"); - return EIO; - } + for (i = 0; i < IEEE80211_WEP_NKID; i++) { + wk = &ic->ic_crypto.cs_nw_keys[i]; - MEM_WRITE_4(sc, 0x3000e0, 0); + 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; } -/* set of macros to handle unaligned little endian data in firmware image */ -#define GETLE32(p) ((p)[0] | (p)[1] << 8 | (p)[2] << 16 | (p)[3] << 24) -#define GETLE16(p) ((p)[0] | (p)[1] << 8) static int -ipw_load_firmware(struct ipw_softc *sc, const char *fw, int size) +ipw_setwpaie(struct ipw_softc *sc, const void *ie, int ielen) { - const uint8_t *p, *end; - uint32_t tmp, dst; - uint16_t len; - int error; - - p = fw; - end = fw + size; - while (p < end) { - dst = GETLE32(p); p += 4; - len = GETLE16(p); p += 2; - - ipw_write_mem_1(sc, dst, p, len); - p += len; - } - - CSR_WRITE_4(sc, IPW_CSR_IO, IPW_IO_GPIO1_ENABLE | IPW_IO_GPIO3_MASK | - IPW_IO_LED_OFF); + struct ipw_wpa_ie wpaie; - /* enable interrupts */ - CSR_WRITE_4(sc, IPW_CSR_INTR_MASK, IPW_INTR_MASK); + memset(&wpaie, 0, sizeof(wpaie)); + wpaie.ielen = 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)); +} - /* kick the firmware */ - CSR_WRITE_4(sc, IPW_CSR_RST, 0); +static int +ipw_setbssid(struct ipw_softc *sc, const uint8_t *bssid) +{ + static const uint8_t zerobssid[IEEE80211_ADDR_LEN]; - tmp = CSR_READ_4(sc, IPW_CSR_CTL); - CSR_WRITE_4(sc, IPW_CSR_CTL, tmp | IPW_CTL_ALLOW_STANDBY); + 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); + } +} - /* wait at most one second for firmware initialization to complete */ - if ((error = msleep(sc, &sc->sc_mtx, 0, "ipwinit", hz)) != 0) { - device_printf(sc->sc_dev, "timeout waiting for firmware " - "initialization to complete\n"); - return error; +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); } +} - tmp = CSR_READ_4(sc, IPW_CSR_IO); - CSR_WRITE_4(sc, IPW_CSR_IO, tmp | IPW_IO_GPIO1_MASK | - IPW_IO_GPIO3_MASK); +static int +ipw_setchannel(struct ipw_softc *sc, struct ieee80211_channel *chan) +{ + struct ieee80211com *ic = &sc->sc_ic; + uint32_t data; + int error; - return 0; + 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 @@ -1745,12 +1989,13 @@ 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: @@ -1771,21 +2016,23 @@ ipw_config(struct ipw_softc *sc) if (error != 0) return error; - 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); + if (ic->ic_opmode == IEEE80211_M_MONITOR) { + 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)); + /* + * Handle any link-level address change. Note that we only + * need to force ic_myaddr; any other addresses are handled + * as a byproduct of the ifnet code marking the interface + * down then up. + * + * XXX should get from lladdr instead of arpcom but that's more work + */ DPRINTF(("Setting MAC address to %6D\n", ic->ic_myaddr, ":")); error = ipw_cmd(sc, IPW_CMD_SET_MAC_ADDRESS, ic->ic_myaddr, IEEE80211_ADDR_LEN); @@ -1798,6 +2045,7 @@ ipw_config(struct ipw_softc *sc) config.flags |= htole32(IPW_CFG_IBSS_AUTO_START); if (ifp->if_flags & IFF_PROMISC) config.flags |= htole32(IPW_CFG_PROMISCUOUS); + /* XXX honor channel list */ config.bss_chan = htole32(0x3fff); /* channels 1-14 */ config.ibss_chan = htole32(0x7ff); /* channels 1-11 */ DPRINTF(("Setting configuration to 0x%x\n", le32toh(config.flags))); @@ -1805,6 +2053,7 @@ ipw_config(struct ipw_softc *sc) if (error != 0) return error; + /* XXX fixed rate? */ data = htole32(0x3); /* 1, 2 */ DPRINTF(("Setting basic tx rates to 0x%x\n", le32toh(data))); error = ipw_cmd(sc, IPW_CMD_SET_BASIC_TX_RATES, &data, sizeof data); @@ -1817,13 +2066,24 @@ ipw_config(struct ipw_softc *sc) if (error != 0) return error; - data = htole32(IPW_POWER_MODE_CAM); - DPRINTF(("Setting power mode to %u\n", le32toh(data))); - error = ipw_cmd(sc, IPW_CMD_SET_POWER_MODE, &data, sizeof data); + /* 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; + + error = ipw_setpowermode(sc); if (error != 0) return error; if (ic->ic_opmode == IEEE80211_M_IBSS) { + data = htole32(ic->ic_bintval); + DPRINTF(("Setting beacon interval to %u\n", le32toh(data))); + error = ipw_cmd(sc, IPW_CMD_SET_BEACON_INTERVAL, &data, + sizeof data); + if (error != 0) + return error; + data = htole32(32); /* default value */ DPRINTF(("Setting tx power index to %u\n", le32toh(data))); error = ipw_cmd(sc, IPW_CMD_SET_TX_POWER_INDEX, &data, @@ -1844,22 +2104,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_setbssid(sc, NULL); 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_setssid(sc, ic->ic_des_ssid[0].ssid, ic->ic_des_ssid[0].len); if (error != 0) return error; @@ -1877,35 +2126,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); @@ -1914,143 +2153,219 @@ ipw_config(struct ipw_softc *sc) if (error != 0) return error; -#if 0 - struct ipw_wpa_ie ie; + return ipw_enable(sc); +} - 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); +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.chanmask = htole32(chanmask); + opts.flags = htole32(flags); + return ipw_cmd(sc, IPW_CMD_SET_SCAN_OPTIONS, &opts, sizeof(opts)); +} + +static int +ipw_scan(struct ipw_softc *sc) +{ + uint32_t params; + int error; + + DPRINTF(("%s: flags 0x%x\n", __func__, sc->flags)); + IPW_STATE_BEGIN(sc, IPW_FW_SCANNING); + 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, sc->chanmask, IPW_SCAN_DO_NOT_ASSOCIATE); if (error != 0) - return error; -#endif + 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; - if (ic->ic_opmode == IEEE80211_M_IBSS) { - data = htole32(ic->ic_bintval); - DPRINTF(("Setting beacon interval to %u\n", le32toh(data))); - error = ipw_cmd(sc, IPW_CMD_SET_BEACON_INTERVAL, &data, - sizeof data); + /* + * NB: the adapter may be disabled on association lost; + * if so just re-enable it to kick off scanning. + */ + 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) { + sc->flags &= ~(IPW_FLAG_SCANNING | IPW_FLAG_HACK); + IPW_STATE_END(sc, IPW_FW_SCANNING); + } + return (error); +} + +static void +ipw_assoc_lost(void *arg, int npending) +{ + struct ipw_softc *sc = arg; + struct ieee80211com *ic = &sc->sc_ic; + IPW_LOCK_DECL; + + DPRINTF(("%s: flags 0x%x\n", __func__, sc->flags)); + IPW_LOCK(sc); + (void) ipw_disable(sc); /* XXX stop fw from re-associating */ + if (ic->ic_state == IEEE80211_S_RUN) + ieee80211_new_state(ic, IEEE80211_S_SCAN, -1); + 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; + + IPW_LOCK_ASSERT(sc); + IPW_STATE_BEGIN(sc, IPW_FW_ASSOCIATING); + + error = ipw_disable(sc); + if (error != 0) + goto done; + + 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) + goto done; + + if (ic->ic_flags & IEEE80211_F_PRIVACY) { + error = ipw_setwepkeys(sc); if (error != 0) - return error; + goto done; + + 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) + goto done; + } } - 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_setssid(sc, ni->ni_essid, ni->ni_esslen); if (error != 0) - return error; + goto done; + + error = ipw_setbssid(sc, ni->ni_bssid); + if (error != 0) + goto done; + + 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) + goto done; + + if (ic->ic_opt_ie != NULL) { + error = ipw_setwpaie(sc, ic->ic_opt_ie, ic->ic_opt_ie_len); + if (error != 0) + goto done; + } + + if (ic->ic_opmode == IEEE80211_M_IBSS) { + error = ipw_setchannel(sc, ni->ni_chan); + if (error != 0) + goto done; + } + + /* lock scan to ap's channel and enable associate */ + error = ipw_setscanopts(sc, + 1<<(ieee80211_chan2ieee(ic, ni->ni_chan)-1), 0); +done: + if (error != 0) { + IPW_STATE_END(sc, IPW_FW_ASSOCIATING); + 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); /* finally, enable adapter */ } -/* - * Handler for sc_init_task. This is a simple wrapper around ipw_init(). - * It is called on firmware panics or on watchdog timeouts. - */ -static void -ipw_init_task(void *context, int pending) +static int +ipw_disassociate(struct ipw_softc *sc) { - ipw_init(context); + struct ieee80211com *ic = &sc->sc_ic; + const struct ieee80211_node *ni = ic->ic_bss; + + DPRINTF(("Disassociate from %6D\n", ni->ni_bssid, ":")); + /* + * 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); } 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); + 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->fw_state == IPW_FW_LOADING) 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"); - goto fail1; - } - - switch (ic->ic_opmode) { - case IEEE80211_M_STA: - imagename = "ipw_bss"; - break; - case IEEE80211_M_IBSS: - imagename = "ipw_ibss"; - break; - case IEEE80211_M_MONITOR: - imagename = "ipw_monitor"; - break; - default: - imagename = NULL; /* should not get there */ + goto fail; } - /* - * Load firmware image using the firmware(9) subsystem. We need to - * release the driver's lock first. - */ - if (sc->sc_firmware == NULL || strcmp(sc->sc_firmware->name, - imagename) != 0) { - mtx_unlock(&sc->sc_mtx); - if (sc->sc_firmware != NULL) - firmware_put(sc->sc_firmware, FIRMWARE_UNLOAD); - sc->sc_firmware = firmware_get(imagename); - mtx_lock(&sc->sc_mtx); - } + IPW_STATE_BEGIN(sc, IPW_FW_LOADING); - if (sc->sc_firmware == NULL) { - device_printf(sc->sc_dev, - "could not load firmware image '%s'\n", imagename); - goto fail1; + IPW_UNLOCK(sc); + /* NB: cannot hold lock while loading firmware */ + if (!ipw_get_firmware(sc)) { + IPW_LOCK(sc); + goto fail; } + IPW_LOCK(sc); - fp = sc->sc_firmware; - if (fp->datasize < sizeof *hdr) { - device_printf(sc->sc_dev, - "firmware image too short %zu\n", fp->datasize); - goto fail2; + if (ipw_load_ucode(sc, &sc->fw_ucode) != 0) { + device_printf(sc->sc_dev, "could not load microcode\n"); + goto fail; } - hdr = (const struct ipw_firmware_hdr *)fp->data; - - if (fp->datasize < sizeof *hdr + le32toh(hdr->mainsz) + - le32toh(hdr->ucodesz)) { - device_printf(sc->sc_dev, - "firmware image too short %zu\n", fp->datasize); - goto fail2; - } - - 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"); - goto fail2; - } - - ipw_stop_master(sc); + ipw_stop_master(sc); /* * Setup tx, rx and status rings. @@ -2072,12 +2387,10 @@ ipw_init(void *priv) CSR_WRITE_4(sc, IPW_CSR_STATUS_BASE, sc->status_phys); - fw = (const char *)fp->data + sizeof *hdr; - if (ipw_load_firmware(sc, fw, le32toh(hdr->mainsz)) != 0) { + if (ipw_load_firmware(sc, &sc->fw_fw) != 0) { device_printf(sc->sc_dev, "could not load firmware\n"); - goto fail2; + goto fail; } - sc->flags |= IPW_FLAG_FW_INITED; /* retrieve information tables base addresses */ @@ -2088,40 +2401,46 @@ ipw_init(void *priv) if (ipw_config(sc) != 0) { device_printf(sc->sc_dev, "device configuration failed\n"); - goto fail1; + goto fail; } + 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); - + IPW_STATE_END(sc, IPW_FW_LOADING); return; -fail2: firmware_put(fp, FIRMWARE_UNLOAD); - sc->sc_firmware = NULL; -fail1: ifp->if_flags &= ~IFF_UP; - ipw_stop(sc); - sc->flags &=~ IPW_FLAG_INIT_LOCKED; - if (!owned) - mtx_unlock(&sc->sc_mtx); +fail: + ifp->if_flags &= ~IFF_UP; /* XXX */ + IPW_STATE_END(sc, IPW_FW_LOADING); + ipw_stop_locked(sc); + ipw_put_firmware(sc); } static void -ipw_stop(void *priv) +ipw_stop_locked(struct ipw_softc *sc) { - struct ipw_softc *sc = priv; struct ieee80211com *ic = &sc->sc_ic; struct ifnet *ifp = ic->ic_ifp; int i; - mtx_lock(&sc->sc_mtx); - - ieee80211_new_state(ic, IEEE80211_S_INIT, -1); + DPRINTF(("%s: state %s flags 0x%x\n", __func__, + ieee80211_state_name[ic->ic_state], sc->flags)); + callout_stop(&sc->sc_wdtimer); ipw_stop_master(sc); CSR_WRITE_4(sc, IPW_CSR_RST, IPW_RST_SW_RESET); @@ -2132,43 +2451,235 @@ ipw_stop(void *priv) for (i = 0; i < IPW_NTBD; i++) 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); + sc->sc_tx_timer = 0; + sc->sc_rfkill_timer = 0; + sc->sc_state_timer = 0; + sc->flags &= ~(IPW_FLAG_SCANNING | IPW_FLAG_ASSOCIATED); + sc->fw_state = IPW_FW_IDLE; + + ieee80211_new_state(ic, IEEE80211_S_INIT, -1); +} + +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_restart(void *arg, int pending) +{ + struct ipw_softc *sc = arg; + IPW_LOCK_DECL; + + IPW_LOCK(sc); + ipw_init_locked(sc, 1); /* NB: force state machine */ + IPW_UNLOCK(sc); +} + +static void +ipw_getfw(struct ipw_fw *fw, const char *fwname) +{ + if (fw->fp == NULL) + fw->fp = firmware_get(fwname); } static int -ipw_sysctl_stats(SYSCTL_HANDLER_ARGS) +ipw_get_firmware(struct ipw_softc *sc) { - struct ipw_softc *sc = arg1; - uint32_t i, size, buf[256]; + struct ieee80211com *ic = &sc->sc_ic; + const struct ipw_firmware_hdr *hdr; + const struct firmware *fp; - if (!(sc->flags & IPW_FLAG_FW_INITED)) { - memset(buf, 0, sizeof buf); - return SYSCTL_OUT(req, buf, sizeof buf); + /* invalidate cached firmware on mode change */ + if (sc->fw_mode != ic->ic_opmode) + ipw_put_firmware(sc); + + switch (ic->ic_opmode) { + case IEEE80211_M_STA: + ipw_getfw(&sc->fw_ucode, "ipw_bss"); + break; + case IEEE80211_M_IBSS: + ipw_getfw(&sc->fw_ucode, "ipw_ibss"); + break; + case IEEE80211_M_MONITOR: + ipw_getfw(&sc->fw_ucode, "ipw_monitor"); + break; + default: + break; + } + fp = sc->fw_ucode.fp; + if (fp == NULL) { + device_printf(sc->sc_dev, "could not load firmware\n"); + goto bad; } - CSR_WRITE_4(sc, IPW_CSR_AUTOINC_ADDR, sc->table1_base); + if (fp->datasize < sizeof *hdr) { + device_printf(sc->sc_dev, "firmware image %s too short %zu\n", + fp->name, fp->datasize); + goto bad; + } - size = min(CSR_READ_4(sc, IPW_CSR_AUTOINC_DATA), 256); - for (i = 1; i < size; i++) - buf[i] = MEM_READ_4(sc, CSR_READ_4(sc, IPW_CSR_AUTOINC_DATA)); + hdr = (const struct ipw_firmware_hdr *)fp->data; - return SYSCTL_OUT(req, buf, sizeof buf); + if (fp->datasize < sizeof *hdr + le32toh(hdr->mainsz) + + le32toh(hdr->ucodesz)) { + device_printf(sc->sc_dev, "firmware image %s too short %zu\n", + fp->name, fp->datasize); + goto bad; + } + + sc->fw_fw.fp = fp; /* copy for consistenncy */ + sc->fw_fw.data = (const char *)fp->data + sizeof *hdr; + sc->fw_fw.size = le32toh(hdr->mainsz); + + sc->fw_ucode.data = sc->fw_fw.data + sc->fw_fw.size; + sc->fw_ucode.size = le32toh(hdr->ucodesz); + + sc->fw_mode = ic->ic_opmode; + return 1; +bad: + ipw_put_firmware(sc); + return 0; } +/* + * Release any cached firmware images. + */ +static void +ipw_put_firmware(struct ipw_softc *sc) +{ + struct ipw_fw *fw = &sc->fw_ucode; + + if (fw->fp != NULL) { + firmware_put(fw->fp, FIRMWARE_UNLOAD); + fw->fp = NULL; + } + fw->data = NULL; + fw->size = 0; + + fw = &sc->fw_fw; + fw->fp = NULL; /* NB: no need to put, copy of fw_ucode */ + fw->data = NULL; + fw->size = 0; +} + +/* + * Upload the microcode to the device. + */ static int -ipw_sysctl_radio(SYSCTL_HANDLER_ARGS) +ipw_load_ucode(struct ipw_softc *sc, const struct ipw_fw *fw) { - struct ipw_softc *sc = arg1; - int val; + const char *uc = fw->data; + int size = fw->size; + int ntries; - val = !((sc->flags & IPW_FLAG_HAS_RADIO_SWITCH) && - (CSR_READ_4(sc, IPW_CSR_IO) & IPW_IO_RADIO_DISABLED)); + MEM_WRITE_4(sc, 0x3000e0, 0x80000000); + CSR_WRITE_4(sc, IPW_CSR_RST, 0); - return SYSCTL_OUT(req, &val, sizeof val); + MEM_WRITE_2(sc, 0x220000, 0x0703); + MEM_WRITE_2(sc, 0x220000, 0x0707); + + MEM_WRITE_1(sc, 0x210014, 0x72); + MEM_WRITE_1(sc, 0x210014, 0x72); + + MEM_WRITE_1(sc, 0x210000, 0x40); + MEM_WRITE_1(sc, 0x210000, 0x00); + MEM_WRITE_1(sc, 0x210000, 0x40); + + MEM_WRITE_MULTI_1(sc, 0x210010, uc, size); + + MEM_WRITE_1(sc, 0x210000, 0x00); + MEM_WRITE_1(sc, 0x210000, 0x00); + MEM_WRITE_1(sc, 0x210000, 0x80); + + MEM_WRITE_2(sc, 0x220000, 0x0703); + MEM_WRITE_2(sc, 0x220000, 0x0707); + + MEM_WRITE_1(sc, 0x210014, 0x72); + MEM_WRITE_1(sc, 0x210014, 0x72); + + MEM_WRITE_1(sc, 0x210000, 0x00); + MEM_WRITE_1(sc, 0x210000, 0x80); + + for (ntries = 0; ntries < 10; ntries++) { + if (MEM_READ_1(sc, 0x210000) & 1) + break; + DELAY(10); + } + if (ntries == 10) { + device_printf(sc->sc_dev, + "timeout waiting for %s ucode to initialize\n", + fw->fp->name); + return EIO; + } + + MEM_WRITE_4(sc, 0x3000e0, 0); + + return 0; +} + +/* unalligned little endian access */ +#define LE_READ_2(p) \ + ((u_int16_t) \ + ((((const u_int8_t *)(p))[0] ) | \ + (((const u_int8_t *)(p))[1] << 8))) +#define LE_READ_4(p) \ + ((u_int32_t) \ + ((((const u_int8_t *)(p))[0] ) | \ + (((const u_int8_t *)(p))[1] << 8) | \ + (((const u_int8_t *)(p))[2] << 16) | \ + (((const u_int8_t *)(p))[3] << 24))) + +static int +ipw_load_firmware(struct ipw_softc *sc, const struct ipw_fw *fw) +{ + const uint8_t *p, *end; + uint32_t tmp, dst; + uint16_t len; + int error; + + p = fw->data; + end = fw->data + fw->size; + while (p < end) { + dst = LE_READ_4(p); p += 4; + len = LE_READ_2(p); p += 2; + + ipw_write_mem_1(sc, dst, p, len); + p += len; + } + + CSR_WRITE_4(sc, IPW_CSR_IO, IPW_IO_GPIO1_ENABLE | IPW_IO_GPIO3_MASK | + IPW_IO_LED_OFF); + + /* enable interrupts */ + CSR_WRITE_4(sc, IPW_CSR_INTR_MASK, IPW_INTR_MASK); + + /* kick the firmware */ + CSR_WRITE_4(sc, IPW_CSR_RST, 0); + + tmp = CSR_READ_4(sc, IPW_CSR_CTL); + CSR_WRITE_4(sc, IPW_CSR_CTL, tmp | IPW_CTL_ALLOW_STANDBY); + + /* wait at most one second for firmware initialization to complete */ + if ((error = msleep(sc, &sc->sc_mtx, 0, "ipwinit", hz)) != 0) { + device_printf(sc->sc_dev, "timeout waiting for %s firmware " + "initialization to complete\n", fw->fp->name); + return error; + } + + tmp = CSR_READ_4(sc, IPW_CSR_IO); + CSR_WRITE_4(sc, IPW_CSR_IO, tmp | IPW_IO_GPIO1_MASK | + IPW_IO_GPIO3_MASK); + + return 0; } static uint32_t @@ -2183,6 +2694,7 @@ ipw_write_table1(struct ipw_softc *sc, u 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) { @@ -2218,6 +2730,7 @@ ipw_read_mem_1(struct ipw_softc *sc, bus *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, @@ -2228,3 +2741,278 @@ ipw_write_mem_1(struct ipw_softc *sc, bu CSR_WRITE_1(sc, IPW_CSR_INDIRECT_DATA + (offset & 3), *datap); } } + +static void +ipw_ops(void *arg, int npending) +{ + struct ipw_softc *sc = arg; + struct ieee80211com *ic = &sc->sc_ic; + IPW_LOCK_DECL; + int cmd; + +again: + IPW_CMD_LOCK(sc); + cmd = sc->sc_cmd[sc->sc_cmd_cur]; + if (cmd == 0) { + /* No more commands to process */ + IPW_CMD_UNLOCK(sc); + return; + } + sc->sc_cmd[sc->sc_cmd_cur] = 0; /* free the slot */ + sc->sc_cmd_cur = (sc->sc_cmd_cur + 1) % IPW_CMD_MAXOPS; + IPW_CMD_UNLOCK(sc); + + IPW_LOCK(sc); + while (sc->fw_state != IPW_FW_IDLE || (sc->flags & IPW_FLAG_BUSY)) { + msleep(sc, &sc->sc_mtx, 0, "ipwcmd", hz/10); + } + + if (!(sc->sc_ifp->if_drv_flags & IFF_DRV_RUNNING)) { + IPW_UNLOCK(sc); + return; + } + + switch (cmd) { + case IPW_ASSOC: + ipw_auth_and_assoc(sc); + break; + case IPW_DISASSOC: + ipw_disassociate(sc); + break; + case IPW_SCAN_START: + if (ipw_scan(sc) != 0) { + /* XXX should not happen */ + ieee80211_new_state(ic, IEEE80211_S_INIT, 0); + } + break; + } + IPW_UNLOCK(sc); + + /* Take another pass */ + goto again; +} + +static int +ipw_queue_cmd(struct ipw_softc *sc, int cmd) +{ + IPW_CMD_LOCK(sc); + if (sc->sc_cmd[sc->sc_cmd_next] != 0) { + IPW_CMD_UNLOCK(sc); + DPRINTF(("%s: command %d dropped\n", __func__, cmd)); + return (EBUSY); + } + + sc->sc_cmd[sc->sc_cmd_next] = cmd; + sc->sc_cmd_next = (sc->sc_cmd_next + 1) % IPW_CMD_MAXOPS; + taskqueue_enqueue(sc->sc_tq, &sc->sc_opstask); + IPW_CMD_UNLOCK(sc); + return (0); +} + +static void +ipw_scan_start(struct ieee80211com *ic) +{ + struct ifnet *ifp = ic->ic_ifp; + struct ipw_softc *sc = ifp->if_softc; + + device_printf(sc->sc_dev, "%s\n", __func__); + ipw_queue_cmd(sc, IPW_SCAN_START); +} + +static void +ipw_set_channel(struct ieee80211com *ic) +{ + struct ifnet *ifp = ic->ic_ifp; + struct ipw_softc *sc = ifp->if_softc; + + device_printf(sc->sc_dev, "%s\n", __func__); +} + +static void +ipw_scan_curchan(struct ieee80211com *ic, unsigned long maxdwell) +{ + struct ifnet *ifp = ic->ic_ifp; + struct ipw_softc *sc = ifp->if_softc; + + device_printf(sc->sc_dev, "%s\n", __func__); +} + +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; + + device_printf(sc->sc_dev, "%s\n", __func__); +} + +static void +ipw_assoc(struct ieee80211com *ic) +{ + struct ifnet *ifp = ic->ic_ifp; + struct ipw_softc *sc = ifp->if_softc; + + /* The firmware will fail if we are already associated */ + if (sc->flags & IPW_FLAG_ASSOCIATED) + ipw_disassoc(ic); + + ipw_queue_cmd(sc, IPW_ASSOC); +} + +static void +ipw_disassoc(struct ieee80211com *ic) +{ + struct ifnet *ifp = ic->ic_ifp; + struct ipw_softc *sc = ifp->if_softc; + + ipw_queue_cmd(sc, IPW_DISASSOC); +} + +/* + * Read 16 bits at address 'addr' from the serial EEPROM. + */ +static uint16_t +ipw_read_prom_word(struct ipw_softc *sc, uint8_t addr) +{ + uint32_t tmp; + uint16_t val; + int n; + + /* clock C once before the first command */ + IPW_EEPROM_CTL(sc, 0); + IPW_EEPROM_CTL(sc, IPW_EEPROM_S); + IPW_EEPROM_CTL(sc, IPW_EEPROM_S | IPW_EEPROM_C); + IPW_EEPROM_CTL(sc, IPW_EEPROM_S); + + /* write start bit (1) */ + IPW_EEPROM_CTL(sc, IPW_EEPROM_S | IPW_EEPROM_D); + IPW_EEPROM_CTL(sc, IPW_EEPROM_S | IPW_EEPROM_D | IPW_EEPROM_C); + + /* write READ opcode (10) */ + IPW_EEPROM_CTL(sc, IPW_EEPROM_S | IPW_EEPROM_D); + IPW_EEPROM_CTL(sc, IPW_EEPROM_S | IPW_EEPROM_D | IPW_EEPROM_C); + IPW_EEPROM_CTL(sc, IPW_EEPROM_S); + IPW_EEPROM_CTL(sc, IPW_EEPROM_S | IPW_EEPROM_C); + + /* write address A7-A0 */ + for (n = 7; n >= 0; n--) { + IPW_EEPROM_CTL(sc, IPW_EEPROM_S | + (((addr >> n) & 1) << IPW_EEPROM_SHIFT_D)); + IPW_EEPROM_CTL(sc, IPW_EEPROM_S | + (((addr >> n) & 1) << IPW_EEPROM_SHIFT_D) | IPW_EEPROM_C); + } + + IPW_EEPROM_CTL(sc, IPW_EEPROM_S); + + /* read data Q15-Q0 */ + val = 0; + for (n = 15; n >= 0; n--) { + IPW_EEPROM_CTL(sc, IPW_EEPROM_S | IPW_EEPROM_C); + IPW_EEPROM_CTL(sc, IPW_EEPROM_S); + tmp = MEM_READ_4(sc, IPW_MEM_EEPROM_CTL); + val |= ((tmp & IPW_EEPROM_Q) >> IPW_EEPROM_SHIFT_Q) << n; + } + + IPW_EEPROM_CTL(sc, 0); + + /* clear Chip Select and clock C */ + IPW_EEPROM_CTL(sc, IPW_EEPROM_S); + IPW_EEPROM_CTL(sc, 0); + IPW_EEPROM_CTL(sc, IPW_EEPROM_C); + + return le16toh(val); +} + +/* + * Return whether or not the radio is enabled in hardware + * (i.e. the rfkill switch is "off"). + */ +static int +ipw_getrfkill(struct ipw_softc *sc) +{ + return (sc->flags & IPW_FLAG_HAS_RFSWITCH) && + (CSR_READ_4(sc, IPW_CSR_IO) & IPW_IO_RADIO_DISABLED) != 0; +} + +static void +ipw_radio_on(void *arg, int pending) +{ + struct ipw_softc *sc = arg; + + device_printf(sc->sc_dev, "radio turned on\n"); + ipw_init(sc); +} + +static void +ipw_radio_off(void *arg, int pending) +{ + struct ipw_softc *sc = arg; + IPW_LOCK_DECL; + + device_printf(sc->sc_dev, "radio turned off\n"); + IPW_LOCK(sc); + ipw_stop_locked(sc); + sc->sc_rfkill_timer = 2; + IPW_UNLOCK(sc); +} + +static int +ipw_sysctl_radio(SYSCTL_HANDLER_ARGS) +{ + struct ipw_softc *sc = arg1; + int val = !ipw_getrfkill(sc); + + return SYSCTL_OUT(req, &val, sizeof val); +} + +static int +ipw_sysctl_stats(SYSCTL_HANDLER_ARGS) +{ + struct ipw_softc *sc = arg1; + uint32_t i, size, buf[256]; + IPW_LOCK_DECL; + + memset(buf, 0, sizeof(buf)); + IPW_LOCK(sc); + if (sc->flags & IPW_FLAG_FW_INITED) { + CSR_WRITE_4(sc, IPW_CSR_AUTOINC_ADDR, sc->table1_base); + + size = min(CSR_READ_4(sc, IPW_CSR_AUTOINC_DATA), 256); + for (i = 1; i < size; i++) + buf[i] = MEM_READ_4(sc, + CSR_READ_4(sc, IPW_CSR_AUTOINC_DATA)); + } + IPW_UNLOCK(sc); + return SYSCTL_OUT(req, buf, sizeof buf); +} + +/* + * Add sysctl knobs. + */ +static void +ipw_sysctlattach(struct ipw_softc *sc) +{ + struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(sc->sc_dev); + struct sysctl_oid *tree = device_get_sysctl_tree(sc->sc_dev); + + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "radio", + CTLTYPE_INT | CTLFLAG_RD, sc, 0, ipw_sysctl_radio, "I", + "radio transmitter switch state (0=off, 1=on)"); + + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "stats", + CTLTYPE_OPAQUE | CTLFLAG_RD, sc, 0, ipw_sysctl_stats, "S", + "statistics"); +#if 0 + /* XXX not implemented (yet) */ + sc->dwelltime = 100; + SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "dwell", + CTLFLAG_RW, &sc->dwelltime, 0, + "channel dwell time (ms) for AP/station scanning"); +#endif +} Index: if_ipwreg.h =================================================================== RCS file: /home/ncvs/src/sys/dev/ipw/if_ipwreg.h,v retrieving revision 1.2 diff -u -p -r1.2 if_ipwreg.h --- if_ipwreg.h 12 Mar 2006 19:01:00 -0000 1.2 +++ if_ipwreg.h 11 Aug 2006 03:34:45 -0000 @@ -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_RADIO_DISABLED 0x0100 +#define IPW_STATE_PS_ENTER 0x0040 /* entered power-save mode */ +#define IPW_STATE_PS_EXIT 0x0080 /* exited power-save mode */ +#define IPW_STATE_RADIO_OFF 0x0100 /* radio switch toggled */ #define IPW_STATE_DISABLED 0x0200 +#define IPW_STATE_POWER_DOWN 0x0400 /* ??? */ #define IPW_STATE_SCANNING 0x0800 /* table1 offsets */ @@ -136,7 +143,7 @@ struct ipw_bd { /* status */ struct ipw_status { - uint32_t len; + uint32_t len; /* frame size */ uint16_t code; #define IPW_STATUS_CODE_COMMAND 0 #define IPW_STATUS_CODE_NEWSTATE 1 @@ -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 @@ -240,9 +254,10 @@ struct ipw_security { /* structure for command IPW_CMD_SET_SCAN_OPTIONS */ struct ipw_scan_options { uint32_t flags; -#define IPW_SCAN_DO_NOT_ASSOCIATE 0x00000001 +#define IPW_SCAN_DO_NOT_ASSOCIATE 0x00000001 /* XXX broken */ +#define IPW_SCAN_MIXED_CELL 0x00000002 #define IPW_SCAN_PASSIVE 0x00000008 - uint32_t channels; + uint32_t chanmask; } __packed; /* structure for command IPW_CMD_SET_CONFIGURATION */ @@ -264,7 +279,7 @@ struct ipw_wpa_ie { uint16_t capinfo; uint16_t lintval; uint8_t bssid[IEEE80211_ADDR_LEN]; - uint32_t len; + uint32_t ielen; struct ieee80211_ie_wpa ie; } __packed; Index: if_ipwvar.h =================================================================== RCS file: /home/ncvs/src/sys/dev/ipw/if_ipwvar.h,v retrieving revision 1.6 diff -u -p -r1.6 if_ipwvar.h --- if_ipwvar.h 15 Feb 2007 17:21:30 -0000 1.6 +++ if_ipwvar.h 12 Jul 2007 09:24:11 -0000 @@ -1,5 +1,3 @@ -/* $FreeBSD: src/sys/dev/ipw/if_ipwvar.h,v 1.6 2007/02/15 17:21:30 luigi Exp $ */ - /*- * Copyright (c) 2004-2006 * Damien Bergamini . All rights reserved. @@ -25,6 +23,8 @@ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. + * + * $FreeBSD: src/sys/dev/ipw/if_ipwvar.h,v 1.4 2006/03/12 19:01:00 damien Exp $ */ #define IPW_MAX_NSEG 1 @@ -63,7 +63,7 @@ struct ipw_rx_radiotap_header { #define IPW_RX_RADIOTAP_PRESENT \ ((1 << IEEE80211_RADIOTAP_FLAGS) | \ (1 << IEEE80211_RADIOTAP_CHANNEL) | \ - (1 << IEEE80211_RADIOTAP_DB_ANTSIGNAL)) + (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL)) struct ipw_tx_radiotap_header { struct ieee80211_radiotap_header wt_ihdr; @@ -76,87 +76,170 @@ struct ipw_tx_radiotap_header { ((1 << IEEE80211_RADIOTAP_FLAGS) | \ (1 << IEEE80211_RADIOTAP_CHANNEL)) -struct ipw_softc { - struct ifnet *sc_ifp; - struct ieee80211com sc_ic; - int (*sc_newstate)(struct ieee80211com *, - enum ieee80211_state, int); - device_t sc_dev; - - struct mtx sc_mtx; - struct task sc_init_task; - - 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) - - int irq_rid; - int mem_rid; - struct resource *irq; - struct resource *mem; - bus_space_tag_t sc_st; - bus_space_handle_t sc_sh; - void *sc_ih; - const struct firmware *sc_firmware; - - int sc_tx_timer; - - bus_dma_tag_t tbd_dmat; - bus_dma_tag_t rbd_dmat; - bus_dma_tag_t status_dmat; - bus_dma_tag_t cmd_dmat; - bus_dma_tag_t hdr_dmat; - bus_dma_tag_t txbuf_dmat; - bus_dma_tag_t rxbuf_dmat; - - bus_dmamap_t tbd_map; - bus_dmamap_t rbd_map; - bus_dmamap_t status_map; - bus_dmamap_t cmd_map; - - bus_addr_t tbd_phys; - bus_addr_t rbd_phys; - bus_addr_t status_phys; - - struct ipw_bd *tbd_list; - struct ipw_bd *rbd_list; - struct ipw_status *status_list; - - struct ipw_cmd cmd; - struct ipw_soft_bd stbd_list[IPW_NTBD]; - struct ipw_soft_buf tx_sbuf_list[IPW_NDATA]; - struct ipw_soft_hdr shdr_list[IPW_NDATA]; - struct ipw_soft_bd srbd_list[IPW_NRBD]; - struct ipw_soft_buf rx_sbuf_list[IPW_NRBD]; - - SLIST_HEAD(, ipw_soft_hdr) free_shdr; - SLIST_HEAD(, ipw_soft_buf) free_sbuf; - - uint32_t table1_base; - uint32_t table2_base; - - uint32_t txcur; - uint32_t txold; - uint32_t rxcur; - int txfree; - - int dwelltime; +struct ipw_fw { + const struct firmware *fp; /* image handle */ + const char *data; /* firmware image data */ + size_t size; /* firmware image size */ +}; - struct bpf_if *sc_drvbpf; +struct ipw_softc { + struct ifnet *sc_ifp; + struct ieee80211com sc_ic; + int (*sc_newstate)(struct ieee80211com *, + enum ieee80211_state, int); + device_t sc_dev; + + struct mtx sc_mtx; + struct mtx sc_cmdlock; + char sc_cmdname[12]; /* e.g. "ipw0_cmd" */ + struct taskqueue *sc_tq; /* private task queue */ +#if __FreeBSD_version < 700000 + struct proc *sc_tqproc; +#endif + struct task sc_opstask; + struct task sc_radiontask; /* radio on processing */ + struct task sc_radiofftask; /* radio off processing */ + struct task sc_assoclosttask;/* assoc lost processing */ + struct task sc_restarttask; /* restart adapter processing */ + struct callout sc_wdtimer; /* watchdog timer */ + + uint32_t flags; +#define IPW_FLAG_FW_INITED 0x00000002 /* firmware initialized */ +#define IPW_FLAG_SCANNING 0x00000004 /* busy scanning */ +#define IPW_FLAG_BUSY 0x00000008 /* busy sending a command */ +#define IPW_FLAG_ASSOCIATED 0x00000010 /* currently associated */ +#define IPW_FLAG_ENABLED 0x00000020 /* adapter enabled */ +#define IPW_FLAG_HAS_RFSWITCH 0x00010000 /* rfkill switch present */ +#define IPW_FLAG_HACK 0x00020000 + uint32_t fw_state; +#define IPW_FW_IDLE 0 +#define IPW_FW_LOADING 1 +#define IPW_FW_ASSOCIATING 2 +#define IPW_FW_DISASSOCIATING 3 +#define IPW_FW_SCANNING 4 + + int irq_rid; + int mem_rid; + struct resource *irq; + struct resource *mem; + bus_space_tag_t sc_st; + bus_space_handle_t sc_sh; + void *sc_ih; + + enum ieee80211_opmode fw_mode; /* mode of current firmware */ + struct ipw_fw fw_ucode; /* cached microcode */ + struct ipw_fw fw_fw; /* cached firmware */ + + bus_dma_tag_t tbd_dmat; + bus_dma_tag_t rbd_dmat; + bus_dma_tag_t status_dmat; + bus_dma_tag_t cmd_dmat; + bus_dma_tag_t hdr_dmat; + bus_dma_tag_t txbuf_dmat; + bus_dma_tag_t rxbuf_dmat; + + bus_dmamap_t tbd_map; + bus_dmamap_t rbd_map; + bus_dmamap_t status_map; + bus_dmamap_t cmd_map; + + bus_addr_t tbd_phys; + bus_addr_t rbd_phys; + bus_addr_t status_phys; + + struct ipw_bd *tbd_list; + struct ipw_bd *rbd_list; + struct ipw_status *status_list; + + struct ipw_cmd cmd; + struct ipw_soft_bd stbd_list[IPW_NTBD]; + struct ipw_soft_buf tx_sbuf_list[IPW_NDATA]; + struct ipw_soft_hdr shdr_list[IPW_NDATA]; + struct ipw_soft_bd srbd_list[IPW_NRBD]; + struct ipw_soft_buf rx_sbuf_list[IPW_NRBD]; + + SLIST_HEAD(, ipw_soft_hdr) free_shdr; + SLIST_HEAD(, ipw_soft_buf) free_sbuf; + + uint32_t table1_base; + uint32_t table2_base; + + uint32_t txcur; + uint32_t txold; + uint32_t rxcur; + int txfree; + + int chanmask; /* supported channels */ + int dwelltime; + + int sc_tx_timer; + int sc_rfkill_timer;/* poll for rfkill change */ + int sc_state_timer; + +#define IPW_SCAN_START (1 << 0) +#define IPW_SET_CHANNEL (1 << 1) +#define IPW_ASSOC (1 << 2) +#define IPW_DISASSOC (1 << 3) +#define IPW_CMD_MAXOPS 10 + int sc_cmd[IPW_CMD_MAXOPS]; + int sc_cmd_cur; /* current queued scan task */ + int sc_cmd_next; /* last queued scan task */ + struct bpf_if *sc_drvbpf; union { struct ipw_rx_radiotap_header th; uint8_t pad[64]; } sc_rxtapu; #define sc_rxtap sc_rxtapu.th - int sc_rxtap_len; + int sc_rxtap_len; union { struct ipw_tx_radiotap_header th; uint8_t pad[64]; } sc_txtapu; #define sc_txtap sc_txtapu.th - int sc_txtap_len; + int sc_txtap_len; }; + +#define IPW_STATE_BEGIN(_sc, _state) do { \ + KASSERT(_sc->fw_state == IPW_FW_IDLE, \ + ("ipw firmware not idle")); \ + _sc->fw_state = _state; \ + _sc->sc_state_timer = 5; \ + DPRINTF(("enter FW state %d\n", _state)); \ +} while (0) + +#define IPW_STATE_END(_sc, _state) do { \ + if (_sc->fw_state == _state) \ + DPRINTF(("exit FW state %d\n", _state)); \ + else \ + DPRINTF(("expected FW state %d, got %d\n", \ + _state, _sc->fw_state)); \ + _sc->fw_state = IPW_FW_IDLE; \ + wakeup(_sc); \ + _sc->sc_state_timer = 0; \ +} while (0) + +/* + * 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) +#define IPW_CMD_LOCK_INIT(sc) do { \ + snprintf((sc)->sc_cmdname, sizeof((sc)->sc_cmdname), "%s_cmd", \ + device_get_nameunit((sc)->sc_dev)); \ + mtx_init(&(sc)->sc_cmdlock, (sc)->sc_cmdname, NULL, MTX_DEF); \ +} while (0) +#define IPW_CMD_LOCK_DESTROY(sc) mtx_destroy(&(sc)->sc_cmdlock) +#define IPW_CMD_LOCK(sc) mtx_lock(&(sc)->sc_cmdlock) +#define IPW_CMD_UNLOCK(sc) mtx_unlock(&(sc)->sc_cmdlock) +