Index: head/sys/dev/iwm/if_iwm.c =================================================================== --- head/sys/dev/iwm/if_iwm.c (revision 289678) +++ head/sys/dev/iwm/if_iwm.c (revision 289679) @@ -1,5025 +1,5023 @@ /* $OpenBSD: if_iwm.c,v 1.39 2015/03/23 00:35:19 jsg Exp $ */ /* * Copyright (c) 2014 genua mbh * Copyright (c) 2014 Fixup Software Ltd. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /*- * Based on BSD-licensed source modules in the Linux iwlwifi driver, * which were used as the reference documentation for this implementation. * * Driver version we are currently based off of is * Linux 3.14.3 (tag id a2df521e42b1d9a23f620ac79dbfe8655a8391dd) * *********************************************************************** * * This file is provided under a dual BSD/GPLv2 license. When using or * redistributing this file, you may do so under either license. * * GPL LICENSE SUMMARY * * Copyright(c) 2007 - 2013 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU General Public License as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, * USA * * The full GNU General Public License is included in this distribution * in the file called COPYING. * * Contact Information: * Intel Linux Wireless * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 * * * BSD LICENSE * * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * Neither the name Intel Corporation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /*- * Copyright (c) 2007-2010 Damien Bergamini * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include const uint8_t iwm_nvm_channels[] = { /* 2.4 GHz */ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 5 GHz */ 36, 40, 44 , 48, 52, 56, 60, 64, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, 149, 153, 157, 161, 165 }; #define IWM_NUM_2GHZ_CHANNELS 14 /* * XXX For now, there's simply a fixed set of rate table entries * that are populated. */ const struct iwm_rate { uint8_t rate; uint8_t plcp; } iwm_rates[] = { { 2, IWM_RATE_1M_PLCP }, { 4, IWM_RATE_2M_PLCP }, { 11, IWM_RATE_5M_PLCP }, { 22, IWM_RATE_11M_PLCP }, { 12, IWM_RATE_6M_PLCP }, { 18, IWM_RATE_9M_PLCP }, { 24, IWM_RATE_12M_PLCP }, { 36, IWM_RATE_18M_PLCP }, { 48, IWM_RATE_24M_PLCP }, { 72, IWM_RATE_36M_PLCP }, { 96, IWM_RATE_48M_PLCP }, { 108, IWM_RATE_54M_PLCP }, }; #define IWM_RIDX_CCK 0 #define IWM_RIDX_OFDM 4 #define IWM_RIDX_MAX (nitems(iwm_rates)-1) #define IWM_RIDX_IS_CCK(_i_) ((_i_) < IWM_RIDX_OFDM) #define IWM_RIDX_IS_OFDM(_i_) ((_i_) >= IWM_RIDX_OFDM) static int iwm_store_cscheme(struct iwm_softc *, const uint8_t *, size_t); static int iwm_firmware_store_section(struct iwm_softc *, enum iwm_ucode_type, const uint8_t *, size_t); static int iwm_set_default_calib(struct iwm_softc *, const void *); static void iwm_fw_info_free(struct iwm_fw_info *); static int iwm_read_firmware(struct iwm_softc *, enum iwm_ucode_type); static void iwm_dma_map_addr(void *, bus_dma_segment_t *, int, int); static int iwm_dma_contig_alloc(bus_dma_tag_t, struct iwm_dma_info *, bus_size_t, bus_size_t); static void iwm_dma_contig_free(struct iwm_dma_info *); static int iwm_alloc_fwmem(struct iwm_softc *); static void iwm_free_fwmem(struct iwm_softc *); static int iwm_alloc_sched(struct iwm_softc *); static void iwm_free_sched(struct iwm_softc *); static int iwm_alloc_kw(struct iwm_softc *); static void iwm_free_kw(struct iwm_softc *); static int iwm_alloc_ict(struct iwm_softc *); static void iwm_free_ict(struct iwm_softc *); static int iwm_alloc_rx_ring(struct iwm_softc *, struct iwm_rx_ring *); static void iwm_reset_rx_ring(struct iwm_softc *, struct iwm_rx_ring *); static void iwm_free_rx_ring(struct iwm_softc *, struct iwm_rx_ring *); static int iwm_alloc_tx_ring(struct iwm_softc *, struct iwm_tx_ring *, int); static void iwm_reset_tx_ring(struct iwm_softc *, struct iwm_tx_ring *); static void iwm_free_tx_ring(struct iwm_softc *, struct iwm_tx_ring *); static void iwm_enable_interrupts(struct iwm_softc *); static void iwm_restore_interrupts(struct iwm_softc *); static void iwm_disable_interrupts(struct iwm_softc *); static void iwm_ict_reset(struct iwm_softc *); static int iwm_allow_mcast(struct ieee80211vap *, struct iwm_softc *); static void iwm_stop_device(struct iwm_softc *); static void iwm_mvm_nic_config(struct iwm_softc *); static int iwm_nic_rx_init(struct iwm_softc *); static int iwm_nic_tx_init(struct iwm_softc *); static int iwm_nic_init(struct iwm_softc *); static void iwm_enable_txq(struct iwm_softc *, int, int); static int iwm_post_alive(struct iwm_softc *); static int iwm_nvm_read_chunk(struct iwm_softc *, uint16_t, uint16_t, uint16_t, uint8_t *, uint16_t *); static int iwm_nvm_read_section(struct iwm_softc *, uint16_t, uint8_t *, uint16_t *); static void iwm_init_channel_map(struct iwm_softc *, const uint16_t * const); static int iwm_parse_nvm_data(struct iwm_softc *, const uint16_t *, const uint16_t *, const uint16_t *, uint8_t, uint8_t); struct iwm_nvm_section; static int iwm_parse_nvm_sections(struct iwm_softc *, struct iwm_nvm_section *); static int iwm_nvm_init(struct iwm_softc *); static int iwm_firmware_load_chunk(struct iwm_softc *, uint32_t, const uint8_t *, uint32_t); static int iwm_load_firmware(struct iwm_softc *, enum iwm_ucode_type); static int iwm_start_fw(struct iwm_softc *, enum iwm_ucode_type); static int iwm_fw_alive(struct iwm_softc *, uint32_t); static int iwm_send_tx_ant_cfg(struct iwm_softc *, uint8_t); static int iwm_send_phy_cfg_cmd(struct iwm_softc *); static int iwm_mvm_load_ucode_wait_alive(struct iwm_softc *, enum iwm_ucode_type); static int iwm_run_init_mvm_ucode(struct iwm_softc *, int); static int iwm_rx_addbuf(struct iwm_softc *, int, int); static int iwm_mvm_calc_rssi(struct iwm_softc *, struct iwm_rx_phy_info *); static int iwm_mvm_get_signal_strength(struct iwm_softc *, struct iwm_rx_phy_info *); static void iwm_mvm_rx_rx_phy_cmd(struct iwm_softc *, struct iwm_rx_packet *, struct iwm_rx_data *); static int iwm_get_noise(const struct iwm_mvm_statistics_rx_non_phy *); static void iwm_mvm_rx_rx_mpdu(struct iwm_softc *, struct iwm_rx_packet *, struct iwm_rx_data *); static void iwm_mvm_rx_tx_cmd_single(struct iwm_softc *, struct iwm_rx_packet *, struct iwm_node *); static void iwm_mvm_rx_tx_cmd(struct iwm_softc *, struct iwm_rx_packet *, struct iwm_rx_data *); static void iwm_cmd_done(struct iwm_softc *, struct iwm_rx_packet *); #if 0 static void iwm_update_sched(struct iwm_softc *, int, int, uint8_t, uint16_t); #endif static const struct iwm_rate * iwm_tx_fill_cmd(struct iwm_softc *, struct iwm_node *, struct ieee80211_frame *, struct iwm_tx_cmd *); static int iwm_tx(struct iwm_softc *, struct mbuf *, struct ieee80211_node *, int); static int iwm_raw_xmit(struct ieee80211_node *, struct mbuf *, const struct ieee80211_bpf_params *); static void iwm_mvm_add_sta_cmd_v6_to_v5(struct iwm_mvm_add_sta_cmd_v6 *, struct iwm_mvm_add_sta_cmd_v5 *); static int iwm_mvm_send_add_sta_cmd_status(struct iwm_softc *, struct iwm_mvm_add_sta_cmd_v6 *, int *); static int iwm_mvm_sta_send_to_fw(struct iwm_softc *, struct iwm_node *, int); static int iwm_mvm_add_sta(struct iwm_softc *, struct iwm_node *); static int iwm_mvm_update_sta(struct iwm_softc *, struct iwm_node *); static int iwm_mvm_add_int_sta_common(struct iwm_softc *, struct iwm_int_sta *, const uint8_t *, uint16_t, uint16_t); static int iwm_mvm_add_aux_sta(struct iwm_softc *); static int iwm_mvm_update_quotas(struct iwm_softc *, struct iwm_node *); static int iwm_auth(struct ieee80211vap *, struct iwm_softc *); static int iwm_assoc(struct ieee80211vap *, struct iwm_softc *); static int iwm_release(struct iwm_softc *, struct iwm_node *); static struct ieee80211_node * iwm_node_alloc(struct ieee80211vap *, const uint8_t[IEEE80211_ADDR_LEN]); static void iwm_setrates(struct iwm_softc *, struct iwm_node *); static int iwm_media_change(struct ifnet *); static int iwm_newstate(struct ieee80211vap *, enum ieee80211_state, int); static void iwm_endscan_cb(void *, int); static int iwm_init_hw(struct iwm_softc *); static void iwm_init(struct iwm_softc *); static void iwm_start(struct iwm_softc *); static void iwm_stop(struct iwm_softc *); static void iwm_watchdog(void *); static void iwm_parent(struct ieee80211com *); #ifdef IWM_DEBUG static const char * iwm_desc_lookup(uint32_t); static void iwm_nic_error(struct iwm_softc *); #endif static void iwm_notif_intr(struct iwm_softc *); static void iwm_intr(void *); static int iwm_attach(device_t); static void iwm_preinit(void *); static int iwm_detach_local(struct iwm_softc *sc, int); static void iwm_init_task(void *); static void iwm_radiotap_attach(struct iwm_softc *); static struct ieee80211vap * iwm_vap_create(struct ieee80211com *, const char [IFNAMSIZ], int, enum ieee80211_opmode, int, const uint8_t [IEEE80211_ADDR_LEN], const uint8_t [IEEE80211_ADDR_LEN]); static void iwm_vap_delete(struct ieee80211vap *); static void iwm_scan_start(struct ieee80211com *); static void iwm_scan_end(struct ieee80211com *); static void iwm_update_mcast(struct ieee80211com *); static void iwm_set_channel(struct ieee80211com *); static void iwm_scan_curchan(struct ieee80211_scan_state *, unsigned long); static void iwm_scan_mindwell(struct ieee80211_scan_state *); static int iwm_detach(device_t); /* * Firmware parser. */ static int iwm_store_cscheme(struct iwm_softc *sc, const uint8_t *data, size_t dlen) { const struct iwm_fw_cscheme_list *l = (const void *)data; if (dlen < sizeof(*l) || dlen < sizeof(l->size) + l->size * sizeof(*l->cs)) return EINVAL; /* we don't actually store anything for now, always use s/w crypto */ return 0; } static int iwm_firmware_store_section(struct iwm_softc *sc, enum iwm_ucode_type type, const uint8_t *data, size_t dlen) { struct iwm_fw_sects *fws; struct iwm_fw_onesect *fwone; if (type >= IWM_UCODE_TYPE_MAX) return EINVAL; if (dlen < sizeof(uint32_t)) return EINVAL; fws = &sc->sc_fw.fw_sects[type]; if (fws->fw_count >= IWM_UCODE_SECT_MAX) return EINVAL; fwone = &fws->fw_sect[fws->fw_count]; /* first 32bit are device load offset */ memcpy(&fwone->fws_devoff, data, sizeof(uint32_t)); /* rest is data */ fwone->fws_data = data + sizeof(uint32_t); fwone->fws_len = dlen - sizeof(uint32_t); fws->fw_count++; fws->fw_totlen += fwone->fws_len; return 0; } /* iwlwifi: iwl-drv.c */ struct iwm_tlv_calib_data { uint32_t ucode_type; struct iwm_tlv_calib_ctrl calib; } __packed; static int iwm_set_default_calib(struct iwm_softc *sc, const void *data) { const struct iwm_tlv_calib_data *def_calib = data; uint32_t ucode_type = le32toh(def_calib->ucode_type); if (ucode_type >= IWM_UCODE_TYPE_MAX) { device_printf(sc->sc_dev, "Wrong ucode_type %u for default " "calibration.\n", ucode_type); return EINVAL; } sc->sc_default_calib[ucode_type].flow_trigger = def_calib->calib.flow_trigger; sc->sc_default_calib[ucode_type].event_trigger = def_calib->calib.event_trigger; return 0; } static void iwm_fw_info_free(struct iwm_fw_info *fw) { firmware_put(fw->fw_rawdata, FIRMWARE_UNLOAD); fw->fw_rawdata = NULL; fw->fw_rawsize = 0; /* don't touch fw->fw_status */ memset(fw->fw_sects, 0, sizeof(fw->fw_sects)); } static int iwm_read_firmware(struct iwm_softc *sc, enum iwm_ucode_type ucode_type) { struct iwm_fw_info *fw = &sc->sc_fw; const struct iwm_tlv_ucode_header *uhdr; struct iwm_ucode_tlv tlv; enum iwm_ucode_tlv_type tlv_type; const struct firmware *fwp; const uint8_t *data; int error = 0; size_t len; if (fw->fw_status == IWM_FW_STATUS_DONE && ucode_type != IWM_UCODE_TYPE_INIT) return 0; while (fw->fw_status == IWM_FW_STATUS_INPROGRESS) msleep(&sc->sc_fw, &sc->sc_mtx, 0, "iwmfwp", 0); fw->fw_status = IWM_FW_STATUS_INPROGRESS; if (fw->fw_rawdata != NULL) iwm_fw_info_free(fw); /* * Load firmware into driver memory. * fw_rawdata and fw_rawsize will be set. */ IWM_UNLOCK(sc); fwp = firmware_get(sc->sc_fwname); if (fwp == NULL) { device_printf(sc->sc_dev, "could not read firmware %s (error %d)\n", sc->sc_fwname, error); IWM_LOCK(sc); goto out; } IWM_LOCK(sc); fw->fw_rawdata = fwp->data; fw->fw_rawsize = fwp->datasize; /* * Parse firmware contents */ uhdr = (const void *)fw->fw_rawdata; if (*(const uint32_t *)fw->fw_rawdata != 0 || le32toh(uhdr->magic) != IWM_TLV_UCODE_MAGIC) { device_printf(sc->sc_dev, "invalid firmware %s\n", sc->sc_fwname); error = EINVAL; goto out; } sc->sc_fwver = le32toh(uhdr->ver); data = uhdr->data; len = fw->fw_rawsize - sizeof(*uhdr); while (len >= sizeof(tlv)) { size_t tlv_len; const void *tlv_data; memcpy(&tlv, data, sizeof(tlv)); tlv_len = le32toh(tlv.length); tlv_type = le32toh(tlv.type); len -= sizeof(tlv); data += sizeof(tlv); tlv_data = data; if (len < tlv_len) { device_printf(sc->sc_dev, "firmware too short: %zu bytes\n", len); error = EINVAL; goto parse_out; } switch ((int)tlv_type) { case IWM_UCODE_TLV_PROBE_MAX_LEN: if (tlv_len < sizeof(uint32_t)) { device_printf(sc->sc_dev, "%s: PROBE_MAX_LEN (%d) < sizeof(uint32_t)\n", __func__, (int) tlv_len); error = EINVAL; goto parse_out; } sc->sc_capa_max_probe_len = le32toh(*(const uint32_t *)tlv_data); /* limit it to something sensible */ if (sc->sc_capa_max_probe_len > (1<<16)) { IWM_DPRINTF(sc, IWM_DEBUG_FIRMWARE_TLV, "%s: IWM_UCODE_TLV_PROBE_MAX_LEN " "ridiculous\n", __func__); error = EINVAL; goto parse_out; } break; case IWM_UCODE_TLV_PAN: if (tlv_len) { device_printf(sc->sc_dev, "%s: IWM_UCODE_TLV_PAN: tlv_len (%d) > 0\n", __func__, (int) tlv_len); error = EINVAL; goto parse_out; } sc->sc_capaflags |= IWM_UCODE_TLV_FLAGS_PAN; break; case IWM_UCODE_TLV_FLAGS: if (tlv_len < sizeof(uint32_t)) { device_printf(sc->sc_dev, "%s: IWM_UCODE_TLV_FLAGS: tlv_len (%d) < sizeof(uint32_t)\n", __func__, (int) tlv_len); error = EINVAL; goto parse_out; } /* * Apparently there can be many flags, but Linux driver * parses only the first one, and so do we. * * XXX: why does this override IWM_UCODE_TLV_PAN? * Intentional or a bug? Observations from * current firmware file: * 1) TLV_PAN is parsed first * 2) TLV_FLAGS contains TLV_FLAGS_PAN * ==> this resets TLV_PAN to itself... hnnnk */ sc->sc_capaflags = le32toh(*(const uint32_t *)tlv_data); break; case IWM_UCODE_TLV_CSCHEME: if ((error = iwm_store_cscheme(sc, tlv_data, tlv_len)) != 0) { device_printf(sc->sc_dev, "%s: iwm_store_cscheme(): returned %d\n", __func__, error); goto parse_out; } break; case IWM_UCODE_TLV_NUM_OF_CPU: if (tlv_len != sizeof(uint32_t)) { device_printf(sc->sc_dev, "%s: IWM_UCODE_TLV_NUM_OF_CPU: tlv_len (%d) < sizeof(uint32_t)\n", __func__, (int) tlv_len); error = EINVAL; goto parse_out; } if (le32toh(*(const uint32_t*)tlv_data) != 1) { device_printf(sc->sc_dev, "%s: driver supports " "only TLV_NUM_OF_CPU == 1", __func__); error = EINVAL; goto parse_out; } break; case IWM_UCODE_TLV_SEC_RT: if ((error = iwm_firmware_store_section(sc, IWM_UCODE_TYPE_REGULAR, tlv_data, tlv_len)) != 0) { device_printf(sc->sc_dev, "%s: IWM_UCODE_TYPE_REGULAR: iwm_firmware_store_section() failed; %d\n", __func__, error); goto parse_out; } break; case IWM_UCODE_TLV_SEC_INIT: if ((error = iwm_firmware_store_section(sc, IWM_UCODE_TYPE_INIT, tlv_data, tlv_len)) != 0) { device_printf(sc->sc_dev, "%s: IWM_UCODE_TYPE_INIT: iwm_firmware_store_section() failed; %d\n", __func__, error); goto parse_out; } break; case IWM_UCODE_TLV_SEC_WOWLAN: if ((error = iwm_firmware_store_section(sc, IWM_UCODE_TYPE_WOW, tlv_data, tlv_len)) != 0) { device_printf(sc->sc_dev, "%s: IWM_UCODE_TYPE_WOW: iwm_firmware_store_section() failed; %d\n", __func__, error); goto parse_out; } break; case IWM_UCODE_TLV_DEF_CALIB: if (tlv_len != sizeof(struct iwm_tlv_calib_data)) { device_printf(sc->sc_dev, "%s: IWM_UCODE_TLV_DEV_CALIB: tlv_len (%d) < sizeof(iwm_tlv_calib_data) (%d)\n", __func__, (int) tlv_len, (int) sizeof(struct iwm_tlv_calib_data)); error = EINVAL; goto parse_out; } if ((error = iwm_set_default_calib(sc, tlv_data)) != 0) { device_printf(sc->sc_dev, "%s: iwm_set_default_calib() failed: %d\n", __func__, error); goto parse_out; } break; case IWM_UCODE_TLV_PHY_SKU: if (tlv_len != sizeof(uint32_t)) { error = EINVAL; device_printf(sc->sc_dev, "%s: IWM_UCODE_TLV_PHY_SKU: tlv_len (%d) < sizeof(uint32_t)\n", __func__, (int) tlv_len); goto parse_out; } sc->sc_fw_phy_config = le32toh(*(const uint32_t *)tlv_data); break; case IWM_UCODE_TLV_API_CHANGES_SET: case IWM_UCODE_TLV_ENABLED_CAPABILITIES: /* ignore, not used by current driver */ break; default: device_printf(sc->sc_dev, "%s: unknown firmware section %d, abort\n", __func__, tlv_type); error = EINVAL; goto parse_out; } len -= roundup(tlv_len, 4); data += roundup(tlv_len, 4); } KASSERT(error == 0, ("unhandled error")); parse_out: if (error) { device_printf(sc->sc_dev, "firmware parse error %d, " "section type %d\n", error, tlv_type); } if (!(sc->sc_capaflags & IWM_UCODE_TLV_FLAGS_PM_CMD_SUPPORT)) { device_printf(sc->sc_dev, "device uses unsupported power ops\n"); error = ENOTSUP; } out: if (error) { fw->fw_status = IWM_FW_STATUS_NONE; if (fw->fw_rawdata != NULL) iwm_fw_info_free(fw); } else fw->fw_status = IWM_FW_STATUS_DONE; wakeup(&sc->sc_fw); return error; } /* * DMA resource routines */ static void iwm_dma_map_addr(void *arg, bus_dma_segment_t *segs, int nsegs, int error) { if (error != 0) return; KASSERT(nsegs == 1, ("too many DMA segments, %d should be 1", nsegs)); *(bus_addr_t *)arg = segs[0].ds_addr; } static int iwm_dma_contig_alloc(bus_dma_tag_t tag, struct iwm_dma_info *dma, bus_size_t size, bus_size_t alignment) { int error; dma->tag = NULL; dma->size = size; error = bus_dma_tag_create(tag, alignment, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, size, - 1, size, BUS_DMA_NOWAIT, NULL, NULL, &dma->tag); + 1, size, 0, NULL, NULL, &dma->tag); if (error != 0) goto fail; error = bus_dmamem_alloc(dma->tag, (void **)&dma->vaddr, BUS_DMA_NOWAIT | BUS_DMA_ZERO | BUS_DMA_COHERENT, &dma->map); if (error != 0) goto fail; error = bus_dmamap_load(dma->tag, dma->map, dma->vaddr, size, iwm_dma_map_addr, &dma->paddr, BUS_DMA_NOWAIT); if (error != 0) goto fail; bus_dmamap_sync(dma->tag, dma->map, BUS_DMASYNC_PREWRITE); return 0; fail: iwm_dma_contig_free(dma); return error; } static void iwm_dma_contig_free(struct iwm_dma_info *dma) { if (dma->map != NULL) { if (dma->vaddr != NULL) { bus_dmamap_sync(dma->tag, dma->map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(dma->tag, dma->map); bus_dmamem_free(dma->tag, dma->vaddr, dma->map); dma->vaddr = NULL; } bus_dmamap_destroy(dma->tag, dma->map); dma->map = NULL; } if (dma->tag != NULL) { bus_dma_tag_destroy(dma->tag); dma->tag = NULL; } } /* fwmem is used to load firmware onto the card */ static int iwm_alloc_fwmem(struct iwm_softc *sc) { /* Must be aligned on a 16-byte boundary. */ return iwm_dma_contig_alloc(sc->sc_dmat, &sc->fw_dma, sc->sc_fwdmasegsz, 16); } static void iwm_free_fwmem(struct iwm_softc *sc) { iwm_dma_contig_free(&sc->fw_dma); } /* tx scheduler rings. not used? */ static int iwm_alloc_sched(struct iwm_softc *sc) { int rv; /* TX scheduler rings must be aligned on a 1KB boundary. */ rv = iwm_dma_contig_alloc(sc->sc_dmat, &sc->sched_dma, nitems(sc->txq) * sizeof(struct iwm_agn_scd_bc_tbl), 1024); return rv; } static void iwm_free_sched(struct iwm_softc *sc) { iwm_dma_contig_free(&sc->sched_dma); } /* keep-warm page is used internally by the card. see iwl-fh.h for more info */ static int iwm_alloc_kw(struct iwm_softc *sc) { return iwm_dma_contig_alloc(sc->sc_dmat, &sc->kw_dma, 4096, 4096); } static void iwm_free_kw(struct iwm_softc *sc) { iwm_dma_contig_free(&sc->kw_dma); } /* interrupt cause table */ static int iwm_alloc_ict(struct iwm_softc *sc) { return iwm_dma_contig_alloc(sc->sc_dmat, &sc->ict_dma, IWM_ICT_SIZE, 1<ict_dma); } static int iwm_alloc_rx_ring(struct iwm_softc *sc, struct iwm_rx_ring *ring) { bus_size_t size; int i, error; ring->cur = 0; /* Allocate RX descriptors (256-byte aligned). */ size = IWM_RX_RING_COUNT * sizeof(uint32_t); error = iwm_dma_contig_alloc(sc->sc_dmat, &ring->desc_dma, size, 256); if (error != 0) { device_printf(sc->sc_dev, "could not allocate RX ring DMA memory\n"); goto fail; } ring->desc = ring->desc_dma.vaddr; /* Allocate RX status area (16-byte aligned). */ error = iwm_dma_contig_alloc(sc->sc_dmat, &ring->stat_dma, sizeof(*ring->stat), 16); if (error != 0) { device_printf(sc->sc_dev, "could not allocate RX status DMA memory\n"); goto fail; } ring->stat = ring->stat_dma.vaddr; /* Create RX buffer DMA tag. */ error = bus_dma_tag_create(sc->sc_dmat, 1, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, - IWM_RBUF_SIZE, 1, IWM_RBUF_SIZE, BUS_DMA_NOWAIT, NULL, NULL, - &ring->data_dmat); + IWM_RBUF_SIZE, 1, IWM_RBUF_SIZE, 0, NULL, NULL, &ring->data_dmat); if (error != 0) { device_printf(sc->sc_dev, "%s: could not create RX buf DMA tag, error %d\n", __func__, error); goto fail; } /* * Allocate and map RX buffers. */ for (i = 0; i < IWM_RX_RING_COUNT; i++) { if ((error = iwm_rx_addbuf(sc, IWM_RBUF_SIZE, i)) != 0) { goto fail; } } return 0; fail: iwm_free_rx_ring(sc, ring); return error; } static void iwm_reset_rx_ring(struct iwm_softc *sc, struct iwm_rx_ring *ring) { /* XXX print out if we can't lock the NIC? */ if (iwm_nic_lock(sc)) { /* XXX handle if RX stop doesn't finish? */ (void) iwm_pcie_rx_stop(sc); iwm_nic_unlock(sc); } /* Reset the ring state */ ring->cur = 0; memset(sc->rxq.stat, 0, sizeof(*sc->rxq.stat)); } static void iwm_free_rx_ring(struct iwm_softc *sc, struct iwm_rx_ring *ring) { int i; iwm_dma_contig_free(&ring->desc_dma); iwm_dma_contig_free(&ring->stat_dma); for (i = 0; i < IWM_RX_RING_COUNT; i++) { struct iwm_rx_data *data = &ring->data[i]; if (data->m != NULL) { bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(ring->data_dmat, data->map); m_freem(data->m); data->m = NULL; } if (data->map != NULL) { bus_dmamap_destroy(ring->data_dmat, data->map); data->map = NULL; } } if (ring->data_dmat != NULL) { bus_dma_tag_destroy(ring->data_dmat); ring->data_dmat = NULL; } } static int iwm_alloc_tx_ring(struct iwm_softc *sc, struct iwm_tx_ring *ring, int qid) { bus_addr_t paddr; bus_size_t size; int i, error; ring->qid = qid; ring->queued = 0; ring->cur = 0; /* Allocate TX descriptors (256-byte aligned). */ size = IWM_TX_RING_COUNT * sizeof (struct iwm_tfd); error = iwm_dma_contig_alloc(sc->sc_dmat, &ring->desc_dma, size, 256); if (error != 0) { device_printf(sc->sc_dev, "could not allocate TX ring DMA memory\n"); goto fail; } ring->desc = ring->desc_dma.vaddr; /* * We only use rings 0 through 9 (4 EDCA + cmd) so there is no need * to allocate commands space for other rings. */ if (qid > IWM_MVM_CMD_QUEUE) return 0; size = IWM_TX_RING_COUNT * sizeof(struct iwm_device_cmd); error = iwm_dma_contig_alloc(sc->sc_dmat, &ring->cmd_dma, size, 4); if (error != 0) { device_printf(sc->sc_dev, "could not allocate TX cmd DMA memory\n"); goto fail; } ring->cmd = ring->cmd_dma.vaddr; error = bus_dma_tag_create(sc->sc_dmat, 1, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, MCLBYTES, - IWM_MAX_SCATTER - 1, MCLBYTES, BUS_DMA_NOWAIT, NULL, NULL, - &ring->data_dmat); + IWM_MAX_SCATTER - 1, MCLBYTES, 0, NULL, NULL, &ring->data_dmat); if (error != 0) { device_printf(sc->sc_dev, "could not create TX buf DMA tag\n"); goto fail; } paddr = ring->cmd_dma.paddr; for (i = 0; i < IWM_TX_RING_COUNT; i++) { struct iwm_tx_data *data = &ring->data[i]; data->cmd_paddr = paddr; data->scratch_paddr = paddr + sizeof(struct iwm_cmd_header) + offsetof(struct iwm_tx_cmd, scratch); paddr += sizeof(struct iwm_device_cmd); error = bus_dmamap_create(ring->data_dmat, 0, &data->map); if (error != 0) { device_printf(sc->sc_dev, "could not create TX buf DMA map\n"); goto fail; } } KASSERT(paddr == ring->cmd_dma.paddr + size, ("invalid physical address")); return 0; fail: iwm_free_tx_ring(sc, ring); return error; } static void iwm_reset_tx_ring(struct iwm_softc *sc, struct iwm_tx_ring *ring) { int i; for (i = 0; i < IWM_TX_RING_COUNT; i++) { struct iwm_tx_data *data = &ring->data[i]; if (data->m != NULL) { bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(ring->data_dmat, data->map); m_freem(data->m); data->m = NULL; } } /* Clear TX descriptors. */ memset(ring->desc, 0, ring->desc_dma.size); bus_dmamap_sync(ring->desc_dma.tag, ring->desc_dma.map, BUS_DMASYNC_PREWRITE); sc->qfullmsk &= ~(1 << ring->qid); ring->queued = 0; ring->cur = 0; } static void iwm_free_tx_ring(struct iwm_softc *sc, struct iwm_tx_ring *ring) { int i; iwm_dma_contig_free(&ring->desc_dma); iwm_dma_contig_free(&ring->cmd_dma); for (i = 0; i < IWM_TX_RING_COUNT; i++) { struct iwm_tx_data *data = &ring->data[i]; if (data->m != NULL) { bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(ring->data_dmat, data->map); m_freem(data->m); data->m = NULL; } if (data->map != NULL) { bus_dmamap_destroy(ring->data_dmat, data->map); data->map = NULL; } } if (ring->data_dmat != NULL) { bus_dma_tag_destroy(ring->data_dmat); ring->data_dmat = NULL; } } /* * High-level hardware frobbing routines */ static void iwm_enable_interrupts(struct iwm_softc *sc) { sc->sc_intmask = IWM_CSR_INI_SET_MASK; IWM_WRITE(sc, IWM_CSR_INT_MASK, sc->sc_intmask); } static void iwm_restore_interrupts(struct iwm_softc *sc) { IWM_WRITE(sc, IWM_CSR_INT_MASK, sc->sc_intmask); } static void iwm_disable_interrupts(struct iwm_softc *sc) { /* disable interrupts */ IWM_WRITE(sc, IWM_CSR_INT_MASK, 0); /* acknowledge all interrupts */ IWM_WRITE(sc, IWM_CSR_INT, ~0); IWM_WRITE(sc, IWM_CSR_FH_INT_STATUS, ~0); } static void iwm_ict_reset(struct iwm_softc *sc) { iwm_disable_interrupts(sc); /* Reset ICT table. */ memset(sc->ict_dma.vaddr, 0, IWM_ICT_SIZE); sc->ict_cur = 0; /* Set physical address of ICT table (4KB aligned). */ IWM_WRITE(sc, IWM_CSR_DRAM_INT_TBL_REG, IWM_CSR_DRAM_INT_TBL_ENABLE | IWM_CSR_DRAM_INIT_TBL_WRAP_CHECK | sc->ict_dma.paddr >> IWM_ICT_PADDR_SHIFT); /* Switch to ICT interrupt mode in driver. */ sc->sc_flags |= IWM_FLAG_USE_ICT; /* Re-enable interrupts. */ IWM_WRITE(sc, IWM_CSR_INT, ~0); iwm_enable_interrupts(sc); } /* iwlwifi pcie/trans.c */ /* * Since this .. hard-resets things, it's time to actually * mark the first vap (if any) as having no mac context. * It's annoying, but since the driver is potentially being * stop/start'ed whilst active (thanks openbsd port!) we * have to correctly track this. */ static void iwm_stop_device(struct iwm_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); int chnl, ntries; int qid; /* tell the device to stop sending interrupts */ iwm_disable_interrupts(sc); /* * FreeBSD-local: mark the first vap as not-uploaded, * so the next transition through auth/assoc * will correctly populate the MAC context. */ if (vap) { struct iwm_vap *iv = IWM_VAP(vap); iv->is_uploaded = 0; } /* device going down, Stop using ICT table */ sc->sc_flags &= ~IWM_FLAG_USE_ICT; /* stop tx and rx. tx and rx bits, as usual, are from if_iwn */ iwm_write_prph(sc, IWM_SCD_TXFACT, 0); /* Stop all DMA channels. */ if (iwm_nic_lock(sc)) { for (chnl = 0; chnl < IWM_FH_TCSR_CHNL_NUM; chnl++) { IWM_WRITE(sc, IWM_FH_TCSR_CHNL_TX_CONFIG_REG(chnl), 0); for (ntries = 0; ntries < 200; ntries++) { uint32_t r; r = IWM_READ(sc, IWM_FH_TSSR_TX_STATUS_REG); if (r & IWM_FH_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE( chnl)) break; DELAY(20); } } iwm_nic_unlock(sc); } /* Stop RX ring. */ iwm_reset_rx_ring(sc, &sc->rxq); /* Reset all TX rings. */ for (qid = 0; qid < nitems(sc->txq); qid++) iwm_reset_tx_ring(sc, &sc->txq[qid]); /* * Power-down device's busmaster DMA clocks */ iwm_write_prph(sc, IWM_APMG_CLK_DIS_REG, IWM_APMG_CLK_VAL_DMA_CLK_RQT); DELAY(5); /* Make sure (redundant) we've released our request to stay awake */ IWM_CLRBITS(sc, IWM_CSR_GP_CNTRL, IWM_CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); /* Stop the device, and put it in low power state */ iwm_apm_stop(sc); /* Upon stop, the APM issues an interrupt if HW RF kill is set. * Clean again the interrupt here */ iwm_disable_interrupts(sc); /* stop and reset the on-board processor */ IWM_WRITE(sc, IWM_CSR_RESET, IWM_CSR_RESET_REG_FLAG_NEVO_RESET); /* * Even if we stop the HW, we still want the RF kill * interrupt */ iwm_enable_rfkill_int(sc); iwm_check_rfkill(sc); } /* iwlwifi: mvm/ops.c */ static void iwm_mvm_nic_config(struct iwm_softc *sc) { uint8_t radio_cfg_type, radio_cfg_step, radio_cfg_dash; uint32_t reg_val = 0; radio_cfg_type = (sc->sc_fw_phy_config & IWM_FW_PHY_CFG_RADIO_TYPE) >> IWM_FW_PHY_CFG_RADIO_TYPE_POS; radio_cfg_step = (sc->sc_fw_phy_config & IWM_FW_PHY_CFG_RADIO_STEP) >> IWM_FW_PHY_CFG_RADIO_STEP_POS; radio_cfg_dash = (sc->sc_fw_phy_config & IWM_FW_PHY_CFG_RADIO_DASH) >> IWM_FW_PHY_CFG_RADIO_DASH_POS; /* SKU control */ reg_val |= IWM_CSR_HW_REV_STEP(sc->sc_hw_rev) << IWM_CSR_HW_IF_CONFIG_REG_POS_MAC_STEP; reg_val |= IWM_CSR_HW_REV_DASH(sc->sc_hw_rev) << IWM_CSR_HW_IF_CONFIG_REG_POS_MAC_DASH; /* radio configuration */ reg_val |= radio_cfg_type << IWM_CSR_HW_IF_CONFIG_REG_POS_PHY_TYPE; reg_val |= radio_cfg_step << IWM_CSR_HW_IF_CONFIG_REG_POS_PHY_STEP; reg_val |= radio_cfg_dash << IWM_CSR_HW_IF_CONFIG_REG_POS_PHY_DASH; IWM_WRITE(sc, IWM_CSR_HW_IF_CONFIG_REG, reg_val); IWM_DPRINTF(sc, IWM_DEBUG_RESET, "Radio type=0x%x-0x%x-0x%x\n", radio_cfg_type, radio_cfg_step, radio_cfg_dash); /* * W/A : NIC is stuck in a reset state after Early PCIe power off * (PCIe power is lost before PERST# is asserted), causing ME FW * to lose ownership and not being able to obtain it back. */ iwm_set_bits_mask_prph(sc, IWM_APMG_PS_CTRL_REG, IWM_APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS, ~IWM_APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS); } static int iwm_nic_rx_init(struct iwm_softc *sc) { if (!iwm_nic_lock(sc)) return EBUSY; /* * Initialize RX ring. This is from the iwn driver. */ memset(sc->rxq.stat, 0, sizeof(*sc->rxq.stat)); /* stop DMA */ IWM_WRITE(sc, IWM_FH_MEM_RCSR_CHNL0_CONFIG_REG, 0); IWM_WRITE(sc, IWM_FH_MEM_RCSR_CHNL0_RBDCB_WPTR, 0); IWM_WRITE(sc, IWM_FH_MEM_RCSR_CHNL0_FLUSH_RB_REQ, 0); IWM_WRITE(sc, IWM_FH_RSCSR_CHNL0_RDPTR, 0); IWM_WRITE(sc, IWM_FH_RSCSR_CHNL0_RBDCB_WPTR_REG, 0); /* Set physical address of RX ring (256-byte aligned). */ IWM_WRITE(sc, IWM_FH_RSCSR_CHNL0_RBDCB_BASE_REG, sc->rxq.desc_dma.paddr >> 8); /* Set physical address of RX status (16-byte aligned). */ IWM_WRITE(sc, IWM_FH_RSCSR_CHNL0_STTS_WPTR_REG, sc->rxq.stat_dma.paddr >> 4); /* Enable RX. */ /* * Note: Linux driver also sets this: * (IWM_RX_RB_TIMEOUT << IWM_FH_RCSR_RX_CONFIG_REG_IRQ_RBTH_POS) | * * It causes weird behavior. YMMV. */ IWM_WRITE(sc, IWM_FH_MEM_RCSR_CHNL0_CONFIG_REG, IWM_FH_RCSR_RX_CONFIG_CHNL_EN_ENABLE_VAL | IWM_FH_RCSR_CHNL0_RX_IGNORE_RXF_EMPTY | /* HW bug */ IWM_FH_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_INT_HOST_VAL | IWM_FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_4K | IWM_RX_QUEUE_SIZE_LOG << IWM_FH_RCSR_RX_CONFIG_RBDCB_SIZE_POS); IWM_WRITE_1(sc, IWM_CSR_INT_COALESCING, IWM_HOST_INT_TIMEOUT_DEF); /* W/A for interrupt coalescing bug in 7260 and 3160 */ if (sc->host_interrupt_operation_mode) IWM_SETBITS(sc, IWM_CSR_INT_COALESCING, IWM_HOST_INT_OPER_MODE); /* * Thus sayeth el jefe (iwlwifi) via a comment: * * This value should initially be 0 (before preparing any * RBs), should be 8 after preparing the first 8 RBs (for example) */ IWM_WRITE(sc, IWM_FH_RSCSR_CHNL0_WPTR, 8); iwm_nic_unlock(sc); return 0; } static int iwm_nic_tx_init(struct iwm_softc *sc) { int qid; if (!iwm_nic_lock(sc)) return EBUSY; /* Deactivate TX scheduler. */ iwm_write_prph(sc, IWM_SCD_TXFACT, 0); /* Set physical address of "keep warm" page (16-byte aligned). */ IWM_WRITE(sc, IWM_FH_KW_MEM_ADDR_REG, sc->kw_dma.paddr >> 4); /* Initialize TX rings. */ for (qid = 0; qid < nitems(sc->txq); qid++) { struct iwm_tx_ring *txq = &sc->txq[qid]; /* Set physical address of TX ring (256-byte aligned). */ IWM_WRITE(sc, IWM_FH_MEM_CBBC_QUEUE(qid), txq->desc_dma.paddr >> 8); IWM_DPRINTF(sc, IWM_DEBUG_XMIT, "%s: loading ring %d descriptors (%p) at %lx\n", __func__, qid, txq->desc, (unsigned long) (txq->desc_dma.paddr >> 8)); } iwm_nic_unlock(sc); return 0; } static int iwm_nic_init(struct iwm_softc *sc) { int error; iwm_apm_init(sc); iwm_set_pwr(sc); iwm_mvm_nic_config(sc); if ((error = iwm_nic_rx_init(sc)) != 0) return error; /* * Ditto for TX, from iwn */ if ((error = iwm_nic_tx_init(sc)) != 0) return error; IWM_DPRINTF(sc, IWM_DEBUG_RESET, "%s: shadow registers enabled\n", __func__); IWM_SETBITS(sc, IWM_CSR_MAC_SHADOW_REG_CTRL, 0x800fffff); return 0; } enum iwm_mvm_tx_fifo { IWM_MVM_TX_FIFO_BK = 0, IWM_MVM_TX_FIFO_BE, IWM_MVM_TX_FIFO_VI, IWM_MVM_TX_FIFO_VO, IWM_MVM_TX_FIFO_MCAST = 5, }; const uint8_t iwm_mvm_ac_to_tx_fifo[] = { IWM_MVM_TX_FIFO_VO, IWM_MVM_TX_FIFO_VI, IWM_MVM_TX_FIFO_BE, IWM_MVM_TX_FIFO_BK, }; static void iwm_enable_txq(struct iwm_softc *sc, int qid, int fifo) { if (!iwm_nic_lock(sc)) { device_printf(sc->sc_dev, "%s: cannot enable txq %d\n", __func__, qid); return; /* XXX return EBUSY */ } /* unactivate before configuration */ iwm_write_prph(sc, IWM_SCD_QUEUE_STATUS_BITS(qid), (0 << IWM_SCD_QUEUE_STTS_REG_POS_ACTIVE) | (1 << IWM_SCD_QUEUE_STTS_REG_POS_SCD_ACT_EN)); if (qid != IWM_MVM_CMD_QUEUE) { iwm_set_bits_prph(sc, IWM_SCD_QUEUECHAIN_SEL, (1 << qid)); } iwm_clear_bits_prph(sc, IWM_SCD_AGGR_SEL, (1 << qid)); IWM_WRITE(sc, IWM_HBUS_TARG_WRPTR, qid << 8 | 0); iwm_write_prph(sc, IWM_SCD_QUEUE_RDPTR(qid), 0); iwm_write_mem32(sc, sc->sched_base + IWM_SCD_CONTEXT_QUEUE_OFFSET(qid), 0); /* Set scheduler window size and frame limit. */ iwm_write_mem32(sc, sc->sched_base + IWM_SCD_CONTEXT_QUEUE_OFFSET(qid) + sizeof(uint32_t), ((IWM_FRAME_LIMIT << IWM_SCD_QUEUE_CTX_REG2_WIN_SIZE_POS) & IWM_SCD_QUEUE_CTX_REG2_WIN_SIZE_MSK) | ((IWM_FRAME_LIMIT << IWM_SCD_QUEUE_CTX_REG2_FRAME_LIMIT_POS) & IWM_SCD_QUEUE_CTX_REG2_FRAME_LIMIT_MSK)); iwm_write_prph(sc, IWM_SCD_QUEUE_STATUS_BITS(qid), (1 << IWM_SCD_QUEUE_STTS_REG_POS_ACTIVE) | (fifo << IWM_SCD_QUEUE_STTS_REG_POS_TXF) | (1 << IWM_SCD_QUEUE_STTS_REG_POS_WSL) | IWM_SCD_QUEUE_STTS_REG_MSK); iwm_nic_unlock(sc); IWM_DPRINTF(sc, IWM_DEBUG_XMIT, "%s: enabled txq %d FIFO %d\n", __func__, qid, fifo); } static int iwm_post_alive(struct iwm_softc *sc) { int nwords; int error, chnl; if (!iwm_nic_lock(sc)) return EBUSY; if (sc->sched_base != iwm_read_prph(sc, IWM_SCD_SRAM_BASE_ADDR)) { device_printf(sc->sc_dev, "%s: sched addr mismatch", __func__); error = EINVAL; goto out; } iwm_ict_reset(sc); /* Clear TX scheduler state in SRAM. */ nwords = (IWM_SCD_TRANS_TBL_MEM_UPPER_BOUND - IWM_SCD_CONTEXT_MEM_LOWER_BOUND) / sizeof(uint32_t); error = iwm_write_mem(sc, sc->sched_base + IWM_SCD_CONTEXT_MEM_LOWER_BOUND, NULL, nwords); if (error) goto out; /* Set physical address of TX scheduler rings (1KB aligned). */ iwm_write_prph(sc, IWM_SCD_DRAM_BASE_ADDR, sc->sched_dma.paddr >> 10); iwm_write_prph(sc, IWM_SCD_CHAINEXT_EN, 0); /* enable command channel */ iwm_enable_txq(sc, IWM_MVM_CMD_QUEUE, 7); iwm_write_prph(sc, IWM_SCD_TXFACT, 0xff); /* Enable DMA channels. */ for (chnl = 0; chnl < IWM_FH_TCSR_CHNL_NUM; chnl++) { IWM_WRITE(sc, IWM_FH_TCSR_CHNL_TX_CONFIG_REG(chnl), IWM_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE | IWM_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_ENABLE); } IWM_SETBITS(sc, IWM_FH_TX_CHICKEN_BITS_REG, IWM_FH_TX_CHICKEN_BITS_SCD_AUTO_RETRY_EN); /* Enable L1-Active */ iwm_clear_bits_prph(sc, IWM_APMG_PCIDEV_STT_REG, IWM_APMG_PCIDEV_STT_VAL_L1_ACT_DIS); out: iwm_nic_unlock(sc); return error; } /* * NVM read access and content parsing. We do not support * external NVM or writing NVM. * iwlwifi/mvm/nvm.c */ /* list of NVM sections we are allowed/need to read */ const int nvm_to_read[] = { IWM_NVM_SECTION_TYPE_HW, IWM_NVM_SECTION_TYPE_SW, IWM_NVM_SECTION_TYPE_CALIBRATION, IWM_NVM_SECTION_TYPE_PRODUCTION, }; /* Default NVM size to read */ #define IWM_NVM_DEFAULT_CHUNK_SIZE (2*1024) #define IWM_MAX_NVM_SECTION_SIZE 7000 #define IWM_NVM_WRITE_OPCODE 1 #define IWM_NVM_READ_OPCODE 0 static int iwm_nvm_read_chunk(struct iwm_softc *sc, uint16_t section, uint16_t offset, uint16_t length, uint8_t *data, uint16_t *len) { offset = 0; struct iwm_nvm_access_cmd nvm_access_cmd = { .offset = htole16(offset), .length = htole16(length), .type = htole16(section), .op_code = IWM_NVM_READ_OPCODE, }; struct iwm_nvm_access_resp *nvm_resp; struct iwm_rx_packet *pkt; struct iwm_host_cmd cmd = { .id = IWM_NVM_ACCESS_CMD, .flags = IWM_CMD_SYNC | IWM_CMD_WANT_SKB | IWM_CMD_SEND_IN_RFKILL, .data = { &nvm_access_cmd, }, }; int ret, bytes_read, offset_read; uint8_t *resp_data; cmd.len[0] = sizeof(struct iwm_nvm_access_cmd); ret = iwm_send_cmd(sc, &cmd); if (ret) return ret; pkt = cmd.resp_pkt; if (pkt->hdr.flags & IWM_CMD_FAILED_MSK) { device_printf(sc->sc_dev, "%s: Bad return from IWM_NVM_ACCES_COMMAND (0x%08X)\n", __func__, pkt->hdr.flags); ret = EIO; goto exit; } /* Extract NVM response */ nvm_resp = (void *)pkt->data; ret = le16toh(nvm_resp->status); bytes_read = le16toh(nvm_resp->length); offset_read = le16toh(nvm_resp->offset); resp_data = nvm_resp->data; if (ret) { device_printf(sc->sc_dev, "%s: NVM access command failed with status %d\n", __func__, ret); ret = EINVAL; goto exit; } if (offset_read != offset) { device_printf(sc->sc_dev, "%s: NVM ACCESS response with invalid offset %d\n", __func__, offset_read); ret = EINVAL; goto exit; } memcpy(data + offset, resp_data, bytes_read); *len = bytes_read; exit: iwm_free_resp(sc, &cmd); return ret; } /* * Reads an NVM section completely. * NICs prior to 7000 family doesn't have a real NVM, but just read * section 0 which is the EEPROM. Because the EEPROM reading is unlimited * by uCode, we need to manually check in this case that we don't * overflow and try to read more than the EEPROM size. * For 7000 family NICs, we supply the maximal size we can read, and * the uCode fills the response with as much data as we can, * without overflowing, so no check is needed. */ static int iwm_nvm_read_section(struct iwm_softc *sc, uint16_t section, uint8_t *data, uint16_t *len) { uint16_t length, seglen; int error; /* Set nvm section read length */ length = seglen = IWM_NVM_DEFAULT_CHUNK_SIZE; *len = 0; /* Read the NVM until exhausted (reading less than requested) */ while (seglen == length) { error = iwm_nvm_read_chunk(sc, section, *len, length, data, &seglen); if (error) { device_printf(sc->sc_dev, "Cannot read NVM from section " "%d offset %d, length %d\n", section, *len, length); return error; } *len += seglen; } IWM_DPRINTF(sc, IWM_DEBUG_RESET, "NVM section %d read completed\n", section); return 0; } /* * BEGIN IWM_NVM_PARSE */ /* iwlwifi/iwl-nvm-parse.c */ /* NVM offsets (in words) definitions */ enum wkp_nvm_offsets { /* NVM HW-Section offset (in words) definitions */ IWM_HW_ADDR = 0x15, /* NVM SW-Section offset (in words) definitions */ IWM_NVM_SW_SECTION = 0x1C0, IWM_NVM_VERSION = 0, IWM_RADIO_CFG = 1, IWM_SKU = 2, IWM_N_HW_ADDRS = 3, IWM_NVM_CHANNELS = 0x1E0 - IWM_NVM_SW_SECTION, /* NVM calibration section offset (in words) definitions */ IWM_NVM_CALIB_SECTION = 0x2B8, IWM_XTAL_CALIB = 0x316 - IWM_NVM_CALIB_SECTION }; /* SKU Capabilities (actual values from NVM definition) */ enum nvm_sku_bits { IWM_NVM_SKU_CAP_BAND_24GHZ = (1 << 0), IWM_NVM_SKU_CAP_BAND_52GHZ = (1 << 1), IWM_NVM_SKU_CAP_11N_ENABLE = (1 << 2), IWM_NVM_SKU_CAP_11AC_ENABLE = (1 << 3), }; /* radio config bits (actual values from NVM definition) */ #define IWM_NVM_RF_CFG_DASH_MSK(x) (x & 0x3) /* bits 0-1 */ #define IWM_NVM_RF_CFG_STEP_MSK(x) ((x >> 2) & 0x3) /* bits 2-3 */ #define IWM_NVM_RF_CFG_TYPE_MSK(x) ((x >> 4) & 0x3) /* bits 4-5 */ #define IWM_NVM_RF_CFG_PNUM_MSK(x) ((x >> 6) & 0x3) /* bits 6-7 */ #define IWM_NVM_RF_CFG_TX_ANT_MSK(x) ((x >> 8) & 0xF) /* bits 8-11 */ #define IWM_NVM_RF_CFG_RX_ANT_MSK(x) ((x >> 12) & 0xF) /* bits 12-15 */ #define DEFAULT_MAX_TX_POWER 16 /** * enum iwm_nvm_channel_flags - channel flags in NVM * @IWM_NVM_CHANNEL_VALID: channel is usable for this SKU/geo * @IWM_NVM_CHANNEL_IBSS: usable as an IBSS channel * @IWM_NVM_CHANNEL_ACTIVE: active scanning allowed * @IWM_NVM_CHANNEL_RADAR: radar detection required * @IWM_NVM_CHANNEL_DFS: dynamic freq selection candidate * @IWM_NVM_CHANNEL_WIDE: 20 MHz channel okay (?) * @IWM_NVM_CHANNEL_40MHZ: 40 MHz channel okay (?) * @IWM_NVM_CHANNEL_80MHZ: 80 MHz channel okay (?) * @IWM_NVM_CHANNEL_160MHZ: 160 MHz channel okay (?) */ enum iwm_nvm_channel_flags { IWM_NVM_CHANNEL_VALID = (1 << 0), IWM_NVM_CHANNEL_IBSS = (1 << 1), IWM_NVM_CHANNEL_ACTIVE = (1 << 3), IWM_NVM_CHANNEL_RADAR = (1 << 4), IWM_NVM_CHANNEL_DFS = (1 << 7), IWM_NVM_CHANNEL_WIDE = (1 << 8), IWM_NVM_CHANNEL_40MHZ = (1 << 9), IWM_NVM_CHANNEL_80MHZ = (1 << 10), IWM_NVM_CHANNEL_160MHZ = (1 << 11), }; /* * Add a channel to the net80211 channel list. * * ieee is the ieee channel number * ch_idx is channel index. * mode is the channel mode - CHAN_A, CHAN_B, CHAN_G. * ch_flags is the iwm channel flags. * * Return 0 on OK, < 0 on error. */ static int iwm_init_net80211_channel(struct iwm_softc *sc, int ieee, int ch_idx, int mode, uint16_t ch_flags) { /* XXX for now, no overflow checking! */ struct ieee80211com *ic = &sc->sc_ic; int is_5ghz, flags; struct ieee80211_channel *channel; channel = &ic->ic_channels[ic->ic_nchans++]; channel->ic_ieee = ieee; is_5ghz = ch_idx >= IWM_NUM_2GHZ_CHANNELS; if (!is_5ghz) { flags = IEEE80211_CHAN_2GHZ; channel->ic_flags = mode; } else { flags = IEEE80211_CHAN_5GHZ; channel->ic_flags = mode; } channel->ic_freq = ieee80211_ieee2mhz(ieee, flags); if (!(ch_flags & IWM_NVM_CHANNEL_ACTIVE)) channel->ic_flags |= IEEE80211_CHAN_PASSIVE; return (0); } static void iwm_init_channel_map(struct iwm_softc *sc, const uint16_t * const nvm_ch_flags) { struct ieee80211com *ic = &sc->sc_ic; struct iwm_nvm_data *data = &sc->sc_nvm; int ch_idx; uint16_t ch_flags; int hw_value; for (ch_idx = 0; ch_idx < nitems(iwm_nvm_channels); ch_idx++) { ch_flags = le16_to_cpup(nvm_ch_flags + ch_idx); if (ch_idx >= IWM_NUM_2GHZ_CHANNELS && !data->sku_cap_band_52GHz_enable) ch_flags &= ~IWM_NVM_CHANNEL_VALID; if (!(ch_flags & IWM_NVM_CHANNEL_VALID)) { IWM_DPRINTF(sc, IWM_DEBUG_EEPROM, "Ch. %d Flags %x [%sGHz] - No traffic\n", iwm_nvm_channels[ch_idx], ch_flags, (ch_idx >= IWM_NUM_2GHZ_CHANNELS) ? "5.2" : "2.4"); continue; } hw_value = iwm_nvm_channels[ch_idx]; /* 5GHz? */ if (ch_idx >= IWM_NUM_2GHZ_CHANNELS) { (void) iwm_init_net80211_channel(sc, hw_value, ch_idx, IEEE80211_CHAN_A, ch_flags); } else { (void) iwm_init_net80211_channel(sc, hw_value, ch_idx, IEEE80211_CHAN_B, ch_flags); /* If it's not channel 13, also add 11g */ if (hw_value != 13) (void) iwm_init_net80211_channel(sc, hw_value, ch_idx, IEEE80211_CHAN_G, ch_flags); } IWM_DPRINTF(sc, IWM_DEBUG_EEPROM, "Ch. %d Flags %x [%sGHz] - Added\n", iwm_nvm_channels[ch_idx], ch_flags, (ch_idx >= IWM_NUM_2GHZ_CHANNELS) ? "5.2" : "2.4"); } ieee80211_sort_channels(ic->ic_channels, ic->ic_nchans); } static int iwm_parse_nvm_data(struct iwm_softc *sc, const uint16_t *nvm_hw, const uint16_t *nvm_sw, const uint16_t *nvm_calib, uint8_t tx_chains, uint8_t rx_chains) { struct iwm_nvm_data *data = &sc->sc_nvm; uint8_t hw_addr[IEEE80211_ADDR_LEN]; uint16_t radio_cfg, sku; data->nvm_version = le16_to_cpup(nvm_sw + IWM_NVM_VERSION); radio_cfg = le16_to_cpup(nvm_sw + IWM_RADIO_CFG); data->radio_cfg_type = IWM_NVM_RF_CFG_TYPE_MSK(radio_cfg); data->radio_cfg_step = IWM_NVM_RF_CFG_STEP_MSK(radio_cfg); data->radio_cfg_dash = IWM_NVM_RF_CFG_DASH_MSK(radio_cfg); data->radio_cfg_pnum = IWM_NVM_RF_CFG_PNUM_MSK(radio_cfg); data->valid_tx_ant = IWM_NVM_RF_CFG_TX_ANT_MSK(radio_cfg); data->valid_rx_ant = IWM_NVM_RF_CFG_RX_ANT_MSK(radio_cfg); sku = le16_to_cpup(nvm_sw + IWM_SKU); data->sku_cap_band_24GHz_enable = sku & IWM_NVM_SKU_CAP_BAND_24GHZ; data->sku_cap_band_52GHz_enable = sku & IWM_NVM_SKU_CAP_BAND_52GHZ; data->sku_cap_11n_enable = 0; if (!data->valid_tx_ant || !data->valid_rx_ant) { device_printf(sc->sc_dev, "%s: invalid antennas (0x%x, 0x%x)\n", __func__, data->valid_tx_ant, data->valid_rx_ant); return EINVAL; } data->n_hw_addrs = le16_to_cpup(nvm_sw + IWM_N_HW_ADDRS); data->xtal_calib[0] = *(nvm_calib + IWM_XTAL_CALIB); data->xtal_calib[1] = *(nvm_calib + IWM_XTAL_CALIB + 1); /* The byte order is little endian 16 bit, meaning 214365 */ IEEE80211_ADDR_COPY(hw_addr, nvm_hw + IWM_HW_ADDR); data->hw_addr[0] = hw_addr[1]; data->hw_addr[1] = hw_addr[0]; data->hw_addr[2] = hw_addr[3]; data->hw_addr[3] = hw_addr[2]; data->hw_addr[4] = hw_addr[5]; data->hw_addr[5] = hw_addr[4]; iwm_init_channel_map(sc, &nvm_sw[IWM_NVM_CHANNELS]); data->calib_version = 255; /* TODO: this value will prevent some checks from failing, we need to check if this field is still needed, and if it does, where is it in the NVM */ return 0; } /* * END NVM PARSE */ struct iwm_nvm_section { uint16_t length; const uint8_t *data; }; static int iwm_parse_nvm_sections(struct iwm_softc *sc, struct iwm_nvm_section *sections) { const uint16_t *hw, *sw, *calib; /* Checking for required sections */ if (!sections[IWM_NVM_SECTION_TYPE_SW].data || !sections[IWM_NVM_SECTION_TYPE_HW].data) { device_printf(sc->sc_dev, "%s: Can't parse empty NVM sections\n", __func__); return ENOENT; } hw = (const uint16_t *)sections[IWM_NVM_SECTION_TYPE_HW].data; sw = (const uint16_t *)sections[IWM_NVM_SECTION_TYPE_SW].data; calib = (const uint16_t *)sections[IWM_NVM_SECTION_TYPE_CALIBRATION].data; return iwm_parse_nvm_data(sc, hw, sw, calib, IWM_FW_VALID_TX_ANT(sc), IWM_FW_VALID_RX_ANT(sc)); } static int iwm_nvm_init(struct iwm_softc *sc) { struct iwm_nvm_section nvm_sections[IWM_NVM_NUM_OF_SECTIONS]; int i, section, error; uint16_t len; uint8_t *nvm_buffer, *temp; /* Read From FW NVM */ IWM_DPRINTF(sc, IWM_DEBUG_EEPROM, "%s: Read NVM\n", __func__); /* TODO: find correct NVM max size for a section */ nvm_buffer = malloc(IWM_OTP_LOW_IMAGE_SIZE, M_DEVBUF, M_NOWAIT); if (nvm_buffer == NULL) return (ENOMEM); for (i = 0; i < nitems(nvm_to_read); i++) { section = nvm_to_read[i]; KASSERT(section <= nitems(nvm_sections), ("too many sections")); error = iwm_nvm_read_section(sc, section, nvm_buffer, &len); if (error) break; temp = malloc(len, M_DEVBUF, M_NOWAIT); if (temp == NULL) { error = ENOMEM; break; } memcpy(temp, nvm_buffer, len); nvm_sections[section].data = temp; nvm_sections[section].length = len; } free(nvm_buffer, M_DEVBUF); if (error) return error; return iwm_parse_nvm_sections(sc, nvm_sections); } /* * Firmware loading gunk. This is kind of a weird hybrid between the * iwn driver and the Linux iwlwifi driver. */ static int iwm_firmware_load_chunk(struct iwm_softc *sc, uint32_t dst_addr, const uint8_t *section, uint32_t byte_cnt) { struct iwm_dma_info *dma = &sc->fw_dma; int error; /* Copy firmware section into pre-allocated DMA-safe memory. */ memcpy(dma->vaddr, section, byte_cnt); bus_dmamap_sync(dma->tag, dma->map, BUS_DMASYNC_PREWRITE); if (!iwm_nic_lock(sc)) return EBUSY; sc->sc_fw_chunk_done = 0; IWM_WRITE(sc, IWM_FH_TCSR_CHNL_TX_CONFIG_REG(IWM_FH_SRVC_CHNL), IWM_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_PAUSE); IWM_WRITE(sc, IWM_FH_SRVC_CHNL_SRAM_ADDR_REG(IWM_FH_SRVC_CHNL), dst_addr); IWM_WRITE(sc, IWM_FH_TFDIB_CTRL0_REG(IWM_FH_SRVC_CHNL), dma->paddr & IWM_FH_MEM_TFDIB_DRAM_ADDR_LSB_MSK); IWM_WRITE(sc, IWM_FH_TFDIB_CTRL1_REG(IWM_FH_SRVC_CHNL), (iwm_get_dma_hi_addr(dma->paddr) << IWM_FH_MEM_TFDIB_REG1_ADDR_BITSHIFT) | byte_cnt); IWM_WRITE(sc, IWM_FH_TCSR_CHNL_TX_BUF_STS_REG(IWM_FH_SRVC_CHNL), 1 << IWM_FH_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_NUM | 1 << IWM_FH_TCSR_CHNL_TX_BUF_STS_REG_POS_TB_IDX | IWM_FH_TCSR_CHNL_TX_BUF_STS_REG_VAL_TFDB_VALID); IWM_WRITE(sc, IWM_FH_TCSR_CHNL_TX_CONFIG_REG(IWM_FH_SRVC_CHNL), IWM_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CHNL_ENABLE | IWM_FH_TCSR_TX_CONFIG_REG_VAL_DMA_CREDIT_DISABLE | IWM_FH_TCSR_TX_CONFIG_REG_VAL_CIRQ_HOST_ENDTFD); iwm_nic_unlock(sc); /* wait 1s for this segment to load */ while (!sc->sc_fw_chunk_done) if ((error = msleep(&sc->sc_fw, &sc->sc_mtx, 0, "iwmfw", hz)) != 0) break; return error; } static int iwm_load_firmware(struct iwm_softc *sc, enum iwm_ucode_type ucode_type) { struct iwm_fw_sects *fws; int error, i, w; const void *data; uint32_t dlen; uint32_t offset; sc->sc_uc.uc_intr = 0; fws = &sc->sc_fw.fw_sects[ucode_type]; for (i = 0; i < fws->fw_count; i++) { data = fws->fw_sect[i].fws_data; dlen = fws->fw_sect[i].fws_len; offset = fws->fw_sect[i].fws_devoff; IWM_DPRINTF(sc, IWM_DEBUG_FIRMWARE_TLV, "LOAD FIRMWARE type %d offset %u len %d\n", ucode_type, offset, dlen); error = iwm_firmware_load_chunk(sc, offset, data, dlen); if (error) { device_printf(sc->sc_dev, "%s: chunk %u of %u returned error %02d\n", __func__, i, fws->fw_count, error); return error; } } /* wait for the firmware to load */ IWM_WRITE(sc, IWM_CSR_RESET, 0); for (w = 0; !sc->sc_uc.uc_intr && w < 10; w++) { error = msleep(&sc->sc_uc, &sc->sc_mtx, 0, "iwmuc", hz/10); } return error; } /* iwlwifi: pcie/trans.c */ static int iwm_start_fw(struct iwm_softc *sc, enum iwm_ucode_type ucode_type) { int error; IWM_WRITE(sc, IWM_CSR_INT, ~0); if ((error = iwm_nic_init(sc)) != 0) { device_printf(sc->sc_dev, "unable to init nic\n"); return error; } /* make sure rfkill handshake bits are cleared */ IWM_WRITE(sc, IWM_CSR_UCODE_DRV_GP1_CLR, IWM_CSR_UCODE_SW_BIT_RFKILL); IWM_WRITE(sc, IWM_CSR_UCODE_DRV_GP1_CLR, IWM_CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED); /* clear (again), then enable host interrupts */ IWM_WRITE(sc, IWM_CSR_INT, ~0); iwm_enable_interrupts(sc); /* really make sure rfkill handshake bits are cleared */ /* maybe we should write a few times more? just to make sure */ IWM_WRITE(sc, IWM_CSR_UCODE_DRV_GP1_CLR, IWM_CSR_UCODE_SW_BIT_RFKILL); IWM_WRITE(sc, IWM_CSR_UCODE_DRV_GP1_CLR, IWM_CSR_UCODE_SW_BIT_RFKILL); /* Load the given image to the HW */ return iwm_load_firmware(sc, ucode_type); } static int iwm_fw_alive(struct iwm_softc *sc, uint32_t sched_base) { return iwm_post_alive(sc); } static int iwm_send_tx_ant_cfg(struct iwm_softc *sc, uint8_t valid_tx_ant) { struct iwm_tx_ant_cfg_cmd tx_ant_cmd = { .valid = htole32(valid_tx_ant), }; return iwm_mvm_send_cmd_pdu(sc, IWM_TX_ANT_CONFIGURATION_CMD, IWM_CMD_SYNC, sizeof(tx_ant_cmd), &tx_ant_cmd); } /* iwlwifi: mvm/fw.c */ static int iwm_send_phy_cfg_cmd(struct iwm_softc *sc) { struct iwm_phy_cfg_cmd phy_cfg_cmd; enum iwm_ucode_type ucode_type = sc->sc_uc_current; /* Set parameters */ phy_cfg_cmd.phy_cfg = htole32(sc->sc_fw_phy_config); phy_cfg_cmd.calib_control.event_trigger = sc->sc_default_calib[ucode_type].event_trigger; phy_cfg_cmd.calib_control.flow_trigger = sc->sc_default_calib[ucode_type].flow_trigger; IWM_DPRINTF(sc, IWM_DEBUG_CMD | IWM_DEBUG_RESET, "Sending Phy CFG command: 0x%x\n", phy_cfg_cmd.phy_cfg); return iwm_mvm_send_cmd_pdu(sc, IWM_PHY_CONFIGURATION_CMD, IWM_CMD_SYNC, sizeof(phy_cfg_cmd), &phy_cfg_cmd); } static int iwm_mvm_load_ucode_wait_alive(struct iwm_softc *sc, enum iwm_ucode_type ucode_type) { enum iwm_ucode_type old_type = sc->sc_uc_current; int error; if ((error = iwm_read_firmware(sc, ucode_type)) != 0) return error; sc->sc_uc_current = ucode_type; error = iwm_start_fw(sc, ucode_type); if (error) { sc->sc_uc_current = old_type; return error; } return iwm_fw_alive(sc, sc->sched_base); } /* * mvm misc bits */ /* * follows iwlwifi/fw.c */ static int iwm_run_init_mvm_ucode(struct iwm_softc *sc, int justnvm) { int error; /* do not operate with rfkill switch turned on */ if ((sc->sc_flags & IWM_FLAG_RFKILL) && !justnvm) { device_printf(sc->sc_dev, "radio is disabled by hardware switch\n"); return EPERM; } sc->sc_init_complete = 0; if ((error = iwm_mvm_load_ucode_wait_alive(sc, IWM_UCODE_TYPE_INIT)) != 0) return error; if (justnvm) { if ((error = iwm_nvm_init(sc)) != 0) { device_printf(sc->sc_dev, "failed to read nvm\n"); return error; } IEEE80211_ADDR_COPY(sc->sc_ic.ic_macaddr, &sc->sc_nvm.hw_addr); sc->sc_scan_cmd_len = sizeof(struct iwm_scan_cmd) + sc->sc_capa_max_probe_len + IWM_MAX_NUM_SCAN_CHANNELS * sizeof(struct iwm_scan_channel); sc->sc_scan_cmd = malloc(sc->sc_scan_cmd_len, M_DEVBUF, M_NOWAIT); if (sc->sc_scan_cmd == NULL) return (ENOMEM); return 0; } /* Send TX valid antennas before triggering calibrations */ if ((error = iwm_send_tx_ant_cfg(sc, IWM_FW_VALID_TX_ANT(sc))) != 0) return error; /* * Send phy configurations command to init uCode * to start the 16.0 uCode init image internal calibrations. */ if ((error = iwm_send_phy_cfg_cmd(sc)) != 0 ) { device_printf(sc->sc_dev, "%s: failed to run internal calibration: %d\n", __func__, error); return error; } /* * Nothing to do but wait for the init complete notification * from the firmware */ while (!sc->sc_init_complete) if ((error = msleep(&sc->sc_init_complete, &sc->sc_mtx, 0, "iwminit", 2*hz)) != 0) break; return error; } /* * receive side */ /* (re)stock rx ring, called at init-time and at runtime */ static int iwm_rx_addbuf(struct iwm_softc *sc, int size, int idx) { struct iwm_rx_ring *ring = &sc->rxq; struct iwm_rx_data *data = &ring->data[idx]; struct mbuf *m; int error; bus_addr_t paddr; m = m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR, IWM_RBUF_SIZE); if (m == NULL) return ENOBUFS; if (data->m != NULL) bus_dmamap_unload(ring->data_dmat, data->map); m->m_len = m->m_pkthdr.len = m->m_ext.ext_size; error = bus_dmamap_create(ring->data_dmat, 0, &data->map); if (error != 0) { device_printf(sc->sc_dev, "%s: could not create RX buf DMA map, error %d\n", __func__, error); goto fail; } data->m = m; error = bus_dmamap_load(ring->data_dmat, data->map, mtod(data->m, void *), IWM_RBUF_SIZE, iwm_dma_map_addr, &paddr, BUS_DMA_NOWAIT); if (error != 0 && error != EFBIG) { device_printf(sc->sc_dev, "%s: can't not map mbuf, error %d\n", __func__, error); goto fail; } bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_PREREAD); /* Update RX descriptor. */ ring->desc[idx] = htole32(paddr >> 8); bus_dmamap_sync(ring->desc_dma.tag, ring->desc_dma.map, BUS_DMASYNC_PREWRITE); return 0; fail: return error; } /* iwlwifi: mvm/rx.c */ #define IWM_RSSI_OFFSET 50 static int iwm_mvm_calc_rssi(struct iwm_softc *sc, struct iwm_rx_phy_info *phy_info) { int rssi_a, rssi_b, rssi_a_dbm, rssi_b_dbm, max_rssi_dbm; uint32_t agc_a, agc_b; uint32_t val; val = le32toh(phy_info->non_cfg_phy[IWM_RX_INFO_AGC_IDX]); agc_a = (val & IWM_OFDM_AGC_A_MSK) >> IWM_OFDM_AGC_A_POS; agc_b = (val & IWM_OFDM_AGC_B_MSK) >> IWM_OFDM_AGC_B_POS; val = le32toh(phy_info->non_cfg_phy[IWM_RX_INFO_RSSI_AB_IDX]); rssi_a = (val & IWM_OFDM_RSSI_INBAND_A_MSK) >> IWM_OFDM_RSSI_A_POS; rssi_b = (val & IWM_OFDM_RSSI_INBAND_B_MSK) >> IWM_OFDM_RSSI_B_POS; /* * dBm = rssi dB - agc dB - constant. * Higher AGC (higher radio gain) means lower signal. */ rssi_a_dbm = rssi_a - IWM_RSSI_OFFSET - agc_a; rssi_b_dbm = rssi_b - IWM_RSSI_OFFSET - agc_b; max_rssi_dbm = MAX(rssi_a_dbm, rssi_b_dbm); IWM_DPRINTF(sc, IWM_DEBUG_RECV, "Rssi In A %d B %d Max %d AGCA %d AGCB %d\n", rssi_a_dbm, rssi_b_dbm, max_rssi_dbm, agc_a, agc_b); return max_rssi_dbm; } /* iwlwifi: mvm/rx.c */ /* * iwm_mvm_get_signal_strength - use new rx PHY INFO API * values are reported by the fw as positive values - need to negate * to obtain their dBM. Account for missing antennas by replacing 0 * values by -256dBm: practically 0 power and a non-feasible 8 bit value. */ static int iwm_mvm_get_signal_strength(struct iwm_softc *sc, struct iwm_rx_phy_info *phy_info) { int energy_a, energy_b, energy_c, max_energy; uint32_t val; val = le32toh(phy_info->non_cfg_phy[IWM_RX_INFO_ENERGY_ANT_ABC_IDX]); energy_a = (val & IWM_RX_INFO_ENERGY_ANT_A_MSK) >> IWM_RX_INFO_ENERGY_ANT_A_POS; energy_a = energy_a ? -energy_a : -256; energy_b = (val & IWM_RX_INFO_ENERGY_ANT_B_MSK) >> IWM_RX_INFO_ENERGY_ANT_B_POS; energy_b = energy_b ? -energy_b : -256; energy_c = (val & IWM_RX_INFO_ENERGY_ANT_C_MSK) >> IWM_RX_INFO_ENERGY_ANT_C_POS; energy_c = energy_c ? -energy_c : -256; max_energy = MAX(energy_a, energy_b); max_energy = MAX(max_energy, energy_c); IWM_DPRINTF(sc, IWM_DEBUG_RECV, "energy In A %d B %d C %d , and max %d\n", energy_a, energy_b, energy_c, max_energy); return max_energy; } static void iwm_mvm_rx_rx_phy_cmd(struct iwm_softc *sc, struct iwm_rx_packet *pkt, struct iwm_rx_data *data) { struct iwm_rx_phy_info *phy_info = (void *)pkt->data; IWM_DPRINTF(sc, IWM_DEBUG_RECV, "received PHY stats\n"); bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD); memcpy(&sc->sc_last_phy_info, phy_info, sizeof(sc->sc_last_phy_info)); } /* * Retrieve the average noise (in dBm) among receivers. */ static int iwm_get_noise(const struct iwm_mvm_statistics_rx_non_phy *stats) { int i, total, nbant, noise; total = nbant = noise = 0; for (i = 0; i < 3; i++) { noise = le32toh(stats->beacon_silence_rssi[i]) & 0xff; if (noise) { total += noise; nbant++; } } /* There should be at least one antenna but check anyway. */ return (nbant == 0) ? -127 : (total / nbant) - 107; } /* * iwm_mvm_rx_rx_mpdu - IWM_REPLY_RX_MPDU_CMD handler * * Handles the actual data of the Rx packet from the fw */ static void iwm_mvm_rx_rx_mpdu(struct iwm_softc *sc, struct iwm_rx_packet *pkt, struct iwm_rx_data *data) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); struct ieee80211_frame *wh; struct ieee80211_node *ni; struct ieee80211_rx_stats rxs; struct mbuf *m; struct iwm_rx_phy_info *phy_info; struct iwm_rx_mpdu_res_start *rx_res; uint32_t len; uint32_t rx_pkt_status; int rssi; bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD); phy_info = &sc->sc_last_phy_info; rx_res = (struct iwm_rx_mpdu_res_start *)pkt->data; wh = (struct ieee80211_frame *)(pkt->data + sizeof(*rx_res)); len = le16toh(rx_res->byte_count); rx_pkt_status = le32toh(*(uint32_t *)(pkt->data + sizeof(*rx_res) + len)); m = data->m; m->m_data = pkt->data + sizeof(*rx_res); m->m_pkthdr.len = m->m_len = len; if (__predict_false(phy_info->cfg_phy_cnt > 20)) { device_printf(sc->sc_dev, "dsp size out of range [0,20]: %d\n", phy_info->cfg_phy_cnt); return; } if (!(rx_pkt_status & IWM_RX_MPDU_RES_STATUS_CRC_OK) || !(rx_pkt_status & IWM_RX_MPDU_RES_STATUS_OVERRUN_OK)) { IWM_DPRINTF(sc, IWM_DEBUG_RECV, "Bad CRC or FIFO: 0x%08X.\n", rx_pkt_status); return; /* drop */ } if (sc->sc_capaflags & IWM_UCODE_TLV_FLAGS_RX_ENERGY_API) { rssi = iwm_mvm_get_signal_strength(sc, phy_info); } else { rssi = iwm_mvm_calc_rssi(sc, phy_info); } rssi = (0 - IWM_MIN_DBM) + rssi; /* normalize */ rssi = MIN(rssi, sc->sc_max_rssi); /* clip to max. 100% */ /* replenish ring for the buffer we're going to feed to the sharks */ if (iwm_rx_addbuf(sc, IWM_RBUF_SIZE, sc->rxq.cur) != 0) { device_printf(sc->sc_dev, "%s: unable to add more buffers\n", __func__); return; } ni = ieee80211_find_rxnode(ic, (struct ieee80211_frame_min *)wh); IWM_DPRINTF(sc, IWM_DEBUG_RECV, "%s: phy_info: channel=%d, flags=0x%08x\n", __func__, le16toh(phy_info->channel), le16toh(phy_info->phy_flags)); /* * Populate an RX state struct with the provided information. */ bzero(&rxs, sizeof(rxs)); rxs.r_flags |= IEEE80211_R_IEEE | IEEE80211_R_FREQ; rxs.r_flags |= IEEE80211_R_NF | IEEE80211_R_RSSI; rxs.c_ieee = le16toh(phy_info->channel); if (le16toh(phy_info->phy_flags & IWM_RX_RES_PHY_FLAGS_BAND_24)) { rxs.c_freq = ieee80211_ieee2mhz(rxs.c_ieee, IEEE80211_CHAN_2GHZ); } else { rxs.c_freq = ieee80211_ieee2mhz(rxs.c_ieee, IEEE80211_CHAN_5GHZ); } rxs.rssi = rssi - sc->sc_noise; rxs.nf = sc->sc_noise; if (ieee80211_radiotap_active_vap(vap)) { struct iwm_rx_radiotap_header *tap = &sc->sc_rxtap; tap->wr_flags = 0; if (phy_info->phy_flags & htole16(IWM_PHY_INFO_FLAG_SHPREAMBLE)) tap->wr_flags |= IEEE80211_RADIOTAP_F_SHORTPRE; tap->wr_chan_freq = htole16(rxs.c_freq); /* XXX only if ic->ic_curchan->ic_ieee == rxs.c_ieee */ tap->wr_chan_flags = htole16(ic->ic_curchan->ic_flags); tap->wr_dbm_antsignal = (int8_t)rssi; tap->wr_dbm_antnoise = (int8_t)sc->sc_noise; tap->wr_tsft = phy_info->system_timestamp; switch (phy_info->rate) { /* CCK rates. */ case 10: tap->wr_rate = 2; break; case 20: tap->wr_rate = 4; break; case 55: tap->wr_rate = 11; break; case 110: tap->wr_rate = 22; break; /* OFDM rates. */ case 0xd: tap->wr_rate = 12; break; case 0xf: tap->wr_rate = 18; break; case 0x5: tap->wr_rate = 24; break; case 0x7: tap->wr_rate = 36; break; case 0x9: tap->wr_rate = 48; break; case 0xb: tap->wr_rate = 72; break; case 0x1: tap->wr_rate = 96; break; case 0x3: tap->wr_rate = 108; break; /* Unknown rate: should not happen. */ default: tap->wr_rate = 0; } } IWM_UNLOCK(sc); if (ni != NULL) { IWM_DPRINTF(sc, IWM_DEBUG_RECV, "input m %p\n", m); ieee80211_input_mimo(ni, m, &rxs); ieee80211_free_node(ni); } else { IWM_DPRINTF(sc, IWM_DEBUG_RECV, "inputall m %p\n", m); ieee80211_input_mimo_all(ic, m, &rxs); } IWM_LOCK(sc); } static void iwm_mvm_rx_tx_cmd_single(struct iwm_softc *sc, struct iwm_rx_packet *pkt, struct iwm_node *in) { struct iwm_mvm_tx_resp *tx_resp = (void *)pkt->data; struct ieee80211vap *vap = in->in_ni.ni_vap; int status = le16toh(tx_resp->status.status) & IWM_TX_STATUS_MSK; int failack = tx_resp->failure_frame; KASSERT(tx_resp->frame_count == 1, ("too many frames")); /* Update rate control statistics. */ if (status != IWM_TX_STATUS_SUCCESS && status != IWM_TX_STATUS_DIRECT_DONE) { if_inc_counter(vap->iv_ifp, IFCOUNTER_OERRORS, 1); ieee80211_ratectl_tx_complete(vap, &in->in_ni, IEEE80211_RATECTL_TX_FAILURE, &failack, NULL); } else { if_inc_counter(vap->iv_ifp, IFCOUNTER_OPACKETS, 1); ieee80211_ratectl_tx_complete(vap, &in->in_ni, IEEE80211_RATECTL_TX_SUCCESS, &failack, NULL); } } static void iwm_mvm_rx_tx_cmd(struct iwm_softc *sc, struct iwm_rx_packet *pkt, struct iwm_rx_data *data) { struct iwm_cmd_header *cmd_hdr = &pkt->hdr; int idx = cmd_hdr->idx; int qid = cmd_hdr->qid; struct iwm_tx_ring *ring = &sc->txq[qid]; struct iwm_tx_data *txd = &ring->data[idx]; struct iwm_node *in = txd->in; if (txd->done) { device_printf(sc->sc_dev, "%s: got tx interrupt that's already been handled!\n", __func__); return; } bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTREAD); sc->sc_tx_timer = 0; iwm_mvm_rx_tx_cmd_single(sc, pkt, in); /* Unmap and free mbuf. */ bus_dmamap_sync(ring->data_dmat, txd->map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(ring->data_dmat, txd->map); m_freem(txd->m); IWM_DPRINTF(sc, IWM_DEBUG_XMIT, "free txd %p, in %p\n", txd, txd->in); KASSERT(txd->done == 0, ("txd not done")); txd->done = 1; KASSERT(txd->in, ("txd without node")); txd->m = NULL; txd->in = NULL; ieee80211_free_node((struct ieee80211_node *)in); if (--ring->queued < IWM_TX_RING_LOMARK) { sc->qfullmsk &= ~(1 << ring->qid); if (sc->qfullmsk == 0) { /* * Well, we're in interrupt context, but then again * I guess net80211 does all sorts of stunts in * interrupt context, so maybe this is no biggie. */ iwm_start(sc); } } } /* * transmit side */ /* * Process a "command done" firmware notification. This is where we wakeup * processes waiting for a synchronous command completion. * from if_iwn */ static void iwm_cmd_done(struct iwm_softc *sc, struct iwm_rx_packet *pkt) { struct iwm_tx_ring *ring = &sc->txq[IWM_MVM_CMD_QUEUE]; struct iwm_tx_data *data; if (pkt->hdr.qid != IWM_MVM_CMD_QUEUE) { return; /* Not a command ack. */ } data = &ring->data[pkt->hdr.idx]; /* If the command was mapped in an mbuf, free it. */ if (data->m != NULL) { bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(ring->data_dmat, data->map); m_freem(data->m); data->m = NULL; } wakeup(&ring->desc[pkt->hdr.idx]); } #if 0 /* * necessary only for block ack mode */ void iwm_update_sched(struct iwm_softc *sc, int qid, int idx, uint8_t sta_id, uint16_t len) { struct iwm_agn_scd_bc_tbl *scd_bc_tbl; uint16_t w_val; scd_bc_tbl = sc->sched_dma.vaddr; len += 8; /* magic numbers came naturally from paris */ if (sc->sc_capaflags & IWM_UCODE_TLV_FLAGS_DW_BC_TABLE) len = roundup(len, 4) / 4; w_val = htole16(sta_id << 12 | len); /* Update TX scheduler. */ scd_bc_tbl[qid].tfd_offset[idx] = w_val; bus_dmamap_sync(sc->sched_dma.tag, sc->sched_dma.map, BUS_DMASYNC_PREWRITE); /* I really wonder what this is ?!? */ if (idx < IWM_TFD_QUEUE_SIZE_BC_DUP) { scd_bc_tbl[qid].tfd_offset[IWM_TFD_QUEUE_SIZE_MAX + idx] = w_val; bus_dmamap_sync(sc->sched_dma.tag, sc->sched_dma.map, BUS_DMASYNC_PREWRITE); } } #endif /* * Take an 802.11 (non-n) rate, find the relevant rate * table entry. return the index into in_ridx[]. * * The caller then uses that index back into in_ridx * to figure out the rate index programmed /into/ * the firmware for this given node. */ static int iwm_tx_rateidx_lookup(struct iwm_softc *sc, struct iwm_node *in, uint8_t rate) { int i; uint8_t r; for (i = 0; i < nitems(in->in_ridx); i++) { r = iwm_rates[in->in_ridx[i]].rate; if (rate == r) return (i); } /* XXX Return the first */ /* XXX TODO: have it return the /lowest/ */ return (0); } /* * Fill in various bit for management frames, and leave them * unfilled for data frames (firmware takes care of that). * Return the selected TX rate. */ static const struct iwm_rate * iwm_tx_fill_cmd(struct iwm_softc *sc, struct iwm_node *in, struct ieee80211_frame *wh, struct iwm_tx_cmd *tx) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_node *ni = &in->in_ni; const struct iwm_rate *rinfo; int type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; int ridx, rate_flags; tx->rts_retry_limit = IWM_RTS_DFAULT_RETRY_LIMIT; tx->data_retry_limit = IWM_DEFAULT_TX_RETRY; /* * XXX TODO: everything about the rate selection here is terrible! */ if (type == IEEE80211_FC0_TYPE_DATA) { int i; /* for data frames, use RS table */ (void) ieee80211_ratectl_rate(ni, NULL, 0); i = iwm_tx_rateidx_lookup(sc, in, ni->ni_txrate); ridx = in->in_ridx[i]; /* This is the index into the programmed table */ tx->initial_rate_index = i; tx->tx_flags |= htole32(IWM_TX_CMD_FLG_STA_RATE); IWM_DPRINTF(sc, IWM_DEBUG_XMIT | IWM_DEBUG_TXRATE, "%s: start with i=%d, txrate %d\n", __func__, i, iwm_rates[ridx].rate); /* XXX no rate_n_flags? */ return &iwm_rates[ridx]; } /* * For non-data, use the lowest supported rate for the given * operational mode. * * Note: there may not be any rate control information available. * This driver currently assumes if we're transmitting data * frames, use the rate control table. Grr. * * XXX TODO: use the configured rate for the traffic type! */ if (ic->ic_curmode == IEEE80211_MODE_11A) { /* * XXX this assumes the mode is either 11a or not 11a; * definitely won't work for 11n. */ ridx = IWM_RIDX_OFDM; } else { ridx = IWM_RIDX_CCK; } rinfo = &iwm_rates[ridx]; IWM_DPRINTF(sc, IWM_DEBUG_TXRATE, "%s: ridx=%d; rate=%d, CCK=%d\n", __func__, ridx, rinfo->rate, !! (IWM_RIDX_IS_CCK(ridx)) ); /* XXX TODO: hard-coded TX antenna? */ rate_flags = 1 << IWM_RATE_MCS_ANT_POS; if (IWM_RIDX_IS_CCK(ridx)) rate_flags |= IWM_RATE_MCS_CCK_MSK; /* XXX hard-coded tx rate */ tx->rate_n_flags = htole32(rate_flags | rinfo->plcp); return rinfo; } #define TB0_SIZE 16 static int iwm_tx(struct iwm_softc *sc, struct mbuf *m, struct ieee80211_node *ni, int ac) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); struct iwm_node *in = (struct iwm_node *)ni; struct iwm_tx_ring *ring; struct iwm_tx_data *data; struct iwm_tfd *desc; struct iwm_device_cmd *cmd; struct iwm_tx_cmd *tx; struct ieee80211_frame *wh; struct ieee80211_key *k = NULL; struct mbuf *m1; const struct iwm_rate *rinfo; uint32_t flags; u_int hdrlen; bus_dma_segment_t *seg, segs[IWM_MAX_SCATTER]; int nsegs; uint8_t tid, type; int i, totlen, error, pad; wh = mtod(m, struct ieee80211_frame *); hdrlen = ieee80211_anyhdrsize(wh); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; tid = 0; ring = &sc->txq[ac]; desc = &ring->desc[ring->cur]; memset(desc, 0, sizeof(*desc)); data = &ring->data[ring->cur]; /* Fill out iwm_tx_cmd to send to the firmware */ cmd = &ring->cmd[ring->cur]; cmd->hdr.code = IWM_TX_CMD; cmd->hdr.flags = 0; cmd->hdr.qid = ring->qid; cmd->hdr.idx = ring->cur; tx = (void *)cmd->data; memset(tx, 0, sizeof(*tx)); rinfo = iwm_tx_fill_cmd(sc, in, wh, tx); /* Encrypt the frame if need be. */ if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { /* Retrieve key for TX && do software encryption. */ k = ieee80211_crypto_encap(ni, m); if (k == NULL) { m_freem(m); return (ENOBUFS); } /* 802.11 header may have moved. */ wh = mtod(m, struct ieee80211_frame *); } if (ieee80211_radiotap_active_vap(vap)) { struct iwm_tx_radiotap_header *tap = &sc->sc_txtap; tap->wt_flags = 0; tap->wt_chan_freq = htole16(ni->ni_chan->ic_freq); tap->wt_chan_flags = htole16(ni->ni_chan->ic_flags); tap->wt_rate = rinfo->rate; tap->wt_hwqueue = ac; if (k != NULL) tap->wt_flags |= IEEE80211_RADIOTAP_F_WEP; ieee80211_radiotap_tx(vap, m); } totlen = m->m_pkthdr.len; flags = 0; if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { flags |= IWM_TX_CMD_FLG_ACK; } if (type != IEEE80211_FC0_TYPE_DATA && (totlen + IEEE80211_CRC_LEN > vap->iv_rtsthreshold) && !IEEE80211_IS_MULTICAST(wh->i_addr1)) { flags |= IWM_TX_CMD_FLG_PROT_REQUIRE; } if (IEEE80211_IS_MULTICAST(wh->i_addr1) || type != IEEE80211_FC0_TYPE_DATA) tx->sta_id = sc->sc_aux_sta.sta_id; else tx->sta_id = IWM_STATION_ID; if (type == IEEE80211_FC0_TYPE_MGT) { uint8_t subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; if (subtype == IEEE80211_FC0_SUBTYPE_ASSOC_REQ || subtype == IEEE80211_FC0_SUBTYPE_REASSOC_REQ) tx->pm_frame_timeout = htole16(3); else tx->pm_frame_timeout = htole16(2); } else { tx->pm_frame_timeout = htole16(0); } if (hdrlen & 3) { /* First segment length must be a multiple of 4. */ flags |= IWM_TX_CMD_FLG_MH_PAD; pad = 4 - (hdrlen & 3); } else pad = 0; tx->driver_txop = 0; tx->next_frame_len = 0; tx->len = htole16(totlen); tx->tid_tspec = tid; tx->life_time = htole32(IWM_TX_CMD_LIFE_TIME_INFINITE); /* Set physical address of "scratch area". */ tx->dram_lsb_ptr = htole32(data->scratch_paddr); tx->dram_msb_ptr = iwm_get_dma_hi_addr(data->scratch_paddr); /* Copy 802.11 header in TX command. */ memcpy(((uint8_t *)tx) + sizeof(*tx), wh, hdrlen); flags |= IWM_TX_CMD_FLG_BT_DIS | IWM_TX_CMD_FLG_SEQ_CTL; tx->sec_ctl = 0; tx->tx_flags |= htole32(flags); /* Trim 802.11 header. */ m_adj(m, hdrlen); error = bus_dmamap_load_mbuf_sg(ring->data_dmat, data->map, m, segs, &nsegs, BUS_DMA_NOWAIT); if (error != 0) { if (error != EFBIG) { device_printf(sc->sc_dev, "can't map mbuf (error %d)\n", error); m_freem(m); return error; } /* Too many DMA segments, linearize mbuf. */ MGETHDR(m1, M_NOWAIT, MT_DATA); if (m1 == NULL) { m_freem(m); return ENOBUFS; } if (m->m_pkthdr.len > MHLEN) { MCLGET(m1, M_NOWAIT); if (!(m1->m_flags & M_EXT)) { m_freem(m); m_freem(m1); return ENOBUFS; } } m_copydata(m, 0, m->m_pkthdr.len, mtod(m1, void *)); m1->m_pkthdr.len = m1->m_len = m->m_pkthdr.len; m_freem(m); m = m1; error = bus_dmamap_load_mbuf_sg(ring->data_dmat, data->map, m, segs, &nsegs, BUS_DMA_NOWAIT); if (error != 0) { device_printf(sc->sc_dev, "can't map mbuf (error %d)\n", error); m_freem(m); return error; } } data->m = m; data->in = in; data->done = 0; IWM_DPRINTF(sc, IWM_DEBUG_XMIT, "sending txd %p, in %p\n", data, data->in); KASSERT(data->in != NULL, ("node is NULL")); IWM_DPRINTF(sc, IWM_DEBUG_XMIT, "sending data: qid=%d idx=%d len=%d nsegs=%d\n", ring->qid, ring->cur, totlen, nsegs); /* Fill TX descriptor. */ desc->num_tbs = 2 + nsegs; desc->tbs[0].lo = htole32(data->cmd_paddr); desc->tbs[0].hi_n_len = htole16(iwm_get_dma_hi_addr(data->cmd_paddr)) | (TB0_SIZE << 4); desc->tbs[1].lo = htole32(data->cmd_paddr + TB0_SIZE); desc->tbs[1].hi_n_len = htole16(iwm_get_dma_hi_addr(data->cmd_paddr)) | ((sizeof(struct iwm_cmd_header) + sizeof(*tx) + hdrlen + pad - TB0_SIZE) << 4); /* Other DMA segments are for data payload. */ for (i = 0; i < nsegs; i++) { seg = &segs[i]; desc->tbs[i+2].lo = htole32(seg->ds_addr); desc->tbs[i+2].hi_n_len = \ htole16(iwm_get_dma_hi_addr(seg->ds_addr)) | ((seg->ds_len) << 4); } bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_PREWRITE); bus_dmamap_sync(ring->cmd_dma.tag, ring->cmd_dma.map, BUS_DMASYNC_PREWRITE); bus_dmamap_sync(ring->desc_dma.tag, ring->desc_dma.map, BUS_DMASYNC_PREWRITE); #if 0 iwm_update_sched(sc, ring->qid, ring->cur, tx->sta_id, le16toh(tx->len)); #endif /* Kick TX ring. */ ring->cur = (ring->cur + 1) % IWM_TX_RING_COUNT; IWM_WRITE(sc, IWM_HBUS_TARG_WRPTR, ring->qid << 8 | ring->cur); /* Mark TX ring as full if we reach a certain threshold. */ if (++ring->queued > IWM_TX_RING_HIMARK) { sc->qfullmsk |= 1 << ring->qid; } return 0; } static int iwm_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = ni->ni_ic; struct iwm_softc *sc = ic->ic_softc; int error = 0; IWM_DPRINTF(sc, IWM_DEBUG_XMIT, "->%s begin\n", __func__); if ((sc->sc_flags & IWM_FLAG_HW_INITED) == 0) { m_freem(m); IWM_DPRINTF(sc, IWM_DEBUG_XMIT, "<-%s not RUNNING\n", __func__); return (ENETDOWN); } IWM_LOCK(sc); /* XXX fix this */ if (params == NULL) { error = iwm_tx(sc, m, ni, 0); } else { error = iwm_tx(sc, m, ni, 0); } sc->sc_tx_timer = 5; IWM_UNLOCK(sc); return (error); } /* * mvm/tx.c */ #if 0 /* * Note that there are transports that buffer frames before they reach * the firmware. This means that after flush_tx_path is called, the * queue might not be empty. The race-free way to handle this is to: * 1) set the station as draining * 2) flush the Tx path * 3) wait for the transport queues to be empty */ int iwm_mvm_flush_tx_path(struct iwm_softc *sc, int tfd_msk, int sync) { struct iwm_tx_path_flush_cmd flush_cmd = { .queues_ctl = htole32(tfd_msk), .flush_ctl = htole16(IWM_DUMP_TX_FIFO_FLUSH), }; int ret; ret = iwm_mvm_send_cmd_pdu(sc, IWM_TXPATH_FLUSH, sync ? IWM_CMD_SYNC : IWM_CMD_ASYNC, sizeof(flush_cmd), &flush_cmd); if (ret) device_printf(sc->sc_dev, "Flushing tx queue failed: %d\n", ret); return ret; } #endif /* * BEGIN mvm/sta.c */ static void iwm_mvm_add_sta_cmd_v6_to_v5(struct iwm_mvm_add_sta_cmd_v6 *cmd_v6, struct iwm_mvm_add_sta_cmd_v5 *cmd_v5) { memset(cmd_v5, 0, sizeof(*cmd_v5)); cmd_v5->add_modify = cmd_v6->add_modify; cmd_v5->tid_disable_tx = cmd_v6->tid_disable_tx; cmd_v5->mac_id_n_color = cmd_v6->mac_id_n_color; IEEE80211_ADDR_COPY(cmd_v5->addr, cmd_v6->addr); cmd_v5->sta_id = cmd_v6->sta_id; cmd_v5->modify_mask = cmd_v6->modify_mask; cmd_v5->station_flags = cmd_v6->station_flags; cmd_v5->station_flags_msk = cmd_v6->station_flags_msk; cmd_v5->add_immediate_ba_tid = cmd_v6->add_immediate_ba_tid; cmd_v5->remove_immediate_ba_tid = cmd_v6->remove_immediate_ba_tid; cmd_v5->add_immediate_ba_ssn = cmd_v6->add_immediate_ba_ssn; cmd_v5->sleep_tx_count = cmd_v6->sleep_tx_count; cmd_v5->sleep_state_flags = cmd_v6->sleep_state_flags; cmd_v5->assoc_id = cmd_v6->assoc_id; cmd_v5->beamform_flags = cmd_v6->beamform_flags; cmd_v5->tfd_queue_msk = cmd_v6->tfd_queue_msk; } static int iwm_mvm_send_add_sta_cmd_status(struct iwm_softc *sc, struct iwm_mvm_add_sta_cmd_v6 *cmd, int *status) { struct iwm_mvm_add_sta_cmd_v5 cmd_v5; if (sc->sc_capaflags & IWM_UCODE_TLV_FLAGS_STA_KEY_CMD) { return iwm_mvm_send_cmd_pdu_status(sc, IWM_ADD_STA, sizeof(*cmd), cmd, status); } iwm_mvm_add_sta_cmd_v6_to_v5(cmd, &cmd_v5); return iwm_mvm_send_cmd_pdu_status(sc, IWM_ADD_STA, sizeof(cmd_v5), &cmd_v5, status); } /* send station add/update command to firmware */ static int iwm_mvm_sta_send_to_fw(struct iwm_softc *sc, struct iwm_node *in, int update) { struct iwm_mvm_add_sta_cmd_v6 add_sta_cmd; int ret; uint32_t status; memset(&add_sta_cmd, 0, sizeof(add_sta_cmd)); add_sta_cmd.sta_id = IWM_STATION_ID; add_sta_cmd.mac_id_n_color = htole32(IWM_FW_CMD_ID_AND_COLOR(IWM_DEFAULT_MACID, IWM_DEFAULT_COLOR)); if (!update) { add_sta_cmd.tfd_queue_msk = htole32(0xf); IEEE80211_ADDR_COPY(&add_sta_cmd.addr, in->in_ni.ni_bssid); } add_sta_cmd.add_modify = update ? 1 : 0; add_sta_cmd.station_flags_msk |= htole32(IWM_STA_FLG_FAT_EN_MSK | IWM_STA_FLG_MIMO_EN_MSK); status = IWM_ADD_STA_SUCCESS; ret = iwm_mvm_send_add_sta_cmd_status(sc, &add_sta_cmd, &status); if (ret) return ret; switch (status) { case IWM_ADD_STA_SUCCESS: break; default: ret = EIO; device_printf(sc->sc_dev, "IWM_ADD_STA failed\n"); break; } return ret; } static int iwm_mvm_add_sta(struct iwm_softc *sc, struct iwm_node *in) { int ret; ret = iwm_mvm_sta_send_to_fw(sc, in, 0); if (ret) return ret; return 0; } static int iwm_mvm_update_sta(struct iwm_softc *sc, struct iwm_node *in) { return iwm_mvm_sta_send_to_fw(sc, in, 1); } static int iwm_mvm_add_int_sta_common(struct iwm_softc *sc, struct iwm_int_sta *sta, const uint8_t *addr, uint16_t mac_id, uint16_t color) { struct iwm_mvm_add_sta_cmd_v6 cmd; int ret; uint32_t status; memset(&cmd, 0, sizeof(cmd)); cmd.sta_id = sta->sta_id; cmd.mac_id_n_color = htole32(IWM_FW_CMD_ID_AND_COLOR(mac_id, color)); cmd.tfd_queue_msk = htole32(sta->tfd_queue_msk); if (addr) IEEE80211_ADDR_COPY(cmd.addr, addr); ret = iwm_mvm_send_add_sta_cmd_status(sc, &cmd, &status); if (ret) return ret; switch (status) { case IWM_ADD_STA_SUCCESS: IWM_DPRINTF(sc, IWM_DEBUG_RESET, "%s: Internal station added.\n", __func__); return 0; default: device_printf(sc->sc_dev, "%s: Add internal station failed, status=0x%x\n", __func__, status); ret = EIO; break; } return ret; } static int iwm_mvm_add_aux_sta(struct iwm_softc *sc) { int ret; sc->sc_aux_sta.sta_id = 3; sc->sc_aux_sta.tfd_queue_msk = 0; ret = iwm_mvm_add_int_sta_common(sc, &sc->sc_aux_sta, NULL, IWM_MAC_INDEX_AUX, 0); if (ret) memset(&sc->sc_aux_sta, 0, sizeof(sc->sc_aux_sta)); return ret; } /* * END mvm/sta.c */ /* * BEGIN mvm/quota.c */ static int iwm_mvm_update_quotas(struct iwm_softc *sc, struct iwm_node *in) { struct iwm_time_quota_cmd cmd; int i, idx, ret, num_active_macs, quota, quota_rem; int colors[IWM_MAX_BINDINGS] = { -1, -1, -1, -1, }; int n_ifs[IWM_MAX_BINDINGS] = {0, }; uint16_t id; memset(&cmd, 0, sizeof(cmd)); /* currently, PHY ID == binding ID */ if (in) { id = in->in_phyctxt->id; KASSERT(id < IWM_MAX_BINDINGS, ("invalid id")); colors[id] = in->in_phyctxt->color; if (1) n_ifs[id] = 1; } /* * The FW's scheduling session consists of * IWM_MVM_MAX_QUOTA fragments. Divide these fragments * equally between all the bindings that require quota */ num_active_macs = 0; for (i = 0; i < IWM_MAX_BINDINGS; i++) { cmd.quotas[i].id_and_color = htole32(IWM_FW_CTXT_INVALID); num_active_macs += n_ifs[i]; } quota = 0; quota_rem = 0; if (num_active_macs) { quota = IWM_MVM_MAX_QUOTA / num_active_macs; quota_rem = IWM_MVM_MAX_QUOTA % num_active_macs; } for (idx = 0, i = 0; i < IWM_MAX_BINDINGS; i++) { if (colors[i] < 0) continue; cmd.quotas[idx].id_and_color = htole32(IWM_FW_CMD_ID_AND_COLOR(i, colors[i])); if (n_ifs[i] <= 0) { cmd.quotas[idx].quota = htole32(0); cmd.quotas[idx].max_duration = htole32(0); } else { cmd.quotas[idx].quota = htole32(quota * n_ifs[i]); cmd.quotas[idx].max_duration = htole32(0); } idx++; } /* Give the remainder of the session to the first binding */ cmd.quotas[0].quota = htole32(le32toh(cmd.quotas[0].quota) + quota_rem); ret = iwm_mvm_send_cmd_pdu(sc, IWM_TIME_QUOTA_CMD, IWM_CMD_SYNC, sizeof(cmd), &cmd); if (ret) device_printf(sc->sc_dev, "%s: Failed to send quota: %d\n", __func__, ret); return ret; } /* * END mvm/quota.c */ /* * ieee80211 routines */ /* * Change to AUTH state in 80211 state machine. Roughly matches what * Linux does in bss_info_changed(). */ static int iwm_auth(struct ieee80211vap *vap, struct iwm_softc *sc) { struct ieee80211_node *ni; struct iwm_node *in; struct iwm_vap *iv = IWM_VAP(vap); uint32_t duration; uint32_t min_duration; int error; /* * XXX i have a feeling that the vap node is being * freed from underneath us. Grr. */ ni = ieee80211_ref_node(vap->iv_bss); in = (struct iwm_node *) ni; IWM_DPRINTF(sc, IWM_DEBUG_RESET | IWM_DEBUG_STATE, "%s: called; vap=%p, bss ni=%p\n", __func__, vap, ni); in->in_assoc = 0; error = iwm_allow_mcast(vap, sc); if (error) { device_printf(sc->sc_dev, "%s: failed to set multicast\n", __func__); goto out; } /* * This is where it deviates from what Linux does. * * Linux iwlwifi doesn't reset the nic each time, nor does it * call ctxt_add() here. Instead, it adds it during vap creation, * and always does does a mac_ctx_changed(). * * The openbsd port doesn't attempt to do that - it reset things * at odd states and does the add here. * * So, until the state handling is fixed (ie, we never reset * the NIC except for a firmware failure, which should drag * the NIC back to IDLE, re-setup and re-add all the mac/phy * contexts that are required), let's do a dirty hack here. */ if (iv->is_uploaded) { if ((error = iwm_mvm_mac_ctxt_changed(sc, vap)) != 0) { device_printf(sc->sc_dev, "%s: failed to add MAC\n", __func__); goto out; } } else { if ((error = iwm_mvm_mac_ctxt_add(sc, vap)) != 0) { device_printf(sc->sc_dev, "%s: failed to add MAC\n", __func__); goto out; } } if ((error = iwm_mvm_phy_ctxt_changed(sc, &sc->sc_phyctxt[0], in->in_ni.ni_chan, 1, 1)) != 0) { device_printf(sc->sc_dev, "%s: failed add phy ctxt\n", __func__); goto out; } in->in_phyctxt = &sc->sc_phyctxt[0]; if ((error = iwm_mvm_binding_add_vif(sc, in)) != 0) { device_printf(sc->sc_dev, "%s: binding cmd\n", __func__); goto out; } if ((error = iwm_mvm_add_sta(sc, in)) != 0) { device_printf(sc->sc_dev, "%s: failed to add MAC\n", __func__); goto out; } /* a bit superfluous? */ while (sc->sc_auth_prot) msleep(&sc->sc_auth_prot, &sc->sc_mtx, 0, "iwmauth", 0); sc->sc_auth_prot = 1; duration = min(IWM_MVM_TE_SESSION_PROTECTION_MAX_TIME_MS, 200 + in->in_ni.ni_intval); min_duration = min(IWM_MVM_TE_SESSION_PROTECTION_MIN_TIME_MS, 100 + in->in_ni.ni_intval); iwm_mvm_protect_session(sc, in, duration, min_duration, 500); IWM_DPRINTF(sc, IWM_DEBUG_RESET, "%s: waiting for auth_prot\n", __func__); while (sc->sc_auth_prot != 2) { /* * well, meh, but if the kernel is sleeping for half a * second, we have bigger problems */ if (sc->sc_auth_prot == 0) { device_printf(sc->sc_dev, "%s: missed auth window!\n", __func__); error = ETIMEDOUT; goto out; } else if (sc->sc_auth_prot == -1) { device_printf(sc->sc_dev, "%s: no time event, denied!\n", __func__); sc->sc_auth_prot = 0; error = EAUTH; goto out; } msleep(&sc->sc_auth_prot, &sc->sc_mtx, 0, "iwmau2", 0); } IWM_DPRINTF(sc, IWM_DEBUG_RESET, "<-%s\n", __func__); error = 0; out: ieee80211_free_node(ni); return (error); } static int iwm_assoc(struct ieee80211vap *vap, struct iwm_softc *sc) { struct iwm_node *in = (struct iwm_node *)vap->iv_bss; int error; if ((error = iwm_mvm_update_sta(sc, in)) != 0) { device_printf(sc->sc_dev, "%s: failed to update STA\n", __func__); return error; } in->in_assoc = 1; if ((error = iwm_mvm_mac_ctxt_changed(sc, vap)) != 0) { device_printf(sc->sc_dev, "%s: failed to update MAC\n", __func__); return error; } return 0; } static int iwm_release(struct iwm_softc *sc, struct iwm_node *in) { /* * Ok, so *technically* the proper set of calls for going * from RUN back to SCAN is: * * iwm_mvm_power_mac_disable(sc, in); * iwm_mvm_mac_ctxt_changed(sc, in); * iwm_mvm_rm_sta(sc, in); * iwm_mvm_update_quotas(sc, NULL); * iwm_mvm_mac_ctxt_changed(sc, in); * iwm_mvm_binding_remove_vif(sc, in); * iwm_mvm_mac_ctxt_remove(sc, in); * * However, that freezes the device not matter which permutations * and modifications are attempted. Obviously, this driver is missing * something since it works in the Linux driver, but figuring out what * is missing is a little more complicated. Now, since we're going * back to nothing anyway, we'll just do a complete device reset. * Up your's, device! */ //iwm_mvm_flush_tx_path(sc, 0xf, 1); iwm_stop_device(sc); iwm_init_hw(sc); if (in) in->in_assoc = 0; return 0; #if 0 int error; iwm_mvm_power_mac_disable(sc, in); if ((error = iwm_mvm_mac_ctxt_changed(sc, in)) != 0) { device_printf(sc->sc_dev, "mac ctxt change fail 1 %d\n", error); return error; } if ((error = iwm_mvm_rm_sta(sc, in)) != 0) { device_printf(sc->sc_dev, "sta remove fail %d\n", error); return error; } error = iwm_mvm_rm_sta(sc, in); in->in_assoc = 0; iwm_mvm_update_quotas(sc, NULL); if ((error = iwm_mvm_mac_ctxt_changed(sc, in)) != 0) { device_printf(sc->sc_dev, "mac ctxt change fail 2 %d\n", error); return error; } iwm_mvm_binding_remove_vif(sc, in); iwm_mvm_mac_ctxt_remove(sc, in); return error; #endif } static struct ieee80211_node * iwm_node_alloc(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN]) { return malloc(sizeof (struct iwm_node), M_80211_NODE, M_NOWAIT | M_ZERO); } static void iwm_setrates(struct iwm_softc *sc, struct iwm_node *in) { struct ieee80211_node *ni = &in->in_ni; struct iwm_lq_cmd *lq = &in->in_lq; int nrates = ni->ni_rates.rs_nrates; int i, ridx, tab = 0; int txant = 0; if (nrates > nitems(lq->rs_table)) { device_printf(sc->sc_dev, "%s: node supports %d rates, driver handles " "only %zu\n", __func__, nrates, nitems(lq->rs_table)); return; } /* * XXX .. and most of iwm_node is not initialised explicitly; * it's all just 0x0 passed to the firmware. */ /* first figure out which rates we should support */ /* XXX TODO: this isn't 11n aware /at all/ */ memset(&in->in_ridx, -1, sizeof(in->in_ridx)); IWM_DPRINTF(sc, IWM_DEBUG_TXRATE, "%s: nrates=%d\n", __func__, nrates); for (i = 0; i < nrates; i++) { int rate = ni->ni_rates.rs_rates[i] & IEEE80211_RATE_VAL; /* Map 802.11 rate to HW rate index. */ for (ridx = 0; ridx <= IWM_RIDX_MAX; ridx++) if (iwm_rates[ridx].rate == rate) break; if (ridx > IWM_RIDX_MAX) { device_printf(sc->sc_dev, "%s: WARNING: device rate for %d not found!\n", __func__, rate); } else { IWM_DPRINTF(sc, IWM_DEBUG_TXRATE, "%s: rate: i: %d, rate=%d, ridx=%d\n", __func__, i, rate, ridx); in->in_ridx[i] = ridx; } } /* then construct a lq_cmd based on those */ memset(lq, 0, sizeof(*lq)); lq->sta_id = IWM_STATION_ID; /* * are these used? (we don't do SISO or MIMO) * need to set them to non-zero, though, or we get an error. */ lq->single_stream_ant_msk = 1; lq->dual_stream_ant_msk = 1; /* * Build the actual rate selection table. * The lowest bits are the rates. Additionally, * CCK needs bit 9 to be set. The rest of the bits * we add to the table select the tx antenna * Note that we add the rates in the highest rate first * (opposite of ni_rates). */ /* * XXX TODO: this should be looping over the min of nrates * and LQ_MAX_RETRY_NUM. Sigh. */ for (i = 0; i < nrates; i++) { int nextant; if (txant == 0) txant = IWM_FW_VALID_TX_ANT(sc); nextant = 1<<(ffs(txant)-1); txant &= ~nextant; /* * Map the rate id into a rate index into * our hardware table containing the * configuration to use for this rate. */ ridx = in->in_ridx[(nrates-1)-i]; tab = iwm_rates[ridx].plcp; tab |= nextant << IWM_RATE_MCS_ANT_POS; if (IWM_RIDX_IS_CCK(ridx)) tab |= IWM_RATE_MCS_CCK_MSK; IWM_DPRINTF(sc, IWM_DEBUG_TXRATE, "station rate i=%d, rate=%d, hw=%x\n", i, iwm_rates[ridx].rate, tab); lq->rs_table[i] = htole32(tab); } /* then fill the rest with the lowest possible rate */ for (i = nrates; i < nitems(lq->rs_table); i++) { KASSERT(tab != 0, ("invalid tab")); lq->rs_table[i] = htole32(tab); } } static int iwm_media_change(struct ifnet *ifp) { struct ieee80211vap *vap = ifp->if_softc; struct ieee80211com *ic = vap->iv_ic; struct iwm_softc *sc = ic->ic_softc; int error; error = ieee80211_media_change(ifp); if (error != ENETRESET) return error; IWM_LOCK(sc); if (ic->ic_nrunning > 0) { iwm_stop(sc); iwm_init(sc); } IWM_UNLOCK(sc); return error; } static int iwm_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct iwm_vap *ivp = IWM_VAP(vap); struct ieee80211com *ic = vap->iv_ic; struct iwm_softc *sc = ic->ic_softc; struct iwm_node *in; int error; IWM_DPRINTF(sc, IWM_DEBUG_STATE, "switching state %s -> %s\n", ieee80211_state_name[vap->iv_state], ieee80211_state_name[nstate]); IEEE80211_UNLOCK(ic); IWM_LOCK(sc); /* disable beacon filtering if we're hopping out of RUN */ if (vap->iv_state == IEEE80211_S_RUN && nstate != vap->iv_state) { iwm_mvm_disable_beacon_filter(sc); if (((in = (void *)vap->iv_bss) != NULL)) in->in_assoc = 0; iwm_release(sc, NULL); /* * It's impossible to directly go RUN->SCAN. If we iwm_release() * above then the card will be completely reinitialized, * so the driver must do everything necessary to bring the card * from INIT to SCAN. * * Additionally, upon receiving deauth frame from AP, * OpenBSD 802.11 stack puts the driver in IEEE80211_S_AUTH * state. This will also fail with this driver, so bring the FSM * from IEEE80211_S_RUN to IEEE80211_S_SCAN in this case as well. * * XXX TODO: fix this for FreeBSD! */ if (nstate == IEEE80211_S_SCAN || nstate == IEEE80211_S_AUTH || nstate == IEEE80211_S_ASSOC) { IWM_DPRINTF(sc, IWM_DEBUG_STATE, "Force transition to INIT; MGT=%d\n", arg); IWM_UNLOCK(sc); IEEE80211_LOCK(ic); vap->iv_newstate(vap, IEEE80211_S_INIT, arg); IWM_DPRINTF(sc, IWM_DEBUG_STATE, "Going INIT->SCAN\n"); nstate = IEEE80211_S_SCAN; IEEE80211_UNLOCK(ic); IWM_LOCK(sc); } } switch (nstate) { case IEEE80211_S_INIT: sc->sc_scanband = 0; break; case IEEE80211_S_AUTH: if ((error = iwm_auth(vap, sc)) != 0) { device_printf(sc->sc_dev, "%s: could not move to auth state: %d\n", __func__, error); break; } break; case IEEE80211_S_ASSOC: if ((error = iwm_assoc(vap, sc)) != 0) { device_printf(sc->sc_dev, "%s: failed to associate: %d\n", __func__, error); break; } break; case IEEE80211_S_RUN: { struct iwm_host_cmd cmd = { .id = IWM_LQ_CMD, .len = { sizeof(in->in_lq), }, .flags = IWM_CMD_SYNC, }; /* Update the association state, now we have it all */ /* (eg associd comes in at this point */ error = iwm_assoc(vap, sc); if (error != 0) { device_printf(sc->sc_dev, "%s: failed to update association state: %d\n", __func__, error); break; } in = (struct iwm_node *)vap->iv_bss; iwm_mvm_power_mac_update_mode(sc, in); iwm_mvm_enable_beacon_filter(sc, in); iwm_mvm_update_quotas(sc, in); iwm_setrates(sc, in); cmd.data[0] = &in->in_lq; if ((error = iwm_send_cmd(sc, &cmd)) != 0) { device_printf(sc->sc_dev, "%s: IWM_LQ_CMD failed\n", __func__); } break; } default: break; } IWM_UNLOCK(sc); IEEE80211_LOCK(ic); return (ivp->iv_newstate(vap, nstate, arg)); } void iwm_endscan_cb(void *arg, int pending) { struct iwm_softc *sc = arg; struct ieee80211com *ic = &sc->sc_ic; int done; int error; IWM_DPRINTF(sc, IWM_DEBUG_SCAN | IWM_DEBUG_TRACE, "%s: scan ended\n", __func__); IWM_LOCK(sc); if (sc->sc_scanband == IEEE80211_CHAN_2GHZ && sc->sc_nvm.sku_cap_band_52GHz_enable) { done = 0; if ((error = iwm_mvm_scan_request(sc, IEEE80211_CHAN_5GHZ, 0, NULL, 0)) != 0) { device_printf(sc->sc_dev, "could not initiate scan\n"); done = 1; } } else { done = 1; } if (done) { IWM_UNLOCK(sc); ieee80211_scan_done(TAILQ_FIRST(&ic->ic_vaps)); IWM_LOCK(sc); sc->sc_scanband = 0; } IWM_UNLOCK(sc); } static int iwm_init_hw(struct iwm_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; int error, i, qid; if ((error = iwm_start_hw(sc)) != 0) return error; if ((error = iwm_run_init_mvm_ucode(sc, 0)) != 0) { return error; } /* * should stop and start HW since that INIT * image just loaded */ iwm_stop_device(sc); if ((error = iwm_start_hw(sc)) != 0) { device_printf(sc->sc_dev, "could not initialize hardware\n"); return error; } /* omstart, this time with the regular firmware */ error = iwm_mvm_load_ucode_wait_alive(sc, IWM_UCODE_TYPE_REGULAR); if (error) { device_printf(sc->sc_dev, "could not load firmware\n"); goto error; } if ((error = iwm_send_tx_ant_cfg(sc, IWM_FW_VALID_TX_ANT(sc))) != 0) goto error; /* Send phy db control command and then phy db calibration*/ if ((error = iwm_send_phy_db_data(sc)) != 0) goto error; if ((error = iwm_send_phy_cfg_cmd(sc)) != 0) goto error; /* Add auxiliary station for scanning */ if ((error = iwm_mvm_add_aux_sta(sc)) != 0) goto error; for (i = 0; i < IWM_NUM_PHY_CTX; i++) { /* * The channel used here isn't relevant as it's * going to be overwritten in the other flows. * For now use the first channel we have. */ if ((error = iwm_mvm_phy_ctxt_add(sc, &sc->sc_phyctxt[i], &ic->ic_channels[1], 1, 1)) != 0) goto error; } error = iwm_mvm_power_update_device(sc); if (error) goto error; /* Mark TX rings as active. */ for (qid = 0; qid < 4; qid++) { iwm_enable_txq(sc, qid, qid); } return 0; error: iwm_stop_device(sc); return error; } /* Allow multicast from our BSSID. */ static int iwm_allow_mcast(struct ieee80211vap *vap, struct iwm_softc *sc) { struct ieee80211_node *ni = vap->iv_bss; struct iwm_mcast_filter_cmd *cmd; size_t size; int error; size = roundup(sizeof(*cmd), 4); cmd = malloc(size, M_DEVBUF, M_NOWAIT | M_ZERO); if (cmd == NULL) return ENOMEM; cmd->filter_own = 1; cmd->port_id = 0; cmd->count = 0; cmd->pass_all = 1; IEEE80211_ADDR_COPY(cmd->bssid, ni->ni_bssid); error = iwm_mvm_send_cmd_pdu(sc, IWM_MCAST_FILTER_CMD, IWM_CMD_SYNC, size, cmd); free(cmd, M_DEVBUF); return (error); } static void iwm_init(struct iwm_softc *sc) { int error; if (sc->sc_flags & IWM_FLAG_HW_INITED) { return; } sc->sc_generation++; sc->sc_flags &= ~IWM_FLAG_STOPPED; if ((error = iwm_init_hw(sc)) != 0) { iwm_stop(sc); return; } /* * Ok, firmware loaded and we are jogging */ sc->sc_flags |= IWM_FLAG_HW_INITED; callout_reset(&sc->sc_watchdog_to, hz, iwm_watchdog, sc); } static int iwm_transmit(struct ieee80211com *ic, struct mbuf *m) { struct iwm_softc *sc; int error; sc = ic->ic_softc; IWM_LOCK(sc); if ((sc->sc_flags & IWM_FLAG_HW_INITED) == 0) { IWM_UNLOCK(sc); return (ENXIO); } error = mbufq_enqueue(&sc->sc_snd, m); if (error) { IWM_UNLOCK(sc); return (error); } iwm_start(sc); IWM_UNLOCK(sc); return (0); } /* * Dequeue packets from sendq and call send. */ static void iwm_start(struct iwm_softc *sc) { struct ieee80211_node *ni; struct mbuf *m; int ac = 0; IWM_DPRINTF(sc, IWM_DEBUG_XMIT | IWM_DEBUG_TRACE, "->%s\n", __func__); while (sc->qfullmsk == 0 && (m = mbufq_dequeue(&sc->sc_snd)) != NULL) { ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; if (iwm_tx(sc, m, ni, ac) != 0) { if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1); ieee80211_free_node(ni); continue; } sc->sc_tx_timer = 15; } IWM_DPRINTF(sc, IWM_DEBUG_XMIT | IWM_DEBUG_TRACE, "<-%s\n", __func__); } static void iwm_stop(struct iwm_softc *sc) { sc->sc_flags &= ~IWM_FLAG_HW_INITED; sc->sc_flags |= IWM_FLAG_STOPPED; sc->sc_generation++; sc->sc_scanband = 0; sc->sc_auth_prot = 0; sc->sc_tx_timer = 0; iwm_stop_device(sc); } static void iwm_watchdog(void *arg) { struct iwm_softc *sc = arg; if (sc->sc_tx_timer > 0) { if (--sc->sc_tx_timer == 0) { device_printf(sc->sc_dev, "device timeout\n"); #ifdef IWM_DEBUG iwm_nic_error(sc); #endif iwm_stop(sc); counter_u64_add(sc->sc_ic.ic_oerrors, 1); return; } } callout_reset(&sc->sc_watchdog_to, hz, iwm_watchdog, sc); } static void iwm_parent(struct ieee80211com *ic) { struct iwm_softc *sc = ic->ic_softc; int startall = 0; IWM_LOCK(sc); if (ic->ic_nrunning > 0) { if (!(sc->sc_flags & IWM_FLAG_HW_INITED)) { iwm_init(sc); startall = 1; } } else if (sc->sc_flags & IWM_FLAG_HW_INITED) iwm_stop(sc); IWM_UNLOCK(sc); if (startall) ieee80211_start_all(ic); } /* * The interrupt side of things */ /* * error dumping routines are from iwlwifi/mvm/utils.c */ /* * Note: This structure is read from the device with IO accesses, * and the reading already does the endian conversion. As it is * read with uint32_t-sized accesses, any members with a different size * need to be ordered correctly though! */ struct iwm_error_event_table { uint32_t valid; /* (nonzero) valid, (0) log is empty */ uint32_t error_id; /* type of error */ uint32_t pc; /* program counter */ uint32_t blink1; /* branch link */ uint32_t blink2; /* branch link */ uint32_t ilink1; /* interrupt link */ uint32_t ilink2; /* interrupt link */ uint32_t data1; /* error-specific data */ uint32_t data2; /* error-specific data */ uint32_t data3; /* error-specific data */ uint32_t bcon_time; /* beacon timer */ uint32_t tsf_low; /* network timestamp function timer */ uint32_t tsf_hi; /* network timestamp function timer */ uint32_t gp1; /* GP1 timer register */ uint32_t gp2; /* GP2 timer register */ uint32_t gp3; /* GP3 timer register */ uint32_t ucode_ver; /* uCode version */ uint32_t hw_ver; /* HW Silicon version */ uint32_t brd_ver; /* HW board version */ uint32_t log_pc; /* log program counter */ uint32_t frame_ptr; /* frame pointer */ uint32_t stack_ptr; /* stack pointer */ uint32_t hcmd; /* last host command header */ uint32_t isr0; /* isr status register LMPM_NIC_ISR0: * rxtx_flag */ uint32_t isr1; /* isr status register LMPM_NIC_ISR1: * host_flag */ uint32_t isr2; /* isr status register LMPM_NIC_ISR2: * enc_flag */ uint32_t isr3; /* isr status register LMPM_NIC_ISR3: * time_flag */ uint32_t isr4; /* isr status register LMPM_NIC_ISR4: * wico interrupt */ uint32_t isr_pref; /* isr status register LMPM_NIC_PREF_STAT */ uint32_t wait_event; /* wait event() caller address */ uint32_t l2p_control; /* L2pControlField */ uint32_t l2p_duration; /* L2pDurationField */ uint32_t l2p_mhvalid; /* L2pMhValidBits */ uint32_t l2p_addr_match; /* L2pAddrMatchStat */ uint32_t lmpm_pmg_sel; /* indicate which clocks are turned on * (LMPM_PMG_SEL) */ uint32_t u_timestamp; /* indicate when the date and time of the * compilation */ uint32_t flow_handler; /* FH read/write pointers, RX credit */ } __packed; #define ERROR_START_OFFSET (1 * sizeof(uint32_t)) #define ERROR_ELEM_SIZE (7 * sizeof(uint32_t)) #ifdef IWM_DEBUG struct { const char *name; uint8_t num; } advanced_lookup[] = { { "NMI_INTERRUPT_WDG", 0x34 }, { "SYSASSERT", 0x35 }, { "UCODE_VERSION_MISMATCH", 0x37 }, { "BAD_COMMAND", 0x38 }, { "NMI_INTERRUPT_DATA_ACTION_PT", 0x3C }, { "FATAL_ERROR", 0x3D }, { "NMI_TRM_HW_ERR", 0x46 }, { "NMI_INTERRUPT_TRM", 0x4C }, { "NMI_INTERRUPT_BREAK_POINT", 0x54 }, { "NMI_INTERRUPT_WDG_RXF_FULL", 0x5C }, { "NMI_INTERRUPT_WDG_NO_RBD_RXF_FULL", 0x64 }, { "NMI_INTERRUPT_HOST", 0x66 }, { "NMI_INTERRUPT_ACTION_PT", 0x7C }, { "NMI_INTERRUPT_UNKNOWN", 0x84 }, { "NMI_INTERRUPT_INST_ACTION_PT", 0x86 }, { "ADVANCED_SYSASSERT", 0 }, }; static const char * iwm_desc_lookup(uint32_t num) { int i; for (i = 0; i < nitems(advanced_lookup) - 1; i++) if (advanced_lookup[i].num == num) return advanced_lookup[i].name; /* No entry matches 'num', so it is the last: ADVANCED_SYSASSERT */ return advanced_lookup[i].name; } /* * Support for dumping the error log seemed like a good idea ... * but it's mostly hex junk and the only sensible thing is the * hw/ucode revision (which we know anyway). Since it's here, * I'll just leave it in, just in case e.g. the Intel guys want to * help us decipher some "ADVANCED_SYSASSERT" later. */ static void iwm_nic_error(struct iwm_softc *sc) { struct iwm_error_event_table table; uint32_t base; device_printf(sc->sc_dev, "dumping device error log\n"); base = sc->sc_uc.uc_error_event_table; if (base < 0x800000 || base >= 0x80C000) { device_printf(sc->sc_dev, "Not valid error log pointer 0x%08x\n", base); return; } if (iwm_read_mem(sc, base, &table, sizeof(table)/sizeof(uint32_t)) != 0) { device_printf(sc->sc_dev, "reading errlog failed\n"); return; } if (!table.valid) { device_printf(sc->sc_dev, "errlog not found, skipping\n"); return; } if (ERROR_START_OFFSET <= table.valid * ERROR_ELEM_SIZE) { device_printf(sc->sc_dev, "Start IWL Error Log Dump:\n"); device_printf(sc->sc_dev, "Status: 0x%x, count: %d\n", sc->sc_flags, table.valid); } device_printf(sc->sc_dev, "0x%08X | %-28s\n", table.error_id, iwm_desc_lookup(table.error_id)); device_printf(sc->sc_dev, "%08X | uPc\n", table.pc); device_printf(sc->sc_dev, "%08X | branchlink1\n", table.blink1); device_printf(sc->sc_dev, "%08X | branchlink2\n", table.blink2); device_printf(sc->sc_dev, "%08X | interruptlink1\n", table.ilink1); device_printf(sc->sc_dev, "%08X | interruptlink2\n", table.ilink2); device_printf(sc->sc_dev, "%08X | data1\n", table.data1); device_printf(sc->sc_dev, "%08X | data2\n", table.data2); device_printf(sc->sc_dev, "%08X | data3\n", table.data3); device_printf(sc->sc_dev, "%08X | beacon time\n", table.bcon_time); device_printf(sc->sc_dev, "%08X | tsf low\n", table.tsf_low); device_printf(sc->sc_dev, "%08X | tsf hi\n", table.tsf_hi); device_printf(sc->sc_dev, "%08X | time gp1\n", table.gp1); device_printf(sc->sc_dev, "%08X | time gp2\n", table.gp2); device_printf(sc->sc_dev, "%08X | time gp3\n", table.gp3); device_printf(sc->sc_dev, "%08X | uCode version\n", table.ucode_ver); device_printf(sc->sc_dev, "%08X | hw version\n", table.hw_ver); device_printf(sc->sc_dev, "%08X | board version\n", table.brd_ver); device_printf(sc->sc_dev, "%08X | hcmd\n", table.hcmd); device_printf(sc->sc_dev, "%08X | isr0\n", table.isr0); device_printf(sc->sc_dev, "%08X | isr1\n", table.isr1); device_printf(sc->sc_dev, "%08X | isr2\n", table.isr2); device_printf(sc->sc_dev, "%08X | isr3\n", table.isr3); device_printf(sc->sc_dev, "%08X | isr4\n", table.isr4); device_printf(sc->sc_dev, "%08X | isr_pref\n", table.isr_pref); device_printf(sc->sc_dev, "%08X | wait_event\n", table.wait_event); device_printf(sc->sc_dev, "%08X | l2p_control\n", table.l2p_control); device_printf(sc->sc_dev, "%08X | l2p_duration\n", table.l2p_duration); device_printf(sc->sc_dev, "%08X | l2p_mhvalid\n", table.l2p_mhvalid); device_printf(sc->sc_dev, "%08X | l2p_addr_match\n", table.l2p_addr_match); device_printf(sc->sc_dev, "%08X | lmpm_pmg_sel\n", table.lmpm_pmg_sel); device_printf(sc->sc_dev, "%08X | timestamp\n", table.u_timestamp); device_printf(sc->sc_dev, "%08X | flow_handler\n", table.flow_handler); } #endif #define SYNC_RESP_STRUCT(_var_, _pkt_) \ do { \ bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTREAD);\ _var_ = (void *)((_pkt_)+1); \ } while (/*CONSTCOND*/0) #define SYNC_RESP_PTR(_ptr_, _len_, _pkt_) \ do { \ bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTREAD);\ _ptr_ = (void *)((_pkt_)+1); \ } while (/*CONSTCOND*/0) #define ADVANCE_RXQ(sc) (sc->rxq.cur = (sc->rxq.cur + 1) % IWM_RX_RING_COUNT); /* * Process an IWM_CSR_INT_BIT_FH_RX or IWM_CSR_INT_BIT_SW_RX interrupt. * Basic structure from if_iwn */ static void iwm_notif_intr(struct iwm_softc *sc) { uint16_t hw; bus_dmamap_sync(sc->rxq.stat_dma.tag, sc->rxq.stat_dma.map, BUS_DMASYNC_POSTREAD); hw = le16toh(sc->rxq.stat->closed_rb_num) & 0xfff; while (sc->rxq.cur != hw) { struct iwm_rx_ring *ring = &sc->rxq; struct iwm_rx_data *data = &sc->rxq.data[sc->rxq.cur]; struct iwm_rx_packet *pkt; struct iwm_cmd_response *cresp; int qid, idx; bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD); pkt = mtod(data->m, struct iwm_rx_packet *); qid = pkt->hdr.qid & ~0x80; idx = pkt->hdr.idx; IWM_DPRINTF(sc, IWM_DEBUG_INTR, "rx packet qid=%d idx=%d flags=%x type=%x %d %d\n", pkt->hdr.qid & ~0x80, pkt->hdr.idx, pkt->hdr.flags, pkt->hdr.code, sc->rxq.cur, hw); /* * randomly get these from the firmware, no idea why. * they at least seem harmless, so just ignore them for now */ if (__predict_false((pkt->hdr.code == 0 && qid == 0 && idx == 0) || pkt->len_n_flags == htole32(0x55550000))) { ADVANCE_RXQ(sc); continue; } switch (pkt->hdr.code) { case IWM_REPLY_RX_PHY_CMD: iwm_mvm_rx_rx_phy_cmd(sc, pkt, data); break; case IWM_REPLY_RX_MPDU_CMD: iwm_mvm_rx_rx_mpdu(sc, pkt, data); break; case IWM_TX_CMD: iwm_mvm_rx_tx_cmd(sc, pkt, data); break; case IWM_MISSED_BEACONS_NOTIFICATION: { struct iwm_missed_beacons_notif *resp; int missed; /* XXX look at mac_id to determine interface ID */ struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); SYNC_RESP_STRUCT(resp, pkt); missed = le32toh(resp->consec_missed_beacons); IWM_DPRINTF(sc, IWM_DEBUG_BEACON | IWM_DEBUG_STATE, "%s: MISSED_BEACON: mac_id=%d, " "consec_since_last_rx=%d, consec=%d, num_expect=%d " "num_rx=%d\n", __func__, le32toh(resp->mac_id), le32toh(resp->consec_missed_beacons_since_last_rx), le32toh(resp->consec_missed_beacons), le32toh(resp->num_expected_beacons), le32toh(resp->num_recvd_beacons)); /* Be paranoid */ if (vap == NULL) break; /* XXX no net80211 locking? */ if (vap->iv_state == IEEE80211_S_RUN && (ic->ic_flags & IEEE80211_F_SCAN) == 0) { if (missed > vap->iv_bmissthreshold) { /* XXX bad locking; turn into task */ IWM_UNLOCK(sc); ieee80211_beacon_miss(ic); IWM_LOCK(sc); } } break; } case IWM_MVM_ALIVE: { struct iwm_mvm_alive_resp *resp; SYNC_RESP_STRUCT(resp, pkt); sc->sc_uc.uc_error_event_table = le32toh(resp->error_event_table_ptr); sc->sc_uc.uc_log_event_table = le32toh(resp->log_event_table_ptr); sc->sched_base = le32toh(resp->scd_base_ptr); sc->sc_uc.uc_ok = resp->status == IWM_ALIVE_STATUS_OK; sc->sc_uc.uc_intr = 1; wakeup(&sc->sc_uc); break; } case IWM_CALIB_RES_NOTIF_PHY_DB: { struct iwm_calib_res_notif_phy_db *phy_db_notif; SYNC_RESP_STRUCT(phy_db_notif, pkt); iwm_phy_db_set_section(sc, phy_db_notif); break; } case IWM_STATISTICS_NOTIFICATION: { struct iwm_notif_statistics *stats; SYNC_RESP_STRUCT(stats, pkt); memcpy(&sc->sc_stats, stats, sizeof(sc->sc_stats)); sc->sc_noise = iwm_get_noise(&stats->rx.general); break; } case IWM_NVM_ACCESS_CMD: if (sc->sc_wantresp == ((qid << 16) | idx)) { bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD); memcpy(sc->sc_cmd_resp, pkt, sizeof(sc->sc_cmd_resp)); } break; case IWM_PHY_CONFIGURATION_CMD: case IWM_TX_ANT_CONFIGURATION_CMD: case IWM_ADD_STA: case IWM_MAC_CONTEXT_CMD: case IWM_REPLY_SF_CFG_CMD: case IWM_POWER_TABLE_CMD: case IWM_PHY_CONTEXT_CMD: case IWM_BINDING_CONTEXT_CMD: case IWM_TIME_EVENT_CMD: case IWM_SCAN_REQUEST_CMD: case IWM_REPLY_BEACON_FILTERING_CMD: case IWM_MAC_PM_POWER_TABLE: case IWM_TIME_QUOTA_CMD: case IWM_REMOVE_STA: case IWM_TXPATH_FLUSH: case IWM_LQ_CMD: SYNC_RESP_STRUCT(cresp, pkt); if (sc->sc_wantresp == ((qid << 16) | idx)) { memcpy(sc->sc_cmd_resp, pkt, sizeof(*pkt)+sizeof(*cresp)); } break; /* ignore */ case 0x6c: /* IWM_PHY_DB_CMD, no idea why it's not in fw-api.h */ break; case IWM_INIT_COMPLETE_NOTIF: sc->sc_init_complete = 1; wakeup(&sc->sc_init_complete); break; case IWM_SCAN_COMPLETE_NOTIFICATION: { struct iwm_scan_complete_notif *notif; SYNC_RESP_STRUCT(notif, pkt); taskqueue_enqueue(sc->sc_tq, &sc->sc_es_task); break; } case IWM_REPLY_ERROR: { struct iwm_error_resp *resp; SYNC_RESP_STRUCT(resp, pkt); device_printf(sc->sc_dev, "firmware error 0x%x, cmd 0x%x\n", le32toh(resp->error_type), resp->cmd_id); break; } case IWM_TIME_EVENT_NOTIFICATION: { struct iwm_time_event_notif *notif; SYNC_RESP_STRUCT(notif, pkt); if (notif->status) { if (le32toh(notif->action) & IWM_TE_V2_NOTIF_HOST_EVENT_START) sc->sc_auth_prot = 2; else sc->sc_auth_prot = 0; } else { sc->sc_auth_prot = -1; } IWM_DPRINTF(sc, IWM_DEBUG_INTR, "%s: time event notification auth_prot=%d\n", __func__, sc->sc_auth_prot); wakeup(&sc->sc_auth_prot); break; } case IWM_MCAST_FILTER_CMD: break; default: device_printf(sc->sc_dev, "frame %d/%d %x UNHANDLED (this should " "not happen)\n", qid, idx, pkt->len_n_flags); break; } /* * Why test bit 0x80? The Linux driver: * * There is one exception: uCode sets bit 15 when it * originates the response/notification, i.e. when the * response/notification is not a direct response to a * command sent by the driver. For example, uCode issues * IWM_REPLY_RX when it sends a received frame to the driver; * it is not a direct response to any driver command. * * Ok, so since when is 7 == 15? Well, the Linux driver * uses a slightly different format for pkt->hdr, and "qid" * is actually the upper byte of a two-byte field. */ if (!(pkt->hdr.qid & (1 << 7))) { iwm_cmd_done(sc, pkt); } ADVANCE_RXQ(sc); } IWM_CLRBITS(sc, IWM_CSR_GP_CNTRL, IWM_CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ); /* * Tell the firmware what we have processed. * Seems like the hardware gets upset unless we align * the write by 8?? */ hw = (hw == 0) ? IWM_RX_RING_COUNT - 1 : hw - 1; IWM_WRITE(sc, IWM_FH_RSCSR_CHNL0_WPTR, hw & ~7); } static void iwm_intr(void *arg) { struct iwm_softc *sc = arg; int handled = 0; int r1, r2, rv = 0; int isperiodic = 0; IWM_LOCK(sc); IWM_WRITE(sc, IWM_CSR_INT_MASK, 0); if (sc->sc_flags & IWM_FLAG_USE_ICT) { uint32_t *ict = sc->ict_dma.vaddr; int tmp; tmp = htole32(ict[sc->ict_cur]); if (!tmp) goto out_ena; /* * ok, there was something. keep plowing until we have all. */ r1 = r2 = 0; while (tmp) { r1 |= tmp; ict[sc->ict_cur] = 0; sc->ict_cur = (sc->ict_cur+1) % IWM_ICT_COUNT; tmp = htole32(ict[sc->ict_cur]); } /* this is where the fun begins. don't ask */ if (r1 == 0xffffffff) r1 = 0; /* i am not expected to understand this */ if (r1 & 0xc0000) r1 |= 0x8000; r1 = (0xff & r1) | ((0xff00 & r1) << 16); } else { r1 = IWM_READ(sc, IWM_CSR_INT); /* "hardware gone" (where, fishing?) */ if (r1 == 0xffffffff || (r1 & 0xfffffff0) == 0xa5a5a5a0) goto out; r2 = IWM_READ(sc, IWM_CSR_FH_INT_STATUS); } if (r1 == 0 && r2 == 0) { goto out_ena; } IWM_WRITE(sc, IWM_CSR_INT, r1 | ~sc->sc_intmask); /* ignored */ handled |= (r1 & (IWM_CSR_INT_BIT_ALIVE /*| IWM_CSR_INT_BIT_SCD*/)); if (r1 & IWM_CSR_INT_BIT_SW_ERR) { #ifdef IWM_DEBUG int i; struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); iwm_nic_error(sc); /* Dump driver status (TX and RX rings) while we're here. */ device_printf(sc->sc_dev, "driver status:\n"); for (i = 0; i < IWM_MVM_MAX_QUEUES; i++) { struct iwm_tx_ring *ring = &sc->txq[i]; device_printf(sc->sc_dev, " tx ring %2d: qid=%-2d cur=%-3d " "queued=%-3d\n", i, ring->qid, ring->cur, ring->queued); } device_printf(sc->sc_dev, " rx ring: cur=%d\n", sc->rxq.cur); device_printf(sc->sc_dev, " 802.11 state %d\n", vap->iv_state); #endif device_printf(sc->sc_dev, "fatal firmware error\n"); iwm_stop(sc); rv = 1; goto out; } if (r1 & IWM_CSR_INT_BIT_HW_ERR) { handled |= IWM_CSR_INT_BIT_HW_ERR; device_printf(sc->sc_dev, "hardware error, stopping device\n"); iwm_stop(sc); rv = 1; goto out; } /* firmware chunk loaded */ if (r1 & IWM_CSR_INT_BIT_FH_TX) { IWM_WRITE(sc, IWM_CSR_FH_INT_STATUS, IWM_CSR_FH_INT_TX_MASK); handled |= IWM_CSR_INT_BIT_FH_TX; sc->sc_fw_chunk_done = 1; wakeup(&sc->sc_fw); } if (r1 & IWM_CSR_INT_BIT_RF_KILL) { handled |= IWM_CSR_INT_BIT_RF_KILL; if (iwm_check_rfkill(sc)) { device_printf(sc->sc_dev, "%s: rfkill switch, disabling interface\n", __func__); iwm_stop(sc); } } /* * The Linux driver uses periodic interrupts to avoid races. * We cargo-cult like it's going out of fashion. */ if (r1 & IWM_CSR_INT_BIT_RX_PERIODIC) { handled |= IWM_CSR_INT_BIT_RX_PERIODIC; IWM_WRITE(sc, IWM_CSR_INT, IWM_CSR_INT_BIT_RX_PERIODIC); if ((r1 & (IWM_CSR_INT_BIT_FH_RX | IWM_CSR_INT_BIT_SW_RX)) == 0) IWM_WRITE_1(sc, IWM_CSR_INT_PERIODIC_REG, IWM_CSR_INT_PERIODIC_DIS); isperiodic = 1; } if ((r1 & (IWM_CSR_INT_BIT_FH_RX | IWM_CSR_INT_BIT_SW_RX)) || isperiodic) { handled |= (IWM_CSR_INT_BIT_FH_RX | IWM_CSR_INT_BIT_SW_RX); IWM_WRITE(sc, IWM_CSR_FH_INT_STATUS, IWM_CSR_FH_INT_RX_MASK); iwm_notif_intr(sc); /* enable periodic interrupt, see above */ if (r1 & (IWM_CSR_INT_BIT_FH_RX | IWM_CSR_INT_BIT_SW_RX) && !isperiodic) IWM_WRITE_1(sc, IWM_CSR_INT_PERIODIC_REG, IWM_CSR_INT_PERIODIC_ENA); } if (__predict_false(r1 & ~handled)) IWM_DPRINTF(sc, IWM_DEBUG_INTR, "%s: unhandled interrupts: %x\n", __func__, r1); rv = 1; out_ena: iwm_restore_interrupts(sc); out: IWM_UNLOCK(sc); return; } /* * Autoconf glue-sniffing */ #define PCI_VENDOR_INTEL 0x8086 #define PCI_PRODUCT_INTEL_WL_3160_1 0x08b3 #define PCI_PRODUCT_INTEL_WL_3160_2 0x08b4 #define PCI_PRODUCT_INTEL_WL_7260_1 0x08b1 #define PCI_PRODUCT_INTEL_WL_7260_2 0x08b2 #define PCI_PRODUCT_INTEL_WL_7265_1 0x095a #define PCI_PRODUCT_INTEL_WL_7265_2 0x095b static const struct iwm_devices { uint16_t device; const char *name; } iwm_devices[] = { { PCI_PRODUCT_INTEL_WL_3160_1, "Intel Dual Band Wireless AC 3160" }, { PCI_PRODUCT_INTEL_WL_3160_2, "Intel Dual Band Wireless AC 3160" }, { PCI_PRODUCT_INTEL_WL_7260_1, "Intel Dual Band Wireless AC 7260" }, { PCI_PRODUCT_INTEL_WL_7260_2, "Intel Dual Band Wireless AC 7260" }, { PCI_PRODUCT_INTEL_WL_7265_1, "Intel Dual Band Wireless AC 7265" }, { PCI_PRODUCT_INTEL_WL_7265_2, "Intel Dual Band Wireless AC 7265" }, }; static int iwm_probe(device_t dev) { int i; for (i = 0; i < nitems(iwm_devices); i++) if (pci_get_vendor(dev) == PCI_VENDOR_INTEL && pci_get_device(dev) == iwm_devices[i].device) { device_set_desc(dev, iwm_devices[i].name); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int iwm_dev_check(device_t dev) { struct iwm_softc *sc; sc = device_get_softc(dev); switch (pci_get_device(dev)) { case PCI_PRODUCT_INTEL_WL_3160_1: case PCI_PRODUCT_INTEL_WL_3160_2: sc->sc_fwname = "iwm3160fw"; sc->host_interrupt_operation_mode = 1; return (0); case PCI_PRODUCT_INTEL_WL_7260_1: case PCI_PRODUCT_INTEL_WL_7260_2: sc->sc_fwname = "iwm7260fw"; sc->host_interrupt_operation_mode = 1; return (0); case PCI_PRODUCT_INTEL_WL_7265_1: case PCI_PRODUCT_INTEL_WL_7265_2: sc->sc_fwname = "iwm7265fw"; sc->host_interrupt_operation_mode = 0; return (0); default: device_printf(dev, "unknown adapter type\n"); return ENXIO; } } static int iwm_pci_attach(device_t dev) { struct iwm_softc *sc; int count, error, rid; uint16_t reg; sc = device_get_softc(dev); /* Clear device-specific "PCI retry timeout" register (41h). */ reg = pci_read_config(dev, 0x40, sizeof(reg)); pci_write_config(dev, 0x40, reg & ~0xff00, sizeof(reg)); /* Enable bus-mastering and hardware bug workaround. */ pci_enable_busmaster(dev); reg = pci_read_config(dev, PCIR_STATUS, sizeof(reg)); /* if !MSI */ if (reg & PCIM_STATUS_INTxSTATE) { reg &= ~PCIM_STATUS_INTxSTATE; } pci_write_config(dev, PCIR_STATUS, reg, sizeof(reg)); rid = PCIR_BAR(0); sc->sc_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->sc_mem == NULL) { device_printf(sc->sc_dev, "can't map mem space\n"); return (ENXIO); } sc->sc_st = rman_get_bustag(sc->sc_mem); sc->sc_sh = rman_get_bushandle(sc->sc_mem); /* Install interrupt handler. */ count = 1; rid = 0; if (pci_alloc_msi(dev, &count) == 0) rid = 1; sc->sc_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE | (rid != 0 ? 0 : RF_SHAREABLE)); if (sc->sc_irq == NULL) { device_printf(dev, "can't map interrupt\n"); return (ENXIO); } error = bus_setup_intr(dev, sc->sc_irq, INTR_TYPE_NET | INTR_MPSAFE, NULL, iwm_intr, sc, &sc->sc_ih); if (sc->sc_ih == NULL) { device_printf(dev, "can't establish interrupt"); return (ENXIO); } sc->sc_dmat = bus_get_dma_tag(sc->sc_dev); return (0); } static void iwm_pci_detach(device_t dev) { struct iwm_softc *sc = device_get_softc(dev); if (sc->sc_irq != NULL) { bus_teardown_intr(dev, sc->sc_irq, sc->sc_ih); bus_release_resource(dev, SYS_RES_IRQ, rman_get_rid(sc->sc_irq), sc->sc_irq); pci_release_msi(dev); } if (sc->sc_mem != NULL) bus_release_resource(dev, SYS_RES_MEMORY, rman_get_rid(sc->sc_mem), sc->sc_mem); } static int iwm_attach(device_t dev) { struct iwm_softc *sc = device_get_softc(dev); struct ieee80211com *ic = &sc->sc_ic; int error; int txq_i, i; sc->sc_dev = dev; mtx_init(&sc->sc_mtx, "iwm_mtx", MTX_DEF, 0); mbufq_init(&sc->sc_snd, ifqmaxlen); callout_init_mtx(&sc->sc_watchdog_to, &sc->sc_mtx, 0); TASK_INIT(&sc->sc_es_task, 0, iwm_endscan_cb, sc); sc->sc_tq = taskqueue_create("iwm_taskq", M_WAITOK, taskqueue_thread_enqueue, &sc->sc_tq); error = taskqueue_start_threads(&sc->sc_tq, 1, 0, "iwm_taskq"); if (error != 0) { device_printf(dev, "can't start threads, error %d\n", error); goto fail; } /* PCI attach */ error = iwm_pci_attach(dev); if (error != 0) goto fail; sc->sc_wantresp = -1; /* Check device type */ error = iwm_dev_check(dev); if (error != 0) goto fail; sc->sc_fwdmasegsz = IWM_FWDMASEGSZ; /* * We now start fiddling with the hardware */ sc->sc_hw_rev = IWM_READ(sc, IWM_CSR_HW_REV); if (iwm_prepare_card_hw(sc) != 0) { device_printf(dev, "could not initialize hardware\n"); goto fail; } /* Allocate DMA memory for firmware transfers. */ if ((error = iwm_alloc_fwmem(sc)) != 0) { device_printf(dev, "could not allocate memory for firmware\n"); goto fail; } /* Allocate "Keep Warm" page. */ if ((error = iwm_alloc_kw(sc)) != 0) { device_printf(dev, "could not allocate keep warm page\n"); goto fail; } /* We use ICT interrupts */ if ((error = iwm_alloc_ict(sc)) != 0) { device_printf(dev, "could not allocate ICT table\n"); goto fail; } /* Allocate TX scheduler "rings". */ if ((error = iwm_alloc_sched(sc)) != 0) { device_printf(dev, "could not allocate TX scheduler rings\n"); goto fail; } /* Allocate TX rings */ for (txq_i = 0; txq_i < nitems(sc->txq); txq_i++) { if ((error = iwm_alloc_tx_ring(sc, &sc->txq[txq_i], txq_i)) != 0) { device_printf(dev, "could not allocate TX ring %d\n", txq_i); goto fail; } } /* Allocate RX ring. */ if ((error = iwm_alloc_rx_ring(sc, &sc->rxq)) != 0) { device_printf(dev, "could not allocate RX ring\n"); goto fail; } /* Clear pending interrupts. */ IWM_WRITE(sc, IWM_CSR_INT, 0xffffffff); ic->ic_softc = sc; ic->ic_name = device_get_nameunit(sc->sc_dev); ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ ic->ic_opmode = IEEE80211_M_STA; /* default to BSS mode */ /* Set device capabilities. */ ic->ic_caps = IEEE80211_C_STA | IEEE80211_C_WPA | /* WPA/RSN */ IEEE80211_C_WME | IEEE80211_C_SHSLOT | /* short slot time supported */ IEEE80211_C_SHPREAMBLE /* short preamble supported */ // IEEE80211_C_BGSCAN /* capable of bg scanning */ ; for (i = 0; i < nitems(sc->sc_phyctxt); i++) { sc->sc_phyctxt[i].id = i; sc->sc_phyctxt[i].color = 0; sc->sc_phyctxt[i].ref = 0; sc->sc_phyctxt[i].channel = NULL; } /* Max RSSI */ sc->sc_max_rssi = IWM_MAX_DBM - IWM_MIN_DBM; sc->sc_preinit_hook.ich_func = iwm_preinit; sc->sc_preinit_hook.ich_arg = sc; if (config_intrhook_establish(&sc->sc_preinit_hook) != 0) { device_printf(dev, "config_intrhook_establish failed\n"); goto fail; } #ifdef IWM_DEBUG SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "debug", CTLFLAG_RW, &sc->sc_debug, 0, "control debugging"); #endif IWM_DPRINTF(sc, IWM_DEBUG_RESET | IWM_DEBUG_TRACE, "<-%s\n", __func__); return 0; /* Free allocated memory if something failed during attachment. */ fail: iwm_detach_local(sc, 0); return ENXIO; } static int iwm_update_edca(struct ieee80211com *ic) { struct iwm_softc *sc = ic->ic_softc; device_printf(sc->sc_dev, "%s: called\n", __func__); return (0); } static void iwm_preinit(void *arg) { struct iwm_softc *sc = arg; device_t dev = sc->sc_dev; struct ieee80211com *ic = &sc->sc_ic; int error; IWM_DPRINTF(sc, IWM_DEBUG_RESET | IWM_DEBUG_TRACE, "->%s\n", __func__); IWM_LOCK(sc); if ((error = iwm_start_hw(sc)) != 0) { device_printf(dev, "could not initialize hardware\n"); IWM_UNLOCK(sc); goto fail; } error = iwm_run_init_mvm_ucode(sc, 1); iwm_stop_device(sc); if (error) { IWM_UNLOCK(sc); goto fail; } device_printf(dev, "revision: 0x%x, firmware %d.%d (API ver. %d)\n", sc->sc_hw_rev & IWM_CSR_HW_REV_TYPE_MSK, IWM_UCODE_MAJOR(sc->sc_fwver), IWM_UCODE_MINOR(sc->sc_fwver), IWM_UCODE_API(sc->sc_fwver)); /* not all hardware can do 5GHz band */ if (!sc->sc_nvm.sku_cap_band_52GHz_enable) memset(&ic->ic_sup_rates[IEEE80211_MODE_11A], 0, sizeof(ic->ic_sup_rates[IEEE80211_MODE_11A])); IWM_UNLOCK(sc); /* * At this point we've committed - if we fail to do setup, * we now also have to tear down the net80211 state. */ ieee80211_ifattach(ic); ic->ic_vap_create = iwm_vap_create; ic->ic_vap_delete = iwm_vap_delete; ic->ic_raw_xmit = iwm_raw_xmit; ic->ic_node_alloc = iwm_node_alloc; ic->ic_scan_start = iwm_scan_start; ic->ic_scan_end = iwm_scan_end; ic->ic_update_mcast = iwm_update_mcast; ic->ic_set_channel = iwm_set_channel; ic->ic_scan_curchan = iwm_scan_curchan; ic->ic_scan_mindwell = iwm_scan_mindwell; ic->ic_wme.wme_update = iwm_update_edca; ic->ic_parent = iwm_parent; ic->ic_transmit = iwm_transmit; iwm_radiotap_attach(sc); if (bootverbose) ieee80211_announce(ic); IWM_DPRINTF(sc, IWM_DEBUG_RESET | IWM_DEBUG_TRACE, "<-%s\n", __func__); config_intrhook_disestablish(&sc->sc_preinit_hook); return; fail: config_intrhook_disestablish(&sc->sc_preinit_hook); iwm_detach_local(sc, 0); } /* * Attach the interface to 802.11 radiotap. */ static void iwm_radiotap_attach(struct iwm_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; IWM_DPRINTF(sc, IWM_DEBUG_RESET | IWM_DEBUG_TRACE, "->%s begin\n", __func__); ieee80211_radiotap_attach(ic, &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap), IWM_TX_RADIOTAP_PRESENT, &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap), IWM_RX_RADIOTAP_PRESENT); IWM_DPRINTF(sc, IWM_DEBUG_RESET | IWM_DEBUG_TRACE, "->%s end\n", __func__); } static struct ieee80211vap * iwm_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, enum ieee80211_opmode opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t mac[IEEE80211_ADDR_LEN]) { struct iwm_vap *ivp; struct ieee80211vap *vap; if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ return NULL; ivp = malloc(sizeof(struct iwm_vap), M_80211_VAP, M_WAITOK | M_ZERO); vap = &ivp->iv_vap; ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid); vap->iv_bmissthreshold = 10; /* override default */ /* Override with driver methods. */ ivp->iv_newstate = vap->iv_newstate; vap->iv_newstate = iwm_newstate; ieee80211_ratectl_init(vap); /* Complete setup. */ ieee80211_vap_attach(vap, iwm_media_change, ieee80211_media_status, mac); ic->ic_opmode = opmode; return vap; } static void iwm_vap_delete(struct ieee80211vap *vap) { struct iwm_vap *ivp = IWM_VAP(vap); ieee80211_ratectl_deinit(vap); ieee80211_vap_detach(vap); free(ivp, M_80211_VAP); } static void iwm_scan_start(struct ieee80211com *ic) { struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); struct iwm_softc *sc = ic->ic_softc; int error; if (sc->sc_scanband) return; IWM_LOCK(sc); error = iwm_mvm_scan_request(sc, IEEE80211_CHAN_2GHZ, 0, NULL, 0); if (error) { device_printf(sc->sc_dev, "could not initiate scan\n"); IWM_UNLOCK(sc); ieee80211_cancel_scan(vap); } else IWM_UNLOCK(sc); } static void iwm_scan_end(struct ieee80211com *ic) { } static void iwm_update_mcast(struct ieee80211com *ic) { } static void iwm_set_channel(struct ieee80211com *ic) { } static void iwm_scan_curchan(struct ieee80211_scan_state *ss, unsigned long maxdwell) { } static void iwm_scan_mindwell(struct ieee80211_scan_state *ss) { return; } void iwm_init_task(void *arg1) { struct iwm_softc *sc = arg1; IWM_LOCK(sc); while (sc->sc_flags & IWM_FLAG_BUSY) msleep(&sc->sc_flags, &sc->sc_mtx, 0, "iwmpwr", 0); sc->sc_flags |= IWM_FLAG_BUSY; iwm_stop(sc); if (sc->sc_ic.ic_nrunning > 0) iwm_init(sc); sc->sc_flags &= ~IWM_FLAG_BUSY; wakeup(&sc->sc_flags); IWM_UNLOCK(sc); } static int iwm_resume(device_t dev) { uint16_t reg; /* Clear device-specific "PCI retry timeout" register (41h). */ reg = pci_read_config(dev, 0x40, sizeof(reg)); pci_write_config(dev, 0x40, reg & ~0xff00, sizeof(reg)); iwm_init_task(device_get_softc(dev)); return 0; } static int iwm_suspend(device_t dev) { struct iwm_softc *sc = device_get_softc(dev); if (sc->sc_ic.ic_nrunning > 0) { IWM_LOCK(sc); iwm_stop(sc); IWM_UNLOCK(sc); } return (0); } static int iwm_detach_local(struct iwm_softc *sc, int do_net80211) { struct iwm_fw_info *fw = &sc->sc_fw; device_t dev = sc->sc_dev; int i; if (sc->sc_tq) { taskqueue_drain_all(sc->sc_tq); taskqueue_free(sc->sc_tq); } callout_drain(&sc->sc_watchdog_to); iwm_stop_device(sc); if (do_net80211) ieee80211_ifdetach(&sc->sc_ic); /* Free descriptor rings */ for (i = 0; i < nitems(sc->txq); i++) iwm_free_tx_ring(sc, &sc->txq[i]); /* Free firmware */ if (fw->fw_rawdata != NULL) iwm_fw_info_free(fw); /* free scheduler */ iwm_free_sched(sc); if (sc->ict_dma.vaddr != NULL) iwm_free_ict(sc); if (sc->kw_dma.vaddr != NULL) iwm_free_kw(sc); if (sc->fw_dma.vaddr != NULL) iwm_free_fwmem(sc); /* Finished with the hardware - detach things */ iwm_pci_detach(dev); mbufq_drain(&sc->sc_snd); mtx_destroy(&sc->sc_mtx); return (0); } static int iwm_detach(device_t dev) { struct iwm_softc *sc = device_get_softc(dev); return (iwm_detach_local(sc, 1)); } static device_method_t iwm_pci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, iwm_probe), DEVMETHOD(device_attach, iwm_attach), DEVMETHOD(device_detach, iwm_detach), DEVMETHOD(device_suspend, iwm_suspend), DEVMETHOD(device_resume, iwm_resume), DEVMETHOD_END }; static driver_t iwm_pci_driver = { "iwm", iwm_pci_methods, sizeof (struct iwm_softc) }; static devclass_t iwm_devclass; DRIVER_MODULE(iwm, pci, iwm_pci_driver, iwm_devclass, NULL, NULL); MODULE_DEPEND(iwm, firmware, 1, 1, 1); MODULE_DEPEND(iwm, pci, 1, 1, 1); MODULE_DEPEND(iwm, wlan, 1, 1, 1); Index: head/sys/dev/iwn/if_iwn.c =================================================================== --- head/sys/dev/iwn/if_iwn.c (revision 289678) +++ head/sys/dev/iwn/if_iwn.c (revision 289679) @@ -1,8979 +1,8977 @@ /*- * Copyright (c) 2007-2009 Damien Bergamini * Copyright (c) 2008 Benjamin Close * Copyright (c) 2008 Sam Leffler, Errno Consulting * Copyright (c) 2011 Intel Corporation * Copyright (c) 2013 Cedric GROSS * Copyright (c) 2013 Adrian Chadd * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Driver for Intel WiFi Link 4965 and 1000/5000/6000 Series 802.11 network * adapters. */ #include __FBSDID("$FreeBSD$"); #include "opt_wlan.h" #include "opt_iwn.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct iwn_ident { uint16_t vendor; uint16_t device; const char *name; }; static const struct iwn_ident iwn_ident_table[] = { { 0x8086, IWN_DID_6x05_1, "Intel Centrino Advanced-N 6205" }, { 0x8086, IWN_DID_1000_1, "Intel Centrino Wireless-N 1000" }, { 0x8086, IWN_DID_1000_2, "Intel Centrino Wireless-N 1000" }, { 0x8086, IWN_DID_6x05_2, "Intel Centrino Advanced-N 6205" }, { 0x8086, IWN_DID_6050_1, "Intel Centrino Advanced-N + WiMAX 6250" }, { 0x8086, IWN_DID_6050_2, "Intel Centrino Advanced-N + WiMAX 6250" }, { 0x8086, IWN_DID_x030_1, "Intel Centrino Wireless-N 1030" }, { 0x8086, IWN_DID_x030_2, "Intel Centrino Wireless-N 1030" }, { 0x8086, IWN_DID_x030_3, "Intel Centrino Advanced-N 6230" }, { 0x8086, IWN_DID_x030_4, "Intel Centrino Advanced-N 6230" }, { 0x8086, IWN_DID_6150_1, "Intel Centrino Wireless-N + WiMAX 6150" }, { 0x8086, IWN_DID_6150_2, "Intel Centrino Wireless-N + WiMAX 6150" }, { 0x8086, IWN_DID_2x00_1, "Intel(R) Centrino(R) Wireless-N 2200 BGN" }, { 0x8086, IWN_DID_2x00_2, "Intel(R) Centrino(R) Wireless-N 2200 BGN" }, /* XXX 2200D is IWN_SDID_2x00_4; there's no way to express this here! */ { 0x8086, IWN_DID_2x30_1, "Intel Centrino Wireless-N 2230" }, { 0x8086, IWN_DID_2x30_2, "Intel Centrino Wireless-N 2230" }, { 0x8086, IWN_DID_130_1, "Intel Centrino Wireless-N 130" }, { 0x8086, IWN_DID_130_2, "Intel Centrino Wireless-N 130" }, { 0x8086, IWN_DID_100_1, "Intel Centrino Wireless-N 100" }, { 0x8086, IWN_DID_100_2, "Intel Centrino Wireless-N 100" }, { 0x8086, IWN_DID_105_1, "Intel Centrino Wireless-N 105" }, { 0x8086, IWN_DID_105_2, "Intel Centrino Wireless-N 105" }, { 0x8086, IWN_DID_135_1, "Intel Centrino Wireless-N 135" }, { 0x8086, IWN_DID_135_2, "Intel Centrino Wireless-N 135" }, { 0x8086, IWN_DID_4965_1, "Intel Wireless WiFi Link 4965" }, { 0x8086, IWN_DID_6x00_1, "Intel Centrino Ultimate-N 6300" }, { 0x8086, IWN_DID_6x00_2, "Intel Centrino Advanced-N 6200" }, { 0x8086, IWN_DID_4965_2, "Intel Wireless WiFi Link 4965" }, { 0x8086, IWN_DID_4965_3, "Intel Wireless WiFi Link 4965" }, { 0x8086, IWN_DID_5x00_1, "Intel WiFi Link 5100" }, { 0x8086, IWN_DID_4965_4, "Intel Wireless WiFi Link 4965" }, { 0x8086, IWN_DID_5x00_3, "Intel Ultimate N WiFi Link 5300" }, { 0x8086, IWN_DID_5x00_4, "Intel Ultimate N WiFi Link 5300" }, { 0x8086, IWN_DID_5x00_2, "Intel WiFi Link 5100" }, { 0x8086, IWN_DID_6x00_3, "Intel Centrino Ultimate-N 6300" }, { 0x8086, IWN_DID_6x00_4, "Intel Centrino Advanced-N 6200" }, { 0x8086, IWN_DID_5x50_1, "Intel WiMAX/WiFi Link 5350" }, { 0x8086, IWN_DID_5x50_2, "Intel WiMAX/WiFi Link 5350" }, { 0x8086, IWN_DID_5x50_3, "Intel WiMAX/WiFi Link 5150" }, { 0x8086, IWN_DID_5x50_4, "Intel WiMAX/WiFi Link 5150" }, { 0x8086, IWN_DID_6035_1, "Intel Centrino Advanced 6235" }, { 0x8086, IWN_DID_6035_2, "Intel Centrino Advanced 6235" }, { 0, 0, NULL } }; static int iwn_probe(device_t); static int iwn_attach(device_t); static int iwn4965_attach(struct iwn_softc *, uint16_t); static int iwn5000_attach(struct iwn_softc *, uint16_t); static int iwn_config_specific(struct iwn_softc *, uint16_t); static void iwn_radiotap_attach(struct iwn_softc *); static void iwn_sysctlattach(struct iwn_softc *); static struct ieee80211vap *iwn_vap_create(struct ieee80211com *, const char [IFNAMSIZ], int, enum ieee80211_opmode, int, const uint8_t [IEEE80211_ADDR_LEN], const uint8_t [IEEE80211_ADDR_LEN]); static void iwn_vap_delete(struct ieee80211vap *); static int iwn_detach(device_t); static int iwn_shutdown(device_t); static int iwn_suspend(device_t); static int iwn_resume(device_t); static int iwn_nic_lock(struct iwn_softc *); static int iwn_eeprom_lock(struct iwn_softc *); static int iwn_init_otprom(struct iwn_softc *); static int iwn_read_prom_data(struct iwn_softc *, uint32_t, void *, int); static void iwn_dma_map_addr(void *, bus_dma_segment_t *, int, int); static int iwn_dma_contig_alloc(struct iwn_softc *, struct iwn_dma_info *, void **, bus_size_t, bus_size_t); static void iwn_dma_contig_free(struct iwn_dma_info *); static int iwn_alloc_sched(struct iwn_softc *); static void iwn_free_sched(struct iwn_softc *); static int iwn_alloc_kw(struct iwn_softc *); static void iwn_free_kw(struct iwn_softc *); static int iwn_alloc_ict(struct iwn_softc *); static void iwn_free_ict(struct iwn_softc *); static int iwn_alloc_fwmem(struct iwn_softc *); static void iwn_free_fwmem(struct iwn_softc *); static int iwn_alloc_rx_ring(struct iwn_softc *, struct iwn_rx_ring *); static void iwn_reset_rx_ring(struct iwn_softc *, struct iwn_rx_ring *); static void iwn_free_rx_ring(struct iwn_softc *, struct iwn_rx_ring *); static int iwn_alloc_tx_ring(struct iwn_softc *, struct iwn_tx_ring *, int); static void iwn_reset_tx_ring(struct iwn_softc *, struct iwn_tx_ring *); static void iwn_free_tx_ring(struct iwn_softc *, struct iwn_tx_ring *); static void iwn5000_ict_reset(struct iwn_softc *); static int iwn_read_eeprom(struct iwn_softc *, uint8_t macaddr[IEEE80211_ADDR_LEN]); static void iwn4965_read_eeprom(struct iwn_softc *); #ifdef IWN_DEBUG static void iwn4965_print_power_group(struct iwn_softc *, int); #endif static void iwn5000_read_eeprom(struct iwn_softc *); static uint32_t iwn_eeprom_channel_flags(struct iwn_eeprom_chan *); static void iwn_read_eeprom_band(struct iwn_softc *, int); static void iwn_read_eeprom_ht40(struct iwn_softc *, int); static void iwn_read_eeprom_channels(struct iwn_softc *, int, uint32_t); static struct iwn_eeprom_chan *iwn_find_eeprom_channel(struct iwn_softc *, struct ieee80211_channel *); static int iwn_setregdomain(struct ieee80211com *, struct ieee80211_regdomain *, int, struct ieee80211_channel[]); static void iwn_read_eeprom_enhinfo(struct iwn_softc *); static struct ieee80211_node *iwn_node_alloc(struct ieee80211vap *, const uint8_t mac[IEEE80211_ADDR_LEN]); static void iwn_newassoc(struct ieee80211_node *, int); static int iwn_media_change(struct ifnet *); static int iwn_newstate(struct ieee80211vap *, enum ieee80211_state, int); static void iwn_calib_timeout(void *); static void iwn_rx_phy(struct iwn_softc *, struct iwn_rx_desc *, struct iwn_rx_data *); static void iwn_rx_done(struct iwn_softc *, struct iwn_rx_desc *, struct iwn_rx_data *); static void iwn_rx_compressed_ba(struct iwn_softc *, struct iwn_rx_desc *, struct iwn_rx_data *); static void iwn5000_rx_calib_results(struct iwn_softc *, struct iwn_rx_desc *, struct iwn_rx_data *); static void iwn_rx_statistics(struct iwn_softc *, struct iwn_rx_desc *, struct iwn_rx_data *); static void iwn4965_tx_done(struct iwn_softc *, struct iwn_rx_desc *, struct iwn_rx_data *); static void iwn5000_tx_done(struct iwn_softc *, struct iwn_rx_desc *, struct iwn_rx_data *); static void iwn_tx_done(struct iwn_softc *, struct iwn_rx_desc *, int, uint8_t); static void iwn_ampdu_tx_done(struct iwn_softc *, int, int, int, int, void *); static void iwn_cmd_done(struct iwn_softc *, struct iwn_rx_desc *); static void iwn_notif_intr(struct iwn_softc *); static void iwn_wakeup_intr(struct iwn_softc *); static void iwn_rftoggle_intr(struct iwn_softc *); static void iwn_fatal_intr(struct iwn_softc *); static void iwn_intr(void *); static void iwn4965_update_sched(struct iwn_softc *, int, int, uint8_t, uint16_t); static void iwn5000_update_sched(struct iwn_softc *, int, int, uint8_t, uint16_t); #ifdef notyet static void iwn5000_reset_sched(struct iwn_softc *, int, int); #endif static int iwn_tx_data(struct iwn_softc *, struct mbuf *, struct ieee80211_node *); static int iwn_tx_data_raw(struct iwn_softc *, struct mbuf *, struct ieee80211_node *, const struct ieee80211_bpf_params *params); static void iwn_xmit_task(void *arg0, int pending); static int iwn_raw_xmit(struct ieee80211_node *, struct mbuf *, const struct ieee80211_bpf_params *); static int iwn_transmit(struct ieee80211com *, struct mbuf *); static void iwn_watchdog(void *); static int iwn_ioctl(struct ieee80211com *, u_long , void *); static void iwn_parent(struct ieee80211com *); static int iwn_cmd(struct iwn_softc *, int, const void *, int, int); static int iwn4965_add_node(struct iwn_softc *, struct iwn_node_info *, int); static int iwn5000_add_node(struct iwn_softc *, struct iwn_node_info *, int); static int iwn_set_link_quality(struct iwn_softc *, struct ieee80211_node *); static int iwn_add_broadcast_node(struct iwn_softc *, int); static int iwn_updateedca(struct ieee80211com *); static void iwn_update_mcast(struct ieee80211com *); static void iwn_set_led(struct iwn_softc *, uint8_t, uint8_t, uint8_t); static int iwn_set_critical_temp(struct iwn_softc *); static int iwn_set_timing(struct iwn_softc *, struct ieee80211_node *); static void iwn4965_power_calibration(struct iwn_softc *, int); static int iwn4965_set_txpower(struct iwn_softc *, struct ieee80211_channel *, int); static int iwn5000_set_txpower(struct iwn_softc *, struct ieee80211_channel *, int); static int iwn4965_get_rssi(struct iwn_softc *, struct iwn_rx_stat *); static int iwn5000_get_rssi(struct iwn_softc *, struct iwn_rx_stat *); static int iwn_get_noise(const struct iwn_rx_general_stats *); static int iwn4965_get_temperature(struct iwn_softc *); static int iwn5000_get_temperature(struct iwn_softc *); static int iwn_init_sensitivity(struct iwn_softc *); static void iwn_collect_noise(struct iwn_softc *, const struct iwn_rx_general_stats *); static int iwn4965_init_gains(struct iwn_softc *); static int iwn5000_init_gains(struct iwn_softc *); static int iwn4965_set_gains(struct iwn_softc *); static int iwn5000_set_gains(struct iwn_softc *); static void iwn_tune_sensitivity(struct iwn_softc *, const struct iwn_rx_stats *); static void iwn_save_stats_counters(struct iwn_softc *, const struct iwn_stats *); static int iwn_send_sensitivity(struct iwn_softc *); static void iwn_check_rx_recovery(struct iwn_softc *, struct iwn_stats *); static int iwn_set_pslevel(struct iwn_softc *, int, int, int); static int iwn_send_btcoex(struct iwn_softc *); static int iwn_send_advanced_btcoex(struct iwn_softc *); static int iwn5000_runtime_calib(struct iwn_softc *); static int iwn_config(struct iwn_softc *); static int iwn_scan(struct iwn_softc *, struct ieee80211vap *, struct ieee80211_scan_state *, struct ieee80211_channel *); static int iwn_auth(struct iwn_softc *, struct ieee80211vap *vap); static int iwn_run(struct iwn_softc *, struct ieee80211vap *vap); static int iwn_ampdu_rx_start(struct ieee80211_node *, struct ieee80211_rx_ampdu *, int, int, int); static void iwn_ampdu_rx_stop(struct ieee80211_node *, struct ieee80211_rx_ampdu *); static int iwn_addba_request(struct ieee80211_node *, struct ieee80211_tx_ampdu *, int, int, int); static int iwn_addba_response(struct ieee80211_node *, struct ieee80211_tx_ampdu *, int, int, int); static int iwn_ampdu_tx_start(struct ieee80211com *, struct ieee80211_node *, uint8_t); static void iwn_ampdu_tx_stop(struct ieee80211_node *, struct ieee80211_tx_ampdu *); static void iwn4965_ampdu_tx_start(struct iwn_softc *, struct ieee80211_node *, int, uint8_t, uint16_t); static void iwn4965_ampdu_tx_stop(struct iwn_softc *, int, uint8_t, uint16_t); static void iwn5000_ampdu_tx_start(struct iwn_softc *, struct ieee80211_node *, int, uint8_t, uint16_t); static void iwn5000_ampdu_tx_stop(struct iwn_softc *, int, uint8_t, uint16_t); static int iwn5000_query_calibration(struct iwn_softc *); static int iwn5000_send_calibration(struct iwn_softc *); static int iwn5000_send_wimax_coex(struct iwn_softc *); static int iwn5000_crystal_calib(struct iwn_softc *); static int iwn5000_temp_offset_calib(struct iwn_softc *); static int iwn5000_temp_offset_calibv2(struct iwn_softc *); static int iwn4965_post_alive(struct iwn_softc *); static int iwn5000_post_alive(struct iwn_softc *); static int iwn4965_load_bootcode(struct iwn_softc *, const uint8_t *, int); static int iwn4965_load_firmware(struct iwn_softc *); static int iwn5000_load_firmware_section(struct iwn_softc *, uint32_t, const uint8_t *, int); static int iwn5000_load_firmware(struct iwn_softc *); static int iwn_read_firmware_leg(struct iwn_softc *, struct iwn_fw_info *); static int iwn_read_firmware_tlv(struct iwn_softc *, struct iwn_fw_info *, uint16_t); static int iwn_read_firmware(struct iwn_softc *); static int iwn_clock_wait(struct iwn_softc *); static int iwn_apm_init(struct iwn_softc *); static void iwn_apm_stop_master(struct iwn_softc *); static void iwn_apm_stop(struct iwn_softc *); static int iwn4965_nic_config(struct iwn_softc *); static int iwn5000_nic_config(struct iwn_softc *); static int iwn_hw_prepare(struct iwn_softc *); static int iwn_hw_init(struct iwn_softc *); static void iwn_hw_stop(struct iwn_softc *); static void iwn_radio_on(void *, int); static void iwn_radio_off(void *, int); static void iwn_panicked(void *, int); static void iwn_init_locked(struct iwn_softc *); static void iwn_init(struct iwn_softc *); static void iwn_stop_locked(struct iwn_softc *); static void iwn_stop(struct iwn_softc *); static void iwn_scan_start(struct ieee80211com *); static void iwn_scan_end(struct ieee80211com *); static void iwn_set_channel(struct ieee80211com *); static void iwn_scan_curchan(struct ieee80211_scan_state *, unsigned long); static void iwn_scan_mindwell(struct ieee80211_scan_state *); static void iwn_hw_reset(void *, int); #ifdef IWN_DEBUG static char *iwn_get_csr_string(int); static void iwn_debug_register(struct iwn_softc *); #endif static device_method_t iwn_methods[] = { /* Device interface */ DEVMETHOD(device_probe, iwn_probe), DEVMETHOD(device_attach, iwn_attach), DEVMETHOD(device_detach, iwn_detach), DEVMETHOD(device_shutdown, iwn_shutdown), DEVMETHOD(device_suspend, iwn_suspend), DEVMETHOD(device_resume, iwn_resume), DEVMETHOD_END }; static driver_t iwn_driver = { "iwn", iwn_methods, sizeof(struct iwn_softc) }; static devclass_t iwn_devclass; DRIVER_MODULE(iwn, pci, iwn_driver, iwn_devclass, NULL, NULL); MODULE_VERSION(iwn, 1); MODULE_DEPEND(iwn, firmware, 1, 1, 1); MODULE_DEPEND(iwn, pci, 1, 1, 1); MODULE_DEPEND(iwn, wlan, 1, 1, 1); static d_ioctl_t iwn_cdev_ioctl; static d_open_t iwn_cdev_open; static d_close_t iwn_cdev_close; static struct cdevsw iwn_cdevsw = { .d_version = D_VERSION, .d_flags = 0, .d_open = iwn_cdev_open, .d_close = iwn_cdev_close, .d_ioctl = iwn_cdev_ioctl, .d_name = "iwn", }; static int iwn_probe(device_t dev) { const struct iwn_ident *ident; for (ident = iwn_ident_table; ident->name != NULL; ident++) { if (pci_get_vendor(dev) == ident->vendor && pci_get_device(dev) == ident->device) { device_set_desc(dev, ident->name); return (BUS_PROBE_DEFAULT); } } return ENXIO; } static int iwn_is_3stream_device(struct iwn_softc *sc) { /* XXX for now only 5300, until the 5350 can be tested */ if (sc->hw_type == IWN_HW_REV_TYPE_5300) return (1); return (0); } static int iwn_attach(device_t dev) { struct iwn_softc *sc = (struct iwn_softc *)device_get_softc(dev); struct ieee80211com *ic; int i, error, rid; sc->sc_dev = dev; #ifdef IWN_DEBUG error = resource_int_value(device_get_name(sc->sc_dev), device_get_unit(sc->sc_dev), "debug", &(sc->sc_debug)); if (error != 0) sc->sc_debug = 0; #else sc->sc_debug = 0; #endif DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: begin\n",__func__); /* * Get the offset of the PCI Express Capability Structure in PCI * Configuration Space. */ error = pci_find_cap(dev, PCIY_EXPRESS, &sc->sc_cap_off); if (error != 0) { device_printf(dev, "PCIe capability structure not found!\n"); return error; } /* Clear device-specific "PCI retry timeout" register (41h). */ pci_write_config(dev, 0x41, 0, 1); /* Enable bus-mastering. */ pci_enable_busmaster(dev); rid = PCIR_BAR(0); sc->mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->mem == NULL) { device_printf(dev, "can't map mem space\n"); error = ENOMEM; return error; } sc->sc_st = rman_get_bustag(sc->mem); sc->sc_sh = rman_get_bushandle(sc->mem); i = 1; rid = 0; if (pci_alloc_msi(dev, &i) == 0) rid = 1; /* Install interrupt handler. */ sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE | (rid != 0 ? 0 : RF_SHAREABLE)); if (sc->irq == NULL) { device_printf(dev, "can't map interrupt\n"); error = ENOMEM; goto fail; } IWN_LOCK_INIT(sc); /* Read hardware revision and attach. */ sc->hw_type = (IWN_READ(sc, IWN_HW_REV) >> IWN_HW_REV_TYPE_SHIFT) & IWN_HW_REV_TYPE_MASK; sc->subdevice_id = pci_get_subdevice(dev); /* * 4965 versus 5000 and later have different methods. * Let's set those up first. */ if (sc->hw_type == IWN_HW_REV_TYPE_4965) error = iwn4965_attach(sc, pci_get_device(dev)); else error = iwn5000_attach(sc, pci_get_device(dev)); if (error != 0) { device_printf(dev, "could not attach device, error %d\n", error); goto fail; } /* * Next, let's setup the various parameters of each NIC. */ error = iwn_config_specific(sc, pci_get_device(dev)); if (error != 0) { device_printf(dev, "could not attach device, error %d\n", error); goto fail; } if ((error = iwn_hw_prepare(sc)) != 0) { device_printf(dev, "hardware not ready, error %d\n", error); goto fail; } /* Allocate DMA memory for firmware transfers. */ if ((error = iwn_alloc_fwmem(sc)) != 0) { device_printf(dev, "could not allocate memory for firmware, error %d\n", error); goto fail; } /* Allocate "Keep Warm" page. */ if ((error = iwn_alloc_kw(sc)) != 0) { device_printf(dev, "could not allocate keep warm page, error %d\n", error); goto fail; } /* Allocate ICT table for 5000 Series. */ if (sc->hw_type != IWN_HW_REV_TYPE_4965 && (error = iwn_alloc_ict(sc)) != 0) { device_printf(dev, "could not allocate ICT table, error %d\n", error); goto fail; } /* Allocate TX scheduler "rings". */ if ((error = iwn_alloc_sched(sc)) != 0) { device_printf(dev, "could not allocate TX scheduler rings, error %d\n", error); goto fail; } /* Allocate TX rings (16 on 4965AGN, 20 on >=5000). */ for (i = 0; i < sc->ntxqs; i++) { if ((error = iwn_alloc_tx_ring(sc, &sc->txq[i], i)) != 0) { device_printf(dev, "could not allocate TX ring %d, error %d\n", i, error); goto fail; } } /* Allocate RX ring. */ if ((error = iwn_alloc_rx_ring(sc, &sc->rxq)) != 0) { device_printf(dev, "could not allocate RX ring, error %d\n", error); goto fail; } /* Clear pending interrupts. */ IWN_WRITE(sc, IWN_INT, 0xffffffff); ic = &sc->sc_ic; ic->ic_softc = sc; ic->ic_name = device_get_nameunit(dev); ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ ic->ic_opmode = IEEE80211_M_STA; /* default to BSS mode */ /* Set device capabilities. */ ic->ic_caps = IEEE80211_C_STA /* station mode supported */ | IEEE80211_C_MONITOR /* monitor mode supported */ #if 0 | IEEE80211_C_BGSCAN /* background scanning */ #endif | IEEE80211_C_TXPMGT /* tx power management */ | IEEE80211_C_SHSLOT /* short slot time supported */ | IEEE80211_C_WPA | IEEE80211_C_SHPREAMBLE /* short preamble supported */ #if 0 | IEEE80211_C_IBSS /* ibss/adhoc mode */ #endif | IEEE80211_C_WME /* WME */ | IEEE80211_C_PMGT /* Station-side power mgmt */ ; /* Read MAC address, channels, etc from EEPROM. */ if ((error = iwn_read_eeprom(sc, ic->ic_macaddr)) != 0) { device_printf(dev, "could not read EEPROM, error %d\n", error); goto fail; } /* Count the number of available chains. */ sc->ntxchains = ((sc->txchainmask >> 2) & 1) + ((sc->txchainmask >> 1) & 1) + ((sc->txchainmask >> 0) & 1); sc->nrxchains = ((sc->rxchainmask >> 2) & 1) + ((sc->rxchainmask >> 1) & 1) + ((sc->rxchainmask >> 0) & 1); if (bootverbose) { device_printf(dev, "MIMO %dT%dR, %.4s, address %6D\n", sc->ntxchains, sc->nrxchains, sc->eeprom_domain, ic->ic_macaddr, ":"); } if (sc->sc_flags & IWN_FLAG_HAS_11N) { ic->ic_rxstream = sc->nrxchains; ic->ic_txstream = sc->ntxchains; /* * Some of the 3 antenna devices (ie, the 4965) only supports * 2x2 operation. So correct the number of streams if * it's not a 3-stream device. */ if (! iwn_is_3stream_device(sc)) { if (ic->ic_rxstream > 2) ic->ic_rxstream = 2; if (ic->ic_txstream > 2) ic->ic_txstream = 2; } ic->ic_htcaps = IEEE80211_HTCAP_SMPS_OFF /* SMPS mode disabled */ | IEEE80211_HTCAP_SHORTGI20 /* short GI in 20MHz */ | IEEE80211_HTCAP_CHWIDTH40 /* 40MHz channel width*/ | IEEE80211_HTCAP_SHORTGI40 /* short GI in 40MHz */ #ifdef notyet | IEEE80211_HTCAP_GREENFIELD #if IWN_RBUF_SIZE == 8192 | IEEE80211_HTCAP_MAXAMSDU_7935 /* max A-MSDU length */ #else | IEEE80211_HTCAP_MAXAMSDU_3839 /* max A-MSDU length */ #endif #endif /* s/w capabilities */ | IEEE80211_HTC_HT /* HT operation */ | IEEE80211_HTC_AMPDU /* tx A-MPDU */ #ifdef notyet | IEEE80211_HTC_AMSDU /* tx A-MSDU */ #endif ; } ieee80211_ifattach(ic); ic->ic_vap_create = iwn_vap_create; ic->ic_ioctl = iwn_ioctl; ic->ic_parent = iwn_parent; ic->ic_vap_delete = iwn_vap_delete; ic->ic_transmit = iwn_transmit; ic->ic_raw_xmit = iwn_raw_xmit; ic->ic_node_alloc = iwn_node_alloc; sc->sc_ampdu_rx_start = ic->ic_ampdu_rx_start; ic->ic_ampdu_rx_start = iwn_ampdu_rx_start; sc->sc_ampdu_rx_stop = ic->ic_ampdu_rx_stop; ic->ic_ampdu_rx_stop = iwn_ampdu_rx_stop; sc->sc_addba_request = ic->ic_addba_request; ic->ic_addba_request = iwn_addba_request; sc->sc_addba_response = ic->ic_addba_response; ic->ic_addba_response = iwn_addba_response; sc->sc_addba_stop = ic->ic_addba_stop; ic->ic_addba_stop = iwn_ampdu_tx_stop; ic->ic_newassoc = iwn_newassoc; ic->ic_wme.wme_update = iwn_updateedca; ic->ic_update_mcast = iwn_update_mcast; ic->ic_scan_start = iwn_scan_start; ic->ic_scan_end = iwn_scan_end; ic->ic_set_channel = iwn_set_channel; ic->ic_scan_curchan = iwn_scan_curchan; ic->ic_scan_mindwell = iwn_scan_mindwell; ic->ic_setregdomain = iwn_setregdomain; iwn_radiotap_attach(sc); callout_init_mtx(&sc->calib_to, &sc->sc_mtx, 0); callout_init_mtx(&sc->watchdog_to, &sc->sc_mtx, 0); TASK_INIT(&sc->sc_reinit_task, 0, iwn_hw_reset, sc); TASK_INIT(&sc->sc_radioon_task, 0, iwn_radio_on, sc); TASK_INIT(&sc->sc_radiooff_task, 0, iwn_radio_off, sc); TASK_INIT(&sc->sc_panic_task, 0, iwn_panicked, sc); TASK_INIT(&sc->sc_xmit_task, 0, iwn_xmit_task, sc); mbufq_init(&sc->sc_xmit_queue, 1024); sc->sc_tq = taskqueue_create("iwn_taskq", M_WAITOK, taskqueue_thread_enqueue, &sc->sc_tq); error = taskqueue_start_threads(&sc->sc_tq, 1, 0, "iwn_taskq"); if (error != 0) { device_printf(dev, "can't start threads, error %d\n", error); goto fail; } iwn_sysctlattach(sc); /* * Hook our interrupt after all initialization is complete. */ error = bus_setup_intr(dev, sc->irq, INTR_TYPE_NET | INTR_MPSAFE, NULL, iwn_intr, sc, &sc->sc_ih); if (error != 0) { device_printf(dev, "can't establish interrupt, error %d\n", error); goto fail; } #if 0 device_printf(sc->sc_dev, "%s: rx_stats=%d, rx_stats_bt=%d\n", __func__, sizeof(struct iwn_stats), sizeof(struct iwn_stats_bt)); #endif if (bootverbose) ieee80211_announce(ic); DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end\n",__func__); /* Add debug ioctl right at the end */ sc->sc_cdev = make_dev(&iwn_cdevsw, device_get_unit(dev), UID_ROOT, GID_WHEEL, 0600, "%s", device_get_nameunit(dev)); if (sc->sc_cdev == NULL) { device_printf(dev, "failed to create debug character device\n"); } else { sc->sc_cdev->si_drv1 = sc; } return 0; fail: iwn_detach(dev); DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end in error\n",__func__); return error; } /* * Define specific configuration based on device id and subdevice id * pid : PCI device id */ static int iwn_config_specific(struct iwn_softc *sc, uint16_t pid) { switch (pid) { /* 4965 series */ case IWN_DID_4965_1: case IWN_DID_4965_2: case IWN_DID_4965_3: case IWN_DID_4965_4: sc->base_params = &iwn4965_base_params; sc->limits = &iwn4965_sensitivity_limits; sc->fwname = "iwn4965fw"; /* Override chains masks, ROM is known to be broken. */ sc->txchainmask = IWN_ANT_AB; sc->rxchainmask = IWN_ANT_ABC; /* Enable normal btcoex */ sc->sc_flags |= IWN_FLAG_BTCOEX; break; /* 1000 Series */ case IWN_DID_1000_1: case IWN_DID_1000_2: switch(sc->subdevice_id) { case IWN_SDID_1000_1: case IWN_SDID_1000_2: case IWN_SDID_1000_3: case IWN_SDID_1000_4: case IWN_SDID_1000_5: case IWN_SDID_1000_6: case IWN_SDID_1000_7: case IWN_SDID_1000_8: case IWN_SDID_1000_9: case IWN_SDID_1000_10: case IWN_SDID_1000_11: case IWN_SDID_1000_12: sc->limits = &iwn1000_sensitivity_limits; sc->base_params = &iwn1000_base_params; sc->fwname = "iwn1000fw"; break; default: device_printf(sc->sc_dev, "adapter type id : 0x%04x sub id :" "0x%04x rev %d not supported (subdevice)\n", pid, sc->subdevice_id,sc->hw_type); return ENOTSUP; } break; /* 6x00 Series */ case IWN_DID_6x00_2: case IWN_DID_6x00_4: case IWN_DID_6x00_1: case IWN_DID_6x00_3: sc->fwname = "iwn6000fw"; sc->limits = &iwn6000_sensitivity_limits; switch(sc->subdevice_id) { case IWN_SDID_6x00_1: case IWN_SDID_6x00_2: case IWN_SDID_6x00_8: //iwl6000_3agn_cfg sc->base_params = &iwn_6000_base_params; break; case IWN_SDID_6x00_3: case IWN_SDID_6x00_6: case IWN_SDID_6x00_9: ////iwl6000i_2agn case IWN_SDID_6x00_4: case IWN_SDID_6x00_7: case IWN_SDID_6x00_10: //iwl6000i_2abg_cfg case IWN_SDID_6x00_5: //iwl6000i_2bg_cfg sc->base_params = &iwn_6000i_base_params; sc->sc_flags |= IWN_FLAG_INTERNAL_PA; sc->txchainmask = IWN_ANT_BC; sc->rxchainmask = IWN_ANT_BC; break; default: device_printf(sc->sc_dev, "adapter type id : 0x%04x sub id :" "0x%04x rev %d not supported (subdevice)\n", pid, sc->subdevice_id,sc->hw_type); return ENOTSUP; } break; /* 6x05 Series */ case IWN_DID_6x05_1: case IWN_DID_6x05_2: switch(sc->subdevice_id) { case IWN_SDID_6x05_1: case IWN_SDID_6x05_4: case IWN_SDID_6x05_6: //iwl6005_2agn_cfg case IWN_SDID_6x05_2: case IWN_SDID_6x05_5: case IWN_SDID_6x05_7: //iwl6005_2abg_cfg case IWN_SDID_6x05_3: //iwl6005_2bg_cfg case IWN_SDID_6x05_8: case IWN_SDID_6x05_9: //iwl6005_2agn_sff_cfg case IWN_SDID_6x05_10: //iwl6005_2agn_d_cfg case IWN_SDID_6x05_11: //iwl6005_2agn_mow1_cfg case IWN_SDID_6x05_12: //iwl6005_2agn_mow2_cfg sc->fwname = "iwn6000g2afw"; sc->limits = &iwn6000_sensitivity_limits; sc->base_params = &iwn_6000g2_base_params; break; default: device_printf(sc->sc_dev, "adapter type id : 0x%04x sub id :" "0x%04x rev %d not supported (subdevice)\n", pid, sc->subdevice_id,sc->hw_type); return ENOTSUP; } break; /* 6x35 Series */ case IWN_DID_6035_1: case IWN_DID_6035_2: switch(sc->subdevice_id) { case IWN_SDID_6035_1: case IWN_SDID_6035_2: case IWN_SDID_6035_3: case IWN_SDID_6035_4: sc->fwname = "iwn6000g2bfw"; sc->limits = &iwn6235_sensitivity_limits; sc->base_params = &iwn_6235_base_params; break; default: device_printf(sc->sc_dev, "adapter type id : 0x%04x sub id :" "0x%04x rev %d not supported (subdevice)\n", pid, sc->subdevice_id,sc->hw_type); return ENOTSUP; } break; /* 6x50 WiFi/WiMax Series */ case IWN_DID_6050_1: case IWN_DID_6050_2: switch(sc->subdevice_id) { case IWN_SDID_6050_1: case IWN_SDID_6050_3: case IWN_SDID_6050_5: //iwl6050_2agn_cfg case IWN_SDID_6050_2: case IWN_SDID_6050_4: case IWN_SDID_6050_6: //iwl6050_2abg_cfg sc->fwname = "iwn6050fw"; sc->txchainmask = IWN_ANT_AB; sc->rxchainmask = IWN_ANT_AB; sc->limits = &iwn6000_sensitivity_limits; sc->base_params = &iwn_6050_base_params; break; default: device_printf(sc->sc_dev, "adapter type id : 0x%04x sub id :" "0x%04x rev %d not supported (subdevice)\n", pid, sc->subdevice_id,sc->hw_type); return ENOTSUP; } break; /* 6150 WiFi/WiMax Series */ case IWN_DID_6150_1: case IWN_DID_6150_2: switch(sc->subdevice_id) { case IWN_SDID_6150_1: case IWN_SDID_6150_3: case IWN_SDID_6150_5: // iwl6150_bgn_cfg case IWN_SDID_6150_2: case IWN_SDID_6150_4: case IWN_SDID_6150_6: //iwl6150_bg_cfg sc->fwname = "iwn6050fw"; sc->limits = &iwn6000_sensitivity_limits; sc->base_params = &iwn_6150_base_params; break; default: device_printf(sc->sc_dev, "adapter type id : 0x%04x sub id :" "0x%04x rev %d not supported (subdevice)\n", pid, sc->subdevice_id,sc->hw_type); return ENOTSUP; } break; /* 6030 Series and 1030 Series */ case IWN_DID_x030_1: case IWN_DID_x030_2: case IWN_DID_x030_3: case IWN_DID_x030_4: switch(sc->subdevice_id) { case IWN_SDID_x030_1: case IWN_SDID_x030_3: case IWN_SDID_x030_5: // iwl1030_bgn_cfg case IWN_SDID_x030_2: case IWN_SDID_x030_4: case IWN_SDID_x030_6: //iwl1030_bg_cfg case IWN_SDID_x030_7: case IWN_SDID_x030_10: case IWN_SDID_x030_14: //iwl6030_2agn_cfg case IWN_SDID_x030_8: case IWN_SDID_x030_11: case IWN_SDID_x030_15: // iwl6030_2bgn_cfg case IWN_SDID_x030_9: case IWN_SDID_x030_12: case IWN_SDID_x030_16: // iwl6030_2abg_cfg case IWN_SDID_x030_13: //iwl6030_2bg_cfg sc->fwname = "iwn6000g2bfw"; sc->limits = &iwn6000_sensitivity_limits; sc->base_params = &iwn_6000g2b_base_params; break; default: device_printf(sc->sc_dev, "adapter type id : 0x%04x sub id :" "0x%04x rev %d not supported (subdevice)\n", pid, sc->subdevice_id,sc->hw_type); return ENOTSUP; } break; /* 130 Series WiFi */ /* XXX: This series will need adjustment for rate. * see rx_with_siso_diversity in linux kernel */ case IWN_DID_130_1: case IWN_DID_130_2: switch(sc->subdevice_id) { case IWN_SDID_130_1: case IWN_SDID_130_3: case IWN_SDID_130_5: //iwl130_bgn_cfg case IWN_SDID_130_2: case IWN_SDID_130_4: case IWN_SDID_130_6: //iwl130_bg_cfg sc->fwname = "iwn6000g2bfw"; sc->limits = &iwn6000_sensitivity_limits; sc->base_params = &iwn_6000g2b_base_params; break; default: device_printf(sc->sc_dev, "adapter type id : 0x%04x sub id :" "0x%04x rev %d not supported (subdevice)\n", pid, sc->subdevice_id,sc->hw_type); return ENOTSUP; } break; /* 100 Series WiFi */ case IWN_DID_100_1: case IWN_DID_100_2: switch(sc->subdevice_id) { case IWN_SDID_100_1: case IWN_SDID_100_2: case IWN_SDID_100_3: case IWN_SDID_100_4: case IWN_SDID_100_5: case IWN_SDID_100_6: sc->limits = &iwn1000_sensitivity_limits; sc->base_params = &iwn1000_base_params; sc->fwname = "iwn100fw"; break; default: device_printf(sc->sc_dev, "adapter type id : 0x%04x sub id :" "0x%04x rev %d not supported (subdevice)\n", pid, sc->subdevice_id,sc->hw_type); return ENOTSUP; } break; /* 105 Series */ /* XXX: This series will need adjustment for rate. * see rx_with_siso_diversity in linux kernel */ case IWN_DID_105_1: case IWN_DID_105_2: switch(sc->subdevice_id) { case IWN_SDID_105_1: case IWN_SDID_105_2: case IWN_SDID_105_3: //iwl105_bgn_cfg case IWN_SDID_105_4: //iwl105_bgn_d_cfg sc->limits = &iwn2030_sensitivity_limits; sc->base_params = &iwn2000_base_params; sc->fwname = "iwn105fw"; break; default: device_printf(sc->sc_dev, "adapter type id : 0x%04x sub id :" "0x%04x rev %d not supported (subdevice)\n", pid, sc->subdevice_id,sc->hw_type); return ENOTSUP; } break; /* 135 Series */ /* XXX: This series will need adjustment for rate. * see rx_with_siso_diversity in linux kernel */ case IWN_DID_135_1: case IWN_DID_135_2: switch(sc->subdevice_id) { case IWN_SDID_135_1: case IWN_SDID_135_2: case IWN_SDID_135_3: sc->limits = &iwn2030_sensitivity_limits; sc->base_params = &iwn2030_base_params; sc->fwname = "iwn135fw"; break; default: device_printf(sc->sc_dev, "adapter type id : 0x%04x sub id :" "0x%04x rev %d not supported (subdevice)\n", pid, sc->subdevice_id,sc->hw_type); return ENOTSUP; } break; /* 2x00 Series */ case IWN_DID_2x00_1: case IWN_DID_2x00_2: switch(sc->subdevice_id) { case IWN_SDID_2x00_1: case IWN_SDID_2x00_2: case IWN_SDID_2x00_3: //iwl2000_2bgn_cfg case IWN_SDID_2x00_4: //iwl2000_2bgn_d_cfg sc->limits = &iwn2030_sensitivity_limits; sc->base_params = &iwn2000_base_params; sc->fwname = "iwn2000fw"; break; default: device_printf(sc->sc_dev, "adapter type id : 0x%04x sub id :" "0x%04x rev %d not supported (subdevice) \n", pid, sc->subdevice_id, sc->hw_type); return ENOTSUP; } break; /* 2x30 Series */ case IWN_DID_2x30_1: case IWN_DID_2x30_2: switch(sc->subdevice_id) { case IWN_SDID_2x30_1: case IWN_SDID_2x30_3: case IWN_SDID_2x30_5: //iwl100_bgn_cfg case IWN_SDID_2x30_2: case IWN_SDID_2x30_4: case IWN_SDID_2x30_6: //iwl100_bg_cfg sc->limits = &iwn2030_sensitivity_limits; sc->base_params = &iwn2030_base_params; sc->fwname = "iwn2030fw"; break; default: device_printf(sc->sc_dev, "adapter type id : 0x%04x sub id :" "0x%04x rev %d not supported (subdevice)\n", pid, sc->subdevice_id,sc->hw_type); return ENOTSUP; } break; /* 5x00 Series */ case IWN_DID_5x00_1: case IWN_DID_5x00_2: case IWN_DID_5x00_3: case IWN_DID_5x00_4: sc->limits = &iwn5000_sensitivity_limits; sc->base_params = &iwn5000_base_params; sc->fwname = "iwn5000fw"; switch(sc->subdevice_id) { case IWN_SDID_5x00_1: case IWN_SDID_5x00_2: case IWN_SDID_5x00_3: case IWN_SDID_5x00_4: case IWN_SDID_5x00_9: case IWN_SDID_5x00_10: case IWN_SDID_5x00_11: case IWN_SDID_5x00_12: case IWN_SDID_5x00_17: case IWN_SDID_5x00_18: case IWN_SDID_5x00_19: case IWN_SDID_5x00_20: //iwl5100_agn_cfg sc->txchainmask = IWN_ANT_B; sc->rxchainmask = IWN_ANT_AB; break; case IWN_SDID_5x00_5: case IWN_SDID_5x00_6: case IWN_SDID_5x00_13: case IWN_SDID_5x00_14: case IWN_SDID_5x00_21: case IWN_SDID_5x00_22: //iwl5100_bgn_cfg sc->txchainmask = IWN_ANT_B; sc->rxchainmask = IWN_ANT_AB; break; case IWN_SDID_5x00_7: case IWN_SDID_5x00_8: case IWN_SDID_5x00_15: case IWN_SDID_5x00_16: case IWN_SDID_5x00_23: case IWN_SDID_5x00_24: //iwl5100_abg_cfg sc->txchainmask = IWN_ANT_B; sc->rxchainmask = IWN_ANT_AB; break; case IWN_SDID_5x00_25: case IWN_SDID_5x00_26: case IWN_SDID_5x00_27: case IWN_SDID_5x00_28: case IWN_SDID_5x00_29: case IWN_SDID_5x00_30: case IWN_SDID_5x00_31: case IWN_SDID_5x00_32: case IWN_SDID_5x00_33: case IWN_SDID_5x00_34: case IWN_SDID_5x00_35: case IWN_SDID_5x00_36: //iwl5300_agn_cfg sc->txchainmask = IWN_ANT_ABC; sc->rxchainmask = IWN_ANT_ABC; break; default: device_printf(sc->sc_dev, "adapter type id : 0x%04x sub id :" "0x%04x rev %d not supported (subdevice)\n", pid, sc->subdevice_id,sc->hw_type); return ENOTSUP; } break; /* 5x50 Series */ case IWN_DID_5x50_1: case IWN_DID_5x50_2: case IWN_DID_5x50_3: case IWN_DID_5x50_4: sc->limits = &iwn5000_sensitivity_limits; sc->base_params = &iwn5000_base_params; sc->fwname = "iwn5000fw"; switch(sc->subdevice_id) { case IWN_SDID_5x50_1: case IWN_SDID_5x50_2: case IWN_SDID_5x50_3: //iwl5350_agn_cfg sc->limits = &iwn5000_sensitivity_limits; sc->base_params = &iwn5000_base_params; sc->fwname = "iwn5000fw"; break; case IWN_SDID_5x50_4: case IWN_SDID_5x50_5: case IWN_SDID_5x50_8: case IWN_SDID_5x50_9: case IWN_SDID_5x50_10: case IWN_SDID_5x50_11: //iwl5150_agn_cfg case IWN_SDID_5x50_6: case IWN_SDID_5x50_7: case IWN_SDID_5x50_12: case IWN_SDID_5x50_13: //iwl5150_abg_cfg sc->limits = &iwn5000_sensitivity_limits; sc->fwname = "iwn5150fw"; sc->base_params = &iwn_5x50_base_params; break; default: device_printf(sc->sc_dev, "adapter type id : 0x%04x sub id :" "0x%04x rev %d not supported (subdevice)\n", pid, sc->subdevice_id,sc->hw_type); return ENOTSUP; } break; default: device_printf(sc->sc_dev, "adapter type id : 0x%04x sub id : 0x%04x" "rev 0x%08x not supported (device)\n", pid, sc->subdevice_id, sc->hw_type); return ENOTSUP; } return 0; } static int iwn4965_attach(struct iwn_softc *sc, uint16_t pid) { struct iwn_ops *ops = &sc->ops; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); ops->load_firmware = iwn4965_load_firmware; ops->read_eeprom = iwn4965_read_eeprom; ops->post_alive = iwn4965_post_alive; ops->nic_config = iwn4965_nic_config; ops->update_sched = iwn4965_update_sched; ops->get_temperature = iwn4965_get_temperature; ops->get_rssi = iwn4965_get_rssi; ops->set_txpower = iwn4965_set_txpower; ops->init_gains = iwn4965_init_gains; ops->set_gains = iwn4965_set_gains; ops->add_node = iwn4965_add_node; ops->tx_done = iwn4965_tx_done; ops->ampdu_tx_start = iwn4965_ampdu_tx_start; ops->ampdu_tx_stop = iwn4965_ampdu_tx_stop; sc->ntxqs = IWN4965_NTXQUEUES; sc->firstaggqueue = IWN4965_FIRSTAGGQUEUE; sc->ndmachnls = IWN4965_NDMACHNLS; sc->broadcast_id = IWN4965_ID_BROADCAST; sc->rxonsz = IWN4965_RXONSZ; sc->schedsz = IWN4965_SCHEDSZ; sc->fw_text_maxsz = IWN4965_FW_TEXT_MAXSZ; sc->fw_data_maxsz = IWN4965_FW_DATA_MAXSZ; sc->fwsz = IWN4965_FWSZ; sc->sched_txfact_addr = IWN4965_SCHED_TXFACT; sc->limits = &iwn4965_sensitivity_limits; sc->fwname = "iwn4965fw"; /* Override chains masks, ROM is known to be broken. */ sc->txchainmask = IWN_ANT_AB; sc->rxchainmask = IWN_ANT_ABC; /* Enable normal btcoex */ sc->sc_flags |= IWN_FLAG_BTCOEX; DPRINTF(sc, IWN_DEBUG_TRACE, "%s: end\n",__func__); return 0; } static int iwn5000_attach(struct iwn_softc *sc, uint16_t pid) { struct iwn_ops *ops = &sc->ops; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); ops->load_firmware = iwn5000_load_firmware; ops->read_eeprom = iwn5000_read_eeprom; ops->post_alive = iwn5000_post_alive; ops->nic_config = iwn5000_nic_config; ops->update_sched = iwn5000_update_sched; ops->get_temperature = iwn5000_get_temperature; ops->get_rssi = iwn5000_get_rssi; ops->set_txpower = iwn5000_set_txpower; ops->init_gains = iwn5000_init_gains; ops->set_gains = iwn5000_set_gains; ops->add_node = iwn5000_add_node; ops->tx_done = iwn5000_tx_done; ops->ampdu_tx_start = iwn5000_ampdu_tx_start; ops->ampdu_tx_stop = iwn5000_ampdu_tx_stop; sc->ntxqs = IWN5000_NTXQUEUES; sc->firstaggqueue = IWN5000_FIRSTAGGQUEUE; sc->ndmachnls = IWN5000_NDMACHNLS; sc->broadcast_id = IWN5000_ID_BROADCAST; sc->rxonsz = IWN5000_RXONSZ; sc->schedsz = IWN5000_SCHEDSZ; sc->fw_text_maxsz = IWN5000_FW_TEXT_MAXSZ; sc->fw_data_maxsz = IWN5000_FW_DATA_MAXSZ; sc->fwsz = IWN5000_FWSZ; sc->sched_txfact_addr = IWN5000_SCHED_TXFACT; sc->reset_noise_gain = IWN5000_PHY_CALIB_RESET_NOISE_GAIN; sc->noise_gain = IWN5000_PHY_CALIB_NOISE_GAIN; return 0; } /* * Attach the interface to 802.11 radiotap. */ static void iwn_radiotap_attach(struct iwn_softc *sc) { DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); ieee80211_radiotap_attach(&sc->sc_ic, &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap), IWN_TX_RADIOTAP_PRESENT, &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap), IWN_RX_RADIOTAP_PRESENT); DPRINTF(sc, IWN_DEBUG_TRACE, "->%s end\n", __func__); } static void iwn_sysctlattach(struct iwn_softc *sc) { #ifdef IWN_DEBUG 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_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "debug", CTLFLAG_RW, &sc->sc_debug, sc->sc_debug, "control debugging printfs"); #endif } static struct ieee80211vap * iwn_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, enum ieee80211_opmode opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t mac[IEEE80211_ADDR_LEN]) { struct iwn_softc *sc = ic->ic_softc; struct iwn_vap *ivp; struct ieee80211vap *vap; if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ return NULL; ivp = malloc(sizeof(struct iwn_vap), M_80211_VAP, M_WAITOK | M_ZERO); vap = &ivp->iv_vap; ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid); ivp->ctx = IWN_RXON_BSS_CTX; vap->iv_bmissthreshold = 10; /* override default */ /* Override with driver methods. */ ivp->iv_newstate = vap->iv_newstate; vap->iv_newstate = iwn_newstate; sc->ivap[IWN_RXON_BSS_CTX] = vap; ieee80211_ratectl_init(vap); /* Complete setup. */ ieee80211_vap_attach(vap, iwn_media_change, ieee80211_media_status, mac); ic->ic_opmode = opmode; return vap; } static void iwn_vap_delete(struct ieee80211vap *vap) { struct iwn_vap *ivp = IWN_VAP(vap); ieee80211_ratectl_deinit(vap); ieee80211_vap_detach(vap); free(ivp, M_80211_VAP); } static void iwn_xmit_queue_drain(struct iwn_softc *sc) { struct mbuf *m; struct ieee80211_node *ni; IWN_LOCK_ASSERT(sc); while ((m = mbufq_dequeue(&sc->sc_xmit_queue)) != NULL) { ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; ieee80211_free_node(ni); m_freem(m); } } static int iwn_xmit_queue_enqueue(struct iwn_softc *sc, struct mbuf *m) { IWN_LOCK_ASSERT(sc); return (mbufq_enqueue(&sc->sc_xmit_queue, m)); } static int iwn_detach(device_t dev) { struct iwn_softc *sc = device_get_softc(dev); int qid; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); if (sc->sc_ic.ic_softc != NULL) { /* Free the mbuf queue and node references */ IWN_LOCK(sc); iwn_xmit_queue_drain(sc); IWN_UNLOCK(sc); ieee80211_draintask(&sc->sc_ic, &sc->sc_reinit_task); ieee80211_draintask(&sc->sc_ic, &sc->sc_radioon_task); ieee80211_draintask(&sc->sc_ic, &sc->sc_radiooff_task); iwn_stop(sc); taskqueue_drain_all(sc->sc_tq); taskqueue_free(sc->sc_tq); callout_drain(&sc->watchdog_to); callout_drain(&sc->calib_to); ieee80211_ifdetach(&sc->sc_ic); } /* Uninstall interrupt handler. */ if (sc->irq != NULL) { bus_teardown_intr(dev, sc->irq, sc->sc_ih); bus_release_resource(dev, SYS_RES_IRQ, rman_get_rid(sc->irq), sc->irq); pci_release_msi(dev); } /* Free DMA resources. */ iwn_free_rx_ring(sc, &sc->rxq); for (qid = 0; qid < sc->ntxqs; qid++) iwn_free_tx_ring(sc, &sc->txq[qid]); iwn_free_sched(sc); iwn_free_kw(sc); if (sc->ict != NULL) iwn_free_ict(sc); iwn_free_fwmem(sc); if (sc->mem != NULL) bus_release_resource(dev, SYS_RES_MEMORY, rman_get_rid(sc->mem), sc->mem); if (sc->sc_cdev) { destroy_dev(sc->sc_cdev); sc->sc_cdev = NULL; } DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end\n", __func__); IWN_LOCK_DESTROY(sc); return 0; } static int iwn_shutdown(device_t dev) { struct iwn_softc *sc = device_get_softc(dev); iwn_stop(sc); return 0; } static int iwn_suspend(device_t dev) { struct iwn_softc *sc = device_get_softc(dev); ieee80211_suspend_all(&sc->sc_ic); return 0; } static int iwn_resume(device_t dev) { struct iwn_softc *sc = device_get_softc(dev); /* Clear device-specific "PCI retry timeout" register (41h). */ pci_write_config(dev, 0x41, 0, 1); ieee80211_resume_all(&sc->sc_ic); return 0; } static int iwn_nic_lock(struct iwn_softc *sc) { int ntries; /* Request exclusive access to NIC. */ IWN_SETBITS(sc, IWN_GP_CNTRL, IWN_GP_CNTRL_MAC_ACCESS_REQ); /* Spin until we actually get the lock. */ for (ntries = 0; ntries < 1000; ntries++) { if ((IWN_READ(sc, IWN_GP_CNTRL) & (IWN_GP_CNTRL_MAC_ACCESS_ENA | IWN_GP_CNTRL_SLEEP)) == IWN_GP_CNTRL_MAC_ACCESS_ENA) return 0; DELAY(10); } return ETIMEDOUT; } static __inline void iwn_nic_unlock(struct iwn_softc *sc) { IWN_CLRBITS(sc, IWN_GP_CNTRL, IWN_GP_CNTRL_MAC_ACCESS_REQ); } static __inline uint32_t iwn_prph_read(struct iwn_softc *sc, uint32_t addr) { IWN_WRITE(sc, IWN_PRPH_RADDR, IWN_PRPH_DWORD | addr); IWN_BARRIER_READ_WRITE(sc); return IWN_READ(sc, IWN_PRPH_RDATA); } static __inline void iwn_prph_write(struct iwn_softc *sc, uint32_t addr, uint32_t data) { IWN_WRITE(sc, IWN_PRPH_WADDR, IWN_PRPH_DWORD | addr); IWN_BARRIER_WRITE(sc); IWN_WRITE(sc, IWN_PRPH_WDATA, data); } static __inline void iwn_prph_setbits(struct iwn_softc *sc, uint32_t addr, uint32_t mask) { iwn_prph_write(sc, addr, iwn_prph_read(sc, addr) | mask); } static __inline void iwn_prph_clrbits(struct iwn_softc *sc, uint32_t addr, uint32_t mask) { iwn_prph_write(sc, addr, iwn_prph_read(sc, addr) & ~mask); } static __inline void iwn_prph_write_region_4(struct iwn_softc *sc, uint32_t addr, const uint32_t *data, int count) { for (; count > 0; count--, data++, addr += 4) iwn_prph_write(sc, addr, *data); } static __inline uint32_t iwn_mem_read(struct iwn_softc *sc, uint32_t addr) { IWN_WRITE(sc, IWN_MEM_RADDR, addr); IWN_BARRIER_READ_WRITE(sc); return IWN_READ(sc, IWN_MEM_RDATA); } static __inline void iwn_mem_write(struct iwn_softc *sc, uint32_t addr, uint32_t data) { IWN_WRITE(sc, IWN_MEM_WADDR, addr); IWN_BARRIER_WRITE(sc); IWN_WRITE(sc, IWN_MEM_WDATA, data); } static __inline void iwn_mem_write_2(struct iwn_softc *sc, uint32_t addr, uint16_t data) { uint32_t tmp; tmp = iwn_mem_read(sc, addr & ~3); if (addr & 3) tmp = (tmp & 0x0000ffff) | data << 16; else tmp = (tmp & 0xffff0000) | data; iwn_mem_write(sc, addr & ~3, tmp); } static __inline void iwn_mem_read_region_4(struct iwn_softc *sc, uint32_t addr, uint32_t *data, int count) { for (; count > 0; count--, addr += 4) *data++ = iwn_mem_read(sc, addr); } static __inline void iwn_mem_set_region_4(struct iwn_softc *sc, uint32_t addr, uint32_t val, int count) { for (; count > 0; count--, addr += 4) iwn_mem_write(sc, addr, val); } static int iwn_eeprom_lock(struct iwn_softc *sc) { int i, ntries; for (i = 0; i < 100; i++) { /* Request exclusive access to EEPROM. */ IWN_SETBITS(sc, IWN_HW_IF_CONFIG, IWN_HW_IF_CONFIG_EEPROM_LOCKED); /* Spin until we actually get the lock. */ for (ntries = 0; ntries < 100; ntries++) { if (IWN_READ(sc, IWN_HW_IF_CONFIG) & IWN_HW_IF_CONFIG_EEPROM_LOCKED) return 0; DELAY(10); } } DPRINTF(sc, IWN_DEBUG_TRACE, "->%s end timeout\n", __func__); return ETIMEDOUT; } static __inline void iwn_eeprom_unlock(struct iwn_softc *sc) { IWN_CLRBITS(sc, IWN_HW_IF_CONFIG, IWN_HW_IF_CONFIG_EEPROM_LOCKED); } /* * Initialize access by host to One Time Programmable ROM. * NB: This kind of ROM can be found on 1000 or 6000 Series only. */ static int iwn_init_otprom(struct iwn_softc *sc) { uint16_t prev, base, next; int count, error; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); /* Wait for clock stabilization before accessing prph. */ if ((error = iwn_clock_wait(sc)) != 0) return error; if ((error = iwn_nic_lock(sc)) != 0) return error; iwn_prph_setbits(sc, IWN_APMG_PS, IWN_APMG_PS_RESET_REQ); DELAY(5); iwn_prph_clrbits(sc, IWN_APMG_PS, IWN_APMG_PS_RESET_REQ); iwn_nic_unlock(sc); /* Set auto clock gate disable bit for HW with OTP shadow RAM. */ if (sc->base_params->shadow_ram_support) { IWN_SETBITS(sc, IWN_DBG_LINK_PWR_MGMT, IWN_RESET_LINK_PWR_MGMT_DIS); } IWN_CLRBITS(sc, IWN_EEPROM_GP, IWN_EEPROM_GP_IF_OWNER); /* Clear ECC status. */ IWN_SETBITS(sc, IWN_OTP_GP, IWN_OTP_GP_ECC_CORR_STTS | IWN_OTP_GP_ECC_UNCORR_STTS); /* * Find the block before last block (contains the EEPROM image) * for HW without OTP shadow RAM. */ if (! sc->base_params->shadow_ram_support) { /* Switch to absolute addressing mode. */ IWN_CLRBITS(sc, IWN_OTP_GP, IWN_OTP_GP_RELATIVE_ACCESS); base = prev = 0; for (count = 0; count < sc->base_params->max_ll_items; count++) { error = iwn_read_prom_data(sc, base, &next, 2); if (error != 0) return error; if (next == 0) /* End of linked-list. */ break; prev = base; base = le16toh(next); } if (count == 0 || count == sc->base_params->max_ll_items) return EIO; /* Skip "next" word. */ sc->prom_base = prev + 1; } DPRINTF(sc, IWN_DEBUG_TRACE, "->%s end\n", __func__); return 0; } static int iwn_read_prom_data(struct iwn_softc *sc, uint32_t addr, void *data, int count) { uint8_t *out = data; uint32_t val, tmp; int ntries; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); addr += sc->prom_base; for (; count > 0; count -= 2, addr++) { IWN_WRITE(sc, IWN_EEPROM, addr << 2); for (ntries = 0; ntries < 10; ntries++) { val = IWN_READ(sc, IWN_EEPROM); if (val & IWN_EEPROM_READ_VALID) break; DELAY(5); } if (ntries == 10) { device_printf(sc->sc_dev, "timeout reading ROM at 0x%x\n", addr); return ETIMEDOUT; } if (sc->sc_flags & IWN_FLAG_HAS_OTPROM) { /* OTPROM, check for ECC errors. */ tmp = IWN_READ(sc, IWN_OTP_GP); if (tmp & IWN_OTP_GP_ECC_UNCORR_STTS) { device_printf(sc->sc_dev, "OTPROM ECC error at 0x%x\n", addr); return EIO; } if (tmp & IWN_OTP_GP_ECC_CORR_STTS) { /* Correctable ECC error, clear bit. */ IWN_SETBITS(sc, IWN_OTP_GP, IWN_OTP_GP_ECC_CORR_STTS); } } *out++ = val >> 16; if (count > 1) *out++ = val >> 24; } DPRINTF(sc, IWN_DEBUG_TRACE, "->%s end\n", __func__); return 0; } static void iwn_dma_map_addr(void *arg, bus_dma_segment_t *segs, int nsegs, int error) { if (error != 0) return; KASSERT(nsegs == 1, ("too many DMA segments, %d should be 1", nsegs)); *(bus_addr_t *)arg = segs[0].ds_addr; } static int iwn_dma_contig_alloc(struct iwn_softc *sc, struct iwn_dma_info *dma, void **kvap, bus_size_t size, bus_size_t alignment) { int error; dma->tag = NULL; dma->size = size; error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), alignment, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, size, - 1, size, BUS_DMA_NOWAIT, NULL, NULL, &dma->tag); + 1, size, 0, NULL, NULL, &dma->tag); if (error != 0) goto fail; error = bus_dmamem_alloc(dma->tag, (void **)&dma->vaddr, BUS_DMA_NOWAIT | BUS_DMA_ZERO | BUS_DMA_COHERENT, &dma->map); if (error != 0) goto fail; error = bus_dmamap_load(dma->tag, dma->map, dma->vaddr, size, iwn_dma_map_addr, &dma->paddr, BUS_DMA_NOWAIT); if (error != 0) goto fail; bus_dmamap_sync(dma->tag, dma->map, BUS_DMASYNC_PREWRITE); if (kvap != NULL) *kvap = dma->vaddr; return 0; fail: iwn_dma_contig_free(dma); return error; } static void iwn_dma_contig_free(struct iwn_dma_info *dma) { if (dma->vaddr != NULL) { bus_dmamap_sync(dma->tag, dma->map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(dma->tag, dma->map); bus_dmamem_free(dma->tag, dma->vaddr, dma->map); dma->vaddr = NULL; } if (dma->tag != NULL) { bus_dma_tag_destroy(dma->tag); dma->tag = NULL; } } static int iwn_alloc_sched(struct iwn_softc *sc) { /* TX scheduler rings must be aligned on a 1KB boundary. */ return iwn_dma_contig_alloc(sc, &sc->sched_dma, (void **)&sc->sched, sc->schedsz, 1024); } static void iwn_free_sched(struct iwn_softc *sc) { iwn_dma_contig_free(&sc->sched_dma); } static int iwn_alloc_kw(struct iwn_softc *sc) { /* "Keep Warm" page must be aligned on a 4KB boundary. */ return iwn_dma_contig_alloc(sc, &sc->kw_dma, NULL, 4096, 4096); } static void iwn_free_kw(struct iwn_softc *sc) { iwn_dma_contig_free(&sc->kw_dma); } static int iwn_alloc_ict(struct iwn_softc *sc) { /* ICT table must be aligned on a 4KB boundary. */ return iwn_dma_contig_alloc(sc, &sc->ict_dma, (void **)&sc->ict, IWN_ICT_SIZE, 4096); } static void iwn_free_ict(struct iwn_softc *sc) { iwn_dma_contig_free(&sc->ict_dma); } static int iwn_alloc_fwmem(struct iwn_softc *sc) { /* Must be aligned on a 16-byte boundary. */ return iwn_dma_contig_alloc(sc, &sc->fw_dma, NULL, sc->fwsz, 16); } static void iwn_free_fwmem(struct iwn_softc *sc) { iwn_dma_contig_free(&sc->fw_dma); } static int iwn_alloc_rx_ring(struct iwn_softc *sc, struct iwn_rx_ring *ring) { bus_size_t size; int i, error; ring->cur = 0; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); /* Allocate RX descriptors (256-byte aligned). */ size = IWN_RX_RING_COUNT * sizeof (uint32_t); error = iwn_dma_contig_alloc(sc, &ring->desc_dma, (void **)&ring->desc, size, 256); if (error != 0) { device_printf(sc->sc_dev, "%s: could not allocate RX ring DMA memory, error %d\n", __func__, error); goto fail; } /* Allocate RX status area (16-byte aligned). */ error = iwn_dma_contig_alloc(sc, &ring->stat_dma, (void **)&ring->stat, sizeof (struct iwn_rx_status), 16); if (error != 0) { device_printf(sc->sc_dev, "%s: could not allocate RX status DMA memory, error %d\n", __func__, error); goto fail; } /* Create RX buffer DMA tag. */ error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), 1, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, - IWN_RBUF_SIZE, 1, IWN_RBUF_SIZE, BUS_DMA_NOWAIT, NULL, NULL, - &ring->data_dmat); + IWN_RBUF_SIZE, 1, IWN_RBUF_SIZE, 0, NULL, NULL, &ring->data_dmat); if (error != 0) { device_printf(sc->sc_dev, "%s: could not create RX buf DMA tag, error %d\n", __func__, error); goto fail; } /* * Allocate and map RX buffers. */ for (i = 0; i < IWN_RX_RING_COUNT; i++) { struct iwn_rx_data *data = &ring->data[i]; bus_addr_t paddr; error = bus_dmamap_create(ring->data_dmat, 0, &data->map); if (error != 0) { device_printf(sc->sc_dev, "%s: could not create RX buf DMA map, error %d\n", __func__, error); goto fail; } data->m = m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR, IWN_RBUF_SIZE); if (data->m == NULL) { device_printf(sc->sc_dev, "%s: could not allocate RX mbuf\n", __func__); error = ENOBUFS; goto fail; } error = bus_dmamap_load(ring->data_dmat, data->map, mtod(data->m, void *), IWN_RBUF_SIZE, iwn_dma_map_addr, &paddr, BUS_DMA_NOWAIT); if (error != 0 && error != EFBIG) { device_printf(sc->sc_dev, "%s: can't map mbuf, error %d\n", __func__, error); goto fail; } /* Set physical address of RX buffer (256-byte aligned). */ ring->desc[i] = htole32(paddr >> 8); } bus_dmamap_sync(ring->desc_dma.tag, ring->desc_dma.map, BUS_DMASYNC_PREWRITE); DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end\n",__func__); return 0; fail: iwn_free_rx_ring(sc, ring); DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end in error\n",__func__); return error; } static void iwn_reset_rx_ring(struct iwn_softc *sc, struct iwn_rx_ring *ring) { int ntries; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); if (iwn_nic_lock(sc) == 0) { IWN_WRITE(sc, IWN_FH_RX_CONFIG, 0); for (ntries = 0; ntries < 1000; ntries++) { if (IWN_READ(sc, IWN_FH_RX_STATUS) & IWN_FH_RX_STATUS_IDLE) break; DELAY(10); } iwn_nic_unlock(sc); } ring->cur = 0; sc->last_rx_valid = 0; } static void iwn_free_rx_ring(struct iwn_softc *sc, struct iwn_rx_ring *ring) { int i; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s \n", __func__); iwn_dma_contig_free(&ring->desc_dma); iwn_dma_contig_free(&ring->stat_dma); for (i = 0; i < IWN_RX_RING_COUNT; i++) { struct iwn_rx_data *data = &ring->data[i]; if (data->m != NULL) { bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(ring->data_dmat, data->map); m_freem(data->m); data->m = NULL; } if (data->map != NULL) bus_dmamap_destroy(ring->data_dmat, data->map); } if (ring->data_dmat != NULL) { bus_dma_tag_destroy(ring->data_dmat); ring->data_dmat = NULL; } } static int iwn_alloc_tx_ring(struct iwn_softc *sc, struct iwn_tx_ring *ring, int qid) { bus_addr_t paddr; bus_size_t size; int i, error; ring->qid = qid; ring->queued = 0; ring->cur = 0; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); /* Allocate TX descriptors (256-byte aligned). */ size = IWN_TX_RING_COUNT * sizeof (struct iwn_tx_desc); error = iwn_dma_contig_alloc(sc, &ring->desc_dma, (void **)&ring->desc, size, 256); if (error != 0) { device_printf(sc->sc_dev, "%s: could not allocate TX ring DMA memory, error %d\n", __func__, error); goto fail; } size = IWN_TX_RING_COUNT * sizeof (struct iwn_tx_cmd); error = iwn_dma_contig_alloc(sc, &ring->cmd_dma, (void **)&ring->cmd, size, 4); if (error != 0) { device_printf(sc->sc_dev, "%s: could not allocate TX cmd DMA memory, error %d\n", __func__, error); goto fail; } error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), 1, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, MCLBYTES, - IWN_MAX_SCATTER - 1, MCLBYTES, BUS_DMA_NOWAIT, NULL, NULL, - &ring->data_dmat); + IWN_MAX_SCATTER - 1, MCLBYTES, 0, NULL, NULL, &ring->data_dmat); if (error != 0) { device_printf(sc->sc_dev, "%s: could not create TX buf DMA tag, error %d\n", __func__, error); goto fail; } paddr = ring->cmd_dma.paddr; for (i = 0; i < IWN_TX_RING_COUNT; i++) { struct iwn_tx_data *data = &ring->data[i]; data->cmd_paddr = paddr; data->scratch_paddr = paddr + 12; paddr += sizeof (struct iwn_tx_cmd); error = bus_dmamap_create(ring->data_dmat, 0, &data->map); if (error != 0) { device_printf(sc->sc_dev, "%s: could not create TX buf DMA map, error %d\n", __func__, error); goto fail; } } DPRINTF(sc, IWN_DEBUG_TRACE, "->%s end\n", __func__); return 0; fail: iwn_free_tx_ring(sc, ring); DPRINTF(sc, IWN_DEBUG_TRACE, "->%s end in error\n", __func__); return error; } static void iwn_reset_tx_ring(struct iwn_softc *sc, struct iwn_tx_ring *ring) { int i; DPRINTF(sc, IWN_DEBUG_TRACE, "->doing %s \n", __func__); for (i = 0; i < IWN_TX_RING_COUNT; i++) { struct iwn_tx_data *data = &ring->data[i]; if (data->m != NULL) { bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(ring->data_dmat, data->map); m_freem(data->m); data->m = NULL; } if (data->ni != NULL) { ieee80211_free_node(data->ni); data->ni = NULL; } } /* Clear TX descriptors. */ memset(ring->desc, 0, ring->desc_dma.size); bus_dmamap_sync(ring->desc_dma.tag, ring->desc_dma.map, BUS_DMASYNC_PREWRITE); sc->qfullmsk &= ~(1 << ring->qid); ring->queued = 0; ring->cur = 0; } static void iwn_free_tx_ring(struct iwn_softc *sc, struct iwn_tx_ring *ring) { int i; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s \n", __func__); iwn_dma_contig_free(&ring->desc_dma); iwn_dma_contig_free(&ring->cmd_dma); for (i = 0; i < IWN_TX_RING_COUNT; i++) { struct iwn_tx_data *data = &ring->data[i]; if (data->m != NULL) { bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(ring->data_dmat, data->map); m_freem(data->m); } if (data->map != NULL) bus_dmamap_destroy(ring->data_dmat, data->map); } if (ring->data_dmat != NULL) { bus_dma_tag_destroy(ring->data_dmat); ring->data_dmat = NULL; } } static void iwn5000_ict_reset(struct iwn_softc *sc) { /* Disable interrupts. */ IWN_WRITE(sc, IWN_INT_MASK, 0); /* Reset ICT table. */ memset(sc->ict, 0, IWN_ICT_SIZE); sc->ict_cur = 0; /* Set physical address of ICT table (4KB aligned). */ DPRINTF(sc, IWN_DEBUG_RESET, "%s: enabling ICT\n", __func__); IWN_WRITE(sc, IWN_DRAM_INT_TBL, IWN_DRAM_INT_TBL_ENABLE | IWN_DRAM_INT_TBL_WRAP_CHECK | sc->ict_dma.paddr >> 12); /* Enable periodic RX interrupt. */ sc->int_mask |= IWN_INT_RX_PERIODIC; /* Switch to ICT interrupt mode in driver. */ sc->sc_flags |= IWN_FLAG_USE_ICT; /* Re-enable interrupts. */ IWN_WRITE(sc, IWN_INT, 0xffffffff); IWN_WRITE(sc, IWN_INT_MASK, sc->int_mask); } static int iwn_read_eeprom(struct iwn_softc *sc, uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct iwn_ops *ops = &sc->ops; uint16_t val; int error; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); /* Check whether adapter has an EEPROM or an OTPROM. */ if (sc->hw_type >= IWN_HW_REV_TYPE_1000 && (IWN_READ(sc, IWN_OTP_GP) & IWN_OTP_GP_DEV_SEL_OTP)) sc->sc_flags |= IWN_FLAG_HAS_OTPROM; DPRINTF(sc, IWN_DEBUG_RESET, "%s found\n", (sc->sc_flags & IWN_FLAG_HAS_OTPROM) ? "OTPROM" : "EEPROM"); /* Adapter has to be powered on for EEPROM access to work. */ if ((error = iwn_apm_init(sc)) != 0) { device_printf(sc->sc_dev, "%s: could not power ON adapter, error %d\n", __func__, error); return error; } if ((IWN_READ(sc, IWN_EEPROM_GP) & 0x7) == 0) { device_printf(sc->sc_dev, "%s: bad ROM signature\n", __func__); return EIO; } if ((error = iwn_eeprom_lock(sc)) != 0) { device_printf(sc->sc_dev, "%s: could not lock ROM, error %d\n", __func__, error); return error; } if (sc->sc_flags & IWN_FLAG_HAS_OTPROM) { if ((error = iwn_init_otprom(sc)) != 0) { device_printf(sc->sc_dev, "%s: could not initialize OTPROM, error %d\n", __func__, error); return error; } } iwn_read_prom_data(sc, IWN_EEPROM_SKU_CAP, &val, 2); DPRINTF(sc, IWN_DEBUG_RESET, "SKU capabilities=0x%04x\n", le16toh(val)); /* Check if HT support is bonded out. */ if (val & htole16(IWN_EEPROM_SKU_CAP_11N)) sc->sc_flags |= IWN_FLAG_HAS_11N; iwn_read_prom_data(sc, IWN_EEPROM_RFCFG, &val, 2); sc->rfcfg = le16toh(val); DPRINTF(sc, IWN_DEBUG_RESET, "radio config=0x%04x\n", sc->rfcfg); /* Read Tx/Rx chains from ROM unless it's known to be broken. */ if (sc->txchainmask == 0) sc->txchainmask = IWN_RFCFG_TXANTMSK(sc->rfcfg); if (sc->rxchainmask == 0) sc->rxchainmask = IWN_RFCFG_RXANTMSK(sc->rfcfg); /* Read MAC address. */ iwn_read_prom_data(sc, IWN_EEPROM_MAC, macaddr, 6); /* Read adapter-specific information from EEPROM. */ ops->read_eeprom(sc); iwn_apm_stop(sc); /* Power OFF adapter. */ iwn_eeprom_unlock(sc); DPRINTF(sc, IWN_DEBUG_TRACE, "->%s end\n", __func__); return 0; } static void iwn4965_read_eeprom(struct iwn_softc *sc) { uint32_t addr; uint16_t val; int i; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); /* Read regulatory domain (4 ASCII characters). */ iwn_read_prom_data(sc, IWN4965_EEPROM_DOMAIN, sc->eeprom_domain, 4); /* Read the list of authorized channels (20MHz ones only). */ for (i = 0; i < IWN_NBANDS - 1; i++) { addr = iwn4965_regulatory_bands[i]; iwn_read_eeprom_channels(sc, i, addr); } /* Read maximum allowed TX power for 2GHz and 5GHz bands. */ iwn_read_prom_data(sc, IWN4965_EEPROM_MAXPOW, &val, 2); sc->maxpwr2GHz = val & 0xff; sc->maxpwr5GHz = val >> 8; /* Check that EEPROM values are within valid range. */ if (sc->maxpwr5GHz < 20 || sc->maxpwr5GHz > 50) sc->maxpwr5GHz = 38; if (sc->maxpwr2GHz < 20 || sc->maxpwr2GHz > 50) sc->maxpwr2GHz = 38; DPRINTF(sc, IWN_DEBUG_RESET, "maxpwr 2GHz=%d 5GHz=%d\n", sc->maxpwr2GHz, sc->maxpwr5GHz); /* Read samples for each TX power group. */ iwn_read_prom_data(sc, IWN4965_EEPROM_BANDS, sc->bands, sizeof sc->bands); /* Read voltage at which samples were taken. */ iwn_read_prom_data(sc, IWN4965_EEPROM_VOLTAGE, &val, 2); sc->eeprom_voltage = (int16_t)le16toh(val); DPRINTF(sc, IWN_DEBUG_RESET, "voltage=%d (in 0.3V)\n", sc->eeprom_voltage); #ifdef IWN_DEBUG /* Print samples. */ if (sc->sc_debug & IWN_DEBUG_ANY) { for (i = 0; i < IWN_NBANDS - 1; i++) iwn4965_print_power_group(sc, i); } #endif DPRINTF(sc, IWN_DEBUG_TRACE, "->%s end\n", __func__); } #ifdef IWN_DEBUG static void iwn4965_print_power_group(struct iwn_softc *sc, int i) { struct iwn4965_eeprom_band *band = &sc->bands[i]; struct iwn4965_eeprom_chan_samples *chans = band->chans; int j, c; printf("===band %d===\n", i); printf("chan lo=%d, chan hi=%d\n", band->lo, band->hi); printf("chan1 num=%d\n", chans[0].num); for (c = 0; c < 2; c++) { for (j = 0; j < IWN_NSAMPLES; j++) { printf("chain %d, sample %d: temp=%d gain=%d " "power=%d pa_det=%d\n", c, j, chans[0].samples[c][j].temp, chans[0].samples[c][j].gain, chans[0].samples[c][j].power, chans[0].samples[c][j].pa_det); } } printf("chan2 num=%d\n", chans[1].num); for (c = 0; c < 2; c++) { for (j = 0; j < IWN_NSAMPLES; j++) { printf("chain %d, sample %d: temp=%d gain=%d " "power=%d pa_det=%d\n", c, j, chans[1].samples[c][j].temp, chans[1].samples[c][j].gain, chans[1].samples[c][j].power, chans[1].samples[c][j].pa_det); } } } #endif static void iwn5000_read_eeprom(struct iwn_softc *sc) { struct iwn5000_eeprom_calib_hdr hdr; int32_t volt; uint32_t base, addr; uint16_t val; int i; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); /* Read regulatory domain (4 ASCII characters). */ iwn_read_prom_data(sc, IWN5000_EEPROM_REG, &val, 2); base = le16toh(val); iwn_read_prom_data(sc, base + IWN5000_EEPROM_DOMAIN, sc->eeprom_domain, 4); /* Read the list of authorized channels (20MHz ones only). */ for (i = 0; i < IWN_NBANDS - 1; i++) { addr = base + sc->base_params->regulatory_bands[i]; iwn_read_eeprom_channels(sc, i, addr); } /* Read enhanced TX power information for 6000 Series. */ if (sc->base_params->enhanced_TX_power) iwn_read_eeprom_enhinfo(sc); iwn_read_prom_data(sc, IWN5000_EEPROM_CAL, &val, 2); base = le16toh(val); iwn_read_prom_data(sc, base, &hdr, sizeof hdr); DPRINTF(sc, IWN_DEBUG_CALIBRATE, "%s: calib version=%u pa type=%u voltage=%u\n", __func__, hdr.version, hdr.pa_type, le16toh(hdr.volt)); sc->calib_ver = hdr.version; if (sc->base_params->calib_need & IWN_FLG_NEED_PHY_CALIB_TEMP_OFFSETv2) { sc->eeprom_voltage = le16toh(hdr.volt); iwn_read_prom_data(sc, base + IWN5000_EEPROM_TEMP, &val, 2); sc->eeprom_temp_high=le16toh(val); iwn_read_prom_data(sc, base + IWN5000_EEPROM_VOLT, &val, 2); sc->eeprom_temp = le16toh(val); } if (sc->hw_type == IWN_HW_REV_TYPE_5150) { /* Compute temperature offset. */ iwn_read_prom_data(sc, base + IWN5000_EEPROM_TEMP, &val, 2); sc->eeprom_temp = le16toh(val); iwn_read_prom_data(sc, base + IWN5000_EEPROM_VOLT, &val, 2); volt = le16toh(val); sc->temp_off = sc->eeprom_temp - (volt / -5); DPRINTF(sc, IWN_DEBUG_CALIBRATE, "temp=%d volt=%d offset=%dK\n", sc->eeprom_temp, volt, sc->temp_off); } else { /* Read crystal calibration. */ iwn_read_prom_data(sc, base + IWN5000_EEPROM_CRYSTAL, &sc->eeprom_crystal, sizeof (uint32_t)); DPRINTF(sc, IWN_DEBUG_CALIBRATE, "crystal calibration 0x%08x\n", le32toh(sc->eeprom_crystal)); } DPRINTF(sc, IWN_DEBUG_TRACE, "->%s end\n", __func__); } /* * Translate EEPROM flags to net80211. */ static uint32_t iwn_eeprom_channel_flags(struct iwn_eeprom_chan *channel) { uint32_t nflags; nflags = 0; if ((channel->flags & IWN_EEPROM_CHAN_ACTIVE) == 0) nflags |= IEEE80211_CHAN_PASSIVE; if ((channel->flags & IWN_EEPROM_CHAN_IBSS) == 0) nflags |= IEEE80211_CHAN_NOADHOC; if (channel->flags & IWN_EEPROM_CHAN_RADAR) { nflags |= IEEE80211_CHAN_DFS; /* XXX apparently IBSS may still be marked */ nflags |= IEEE80211_CHAN_NOADHOC; } return nflags; } static void iwn_read_eeprom_band(struct iwn_softc *sc, int n) { struct ieee80211com *ic = &sc->sc_ic; struct iwn_eeprom_chan *channels = sc->eeprom_channels[n]; const struct iwn_chan_band *band = &iwn_bands[n]; struct ieee80211_channel *c; uint8_t chan; int i, nflags; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); for (i = 0; i < band->nchan; i++) { if (!(channels[i].flags & IWN_EEPROM_CHAN_VALID)) { DPRINTF(sc, IWN_DEBUG_RESET, "skip chan %d flags 0x%x maxpwr %d\n", band->chan[i], channels[i].flags, channels[i].maxpwr); continue; } chan = band->chan[i]; nflags = iwn_eeprom_channel_flags(&channels[i]); c = &ic->ic_channels[ic->ic_nchans++]; c->ic_ieee = chan; c->ic_maxregpower = channels[i].maxpwr; c->ic_maxpower = 2*c->ic_maxregpower; if (n == 0) { /* 2GHz band */ c->ic_freq = ieee80211_ieee2mhz(chan, IEEE80211_CHAN_G); /* G =>'s B is supported */ c->ic_flags = IEEE80211_CHAN_B | nflags; c = &ic->ic_channels[ic->ic_nchans++]; c[0] = c[-1]; c->ic_flags = IEEE80211_CHAN_G | nflags; } else { /* 5GHz band */ c->ic_freq = ieee80211_ieee2mhz(chan, IEEE80211_CHAN_A); c->ic_flags = IEEE80211_CHAN_A | nflags; } /* Save maximum allowed TX power for this channel. */ sc->maxpwr[chan] = channels[i].maxpwr; DPRINTF(sc, IWN_DEBUG_RESET, "add chan %d flags 0x%x maxpwr %d\n", chan, channels[i].flags, channels[i].maxpwr); if (sc->sc_flags & IWN_FLAG_HAS_11N) { /* add HT20, HT40 added separately */ c = &ic->ic_channels[ic->ic_nchans++]; c[0] = c[-1]; c->ic_flags |= IEEE80211_CHAN_HT20; } } DPRINTF(sc, IWN_DEBUG_TRACE, "->%s end\n", __func__); } static void iwn_read_eeprom_ht40(struct iwn_softc *sc, int n) { struct ieee80211com *ic = &sc->sc_ic; struct iwn_eeprom_chan *channels = sc->eeprom_channels[n]; const struct iwn_chan_band *band = &iwn_bands[n]; struct ieee80211_channel *c, *cent, *extc; uint8_t chan; int i, nflags; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s start\n", __func__); if (!(sc->sc_flags & IWN_FLAG_HAS_11N)) { DPRINTF(sc, IWN_DEBUG_TRACE, "->%s end no 11n\n", __func__); return; } for (i = 0; i < band->nchan; i++) { if (!(channels[i].flags & IWN_EEPROM_CHAN_VALID)) { DPRINTF(sc, IWN_DEBUG_RESET, "skip chan %d flags 0x%x maxpwr %d\n", band->chan[i], channels[i].flags, channels[i].maxpwr); continue; } chan = band->chan[i]; nflags = iwn_eeprom_channel_flags(&channels[i]); /* * Each entry defines an HT40 channel pair; find the * center channel, then the extension channel above. */ cent = ieee80211_find_channel_byieee(ic, chan, (n == 5 ? IEEE80211_CHAN_G : IEEE80211_CHAN_A)); if (cent == NULL) { /* XXX shouldn't happen */ device_printf(sc->sc_dev, "%s: no entry for channel %d\n", __func__, chan); continue; } extc = ieee80211_find_channel(ic, cent->ic_freq+20, (n == 5 ? IEEE80211_CHAN_G : IEEE80211_CHAN_A)); if (extc == NULL) { DPRINTF(sc, IWN_DEBUG_RESET, "%s: skip chan %d, extension channel not found\n", __func__, chan); continue; } DPRINTF(sc, IWN_DEBUG_RESET, "add ht40 chan %d flags 0x%x maxpwr %d\n", chan, channels[i].flags, channels[i].maxpwr); c = &ic->ic_channels[ic->ic_nchans++]; c[0] = cent[0]; c->ic_extieee = extc->ic_ieee; c->ic_flags &= ~IEEE80211_CHAN_HT; c->ic_flags |= IEEE80211_CHAN_HT40U | nflags; c = &ic->ic_channels[ic->ic_nchans++]; c[0] = extc[0]; c->ic_extieee = cent->ic_ieee; c->ic_flags &= ~IEEE80211_CHAN_HT; c->ic_flags |= IEEE80211_CHAN_HT40D | nflags; } DPRINTF(sc, IWN_DEBUG_TRACE, "->%s end\n", __func__); } static void iwn_read_eeprom_channels(struct iwn_softc *sc, int n, uint32_t addr) { struct ieee80211com *ic = &sc->sc_ic; iwn_read_prom_data(sc, addr, &sc->eeprom_channels[n], iwn_bands[n].nchan * sizeof (struct iwn_eeprom_chan)); if (n < 5) iwn_read_eeprom_band(sc, n); else iwn_read_eeprom_ht40(sc, n); ieee80211_sort_channels(ic->ic_channels, ic->ic_nchans); } static struct iwn_eeprom_chan * iwn_find_eeprom_channel(struct iwn_softc *sc, struct ieee80211_channel *c) { int band, chan, i, j; if (IEEE80211_IS_CHAN_HT40(c)) { band = IEEE80211_IS_CHAN_5GHZ(c) ? 6 : 5; if (IEEE80211_IS_CHAN_HT40D(c)) chan = c->ic_extieee; else chan = c->ic_ieee; for (i = 0; i < iwn_bands[band].nchan; i++) { if (iwn_bands[band].chan[i] == chan) return &sc->eeprom_channels[band][i]; } } else { for (j = 0; j < 5; j++) { for (i = 0; i < iwn_bands[j].nchan; i++) { if (iwn_bands[j].chan[i] == c->ic_ieee) return &sc->eeprom_channels[j][i]; } } } return NULL; } /* * Enforce flags read from EEPROM. */ static int iwn_setregdomain(struct ieee80211com *ic, struct ieee80211_regdomain *rd, int nchan, struct ieee80211_channel chans[]) { struct iwn_softc *sc = ic->ic_softc; int i; for (i = 0; i < nchan; i++) { struct ieee80211_channel *c = &chans[i]; struct iwn_eeprom_chan *channel; channel = iwn_find_eeprom_channel(sc, c); if (channel == NULL) { ic_printf(ic, "%s: invalid channel %u freq %u/0x%x\n", __func__, c->ic_ieee, c->ic_freq, c->ic_flags); return EINVAL; } c->ic_flags |= iwn_eeprom_channel_flags(channel); } return 0; } static void iwn_read_eeprom_enhinfo(struct iwn_softc *sc) { struct iwn_eeprom_enhinfo enhinfo[35]; struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_channel *c; uint16_t val, base; int8_t maxpwr; uint8_t flags; int i, j; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); iwn_read_prom_data(sc, IWN5000_EEPROM_REG, &val, 2); base = le16toh(val); iwn_read_prom_data(sc, base + IWN6000_EEPROM_ENHINFO, enhinfo, sizeof enhinfo); for (i = 0; i < nitems(enhinfo); i++) { flags = enhinfo[i].flags; if (!(flags & IWN_ENHINFO_VALID)) continue; /* Skip invalid entries. */ maxpwr = 0; if (sc->txchainmask & IWN_ANT_A) maxpwr = MAX(maxpwr, enhinfo[i].chain[0]); if (sc->txchainmask & IWN_ANT_B) maxpwr = MAX(maxpwr, enhinfo[i].chain[1]); if (sc->txchainmask & IWN_ANT_C) maxpwr = MAX(maxpwr, enhinfo[i].chain[2]); if (sc->ntxchains == 2) maxpwr = MAX(maxpwr, enhinfo[i].mimo2); else if (sc->ntxchains == 3) maxpwr = MAX(maxpwr, enhinfo[i].mimo3); for (j = 0; j < ic->ic_nchans; j++) { c = &ic->ic_channels[j]; if ((flags & IWN_ENHINFO_5GHZ)) { if (!IEEE80211_IS_CHAN_A(c)) continue; } else if ((flags & IWN_ENHINFO_OFDM)) { if (!IEEE80211_IS_CHAN_G(c)) continue; } else if (!IEEE80211_IS_CHAN_B(c)) continue; if ((flags & IWN_ENHINFO_HT40)) { if (!IEEE80211_IS_CHAN_HT40(c)) continue; } else { if (IEEE80211_IS_CHAN_HT40(c)) continue; } if (enhinfo[i].chan != 0 && enhinfo[i].chan != c->ic_ieee) continue; DPRINTF(sc, IWN_DEBUG_RESET, "channel %d(%x), maxpwr %d\n", c->ic_ieee, c->ic_flags, maxpwr / 2); c->ic_maxregpower = maxpwr / 2; c->ic_maxpower = maxpwr; } } DPRINTF(sc, IWN_DEBUG_TRACE, "->%s end\n", __func__); } static struct ieee80211_node * iwn_node_alloc(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN]) { return malloc(sizeof (struct iwn_node), M_80211_NODE,M_NOWAIT | M_ZERO); } static __inline int rate2plcp(int rate) { switch (rate & 0xff) { case 12: return 0xd; case 18: return 0xf; case 24: return 0x5; case 36: return 0x7; case 48: return 0x9; case 72: return 0xb; case 96: return 0x1; case 108: return 0x3; case 2: return 10; case 4: return 20; case 11: return 55; case 22: return 110; } return 0; } static int iwn_get_1stream_tx_antmask(struct iwn_softc *sc) { return IWN_LSB(sc->txchainmask); } static int iwn_get_2stream_tx_antmask(struct iwn_softc *sc) { int tx; /* * The '2 stream' setup is a bit .. odd. * * For NICs that support only 1 antenna, default to IWN_ANT_AB or * the firmware panics (eg Intel 5100.) * * For NICs that support two antennas, we use ANT_AB. * * For NICs that support three antennas, we use the two that * wasn't the default one. * * XXX TODO: if bluetooth (full concurrent) is enabled, restrict * this to only one antenna. */ /* Default - transmit on the other antennas */ tx = (sc->txchainmask & ~IWN_LSB(sc->txchainmask)); /* Now, if it's zero, set it to IWN_ANT_AB, so to not panic firmware */ if (tx == 0) tx = IWN_ANT_AB; /* * If the NIC is a two-stream TX NIC, configure the TX mask to * the default chainmask */ else if (sc->ntxchains == 2) tx = sc->txchainmask; return (tx); } /* * Calculate the required PLCP value from the given rate, * to the given node. * * This will take the node configuration (eg 11n, rate table * setup, etc) into consideration. */ static uint32_t iwn_rate_to_plcp(struct iwn_softc *sc, struct ieee80211_node *ni, uint8_t rate) { struct ieee80211com *ic = ni->ni_ic; uint32_t plcp = 0; int ridx; /* * If it's an MCS rate, let's set the plcp correctly * and set the relevant flags based on the node config. */ if (rate & IEEE80211_RATE_MCS) { /* * Set the initial PLCP value to be between 0->31 for * MCS 0 -> MCS 31, then set the "I'm an MCS rate!" * flag. */ plcp = IEEE80211_RV(rate) | IWN_RFLAG_MCS; /* * XXX the following should only occur if both * the local configuration _and_ the remote node * advertise these capabilities. Thus this code * may need fixing! */ /* * Set the channel width and guard interval. */ if (IEEE80211_IS_CHAN_HT40(ni->ni_chan)) { plcp |= IWN_RFLAG_HT40; if (ni->ni_htcap & IEEE80211_HTCAP_SHORTGI40) plcp |= IWN_RFLAG_SGI; } else if (ni->ni_htcap & IEEE80211_HTCAP_SHORTGI20) { plcp |= IWN_RFLAG_SGI; } /* * Ensure the selected rate matches the link quality * table entries being used. */ if (rate > 0x8f) plcp |= IWN_RFLAG_ANT(sc->txchainmask); else if (rate > 0x87) plcp |= IWN_RFLAG_ANT(iwn_get_2stream_tx_antmask(sc)); else plcp |= IWN_RFLAG_ANT(iwn_get_1stream_tx_antmask(sc)); } else { /* * Set the initial PLCP - fine for both * OFDM and CCK rates. */ plcp = rate2plcp(rate); /* Set CCK flag if it's CCK */ /* XXX It would be nice to have a method * to map the ridx -> phy table entry * so we could just query that, rather than * this hack to check against IWN_RIDX_OFDM6. */ ridx = ieee80211_legacy_rate_lookup(ic->ic_rt, rate & IEEE80211_RATE_VAL); if (ridx < IWN_RIDX_OFDM6 && IEEE80211_IS_CHAN_2GHZ(ni->ni_chan)) plcp |= IWN_RFLAG_CCK; /* Set antenna configuration */ /* XXX TODO: is this the right antenna to use for legacy? */ plcp |= IWN_RFLAG_ANT(iwn_get_1stream_tx_antmask(sc)); } DPRINTF(sc, IWN_DEBUG_TXRATE, "%s: rate=0x%02x, plcp=0x%08x\n", __func__, rate, plcp); return (htole32(plcp)); } static void iwn_newassoc(struct ieee80211_node *ni, int isnew) { /* Doesn't do anything at the moment */ } static int iwn_media_change(struct ifnet *ifp) { int error; error = ieee80211_media_change(ifp); /* NB: only the fixed rate can change and that doesn't need a reset */ return (error == ENETRESET ? 0 : error); } static int iwn_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct iwn_vap *ivp = IWN_VAP(vap); struct ieee80211com *ic = vap->iv_ic; struct iwn_softc *sc = ic->ic_softc; int error = 0; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); DPRINTF(sc, IWN_DEBUG_STATE, "%s: %s -> %s\n", __func__, ieee80211_state_name[vap->iv_state], ieee80211_state_name[nstate]); IEEE80211_UNLOCK(ic); IWN_LOCK(sc); callout_stop(&sc->calib_to); sc->rxon = &sc->rx_on[IWN_RXON_BSS_CTX]; switch (nstate) { case IEEE80211_S_ASSOC: if (vap->iv_state != IEEE80211_S_RUN) break; /* FALLTHROUGH */ case IEEE80211_S_AUTH: if (vap->iv_state == IEEE80211_S_AUTH) break; /* * !AUTH -> AUTH transition requires state reset to handle * reassociations correctly. */ sc->rxon->associd = 0; sc->rxon->filter &= ~htole32(IWN_FILTER_BSS); sc->calib.state = IWN_CALIB_STATE_INIT; /* Wait until we hear a beacon before we transmit */ sc->sc_beacon_wait = 1; if ((error = iwn_auth(sc, vap)) != 0) { device_printf(sc->sc_dev, "%s: could not move to auth state\n", __func__); } break; case IEEE80211_S_RUN: /* * RUN -> RUN transition; Just restart the timers. */ if (vap->iv_state == IEEE80211_S_RUN) { sc->calib_cnt = 0; break; } /* Wait until we hear a beacon before we transmit */ sc->sc_beacon_wait = 1; /* * !RUN -> RUN requires setting the association id * which is done with a firmware cmd. We also defer * starting the timers until that work is done. */ if ((error = iwn_run(sc, vap)) != 0) { device_printf(sc->sc_dev, "%s: could not move to run state\n", __func__); } break; case IEEE80211_S_INIT: sc->calib.state = IWN_CALIB_STATE_INIT; /* * Purge the xmit queue so we don't have old frames * during a new association attempt. */ sc->sc_beacon_wait = 0; iwn_xmit_queue_drain(sc); break; default: break; } IWN_UNLOCK(sc); IEEE80211_LOCK(ic); if (error != 0){ DPRINTF(sc, IWN_DEBUG_TRACE, "->%s end in error\n", __func__); return error; } DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end\n",__func__); return ivp->iv_newstate(vap, nstate, arg); } static void iwn_calib_timeout(void *arg) { struct iwn_softc *sc = arg; IWN_LOCK_ASSERT(sc); /* Force automatic TX power calibration every 60 secs. */ if (++sc->calib_cnt >= 120) { uint32_t flags = 0; DPRINTF(sc, IWN_DEBUG_CALIBRATE, "%s\n", "sending request for statistics"); (void)iwn_cmd(sc, IWN_CMD_GET_STATISTICS, &flags, sizeof flags, 1); sc->calib_cnt = 0; } callout_reset(&sc->calib_to, msecs_to_ticks(500), iwn_calib_timeout, sc); } /* * Process an RX_PHY firmware notification. This is usually immediately * followed by an MPDU_RX_DONE notification. */ static void iwn_rx_phy(struct iwn_softc *sc, struct iwn_rx_desc *desc, struct iwn_rx_data *data) { struct iwn_rx_stat *stat = (struct iwn_rx_stat *)(desc + 1); DPRINTF(sc, IWN_DEBUG_CALIBRATE, "%s: received PHY stats\n", __func__); bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD); /* Save RX statistics, they will be used on MPDU_RX_DONE. */ memcpy(&sc->last_rx_stat, stat, sizeof (*stat)); sc->last_rx_valid = 1; } /* * Process an RX_DONE (4965AGN only) or MPDU_RX_DONE firmware notification. * Each MPDU_RX_DONE notification must be preceded by an RX_PHY one. */ static void iwn_rx_done(struct iwn_softc *sc, struct iwn_rx_desc *desc, struct iwn_rx_data *data) { struct iwn_ops *ops = &sc->ops; struct ieee80211com *ic = &sc->sc_ic; struct iwn_rx_ring *ring = &sc->rxq; struct ieee80211_frame *wh; struct ieee80211_node *ni; struct mbuf *m, *m1; struct iwn_rx_stat *stat; caddr_t head; bus_addr_t paddr; uint32_t flags; int error, len, rssi, nf; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); if (desc->type == IWN_MPDU_RX_DONE) { /* Check for prior RX_PHY notification. */ if (!sc->last_rx_valid) { DPRINTF(sc, IWN_DEBUG_ANY, "%s: missing RX_PHY\n", __func__); return; } stat = &sc->last_rx_stat; } else stat = (struct iwn_rx_stat *)(desc + 1); bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTREAD); if (stat->cfg_phy_len > IWN_STAT_MAXLEN) { device_printf(sc->sc_dev, "%s: invalid RX statistic header, len %d\n", __func__, stat->cfg_phy_len); return; } if (desc->type == IWN_MPDU_RX_DONE) { struct iwn_rx_mpdu *mpdu = (struct iwn_rx_mpdu *)(desc + 1); head = (caddr_t)(mpdu + 1); len = le16toh(mpdu->len); } else { head = (caddr_t)(stat + 1) + stat->cfg_phy_len; len = le16toh(stat->len); } flags = le32toh(*(uint32_t *)(head + len)); /* Discard frames with a bad FCS early. */ if ((flags & IWN_RX_NOERROR) != IWN_RX_NOERROR) { DPRINTF(sc, IWN_DEBUG_RECV, "%s: RX flags error %x\n", __func__, flags); counter_u64_add(ic->ic_ierrors, 1); return; } /* Discard frames that are too short. */ if (len < sizeof (struct ieee80211_frame_ack)) { DPRINTF(sc, IWN_DEBUG_RECV, "%s: frame too short: %d\n", __func__, len); counter_u64_add(ic->ic_ierrors, 1); return; } m1 = m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR, IWN_RBUF_SIZE); if (m1 == NULL) { DPRINTF(sc, IWN_DEBUG_ANY, "%s: no mbuf to restock ring\n", __func__); counter_u64_add(ic->ic_ierrors, 1); return; } bus_dmamap_unload(ring->data_dmat, data->map); error = bus_dmamap_load(ring->data_dmat, data->map, mtod(m1, void *), IWN_RBUF_SIZE, iwn_dma_map_addr, &paddr, BUS_DMA_NOWAIT); if (error != 0 && error != EFBIG) { device_printf(sc->sc_dev, "%s: bus_dmamap_load failed, error %d\n", __func__, error); m_freem(m1); /* Try to reload the old mbuf. */ error = bus_dmamap_load(ring->data_dmat, data->map, mtod(data->m, void *), IWN_RBUF_SIZE, iwn_dma_map_addr, &paddr, BUS_DMA_NOWAIT); if (error != 0 && error != EFBIG) { panic("%s: could not load old RX mbuf", __func__); } /* Physical address may have changed. */ ring->desc[ring->cur] = htole32(paddr >> 8); bus_dmamap_sync(ring->data_dmat, ring->desc_dma.map, BUS_DMASYNC_PREWRITE); counter_u64_add(ic->ic_ierrors, 1); return; } m = data->m; data->m = m1; /* Update RX descriptor. */ ring->desc[ring->cur] = htole32(paddr >> 8); bus_dmamap_sync(ring->desc_dma.tag, ring->desc_dma.map, BUS_DMASYNC_PREWRITE); /* Finalize mbuf. */ m->m_data = head; m->m_pkthdr.len = m->m_len = len; /* Grab a reference to the source node. */ wh = mtod(m, struct ieee80211_frame *); if (len >= sizeof(struct ieee80211_frame_min)) ni = ieee80211_find_rxnode(ic, (struct ieee80211_frame_min *)wh); else ni = NULL; nf = (ni != NULL && ni->ni_vap->iv_state == IEEE80211_S_RUN && (ic->ic_flags & IEEE80211_F_SCAN) == 0) ? sc->noise : -95; rssi = ops->get_rssi(sc, stat); if (ieee80211_radiotap_active(ic)) { struct iwn_rx_radiotap_header *tap = &sc->sc_rxtap; tap->wr_flags = 0; if (stat->flags & htole16(IWN_STAT_FLAG_SHPREAMBLE)) tap->wr_flags |= IEEE80211_RADIOTAP_F_SHORTPRE; tap->wr_dbm_antsignal = (int8_t)rssi; tap->wr_dbm_antnoise = (int8_t)nf; tap->wr_tsft = stat->tstamp; switch (stat->rate) { /* CCK rates. */ case 10: tap->wr_rate = 2; break; case 20: tap->wr_rate = 4; break; case 55: tap->wr_rate = 11; break; case 110: tap->wr_rate = 22; break; /* OFDM rates. */ case 0xd: tap->wr_rate = 12; break; case 0xf: tap->wr_rate = 18; break; case 0x5: tap->wr_rate = 24; break; case 0x7: tap->wr_rate = 36; break; case 0x9: tap->wr_rate = 48; break; case 0xb: tap->wr_rate = 72; break; case 0x1: tap->wr_rate = 96; break; case 0x3: tap->wr_rate = 108; break; /* Unknown rate: should not happen. */ default: tap->wr_rate = 0; } } /* * If it's a beacon and we're waiting, then do the * wakeup. This should unblock raw_xmit/start. */ if (sc->sc_beacon_wait) { uint8_t type, subtype; /* NB: Re-assign wh */ wh = mtod(m, struct ieee80211_frame *); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; /* * This assumes at this point we've received our own * beacon. */ DPRINTF(sc, IWN_DEBUG_TRACE, "%s: beacon_wait, type=%d, subtype=%d\n", __func__, type, subtype); if (type == IEEE80211_FC0_TYPE_MGT && subtype == IEEE80211_FC0_SUBTYPE_BEACON) { DPRINTF(sc, IWN_DEBUG_TRACE | IWN_DEBUG_XMIT, "%s: waking things up\n", __func__); /* queue taskqueue to transmit! */ taskqueue_enqueue(sc->sc_tq, &sc->sc_xmit_task); } } IWN_UNLOCK(sc); /* Send the frame to the 802.11 layer. */ if (ni != NULL) { if (ni->ni_flags & IEEE80211_NODE_HT) m->m_flags |= M_AMPDU; (void)ieee80211_input(ni, m, rssi - nf, nf); /* Node is no longer needed. */ ieee80211_free_node(ni); } else (void)ieee80211_input_all(ic, m, rssi - nf, nf); IWN_LOCK(sc); DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end\n",__func__); } /* Process an incoming Compressed BlockAck. */ static void iwn_rx_compressed_ba(struct iwn_softc *sc, struct iwn_rx_desc *desc, struct iwn_rx_data *data) { struct iwn_ops *ops = &sc->ops; struct iwn_node *wn; struct ieee80211_node *ni; struct iwn_compressed_ba *ba = (struct iwn_compressed_ba *)(desc + 1); struct iwn_tx_ring *txq; struct iwn_tx_data *txdata; struct ieee80211_tx_ampdu *tap; struct mbuf *m; uint64_t bitmap; uint16_t ssn; uint8_t tid; int ackfailcnt = 0, i, lastidx, qid, *res, shift; int tx_ok = 0, tx_err = 0; DPRINTF(sc, IWN_DEBUG_TRACE | IWN_DEBUG_XMIT, "->%s begin\n", __func__); bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD); qid = le16toh(ba->qid); txq = &sc->txq[ba->qid]; tap = sc->qid2tap[ba->qid]; tid = tap->txa_tid; wn = (void *)tap->txa_ni; res = NULL; ssn = 0; if (!IEEE80211_AMPDU_RUNNING(tap)) { res = tap->txa_private; ssn = tap->txa_start & 0xfff; } for (lastidx = le16toh(ba->ssn) & 0xff; txq->read != lastidx;) { txdata = &txq->data[txq->read]; /* Unmap and free mbuf. */ bus_dmamap_sync(txq->data_dmat, txdata->map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(txq->data_dmat, txdata->map); m = txdata->m, txdata->m = NULL; ni = txdata->ni, txdata->ni = NULL; KASSERT(ni != NULL, ("no node")); KASSERT(m != NULL, ("no mbuf")); DPRINTF(sc, IWN_DEBUG_XMIT, "%s: freeing m=%p\n", __func__, m); ieee80211_tx_complete(ni, m, 1); txq->queued--; txq->read = (txq->read + 1) % IWN_TX_RING_COUNT; } if (txq->queued == 0 && res != NULL) { iwn_nic_lock(sc); ops->ampdu_tx_stop(sc, qid, tid, ssn); iwn_nic_unlock(sc); sc->qid2tap[qid] = NULL; free(res, M_DEVBUF); return; } if (wn->agg[tid].bitmap == 0) return; shift = wn->agg[tid].startidx - ((le16toh(ba->seq) >> 4) & 0xff); if (shift < 0) shift += 0x100; if (wn->agg[tid].nframes > (64 - shift)) return; /* * Walk the bitmap and calculate how many successful and failed * attempts are made. * * Yes, the rate control code doesn't know these are A-MPDU * subframes and that it's okay to fail some of these. */ ni = tap->txa_ni; bitmap = (le64toh(ba->bitmap) >> shift) & wn->agg[tid].bitmap; for (i = 0; bitmap; i++) { if ((bitmap & 1) == 0) { tx_err ++; ieee80211_ratectl_tx_complete(ni->ni_vap, ni, IEEE80211_RATECTL_TX_FAILURE, &ackfailcnt, NULL); } else { tx_ok ++; ieee80211_ratectl_tx_complete(ni->ni_vap, ni, IEEE80211_RATECTL_TX_SUCCESS, &ackfailcnt, NULL); } bitmap >>= 1; } DPRINTF(sc, IWN_DEBUG_TRACE | IWN_DEBUG_XMIT, "->%s: end; %d ok; %d err\n",__func__, tx_ok, tx_err); } /* * Process a CALIBRATION_RESULT notification sent by the initialization * firmware on response to a CMD_CALIB_CONFIG command (5000 only). */ static void iwn5000_rx_calib_results(struct iwn_softc *sc, struct iwn_rx_desc *desc, struct iwn_rx_data *data) { struct iwn_phy_calib *calib = (struct iwn_phy_calib *)(desc + 1); int len, idx = -1; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); /* Runtime firmware should not send such a notification. */ if (sc->sc_flags & IWN_FLAG_CALIB_DONE){ DPRINTF(sc, IWN_DEBUG_TRACE, "->%s received after clib done\n", __func__); return; } len = (le32toh(desc->len) & 0x3fff) - 4; bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD); switch (calib->code) { case IWN5000_PHY_CALIB_DC: if (sc->base_params->calib_need & IWN_FLG_NEED_PHY_CALIB_DC) idx = 0; break; case IWN5000_PHY_CALIB_LO: if (sc->base_params->calib_need & IWN_FLG_NEED_PHY_CALIB_LO) idx = 1; break; case IWN5000_PHY_CALIB_TX_IQ: if (sc->base_params->calib_need & IWN_FLG_NEED_PHY_CALIB_TX_IQ) idx = 2; break; case IWN5000_PHY_CALIB_TX_IQ_PERIODIC: if (sc->base_params->calib_need & IWN_FLG_NEED_PHY_CALIB_TX_IQ_PERIODIC) idx = 3; break; case IWN5000_PHY_CALIB_BASE_BAND: if (sc->base_params->calib_need & IWN_FLG_NEED_PHY_CALIB_BASE_BAND) idx = 4; break; } if (idx == -1) /* Ignore other results. */ return; /* Save calibration result. */ if (sc->calibcmd[idx].buf != NULL) free(sc->calibcmd[idx].buf, M_DEVBUF); sc->calibcmd[idx].buf = malloc(len, M_DEVBUF, M_NOWAIT); if (sc->calibcmd[idx].buf == NULL) { DPRINTF(sc, IWN_DEBUG_CALIBRATE, "not enough memory for calibration result %d\n", calib->code); return; } DPRINTF(sc, IWN_DEBUG_CALIBRATE, "saving calibration result idx=%d, code=%d len=%d\n", idx, calib->code, len); sc->calibcmd[idx].len = len; memcpy(sc->calibcmd[idx].buf, calib, len); } static void iwn_stats_update(struct iwn_softc *sc, struct iwn_calib_state *calib, struct iwn_stats *stats, int len) { struct iwn_stats_bt *stats_bt; struct iwn_stats *lstats; /* * First - check whether the length is the bluetooth or normal. * * If it's normal - just copy it and bump out. * Otherwise we have to convert things. */ if (len == sizeof(struct iwn_stats) + 4) { memcpy(&sc->last_stat, stats, sizeof(struct iwn_stats)); sc->last_stat_valid = 1; return; } /* * If it's not the bluetooth size - log, then just copy. */ if (len != sizeof(struct iwn_stats_bt) + 4) { DPRINTF(sc, IWN_DEBUG_STATS, "%s: size of rx statistics (%d) not an expected size!\n", __func__, len); memcpy(&sc->last_stat, stats, sizeof(struct iwn_stats)); sc->last_stat_valid = 1; return; } /* * Ok. Time to copy. */ stats_bt = (struct iwn_stats_bt *) stats; lstats = &sc->last_stat; /* flags */ lstats->flags = stats_bt->flags; /* rx_bt */ memcpy(&lstats->rx.ofdm, &stats_bt->rx_bt.ofdm, sizeof(struct iwn_rx_phy_stats)); memcpy(&lstats->rx.cck, &stats_bt->rx_bt.cck, sizeof(struct iwn_rx_phy_stats)); memcpy(&lstats->rx.general, &stats_bt->rx_bt.general_bt.common, sizeof(struct iwn_rx_general_stats)); memcpy(&lstats->rx.ht, &stats_bt->rx_bt.ht, sizeof(struct iwn_rx_ht_phy_stats)); /* tx */ memcpy(&lstats->tx, &stats_bt->tx, sizeof(struct iwn_tx_stats)); /* general */ memcpy(&lstats->general, &stats_bt->general, sizeof(struct iwn_general_stats)); /* XXX TODO: Squirrel away the extra bluetooth stats somewhere */ sc->last_stat_valid = 1; } /* * Process an RX_STATISTICS or BEACON_STATISTICS firmware notification. * The latter is sent by the firmware after each received beacon. */ static void iwn_rx_statistics(struct iwn_softc *sc, struct iwn_rx_desc *desc, struct iwn_rx_data *data) { struct iwn_ops *ops = &sc->ops; struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); struct iwn_calib_state *calib = &sc->calib; struct iwn_stats *stats = (struct iwn_stats *)(desc + 1); struct iwn_stats *lstats; int temp; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); /* Ignore statistics received during a scan. */ if (vap->iv_state != IEEE80211_S_RUN || (ic->ic_flags & IEEE80211_F_SCAN)){ DPRINTF(sc, IWN_DEBUG_TRACE, "->%s received during calib\n", __func__); return; } bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD); DPRINTF(sc, IWN_DEBUG_CALIBRATE | IWN_DEBUG_STATS, "%s: received statistics, cmd %d, len %d\n", __func__, desc->type, le16toh(desc->len)); sc->calib_cnt = 0; /* Reset TX power calibration timeout. */ /* * Collect/track general statistics for reporting. * * This takes care of ensuring that the bluetooth sized message * will be correctly converted to the legacy sized message. */ iwn_stats_update(sc, calib, stats, le16toh(desc->len)); /* * And now, let's take a reference of it to use! */ lstats = &sc->last_stat; /* Test if temperature has changed. */ if (lstats->general.temp != sc->rawtemp) { /* Convert "raw" temperature to degC. */ sc->rawtemp = stats->general.temp; temp = ops->get_temperature(sc); DPRINTF(sc, IWN_DEBUG_CALIBRATE, "%s: temperature %d\n", __func__, temp); /* Update TX power if need be (4965AGN only). */ if (sc->hw_type == IWN_HW_REV_TYPE_4965) iwn4965_power_calibration(sc, temp); } if (desc->type != IWN_BEACON_STATISTICS) return; /* Reply to a statistics request. */ sc->noise = iwn_get_noise(&lstats->rx.general); DPRINTF(sc, IWN_DEBUG_CALIBRATE, "%s: noise %d\n", __func__, sc->noise); /* Test that RSSI and noise are present in stats report. */ if (le32toh(lstats->rx.general.flags) != 1) { DPRINTF(sc, IWN_DEBUG_ANY, "%s\n", "received statistics without RSSI"); return; } if (calib->state == IWN_CALIB_STATE_ASSOC) iwn_collect_noise(sc, &lstats->rx.general); else if (calib->state == IWN_CALIB_STATE_RUN) { iwn_tune_sensitivity(sc, &lstats->rx); /* * XXX TODO: Only run the RX recovery if we're associated! */ iwn_check_rx_recovery(sc, lstats); iwn_save_stats_counters(sc, lstats); } DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end\n",__func__); } /* * Save the relevant statistic counters for the next calibration * pass. */ static void iwn_save_stats_counters(struct iwn_softc *sc, const struct iwn_stats *rs) { struct iwn_calib_state *calib = &sc->calib; /* Save counters values for next call. */ calib->bad_plcp_cck = le32toh(rs->rx.cck.bad_plcp); calib->fa_cck = le32toh(rs->rx.cck.fa); calib->bad_plcp_ht = le32toh(rs->rx.ht.bad_plcp); calib->bad_plcp_ofdm = le32toh(rs->rx.ofdm.bad_plcp); calib->fa_ofdm = le32toh(rs->rx.ofdm.fa); /* Last time we received these tick values */ sc->last_calib_ticks = ticks; } /* * Process a TX_DONE firmware notification. Unfortunately, the 4965AGN * and 5000 adapters have different incompatible TX status formats. */ static void iwn4965_tx_done(struct iwn_softc *sc, struct iwn_rx_desc *desc, struct iwn_rx_data *data) { struct iwn4965_tx_stat *stat = (struct iwn4965_tx_stat *)(desc + 1); struct iwn_tx_ring *ring; int qid; qid = desc->qid & 0xf; ring = &sc->txq[qid]; DPRINTF(sc, IWN_DEBUG_XMIT, "%s: " "qid %d idx %d RTS retries %d ACK retries %d nkill %d rate %x duration %d status %x\n", __func__, desc->qid, desc->idx, stat->rtsfailcnt, stat->ackfailcnt, stat->btkillcnt, stat->rate, le16toh(stat->duration), le32toh(stat->status)); bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTREAD); if (qid >= sc->firstaggqueue) { iwn_ampdu_tx_done(sc, qid, desc->idx, stat->nframes, stat->ackfailcnt, &stat->status); } else { iwn_tx_done(sc, desc, stat->ackfailcnt, le32toh(stat->status) & 0xff); } } static void iwn5000_tx_done(struct iwn_softc *sc, struct iwn_rx_desc *desc, struct iwn_rx_data *data) { struct iwn5000_tx_stat *stat = (struct iwn5000_tx_stat *)(desc + 1); struct iwn_tx_ring *ring; int qid; qid = desc->qid & 0xf; ring = &sc->txq[qid]; DPRINTF(sc, IWN_DEBUG_XMIT, "%s: " "qid %d idx %d RTS retries %d ACK retries %d nkill %d rate %x duration %d status %x\n", __func__, desc->qid, desc->idx, stat->rtsfailcnt, stat->ackfailcnt, stat->btkillcnt, stat->rate, le16toh(stat->duration), le32toh(stat->status)); #ifdef notyet /* Reset TX scheduler slot. */ iwn5000_reset_sched(sc, desc->qid & 0xf, desc->idx); #endif bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTREAD); if (qid >= sc->firstaggqueue) { iwn_ampdu_tx_done(sc, qid, desc->idx, stat->nframes, stat->ackfailcnt, &stat->status); } else { iwn_tx_done(sc, desc, stat->ackfailcnt, le16toh(stat->status) & 0xff); } } /* * Adapter-independent backend for TX_DONE firmware notifications. */ static void iwn_tx_done(struct iwn_softc *sc, struct iwn_rx_desc *desc, int ackfailcnt, uint8_t status) { struct iwn_tx_ring *ring = &sc->txq[desc->qid & 0xf]; struct iwn_tx_data *data = &ring->data[desc->idx]; struct mbuf *m; struct ieee80211_node *ni; struct ieee80211vap *vap; KASSERT(data->ni != NULL, ("no node")); DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); /* Unmap and free mbuf. */ bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(ring->data_dmat, data->map); m = data->m, data->m = NULL; ni = data->ni, data->ni = NULL; vap = ni->ni_vap; /* * Update rate control statistics for the node. */ if (status & IWN_TX_FAIL) ieee80211_ratectl_tx_complete(vap, ni, IEEE80211_RATECTL_TX_FAILURE, &ackfailcnt, NULL); else ieee80211_ratectl_tx_complete(vap, ni, IEEE80211_RATECTL_TX_SUCCESS, &ackfailcnt, NULL); /* * Channels marked for "radar" require traffic to be received * to unlock before we can transmit. Until traffic is seen * any attempt to transmit is returned immediately with status * set to IWN_TX_FAIL_TX_LOCKED. Unfortunately this can easily * happen on first authenticate after scanning. To workaround * this we ignore a failure of this sort in AUTH state so the * 802.11 layer will fall back to using a timeout to wait for * the AUTH reply. This allows the firmware time to see * traffic so a subsequent retry of AUTH succeeds. It's * unclear why the firmware does not maintain state for * channels recently visited as this would allow immediate * use of the channel after a scan (where we see traffic). */ if (status == IWN_TX_FAIL_TX_LOCKED && ni->ni_vap->iv_state == IEEE80211_S_AUTH) ieee80211_tx_complete(ni, m, 0); else ieee80211_tx_complete(ni, m, (status & IWN_TX_FAIL) != 0); sc->sc_tx_timer = 0; if (--ring->queued < IWN_TX_RING_LOMARK) sc->qfullmsk &= ~(1 << ring->qid); DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end\n",__func__); } /* * Process a "command done" firmware notification. This is where we wakeup * processes waiting for a synchronous command completion. */ static void iwn_cmd_done(struct iwn_softc *sc, struct iwn_rx_desc *desc) { struct iwn_tx_ring *ring; struct iwn_tx_data *data; int cmd_queue_num; if (sc->sc_flags & IWN_FLAG_PAN_SUPPORT) cmd_queue_num = IWN_PAN_CMD_QUEUE; else cmd_queue_num = IWN_CMD_QUEUE_NUM; if ((desc->qid & IWN_RX_DESC_QID_MSK) != cmd_queue_num) return; /* Not a command ack. */ ring = &sc->txq[cmd_queue_num]; data = &ring->data[desc->idx]; /* If the command was mapped in an mbuf, free it. */ if (data->m != NULL) { bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(ring->data_dmat, data->map); m_freem(data->m); data->m = NULL; } wakeup(&ring->desc[desc->idx]); } static void iwn_ampdu_tx_done(struct iwn_softc *sc, int qid, int idx, int nframes, int ackfailcnt, void *stat) { struct iwn_ops *ops = &sc->ops; struct iwn_tx_ring *ring = &sc->txq[qid]; struct iwn_tx_data *data; struct mbuf *m; struct iwn_node *wn; struct ieee80211_node *ni; struct ieee80211_tx_ampdu *tap; uint64_t bitmap; uint32_t *status = stat; uint16_t *aggstatus = stat; uint16_t ssn; uint8_t tid; int bit, i, lastidx, *res, seqno, shift, start; /* XXX TODO: status is le16 field! Grr */ DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); DPRINTF(sc, IWN_DEBUG_XMIT, "%s: nframes=%d, status=0x%08x\n", __func__, nframes, *status); tap = sc->qid2tap[qid]; tid = tap->txa_tid; wn = (void *)tap->txa_ni; ni = tap->txa_ni; /* * XXX TODO: ACK and RTS failures would be nice here! */ /* * A-MPDU single frame status - if we failed to transmit it * in A-MPDU, then it may be a permanent failure. * * XXX TODO: check what the Linux iwlwifi driver does here; * there's some permanent and temporary failures that may be * handled differently. */ if (nframes == 1) { if ((*status & 0xff) != 1 && (*status & 0xff) != 2) { #ifdef NOT_YET printf("ieee80211_send_bar()\n"); #endif /* * If we completely fail a transmit, make sure a * notification is pushed up to the rate control * layer. */ ieee80211_ratectl_tx_complete(ni->ni_vap, ni, IEEE80211_RATECTL_TX_FAILURE, &ackfailcnt, NULL); } else { /* * If nframes=1, then we won't be getting a BA for * this frame. Ensure that we correctly update the * rate control code with how many retries were * needed to send it. */ ieee80211_ratectl_tx_complete(ni->ni_vap, ni, IEEE80211_RATECTL_TX_SUCCESS, &ackfailcnt, NULL); } } bitmap = 0; start = idx; for (i = 0; i < nframes; i++) { if (le16toh(aggstatus[i * 2]) & 0xc) continue; idx = le16toh(aggstatus[2*i + 1]) & 0xff; bit = idx - start; shift = 0; if (bit >= 64) { shift = 0x100 - idx + start; bit = 0; start = idx; } else if (bit <= -64) bit = 0x100 - start + idx; else if (bit < 0) { shift = start - idx; start = idx; bit = 0; } bitmap = bitmap << shift; bitmap |= 1ULL << bit; } tap = sc->qid2tap[qid]; tid = tap->txa_tid; wn = (void *)tap->txa_ni; wn->agg[tid].bitmap = bitmap; wn->agg[tid].startidx = start; wn->agg[tid].nframes = nframes; res = NULL; ssn = 0; if (!IEEE80211_AMPDU_RUNNING(tap)) { res = tap->txa_private; ssn = tap->txa_start & 0xfff; } /* This is going nframes DWORDS into the descriptor? */ seqno = le32toh(*(status + nframes)) & 0xfff; for (lastidx = (seqno & 0xff); ring->read != lastidx;) { data = &ring->data[ring->read]; /* Unmap and free mbuf. */ bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(ring->data_dmat, data->map); m = data->m, data->m = NULL; ni = data->ni, data->ni = NULL; KASSERT(ni != NULL, ("no node")); KASSERT(m != NULL, ("no mbuf")); DPRINTF(sc, IWN_DEBUG_XMIT, "%s: freeing m=%p\n", __func__, m); ieee80211_tx_complete(ni, m, 1); ring->queued--; ring->read = (ring->read + 1) % IWN_TX_RING_COUNT; } if (ring->queued == 0 && res != NULL) { iwn_nic_lock(sc); ops->ampdu_tx_stop(sc, qid, tid, ssn); iwn_nic_unlock(sc); sc->qid2tap[qid] = NULL; free(res, M_DEVBUF); return; } sc->sc_tx_timer = 0; if (ring->queued < IWN_TX_RING_LOMARK) sc->qfullmsk &= ~(1 << ring->qid); DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end\n",__func__); } /* * Process an INT_FH_RX or INT_SW_RX interrupt. */ static void iwn_notif_intr(struct iwn_softc *sc) { struct iwn_ops *ops = &sc->ops; struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); uint16_t hw; bus_dmamap_sync(sc->rxq.stat_dma.tag, sc->rxq.stat_dma.map, BUS_DMASYNC_POSTREAD); hw = le16toh(sc->rxq.stat->closed_count) & 0xfff; while (sc->rxq.cur != hw) { struct iwn_rx_data *data = &sc->rxq.data[sc->rxq.cur]; struct iwn_rx_desc *desc; bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD); desc = mtod(data->m, struct iwn_rx_desc *); DPRINTF(sc, IWN_DEBUG_RECV, "%s: cur=%d; qid %x idx %d flags %x type %d(%s) len %d\n", __func__, sc->rxq.cur, desc->qid & 0xf, desc->idx, desc->flags, desc->type, iwn_intr_str(desc->type), le16toh(desc->len)); if (!(desc->qid & IWN_UNSOLICITED_RX_NOTIF)) /* Reply to a command. */ iwn_cmd_done(sc, desc); switch (desc->type) { case IWN_RX_PHY: iwn_rx_phy(sc, desc, data); break; case IWN_RX_DONE: /* 4965AGN only. */ case IWN_MPDU_RX_DONE: /* An 802.11 frame has been received. */ iwn_rx_done(sc, desc, data); break; case IWN_RX_COMPRESSED_BA: /* A Compressed BlockAck has been received. */ iwn_rx_compressed_ba(sc, desc, data); break; case IWN_TX_DONE: /* An 802.11 frame has been transmitted. */ ops->tx_done(sc, desc, data); break; case IWN_RX_STATISTICS: case IWN_BEACON_STATISTICS: iwn_rx_statistics(sc, desc, data); break; case IWN_BEACON_MISSED: { struct iwn_beacon_missed *miss = (struct iwn_beacon_missed *)(desc + 1); int misses; bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD); misses = le32toh(miss->consecutive); DPRINTF(sc, IWN_DEBUG_STATE, "%s: beacons missed %d/%d\n", __func__, misses, le32toh(miss->total)); /* * If more than 5 consecutive beacons are missed, * reinitialize the sensitivity state machine. */ if (vap->iv_state == IEEE80211_S_RUN && (ic->ic_flags & IEEE80211_F_SCAN) == 0) { if (misses > 5) (void)iwn_init_sensitivity(sc); if (misses >= vap->iv_bmissthreshold) { IWN_UNLOCK(sc); ieee80211_beacon_miss(ic); IWN_LOCK(sc); } } break; } case IWN_UC_READY: { struct iwn_ucode_info *uc = (struct iwn_ucode_info *)(desc + 1); /* The microcontroller is ready. */ bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD); DPRINTF(sc, IWN_DEBUG_RESET, "microcode alive notification version=%d.%d " "subtype=%x alive=%x\n", uc->major, uc->minor, uc->subtype, le32toh(uc->valid)); if (le32toh(uc->valid) != 1) { device_printf(sc->sc_dev, "microcontroller initialization failed"); break; } if (uc->subtype == IWN_UCODE_INIT) { /* Save microcontroller report. */ memcpy(&sc->ucode_info, uc, sizeof (*uc)); } /* Save the address of the error log in SRAM. */ sc->errptr = le32toh(uc->errptr); break; } case IWN_STATE_CHANGED: { /* * State change allows hardware switch change to be * noted. However, we handle this in iwn_intr as we * get both the enable/disble intr. */ bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD); #ifdef IWN_DEBUG uint32_t *status = (uint32_t *)(desc + 1); DPRINTF(sc, IWN_DEBUG_INTR | IWN_DEBUG_STATE, "state changed to %x\n", le32toh(*status)); #endif break; } case IWN_START_SCAN: { bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD); #ifdef IWN_DEBUG struct iwn_start_scan *scan = (struct iwn_start_scan *)(desc + 1); DPRINTF(sc, IWN_DEBUG_ANY, "%s: scanning channel %d status %x\n", __func__, scan->chan, le32toh(scan->status)); #endif break; } case IWN_STOP_SCAN: { bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD); #ifdef IWN_DEBUG struct iwn_stop_scan *scan = (struct iwn_stop_scan *)(desc + 1); DPRINTF(sc, IWN_DEBUG_STATE | IWN_DEBUG_SCAN, "scan finished nchan=%d status=%d chan=%d\n", scan->nchan, scan->status, scan->chan); #endif sc->sc_is_scanning = 0; IWN_UNLOCK(sc); ieee80211_scan_next(vap); IWN_LOCK(sc); break; } case IWN5000_CALIBRATION_RESULT: iwn5000_rx_calib_results(sc, desc, data); break; case IWN5000_CALIBRATION_DONE: sc->sc_flags |= IWN_FLAG_CALIB_DONE; wakeup(sc); break; } sc->rxq.cur = (sc->rxq.cur + 1) % IWN_RX_RING_COUNT; } /* Tell the firmware what we have processed. */ hw = (hw == 0) ? IWN_RX_RING_COUNT - 1 : hw - 1; IWN_WRITE(sc, IWN_FH_RX_WPTR, hw & ~7); } /* * Process an INT_WAKEUP interrupt raised when the microcontroller wakes up * from power-down sleep mode. */ static void iwn_wakeup_intr(struct iwn_softc *sc) { int qid; DPRINTF(sc, IWN_DEBUG_RESET, "%s: ucode wakeup from power-down sleep\n", __func__); /* Wakeup RX and TX rings. */ IWN_WRITE(sc, IWN_FH_RX_WPTR, sc->rxq.cur & ~7); for (qid = 0; qid < sc->ntxqs; qid++) { struct iwn_tx_ring *ring = &sc->txq[qid]; IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, qid << 8 | ring->cur); } } static void iwn_rftoggle_intr(struct iwn_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; uint32_t tmp = IWN_READ(sc, IWN_GP_CNTRL); IWN_LOCK_ASSERT(sc); device_printf(sc->sc_dev, "RF switch: radio %s\n", (tmp & IWN_GP_CNTRL_RFKILL) ? "enabled" : "disabled"); if (tmp & IWN_GP_CNTRL_RFKILL) ieee80211_runtask(ic, &sc->sc_radioon_task); else ieee80211_runtask(ic, &sc->sc_radiooff_task); } /* * Dump the error log of the firmware when a firmware panic occurs. Although * we can't debug the firmware because it is neither open source nor free, it * can help us to identify certain classes of problems. */ static void iwn_fatal_intr(struct iwn_softc *sc) { struct iwn_fw_dump dump; int i; IWN_LOCK_ASSERT(sc); /* Force a complete recalibration on next init. */ sc->sc_flags &= ~IWN_FLAG_CALIB_DONE; /* Check that the error log address is valid. */ if (sc->errptr < IWN_FW_DATA_BASE || sc->errptr + sizeof (dump) > IWN_FW_DATA_BASE + sc->fw_data_maxsz) { printf("%s: bad firmware error log address 0x%08x\n", __func__, sc->errptr); return; } if (iwn_nic_lock(sc) != 0) { printf("%s: could not read firmware error log\n", __func__); return; } /* Read firmware error log from SRAM. */ iwn_mem_read_region_4(sc, sc->errptr, (uint32_t *)&dump, sizeof (dump) / sizeof (uint32_t)); iwn_nic_unlock(sc); if (dump.valid == 0) { printf("%s: firmware error log is empty\n", __func__); return; } printf("firmware error log:\n"); printf(" error type = \"%s\" (0x%08X)\n", (dump.id < nitems(iwn_fw_errmsg)) ? iwn_fw_errmsg[dump.id] : "UNKNOWN", dump.id); printf(" program counter = 0x%08X\n", dump.pc); printf(" source line = 0x%08X\n", dump.src_line); printf(" error data = 0x%08X%08X\n", dump.error_data[0], dump.error_data[1]); printf(" branch link = 0x%08X%08X\n", dump.branch_link[0], dump.branch_link[1]); printf(" interrupt link = 0x%08X%08X\n", dump.interrupt_link[0], dump.interrupt_link[1]); printf(" time = %u\n", dump.time[0]); /* Dump driver status (TX and RX rings) while we're here. */ printf("driver status:\n"); for (i = 0; i < sc->ntxqs; i++) { struct iwn_tx_ring *ring = &sc->txq[i]; printf(" tx ring %2d: qid=%-2d cur=%-3d queued=%-3d\n", i, ring->qid, ring->cur, ring->queued); } printf(" rx ring: cur=%d\n", sc->rxq.cur); } static void iwn_intr(void *arg) { struct iwn_softc *sc = arg; uint32_t r1, r2, tmp; IWN_LOCK(sc); /* Disable interrupts. */ IWN_WRITE(sc, IWN_INT_MASK, 0); /* Read interrupts from ICT (fast) or from registers (slow). */ if (sc->sc_flags & IWN_FLAG_USE_ICT) { tmp = 0; while (sc->ict[sc->ict_cur] != 0) { tmp |= sc->ict[sc->ict_cur]; sc->ict[sc->ict_cur] = 0; /* Acknowledge. */ sc->ict_cur = (sc->ict_cur + 1) % IWN_ICT_COUNT; } tmp = le32toh(tmp); if (tmp == 0xffffffff) /* Shouldn't happen. */ tmp = 0; else if (tmp & 0xc0000) /* Workaround a HW bug. */ tmp |= 0x8000; r1 = (tmp & 0xff00) << 16 | (tmp & 0xff); r2 = 0; /* Unused. */ } else { r1 = IWN_READ(sc, IWN_INT); if (r1 == 0xffffffff || (r1 & 0xfffffff0) == 0xa5a5a5a0) { IWN_UNLOCK(sc); return; /* Hardware gone! */ } r2 = IWN_READ(sc, IWN_FH_INT); } DPRINTF(sc, IWN_DEBUG_INTR, "interrupt reg1=0x%08x reg2=0x%08x\n" , r1, r2); if (r1 == 0 && r2 == 0) goto done; /* Interrupt not for us. */ /* Acknowledge interrupts. */ IWN_WRITE(sc, IWN_INT, r1); if (!(sc->sc_flags & IWN_FLAG_USE_ICT)) IWN_WRITE(sc, IWN_FH_INT, r2); if (r1 & IWN_INT_RF_TOGGLED) { iwn_rftoggle_intr(sc); goto done; } if (r1 & IWN_INT_CT_REACHED) { device_printf(sc->sc_dev, "%s: critical temperature reached!\n", __func__); } if (r1 & (IWN_INT_SW_ERR | IWN_INT_HW_ERR)) { device_printf(sc->sc_dev, "%s: fatal firmware error\n", __func__); #ifdef IWN_DEBUG iwn_debug_register(sc); #endif /* Dump firmware error log and stop. */ iwn_fatal_intr(sc); taskqueue_enqueue(sc->sc_tq, &sc->sc_panic_task); goto done; } if ((r1 & (IWN_INT_FH_RX | IWN_INT_SW_RX | IWN_INT_RX_PERIODIC)) || (r2 & IWN_FH_INT_RX)) { if (sc->sc_flags & IWN_FLAG_USE_ICT) { if (r1 & (IWN_INT_FH_RX | IWN_INT_SW_RX)) IWN_WRITE(sc, IWN_FH_INT, IWN_FH_INT_RX); IWN_WRITE_1(sc, IWN_INT_PERIODIC, IWN_INT_PERIODIC_DIS); iwn_notif_intr(sc); if (r1 & (IWN_INT_FH_RX | IWN_INT_SW_RX)) { IWN_WRITE_1(sc, IWN_INT_PERIODIC, IWN_INT_PERIODIC_ENA); } } else iwn_notif_intr(sc); } if ((r1 & IWN_INT_FH_TX) || (r2 & IWN_FH_INT_TX)) { if (sc->sc_flags & IWN_FLAG_USE_ICT) IWN_WRITE(sc, IWN_FH_INT, IWN_FH_INT_TX); wakeup(sc); /* FH DMA transfer completed. */ } if (r1 & IWN_INT_ALIVE) wakeup(sc); /* Firmware is alive. */ if (r1 & IWN_INT_WAKEUP) iwn_wakeup_intr(sc); done: /* Re-enable interrupts. */ if (sc->sc_flags & IWN_FLAG_RUNNING) IWN_WRITE(sc, IWN_INT_MASK, sc->int_mask); IWN_UNLOCK(sc); } /* * Update TX scheduler ring when transmitting an 802.11 frame (4965AGN and * 5000 adapters use a slightly different format). */ static void iwn4965_update_sched(struct iwn_softc *sc, int qid, int idx, uint8_t id, uint16_t len) { uint16_t *w = &sc->sched[qid * IWN4965_SCHED_COUNT + idx]; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); *w = htole16(len + 8); bus_dmamap_sync(sc->sched_dma.tag, sc->sched_dma.map, BUS_DMASYNC_PREWRITE); if (idx < IWN_SCHED_WINSZ) { *(w + IWN_TX_RING_COUNT) = *w; bus_dmamap_sync(sc->sched_dma.tag, sc->sched_dma.map, BUS_DMASYNC_PREWRITE); } } static void iwn5000_update_sched(struct iwn_softc *sc, int qid, int idx, uint8_t id, uint16_t len) { uint16_t *w = &sc->sched[qid * IWN5000_SCHED_COUNT + idx]; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); *w = htole16(id << 12 | (len + 8)); bus_dmamap_sync(sc->sched_dma.tag, sc->sched_dma.map, BUS_DMASYNC_PREWRITE); if (idx < IWN_SCHED_WINSZ) { *(w + IWN_TX_RING_COUNT) = *w; bus_dmamap_sync(sc->sched_dma.tag, sc->sched_dma.map, BUS_DMASYNC_PREWRITE); } } #ifdef notyet static void iwn5000_reset_sched(struct iwn_softc *sc, int qid, int idx) { uint16_t *w = &sc->sched[qid * IWN5000_SCHED_COUNT + idx]; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); *w = (*w & htole16(0xf000)) | htole16(1); bus_dmamap_sync(sc->sched_dma.tag, sc->sched_dma.map, BUS_DMASYNC_PREWRITE); if (idx < IWN_SCHED_WINSZ) { *(w + IWN_TX_RING_COUNT) = *w; bus_dmamap_sync(sc->sched_dma.tag, sc->sched_dma.map, BUS_DMASYNC_PREWRITE); } } #endif /* * Check whether OFDM 11g protection will be enabled for the given rate. * * The original driver code only enabled protection for OFDM rates. * It didn't check to see whether it was operating in 11a or 11bg mode. */ static int iwn_check_rate_needs_protection(struct iwn_softc *sc, struct ieee80211vap *vap, uint8_t rate) { struct ieee80211com *ic = vap->iv_ic; /* * Not in 2GHz mode? Then there's no need to enable OFDM * 11bg protection. */ if (! IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) { return (0); } /* * 11bg protection not enabled? Then don't use it. */ if ((ic->ic_flags & IEEE80211_F_USEPROT) == 0) return (0); /* * If it's an 11n rate - no protection. * We'll do it via a specific 11n check. */ if (rate & IEEE80211_RATE_MCS) { return (0); } /* * Do a rate table lookup. If the PHY is CCK, * don't do protection. */ if (ieee80211_rate2phytype(ic->ic_rt, rate) == IEEE80211_T_CCK) return (0); /* * Yup, enable protection. */ return (1); } /* * return a value between 0 and IWN_MAX_TX_RETRIES-1 as an index into * the link quality table that reflects this particular entry. */ static int iwn_tx_rate_to_linkq_offset(struct iwn_softc *sc, struct ieee80211_node *ni, uint8_t rate) { struct ieee80211_rateset *rs; int is_11n; int nr; int i; uint8_t cmp_rate; /* * Figure out if we're using 11n or not here. */ if (IEEE80211_IS_CHAN_HT(ni->ni_chan) && ni->ni_htrates.rs_nrates > 0) is_11n = 1; else is_11n = 0; /* * Use the correct rate table. */ if (is_11n) { rs = (struct ieee80211_rateset *) &ni->ni_htrates; nr = ni->ni_htrates.rs_nrates; } else { rs = &ni->ni_rates; nr = rs->rs_nrates; } /* * Find the relevant link quality entry in the table. */ for (i = 0; i < nr && i < IWN_MAX_TX_RETRIES - 1 ; i++) { /* * The link quality table index starts at 0 == highest * rate, so we walk the rate table backwards. */ cmp_rate = rs->rs_rates[(nr - 1) - i]; if (rate & IEEE80211_RATE_MCS) cmp_rate |= IEEE80211_RATE_MCS; #if 0 DPRINTF(sc, IWN_DEBUG_XMIT, "%s: idx %d: nr=%d, rate=0x%02x, rateentry=0x%02x\n", __func__, i, nr, rate, cmp_rate); #endif if (cmp_rate == rate) return (i); } /* Failed? Start at the end */ return (IWN_MAX_TX_RETRIES - 1); } static int iwn_tx_data(struct iwn_softc *sc, struct mbuf *m, struct ieee80211_node *ni) { struct iwn_ops *ops = &sc->ops; const struct ieee80211_txparam *tp; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct iwn_node *wn = (void *)ni; struct iwn_tx_ring *ring; struct iwn_tx_desc *desc; struct iwn_tx_data *data; struct iwn_tx_cmd *cmd; struct iwn_cmd_data *tx; struct ieee80211_frame *wh; struct ieee80211_key *k = NULL; struct mbuf *m1; uint32_t flags; uint16_t qos; u_int hdrlen; bus_dma_segment_t *seg, segs[IWN_MAX_SCATTER]; uint8_t tid, type; int ac, i, totlen, error, pad, nsegs = 0, rate; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); IWN_LOCK_ASSERT(sc); wh = mtod(m, struct ieee80211_frame *); hdrlen = ieee80211_anyhdrsize(wh); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; /* Select EDCA Access Category and TX ring for this frame. */ if (IEEE80211_QOS_HAS_SEQ(wh)) { qos = ((const struct ieee80211_qosframe *)wh)->i_qos[0]; tid = qos & IEEE80211_QOS_TID; } else { qos = 0; tid = 0; } ac = M_WME_GETAC(m); if (m->m_flags & M_AMPDU_MPDU) { uint16_t seqno; struct ieee80211_tx_ampdu *tap = &ni->ni_tx_ampdu[ac]; if (!IEEE80211_AMPDU_RUNNING(tap)) { return EINVAL; } /* * Queue this frame to the hardware ring that we've * negotiated AMPDU TX on. * * Note that the sequence number must match the TX slot * being used! */ ac = *(int *)tap->txa_private; seqno = ni->ni_txseqs[tid]; *(uint16_t *)wh->i_seq = htole16(seqno << IEEE80211_SEQ_SEQ_SHIFT); ring = &sc->txq[ac]; if ((seqno % 256) != ring->cur) { device_printf(sc->sc_dev, "%s: m=%p: seqno (%d) (%d) != ring index (%d) !\n", __func__, m, seqno, seqno % 256, ring->cur); } ni->ni_txseqs[tid]++; } ring = &sc->txq[ac]; desc = &ring->desc[ring->cur]; data = &ring->data[ring->cur]; /* Choose a TX rate index. */ tp = &vap->iv_txparms[ieee80211_chan2mode(ni->ni_chan)]; if (type == IEEE80211_FC0_TYPE_MGT) rate = tp->mgmtrate; else if (IEEE80211_IS_MULTICAST(wh->i_addr1)) rate = tp->mcastrate; else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) rate = tp->ucastrate; else if (m->m_flags & M_EAPOL) rate = tp->mgmtrate; else { /* XXX pass pktlen */ (void) ieee80211_ratectl_rate(ni, NULL, 0); rate = ni->ni_txrate; } /* Encrypt the frame if need be. */ if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { /* Retrieve key for TX. */ k = ieee80211_crypto_encap(ni, m); if (k == NULL) { return ENOBUFS; } /* 802.11 header may have moved. */ wh = mtod(m, struct ieee80211_frame *); } totlen = m->m_pkthdr.len; if (ieee80211_radiotap_active_vap(vap)) { struct iwn_tx_radiotap_header *tap = &sc->sc_txtap; tap->wt_flags = 0; tap->wt_rate = rate; if (k != NULL) tap->wt_flags |= IEEE80211_RADIOTAP_F_WEP; ieee80211_radiotap_tx(vap, m); } /* Prepare TX firmware command. */ cmd = &ring->cmd[ring->cur]; cmd->code = IWN_CMD_TX_DATA; cmd->flags = 0; cmd->qid = ring->qid; cmd->idx = ring->cur; tx = (struct iwn_cmd_data *)cmd->data; /* NB: No need to clear tx, all fields are reinitialized here. */ tx->scratch = 0; /* clear "scratch" area */ flags = 0; if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { /* Unicast frame, check if an ACK is expected. */ if (!qos || (qos & IEEE80211_QOS_ACKPOLICY) != IEEE80211_QOS_ACKPOLICY_NOACK) flags |= IWN_TX_NEED_ACK; } if ((wh->i_fc[0] & (IEEE80211_FC0_TYPE_MASK | IEEE80211_FC0_SUBTYPE_MASK)) == (IEEE80211_FC0_TYPE_CTL | IEEE80211_FC0_SUBTYPE_BAR)) flags |= IWN_TX_IMM_BA; /* Cannot happen yet. */ if (wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG) flags |= IWN_TX_MORE_FRAG; /* Cannot happen yet. */ /* Check if frame must be protected using RTS/CTS or CTS-to-self. */ if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { /* NB: Group frames are sent using CCK in 802.11b/g. */ if (totlen + IEEE80211_CRC_LEN > vap->iv_rtsthreshold) { flags |= IWN_TX_NEED_RTS; } else if (iwn_check_rate_needs_protection(sc, vap, rate)) { if (ic->ic_protmode == IEEE80211_PROT_CTSONLY) flags |= IWN_TX_NEED_CTS; else if (ic->ic_protmode == IEEE80211_PROT_RTSCTS) flags |= IWN_TX_NEED_RTS; } else if ((rate & IEEE80211_RATE_MCS) && (ic->ic_htprotmode == IEEE80211_PROT_RTSCTS)) { flags |= IWN_TX_NEED_RTS; } /* XXX HT protection? */ if (flags & (IWN_TX_NEED_RTS | IWN_TX_NEED_CTS)) { if (sc->hw_type != IWN_HW_REV_TYPE_4965) { /* 5000 autoselects RTS/CTS or CTS-to-self. */ flags &= ~(IWN_TX_NEED_RTS | IWN_TX_NEED_CTS); flags |= IWN_TX_NEED_PROTECTION; } else flags |= IWN_TX_FULL_TXOP; } } if (IEEE80211_IS_MULTICAST(wh->i_addr1) || type != IEEE80211_FC0_TYPE_DATA) tx->id = sc->broadcast_id; else tx->id = wn->id; if (type == IEEE80211_FC0_TYPE_MGT) { uint8_t subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; /* Tell HW to set timestamp in probe responses. */ if (subtype == IEEE80211_FC0_SUBTYPE_PROBE_RESP) flags |= IWN_TX_INSERT_TSTAMP; if (subtype == IEEE80211_FC0_SUBTYPE_ASSOC_REQ || subtype == IEEE80211_FC0_SUBTYPE_REASSOC_REQ) tx->timeout = htole16(3); else tx->timeout = htole16(2); } else tx->timeout = htole16(0); if (hdrlen & 3) { /* First segment length must be a multiple of 4. */ flags |= IWN_TX_NEED_PADDING; pad = 4 - (hdrlen & 3); } else pad = 0; tx->len = htole16(totlen); tx->tid = tid; tx->rts_ntries = 60; tx->data_ntries = 15; tx->lifetime = htole32(IWN_LIFETIME_INFINITE); tx->rate = iwn_rate_to_plcp(sc, ni, rate); if (tx->id == sc->broadcast_id) { /* Group or management frame. */ tx->linkq = 0; } else { tx->linkq = iwn_tx_rate_to_linkq_offset(sc, ni, rate); flags |= IWN_TX_LINKQ; /* enable MRR */ } /* Set physical address of "scratch area". */ tx->loaddr = htole32(IWN_LOADDR(data->scratch_paddr)); tx->hiaddr = IWN_HIADDR(data->scratch_paddr); /* Copy 802.11 header in TX command. */ memcpy((uint8_t *)(tx + 1), wh, hdrlen); /* Trim 802.11 header. */ m_adj(m, hdrlen); tx->security = 0; tx->flags = htole32(flags); error = bus_dmamap_load_mbuf_sg(ring->data_dmat, data->map, m, segs, &nsegs, BUS_DMA_NOWAIT); if (error != 0) { if (error != EFBIG) { device_printf(sc->sc_dev, "%s: can't map mbuf (error %d)\n", __func__, error); return error; } /* Too many DMA segments, linearize mbuf. */ m1 = m_collapse(m, M_NOWAIT, IWN_MAX_SCATTER - 1); if (m1 == NULL) { device_printf(sc->sc_dev, "%s: could not defrag mbuf\n", __func__); return ENOBUFS; } m = m1; error = bus_dmamap_load_mbuf_sg(ring->data_dmat, data->map, m, segs, &nsegs, BUS_DMA_NOWAIT); if (error != 0) { device_printf(sc->sc_dev, "%s: can't map mbuf (error %d)\n", __func__, error); return error; } } data->m = m; data->ni = ni; DPRINTF(sc, IWN_DEBUG_XMIT, "%s: qid %d idx %d len %d nsegs %d flags 0x%08x rate 0x%04x plcp 0x%08x\n", __func__, ring->qid, ring->cur, m->m_pkthdr.len, nsegs, flags, rate, tx->rate); /* Fill TX descriptor. */ desc->nsegs = 1; if (m->m_len != 0) desc->nsegs += nsegs; /* First DMA segment is used by the TX command. */ desc->segs[0].addr = htole32(IWN_LOADDR(data->cmd_paddr)); desc->segs[0].len = htole16(IWN_HIADDR(data->cmd_paddr) | (4 + sizeof (*tx) + hdrlen + pad) << 4); /* Other DMA segments are for data payload. */ seg = &segs[0]; for (i = 1; i <= nsegs; i++) { desc->segs[i].addr = htole32(IWN_LOADDR(seg->ds_addr)); desc->segs[i].len = htole16(IWN_HIADDR(seg->ds_addr) | seg->ds_len << 4); seg++; } bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_PREWRITE); bus_dmamap_sync(ring->data_dmat, ring->cmd_dma.map, BUS_DMASYNC_PREWRITE); bus_dmamap_sync(ring->desc_dma.tag, ring->desc_dma.map, BUS_DMASYNC_PREWRITE); /* Update TX scheduler. */ if (ring->qid >= sc->firstaggqueue) ops->update_sched(sc, ring->qid, ring->cur, tx->id, totlen); /* Kick TX ring. */ ring->cur = (ring->cur + 1) % IWN_TX_RING_COUNT; IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, ring->qid << 8 | ring->cur); /* Mark TX ring as full if we reach a certain threshold. */ if (++ring->queued > IWN_TX_RING_HIMARK) sc->qfullmsk |= 1 << ring->qid; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end\n",__func__); return 0; } static int iwn_tx_data_raw(struct iwn_softc *sc, struct mbuf *m, struct ieee80211_node *ni, const struct ieee80211_bpf_params *params) { struct iwn_ops *ops = &sc->ops; struct ieee80211vap *vap = ni->ni_vap; struct iwn_tx_cmd *cmd; struct iwn_cmd_data *tx; struct ieee80211_frame *wh; struct iwn_tx_ring *ring; struct iwn_tx_desc *desc; struct iwn_tx_data *data; struct mbuf *m1; bus_dma_segment_t *seg, segs[IWN_MAX_SCATTER]; uint32_t flags; u_int hdrlen; int ac, totlen, error, pad, nsegs = 0, i, rate; uint8_t type; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); IWN_LOCK_ASSERT(sc); wh = mtod(m, struct ieee80211_frame *); hdrlen = ieee80211_anyhdrsize(wh); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; ac = params->ibp_pri & 3; ring = &sc->txq[ac]; desc = &ring->desc[ring->cur]; data = &ring->data[ring->cur]; /* Choose a TX rate. */ rate = params->ibp_rate0; totlen = m->m_pkthdr.len; /* Prepare TX firmware command. */ cmd = &ring->cmd[ring->cur]; cmd->code = IWN_CMD_TX_DATA; cmd->flags = 0; cmd->qid = ring->qid; cmd->idx = ring->cur; tx = (struct iwn_cmd_data *)cmd->data; /* NB: No need to clear tx, all fields are reinitialized here. */ tx->scratch = 0; /* clear "scratch" area */ flags = 0; if ((params->ibp_flags & IEEE80211_BPF_NOACK) == 0) flags |= IWN_TX_NEED_ACK; if (params->ibp_flags & IEEE80211_BPF_RTS) { if (sc->hw_type != IWN_HW_REV_TYPE_4965) { /* 5000 autoselects RTS/CTS or CTS-to-self. */ flags &= ~IWN_TX_NEED_RTS; flags |= IWN_TX_NEED_PROTECTION; } else flags |= IWN_TX_NEED_RTS | IWN_TX_FULL_TXOP; } if (params->ibp_flags & IEEE80211_BPF_CTS) { if (sc->hw_type != IWN_HW_REV_TYPE_4965) { /* 5000 autoselects RTS/CTS or CTS-to-self. */ flags &= ~IWN_TX_NEED_CTS; flags |= IWN_TX_NEED_PROTECTION; } else flags |= IWN_TX_NEED_CTS | IWN_TX_FULL_TXOP; } if (type == IEEE80211_FC0_TYPE_MGT) { uint8_t subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; /* Tell HW to set timestamp in probe responses. */ if (subtype == IEEE80211_FC0_SUBTYPE_PROBE_RESP) flags |= IWN_TX_INSERT_TSTAMP; if (subtype == IEEE80211_FC0_SUBTYPE_ASSOC_REQ || subtype == IEEE80211_FC0_SUBTYPE_REASSOC_REQ) tx->timeout = htole16(3); else tx->timeout = htole16(2); } else tx->timeout = htole16(0); if (hdrlen & 3) { /* First segment length must be a multiple of 4. */ flags |= IWN_TX_NEED_PADDING; pad = 4 - (hdrlen & 3); } else pad = 0; if (ieee80211_radiotap_active_vap(vap)) { struct iwn_tx_radiotap_header *tap = &sc->sc_txtap; tap->wt_flags = 0; tap->wt_rate = rate; ieee80211_radiotap_tx(vap, m); } tx->len = htole16(totlen); tx->tid = 0; tx->id = sc->broadcast_id; tx->rts_ntries = params->ibp_try1; tx->data_ntries = params->ibp_try0; tx->lifetime = htole32(IWN_LIFETIME_INFINITE); tx->rate = iwn_rate_to_plcp(sc, ni, rate); /* Group or management frame. */ tx->linkq = 0; /* Set physical address of "scratch area". */ tx->loaddr = htole32(IWN_LOADDR(data->scratch_paddr)); tx->hiaddr = IWN_HIADDR(data->scratch_paddr); /* Copy 802.11 header in TX command. */ memcpy((uint8_t *)(tx + 1), wh, hdrlen); /* Trim 802.11 header. */ m_adj(m, hdrlen); tx->security = 0; tx->flags = htole32(flags); error = bus_dmamap_load_mbuf_sg(ring->data_dmat, data->map, m, segs, &nsegs, BUS_DMA_NOWAIT); if (error != 0) { if (error != EFBIG) { device_printf(sc->sc_dev, "%s: can't map mbuf (error %d)\n", __func__, error); return error; } /* Too many DMA segments, linearize mbuf. */ m1 = m_collapse(m, M_NOWAIT, IWN_MAX_SCATTER - 1); if (m1 == NULL) { device_printf(sc->sc_dev, "%s: could not defrag mbuf\n", __func__); return ENOBUFS; } m = m1; error = bus_dmamap_load_mbuf_sg(ring->data_dmat, data->map, m, segs, &nsegs, BUS_DMA_NOWAIT); if (error != 0) { device_printf(sc->sc_dev, "%s: can't map mbuf (error %d)\n", __func__, error); return error; } } data->m = m; data->ni = ni; DPRINTF(sc, IWN_DEBUG_XMIT, "%s: qid %d idx %d len %d nsegs %d\n", __func__, ring->qid, ring->cur, m->m_pkthdr.len, nsegs); /* Fill TX descriptor. */ desc->nsegs = 1; if (m->m_len != 0) desc->nsegs += nsegs; /* First DMA segment is used by the TX command. */ desc->segs[0].addr = htole32(IWN_LOADDR(data->cmd_paddr)); desc->segs[0].len = htole16(IWN_HIADDR(data->cmd_paddr) | (4 + sizeof (*tx) + hdrlen + pad) << 4); /* Other DMA segments are for data payload. */ seg = &segs[0]; for (i = 1; i <= nsegs; i++) { desc->segs[i].addr = htole32(IWN_LOADDR(seg->ds_addr)); desc->segs[i].len = htole16(IWN_HIADDR(seg->ds_addr) | seg->ds_len << 4); seg++; } bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_PREWRITE); bus_dmamap_sync(ring->data_dmat, ring->cmd_dma.map, BUS_DMASYNC_PREWRITE); bus_dmamap_sync(ring->desc_dma.tag, ring->desc_dma.map, BUS_DMASYNC_PREWRITE); /* Update TX scheduler. */ if (ring->qid >= sc->firstaggqueue) ops->update_sched(sc, ring->qid, ring->cur, tx->id, totlen); /* Kick TX ring. */ ring->cur = (ring->cur + 1) % IWN_TX_RING_COUNT; IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, ring->qid << 8 | ring->cur); /* Mark TX ring as full if we reach a certain threshold. */ if (++ring->queued > IWN_TX_RING_HIMARK) sc->qfullmsk |= 1 << ring->qid; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end\n",__func__); return 0; } static void iwn_xmit_task(void *arg0, int pending) { struct iwn_softc *sc = arg0; struct ieee80211_node *ni; struct mbuf *m; int error; struct ieee80211_bpf_params p; int have_p; DPRINTF(sc, IWN_DEBUG_XMIT, "%s: called\n", __func__); IWN_LOCK(sc); /* * Dequeue frames, attempt to transmit, * then disable beaconwait when we're done. */ while ((m = mbufq_dequeue(&sc->sc_xmit_queue)) != NULL) { have_p = 0; ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; /* Get xmit params if appropriate */ if (ieee80211_get_xmit_params(m, &p) == 0) have_p = 1; DPRINTF(sc, IWN_DEBUG_XMIT, "%s: m=%p, have_p=%d\n", __func__, m, have_p); /* If we have xmit params, use them */ if (have_p) error = iwn_tx_data_raw(sc, m, ni, &p); else error = iwn_tx_data(sc, m, ni); if (error != 0) { if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1); ieee80211_free_node(ni); } } sc->sc_beacon_wait = 0; IWN_UNLOCK(sc); } /* * raw frame xmit - free node/reference if failed. */ static int iwn_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = ni->ni_ic; struct iwn_softc *sc = ic->ic_softc; int error = 0; DPRINTF(sc, IWN_DEBUG_XMIT | IWN_DEBUG_TRACE, "->%s begin\n", __func__); if ((sc->sc_flags & IWN_FLAG_RUNNING) == 0) { m_freem(m); return ENETDOWN; } /* XXX? net80211 doesn't set this on xmit'ed raw frames? */ m->m_pkthdr.rcvif = (void *) ni; IWN_LOCK(sc); /* queue frame if we have to */ if (sc->sc_beacon_wait) { if (iwn_xmit_queue_enqueue(sc, m) != 0) { m_freem(m); IWN_UNLOCK(sc); return (ENOBUFS); } /* Queued, so just return OK */ IWN_UNLOCK(sc); return (0); } if (params == NULL) { /* * Legacy path; interpret frame contents to decide * precisely how to send the frame. */ error = iwn_tx_data(sc, m, ni); } else { /* * Caller supplied explicit parameters to use in * sending the frame. */ error = iwn_tx_data_raw(sc, m, ni, params); } if (error == 0) sc->sc_tx_timer = 5; IWN_UNLOCK(sc); DPRINTF(sc, IWN_DEBUG_TRACE | IWN_DEBUG_XMIT, "->%s: end\n",__func__); return error; } /* * transmit - don't free mbuf if failed; don't free node ref if failed. */ static int iwn_transmit(struct ieee80211com *ic, struct mbuf *m) { struct iwn_softc *sc = ic->ic_softc; struct ieee80211_node *ni; int error; ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; IWN_LOCK(sc); if ((sc->sc_flags & IWN_FLAG_RUNNING) == 0 || sc->sc_beacon_wait) { IWN_UNLOCK(sc); return (ENXIO); } if (sc->qfullmsk) { IWN_UNLOCK(sc); return (ENOBUFS); } error = iwn_tx_data(sc, m, ni); if (!error) sc->sc_tx_timer = 5; IWN_UNLOCK(sc); return (error); } static void iwn_watchdog(void *arg) { struct iwn_softc *sc = arg; struct ieee80211com *ic = &sc->sc_ic; IWN_LOCK_ASSERT(sc); KASSERT(sc->sc_flags & IWN_FLAG_RUNNING, ("not running")); DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); if (sc->sc_tx_timer > 0) { if (--sc->sc_tx_timer == 0) { ic_printf(ic, "device timeout\n"); ieee80211_runtask(ic, &sc->sc_reinit_task); return; } } callout_reset(&sc->watchdog_to, hz, iwn_watchdog, sc); } static int iwn_cdev_open(struct cdev *dev, int flags, int type, struct thread *td) { return (0); } static int iwn_cdev_close(struct cdev *dev, int flags, int type, struct thread *td) { return (0); } static int iwn_cdev_ioctl(struct cdev *dev, unsigned long cmd, caddr_t data, int fflag, struct thread *td) { int rc; struct iwn_softc *sc = dev->si_drv1; struct iwn_ioctl_data *d; rc = priv_check(td, PRIV_DRIVER); if (rc != 0) return (0); switch (cmd) { case SIOCGIWNSTATS: d = (struct iwn_ioctl_data *) data; IWN_LOCK(sc); /* XXX validate permissions/memory/etc? */ rc = copyout(&sc->last_stat, d->dst_addr, sizeof(struct iwn_stats)); IWN_UNLOCK(sc); break; case SIOCZIWNSTATS: IWN_LOCK(sc); memset(&sc->last_stat, 0, sizeof(struct iwn_stats)); IWN_UNLOCK(sc); break; default: rc = EINVAL; break; } return (rc); } static int iwn_ioctl(struct ieee80211com *ic, u_long cmd, void *data) { return (ENOTTY); } static void iwn_parent(struct ieee80211com *ic) { struct iwn_softc *sc = ic->ic_softc; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); int startall = 0, stop = 0; IWN_LOCK(sc); if (ic->ic_nrunning > 0) { if (!(sc->sc_flags & IWN_FLAG_RUNNING)) { iwn_init_locked(sc); if (IWN_READ(sc, IWN_GP_CNTRL) & IWN_GP_CNTRL_RFKILL) startall = 1; else stop = 1; } } else if (sc->sc_flags & IWN_FLAG_RUNNING) iwn_stop_locked(sc); IWN_UNLOCK(sc); if (startall) ieee80211_start_all(ic); else if (vap != NULL && stop) ieee80211_stop(vap); } /* * Send a command to the firmware. */ static int iwn_cmd(struct iwn_softc *sc, int code, const void *buf, int size, int async) { struct iwn_tx_ring *ring; struct iwn_tx_desc *desc; struct iwn_tx_data *data; struct iwn_tx_cmd *cmd; struct mbuf *m; bus_addr_t paddr; int totlen, error; int cmd_queue_num; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); if (async == 0) IWN_LOCK_ASSERT(sc); if (sc->sc_flags & IWN_FLAG_PAN_SUPPORT) cmd_queue_num = IWN_PAN_CMD_QUEUE; else cmd_queue_num = IWN_CMD_QUEUE_NUM; ring = &sc->txq[cmd_queue_num]; desc = &ring->desc[ring->cur]; data = &ring->data[ring->cur]; totlen = 4 + size; if (size > sizeof cmd->data) { /* Command is too large to fit in a descriptor. */ if (totlen > MCLBYTES) return EINVAL; m = m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR, MJUMPAGESIZE); if (m == NULL) return ENOMEM; cmd = mtod(m, struct iwn_tx_cmd *); error = bus_dmamap_load(ring->data_dmat, data->map, cmd, totlen, iwn_dma_map_addr, &paddr, BUS_DMA_NOWAIT); if (error != 0) { m_freem(m); return error; } data->m = m; } else { cmd = &ring->cmd[ring->cur]; paddr = data->cmd_paddr; } cmd->code = code; cmd->flags = 0; cmd->qid = ring->qid; cmd->idx = ring->cur; memcpy(cmd->data, buf, size); desc->nsegs = 1; desc->segs[0].addr = htole32(IWN_LOADDR(paddr)); desc->segs[0].len = htole16(IWN_HIADDR(paddr) | totlen << 4); DPRINTF(sc, IWN_DEBUG_CMD, "%s: %s (0x%x) flags %d qid %d idx %d\n", __func__, iwn_intr_str(cmd->code), cmd->code, cmd->flags, cmd->qid, cmd->idx); if (size > sizeof cmd->data) { bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_PREWRITE); } else { bus_dmamap_sync(ring->data_dmat, ring->cmd_dma.map, BUS_DMASYNC_PREWRITE); } bus_dmamap_sync(ring->desc_dma.tag, ring->desc_dma.map, BUS_DMASYNC_PREWRITE); /* Kick command ring. */ ring->cur = (ring->cur + 1) % IWN_TX_RING_COUNT; IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, ring->qid << 8 | ring->cur); DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end\n",__func__); return async ? 0 : msleep(desc, &sc->sc_mtx, PCATCH, "iwncmd", hz); } static int iwn4965_add_node(struct iwn_softc *sc, struct iwn_node_info *node, int async) { struct iwn4965_node_info hnode; caddr_t src, dst; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); /* * We use the node structure for 5000 Series internally (it is * a superset of the one for 4965AGN). We thus copy the common * fields before sending the command. */ src = (caddr_t)node; dst = (caddr_t)&hnode; memcpy(dst, src, 48); /* Skip TSC, RX MIC and TX MIC fields from ``src''. */ memcpy(dst + 48, src + 72, 20); return iwn_cmd(sc, IWN_CMD_ADD_NODE, &hnode, sizeof hnode, async); } static int iwn5000_add_node(struct iwn_softc *sc, struct iwn_node_info *node, int async) { DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); /* Direct mapping. */ return iwn_cmd(sc, IWN_CMD_ADD_NODE, node, sizeof (*node), async); } static int iwn_set_link_quality(struct iwn_softc *sc, struct ieee80211_node *ni) { struct iwn_node *wn = (void *)ni; struct ieee80211_rateset *rs; struct iwn_cmd_link_quality linkq; int i, rate, txrate; int is_11n; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); memset(&linkq, 0, sizeof linkq); linkq.id = wn->id; linkq.antmsk_1stream = iwn_get_1stream_tx_antmask(sc); linkq.antmsk_2stream = iwn_get_2stream_tx_antmask(sc); linkq.ampdu_max = 32; /* XXX negotiated? */ linkq.ampdu_threshold = 3; linkq.ampdu_limit = htole16(4000); /* 4ms */ DPRINTF(sc, IWN_DEBUG_XMIT, "%s: 1stream antenna=0x%02x, 2stream antenna=0x%02x, ntxstreams=%d\n", __func__, linkq.antmsk_1stream, linkq.antmsk_2stream, sc->ntxchains); /* * Are we using 11n rates? Ensure the channel is * 11n _and_ we have some 11n rates, or don't * try. */ if (IEEE80211_IS_CHAN_HT(ni->ni_chan) && ni->ni_htrates.rs_nrates > 0) { rs = (struct ieee80211_rateset *) &ni->ni_htrates; is_11n = 1; } else { rs = &ni->ni_rates; is_11n = 0; } /* Start at highest available bit-rate. */ /* * XXX this is all very dirty! */ if (is_11n) txrate = ni->ni_htrates.rs_nrates - 1; else txrate = rs->rs_nrates - 1; for (i = 0; i < IWN_MAX_TX_RETRIES; i++) { uint32_t plcp; /* * XXX TODO: ensure the last two slots are the two lowest * rate entries, just for now. */ if (i == 14 || i == 15) txrate = 0; if (is_11n) rate = IEEE80211_RATE_MCS | rs->rs_rates[txrate]; else rate = IEEE80211_RV(rs->rs_rates[txrate]); /* Do rate -> PLCP config mapping */ plcp = iwn_rate_to_plcp(sc, ni, rate); linkq.retry[i] = plcp; DPRINTF(sc, IWN_DEBUG_XMIT, "%s: i=%d, txrate=%d, rate=0x%02x, plcp=0x%08x\n", __func__, i, txrate, rate, le32toh(plcp)); /* * The mimo field is an index into the table which * indicates the first index where it and subsequent entries * will not be using MIMO. * * Since we're filling linkq from 0..15 and we're filling * from the higest MCS rates to the lowest rates, if we * _are_ doing a dual-stream rate, set mimo to idx+1 (ie, * the next entry.) That way if the next entry is a non-MIMO * entry, we're already pointing at it. */ if ((le32toh(plcp) & IWN_RFLAG_MCS) && IEEE80211_RV(le32toh(plcp)) > 7) linkq.mimo = i + 1; /* Next retry at immediate lower bit-rate. */ if (txrate > 0) txrate--; } /* * If we reached the end of the list and indeed we hit * all MIMO rates (eg 5300 doing MCS23-15) then yes, * set mimo to 15. Setting it to 16 panics the firmware. */ if (linkq.mimo > 15) linkq.mimo = 15; DPRINTF(sc, IWN_DEBUG_XMIT, "%s: mimo = %d\n", __func__, linkq.mimo); DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end\n",__func__); return iwn_cmd(sc, IWN_CMD_LINK_QUALITY, &linkq, sizeof linkq, 1); } /* * Broadcast node is used to send group-addressed and management frames. */ static int iwn_add_broadcast_node(struct iwn_softc *sc, int async) { struct iwn_ops *ops = &sc->ops; struct ieee80211com *ic = &sc->sc_ic; struct iwn_node_info node; struct iwn_cmd_link_quality linkq; uint8_t txant; int i, error; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); sc->rxon = &sc->rx_on[IWN_RXON_BSS_CTX]; memset(&node, 0, sizeof node); IEEE80211_ADDR_COPY(node.macaddr, ieee80211broadcastaddr); node.id = sc->broadcast_id; DPRINTF(sc, IWN_DEBUG_RESET, "%s: adding broadcast node\n", __func__); if ((error = ops->add_node(sc, &node, async)) != 0) return error; /* Use the first valid TX antenna. */ txant = IWN_LSB(sc->txchainmask); memset(&linkq, 0, sizeof linkq); linkq.id = sc->broadcast_id; linkq.antmsk_1stream = iwn_get_1stream_tx_antmask(sc); linkq.antmsk_2stream = iwn_get_2stream_tx_antmask(sc); linkq.ampdu_max = 64; linkq.ampdu_threshold = 3; linkq.ampdu_limit = htole16(4000); /* 4ms */ /* Use lowest mandatory bit-rate. */ /* XXX rate table lookup? */ if (IEEE80211_IS_CHAN_5GHZ(ic->ic_curchan)) linkq.retry[0] = htole32(0xd); else linkq.retry[0] = htole32(10 | IWN_RFLAG_CCK); linkq.retry[0] |= htole32(IWN_RFLAG_ANT(txant)); /* Use same bit-rate for all TX retries. */ for (i = 1; i < IWN_MAX_TX_RETRIES; i++) { linkq.retry[i] = linkq.retry[0]; } DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end\n",__func__); return iwn_cmd(sc, IWN_CMD_LINK_QUALITY, &linkq, sizeof linkq, async); } static int iwn_updateedca(struct ieee80211com *ic) { #define IWN_EXP2(x) ((1 << (x)) - 1) /* CWmin = 2^ECWmin - 1 */ struct iwn_softc *sc = ic->ic_softc; struct iwn_edca_params cmd; int aci; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); memset(&cmd, 0, sizeof cmd); cmd.flags = htole32(IWN_EDCA_UPDATE); IEEE80211_LOCK(ic); for (aci = 0; aci < WME_NUM_AC; aci++) { const struct wmeParams *ac = &ic->ic_wme.wme_chanParams.cap_wmeParams[aci]; cmd.ac[aci].aifsn = ac->wmep_aifsn; cmd.ac[aci].cwmin = htole16(IWN_EXP2(ac->wmep_logcwmin)); cmd.ac[aci].cwmax = htole16(IWN_EXP2(ac->wmep_logcwmax)); cmd.ac[aci].txoplimit = htole16(IEEE80211_TXOP_TO_US(ac->wmep_txopLimit)); } IEEE80211_UNLOCK(ic); IWN_LOCK(sc); (void)iwn_cmd(sc, IWN_CMD_EDCA_PARAMS, &cmd, sizeof cmd, 1); IWN_UNLOCK(sc); DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end\n",__func__); return 0; #undef IWN_EXP2 } static void iwn_update_mcast(struct ieee80211com *ic) { /* Ignore */ } static void iwn_set_led(struct iwn_softc *sc, uint8_t which, uint8_t off, uint8_t on) { struct iwn_cmd_led led; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); #if 0 /* XXX don't set LEDs during scan? */ if (sc->sc_is_scanning) return; #endif /* Clear microcode LED ownership. */ IWN_CLRBITS(sc, IWN_LED, IWN_LED_BSM_CTRL); led.which = which; led.unit = htole32(10000); /* on/off in unit of 100ms */ led.off = off; led.on = on; (void)iwn_cmd(sc, IWN_CMD_SET_LED, &led, sizeof led, 1); } /* * Set the critical temperature at which the firmware will stop the radio * and notify us. */ static int iwn_set_critical_temp(struct iwn_softc *sc) { struct iwn_critical_temp crit; int32_t temp; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); IWN_WRITE(sc, IWN_UCODE_GP1_CLR, IWN_UCODE_GP1_CTEMP_STOP_RF); if (sc->hw_type == IWN_HW_REV_TYPE_5150) temp = (IWN_CTOK(110) - sc->temp_off) * -5; else if (sc->hw_type == IWN_HW_REV_TYPE_4965) temp = IWN_CTOK(110); else temp = 110; memset(&crit, 0, sizeof crit); crit.tempR = htole32(temp); DPRINTF(sc, IWN_DEBUG_RESET, "setting critical temp to %d\n", temp); return iwn_cmd(sc, IWN_CMD_SET_CRITICAL_TEMP, &crit, sizeof crit, 0); } static int iwn_set_timing(struct iwn_softc *sc, struct ieee80211_node *ni) { struct iwn_cmd_timing cmd; uint64_t val, mod; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); memset(&cmd, 0, sizeof cmd); memcpy(&cmd.tstamp, ni->ni_tstamp.data, sizeof (uint64_t)); cmd.bintval = htole16(ni->ni_intval); cmd.lintval = htole16(10); /* Compute remaining time until next beacon. */ val = (uint64_t)ni->ni_intval * IEEE80211_DUR_TU; mod = le64toh(cmd.tstamp) % val; cmd.binitval = htole32((uint32_t)(val - mod)); DPRINTF(sc, IWN_DEBUG_RESET, "timing bintval=%u tstamp=%ju, init=%u\n", ni->ni_intval, le64toh(cmd.tstamp), (uint32_t)(val - mod)); return iwn_cmd(sc, IWN_CMD_TIMING, &cmd, sizeof cmd, 1); } static void iwn4965_power_calibration(struct iwn_softc *sc, int temp) { struct ieee80211com *ic = &sc->sc_ic; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); /* Adjust TX power if need be (delta >= 3 degC). */ DPRINTF(sc, IWN_DEBUG_CALIBRATE, "%s: temperature %d->%d\n", __func__, sc->temp, temp); if (abs(temp - sc->temp) >= 3) { /* Record temperature of last calibration. */ sc->temp = temp; (void)iwn4965_set_txpower(sc, ic->ic_bsschan, 1); } } /* * Set TX power for current channel (each rate has its own power settings). * This function takes into account the regulatory information from EEPROM, * the current temperature and the current voltage. */ static int iwn4965_set_txpower(struct iwn_softc *sc, struct ieee80211_channel *ch, int async) { /* Fixed-point arithmetic division using a n-bit fractional part. */ #define fdivround(a, b, n) \ ((((1 << n) * (a)) / (b) + (1 << n) / 2) / (1 << n)) /* Linear interpolation. */ #define interpolate(x, x1, y1, x2, y2, n) \ ((y1) + fdivround(((int)(x) - (x1)) * ((y2) - (y1)), (x2) - (x1), n)) static const int tdiv[IWN_NATTEN_GROUPS] = { 9, 8, 8, 8, 6 }; struct iwn_ucode_info *uc = &sc->ucode_info; struct iwn4965_cmd_txpower cmd; struct iwn4965_eeprom_chan_samples *chans; const uint8_t *rf_gain, *dsp_gain; int32_t vdiff, tdiff; int i, c, grp, maxpwr; uint8_t chan; sc->rxon = &sc->rx_on[IWN_RXON_BSS_CTX]; /* Retrieve current channel from last RXON. */ chan = sc->rxon->chan; DPRINTF(sc, IWN_DEBUG_RESET, "setting TX power for channel %d\n", chan); memset(&cmd, 0, sizeof cmd); cmd.band = IEEE80211_IS_CHAN_5GHZ(ch) ? 0 : 1; cmd.chan = chan; if (IEEE80211_IS_CHAN_5GHZ(ch)) { maxpwr = sc->maxpwr5GHz; rf_gain = iwn4965_rf_gain_5ghz; dsp_gain = iwn4965_dsp_gain_5ghz; } else { maxpwr = sc->maxpwr2GHz; rf_gain = iwn4965_rf_gain_2ghz; dsp_gain = iwn4965_dsp_gain_2ghz; } /* Compute voltage compensation. */ vdiff = ((int32_t)le32toh(uc->volt) - sc->eeprom_voltage) / 7; if (vdiff > 0) vdiff *= 2; if (abs(vdiff) > 2) vdiff = 0; DPRINTF(sc, IWN_DEBUG_CALIBRATE | IWN_DEBUG_TXPOW, "%s: voltage compensation=%d (UCODE=%d, EEPROM=%d)\n", __func__, vdiff, le32toh(uc->volt), sc->eeprom_voltage); /* Get channel attenuation group. */ if (chan <= 20) /* 1-20 */ grp = 4; else if (chan <= 43) /* 34-43 */ grp = 0; else if (chan <= 70) /* 44-70 */ grp = 1; else if (chan <= 124) /* 71-124 */ grp = 2; else /* 125-200 */ grp = 3; DPRINTF(sc, IWN_DEBUG_CALIBRATE | IWN_DEBUG_TXPOW, "%s: chan %d, attenuation group=%d\n", __func__, chan, grp); /* Get channel sub-band. */ for (i = 0; i < IWN_NBANDS; i++) if (sc->bands[i].lo != 0 && sc->bands[i].lo <= chan && chan <= sc->bands[i].hi) break; if (i == IWN_NBANDS) /* Can't happen in real-life. */ return EINVAL; chans = sc->bands[i].chans; DPRINTF(sc, IWN_DEBUG_CALIBRATE | IWN_DEBUG_TXPOW, "%s: chan %d sub-band=%d\n", __func__, chan, i); for (c = 0; c < 2; c++) { uint8_t power, gain, temp; int maxchpwr, pwr, ridx, idx; power = interpolate(chan, chans[0].num, chans[0].samples[c][1].power, chans[1].num, chans[1].samples[c][1].power, 1); gain = interpolate(chan, chans[0].num, chans[0].samples[c][1].gain, chans[1].num, chans[1].samples[c][1].gain, 1); temp = interpolate(chan, chans[0].num, chans[0].samples[c][1].temp, chans[1].num, chans[1].samples[c][1].temp, 1); DPRINTF(sc, IWN_DEBUG_CALIBRATE | IWN_DEBUG_TXPOW, "%s: Tx chain %d: power=%d gain=%d temp=%d\n", __func__, c, power, gain, temp); /* Compute temperature compensation. */ tdiff = ((sc->temp - temp) * 2) / tdiv[grp]; DPRINTF(sc, IWN_DEBUG_CALIBRATE | IWN_DEBUG_TXPOW, "%s: temperature compensation=%d (current=%d, EEPROM=%d)\n", __func__, tdiff, sc->temp, temp); for (ridx = 0; ridx <= IWN_RIDX_MAX; ridx++) { /* Convert dBm to half-dBm. */ maxchpwr = sc->maxpwr[chan] * 2; if ((ridx / 8) & 1) maxchpwr -= 6; /* MIMO 2T: -3dB */ pwr = maxpwr; /* Adjust TX power based on rate. */ if ((ridx % 8) == 5) pwr -= 15; /* OFDM48: -7.5dB */ else if ((ridx % 8) == 6) pwr -= 17; /* OFDM54: -8.5dB */ else if ((ridx % 8) == 7) pwr -= 20; /* OFDM60: -10dB */ else pwr -= 10; /* Others: -5dB */ /* Do not exceed channel max TX power. */ if (pwr > maxchpwr) pwr = maxchpwr; idx = gain - (pwr - power) - tdiff - vdiff; if ((ridx / 8) & 1) /* MIMO */ idx += (int32_t)le32toh(uc->atten[grp][c]); if (cmd.band == 0) idx += 9; /* 5GHz */ if (ridx == IWN_RIDX_MAX) idx += 5; /* CCK */ /* Make sure idx stays in a valid range. */ if (idx < 0) idx = 0; else if (idx > IWN4965_MAX_PWR_INDEX) idx = IWN4965_MAX_PWR_INDEX; DPRINTF(sc, IWN_DEBUG_CALIBRATE | IWN_DEBUG_TXPOW, "%s: Tx chain %d, rate idx %d: power=%d\n", __func__, c, ridx, idx); cmd.power[ridx].rf_gain[c] = rf_gain[idx]; cmd.power[ridx].dsp_gain[c] = dsp_gain[idx]; } } DPRINTF(sc, IWN_DEBUG_CALIBRATE | IWN_DEBUG_TXPOW, "%s: set tx power for chan %d\n", __func__, chan); return iwn_cmd(sc, IWN_CMD_TXPOWER, &cmd, sizeof cmd, async); #undef interpolate #undef fdivround } static int iwn5000_set_txpower(struct iwn_softc *sc, struct ieee80211_channel *ch, int async) { struct iwn5000_cmd_txpower cmd; int cmdid; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); /* * TX power calibration is handled automatically by the firmware * for 5000 Series. */ memset(&cmd, 0, sizeof cmd); cmd.global_limit = 2 * IWN5000_TXPOWER_MAX_DBM; /* 16 dBm */ cmd.flags = IWN5000_TXPOWER_NO_CLOSED; cmd.srv_limit = IWN5000_TXPOWER_AUTO; DPRINTF(sc, IWN_DEBUG_CALIBRATE | IWN_DEBUG_XMIT, "%s: setting TX power; rev=%d\n", __func__, IWN_UCODE_API(sc->ucode_rev)); if (IWN_UCODE_API(sc->ucode_rev) == 1) cmdid = IWN_CMD_TXPOWER_DBM_V1; else cmdid = IWN_CMD_TXPOWER_DBM; return iwn_cmd(sc, cmdid, &cmd, sizeof cmd, async); } /* * Retrieve the maximum RSSI (in dBm) among receivers. */ static int iwn4965_get_rssi(struct iwn_softc *sc, struct iwn_rx_stat *stat) { struct iwn4965_rx_phystat *phy = (void *)stat->phybuf; uint8_t mask, agc; int rssi; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); mask = (le16toh(phy->antenna) >> 4) & IWN_ANT_ABC; agc = (le16toh(phy->agc) >> 7) & 0x7f; rssi = 0; if (mask & IWN_ANT_A) rssi = MAX(rssi, phy->rssi[0]); if (mask & IWN_ANT_B) rssi = MAX(rssi, phy->rssi[2]); if (mask & IWN_ANT_C) rssi = MAX(rssi, phy->rssi[4]); DPRINTF(sc, IWN_DEBUG_RECV, "%s: agc %d mask 0x%x rssi %d %d %d result %d\n", __func__, agc, mask, phy->rssi[0], phy->rssi[2], phy->rssi[4], rssi - agc - IWN_RSSI_TO_DBM); return rssi - agc - IWN_RSSI_TO_DBM; } static int iwn5000_get_rssi(struct iwn_softc *sc, struct iwn_rx_stat *stat) { struct iwn5000_rx_phystat *phy = (void *)stat->phybuf; uint8_t agc; int rssi; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); agc = (le32toh(phy->agc) >> 9) & 0x7f; rssi = MAX(le16toh(phy->rssi[0]) & 0xff, le16toh(phy->rssi[1]) & 0xff); rssi = MAX(le16toh(phy->rssi[2]) & 0xff, rssi); DPRINTF(sc, IWN_DEBUG_RECV, "%s: agc %d rssi %d %d %d result %d\n", __func__, agc, phy->rssi[0], phy->rssi[1], phy->rssi[2], rssi - agc - IWN_RSSI_TO_DBM); return rssi - agc - IWN_RSSI_TO_DBM; } /* * Retrieve the average noise (in dBm) among receivers. */ static int iwn_get_noise(const struct iwn_rx_general_stats *stats) { int i, total, nbant, noise; total = nbant = 0; for (i = 0; i < 3; i++) { if ((noise = le32toh(stats->noise[i]) & 0xff) == 0) continue; total += noise; nbant++; } /* There should be at least one antenna but check anyway. */ return (nbant == 0) ? -127 : (total / nbant) - 107; } /* * Compute temperature (in degC) from last received statistics. */ static int iwn4965_get_temperature(struct iwn_softc *sc) { struct iwn_ucode_info *uc = &sc->ucode_info; int32_t r1, r2, r3, r4, temp; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); r1 = le32toh(uc->temp[0].chan20MHz); r2 = le32toh(uc->temp[1].chan20MHz); r3 = le32toh(uc->temp[2].chan20MHz); r4 = le32toh(sc->rawtemp); if (r1 == r3) /* Prevents division by 0 (should not happen). */ return 0; /* Sign-extend 23-bit R4 value to 32-bit. */ r4 = ((r4 & 0xffffff) ^ 0x800000) - 0x800000; /* Compute temperature in Kelvin. */ temp = (259 * (r4 - r2)) / (r3 - r1); temp = (temp * 97) / 100 + 8; DPRINTF(sc, IWN_DEBUG_ANY, "temperature %dK/%dC\n", temp, IWN_KTOC(temp)); return IWN_KTOC(temp); } static int iwn5000_get_temperature(struct iwn_softc *sc) { int32_t temp; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); /* * Temperature is not used by the driver for 5000 Series because * TX power calibration is handled by firmware. */ temp = le32toh(sc->rawtemp); if (sc->hw_type == IWN_HW_REV_TYPE_5150) { temp = (temp / -5) + sc->temp_off; temp = IWN_KTOC(temp); } return temp; } /* * Initialize sensitivity calibration state machine. */ static int iwn_init_sensitivity(struct iwn_softc *sc) { struct iwn_ops *ops = &sc->ops; struct iwn_calib_state *calib = &sc->calib; uint32_t flags; int error; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); /* Reset calibration state machine. */ memset(calib, 0, sizeof (*calib)); calib->state = IWN_CALIB_STATE_INIT; calib->cck_state = IWN_CCK_STATE_HIFA; /* Set initial correlation values. */ calib->ofdm_x1 = sc->limits->min_ofdm_x1; calib->ofdm_mrc_x1 = sc->limits->min_ofdm_mrc_x1; calib->ofdm_x4 = sc->limits->min_ofdm_x4; calib->ofdm_mrc_x4 = sc->limits->min_ofdm_mrc_x4; calib->cck_x4 = 125; calib->cck_mrc_x4 = sc->limits->min_cck_mrc_x4; calib->energy_cck = sc->limits->energy_cck; /* Write initial sensitivity. */ if ((error = iwn_send_sensitivity(sc)) != 0) return error; /* Write initial gains. */ if ((error = ops->init_gains(sc)) != 0) return error; /* Request statistics at each beacon interval. */ flags = 0; DPRINTF(sc, IWN_DEBUG_CALIBRATE, "%s: sending request for statistics\n", __func__); return iwn_cmd(sc, IWN_CMD_GET_STATISTICS, &flags, sizeof flags, 1); } /* * Collect noise and RSSI statistics for the first 20 beacons received * after association and use them to determine connected antennas and * to set differential gains. */ static void iwn_collect_noise(struct iwn_softc *sc, const struct iwn_rx_general_stats *stats) { struct iwn_ops *ops = &sc->ops; struct iwn_calib_state *calib = &sc->calib; struct ieee80211com *ic = &sc->sc_ic; uint32_t val; int i; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); /* Accumulate RSSI and noise for all 3 antennas. */ for (i = 0; i < 3; i++) { calib->rssi[i] += le32toh(stats->rssi[i]) & 0xff; calib->noise[i] += le32toh(stats->noise[i]) & 0xff; } /* NB: We update differential gains only once after 20 beacons. */ if (++calib->nbeacons < 20) return; /* Determine highest average RSSI. */ val = MAX(calib->rssi[0], calib->rssi[1]); val = MAX(calib->rssi[2], val); /* Determine which antennas are connected. */ sc->chainmask = sc->rxchainmask; for (i = 0; i < 3; i++) if (val - calib->rssi[i] > 15 * 20) sc->chainmask &= ~(1 << i); DPRINTF(sc, IWN_DEBUG_CALIBRATE | IWN_DEBUG_XMIT, "%s: RX chains mask: theoretical=0x%x, actual=0x%x\n", __func__, sc->rxchainmask, sc->chainmask); /* If none of the TX antennas are connected, keep at least one. */ if ((sc->chainmask & sc->txchainmask) == 0) sc->chainmask |= IWN_LSB(sc->txchainmask); (void)ops->set_gains(sc); calib->state = IWN_CALIB_STATE_RUN; #ifdef notyet /* XXX Disable RX chains with no antennas connected. */ sc->rxon->rxchain = htole16(IWN_RXCHAIN_SEL(sc->chainmask)); if (sc->sc_is_scanning) device_printf(sc->sc_dev, "%s: is_scanning set, before RXON\n", __func__); (void)iwn_cmd(sc, IWN_CMD_RXON, sc->rxon, sc->rxonsz, 1); #endif /* Enable power-saving mode if requested by user. */ if (ic->ic_flags & IEEE80211_F_PMGTON) (void)iwn_set_pslevel(sc, 0, 3, 1); DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end\n",__func__); } static int iwn4965_init_gains(struct iwn_softc *sc) { struct iwn_phy_calib_gain cmd; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); memset(&cmd, 0, sizeof cmd); cmd.code = IWN4965_PHY_CALIB_DIFF_GAIN; /* Differential gains initially set to 0 for all 3 antennas. */ DPRINTF(sc, IWN_DEBUG_CALIBRATE, "%s: setting initial differential gains\n", __func__); return iwn_cmd(sc, IWN_CMD_PHY_CALIB, &cmd, sizeof cmd, 1); } static int iwn5000_init_gains(struct iwn_softc *sc) { struct iwn_phy_calib cmd; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); memset(&cmd, 0, sizeof cmd); cmd.code = sc->reset_noise_gain; cmd.ngroups = 1; cmd.isvalid = 1; DPRINTF(sc, IWN_DEBUG_CALIBRATE, "%s: setting initial differential gains\n", __func__); return iwn_cmd(sc, IWN_CMD_PHY_CALIB, &cmd, sizeof cmd, 1); } static int iwn4965_set_gains(struct iwn_softc *sc) { struct iwn_calib_state *calib = &sc->calib; struct iwn_phy_calib_gain cmd; int i, delta, noise; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); /* Get minimal noise among connected antennas. */ noise = INT_MAX; /* NB: There's at least one antenna. */ for (i = 0; i < 3; i++) if (sc->chainmask & (1 << i)) noise = MIN(calib->noise[i], noise); memset(&cmd, 0, sizeof cmd); cmd.code = IWN4965_PHY_CALIB_DIFF_GAIN; /* Set differential gains for connected antennas. */ for (i = 0; i < 3; i++) { if (sc->chainmask & (1 << i)) { /* Compute attenuation (in unit of 1.5dB). */ delta = (noise - (int32_t)calib->noise[i]) / 30; /* NB: delta <= 0 */ /* Limit to [-4.5dB,0]. */ cmd.gain[i] = MIN(abs(delta), 3); if (delta < 0) cmd.gain[i] |= 1 << 2; /* sign bit */ } } DPRINTF(sc, IWN_DEBUG_CALIBRATE, "setting differential gains Ant A/B/C: %x/%x/%x (%x)\n", cmd.gain[0], cmd.gain[1], cmd.gain[2], sc->chainmask); return iwn_cmd(sc, IWN_CMD_PHY_CALIB, &cmd, sizeof cmd, 1); } static int iwn5000_set_gains(struct iwn_softc *sc) { struct iwn_calib_state *calib = &sc->calib; struct iwn_phy_calib_gain cmd; int i, ant, div, delta; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); /* We collected 20 beacons and !=6050 need a 1.5 factor. */ div = (sc->hw_type == IWN_HW_REV_TYPE_6050) ? 20 : 30; memset(&cmd, 0, sizeof cmd); cmd.code = sc->noise_gain; cmd.ngroups = 1; cmd.isvalid = 1; /* Get first available RX antenna as referential. */ ant = IWN_LSB(sc->rxchainmask); /* Set differential gains for other antennas. */ for (i = ant + 1; i < 3; i++) { if (sc->chainmask & (1 << i)) { /* The delta is relative to antenna "ant". */ delta = ((int32_t)calib->noise[ant] - (int32_t)calib->noise[i]) / div; /* Limit to [-4.5dB,+4.5dB]. */ cmd.gain[i - 1] = MIN(abs(delta), 3); if (delta < 0) cmd.gain[i - 1] |= 1 << 2; /* sign bit */ } } DPRINTF(sc, IWN_DEBUG_CALIBRATE | IWN_DEBUG_XMIT, "setting differential gains Ant B/C: %x/%x (%x)\n", cmd.gain[0], cmd.gain[1], sc->chainmask); return iwn_cmd(sc, IWN_CMD_PHY_CALIB, &cmd, sizeof cmd, 1); } /* * Tune RF RX sensitivity based on the number of false alarms detected * during the last beacon period. */ static void iwn_tune_sensitivity(struct iwn_softc *sc, const struct iwn_rx_stats *stats) { #define inc(val, inc, max) \ if ((val) < (max)) { \ if ((val) < (max) - (inc)) \ (val) += (inc); \ else \ (val) = (max); \ needs_update = 1; \ } #define dec(val, dec, min) \ if ((val) > (min)) { \ if ((val) > (min) + (dec)) \ (val) -= (dec); \ else \ (val) = (min); \ needs_update = 1; \ } const struct iwn_sensitivity_limits *limits = sc->limits; struct iwn_calib_state *calib = &sc->calib; uint32_t val, rxena, fa; uint32_t energy[3], energy_min; uint8_t noise[3], noise_ref; int i, needs_update = 0; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); /* Check that we've been enabled long enough. */ if ((rxena = le32toh(stats->general.load)) == 0){ DPRINTF(sc, IWN_DEBUG_TRACE, "->%s end not so long\n", __func__); return; } /* Compute number of false alarms since last call for OFDM. */ fa = le32toh(stats->ofdm.bad_plcp) - calib->bad_plcp_ofdm; fa += le32toh(stats->ofdm.fa) - calib->fa_ofdm; fa *= 200 * IEEE80211_DUR_TU; /* 200TU */ if (fa > 50 * rxena) { /* High false alarm count, decrease sensitivity. */ DPRINTF(sc, IWN_DEBUG_CALIBRATE, "%s: OFDM high false alarm count: %u\n", __func__, fa); inc(calib->ofdm_x1, 1, limits->max_ofdm_x1); inc(calib->ofdm_mrc_x1, 1, limits->max_ofdm_mrc_x1); inc(calib->ofdm_x4, 1, limits->max_ofdm_x4); inc(calib->ofdm_mrc_x4, 1, limits->max_ofdm_mrc_x4); } else if (fa < 5 * rxena) { /* Low false alarm count, increase sensitivity. */ DPRINTF(sc, IWN_DEBUG_CALIBRATE, "%s: OFDM low false alarm count: %u\n", __func__, fa); dec(calib->ofdm_x1, 1, limits->min_ofdm_x1); dec(calib->ofdm_mrc_x1, 1, limits->min_ofdm_mrc_x1); dec(calib->ofdm_x4, 1, limits->min_ofdm_x4); dec(calib->ofdm_mrc_x4, 1, limits->min_ofdm_mrc_x4); } /* Compute maximum noise among 3 receivers. */ for (i = 0; i < 3; i++) noise[i] = (le32toh(stats->general.noise[i]) >> 8) & 0xff; val = MAX(noise[0], noise[1]); val = MAX(noise[2], val); /* Insert it into our samples table. */ calib->noise_samples[calib->cur_noise_sample] = val; calib->cur_noise_sample = (calib->cur_noise_sample + 1) % 20; /* Compute maximum noise among last 20 samples. */ noise_ref = calib->noise_samples[0]; for (i = 1; i < 20; i++) noise_ref = MAX(noise_ref, calib->noise_samples[i]); /* Compute maximum energy among 3 receivers. */ for (i = 0; i < 3; i++) energy[i] = le32toh(stats->general.energy[i]); val = MIN(energy[0], energy[1]); val = MIN(energy[2], val); /* Insert it into our samples table. */ calib->energy_samples[calib->cur_energy_sample] = val; calib->cur_energy_sample = (calib->cur_energy_sample + 1) % 10; /* Compute minimum energy among last 10 samples. */ energy_min = calib->energy_samples[0]; for (i = 1; i < 10; i++) energy_min = MAX(energy_min, calib->energy_samples[i]); energy_min += 6; /* Compute number of false alarms since last call for CCK. */ fa = le32toh(stats->cck.bad_plcp) - calib->bad_plcp_cck; fa += le32toh(stats->cck.fa) - calib->fa_cck; fa *= 200 * IEEE80211_DUR_TU; /* 200TU */ if (fa > 50 * rxena) { /* High false alarm count, decrease sensitivity. */ DPRINTF(sc, IWN_DEBUG_CALIBRATE, "%s: CCK high false alarm count: %u\n", __func__, fa); calib->cck_state = IWN_CCK_STATE_HIFA; calib->low_fa = 0; if (calib->cck_x4 > 160) { calib->noise_ref = noise_ref; if (calib->energy_cck > 2) dec(calib->energy_cck, 2, energy_min); } if (calib->cck_x4 < 160) { calib->cck_x4 = 161; needs_update = 1; } else inc(calib->cck_x4, 3, limits->max_cck_x4); inc(calib->cck_mrc_x4, 3, limits->max_cck_mrc_x4); } else if (fa < 5 * rxena) { /* Low false alarm count, increase sensitivity. */ DPRINTF(sc, IWN_DEBUG_CALIBRATE, "%s: CCK low false alarm count: %u\n", __func__, fa); calib->cck_state = IWN_CCK_STATE_LOFA; calib->low_fa++; if (calib->cck_state != IWN_CCK_STATE_INIT && (((int32_t)calib->noise_ref - (int32_t)noise_ref) > 2 || calib->low_fa > 100)) { inc(calib->energy_cck, 2, limits->min_energy_cck); dec(calib->cck_x4, 3, limits->min_cck_x4); dec(calib->cck_mrc_x4, 3, limits->min_cck_mrc_x4); } } else { /* Not worth to increase or decrease sensitivity. */ DPRINTF(sc, IWN_DEBUG_CALIBRATE, "%s: CCK normal false alarm count: %u\n", __func__, fa); calib->low_fa = 0; calib->noise_ref = noise_ref; if (calib->cck_state == IWN_CCK_STATE_HIFA) { /* Previous interval had many false alarms. */ dec(calib->energy_cck, 8, energy_min); } calib->cck_state = IWN_CCK_STATE_INIT; } if (needs_update) (void)iwn_send_sensitivity(sc); DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end\n",__func__); #undef dec #undef inc } static int iwn_send_sensitivity(struct iwn_softc *sc) { struct iwn_calib_state *calib = &sc->calib; struct iwn_enhanced_sensitivity_cmd cmd; int len; memset(&cmd, 0, sizeof cmd); len = sizeof (struct iwn_sensitivity_cmd); cmd.which = IWN_SENSITIVITY_WORKTBL; /* OFDM modulation. */ cmd.corr_ofdm_x1 = htole16(calib->ofdm_x1); cmd.corr_ofdm_mrc_x1 = htole16(calib->ofdm_mrc_x1); cmd.corr_ofdm_x4 = htole16(calib->ofdm_x4); cmd.corr_ofdm_mrc_x4 = htole16(calib->ofdm_mrc_x4); cmd.energy_ofdm = htole16(sc->limits->energy_ofdm); cmd.energy_ofdm_th = htole16(62); /* CCK modulation. */ cmd.corr_cck_x4 = htole16(calib->cck_x4); cmd.corr_cck_mrc_x4 = htole16(calib->cck_mrc_x4); cmd.energy_cck = htole16(calib->energy_cck); /* Barker modulation: use default values. */ cmd.corr_barker = htole16(190); cmd.corr_barker_mrc = htole16(sc->limits->barker_mrc); DPRINTF(sc, IWN_DEBUG_CALIBRATE, "%s: set sensitivity %d/%d/%d/%d/%d/%d/%d\n", __func__, calib->ofdm_x1, calib->ofdm_mrc_x1, calib->ofdm_x4, calib->ofdm_mrc_x4, calib->cck_x4, calib->cck_mrc_x4, calib->energy_cck); if (!(sc->sc_flags & IWN_FLAG_ENH_SENS)) goto send; /* Enhanced sensitivity settings. */ len = sizeof (struct iwn_enhanced_sensitivity_cmd); cmd.ofdm_det_slope_mrc = htole16(668); cmd.ofdm_det_icept_mrc = htole16(4); cmd.ofdm_det_slope = htole16(486); cmd.ofdm_det_icept = htole16(37); cmd.cck_det_slope_mrc = htole16(853); cmd.cck_det_icept_mrc = htole16(4); cmd.cck_det_slope = htole16(476); cmd.cck_det_icept = htole16(99); send: return iwn_cmd(sc, IWN_CMD_SET_SENSITIVITY, &cmd, len, 1); } /* * Look at the increase of PLCP errors over time; if it exceeds * a programmed threshold then trigger an RF retune. */ static void iwn_check_rx_recovery(struct iwn_softc *sc, struct iwn_stats *rs) { int32_t delta_ofdm, delta_ht, delta_cck; struct iwn_calib_state *calib = &sc->calib; int delta_ticks, cur_ticks; int delta_msec; int thresh; /* * Calculate the difference between the current and * previous statistics. */ delta_cck = le32toh(rs->rx.cck.bad_plcp) - calib->bad_plcp_cck; delta_ofdm = le32toh(rs->rx.ofdm.bad_plcp) - calib->bad_plcp_ofdm; delta_ht = le32toh(rs->rx.ht.bad_plcp) - calib->bad_plcp_ht; /* * Calculate the delta in time between successive statistics * messages. Yes, it can roll over; so we make sure that * this doesn't happen. * * XXX go figure out what to do about rollover * XXX go figure out what to do if ticks rolls over to -ve instead! * XXX go stab signed integer overflow undefined-ness in the face. */ cur_ticks = ticks; delta_ticks = cur_ticks - sc->last_calib_ticks; /* * If any are negative, then the firmware likely reset; so just * bail. We'll pick this up next time. */ if (delta_cck < 0 || delta_ofdm < 0 || delta_ht < 0 || delta_ticks < 0) return; /* * delta_ticks is in ticks; we need to convert it up to milliseconds * so we can do some useful math with it. */ delta_msec = ticks_to_msecs(delta_ticks); /* * Calculate what our threshold is given the current delta_msec. */ thresh = sc->base_params->plcp_err_threshold * delta_msec; DPRINTF(sc, IWN_DEBUG_STATE, "%s: time delta: %d; cck=%d, ofdm=%d, ht=%d, total=%d, thresh=%d\n", __func__, delta_msec, delta_cck, delta_ofdm, delta_ht, (delta_msec + delta_cck + delta_ofdm + delta_ht), thresh); /* * If we need a retune, then schedule a single channel scan * to a channel that isn't the currently active one! * * The math from linux iwlwifi: * * if ((delta * 100 / msecs) > threshold) */ if (thresh > 0 && (delta_cck + delta_ofdm + delta_ht) * 100 > thresh) { DPRINTF(sc, IWN_DEBUG_ANY, "%s: PLCP error threshold raw (%d) comparison (%d) " "over limit (%d); retune!\n", __func__, (delta_cck + delta_ofdm + delta_ht), (delta_cck + delta_ofdm + delta_ht) * 100, thresh); } } /* * Set STA mode power saving level (between 0 and 5). * Level 0 is CAM (Continuously Aware Mode), 5 is for maximum power saving. */ static int iwn_set_pslevel(struct iwn_softc *sc, int dtim, int level, int async) { struct iwn_pmgt_cmd cmd; const struct iwn_pmgt *pmgt; uint32_t max, skip_dtim; uint32_t reg; int i; DPRINTF(sc, IWN_DEBUG_PWRSAVE, "%s: dtim=%d, level=%d, async=%d\n", __func__, dtim, level, async); /* Select which PS parameters to use. */ if (dtim <= 2) pmgt = &iwn_pmgt[0][level]; else if (dtim <= 10) pmgt = &iwn_pmgt[1][level]; else pmgt = &iwn_pmgt[2][level]; memset(&cmd, 0, sizeof cmd); if (level != 0) /* not CAM */ cmd.flags |= htole16(IWN_PS_ALLOW_SLEEP); if (level == 5) cmd.flags |= htole16(IWN_PS_FAST_PD); /* Retrieve PCIe Active State Power Management (ASPM). */ reg = pci_read_config(sc->sc_dev, sc->sc_cap_off + PCIER_LINK_CTL, 4); if (!(reg & PCIEM_LINK_CTL_ASPMC_L0S)) /* L0s Entry disabled. */ cmd.flags |= htole16(IWN_PS_PCI_PMGT); cmd.rxtimeout = htole32(pmgt->rxtimeout * 1024); cmd.txtimeout = htole32(pmgt->txtimeout * 1024); if (dtim == 0) { dtim = 1; skip_dtim = 0; } else skip_dtim = pmgt->skip_dtim; if (skip_dtim != 0) { cmd.flags |= htole16(IWN_PS_SLEEP_OVER_DTIM); max = pmgt->intval[4]; if (max == (uint32_t)-1) max = dtim * (skip_dtim + 1); else if (max > dtim) max = (max / dtim) * dtim; } else max = dtim; for (i = 0; i < 5; i++) cmd.intval[i] = htole32(MIN(max, pmgt->intval[i])); DPRINTF(sc, IWN_DEBUG_RESET, "setting power saving level to %d\n", level); return iwn_cmd(sc, IWN_CMD_SET_POWER_MODE, &cmd, sizeof cmd, async); } static int iwn_send_btcoex(struct iwn_softc *sc) { struct iwn_bluetooth cmd; memset(&cmd, 0, sizeof cmd); cmd.flags = IWN_BT_COEX_CHAN_ANN | IWN_BT_COEX_BT_PRIO; cmd.lead_time = IWN_BT_LEAD_TIME_DEF; cmd.max_kill = IWN_BT_MAX_KILL_DEF; DPRINTF(sc, IWN_DEBUG_RESET, "%s: configuring bluetooth coexistence\n", __func__); return iwn_cmd(sc, IWN_CMD_BT_COEX, &cmd, sizeof(cmd), 0); } static int iwn_send_advanced_btcoex(struct iwn_softc *sc) { static const uint32_t btcoex_3wire[12] = { 0xaaaaaaaa, 0xaaaaaaaa, 0xaeaaaaaa, 0xaaaaaaaa, 0xcc00ff28, 0x0000aaaa, 0xcc00aaaa, 0x0000aaaa, 0xc0004000, 0x00004000, 0xf0005000, 0xf0005000, }; struct iwn6000_btcoex_config btconfig; struct iwn2000_btcoex_config btconfig2k; struct iwn_btcoex_priotable btprio; struct iwn_btcoex_prot btprot; int error, i; uint8_t flags; memset(&btconfig, 0, sizeof btconfig); memset(&btconfig2k, 0, sizeof btconfig2k); flags = IWN_BT_FLAG_COEX6000_MODE_3W << IWN_BT_FLAG_COEX6000_MODE_SHIFT; // Done as is in linux kernel 3.2 if (sc->base_params->bt_sco_disable) flags &= ~IWN_BT_FLAG_SYNC_2_BT_DISABLE; else flags |= IWN_BT_FLAG_SYNC_2_BT_DISABLE; flags |= IWN_BT_FLAG_COEX6000_CHAN_INHIBITION; /* Default flags result is 145 as old value */ /* * Flags value has to be review. Values must change if we * which to disable it */ if (sc->base_params->bt_session_2) { btconfig2k.flags = flags; btconfig2k.max_kill = 5; btconfig2k.bt3_t7_timer = 1; btconfig2k.kill_ack = htole32(0xffff0000); btconfig2k.kill_cts = htole32(0xffff0000); btconfig2k.sample_time = 2; btconfig2k.bt3_t2_timer = 0xc; for (i = 0; i < 12; i++) btconfig2k.lookup_table[i] = htole32(btcoex_3wire[i]); btconfig2k.valid = htole16(0xff); btconfig2k.prio_boost = htole32(0xf0); DPRINTF(sc, IWN_DEBUG_RESET, "%s: configuring advanced bluetooth coexistence" " session 2, flags : 0x%x\n", __func__, flags); error = iwn_cmd(sc, IWN_CMD_BT_COEX, &btconfig2k, sizeof(btconfig2k), 1); } else { btconfig.flags = flags; btconfig.max_kill = 5; btconfig.bt3_t7_timer = 1; btconfig.kill_ack = htole32(0xffff0000); btconfig.kill_cts = htole32(0xffff0000); btconfig.sample_time = 2; btconfig.bt3_t2_timer = 0xc; for (i = 0; i < 12; i++) btconfig.lookup_table[i] = htole32(btcoex_3wire[i]); btconfig.valid = htole16(0xff); btconfig.prio_boost = 0xf0; DPRINTF(sc, IWN_DEBUG_RESET, "%s: configuring advanced bluetooth coexistence," " flags : 0x%x\n", __func__, flags); error = iwn_cmd(sc, IWN_CMD_BT_COEX, &btconfig, sizeof(btconfig), 1); } if (error != 0) return error; memset(&btprio, 0, sizeof btprio); btprio.calib_init1 = 0x6; btprio.calib_init2 = 0x7; btprio.calib_periodic_low1 = 0x2; btprio.calib_periodic_low2 = 0x3; btprio.calib_periodic_high1 = 0x4; btprio.calib_periodic_high2 = 0x5; btprio.dtim = 0x6; btprio.scan52 = 0x8; btprio.scan24 = 0xa; error = iwn_cmd(sc, IWN_CMD_BT_COEX_PRIOTABLE, &btprio, sizeof(btprio), 1); if (error != 0) return error; /* Force BT state machine change. */ memset(&btprot, 0, sizeof btprot); btprot.open = 1; btprot.type = 1; error = iwn_cmd(sc, IWN_CMD_BT_COEX_PROT, &btprot, sizeof(btprot), 1); if (error != 0) return error; btprot.open = 0; return iwn_cmd(sc, IWN_CMD_BT_COEX_PROT, &btprot, sizeof(btprot), 1); } static int iwn5000_runtime_calib(struct iwn_softc *sc) { struct iwn5000_calib_config cmd; memset(&cmd, 0, sizeof cmd); cmd.ucode.once.enable = 0xffffffff; cmd.ucode.once.start = IWN5000_CALIB_DC; DPRINTF(sc, IWN_DEBUG_CALIBRATE, "%s: configuring runtime calibration\n", __func__); return iwn_cmd(sc, IWN5000_CMD_CALIB_CONFIG, &cmd, sizeof(cmd), 0); } static uint32_t iwn_get_rxon_ht_flags(struct iwn_softc *sc, struct ieee80211_channel *c) { struct ieee80211com *ic = &sc->sc_ic; uint32_t htflags = 0; if (! IEEE80211_IS_CHAN_HT(c)) return (0); htflags |= IWN_RXON_HT_PROTMODE(ic->ic_curhtprotmode); if (IEEE80211_IS_CHAN_HT40(c)) { switch (ic->ic_curhtprotmode) { case IEEE80211_HTINFO_OPMODE_HT20PR: htflags |= IWN_RXON_HT_MODEPURE40; break; default: htflags |= IWN_RXON_HT_MODEMIXED; break; } } if (IEEE80211_IS_CHAN_HT40D(c)) htflags |= IWN_RXON_HT_HT40MINUS; return (htflags); } static int iwn_config(struct iwn_softc *sc) { struct iwn_ops *ops = &sc->ops; struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); const uint8_t *macaddr; uint32_t txmask; uint16_t rxchain; int error; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); if ((sc->base_params->calib_need & IWN_FLG_NEED_PHY_CALIB_TEMP_OFFSET) && (sc->base_params->calib_need & IWN_FLG_NEED_PHY_CALIB_TEMP_OFFSETv2)) { device_printf(sc->sc_dev,"%s: temp_offset and temp_offsetv2 are" " exclusive each together. Review NIC config file. Conf" " : 0x%08x Flags : 0x%08x \n", __func__, sc->base_params->calib_need, (IWN_FLG_NEED_PHY_CALIB_TEMP_OFFSET | IWN_FLG_NEED_PHY_CALIB_TEMP_OFFSETv2)); return (EINVAL); } /* Compute temperature calib if needed. Will be send by send calib */ if (sc->base_params->calib_need & IWN_FLG_NEED_PHY_CALIB_TEMP_OFFSET) { error = iwn5000_temp_offset_calib(sc); if (error != 0) { device_printf(sc->sc_dev, "%s: could not set temperature offset\n", __func__); return (error); } } else if (sc->base_params->calib_need & IWN_FLG_NEED_PHY_CALIB_TEMP_OFFSETv2) { error = iwn5000_temp_offset_calibv2(sc); if (error != 0) { device_printf(sc->sc_dev, "%s: could not compute temperature offset v2\n", __func__); return (error); } } if (sc->hw_type == IWN_HW_REV_TYPE_6050) { /* Configure runtime DC calibration. */ error = iwn5000_runtime_calib(sc); if (error != 0) { device_printf(sc->sc_dev, "%s: could not configure runtime calibration\n", __func__); return error; } } /* Configure valid TX chains for >=5000 Series. */ if (sc->hw_type != IWN_HW_REV_TYPE_4965 && IWN_UCODE_API(sc->ucode_rev) > 1) { txmask = htole32(sc->txchainmask); DPRINTF(sc, IWN_DEBUG_RESET | IWN_DEBUG_XMIT, "%s: configuring valid TX chains 0x%x\n", __func__, txmask); error = iwn_cmd(sc, IWN5000_CMD_TX_ANT_CONFIG, &txmask, sizeof txmask, 0); if (error != 0) { device_printf(sc->sc_dev, "%s: could not configure valid TX chains, " "error %d\n", __func__, error); return error; } } /* Configure bluetooth coexistence. */ error = 0; /* Configure bluetooth coexistence if needed. */ if (sc->base_params->bt_mode == IWN_BT_ADVANCED) error = iwn_send_advanced_btcoex(sc); if (sc->base_params->bt_mode == IWN_BT_SIMPLE) error = iwn_send_btcoex(sc); if (error != 0) { device_printf(sc->sc_dev, "%s: could not configure bluetooth coexistence, error %d\n", __func__, error); return error; } /* Set mode, channel, RX filter and enable RX. */ sc->rxon = &sc->rx_on[IWN_RXON_BSS_CTX]; memset(sc->rxon, 0, sizeof (struct iwn_rxon)); macaddr = vap ? vap->iv_myaddr : ic->ic_macaddr; IEEE80211_ADDR_COPY(sc->rxon->myaddr, macaddr); IEEE80211_ADDR_COPY(sc->rxon->wlap, macaddr); sc->rxon->chan = ieee80211_chan2ieee(ic, ic->ic_curchan); sc->rxon->flags = htole32(IWN_RXON_TSF | IWN_RXON_CTS_TO_SELF); if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) sc->rxon->flags |= htole32(IWN_RXON_AUTO | IWN_RXON_24GHZ); switch (ic->ic_opmode) { case IEEE80211_M_STA: sc->rxon->mode = IWN_MODE_STA; sc->rxon->filter = htole32(IWN_FILTER_MULTICAST); break; case IEEE80211_M_MONITOR: sc->rxon->mode = IWN_MODE_MONITOR; sc->rxon->filter = htole32(IWN_FILTER_MULTICAST | IWN_FILTER_CTL | IWN_FILTER_PROMISC); break; default: /* Should not get there. */ break; } sc->rxon->cck_mask = 0x0f; /* not yet negotiated */ sc->rxon->ofdm_mask = 0xff; /* not yet negotiated */ sc->rxon->ht_single_mask = 0xff; sc->rxon->ht_dual_mask = 0xff; sc->rxon->ht_triple_mask = 0xff; /* * In active association mode, ensure that * all the receive chains are enabled. * * Since we're not yet doing SMPS, don't allow the * number of idle RX chains to be less than the active * number. */ rxchain = IWN_RXCHAIN_VALID(sc->rxchainmask) | IWN_RXCHAIN_MIMO_COUNT(sc->nrxchains) | IWN_RXCHAIN_IDLE_COUNT(sc->nrxchains); sc->rxon->rxchain = htole16(rxchain); DPRINTF(sc, IWN_DEBUG_RESET | IWN_DEBUG_XMIT, "%s: rxchainmask=0x%x, nrxchains=%d\n", __func__, sc->rxchainmask, sc->nrxchains); sc->rxon->flags |= htole32(iwn_get_rxon_ht_flags(sc, ic->ic_curchan)); DPRINTF(sc, IWN_DEBUG_RESET, "%s: setting configuration; flags=0x%08x\n", __func__, le32toh(sc->rxon->flags)); if (sc->sc_is_scanning) device_printf(sc->sc_dev, "%s: is_scanning set, before RXON\n", __func__); error = iwn_cmd(sc, IWN_CMD_RXON, sc->rxon, sc->rxonsz, 0); if (error != 0) { device_printf(sc->sc_dev, "%s: RXON command failed\n", __func__); return error; } if ((error = iwn_add_broadcast_node(sc, 0)) != 0) { device_printf(sc->sc_dev, "%s: could not add broadcast node\n", __func__); return error; } /* Configuration has changed, set TX power accordingly. */ if ((error = ops->set_txpower(sc, ic->ic_curchan, 0)) != 0) { device_printf(sc->sc_dev, "%s: could not set TX power\n", __func__); return error; } if ((error = iwn_set_critical_temp(sc)) != 0) { device_printf(sc->sc_dev, "%s: could not set critical temperature\n", __func__); return error; } /* Set power saving level to CAM during initialization. */ if ((error = iwn_set_pslevel(sc, 0, 0, 0)) != 0) { device_printf(sc->sc_dev, "%s: could not set power saving level\n", __func__); return error; } DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end\n",__func__); return 0; } static uint16_t iwn_get_active_dwell_time(struct iwn_softc *sc, struct ieee80211_channel *c, uint8_t n_probes) { /* No channel? Default to 2GHz settings */ if (c == NULL || IEEE80211_IS_CHAN_2GHZ(c)) { return (IWN_ACTIVE_DWELL_TIME_2GHZ + IWN_ACTIVE_DWELL_FACTOR_2GHZ * (n_probes + 1)); } /* 5GHz dwell time */ return (IWN_ACTIVE_DWELL_TIME_5GHZ + IWN_ACTIVE_DWELL_FACTOR_5GHZ * (n_probes + 1)); } /* * Limit the total dwell time to 85% of the beacon interval. * * Returns the dwell time in milliseconds. */ static uint16_t iwn_limit_dwell(struct iwn_softc *sc, uint16_t dwell_time) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = NULL; int bintval = 0; /* bintval is in TU (1.024mS) */ if (! TAILQ_EMPTY(&ic->ic_vaps)) { vap = TAILQ_FIRST(&ic->ic_vaps); bintval = vap->iv_bss->ni_intval; } /* * If it's non-zero, we should calculate the minimum of * it and the DWELL_BASE. * * XXX Yes, the math should take into account that bintval * is 1.024mS, not 1mS.. */ if (bintval > 0) { DPRINTF(sc, IWN_DEBUG_SCAN, "%s: bintval=%d\n", __func__, bintval); return (MIN(IWN_PASSIVE_DWELL_BASE, ((bintval * 85) / 100))); } /* No association context? Default */ return (IWN_PASSIVE_DWELL_BASE); } static uint16_t iwn_get_passive_dwell_time(struct iwn_softc *sc, struct ieee80211_channel *c) { uint16_t passive; if (c == NULL || IEEE80211_IS_CHAN_2GHZ(c)) { passive = IWN_PASSIVE_DWELL_BASE + IWN_PASSIVE_DWELL_TIME_2GHZ; } else { passive = IWN_PASSIVE_DWELL_BASE + IWN_PASSIVE_DWELL_TIME_5GHZ; } /* Clamp to the beacon interval if we're associated */ return (iwn_limit_dwell(sc, passive)); } static int iwn_scan(struct iwn_softc *sc, struct ieee80211vap *vap, struct ieee80211_scan_state *ss, struct ieee80211_channel *c) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_node *ni = vap->iv_bss; struct iwn_scan_hdr *hdr; struct iwn_cmd_data *tx; struct iwn_scan_essid *essid; struct iwn_scan_chan *chan; struct ieee80211_frame *wh; struct ieee80211_rateset *rs; uint8_t *buf, *frm; uint16_t rxchain; uint8_t txant; int buflen, error; int is_active; uint16_t dwell_active, dwell_passive; uint32_t extra, scan_service_time; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); /* * We are absolutely not allowed to send a scan command when another * scan command is pending. */ if (sc->sc_is_scanning) { device_printf(sc->sc_dev, "%s: called whilst scanning!\n", __func__); return (EAGAIN); } /* Assign the scan channel */ c = ic->ic_curchan; sc->rxon = &sc->rx_on[IWN_RXON_BSS_CTX]; buf = malloc(IWN_SCAN_MAXSZ, M_DEVBUF, M_NOWAIT | M_ZERO); if (buf == NULL) { device_printf(sc->sc_dev, "%s: could not allocate buffer for scan command\n", __func__); return ENOMEM; } hdr = (struct iwn_scan_hdr *)buf; /* * Move to the next channel if no frames are received within 10ms * after sending the probe request. */ hdr->quiet_time = htole16(10); /* timeout in milliseconds */ hdr->quiet_threshold = htole16(1); /* min # of packets */ /* * Max needs to be greater than active and passive and quiet! * It's also in microseconds! */ hdr->max_svc = htole32(250 * 1024); /* * Reset scan: interval=100 * Normal scan: interval=becaon interval * suspend_time: 100 (TU) * */ extra = (100 /* suspend_time */ / 100 /* beacon interval */) << 22; //scan_service_time = extra | ((100 /* susp */ % 100 /* int */) * 1024); scan_service_time = (4 << 22) | (100 * 1024); /* Hardcode for now! */ hdr->pause_svc = htole32(scan_service_time); /* Select antennas for scanning. */ rxchain = IWN_RXCHAIN_VALID(sc->rxchainmask) | IWN_RXCHAIN_FORCE_MIMO_SEL(sc->rxchainmask) | IWN_RXCHAIN_DRIVER_FORCE; if (IEEE80211_IS_CHAN_A(c) && sc->hw_type == IWN_HW_REV_TYPE_4965) { /* Ant A must be avoided in 5GHz because of an HW bug. */ rxchain |= IWN_RXCHAIN_FORCE_SEL(IWN_ANT_B); } else /* Use all available RX antennas. */ rxchain |= IWN_RXCHAIN_FORCE_SEL(sc->rxchainmask); hdr->rxchain = htole16(rxchain); hdr->filter = htole32(IWN_FILTER_MULTICAST | IWN_FILTER_BEACON); tx = (struct iwn_cmd_data *)(hdr + 1); tx->flags = htole32(IWN_TX_AUTO_SEQ); tx->id = sc->broadcast_id; tx->lifetime = htole32(IWN_LIFETIME_INFINITE); if (IEEE80211_IS_CHAN_5GHZ(c)) { /* Send probe requests at 6Mbps. */ tx->rate = htole32(0xd); rs = &ic->ic_sup_rates[IEEE80211_MODE_11A]; } else { hdr->flags = htole32(IWN_RXON_24GHZ | IWN_RXON_AUTO); if (sc->hw_type == IWN_HW_REV_TYPE_4965 && sc->rxon->associd && sc->rxon->chan > 14) tx->rate = htole32(0xd); else { /* Send probe requests at 1Mbps. */ tx->rate = htole32(10 | IWN_RFLAG_CCK); } rs = &ic->ic_sup_rates[IEEE80211_MODE_11G]; } /* Use the first valid TX antenna. */ txant = IWN_LSB(sc->txchainmask); tx->rate |= htole32(IWN_RFLAG_ANT(txant)); /* * Only do active scanning if we're announcing a probe request * for a given SSID (or more, if we ever add it to the driver.) */ is_active = 0; /* * If we're scanning for a specific SSID, add it to the command. * * XXX maybe look at adding support for scanning multiple SSIDs? */ essid = (struct iwn_scan_essid *)(tx + 1); if (ss != NULL) { if (ss->ss_ssid[0].len != 0) { essid[0].id = IEEE80211_ELEMID_SSID; essid[0].len = ss->ss_ssid[0].len; memcpy(essid[0].data, ss->ss_ssid[0].ssid, ss->ss_ssid[0].len); } DPRINTF(sc, IWN_DEBUG_SCAN, "%s: ssid_len=%d, ssid=%*s\n", __func__, ss->ss_ssid[0].len, ss->ss_ssid[0].len, ss->ss_ssid[0].ssid); if (ss->ss_nssid > 0) is_active = 1; } /* * Build a probe request frame. Most of the following code is a * copy & paste of what is done in net80211. */ wh = (struct ieee80211_frame *)(essid + 20); wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_PROBE_REQ; wh->i_fc[1] = IEEE80211_FC1_DIR_NODS; IEEE80211_ADDR_COPY(wh->i_addr1, vap->iv_ifp->if_broadcastaddr); IEEE80211_ADDR_COPY(wh->i_addr2, IF_LLADDR(vap->iv_ifp)); IEEE80211_ADDR_COPY(wh->i_addr3, vap->iv_ifp->if_broadcastaddr); *(uint16_t *)&wh->i_dur[0] = 0; /* filled by HW */ *(uint16_t *)&wh->i_seq[0] = 0; /* filled by HW */ frm = (uint8_t *)(wh + 1); frm = ieee80211_add_ssid(frm, NULL, 0); frm = ieee80211_add_rates(frm, rs); if (rs->rs_nrates > IEEE80211_RATE_SIZE) frm = ieee80211_add_xrates(frm, rs); if (ic->ic_htcaps & IEEE80211_HTC_HT) frm = ieee80211_add_htcap(frm, ni); /* Set length of probe request. */ tx->len = htole16(frm - (uint8_t *)wh); /* * If active scanning is requested but a certain channel is * marked passive, we can do active scanning if we detect * transmissions. * * There is an issue with some firmware versions that triggers * a sysassert on a "good CRC threshold" of zero (== disabled), * on a radar channel even though this means that we should NOT * send probes. * * The "good CRC threshold" is the number of frames that we * need to receive during our dwell time on a channel before * sending out probes -- setting this to a huge value will * mean we never reach it, but at the same time work around * the aforementioned issue. Thus use IWL_GOOD_CRC_TH_NEVER * here instead of IWL_GOOD_CRC_TH_DISABLED. * * This was fixed in later versions along with some other * scan changes, and the threshold behaves as a flag in those * versions. */ /* * If we're doing active scanning, set the crc_threshold * to a suitable value. This is different to active veruss * passive scanning depending upon the channel flags; the * firmware will obey that particular check for us. */ if (sc->tlv_feature_flags & IWN_UCODE_TLV_FLAGS_NEWSCAN) hdr->crc_threshold = is_active ? IWN_GOOD_CRC_TH_DEFAULT : IWN_GOOD_CRC_TH_DISABLED; else hdr->crc_threshold = is_active ? IWN_GOOD_CRC_TH_DEFAULT : IWN_GOOD_CRC_TH_NEVER; chan = (struct iwn_scan_chan *)frm; chan->chan = htole16(ieee80211_chan2ieee(ic, c)); chan->flags = 0; if (ss->ss_nssid > 0) chan->flags |= htole32(IWN_CHAN_NPBREQS(1)); chan->dsp_gain = 0x6e; /* * Set the passive/active flag depending upon the channel mode. * XXX TODO: take the is_active flag into account as well? */ if (c->ic_flags & IEEE80211_CHAN_PASSIVE) chan->flags |= htole32(IWN_CHAN_PASSIVE); else chan->flags |= htole32(IWN_CHAN_ACTIVE); /* * Calculate the active/passive dwell times. */ dwell_active = iwn_get_active_dwell_time(sc, c, ss->ss_nssid); dwell_passive = iwn_get_passive_dwell_time(sc, c); /* Make sure they're valid */ if (dwell_passive <= dwell_active) dwell_passive = dwell_active + 1; chan->active = htole16(dwell_active); chan->passive = htole16(dwell_passive); if (IEEE80211_IS_CHAN_5GHZ(c)) chan->rf_gain = 0x3b; else chan->rf_gain = 0x28; DPRINTF(sc, IWN_DEBUG_STATE, "%s: chan %u flags 0x%x rf_gain 0x%x " "dsp_gain 0x%x active %d passive %d scan_svc_time %d crc 0x%x " "isactive=%d numssid=%d\n", __func__, chan->chan, chan->flags, chan->rf_gain, chan->dsp_gain, dwell_active, dwell_passive, scan_service_time, hdr->crc_threshold, is_active, ss->ss_nssid); hdr->nchan++; chan++; buflen = (uint8_t *)chan - buf; hdr->len = htole16(buflen); if (sc->sc_is_scanning) { device_printf(sc->sc_dev, "%s: called with is_scanning set!\n", __func__); } sc->sc_is_scanning = 1; DPRINTF(sc, IWN_DEBUG_STATE, "sending scan command nchan=%d\n", hdr->nchan); error = iwn_cmd(sc, IWN_CMD_SCAN, buf, buflen, 1); free(buf, M_DEVBUF); DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end\n",__func__); return error; } static int iwn_auth(struct iwn_softc *sc, struct ieee80211vap *vap) { struct iwn_ops *ops = &sc->ops; struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_node *ni = vap->iv_bss; int error; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); sc->rxon = &sc->rx_on[IWN_RXON_BSS_CTX]; /* Update adapter configuration. */ IEEE80211_ADDR_COPY(sc->rxon->bssid, ni->ni_bssid); sc->rxon->chan = ieee80211_chan2ieee(ic, ni->ni_chan); sc->rxon->flags = htole32(IWN_RXON_TSF | IWN_RXON_CTS_TO_SELF); if (IEEE80211_IS_CHAN_2GHZ(ni->ni_chan)) sc->rxon->flags |= htole32(IWN_RXON_AUTO | IWN_RXON_24GHZ); if (ic->ic_flags & IEEE80211_F_SHSLOT) sc->rxon->flags |= htole32(IWN_RXON_SHSLOT); if (ic->ic_flags & IEEE80211_F_SHPREAMBLE) sc->rxon->flags |= htole32(IWN_RXON_SHPREAMBLE); if (IEEE80211_IS_CHAN_A(ni->ni_chan)) { sc->rxon->cck_mask = 0; sc->rxon->ofdm_mask = 0x15; } else if (IEEE80211_IS_CHAN_B(ni->ni_chan)) { sc->rxon->cck_mask = 0x03; sc->rxon->ofdm_mask = 0; } else { /* Assume 802.11b/g. */ sc->rxon->cck_mask = 0x03; sc->rxon->ofdm_mask = 0x15; } /* try HT */ sc->rxon->flags |= htole32(iwn_get_rxon_ht_flags(sc, ic->ic_curchan)); DPRINTF(sc, IWN_DEBUG_STATE, "rxon chan %d flags %x cck %x ofdm %x\n", sc->rxon->chan, sc->rxon->flags, sc->rxon->cck_mask, sc->rxon->ofdm_mask); if (sc->sc_is_scanning) device_printf(sc->sc_dev, "%s: is_scanning set, before RXON\n", __func__); error = iwn_cmd(sc, IWN_CMD_RXON, sc->rxon, sc->rxonsz, 1); if (error != 0) { device_printf(sc->sc_dev, "%s: RXON command failed, error %d\n", __func__, error); return error; } /* Configuration has changed, set TX power accordingly. */ if ((error = ops->set_txpower(sc, ni->ni_chan, 1)) != 0) { device_printf(sc->sc_dev, "%s: could not set TX power, error %d\n", __func__, error); return error; } /* * Reconfiguring RXON clears the firmware nodes table so we must * add the broadcast node again. */ if ((error = iwn_add_broadcast_node(sc, 1)) != 0) { device_printf(sc->sc_dev, "%s: could not add broadcast node, error %d\n", __func__, error); return error; } DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end\n",__func__); return 0; } static int iwn_run(struct iwn_softc *sc, struct ieee80211vap *vap) { struct iwn_ops *ops = &sc->ops; struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_node *ni = vap->iv_bss; struct iwn_node_info node; int error; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); sc->rxon = &sc->rx_on[IWN_RXON_BSS_CTX]; if (ic->ic_opmode == IEEE80211_M_MONITOR) { /* Link LED blinks while monitoring. */ iwn_set_led(sc, IWN_LED_LINK, 5, 5); return 0; } if ((error = iwn_set_timing(sc, ni)) != 0) { device_printf(sc->sc_dev, "%s: could not set timing, error %d\n", __func__, error); return error; } /* Update adapter configuration. */ IEEE80211_ADDR_COPY(sc->rxon->bssid, ni->ni_bssid); sc->rxon->associd = htole16(IEEE80211_AID(ni->ni_associd)); sc->rxon->chan = ieee80211_chan2ieee(ic, ni->ni_chan); sc->rxon->flags = htole32(IWN_RXON_TSF | IWN_RXON_CTS_TO_SELF); if (IEEE80211_IS_CHAN_2GHZ(ni->ni_chan)) sc->rxon->flags |= htole32(IWN_RXON_AUTO | IWN_RXON_24GHZ); if (ic->ic_flags & IEEE80211_F_SHSLOT) sc->rxon->flags |= htole32(IWN_RXON_SHSLOT); if (ic->ic_flags & IEEE80211_F_SHPREAMBLE) sc->rxon->flags |= htole32(IWN_RXON_SHPREAMBLE); if (IEEE80211_IS_CHAN_A(ni->ni_chan)) { sc->rxon->cck_mask = 0; sc->rxon->ofdm_mask = 0x15; } else if (IEEE80211_IS_CHAN_B(ni->ni_chan)) { sc->rxon->cck_mask = 0x03; sc->rxon->ofdm_mask = 0; } else { /* Assume 802.11b/g. */ sc->rxon->cck_mask = 0x0f; sc->rxon->ofdm_mask = 0x15; } /* try HT */ sc->rxon->flags |= htole32(iwn_get_rxon_ht_flags(sc, ni->ni_chan)); sc->rxon->filter |= htole32(IWN_FILTER_BSS); DPRINTF(sc, IWN_DEBUG_STATE, "rxon chan %d flags %x, curhtprotmode=%d\n", sc->rxon->chan, le32toh(sc->rxon->flags), ic->ic_curhtprotmode); if (sc->sc_is_scanning) device_printf(sc->sc_dev, "%s: is_scanning set, before RXON\n", __func__); error = iwn_cmd(sc, IWN_CMD_RXON, sc->rxon, sc->rxonsz, 1); if (error != 0) { device_printf(sc->sc_dev, "%s: could not update configuration, error %d\n", __func__, error); return error; } /* Configuration has changed, set TX power accordingly. */ if ((error = ops->set_txpower(sc, ni->ni_chan, 1)) != 0) { device_printf(sc->sc_dev, "%s: could not set TX power, error %d\n", __func__, error); return error; } /* Fake a join to initialize the TX rate. */ ((struct iwn_node *)ni)->id = IWN_ID_BSS; iwn_newassoc(ni, 1); /* Add BSS node. */ memset(&node, 0, sizeof node); IEEE80211_ADDR_COPY(node.macaddr, ni->ni_macaddr); node.id = IWN_ID_BSS; if (IEEE80211_IS_CHAN_HT(ni->ni_chan)) { switch (ni->ni_htcap & IEEE80211_HTCAP_SMPS) { case IEEE80211_HTCAP_SMPS_ENA: node.htflags |= htole32(IWN_SMPS_MIMO_DIS); break; case IEEE80211_HTCAP_SMPS_DYNAMIC: node.htflags |= htole32(IWN_SMPS_MIMO_PROT); break; } node.htflags |= htole32(IWN_AMDPU_SIZE_FACTOR(3) | IWN_AMDPU_DENSITY(5)); /* 4us */ if (IEEE80211_IS_CHAN_HT40(ni->ni_chan)) node.htflags |= htole32(IWN_NODE_HT40); } DPRINTF(sc, IWN_DEBUG_STATE, "%s: adding BSS node\n", __func__); error = ops->add_node(sc, &node, 1); if (error != 0) { device_printf(sc->sc_dev, "%s: could not add BSS node, error %d\n", __func__, error); return error; } DPRINTF(sc, IWN_DEBUG_STATE, "%s: setting link quality for node %d\n", __func__, node.id); if ((error = iwn_set_link_quality(sc, ni)) != 0) { device_printf(sc->sc_dev, "%s: could not setup link quality for node %d, error %d\n", __func__, node.id, error); return error; } if ((error = iwn_init_sensitivity(sc)) != 0) { device_printf(sc->sc_dev, "%s: could not set sensitivity, error %d\n", __func__, error); return error; } /* Start periodic calibration timer. */ sc->calib.state = IWN_CALIB_STATE_ASSOC; sc->calib_cnt = 0; callout_reset(&sc->calib_to, msecs_to_ticks(500), iwn_calib_timeout, sc); /* Link LED always on while associated. */ iwn_set_led(sc, IWN_LED_LINK, 0, 1); DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end\n",__func__); return 0; } /* * This function is called by upper layer when an ADDBA request is received * from another STA and before the ADDBA response is sent. */ static int iwn_ampdu_rx_start(struct ieee80211_node *ni, struct ieee80211_rx_ampdu *rap, int baparamset, int batimeout, int baseqctl) { #define MS(_v, _f) (((_v) & _f) >> _f##_S) struct iwn_softc *sc = ni->ni_ic->ic_softc; struct iwn_ops *ops = &sc->ops; struct iwn_node *wn = (void *)ni; struct iwn_node_info node; uint16_t ssn; uint8_t tid; int error; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); tid = MS(le16toh(baparamset), IEEE80211_BAPS_TID); ssn = MS(le16toh(baseqctl), IEEE80211_BASEQ_START); memset(&node, 0, sizeof node); node.id = wn->id; node.control = IWN_NODE_UPDATE; node.flags = IWN_FLAG_SET_ADDBA; node.addba_tid = tid; node.addba_ssn = htole16(ssn); DPRINTF(sc, IWN_DEBUG_RECV, "ADDBA RA=%d TID=%d SSN=%d\n", wn->id, tid, ssn); error = ops->add_node(sc, &node, 1); if (error != 0) return error; return sc->sc_ampdu_rx_start(ni, rap, baparamset, batimeout, baseqctl); #undef MS } /* * This function is called by upper layer on teardown of an HT-immediate * Block Ack agreement (eg. uppon receipt of a DELBA frame). */ static void iwn_ampdu_rx_stop(struct ieee80211_node *ni, struct ieee80211_rx_ampdu *rap) { struct ieee80211com *ic = ni->ni_ic; struct iwn_softc *sc = ic->ic_softc; struct iwn_ops *ops = &sc->ops; struct iwn_node *wn = (void *)ni; struct iwn_node_info node; uint8_t tid; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); /* XXX: tid as an argument */ for (tid = 0; tid < WME_NUM_TID; tid++) { if (&ni->ni_rx_ampdu[tid] == rap) break; } memset(&node, 0, sizeof node); node.id = wn->id; node.control = IWN_NODE_UPDATE; node.flags = IWN_FLAG_SET_DELBA; node.delba_tid = tid; DPRINTF(sc, IWN_DEBUG_RECV, "DELBA RA=%d TID=%d\n", wn->id, tid); (void)ops->add_node(sc, &node, 1); sc->sc_ampdu_rx_stop(ni, rap); } static int iwn_addba_request(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap, int dialogtoken, int baparamset, int batimeout) { struct iwn_softc *sc = ni->ni_ic->ic_softc; int qid; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); for (qid = sc->firstaggqueue; qid < sc->ntxqs; qid++) { if (sc->qid2tap[qid] == NULL) break; } if (qid == sc->ntxqs) { DPRINTF(sc, IWN_DEBUG_XMIT, "%s: not free aggregation queue\n", __func__); return 0; } tap->txa_private = malloc(sizeof(int), M_DEVBUF, M_NOWAIT); if (tap->txa_private == NULL) { device_printf(sc->sc_dev, "%s: failed to alloc TX aggregation structure\n", __func__); return 0; } sc->qid2tap[qid] = tap; *(int *)tap->txa_private = qid; return sc->sc_addba_request(ni, tap, dialogtoken, baparamset, batimeout); } static int iwn_addba_response(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap, int code, int baparamset, int batimeout) { struct iwn_softc *sc = ni->ni_ic->ic_softc; int qid = *(int *)tap->txa_private; uint8_t tid = tap->txa_tid; int ret; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); if (code == IEEE80211_STATUS_SUCCESS) { ni->ni_txseqs[tid] = tap->txa_start & 0xfff; ret = iwn_ampdu_tx_start(ni->ni_ic, ni, tid); if (ret != 1) return ret; } else { sc->qid2tap[qid] = NULL; free(tap->txa_private, M_DEVBUF); tap->txa_private = NULL; } return sc->sc_addba_response(ni, tap, code, baparamset, batimeout); } /* * This function is called by upper layer when an ADDBA response is received * from another STA. */ static int iwn_ampdu_tx_start(struct ieee80211com *ic, struct ieee80211_node *ni, uint8_t tid) { struct ieee80211_tx_ampdu *tap = &ni->ni_tx_ampdu[tid]; struct iwn_softc *sc = ni->ni_ic->ic_softc; struct iwn_ops *ops = &sc->ops; struct iwn_node *wn = (void *)ni; struct iwn_node_info node; int error, qid; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); /* Enable TX for the specified RA/TID. */ wn->disable_tid &= ~(1 << tid); memset(&node, 0, sizeof node); node.id = wn->id; node.control = IWN_NODE_UPDATE; node.flags = IWN_FLAG_SET_DISABLE_TID; node.disable_tid = htole16(wn->disable_tid); error = ops->add_node(sc, &node, 1); if (error != 0) return 0; if ((error = iwn_nic_lock(sc)) != 0) return 0; qid = *(int *)tap->txa_private; DPRINTF(sc, IWN_DEBUG_XMIT, "%s: ra=%d tid=%d ssn=%d qid=%d\n", __func__, wn->id, tid, tap->txa_start, qid); ops->ampdu_tx_start(sc, ni, qid, tid, tap->txa_start & 0xfff); iwn_nic_unlock(sc); iwn_set_link_quality(sc, ni); return 1; } static void iwn_ampdu_tx_stop(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap) { struct iwn_softc *sc = ni->ni_ic->ic_softc; struct iwn_ops *ops = &sc->ops; uint8_t tid = tap->txa_tid; int qid; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); sc->sc_addba_stop(ni, tap); if (tap->txa_private == NULL) return; qid = *(int *)tap->txa_private; if (sc->txq[qid].queued != 0) return; if (iwn_nic_lock(sc) != 0) return; ops->ampdu_tx_stop(sc, qid, tid, tap->txa_start & 0xfff); iwn_nic_unlock(sc); sc->qid2tap[qid] = NULL; free(tap->txa_private, M_DEVBUF); tap->txa_private = NULL; } static void iwn4965_ampdu_tx_start(struct iwn_softc *sc, struct ieee80211_node *ni, int qid, uint8_t tid, uint16_t ssn) { struct iwn_node *wn = (void *)ni; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); /* Stop TX scheduler while we're changing its configuration. */ iwn_prph_write(sc, IWN4965_SCHED_QUEUE_STATUS(qid), IWN4965_TXQ_STATUS_CHGACT); /* Assign RA/TID translation to the queue. */ iwn_mem_write_2(sc, sc->sched_base + IWN4965_SCHED_TRANS_TBL(qid), wn->id << 4 | tid); /* Enable chain-building mode for the queue. */ iwn_prph_setbits(sc, IWN4965_SCHED_QCHAIN_SEL, 1 << qid); /* Set starting sequence number from the ADDBA request. */ sc->txq[qid].cur = sc->txq[qid].read = (ssn & 0xff); IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, qid << 8 | (ssn & 0xff)); iwn_prph_write(sc, IWN4965_SCHED_QUEUE_RDPTR(qid), ssn); /* Set scheduler window size. */ iwn_mem_write(sc, sc->sched_base + IWN4965_SCHED_QUEUE_OFFSET(qid), IWN_SCHED_WINSZ); /* Set scheduler frame limit. */ iwn_mem_write(sc, sc->sched_base + IWN4965_SCHED_QUEUE_OFFSET(qid) + 4, IWN_SCHED_LIMIT << 16); /* Enable interrupts for the queue. */ iwn_prph_setbits(sc, IWN4965_SCHED_INTR_MASK, 1 << qid); /* Mark the queue as active. */ iwn_prph_write(sc, IWN4965_SCHED_QUEUE_STATUS(qid), IWN4965_TXQ_STATUS_ACTIVE | IWN4965_TXQ_STATUS_AGGR_ENA | iwn_tid2fifo[tid] << 1); } static void iwn4965_ampdu_tx_stop(struct iwn_softc *sc, int qid, uint8_t tid, uint16_t ssn) { DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); /* Stop TX scheduler while we're changing its configuration. */ iwn_prph_write(sc, IWN4965_SCHED_QUEUE_STATUS(qid), IWN4965_TXQ_STATUS_CHGACT); /* Set starting sequence number from the ADDBA request. */ IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, qid << 8 | (ssn & 0xff)); iwn_prph_write(sc, IWN4965_SCHED_QUEUE_RDPTR(qid), ssn); /* Disable interrupts for the queue. */ iwn_prph_clrbits(sc, IWN4965_SCHED_INTR_MASK, 1 << qid); /* Mark the queue as inactive. */ iwn_prph_write(sc, IWN4965_SCHED_QUEUE_STATUS(qid), IWN4965_TXQ_STATUS_INACTIVE | iwn_tid2fifo[tid] << 1); } static void iwn5000_ampdu_tx_start(struct iwn_softc *sc, struct ieee80211_node *ni, int qid, uint8_t tid, uint16_t ssn) { DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); struct iwn_node *wn = (void *)ni; /* Stop TX scheduler while we're changing its configuration. */ iwn_prph_write(sc, IWN5000_SCHED_QUEUE_STATUS(qid), IWN5000_TXQ_STATUS_CHGACT); /* Assign RA/TID translation to the queue. */ iwn_mem_write_2(sc, sc->sched_base + IWN5000_SCHED_TRANS_TBL(qid), wn->id << 4 | tid); /* Enable chain-building mode for the queue. */ iwn_prph_setbits(sc, IWN5000_SCHED_QCHAIN_SEL, 1 << qid); /* Enable aggregation for the queue. */ iwn_prph_setbits(sc, IWN5000_SCHED_AGGR_SEL, 1 << qid); /* Set starting sequence number from the ADDBA request. */ sc->txq[qid].cur = sc->txq[qid].read = (ssn & 0xff); IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, qid << 8 | (ssn & 0xff)); iwn_prph_write(sc, IWN5000_SCHED_QUEUE_RDPTR(qid), ssn); /* Set scheduler window size and frame limit. */ iwn_mem_write(sc, sc->sched_base + IWN5000_SCHED_QUEUE_OFFSET(qid) + 4, IWN_SCHED_LIMIT << 16 | IWN_SCHED_WINSZ); /* Enable interrupts for the queue. */ iwn_prph_setbits(sc, IWN5000_SCHED_INTR_MASK, 1 << qid); /* Mark the queue as active. */ iwn_prph_write(sc, IWN5000_SCHED_QUEUE_STATUS(qid), IWN5000_TXQ_STATUS_ACTIVE | iwn_tid2fifo[tid]); } static void iwn5000_ampdu_tx_stop(struct iwn_softc *sc, int qid, uint8_t tid, uint16_t ssn) { DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); /* Stop TX scheduler while we're changing its configuration. */ iwn_prph_write(sc, IWN5000_SCHED_QUEUE_STATUS(qid), IWN5000_TXQ_STATUS_CHGACT); /* Disable aggregation for the queue. */ iwn_prph_clrbits(sc, IWN5000_SCHED_AGGR_SEL, 1 << qid); /* Set starting sequence number from the ADDBA request. */ IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, qid << 8 | (ssn & 0xff)); iwn_prph_write(sc, IWN5000_SCHED_QUEUE_RDPTR(qid), ssn); /* Disable interrupts for the queue. */ iwn_prph_clrbits(sc, IWN5000_SCHED_INTR_MASK, 1 << qid); /* Mark the queue as inactive. */ iwn_prph_write(sc, IWN5000_SCHED_QUEUE_STATUS(qid), IWN5000_TXQ_STATUS_INACTIVE | iwn_tid2fifo[tid]); } /* * Query calibration tables from the initialization firmware. We do this * only once at first boot. Called from a process context. */ static int iwn5000_query_calibration(struct iwn_softc *sc) { struct iwn5000_calib_config cmd; int error; memset(&cmd, 0, sizeof cmd); cmd.ucode.once.enable = htole32(0xffffffff); cmd.ucode.once.start = htole32(0xffffffff); cmd.ucode.once.send = htole32(0xffffffff); cmd.ucode.flags = htole32(0xffffffff); DPRINTF(sc, IWN_DEBUG_CALIBRATE, "%s: sending calibration query\n", __func__); error = iwn_cmd(sc, IWN5000_CMD_CALIB_CONFIG, &cmd, sizeof cmd, 0); if (error != 0) return error; /* Wait at most two seconds for calibration to complete. */ if (!(sc->sc_flags & IWN_FLAG_CALIB_DONE)) error = msleep(sc, &sc->sc_mtx, PCATCH, "iwncal", 2 * hz); return error; } /* * Send calibration results to the runtime firmware. These results were * obtained on first boot from the initialization firmware. */ static int iwn5000_send_calibration(struct iwn_softc *sc) { int idx, error; for (idx = 0; idx < IWN5000_PHY_CALIB_MAX_RESULT; idx++) { if (!(sc->base_params->calib_need & (1<calibcmd[idx].buf == NULL) { DPRINTF(sc, IWN_DEBUG_CALIBRATE, "Need calib idx : %d but no available data\n", idx); continue; } DPRINTF(sc, IWN_DEBUG_CALIBRATE, "send calibration result idx=%d len=%d\n", idx, sc->calibcmd[idx].len); error = iwn_cmd(sc, IWN_CMD_PHY_CALIB, sc->calibcmd[idx].buf, sc->calibcmd[idx].len, 0); if (error != 0) { device_printf(sc->sc_dev, "%s: could not send calibration result, error %d\n", __func__, error); return error; } } return 0; } static int iwn5000_send_wimax_coex(struct iwn_softc *sc) { struct iwn5000_wimax_coex wimax; #if 0 if (sc->hw_type == IWN_HW_REV_TYPE_6050) { /* Enable WiMAX coexistence for combo adapters. */ wimax.flags = IWN_WIMAX_COEX_ASSOC_WA_UNMASK | IWN_WIMAX_COEX_UNASSOC_WA_UNMASK | IWN_WIMAX_COEX_STA_TABLE_VALID | IWN_WIMAX_COEX_ENABLE; memcpy(wimax.events, iwn6050_wimax_events, sizeof iwn6050_wimax_events); } else #endif { /* Disable WiMAX coexistence. */ wimax.flags = 0; memset(wimax.events, 0, sizeof wimax.events); } DPRINTF(sc, IWN_DEBUG_RESET, "%s: Configuring WiMAX coexistence\n", __func__); return iwn_cmd(sc, IWN5000_CMD_WIMAX_COEX, &wimax, sizeof wimax, 0); } static int iwn5000_crystal_calib(struct iwn_softc *sc) { struct iwn5000_phy_calib_crystal cmd; memset(&cmd, 0, sizeof cmd); cmd.code = IWN5000_PHY_CALIB_CRYSTAL; cmd.ngroups = 1; cmd.isvalid = 1; cmd.cap_pin[0] = le32toh(sc->eeprom_crystal) & 0xff; cmd.cap_pin[1] = (le32toh(sc->eeprom_crystal) >> 16) & 0xff; DPRINTF(sc, IWN_DEBUG_CALIBRATE, "sending crystal calibration %d, %d\n", cmd.cap_pin[0], cmd.cap_pin[1]); return iwn_cmd(sc, IWN_CMD_PHY_CALIB, &cmd, sizeof cmd, 0); } static int iwn5000_temp_offset_calib(struct iwn_softc *sc) { struct iwn5000_phy_calib_temp_offset cmd; memset(&cmd, 0, sizeof cmd); cmd.code = IWN5000_PHY_CALIB_TEMP_OFFSET; cmd.ngroups = 1; cmd.isvalid = 1; if (sc->eeprom_temp != 0) cmd.offset = htole16(sc->eeprom_temp); else cmd.offset = htole16(IWN_DEFAULT_TEMP_OFFSET); DPRINTF(sc, IWN_DEBUG_CALIBRATE, "setting radio sensor offset to %d\n", le16toh(cmd.offset)); return iwn_cmd(sc, IWN_CMD_PHY_CALIB, &cmd, sizeof cmd, 0); } static int iwn5000_temp_offset_calibv2(struct iwn_softc *sc) { struct iwn5000_phy_calib_temp_offsetv2 cmd; memset(&cmd, 0, sizeof cmd); cmd.code = IWN5000_PHY_CALIB_TEMP_OFFSET; cmd.ngroups = 1; cmd.isvalid = 1; if (sc->eeprom_temp != 0) { cmd.offset_low = htole16(sc->eeprom_temp); cmd.offset_high = htole16(sc->eeprom_temp_high); } else { cmd.offset_low = htole16(IWN_DEFAULT_TEMP_OFFSET); cmd.offset_high = htole16(IWN_DEFAULT_TEMP_OFFSET); } cmd.burnt_voltage_ref = htole16(sc->eeprom_voltage); DPRINTF(sc, IWN_DEBUG_CALIBRATE, "setting radio sensor low offset to %d, high offset to %d, voltage to %d\n", le16toh(cmd.offset_low), le16toh(cmd.offset_high), le16toh(cmd.burnt_voltage_ref)); return iwn_cmd(sc, IWN_CMD_PHY_CALIB, &cmd, sizeof cmd, 0); } /* * This function is called after the runtime firmware notifies us of its * readiness (called in a process context). */ static int iwn4965_post_alive(struct iwn_softc *sc) { int error, qid; if ((error = iwn_nic_lock(sc)) != 0) return error; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); /* Clear TX scheduler state in SRAM. */ sc->sched_base = iwn_prph_read(sc, IWN_SCHED_SRAM_ADDR); iwn_mem_set_region_4(sc, sc->sched_base + IWN4965_SCHED_CTX_OFF, 0, IWN4965_SCHED_CTX_LEN / sizeof (uint32_t)); /* Set physical address of TX scheduler rings (1KB aligned). */ iwn_prph_write(sc, IWN4965_SCHED_DRAM_ADDR, sc->sched_dma.paddr >> 10); IWN_SETBITS(sc, IWN_FH_TX_CHICKEN, IWN_FH_TX_CHICKEN_SCHED_RETRY); /* Disable chain mode for all our 16 queues. */ iwn_prph_write(sc, IWN4965_SCHED_QCHAIN_SEL, 0); for (qid = 0; qid < IWN4965_NTXQUEUES; qid++) { iwn_prph_write(sc, IWN4965_SCHED_QUEUE_RDPTR(qid), 0); IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, qid << 8 | 0); /* Set scheduler window size. */ iwn_mem_write(sc, sc->sched_base + IWN4965_SCHED_QUEUE_OFFSET(qid), IWN_SCHED_WINSZ); /* Set scheduler frame limit. */ iwn_mem_write(sc, sc->sched_base + IWN4965_SCHED_QUEUE_OFFSET(qid) + 4, IWN_SCHED_LIMIT << 16); } /* Enable interrupts for all our 16 queues. */ iwn_prph_write(sc, IWN4965_SCHED_INTR_MASK, 0xffff); /* Identify TX FIFO rings (0-7). */ iwn_prph_write(sc, IWN4965_SCHED_TXFACT, 0xff); /* Mark TX rings (4 EDCA + cmd + 2 HCCA) as active. */ for (qid = 0; qid < 7; qid++) { static uint8_t qid2fifo[] = { 3, 2, 1, 0, 4, 5, 6 }; iwn_prph_write(sc, IWN4965_SCHED_QUEUE_STATUS(qid), IWN4965_TXQ_STATUS_ACTIVE | qid2fifo[qid] << 1); } iwn_nic_unlock(sc); return 0; } /* * This function is called after the initialization or runtime firmware * notifies us of its readiness (called in a process context). */ static int iwn5000_post_alive(struct iwn_softc *sc) { int error, qid; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); /* Switch to using ICT interrupt mode. */ iwn5000_ict_reset(sc); if ((error = iwn_nic_lock(sc)) != 0){ DPRINTF(sc, IWN_DEBUG_TRACE, "->%s end in error\n", __func__); return error; } /* Clear TX scheduler state in SRAM. */ sc->sched_base = iwn_prph_read(sc, IWN_SCHED_SRAM_ADDR); iwn_mem_set_region_4(sc, sc->sched_base + IWN5000_SCHED_CTX_OFF, 0, IWN5000_SCHED_CTX_LEN / sizeof (uint32_t)); /* Set physical address of TX scheduler rings (1KB aligned). */ iwn_prph_write(sc, IWN5000_SCHED_DRAM_ADDR, sc->sched_dma.paddr >> 10); IWN_SETBITS(sc, IWN_FH_TX_CHICKEN, IWN_FH_TX_CHICKEN_SCHED_RETRY); /* Enable chain mode for all queues, except command queue. */ if (sc->sc_flags & IWN_FLAG_PAN_SUPPORT) iwn_prph_write(sc, IWN5000_SCHED_QCHAIN_SEL, 0xfffdf); else iwn_prph_write(sc, IWN5000_SCHED_QCHAIN_SEL, 0xfffef); iwn_prph_write(sc, IWN5000_SCHED_AGGR_SEL, 0); for (qid = 0; qid < IWN5000_NTXQUEUES; qid++) { iwn_prph_write(sc, IWN5000_SCHED_QUEUE_RDPTR(qid), 0); IWN_WRITE(sc, IWN_HBUS_TARG_WRPTR, qid << 8 | 0); iwn_mem_write(sc, sc->sched_base + IWN5000_SCHED_QUEUE_OFFSET(qid), 0); /* Set scheduler window size and frame limit. */ iwn_mem_write(sc, sc->sched_base + IWN5000_SCHED_QUEUE_OFFSET(qid) + 4, IWN_SCHED_LIMIT << 16 | IWN_SCHED_WINSZ); } /* Enable interrupts for all our 20 queues. */ iwn_prph_write(sc, IWN5000_SCHED_INTR_MASK, 0xfffff); /* Identify TX FIFO rings (0-7). */ iwn_prph_write(sc, IWN5000_SCHED_TXFACT, 0xff); /* Mark TX rings (4 EDCA + cmd + 2 HCCA) as active. */ if (sc->sc_flags & IWN_FLAG_PAN_SUPPORT) { /* Mark TX rings as active. */ for (qid = 0; qid < 11; qid++) { static uint8_t qid2fifo[] = { 3, 2, 1, 0, 0, 4, 2, 5, 4, 7, 5 }; iwn_prph_write(sc, IWN5000_SCHED_QUEUE_STATUS(qid), IWN5000_TXQ_STATUS_ACTIVE | qid2fifo[qid]); } } else { /* Mark TX rings (4 EDCA + cmd + 2 HCCA) as active. */ for (qid = 0; qid < 7; qid++) { static uint8_t qid2fifo[] = { 3, 2, 1, 0, 7, 5, 6 }; iwn_prph_write(sc, IWN5000_SCHED_QUEUE_STATUS(qid), IWN5000_TXQ_STATUS_ACTIVE | qid2fifo[qid]); } } iwn_nic_unlock(sc); /* Configure WiMAX coexistence for combo adapters. */ error = iwn5000_send_wimax_coex(sc); if (error != 0) { device_printf(sc->sc_dev, "%s: could not configure WiMAX coexistence, error %d\n", __func__, error); return error; } if (sc->hw_type != IWN_HW_REV_TYPE_5150) { /* Perform crystal calibration. */ error = iwn5000_crystal_calib(sc); if (error != 0) { device_printf(sc->sc_dev, "%s: crystal calibration failed, error %d\n", __func__, error); return error; } } if (!(sc->sc_flags & IWN_FLAG_CALIB_DONE)) { /* Query calibration from the initialization firmware. */ if ((error = iwn5000_query_calibration(sc)) != 0) { device_printf(sc->sc_dev, "%s: could not query calibration, error %d\n", __func__, error); return error; } /* * We have the calibration results now, reboot with the * runtime firmware (call ourselves recursively!) */ iwn_hw_stop(sc); error = iwn_hw_init(sc); } else { /* Send calibration results to runtime firmware. */ error = iwn5000_send_calibration(sc); } DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end\n",__func__); return error; } /* * The firmware boot code is small and is intended to be copied directly into * the NIC internal memory (no DMA transfer). */ static int iwn4965_load_bootcode(struct iwn_softc *sc, const uint8_t *ucode, int size) { int error, ntries; size /= sizeof (uint32_t); if ((error = iwn_nic_lock(sc)) != 0) return error; /* Copy microcode image into NIC memory. */ iwn_prph_write_region_4(sc, IWN_BSM_SRAM_BASE, (const uint32_t *)ucode, size); iwn_prph_write(sc, IWN_BSM_WR_MEM_SRC, 0); iwn_prph_write(sc, IWN_BSM_WR_MEM_DST, IWN_FW_TEXT_BASE); iwn_prph_write(sc, IWN_BSM_WR_DWCOUNT, size); /* Start boot load now. */ iwn_prph_write(sc, IWN_BSM_WR_CTRL, IWN_BSM_WR_CTRL_START); /* Wait for transfer to complete. */ for (ntries = 0; ntries < 1000; ntries++) { if (!(iwn_prph_read(sc, IWN_BSM_WR_CTRL) & IWN_BSM_WR_CTRL_START)) break; DELAY(10); } if (ntries == 1000) { device_printf(sc->sc_dev, "%s: could not load boot firmware\n", __func__); iwn_nic_unlock(sc); return ETIMEDOUT; } /* Enable boot after power up. */ iwn_prph_write(sc, IWN_BSM_WR_CTRL, IWN_BSM_WR_CTRL_START_EN); iwn_nic_unlock(sc); return 0; } static int iwn4965_load_firmware(struct iwn_softc *sc) { struct iwn_fw_info *fw = &sc->fw; struct iwn_dma_info *dma = &sc->fw_dma; int error; /* Copy initialization sections into pre-allocated DMA-safe memory. */ memcpy(dma->vaddr, fw->init.data, fw->init.datasz); bus_dmamap_sync(dma->tag, dma->map, BUS_DMASYNC_PREWRITE); memcpy(dma->vaddr + IWN4965_FW_DATA_MAXSZ, fw->init.text, fw->init.textsz); bus_dmamap_sync(dma->tag, dma->map, BUS_DMASYNC_PREWRITE); /* Tell adapter where to find initialization sections. */ if ((error = iwn_nic_lock(sc)) != 0) return error; iwn_prph_write(sc, IWN_BSM_DRAM_DATA_ADDR, dma->paddr >> 4); iwn_prph_write(sc, IWN_BSM_DRAM_DATA_SIZE, fw->init.datasz); iwn_prph_write(sc, IWN_BSM_DRAM_TEXT_ADDR, (dma->paddr + IWN4965_FW_DATA_MAXSZ) >> 4); iwn_prph_write(sc, IWN_BSM_DRAM_TEXT_SIZE, fw->init.textsz); iwn_nic_unlock(sc); /* Load firmware boot code. */ error = iwn4965_load_bootcode(sc, fw->boot.text, fw->boot.textsz); if (error != 0) { device_printf(sc->sc_dev, "%s: could not load boot firmware\n", __func__); return error; } /* Now press "execute". */ IWN_WRITE(sc, IWN_RESET, 0); /* Wait at most one second for first alive notification. */ if ((error = msleep(sc, &sc->sc_mtx, PCATCH, "iwninit", hz)) != 0) { device_printf(sc->sc_dev, "%s: timeout waiting for adapter to initialize, error %d\n", __func__, error); return error; } /* Retrieve current temperature for initial TX power calibration. */ sc->rawtemp = sc->ucode_info.temp[3].chan20MHz; sc->temp = iwn4965_get_temperature(sc); /* Copy runtime sections into pre-allocated DMA-safe memory. */ memcpy(dma->vaddr, fw->main.data, fw->main.datasz); bus_dmamap_sync(dma->tag, dma->map, BUS_DMASYNC_PREWRITE); memcpy(dma->vaddr + IWN4965_FW_DATA_MAXSZ, fw->main.text, fw->main.textsz); bus_dmamap_sync(dma->tag, dma->map, BUS_DMASYNC_PREWRITE); /* Tell adapter where to find runtime sections. */ if ((error = iwn_nic_lock(sc)) != 0) return error; iwn_prph_write(sc, IWN_BSM_DRAM_DATA_ADDR, dma->paddr >> 4); iwn_prph_write(sc, IWN_BSM_DRAM_DATA_SIZE, fw->main.datasz); iwn_prph_write(sc, IWN_BSM_DRAM_TEXT_ADDR, (dma->paddr + IWN4965_FW_DATA_MAXSZ) >> 4); iwn_prph_write(sc, IWN_BSM_DRAM_TEXT_SIZE, IWN_FW_UPDATED | fw->main.textsz); iwn_nic_unlock(sc); return 0; } static int iwn5000_load_firmware_section(struct iwn_softc *sc, uint32_t dst, const uint8_t *section, int size) { struct iwn_dma_info *dma = &sc->fw_dma; int error; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); /* Copy firmware section into pre-allocated DMA-safe memory. */ memcpy(dma->vaddr, section, size); bus_dmamap_sync(dma->tag, dma->map, BUS_DMASYNC_PREWRITE); if ((error = iwn_nic_lock(sc)) != 0) return error; IWN_WRITE(sc, IWN_FH_TX_CONFIG(IWN_SRVC_DMACHNL), IWN_FH_TX_CONFIG_DMA_PAUSE); IWN_WRITE(sc, IWN_FH_SRAM_ADDR(IWN_SRVC_DMACHNL), dst); IWN_WRITE(sc, IWN_FH_TFBD_CTRL0(IWN_SRVC_DMACHNL), IWN_LOADDR(dma->paddr)); IWN_WRITE(sc, IWN_FH_TFBD_CTRL1(IWN_SRVC_DMACHNL), IWN_HIADDR(dma->paddr) << 28 | size); IWN_WRITE(sc, IWN_FH_TXBUF_STATUS(IWN_SRVC_DMACHNL), IWN_FH_TXBUF_STATUS_TBNUM(1) | IWN_FH_TXBUF_STATUS_TBIDX(1) | IWN_FH_TXBUF_STATUS_TFBD_VALID); /* Kick Flow Handler to start DMA transfer. */ IWN_WRITE(sc, IWN_FH_TX_CONFIG(IWN_SRVC_DMACHNL), IWN_FH_TX_CONFIG_DMA_ENA | IWN_FH_TX_CONFIG_CIRQ_HOST_ENDTFD); iwn_nic_unlock(sc); /* Wait at most five seconds for FH DMA transfer to complete. */ return msleep(sc, &sc->sc_mtx, PCATCH, "iwninit", 5 * hz); } static int iwn5000_load_firmware(struct iwn_softc *sc) { struct iwn_fw_part *fw; int error; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); /* Load the initialization firmware on first boot only. */ fw = (sc->sc_flags & IWN_FLAG_CALIB_DONE) ? &sc->fw.main : &sc->fw.init; error = iwn5000_load_firmware_section(sc, IWN_FW_TEXT_BASE, fw->text, fw->textsz); if (error != 0) { device_printf(sc->sc_dev, "%s: could not load firmware %s section, error %d\n", __func__, ".text", error); return error; } error = iwn5000_load_firmware_section(sc, IWN_FW_DATA_BASE, fw->data, fw->datasz); if (error != 0) { device_printf(sc->sc_dev, "%s: could not load firmware %s section, error %d\n", __func__, ".data", error); return error; } /* Now press "execute". */ IWN_WRITE(sc, IWN_RESET, 0); return 0; } /* * Extract text and data sections from a legacy firmware image. */ static int iwn_read_firmware_leg(struct iwn_softc *sc, struct iwn_fw_info *fw) { const uint32_t *ptr; size_t hdrlen = 24; uint32_t rev; ptr = (const uint32_t *)fw->data; rev = le32toh(*ptr++); sc->ucode_rev = rev; /* Check firmware API version. */ if (IWN_FW_API(rev) <= 1) { device_printf(sc->sc_dev, "%s: bad firmware, need API version >=2\n", __func__); return EINVAL; } if (IWN_FW_API(rev) >= 3) { /* Skip build number (version 2 header). */ hdrlen += 4; ptr++; } if (fw->size < hdrlen) { device_printf(sc->sc_dev, "%s: firmware too short: %zu bytes\n", __func__, fw->size); return EINVAL; } fw->main.textsz = le32toh(*ptr++); fw->main.datasz = le32toh(*ptr++); fw->init.textsz = le32toh(*ptr++); fw->init.datasz = le32toh(*ptr++); fw->boot.textsz = le32toh(*ptr++); /* Check that all firmware sections fit. */ if (fw->size < hdrlen + fw->main.textsz + fw->main.datasz + fw->init.textsz + fw->init.datasz + fw->boot.textsz) { device_printf(sc->sc_dev, "%s: firmware too short: %zu bytes\n", __func__, fw->size); return EINVAL; } /* Get pointers to firmware sections. */ fw->main.text = (const uint8_t *)ptr; fw->main.data = fw->main.text + fw->main.textsz; fw->init.text = fw->main.data + fw->main.datasz; fw->init.data = fw->init.text + fw->init.textsz; fw->boot.text = fw->init.data + fw->init.datasz; return 0; } /* * Extract text and data sections from a TLV firmware image. */ static int iwn_read_firmware_tlv(struct iwn_softc *sc, struct iwn_fw_info *fw, uint16_t alt) { const struct iwn_fw_tlv_hdr *hdr; const struct iwn_fw_tlv *tlv; const uint8_t *ptr, *end; uint64_t altmask; uint32_t len, tmp; if (fw->size < sizeof (*hdr)) { device_printf(sc->sc_dev, "%s: firmware too short: %zu bytes\n", __func__, fw->size); return EINVAL; } hdr = (const struct iwn_fw_tlv_hdr *)fw->data; if (hdr->signature != htole32(IWN_FW_SIGNATURE)) { device_printf(sc->sc_dev, "%s: bad firmware signature 0x%08x\n", __func__, le32toh(hdr->signature)); return EINVAL; } DPRINTF(sc, IWN_DEBUG_RESET, "FW: \"%.64s\", build 0x%x\n", hdr->descr, le32toh(hdr->build)); sc->ucode_rev = le32toh(hdr->rev); /* * Select the closest supported alternative that is less than * or equal to the specified one. */ altmask = le64toh(hdr->altmask); while (alt > 0 && !(altmask & (1ULL << alt))) alt--; /* Downgrade. */ DPRINTF(sc, IWN_DEBUG_RESET, "using alternative %d\n", alt); ptr = (const uint8_t *)(hdr + 1); end = (const uint8_t *)(fw->data + fw->size); /* Parse type-length-value fields. */ while (ptr + sizeof (*tlv) <= end) { tlv = (const struct iwn_fw_tlv *)ptr; len = le32toh(tlv->len); ptr += sizeof (*tlv); if (ptr + len > end) { device_printf(sc->sc_dev, "%s: firmware too short: %zu bytes\n", __func__, fw->size); return EINVAL; } /* Skip other alternatives. */ if (tlv->alt != 0 && tlv->alt != htole16(alt)) goto next; switch (le16toh(tlv->type)) { case IWN_FW_TLV_MAIN_TEXT: fw->main.text = ptr; fw->main.textsz = len; break; case IWN_FW_TLV_MAIN_DATA: fw->main.data = ptr; fw->main.datasz = len; break; case IWN_FW_TLV_INIT_TEXT: fw->init.text = ptr; fw->init.textsz = len; break; case IWN_FW_TLV_INIT_DATA: fw->init.data = ptr; fw->init.datasz = len; break; case IWN_FW_TLV_BOOT_TEXT: fw->boot.text = ptr; fw->boot.textsz = len; break; case IWN_FW_TLV_ENH_SENS: if (!len) sc->sc_flags |= IWN_FLAG_ENH_SENS; break; case IWN_FW_TLV_PHY_CALIB: tmp = le32toh(*ptr); if (tmp < 253) { sc->reset_noise_gain = tmp; sc->noise_gain = tmp + 1; } break; case IWN_FW_TLV_PAN: sc->sc_flags |= IWN_FLAG_PAN_SUPPORT; DPRINTF(sc, IWN_DEBUG_RESET, "PAN Support found: %d\n", 1); break; case IWN_FW_TLV_FLAGS: if (len < sizeof(uint32_t)) break; if (len % sizeof(uint32_t)) break; sc->tlv_feature_flags = le32toh(*ptr); DPRINTF(sc, IWN_DEBUG_RESET, "%s: feature: 0x%08x\n", __func__, sc->tlv_feature_flags); break; case IWN_FW_TLV_PBREQ_MAXLEN: case IWN_FW_TLV_RUNT_EVTLOG_PTR: case IWN_FW_TLV_RUNT_EVTLOG_SIZE: case IWN_FW_TLV_RUNT_ERRLOG_PTR: case IWN_FW_TLV_INIT_EVTLOG_PTR: case IWN_FW_TLV_INIT_EVTLOG_SIZE: case IWN_FW_TLV_INIT_ERRLOG_PTR: case IWN_FW_TLV_WOWLAN_INST: case IWN_FW_TLV_WOWLAN_DATA: DPRINTF(sc, IWN_DEBUG_RESET, "TLV type %d recognized but not handled\n", le16toh(tlv->type)); break; default: DPRINTF(sc, IWN_DEBUG_RESET, "TLV type %d not handled\n", le16toh(tlv->type)); break; } next: /* TLV fields are 32-bit aligned. */ ptr += (len + 3) & ~3; } return 0; } static int iwn_read_firmware(struct iwn_softc *sc) { struct iwn_fw_info *fw = &sc->fw; int error; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); IWN_UNLOCK(sc); memset(fw, 0, sizeof (*fw)); /* Read firmware image from filesystem. */ sc->fw_fp = firmware_get(sc->fwname); if (sc->fw_fp == NULL) { device_printf(sc->sc_dev, "%s: could not read firmware %s\n", __func__, sc->fwname); IWN_LOCK(sc); return EINVAL; } IWN_LOCK(sc); fw->size = sc->fw_fp->datasize; fw->data = (const uint8_t *)sc->fw_fp->data; if (fw->size < sizeof (uint32_t)) { device_printf(sc->sc_dev, "%s: firmware too short: %zu bytes\n", __func__, fw->size); firmware_put(sc->fw_fp, FIRMWARE_UNLOAD); sc->fw_fp = NULL; return EINVAL; } /* Retrieve text and data sections. */ if (*(const uint32_t *)fw->data != 0) /* Legacy image. */ error = iwn_read_firmware_leg(sc, fw); else error = iwn_read_firmware_tlv(sc, fw, 1); if (error != 0) { device_printf(sc->sc_dev, "%s: could not read firmware sections, error %d\n", __func__, error); firmware_put(sc->fw_fp, FIRMWARE_UNLOAD); sc->fw_fp = NULL; return error; } device_printf(sc->sc_dev, "%s: ucode rev=0x%08x\n", __func__, sc->ucode_rev); /* Make sure text and data sections fit in hardware memory. */ if (fw->main.textsz > sc->fw_text_maxsz || fw->main.datasz > sc->fw_data_maxsz || fw->init.textsz > sc->fw_text_maxsz || fw->init.datasz > sc->fw_data_maxsz || fw->boot.textsz > IWN_FW_BOOT_TEXT_MAXSZ || (fw->boot.textsz & 3) != 0) { device_printf(sc->sc_dev, "%s: firmware sections too large\n", __func__); firmware_put(sc->fw_fp, FIRMWARE_UNLOAD); sc->fw_fp = NULL; return EINVAL; } /* We can proceed with loading the firmware. */ return 0; } static int iwn_clock_wait(struct iwn_softc *sc) { int ntries; /* Set "initialization complete" bit. */ IWN_SETBITS(sc, IWN_GP_CNTRL, IWN_GP_CNTRL_INIT_DONE); /* Wait for clock stabilization. */ for (ntries = 0; ntries < 2500; ntries++) { if (IWN_READ(sc, IWN_GP_CNTRL) & IWN_GP_CNTRL_MAC_CLOCK_READY) return 0; DELAY(10); } device_printf(sc->sc_dev, "%s: timeout waiting for clock stabilization\n", __func__); return ETIMEDOUT; } static int iwn_apm_init(struct iwn_softc *sc) { uint32_t reg; int error; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); /* Disable L0s exit timer (NMI bug workaround). */ IWN_SETBITS(sc, IWN_GIO_CHICKEN, IWN_GIO_CHICKEN_DIS_L0S_TIMER); /* Don't wait for ICH L0s (ICH bug workaround). */ IWN_SETBITS(sc, IWN_GIO_CHICKEN, IWN_GIO_CHICKEN_L1A_NO_L0S_RX); /* Set FH wait threshold to max (HW bug under stress workaround). */ IWN_SETBITS(sc, IWN_DBG_HPET_MEM, 0xffff0000); /* Enable HAP INTA to move adapter from L1a to L0s. */ IWN_SETBITS(sc, IWN_HW_IF_CONFIG, IWN_HW_IF_CONFIG_HAP_WAKE_L1A); /* Retrieve PCIe Active State Power Management (ASPM). */ reg = pci_read_config(sc->sc_dev, sc->sc_cap_off + PCIER_LINK_CTL, 4); /* Workaround for HW instability in PCIe L0->L0s->L1 transition. */ if (reg & PCIEM_LINK_CTL_ASPMC_L1) /* L1 Entry enabled. */ IWN_SETBITS(sc, IWN_GIO, IWN_GIO_L0S_ENA); else IWN_CLRBITS(sc, IWN_GIO, IWN_GIO_L0S_ENA); if (sc->base_params->pll_cfg_val) IWN_SETBITS(sc, IWN_ANA_PLL, sc->base_params->pll_cfg_val); /* Wait for clock stabilization before accessing prph. */ if ((error = iwn_clock_wait(sc)) != 0) return error; if ((error = iwn_nic_lock(sc)) != 0) return error; if (sc->hw_type == IWN_HW_REV_TYPE_4965) { /* Enable DMA and BSM (Bootstrap State Machine). */ iwn_prph_write(sc, IWN_APMG_CLK_EN, IWN_APMG_CLK_CTRL_DMA_CLK_RQT | IWN_APMG_CLK_CTRL_BSM_CLK_RQT); } else { /* Enable DMA. */ iwn_prph_write(sc, IWN_APMG_CLK_EN, IWN_APMG_CLK_CTRL_DMA_CLK_RQT); } DELAY(20); /* Disable L1-Active. */ iwn_prph_setbits(sc, IWN_APMG_PCI_STT, IWN_APMG_PCI_STT_L1A_DIS); iwn_nic_unlock(sc); return 0; } static void iwn_apm_stop_master(struct iwn_softc *sc) { int ntries; /* Stop busmaster DMA activity. */ IWN_SETBITS(sc, IWN_RESET, IWN_RESET_STOP_MASTER); for (ntries = 0; ntries < 100; ntries++) { if (IWN_READ(sc, IWN_RESET) & IWN_RESET_MASTER_DISABLED) return; DELAY(10); } device_printf(sc->sc_dev, "%s: timeout waiting for master\n", __func__); } static void iwn_apm_stop(struct iwn_softc *sc) { iwn_apm_stop_master(sc); /* Reset the entire device. */ IWN_SETBITS(sc, IWN_RESET, IWN_RESET_SW); DELAY(10); /* Clear "initialization complete" bit. */ IWN_CLRBITS(sc, IWN_GP_CNTRL, IWN_GP_CNTRL_INIT_DONE); } static int iwn4965_nic_config(struct iwn_softc *sc) { DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); if (IWN_RFCFG_TYPE(sc->rfcfg) == 1) { /* * I don't believe this to be correct but this is what the * vendor driver is doing. Probably the bits should not be * shifted in IWN_RFCFG_*. */ IWN_SETBITS(sc, IWN_HW_IF_CONFIG, IWN_RFCFG_TYPE(sc->rfcfg) | IWN_RFCFG_STEP(sc->rfcfg) | IWN_RFCFG_DASH(sc->rfcfg)); } IWN_SETBITS(sc, IWN_HW_IF_CONFIG, IWN_HW_IF_CONFIG_RADIO_SI | IWN_HW_IF_CONFIG_MAC_SI); return 0; } static int iwn5000_nic_config(struct iwn_softc *sc) { uint32_t tmp; int error; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); if (IWN_RFCFG_TYPE(sc->rfcfg) < 3) { IWN_SETBITS(sc, IWN_HW_IF_CONFIG, IWN_RFCFG_TYPE(sc->rfcfg) | IWN_RFCFG_STEP(sc->rfcfg) | IWN_RFCFG_DASH(sc->rfcfg)); } IWN_SETBITS(sc, IWN_HW_IF_CONFIG, IWN_HW_IF_CONFIG_RADIO_SI | IWN_HW_IF_CONFIG_MAC_SI); if ((error = iwn_nic_lock(sc)) != 0) return error; iwn_prph_setbits(sc, IWN_APMG_PS, IWN_APMG_PS_EARLY_PWROFF_DIS); if (sc->hw_type == IWN_HW_REV_TYPE_1000) { /* * Select first Switching Voltage Regulator (1.32V) to * solve a stability issue related to noisy DC2DC line * in the silicon of 1000 Series. */ tmp = iwn_prph_read(sc, IWN_APMG_DIGITAL_SVR); tmp &= ~IWN_APMG_DIGITAL_SVR_VOLTAGE_MASK; tmp |= IWN_APMG_DIGITAL_SVR_VOLTAGE_1_32; iwn_prph_write(sc, IWN_APMG_DIGITAL_SVR, tmp); } iwn_nic_unlock(sc); if (sc->sc_flags & IWN_FLAG_INTERNAL_PA) { /* Use internal power amplifier only. */ IWN_WRITE(sc, IWN_GP_DRIVER, IWN_GP_DRIVER_RADIO_2X2_IPA); } if (sc->base_params->additional_nic_config && sc->calib_ver >= 6) { /* Indicate that ROM calibration version is >=6. */ IWN_SETBITS(sc, IWN_GP_DRIVER, IWN_GP_DRIVER_CALIB_VER6); } if (sc->base_params->additional_gp_drv_bit) IWN_SETBITS(sc, IWN_GP_DRIVER, sc->base_params->additional_gp_drv_bit); return 0; } /* * Take NIC ownership over Intel Active Management Technology (AMT). */ static int iwn_hw_prepare(struct iwn_softc *sc) { int ntries; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); /* Check if hardware is ready. */ IWN_SETBITS(sc, IWN_HW_IF_CONFIG, IWN_HW_IF_CONFIG_NIC_READY); for (ntries = 0; ntries < 5; ntries++) { if (IWN_READ(sc, IWN_HW_IF_CONFIG) & IWN_HW_IF_CONFIG_NIC_READY) return 0; DELAY(10); } /* Hardware not ready, force into ready state. */ IWN_SETBITS(sc, IWN_HW_IF_CONFIG, IWN_HW_IF_CONFIG_PREPARE); for (ntries = 0; ntries < 15000; ntries++) { if (!(IWN_READ(sc, IWN_HW_IF_CONFIG) & IWN_HW_IF_CONFIG_PREPARE_DONE)) break; DELAY(10); } if (ntries == 15000) return ETIMEDOUT; /* Hardware should be ready now. */ IWN_SETBITS(sc, IWN_HW_IF_CONFIG, IWN_HW_IF_CONFIG_NIC_READY); for (ntries = 0; ntries < 5; ntries++) { if (IWN_READ(sc, IWN_HW_IF_CONFIG) & IWN_HW_IF_CONFIG_NIC_READY) return 0; DELAY(10); } return ETIMEDOUT; } static int iwn_hw_init(struct iwn_softc *sc) { struct iwn_ops *ops = &sc->ops; int error, chnl, qid; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); /* Clear pending interrupts. */ IWN_WRITE(sc, IWN_INT, 0xffffffff); if ((error = iwn_apm_init(sc)) != 0) { device_printf(sc->sc_dev, "%s: could not power ON adapter, error %d\n", __func__, error); return error; } /* Select VMAIN power source. */ if ((error = iwn_nic_lock(sc)) != 0) return error; iwn_prph_clrbits(sc, IWN_APMG_PS, IWN_APMG_PS_PWR_SRC_MASK); iwn_nic_unlock(sc); /* Perform adapter-specific initialization. */ if ((error = ops->nic_config(sc)) != 0) return error; /* Initialize RX ring. */ if ((error = iwn_nic_lock(sc)) != 0) return error; IWN_WRITE(sc, IWN_FH_RX_CONFIG, 0); IWN_WRITE(sc, IWN_FH_RX_WPTR, 0); /* Set physical address of RX ring (256-byte aligned). */ IWN_WRITE(sc, IWN_FH_RX_BASE, sc->rxq.desc_dma.paddr >> 8); /* Set physical address of RX status (16-byte aligned). */ IWN_WRITE(sc, IWN_FH_STATUS_WPTR, sc->rxq.stat_dma.paddr >> 4); /* Enable RX. */ IWN_WRITE(sc, IWN_FH_RX_CONFIG, IWN_FH_RX_CONFIG_ENA | IWN_FH_RX_CONFIG_IGN_RXF_EMPTY | /* HW bug workaround */ IWN_FH_RX_CONFIG_IRQ_DST_HOST | IWN_FH_RX_CONFIG_SINGLE_FRAME | IWN_FH_RX_CONFIG_RB_TIMEOUT(0) | IWN_FH_RX_CONFIG_NRBD(IWN_RX_RING_COUNT_LOG)); iwn_nic_unlock(sc); IWN_WRITE(sc, IWN_FH_RX_WPTR, (IWN_RX_RING_COUNT - 1) & ~7); if ((error = iwn_nic_lock(sc)) != 0) return error; /* Initialize TX scheduler. */ iwn_prph_write(sc, sc->sched_txfact_addr, 0); /* Set physical address of "keep warm" page (16-byte aligned). */ IWN_WRITE(sc, IWN_FH_KW_ADDR, sc->kw_dma.paddr >> 4); /* Initialize TX rings. */ for (qid = 0; qid < sc->ntxqs; qid++) { struct iwn_tx_ring *txq = &sc->txq[qid]; /* Set physical address of TX ring (256-byte aligned). */ IWN_WRITE(sc, IWN_FH_CBBC_QUEUE(qid), txq->desc_dma.paddr >> 8); } iwn_nic_unlock(sc); /* Enable DMA channels. */ for (chnl = 0; chnl < sc->ndmachnls; chnl++) { IWN_WRITE(sc, IWN_FH_TX_CONFIG(chnl), IWN_FH_TX_CONFIG_DMA_ENA | IWN_FH_TX_CONFIG_DMA_CREDIT_ENA); } /* Clear "radio off" and "commands blocked" bits. */ IWN_WRITE(sc, IWN_UCODE_GP1_CLR, IWN_UCODE_GP1_RFKILL); IWN_WRITE(sc, IWN_UCODE_GP1_CLR, IWN_UCODE_GP1_CMD_BLOCKED); /* Clear pending interrupts. */ IWN_WRITE(sc, IWN_INT, 0xffffffff); /* Enable interrupt coalescing. */ IWN_WRITE(sc, IWN_INT_COALESCING, 512 / 8); /* Enable interrupts. */ IWN_WRITE(sc, IWN_INT_MASK, sc->int_mask); /* _Really_ make sure "radio off" bit is cleared! */ IWN_WRITE(sc, IWN_UCODE_GP1_CLR, IWN_UCODE_GP1_RFKILL); IWN_WRITE(sc, IWN_UCODE_GP1_CLR, IWN_UCODE_GP1_RFKILL); /* Enable shadow registers. */ if (sc->base_params->shadow_reg_enable) IWN_SETBITS(sc, IWN_SHADOW_REG_CTRL, 0x800fffff); if ((error = ops->load_firmware(sc)) != 0) { device_printf(sc->sc_dev, "%s: could not load firmware, error %d\n", __func__, error); return error; } /* Wait at most one second for firmware alive notification. */ if ((error = msleep(sc, &sc->sc_mtx, PCATCH, "iwninit", hz)) != 0) { device_printf(sc->sc_dev, "%s: timeout waiting for adapter to initialize, error %d\n", __func__, error); return error; } /* Do post-firmware initialization. */ DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end\n",__func__); return ops->post_alive(sc); } static void iwn_hw_stop(struct iwn_softc *sc) { int chnl, qid, ntries; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); IWN_WRITE(sc, IWN_RESET, IWN_RESET_NEVO); /* Disable interrupts. */ IWN_WRITE(sc, IWN_INT_MASK, 0); IWN_WRITE(sc, IWN_INT, 0xffffffff); IWN_WRITE(sc, IWN_FH_INT, 0xffffffff); sc->sc_flags &= ~IWN_FLAG_USE_ICT; /* Make sure we no longer hold the NIC lock. */ iwn_nic_unlock(sc); /* Stop TX scheduler. */ iwn_prph_write(sc, sc->sched_txfact_addr, 0); /* Stop all DMA channels. */ if (iwn_nic_lock(sc) == 0) { for (chnl = 0; chnl < sc->ndmachnls; chnl++) { IWN_WRITE(sc, IWN_FH_TX_CONFIG(chnl), 0); for (ntries = 0; ntries < 200; ntries++) { if (IWN_READ(sc, IWN_FH_TX_STATUS) & IWN_FH_TX_STATUS_IDLE(chnl)) break; DELAY(10); } } iwn_nic_unlock(sc); } /* Stop RX ring. */ iwn_reset_rx_ring(sc, &sc->rxq); /* Reset all TX rings. */ for (qid = 0; qid < sc->ntxqs; qid++) iwn_reset_tx_ring(sc, &sc->txq[qid]); if (iwn_nic_lock(sc) == 0) { iwn_prph_write(sc, IWN_APMG_CLK_DIS, IWN_APMG_CLK_CTRL_DMA_CLK_RQT); iwn_nic_unlock(sc); } DELAY(5); /* Power OFF adapter. */ iwn_apm_stop(sc); } static void iwn_radio_on(void *arg0, int pending) { struct iwn_softc *sc = arg0; struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); if (vap != NULL) { iwn_init(sc); ieee80211_init(vap); } } static void iwn_radio_off(void *arg0, int pending) { struct iwn_softc *sc = arg0; struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); iwn_stop(sc); if (vap != NULL) ieee80211_stop(vap); /* Enable interrupts to get RF toggle notification. */ IWN_LOCK(sc); IWN_WRITE(sc, IWN_INT, 0xffffffff); IWN_WRITE(sc, IWN_INT_MASK, sc->int_mask); IWN_UNLOCK(sc); } static void iwn_panicked(void *arg0, int pending) { struct iwn_softc *sc = arg0; struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); int error; if (vap == NULL) { printf("%s: null vap\n", __func__); return; } device_printf(sc->sc_dev, "%s: controller panicked, iv_state = %d; " "resetting...\n", __func__, vap->iv_state); IWN_LOCK(sc); iwn_stop_locked(sc); iwn_init_locked(sc); if (vap->iv_state >= IEEE80211_S_AUTH && (error = iwn_auth(sc, vap)) != 0) { device_printf(sc->sc_dev, "%s: could not move to auth state\n", __func__); } if (vap->iv_state >= IEEE80211_S_RUN && (error = iwn_run(sc, vap)) != 0) { device_printf(sc->sc_dev, "%s: could not move to run state\n", __func__); } IWN_UNLOCK(sc); } static void iwn_init_locked(struct iwn_softc *sc) { int error; DPRINTF(sc, IWN_DEBUG_TRACE, "->%s begin\n", __func__); IWN_LOCK_ASSERT(sc); sc->sc_flags |= IWN_FLAG_RUNNING; if ((error = iwn_hw_prepare(sc)) != 0) { device_printf(sc->sc_dev, "%s: hardware not ready, error %d\n", __func__, error); goto fail; } /* Initialize interrupt mask to default value. */ sc->int_mask = IWN_INT_MASK_DEF; sc->sc_flags &= ~IWN_FLAG_USE_ICT; /* Check that the radio is not disabled by hardware switch. */ if (!(IWN_READ(sc, IWN_GP_CNTRL) & IWN_GP_CNTRL_RFKILL)) { device_printf(sc->sc_dev, "radio is disabled by hardware switch\n"); /* Enable interrupts to get RF toggle notifications. */ IWN_WRITE(sc, IWN_INT, 0xffffffff); IWN_WRITE(sc, IWN_INT_MASK, sc->int_mask); return; } /* Read firmware images from the filesystem. */ if ((error = iwn_read_firmware(sc)) != 0) { device_printf(sc->sc_dev, "%s: could not read firmware, error %d\n", __func__, error); goto fail; } /* Initialize hardware and upload firmware. */ error = iwn_hw_init(sc); firmware_put(sc->fw_fp, FIRMWARE_UNLOAD); sc->fw_fp = NULL; if (error != 0) { device_printf(sc->sc_dev, "%s: could not initialize hardware, error %d\n", __func__, error); goto fail; } /* Configure adapter now that it is ready. */ if ((error = iwn_config(sc)) != 0) { device_printf(sc->sc_dev, "%s: could not configure device, error %d\n", __func__, error); goto fail; } callout_reset(&sc->watchdog_to, hz, iwn_watchdog, sc); DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end\n",__func__); return; fail: sc->sc_flags &= ~IWN_FLAG_RUNNING; iwn_stop_locked(sc); DPRINTF(sc, IWN_DEBUG_TRACE, "->%s: end in error\n",__func__); } static void iwn_init(struct iwn_softc *sc) { IWN_LOCK(sc); iwn_init_locked(sc); IWN_UNLOCK(sc); if (sc->sc_flags & IWN_FLAG_RUNNING) ieee80211_start_all(&sc->sc_ic); } static void iwn_stop_locked(struct iwn_softc *sc) { IWN_LOCK_ASSERT(sc); sc->sc_is_scanning = 0; sc->sc_tx_timer = 0; callout_stop(&sc->watchdog_to); callout_stop(&sc->calib_to); sc->sc_flags &= ~IWN_FLAG_RUNNING; /* Power OFF hardware. */ iwn_hw_stop(sc); } static void iwn_stop(struct iwn_softc *sc) { IWN_LOCK(sc); iwn_stop_locked(sc); IWN_UNLOCK(sc); } /* * Callback from net80211 to start a scan. */ static void iwn_scan_start(struct ieee80211com *ic) { struct iwn_softc *sc = ic->ic_softc; IWN_LOCK(sc); /* make the link LED blink while we're scanning */ iwn_set_led(sc, IWN_LED_LINK, 20, 2); IWN_UNLOCK(sc); } /* * Callback from net80211 to terminate a scan. */ static void iwn_scan_end(struct ieee80211com *ic) { struct iwn_softc *sc = ic->ic_softc; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); IWN_LOCK(sc); if (vap->iv_state == IEEE80211_S_RUN) { /* Set link LED to ON status if we are associated */ iwn_set_led(sc, IWN_LED_LINK, 0, 1); } IWN_UNLOCK(sc); } /* * Callback from net80211 to force a channel change. */ static void iwn_set_channel(struct ieee80211com *ic) { const struct ieee80211_channel *c = ic->ic_curchan; struct iwn_softc *sc = ic->ic_softc; int error; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); IWN_LOCK(sc); sc->sc_rxtap.wr_chan_freq = htole16(c->ic_freq); sc->sc_rxtap.wr_chan_flags = htole16(c->ic_flags); sc->sc_txtap.wt_chan_freq = htole16(c->ic_freq); sc->sc_txtap.wt_chan_flags = htole16(c->ic_flags); /* * Only need to set the channel in Monitor mode. AP scanning and auth * are already taken care of by their respective firmware commands. */ if (ic->ic_opmode == IEEE80211_M_MONITOR) { error = iwn_config(sc); if (error != 0) device_printf(sc->sc_dev, "%s: error %d settting channel\n", __func__, error); } IWN_UNLOCK(sc); } /* * Callback from net80211 to start scanning of the current channel. */ static void iwn_scan_curchan(struct ieee80211_scan_state *ss, unsigned long maxdwell) { struct ieee80211vap *vap = ss->ss_vap; struct ieee80211com *ic = vap->iv_ic; struct iwn_softc *sc = ic->ic_softc; int error; IWN_LOCK(sc); error = iwn_scan(sc, vap, ss, ic->ic_curchan); IWN_UNLOCK(sc); if (error != 0) ieee80211_cancel_scan(vap); } /* * Callback from net80211 to handle the minimum dwell time being met. * The intent is to terminate the scan but we just let the firmware * notify us when it's finished as we have no safe way to abort it. */ static void iwn_scan_mindwell(struct ieee80211_scan_state *ss) { /* NB: don't try to abort scan; wait for firmware to finish */ } static void iwn_hw_reset(void *arg0, int pending) { struct iwn_softc *sc = arg0; struct ieee80211com *ic = &sc->sc_ic; DPRINTF(sc, IWN_DEBUG_TRACE, "->Doing %s\n", __func__); iwn_stop(sc); iwn_init(sc); ieee80211_notify_radio(ic, 1); } #ifdef IWN_DEBUG #define IWN_DESC(x) case x: return #x /* * Translate CSR code to string */ static char *iwn_get_csr_string(int csr) { switch (csr) { IWN_DESC(IWN_HW_IF_CONFIG); IWN_DESC(IWN_INT_COALESCING); IWN_DESC(IWN_INT); IWN_DESC(IWN_INT_MASK); IWN_DESC(IWN_FH_INT); IWN_DESC(IWN_GPIO_IN); IWN_DESC(IWN_RESET); IWN_DESC(IWN_GP_CNTRL); IWN_DESC(IWN_HW_REV); IWN_DESC(IWN_EEPROM); IWN_DESC(IWN_EEPROM_GP); IWN_DESC(IWN_OTP_GP); IWN_DESC(IWN_GIO); IWN_DESC(IWN_GP_UCODE); IWN_DESC(IWN_GP_DRIVER); IWN_DESC(IWN_UCODE_GP1); IWN_DESC(IWN_UCODE_GP2); IWN_DESC(IWN_LED); IWN_DESC(IWN_DRAM_INT_TBL); IWN_DESC(IWN_GIO_CHICKEN); IWN_DESC(IWN_ANA_PLL); IWN_DESC(IWN_HW_REV_WA); IWN_DESC(IWN_DBG_HPET_MEM); default: return "UNKNOWN CSR"; } } /* * This function print firmware register */ static void iwn_debug_register(struct iwn_softc *sc) { int i; static const uint32_t csr_tbl[] = { IWN_HW_IF_CONFIG, IWN_INT_COALESCING, IWN_INT, IWN_INT_MASK, IWN_FH_INT, IWN_GPIO_IN, IWN_RESET, IWN_GP_CNTRL, IWN_HW_REV, IWN_EEPROM, IWN_EEPROM_GP, IWN_OTP_GP, IWN_GIO, IWN_GP_UCODE, IWN_GP_DRIVER, IWN_UCODE_GP1, IWN_UCODE_GP2, IWN_LED, IWN_DRAM_INT_TBL, IWN_GIO_CHICKEN, IWN_ANA_PLL, IWN_HW_REV_WA, IWN_DBG_HPET_MEM, }; DPRINTF(sc, IWN_DEBUG_REGISTER, "CSR values: (2nd byte of IWN_INT_COALESCING is IWN_INT_PERIODIC)%s", "\n"); for (i = 0; i < nitems(csr_tbl); i++){ DPRINTF(sc, IWN_DEBUG_REGISTER," %10s: 0x%08x ", iwn_get_csr_string(csr_tbl[i]), IWN_READ(sc, csr_tbl[i])); if ((i+1) % 3 == 0) DPRINTF(sc, IWN_DEBUG_REGISTER,"%s","\n"); } DPRINTF(sc, IWN_DEBUG_REGISTER,"%s","\n"); } #endif Index: head/sys/dev/wpi/if_wpi.c =================================================================== --- head/sys/dev/wpi/if_wpi.c (revision 289678) +++ head/sys/dev/wpi/if_wpi.c (revision 289679) @@ -1,5646 +1,5644 @@ /*- * Copyright (c) 2006,2007 * Damien Bergamini * Benjamin Close * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include __FBSDID("$FreeBSD$"); /* * Driver for Intel PRO/Wireless 3945ABG 802.11 network adapters. * * The 3945ABG network adapter doesn't use traditional hardware as * many other adaptors do. Instead at run time the eeprom is set into a known * state and told to load boot firmware. The boot firmware loads an init and a * main binary firmware image into SRAM on the card via DMA. * Once the firmware is loaded, the driver/hw then * communicate by way of circular dma rings via the SRAM to the firmware. * * There is 6 memory rings. 1 command ring, 1 rx data ring & 4 tx data rings. * The 4 tx data rings allow for prioritization QoS. * * The rx data ring consists of 32 dma buffers. Two registers are used to * indicate where in the ring the driver and the firmware are up to. The * driver sets the initial read index (reg1) and the initial write index (reg2), * the firmware updates the read index (reg1) on rx of a packet and fires an * interrupt. The driver then processes the buffers starting at reg1 indicating * to the firmware which buffers have been accessed by updating reg2. At the * same time allocating new memory for the processed buffer. * * A similar thing happens with the tx rings. The difference is the firmware * stop processing buffers once the queue is full and until confirmation * of a successful transmition (tx_done) has occurred. * * The command ring operates in the same manner as the tx queues. * * All communication direct to the card (ie eeprom) is classed as Stage1 * communication * * All communication via the firmware to the card is classed as State2. * The firmware consists of 2 parts. A bootstrap firmware and a runtime * firmware. The bootstrap firmware and runtime firmware are loaded * from host memory via dma to the card then told to execute. From this point * on the majority of communications between the driver and the card goes * via the firmware. */ #include "opt_wlan.h" #include "opt_wpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct wpi_ident { uint16_t vendor; uint16_t device; uint16_t subdevice; const char *name; }; static const struct wpi_ident wpi_ident_table[] = { /* The below entries support ABG regardless of the subid */ { 0x8086, 0x4222, 0x0, "Intel(R) PRO/Wireless 3945ABG" }, { 0x8086, 0x4227, 0x0, "Intel(R) PRO/Wireless 3945ABG" }, /* The below entries only support BG */ { 0x8086, 0x4222, 0x1005, "Intel(R) PRO/Wireless 3945BG" }, { 0x8086, 0x4222, 0x1034, "Intel(R) PRO/Wireless 3945BG" }, { 0x8086, 0x4227, 0x1014, "Intel(R) PRO/Wireless 3945BG" }, { 0x8086, 0x4222, 0x1044, "Intel(R) PRO/Wireless 3945BG" }, { 0, 0, 0, NULL } }; static int wpi_probe(device_t); static int wpi_attach(device_t); static void wpi_radiotap_attach(struct wpi_softc *); static void wpi_sysctlattach(struct wpi_softc *); static void wpi_init_beacon(struct wpi_vap *); static struct ieee80211vap *wpi_vap_create(struct ieee80211com *, const char [IFNAMSIZ], int, enum ieee80211_opmode, int, const uint8_t [IEEE80211_ADDR_LEN], const uint8_t [IEEE80211_ADDR_LEN]); static void wpi_vap_delete(struct ieee80211vap *); static int wpi_detach(device_t); static int wpi_shutdown(device_t); static int wpi_suspend(device_t); static int wpi_resume(device_t); static int wpi_nic_lock(struct wpi_softc *); static int wpi_read_prom_data(struct wpi_softc *, uint32_t, void *, int); static void wpi_dma_map_addr(void *, bus_dma_segment_t *, int, int); static int wpi_dma_contig_alloc(struct wpi_softc *, struct wpi_dma_info *, void **, bus_size_t, bus_size_t); static void wpi_dma_contig_free(struct wpi_dma_info *); static int wpi_alloc_shared(struct wpi_softc *); static void wpi_free_shared(struct wpi_softc *); static int wpi_alloc_fwmem(struct wpi_softc *); static void wpi_free_fwmem(struct wpi_softc *); static int wpi_alloc_rx_ring(struct wpi_softc *); static void wpi_update_rx_ring(struct wpi_softc *); static void wpi_update_rx_ring_ps(struct wpi_softc *); static void wpi_reset_rx_ring(struct wpi_softc *); static void wpi_free_rx_ring(struct wpi_softc *); static int wpi_alloc_tx_ring(struct wpi_softc *, struct wpi_tx_ring *, uint8_t); static void wpi_update_tx_ring(struct wpi_softc *, struct wpi_tx_ring *); static void wpi_update_tx_ring_ps(struct wpi_softc *, struct wpi_tx_ring *); static void wpi_reset_tx_ring(struct wpi_softc *, struct wpi_tx_ring *); static void wpi_free_tx_ring(struct wpi_softc *, struct wpi_tx_ring *); static int wpi_read_eeprom(struct wpi_softc *, uint8_t macaddr[IEEE80211_ADDR_LEN]); static uint32_t wpi_eeprom_channel_flags(struct wpi_eeprom_chan *); static void wpi_read_eeprom_band(struct wpi_softc *, uint8_t); static int wpi_read_eeprom_channels(struct wpi_softc *, uint8_t); static struct wpi_eeprom_chan *wpi_find_eeprom_channel(struct wpi_softc *, struct ieee80211_channel *); static int wpi_setregdomain(struct ieee80211com *, struct ieee80211_regdomain *, int, struct ieee80211_channel[]); static int wpi_read_eeprom_group(struct wpi_softc *, uint8_t); static struct ieee80211_node *wpi_node_alloc(struct ieee80211vap *, const uint8_t mac[IEEE80211_ADDR_LEN]); static void wpi_node_free(struct ieee80211_node *); static void wpi_ibss_recv_mgmt(struct ieee80211_node *, struct mbuf *, int, const struct ieee80211_rx_stats *, int, int); static void wpi_restore_node(void *, struct ieee80211_node *); static void wpi_restore_node_table(struct wpi_softc *, struct wpi_vap *); static int wpi_newstate(struct ieee80211vap *, enum ieee80211_state, int); static void wpi_calib_timeout(void *); static void wpi_rx_done(struct wpi_softc *, struct wpi_rx_desc *, struct wpi_rx_data *); static void wpi_rx_statistics(struct wpi_softc *, struct wpi_rx_desc *, struct wpi_rx_data *); static void wpi_tx_done(struct wpi_softc *, struct wpi_rx_desc *); static void wpi_cmd_done(struct wpi_softc *, struct wpi_rx_desc *); static void wpi_notif_intr(struct wpi_softc *); static void wpi_wakeup_intr(struct wpi_softc *); #ifdef WPI_DEBUG static void wpi_debug_registers(struct wpi_softc *); #endif static void wpi_fatal_intr(struct wpi_softc *); static void wpi_intr(void *); static void wpi_free_txfrags(struct wpi_softc *, uint16_t); static int wpi_cmd2(struct wpi_softc *, struct wpi_buf *); static int wpi_tx_data(struct wpi_softc *, struct mbuf *, struct ieee80211_node *); static int wpi_tx_data_raw(struct wpi_softc *, struct mbuf *, struct ieee80211_node *, const struct ieee80211_bpf_params *); static int wpi_raw_xmit(struct ieee80211_node *, struct mbuf *, const struct ieee80211_bpf_params *); static int wpi_transmit(struct ieee80211com *, struct mbuf *); static void wpi_watchdog_rfkill(void *); static void wpi_scan_timeout(void *); static void wpi_tx_timeout(void *); static void wpi_parent(struct ieee80211com *); static int wpi_cmd(struct wpi_softc *, uint8_t, const void *, uint16_t, int); static int wpi_mrr_setup(struct wpi_softc *); static int wpi_add_node(struct wpi_softc *, struct ieee80211_node *); static int wpi_add_broadcast_node(struct wpi_softc *, int); static int wpi_add_ibss_node(struct wpi_softc *, struct ieee80211_node *); static void wpi_del_node(struct wpi_softc *, struct ieee80211_node *); static int wpi_updateedca(struct ieee80211com *); static void wpi_set_promisc(struct wpi_softc *); static void wpi_update_promisc(struct ieee80211com *); static void wpi_update_mcast(struct ieee80211com *); static void wpi_set_led(struct wpi_softc *, uint8_t, uint8_t, uint8_t); static int wpi_set_timing(struct wpi_softc *, struct ieee80211_node *); static void wpi_power_calibration(struct wpi_softc *); static int wpi_set_txpower(struct wpi_softc *, int); static int wpi_get_power_index(struct wpi_softc *, struct wpi_power_group *, uint8_t, int, int); static int wpi_set_pslevel(struct wpi_softc *, uint8_t, int, int); static int wpi_send_btcoex(struct wpi_softc *); static int wpi_send_rxon(struct wpi_softc *, int, int); static int wpi_config(struct wpi_softc *); static uint16_t wpi_get_active_dwell_time(struct wpi_softc *, struct ieee80211_channel *, uint8_t); static uint16_t wpi_limit_dwell(struct wpi_softc *, uint16_t); static uint16_t wpi_get_passive_dwell_time(struct wpi_softc *, struct ieee80211_channel *); static uint32_t wpi_get_scan_pause_time(uint32_t, uint16_t); static int wpi_scan(struct wpi_softc *, struct ieee80211_channel *); static int wpi_auth(struct wpi_softc *, struct ieee80211vap *); static int wpi_config_beacon(struct wpi_vap *); static int wpi_setup_beacon(struct wpi_softc *, struct ieee80211_node *); static void wpi_update_beacon(struct ieee80211vap *, int); static void wpi_newassoc(struct ieee80211_node *, int); static int wpi_run(struct wpi_softc *, struct ieee80211vap *); static int wpi_load_key(struct ieee80211_node *, const struct ieee80211_key *); static void wpi_load_key_cb(void *, struct ieee80211_node *); static int wpi_set_global_keys(struct ieee80211_node *); static int wpi_del_key(struct ieee80211_node *, const struct ieee80211_key *); static void wpi_del_key_cb(void *, struct ieee80211_node *); static int wpi_process_key(struct ieee80211vap *, const struct ieee80211_key *, int); static int wpi_key_set(struct ieee80211vap *, const struct ieee80211_key *); static int wpi_key_delete(struct ieee80211vap *, const struct ieee80211_key *); static int wpi_post_alive(struct wpi_softc *); static int wpi_load_bootcode(struct wpi_softc *, const uint8_t *, uint32_t); static int wpi_load_firmware(struct wpi_softc *); static int wpi_read_firmware(struct wpi_softc *); static void wpi_unload_firmware(struct wpi_softc *); static int wpi_clock_wait(struct wpi_softc *); static int wpi_apm_init(struct wpi_softc *); static void wpi_apm_stop_master(struct wpi_softc *); static void wpi_apm_stop(struct wpi_softc *); static void wpi_nic_config(struct wpi_softc *); static int wpi_hw_init(struct wpi_softc *); static void wpi_hw_stop(struct wpi_softc *); static void wpi_radio_on(void *, int); static void wpi_radio_off(void *, int); static int wpi_init(struct wpi_softc *); static void wpi_stop_locked(struct wpi_softc *); static void wpi_stop(struct wpi_softc *); static void wpi_scan_start(struct ieee80211com *); static void wpi_scan_end(struct ieee80211com *); static void wpi_set_channel(struct ieee80211com *); static void wpi_scan_curchan(struct ieee80211_scan_state *, unsigned long); static void wpi_scan_mindwell(struct ieee80211_scan_state *); static void wpi_hw_reset(void *, int); static device_method_t wpi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, wpi_probe), DEVMETHOD(device_attach, wpi_attach), DEVMETHOD(device_detach, wpi_detach), DEVMETHOD(device_shutdown, wpi_shutdown), DEVMETHOD(device_suspend, wpi_suspend), DEVMETHOD(device_resume, wpi_resume), DEVMETHOD_END }; static driver_t wpi_driver = { "wpi", wpi_methods, sizeof (struct wpi_softc) }; static devclass_t wpi_devclass; DRIVER_MODULE(wpi, pci, wpi_driver, wpi_devclass, NULL, NULL); MODULE_VERSION(wpi, 1); MODULE_DEPEND(wpi, pci, 1, 1, 1); MODULE_DEPEND(wpi, wlan, 1, 1, 1); MODULE_DEPEND(wpi, firmware, 1, 1, 1); static int wpi_probe(device_t dev) { const struct wpi_ident *ident; for (ident = wpi_ident_table; ident->name != NULL; ident++) { if (pci_get_vendor(dev) == ident->vendor && pci_get_device(dev) == ident->device) { device_set_desc(dev, ident->name); return (BUS_PROBE_DEFAULT); } } return ENXIO; } static int wpi_attach(device_t dev) { struct wpi_softc *sc = (struct wpi_softc *)device_get_softc(dev); struct ieee80211com *ic; uint8_t i; int error, rid; #ifdef WPI_DEBUG int supportsa = 1; const struct wpi_ident *ident; #endif sc->sc_dev = dev; #ifdef WPI_DEBUG error = resource_int_value(device_get_name(sc->sc_dev), device_get_unit(sc->sc_dev), "debug", &(sc->sc_debug)); if (error != 0) sc->sc_debug = 0; #else sc->sc_debug = 0; #endif DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_BEGIN, __func__); /* * Get the offset of the PCI Express Capability Structure in PCI * Configuration Space. */ error = pci_find_cap(dev, PCIY_EXPRESS, &sc->sc_cap_off); if (error != 0) { device_printf(dev, "PCIe capability structure not found!\n"); return error; } /* * Some card's only support 802.11b/g not a, check to see if * this is one such card. A 0x0 in the subdevice table indicates * the entire subdevice range is to be ignored. */ #ifdef WPI_DEBUG for (ident = wpi_ident_table; ident->name != NULL; ident++) { if (ident->subdevice && pci_get_subdevice(dev) == ident->subdevice) { supportsa = 0; break; } } #endif /* Clear device-specific "PCI retry timeout" register (41h). */ pci_write_config(dev, 0x41, 0, 1); /* Enable bus-mastering. */ pci_enable_busmaster(dev); rid = PCIR_BAR(0); sc->mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->mem == NULL) { device_printf(dev, "can't map mem space\n"); return ENOMEM; } sc->sc_st = rman_get_bustag(sc->mem); sc->sc_sh = rman_get_bushandle(sc->mem); rid = 1; if (pci_alloc_msi(dev, &rid) == 0) rid = 1; else rid = 0; /* Install interrupt handler. */ sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE | (rid != 0 ? 0 : RF_SHAREABLE)); if (sc->irq == NULL) { device_printf(dev, "can't map interrupt\n"); error = ENOMEM; goto fail; } WPI_LOCK_INIT(sc); WPI_TX_LOCK_INIT(sc); WPI_RXON_LOCK_INIT(sc); WPI_NT_LOCK_INIT(sc); WPI_TXQ_LOCK_INIT(sc); WPI_TXQ_STATE_LOCK_INIT(sc); /* Allocate DMA memory for firmware transfers. */ if ((error = wpi_alloc_fwmem(sc)) != 0) { device_printf(dev, "could not allocate memory for firmware, error %d\n", error); goto fail; } /* Allocate shared page. */ if ((error = wpi_alloc_shared(sc)) != 0) { device_printf(dev, "could not allocate shared page\n"); goto fail; } /* Allocate TX rings - 4 for QoS purposes, 1 for commands. */ for (i = 0; i < WPI_DRV_NTXQUEUES; i++) { if ((error = wpi_alloc_tx_ring(sc, &sc->txq[i], i)) != 0) { device_printf(dev, "could not allocate TX ring %d, error %d\n", i, error); goto fail; } } /* Allocate RX ring. */ if ((error = wpi_alloc_rx_ring(sc)) != 0) { device_printf(dev, "could not allocate RX ring, error %d\n", error); goto fail; } /* Clear pending interrupts. */ WPI_WRITE(sc, WPI_INT, 0xffffffff); ic = &sc->sc_ic; ic->ic_softc = sc; ic->ic_name = device_get_nameunit(dev); ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ ic->ic_opmode = IEEE80211_M_STA; /* default to BSS mode */ /* Set device capabilities. */ ic->ic_caps = IEEE80211_C_STA /* station mode supported */ | IEEE80211_C_IBSS /* IBSS mode supported */ | IEEE80211_C_HOSTAP /* Host access point mode */ | IEEE80211_C_MONITOR /* monitor mode supported */ | IEEE80211_C_AHDEMO /* adhoc demo mode */ | IEEE80211_C_BGSCAN /* capable of bg scanning */ | IEEE80211_C_TXFRAG /* handle tx frags */ | IEEE80211_C_TXPMGT /* tx power management */ | IEEE80211_C_SHSLOT /* short slot time supported */ | IEEE80211_C_WPA /* 802.11i */ | IEEE80211_C_SHPREAMBLE /* short preamble supported */ | IEEE80211_C_WME /* 802.11e */ | IEEE80211_C_PMGT /* Station-side power mgmt */ ; ic->ic_cryptocaps = IEEE80211_CRYPTO_AES_CCM; /* * Read in the eeprom and also setup the channels for * net80211. We don't set the rates as net80211 does this for us */ if ((error = wpi_read_eeprom(sc, ic->ic_macaddr)) != 0) { device_printf(dev, "could not read EEPROM, error %d\n", error); goto fail; } #ifdef WPI_DEBUG if (bootverbose) { device_printf(sc->sc_dev, "Regulatory Domain: %.4s\n", sc->domain); device_printf(sc->sc_dev, "Hardware Type: %c\n", sc->type > 1 ? 'B': '?'); device_printf(sc->sc_dev, "Hardware Revision: %c\n", ((sc->rev & 0xf0) == 0xd0) ? 'D': '?'); device_printf(sc->sc_dev, "SKU %s support 802.11a\n", supportsa ? "does" : "does not"); /* XXX hw_config uses the PCIDEV for the Hardware rev. Must check what sc->rev really represents - benjsc 20070615 */ } #endif ieee80211_ifattach(ic); ic->ic_vap_create = wpi_vap_create; ic->ic_vap_delete = wpi_vap_delete; ic->ic_parent = wpi_parent; ic->ic_raw_xmit = wpi_raw_xmit; ic->ic_transmit = wpi_transmit; ic->ic_node_alloc = wpi_node_alloc; sc->sc_node_free = ic->ic_node_free; ic->ic_node_free = wpi_node_free; ic->ic_wme.wme_update = wpi_updateedca; ic->ic_update_promisc = wpi_update_promisc; ic->ic_update_mcast = wpi_update_mcast; ic->ic_newassoc = wpi_newassoc; ic->ic_scan_start = wpi_scan_start; ic->ic_scan_end = wpi_scan_end; ic->ic_set_channel = wpi_set_channel; ic->ic_scan_curchan = wpi_scan_curchan; ic->ic_scan_mindwell = wpi_scan_mindwell; ic->ic_setregdomain = wpi_setregdomain; sc->sc_update_rx_ring = wpi_update_rx_ring; sc->sc_update_tx_ring = wpi_update_tx_ring; wpi_radiotap_attach(sc); callout_init_mtx(&sc->calib_to, &sc->rxon_mtx, 0); callout_init_mtx(&sc->scan_timeout, &sc->rxon_mtx, 0); callout_init_mtx(&sc->tx_timeout, &sc->txq_state_mtx, 0); callout_init_mtx(&sc->watchdog_rfkill, &sc->sc_mtx, 0); TASK_INIT(&sc->sc_reinittask, 0, wpi_hw_reset, sc); TASK_INIT(&sc->sc_radiooff_task, 0, wpi_radio_off, sc); TASK_INIT(&sc->sc_radioon_task, 0, wpi_radio_on, sc); sc->sc_tq = taskqueue_create("wpi_taskq", M_WAITOK, taskqueue_thread_enqueue, &sc->sc_tq); error = taskqueue_start_threads(&sc->sc_tq, 1, 0, "wpi_taskq"); if (error != 0) { device_printf(dev, "can't start threads, error %d\n", error); goto fail; } wpi_sysctlattach(sc); /* * Hook our interrupt after all initialization is complete. */ error = bus_setup_intr(dev, sc->irq, INTR_TYPE_NET | INTR_MPSAFE, NULL, wpi_intr, sc, &sc->sc_ih); if (error != 0) { device_printf(dev, "can't establish interrupt, error %d\n", error); goto fail; } if (bootverbose) ieee80211_announce(ic); #ifdef WPI_DEBUG if (sc->sc_debug & WPI_DEBUG_HW) ieee80211_announce_channels(ic); #endif DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END, __func__); return 0; fail: wpi_detach(dev); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END_ERR, __func__); return error; } /* * Attach the interface to 802.11 radiotap. */ static void wpi_radiotap_attach(struct wpi_softc *sc) { struct wpi_rx_radiotap_header *rxtap = &sc->sc_rxtap; struct wpi_tx_radiotap_header *txtap = &sc->sc_txtap; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_BEGIN, __func__); ieee80211_radiotap_attach(&sc->sc_ic, &txtap->wt_ihdr, sizeof(*txtap), WPI_TX_RADIOTAP_PRESENT, &rxtap->wr_ihdr, sizeof(*rxtap), WPI_RX_RADIOTAP_PRESENT); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END, __func__); } static void wpi_sysctlattach(struct wpi_softc *sc) { #ifdef WPI_DEBUG 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_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "debug", CTLFLAG_RW, &sc->sc_debug, sc->sc_debug, "control debugging printfs"); #endif } static void wpi_init_beacon(struct wpi_vap *wvp) { struct wpi_buf *bcn = &wvp->wv_bcbuf; struct wpi_cmd_beacon *cmd = (struct wpi_cmd_beacon *)&bcn->data; cmd->id = WPI_ID_BROADCAST; cmd->ofdm_mask = 0xff; cmd->cck_mask = 0x0f; cmd->lifetime = htole32(WPI_LIFETIME_INFINITE); /* * XXX WPI_TX_AUTO_SEQ seems to be ignored - workaround this issue * XXX by using WPI_TX_NEED_ACK instead (with some side effects). */ cmd->flags = htole32(WPI_TX_NEED_ACK | WPI_TX_INSERT_TSTAMP); bcn->code = WPI_CMD_SET_BEACON; bcn->ac = WPI_CMD_QUEUE_NUM; bcn->size = sizeof(struct wpi_cmd_beacon); } static struct ieee80211vap * wpi_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, enum ieee80211_opmode opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t mac[IEEE80211_ADDR_LEN]) { struct wpi_vap *wvp; struct ieee80211vap *vap; if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ return NULL; wvp = malloc(sizeof(struct wpi_vap), M_80211_VAP, M_WAITOK | M_ZERO); vap = &wvp->wv_vap; ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid); if (opmode == IEEE80211_M_IBSS || opmode == IEEE80211_M_HOSTAP) { WPI_VAP_LOCK_INIT(wvp); wpi_init_beacon(wvp); } /* Override with driver methods. */ vap->iv_key_set = wpi_key_set; vap->iv_key_delete = wpi_key_delete; if (opmode == IEEE80211_M_IBSS) { wvp->wv_recv_mgmt = vap->iv_recv_mgmt; vap->iv_recv_mgmt = wpi_ibss_recv_mgmt; } wvp->wv_newstate = vap->iv_newstate; vap->iv_newstate = wpi_newstate; vap->iv_update_beacon = wpi_update_beacon; vap->iv_max_aid = WPI_ID_IBSS_MAX - WPI_ID_IBSS_MIN + 1; ieee80211_ratectl_init(vap); /* Complete setup. */ ieee80211_vap_attach(vap, ieee80211_media_change, ieee80211_media_status, mac); ic->ic_opmode = opmode; return vap; } static void wpi_vap_delete(struct ieee80211vap *vap) { struct wpi_vap *wvp = WPI_VAP(vap); struct wpi_buf *bcn = &wvp->wv_bcbuf; enum ieee80211_opmode opmode = vap->iv_opmode; ieee80211_ratectl_deinit(vap); ieee80211_vap_detach(vap); if (opmode == IEEE80211_M_IBSS || opmode == IEEE80211_M_HOSTAP) { if (bcn->m != NULL) m_freem(bcn->m); WPI_VAP_LOCK_DESTROY(wvp); } free(wvp, M_80211_VAP); } static int wpi_detach(device_t dev) { struct wpi_softc *sc = device_get_softc(dev); struct ieee80211com *ic = &sc->sc_ic; uint8_t qid; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_BEGIN, __func__); if (ic->ic_vap_create == wpi_vap_create) { ieee80211_draintask(ic, &sc->sc_radioon_task); wpi_stop(sc); if (sc->sc_tq != NULL) { taskqueue_drain_all(sc->sc_tq); taskqueue_free(sc->sc_tq); } callout_drain(&sc->watchdog_rfkill); callout_drain(&sc->tx_timeout); callout_drain(&sc->scan_timeout); callout_drain(&sc->calib_to); ieee80211_ifdetach(ic); } /* Uninstall interrupt handler. */ if (sc->irq != NULL) { bus_teardown_intr(dev, sc->irq, sc->sc_ih); bus_release_resource(dev, SYS_RES_IRQ, rman_get_rid(sc->irq), sc->irq); pci_release_msi(dev); } if (sc->txq[0].data_dmat) { /* Free DMA resources. */ for (qid = 0; qid < WPI_DRV_NTXQUEUES; qid++) wpi_free_tx_ring(sc, &sc->txq[qid]); wpi_free_rx_ring(sc); wpi_free_shared(sc); } if (sc->fw_dma.tag) wpi_free_fwmem(sc); if (sc->mem != NULL) bus_release_resource(dev, SYS_RES_MEMORY, rman_get_rid(sc->mem), sc->mem); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END, __func__); WPI_TXQ_STATE_LOCK_DESTROY(sc); WPI_TXQ_LOCK_DESTROY(sc); WPI_NT_LOCK_DESTROY(sc); WPI_RXON_LOCK_DESTROY(sc); WPI_TX_LOCK_DESTROY(sc); WPI_LOCK_DESTROY(sc); return 0; } static int wpi_shutdown(device_t dev) { struct wpi_softc *sc = device_get_softc(dev); wpi_stop(sc); return 0; } static int wpi_suspend(device_t dev) { struct wpi_softc *sc = device_get_softc(dev); struct ieee80211com *ic = &sc->sc_ic; ieee80211_suspend_all(ic); return 0; } static int wpi_resume(device_t dev) { struct wpi_softc *sc = device_get_softc(dev); struct ieee80211com *ic = &sc->sc_ic; /* Clear device-specific "PCI retry timeout" register (41h). */ pci_write_config(dev, 0x41, 0, 1); ieee80211_resume_all(ic); return 0; } /* * Grab exclusive access to NIC memory. */ static int wpi_nic_lock(struct wpi_softc *sc) { int ntries; /* Request exclusive access to NIC. */ WPI_SETBITS(sc, WPI_GP_CNTRL, WPI_GP_CNTRL_MAC_ACCESS_REQ); /* Spin until we actually get the lock. */ for (ntries = 0; ntries < 1000; ntries++) { if ((WPI_READ(sc, WPI_GP_CNTRL) & (WPI_GP_CNTRL_MAC_ACCESS_ENA | WPI_GP_CNTRL_SLEEP)) == WPI_GP_CNTRL_MAC_ACCESS_ENA) return 0; DELAY(10); } device_printf(sc->sc_dev, "could not lock memory\n"); return ETIMEDOUT; } /* * Release lock on NIC memory. */ static __inline void wpi_nic_unlock(struct wpi_softc *sc) { WPI_CLRBITS(sc, WPI_GP_CNTRL, WPI_GP_CNTRL_MAC_ACCESS_REQ); } static __inline uint32_t wpi_prph_read(struct wpi_softc *sc, uint32_t addr) { WPI_WRITE(sc, WPI_PRPH_RADDR, WPI_PRPH_DWORD | addr); WPI_BARRIER_READ_WRITE(sc); return WPI_READ(sc, WPI_PRPH_RDATA); } static __inline void wpi_prph_write(struct wpi_softc *sc, uint32_t addr, uint32_t data) { WPI_WRITE(sc, WPI_PRPH_WADDR, WPI_PRPH_DWORD | addr); WPI_BARRIER_WRITE(sc); WPI_WRITE(sc, WPI_PRPH_WDATA, data); } static __inline void wpi_prph_setbits(struct wpi_softc *sc, uint32_t addr, uint32_t mask) { wpi_prph_write(sc, addr, wpi_prph_read(sc, addr) | mask); } static __inline void wpi_prph_clrbits(struct wpi_softc *sc, uint32_t addr, uint32_t mask) { wpi_prph_write(sc, addr, wpi_prph_read(sc, addr) & ~mask); } static __inline void wpi_prph_write_region_4(struct wpi_softc *sc, uint32_t addr, const uint32_t *data, uint32_t count) { for (; count != 0; count--, data++, addr += 4) wpi_prph_write(sc, addr, *data); } static __inline uint32_t wpi_mem_read(struct wpi_softc *sc, uint32_t addr) { WPI_WRITE(sc, WPI_MEM_RADDR, addr); WPI_BARRIER_READ_WRITE(sc); return WPI_READ(sc, WPI_MEM_RDATA); } static __inline void wpi_mem_read_region_4(struct wpi_softc *sc, uint32_t addr, uint32_t *data, int count) { for (; count > 0; count--, addr += 4) *data++ = wpi_mem_read(sc, addr); } static int wpi_read_prom_data(struct wpi_softc *sc, uint32_t addr, void *data, int count) { uint8_t *out = data; uint32_t val; int error, ntries; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_BEGIN, __func__); if ((error = wpi_nic_lock(sc)) != 0) return error; for (; count > 0; count -= 2, addr++) { WPI_WRITE(sc, WPI_EEPROM, addr << 2); for (ntries = 0; ntries < 10; ntries++) { val = WPI_READ(sc, WPI_EEPROM); if (val & WPI_EEPROM_READ_VALID) break; DELAY(5); } if (ntries == 10) { device_printf(sc->sc_dev, "timeout reading ROM at 0x%x\n", addr); return ETIMEDOUT; } *out++= val >> 16; if (count > 1) *out ++= val >> 24; } wpi_nic_unlock(sc); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END, __func__); return 0; } static void wpi_dma_map_addr(void *arg, bus_dma_segment_t *segs, int nsegs, int error) { if (error != 0) return; KASSERT(nsegs == 1, ("too many DMA segments, %d should be 1", nsegs)); *(bus_addr_t *)arg = segs[0].ds_addr; } /* * Allocates a contiguous block of dma memory of the requested size and * alignment. */ static int wpi_dma_contig_alloc(struct wpi_softc *sc, struct wpi_dma_info *dma, void **kvap, bus_size_t size, bus_size_t alignment) { int error; dma->tag = NULL; dma->size = size; error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), alignment, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, size, - 1, size, BUS_DMA_NOWAIT, NULL, NULL, &dma->tag); + 1, size, 0, NULL, NULL, &dma->tag); if (error != 0) goto fail; error = bus_dmamem_alloc(dma->tag, (void **)&dma->vaddr, BUS_DMA_NOWAIT | BUS_DMA_ZERO | BUS_DMA_COHERENT, &dma->map); if (error != 0) goto fail; error = bus_dmamap_load(dma->tag, dma->map, dma->vaddr, size, wpi_dma_map_addr, &dma->paddr, BUS_DMA_NOWAIT); if (error != 0) goto fail; bus_dmamap_sync(dma->tag, dma->map, BUS_DMASYNC_PREWRITE); if (kvap != NULL) *kvap = dma->vaddr; return 0; fail: wpi_dma_contig_free(dma); return error; } static void wpi_dma_contig_free(struct wpi_dma_info *dma) { if (dma->vaddr != NULL) { bus_dmamap_sync(dma->tag, dma->map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(dma->tag, dma->map); bus_dmamem_free(dma->tag, dma->vaddr, dma->map); dma->vaddr = NULL; } if (dma->tag != NULL) { bus_dma_tag_destroy(dma->tag); dma->tag = NULL; } } /* * Allocate a shared page between host and NIC. */ static int wpi_alloc_shared(struct wpi_softc *sc) { /* Shared buffer must be aligned on a 4KB boundary. */ return wpi_dma_contig_alloc(sc, &sc->shared_dma, (void **)&sc->shared, sizeof (struct wpi_shared), 4096); } static void wpi_free_shared(struct wpi_softc *sc) { wpi_dma_contig_free(&sc->shared_dma); } /* * Allocate DMA-safe memory for firmware transfer. */ static int wpi_alloc_fwmem(struct wpi_softc *sc) { /* Must be aligned on a 16-byte boundary. */ return wpi_dma_contig_alloc(sc, &sc->fw_dma, NULL, WPI_FW_TEXT_MAXSZ + WPI_FW_DATA_MAXSZ, 16); } static void wpi_free_fwmem(struct wpi_softc *sc) { wpi_dma_contig_free(&sc->fw_dma); } static int wpi_alloc_rx_ring(struct wpi_softc *sc) { struct wpi_rx_ring *ring = &sc->rxq; bus_size_t size; int i, error; ring->cur = 0; ring->update = 0; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_BEGIN, __func__); /* Allocate RX descriptors (16KB aligned.) */ size = WPI_RX_RING_COUNT * sizeof (uint32_t); error = wpi_dma_contig_alloc(sc, &ring->desc_dma, (void **)&ring->desc, size, WPI_RING_DMA_ALIGN); if (error != 0) { device_printf(sc->sc_dev, "%s: could not allocate RX ring DMA memory, error %d\n", __func__, error); goto fail; } /* Create RX buffer DMA tag. */ error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), 1, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, - MJUMPAGESIZE, 1, MJUMPAGESIZE, BUS_DMA_NOWAIT, NULL, NULL, - &ring->data_dmat); + MJUMPAGESIZE, 1, MJUMPAGESIZE, 0, NULL, NULL, &ring->data_dmat); if (error != 0) { device_printf(sc->sc_dev, "%s: could not create RX buf DMA tag, error %d\n", __func__, error); goto fail; } /* * Allocate and map RX buffers. */ for (i = 0; i < WPI_RX_RING_COUNT; i++) { struct wpi_rx_data *data = &ring->data[i]; bus_addr_t paddr; error = bus_dmamap_create(ring->data_dmat, 0, &data->map); if (error != 0) { device_printf(sc->sc_dev, "%s: could not create RX buf DMA map, error %d\n", __func__, error); goto fail; } data->m = m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR, MJUMPAGESIZE); if (data->m == NULL) { device_printf(sc->sc_dev, "%s: could not allocate RX mbuf\n", __func__); error = ENOBUFS; goto fail; } error = bus_dmamap_load(ring->data_dmat, data->map, mtod(data->m, void *), MJUMPAGESIZE, wpi_dma_map_addr, &paddr, BUS_DMA_NOWAIT); if (error != 0 && error != EFBIG) { device_printf(sc->sc_dev, "%s: can't map mbuf (error %d)\n", __func__, error); goto fail; } /* Set physical address of RX buffer. */ ring->desc[i] = htole32(paddr); } bus_dmamap_sync(ring->desc_dma.tag, ring->desc_dma.map, BUS_DMASYNC_PREWRITE); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END, __func__); return 0; fail: wpi_free_rx_ring(sc); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END_ERR, __func__); return error; } static void wpi_update_rx_ring(struct wpi_softc *sc) { WPI_WRITE(sc, WPI_FH_RX_WPTR, sc->rxq.cur & ~7); } static void wpi_update_rx_ring_ps(struct wpi_softc *sc) { struct wpi_rx_ring *ring = &sc->rxq; if (ring->update != 0) { /* Wait for INT_WAKEUP event. */ return; } WPI_TXQ_LOCK(sc); WPI_SETBITS(sc, WPI_GP_CNTRL, WPI_GP_CNTRL_MAC_ACCESS_REQ); if (WPI_READ(sc, WPI_GP_CNTRL) & WPI_GP_CNTRL_SLEEP) { DPRINTF(sc, WPI_DEBUG_PWRSAVE, "%s: wakeup request\n", __func__); ring->update = 1; } else { wpi_update_rx_ring(sc); WPI_CLRBITS(sc, WPI_GP_CNTRL, WPI_GP_CNTRL_MAC_ACCESS_REQ); } WPI_TXQ_UNLOCK(sc); } static void wpi_reset_rx_ring(struct wpi_softc *sc) { struct wpi_rx_ring *ring = &sc->rxq; int ntries; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); if (wpi_nic_lock(sc) == 0) { WPI_WRITE(sc, WPI_FH_RX_CONFIG, 0); for (ntries = 0; ntries < 1000; ntries++) { if (WPI_READ(sc, WPI_FH_RX_STATUS) & WPI_FH_RX_STATUS_IDLE) break; DELAY(10); } wpi_nic_unlock(sc); } ring->cur = 0; ring->update = 0; } static void wpi_free_rx_ring(struct wpi_softc *sc) { struct wpi_rx_ring *ring = &sc->rxq; int i; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); wpi_dma_contig_free(&ring->desc_dma); for (i = 0; i < WPI_RX_RING_COUNT; i++) { struct wpi_rx_data *data = &ring->data[i]; if (data->m != NULL) { bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(ring->data_dmat, data->map); m_freem(data->m); data->m = NULL; } if (data->map != NULL) bus_dmamap_destroy(ring->data_dmat, data->map); } if (ring->data_dmat != NULL) { bus_dma_tag_destroy(ring->data_dmat); ring->data_dmat = NULL; } } static int wpi_alloc_tx_ring(struct wpi_softc *sc, struct wpi_tx_ring *ring, uint8_t qid) { bus_addr_t paddr; bus_size_t size; int i, error; ring->qid = qid; ring->queued = 0; ring->cur = 0; ring->pending = 0; ring->update = 0; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_BEGIN, __func__); /* Allocate TX descriptors (16KB aligned.) */ size = WPI_TX_RING_COUNT * sizeof (struct wpi_tx_desc); error = wpi_dma_contig_alloc(sc, &ring->desc_dma, (void **)&ring->desc, size, WPI_RING_DMA_ALIGN); if (error != 0) { device_printf(sc->sc_dev, "%s: could not allocate TX ring DMA memory, error %d\n", __func__, error); goto fail; } /* Update shared area with ring physical address. */ sc->shared->txbase[qid] = htole32(ring->desc_dma.paddr); bus_dmamap_sync(sc->shared_dma.tag, sc->shared_dma.map, BUS_DMASYNC_PREWRITE); size = WPI_TX_RING_COUNT * sizeof (struct wpi_tx_cmd); error = wpi_dma_contig_alloc(sc, &ring->cmd_dma, (void **)&ring->cmd, size, 4); if (error != 0) { device_printf(sc->sc_dev, "%s: could not allocate TX cmd DMA memory, error %d\n", __func__, error); goto fail; } error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), 1, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, MCLBYTES, - WPI_MAX_SCATTER - 1, MCLBYTES, BUS_DMA_NOWAIT, NULL, NULL, - &ring->data_dmat); + WPI_MAX_SCATTER - 1, MCLBYTES, 0, NULL, NULL, &ring->data_dmat); if (error != 0) { device_printf(sc->sc_dev, "%s: could not create TX buf DMA tag, error %d\n", __func__, error); goto fail; } paddr = ring->cmd_dma.paddr; for (i = 0; i < WPI_TX_RING_COUNT; i++) { struct wpi_tx_data *data = &ring->data[i]; data->cmd_paddr = paddr; paddr += sizeof (struct wpi_tx_cmd); error = bus_dmamap_create(ring->data_dmat, 0, &data->map); if (error != 0) { device_printf(sc->sc_dev, "%s: could not create TX buf DMA map, error %d\n", __func__, error); goto fail; } } DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END, __func__); return 0; fail: wpi_free_tx_ring(sc, ring); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END_ERR, __func__); return error; } static void wpi_update_tx_ring(struct wpi_softc *sc, struct wpi_tx_ring *ring) { WPI_WRITE(sc, WPI_HBUS_TARG_WRPTR, ring->qid << 8 | ring->cur); } static void wpi_update_tx_ring_ps(struct wpi_softc *sc, struct wpi_tx_ring *ring) { if (ring->update != 0) { /* Wait for INT_WAKEUP event. */ return; } WPI_SETBITS(sc, WPI_GP_CNTRL, WPI_GP_CNTRL_MAC_ACCESS_REQ); if (WPI_READ(sc, WPI_GP_CNTRL) & WPI_GP_CNTRL_SLEEP) { DPRINTF(sc, WPI_DEBUG_PWRSAVE, "%s (%d): requesting wakeup\n", __func__, ring->qid); ring->update = 1; } else { wpi_update_tx_ring(sc, ring); WPI_CLRBITS(sc, WPI_GP_CNTRL, WPI_GP_CNTRL_MAC_ACCESS_REQ); } } static void wpi_reset_tx_ring(struct wpi_softc *sc, struct wpi_tx_ring *ring) { int i; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); for (i = 0; i < WPI_TX_RING_COUNT; i++) { struct wpi_tx_data *data = &ring->data[i]; if (data->m != NULL) { bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(ring->data_dmat, data->map); m_freem(data->m); data->m = NULL; } if (data->ni != NULL) { ieee80211_free_node(data->ni); data->ni = NULL; } } /* Clear TX descriptors. */ memset(ring->desc, 0, ring->desc_dma.size); bus_dmamap_sync(ring->desc_dma.tag, ring->desc_dma.map, BUS_DMASYNC_PREWRITE); ring->queued = 0; ring->cur = 0; ring->pending = 0; ring->update = 0; } static void wpi_free_tx_ring(struct wpi_softc *sc, struct wpi_tx_ring *ring) { int i; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); wpi_dma_contig_free(&ring->desc_dma); wpi_dma_contig_free(&ring->cmd_dma); for (i = 0; i < WPI_TX_RING_COUNT; i++) { struct wpi_tx_data *data = &ring->data[i]; if (data->m != NULL) { bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(ring->data_dmat, data->map); m_freem(data->m); } if (data->map != NULL) bus_dmamap_destroy(ring->data_dmat, data->map); } if (ring->data_dmat != NULL) { bus_dma_tag_destroy(ring->data_dmat); ring->data_dmat = NULL; } } /* * Extract various information from EEPROM. */ static int wpi_read_eeprom(struct wpi_softc *sc, uint8_t macaddr[IEEE80211_ADDR_LEN]) { #define WPI_CHK(res) do { \ if ((error = res) != 0) \ goto fail; \ } while (0) uint8_t i; int error; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_BEGIN, __func__); /* Adapter has to be powered on for EEPROM access to work. */ if ((error = wpi_apm_init(sc)) != 0) { device_printf(sc->sc_dev, "%s: could not power ON adapter, error %d\n", __func__, error); return error; } if ((WPI_READ(sc, WPI_EEPROM_GP) & 0x6) == 0) { device_printf(sc->sc_dev, "bad EEPROM signature\n"); error = EIO; goto fail; } /* Clear HW ownership of EEPROM. */ WPI_CLRBITS(sc, WPI_EEPROM_GP, WPI_EEPROM_GP_IF_OWNER); /* Read the hardware capabilities, revision and SKU type. */ WPI_CHK(wpi_read_prom_data(sc, WPI_EEPROM_SKU_CAP, &sc->cap, sizeof(sc->cap))); WPI_CHK(wpi_read_prom_data(sc, WPI_EEPROM_REVISION, &sc->rev, sizeof(sc->rev))); WPI_CHK(wpi_read_prom_data(sc, WPI_EEPROM_TYPE, &sc->type, sizeof(sc->type))); sc->rev = le16toh(sc->rev); DPRINTF(sc, WPI_DEBUG_EEPROM, "cap=%x rev=%x type=%x\n", sc->cap, sc->rev, sc->type); /* Read the regulatory domain (4 ASCII characters.) */ WPI_CHK(wpi_read_prom_data(sc, WPI_EEPROM_DOMAIN, sc->domain, sizeof(sc->domain))); /* Read MAC address. */ WPI_CHK(wpi_read_prom_data(sc, WPI_EEPROM_MAC, macaddr, IEEE80211_ADDR_LEN)); /* Read the list of authorized channels. */ for (i = 0; i < WPI_CHAN_BANDS_COUNT; i++) WPI_CHK(wpi_read_eeprom_channels(sc, i)); /* Read the list of TX power groups. */ for (i = 0; i < WPI_POWER_GROUPS_COUNT; i++) WPI_CHK(wpi_read_eeprom_group(sc, i)); fail: wpi_apm_stop(sc); /* Power OFF adapter. */ DPRINTF(sc, WPI_DEBUG_TRACE, error ? TRACE_STR_END_ERR : TRACE_STR_END, __func__); return error; #undef WPI_CHK } /* * Translate EEPROM flags to net80211. */ static uint32_t wpi_eeprom_channel_flags(struct wpi_eeprom_chan *channel) { uint32_t nflags; nflags = 0; if ((channel->flags & WPI_EEPROM_CHAN_ACTIVE) == 0) nflags |= IEEE80211_CHAN_PASSIVE; if ((channel->flags & WPI_EEPROM_CHAN_IBSS) == 0) nflags |= IEEE80211_CHAN_NOADHOC; if (channel->flags & WPI_EEPROM_CHAN_RADAR) { nflags |= IEEE80211_CHAN_DFS; /* XXX apparently IBSS may still be marked */ nflags |= IEEE80211_CHAN_NOADHOC; } /* XXX HOSTAP uses WPI_MODE_IBSS */ if (nflags & IEEE80211_CHAN_NOADHOC) nflags |= IEEE80211_CHAN_NOHOSTAP; return nflags; } static void wpi_read_eeprom_band(struct wpi_softc *sc, uint8_t n) { struct ieee80211com *ic = &sc->sc_ic; struct wpi_eeprom_chan *channels = sc->eeprom_channels[n]; const struct wpi_chan_band *band = &wpi_bands[n]; struct ieee80211_channel *c; uint32_t nflags; uint8_t chan, i; for (i = 0; i < band->nchan; i++) { if (!(channels[i].flags & WPI_EEPROM_CHAN_VALID)) { DPRINTF(sc, WPI_DEBUG_EEPROM, "Channel Not Valid: %d, band %d\n", band->chan[i],n); continue; } chan = band->chan[i]; nflags = wpi_eeprom_channel_flags(&channels[i]); c = &ic->ic_channels[ic->ic_nchans++]; c->ic_ieee = chan; c->ic_maxregpower = channels[i].maxpwr; c->ic_maxpower = 2*c->ic_maxregpower; if (n == 0) { /* 2GHz band */ c->ic_freq = ieee80211_ieee2mhz(chan, IEEE80211_CHAN_G); /* G =>'s B is supported */ c->ic_flags = IEEE80211_CHAN_B | nflags; c = &ic->ic_channels[ic->ic_nchans++]; c[0] = c[-1]; c->ic_flags = IEEE80211_CHAN_G | nflags; } else { /* 5GHz band */ c->ic_freq = ieee80211_ieee2mhz(chan, IEEE80211_CHAN_A); c->ic_flags = IEEE80211_CHAN_A | nflags; } /* Save maximum allowed TX power for this channel. */ sc->maxpwr[chan] = channels[i].maxpwr; DPRINTF(sc, WPI_DEBUG_EEPROM, "adding chan %d (%dMHz) flags=0x%x maxpwr=%d passive=%d," " offset %d\n", chan, c->ic_freq, channels[i].flags, sc->maxpwr[chan], IEEE80211_IS_CHAN_PASSIVE(c), ic->ic_nchans); } } /** * Read the eeprom to find out what channels are valid for the given * band and update net80211 with what we find. */ static int wpi_read_eeprom_channels(struct wpi_softc *sc, uint8_t n) { struct ieee80211com *ic = &sc->sc_ic; const struct wpi_chan_band *band = &wpi_bands[n]; int error; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_BEGIN, __func__); error = wpi_read_prom_data(sc, band->addr, &sc->eeprom_channels[n], band->nchan * sizeof (struct wpi_eeprom_chan)); if (error != 0) { DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END_ERR, __func__); return error; } wpi_read_eeprom_band(sc, n); ieee80211_sort_channels(ic->ic_channels, ic->ic_nchans); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END, __func__); return 0; } static struct wpi_eeprom_chan * wpi_find_eeprom_channel(struct wpi_softc *sc, struct ieee80211_channel *c) { int i, j; for (j = 0; j < WPI_CHAN_BANDS_COUNT; j++) for (i = 0; i < wpi_bands[j].nchan; i++) if (wpi_bands[j].chan[i] == c->ic_ieee) return &sc->eeprom_channels[j][i]; return NULL; } /* * Enforce flags read from EEPROM. */ static int wpi_setregdomain(struct ieee80211com *ic, struct ieee80211_regdomain *rd, int nchan, struct ieee80211_channel chans[]) { struct wpi_softc *sc = ic->ic_softc; int i; for (i = 0; i < nchan; i++) { struct ieee80211_channel *c = &chans[i]; struct wpi_eeprom_chan *channel; channel = wpi_find_eeprom_channel(sc, c); if (channel == NULL) { ic_printf(ic, "%s: invalid channel %u freq %u/0x%x\n", __func__, c->ic_ieee, c->ic_freq, c->ic_flags); return EINVAL; } c->ic_flags |= wpi_eeprom_channel_flags(channel); } return 0; } static int wpi_read_eeprom_group(struct wpi_softc *sc, uint8_t n) { struct wpi_power_group *group = &sc->groups[n]; struct wpi_eeprom_group rgroup; int i, error; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_BEGIN, __func__); if ((error = wpi_read_prom_data(sc, WPI_EEPROM_POWER_GRP + n * 32, &rgroup, sizeof rgroup)) != 0) { DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END_ERR, __func__); return error; } /* Save TX power group information. */ group->chan = rgroup.chan; group->maxpwr = rgroup.maxpwr; /* Retrieve temperature at which the samples were taken. */ group->temp = (int16_t)le16toh(rgroup.temp); DPRINTF(sc, WPI_DEBUG_EEPROM, "power group %d: chan=%d maxpwr=%d temp=%d\n", n, group->chan, group->maxpwr, group->temp); for (i = 0; i < WPI_SAMPLES_COUNT; i++) { group->samples[i].index = rgroup.samples[i].index; group->samples[i].power = rgroup.samples[i].power; DPRINTF(sc, WPI_DEBUG_EEPROM, "\tsample %d: index=%d power=%d\n", i, group->samples[i].index, group->samples[i].power); } DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END, __func__); return 0; } static __inline uint8_t wpi_add_node_entry_adhoc(struct wpi_softc *sc) { uint8_t newid = WPI_ID_IBSS_MIN; for (; newid <= WPI_ID_IBSS_MAX; newid++) { if ((sc->nodesmsk & (1 << newid)) == 0) { sc->nodesmsk |= 1 << newid; return newid; } } return WPI_ID_UNDEFINED; } static __inline uint8_t wpi_add_node_entry_sta(struct wpi_softc *sc) { sc->nodesmsk |= 1 << WPI_ID_BSS; return WPI_ID_BSS; } static __inline int wpi_check_node_entry(struct wpi_softc *sc, uint8_t id) { if (id == WPI_ID_UNDEFINED) return 0; return (sc->nodesmsk >> id) & 1; } static __inline void wpi_clear_node_table(struct wpi_softc *sc) { sc->nodesmsk = 0; } static __inline void wpi_del_node_entry(struct wpi_softc *sc, uint8_t id) { sc->nodesmsk &= ~(1 << id); } static struct ieee80211_node * wpi_node_alloc(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN]) { struct wpi_node *wn; wn = malloc(sizeof (struct wpi_node), M_80211_NODE, M_NOWAIT | M_ZERO); if (wn == NULL) return NULL; wn->id = WPI_ID_UNDEFINED; return &wn->ni; } static void wpi_node_free(struct ieee80211_node *ni) { struct wpi_softc *sc = ni->ni_ic->ic_softc; struct wpi_node *wn = WPI_NODE(ni); if (wn->id != WPI_ID_UNDEFINED) { WPI_NT_LOCK(sc); if (wpi_check_node_entry(sc, wn->id)) { wpi_del_node_entry(sc, wn->id); wpi_del_node(sc, ni); } WPI_NT_UNLOCK(sc); } sc->sc_node_free(ni); } static __inline int wpi_check_bss_filter(struct wpi_softc *sc) { return (sc->rxon.filter & htole32(WPI_FILTER_BSS)) != 0; } static void wpi_ibss_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m, int subtype, const struct ieee80211_rx_stats *rxs, int rssi, int nf) { struct ieee80211vap *vap = ni->ni_vap; struct wpi_softc *sc = vap->iv_ic->ic_softc; struct wpi_vap *wvp = WPI_VAP(vap); uint64_t ni_tstamp, rx_tstamp; wvp->wv_recv_mgmt(ni, m, subtype, rxs, rssi, nf); if (vap->iv_state == IEEE80211_S_RUN && (subtype == IEEE80211_FC0_SUBTYPE_BEACON || subtype == IEEE80211_FC0_SUBTYPE_PROBE_RESP)) { ni_tstamp = le64toh(ni->ni_tstamp.tsf); rx_tstamp = le64toh(sc->rx_tstamp); if (ni_tstamp >= rx_tstamp) { DPRINTF(sc, WPI_DEBUG_STATE, "ibss merge, tsf %ju tstamp %ju\n", (uintmax_t)rx_tstamp, (uintmax_t)ni_tstamp); (void) ieee80211_ibss_merge(ni); } } } static void wpi_restore_node(void *arg, struct ieee80211_node *ni) { struct wpi_softc *sc = arg; struct wpi_node *wn = WPI_NODE(ni); int error; WPI_NT_LOCK(sc); if (wn->id != WPI_ID_UNDEFINED) { wn->id = WPI_ID_UNDEFINED; if ((error = wpi_add_ibss_node(sc, ni)) != 0) { device_printf(sc->sc_dev, "%s: could not add IBSS node, error %d\n", __func__, error); } } WPI_NT_UNLOCK(sc); } static void wpi_restore_node_table(struct wpi_softc *sc, struct wpi_vap *wvp) { struct ieee80211com *ic = &sc->sc_ic; /* Set group keys once. */ WPI_NT_LOCK(sc); wvp->wv_gtk = 0; WPI_NT_UNLOCK(sc); ieee80211_iterate_nodes(&ic->ic_sta, wpi_restore_node, sc); ieee80211_crypto_reload_keys(ic); } /** * Called by net80211 when ever there is a change to 80211 state machine */ static int wpi_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct wpi_vap *wvp = WPI_VAP(vap); struct ieee80211com *ic = vap->iv_ic; struct wpi_softc *sc = ic->ic_softc; int error = 0; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_BEGIN, __func__); WPI_TXQ_LOCK(sc); if (nstate > IEEE80211_S_INIT && sc->sc_running == 0) { DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END_ERR, __func__); WPI_TXQ_UNLOCK(sc); return ENXIO; } WPI_TXQ_UNLOCK(sc); DPRINTF(sc, WPI_DEBUG_STATE, "%s: %s -> %s\n", __func__, ieee80211_state_name[vap->iv_state], ieee80211_state_name[nstate]); if (vap->iv_state == IEEE80211_S_RUN && nstate < IEEE80211_S_RUN) { if ((error = wpi_set_pslevel(sc, 0, 0, 1)) != 0) { device_printf(sc->sc_dev, "%s: could not set power saving level\n", __func__); return error; } wpi_set_led(sc, WPI_LED_LINK, 1, 0); } switch (nstate) { case IEEE80211_S_SCAN: WPI_RXON_LOCK(sc); if (wpi_check_bss_filter(sc) != 0) { sc->rxon.filter &= ~htole32(WPI_FILTER_BSS); if ((error = wpi_send_rxon(sc, 0, 1)) != 0) { device_printf(sc->sc_dev, "%s: could not send RXON\n", __func__); } } WPI_RXON_UNLOCK(sc); break; case IEEE80211_S_ASSOC: if (vap->iv_state != IEEE80211_S_RUN) break; /* FALLTHROUGH */ case IEEE80211_S_AUTH: /* * NB: do not optimize AUTH -> AUTH state transmission - * this will break powersave with non-QoS AP! */ /* * The node must be registered in the firmware before auth. * Also the associd must be cleared on RUN -> ASSOC * transitions. */ if ((error = wpi_auth(sc, vap)) != 0) { device_printf(sc->sc_dev, "%s: could not move to AUTH state, error %d\n", __func__, error); } break; case IEEE80211_S_RUN: /* * RUN -> RUN transition: * STA mode: Just restart the timers. * IBSS mode: Process IBSS merge. */ if (vap->iv_state == IEEE80211_S_RUN) { if (vap->iv_opmode != IEEE80211_M_IBSS) { WPI_RXON_LOCK(sc); wpi_calib_timeout(sc); WPI_RXON_UNLOCK(sc); break; } else { /* * Drop the BSS_FILTER bit * (there is no another way to change bssid). */ WPI_RXON_LOCK(sc); sc->rxon.filter &= ~htole32(WPI_FILTER_BSS); if ((error = wpi_send_rxon(sc, 0, 1)) != 0) { device_printf(sc->sc_dev, "%s: could not send RXON\n", __func__); } WPI_RXON_UNLOCK(sc); /* Restore all what was lost. */ wpi_restore_node_table(sc, wvp); /* XXX set conditionally? */ wpi_updateedca(ic); } } /* * !RUN -> RUN requires setting the association id * which is done with a firmware cmd. We also defer * starting the timers until that work is done. */ if ((error = wpi_run(sc, vap)) != 0) { device_printf(sc->sc_dev, "%s: could not move to RUN state\n", __func__); } break; default: break; } if (error != 0) { DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END_ERR, __func__); return error; } DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END, __func__); return wvp->wv_newstate(vap, nstate, arg); } static void wpi_calib_timeout(void *arg) { struct wpi_softc *sc = arg; if (wpi_check_bss_filter(sc) == 0) return; wpi_power_calibration(sc); callout_reset(&sc->calib_to, 60*hz, wpi_calib_timeout, sc); } static __inline uint8_t rate2plcp(const uint8_t rate) { switch (rate) { case 12: return 0xd; case 18: return 0xf; case 24: return 0x5; case 36: return 0x7; case 48: return 0x9; case 72: return 0xb; case 96: return 0x1; case 108: return 0x3; case 2: return 10; case 4: return 20; case 11: return 55; case 22: return 110; default: return 0; } } static __inline uint8_t plcp2rate(const uint8_t plcp) { switch (plcp) { case 0xd: return 12; case 0xf: return 18; case 0x5: return 24; case 0x7: return 36; case 0x9: return 48; case 0xb: return 72; case 0x1: return 96; case 0x3: return 108; case 10: return 2; case 20: return 4; case 55: return 11; case 110: return 22; default: return 0; } } /* Quickly determine if a given rate is CCK or OFDM. */ #define WPI_RATE_IS_OFDM(rate) ((rate) >= 12 && (rate) != 22) static void wpi_rx_done(struct wpi_softc *sc, struct wpi_rx_desc *desc, struct wpi_rx_data *data) { struct ieee80211com *ic = &sc->sc_ic; struct wpi_rx_ring *ring = &sc->rxq; struct wpi_rx_stat *stat; struct wpi_rx_head *head; struct wpi_rx_tail *tail; struct ieee80211_frame *wh; struct ieee80211_node *ni; struct mbuf *m, *m1; bus_addr_t paddr; uint32_t flags; uint16_t len; int error; stat = (struct wpi_rx_stat *)(desc + 1); if (__predict_false(stat->len > WPI_STAT_MAXLEN)) { device_printf(sc->sc_dev, "invalid RX statistic header\n"); goto fail1; } bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTREAD); head = (struct wpi_rx_head *)((caddr_t)(stat + 1) + stat->len); len = le16toh(head->len); tail = (struct wpi_rx_tail *)((caddr_t)(head + 1) + len); flags = le32toh(tail->flags); DPRINTF(sc, WPI_DEBUG_RECV, "%s: idx %d len %d stat len %u rssi %d" " rate %x chan %d tstamp %ju\n", __func__, ring->cur, le32toh(desc->len), len, (int8_t)stat->rssi, head->plcp, head->chan, (uintmax_t)le64toh(tail->tstamp)); /* Discard frames with a bad FCS early. */ if ((flags & WPI_RX_NOERROR) != WPI_RX_NOERROR) { DPRINTF(sc, WPI_DEBUG_RECV, "%s: RX flags error %x\n", __func__, flags); goto fail1; } /* Discard frames that are too short. */ if (len < sizeof (struct ieee80211_frame_ack)) { DPRINTF(sc, WPI_DEBUG_RECV, "%s: frame too short: %d\n", __func__, len); goto fail1; } m1 = m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR, MJUMPAGESIZE); if (__predict_false(m1 == NULL)) { DPRINTF(sc, WPI_DEBUG_ANY, "%s: no mbuf to restock ring\n", __func__); goto fail1; } bus_dmamap_unload(ring->data_dmat, data->map); error = bus_dmamap_load(ring->data_dmat, data->map, mtod(m1, void *), MJUMPAGESIZE, wpi_dma_map_addr, &paddr, BUS_DMA_NOWAIT); if (__predict_false(error != 0 && error != EFBIG)) { device_printf(sc->sc_dev, "%s: bus_dmamap_load failed, error %d\n", __func__, error); m_freem(m1); /* Try to reload the old mbuf. */ error = bus_dmamap_load(ring->data_dmat, data->map, mtod(data->m, void *), MJUMPAGESIZE, wpi_dma_map_addr, &paddr, BUS_DMA_NOWAIT); if (error != 0 && error != EFBIG) { panic("%s: could not load old RX mbuf", __func__); } /* Physical address may have changed. */ ring->desc[ring->cur] = htole32(paddr); bus_dmamap_sync(ring->data_dmat, ring->desc_dma.map, BUS_DMASYNC_PREWRITE); goto fail1; } m = data->m; data->m = m1; /* Update RX descriptor. */ ring->desc[ring->cur] = htole32(paddr); bus_dmamap_sync(ring->desc_dma.tag, ring->desc_dma.map, BUS_DMASYNC_PREWRITE); /* Finalize mbuf. */ m->m_data = (caddr_t)(head + 1); m->m_pkthdr.len = m->m_len = len; /* Grab a reference to the source node. */ wh = mtod(m, struct ieee80211_frame *); if ((wh->i_fc[1] & IEEE80211_FC1_PROTECTED) && (flags & WPI_RX_CIPHER_MASK) == WPI_RX_CIPHER_CCMP) { /* Check whether decryption was successful or not. */ if ((flags & WPI_RX_DECRYPT_MASK) != WPI_RX_DECRYPT_OK) { DPRINTF(sc, WPI_DEBUG_RECV, "CCMP decryption failed 0x%x\n", flags); goto fail2; } m->m_flags |= M_WEP; } if (len >= sizeof(struct ieee80211_frame_min)) ni = ieee80211_find_rxnode(ic, (struct ieee80211_frame_min *)wh); else ni = NULL; sc->rx_tstamp = tail->tstamp; if (ieee80211_radiotap_active(ic)) { struct wpi_rx_radiotap_header *tap = &sc->sc_rxtap; tap->wr_flags = 0; if (head->flags & htole16(WPI_STAT_FLAG_SHPREAMBLE)) tap->wr_flags |= IEEE80211_RADIOTAP_F_SHORTPRE; tap->wr_dbm_antsignal = (int8_t)(stat->rssi + WPI_RSSI_OFFSET); tap->wr_dbm_antnoise = WPI_RSSI_OFFSET; tap->wr_tsft = tail->tstamp; tap->wr_antenna = (le16toh(head->flags) >> 4) & 0xf; tap->wr_rate = plcp2rate(head->plcp); } WPI_UNLOCK(sc); /* Send the frame to the 802.11 layer. */ if (ni != NULL) { (void)ieee80211_input(ni, m, stat->rssi, WPI_RSSI_OFFSET); /* Node is no longer needed. */ ieee80211_free_node(ni); } else (void)ieee80211_input_all(ic, m, stat->rssi, WPI_RSSI_OFFSET); WPI_LOCK(sc); return; fail2: m_freem(m); fail1: counter_u64_add(ic->ic_ierrors, 1); } static void wpi_rx_statistics(struct wpi_softc *sc, struct wpi_rx_desc *desc, struct wpi_rx_data *data) { /* Ignore */ } static void wpi_tx_done(struct wpi_softc *sc, struct wpi_rx_desc *desc) { struct wpi_tx_ring *ring = &sc->txq[desc->qid & 0x3]; struct wpi_tx_data *data = &ring->data[desc->idx]; struct wpi_tx_stat *stat = (struct wpi_tx_stat *)(desc + 1); struct mbuf *m; struct ieee80211_node *ni; struct ieee80211vap *vap; struct ieee80211com *ic; uint32_t status = le32toh(stat->status); int ackfailcnt = stat->ackfailcnt / WPI_NTRIES_DEFAULT; KASSERT(data->ni != NULL, ("no node")); KASSERT(data->m != NULL, ("no mbuf")); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_BEGIN, __func__); DPRINTF(sc, WPI_DEBUG_XMIT, "%s: " "qid %d idx %d retries %d btkillcnt %d rate %x duration %d " "status %x\n", __func__, desc->qid, desc->idx, stat->ackfailcnt, stat->btkillcnt, stat->rate, le32toh(stat->duration), status); /* Unmap and free mbuf. */ bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(ring->data_dmat, data->map); m = data->m, data->m = NULL; ni = data->ni, data->ni = NULL; vap = ni->ni_vap; ic = vap->iv_ic; /* * Update rate control statistics for the node. */ if (status & WPI_TX_STATUS_FAIL) { ieee80211_ratectl_tx_complete(vap, ni, IEEE80211_RATECTL_TX_FAILURE, &ackfailcnt, NULL); } else ieee80211_ratectl_tx_complete(vap, ni, IEEE80211_RATECTL_TX_SUCCESS, &ackfailcnt, NULL); ieee80211_tx_complete(ni, m, (status & WPI_TX_STATUS_FAIL) != 0); WPI_TXQ_STATE_LOCK(sc); if (--ring->queued > 0) callout_reset(&sc->tx_timeout, 5*hz, wpi_tx_timeout, sc); else callout_stop(&sc->tx_timeout); WPI_TXQ_STATE_UNLOCK(sc); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END, __func__); } /* * Process a "command done" firmware notification. This is where we wakeup * processes waiting for a synchronous command completion. */ static void wpi_cmd_done(struct wpi_softc *sc, struct wpi_rx_desc *desc) { struct wpi_tx_ring *ring = &sc->txq[WPI_CMD_QUEUE_NUM]; struct wpi_tx_data *data; struct wpi_tx_cmd *cmd; DPRINTF(sc, WPI_DEBUG_CMD, "cmd notification qid %x idx %d flags %x " "type %s len %d\n", desc->qid, desc->idx, desc->flags, wpi_cmd_str(desc->type), le32toh(desc->len)); if ((desc->qid & WPI_RX_DESC_QID_MSK) != WPI_CMD_QUEUE_NUM) return; /* Not a command ack. */ KASSERT(ring->queued == 0, ("ring->queued must be 0")); data = &ring->data[desc->idx]; cmd = &ring->cmd[desc->idx]; /* If the command was mapped in an mbuf, free it. */ if (data->m != NULL) { bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(ring->data_dmat, data->map); m_freem(data->m); data->m = NULL; } wakeup(cmd); if (desc->type == WPI_CMD_SET_POWER_MODE) { struct wpi_pmgt_cmd *pcmd = (struct wpi_pmgt_cmd *)cmd->data; bus_dmamap_sync(ring->data_dmat, ring->cmd_dma.map, BUS_DMASYNC_POSTREAD); WPI_TXQ_LOCK(sc); if (le16toh(pcmd->flags) & WPI_PS_ALLOW_SLEEP) { sc->sc_update_rx_ring = wpi_update_rx_ring_ps; sc->sc_update_tx_ring = wpi_update_tx_ring_ps; } else { sc->sc_update_rx_ring = wpi_update_rx_ring; sc->sc_update_tx_ring = wpi_update_tx_ring; } WPI_TXQ_UNLOCK(sc); } } static void wpi_notif_intr(struct wpi_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); uint32_t hw; bus_dmamap_sync(sc->shared_dma.tag, sc->shared_dma.map, BUS_DMASYNC_POSTREAD); hw = le32toh(sc->shared->next) & 0xfff; hw = (hw == 0) ? WPI_RX_RING_COUNT - 1 : hw - 1; while (sc->rxq.cur != hw) { sc->rxq.cur = (sc->rxq.cur + 1) % WPI_RX_RING_COUNT; struct wpi_rx_data *data = &sc->rxq.data[sc->rxq.cur]; struct wpi_rx_desc *desc; bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD); desc = mtod(data->m, struct wpi_rx_desc *); DPRINTF(sc, WPI_DEBUG_NOTIFY, "%s: cur=%d; qid %x idx %d flags %x type %d(%s) len %d\n", __func__, sc->rxq.cur, desc->qid, desc->idx, desc->flags, desc->type, wpi_cmd_str(desc->type), le32toh(desc->len)); if (!(desc->qid & WPI_UNSOLICITED_RX_NOTIF)) { /* Reply to a command. */ wpi_cmd_done(sc, desc); } switch (desc->type) { case WPI_RX_DONE: /* An 802.11 frame has been received. */ wpi_rx_done(sc, desc, data); if (__predict_false(sc->sc_running == 0)) { /* wpi_stop() was called. */ return; } break; case WPI_TX_DONE: /* An 802.11 frame has been transmitted. */ wpi_tx_done(sc, desc); break; case WPI_RX_STATISTICS: case WPI_BEACON_STATISTICS: wpi_rx_statistics(sc, desc, data); break; case WPI_BEACON_MISSED: { struct wpi_beacon_missed *miss = (struct wpi_beacon_missed *)(desc + 1); uint32_t expected, misses, received, threshold; bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD); misses = le32toh(miss->consecutive); expected = le32toh(miss->expected); received = le32toh(miss->received); threshold = MAX(2, vap->iv_bmissthreshold); DPRINTF(sc, WPI_DEBUG_BMISS, "%s: beacons missed %u(%u) (received %u/%u)\n", __func__, misses, le32toh(miss->total), received, expected); if (misses >= threshold || (received == 0 && expected >= threshold)) { WPI_RXON_LOCK(sc); if (callout_pending(&sc->scan_timeout)) { wpi_cmd(sc, WPI_CMD_SCAN_ABORT, NULL, 0, 1); } WPI_RXON_UNLOCK(sc); if (vap->iv_state == IEEE80211_S_RUN && (ic->ic_flags & IEEE80211_F_SCAN) == 0) ieee80211_beacon_miss(ic); } break; } #ifdef WPI_DEBUG case WPI_BEACON_SENT: { struct wpi_tx_stat *stat = (struct wpi_tx_stat *)(desc + 1); uint64_t *tsf = (uint64_t *)(stat + 1); uint32_t *mode = (uint32_t *)(tsf + 1); bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD); DPRINTF(sc, WPI_DEBUG_BEACON, "beacon sent: rts %u, ack %u, btkill %u, rate %u, " "duration %u, status %x, tsf %ju, mode %x\n", stat->rtsfailcnt, stat->ackfailcnt, stat->btkillcnt, stat->rate, le32toh(stat->duration), le32toh(stat->status), le64toh(*tsf), le32toh(*mode)); break; } #endif case WPI_UC_READY: { struct wpi_ucode_info *uc = (struct wpi_ucode_info *)(desc + 1); /* The microcontroller is ready. */ bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD); DPRINTF(sc, WPI_DEBUG_RESET, "microcode alive notification version=%d.%d " "subtype=%x alive=%x\n", uc->major, uc->minor, uc->subtype, le32toh(uc->valid)); if (le32toh(uc->valid) != 1) { device_printf(sc->sc_dev, "microcontroller initialization failed\n"); wpi_stop_locked(sc); return; } /* Save the address of the error log in SRAM. */ sc->errptr = le32toh(uc->errptr); break; } case WPI_STATE_CHANGED: { bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD); uint32_t *status = (uint32_t *)(desc + 1); DPRINTF(sc, WPI_DEBUG_STATE, "state changed to %x\n", le32toh(*status)); if (le32toh(*status) & 1) { WPI_NT_LOCK(sc); wpi_clear_node_table(sc); WPI_NT_UNLOCK(sc); taskqueue_enqueue(sc->sc_tq, &sc->sc_radiooff_task); return; } break; } #ifdef WPI_DEBUG case WPI_START_SCAN: { bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD); struct wpi_start_scan *scan = (struct wpi_start_scan *)(desc + 1); DPRINTF(sc, WPI_DEBUG_SCAN, "%s: scanning channel %d status %x\n", __func__, scan->chan, le32toh(scan->status)); break; } #endif case WPI_STOP_SCAN: { bus_dmamap_sync(sc->rxq.data_dmat, data->map, BUS_DMASYNC_POSTREAD); struct wpi_stop_scan *scan = (struct wpi_stop_scan *)(desc + 1); DPRINTF(sc, WPI_DEBUG_SCAN, "scan finished nchan=%d status=%d chan=%d\n", scan->nchan, scan->status, scan->chan); WPI_RXON_LOCK(sc); callout_stop(&sc->scan_timeout); WPI_RXON_UNLOCK(sc); if (scan->status == WPI_SCAN_ABORTED) ieee80211_cancel_scan(vap); else ieee80211_scan_next(vap); break; } } if (sc->rxq.cur % 8 == 0) { /* Tell the firmware what we have processed. */ sc->sc_update_rx_ring(sc); } } } /* * Process an INT_WAKEUP interrupt raised when the microcontroller wakes up * from power-down sleep mode. */ static void wpi_wakeup_intr(struct wpi_softc *sc) { int qid; DPRINTF(sc, WPI_DEBUG_PWRSAVE, "%s: ucode wakeup from power-down sleep\n", __func__); /* Wakeup RX and TX rings. */ if (sc->rxq.update) { sc->rxq.update = 0; wpi_update_rx_ring(sc); } WPI_TXQ_LOCK(sc); for (qid = 0; qid < WPI_DRV_NTXQUEUES; qid++) { struct wpi_tx_ring *ring = &sc->txq[qid]; if (ring->update) { ring->update = 0; wpi_update_tx_ring(sc, ring); } } WPI_CLRBITS(sc, WPI_GP_CNTRL, WPI_GP_CNTRL_MAC_ACCESS_REQ); WPI_TXQ_UNLOCK(sc); } /* * This function prints firmware registers */ #ifdef WPI_DEBUG static void wpi_debug_registers(struct wpi_softc *sc) { size_t i; static const uint32_t csr_tbl[] = { WPI_HW_IF_CONFIG, WPI_INT, WPI_INT_MASK, WPI_FH_INT, WPI_GPIO_IN, WPI_RESET, WPI_GP_CNTRL, WPI_EEPROM, WPI_EEPROM_GP, WPI_GIO, WPI_UCODE_GP1, WPI_UCODE_GP2, WPI_GIO_CHICKEN, WPI_ANA_PLL, WPI_DBG_HPET_MEM, }; static const uint32_t prph_tbl[] = { WPI_APMG_CLK_CTRL, WPI_APMG_PS, WPI_APMG_PCI_STT, WPI_APMG_RFKILL, }; DPRINTF(sc, WPI_DEBUG_REGISTER,"%s","\n"); for (i = 0; i < nitems(csr_tbl); i++) { DPRINTF(sc, WPI_DEBUG_REGISTER, " %-18s: 0x%08x ", wpi_get_csr_string(csr_tbl[i]), WPI_READ(sc, csr_tbl[i])); if ((i + 1) % 2 == 0) DPRINTF(sc, WPI_DEBUG_REGISTER, "\n"); } DPRINTF(sc, WPI_DEBUG_REGISTER, "\n\n"); if (wpi_nic_lock(sc) == 0) { for (i = 0; i < nitems(prph_tbl); i++) { DPRINTF(sc, WPI_DEBUG_REGISTER, " %-18s: 0x%08x ", wpi_get_prph_string(prph_tbl[i]), wpi_prph_read(sc, prph_tbl[i])); if ((i + 1) % 2 == 0) DPRINTF(sc, WPI_DEBUG_REGISTER, "\n"); } DPRINTF(sc, WPI_DEBUG_REGISTER, "\n"); wpi_nic_unlock(sc); } else { DPRINTF(sc, WPI_DEBUG_REGISTER, "Cannot access internal registers.\n"); } } #endif /* * Dump the error log of the firmware when a firmware panic occurs. Although * we can't debug the firmware because it is neither open source nor free, it * can help us to identify certain classes of problems. */ static void wpi_fatal_intr(struct wpi_softc *sc) { struct wpi_fw_dump dump; uint32_t i, offset, count; /* Check that the error log address is valid. */ if (sc->errptr < WPI_FW_DATA_BASE || sc->errptr + sizeof (dump) > WPI_FW_DATA_BASE + WPI_FW_DATA_MAXSZ) { printf("%s: bad firmware error log address 0x%08x\n", __func__, sc->errptr); return; } if (wpi_nic_lock(sc) != 0) { printf("%s: could not read firmware error log\n", __func__); return; } /* Read number of entries in the log. */ count = wpi_mem_read(sc, sc->errptr); if (count == 0 || count * sizeof (dump) > WPI_FW_DATA_MAXSZ) { printf("%s: invalid count field (count = %u)\n", __func__, count); wpi_nic_unlock(sc); return; } /* Skip "count" field. */ offset = sc->errptr + sizeof (uint32_t); printf("firmware error log (count = %u):\n", count); for (i = 0; i < count; i++) { wpi_mem_read_region_4(sc, offset, (uint32_t *)&dump, sizeof (dump) / sizeof (uint32_t)); printf(" error type = \"%s\" (0x%08X)\n", (dump.desc < nitems(wpi_fw_errmsg)) ? wpi_fw_errmsg[dump.desc] : "UNKNOWN", dump.desc); printf(" error data = 0x%08X\n", dump.data); printf(" branch link = 0x%08X%08X\n", dump.blink[0], dump.blink[1]); printf(" interrupt link = 0x%08X%08X\n", dump.ilink[0], dump.ilink[1]); printf(" time = %u\n", dump.time); offset += sizeof (dump); } wpi_nic_unlock(sc); /* Dump driver status (TX and RX rings) while we're here. */ printf("driver status:\n"); WPI_TXQ_LOCK(sc); for (i = 0; i < WPI_DRV_NTXQUEUES; i++) { struct wpi_tx_ring *ring = &sc->txq[i]; printf(" tx ring %2d: qid=%-2d cur=%-3d queued=%-3d\n", i, ring->qid, ring->cur, ring->queued); } WPI_TXQ_UNLOCK(sc); printf(" rx ring: cur=%d\n", sc->rxq.cur); } static void wpi_intr(void *arg) { struct wpi_softc *sc = arg; uint32_t r1, r2; WPI_LOCK(sc); /* Disable interrupts. */ WPI_WRITE(sc, WPI_INT_MASK, 0); r1 = WPI_READ(sc, WPI_INT); if (__predict_false(r1 == 0xffffffff || (r1 & 0xfffffff0) == 0xa5a5a5a0)) goto end; /* Hardware gone! */ r2 = WPI_READ(sc, WPI_FH_INT); DPRINTF(sc, WPI_DEBUG_INTR, "%s: reg1=0x%08x reg2=0x%08x\n", __func__, r1, r2); if (r1 == 0 && r2 == 0) goto done; /* Interrupt not for us. */ /* Acknowledge interrupts. */ WPI_WRITE(sc, WPI_INT, r1); WPI_WRITE(sc, WPI_FH_INT, r2); if (__predict_false(r1 & (WPI_INT_SW_ERR | WPI_INT_HW_ERR))) { device_printf(sc->sc_dev, "fatal firmware error\n"); #ifdef WPI_DEBUG wpi_debug_registers(sc); #endif wpi_fatal_intr(sc); DPRINTF(sc, WPI_DEBUG_HW, "(%s)\n", (r1 & WPI_INT_SW_ERR) ? "(Software Error)" : "(Hardware Error)"); taskqueue_enqueue(sc->sc_tq, &sc->sc_reinittask); goto end; } if ((r1 & (WPI_INT_FH_RX | WPI_INT_SW_RX)) || (r2 & WPI_FH_INT_RX)) wpi_notif_intr(sc); if (r1 & WPI_INT_ALIVE) wakeup(sc); /* Firmware is alive. */ if (r1 & WPI_INT_WAKEUP) wpi_wakeup_intr(sc); done: /* Re-enable interrupts. */ if (__predict_true(sc->sc_running)) WPI_WRITE(sc, WPI_INT_MASK, WPI_INT_MASK_DEF); end: WPI_UNLOCK(sc); } static void wpi_free_txfrags(struct wpi_softc *sc, uint16_t ac) { struct wpi_tx_ring *ring; struct wpi_tx_data *data; uint8_t cur; WPI_TXQ_LOCK(sc); ring = &sc->txq[ac]; while (ring->pending != 0) { ring->pending--; cur = (ring->cur + ring->pending) % WPI_TX_RING_COUNT; data = &ring->data[cur]; bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(ring->data_dmat, data->map); m_freem(data->m); data->m = NULL; ieee80211_node_decref(data->ni); data->ni = NULL; } WPI_TXQ_UNLOCK(sc); } static int wpi_cmd2(struct wpi_softc *sc, struct wpi_buf *buf) { struct ieee80211_frame *wh; struct wpi_tx_cmd *cmd; struct wpi_tx_data *data; struct wpi_tx_desc *desc; struct wpi_tx_ring *ring; struct mbuf *m1; bus_dma_segment_t *seg, segs[WPI_MAX_SCATTER]; uint8_t cur, pad; uint16_t hdrlen; int error, i, nsegs, totlen, frag; WPI_TXQ_LOCK(sc); KASSERT(buf->size <= sizeof(buf->data), ("buffer overflow")); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_BEGIN, __func__); if (__predict_false(sc->sc_running == 0)) { /* wpi_stop() was called */ error = ENETDOWN; goto end; } wh = mtod(buf->m, struct ieee80211_frame *); hdrlen = ieee80211_anyhdrsize(wh); totlen = buf->m->m_pkthdr.len; frag = ((buf->m->m_flags & (M_FRAG | M_LASTFRAG)) == M_FRAG); if (__predict_false(totlen < sizeof(struct ieee80211_frame_min))) { error = EINVAL; goto end; } if (hdrlen & 3) { /* First segment length must be a multiple of 4. */ pad = 4 - (hdrlen & 3); } else pad = 0; ring = &sc->txq[buf->ac]; cur = (ring->cur + ring->pending) % WPI_TX_RING_COUNT; desc = &ring->desc[cur]; data = &ring->data[cur]; /* Prepare TX firmware command. */ cmd = &ring->cmd[cur]; cmd->code = buf->code; cmd->flags = 0; cmd->qid = ring->qid; cmd->idx = cur; memcpy(cmd->data, buf->data, buf->size); /* Save and trim IEEE802.11 header. */ memcpy((uint8_t *)(cmd->data + buf->size), wh, hdrlen); m_adj(buf->m, hdrlen); error = bus_dmamap_load_mbuf_sg(ring->data_dmat, data->map, buf->m, segs, &nsegs, BUS_DMA_NOWAIT); if (error != 0 && error != EFBIG) { device_printf(sc->sc_dev, "%s: can't map mbuf (error %d)\n", __func__, error); goto end; } if (error != 0) { /* Too many DMA segments, linearize mbuf. */ m1 = m_collapse(buf->m, M_NOWAIT, WPI_MAX_SCATTER - 1); if (m1 == NULL) { device_printf(sc->sc_dev, "%s: could not defrag mbuf\n", __func__); error = ENOBUFS; goto end; } buf->m = m1; error = bus_dmamap_load_mbuf_sg(ring->data_dmat, data->map, buf->m, segs, &nsegs, BUS_DMA_NOWAIT); if (__predict_false(error != 0)) { /* XXX fix this (applicable to the iwn(4) too) */ /* * NB: Do not return error; * original mbuf does not exist anymore. */ device_printf(sc->sc_dev, "%s: can't map mbuf (error %d)\n", __func__, error); if (ring->qid < WPI_CMD_QUEUE_NUM) { if_inc_counter(buf->ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1); if (!frag) ieee80211_free_node(buf->ni); } m_freem(buf->m); error = 0; goto end; } } KASSERT(nsegs < WPI_MAX_SCATTER, ("too many DMA segments, nsegs (%d) should be less than %d", nsegs, WPI_MAX_SCATTER)); data->m = buf->m; data->ni = buf->ni; DPRINTF(sc, WPI_DEBUG_XMIT, "%s: qid %d idx %d len %d nsegs %d\n", __func__, ring->qid, cur, totlen, nsegs); /* Fill TX descriptor. */ desc->nsegs = WPI_PAD32(totlen + pad) << 4 | (1 + nsegs); /* First DMA segment is used by the TX command. */ desc->segs[0].addr = htole32(data->cmd_paddr); desc->segs[0].len = htole32(4 + buf->size + hdrlen + pad); /* Other DMA segments are for data payload. */ seg = &segs[0]; for (i = 1; i <= nsegs; i++) { desc->segs[i].addr = htole32(seg->ds_addr); desc->segs[i].len = htole32(seg->ds_len); seg++; } bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_PREWRITE); bus_dmamap_sync(ring->data_dmat, ring->cmd_dma.map, BUS_DMASYNC_PREWRITE); bus_dmamap_sync(ring->desc_dma.tag, ring->desc_dma.map, BUS_DMASYNC_PREWRITE); ring->pending += 1; if (!frag) { if (ring->qid < WPI_CMD_QUEUE_NUM) { WPI_TXQ_STATE_LOCK(sc); ring->queued += ring->pending; callout_reset(&sc->tx_timeout, 5*hz, wpi_tx_timeout, sc); WPI_TXQ_STATE_UNLOCK(sc); } /* Kick TX ring. */ ring->cur = (ring->cur + ring->pending) % WPI_TX_RING_COUNT; ring->pending = 0; sc->sc_update_tx_ring(sc, ring); } else ieee80211_node_incref(data->ni); end: DPRINTF(sc, WPI_DEBUG_TRACE, error ? TRACE_STR_END_ERR : TRACE_STR_END, __func__); WPI_TXQ_UNLOCK(sc); return (error); } /* * Construct the data packet for a transmit buffer. */ static int wpi_tx_data(struct wpi_softc *sc, struct mbuf *m, struct ieee80211_node *ni) { const struct ieee80211_txparam *tp; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct wpi_node *wn = WPI_NODE(ni); struct ieee80211_channel *chan; struct ieee80211_frame *wh; struct ieee80211_key *k = NULL; struct wpi_buf tx_data; struct wpi_cmd_data *tx = (struct wpi_cmd_data *)&tx_data.data; uint32_t flags; uint16_t ac, qos; uint8_t tid, type, rate; int swcrypt, ismcast, totlen; wh = mtod(m, struct ieee80211_frame *); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; ismcast = IEEE80211_IS_MULTICAST(wh->i_addr1); swcrypt = 1; /* Select EDCA Access Category and TX ring for this frame. */ if (IEEE80211_QOS_HAS_SEQ(wh)) { qos = ((const struct ieee80211_qosframe *)wh)->i_qos[0]; tid = qos & IEEE80211_QOS_TID; } else { qos = 0; tid = 0; } ac = M_WME_GETAC(m); chan = (ni->ni_chan != IEEE80211_CHAN_ANYC) ? ni->ni_chan : ic->ic_curchan; tp = &vap->iv_txparms[ieee80211_chan2mode(chan)]; /* Choose a TX rate index. */ if (type == IEEE80211_FC0_TYPE_MGT) rate = tp->mgmtrate; else if (ismcast) rate = tp->mcastrate; else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) rate = tp->ucastrate; else if (m->m_flags & M_EAPOL) rate = tp->mgmtrate; else { /* XXX pass pktlen */ (void) ieee80211_ratectl_rate(ni, NULL, 0); rate = ni->ni_txrate; } /* Encrypt the frame if need be. */ if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { /* Retrieve key for TX. */ k = ieee80211_crypto_encap(ni, m); if (k == NULL) return (ENOBUFS); swcrypt = k->wk_flags & IEEE80211_KEY_SWCRYPT; /* 802.11 header may have moved. */ wh = mtod(m, struct ieee80211_frame *); } totlen = m->m_pkthdr.len; if (ieee80211_radiotap_active_vap(vap)) { struct wpi_tx_radiotap_header *tap = &sc->sc_txtap; tap->wt_flags = 0; tap->wt_rate = rate; if (k != NULL) tap->wt_flags |= IEEE80211_RADIOTAP_F_WEP; if (wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG) tap->wt_flags |= IEEE80211_RADIOTAP_F_FRAG; ieee80211_radiotap_tx(vap, m); } flags = 0; if (!ismcast) { /* Unicast frame, check if an ACK is expected. */ if (!qos || (qos & IEEE80211_QOS_ACKPOLICY) != IEEE80211_QOS_ACKPOLICY_NOACK) flags |= WPI_TX_NEED_ACK; } if (!IEEE80211_QOS_HAS_SEQ(wh)) flags |= WPI_TX_AUTO_SEQ; if (wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG) flags |= WPI_TX_MORE_FRAG; /* Check if frame must be protected using RTS/CTS or CTS-to-self. */ if (!ismcast) { /* NB: Group frames are sent using CCK in 802.11b/g. */ if (totlen + IEEE80211_CRC_LEN > vap->iv_rtsthreshold) { flags |= WPI_TX_NEED_RTS; } else if ((ic->ic_flags & IEEE80211_F_USEPROT) && WPI_RATE_IS_OFDM(rate)) { if (ic->ic_protmode == IEEE80211_PROT_CTSONLY) flags |= WPI_TX_NEED_CTS; else if (ic->ic_protmode == IEEE80211_PROT_RTSCTS) flags |= WPI_TX_NEED_RTS; } if (flags & (WPI_TX_NEED_RTS | WPI_TX_NEED_CTS)) flags |= WPI_TX_FULL_TXOP; } memset(tx, 0, sizeof (struct wpi_cmd_data)); if (type == IEEE80211_FC0_TYPE_MGT) { uint8_t subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; /* Tell HW to set timestamp in probe responses. */ if (subtype == IEEE80211_FC0_SUBTYPE_PROBE_RESP) flags |= WPI_TX_INSERT_TSTAMP; if (subtype == IEEE80211_FC0_SUBTYPE_ASSOC_REQ || subtype == IEEE80211_FC0_SUBTYPE_REASSOC_REQ) tx->timeout = htole16(3); else tx->timeout = htole16(2); } if (ismcast || type != IEEE80211_FC0_TYPE_DATA) tx->id = WPI_ID_BROADCAST; else { if (wn->id == WPI_ID_UNDEFINED) { device_printf(sc->sc_dev, "%s: undefined node id\n", __func__); return (EINVAL); } tx->id = wn->id; } if (!swcrypt) { switch (k->wk_cipher->ic_cipher) { case IEEE80211_CIPHER_AES_CCM: tx->security = WPI_CIPHER_CCMP; break; default: break; } memcpy(tx->key, k->wk_key, k->wk_keylen); } if (wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG) { struct mbuf *next = m->m_nextpkt; tx->lnext = htole16(next->m_pkthdr.len); tx->fnext = htole32(tx->security | (flags & WPI_TX_NEED_ACK) | WPI_NEXT_STA_ID(tx->id)); } tx->len = htole16(totlen); tx->flags = htole32(flags); tx->plcp = rate2plcp(rate); tx->tid = tid; tx->lifetime = htole32(WPI_LIFETIME_INFINITE); tx->ofdm_mask = 0xff; tx->cck_mask = 0x0f; tx->rts_ntries = 7; tx->data_ntries = tp->maxretry; tx_data.ni = ni; tx_data.m = m; tx_data.size = sizeof(struct wpi_cmd_data); tx_data.code = WPI_CMD_TX_DATA; tx_data.ac = ac; return wpi_cmd2(sc, &tx_data); } static int wpi_tx_data_raw(struct wpi_softc *sc, struct mbuf *m, struct ieee80211_node *ni, const struct ieee80211_bpf_params *params) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_key *k = NULL; struct ieee80211_frame *wh; struct wpi_buf tx_data; struct wpi_cmd_data *tx = (struct wpi_cmd_data *)&tx_data.data; uint32_t flags; uint8_t ac, type, rate; int swcrypt, totlen; wh = mtod(m, struct ieee80211_frame *); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; swcrypt = 1; ac = params->ibp_pri & 3; /* Choose a TX rate index. */ rate = params->ibp_rate0; flags = 0; if (!IEEE80211_QOS_HAS_SEQ(wh)) flags |= WPI_TX_AUTO_SEQ; if ((params->ibp_flags & IEEE80211_BPF_NOACK) == 0) flags |= WPI_TX_NEED_ACK; if (params->ibp_flags & IEEE80211_BPF_RTS) flags |= WPI_TX_NEED_RTS; if (params->ibp_flags & IEEE80211_BPF_CTS) flags |= WPI_TX_NEED_CTS; if (flags & (WPI_TX_NEED_RTS | WPI_TX_NEED_CTS)) flags |= WPI_TX_FULL_TXOP; /* Encrypt the frame if need be. */ if (params->ibp_flags & IEEE80211_BPF_CRYPTO) { /* Retrieve key for TX. */ k = ieee80211_crypto_encap(ni, m); if (k == NULL) return (ENOBUFS); swcrypt = k->wk_flags & IEEE80211_KEY_SWCRYPT; /* 802.11 header may have moved. */ wh = mtod(m, struct ieee80211_frame *); } totlen = m->m_pkthdr.len; if (ieee80211_radiotap_active_vap(vap)) { struct wpi_tx_radiotap_header *tap = &sc->sc_txtap; tap->wt_flags = 0; tap->wt_rate = rate; if (params->ibp_flags & IEEE80211_BPF_CRYPTO) tap->wt_flags |= IEEE80211_RADIOTAP_F_WEP; ieee80211_radiotap_tx(vap, m); } memset(tx, 0, sizeof (struct wpi_cmd_data)); if (type == IEEE80211_FC0_TYPE_MGT) { uint8_t subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; /* Tell HW to set timestamp in probe responses. */ if (subtype == IEEE80211_FC0_SUBTYPE_PROBE_RESP) flags |= WPI_TX_INSERT_TSTAMP; if (subtype == IEEE80211_FC0_SUBTYPE_ASSOC_REQ || subtype == IEEE80211_FC0_SUBTYPE_REASSOC_REQ) tx->timeout = htole16(3); else tx->timeout = htole16(2); } if (!swcrypt) { switch (k->wk_cipher->ic_cipher) { case IEEE80211_CIPHER_AES_CCM: tx->security = WPI_CIPHER_CCMP; break; default: break; } memcpy(tx->key, k->wk_key, k->wk_keylen); } tx->len = htole16(totlen); tx->flags = htole32(flags); tx->plcp = rate2plcp(rate); tx->id = WPI_ID_BROADCAST; tx->lifetime = htole32(WPI_LIFETIME_INFINITE); tx->rts_ntries = params->ibp_try1; tx->data_ntries = params->ibp_try0; tx_data.ni = ni; tx_data.m = m; tx_data.size = sizeof(struct wpi_cmd_data); tx_data.code = WPI_CMD_TX_DATA; tx_data.ac = ac; return wpi_cmd2(sc, &tx_data); } static __inline int wpi_tx_ring_free_space(struct wpi_softc *sc, uint16_t ac) { struct wpi_tx_ring *ring = &sc->txq[ac]; int retval; WPI_TXQ_STATE_LOCK(sc); retval = WPI_TX_RING_HIMARK - ring->queued; WPI_TXQ_STATE_UNLOCK(sc); return retval; } static int wpi_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = ni->ni_ic; struct wpi_softc *sc = ic->ic_softc; uint16_t ac; int error = 0; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_BEGIN, __func__); ac = M_WME_GETAC(m); WPI_TX_LOCK(sc); /* NB: no fragments here */ if (sc->sc_running == 0 || wpi_tx_ring_free_space(sc, ac) < 1) { error = sc->sc_running ? ENOBUFS : ENETDOWN; goto unlock; } if (params == NULL) { /* * Legacy path; interpret frame contents to decide * precisely how to send the frame. */ error = wpi_tx_data(sc, m, ni); } else { /* * Caller supplied explicit parameters to use in * sending the frame. */ error = wpi_tx_data_raw(sc, m, ni, params); } unlock: WPI_TX_UNLOCK(sc); if (error != 0) { m_freem(m); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END_ERR, __func__); return error; } DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END, __func__); return 0; } static int wpi_transmit(struct ieee80211com *ic, struct mbuf *m) { struct wpi_softc *sc = ic->ic_softc; struct ieee80211_node *ni; struct mbuf *mnext; uint16_t ac; int error, nmbufs; WPI_TX_LOCK(sc); DPRINTF(sc, WPI_DEBUG_XMIT, "%s: called\n", __func__); /* Check if interface is up & running. */ if (__predict_false(sc->sc_running == 0)) { error = ENXIO; goto unlock; } nmbufs = 1; for (mnext = m->m_nextpkt; mnext != NULL; mnext = mnext->m_nextpkt) nmbufs++; /* Check for available space. */ ac = M_WME_GETAC(m); if (wpi_tx_ring_free_space(sc, ac) < nmbufs) { error = ENOBUFS; goto unlock; } error = 0; ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; do { mnext = m->m_nextpkt; if (wpi_tx_data(sc, m, ni) != 0) { if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, nmbufs); wpi_free_txfrags(sc, ac); ieee80211_free_mbuf(m); ieee80211_free_node(ni); break; } } while((m = mnext) != NULL); DPRINTF(sc, WPI_DEBUG_XMIT, "%s: done\n", __func__); unlock: WPI_TX_UNLOCK(sc); return (error); } static void wpi_watchdog_rfkill(void *arg) { struct wpi_softc *sc = arg; struct ieee80211com *ic = &sc->sc_ic; DPRINTF(sc, WPI_DEBUG_WATCHDOG, "RFkill Watchdog: tick\n"); /* No need to lock firmware memory. */ if ((wpi_prph_read(sc, WPI_APMG_RFKILL) & 0x1) == 0) { /* Radio kill switch is still off. */ callout_reset(&sc->watchdog_rfkill, hz, wpi_watchdog_rfkill, sc); } else ieee80211_runtask(ic, &sc->sc_radioon_task); } static void wpi_scan_timeout(void *arg) { struct wpi_softc *sc = arg; struct ieee80211com *ic = &sc->sc_ic; ic_printf(ic, "scan timeout\n"); taskqueue_enqueue(sc->sc_tq, &sc->sc_reinittask); } static void wpi_tx_timeout(void *arg) { struct wpi_softc *sc = arg; struct ieee80211com *ic = &sc->sc_ic; ic_printf(ic, "device timeout\n"); taskqueue_enqueue(sc->sc_tq, &sc->sc_reinittask); } static void wpi_parent(struct ieee80211com *ic) { struct wpi_softc *sc = ic->ic_softc; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); if (ic->ic_nrunning > 0) { if (wpi_init(sc) == 0) { ieee80211_notify_radio(ic, 1); ieee80211_start_all(ic); } else { ieee80211_notify_radio(ic, 0); ieee80211_stop(vap); } } else wpi_stop(sc); } /* * Send a command to the firmware. */ static int wpi_cmd(struct wpi_softc *sc, uint8_t code, const void *buf, uint16_t size, int async) { struct wpi_tx_ring *ring = &sc->txq[WPI_CMD_QUEUE_NUM]; struct wpi_tx_desc *desc; struct wpi_tx_data *data; struct wpi_tx_cmd *cmd; struct mbuf *m; bus_addr_t paddr; uint16_t totlen; int error; WPI_TXQ_LOCK(sc); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_BEGIN, __func__); if (__predict_false(sc->sc_running == 0)) { /* wpi_stop() was called */ if (code == WPI_CMD_SCAN) error = ENETDOWN; else error = 0; goto fail; } if (async == 0) WPI_LOCK_ASSERT(sc); DPRINTF(sc, WPI_DEBUG_CMD, "%s: cmd %s size %u async %d\n", __func__, wpi_cmd_str(code), size, async); desc = &ring->desc[ring->cur]; data = &ring->data[ring->cur]; totlen = 4 + size; if (size > sizeof cmd->data) { /* Command is too large to fit in a descriptor. */ if (totlen > MCLBYTES) { error = EINVAL; goto fail; } m = m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR, MJUMPAGESIZE); if (m == NULL) { error = ENOMEM; goto fail; } cmd = mtod(m, struct wpi_tx_cmd *); error = bus_dmamap_load(ring->data_dmat, data->map, cmd, totlen, wpi_dma_map_addr, &paddr, BUS_DMA_NOWAIT); if (error != 0) { m_freem(m); goto fail; } data->m = m; } else { cmd = &ring->cmd[ring->cur]; paddr = data->cmd_paddr; } cmd->code = code; cmd->flags = 0; cmd->qid = ring->qid; cmd->idx = ring->cur; memcpy(cmd->data, buf, size); desc->nsegs = 1 + (WPI_PAD32(size) << 4); desc->segs[0].addr = htole32(paddr); desc->segs[0].len = htole32(totlen); if (size > sizeof cmd->data) { bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_PREWRITE); } else { bus_dmamap_sync(ring->data_dmat, ring->cmd_dma.map, BUS_DMASYNC_PREWRITE); } bus_dmamap_sync(ring->desc_dma.tag, ring->desc_dma.map, BUS_DMASYNC_PREWRITE); /* Kick command ring. */ ring->cur = (ring->cur + 1) % WPI_TX_RING_COUNT; sc->sc_update_tx_ring(sc, ring); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END, __func__); WPI_TXQ_UNLOCK(sc); return async ? 0 : mtx_sleep(cmd, &sc->sc_mtx, PCATCH, "wpicmd", hz); fail: DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END_ERR, __func__); WPI_TXQ_UNLOCK(sc); return error; } /* * Configure HW multi-rate retries. */ static int wpi_mrr_setup(struct wpi_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct wpi_mrr_setup mrr; uint8_t i; int error; /* CCK rates (not used with 802.11a). */ for (i = WPI_RIDX_CCK1; i <= WPI_RIDX_CCK11; i++) { mrr.rates[i].flags = 0; mrr.rates[i].plcp = wpi_ridx_to_plcp[i]; /* Fallback to the immediate lower CCK rate (if any.) */ mrr.rates[i].next = (i == WPI_RIDX_CCK1) ? WPI_RIDX_CCK1 : i - 1; /* Try twice at this rate before falling back to "next". */ mrr.rates[i].ntries = WPI_NTRIES_DEFAULT; } /* OFDM rates (not used with 802.11b). */ for (i = WPI_RIDX_OFDM6; i <= WPI_RIDX_OFDM54; i++) { mrr.rates[i].flags = 0; mrr.rates[i].plcp = wpi_ridx_to_plcp[i]; /* Fallback to the immediate lower rate (if any.) */ /* We allow fallback from OFDM/6 to CCK/2 in 11b/g mode. */ mrr.rates[i].next = (i == WPI_RIDX_OFDM6) ? ((ic->ic_curmode == IEEE80211_MODE_11A) ? WPI_RIDX_OFDM6 : WPI_RIDX_CCK2) : i - 1; /* Try twice at this rate before falling back to "next". */ mrr.rates[i].ntries = WPI_NTRIES_DEFAULT; } /* Setup MRR for control frames. */ mrr.which = htole32(WPI_MRR_CTL); error = wpi_cmd(sc, WPI_CMD_MRR_SETUP, &mrr, sizeof mrr, 0); if (error != 0) { device_printf(sc->sc_dev, "could not setup MRR for control frames\n"); return error; } /* Setup MRR for data frames. */ mrr.which = htole32(WPI_MRR_DATA); error = wpi_cmd(sc, WPI_CMD_MRR_SETUP, &mrr, sizeof mrr, 0); if (error != 0) { device_printf(sc->sc_dev, "could not setup MRR for data frames\n"); return error; } return 0; } static int wpi_add_node(struct wpi_softc *sc, struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; struct wpi_vap *wvp = WPI_VAP(ni->ni_vap); struct wpi_node *wn = WPI_NODE(ni); struct wpi_node_info node; int error; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); if (wn->id == WPI_ID_UNDEFINED) return EINVAL; memset(&node, 0, sizeof node); IEEE80211_ADDR_COPY(node.macaddr, ni->ni_macaddr); node.id = wn->id; node.plcp = (ic->ic_curmode == IEEE80211_MODE_11A) ? wpi_ridx_to_plcp[WPI_RIDX_OFDM6] : wpi_ridx_to_plcp[WPI_RIDX_CCK1]; node.action = htole32(WPI_ACTION_SET_RATE); node.antenna = WPI_ANTENNA_BOTH; DPRINTF(sc, WPI_DEBUG_NODE, "%s: adding node %d (%s)\n", __func__, wn->id, ether_sprintf(ni->ni_macaddr)); error = wpi_cmd(sc, WPI_CMD_ADD_NODE, &node, sizeof node, 1); if (error != 0) { device_printf(sc->sc_dev, "%s: wpi_cmd() call failed with error code %d\n", __func__, error); return error; } if (wvp->wv_gtk != 0) { error = wpi_set_global_keys(ni); if (error != 0) { device_printf(sc->sc_dev, "%s: error while setting global keys\n", __func__); return ENXIO; } } return 0; } /* * Broadcast node is used to send group-addressed and management frames. */ static int wpi_add_broadcast_node(struct wpi_softc *sc, int async) { struct ieee80211com *ic = &sc->sc_ic; struct wpi_node_info node; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); memset(&node, 0, sizeof node); IEEE80211_ADDR_COPY(node.macaddr, ieee80211broadcastaddr); node.id = WPI_ID_BROADCAST; node.plcp = (ic->ic_curmode == IEEE80211_MODE_11A) ? wpi_ridx_to_plcp[WPI_RIDX_OFDM6] : wpi_ridx_to_plcp[WPI_RIDX_CCK1]; node.action = htole32(WPI_ACTION_SET_RATE); node.antenna = WPI_ANTENNA_BOTH; DPRINTF(sc, WPI_DEBUG_NODE, "%s: adding broadcast node\n", __func__); return wpi_cmd(sc, WPI_CMD_ADD_NODE, &node, sizeof node, async); } static int wpi_add_sta_node(struct wpi_softc *sc, struct ieee80211_node *ni) { struct wpi_node *wn = WPI_NODE(ni); int error; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); wn->id = wpi_add_node_entry_sta(sc); if ((error = wpi_add_node(sc, ni)) != 0) { wpi_del_node_entry(sc, wn->id); wn->id = WPI_ID_UNDEFINED; return error; } return 0; } static int wpi_add_ibss_node(struct wpi_softc *sc, struct ieee80211_node *ni) { struct wpi_node *wn = WPI_NODE(ni); int error; KASSERT(wn->id == WPI_ID_UNDEFINED, ("the node %d was added before", wn->id)); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); if ((wn->id = wpi_add_node_entry_adhoc(sc)) == WPI_ID_UNDEFINED) { device_printf(sc->sc_dev, "%s: h/w table is full\n", __func__); return ENOMEM; } if ((error = wpi_add_node(sc, ni)) != 0) { wpi_del_node_entry(sc, wn->id); wn->id = WPI_ID_UNDEFINED; return error; } return 0; } static void wpi_del_node(struct wpi_softc *sc, struct ieee80211_node *ni) { struct wpi_node *wn = WPI_NODE(ni); struct wpi_cmd_del_node node; int error; KASSERT(wn->id != WPI_ID_UNDEFINED, ("undefined node id passed")); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); memset(&node, 0, sizeof node); IEEE80211_ADDR_COPY(node.macaddr, ni->ni_macaddr); node.count = 1; DPRINTF(sc, WPI_DEBUG_NODE, "%s: deleting node %d (%s)\n", __func__, wn->id, ether_sprintf(ni->ni_macaddr)); error = wpi_cmd(sc, WPI_CMD_DEL_NODE, &node, sizeof node, 1); if (error != 0) { device_printf(sc->sc_dev, "%s: could not delete node %u, error %d\n", __func__, wn->id, error); } } static int wpi_updateedca(struct ieee80211com *ic) { #define WPI_EXP2(x) ((1 << (x)) - 1) /* CWmin = 2^ECWmin - 1 */ struct wpi_softc *sc = ic->ic_softc; struct wpi_edca_params cmd; int aci, error; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_BEGIN, __func__); memset(&cmd, 0, sizeof cmd); cmd.flags = htole32(WPI_EDCA_UPDATE); for (aci = 0; aci < WME_NUM_AC; aci++) { const struct wmeParams *ac = &ic->ic_wme.wme_chanParams.cap_wmeParams[aci]; cmd.ac[aci].aifsn = ac->wmep_aifsn; cmd.ac[aci].cwmin = htole16(WPI_EXP2(ac->wmep_logcwmin)); cmd.ac[aci].cwmax = htole16(WPI_EXP2(ac->wmep_logcwmax)); cmd.ac[aci].txoplimit = htole16(IEEE80211_TXOP_TO_US(ac->wmep_txopLimit)); DPRINTF(sc, WPI_DEBUG_EDCA, "setting WME for queue %d aifsn=%d cwmin=%d cwmax=%d " "txoplimit=%d\n", aci, cmd.ac[aci].aifsn, cmd.ac[aci].cwmin, cmd.ac[aci].cwmax, cmd.ac[aci].txoplimit); } error = wpi_cmd(sc, WPI_CMD_EDCA_PARAMS, &cmd, sizeof cmd, 1); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END, __func__); return error; #undef WPI_EXP2 } static void wpi_set_promisc(struct wpi_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); uint32_t promisc_filter; promisc_filter = WPI_FILTER_CTL; if (vap != NULL && vap->iv_opmode != IEEE80211_M_HOSTAP) promisc_filter |= WPI_FILTER_PROMISC; if (ic->ic_promisc > 0) sc->rxon.filter |= htole32(promisc_filter); else sc->rxon.filter &= ~htole32(promisc_filter); } static void wpi_update_promisc(struct ieee80211com *ic) { struct wpi_softc *sc = ic->ic_softc; WPI_RXON_LOCK(sc); wpi_set_promisc(sc); if (wpi_send_rxon(sc, 1, 1) != 0) { device_printf(sc->sc_dev, "%s: could not send RXON\n", __func__); } WPI_RXON_UNLOCK(sc); } static void wpi_update_mcast(struct ieee80211com *ic) { /* Ignore */ } static void wpi_set_led(struct wpi_softc *sc, uint8_t which, uint8_t off, uint8_t on) { struct wpi_cmd_led led; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); led.which = which; led.unit = htole32(100000); /* on/off in unit of 100ms */ led.off = off; led.on = on; (void)wpi_cmd(sc, WPI_CMD_SET_LED, &led, sizeof led, 1); } static int wpi_set_timing(struct wpi_softc *sc, struct ieee80211_node *ni) { struct wpi_cmd_timing cmd; uint64_t val, mod; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); memset(&cmd, 0, sizeof cmd); memcpy(&cmd.tstamp, ni->ni_tstamp.data, sizeof (uint64_t)); cmd.bintval = htole16(ni->ni_intval); cmd.lintval = htole16(10); /* Compute remaining time until next beacon. */ val = (uint64_t)ni->ni_intval * IEEE80211_DUR_TU; mod = le64toh(cmd.tstamp) % val; cmd.binitval = htole32((uint32_t)(val - mod)); DPRINTF(sc, WPI_DEBUG_RESET, "timing bintval=%u tstamp=%ju, init=%u\n", ni->ni_intval, le64toh(cmd.tstamp), (uint32_t)(val - mod)); return wpi_cmd(sc, WPI_CMD_TIMING, &cmd, sizeof cmd, 1); } /* * This function is called periodically (every 60 seconds) to adjust output * power to temperature changes. */ static void wpi_power_calibration(struct wpi_softc *sc) { int temp; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); /* Update sensor data. */ temp = (int)WPI_READ(sc, WPI_UCODE_GP2); DPRINTF(sc, WPI_DEBUG_TEMP, "Temp in calibration is: %d\n", temp); /* Sanity-check read value. */ if (temp < -260 || temp > 25) { /* This can't be correct, ignore. */ DPRINTF(sc, WPI_DEBUG_TEMP, "out-of-range temperature reported: %d\n", temp); return; } DPRINTF(sc, WPI_DEBUG_TEMP, "temperature %d->%d\n", sc->temp, temp); /* Adjust Tx power if need be. */ if (abs(temp - sc->temp) <= 6) return; sc->temp = temp; if (wpi_set_txpower(sc, 1) != 0) { /* just warn, too bad for the automatic calibration... */ device_printf(sc->sc_dev,"could not adjust Tx power\n"); } } /* * Set TX power for current channel. */ static int wpi_set_txpower(struct wpi_softc *sc, int async) { struct wpi_power_group *group; struct wpi_cmd_txpower cmd; uint8_t chan; int idx, is_chan_5ghz, i; /* Retrieve current channel from last RXON. */ chan = sc->rxon.chan; is_chan_5ghz = (sc->rxon.flags & htole32(WPI_RXON_24GHZ)) == 0; /* Find the TX power group to which this channel belongs. */ if (is_chan_5ghz) { for (group = &sc->groups[1]; group < &sc->groups[4]; group++) if (chan <= group->chan) break; } else group = &sc->groups[0]; memset(&cmd, 0, sizeof cmd); cmd.band = is_chan_5ghz ? WPI_BAND_5GHZ : WPI_BAND_2GHZ; cmd.chan = htole16(chan); /* Set TX power for all OFDM and CCK rates. */ for (i = 0; i <= WPI_RIDX_MAX ; i++) { /* Retrieve TX power for this channel/rate. */ idx = wpi_get_power_index(sc, group, chan, is_chan_5ghz, i); cmd.rates[i].plcp = wpi_ridx_to_plcp[i]; if (is_chan_5ghz) { cmd.rates[i].rf_gain = wpi_rf_gain_5ghz[idx]; cmd.rates[i].dsp_gain = wpi_dsp_gain_5ghz[idx]; } else { cmd.rates[i].rf_gain = wpi_rf_gain_2ghz[idx]; cmd.rates[i].dsp_gain = wpi_dsp_gain_2ghz[idx]; } DPRINTF(sc, WPI_DEBUG_TEMP, "chan %d/ridx %d: power index %d\n", chan, i, idx); } return wpi_cmd(sc, WPI_CMD_TXPOWER, &cmd, sizeof cmd, async); } /* * Determine Tx power index for a given channel/rate combination. * This takes into account the regulatory information from EEPROM and the * current temperature. */ static int wpi_get_power_index(struct wpi_softc *sc, struct wpi_power_group *group, uint8_t chan, int is_chan_5ghz, int ridx) { /* Fixed-point arithmetic division using a n-bit fractional part. */ #define fdivround(a, b, n) \ ((((1 << n) * (a)) / (b) + (1 << n) / 2) / (1 << n)) /* Linear interpolation. */ #define interpolate(x, x1, y1, x2, y2, n) \ ((y1) + fdivround(((x) - (x1)) * ((y2) - (y1)), (x2) - (x1), n)) struct wpi_power_sample *sample; int pwr, idx; /* Default TX power is group maximum TX power minus 3dB. */ pwr = group->maxpwr / 2; /* Decrease TX power for highest OFDM rates to reduce distortion. */ switch (ridx) { case WPI_RIDX_OFDM36: pwr -= is_chan_5ghz ? 5 : 0; break; case WPI_RIDX_OFDM48: pwr -= is_chan_5ghz ? 10 : 7; break; case WPI_RIDX_OFDM54: pwr -= is_chan_5ghz ? 12 : 9; break; } /* Never exceed the channel maximum allowed TX power. */ pwr = min(pwr, sc->maxpwr[chan]); /* Retrieve TX power index into gain tables from samples. */ for (sample = group->samples; sample < &group->samples[3]; sample++) if (pwr > sample[1].power) break; /* Fixed-point linear interpolation using a 19-bit fractional part. */ idx = interpolate(pwr, sample[0].power, sample[0].index, sample[1].power, sample[1].index, 19); /*- * Adjust power index based on current temperature: * - if cooler than factory-calibrated: decrease output power * - if warmer than factory-calibrated: increase output power */ idx -= (sc->temp - group->temp) * 11 / 100; /* Decrease TX power for CCK rates (-5dB). */ if (ridx >= WPI_RIDX_CCK1) idx += 10; /* Make sure idx stays in a valid range. */ if (idx < 0) return 0; if (idx > WPI_MAX_PWR_INDEX) return WPI_MAX_PWR_INDEX; return idx; #undef interpolate #undef fdivround } /* * Set STA mode power saving level (between 0 and 5). * Level 0 is CAM (Continuously Aware Mode), 5 is for maximum power saving. */ static int wpi_set_pslevel(struct wpi_softc *sc, uint8_t dtim, int level, int async) { struct wpi_pmgt_cmd cmd; const struct wpi_pmgt *pmgt; uint32_t max, reg; uint8_t skip_dtim; int i; DPRINTF(sc, WPI_DEBUG_PWRSAVE, "%s: dtim=%d, level=%d, async=%d\n", __func__, dtim, level, async); /* Select which PS parameters to use. */ if (dtim <= 10) pmgt = &wpi_pmgt[0][level]; else pmgt = &wpi_pmgt[1][level]; memset(&cmd, 0, sizeof cmd); if (level != 0) /* not CAM */ cmd.flags |= htole16(WPI_PS_ALLOW_SLEEP); /* Retrieve PCIe Active State Power Management (ASPM). */ reg = pci_read_config(sc->sc_dev, sc->sc_cap_off + 0x10, 1); if (!(reg & 0x1)) /* L0s Entry disabled. */ cmd.flags |= htole16(WPI_PS_PCI_PMGT); cmd.rxtimeout = htole32(pmgt->rxtimeout * IEEE80211_DUR_TU); cmd.txtimeout = htole32(pmgt->txtimeout * IEEE80211_DUR_TU); if (dtim == 0) { dtim = 1; skip_dtim = 0; } else skip_dtim = pmgt->skip_dtim; if (skip_dtim != 0) { cmd.flags |= htole16(WPI_PS_SLEEP_OVER_DTIM); max = pmgt->intval[4]; if (max == (uint32_t)-1) max = dtim * (skip_dtim + 1); else if (max > dtim) max = (max / dtim) * dtim; } else max = dtim; for (i = 0; i < 5; i++) cmd.intval[i] = htole32(MIN(max, pmgt->intval[i])); return wpi_cmd(sc, WPI_CMD_SET_POWER_MODE, &cmd, sizeof cmd, async); } static int wpi_send_btcoex(struct wpi_softc *sc) { struct wpi_bluetooth cmd; memset(&cmd, 0, sizeof cmd); cmd.flags = WPI_BT_COEX_MODE_4WIRE; cmd.lead_time = WPI_BT_LEAD_TIME_DEF; cmd.max_kill = WPI_BT_MAX_KILL_DEF; DPRINTF(sc, WPI_DEBUG_RESET, "%s: configuring bluetooth coexistence\n", __func__); return wpi_cmd(sc, WPI_CMD_BT_COEX, &cmd, sizeof(cmd), 0); } static int wpi_send_rxon(struct wpi_softc *sc, int assoc, int async) { int error; if (async) WPI_RXON_LOCK_ASSERT(sc); if (assoc && wpi_check_bss_filter(sc) != 0) { struct wpi_assoc rxon_assoc; rxon_assoc.flags = sc->rxon.flags; rxon_assoc.filter = sc->rxon.filter; rxon_assoc.ofdm_mask = sc->rxon.ofdm_mask; rxon_assoc.cck_mask = sc->rxon.cck_mask; rxon_assoc.reserved = 0; error = wpi_cmd(sc, WPI_CMD_RXON_ASSOC, &rxon_assoc, sizeof (struct wpi_assoc), async); if (error != 0) { device_printf(sc->sc_dev, "RXON_ASSOC command failed, error %d\n", error); return error; } } else { if (async) { WPI_NT_LOCK(sc); error = wpi_cmd(sc, WPI_CMD_RXON, &sc->rxon, sizeof (struct wpi_rxon), async); if (error == 0) wpi_clear_node_table(sc); WPI_NT_UNLOCK(sc); } else { error = wpi_cmd(sc, WPI_CMD_RXON, &sc->rxon, sizeof (struct wpi_rxon), async); if (error == 0) wpi_clear_node_table(sc); } if (error != 0) { device_printf(sc->sc_dev, "RXON command failed, error %d\n", error); return error; } /* Add broadcast node. */ error = wpi_add_broadcast_node(sc, async); if (error != 0) { device_printf(sc->sc_dev, "could not add broadcast node, error %d\n", error); return error; } } /* Configuration has changed, set Tx power accordingly. */ if ((error = wpi_set_txpower(sc, async)) != 0) { device_printf(sc->sc_dev, "%s: could not set TX power, error %d\n", __func__, error); return error; } return 0; } /** * Configure the card to listen to a particular channel, this transisions the * card in to being able to receive frames from remote devices. */ static int wpi_config(struct wpi_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); struct ieee80211_channel *c = ic->ic_curchan; int error; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_BEGIN, __func__); /* Set power saving level to CAM during initialization. */ if ((error = wpi_set_pslevel(sc, 0, 0, 0)) != 0) { device_printf(sc->sc_dev, "%s: could not set power saving level\n", __func__); return error; } /* Configure bluetooth coexistence. */ if ((error = wpi_send_btcoex(sc)) != 0) { device_printf(sc->sc_dev, "could not configure bluetooth coexistence\n"); return error; } /* Configure adapter. */ memset(&sc->rxon, 0, sizeof (struct wpi_rxon)); IEEE80211_ADDR_COPY(sc->rxon.myaddr, vap->iv_myaddr); /* Set default channel. */ sc->rxon.chan = ieee80211_chan2ieee(ic, c); sc->rxon.flags = htole32(WPI_RXON_TSF | WPI_RXON_CTS_TO_SELF); if (IEEE80211_IS_CHAN_2GHZ(c)) sc->rxon.flags |= htole32(WPI_RXON_AUTO | WPI_RXON_24GHZ); sc->rxon.filter = WPI_FILTER_MULTICAST; switch (ic->ic_opmode) { case IEEE80211_M_STA: sc->rxon.mode = WPI_MODE_STA; break; case IEEE80211_M_IBSS: sc->rxon.mode = WPI_MODE_IBSS; sc->rxon.filter |= WPI_FILTER_BEACON; break; case IEEE80211_M_HOSTAP: /* XXX workaround for beaconing */ sc->rxon.mode = WPI_MODE_IBSS; sc->rxon.filter |= WPI_FILTER_ASSOC | WPI_FILTER_PROMISC; break; case IEEE80211_M_AHDEMO: sc->rxon.mode = WPI_MODE_HOSTAP; break; case IEEE80211_M_MONITOR: sc->rxon.mode = WPI_MODE_MONITOR; break; default: device_printf(sc->sc_dev, "unknown opmode %d\n", ic->ic_opmode); return EINVAL; } sc->rxon.filter = htole32(sc->rxon.filter); wpi_set_promisc(sc); sc->rxon.cck_mask = 0x0f; /* not yet negotiated */ sc->rxon.ofdm_mask = 0xff; /* not yet negotiated */ if ((error = wpi_send_rxon(sc, 0, 0)) != 0) { device_printf(sc->sc_dev, "%s: could not send RXON\n", __func__); return error; } /* Setup rate scalling. */ if ((error = wpi_mrr_setup(sc)) != 0) { device_printf(sc->sc_dev, "could not setup MRR, error %d\n", error); return error; } DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END, __func__); return 0; } static uint16_t wpi_get_active_dwell_time(struct wpi_softc *sc, struct ieee80211_channel *c, uint8_t n_probes) { /* No channel? Default to 2GHz settings. */ if (c == NULL || IEEE80211_IS_CHAN_2GHZ(c)) { return (WPI_ACTIVE_DWELL_TIME_2GHZ + WPI_ACTIVE_DWELL_FACTOR_2GHZ * (n_probes + 1)); } /* 5GHz dwell time. */ return (WPI_ACTIVE_DWELL_TIME_5GHZ + WPI_ACTIVE_DWELL_FACTOR_5GHZ * (n_probes + 1)); } /* * Limit the total dwell time. * * Returns the dwell time in milliseconds. */ static uint16_t wpi_limit_dwell(struct wpi_softc *sc, uint16_t dwell_time) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); uint16_t bintval = 0; /* bintval is in TU (1.024mS) */ if (vap != NULL) bintval = vap->iv_bss->ni_intval; /* * If it's non-zero, we should calculate the minimum of * it and the DWELL_BASE. * * XXX Yes, the math should take into account that bintval * is 1.024mS, not 1mS.. */ if (bintval > 0) { DPRINTF(sc, WPI_DEBUG_SCAN, "%s: bintval=%d\n", __func__, bintval); return (MIN(dwell_time, bintval - WPI_CHANNEL_TUNE_TIME * 2)); } /* No association context? Default. */ return dwell_time; } static uint16_t wpi_get_passive_dwell_time(struct wpi_softc *sc, struct ieee80211_channel *c) { uint16_t passive; if (c == NULL || IEEE80211_IS_CHAN_2GHZ(c)) passive = WPI_PASSIVE_DWELL_BASE + WPI_PASSIVE_DWELL_TIME_2GHZ; else passive = WPI_PASSIVE_DWELL_BASE + WPI_PASSIVE_DWELL_TIME_5GHZ; /* Clamp to the beacon interval if we're associated. */ return (wpi_limit_dwell(sc, passive)); } static uint32_t wpi_get_scan_pause_time(uint32_t time, uint16_t bintval) { uint32_t mod = (time % bintval) * IEEE80211_DUR_TU; uint32_t nbeacons = time / bintval; if (mod > WPI_PAUSE_MAX_TIME) mod = WPI_PAUSE_MAX_TIME; return WPI_PAUSE_SCAN(nbeacons, mod); } /* * Send a scan request to the firmware. */ static int wpi_scan(struct wpi_softc *sc, struct ieee80211_channel *c) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_scan_state *ss = ic->ic_scan; struct ieee80211vap *vap = ss->ss_vap; struct wpi_scan_hdr *hdr; struct wpi_cmd_data *tx; struct wpi_scan_essid *essids; struct wpi_scan_chan *chan; struct ieee80211_frame *wh; struct ieee80211_rateset *rs; uint16_t bintval, buflen, dwell_active, dwell_passive; uint8_t *buf, *frm, i, nssid; int bgscan, error; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_BEGIN, __func__); /* * We are absolutely not allowed to send a scan command when another * scan command is pending. */ if (callout_pending(&sc->scan_timeout)) { device_printf(sc->sc_dev, "%s: called whilst scanning!\n", __func__); error = EAGAIN; goto fail; } bgscan = wpi_check_bss_filter(sc); bintval = vap->iv_bss->ni_intval; if (bgscan != 0 && bintval < WPI_QUIET_TIME_DEFAULT + WPI_CHANNEL_TUNE_TIME * 2) { error = EOPNOTSUPP; goto fail; } buf = malloc(WPI_SCAN_MAXSZ, M_DEVBUF, M_NOWAIT | M_ZERO); if (buf == NULL) { device_printf(sc->sc_dev, "%s: could not allocate buffer for scan command\n", __func__); error = ENOMEM; goto fail; } hdr = (struct wpi_scan_hdr *)buf; /* * Move to the next channel if no packets are received within 10 msecs * after sending the probe request. */ hdr->quiet_time = htole16(WPI_QUIET_TIME_DEFAULT); hdr->quiet_threshold = htole16(1); if (bgscan != 0) { /* * Max needs to be greater than active and passive and quiet! * It's also in microseconds! */ hdr->max_svc = htole32(250 * IEEE80211_DUR_TU); hdr->pause_svc = htole32(wpi_get_scan_pause_time(100, bintval)); } hdr->filter = htole32(WPI_FILTER_MULTICAST | WPI_FILTER_BEACON); tx = (struct wpi_cmd_data *)(hdr + 1); tx->flags = htole32(WPI_TX_AUTO_SEQ); tx->id = WPI_ID_BROADCAST; tx->lifetime = htole32(WPI_LIFETIME_INFINITE); if (IEEE80211_IS_CHAN_5GHZ(c)) { /* Send probe requests at 6Mbps. */ tx->plcp = wpi_ridx_to_plcp[WPI_RIDX_OFDM6]; rs = &ic->ic_sup_rates[IEEE80211_MODE_11A]; } else { hdr->flags = htole32(WPI_RXON_24GHZ | WPI_RXON_AUTO); /* Send probe requests at 1Mbps. */ tx->plcp = wpi_ridx_to_plcp[WPI_RIDX_CCK1]; rs = &ic->ic_sup_rates[IEEE80211_MODE_11G]; } essids = (struct wpi_scan_essid *)(tx + 1); nssid = MIN(ss->ss_nssid, WPI_SCAN_MAX_ESSIDS); for (i = 0; i < nssid; i++) { essids[i].id = IEEE80211_ELEMID_SSID; essids[i].len = MIN(ss->ss_ssid[i].len, IEEE80211_NWID_LEN); memcpy(essids[i].data, ss->ss_ssid[i].ssid, essids[i].len); #ifdef WPI_DEBUG if (sc->sc_debug & WPI_DEBUG_SCAN) { printf("Scanning Essid: "); ieee80211_print_essid(essids[i].data, essids[i].len); printf("\n"); } #endif } /* * Build a probe request frame. Most of the following code is a * copy & paste of what is done in net80211. */ wh = (struct ieee80211_frame *)(essids + WPI_SCAN_MAX_ESSIDS); wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_PROBE_REQ; wh->i_fc[1] = IEEE80211_FC1_DIR_NODS; IEEE80211_ADDR_COPY(wh->i_addr1, ieee80211broadcastaddr); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, ieee80211broadcastaddr); frm = (uint8_t *)(wh + 1); frm = ieee80211_add_ssid(frm, NULL, 0); frm = ieee80211_add_rates(frm, rs); if (rs->rs_nrates > IEEE80211_RATE_SIZE) frm = ieee80211_add_xrates(frm, rs); /* Set length of probe request. */ tx->len = htole16(frm - (uint8_t *)wh); /* * Construct information about the channel that we * want to scan. The firmware expects this to be directly * after the scan probe request */ chan = (struct wpi_scan_chan *)frm; chan->chan = ieee80211_chan2ieee(ic, c); chan->flags = 0; if (nssid) { hdr->crc_threshold = WPI_SCAN_CRC_TH_DEFAULT; chan->flags |= WPI_CHAN_NPBREQS(nssid); } else hdr->crc_threshold = WPI_SCAN_CRC_TH_NEVER; if (!IEEE80211_IS_CHAN_PASSIVE(c)) chan->flags |= WPI_CHAN_ACTIVE; /* * Calculate the active/passive dwell times. */ dwell_active = wpi_get_active_dwell_time(sc, c, nssid); dwell_passive = wpi_get_passive_dwell_time(sc, c); /* Make sure they're valid. */ if (dwell_active > dwell_passive) dwell_active = dwell_passive; chan->active = htole16(dwell_active); chan->passive = htole16(dwell_passive); chan->dsp_gain = 0x6e; /* Default level */ if (IEEE80211_IS_CHAN_5GHZ(c)) chan->rf_gain = 0x3b; else chan->rf_gain = 0x28; DPRINTF(sc, WPI_DEBUG_SCAN, "Scanning %u Passive: %d\n", chan->chan, IEEE80211_IS_CHAN_PASSIVE(c)); hdr->nchan++; if (hdr->nchan == 1 && sc->rxon.chan == chan->chan) { /* XXX Force probe request transmission. */ memcpy(chan + 1, chan, sizeof (struct wpi_scan_chan)); chan++; /* Reduce unnecessary delay. */ chan->flags = 0; chan->passive = chan->active = hdr->quiet_time; hdr->nchan++; } chan++; buflen = (uint8_t *)chan - buf; hdr->len = htole16(buflen); DPRINTF(sc, WPI_DEBUG_CMD, "sending scan command nchan=%d\n", hdr->nchan); error = wpi_cmd(sc, WPI_CMD_SCAN, buf, buflen, 1); free(buf, M_DEVBUF); if (error != 0) goto fail; callout_reset(&sc->scan_timeout, 5*hz, wpi_scan_timeout, sc); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END, __func__); return 0; fail: DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END_ERR, __func__); return error; } static int wpi_auth(struct wpi_softc *sc, struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni = vap->iv_bss; struct ieee80211_channel *c = ni->ni_chan; int error; WPI_RXON_LOCK(sc); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_BEGIN, __func__); /* Update adapter configuration. */ sc->rxon.associd = 0; sc->rxon.filter &= ~htole32(WPI_FILTER_BSS); IEEE80211_ADDR_COPY(sc->rxon.bssid, ni->ni_bssid); sc->rxon.chan = ieee80211_chan2ieee(ic, c); sc->rxon.flags = htole32(WPI_RXON_TSF | WPI_RXON_CTS_TO_SELF); if (IEEE80211_IS_CHAN_2GHZ(c)) sc->rxon.flags |= htole32(WPI_RXON_AUTO | WPI_RXON_24GHZ); if (ic->ic_flags & IEEE80211_F_SHSLOT) sc->rxon.flags |= htole32(WPI_RXON_SHSLOT); if (ic->ic_flags & IEEE80211_F_SHPREAMBLE) sc->rxon.flags |= htole32(WPI_RXON_SHPREAMBLE); if (IEEE80211_IS_CHAN_A(c)) { sc->rxon.cck_mask = 0; sc->rxon.ofdm_mask = 0x15; } else if (IEEE80211_IS_CHAN_B(c)) { sc->rxon.cck_mask = 0x03; sc->rxon.ofdm_mask = 0; } else { /* Assume 802.11b/g. */ sc->rxon.cck_mask = 0x0f; sc->rxon.ofdm_mask = 0x15; } DPRINTF(sc, WPI_DEBUG_STATE, "rxon chan %d flags %x cck %x ofdm %x\n", sc->rxon.chan, sc->rxon.flags, sc->rxon.cck_mask, sc->rxon.ofdm_mask); if ((error = wpi_send_rxon(sc, 0, 1)) != 0) { device_printf(sc->sc_dev, "%s: could not send RXON\n", __func__); } DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END, __func__); WPI_RXON_UNLOCK(sc); return error; } static int wpi_config_beacon(struct wpi_vap *wvp) { struct ieee80211vap *vap = &wvp->wv_vap; struct ieee80211com *ic = vap->iv_ic; struct ieee80211_beacon_offsets *bo = &vap->iv_bcn_off; struct wpi_buf *bcn = &wvp->wv_bcbuf; struct wpi_softc *sc = ic->ic_softc; struct wpi_cmd_beacon *cmd = (struct wpi_cmd_beacon *)&bcn->data; struct ieee80211_tim_ie *tie; struct mbuf *m; uint8_t *ptr; int error; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); WPI_VAP_LOCK_ASSERT(wvp); cmd->len = htole16(bcn->m->m_pkthdr.len); cmd->plcp = (ic->ic_curmode == IEEE80211_MODE_11A) ? wpi_ridx_to_plcp[WPI_RIDX_OFDM6] : wpi_ridx_to_plcp[WPI_RIDX_CCK1]; /* XXX seems to be unused */ if (*(bo->bo_tim) == IEEE80211_ELEMID_TIM) { tie = (struct ieee80211_tim_ie *) bo->bo_tim; ptr = mtod(bcn->m, uint8_t *); cmd->tim = htole16(bo->bo_tim - ptr); cmd->timsz = tie->tim_len; } /* Necessary for recursion in ieee80211_beacon_update(). */ m = bcn->m; bcn->m = m_dup(m, M_NOWAIT); if (bcn->m == NULL) { device_printf(sc->sc_dev, "%s: could not copy beacon frame\n", __func__); error = ENOMEM; goto end; } if ((error = wpi_cmd2(sc, bcn)) != 0) { device_printf(sc->sc_dev, "%s: could not update beacon frame, error %d", __func__, error); m_freem(bcn->m); } /* Restore mbuf. */ end: bcn->m = m; return error; } static int wpi_setup_beacon(struct wpi_softc *sc, struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct wpi_vap *wvp = WPI_VAP(vap); struct wpi_buf *bcn = &wvp->wv_bcbuf; struct mbuf *m; int error; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); if (ni->ni_chan == IEEE80211_CHAN_ANYC) return EINVAL; m = ieee80211_beacon_alloc(ni); if (m == NULL) { device_printf(sc->sc_dev, "%s: could not allocate beacon frame\n", __func__); return ENOMEM; } WPI_VAP_LOCK(wvp); if (bcn->m != NULL) m_freem(bcn->m); bcn->m = m; error = wpi_config_beacon(wvp); WPI_VAP_UNLOCK(wvp); return error; } static void wpi_update_beacon(struct ieee80211vap *vap, int item) { struct wpi_softc *sc = vap->iv_ic->ic_softc; struct wpi_vap *wvp = WPI_VAP(vap); struct wpi_buf *bcn = &wvp->wv_bcbuf; struct ieee80211_beacon_offsets *bo = &vap->iv_bcn_off; struct ieee80211_node *ni = vap->iv_bss; int mcast = 0; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_BEGIN, __func__); WPI_VAP_LOCK(wvp); if (bcn->m == NULL) { bcn->m = ieee80211_beacon_alloc(ni); if (bcn->m == NULL) { device_printf(sc->sc_dev, "%s: could not allocate beacon frame\n", __func__); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END_ERR, __func__); WPI_VAP_UNLOCK(wvp); return; } } WPI_VAP_UNLOCK(wvp); if (item == IEEE80211_BEACON_TIM) mcast = 1; /* TODO */ setbit(bo->bo_flags, item); ieee80211_beacon_update(ni, bcn->m, mcast); WPI_VAP_LOCK(wvp); wpi_config_beacon(wvp); WPI_VAP_UNLOCK(wvp); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END, __func__); } static void wpi_newassoc(struct ieee80211_node *ni, int isnew) { struct ieee80211vap *vap = ni->ni_vap; struct wpi_softc *sc = ni->ni_ic->ic_softc; struct wpi_node *wn = WPI_NODE(ni); int error; WPI_NT_LOCK(sc); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); if (vap->iv_opmode != IEEE80211_M_STA && wn->id == WPI_ID_UNDEFINED) { if ((error = wpi_add_ibss_node(sc, ni)) != 0) { device_printf(sc->sc_dev, "%s: could not add IBSS node, error %d\n", __func__, error); } } WPI_NT_UNLOCK(sc); } static int wpi_run(struct wpi_softc *sc, struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni = vap->iv_bss; struct ieee80211_channel *c = ni->ni_chan; int error; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_BEGIN, __func__); if (vap->iv_opmode == IEEE80211_M_MONITOR) { /* Link LED blinks while monitoring. */ wpi_set_led(sc, WPI_LED_LINK, 5, 5); return 0; } /* XXX kernel panic workaround */ if (c == IEEE80211_CHAN_ANYC) { device_printf(sc->sc_dev, "%s: incomplete configuration\n", __func__); return EINVAL; } if ((error = wpi_set_timing(sc, ni)) != 0) { device_printf(sc->sc_dev, "%s: could not set timing, error %d\n", __func__, error); return error; } /* Update adapter configuration. */ WPI_RXON_LOCK(sc); IEEE80211_ADDR_COPY(sc->rxon.bssid, ni->ni_bssid); sc->rxon.associd = htole16(IEEE80211_NODE_AID(ni)); sc->rxon.chan = ieee80211_chan2ieee(ic, c); sc->rxon.flags = htole32(WPI_RXON_TSF | WPI_RXON_CTS_TO_SELF); if (IEEE80211_IS_CHAN_2GHZ(c)) sc->rxon.flags |= htole32(WPI_RXON_AUTO | WPI_RXON_24GHZ); if (ic->ic_flags & IEEE80211_F_SHSLOT) sc->rxon.flags |= htole32(WPI_RXON_SHSLOT); if (ic->ic_flags & IEEE80211_F_SHPREAMBLE) sc->rxon.flags |= htole32(WPI_RXON_SHPREAMBLE); if (IEEE80211_IS_CHAN_A(c)) { sc->rxon.cck_mask = 0; sc->rxon.ofdm_mask = 0x15; } else if (IEEE80211_IS_CHAN_B(c)) { sc->rxon.cck_mask = 0x03; sc->rxon.ofdm_mask = 0; } else { /* Assume 802.11b/g. */ sc->rxon.cck_mask = 0x0f; sc->rxon.ofdm_mask = 0x15; } sc->rxon.filter |= htole32(WPI_FILTER_BSS); DPRINTF(sc, WPI_DEBUG_STATE, "rxon chan %d flags %x\n", sc->rxon.chan, sc->rxon.flags); if ((error = wpi_send_rxon(sc, 0, 1)) != 0) { device_printf(sc->sc_dev, "%s: could not send RXON\n", __func__); return error; } /* Start periodic calibration timer. */ callout_reset(&sc->calib_to, 60*hz, wpi_calib_timeout, sc); WPI_RXON_UNLOCK(sc); if (vap->iv_opmode == IEEE80211_M_IBSS || vap->iv_opmode == IEEE80211_M_HOSTAP) { if ((error = wpi_setup_beacon(sc, ni)) != 0) { device_printf(sc->sc_dev, "%s: could not setup beacon, error %d\n", __func__, error); return error; } } if (vap->iv_opmode == IEEE80211_M_STA) { /* Add BSS node. */ WPI_NT_LOCK(sc); error = wpi_add_sta_node(sc, ni); WPI_NT_UNLOCK(sc); if (error != 0) { device_printf(sc->sc_dev, "%s: could not add BSS node, error %d\n", __func__, error); return error; } } /* Link LED always on while associated. */ wpi_set_led(sc, WPI_LED_LINK, 0, 1); /* Enable power-saving mode if requested by user. */ if ((vap->iv_flags & IEEE80211_F_PMGTON) && vap->iv_opmode != IEEE80211_M_IBSS) (void)wpi_set_pslevel(sc, 0, 3, 1); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END, __func__); return 0; } static int wpi_load_key(struct ieee80211_node *ni, const struct ieee80211_key *k) { const struct ieee80211_cipher *cip = k->wk_cipher; struct ieee80211vap *vap = ni->ni_vap; struct wpi_softc *sc = ni->ni_ic->ic_softc; struct wpi_node *wn = WPI_NODE(ni); struct wpi_node_info node; uint16_t kflags; int error; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); if (wpi_check_node_entry(sc, wn->id) == 0) { device_printf(sc->sc_dev, "%s: node does not exist\n", __func__); return 0; } switch (cip->ic_cipher) { case IEEE80211_CIPHER_AES_CCM: kflags = WPI_KFLAG_CCMP; break; default: device_printf(sc->sc_dev, "%s: unknown cipher %d\n", __func__, cip->ic_cipher); return 0; } kflags |= WPI_KFLAG_KID(k->wk_keyix); if (k->wk_flags & IEEE80211_KEY_GROUP) kflags |= WPI_KFLAG_MULTICAST; memset(&node, 0, sizeof node); node.id = wn->id; node.control = WPI_NODE_UPDATE; node.flags = WPI_FLAG_KEY_SET; node.kflags = htole16(kflags); memcpy(node.key, k->wk_key, k->wk_keylen); again: DPRINTF(sc, WPI_DEBUG_KEY, "%s: setting %s key id %d for node %d (%s)\n", __func__, (kflags & WPI_KFLAG_MULTICAST) ? "group" : "ucast", k->wk_keyix, node.id, ether_sprintf(ni->ni_macaddr)); error = wpi_cmd(sc, WPI_CMD_ADD_NODE, &node, sizeof node, 1); if (error != 0) { device_printf(sc->sc_dev, "can't update node info, error %d\n", error); return !error; } if (!(kflags & WPI_KFLAG_MULTICAST) && &vap->iv_nw_keys[0] <= k && k < &vap->iv_nw_keys[IEEE80211_WEP_NKID]) { kflags |= WPI_KFLAG_MULTICAST; node.kflags = htole16(kflags); goto again; } return 1; } static void wpi_load_key_cb(void *arg, struct ieee80211_node *ni) { const struct ieee80211_key *k = arg; struct ieee80211vap *vap = ni->ni_vap; struct wpi_softc *sc = ni->ni_ic->ic_softc; struct wpi_node *wn = WPI_NODE(ni); int error; if (vap->iv_bss == ni && wn->id == WPI_ID_UNDEFINED) return; WPI_NT_LOCK(sc); error = wpi_load_key(ni, k); WPI_NT_UNLOCK(sc); if (error == 0) { device_printf(sc->sc_dev, "%s: error while setting key\n", __func__); } } static int wpi_set_global_keys(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_key *wk = &vap->iv_nw_keys[0]; int error = 1; for (; wk < &vap->iv_nw_keys[IEEE80211_WEP_NKID] && error; wk++) if (wk->wk_keyix != IEEE80211_KEYIX_NONE) error = wpi_load_key(ni, wk); return !error; } static int wpi_del_key(struct ieee80211_node *ni, const struct ieee80211_key *k) { struct ieee80211vap *vap = ni->ni_vap; struct wpi_softc *sc = ni->ni_ic->ic_softc; struct wpi_node *wn = WPI_NODE(ni); struct wpi_node_info node; uint16_t kflags; int error; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); if (wpi_check_node_entry(sc, wn->id) == 0) { DPRINTF(sc, WPI_DEBUG_KEY, "%s: node was removed\n", __func__); return 1; /* Nothing to do. */ } kflags = WPI_KFLAG_KID(k->wk_keyix); if (k->wk_flags & IEEE80211_KEY_GROUP) kflags |= WPI_KFLAG_MULTICAST; memset(&node, 0, sizeof node); node.id = wn->id; node.control = WPI_NODE_UPDATE; node.flags = WPI_FLAG_KEY_SET; node.kflags = htole16(kflags); again: DPRINTF(sc, WPI_DEBUG_KEY, "%s: deleting %s key %d for node %d (%s)\n", __func__, (kflags & WPI_KFLAG_MULTICAST) ? "group" : "ucast", k->wk_keyix, node.id, ether_sprintf(ni->ni_macaddr)); error = wpi_cmd(sc, WPI_CMD_ADD_NODE, &node, sizeof node, 1); if (error != 0) { device_printf(sc->sc_dev, "can't update node info, error %d\n", error); return !error; } if (!(kflags & WPI_KFLAG_MULTICAST) && &vap->iv_nw_keys[0] <= k && k < &vap->iv_nw_keys[IEEE80211_WEP_NKID]) { kflags |= WPI_KFLAG_MULTICAST; node.kflags = htole16(kflags); goto again; } return 1; } static void wpi_del_key_cb(void *arg, struct ieee80211_node *ni) { const struct ieee80211_key *k = arg; struct ieee80211vap *vap = ni->ni_vap; struct wpi_softc *sc = ni->ni_ic->ic_softc; struct wpi_node *wn = WPI_NODE(ni); int error; if (vap->iv_bss == ni && wn->id == WPI_ID_UNDEFINED) return; WPI_NT_LOCK(sc); error = wpi_del_key(ni, k); WPI_NT_UNLOCK(sc); if (error == 0) { device_printf(sc->sc_dev, "%s: error while deleting key\n", __func__); } } static int wpi_process_key(struct ieee80211vap *vap, const struct ieee80211_key *k, int set) { struct ieee80211com *ic = vap->iv_ic; struct wpi_softc *sc = ic->ic_softc; struct wpi_vap *wvp = WPI_VAP(vap); struct ieee80211_node *ni; int error, ni_ref = 0; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); if (k->wk_flags & IEEE80211_KEY_SWCRYPT) { /* Not for us. */ return 1; } if (!(k->wk_flags & IEEE80211_KEY_RECV)) { /* XMIT keys are handled in wpi_tx_data(). */ return 1; } /* Handle group keys. */ if (&vap->iv_nw_keys[0] <= k && k < &vap->iv_nw_keys[IEEE80211_WEP_NKID]) { WPI_NT_LOCK(sc); if (set) wvp->wv_gtk |= WPI_VAP_KEY(k->wk_keyix); else wvp->wv_gtk &= ~WPI_VAP_KEY(k->wk_keyix); WPI_NT_UNLOCK(sc); if (vap->iv_state == IEEE80211_S_RUN) { ieee80211_iterate_nodes(&ic->ic_sta, set ? wpi_load_key_cb : wpi_del_key_cb, __DECONST(void *, k)); } return 1; } switch (vap->iv_opmode) { case IEEE80211_M_STA: ni = vap->iv_bss; break; case IEEE80211_M_IBSS: case IEEE80211_M_AHDEMO: case IEEE80211_M_HOSTAP: ni = ieee80211_find_vap_node(&ic->ic_sta, vap, k->wk_macaddr); if (ni == NULL) return 0; /* should not happen */ ni_ref = 1; break; default: device_printf(sc->sc_dev, "%s: unknown opmode %d\n", __func__, vap->iv_opmode); return 0; } WPI_NT_LOCK(sc); if (set) error = wpi_load_key(ni, k); else error = wpi_del_key(ni, k); WPI_NT_UNLOCK(sc); if (ni_ref) ieee80211_node_decref(ni); return error; } static int wpi_key_set(struct ieee80211vap *vap, const struct ieee80211_key *k) { return wpi_process_key(vap, k, 1); } static int wpi_key_delete(struct ieee80211vap *vap, const struct ieee80211_key *k) { return wpi_process_key(vap, k, 0); } /* * This function is called after the runtime firmware notifies us of its * readiness (called in a process context). */ static int wpi_post_alive(struct wpi_softc *sc) { int ntries, error; /* Check (again) that the radio is not disabled. */ if ((error = wpi_nic_lock(sc)) != 0) return error; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); /* NB: Runtime firmware must be up and running. */ if (!(wpi_prph_read(sc, WPI_APMG_RFKILL) & 1)) { device_printf(sc->sc_dev, "RF switch: radio disabled (%s)\n", __func__); wpi_nic_unlock(sc); return EPERM; /* :-) */ } wpi_nic_unlock(sc); /* Wait for thermal sensor to calibrate. */ for (ntries = 0; ntries < 1000; ntries++) { if ((sc->temp = (int)WPI_READ(sc, WPI_UCODE_GP2)) != 0) break; DELAY(10); } if (ntries == 1000) { device_printf(sc->sc_dev, "timeout waiting for thermal sensor calibration\n"); return ETIMEDOUT; } DPRINTF(sc, WPI_DEBUG_TEMP, "temperature %d\n", sc->temp); return 0; } /* * The firmware boot code is small and is intended to be copied directly into * the NIC internal memory (no DMA transfer). */ static int wpi_load_bootcode(struct wpi_softc *sc, const uint8_t *ucode, uint32_t size) { int error, ntries; DPRINTF(sc, WPI_DEBUG_HW, "Loading microcode size 0x%x\n", size); size /= sizeof (uint32_t); if ((error = wpi_nic_lock(sc)) != 0) return error; /* Copy microcode image into NIC memory. */ wpi_prph_write_region_4(sc, WPI_BSM_SRAM_BASE, (const uint32_t *)ucode, size); wpi_prph_write(sc, WPI_BSM_WR_MEM_SRC, 0); wpi_prph_write(sc, WPI_BSM_WR_MEM_DST, WPI_FW_TEXT_BASE); wpi_prph_write(sc, WPI_BSM_WR_DWCOUNT, size); /* Start boot load now. */ wpi_prph_write(sc, WPI_BSM_WR_CTRL, WPI_BSM_WR_CTRL_START); /* Wait for transfer to complete. */ for (ntries = 0; ntries < 1000; ntries++) { uint32_t status = WPI_READ(sc, WPI_FH_TX_STATUS); DPRINTF(sc, WPI_DEBUG_HW, "firmware status=0x%x, val=0x%x, result=0x%x\n", status, WPI_FH_TX_STATUS_IDLE(6), status & WPI_FH_TX_STATUS_IDLE(6)); if (status & WPI_FH_TX_STATUS_IDLE(6)) { DPRINTF(sc, WPI_DEBUG_HW, "Status Match! - ntries = %d\n", ntries); break; } DELAY(10); } if (ntries == 1000) { device_printf(sc->sc_dev, "%s: could not load boot firmware\n", __func__); wpi_nic_unlock(sc); return ETIMEDOUT; } /* Enable boot after power up. */ wpi_prph_write(sc, WPI_BSM_WR_CTRL, WPI_BSM_WR_CTRL_START_EN); wpi_nic_unlock(sc); return 0; } static int wpi_load_firmware(struct wpi_softc *sc) { struct wpi_fw_info *fw = &sc->fw; struct wpi_dma_info *dma = &sc->fw_dma; int error; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); /* Copy initialization sections into pre-allocated DMA-safe memory. */ memcpy(dma->vaddr, fw->init.data, fw->init.datasz); bus_dmamap_sync(dma->tag, dma->map, BUS_DMASYNC_PREWRITE); memcpy(dma->vaddr + WPI_FW_DATA_MAXSZ, fw->init.text, fw->init.textsz); bus_dmamap_sync(dma->tag, dma->map, BUS_DMASYNC_PREWRITE); /* Tell adapter where to find initialization sections. */ if ((error = wpi_nic_lock(sc)) != 0) return error; wpi_prph_write(sc, WPI_BSM_DRAM_DATA_ADDR, dma->paddr); wpi_prph_write(sc, WPI_BSM_DRAM_DATA_SIZE, fw->init.datasz); wpi_prph_write(sc, WPI_BSM_DRAM_TEXT_ADDR, dma->paddr + WPI_FW_DATA_MAXSZ); wpi_prph_write(sc, WPI_BSM_DRAM_TEXT_SIZE, fw->init.textsz); wpi_nic_unlock(sc); /* Load firmware boot code. */ error = wpi_load_bootcode(sc, fw->boot.text, fw->boot.textsz); if (error != 0) { device_printf(sc->sc_dev, "%s: could not load boot firmware\n", __func__); return error; } /* Now press "execute". */ WPI_WRITE(sc, WPI_RESET, 0); /* Wait at most one second for first alive notification. */ if ((error = mtx_sleep(sc, &sc->sc_mtx, PCATCH, "wpiinit", hz)) != 0) { device_printf(sc->sc_dev, "%s: timeout waiting for adapter to initialize, error %d\n", __func__, error); return error; } /* Copy runtime sections into pre-allocated DMA-safe memory. */ memcpy(dma->vaddr, fw->main.data, fw->main.datasz); bus_dmamap_sync(dma->tag, dma->map, BUS_DMASYNC_PREWRITE); memcpy(dma->vaddr + WPI_FW_DATA_MAXSZ, fw->main.text, fw->main.textsz); bus_dmamap_sync(dma->tag, dma->map, BUS_DMASYNC_PREWRITE); /* Tell adapter where to find runtime sections. */ if ((error = wpi_nic_lock(sc)) != 0) return error; wpi_prph_write(sc, WPI_BSM_DRAM_DATA_ADDR, dma->paddr); wpi_prph_write(sc, WPI_BSM_DRAM_DATA_SIZE, fw->main.datasz); wpi_prph_write(sc, WPI_BSM_DRAM_TEXT_ADDR, dma->paddr + WPI_FW_DATA_MAXSZ); wpi_prph_write(sc, WPI_BSM_DRAM_TEXT_SIZE, WPI_FW_UPDATED | fw->main.textsz); wpi_nic_unlock(sc); return 0; } static int wpi_read_firmware(struct wpi_softc *sc) { const struct firmware *fp; struct wpi_fw_info *fw = &sc->fw; const struct wpi_firmware_hdr *hdr; int error; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); DPRINTF(sc, WPI_DEBUG_FIRMWARE, "Attempting Loading Firmware from %s module\n", WPI_FW_NAME); WPI_UNLOCK(sc); fp = firmware_get(WPI_FW_NAME); WPI_LOCK(sc); if (fp == NULL) { device_printf(sc->sc_dev, "could not load firmware image '%s'\n", WPI_FW_NAME); return EINVAL; } sc->fw_fp = fp; if (fp->datasize < sizeof (struct wpi_firmware_hdr)) { device_printf(sc->sc_dev, "firmware file too short: %zu bytes\n", fp->datasize); error = EINVAL; goto fail; } fw->size = fp->datasize; fw->data = (const uint8_t *)fp->data; /* Extract firmware header information. */ hdr = (const struct wpi_firmware_hdr *)fw->data; /* | RUNTIME FIRMWARE | INIT FIRMWARE | BOOT FW | |HDR|<--TEXT-->|<--DATA-->|<--TEXT-->|<--DATA-->|<--TEXT-->| */ fw->main.textsz = le32toh(hdr->rtextsz); fw->main.datasz = le32toh(hdr->rdatasz); fw->init.textsz = le32toh(hdr->itextsz); fw->init.datasz = le32toh(hdr->idatasz); fw->boot.textsz = le32toh(hdr->btextsz); fw->boot.datasz = 0; /* Sanity-check firmware header. */ if (fw->main.textsz > WPI_FW_TEXT_MAXSZ || fw->main.datasz > WPI_FW_DATA_MAXSZ || fw->init.textsz > WPI_FW_TEXT_MAXSZ || fw->init.datasz > WPI_FW_DATA_MAXSZ || fw->boot.textsz > WPI_FW_BOOT_TEXT_MAXSZ || (fw->boot.textsz & 3) != 0) { device_printf(sc->sc_dev, "invalid firmware header\n"); error = EINVAL; goto fail; } /* Check that all firmware sections fit. */ if (fw->size < sizeof (*hdr) + fw->main.textsz + fw->main.datasz + fw->init.textsz + fw->init.datasz + fw->boot.textsz) { device_printf(sc->sc_dev, "firmware file too short: %zu bytes\n", fw->size); error = EINVAL; goto fail; } /* Get pointers to firmware sections. */ fw->main.text = (const uint8_t *)(hdr + 1); fw->main.data = fw->main.text + fw->main.textsz; fw->init.text = fw->main.data + fw->main.datasz; fw->init.data = fw->init.text + fw->init.textsz; fw->boot.text = fw->init.data + fw->init.datasz; DPRINTF(sc, WPI_DEBUG_FIRMWARE, "Firmware Version: Major %d, Minor %d, Driver %d, \n" "runtime (text: %u, data: %u) init (text: %u, data %u) " "boot (text %u)\n", hdr->major, hdr->minor, le32toh(hdr->driver), fw->main.textsz, fw->main.datasz, fw->init.textsz, fw->init.datasz, fw->boot.textsz); DPRINTF(sc, WPI_DEBUG_FIRMWARE, "fw->main.text %p\n", fw->main.text); DPRINTF(sc, WPI_DEBUG_FIRMWARE, "fw->main.data %p\n", fw->main.data); DPRINTF(sc, WPI_DEBUG_FIRMWARE, "fw->init.text %p\n", fw->init.text); DPRINTF(sc, WPI_DEBUG_FIRMWARE, "fw->init.data %p\n", fw->init.data); DPRINTF(sc, WPI_DEBUG_FIRMWARE, "fw->boot.text %p\n", fw->boot.text); return 0; fail: wpi_unload_firmware(sc); return error; } /** * Free the referenced firmware image */ static void wpi_unload_firmware(struct wpi_softc *sc) { if (sc->fw_fp != NULL) { firmware_put(sc->fw_fp, FIRMWARE_UNLOAD); sc->fw_fp = NULL; } } static int wpi_clock_wait(struct wpi_softc *sc) { int ntries; /* Set "initialization complete" bit. */ WPI_SETBITS(sc, WPI_GP_CNTRL, WPI_GP_CNTRL_INIT_DONE); /* Wait for clock stabilization. */ for (ntries = 0; ntries < 2500; ntries++) { if (WPI_READ(sc, WPI_GP_CNTRL) & WPI_GP_CNTRL_MAC_CLOCK_READY) return 0; DELAY(100); } device_printf(sc->sc_dev, "%s: timeout waiting for clock stabilization\n", __func__); return ETIMEDOUT; } static int wpi_apm_init(struct wpi_softc *sc) { uint32_t reg; int error; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); /* Disable L0s exit timer (NMI bug workaround). */ WPI_SETBITS(sc, WPI_GIO_CHICKEN, WPI_GIO_CHICKEN_DIS_L0S_TIMER); /* Don't wait for ICH L0s (ICH bug workaround). */ WPI_SETBITS(sc, WPI_GIO_CHICKEN, WPI_GIO_CHICKEN_L1A_NO_L0S_RX); /* Set FH wait threshold to max (HW bug under stress workaround). */ WPI_SETBITS(sc, WPI_DBG_HPET_MEM, 0xffff0000); /* Retrieve PCIe Active State Power Management (ASPM). */ reg = pci_read_config(sc->sc_dev, sc->sc_cap_off + 0x10, 1); /* Workaround for HW instability in PCIe L0->L0s->L1 transition. */ if (reg & 0x02) /* L1 Entry enabled. */ WPI_SETBITS(sc, WPI_GIO, WPI_GIO_L0S_ENA); else WPI_CLRBITS(sc, WPI_GIO, WPI_GIO_L0S_ENA); WPI_SETBITS(sc, WPI_ANA_PLL, WPI_ANA_PLL_INIT); /* Wait for clock stabilization before accessing prph. */ if ((error = wpi_clock_wait(sc)) != 0) return error; if ((error = wpi_nic_lock(sc)) != 0) return error; /* Cleanup. */ wpi_prph_write(sc, WPI_APMG_CLK_DIS, 0x00000400); wpi_prph_clrbits(sc, WPI_APMG_PS, 0x00000200); /* Enable DMA and BSM (Bootstrap State Machine). */ wpi_prph_write(sc, WPI_APMG_CLK_EN, WPI_APMG_CLK_CTRL_DMA_CLK_RQT | WPI_APMG_CLK_CTRL_BSM_CLK_RQT); DELAY(20); /* Disable L1-Active. */ wpi_prph_setbits(sc, WPI_APMG_PCI_STT, WPI_APMG_PCI_STT_L1A_DIS); wpi_nic_unlock(sc); return 0; } static void wpi_apm_stop_master(struct wpi_softc *sc) { int ntries; /* Stop busmaster DMA activity. */ WPI_SETBITS(sc, WPI_RESET, WPI_RESET_STOP_MASTER); if ((WPI_READ(sc, WPI_GP_CNTRL) & WPI_GP_CNTRL_PS_MASK) == WPI_GP_CNTRL_MAC_PS) return; /* Already asleep. */ for (ntries = 0; ntries < 100; ntries++) { if (WPI_READ(sc, WPI_RESET) & WPI_RESET_MASTER_DISABLED) return; DELAY(10); } device_printf(sc->sc_dev, "%s: timeout waiting for master\n", __func__); } static void wpi_apm_stop(struct wpi_softc *sc) { wpi_apm_stop_master(sc); /* Reset the entire device. */ WPI_SETBITS(sc, WPI_RESET, WPI_RESET_SW); DELAY(10); /* Clear "initialization complete" bit. */ WPI_CLRBITS(sc, WPI_GP_CNTRL, WPI_GP_CNTRL_INIT_DONE); } static void wpi_nic_config(struct wpi_softc *sc) { uint32_t rev; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); /* voodoo from the Linux "driver".. */ rev = pci_read_config(sc->sc_dev, PCIR_REVID, 1); if ((rev & 0xc0) == 0x40) WPI_SETBITS(sc, WPI_HW_IF_CONFIG, WPI_HW_IF_CONFIG_ALM_MB); else if (!(rev & 0x80)) WPI_SETBITS(sc, WPI_HW_IF_CONFIG, WPI_HW_IF_CONFIG_ALM_MM); if (sc->cap == 0x80) WPI_SETBITS(sc, WPI_HW_IF_CONFIG, WPI_HW_IF_CONFIG_SKU_MRC); if ((sc->rev & 0xf0) == 0xd0) WPI_SETBITS(sc, WPI_HW_IF_CONFIG, WPI_HW_IF_CONFIG_REV_D); else WPI_CLRBITS(sc, WPI_HW_IF_CONFIG, WPI_HW_IF_CONFIG_REV_D); if (sc->type > 1) WPI_SETBITS(sc, WPI_HW_IF_CONFIG, WPI_HW_IF_CONFIG_TYPE_B); } static int wpi_hw_init(struct wpi_softc *sc) { uint8_t chnl; int ntries, error; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_BEGIN, __func__); /* Clear pending interrupts. */ WPI_WRITE(sc, WPI_INT, 0xffffffff); if ((error = wpi_apm_init(sc)) != 0) { device_printf(sc->sc_dev, "%s: could not power ON adapter, error %d\n", __func__, error); return error; } /* Select VMAIN power source. */ if ((error = wpi_nic_lock(sc)) != 0) return error; wpi_prph_clrbits(sc, WPI_APMG_PS, WPI_APMG_PS_PWR_SRC_MASK); wpi_nic_unlock(sc); /* Spin until VMAIN gets selected. */ for (ntries = 0; ntries < 5000; ntries++) { if (WPI_READ(sc, WPI_GPIO_IN) & WPI_GPIO_IN_VMAIN) break; DELAY(10); } if (ntries == 5000) { device_printf(sc->sc_dev, "timeout selecting power source\n"); return ETIMEDOUT; } /* Perform adapter initialization. */ wpi_nic_config(sc); /* Initialize RX ring. */ if ((error = wpi_nic_lock(sc)) != 0) return error; /* Set physical address of RX ring. */ WPI_WRITE(sc, WPI_FH_RX_BASE, sc->rxq.desc_dma.paddr); /* Set physical address of RX read pointer. */ WPI_WRITE(sc, WPI_FH_RX_RPTR_ADDR, sc->shared_dma.paddr + offsetof(struct wpi_shared, next)); WPI_WRITE(sc, WPI_FH_RX_WPTR, 0); /* Enable RX. */ WPI_WRITE(sc, WPI_FH_RX_CONFIG, WPI_FH_RX_CONFIG_DMA_ENA | WPI_FH_RX_CONFIG_RDRBD_ENA | WPI_FH_RX_CONFIG_WRSTATUS_ENA | WPI_FH_RX_CONFIG_MAXFRAG | WPI_FH_RX_CONFIG_NRBD(WPI_RX_RING_COUNT_LOG) | WPI_FH_RX_CONFIG_IRQ_DST_HOST | WPI_FH_RX_CONFIG_IRQ_TIMEOUT(1)); (void)WPI_READ(sc, WPI_FH_RSSR_TBL); /* barrier */ wpi_nic_unlock(sc); WPI_WRITE(sc, WPI_FH_RX_WPTR, (WPI_RX_RING_COUNT - 1) & ~7); /* Initialize TX rings. */ if ((error = wpi_nic_lock(sc)) != 0) return error; wpi_prph_write(sc, WPI_ALM_SCHED_MODE, 2); /* bypass mode */ wpi_prph_write(sc, WPI_ALM_SCHED_ARASTAT, 1); /* enable RA0 */ /* Enable all 6 TX rings. */ wpi_prph_write(sc, WPI_ALM_SCHED_TXFACT, 0x3f); wpi_prph_write(sc, WPI_ALM_SCHED_SBYPASS_MODE1, 0x10000); wpi_prph_write(sc, WPI_ALM_SCHED_SBYPASS_MODE2, 0x30002); wpi_prph_write(sc, WPI_ALM_SCHED_TXF4MF, 4); wpi_prph_write(sc, WPI_ALM_SCHED_TXF5MF, 5); /* Set physical address of TX rings. */ WPI_WRITE(sc, WPI_FH_TX_BASE, sc->shared_dma.paddr); WPI_WRITE(sc, WPI_FH_MSG_CONFIG, 0xffff05a5); /* Enable all DMA channels. */ for (chnl = 0; chnl < WPI_NDMACHNLS; chnl++) { WPI_WRITE(sc, WPI_FH_CBBC_CTRL(chnl), 0); WPI_WRITE(sc, WPI_FH_CBBC_BASE(chnl), 0); WPI_WRITE(sc, WPI_FH_TX_CONFIG(chnl), 0x80200008); } wpi_nic_unlock(sc); (void)WPI_READ(sc, WPI_FH_TX_BASE); /* barrier */ /* Clear "radio off" and "commands blocked" bits. */ WPI_WRITE(sc, WPI_UCODE_GP1_CLR, WPI_UCODE_GP1_RFKILL); WPI_WRITE(sc, WPI_UCODE_GP1_CLR, WPI_UCODE_GP1_CMD_BLOCKED); /* Clear pending interrupts. */ WPI_WRITE(sc, WPI_INT, 0xffffffff); /* Enable interrupts. */ WPI_WRITE(sc, WPI_INT_MASK, WPI_INT_MASK_DEF); /* _Really_ make sure "radio off" bit is cleared! */ WPI_WRITE(sc, WPI_UCODE_GP1_CLR, WPI_UCODE_GP1_RFKILL); WPI_WRITE(sc, WPI_UCODE_GP1_CLR, WPI_UCODE_GP1_RFKILL); if ((error = wpi_load_firmware(sc)) != 0) { device_printf(sc->sc_dev, "%s: could not load firmware, error %d\n", __func__, error); return error; } /* Wait at most one second for firmware alive notification. */ if ((error = mtx_sleep(sc, &sc->sc_mtx, PCATCH, "wpiinit", hz)) != 0) { device_printf(sc->sc_dev, "%s: timeout waiting for adapter to initialize, error %d\n", __func__, error); return error; } DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END, __func__); /* Do post-firmware initialization. */ return wpi_post_alive(sc); } static void wpi_hw_stop(struct wpi_softc *sc) { uint8_t chnl, qid; int ntries; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); if (WPI_READ(sc, WPI_UCODE_GP1) & WPI_UCODE_GP1_MAC_SLEEP) wpi_nic_lock(sc); WPI_WRITE(sc, WPI_RESET, WPI_RESET_NEVO); /* Disable interrupts. */ WPI_WRITE(sc, WPI_INT_MASK, 0); WPI_WRITE(sc, WPI_INT, 0xffffffff); WPI_WRITE(sc, WPI_FH_INT, 0xffffffff); /* Make sure we no longer hold the NIC lock. */ wpi_nic_unlock(sc); if (wpi_nic_lock(sc) == 0) { /* Stop TX scheduler. */ wpi_prph_write(sc, WPI_ALM_SCHED_MODE, 0); wpi_prph_write(sc, WPI_ALM_SCHED_TXFACT, 0); /* Stop all DMA channels. */ for (chnl = 0; chnl < WPI_NDMACHNLS; chnl++) { WPI_WRITE(sc, WPI_FH_TX_CONFIG(chnl), 0); for (ntries = 0; ntries < 200; ntries++) { if (WPI_READ(sc, WPI_FH_TX_STATUS) & WPI_FH_TX_STATUS_IDLE(chnl)) break; DELAY(10); } } wpi_nic_unlock(sc); } /* Stop RX ring. */ wpi_reset_rx_ring(sc); /* Reset all TX rings. */ for (qid = 0; qid < WPI_DRV_NTXQUEUES; qid++) wpi_reset_tx_ring(sc, &sc->txq[qid]); if (wpi_nic_lock(sc) == 0) { wpi_prph_write(sc, WPI_APMG_CLK_DIS, WPI_APMG_CLK_CTRL_DMA_CLK_RQT); wpi_nic_unlock(sc); } DELAY(5); /* Power OFF adapter. */ wpi_apm_stop(sc); } static void wpi_radio_on(void *arg0, int pending) { struct wpi_softc *sc = arg0; struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); device_printf(sc->sc_dev, "RF switch: radio enabled\n"); WPI_LOCK(sc); callout_stop(&sc->watchdog_rfkill); WPI_UNLOCK(sc); if (vap != NULL) ieee80211_init(vap); } static void wpi_radio_off(void *arg0, int pending) { struct wpi_softc *sc = arg0; struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); device_printf(sc->sc_dev, "RF switch: radio disabled\n"); ieee80211_notify_radio(ic, 0); wpi_stop(sc); if (vap != NULL) ieee80211_stop(vap); WPI_LOCK(sc); callout_reset(&sc->watchdog_rfkill, hz, wpi_watchdog_rfkill, sc); WPI_UNLOCK(sc); } static int wpi_init(struct wpi_softc *sc) { int error = 0; WPI_LOCK(sc); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_BEGIN, __func__); if (sc->sc_running != 0) goto end; /* Check that the radio is not disabled by hardware switch. */ if (!(WPI_READ(sc, WPI_GP_CNTRL) & WPI_GP_CNTRL_RFKILL)) { device_printf(sc->sc_dev, "RF switch: radio disabled (%s)\n", __func__); callout_reset(&sc->watchdog_rfkill, hz, wpi_watchdog_rfkill, sc); error = EINPROGRESS; goto end; } /* Read firmware images from the filesystem. */ if ((error = wpi_read_firmware(sc)) != 0) { device_printf(sc->sc_dev, "%s: could not read firmware, error %d\n", __func__, error); goto end; } sc->sc_running = 1; /* Initialize hardware and upload firmware. */ error = wpi_hw_init(sc); wpi_unload_firmware(sc); if (error != 0) { device_printf(sc->sc_dev, "%s: could not initialize hardware, error %d\n", __func__, error); goto fail; } /* Configure adapter now that it is ready. */ if ((error = wpi_config(sc)) != 0) { device_printf(sc->sc_dev, "%s: could not configure device, error %d\n", __func__, error); goto fail; } DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END, __func__); WPI_UNLOCK(sc); return 0; fail: wpi_stop_locked(sc); end: DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_END_ERR, __func__); WPI_UNLOCK(sc); return error; } static void wpi_stop_locked(struct wpi_softc *sc) { WPI_LOCK_ASSERT(sc); if (sc->sc_running == 0) return; WPI_TX_LOCK(sc); WPI_TXQ_LOCK(sc); sc->sc_running = 0; WPI_TXQ_UNLOCK(sc); WPI_TX_UNLOCK(sc); WPI_TXQ_STATE_LOCK(sc); callout_stop(&sc->tx_timeout); WPI_TXQ_STATE_UNLOCK(sc); WPI_RXON_LOCK(sc); callout_stop(&sc->scan_timeout); callout_stop(&sc->calib_to); WPI_RXON_UNLOCK(sc); /* Power OFF hardware. */ wpi_hw_stop(sc); } static void wpi_stop(struct wpi_softc *sc) { WPI_LOCK(sc); wpi_stop_locked(sc); WPI_UNLOCK(sc); } /* * Callback from net80211 to start a scan. */ static void wpi_scan_start(struct ieee80211com *ic) { struct wpi_softc *sc = ic->ic_softc; wpi_set_led(sc, WPI_LED_LINK, 20, 2); } /* * Callback from net80211 to terminate a scan. */ static void wpi_scan_end(struct ieee80211com *ic) { struct wpi_softc *sc = ic->ic_softc; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); if (vap->iv_state == IEEE80211_S_RUN) wpi_set_led(sc, WPI_LED_LINK, 0, 1); } /** * Called by the net80211 framework to indicate to the driver * that the channel should be changed */ static void wpi_set_channel(struct ieee80211com *ic) { const struct ieee80211_channel *c = ic->ic_curchan; struct wpi_softc *sc = ic->ic_softc; int error; DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); WPI_LOCK(sc); sc->sc_rxtap.wr_chan_freq = htole16(c->ic_freq); sc->sc_rxtap.wr_chan_flags = htole16(c->ic_flags); WPI_UNLOCK(sc); WPI_TX_LOCK(sc); sc->sc_txtap.wt_chan_freq = htole16(c->ic_freq); sc->sc_txtap.wt_chan_flags = htole16(c->ic_flags); WPI_TX_UNLOCK(sc); /* * Only need to set the channel in Monitor mode. AP scanning and auth * are already taken care of by their respective firmware commands. */ if (ic->ic_opmode == IEEE80211_M_MONITOR) { WPI_RXON_LOCK(sc); sc->rxon.chan = ieee80211_chan2ieee(ic, c); if (IEEE80211_IS_CHAN_2GHZ(c)) { sc->rxon.flags |= htole32(WPI_RXON_AUTO | WPI_RXON_24GHZ); } else { sc->rxon.flags &= ~htole32(WPI_RXON_AUTO | WPI_RXON_24GHZ); } if ((error = wpi_send_rxon(sc, 0, 1)) != 0) device_printf(sc->sc_dev, "%s: error %d setting channel\n", __func__, error); WPI_RXON_UNLOCK(sc); } } /** * Called by net80211 to indicate that we need to scan the current * channel. The channel is previously be set via the wpi_set_channel * callback. */ static void wpi_scan_curchan(struct ieee80211_scan_state *ss, unsigned long maxdwell) { struct ieee80211vap *vap = ss->ss_vap; struct ieee80211com *ic = vap->iv_ic; struct wpi_softc *sc = ic->ic_softc; int error; WPI_RXON_LOCK(sc); error = wpi_scan(sc, ic->ic_curchan); WPI_RXON_UNLOCK(sc); if (error != 0) ieee80211_cancel_scan(vap); } /** * Called by the net80211 framework to indicate * the minimum dwell time has been met, terminate the scan. * We don't actually terminate the scan as the firmware will notify * us when it's finished and we have no way to interrupt it. */ static void wpi_scan_mindwell(struct ieee80211_scan_state *ss) { /* NB: don't try to abort scan; wait for firmware to finish */ } static void wpi_hw_reset(void *arg, int pending) { struct wpi_softc *sc = arg; struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); DPRINTF(sc, WPI_DEBUG_TRACE, TRACE_STR_DOING, __func__); ieee80211_notify_radio(ic, 0); if (vap != NULL && (ic->ic_flags & IEEE80211_F_SCAN)) ieee80211_cancel_scan(vap); wpi_stop(sc); if (vap != NULL) { ieee80211_stop(vap); ieee80211_init(vap); } }