Index: head/sys/dev/iwm/if_iwm.c =================================================================== --- head/sys/dev/iwm/if_iwm.c (revision 307157) +++ head/sys/dev/iwm/if_iwm.c (revision 307158) @@ -1,6270 +1,6272 @@ /* $OpenBSD: if_iwm.c,v 1.42 2015/05/30 02:49:23 deraadt 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 "opt_wlan.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 #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 }; _Static_assert(nitems(iwm_nvm_channels) <= IWM_NUM_CHANNELS, "IWM_NUM_CHANNELS is too small"); const uint8_t iwm_nvm_channels_8000[] = { /* 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, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, 149, 153, 157, 161, 165, 169, 173, 177, 181 }; _Static_assert(nitems(iwm_nvm_channels_8000) <= IWM_NUM_CHANNELS_8000, "IWM_NUM_CHANNELS_8000 is too small"); #define IWM_NUM_2GHZ_CHANNELS 14 #define IWM_N_HW_ADDR_MASK 0xF /* * 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) struct iwm_nvm_section { uint16_t length; uint8_t *data; }; 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 int iwm_alloc_sched(struct iwm_softc *); static int iwm_alloc_kw(struct iwm_softc *); static int iwm_alloc_ict(struct iwm_softc *); static int iwm_alloc_rx_ring(struct iwm_softc *, struct iwm_rx_ring *); static void iwm_disable_rx_dma(struct iwm_softc *); 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 int iwm_enable_txq(struct iwm_softc *, int, 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 *, size_t); static uint32_t iwm_eeprom_channel_flags(uint16_t); static void iwm_add_channel_band(struct iwm_softc *, struct ieee80211_channel[], int, int *, int, size_t, const uint8_t[]); static void iwm_init_channel_map(struct ieee80211com *, int, int *, struct ieee80211_channel[]); static int iwm_parse_nvm_data(struct iwm_softc *, const uint16_t *, const uint16_t *, const uint16_t *, const uint16_t *, const uint16_t *, const uint16_t *); static void iwm_set_hw_address_8000(struct iwm_softc *, struct iwm_nvm_data *, const uint16_t *, const uint16_t *); static int iwm_get_sku(const struct iwm_softc *, const uint16_t *, const uint16_t *); static int iwm_get_nvm_version(const struct iwm_softc *, const uint16_t *); static int iwm_get_radio_cfg(const struct iwm_softc *, const uint16_t *, const uint16_t *); static int iwm_get_n_hw_addrs(const struct iwm_softc *, const uint16_t *); static void iwm_set_radio_cfg(const struct iwm_softc *, struct iwm_nvm_data *, uint32_t); 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_sect(struct iwm_softc *, uint32_t, const uint8_t *, uint32_t); static int iwm_firmware_load_chunk(struct iwm_softc *, uint32_t, const uint8_t *, uint32_t); static int iwm_load_firmware_7000(struct iwm_softc *, enum iwm_ucode_type); static int iwm_load_cpu_sections_8000(struct iwm_softc *, struct iwm_fw_sects *, int , int *); static int iwm_load_firmware_8000(struct iwm_softc *, enum iwm_ucode_type); 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_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(struct iwm_softc *sc, 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 int 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 mbuf *, 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 int iwm_mvm_flush_tx_path(struct iwm_softc *sc, uint32_t tfd_msk, uint32_t flags); static int iwm_mvm_send_add_sta_cmd_status(struct iwm_softc *, struct iwm_mvm_add_sta_cmd_v7 *, 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 void iwm_mvm_fill_sf_command(struct iwm_softc *, struct iwm_sf_cfg_cmd *, struct ieee80211_node *); static int iwm_mvm_sf_config(struct iwm_softc *, enum iwm_sf_state); static int iwm_send_bt_init_conf(struct iwm_softc *); static int iwm_send_update_mcc_cmd(struct iwm_softc *, const char *); static void iwm_mvm_tt_tx_backoff(struct iwm_softc *, uint32_t); 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 *); static void iwm_nic_umac_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 int iwm_is_valid_ether_addr(uint8_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++; return 0; } #define IWM_DEFAULT_SCAN_CHANNELS 40 /* 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_fp, FIRMWARE_UNLOAD); fw->fw_fp = NULL; /* 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_fp != NULL) iwm_fw_info_free(fw); /* * Load firmware into driver memory. * fw_fp will be set. */ IWM_UNLOCK(sc); fwp = firmware_get(sc->sc_fwname); IWM_LOCK(sc); if (fwp == NULL) { device_printf(sc->sc_dev, "could not read firmware %s (error %d)\n", sc->sc_fwname, error); goto out; } fw->fw_fp = fwp; /* (Re-)Initialize default values. */ sc->sc_capaflags = 0; sc->sc_capa_n_scan_channels = IWM_DEFAULT_SCAN_CHANNELS; memset(sc->sc_enabled_capa, 0, sizeof(sc->sc_enabled_capa)); memset(sc->sc_fw_mcc, 0, sizeof(sc->sc_fw_mcc)); /* * Parse firmware contents */ uhdr = (const void *)fw->fw_fp->data; if (*(const uint32_t *)fw->fw_fp->data != 0 || le32toh(uhdr->magic) != IWM_TLV_UCODE_MAGIC) { device_printf(sc->sc_dev, "invalid firmware %s\n", sc->sc_fwname); error = EINVAL; goto out; } snprintf(sc->sc_fwver, sizeof(sc->sc_fwver), "%d.%d (API ver %d)", IWM_UCODE_MAJOR(le32toh(uhdr->ver)), IWM_UCODE_MINOR(le32toh(uhdr->ver)), IWM_UCODE_API(le32toh(uhdr->ver))); data = uhdr->data; len = fw->fw_fp->datasize - 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 > IWM_SCAN_OFFLOAD_PROBE_REQ_SIZE) { 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: { uint32_t num_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; } num_cpu = le32toh(*(const uint32_t *)tlv_data); if (num_cpu < 1 || num_cpu > 2) { device_printf(sc->sc_dev, "%s: Driver supports only 1 or 2 CPUs\n", __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: { const struct iwm_ucode_api *api; if (tlv_len != sizeof(*api)) { error = EINVAL; goto parse_out; } api = (const struct iwm_ucode_api *)tlv_data; /* Flags may exceed 32 bits in future firmware. */ if (le32toh(api->api_index) > 0) { device_printf(sc->sc_dev, "unsupported API index %d\n", le32toh(api->api_index)); goto parse_out; } sc->sc_ucode_api = le32toh(api->api_flags); break; } case IWM_UCODE_TLV_ENABLED_CAPABILITIES: { const struct iwm_ucode_capa *capa; int idx, i; if (tlv_len != sizeof(*capa)) { error = EINVAL; goto parse_out; } capa = (const struct iwm_ucode_capa *)tlv_data; idx = le32toh(capa->api_index); if (idx >= howmany(IWM_NUM_UCODE_TLV_CAPA, 32)) { device_printf(sc->sc_dev, "unsupported API index %d\n", idx); goto parse_out; } for (i = 0; i < 32; i++) { if ((le32toh(capa->api_capa) & (1U << i)) == 0) continue; setbit(sc->sc_enabled_capa, i + (32 * idx)); } break; } case 48: /* undocumented TLV */ case IWM_UCODE_TLV_SDIO_ADMA_ADDR: case IWM_UCODE_TLV_FW_GSCAN_CAPA: /* ignore, not used by current driver */ break; case IWM_UCODE_TLV_SEC_RT_USNIFFER: if ((error = iwm_firmware_store_section(sc, IWM_UCODE_TYPE_REGULAR_USNIFFER, tlv_data, tlv_len)) != 0) goto parse_out; break; case IWM_UCODE_TLV_N_SCAN_CHANNELS: if (tlv_len != sizeof(uint32_t)) { error = EINVAL; goto parse_out; } sc->sc_capa_n_scan_channels = le32toh(*(const uint32_t *)tlv_data); break; case IWM_UCODE_TLV_FW_VERSION: if (tlv_len != sizeof(uint32_t) * 3) { error = EINVAL; goto parse_out; } snprintf(sc->sc_fwver, sizeof(sc->sc_fwver), "%d.%d.%d", le32toh(((const uint32_t *)tlv_data)[0]), le32toh(((const uint32_t *)tlv_data)[1]), le32toh(((const uint32_t *)tlv_data)[2])); 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_fp != 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->map = NULL; dma->size = size; dma->vaddr = NULL; error = bus_dma_tag_create(tag, alignment, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, size, 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) { bus_dmamem_free(dma->tag, dma->vaddr, dma->map); dma->vaddr = NULL; 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->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; } } /* 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); } /* tx scheduler rings. not used? */ static int iwm_alloc_sched(struct iwm_softc *sc) { /* TX scheduler rings must be aligned on a 1KB boundary. */ return iwm_dma_contig_alloc(sc->sc_dmat, &sc->sched_dma, nitems(sc->txq) * sizeof(struct iwm_agn_scd_bc_tbl), 1024); } /* 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); } /* 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<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, 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 spare bus_dmamap_t for iwm_rx_addbuf() */ error = bus_dmamap_create(ring->data_dmat, 0, &ring->spare_map); if (error != 0) { device_printf(sc->sc_dev, "%s: could not create RX buf DMA map, error %d\n", __func__, error); goto fail; } /* * Allocate and map RX buffers. */ for (i = 0; i < IWM_RX_RING_COUNT; i++) { struct iwm_rx_data *data = &ring->data[i]; 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 = NULL; 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_disable_rx_dma(struct iwm_softc *sc) { /* XXX conditional nic locks are stupid */ /* 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); } } static void iwm_reset_rx_ring(struct iwm_softc *sc, struct iwm_rx_ring *ring) { /* Reset the ring state */ ring->cur = 0; /* * The hw rx ring index in shared memory must also be cleared, * otherwise the discrepancy can cause reprocessing chaos. */ 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->spare_map != NULL) { bus_dmamap_destroy(ring->data_dmat, ring->spare_map); ring->spare_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; size_t maxsize; int nsegments; 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; /* FW commands may require more mapped space than packets. */ if (qid == IWM_MVM_CMD_QUEUE) { maxsize = IWM_RBUF_SIZE; nsegments = 1; } else { maxsize = MCLBYTES; nsegments = IWM_MAX_SCATTER - 2; } error = bus_dma_tag_create(sc->sc_dmat, 1, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, maxsize, nsegments, maxsize, 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_WRITE_POINTER | 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, qid; uint32_t mask = 0; /* 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); if (iwm_nic_lock(sc)) { /* Stop each Tx DMA channel */ for (chnl = 0; chnl < IWM_FH_TCSR_CHNL_NUM; chnl++) { IWM_WRITE(sc, IWM_FH_TCSR_CHNL_TX_CONFIG_REG(chnl), 0); mask |= IWM_FH_TSSR_TX_STATUS_REG_MSK_CHNL_IDLE(chnl); } /* Wait for DMA channels to be idle */ if (!iwm_poll_bit(sc, IWM_FH_TSSR_TX_STATUS_REG, mask, mask, 5000)) { device_printf(sc->sc_dev, "Failing on timeout while stopping DMA channel: [0x%08x]\n", IWM_READ(sc, IWM_FH_TSSR_TX_STATUS_REG)); } iwm_nic_unlock(sc); } iwm_disable_rx_dma(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_SW_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. */ if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000) { 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_disable_rx_dma(sc); 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. */ 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_CHNL0_RX_CONFIG_SINGLE_FRAME_MSK | (IWM_RX_RB_TIMEOUT << IWM_FH_RCSR_RX_CONFIG_REG_IRQ_RBTH_POS) | 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_write_prph(sc, IWM_SCD_GP_CTRL, IWM_SCD_GP_CTRL_AUTO_ACTIVE_MODE); iwm_nic_unlock(sc); return 0; } static int iwm_nic_init(struct iwm_softc *sc) { int error; iwm_apm_init(sc); if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000) 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; } 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 int iwm_enable_txq(struct iwm_softc *sc, int sta_id, int qid, int fifo) { if (!iwm_nic_lock(sc)) { device_printf(sc->sc_dev, "%s: cannot enable txq %d\n", __func__, qid); return EBUSY; } IWM_WRITE(sc, IWM_HBUS_TARG_WRPTR, qid << 8 | 0); if (qid == IWM_MVM_CMD_QUEUE) { /* 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)); iwm_clear_bits_prph(sc, IWM_SCD_AGGR_SEL, (1 << qid)); 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); } else { struct iwm_scd_txq_cfg_cmd cmd; int error; iwm_nic_unlock(sc); memset(&cmd, 0, sizeof(cmd)); cmd.scd_queue = qid; cmd.enable = 1; cmd.sta_id = sta_id; cmd.tx_fifo = fifo; cmd.aggregate = 0; cmd.window = IWM_FRAME_LIMIT; error = iwm_mvm_send_cmd_pdu(sc, IWM_SCD_QUEUE_CFG, IWM_CMD_SYNC, sizeof(cmd), &cmd); if (error) { device_printf(sc->sc_dev, "cannot enable txq %d\n", qid); return error; } if (!iwm_nic_lock(sc)) return EBUSY; } iwm_write_prph(sc, IWM_SCD_EN_CTRL, iwm_read_prph(sc, IWM_SCD_EN_CTRL) | qid); iwm_nic_unlock(sc); IWM_DPRINTF(sc, IWM_DEBUG_XMIT, "%s: enabled txq %d FIFO %d\n", __func__, qid, fifo); return 0; } static int iwm_post_alive(struct iwm_softc *sc) { int nwords; int error, chnl; uint32_t base; if (!iwm_nic_lock(sc)) return EBUSY; base = iwm_read_prph(sc, IWM_SCD_SRAM_BASE_ADDR); if (sc->sched_base != base) { device_printf(sc->sc_dev, "%s: sched addr mismatch: alive: 0x%x prph: 0x%x\n", __func__, sc->sched_base, base); } 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); iwm_nic_unlock(sc); /* enable command channel */ error = iwm_enable_txq(sc, 0 /* unused */, IWM_MVM_CMD_QUEUE, 7); if (error) return error; if (!iwm_nic_lock(sc)) return EBUSY; 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 */ if (sc->sc_device_family != IWM_DEVICE_FAMILY_8000) { 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_REGULATORY, IWM_NVM_SECTION_TYPE_CALIBRATION, IWM_NVM_SECTION_TYPE_PRODUCTION, IWM_NVM_SECTION_TYPE_HW_8000, IWM_NVM_SECTION_TYPE_MAC_OVERRIDE, IWM_NVM_SECTION_TYPE_PHY_SKU, }; /* Default NVM size to read */ #define IWM_NVM_DEFAULT_CHUNK_SIZE (2*1024) #define IWM_MAX_NVM_SECTION_SIZE 8192 #define IWM_NVM_WRITE_OPCODE 1 #define IWM_NVM_READ_OPCODE 0 /* load nvm chunk response */ #define IWM_READ_NVM_CHUNK_SUCCEED 0 #define IWM_READ_NVM_CHUNK_INVALID_ADDRESS 1 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, offset_read; size_t bytes_read; uint8_t *resp_data; cmd.len[0] = sizeof(struct iwm_nvm_access_cmd); ret = iwm_send_cmd(sc, &cmd); if (ret) { device_printf(sc->sc_dev, "Could not send NVM_ACCESS command (error=%d)\n", ret); return ret; } pkt = cmd.resp_pkt; if (pkt->hdr.flags & IWM_CMD_FAILED_MSK) { device_printf(sc->sc_dev, "Bad return from IWM_NVM_ACCES_COMMAND (0x%08X)\n", 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) { IWM_DPRINTF(sc, IWM_DEBUG_RESET, "NVM access command failed with status %d\n", ret); ret = EINVAL; goto exit; } if (offset_read != offset) { device_printf(sc->sc_dev, "NVM ACCESS response with invalid offset %d\n", offset_read); ret = EINVAL; goto exit; } if (bytes_read > length) { device_printf(sc->sc_dev, "NVM ACCESS response with too much data " "(%d bytes requested, %zd bytes received)\n", length, bytes_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 don'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, size_t max_len) { uint16_t chunklen, seglen; int error = 0; IWM_DPRINTF(sc, IWM_DEBUG_RESET, "reading NVM section %d\n", section); chunklen = seglen = IWM_NVM_DEFAULT_CHUNK_SIZE; *len = 0; /* Read NVM chunks until exhausted (reading less than requested) */ while (seglen == chunklen && *len < max_len) { error = iwm_nvm_read_chunk(sc, section, *len, chunklen, data, &seglen); if (error) { IWM_DPRINTF(sc, IWM_DEBUG_RESET, "Cannot read from NVM section " "%d at offset %d\n", section, *len); return error; } *len += seglen; } IWM_DPRINTF(sc, IWM_DEBUG_RESET, "NVM section %d read completed (%d bytes, error=%d)\n", section, *len, error); return error; } /* * BEGIN IWM_NVM_PARSE */ /* iwlwifi/iwl-nvm-parse.c */ /* NVM offsets (in words) definitions */ enum iwm_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 }; enum iwm_8000_nvm_offsets { /* NVM HW-Section offset (in words) definitions */ IWM_HW_ADDR0_WFPM_8000 = 0x12, IWM_HW_ADDR1_WFPM_8000 = 0x16, IWM_HW_ADDR0_PCIE_8000 = 0x8A, IWM_HW_ADDR1_PCIE_8000 = 0x8E, IWM_MAC_ADDRESS_OVERRIDE_8000 = 1, /* NVM SW-Section offset (in words) definitions */ IWM_NVM_SW_SECTION_8000 = 0x1C0, IWM_NVM_VERSION_8000 = 0, IWM_RADIO_CFG_8000 = 0, IWM_SKU_8000 = 2, IWM_N_HW_ADDRS_8000 = 3, /* NVM REGULATORY -Section offset (in words) definitions */ IWM_NVM_CHANNELS_8000 = 0, IWM_NVM_LAR_OFFSET_8000_OLD = 0x4C7, IWM_NVM_LAR_OFFSET_8000 = 0x507, IWM_NVM_LAR_ENABLED_8000 = 0x7, /* NVM calibration section offset (in words) definitions */ IWM_NVM_CALIB_SECTION_8000 = 0x2B8, IWM_XTAL_CALIB_8000 = 0x316 - IWM_NVM_CALIB_SECTION_8000 }; /* 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 IWM_NVM_RF_CFG_FLAVOR_MSK_8000(x) (x & 0xF) #define IWM_NVM_RF_CFG_DASH_MSK_8000(x) ((x >> 4) & 0xF) #define IWM_NVM_RF_CFG_STEP_MSK_8000(x) ((x >> 8) & 0xF) #define IWM_NVM_RF_CFG_TYPE_MSK_8000(x) ((x >> 12) & 0xFFF) #define IWM_NVM_RF_CFG_TX_ANT_MSK_8000(x) ((x >> 24) & 0xF) #define IWM_NVM_RF_CFG_RX_ANT_MSK_8000(x) ((x >> 28) & 0xF) #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 * XXX cannot find this (DFS) flag in iwl-nvm-parse.c * @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), }; /* * Translate EEPROM flags to net80211. */ static uint32_t iwm_eeprom_channel_flags(uint16_t ch_flags) { uint32_t nflags; nflags = 0; if ((ch_flags & IWM_NVM_CHANNEL_ACTIVE) == 0) nflags |= IEEE80211_CHAN_PASSIVE; if ((ch_flags & IWM_NVM_CHANNEL_IBSS) == 0) nflags |= IEEE80211_CHAN_NOADHOC; if (ch_flags & IWM_NVM_CHANNEL_RADAR) { nflags |= IEEE80211_CHAN_DFS; /* Just in case. */ nflags |= IEEE80211_CHAN_NOADHOC; } return (nflags); } static void iwm_add_channel_band(struct iwm_softc *sc, struct ieee80211_channel chans[], int maxchans, int *nchans, int ch_idx, size_t ch_num, const uint8_t bands[]) { const uint16_t * const nvm_ch_flags = sc->sc_nvm.nvm_ch_flags; uint32_t nflags; uint16_t ch_flags; uint8_t ieee; int error; for (; ch_idx < ch_num; ch_idx++) { ch_flags = le16_to_cpup(nvm_ch_flags + ch_idx); if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000) ieee = iwm_nvm_channels[ch_idx]; else ieee = iwm_nvm_channels_8000[ch_idx]; if (!(ch_flags & IWM_NVM_CHANNEL_VALID)) { IWM_DPRINTF(sc, IWM_DEBUG_EEPROM, "Ch. %d Flags %x [%sGHz] - No traffic\n", ieee, ch_flags, (ch_idx >= IWM_NUM_2GHZ_CHANNELS) ? "5.2" : "2.4"); continue; } nflags = iwm_eeprom_channel_flags(ch_flags); error = ieee80211_add_channel(chans, maxchans, nchans, ieee, 0, 0, nflags, bands); if (error != 0) break; IWM_DPRINTF(sc, IWM_DEBUG_EEPROM, "Ch. %d Flags %x [%sGHz] - Added\n", ieee, ch_flags, (ch_idx >= IWM_NUM_2GHZ_CHANNELS) ? "5.2" : "2.4"); } } static void iwm_init_channel_map(struct ieee80211com *ic, int maxchans, int *nchans, struct ieee80211_channel chans[]) { struct iwm_softc *sc = ic->ic_softc; struct iwm_nvm_data *data = &sc->sc_nvm; uint8_t bands[IEEE80211_MODE_BYTES]; size_t ch_num; memset(bands, 0, sizeof(bands)); /* 1-13: 11b/g channels. */ setbit(bands, IEEE80211_MODE_11B); setbit(bands, IEEE80211_MODE_11G); iwm_add_channel_band(sc, chans, maxchans, nchans, 0, IWM_NUM_2GHZ_CHANNELS - 1, bands); /* 14: 11b channel only. */ clrbit(bands, IEEE80211_MODE_11G); iwm_add_channel_band(sc, chans, maxchans, nchans, IWM_NUM_2GHZ_CHANNELS - 1, IWM_NUM_2GHZ_CHANNELS, bands); if (data->sku_cap_band_52GHz_enable) { if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000) ch_num = nitems(iwm_nvm_channels); else ch_num = nitems(iwm_nvm_channels_8000); memset(bands, 0, sizeof(bands)); setbit(bands, IEEE80211_MODE_11A); iwm_add_channel_band(sc, chans, maxchans, nchans, IWM_NUM_2GHZ_CHANNELS, ch_num, bands); } } static void iwm_set_hw_address_8000(struct iwm_softc *sc, struct iwm_nvm_data *data, const uint16_t *mac_override, const uint16_t *nvm_hw) { const uint8_t *hw_addr; if (mac_override) { static const uint8_t reserved_mac[] = { 0x02, 0xcc, 0xaa, 0xff, 0xee, 0x00 }; hw_addr = (const uint8_t *)(mac_override + IWM_MAC_ADDRESS_OVERRIDE_8000); /* * Store the MAC address from MAO section. * No byte swapping is required in MAO section */ IEEE80211_ADDR_COPY(data->hw_addr, hw_addr); /* * Force the use of the OTP MAC address in case of reserved MAC * address in the NVM, or if address is given but invalid. */ if (!IEEE80211_ADDR_EQ(reserved_mac, hw_addr) && !IEEE80211_ADDR_EQ(ieee80211broadcastaddr, data->hw_addr) && iwm_is_valid_ether_addr(data->hw_addr) && !IEEE80211_IS_MULTICAST(data->hw_addr)) return; IWM_DPRINTF(sc, IWM_DEBUG_RESET, "%s: mac address from nvm override section invalid\n", __func__); } if (nvm_hw) { /* read the mac address from WFMP registers */ uint32_t mac_addr0 = htole32(iwm_read_prph(sc, IWM_WFMP_MAC_ADDR_0)); uint32_t mac_addr1 = htole32(iwm_read_prph(sc, IWM_WFMP_MAC_ADDR_1)); hw_addr = (const uint8_t *)&mac_addr0; data->hw_addr[0] = hw_addr[3]; data->hw_addr[1] = hw_addr[2]; data->hw_addr[2] = hw_addr[1]; data->hw_addr[3] = hw_addr[0]; hw_addr = (const uint8_t *)&mac_addr1; data->hw_addr[4] = hw_addr[1]; data->hw_addr[5] = hw_addr[0]; return; } device_printf(sc->sc_dev, "%s: mac address not found\n", __func__); memset(data->hw_addr, 0, sizeof(data->hw_addr)); } static int iwm_get_sku(const struct iwm_softc *sc, const uint16_t *nvm_sw, const uint16_t *phy_sku) { if (sc->sc_device_family != IWM_DEVICE_FAMILY_8000) return le16_to_cpup(nvm_sw + IWM_SKU); return le32_to_cpup((const uint32_t *)(phy_sku + IWM_SKU_8000)); } static int iwm_get_nvm_version(const struct iwm_softc *sc, const uint16_t *nvm_sw) { if (sc->sc_device_family != IWM_DEVICE_FAMILY_8000) return le16_to_cpup(nvm_sw + IWM_NVM_VERSION); else return le32_to_cpup((const uint32_t *)(nvm_sw + IWM_NVM_VERSION_8000)); } static int iwm_get_radio_cfg(const struct iwm_softc *sc, const uint16_t *nvm_sw, const uint16_t *phy_sku) { if (sc->sc_device_family != IWM_DEVICE_FAMILY_8000) return le16_to_cpup(nvm_sw + IWM_RADIO_CFG); return le32_to_cpup((const uint32_t *)(phy_sku + IWM_RADIO_CFG_8000)); } static int iwm_get_n_hw_addrs(const struct iwm_softc *sc, const uint16_t *nvm_sw) { int n_hw_addr; if (sc->sc_device_family != IWM_DEVICE_FAMILY_8000) return le16_to_cpup(nvm_sw + IWM_N_HW_ADDRS); n_hw_addr = le32_to_cpup((const uint32_t *)(nvm_sw + IWM_N_HW_ADDRS_8000)); return n_hw_addr & IWM_N_HW_ADDR_MASK; } static void iwm_set_radio_cfg(const struct iwm_softc *sc, struct iwm_nvm_data *data, uint32_t radio_cfg) { if (sc->sc_device_family != IWM_DEVICE_FAMILY_8000) { 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); return; } /* set the radio configuration for family 8000 */ data->radio_cfg_type = IWM_NVM_RF_CFG_TYPE_MSK_8000(radio_cfg); data->radio_cfg_step = IWM_NVM_RF_CFG_STEP_MSK_8000(radio_cfg); data->radio_cfg_dash = IWM_NVM_RF_CFG_DASH_MSK_8000(radio_cfg); data->radio_cfg_pnum = IWM_NVM_RF_CFG_FLAVOR_MSK_8000(radio_cfg); data->valid_tx_ant = IWM_NVM_RF_CFG_TX_ANT_MSK_8000(radio_cfg); data->valid_rx_ant = IWM_NVM_RF_CFG_RX_ANT_MSK_8000(radio_cfg); } 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, const uint16_t *mac_override, const uint16_t *phy_sku, const uint16_t *regulatory) { struct iwm_nvm_data *data = &sc->sc_nvm; uint8_t hw_addr[IEEE80211_ADDR_LEN]; uint32_t sku, radio_cfg; data->nvm_version = iwm_get_nvm_version(sc, nvm_sw); radio_cfg = iwm_get_radio_cfg(sc, nvm_sw, phy_sku); iwm_set_radio_cfg(sc, data, radio_cfg); sku = iwm_get_sku(sc, nvm_sw, phy_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; data->n_hw_addrs = iwm_get_n_hw_addrs(sc, nvm_sw); /* The byte order is little endian 16 bit, meaning 214365 */ if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000) { 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]; } else { iwm_set_hw_address_8000(sc, data, mac_override, nvm_hw); } if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000) { memcpy(data->nvm_ch_flags, &nvm_sw[IWM_NVM_CHANNELS], IWM_NUM_CHANNELS * sizeof(uint16_t)); } else { memcpy(data->nvm_ch_flags, ®ulatory[IWM_NVM_CHANNELS_8000], IWM_NUM_CHANNELS_8000 * sizeof(uint16_t)); } return 0; } /* * END NVM PARSE */ static int iwm_parse_nvm_sections(struct iwm_softc *sc, struct iwm_nvm_section *sections) { const uint16_t *hw, *sw, *calib, *regulatory, *mac_override, *phy_sku; /* Checking for required sections */ if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000) { if (!sections[IWM_NVM_SECTION_TYPE_SW].data || !sections[IWM_NVM_SECTION_TYPE_HW].data) { device_printf(sc->sc_dev, "Can't parse empty OTP/NVM sections\n"); return ENOENT; } hw = (const uint16_t *) sections[IWM_NVM_SECTION_TYPE_HW].data; } else if (sc->sc_device_family == IWM_DEVICE_FAMILY_8000) { /* SW and REGULATORY sections are mandatory */ if (!sections[IWM_NVM_SECTION_TYPE_SW].data || !sections[IWM_NVM_SECTION_TYPE_REGULATORY].data) { device_printf(sc->sc_dev, "Can't parse empty OTP/NVM sections\n"); return ENOENT; } /* MAC_OVERRIDE or at least HW section must exist */ if (!sections[IWM_NVM_SECTION_TYPE_HW_8000].data && !sections[IWM_NVM_SECTION_TYPE_MAC_OVERRIDE].data) { device_printf(sc->sc_dev, "Can't parse mac_address, empty sections\n"); return ENOENT; } /* PHY_SKU section is mandatory in B0 */ if (!sections[IWM_NVM_SECTION_TYPE_PHY_SKU].data) { device_printf(sc->sc_dev, "Can't parse phy_sku in B0, empty sections\n"); return ENOENT; } hw = (const uint16_t *) sections[IWM_NVM_SECTION_TYPE_HW_8000].data; } else { panic("unknown device family %d\n", sc->sc_device_family); } sw = (const uint16_t *)sections[IWM_NVM_SECTION_TYPE_SW].data; calib = (const uint16_t *) sections[IWM_NVM_SECTION_TYPE_CALIBRATION].data; regulatory = (const uint16_t *) sections[IWM_NVM_SECTION_TYPE_REGULATORY].data; mac_override = (const uint16_t *) sections[IWM_NVM_SECTION_TYPE_MAC_OVERRIDE].data; phy_sku = (const uint16_t *)sections[IWM_NVM_SECTION_TYPE_PHY_SKU].data; return iwm_parse_nvm_data(sc, hw, sw, calib, mac_override, phy_sku, regulatory); } 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 *buf; const size_t bufsz = IWM_MAX_NVM_SECTION_SIZE; memset(nvm_sections, 0 , sizeof(nvm_sections)); buf = malloc(bufsz, M_DEVBUF, M_NOWAIT); if (buf == 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, buf, &len, bufsz); if (error) { error = 0; continue; } nvm_sections[section].data = malloc(len, M_DEVBUF, M_NOWAIT); if (nvm_sections[section].data == NULL) { error = ENOMEM; break; } memcpy(nvm_sections[section].data, buf, len); nvm_sections[section].length = len; } free(buf, M_DEVBUF); if (error == 0) error = iwm_parse_nvm_sections(sc, nvm_sections); for (i = 0; i < IWM_NVM_NUM_OF_SECTIONS; i++) { if (nvm_sections[i].data != NULL) free(nvm_sections[i].data, M_DEVBUF); } return error; } /* * Firmware loading gunk. This is kind of a weird hybrid between the * iwn driver and the Linux iwlwifi driver. */ static int iwm_firmware_load_sect(struct iwm_softc *sc, uint32_t dst_addr, const uint8_t *section, uint32_t byte_cnt) { int error = EINVAL; uint32_t chunk_sz, offset; chunk_sz = MIN(IWM_FH_MEM_TB_MAX_LENGTH, byte_cnt); for (offset = 0; offset < byte_cnt; offset += chunk_sz) { uint32_t addr, len; const uint8_t *data; addr = dst_addr + offset; len = MIN(chunk_sz, byte_cnt - offset); data = section + offset; error = iwm_firmware_load_chunk(sc, addr, data, len); if (error) break; } return error; } static int iwm_firmware_load_chunk(struct iwm_softc *sc, uint32_t dst_addr, const uint8_t *chunk, uint32_t byte_cnt) { struct iwm_dma_info *dma = &sc->fw_dma; int error; /* Copy firmware chunk into pre-allocated DMA-safe memory. */ memcpy(dma->vaddr, chunk, byte_cnt); bus_dmamap_sync(dma->tag, dma->map, BUS_DMASYNC_PREWRITE); if (dst_addr >= IWM_FW_MEM_EXTENDED_START && dst_addr <= IWM_FW_MEM_EXTENDED_END) { iwm_set_bits_prph(sc, IWM_LMPM_CHICK, IWM_LMPM_CHICK_EXTENDED_ADDR_SPACE); } sc->sc_fw_chunk_done = 0; if (!iwm_nic_lock(sc)) return EBUSY; 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; if (!sc->sc_fw_chunk_done) { device_printf(sc->sc_dev, "fw chunk addr 0x%x len %d failed to load\n", dst_addr, byte_cnt); } if (dst_addr >= IWM_FW_MEM_EXTENDED_START && dst_addr <= IWM_FW_MEM_EXTENDED_END && iwm_nic_lock(sc)) { iwm_clear_bits_prph(sc, IWM_LMPM_CHICK, IWM_LMPM_CHICK_EXTENDED_ADDR_SPACE); iwm_nic_unlock(sc); } return error; } int iwm_load_cpu_sections_8000(struct iwm_softc *sc, struct iwm_fw_sects *fws, int cpu, int *first_ucode_section) { int shift_param; int i, error = 0, sec_num = 0x1; uint32_t val, last_read_idx = 0; const void *data; uint32_t dlen; uint32_t offset; if (cpu == 1) { shift_param = 0; *first_ucode_section = 0; } else { shift_param = 16; (*first_ucode_section)++; } for (i = *first_ucode_section; i < IWM_UCODE_SECT_MAX; i++) { last_read_idx = i; data = fws->fw_sect[i].fws_data; dlen = fws->fw_sect[i].fws_len; offset = fws->fw_sect[i].fws_devoff; /* * CPU1_CPU2_SEPARATOR_SECTION delimiter - separate between * CPU1 to CPU2. * PAGING_SEPARATOR_SECTION delimiter - separate between * CPU2 non paged to CPU2 paging sec. */ if (!data || offset == IWM_CPU1_CPU2_SEPARATOR_SECTION || offset == IWM_PAGING_SEPARATOR_SECTION) break; IWM_DPRINTF(sc, IWM_DEBUG_RESET, "LOAD FIRMWARE chunk %d offset 0x%x len %d for cpu %d\n", i, offset, dlen, cpu); if (dlen > sc->sc_fwdmasegsz) { IWM_DPRINTF(sc, IWM_DEBUG_RESET, "chunk %d too large (%d bytes)\n", i, dlen); error = EFBIG; } else { error = iwm_firmware_load_sect(sc, offset, data, dlen); } if (error) { device_printf(sc->sc_dev, "could not load firmware chunk %d (error %d)\n", i, error); return error; } /* Notify the ucode of the loaded section number and status */ if (iwm_nic_lock(sc)) { val = IWM_READ(sc, IWM_FH_UCODE_LOAD_STATUS); val = val | (sec_num << shift_param); IWM_WRITE(sc, IWM_FH_UCODE_LOAD_STATUS, val); sec_num = (sec_num << 1) | 0x1; iwm_nic_unlock(sc); /* * The firmware won't load correctly without this delay. */ DELAY(8000); } } *first_ucode_section = last_read_idx; if (iwm_nic_lock(sc)) { if (cpu == 1) IWM_WRITE(sc, IWM_FH_UCODE_LOAD_STATUS, 0xFFFF); else IWM_WRITE(sc, IWM_FH_UCODE_LOAD_STATUS, 0xFFFFFFFF); iwm_nic_unlock(sc); } return 0; } int iwm_load_firmware_8000(struct iwm_softc *sc, enum iwm_ucode_type ucode_type) { struct iwm_fw_sects *fws; int error = 0; int first_ucode_section; IWM_DPRINTF(sc, IWM_DEBUG_RESET, "loading ucode type %d\n", ucode_type); fws = &sc->sc_fw.fw_sects[ucode_type]; /* configure the ucode to be ready to get the secured image */ /* release CPU reset */ iwm_write_prph(sc, IWM_RELEASE_CPU_RESET, IWM_RELEASE_CPU_RESET_BIT); /* load to FW the binary Secured sections of CPU1 */ error = iwm_load_cpu_sections_8000(sc, fws, 1, &first_ucode_section); if (error) return error; /* load to FW the binary sections of CPU2 */ return iwm_load_cpu_sections_8000(sc, fws, 2, &first_ucode_section); } static int iwm_load_firmware_7000(struct iwm_softc *sc, enum iwm_ucode_type ucode_type) { struct iwm_fw_sects *fws; int error, i; 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); if (dlen > sc->sc_fwdmasegsz) { IWM_DPRINTF(sc, IWM_DEBUG_FIRMWARE_TLV, "chunk %d too large (%d bytes)\n", i, dlen); error = EFBIG; } else { error = iwm_firmware_load_sect(sc, offset, data, dlen); } if (error) { device_printf(sc->sc_dev, "could not load firmware chunk %u of %u " "(error=%d)\n", i, fws->fw_count, error); return error; } } IWM_WRITE(sc, IWM_CSR_RESET, 0); return 0; } static int iwm_load_firmware(struct iwm_softc *sc, enum iwm_ucode_type ucode_type) { int error, w; if (sc->sc_device_family == IWM_DEVICE_FAMILY_8000) error = iwm_load_firmware_8000(sc, ucode_type); else error = iwm_load_firmware_7000(sc, ucode_type); if (error) return error; /* wait for the firmware to load */ for (w = 0; !sc->sc_uc.uc_intr && w < 10; w++) { error = msleep(&sc->sc_uc, &sc->sc_mtx, 0, "iwmuc", hz/10); } if (error || !sc->sc_uc.uc_ok) { device_printf(sc->sc_dev, "could not load firmware\n"); if (sc->sc_device_family == IWM_DEVICE_FAMILY_8000) { device_printf(sc->sc_dev, "cpu1 status: 0x%x\n", iwm_read_prph(sc, IWM_SB_CPU_1_STATUS)); device_printf(sc->sc_dev, "cpu2 status: 0x%x\n", iwm_read_prph(sc, IWM_SB_CPU_2_STATUS)); } } /* * Give the firmware some time to initialize. * Accessing it too early causes errors. */ msleep(&w, &sc->sc_mtx, 0, "iwmfwinit", hz); 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_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) { device_printf(sc->sc_dev, "iwm_read_firmware: failed %d\n", error); return error; } sc->sc_uc_current = ucode_type; error = iwm_start_fw(sc, ucode_type); if (error) { device_printf(sc->sc_dev, "iwm_start_fw: failed %d\n", error); sc->sc_uc_current = old_type; return error; } error = iwm_post_alive(sc); if (error) { device_printf(sc->sc_dev, "iwm_fw_alive: failed %d\n", error); } return error; } /* * 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) { device_printf(sc->sc_dev, "failed to load init firmware\n"); 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); return 0; } if ((error = iwm_send_bt_init_conf(sc)) != 0) { device_printf(sc->sc_dev, "failed to send bt coex configuration: %d\n", error); return error; } /* Init Smart FIFO. */ error = iwm_mvm_sf_config(sc, IWM_SF_INIT_OFF); if (error != 0) return error; IWM_DPRINTF(sc, IWM_DEBUG_RESET, "%s: phy_txant=0x%08x, nvm_valid_tx_ant=0x%02x, valid=0x%02x\n", __func__, ((sc->sc_fw_phy_config & IWM_FW_PHY_CFG_TX_CHAIN) >> IWM_FW_PHY_CFG_TX_CHAIN_POS), sc->sc_nvm.valid_tx_ant, iwm_fw_valid_tx_ant(sc)); /* Send TX valid antennas before triggering calibrations */ if ((error = iwm_send_tx_ant_cfg(sc, iwm_fw_valid_tx_ant(sc))) != 0) { device_printf(sc->sc_dev, "failed to send antennas before calibration: %d\n", error); 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) { error = msleep(&sc->sc_init_complete, &sc->sc_mtx, 0, "iwminit", 2*hz); if (error) { device_printf(sc->sc_dev, "init complete failed: %d\n", sc->sc_init_complete); break; } } IWM_DPRINTF(sc, IWM_DEBUG_RESET, "init %scomplete\n", sc->sc_init_complete ? "" : "not "); 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; bus_dmamap_t dmamap = NULL; bus_dma_segment_t seg; int nsegs, error; m = m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR, IWM_RBUF_SIZE); if (m == NULL) return ENOBUFS; m->m_len = m->m_pkthdr.len = m->m_ext.ext_size; error = bus_dmamap_load_mbuf_sg(ring->data_dmat, ring->spare_map, m, &seg, &nsegs, BUS_DMA_NOWAIT); if (error != 0) { device_printf(sc->sc_dev, "%s: can't map mbuf, error %d\n", __func__, error); goto fail; } if (data->m != NULL) bus_dmamap_unload(ring->data_dmat, data->map); /* Swap ring->spare_map with data->map */ dmamap = data->map; data->map = ring->spare_map; ring->spare_map = dmamap; bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_PREREAD); data->m = m; /* Update RX descriptor. */ KASSERT((seg.ds_addr & 255) == 0, ("seg.ds_addr not aligned")); ring->desc[idx] = htole32(seg.ds_addr >> 8); bus_dmamap_sync(ring->desc_dma.tag, ring->desc_dma.map, BUS_DMASYNC_PREWRITE); return 0; fail: m_freem(m); 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(struct iwm_softc *sc, 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; IWM_DPRINTF(sc, IWM_DEBUG_RECV, "%s: i=%d, noise=%d\n", __func__, i, noise); if (noise) { total += noise; nbant++; } } IWM_DPRINTF(sc, IWM_DEBUG_RECV, "%s: nbant=%d, total=%d\n", __func__, nbant, total); #if 0 /* There should be at least one antenna but check anyway. */ return (nbant == 0) ? -127 : (total / nbant) - 107; #else /* For now, just hard-code it to -96 to be safe */ return (-96); #endif } /* * 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); goto fail; } 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); goto fail; } 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); } /* Note: RSSI is absolute (ie a -ve value) */ if (rssi < IWM_MIN_DBM) rssi = IWM_MIN_DBM; else if (rssi > IWM_MAX_DBM) rssi = IWM_MAX_DBM; /* Map it to relative value */ rssi = rssi - sc->sc_noise; /* 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__); goto fail; } IWM_DPRINTF(sc, IWM_DEBUG_RECV, "%s: rssi=%d, noise=%d\n", __func__, rssi, sc->sc_noise); 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); } /* rssi is in 1/2db units */ rxs.c_rssi = rssi * 2; rxs.c_nf = sc->sc_noise; + if (ieee80211_add_rx_params(m, &rxs) == 0) + goto fail; 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_input_mimo(ni, m); ieee80211_free_node(ni); } else { IWM_DPRINTF(sc, IWM_DEBUG_RECV, "inputall m %p\n", m); - ieee80211_input_mimo_all(ic, m, &rxs); + ieee80211_input_mimo_all(ic, m); } IWM_LOCK(sc); return; fail: counter_u64_add(ic->ic_ierrors, 1); } static int 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 ieee80211_ratectl_tx_status *txs = &sc->sc_txs; struct ieee80211_node *ni = &in->in_ni; int status = le16toh(tx_resp->status.status) & IWM_TX_STATUS_MSK; KASSERT(tx_resp->frame_count == 1, ("too many frames")); /* Update rate control statistics. */ IWM_DPRINTF(sc, IWM_DEBUG_XMIT, "%s: status=0x%04x, seq=%d, fc=%d, btc=%d, frts=%d, ff=%d, irate=%08x, wmt=%d\n", __func__, (int) le16toh(tx_resp->status.status), (int) le16toh(tx_resp->status.sequence), tx_resp->frame_count, tx_resp->bt_kill_count, tx_resp->failure_rts, tx_resp->failure_frame, le32toh(tx_resp->initial_rate), (int) le16toh(tx_resp->wireless_media_time)); txs->flags = IEEE80211_RATECTL_STATUS_SHORT_RETRY | IEEE80211_RATECTL_STATUS_LONG_RETRY; txs->short_retries = tx_resp->failure_rts; txs->long_retries = tx_resp->failure_frame; if (status != IWM_TX_STATUS_SUCCESS && status != IWM_TX_STATUS_DIRECT_DONE) { switch (status) { case IWM_TX_STATUS_FAIL_SHORT_LIMIT: txs->status = IEEE80211_RATECTL_TX_FAIL_SHORT; break; case IWM_TX_STATUS_FAIL_LONG_LIMIT: txs->status = IEEE80211_RATECTL_TX_FAIL_LONG; break; case IWM_TX_STATUS_FAIL_LIFE_EXPIRE: txs->status = IEEE80211_RATECTL_TX_FAIL_EXPIRED; break; default: txs->status = IEEE80211_RATECTL_TX_FAIL_UNSPECIFIED; break; } } else { txs->status = IEEE80211_RATECTL_TX_SUCCESS; } ieee80211_ratectl_tx_complete(ni, txs); return (txs->status != IEEE80211_RATECTL_TX_SUCCESS); } 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; struct mbuf *m = txd->m; int status; KASSERT(txd->done == 0, ("txd not done")); KASSERT(txd->in != NULL, ("txd without node")); KASSERT(txd->m != NULL, ("txd without mbuf")); bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTREAD); sc->sc_tx_timer = 0; status = 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); IWM_DPRINTF(sc, IWM_DEBUG_XMIT, "free txd %p, in %p\n", txd, txd->in); txd->done = 1; txd->m = NULL; txd->in = NULL; ieee80211_tx_complete(&in->in_ni, m, status); 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. */ } /* XXX wide commands? */ IWM_DPRINTF(sc, IWM_DEBUG_CMD, "cmd notification type 0x%x qid %d idx %d\n", pkt->hdr.code, pkt->hdr.qid, pkt->hdr.idx); 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); } IWM_DPRINTF(sc, IWM_DEBUG_XMIT | IWM_DEBUG_TXRATE, "%s: couldn't find an entry for rate=%d\n", __func__, rate); /* XXX Return the first */ /* XXX TODO: have it return the /lowest/ */ return (0); } static int iwm_tx_rateidx_global_lookup(struct iwm_softc *sc, uint8_t rate) { int i; for (i = 0; i < nitems(iwm_rates); i++) { if (iwm_rates[i].rate == rate) return (i); } /* XXX error? */ IWM_DPRINTF(sc, IWM_DEBUG_XMIT | IWM_DEBUG_TXRATE, "%s: couldn't find an entry for rate=%d\n", __func__, rate); return (0); } /* * Fill in the rate related information for a transmit command. */ static const struct iwm_rate * iwm_tx_fill_cmd(struct iwm_softc *sc, struct iwm_node *in, struct mbuf *m, struct iwm_tx_cmd *tx) { struct ieee80211_node *ni = &in->in_ni; struct ieee80211_frame *wh; const struct ieee80211_txparam *tp = ni->ni_txparms; const struct iwm_rate *rinfo; int type; int ridx, rate_flags; wh = mtod(m, struct ieee80211_frame *); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; tx->rts_retry_limit = IWM_RTS_DFAULT_RETRY_LIMIT; tx->data_retry_limit = IWM_DEFAULT_TX_RETRY; if (type == IEEE80211_FC0_TYPE_MGT) { ridx = iwm_tx_rateidx_global_lookup(sc, tp->mgmtrate); IWM_DPRINTF(sc, IWM_DEBUG_TXRATE, "%s: MGT (%d)\n", __func__, tp->mgmtrate); } else if (IEEE80211_IS_MULTICAST(wh->i_addr1)) { ridx = iwm_tx_rateidx_global_lookup(sc, tp->mcastrate); IWM_DPRINTF(sc, IWM_DEBUG_TXRATE, "%s: MCAST (%d)\n", __func__, tp->mcastrate); } else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) { ridx = iwm_tx_rateidx_global_lookup(sc, tp->ucastrate); IWM_DPRINTF(sc, IWM_DEBUG_TXRATE, "%s: FIXED_RATE (%d)\n", __func__, tp->ucastrate); } else if (m->m_flags & M_EAPOL) { ridx = iwm_tx_rateidx_global_lookup(sc, tp->mgmtrate); IWM_DPRINTF(sc, IWM_DEBUG_TXRATE, "%s: EAPOL\n", __func__); } else if (type == IEEE80211_FC0_TYPE_DATA) { int i; /* for data frames, use RS table */ IWM_DPRINTF(sc, IWM_DEBUG_TXRATE, "%s: DATA\n", __func__); /* XXX pass pktlen */ (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); } else { ridx = iwm_tx_rateidx_global_lookup(sc, tp->mgmtrate); IWM_DPRINTF(sc, IWM_DEBUG_TXRATE, "%s: DEFAULT (%d)\n", __func__, tp->mgmtrate); } IWM_DPRINTF(sc, IWM_DEBUG_XMIT | IWM_DEBUG_TXRATE, "%s: frame type=%d txrate %d\n", __func__, type, iwm_rates[ridx].rate); 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; 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 = 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, m, 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; 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(IWM_PM_FRAME_ASSOC); } else if (subtype == IEEE80211_FC0_SUBTYPE_ACTION) { tx->pm_frame_timeout = htole16(IWM_PM_FRAME_NONE); } else { tx->pm_frame_timeout = htole16(IWM_PM_FRAME_MGMT); } } else { tx->pm_frame_timeout = htole16(IWM_PM_FRAME_NONE); } 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. */ m1 = m_collapse(m, M_NOWAIT, IWM_MAX_SCATTER - 2); if (m1 == NULL) { device_printf(sc->sc_dev, "%s: could not defrag mbuf\n", __func__); m_freem(m); 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, "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 txflags=0x%08x rate_n_flags=0x%08x rateidx=%u\n", ring->qid, ring->cur, totlen, nsegs, le32toh(tx->tx_flags), le32toh(tx->rate_n_flags), tx->initial_rate_index ); /* 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 */ /* * 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, uint32_t tfd_msk, uint32_t flags) { int ret; struct iwm_tx_path_flush_cmd flush_cmd = { .queues_ctl = htole32(tfd_msk), .flush_ctl = htole16(IWM_DUMP_TX_FIFO_FLUSH), }; ret = iwm_mvm_send_cmd_pdu(sc, IWM_TXPATH_FLUSH, flags, sizeof(flush_cmd), &flush_cmd); if (ret) device_printf(sc->sc_dev, "Flushing tx queue failed: %d\n", ret); return ret; } /* * BEGIN mvm/sta.c */ static int iwm_mvm_send_add_sta_cmd_status(struct iwm_softc *sc, struct iwm_mvm_add_sta_cmd_v7 *cmd, int *status) { return iwm_mvm_send_cmd_pdu_status(sc, IWM_ADD_STA, sizeof(*cmd), cmd, 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_v7 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) { int ac; for (ac = 0; ac < WME_NUM_AC; ac++) { add_sta_cmd.tfd_queue_msk |= htole32(1 << iwm_mvm_ac_to_tx_fifo[ac]); } 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); add_sta_cmd.tid_disable_tx = htole16(0xffff); if (update) add_sta_cmd.modify_mask |= (IWM_STA_MODIFY_TID_DISABLE_TX); 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) { return iwm_mvm_sta_send_to_fw(sc, in, 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_v7 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); cmd.tid_disable_tx = htole16(0xffff); 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 = IWM_AUX_STA_ID; sc->sc_aux_sta.tfd_queue_msk = (1 << IWM_MVM_AUX_QUEUE); ret = iwm_enable_txq(sc, 0, IWM_MVM_AUX_QUEUE, IWM_MVM_TX_FIFO_MCAST); if (ret) return ret; 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; 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 = 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_mvm_sf_config(sc, IWM_SF_FULL_ON); if (error != 0) return error; 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 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 update 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 update phy ctxt\n", __func__); goto out; } in->in_phyctxt = &sc->sc_phyctxt[0]; if ((error = iwm_mvm_binding_update(sc, in)) != 0) { device_printf(sc->sc_dev, "%s: binding update cmd\n", __func__); goto out; } if ((error = iwm_mvm_update_sta(sc, in)) != 0) { device_printf(sc->sc_dev, "%s: failed to update sta\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__); error = ETIMEDOUT; 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 add cmd\n", __func__); goto out; } if ((error = iwm_mvm_add_sta(sc, in)) != 0) { device_printf(sc->sc_dev, "%s: failed to add sta\n", __func__); goto out; } } /* * Prevent the FW from wandering off channel during association * by "protecting" the session with a time event. */ /* XXX duration is in units of TU, not MS */ duration = IWM_MVM_TE_SESSION_PROTECTION_MAX_TIME_MS; iwm_mvm_protect_session(sc, in, duration, 500 /* XXX magic number */); DELAY(100); error = 0; out: ieee80211_free_node(ni); return (error); } static int iwm_assoc(struct ieee80211vap *vap, struct iwm_softc *sc) { struct iwm_node *in = 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) { uint32_t tfd_msk; /* * 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! */ /* * Just using 0xf for the queues mask is fine as long as we only * get here from RUN state. */ tfd_msk = 0xf; mbufq_drain(&sc->sc_snd); iwm_mvm_flush_tx_path(sc, tfd_msk, IWM_CMD_SYNC); /* * We seem to get away with just synchronously sending the * IWM_TXPATH_FLUSH command. */ // iwm_trans_wait_tx_queue_empty(sc, tfd_msk); 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; } if (nrates == 0) { device_printf(sc->sc_dev, "%s: node supports 0 rates, odd!\n", __func__); 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); /* * Loop over nrates and populate in_ridx from the highest * rate to the lowest rate. Remember, in_ridx[] has * IEEE80211_RATE_MAXSIZE entries! */ for (i = 0; i < min(nrates, IEEE80211_RATE_MAXSIZE); i++) { int rate = ni->ni_rates.rs_rates[(nrates - 1) - 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; /* For HT, always enable RTS/CTS to avoid excessive retries. */ if (ni->ni_flags & IEEE80211_NODE_HT) lq->flags |= IWM_LQ_FLAG_USE_RTS_MSK; /* * 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 0 if (txant == 0) txant = iwm_fw_valid_tx_ant(sc); nextant = 1<<(ffs(txant)-1); txant &= ~nextant; #else nextant = iwm_fw_valid_tx_ant(sc); #endif /* * Map the rate id into a rate index into * our hardware table containing the * configuration to use for this rate. */ ridx = in->in_ridx[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); if (vap->iv_state == IEEE80211_S_SCAN && nstate != vap->iv_state) iwm_led_blink_stop(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 = IWM_NODE(vap->iv_bss)) != NULL)) in->in_assoc = 0; if (nstate == IEEE80211_S_INIT) { IWM_UNLOCK(sc); IEEE80211_LOCK(ic); error = ivp->iv_newstate(vap, nstate, arg); IEEE80211_UNLOCK(ic); IWM_LOCK(sc); iwm_release(sc, NULL); IWM_UNLOCK(sc); IEEE80211_LOCK(ic); return error; } /* * 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); /* Always pass arg as -1 since we can't Tx right now. */ /* * XXX arg is just ignored anyway when transitioning * to IEEE80211_S_INIT. */ vap->iv_newstate(vap, IEEE80211_S_INIT, -1); 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: 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 = 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__); } iwm_mvm_led_enable(sc); 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; IWM_DPRINTF(sc, IWM_DEBUG_SCAN | IWM_DEBUG_TRACE, "%s: scan ended\n", __func__); ieee80211_scan_done(TAILQ_FIRST(&ic->ic_vaps)); } /* * Aging and idle timeouts for the different possible scenarios * in default configuration */ static const uint32_t iwm_sf_full_timeout_def[IWM_SF_NUM_SCENARIO][IWM_SF_NUM_TIMEOUT_TYPES] = { { htole32(IWM_SF_SINGLE_UNICAST_AGING_TIMER_DEF), htole32(IWM_SF_SINGLE_UNICAST_IDLE_TIMER_DEF) }, { htole32(IWM_SF_AGG_UNICAST_AGING_TIMER_DEF), htole32(IWM_SF_AGG_UNICAST_IDLE_TIMER_DEF) }, { htole32(IWM_SF_MCAST_AGING_TIMER_DEF), htole32(IWM_SF_MCAST_IDLE_TIMER_DEF) }, { htole32(IWM_SF_BA_AGING_TIMER_DEF), htole32(IWM_SF_BA_IDLE_TIMER_DEF) }, { htole32(IWM_SF_TX_RE_AGING_TIMER_DEF), htole32(IWM_SF_TX_RE_IDLE_TIMER_DEF) }, }; /* * Aging and idle timeouts for the different possible scenarios * in single BSS MAC configuration. */ static const uint32_t iwm_sf_full_timeout[IWM_SF_NUM_SCENARIO][IWM_SF_NUM_TIMEOUT_TYPES] = { { htole32(IWM_SF_SINGLE_UNICAST_AGING_TIMER), htole32(IWM_SF_SINGLE_UNICAST_IDLE_TIMER) }, { htole32(IWM_SF_AGG_UNICAST_AGING_TIMER), htole32(IWM_SF_AGG_UNICAST_IDLE_TIMER) }, { htole32(IWM_SF_MCAST_AGING_TIMER), htole32(IWM_SF_MCAST_IDLE_TIMER) }, { htole32(IWM_SF_BA_AGING_TIMER), htole32(IWM_SF_BA_IDLE_TIMER) }, { htole32(IWM_SF_TX_RE_AGING_TIMER), htole32(IWM_SF_TX_RE_IDLE_TIMER) }, }; static void iwm_mvm_fill_sf_command(struct iwm_softc *sc, struct iwm_sf_cfg_cmd *sf_cmd, struct ieee80211_node *ni) { int i, j, watermark; sf_cmd->watermark[IWM_SF_LONG_DELAY_ON] = htole32(IWM_SF_W_MARK_SCAN); /* * If we are in association flow - check antenna configuration * capabilities of the AP station, and choose the watermark accordingly. */ if (ni) { if (ni->ni_flags & IEEE80211_NODE_HT) { #ifdef notyet if (ni->ni_rxmcs[2] != 0) watermark = IWM_SF_W_MARK_MIMO3; else if (ni->ni_rxmcs[1] != 0) watermark = IWM_SF_W_MARK_MIMO2; else #endif watermark = IWM_SF_W_MARK_SISO; } else { watermark = IWM_SF_W_MARK_LEGACY; } /* default watermark value for unassociated mode. */ } else { watermark = IWM_SF_W_MARK_MIMO2; } sf_cmd->watermark[IWM_SF_FULL_ON] = htole32(watermark); for (i = 0; i < IWM_SF_NUM_SCENARIO; i++) { for (j = 0; j < IWM_SF_NUM_TIMEOUT_TYPES; j++) { sf_cmd->long_delay_timeouts[i][j] = htole32(IWM_SF_LONG_DELAY_AGING_TIMER); } } if (ni) { memcpy(sf_cmd->full_on_timeouts, iwm_sf_full_timeout, sizeof(iwm_sf_full_timeout)); } else { memcpy(sf_cmd->full_on_timeouts, iwm_sf_full_timeout_def, sizeof(iwm_sf_full_timeout_def)); } } static int iwm_mvm_sf_config(struct iwm_softc *sc, enum iwm_sf_state new_state) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); struct iwm_sf_cfg_cmd sf_cmd = { .state = htole32(IWM_SF_FULL_ON), }; int ret = 0; if (sc->sc_device_family == IWM_DEVICE_FAMILY_8000) sf_cmd.state |= htole32(IWM_SF_CFG_DUMMY_NOTIF_OFF); switch (new_state) { case IWM_SF_UNINIT: case IWM_SF_INIT_OFF: iwm_mvm_fill_sf_command(sc, &sf_cmd, NULL); break; case IWM_SF_FULL_ON: iwm_mvm_fill_sf_command(sc, &sf_cmd, vap->iv_bss); break; default: IWM_DPRINTF(sc, IWM_DEBUG_PWRSAVE, "Invalid state: %d. not sending Smart Fifo cmd\n", new_state); return EINVAL; } ret = iwm_mvm_send_cmd_pdu(sc, IWM_REPLY_SF_CFG_CMD, IWM_CMD_ASYNC, sizeof(sf_cmd), &sf_cmd); return ret; } static int iwm_send_bt_init_conf(struct iwm_softc *sc) { struct iwm_bt_coex_cmd bt_cmd; bt_cmd.mode = htole32(IWM_BT_COEX_WIFI); bt_cmd.enabled_modules = htole32(IWM_BT_COEX_HIGH_BAND_RET); return iwm_mvm_send_cmd_pdu(sc, IWM_BT_CONFIG, 0, sizeof(bt_cmd), &bt_cmd); } static int iwm_send_update_mcc_cmd(struct iwm_softc *sc, const char *alpha2) { struct iwm_mcc_update_cmd mcc_cmd; struct iwm_host_cmd hcmd = { .id = IWM_MCC_UPDATE_CMD, .flags = (IWM_CMD_SYNC | IWM_CMD_WANT_SKB), .data = { &mcc_cmd }, }; int ret; #ifdef IWM_DEBUG struct iwm_rx_packet *pkt; struct iwm_mcc_update_resp_v1 *mcc_resp_v1 = NULL; struct iwm_mcc_update_resp *mcc_resp; int n_channels; uint16_t mcc; #endif int resp_v2 = isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_LAR_SUPPORT_V2); memset(&mcc_cmd, 0, sizeof(mcc_cmd)); mcc_cmd.mcc = htole16(alpha2[0] << 8 | alpha2[1]); if ((sc->sc_ucode_api & IWM_UCODE_TLV_API_WIFI_MCC_UPDATE) || isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_LAR_MULTI_MCC)) mcc_cmd.source_id = IWM_MCC_SOURCE_GET_CURRENT; else mcc_cmd.source_id = IWM_MCC_SOURCE_OLD_FW; if (resp_v2) hcmd.len[0] = sizeof(struct iwm_mcc_update_cmd); else hcmd.len[0] = sizeof(struct iwm_mcc_update_cmd_v1); IWM_DPRINTF(sc, IWM_DEBUG_NODE, "send MCC update to FW with '%c%c' src = %d\n", alpha2[0], alpha2[1], mcc_cmd.source_id); ret = iwm_send_cmd(sc, &hcmd); if (ret) return ret; #ifdef IWM_DEBUG pkt = hcmd.resp_pkt; /* Extract MCC response */ if (resp_v2) { mcc_resp = (void *)pkt->data; mcc = mcc_resp->mcc; n_channels = le32toh(mcc_resp->n_channels); } else { mcc_resp_v1 = (void *)pkt->data; mcc = mcc_resp_v1->mcc; n_channels = le32toh(mcc_resp_v1->n_channels); } /* W/A for a FW/NVM issue - returns 0x00 for the world domain */ if (mcc == 0) mcc = 0x3030; /* "00" - world */ IWM_DPRINTF(sc, IWM_DEBUG_NODE, "regulatory domain '%c%c' (%d channels available)\n", mcc >> 8, mcc & 0xff, n_channels); #endif iwm_free_resp(sc, &hcmd); return 0; } static void iwm_mvm_tt_tx_backoff(struct iwm_softc *sc, uint32_t backoff) { struct iwm_host_cmd cmd = { .id = IWM_REPLY_THERMAL_MNG_BACKOFF, .len = { sizeof(uint32_t), }, .data = { &backoff, }, }; if (iwm_send_cmd(sc, &cmd) != 0) { device_printf(sc->sc_dev, "failed to change thermal tx backoff\n"); } } static int iwm_init_hw(struct iwm_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; int error, i, ac; if ((error = iwm_start_hw(sc)) != 0) { printf("iwm_start_hw: failed %d\n", error); return error; } if ((error = iwm_run_init_mvm_ucode(sc, 0)) != 0) { printf("iwm_run_init_mvm_ucode: failed %d\n", error); 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_bt_init_conf(sc)) != 0) { device_printf(sc->sc_dev, "bt init conf failed\n"); goto error; } if ((error = iwm_send_tx_ant_cfg(sc, iwm_fw_valid_tx_ant(sc))) != 0) { device_printf(sc->sc_dev, "antenna config failed\n"); goto error; } /* Send phy db control command and then phy db calibration*/ if ((error = iwm_send_phy_db_data(sc)) != 0) { device_printf(sc->sc_dev, "phy_db_data failed\n"); goto error; } if ((error = iwm_send_phy_cfg_cmd(sc)) != 0) { device_printf(sc->sc_dev, "phy_cfg_cmd failed\n"); goto error; } /* Add auxiliary station for scanning */ if ((error = iwm_mvm_add_aux_sta(sc)) != 0) { device_printf(sc->sc_dev, "add_aux_sta failed\n"); 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; } /* Initialize tx backoffs to the minimum. */ if (sc->sc_device_family == IWM_DEVICE_FAMILY_7000) iwm_mvm_tt_tx_backoff(sc, 0); error = iwm_mvm_power_update_device(sc); if (error) goto error; if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_LAR_SUPPORT)) { if ((error = iwm_send_update_mcc_cmd(sc, "ZZ")) != 0) goto error; } if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_UMAC_SCAN)) { if ((error = iwm_mvm_config_umac_scan(sc)) != 0) goto error; } /* Enable Tx queues. */ for (ac = 0; ac < WME_NUM_AC; ac++) { error = iwm_enable_txq(sc, IWM_STATION_ID, ac, iwm_mvm_ac_to_tx_fifo[ac]); if (error) goto error; } if ((error = iwm_mvm_disable_beacon_filter(sc)) != 0) { device_printf(sc->sc_dev, "failed to disable beacon filter\n"); goto error; } 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); } /* * ifnet interfaces */ 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) { printf("iwm_init_hw failed %d\n", error); 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++; iwm_led_blink_stop(sc); sc->sc_tx_timer = 0; iwm_stop_device(sc); } static void iwm_watchdog(void *arg) { struct iwm_softc *sc = arg; struct ieee80211com *ic = &sc->sc_ic; 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 ieee80211_restart_all(ic); 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 trm_hw_status0; /* TRM HW status */ uint32_t trm_hw_status1; /* TRM HW status */ 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 fw_rev_type; /* firmware revision type */ uint32_t major; /* uCode version major */ uint32_t minor; /* uCode version minor */ 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 last_cmd_id; /* last HCMD id handled by the firmware */ 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 /* LOG_ERROR_TABLE_API_S_VER_3 */; /* * UMAC error struct - relevant starting from family 8000 chip. * Note: This structure is read from the device with IO accesses, * and the reading already does the endian conversion. As it is * read with u32-sized accesses, any members with a different size * need to be ordered correctly though! */ struct iwm_umac_error_event_table { uint32_t valid; /* (nonzero) valid, (0) log is empty */ uint32_t error_id; /* type of error */ 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 umac_major; uint32_t umac_minor; uint32_t frame_pointer; /* core register 27*/ uint32_t stack_pointer; /* core register 28 */ uint32_t cmd_header; /* latest host cmd sent to UMAC */ uint32_t nic_isr_pref; /* ISR status register */ } __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; } static void iwm_nic_umac_error(struct iwm_softc *sc) { struct iwm_umac_error_event_table table; uint32_t base; base = sc->sc_uc.uc_umac_error_event_table; if (base < 0x800000) { device_printf(sc->sc_dev, "Invalid error log pointer 0x%08x\n", base); return; } if (iwm_read_mem(sc, base, &table, sizeof(table)/sizeof(uint32_t))) { device_printf(sc->sc_dev, "reading errlog failed\n"); return; } if (ERROR_START_OFFSET <= table.valid * ERROR_ELEM_SIZE) { device_printf(sc->sc_dev, "Start UMAC 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 | %s\n", table.error_id, iwm_desc_lookup(table.error_id)); device_printf(sc->sc_dev, "0x%08X | umac branchlink1\n", table.blink1); device_printf(sc->sc_dev, "0x%08X | umac branchlink2\n", table.blink2); device_printf(sc->sc_dev, "0x%08X | umac interruptlink1\n", table.ilink1); device_printf(sc->sc_dev, "0x%08X | umac interruptlink2\n", table.ilink2); device_printf(sc->sc_dev, "0x%08X | umac data1\n", table.data1); device_printf(sc->sc_dev, "0x%08X | umac data2\n", table.data2); device_printf(sc->sc_dev, "0x%08X | umac data3\n", table.data3); device_printf(sc->sc_dev, "0x%08X | umac major\n", table.umac_major); device_printf(sc->sc_dev, "0x%08X | umac minor\n", table.umac_minor); device_printf(sc->sc_dev, "0x%08X | frame pointer\n", table.frame_pointer); device_printf(sc->sc_dev, "0x%08X | stack pointer\n", table.stack_pointer); device_printf(sc->sc_dev, "0x%08X | last host cmd\n", table.cmd_header); device_printf(sc->sc_dev, "0x%08X | isr status reg\n", table.nic_isr_pref); } /* * 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) { device_printf(sc->sc_dev, "Invalid error log pointer 0x%08x\n", base); return; } if (iwm_read_mem(sc, base, &table, sizeof(table)/sizeof(uint32_t))) { 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 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 | trm_hw_status0\n", table.trm_hw_status0); device_printf(sc->sc_dev, "%08X | trm_hw_status1\n", table.trm_hw_status1); 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 | uCode revision type\n", table.fw_rev_type); device_printf(sc->sc_dev, "%08X | uCode version major\n", table.major); device_printf(sc->sc_dev, "%08X | uCode version minor\n", table.minor); 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 | last cmd Id\n", table.last_cmd_id); 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); if (sc->sc_uc.uc_umac_error_event_table) iwm_nic_umac_error(sc); } #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) { struct ieee80211com *ic = &sc->sc_ic; 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; /* * Process responses */ 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, code; 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; code = IWM_WIDE_ID(pkt->hdr.flags, pkt->hdr.code); IWM_DPRINTF(sc, IWM_DEBUG_INTR, "rx packet qid=%d idx=%d type=%x %d %d\n", pkt->hdr.qid & ~0x80, pkt->hdr.idx, 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 (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 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_MFUART_LOAD_NOTIFICATION: break; case IWM_MVM_ALIVE: { struct iwm_mvm_alive_resp_v1 *resp1; struct iwm_mvm_alive_resp_v2 *resp2; struct iwm_mvm_alive_resp_v3 *resp3; if (iwm_rx_packet_payload_len(pkt) == sizeof(*resp1)) { SYNC_RESP_STRUCT(resp1, pkt); sc->sc_uc.uc_error_event_table = le32toh(resp1->error_event_table_ptr); sc->sc_uc.uc_log_event_table = le32toh(resp1->log_event_table_ptr); sc->sched_base = le32toh(resp1->scd_base_ptr); if (resp1->status == IWM_ALIVE_STATUS_OK) sc->sc_uc.uc_ok = 1; else sc->sc_uc.uc_ok = 0; } if (iwm_rx_packet_payload_len(pkt) == sizeof(*resp2)) { SYNC_RESP_STRUCT(resp2, pkt); sc->sc_uc.uc_error_event_table = le32toh(resp2->error_event_table_ptr); sc->sc_uc.uc_log_event_table = le32toh(resp2->log_event_table_ptr); sc->sched_base = le32toh(resp2->scd_base_ptr); sc->sc_uc.uc_umac_error_event_table = le32toh(resp2->error_info_addr); if (resp2->status == IWM_ALIVE_STATUS_OK) sc->sc_uc.uc_ok = 1; else sc->sc_uc.uc_ok = 0; } if (iwm_rx_packet_payload_len(pkt) == sizeof(*resp3)) { SYNC_RESP_STRUCT(resp3, pkt); sc->sc_uc.uc_error_event_table = le32toh(resp3->error_event_table_ptr); sc->sc_uc.uc_log_event_table = le32toh(resp3->log_event_table_ptr); sc->sched_base = le32toh(resp3->scd_base_ptr); sc->sc_uc.uc_umac_error_event_table = le32toh(resp3->error_info_addr); if (resp3->status == IWM_ALIVE_STATUS_OK) sc->sc_uc.uc_ok = 1; else sc->sc_uc.uc_ok = 0; } 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(sc, &stats->rx.general); break; } case IWM_NVM_ACCESS_CMD: case IWM_MCC_UPDATE_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_MCC_CHUB_UPDATE_CMD: { struct iwm_mcc_chub_notif *notif; SYNC_RESP_STRUCT(notif, pkt); sc->sc_fw_mcc[0] = (notif->mcc & 0xff00) >> 8; sc->sc_fw_mcc[1] = notif->mcc & 0xff; sc->sc_fw_mcc[2] = '\0'; IWM_DPRINTF(sc, IWM_DEBUG_RESET, "fw source %d sent CC '%s'\n", notif->source_id, sc->sc_fw_mcc); break; } case IWM_DTS_MEASUREMENT_NOTIFICATION: 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_WIDE_ID(IWM_ALWAYS_LONG_GROUP, IWM_SCAN_CFG_CMD): case IWM_WIDE_ID(IWM_ALWAYS_LONG_GROUP, IWM_SCAN_REQ_UMAC): case IWM_SCAN_OFFLOAD_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: case IWM_BT_CONFIG: case IWM_REPLY_THERMAL_MNG_BACKOFF: 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_OFFLOAD_COMPLETE: { struct iwm_periodic_scan_complete *notif; SYNC_RESP_STRUCT(notif, pkt); break; } case IWM_SCAN_ITERATION_COMPLETE: { struct iwm_lmac_scan_complete_notif *notif; SYNC_RESP_STRUCT(notif, pkt); ieee80211_runtask(&sc->sc_ic, &sc->sc_es_task); break; } case IWM_SCAN_COMPLETE_UMAC: { struct iwm_umac_scan_complete *notif; SYNC_RESP_STRUCT(notif, pkt); IWM_DPRINTF(sc, IWM_DEBUG_SCAN, "UMAC scan complete, status=0x%x\n", notif->status); #if 0 /* XXX This would be a duplicate scan end call */ taskqueue_enqueue(sc->sc_tq, &sc->sc_es_task); #endif break; } case IWM_SCAN_ITERATION_COMPLETE_UMAC: { struct iwm_umac_scan_iter_complete_notif *notif; SYNC_RESP_STRUCT(notif, pkt); IWM_DPRINTF(sc, IWM_DEBUG_SCAN, "UMAC scan iteration " "complete, status=0x%x, %d channels scanned\n", notif->status, notif->scanned_channels); ieee80211_runtask(&sc->sc_ic, &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); IWM_DPRINTF(sc, IWM_DEBUG_INTR, "TE notif status = 0x%x action = 0x%x\n", notif->status, notif->action); break; } case IWM_MCAST_FILTER_CMD: break; case IWM_SCD_QUEUE_CFG: { struct iwm_scd_txq_cfg_rsp *rsp; SYNC_RESP_STRUCT(rsp, pkt); IWM_DPRINTF(sc, IWM_DEBUG_CMD, "queue cfg token=0x%x sta_id=%d " "tid=%d scd_queue=%d\n", rsp->token, rsp->sta_id, rsp->tid, rsp->scd_queue); 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) { int i; struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); #ifdef IWM_DEBUG iwm_nic_error(sc); #endif /* 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 == NULL) ? -1 : vap->iv_state); /* Don't stop the device; just do a VAP restart */ IWM_UNLOCK(sc); if (vap == NULL) { printf("%s: null vap\n", __func__); return; } device_printf(sc->sc_dev, "%s: controller panicked, iv_state = %d; " "restarting\n", __func__, vap->iv_state); /* XXX TODO: turn this into a callout/taskqueue */ ieee80211_restart_all(ic); return; } 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_3165_1 0x3165 #define PCI_PRODUCT_INTEL_WL_3165_2 0x3166 #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 #define PCI_PRODUCT_INTEL_WL_8260_1 0x24f3 #define PCI_PRODUCT_INTEL_WL_8260_2 0x24f4 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_3165_1, "Intel Dual Band Wireless AC 3165" }, { PCI_PRODUCT_INTEL_WL_3165_2, "Intel Dual Band Wireless AC 3165" }, { 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" }, { PCI_PRODUCT_INTEL_WL_8260_1, "Intel Dual Band Wireless AC 8260" }, { PCI_PRODUCT_INTEL_WL_8260_2, "Intel Dual Band Wireless AC 8260" }, }; 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); sc->sc_hw_rev = IWM_READ(sc, IWM_CSR_HW_REV); 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; sc->sc_device_family = IWM_DEVICE_FAMILY_7000; sc->sc_fwdmasegsz = IWM_FWDMASEGSZ; return (0); case PCI_PRODUCT_INTEL_WL_3165_1: case PCI_PRODUCT_INTEL_WL_3165_2: sc->sc_fwname = "iwm7265fw"; sc->host_interrupt_operation_mode = 0; sc->sc_device_family = IWM_DEVICE_FAMILY_7000; sc->sc_fwdmasegsz = IWM_FWDMASEGSZ; 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; sc->sc_device_family = IWM_DEVICE_FAMILY_7000; sc->sc_fwdmasegsz = IWM_FWDMASEGSZ; 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; sc->sc_device_family = IWM_DEVICE_FAMILY_7000; sc->sc_fwdmasegsz = IWM_FWDMASEGSZ; return (0); case PCI_PRODUCT_INTEL_WL_8260_1: case PCI_PRODUCT_INTEL_WL_8260_2: sc->sc_fwname = "iwm8000Cfw"; sc->host_interrupt_operation_mode = 0; sc->sc_device_family = IWM_DEVICE_FAMILY_8000; sc->sc_fwdmasegsz = IWM_FWDMASEGSZ_8000; 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; IWM_LOCK_INIT(sc); mbufq_init(&sc->sc_snd, ifqmaxlen); callout_init_mtx(&sc->sc_watchdog_to, &sc->sc_mtx, 0); callout_init_mtx(&sc->sc_led_blink_to, &sc->sc_mtx, 0); TASK_INIT(&sc->sc_es_task, 0, iwm_endscan_cb, sc); /* 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; /* * We now start fiddling with the hardware */ /* * In the 8000 HW family the format of the 4 bytes of CSR_HW_REV have * changed, and now the revision step also includes bit 0-1 (no more * "dash" value). To keep hw_rev backwards compatible - we'll store it * in the old format. */ if (sc->sc_device_family == IWM_DEVICE_FAMILY_8000) sc->sc_hw_rev = (sc->sc_hw_rev & 0xfff0) | (IWM_CSR_HW_REV_STEP(sc->sc_hw_rev << 2) << 2); if (iwm_prepare_card_hw(sc) != 0) { device_printf(dev, "could not initialize hardware\n"); goto fail; } if (sc->sc_device_family == IWM_DEVICE_FAMILY_8000) { int ret; uint32_t hw_step; /* * In order to recognize C step the driver should read the * chip version id located at the AUX bus MISC address. */ IWM_SETBITS(sc, IWM_CSR_GP_CNTRL, IWM_CSR_GP_CNTRL_REG_FLAG_INIT_DONE); DELAY(2); ret = iwm_poll_bit(sc, IWM_CSR_GP_CNTRL, IWM_CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY, IWM_CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY, 25000); if (!ret) { device_printf(sc->sc_dev, "Failed to wake up the nic\n"); goto fail; } if (iwm_nic_lock(sc)) { hw_step = iwm_read_prph(sc, IWM_WFPM_CTRL_REG); hw_step |= IWM_ENABLE_WFPM; iwm_write_prph(sc, IWM_WFPM_CTRL_REG, hw_step); hw_step = iwm_read_prph(sc, IWM_AUX_MISC_REG); hw_step = (hw_step >> IWM_HW_STEP_LOCATION_BITS) & 0xF; if (hw_step == 0x3) sc->sc_hw_rev = (sc->sc_hw_rev & 0xFFFFFFF3) | (IWM_SILICON_C_STEP << 2); iwm_nic_unlock(sc); } else { device_printf(sc->sc_dev, "Failed to lock the nic\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; } /* Default noise floor */ sc->sc_noise = -96; /* 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_is_valid_ether_addr(uint8_t *addr) { char zero_addr[IEEE80211_ADDR_LEN] = { 0, 0, 0, 0, 0, 0 }; if ((addr[0] & 1) || IEEE80211_ADDR_EQ(zero_addr, addr)) return (FALSE); return (TRUE); } 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, "hw rev 0x%x, fw ver %s, address %s\n", sc->sc_hw_rev & IWM_CSR_HW_REV_TYPE_MSK, sc->sc_fwver, ether_sprintf(sc->sc_nvm.hw_addr)); /* 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); iwm_init_channel_map(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans, ic->ic_channels); /* * 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_getradiocaps = iwm_init_channel_map; 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; IWM_LOCK(sc); if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_UMAC_SCAN)) error = iwm_mvm_umac_scan(sc); else error = iwm_mvm_lmac_scan(sc); if (error != 0) { device_printf(sc->sc_dev, "could not initiate 2 GHz scan\n"); IWM_UNLOCK(sc); ieee80211_cancel_scan(vap); } else { iwm_led_blink_start(sc); IWM_UNLOCK(sc); } } static void iwm_scan_end(struct ieee80211com *ic) { struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); struct iwm_softc *sc = ic->ic_softc; IWM_LOCK(sc); iwm_led_blink_stop(sc); if (vap->iv_state == IEEE80211_S_RUN) iwm_mvm_led_enable(sc); IWM_UNLOCK(sc); } 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) { struct iwm_softc *sc = device_get_softc(dev); int do_reinit = 0; 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)); IWM_LOCK(sc); if (sc->sc_flags & IWM_FLAG_SCANNING) { sc->sc_flags &= ~IWM_FLAG_SCANNING; do_reinit = 1; } IWM_UNLOCK(sc); if (do_reinit) ieee80211_resume_all(&sc->sc_ic); return 0; } static int iwm_suspend(device_t dev) { int do_stop = 0; struct iwm_softc *sc = device_get_softc(dev); do_stop = !! (sc->sc_ic.ic_nrunning > 0); ieee80211_suspend_all(&sc->sc_ic); if (do_stop) { IWM_LOCK(sc); iwm_stop(sc); sc->sc_flags |= IWM_FLAG_SCANNING; 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; ieee80211_draintask(&sc->sc_ic, &sc->sc_es_task); callout_drain(&sc->sc_led_blink_to); callout_drain(&sc->sc_watchdog_to); iwm_stop_device(sc); if (do_net80211) { ieee80211_ifdetach(&sc->sc_ic); } iwm_phy_db_free(sc); /* Free descriptor rings */ iwm_free_rx_ring(sc, &sc->rxq); for (i = 0; i < nitems(sc->txq); i++) iwm_free_tx_ring(sc, &sc->txq[i]); /* Free firmware */ if (fw->fw_fp != NULL) iwm_fw_info_free(fw); /* Free scheduler */ iwm_dma_contig_free(&sc->sched_dma); iwm_dma_contig_free(&sc->ict_dma); iwm_dma_contig_free(&sc->kw_dma); iwm_dma_contig_free(&sc->fw_dma); /* Finished with the hardware - detach things */ iwm_pci_detach(dev); mbufq_drain(&sc->sc_snd); IWM_LOCK_DESTROY(sc); 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/otus/if_otus.c =================================================================== --- head/sys/dev/otus/if_otus.c (revision 307157) +++ head/sys/dev/otus/if_otus.c (revision 307158) @@ -1,3234 +1,3237 @@ /* $OpenBSD: if_otus.c,v 1.49 2015/11/24 13:33:18 mpi Exp $ */ /*- * Copyright (c) 2009 Damien Bergamini * Copyright (c) 2015 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 Atheros AR9001U chipset. */ #include __FBSDID("$FreeBSD$"); #include "opt_wlan.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 #ifdef IEEE80211_SUPPORT_SUPERG #include #endif #include #include #include "usbdevs.h" #define USB_DEBUG_VAR otus_debug #include #include "if_otusreg.h" static int otus_debug = 0; static SYSCTL_NODE(_hw_usb, OID_AUTO, otus, CTLFLAG_RW, 0, "USB otus"); SYSCTL_INT(_hw_usb_otus, OID_AUTO, debug, CTLFLAG_RWTUN, &otus_debug, 0, "Debug level"); #define OTUS_DEBUG_XMIT 0x00000001 #define OTUS_DEBUG_RECV 0x00000002 #define OTUS_DEBUG_TXDONE 0x00000004 #define OTUS_DEBUG_RXDONE 0x00000008 #define OTUS_DEBUG_CMD 0x00000010 #define OTUS_DEBUG_CMDDONE 0x00000020 #define OTUS_DEBUG_RESET 0x00000040 #define OTUS_DEBUG_STATE 0x00000080 #define OTUS_DEBUG_CMDNOTIFY 0x00000100 #define OTUS_DEBUG_REGIO 0x00000200 #define OTUS_DEBUG_IRQ 0x00000400 #define OTUS_DEBUG_TXCOMP 0x00000800 #define OTUS_DEBUG_ANY 0xffffffff #define OTUS_DPRINTF(sc, dm, ...) \ do { \ if ((dm == OTUS_DEBUG_ANY) || (dm & otus_debug)) \ device_printf(sc->sc_dev, __VA_ARGS__); \ } while (0) #define OTUS_DEV(v, p) { USB_VPI(v, p, 0) } static const STRUCT_USB_HOST_ID otus_devs[] = { OTUS_DEV(USB_VENDOR_ACCTON, USB_PRODUCT_ACCTON_WN7512), OTUS_DEV(USB_VENDOR_ATHEROS2, USB_PRODUCT_ATHEROS2_3CRUSBN275), OTUS_DEV(USB_VENDOR_ATHEROS2, USB_PRODUCT_ATHEROS2_TG121N), OTUS_DEV(USB_VENDOR_ATHEROS2, USB_PRODUCT_ATHEROS2_AR9170), OTUS_DEV(USB_VENDOR_ATHEROS2, USB_PRODUCT_ATHEROS2_WN612), OTUS_DEV(USB_VENDOR_ATHEROS2, USB_PRODUCT_ATHEROS2_WN821NV2), OTUS_DEV(USB_VENDOR_AVM, USB_PRODUCT_AVM_FRITZWLAN), OTUS_DEV(USB_VENDOR_CACE, USB_PRODUCT_CACE_AIRPCAPNX), OTUS_DEV(USB_VENDOR_DLINK2, USB_PRODUCT_DLINK2_DWA130D1), OTUS_DEV(USB_VENDOR_DLINK2, USB_PRODUCT_DLINK2_DWA160A1), OTUS_DEV(USB_VENDOR_DLINK2, USB_PRODUCT_DLINK2_DWA160A2), OTUS_DEV(USB_VENDOR_IODATA, USB_PRODUCT_IODATA_WNGDNUS2), OTUS_DEV(USB_VENDOR_NEC, USB_PRODUCT_NEC_WL300NUG), OTUS_DEV(USB_VENDOR_NETGEAR, USB_PRODUCT_NETGEAR_WN111V2), OTUS_DEV(USB_VENDOR_NETGEAR, USB_PRODUCT_NETGEAR_WNA1000), OTUS_DEV(USB_VENDOR_NETGEAR, USB_PRODUCT_NETGEAR_WNDA3100), OTUS_DEV(USB_VENDOR_PLANEX2, USB_PRODUCT_PLANEX2_GW_US300), OTUS_DEV(USB_VENDOR_WISTRONNEWEB, USB_PRODUCT_WISTRONNEWEB_O8494), OTUS_DEV(USB_VENDOR_WISTRONNEWEB, USB_PRODUCT_WISTRONNEWEB_WNC0600), OTUS_DEV(USB_VENDOR_ZCOM, USB_PRODUCT_ZCOM_UB81), OTUS_DEV(USB_VENDOR_ZCOM, USB_PRODUCT_ZCOM_UB82), OTUS_DEV(USB_VENDOR_ZYDAS, USB_PRODUCT_ZYDAS_ZD1221), OTUS_DEV(USB_VENDOR_ZYXEL, USB_PRODUCT_ZYXEL_NWD271N), }; static device_probe_t otus_match; static device_attach_t otus_attach; static device_detach_t otus_detach; static int otus_attachhook(struct otus_softc *); void otus_get_chanlist(struct otus_softc *); static void otus_getradiocaps(struct ieee80211com *, int, int *, struct ieee80211_channel[]); int otus_load_firmware(struct otus_softc *, const char *, uint32_t); int otus_open_pipes(struct otus_softc *); void otus_close_pipes(struct otus_softc *); static int otus_alloc_tx_cmd_list(struct otus_softc *); static void otus_free_tx_cmd_list(struct otus_softc *); static int otus_alloc_rx_list(struct otus_softc *); static void otus_free_rx_list(struct otus_softc *); static int otus_alloc_tx_list(struct otus_softc *); static void otus_free_tx_list(struct otus_softc *); static void otus_free_list(struct otus_softc *, struct otus_data [], int); static struct otus_data *_otus_getbuf(struct otus_softc *); static struct otus_data *otus_getbuf(struct otus_softc *); static void otus_freebuf(struct otus_softc *, struct otus_data *); static struct otus_tx_cmd *_otus_get_txcmd(struct otus_softc *); static struct otus_tx_cmd *otus_get_txcmd(struct otus_softc *); static void otus_free_txcmd(struct otus_softc *, struct otus_tx_cmd *); void otus_next_scan(void *, int); static void otus_tx_task(void *, int pending); void otus_do_async(struct otus_softc *, void (*)(struct otus_softc *, void *), void *, int); int otus_newstate(struct ieee80211vap *, enum ieee80211_state, int); int otus_cmd(struct otus_softc *, uint8_t, const void *, int, void *, int); void otus_write(struct otus_softc *, uint32_t, uint32_t); int otus_write_barrier(struct otus_softc *); static struct ieee80211_node *otus_node_alloc(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN]); int otus_media_change(struct ifnet *); int otus_read_eeprom(struct otus_softc *); void otus_newassoc(struct ieee80211_node *, int); void otus_cmd_rxeof(struct otus_softc *, uint8_t *, int); void otus_sub_rxeof(struct otus_softc *, uint8_t *, int, struct mbufq *); static int otus_tx(struct otus_softc *, struct ieee80211_node *, struct mbuf *, struct otus_data *, const struct ieee80211_bpf_params *); int otus_ioctl(struct ifnet *, u_long, caddr_t); int otus_set_multi(struct otus_softc *); static int otus_updateedca(struct ieee80211com *); static void otus_updateedca_locked(struct otus_softc *); static void otus_updateslot(struct otus_softc *); static void otus_set_operating_mode(struct otus_softc *sc); static void otus_set_rx_filter(struct otus_softc *sc); int otus_init_mac(struct otus_softc *); uint32_t otus_phy_get_def(struct otus_softc *, uint32_t); int otus_set_board_values(struct otus_softc *, struct ieee80211_channel *); int otus_program_phy(struct otus_softc *, struct ieee80211_channel *); int otus_set_rf_bank4(struct otus_softc *, struct ieee80211_channel *); void otus_get_delta_slope(uint32_t, uint32_t *, uint32_t *); static int otus_set_chan(struct otus_softc *, struct ieee80211_channel *, int); int otus_set_key(struct ieee80211com *, struct ieee80211_node *, struct ieee80211_key *); void otus_set_key_cb(struct otus_softc *, void *); void otus_delete_key(struct ieee80211com *, struct ieee80211_node *, struct ieee80211_key *); void otus_delete_key_cb(struct otus_softc *, void *); void otus_calibrate_to(void *, int); int otus_set_bssid(struct otus_softc *, const uint8_t *); int otus_set_macaddr(struct otus_softc *, const uint8_t *); void otus_led_newstate_type1(struct otus_softc *); void otus_led_newstate_type2(struct otus_softc *); void otus_led_newstate_type3(struct otus_softc *); int otus_init(struct otus_softc *sc); void otus_stop(struct otus_softc *sc); static device_method_t otus_methods[] = { DEVMETHOD(device_probe, otus_match), DEVMETHOD(device_attach, otus_attach), DEVMETHOD(device_detach, otus_detach), DEVMETHOD_END }; static driver_t otus_driver = { .name = "otus", .methods = otus_methods, .size = sizeof(struct otus_softc) }; static devclass_t otus_devclass; DRIVER_MODULE(otus, uhub, otus_driver, otus_devclass, NULL, 0); MODULE_DEPEND(otus, wlan, 1, 1, 1); MODULE_DEPEND(otus, usb, 1, 1, 1); MODULE_DEPEND(otus, firmware, 1, 1, 1); MODULE_VERSION(otus, 1); static usb_callback_t otus_bulk_tx_callback; static usb_callback_t otus_bulk_rx_callback; static usb_callback_t otus_bulk_irq_callback; static usb_callback_t otus_bulk_cmd_callback; static const struct usb_config otus_config[OTUS_N_XFER] = { [OTUS_BULK_TX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = 0x200, .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = otus_bulk_tx_callback, .timeout = 5000, /* ms */ }, [OTUS_BULK_RX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = OTUS_RXBUFSZ, .flags = { .ext_buffer = 1, .pipe_bof = 1,.short_xfer_ok = 1,}, .callback = otus_bulk_rx_callback, }, [OTUS_BULK_IRQ] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = OTUS_MAX_CTRLSZ, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = otus_bulk_irq_callback, }, [OTUS_BULK_CMD] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = OTUS_MAX_CTRLSZ, .flags = {.pipe_bof = 1,.force_short_xfer = 1,}, .callback = otus_bulk_cmd_callback, .timeout = 5000, /* ms */ }, }; static int otus_match(device_t self) { struct usb_attach_arg *uaa = device_get_ivars(self); if (uaa->usb_mode != USB_MODE_HOST || uaa->info.bIfaceIndex != 0 || uaa->info.bConfigIndex != 0) return (ENXIO); return (usbd_lookup_id_by_uaa(otus_devs, sizeof(otus_devs), uaa)); } static int otus_attach(device_t self) { struct usb_attach_arg *uaa = device_get_ivars(self); struct otus_softc *sc = device_get_softc(self); int error; uint8_t iface_index; device_set_usb_desc(self); sc->sc_udev = uaa->device; sc->sc_dev = self; mtx_init(&sc->sc_mtx, device_get_nameunit(self), MTX_NETWORK_LOCK, MTX_DEF); TIMEOUT_TASK_INIT(taskqueue_thread, &sc->scan_to, 0, otus_next_scan, sc); TIMEOUT_TASK_INIT(taskqueue_thread, &sc->calib_to, 0, otus_calibrate_to, sc); TASK_INIT(&sc->tx_task, 0, otus_tx_task, sc); mbufq_init(&sc->sc_snd, ifqmaxlen); iface_index = 0; error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, otus_config, OTUS_N_XFER, sc, &sc->sc_mtx); if (error) { device_printf(sc->sc_dev, "could not allocate USB transfers, err=%s\n", usbd_errstr(error)); goto fail_usb; } if ((error = otus_open_pipes(sc)) != 0) { device_printf(sc->sc_dev, "%s: could not open pipes\n", __func__); goto fail; } /* XXX check return status; fail out if appropriate */ if (otus_attachhook(sc) != 0) goto fail; return (0); fail: otus_close_pipes(sc); fail_usb: mtx_destroy(&sc->sc_mtx); return (ENXIO); } static int otus_detach(device_t self) { struct otus_softc *sc = device_get_softc(self); struct ieee80211com *ic = &sc->sc_ic; otus_stop(sc); usbd_transfer_unsetup(sc->sc_xfer, OTUS_N_XFER); taskqueue_drain_timeout(taskqueue_thread, &sc->scan_to); taskqueue_drain_timeout(taskqueue_thread, &sc->calib_to); taskqueue_drain(taskqueue_thread, &sc->tx_task); otus_close_pipes(sc); #if 0 /* Wait for all queued asynchronous commands to complete. */ usb_rem_wait_task(sc->sc_udev, &sc->sc_task); usbd_ref_wait(sc->sc_udev); #endif ieee80211_ifdetach(ic); mtx_destroy(&sc->sc_mtx); return 0; } static void otus_delay_ms(struct otus_softc *sc, int ms) { DELAY(1000 * ms); } static struct ieee80211vap * otus_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 otus_vap *uvp; struct ieee80211vap *vap; if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ return (NULL); uvp = malloc(sizeof(struct otus_vap), M_80211_VAP, M_WAITOK | M_ZERO); vap = &uvp->vap; if (ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid) != 0) { /* out of memory */ free(uvp, M_80211_VAP); return (NULL); } /* override state transition machine */ uvp->newstate = vap->iv_newstate; vap->iv_newstate = otus_newstate; /* XXX TODO: double-check */ vap->iv_ampdu_density = IEEE80211_HTCAP_MPDUDENSITY_16; vap->iv_ampdu_rxmax = IEEE80211_HTCAP_MAXRXAMPDU_32K; 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 otus_vap_delete(struct ieee80211vap *vap) { struct otus_vap *uvp = OTUS_VAP(vap); ieee80211_ratectl_deinit(vap); ieee80211_vap_detach(vap); free(uvp, M_80211_VAP); } static void otus_parent(struct ieee80211com *ic) { struct otus_softc *sc = ic->ic_softc; int startall = 0; if (ic->ic_nrunning > 0) { if (!sc->sc_running) { otus_init(sc); startall = 1; } else { (void) otus_set_multi(sc); } } else if (sc->sc_running) otus_stop(sc); if (startall) ieee80211_start_all(ic); } static void otus_drain_mbufq(struct otus_softc *sc) { struct mbuf *m; struct ieee80211_node *ni; OTUS_LOCK_ASSERT(sc); while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) { ni = (struct ieee80211_node *) m->m_pkthdr.rcvif; m->m_pkthdr.rcvif = NULL; ieee80211_free_node(ni); m_freem(m); } } static void otus_tx_start(struct otus_softc *sc) { taskqueue_enqueue(taskqueue_thread, &sc->tx_task); } static int otus_transmit(struct ieee80211com *ic, struct mbuf *m) { struct otus_softc *sc = ic->ic_softc; int error; OTUS_LOCK(sc); if (! sc->sc_running) { OTUS_UNLOCK(sc); return (ENXIO); } /* XXX TODO: handle fragments */ error = mbufq_enqueue(&sc->sc_snd, m); if (error) { OTUS_DPRINTF(sc, OTUS_DEBUG_XMIT, "%s: mbufq_enqueue failed: %d\n", __func__, error); OTUS_UNLOCK(sc); return (error); } OTUS_UNLOCK(sc); /* Kick TX */ otus_tx_start(sc); return (0); } static void _otus_start(struct otus_softc *sc) { struct ieee80211_node *ni; struct otus_data *bf; struct mbuf *m; OTUS_LOCK_ASSERT(sc); while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) { bf = otus_getbuf(sc); if (bf == NULL) { OTUS_DPRINTF(sc, OTUS_DEBUG_XMIT, "%s: failed to get buffer\n", __func__); mbufq_prepend(&sc->sc_snd, m); break; } ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; m->m_pkthdr.rcvif = NULL; if (otus_tx(sc, ni, m, bf, NULL) != 0) { OTUS_DPRINTF(sc, OTUS_DEBUG_XMIT, "%s: failed to transmit\n", __func__); if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1); otus_freebuf(sc, bf); ieee80211_free_node(ni); m_freem(m); break; } } } static void otus_tx_task(void *arg, int pending) { struct otus_softc *sc = arg; OTUS_LOCK(sc); _otus_start(sc); OTUS_UNLOCK(sc); } static int otus_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic= ni->ni_ic; struct otus_softc *sc = ic->ic_softc; struct otus_data *bf = NULL; int error = 0; /* Don't transmit if we're not running */ OTUS_LOCK(sc); if (! sc->sc_running) { error = ENETDOWN; goto error; } bf = otus_getbuf(sc); if (bf == NULL) { error = ENOBUFS; goto error; } if (otus_tx(sc, ni, m, bf, params) != 0) { error = EIO; goto error; } OTUS_UNLOCK(sc); return (0); error: if (bf) otus_freebuf(sc, bf); OTUS_UNLOCK(sc); m_freem(m); return (ENXIO); } static void otus_update_chw(struct ieee80211com *ic) { printf("%s: TODO\n", __func__); } static void otus_set_channel(struct ieee80211com *ic) { struct otus_softc *sc = ic->ic_softc; OTUS_DPRINTF(sc, OTUS_DEBUG_RESET, "%s: set channel: %d\n", __func__, ic->ic_curchan->ic_freq); OTUS_LOCK(sc); (void) otus_set_chan(sc, ic->ic_curchan, 0); OTUS_UNLOCK(sc); } static int otus_ampdu_enable(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap) { /* For now, no A-MPDU TX support in the driver */ return (0); } static void otus_scan_start(struct ieee80211com *ic) { // printf("%s: TODO\n", __func__); } static void otus_scan_end(struct ieee80211com *ic) { // printf("%s: TODO\n", __func__); } static void otus_update_mcast(struct ieee80211com *ic) { struct otus_softc *sc = ic->ic_softc; (void) otus_set_multi(sc); } static int otus_attachhook(struct otus_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; usb_device_request_t req; uint32_t in, out; int error; /* Not locked */ error = otus_load_firmware(sc, "otusfw_init", AR_FW_INIT_ADDR); if (error != 0) { device_printf(sc->sc_dev, "%s: could not load %s firmware\n", __func__, "init"); return (ENXIO); } /* XXX not locked? */ otus_delay_ms(sc, 1000); /* Not locked */ error = otus_load_firmware(sc, "otusfw_main", AR_FW_MAIN_ADDR); if (error != 0) { device_printf(sc->sc_dev, "%s: could not load %s firmware\n", __func__, "main"); return (ENXIO); } OTUS_LOCK(sc); /* Tell device that firmware transfer is complete. */ req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = AR_FW_DOWNLOAD_COMPLETE; USETW(req.wValue, 0); USETW(req.wIndex, 0); USETW(req.wLength, 0); if (usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, &req, NULL, 0, NULL, 250) != 0) { OTUS_UNLOCK(sc); device_printf(sc->sc_dev, "%s: firmware initialization failed\n", __func__); return (ENXIO); } /* Send an ECHO command to check that everything is settled. */ in = 0xbadc0ffe; if (otus_cmd(sc, AR_CMD_ECHO, &in, sizeof in, &out, sizeof(out)) != 0) { OTUS_UNLOCK(sc); device_printf(sc->sc_dev, "%s: echo command failed\n", __func__); return (ENXIO); } if (in != out) { OTUS_UNLOCK(sc); device_printf(sc->sc_dev, "%s: echo reply mismatch: 0x%08x!=0x%08x\n", __func__, in, out); return (ENXIO); } /* Read entire EEPROM. */ if (otus_read_eeprom(sc) != 0) { OTUS_UNLOCK(sc); device_printf(sc->sc_dev, "%s: could not read EEPROM\n", __func__); return (ENXIO); } OTUS_UNLOCK(sc); sc->txmask = sc->eeprom.baseEepHeader.txMask; sc->rxmask = sc->eeprom.baseEepHeader.rxMask; sc->capflags = sc->eeprom.baseEepHeader.opCapFlags; IEEE80211_ADDR_COPY(ic->ic_macaddr, sc->eeprom.baseEepHeader.macAddr); sc->sc_led_newstate = otus_led_newstate_type3; /* XXX */ device_printf(sc->sc_dev, "MAC/BBP AR9170, RF AR%X, MIMO %dT%dR, address %s\n", (sc->capflags & AR5416_OPFLAGS_11A) ? 0x9104 : ((sc->txmask == 0x5) ? 0x9102 : 0x9101), (sc->txmask == 0x5) ? 2 : 1, (sc->rxmask == 0x5) ? 2 : 1, ether_sprintf(ic->ic_macaddr)); 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 | /* station mode */ #if 0 IEEE80211_C_BGSCAN | /* Background scan. */ #endif IEEE80211_C_SHPREAMBLE | /* Short preamble supported. */ IEEE80211_C_WME | /* WME/QoS */ IEEE80211_C_SHSLOT | /* Short slot time supported. */ IEEE80211_C_FF | /* Atheros fast-frames supported. */ IEEE80211_C_MONITOR | IEEE80211_C_WPA; /* WPA/RSN. */ /* XXX TODO: 11n */ #if 0 if (sc->eeprom.baseEepHeader.opCapFlags & AR5416_OPFLAGS_11G) { /* Set supported .11b and .11g rates. */ ic->ic_sup_rates[IEEE80211_MODE_11B] = ieee80211_std_rateset_11b; ic->ic_sup_rates[IEEE80211_MODE_11G] = ieee80211_std_rateset_11g; } if (sc->eeprom.baseEepHeader.opCapFlags & AR5416_OPFLAGS_11A) { /* Set supported .11a rates. */ ic->ic_sup_rates[IEEE80211_MODE_11A] = ieee80211_std_rateset_11a; } #endif #if 0 /* Build the list of supported channels. */ otus_get_chanlist(sc); #else otus_getradiocaps(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans, ic->ic_channels); #endif ieee80211_ifattach(ic); ic->ic_raw_xmit = otus_raw_xmit; ic->ic_scan_start = otus_scan_start; ic->ic_scan_end = otus_scan_end; ic->ic_set_channel = otus_set_channel; ic->ic_getradiocaps = otus_getradiocaps; ic->ic_vap_create = otus_vap_create; ic->ic_vap_delete = otus_vap_delete; ic->ic_update_mcast = otus_update_mcast; ic->ic_update_promisc = otus_update_mcast; ic->ic_parent = otus_parent; ic->ic_transmit = otus_transmit; ic->ic_update_chw = otus_update_chw; ic->ic_ampdu_enable = otus_ampdu_enable; ic->ic_wme.wme_update = otus_updateedca; ic->ic_newassoc = otus_newassoc; ic->ic_node_alloc = otus_node_alloc; #ifdef notyet ic->ic_set_key = otus_set_key; ic->ic_delete_key = otus_delete_key; #endif ieee80211_radiotap_attach(ic, &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap), OTUS_TX_RADIOTAP_PRESENT, &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap), OTUS_RX_RADIOTAP_PRESENT); return (0); } void otus_get_chanlist(struct otus_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; uint16_t domain; uint8_t chan; int i; /* XXX regulatory domain. */ domain = le16toh(sc->eeprom.baseEepHeader.regDmn[0]); OTUS_DPRINTF(sc, OTUS_DEBUG_RESET, "regdomain=0x%04x\n", domain); if (sc->eeprom.baseEepHeader.opCapFlags & AR5416_OPFLAGS_11G) { for (i = 0; i < 14; i++) { chan = ar_chans[i]; ic->ic_channels[chan].ic_freq = ieee80211_ieee2mhz(chan, IEEE80211_CHAN_2GHZ); ic->ic_channels[chan].ic_flags = IEEE80211_CHAN_CCK | IEEE80211_CHAN_OFDM | IEEE80211_CHAN_DYN | IEEE80211_CHAN_2GHZ; } } if (sc->eeprom.baseEepHeader.opCapFlags & AR5416_OPFLAGS_11A) { for (i = 14; i < nitems(ar_chans); i++) { chan = ar_chans[i]; ic->ic_channels[chan].ic_freq = ieee80211_ieee2mhz(chan, IEEE80211_CHAN_5GHZ); ic->ic_channels[chan].ic_flags = IEEE80211_CHAN_A; } } } static void otus_getradiocaps(struct ieee80211com *ic, int maxchans, int *nchans, struct ieee80211_channel chans[]) { struct otus_softc *sc = ic->ic_softc; uint8_t bands[IEEE80211_MODE_BYTES]; /* Set supported .11b and .11g rates. */ memset(bands, 0, sizeof(bands)); if (sc->eeprom.baseEepHeader.opCapFlags & AR5416_OPFLAGS_11G) { setbit(bands, IEEE80211_MODE_11B); setbit(bands, IEEE80211_MODE_11G); #if 0 if (sc->sc_ht) setbit(bands, IEEE80211_MODE_11NG); #endif ieee80211_add_channel_list_2ghz(chans, maxchans, nchans, ar_chans, 14, bands, 0); } if (sc->eeprom.baseEepHeader.opCapFlags & AR5416_OPFLAGS_11A) { setbit(bands, IEEE80211_MODE_11A); ieee80211_add_channel_list_5ghz(chans, maxchans, nchans, &ar_chans[14], nitems(ar_chans) - 14, bands, 0); } } int otus_load_firmware(struct otus_softc *sc, const char *name, uint32_t addr) { usb_device_request_t req; char *ptr; const struct firmware *fw; int mlen, error, size; error = 0; /* Read firmware image from the filesystem. */ if ((fw = firmware_get(name)) == NULL) { device_printf(sc->sc_dev, "%s: failed loadfirmware of file %s\n", __func__, name); return (ENXIO); } req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = AR_FW_DOWNLOAD; USETW(req.wIndex, 0); OTUS_LOCK(sc); /* XXX const */ ptr = __DECONST(char *, fw->data); size = fw->datasize; addr >>= 8; while (size > 0) { mlen = MIN(size, 4096); USETW(req.wValue, addr); USETW(req.wLength, mlen); if (usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, &req, ptr, 0, NULL, 250) != 0) { error = EIO; break; } addr += mlen >> 8; ptr += mlen; size -= mlen; } OTUS_UNLOCK(sc); firmware_put(fw, FIRMWARE_UNLOAD); if (error != 0) device_printf(sc->sc_dev, "%s: %s: error=%d\n", __func__, name, error); return error; } int otus_open_pipes(struct otus_softc *sc) { #if 0 int isize, error; int i; #endif int error; OTUS_UNLOCK_ASSERT(sc); if ((error = otus_alloc_tx_cmd_list(sc)) != 0) { device_printf(sc->sc_dev, "%s: could not allocate command xfer\n", __func__); goto fail; } if ((error = otus_alloc_tx_list(sc)) != 0) { device_printf(sc->sc_dev, "%s: could not allocate Tx xfers\n", __func__); goto fail; } if ((error = otus_alloc_rx_list(sc)) != 0) { device_printf(sc->sc_dev, "%s: could not allocate Rx xfers\n", __func__); goto fail; } /* Enable RX transfers; needed for initial firmware messages */ OTUS_LOCK(sc); usbd_transfer_start(sc->sc_xfer[OTUS_BULK_RX]); usbd_transfer_start(sc->sc_xfer[OTUS_BULK_IRQ]); OTUS_UNLOCK(sc); return 0; fail: otus_close_pipes(sc); return error; } void otus_close_pipes(struct otus_softc *sc) { OTUS_LOCK(sc); otus_free_tx_cmd_list(sc); otus_free_tx_list(sc); otus_free_rx_list(sc); OTUS_UNLOCK(sc); usbd_transfer_unsetup(sc->sc_xfer, OTUS_N_XFER); } static void otus_free_cmd_list(struct otus_softc *sc, struct otus_tx_cmd cmd[], int ndata) { int i; /* XXX TODO: someone has to have waken up waiters! */ for (i = 0; i < ndata; i++) { struct otus_tx_cmd *dp = &cmd[i]; if (dp->buf != NULL) { free(dp->buf, M_USBDEV); dp->buf = NULL; } } } static int otus_alloc_cmd_list(struct otus_softc *sc, struct otus_tx_cmd cmd[], int ndata, int maxsz) { int i, error; for (i = 0; i < ndata; i++) { struct otus_tx_cmd *dp = &cmd[i]; dp->buf = malloc(maxsz, M_USBDEV, M_NOWAIT | M_ZERO); dp->odata = NULL; if (dp->buf == NULL) { device_printf(sc->sc_dev, "could not allocate buffer\n"); error = ENOMEM; goto fail; } } return (0); fail: otus_free_cmd_list(sc, cmd, ndata); return (error); } static int otus_alloc_tx_cmd_list(struct otus_softc *sc) { int error, i; error = otus_alloc_cmd_list(sc, sc->sc_cmd, OTUS_CMD_LIST_COUNT, OTUS_MAX_TXCMDSZ); if (error != 0) return (error); STAILQ_INIT(&sc->sc_cmd_active); STAILQ_INIT(&sc->sc_cmd_inactive); STAILQ_INIT(&sc->sc_cmd_pending); STAILQ_INIT(&sc->sc_cmd_waiting); for (i = 0; i < OTUS_CMD_LIST_COUNT; i++) STAILQ_INSERT_HEAD(&sc->sc_cmd_inactive, &sc->sc_cmd[i], next_cmd); return (0); } static void otus_free_tx_cmd_list(struct otus_softc *sc) { /* * XXX TODO: something needs to wake up any pending/sleeping * waiters! */ STAILQ_INIT(&sc->sc_cmd_active); STAILQ_INIT(&sc->sc_cmd_inactive); STAILQ_INIT(&sc->sc_cmd_pending); STAILQ_INIT(&sc->sc_cmd_waiting); otus_free_cmd_list(sc, sc->sc_cmd, OTUS_CMD_LIST_COUNT); } static int otus_alloc_list(struct otus_softc *sc, struct otus_data data[], int ndata, int maxsz) { int i, error; for (i = 0; i < ndata; i++) { struct otus_data *dp = &data[i]; dp->sc = sc; dp->m = NULL; dp->buf = malloc(maxsz, M_USBDEV, M_NOWAIT | M_ZERO); if (dp->buf == NULL) { device_printf(sc->sc_dev, "could not allocate buffer\n"); error = ENOMEM; goto fail; } dp->ni = NULL; } return (0); fail: otus_free_list(sc, data, ndata); return (error); } static int otus_alloc_rx_list(struct otus_softc *sc) { int error, i; error = otus_alloc_list(sc, sc->sc_rx, OTUS_RX_LIST_COUNT, OTUS_RXBUFSZ); if (error != 0) return (error); STAILQ_INIT(&sc->sc_rx_active); STAILQ_INIT(&sc->sc_rx_inactive); for (i = 0; i < OTUS_RX_LIST_COUNT; i++) STAILQ_INSERT_HEAD(&sc->sc_rx_inactive, &sc->sc_rx[i], next); return (0); } static int otus_alloc_tx_list(struct otus_softc *sc) { int error, i; error = otus_alloc_list(sc, sc->sc_tx, OTUS_TX_LIST_COUNT, OTUS_TXBUFSZ); if (error != 0) return (error); STAILQ_INIT(&sc->sc_tx_inactive); for (i = 0; i != OTUS_N_XFER; i++) { STAILQ_INIT(&sc->sc_tx_active[i]); STAILQ_INIT(&sc->sc_tx_pending[i]); } for (i = 0; i < OTUS_TX_LIST_COUNT; i++) { STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, &sc->sc_tx[i], next); } return (0); } static void otus_free_tx_list(struct otus_softc *sc) { int i; /* prevent further allocations from TX list(s) */ STAILQ_INIT(&sc->sc_tx_inactive); for (i = 0; i != OTUS_N_XFER; i++) { STAILQ_INIT(&sc->sc_tx_active[i]); STAILQ_INIT(&sc->sc_tx_pending[i]); } otus_free_list(sc, sc->sc_tx, OTUS_TX_LIST_COUNT); } static void otus_free_rx_list(struct otus_softc *sc) { /* prevent further allocations from RX list(s) */ STAILQ_INIT(&sc->sc_rx_inactive); STAILQ_INIT(&sc->sc_rx_active); otus_free_list(sc, sc->sc_rx, OTUS_RX_LIST_COUNT); } static void otus_free_list(struct otus_softc *sc, struct otus_data data[], int ndata) { int i; for (i = 0; i < ndata; i++) { struct otus_data *dp = &data[i]; if (dp->buf != NULL) { free(dp->buf, M_USBDEV); dp->buf = NULL; } if (dp->ni != NULL) { ieee80211_free_node(dp->ni); dp->ni = NULL; } } } static struct otus_data * _otus_getbuf(struct otus_softc *sc) { struct otus_data *bf; bf = STAILQ_FIRST(&sc->sc_tx_inactive); if (bf != NULL) STAILQ_REMOVE_HEAD(&sc->sc_tx_inactive, next); else bf = NULL; /* XXX bzero? */ return (bf); } static struct otus_data * otus_getbuf(struct otus_softc *sc) { struct otus_data *bf; OTUS_LOCK_ASSERT(sc); bf = _otus_getbuf(sc); return (bf); } static void otus_freebuf(struct otus_softc *sc, struct otus_data *bf) { OTUS_LOCK_ASSERT(sc); STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, bf, next); } static struct otus_tx_cmd * _otus_get_txcmd(struct otus_softc *sc) { struct otus_tx_cmd *bf; bf = STAILQ_FIRST(&sc->sc_cmd_inactive); if (bf != NULL) STAILQ_REMOVE_HEAD(&sc->sc_cmd_inactive, next_cmd); else bf = NULL; return (bf); } static struct otus_tx_cmd * otus_get_txcmd(struct otus_softc *sc) { struct otus_tx_cmd *bf; OTUS_LOCK_ASSERT(sc); bf = _otus_get_txcmd(sc); if (bf == NULL) { device_printf(sc->sc_dev, "%s: no tx cmd buffers\n", __func__); } return (bf); } static void otus_free_txcmd(struct otus_softc *sc, struct otus_tx_cmd *bf) { OTUS_LOCK_ASSERT(sc); STAILQ_INSERT_TAIL(&sc->sc_cmd_inactive, bf, next_cmd); } void otus_next_scan(void *arg, int pending) { #if 0 struct otus_softc *sc = arg; if (usbd_is_dying(sc->sc_udev)) return; usbd_ref_incr(sc->sc_udev); if (sc->sc_ic.ic_state == IEEE80211_S_SCAN) ieee80211_next_scan(&sc->sc_ic.ic_if); usbd_ref_decr(sc->sc_udev); #endif } int otus_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct otus_vap *uvp = OTUS_VAP(vap); struct ieee80211com *ic = vap->iv_ic; struct otus_softc *sc = ic->ic_softc; enum ieee80211_state ostate; ostate = vap->iv_state; OTUS_DPRINTF(sc, OTUS_DEBUG_STATE, "%s: %s -> %s\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate]); IEEE80211_UNLOCK(ic); OTUS_LOCK(sc); /* XXX TODO: more fleshing out! */ switch (nstate) { case IEEE80211_S_INIT: otus_set_operating_mode(sc); otus_set_rx_filter(sc); break; case IEEE80211_S_RUN: if (ic->ic_opmode == IEEE80211_M_STA) { otus_updateslot(sc); otus_set_operating_mode(sc); otus_set_rx_filter(sc); /* Start calibration timer. */ taskqueue_enqueue_timeout(taskqueue_thread, &sc->calib_to, hz); } break; default: break; } /* XXX TODO: calibration? */ sc->sc_led_newstate(sc); OTUS_UNLOCK(sc); IEEE80211_LOCK(ic); return (uvp->newstate(vap, nstate, arg)); } int otus_cmd(struct otus_softc *sc, uint8_t code, const void *idata, int ilen, void *odata, int odatalen) { struct otus_tx_cmd *cmd; struct ar_cmd_hdr *hdr; int xferlen, error; OTUS_LOCK_ASSERT(sc); /* Always bulk-out a multiple of 4 bytes. */ xferlen = (sizeof (*hdr) + ilen + 3) & ~3; if (xferlen > OTUS_MAX_TXCMDSZ) { device_printf(sc->sc_dev, "%s: command (0x%02x) size (%d) > %d\n", __func__, code, xferlen, OTUS_MAX_TXCMDSZ); return (EIO); } cmd = otus_get_txcmd(sc); if (cmd == NULL) { device_printf(sc->sc_dev, "%s: failed to get buf\n", __func__); return (EIO); } hdr = (struct ar_cmd_hdr *)cmd->buf; hdr->code = code; hdr->len = ilen; hdr->token = ++sc->token; /* Don't care about endianness. */ cmd->token = hdr->token; /* XXX TODO: check max cmd length? */ memcpy((uint8_t *)&hdr[1], idata, ilen); OTUS_DPRINTF(sc, OTUS_DEBUG_CMD, "%s: sending command code=0x%02x len=%d token=%d\n", __func__, code, ilen, hdr->token); cmd->odata = odata; cmd->odatalen = odatalen; cmd->buflen = xferlen; /* Queue the command to the endpoint */ STAILQ_INSERT_TAIL(&sc->sc_cmd_pending, cmd, next_cmd); usbd_transfer_start(sc->sc_xfer[OTUS_BULK_CMD]); /* Sleep on the command; wait for it to complete */ error = msleep(cmd, &sc->sc_mtx, PCATCH, "otuscmd", hz); /* * At this point we don't own cmd any longer; it'll be * freed by the cmd bulk path or the RX notification * path. If the data is made available then it'll be copied * to the caller. All that is left to do is communicate * status back to the caller. */ if (error != 0) { device_printf(sc->sc_dev, "%s: timeout waiting for command 0x%02x reply\n", __func__, code); } return error; } void otus_write(struct otus_softc *sc, uint32_t reg, uint32_t val) { OTUS_LOCK_ASSERT(sc); sc->write_buf[sc->write_idx].reg = htole32(reg); sc->write_buf[sc->write_idx].val = htole32(val); if (++sc->write_idx > (AR_MAX_WRITE_IDX-1)) (void)otus_write_barrier(sc); } int otus_write_barrier(struct otus_softc *sc) { int error; OTUS_LOCK_ASSERT(sc); if (sc->write_idx == 0) return 0; /* Nothing to flush. */ OTUS_DPRINTF(sc, OTUS_DEBUG_REGIO, "%s: called; %d updates\n", __func__, sc->write_idx); error = otus_cmd(sc, AR_CMD_WREG, sc->write_buf, sizeof (sc->write_buf[0]) * sc->write_idx, NULL, 0); sc->write_idx = 0; return error; } static struct ieee80211_node * otus_node_alloc(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN]) { return malloc(sizeof (struct otus_node), M_80211_NODE, M_NOWAIT | M_ZERO); } #if 0 int otus_media_change(struct ifnet *ifp) { struct otus_softc *sc = ifp->if_softc; struct ieee80211com *ic = &sc->sc_ic; uint8_t rate, ridx; int error; error = ieee80211_media_change(ifp); if (error != ENETRESET) return error; if (ic->ic_fixed_rate != -1) { rate = ic->ic_sup_rates[ic->ic_curmode]. rs_rates[ic->ic_fixed_rate] & IEEE80211_RATE_VAL; for (ridx = 0; ridx <= OTUS_RIDX_MAX; ridx++) if (otus_rates[ridx].rate == rate) break; sc->fixed_ridx = ridx; } if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) == (IFF_UP | IFF_RUNNING)) error = otus_init(sc); return error; } #endif int otus_read_eeprom(struct otus_softc *sc) { uint32_t regs[8], reg; uint8_t *eep; int i, j, error; OTUS_LOCK_ASSERT(sc); /* Read EEPROM by blocks of 32 bytes. */ eep = (uint8_t *)&sc->eeprom; reg = AR_EEPROM_OFFSET; for (i = 0; i < sizeof (sc->eeprom) / 32; i++) { for (j = 0; j < 8; j++, reg += 4) regs[j] = htole32(reg); error = otus_cmd(sc, AR_CMD_RREG, regs, sizeof regs, eep, 32); if (error != 0) break; eep += 32; } return error; } void otus_newassoc(struct ieee80211_node *ni, int isnew) { struct ieee80211com *ic = ni->ni_ic; struct otus_softc *sc = ic->ic_softc; struct otus_node *on = OTUS_NODE(ni); OTUS_DPRINTF(sc, OTUS_DEBUG_STATE, "new assoc isnew=%d addr=%s\n", isnew, ether_sprintf(ni->ni_macaddr)); on->tx_done = 0; on->tx_err = 0; on->tx_retries = 0; } static void otus_cmd_handle_response(struct otus_softc *sc, struct ar_cmd_hdr *hdr) { struct otus_tx_cmd *cmd; OTUS_LOCK_ASSERT(sc); OTUS_DPRINTF(sc, OTUS_DEBUG_CMDDONE, "%s: received reply code=0x%02x len=%d token=%d\n", __func__, hdr->code, hdr->len, hdr->token); /* * Walk the list, freeing items that aren't ours, * stopping when we hit our token. */ while ((cmd = STAILQ_FIRST(&sc->sc_cmd_waiting)) != NULL) { STAILQ_REMOVE_HEAD(&sc->sc_cmd_waiting, next_cmd); OTUS_DPRINTF(sc, OTUS_DEBUG_CMDDONE, "%s: cmd=%p; hdr.token=%d, cmd.token=%d\n", __func__, cmd, (int) hdr->token, (int) cmd->token); if (hdr->token == cmd->token) { /* Copy answer into caller's supplied buffer. */ if (cmd->odata != NULL) { if (hdr->len != cmd->odatalen) { device_printf(sc->sc_dev, "%s: code 0x%02x, len=%d, olen=%d\n", __func__, (int) hdr->code, (int) hdr->len, (int) cmd->odatalen); } memcpy(cmd->odata, &hdr[1], MIN(cmd->odatalen, hdr->len)); } wakeup(cmd); } STAILQ_INSERT_TAIL(&sc->sc_cmd_inactive, cmd, next_cmd); } } void otus_cmd_rxeof(struct otus_softc *sc, uint8_t *buf, int len) { struct ieee80211com *ic = &sc->sc_ic; struct ar_cmd_hdr *hdr; OTUS_LOCK_ASSERT(sc); if (__predict_false(len < sizeof (*hdr))) { OTUS_DPRINTF(sc, OTUS_DEBUG_CMDDONE, "cmd too small %d\n", len); return; } hdr = (struct ar_cmd_hdr *)buf; if (__predict_false(sizeof (*hdr) + hdr->len > len || sizeof (*hdr) + hdr->len > 64)) { OTUS_DPRINTF(sc, OTUS_DEBUG_CMDDONE, "cmd too large %d\n", hdr->len); return; } OTUS_DPRINTF(sc, OTUS_DEBUG_RXDONE, "%s: code=%.02x\n", __func__, hdr->code); /* * This has to reach into the cmd queue "waiting for * an RX response" list, grab the head entry and check * if we need to wake anyone up. */ if ((hdr->code & 0xc0) != 0xc0) { otus_cmd_handle_response(sc, hdr); return; } /* Received unsolicited notification. */ switch (hdr->code & 0x3f) { case AR_EVT_BEACON: break; case AR_EVT_TX_COMP: { struct ar_evt_tx_comp *tx = (struct ar_evt_tx_comp *)&hdr[1]; struct ieee80211_node *ni; ni = ieee80211_find_node(&ic->ic_sta, tx->macaddr); if (ni == NULL) { device_printf(sc->sc_dev, "%s: txcomp on unknown node (%s)\n", __func__, ether_sprintf(tx->macaddr)); break; } OTUS_DPRINTF(sc, OTUS_DEBUG_TXCOMP, "tx completed %s status=%d phy=0x%x\n", ether_sprintf(tx->macaddr), le16toh(tx->status), le32toh(tx->phy)); switch (le16toh(tx->status)) { case AR_TX_STATUS_COMP: #if 0 ackfailcnt = 0; ieee80211_ratectl_tx_complete(ni->ni_vap, ni, IEEE80211_RATECTL_TX_SUCCESS, &ackfailcnt, NULL); #endif /* * We don't get the above; only error notifications. * Sigh. So, don't worry about this. */ break; case AR_TX_STATUS_RETRY_COMP: OTUS_NODE(ni)->tx_retries++; break; case AR_TX_STATUS_FAILED: OTUS_NODE(ni)->tx_err++; break; } ieee80211_free_node(ni); break; } case AR_EVT_TBTT: break; case AR_EVT_DO_BB_RESET: /* * This is "tell driver to reset baseband" from ar9170-fw. * * I'm not sure what we should do here, so I'm going to * fall through; it gets generated when RTSRetryCnt internally * reaches '5' - I guess the firmware authors thought that * meant that the BB may have gone deaf or something. */ default: device_printf(sc->sc_dev, "%s: received notification code=0x%02x len=%d\n", __func__, hdr->code, hdr->len); } } void otus_sub_rxeof(struct otus_softc *sc, uint8_t *buf, int len, struct mbufq *rxq) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_rx_stats rxs; #if 0 struct ieee80211_node *ni; #endif struct ar_rx_tail *tail; struct ieee80211_frame *wh; struct mbuf *m; uint8_t *plcp; // int s; int mlen; if (__predict_false(len < AR_PLCP_HDR_LEN)) { OTUS_DPRINTF(sc, OTUS_DEBUG_RXDONE, "sub-xfer too short %d\n", len); return; } plcp = buf; /* All bits in the PLCP header are set to 1 for non-MPDU. */ if (memcmp(plcp, AR_PLCP_HDR_INTR, AR_PLCP_HDR_LEN) == 0) { otus_cmd_rxeof(sc, plcp + AR_PLCP_HDR_LEN, len - AR_PLCP_HDR_LEN); return; } /* Received MPDU. */ if (__predict_false(len < AR_PLCP_HDR_LEN + sizeof (*tail))) { OTUS_DPRINTF(sc, OTUS_DEBUG_RXDONE, "MPDU too short %d\n", len); counter_u64_add(ic->ic_ierrors, 1); return; } tail = (struct ar_rx_tail *)(plcp + len - sizeof (*tail)); /* Discard error frames; don't discard BAD_RA (eg monitor mode); let net80211 do that */ if (__predict_false((tail->error & ~AR_RX_ERROR_BAD_RA) != 0)) { OTUS_DPRINTF(sc, OTUS_DEBUG_RXDONE, "error frame 0x%02x\n", tail->error); if (tail->error & AR_RX_ERROR_FCS) { OTUS_DPRINTF(sc, OTUS_DEBUG_RXDONE, "bad FCS\n"); } else if (tail->error & AR_RX_ERROR_MMIC) { /* Report Michael MIC failures to net80211. */ #if 0 ieee80211_notify_michael_failure(ni->ni_vap, wh, keyidx); #endif device_printf(sc->sc_dev, "%s: MIC failure\n", __func__); } counter_u64_add(ic->ic_ierrors, 1); return; } /* Compute MPDU's length. */ mlen = len - AR_PLCP_HDR_LEN - sizeof (*tail); /* Make sure there's room for an 802.11 header + FCS. */ if (__predict_false(mlen < IEEE80211_MIN_LEN)) { counter_u64_add(ic->ic_ierrors, 1); return; } mlen -= IEEE80211_CRC_LEN; /* strip 802.11 FCS */ wh = (struct ieee80211_frame *)(plcp + AR_PLCP_HDR_LEN); /* * TODO: I see > 2KiB buffers in this path; is it A-MSDU or something? */ m = m_get2(mlen, M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) { device_printf(sc->sc_dev, "%s: failed m_get2() (mlen=%d)\n", __func__, mlen); counter_u64_add(ic->ic_ierrors, 1); return; } /* Finalize mbuf. */ memcpy(mtod(m, uint8_t *), wh, mlen); m->m_pkthdr.len = m->m_len = mlen; #if 0 if (__predict_false(sc->sc_drvbpf != NULL)) { struct otus_rx_radiotap_header *tap = &sc->sc_rxtap; struct mbuf mb; tap->wr_flags = 0; tap->wr_chan_freq = htole16(ic->ic_ibss_chan->ic_freq); tap->wr_chan_flags = htole16(ic->ic_ibss_chan->ic_flags); tap->wr_antsignal = tail->rssi; tap->wr_rate = 2; /* In case it can't be found below. */ switch (tail->status & AR_RX_STATUS_MT_MASK) { case AR_RX_STATUS_MT_CCK: switch (plcp[0]) { 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; } if (tail->status & AR_RX_STATUS_SHPREAMBLE) tap->wr_flags |= IEEE80211_RADIOTAP_F_SHORTPRE; break; case AR_RX_STATUS_MT_OFDM: switch (plcp[0] & 0xf) { case 0xb: tap->wr_rate = 12; break; case 0xf: tap->wr_rate = 18; break; case 0xa: tap->wr_rate = 24; break; case 0xe: tap->wr_rate = 36; break; case 0x9: tap->wr_rate = 48; break; case 0xd: tap->wr_rate = 72; break; case 0x8: tap->wr_rate = 96; break; case 0xc: tap->wr_rate = 108; break; } break; } mb.m_data = (caddr_t)tap; mb.m_next = m; mb.m_nextpkt = NULL; mb.m_type = 0; mb.m_flags = 0; bpf_mtap(sc->sc_drvbpf, &mb, BPF_DIRECTION_IN); } #endif /* Add RSSI/NF to this mbuf */ bzero(&rxs, sizeof(rxs)); rxs.r_flags = IEEE80211_R_NF | IEEE80211_R_RSSI; rxs.c_nf = sc->sc_nf[0]; /* XXX chain 0 != combined rssi/nf */ rxs.c_rssi = tail->rssi; /* XXX TODO: add MIMO RSSI/NF as well */ - ieee80211_add_rx_params(m, &rxs); + if (ieee80211_add_rx_params(m, &rxs) == 0) { + counter_u64_add(ic->ic_ierrors, 1); + return; + } /* XXX make a method */ STAILQ_INSERT_TAIL(&rxq->mq_head, m, m_stailqpkt); #if 0 OTUS_UNLOCK(sc); ni = ieee80211_find_rxnode(ic, wh); rxi.rxi_flags = 0; rxi.rxi_rssi = tail->rssi; rxi.rxi_tstamp = 0; /* unused */ ieee80211_input(ifp, m, ni, &rxi); /* Node is no longer needed. */ ieee80211_release_node(ic, ni); OTUS_LOCK(sc); #endif } static void otus_rxeof(struct usb_xfer *xfer, struct otus_data *data, struct mbufq *rxq) { struct otus_softc *sc = usbd_xfer_softc(xfer); caddr_t buf = data->buf; struct ar_rx_head *head; uint16_t hlen; int len; usbd_xfer_status(xfer, &len, NULL, NULL, NULL); while (len >= sizeof (*head)) { head = (struct ar_rx_head *)buf; if (__predict_false(head->tag != htole16(AR_RX_HEAD_TAG))) { OTUS_DPRINTF(sc, OTUS_DEBUG_RXDONE, "tag not valid 0x%x\n", le16toh(head->tag)); break; } hlen = le16toh(head->len); if (__predict_false(sizeof (*head) + hlen > len)) { OTUS_DPRINTF(sc, OTUS_DEBUG_RXDONE, "xfer too short %d/%d\n", len, hlen); break; } /* Process sub-xfer. */ otus_sub_rxeof(sc, (uint8_t *)&head[1], hlen, rxq); /* Next sub-xfer is aligned on a 32-bit boundary. */ hlen = (sizeof (*head) + hlen + 3) & ~3; buf += hlen; len -= hlen; } } static void otus_bulk_rx_callback(struct usb_xfer *xfer, usb_error_t error) { struct otus_softc *sc = usbd_xfer_softc(xfer); struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_frame *wh; struct ieee80211_node *ni; struct mbuf *m; struct mbufq scrx; struct otus_data *data; OTUS_LOCK_ASSERT(sc); mbufq_init(&scrx, 1024); #if 0 device_printf(sc->sc_dev, "%s: called; state=%d; error=%d\n", __func__, USB_GET_STATE(xfer), error); #endif switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: data = STAILQ_FIRST(&sc->sc_rx_active); if (data == NULL) goto tr_setup; STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next); otus_rxeof(xfer, data, &scrx); STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: /* * XXX TODO: what if sc_rx isn't empty, but data * is empty? Then we leak mbufs. */ data = STAILQ_FIRST(&sc->sc_rx_inactive); if (data == NULL) { //KASSERT(m == NULL, ("mbuf isn't NULL")); return; } STAILQ_REMOVE_HEAD(&sc->sc_rx_inactive, next); STAILQ_INSERT_TAIL(&sc->sc_rx_active, data, next); usbd_xfer_set_frame_data(xfer, 0, data->buf, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); /* * To avoid LOR we should unlock our private mutex here to call * ieee80211_input() because here is at the end of a USB * callback and safe to unlock. */ OTUS_UNLOCK(sc); while ((m = mbufq_dequeue(&scrx)) != NULL) { wh = mtod(m, struct ieee80211_frame *); ni = ieee80211_find_rxnode(ic, (struct ieee80211_frame_min *)wh); if (ni != NULL) { if (ni->ni_flags & IEEE80211_NODE_HT) m->m_flags |= M_AMPDU; - (void)ieee80211_input_mimo(ni, m, NULL); + (void)ieee80211_input_mimo(ni, m); ieee80211_free_node(ni); } else - (void)ieee80211_input_mimo_all(ic, m, NULL); + (void)ieee80211_input_mimo_all(ic, m); } #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_ff_age_all(ic, 100); #endif OTUS_LOCK(sc); break; default: /* needs it to the inactive queue due to a error. */ data = STAILQ_FIRST(&sc->sc_rx_active); if (data != NULL) { STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next); STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); } if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); counter_u64_add(ic->ic_ierrors, 1); goto tr_setup; } break; } } static void otus_txeof(struct usb_xfer *xfer, struct otus_data *data) { struct otus_softc *sc = usbd_xfer_softc(xfer); OTUS_DPRINTF(sc, OTUS_DEBUG_TXDONE, "%s: called; data=%p\n", __func__, data); OTUS_LOCK_ASSERT(sc); if (sc->sc_tx_n_active == 0) { device_printf(sc->sc_dev, "%s: completed but tx_active=0\n", __func__); } else { sc->sc_tx_n_active--; } if (data->m) { /* XXX status? */ /* XXX we get TX status via the RX path.. */ ieee80211_tx_complete(data->ni, data->m, 0); data->m = NULL; data->ni = NULL; } } static void otus_txcmdeof(struct usb_xfer *xfer, struct otus_tx_cmd *cmd) { struct otus_softc *sc = usbd_xfer_softc(xfer); OTUS_LOCK_ASSERT(sc); OTUS_DPRINTF(sc, OTUS_DEBUG_CMDDONE, "%s: called; data=%p; odata=%p\n", __func__, cmd, cmd->odata); /* * Non-response commands still need wakeup so the caller * knows it was submitted and completed OK; response commands should * wait until they're ACKed by the firmware with a response. */ if (cmd->odata) { STAILQ_INSERT_TAIL(&sc->sc_cmd_waiting, cmd, next_cmd); } else { wakeup(cmd); otus_free_txcmd(sc, cmd); } } static void otus_bulk_tx_callback(struct usb_xfer *xfer, usb_error_t error) { uint8_t which = OTUS_BULK_TX; struct otus_softc *sc = usbd_xfer_softc(xfer); struct ieee80211com *ic = &sc->sc_ic; struct otus_data *data; OTUS_LOCK_ASSERT(sc); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: data = STAILQ_FIRST(&sc->sc_tx_active[which]); if (data == NULL) goto tr_setup; OTUS_DPRINTF(sc, OTUS_DEBUG_TXDONE, "%s: transfer done %p\n", __func__, data); STAILQ_REMOVE_HEAD(&sc->sc_tx_active[which], next); otus_txeof(xfer, data); otus_freebuf(sc, data); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: data = STAILQ_FIRST(&sc->sc_tx_pending[which]); if (data == NULL) { OTUS_DPRINTF(sc, OTUS_DEBUG_XMIT, "%s: empty pending queue sc %p\n", __func__, sc); sc->sc_tx_n_active = 0; goto finish; } STAILQ_REMOVE_HEAD(&sc->sc_tx_pending[which], next); STAILQ_INSERT_TAIL(&sc->sc_tx_active[which], data, next); usbd_xfer_set_frame_data(xfer, 0, data->buf, data->buflen); OTUS_DPRINTF(sc, OTUS_DEBUG_XMIT, "%s: submitting transfer %p\n", __func__, data); usbd_transfer_submit(xfer); sc->sc_tx_n_active++; break; default: data = STAILQ_FIRST(&sc->sc_tx_active[which]); if (data != NULL) { STAILQ_REMOVE_HEAD(&sc->sc_tx_active[which], next); otus_txeof(xfer, data); otus_freebuf(sc, data); } counter_u64_add(ic->ic_oerrors, 1); if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); goto tr_setup; } break; } finish: #ifdef IEEE80211_SUPPORT_SUPERG /* * If the TX active queue drops below a certain * threshold, ensure we age fast-frames out so they're * transmitted. */ if (sc->sc_tx_n_active < 2) { /* XXX ew - net80211 should defer this for us! */ OTUS_UNLOCK(sc); ieee80211_ff_flush(ic, WME_AC_VO); ieee80211_ff_flush(ic, WME_AC_VI); ieee80211_ff_flush(ic, WME_AC_BE); ieee80211_ff_flush(ic, WME_AC_BK); OTUS_LOCK(sc); } #endif /* Kick TX */ otus_tx_start(sc); } static void otus_bulk_cmd_callback(struct usb_xfer *xfer, usb_error_t error) { struct otus_softc *sc = usbd_xfer_softc(xfer); #if 0 struct ieee80211com *ic = &sc->sc_ic; #endif struct otus_tx_cmd *cmd; OTUS_LOCK_ASSERT(sc); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: cmd = STAILQ_FIRST(&sc->sc_cmd_active); if (cmd == NULL) goto tr_setup; OTUS_DPRINTF(sc, OTUS_DEBUG_CMDDONE, "%s: transfer done %p\n", __func__, cmd); STAILQ_REMOVE_HEAD(&sc->sc_cmd_active, next_cmd); otus_txcmdeof(xfer, cmd); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: cmd = STAILQ_FIRST(&sc->sc_cmd_pending); if (cmd == NULL) { OTUS_DPRINTF(sc, OTUS_DEBUG_CMD, "%s: empty pending queue sc %p\n", __func__, sc); return; } STAILQ_REMOVE_HEAD(&sc->sc_cmd_pending, next_cmd); STAILQ_INSERT_TAIL(&sc->sc_cmd_active, cmd, next_cmd); usbd_xfer_set_frame_data(xfer, 0, cmd->buf, cmd->buflen); OTUS_DPRINTF(sc, OTUS_DEBUG_CMD, "%s: submitting transfer %p; buf=%p, buflen=%d\n", __func__, cmd, cmd->buf, cmd->buflen); usbd_transfer_submit(xfer); break; default: cmd = STAILQ_FIRST(&sc->sc_cmd_active); if (cmd != NULL) { STAILQ_REMOVE_HEAD(&sc->sc_cmd_active, next_cmd); otus_txcmdeof(xfer, cmd); } if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } /* * This isn't used by carl9170; it however may be used by the * initial bootloader. */ static void otus_bulk_irq_callback(struct usb_xfer *xfer, usb_error_t error) { struct otus_softc *sc = usbd_xfer_softc(xfer); int actlen; int sumlen; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); OTUS_DPRINTF(sc, OTUS_DEBUG_IRQ, "%s: called; state=%d\n", __func__, USB_GET_STATE(xfer)); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: /* * Read usb frame data, if any. * "actlen" has the total length for all frames * transferred. */ OTUS_DPRINTF(sc, OTUS_DEBUG_IRQ, "%s: comp; %d bytes\n", __func__, actlen); #if 0 pc = usbd_xfer_get_frame(xfer, 0); otus_dump_usb_rx_page(sc, pc, actlen); #endif /* XXX fallthrough */ case USB_ST_SETUP: /* * Setup xfer frame lengths/count and data */ OTUS_DPRINTF(sc, OTUS_DEBUG_IRQ, "%s: setup\n", __func__); usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ /* * Print error message and clear stall * for example. */ OTUS_DPRINTF(sc, OTUS_DEBUG_IRQ, "%s: ERROR?\n", __func__); break; } } /* * Map net80211 rate to hw rate for otus MAC/PHY. */ static uint8_t otus_rate_to_hw_rate(struct otus_softc *sc, uint8_t rate) { int is_2ghz; is_2ghz = !! (IEEE80211_IS_CHAN_2GHZ(sc->sc_ic.ic_curchan)); switch (rate) { /* CCK */ case 2: return (0x0); case 4: return (0x1); case 11: return (0x2); case 22: return (0x3); /* OFDM */ case 12: return (0xb); case 18: return (0xf); case 24: return (0xa); case 36: return (0xe); case 48: return (0x9); case 72: return (0xd); case 96: return (0x8); case 108: return (0xc); default: device_printf(sc->sc_dev, "%s: unknown rate '%d'\n", __func__, (int) rate); case 0: if (is_2ghz) return (0x0); /* 1MB CCK */ else return (0xb); /* 6MB OFDM */ /* XXX TODO: HT */ } } static int otus_hw_rate_is_ofdm(struct otus_softc *sc, uint8_t hw_rate) { switch (hw_rate) { case 0x0: case 0x1: case 0x2: case 0x3: return (0); default: return (1); } } static void otus_tx_update_ratectl(struct otus_softc *sc, struct ieee80211_node *ni) { struct ieee80211_ratectl_tx_stats *txs = &sc->sc_txs; struct otus_node *on = OTUS_NODE(ni); txs->flags = IEEE80211_RATECTL_TX_STATS_NODE | IEEE80211_RATECTL_TX_STATS_RETRIES; txs->ni = ni; txs->nframes = on->tx_done; txs->nsuccess = on->tx_done - on->tx_err; txs->nretries = on->tx_retries; ieee80211_ratectl_tx_update(ni->ni_vap, txs); on->tx_done = on->tx_err = on->tx_retries = 0; } /* * XXX TODO: support tx bpf parameters for configuration! * * Relevant pieces: * * ac = params->ibp_pri & 3; * rate = params->ibp_rate0; * params->ibp_flags & IEEE80211_BPF_NOACK * params->ibp_flags & IEEE80211_BPF_RTS * params->ibp_flags & IEEE80211_BPF_CTS * tx->rts_ntries = params->ibp_try1; * tx->data_ntries = params->ibp_try0; */ static int otus_tx(struct otus_softc *sc, struct ieee80211_node *ni, struct mbuf *m, struct otus_data *data, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_frame *wh; struct ieee80211_key *k; struct ar_tx_head *head; uint32_t phyctl; uint16_t macctl, qos; uint8_t qid, rate; int hasqos, xferlen; wh = mtod(m, struct ieee80211_frame *); if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { k = ieee80211_crypto_encap(ni, m); if (k == NULL) { device_printf(sc->sc_dev, "%s: m=%p: ieee80211_crypto_encap returns NULL\n", __func__, m); return (ENOBUFS); } wh = mtod(m, struct ieee80211_frame *); } /* Calculate transfer length; ensure data buffer is large enough */ xferlen = sizeof (*head) + m->m_pkthdr.len; if (xferlen > OTUS_TXBUFSZ) { device_printf(sc->sc_dev, "%s: 802.11 TX frame is %d bytes, max %d bytes\n", __func__, xferlen, OTUS_TXBUFSZ); return (ENOBUFS); } hasqos = !! IEEE80211_QOS_HAS_SEQ(wh); if (hasqos) { uint8_t tid; qos = ((const struct ieee80211_qosframe *)wh)->i_qos[0]; tid = qos & IEEE80211_QOS_TID; qid = TID_TO_WME_AC(tid); } else { qos = 0; qid = WME_AC_BE; } /* Pickup a rate index. */ if (params != NULL) { rate = otus_rate_to_hw_rate(sc, params->ibp_rate0); } else if (IEEE80211_IS_MULTICAST(wh->i_addr1) || (wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) != IEEE80211_FC0_TYPE_DATA) { /* Get lowest rate */ rate = otus_rate_to_hw_rate(sc, 0); } else if (m->m_flags & M_EAPOL) { /* Get lowest rate */ rate = otus_rate_to_hw_rate(sc, 0); } else { (void) ieee80211_ratectl_rate(ni, NULL, 0); rate = otus_rate_to_hw_rate(sc, ni->ni_txrate); } phyctl = 0; macctl = AR_TX_MAC_BACKOFF | AR_TX_MAC_HW_DUR | AR_TX_MAC_QID(qid); /* * XXX TODO: params for NOACK, ACK, RTS, CTS, etc */ if (IEEE80211_IS_MULTICAST(wh->i_addr1) || (hasqos && ((qos & IEEE80211_QOS_ACKPOLICY) == IEEE80211_QOS_ACKPOLICY_NOACK))) macctl |= AR_TX_MAC_NOACK; if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { if (m->m_pkthdr.len + IEEE80211_CRC_LEN >= vap->iv_rtsthreshold) macctl |= AR_TX_MAC_RTS; else if (ic->ic_flags & IEEE80211_F_USEPROT) { if (ic->ic_protmode == IEEE80211_PROT_CTSONLY) macctl |= AR_TX_MAC_CTS; else if (ic->ic_protmode == IEEE80211_PROT_RTSCTS) macctl |= AR_TX_MAC_RTS; } } phyctl |= AR_TX_PHY_MCS(rate); if (otus_hw_rate_is_ofdm(sc, rate)) { phyctl |= AR_TX_PHY_MT_OFDM; /* Always use all tx antennas for now, just to be safe */ phyctl |= AR_TX_PHY_ANTMSK(sc->txmask); } else { /* CCK */ phyctl |= AR_TX_PHY_MT_CCK; phyctl |= AR_TX_PHY_ANTMSK(sc->txmask); } /* Update net80211 with the current counters */ otus_tx_update_ratectl(sc, ni); /* Update rate control stats for frames that are ACK'ed. */ if (!(macctl & AR_TX_MAC_NOACK)) OTUS_NODE(ni)->tx_done++; /* Fill Tx descriptor. */ head = (struct ar_tx_head *)data->buf; head->len = htole16(m->m_pkthdr.len + IEEE80211_CRC_LEN); head->macctl = htole16(macctl); head->phyctl = htole32(phyctl); m_copydata(m, 0, m->m_pkthdr.len, (caddr_t)&head[1]); data->buflen = xferlen; data->ni = ni; data->m = m; OTUS_DPRINTF(sc, OTUS_DEBUG_XMIT, "%s: tx: m=%p; data=%p; len=%d mac=0x%04x phy=0x%08x rate=0x%02x, ni_txrate=%d\n", __func__, m, data, le16toh(head->len), macctl, phyctl, (int) rate, (int) ni->ni_txrate); /* Submit transfer */ STAILQ_INSERT_TAIL(&sc->sc_tx_pending[OTUS_BULK_TX], data, next); usbd_transfer_start(sc->sc_xfer[OTUS_BULK_TX]); return 0; } int otus_set_multi(struct otus_softc *sc) { uint32_t lo, hi; struct ieee80211com *ic = &sc->sc_ic; int r; if (ic->ic_allmulti > 0 || ic->ic_promisc > 0 || ic->ic_opmode == IEEE80211_M_MONITOR) { lo = 0xffffffff; hi = 0xffffffff; } else { struct ieee80211vap *vap; struct ifnet *ifp; struct ifmultiaddr *ifma; lo = hi = 0; TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { ifp = vap->iv_ifp; if_maddr_rlock(ifp); TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { caddr_t dl; uint32_t val; dl = LLADDR((struct sockaddr_dl *) ifma->ifma_addr); val = le32dec(dl + 4); /* Get address byte 5 */ val = val & 0x0000ff00; val = val >> 8; /* As per below, shift it >> 2 to get only 6 bits */ val = val >> 2; if (val < 32) lo |= 1 << val; else hi |= 1 << (val - 32); } if_maddr_runlock(ifp); } } #if 0 /* XXX openbsd code */ while (enm != NULL) { bit = enm->enm_addrlo[5] >> 2; if (bit < 32) lo |= 1 << bit; else hi |= 1 << (bit - 32); ETHER_NEXT_MULTI(step, enm); } #endif hi |= 1U << 31; /* Make sure the broadcast bit is set. */ OTUS_LOCK(sc); otus_write(sc, AR_MAC_REG_GROUP_HASH_TBL_L, lo); otus_write(sc, AR_MAC_REG_GROUP_HASH_TBL_H, hi); r = otus_write_barrier(sc); /* XXX operating mode? filter? */ OTUS_UNLOCK(sc); return (r); } static int otus_updateedca(struct ieee80211com *ic) { struct otus_softc *sc = ic->ic_softc; OTUS_LOCK(sc); /* * XXX TODO: take temporary copy of EDCA information * when scheduling this so we have a more time-correct view * of things. * XXX TODO: this can be done on the net80211 level */ otus_updateedca_locked(sc); OTUS_UNLOCK(sc); return (0); } static void otus_updateedca_locked(struct otus_softc *sc) { #define EXP2(val) ((1 << (val)) - 1) #define AIFS(val) ((val) * 9 + 10) struct ieee80211com *ic = &sc->sc_ic; const struct wmeParams *edca; OTUS_LOCK_ASSERT(sc); edca = ic->ic_wme.wme_chanParams.cap_wmeParams; /* Set CWmin/CWmax values. */ otus_write(sc, AR_MAC_REG_AC0_CW, EXP2(edca[WME_AC_BE].wmep_logcwmax) << 16 | EXP2(edca[WME_AC_BE].wmep_logcwmin)); otus_write(sc, AR_MAC_REG_AC1_CW, EXP2(edca[WME_AC_BK].wmep_logcwmax) << 16 | EXP2(edca[WME_AC_BK].wmep_logcwmin)); otus_write(sc, AR_MAC_REG_AC2_CW, EXP2(edca[WME_AC_VI].wmep_logcwmax) << 16 | EXP2(edca[WME_AC_VI].wmep_logcwmin)); otus_write(sc, AR_MAC_REG_AC3_CW, EXP2(edca[WME_AC_VO].wmep_logcwmax) << 16 | EXP2(edca[WME_AC_VO].wmep_logcwmin)); otus_write(sc, AR_MAC_REG_AC4_CW, /* Special TXQ. */ EXP2(edca[WME_AC_VO].wmep_logcwmax) << 16 | EXP2(edca[WME_AC_VO].wmep_logcwmin)); /* Set AIFSN values. */ otus_write(sc, AR_MAC_REG_AC1_AC0_AIFS, AIFS(edca[WME_AC_VI].wmep_aifsn) << 24 | AIFS(edca[WME_AC_BK].wmep_aifsn) << 12 | AIFS(edca[WME_AC_BE].wmep_aifsn)); otus_write(sc, AR_MAC_REG_AC3_AC2_AIFS, AIFS(edca[WME_AC_VO].wmep_aifsn) << 16 | /* Special TXQ. */ AIFS(edca[WME_AC_VO].wmep_aifsn) << 4 | AIFS(edca[WME_AC_VI].wmep_aifsn) >> 8); /* Set TXOP limit. */ otus_write(sc, AR_MAC_REG_AC1_AC0_TXOP, edca[WME_AC_BK].wmep_txopLimit << 16 | edca[WME_AC_BE].wmep_txopLimit); otus_write(sc, AR_MAC_REG_AC3_AC2_TXOP, edca[WME_AC_VO].wmep_txopLimit << 16 | edca[WME_AC_VI].wmep_txopLimit); /* XXX ACK policy? */ (void)otus_write_barrier(sc); #undef AIFS #undef EXP2 } static void otus_updateslot(struct otus_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; uint32_t slottime; OTUS_LOCK_ASSERT(sc); slottime = IEEE80211_GET_SLOTTIME(ic); otus_write(sc, AR_MAC_REG_SLOT_TIME, slottime << 10); (void)otus_write_barrier(sc); } int otus_init_mac(struct otus_softc *sc) { int error; OTUS_LOCK_ASSERT(sc); otus_write(sc, AR_MAC_REG_ACK_EXTENSION, 0x40); otus_write(sc, AR_MAC_REG_RETRY_MAX, 0); otus_write(sc, AR_MAC_REG_RX_THRESHOLD, 0xc1f80); otus_write(sc, AR_MAC_REG_RX_PE_DELAY, 0x70); otus_write(sc, AR_MAC_REG_EIFS_AND_SIFS, 0xa144000); otus_write(sc, AR_MAC_REG_SLOT_TIME, 9 << 10); otus_write(sc, AR_MAC_REG_TID_CFACK_CFEND_RATE, 0x19000000); /* NAV protects ACK only (in TXOP). */ otus_write(sc, AR_MAC_REG_TXOP_DURATION, 0x201); /* Set beacon Tx power to 0x7. */ otus_write(sc, AR_MAC_REG_BCN_HT1, 0x8000170); otus_write(sc, AR_MAC_REG_BACKOFF_PROTECT, 0x105); otus_write(sc, AR_MAC_REG_AMPDU_FACTOR, 0x10000a); otus_set_rx_filter(sc); otus_write(sc, AR_MAC_REG_BASIC_RATE, 0x150f); otus_write(sc, AR_MAC_REG_MANDATORY_RATE, 0x150f); otus_write(sc, AR_MAC_REG_RTS_CTS_RATE, 0x10b01bb); otus_write(sc, AR_MAC_REG_ACK_TPC, 0x4003c1e); /* Enable LED0 and LED1. */ otus_write(sc, AR_GPIO_REG_PORT_TYPE, 0x3); otus_write(sc, AR_GPIO_REG_PORT_DATA, 0x3); /* Switch MAC to OTUS interface. */ otus_write(sc, 0x1c3600, 0x3); otus_write(sc, AR_MAC_REG_AMPDU_RX_THRESH, 0xffff); otus_write(sc, AR_MAC_REG_MISC_680, 0xf00008); /* Disable Rx timeout (workaround). */ otus_write(sc, AR_MAC_REG_RX_TIMEOUT, 0); /* Set USB Rx stream mode maximum frame number to 2. */ otus_write(sc, 0x1e1110, 0x4); /* Set USB Rx stream mode timeout to 10us. */ otus_write(sc, 0x1e1114, 0x80); /* Set clock frequency to 88/80MHz. */ otus_write(sc, AR_PWR_REG_CLOCK_SEL, 0x73); /* Set WLAN DMA interrupt mode: generate intr per packet. */ otus_write(sc, AR_MAC_REG_TXRX_MPI, 0x110011); otus_write(sc, AR_MAC_REG_FCS_SELECT, 0x4); otus_write(sc, AR_MAC_REG_TXOP_NOT_ENOUGH_INDICATION, 0x141e0f48); /* Disable HW decryption for now. */ otus_write(sc, AR_MAC_REG_ENCRYPTION, 0x78); if ((error = otus_write_barrier(sc)) != 0) return error; /* Set default EDCA parameters. */ otus_updateedca_locked(sc); return 0; } /* * Return default value for PHY register based on current operating mode. */ uint32_t otus_phy_get_def(struct otus_softc *sc, uint32_t reg) { int i; for (i = 0; i < nitems(ar5416_phy_regs); i++) if (AR_PHY(ar5416_phy_regs[i]) == reg) return sc->phy_vals[i]; return 0; /* Register not found. */ } /* * Update PHY's programming based on vendor-specific data stored in EEPROM. * This is for FEM-type devices only. */ int otus_set_board_values(struct otus_softc *sc, struct ieee80211_channel *c) { const struct ModalEepHeader *eep; uint32_t tmp, offset; if (IEEE80211_IS_CHAN_5GHZ(c)) eep = &sc->eeprom.modalHeader[0]; else eep = &sc->eeprom.modalHeader[1]; /* Offset of chain 2. */ offset = 2 * 0x1000; tmp = le32toh(eep->antCtrlCommon); otus_write(sc, AR_PHY_SWITCH_COM, tmp); tmp = le32toh(eep->antCtrlChain[0]); otus_write(sc, AR_PHY_SWITCH_CHAIN_0, tmp); tmp = le32toh(eep->antCtrlChain[1]); otus_write(sc, AR_PHY_SWITCH_CHAIN_0 + offset, tmp); if (1 /* sc->sc_sco == AR_SCO_SCN */) { tmp = otus_phy_get_def(sc, AR_PHY_SETTLING); tmp &= ~(0x7f << 7); tmp |= (eep->switchSettling & 0x7f) << 7; otus_write(sc, AR_PHY_SETTLING, tmp); } tmp = otus_phy_get_def(sc, AR_PHY_DESIRED_SZ); tmp &= ~0xffff; tmp |= eep->pgaDesiredSize << 8 | eep->adcDesiredSize; otus_write(sc, AR_PHY_DESIRED_SZ, tmp); tmp = eep->txEndToXpaOff << 24 | eep->txEndToXpaOff << 16 | eep->txFrameToXpaOn << 8 | eep->txFrameToXpaOn; otus_write(sc, AR_PHY_RF_CTL4, tmp); tmp = otus_phy_get_def(sc, AR_PHY_RF_CTL3); tmp &= ~(0xff << 16); tmp |= eep->txEndToRxOn << 16; otus_write(sc, AR_PHY_RF_CTL3, tmp); tmp = otus_phy_get_def(sc, AR_PHY_CCA); tmp &= ~(0x7f << 12); tmp |= (eep->thresh62 & 0x7f) << 12; otus_write(sc, AR_PHY_CCA, tmp); tmp = otus_phy_get_def(sc, AR_PHY_RXGAIN); tmp &= ~(0x3f << 12); tmp |= (eep->txRxAttenCh[0] & 0x3f) << 12; otus_write(sc, AR_PHY_RXGAIN, tmp); tmp = otus_phy_get_def(sc, AR_PHY_RXGAIN + offset); tmp &= ~(0x3f << 12); tmp |= (eep->txRxAttenCh[1] & 0x3f) << 12; otus_write(sc, AR_PHY_RXGAIN + offset, tmp); tmp = otus_phy_get_def(sc, AR_PHY_GAIN_2GHZ); tmp &= ~(0x3f << 18); tmp |= (eep->rxTxMarginCh[0] & 0x3f) << 18; if (IEEE80211_IS_CHAN_5GHZ(c)) { tmp &= ~(0xf << 10); tmp |= (eep->bswMargin[0] & 0xf) << 10; } otus_write(sc, AR_PHY_GAIN_2GHZ, tmp); tmp = otus_phy_get_def(sc, AR_PHY_GAIN_2GHZ + offset); tmp &= ~(0x3f << 18); tmp |= (eep->rxTxMarginCh[1] & 0x3f) << 18; otus_write(sc, AR_PHY_GAIN_2GHZ + offset, tmp); tmp = otus_phy_get_def(sc, AR_PHY_TIMING_CTRL4); tmp &= ~(0x3f << 5 | 0x1f); tmp |= (eep->iqCalICh[0] & 0x3f) << 5 | (eep->iqCalQCh[0] & 0x1f); otus_write(sc, AR_PHY_TIMING_CTRL4, tmp); tmp = otus_phy_get_def(sc, AR_PHY_TIMING_CTRL4 + offset); tmp &= ~(0x3f << 5 | 0x1f); tmp |= (eep->iqCalICh[1] & 0x3f) << 5 | (eep->iqCalQCh[1] & 0x1f); otus_write(sc, AR_PHY_TIMING_CTRL4 + offset, tmp); tmp = otus_phy_get_def(sc, AR_PHY_TPCRG1); tmp &= ~(0xf << 16); tmp |= (eep->xpd & 0xf) << 16; otus_write(sc, AR_PHY_TPCRG1, tmp); return otus_write_barrier(sc); } int otus_program_phy(struct otus_softc *sc, struct ieee80211_channel *c) { const uint32_t *vals; int error, i; /* Select PHY programming based on band and bandwidth. */ if (IEEE80211_IS_CHAN_2GHZ(c)) vals = ar5416_phy_vals_2ghz_20mhz; else vals = ar5416_phy_vals_5ghz_20mhz; for (i = 0; i < nitems(ar5416_phy_regs); i++) otus_write(sc, AR_PHY(ar5416_phy_regs[i]), vals[i]); sc->phy_vals = vals; if (sc->eeprom.baseEepHeader.deviceType == 0x80) /* FEM */ if ((error = otus_set_board_values(sc, c)) != 0) return error; /* Initial Tx power settings. */ otus_write(sc, AR_PHY_POWER_TX_RATE_MAX, 0x7f); otus_write(sc, AR_PHY_POWER_TX_RATE1, 0x3f3f3f3f); otus_write(sc, AR_PHY_POWER_TX_RATE2, 0x3f3f3f3f); otus_write(sc, AR_PHY_POWER_TX_RATE3, 0x3f3f3f3f); otus_write(sc, AR_PHY_POWER_TX_RATE4, 0x3f3f3f3f); otus_write(sc, AR_PHY_POWER_TX_RATE5, 0x3f3f3f3f); otus_write(sc, AR_PHY_POWER_TX_RATE6, 0x3f3f3f3f); otus_write(sc, AR_PHY_POWER_TX_RATE7, 0x3f3f3f3f); otus_write(sc, AR_PHY_POWER_TX_RATE8, 0x3f3f3f3f); otus_write(sc, AR_PHY_POWER_TX_RATE9, 0x3f3f3f3f); if (IEEE80211_IS_CHAN_2GHZ(c)) otus_write(sc, AR_PWR_REG_PLL_ADDAC, 0x5163); else otus_write(sc, AR_PWR_REG_PLL_ADDAC, 0x5143); return otus_write_barrier(sc); } static __inline uint8_t otus_reverse_bits(uint8_t v) { v = ((v >> 1) & 0x55) | ((v & 0x55) << 1); v = ((v >> 2) & 0x33) | ((v & 0x33) << 2); v = ((v >> 4) & 0x0f) | ((v & 0x0f) << 4); return v; } int otus_set_rf_bank4(struct otus_softc *sc, struct ieee80211_channel *c) { uint8_t chansel, d0, d1; uint16_t data; int error; OTUS_LOCK_ASSERT(sc); d0 = 0; if (IEEE80211_IS_CHAN_5GHZ(c)) { chansel = (c->ic_freq - 4800) / 5; if (chansel & 1) d0 |= AR_BANK4_AMODE_REFSEL(2); else d0 |= AR_BANK4_AMODE_REFSEL(1); } else { d0 |= AR_BANK4_AMODE_REFSEL(2); if (c->ic_freq == 2484) { /* CH 14 */ d0 |= AR_BANK4_BMODE_LF_SYNTH_FREQ; chansel = 10 + (c->ic_freq - 2274) / 5; } else chansel = 16 + (c->ic_freq - 2272) / 5; chansel <<= 2; } d0 |= AR_BANK4_ADDR(1) | AR_BANK4_CHUP; d1 = otus_reverse_bits(chansel); /* Write bits 0-4 of d0 and d1. */ data = (d1 & 0x1f) << 5 | (d0 & 0x1f); otus_write(sc, AR_PHY(44), data); /* Write bits 5-7 of d0 and d1. */ data = (d1 >> 5) << 5 | (d0 >> 5); otus_write(sc, AR_PHY(58), data); if ((error = otus_write_barrier(sc)) == 0) otus_delay_ms(sc, 10); return error; } void otus_get_delta_slope(uint32_t coeff, uint32_t *exponent, uint32_t *mantissa) { #define COEFF_SCALE_SHIFT 24 uint32_t exp, man; /* exponent = 14 - floor(log2(coeff)) */ for (exp = 31; exp > 0; exp--) if (coeff & (1 << exp)) break; KASSERT(exp != 0, ("exp")); exp = 14 - (exp - COEFF_SCALE_SHIFT); /* mantissa = floor(coeff * 2^exponent + 0.5) */ man = coeff + (1 << (COEFF_SCALE_SHIFT - exp - 1)); *mantissa = man >> (COEFF_SCALE_SHIFT - exp); *exponent = exp - 16; #undef COEFF_SCALE_SHIFT } static int otus_set_chan(struct otus_softc *sc, struct ieee80211_channel *c, int assoc) { struct ieee80211com *ic = &sc->sc_ic; struct ar_cmd_frequency cmd; struct ar_rsp_frequency rsp; const uint32_t *vals; uint32_t coeff, exp, man, tmp; uint8_t code; int error, chan, i; error = 0; chan = ieee80211_chan2ieee(ic, c); OTUS_DPRINTF(sc, OTUS_DEBUG_RESET, "setting channel %d (%dMHz)\n", chan, c->ic_freq); tmp = IEEE80211_IS_CHAN_2GHZ(c) ? 0x105 : 0x104; otus_write(sc, AR_MAC_REG_DYNAMIC_SIFS_ACK, tmp); if ((error = otus_write_barrier(sc)) != 0) goto finish; /* Disable BB Heavy Clip. */ otus_write(sc, AR_PHY_HEAVY_CLIP_ENABLE, 0x200); if ((error = otus_write_barrier(sc)) != 0) goto finish; /* XXX Is that FREQ_START ? */ error = otus_cmd(sc, AR_CMD_FREQ_STRAT, NULL, 0, NULL, 0); if (error != 0) goto finish; /* Reprogram PHY and RF on channel band or bandwidth changes. */ if (sc->bb_reset || c->ic_flags != sc->sc_curchan->ic_flags) { OTUS_DPRINTF(sc, OTUS_DEBUG_RESET, "band switch\n"); /* Cold/Warm reset BB/ADDA. */ otus_write(sc, AR_PWR_REG_RESET, sc->bb_reset ? 0x800 : 0x400); if ((error = otus_write_barrier(sc)) != 0) goto finish; otus_write(sc, AR_PWR_REG_RESET, 0); if ((error = otus_write_barrier(sc)) != 0) goto finish; sc->bb_reset = 0; if ((error = otus_program_phy(sc, c)) != 0) { device_printf(sc->sc_dev, "%s: could not program PHY\n", __func__); goto finish; } /* Select RF programming based on band. */ if (IEEE80211_IS_CHAN_5GHZ(c)) vals = ar5416_banks_vals_5ghz; else vals = ar5416_banks_vals_2ghz; for (i = 0; i < nitems(ar5416_banks_regs); i++) otus_write(sc, AR_PHY(ar5416_banks_regs[i]), vals[i]); if ((error = otus_write_barrier(sc)) != 0) { device_printf(sc->sc_dev, "%s: could not program RF\n", __func__); goto finish; } code = AR_CMD_RF_INIT; } else { code = AR_CMD_FREQUENCY; } if ((error = otus_set_rf_bank4(sc, c)) != 0) goto finish; tmp = (sc->txmask == 0x5) ? 0x340 : 0x240; otus_write(sc, AR_PHY_TURBO, tmp); if ((error = otus_write_barrier(sc)) != 0) goto finish; /* Send firmware command to set channel. */ cmd.freq = htole32((uint32_t)c->ic_freq * 1000); cmd.dynht2040 = htole32(0); cmd.htena = htole32(1); /* Set Delta Slope (exponent and mantissa). */ coeff = (100 << 24) / c->ic_freq; otus_get_delta_slope(coeff, &exp, &man); cmd.dsc_exp = htole32(exp); cmd.dsc_man = htole32(man); OTUS_DPRINTF(sc, OTUS_DEBUG_RESET, "ds coeff=%u exp=%u man=%u\n", coeff, exp, man); /* For Short GI, coeff is 9/10 that of normal coeff. */ coeff = (9 * coeff) / 10; otus_get_delta_slope(coeff, &exp, &man); cmd.dsc_shgi_exp = htole32(exp); cmd.dsc_shgi_man = htole32(man); OTUS_DPRINTF(sc, OTUS_DEBUG_RESET, "ds shgi coeff=%u exp=%u man=%u\n", coeff, exp, man); /* Set wait time for AGC and noise calibration (100 or 200ms). */ cmd.check_loop_count = assoc ? htole32(2000) : htole32(1000); OTUS_DPRINTF(sc, OTUS_DEBUG_RESET, "%s\n", (code == AR_CMD_RF_INIT) ? "RF_INIT" : "FREQUENCY"); error = otus_cmd(sc, code, &cmd, sizeof cmd, &rsp, sizeof(rsp)); if (error != 0) goto finish; if ((rsp.status & htole32(AR_CAL_ERR_AGC | AR_CAL_ERR_NF_VAL)) != 0) { OTUS_DPRINTF(sc, OTUS_DEBUG_RESET, "status=0x%x\n", le32toh(rsp.status)); /* Force cold reset on next channel. */ sc->bb_reset = 1; } #ifdef USB_DEBUG if (otus_debug & OTUS_DEBUG_RESET) { device_printf(sc->sc_dev, "calibration status=0x%x\n", le32toh(rsp.status)); for (i = 0; i < 2; i++) { /* 2 Rx chains */ /* Sign-extend 9-bit NF values. */ device_printf(sc->sc_dev, "noisefloor chain %d=%d\n", i, (((int32_t)le32toh(rsp.nf[i])) << 4) >> 23); device_printf(sc->sc_dev, "noisefloor ext chain %d=%d\n", i, ((int32_t)le32toh(rsp.nf_ext[i])) >> 23); } } #endif for (i = 0; i < OTUS_NUM_CHAINS; i++) { sc->sc_nf[i] = ((((int32_t)le32toh(rsp.nf[i])) << 4) >> 23); } sc->sc_curchan = c; finish: return (error); } #ifdef notyet int otus_set_key(struct ieee80211com *ic, struct ieee80211_node *ni, struct ieee80211_key *k) { struct otus_softc *sc = ic->ic_softc; struct otus_cmd_key cmd; /* Defer setting of WEP keys until interface is brought up. */ if ((ic->ic_if.if_flags & (IFF_UP | IFF_RUNNING)) != (IFF_UP | IFF_RUNNING)) return 0; /* Do it in a process context. */ cmd.key = *k; cmd.associd = (ni != NULL) ? ni->ni_associd : 0; otus_do_async(sc, otus_set_key_cb, &cmd, sizeof cmd); return 0; } void otus_set_key_cb(struct otus_softc *sc, void *arg) { struct otus_cmd_key *cmd = arg; struct ieee80211_key *k = &cmd->key; struct ar_cmd_ekey key; uint16_t cipher; int error; memset(&key, 0, sizeof key); if (k->k_flags & IEEE80211_KEY_GROUP) { key.uid = htole16(k->k_id); IEEE80211_ADDR_COPY(key.macaddr, sc->sc_ic.ic_myaddr); key.macaddr[0] |= 0x80; } else { key.uid = htole16(OTUS_UID(cmd->associd)); IEEE80211_ADDR_COPY(key.macaddr, ni->ni_macaddr); } key.kix = htole16(0); /* Map net80211 cipher to hardware. */ switch (k->k_cipher) { case IEEE80211_CIPHER_WEP40: cipher = AR_CIPHER_WEP64; break; case IEEE80211_CIPHER_WEP104: cipher = AR_CIPHER_WEP128; break; case IEEE80211_CIPHER_TKIP: cipher = AR_CIPHER_TKIP; break; case IEEE80211_CIPHER_CCMP: cipher = AR_CIPHER_AES; break; default: return; } key.cipher = htole16(cipher); memcpy(key.key, k->k_key, MIN(k->k_len, 16)); error = otus_cmd(sc, AR_CMD_EKEY, &key, sizeof key, NULL, 0); if (error != 0 || k->k_cipher != IEEE80211_CIPHER_TKIP) return; /* TKIP: set Tx/Rx MIC Key. */ key.kix = htole16(1); memcpy(key.key, k->k_key + 16, 16); (void)otus_cmd(sc, AR_CMD_EKEY, &key, sizeof key, NULL, 0); } void otus_delete_key(struct ieee80211com *ic, struct ieee80211_node *ni, struct ieee80211_key *k) { struct otus_softc *sc = ic->ic_softc; struct otus_cmd_key cmd; if (!(ic->ic_if.if_flags & IFF_RUNNING) || ic->ic_state != IEEE80211_S_RUN) return; /* Nothing to do. */ /* Do it in a process context. */ cmd.key = *k; cmd.associd = (ni != NULL) ? ni->ni_associd : 0; otus_do_async(sc, otus_delete_key_cb, &cmd, sizeof cmd); } void otus_delete_key_cb(struct otus_softc *sc, void *arg) { struct otus_cmd_key *cmd = arg; struct ieee80211_key *k = &cmd->key; uint32_t uid; if (k->k_flags & IEEE80211_KEY_GROUP) uid = htole32(k->k_id); else uid = htole32(OTUS_UID(cmd->associd)); (void)otus_cmd(sc, AR_CMD_DKEY, &uid, sizeof uid, NULL, 0); } #endif /* * XXX TODO: check if we have to be doing any calibration in the host * or whether it's purely a firmware thing. */ void otus_calibrate_to(void *arg, int pending) { #if 0 struct otus_softc *sc = arg; device_printf(sc->sc_dev, "%s: called\n", __func__); struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_node *ni; int s; if (usbd_is_dying(sc->sc_udev)) return; usbd_ref_incr(sc->sc_udev); s = splnet(); ni = ic->ic_bss; ieee80211_amrr_choose(&sc->amrr, ni, &((struct otus_node *)ni)->amn); splx(s); if (!usbd_is_dying(sc->sc_udev)) timeout_add_sec(&sc->calib_to, 1); usbd_ref_decr(sc->sc_udev); #endif } int otus_set_bssid(struct otus_softc *sc, const uint8_t *bssid) { OTUS_LOCK_ASSERT(sc); otus_write(sc, AR_MAC_REG_BSSID_L, bssid[0] | bssid[1] << 8 | bssid[2] << 16 | bssid[3] << 24); otus_write(sc, AR_MAC_REG_BSSID_H, bssid[4] | bssid[5] << 8); return otus_write_barrier(sc); } int otus_set_macaddr(struct otus_softc *sc, const uint8_t *addr) { OTUS_LOCK_ASSERT(sc); otus_write(sc, AR_MAC_REG_MAC_ADDR_L, addr[0] | addr[1] << 8 | addr[2] << 16 | addr[3] << 24); otus_write(sc, AR_MAC_REG_MAC_ADDR_H, addr[4] | addr[5] << 8); return otus_write_barrier(sc); } /* Default single-LED. */ void otus_led_newstate_type1(struct otus_softc *sc) { /* TBD */ device_printf(sc->sc_dev, "%s: TODO\n", __func__); } /* NETGEAR, dual-LED. */ void otus_led_newstate_type2(struct otus_softc *sc) { /* TBD */ device_printf(sc->sc_dev, "%s: TODO\n", __func__); } /* NETGEAR, single-LED/3 colors (blue, red, purple.) */ void otus_led_newstate_type3(struct otus_softc *sc) { #if 0 struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); uint32_t state = sc->led_state; OTUS_LOCK_ASSERT(sc); if (!vap) { state = 0; /* led off */ } else if (vap->iv_state == IEEE80211_S_INIT) { state = 0; /* LED off. */ } else if (vap->iv_state == IEEE80211_S_RUN) { /* Associated, LED always on. */ if (IEEE80211_IS_CHAN_2GHZ(sc->sc_curchan)) state = AR_LED0_ON; /* 2GHz=>Red. */ else state = AR_LED1_ON; /* 5GHz=>Blue. */ } else { /* Scanning, blink LED. */ state ^= AR_LED0_ON | AR_LED1_ON; if (IEEE80211_IS_CHAN_2GHZ(sc->sc_curchan)) state &= ~AR_LED1_ON; else state &= ~AR_LED0_ON; } if (state != sc->led_state) { otus_write(sc, AR_GPIO_REG_PORT_DATA, state); if (otus_write_barrier(sc) == 0) sc->led_state = state; } #endif } static uint8_t zero_macaddr[IEEE80211_ADDR_LEN] = { 0,0,0,0,0,0 }; /* * Set up operating mode, MAC/BSS address and RX filter. */ static void otus_set_operating_mode(struct otus_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap; uint32_t cam_mode = AR_MAC_CAM_DEFAULTS; uint32_t rx_ctrl = AR_MAC_RX_CTRL_DEAGG | AR_MAC_RX_CTRL_SHORT_FILTER; uint32_t sniffer = AR_MAC_SNIFFER_DEFAULTS; uint32_t enc_mode = 0x78; /* XXX */ const uint8_t *macaddr; uint8_t bssid[IEEE80211_ADDR_LEN]; struct ieee80211_node *ni; OTUS_LOCK_ASSERT(sc); /* * If we're in sniffer mode or we don't have a MAC * address assigned, ensure it gets reset to all-zero. */ IEEE80211_ADDR_COPY(bssid, zero_macaddr); vap = TAILQ_FIRST(&ic->ic_vaps); macaddr = ic->ic_macaddr; switch (ic->ic_opmode) { case IEEE80211_M_STA: if (vap) { ni = ieee80211_ref_node(vap->iv_bss); IEEE80211_ADDR_COPY(bssid, ni->ni_bssid); ieee80211_free_node(ni); } cam_mode |= AR_MAC_CAM_STA; rx_ctrl |= AR_MAC_RX_CTRL_PASS_TO_HOST; break; case IEEE80211_M_MONITOR: /* * Note: monitor mode ends up causing the MAC to * generate ACK frames for everything it sees. * So don't do that; instead just put it in STA mode * and disable RX filters. */ default: cam_mode |= AR_MAC_CAM_STA; rx_ctrl |= AR_MAC_RX_CTRL_PASS_TO_HOST; break; } /* * TODO: if/when we do hardware encryption, ensure it's * disabled if the NIC is in monitor mode. */ otus_write(sc, AR_MAC_REG_SNIFFER, sniffer); otus_write(sc, AR_MAC_REG_CAM_MODE, cam_mode); otus_write(sc, AR_MAC_REG_ENCRYPTION, enc_mode); otus_write(sc, AR_MAC_REG_RX_CONTROL, rx_ctrl); otus_set_macaddr(sc, macaddr); otus_set_bssid(sc, bssid); /* XXX barrier? */ } static void otus_set_rx_filter(struct otus_softc *sc) { // struct ieee80211com *ic = &sc->sc_ic; OTUS_LOCK_ASSERT(sc); #if 0 if (ic->ic_allmulti > 0 || ic->ic_promisc > 0 || ic->ic_opmode == IEEE80211_M_MONITOR) { otus_write(sc, AR_MAC_REG_FRAMETYPE_FILTER, 0xff00ffff); } else { #endif /* Filter any control frames, BAR is bit 24. */ otus_write(sc, AR_MAC_REG_FRAMETYPE_FILTER, 0x0500ffff); #if 0 } #endif } int otus_init(struct otus_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; int error; OTUS_UNLOCK_ASSERT(sc); OTUS_LOCK(sc); /* Drain any pending TX frames */ otus_drain_mbufq(sc); /* Init MAC */ if ((error = otus_init_mac(sc)) != 0) { OTUS_UNLOCK(sc); device_printf(sc->sc_dev, "%s: could not initialize MAC\n", __func__); return error; } otus_set_operating_mode(sc); otus_set_rx_filter(sc); (void) otus_set_operating_mode(sc); sc->bb_reset = 1; /* Force cold reset. */ if ((error = otus_set_chan(sc, ic->ic_curchan, 0)) != 0) { OTUS_UNLOCK(sc); device_printf(sc->sc_dev, "%s: could not set channel\n", __func__); return error; } /* Start Rx. */ otus_write(sc, AR_MAC_REG_DMA_TRIGGER, 0x100); (void)otus_write_barrier(sc); sc->sc_running = 1; OTUS_UNLOCK(sc); return 0; } void otus_stop(struct otus_softc *sc) { #if 0 int s; #endif OTUS_UNLOCK_ASSERT(sc); OTUS_LOCK(sc); sc->sc_running = 0; sc->sc_tx_timer = 0; OTUS_UNLOCK(sc); taskqueue_drain_timeout(taskqueue_thread, &sc->scan_to); taskqueue_drain_timeout(taskqueue_thread, &sc->calib_to); taskqueue_drain(taskqueue_thread, &sc->tx_task); OTUS_LOCK(sc); sc->sc_running = 0; /* Stop Rx. */ otus_write(sc, AR_MAC_REG_DMA_TRIGGER, 0); (void)otus_write_barrier(sc); /* Drain any pending TX frames */ otus_drain_mbufq(sc); OTUS_UNLOCK(sc); } Index: head/sys/dev/usb/wlan/if_rsu.c =================================================================== --- head/sys/dev/usb/wlan/if_rsu.c (revision 307157) +++ head/sys/dev/usb/wlan/if_rsu.c (revision 307158) @@ -1,2966 +1,2968 @@ /* $OpenBSD: if_rsu.c,v 1.17 2013/04/15 09:23:01 mglocker Exp $ */ /*- * Copyright (c) 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$"); /* * Driver for Realtek RTL8188SU/RTL8191SU/RTL8192SU. * * TODO: * o h/w crypto * o hostap / ibss / mesh * o sensible RSSI levels * o power-save operation */ #include "opt_wlan.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 "usbdevs.h" #define USB_DEBUG_VAR rsu_debug #include #include #ifdef USB_DEBUG static int rsu_debug = 0; SYSCTL_NODE(_hw_usb, OID_AUTO, rsu, CTLFLAG_RW, 0, "USB rsu"); SYSCTL_INT(_hw_usb_rsu, OID_AUTO, debug, CTLFLAG_RWTUN, &rsu_debug, 0, "Debug level"); #define RSU_DPRINTF(_sc, _flg, ...) \ do \ if (((_flg) == (RSU_DEBUG_ANY)) || (rsu_debug & (_flg))) \ device_printf((_sc)->sc_dev, __VA_ARGS__); \ while (0) #else #define RSU_DPRINTF(_sc, _flg, ...) #endif static int rsu_enable_11n = 1; TUNABLE_INT("hw.usb.rsu.enable_11n", &rsu_enable_11n); #define RSU_DEBUG_ANY 0xffffffff #define RSU_DEBUG_TX 0x00000001 #define RSU_DEBUG_RX 0x00000002 #define RSU_DEBUG_RESET 0x00000004 #define RSU_DEBUG_CALIB 0x00000008 #define RSU_DEBUG_STATE 0x00000010 #define RSU_DEBUG_SCAN 0x00000020 #define RSU_DEBUG_FWCMD 0x00000040 #define RSU_DEBUG_TXDONE 0x00000080 #define RSU_DEBUG_FW 0x00000100 #define RSU_DEBUG_FWDBG 0x00000200 #define RSU_DEBUG_AMPDU 0x00000400 static const STRUCT_USB_HOST_ID rsu_devs[] = { #define RSU_HT_NOT_SUPPORTED 0 #define RSU_HT_SUPPORTED 1 #define RSU_DEV_HT(v,p) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, \ RSU_HT_SUPPORTED) } #define RSU_DEV(v,p) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, \ RSU_HT_NOT_SUPPORTED) } RSU_DEV(ASUS, RTL8192SU), RSU_DEV(AZUREWAVE, RTL8192SU_4), RSU_DEV_HT(ACCTON, RTL8192SU), RSU_DEV_HT(ASUS, USBN10), RSU_DEV_HT(AZUREWAVE, RTL8192SU_1), RSU_DEV_HT(AZUREWAVE, RTL8192SU_2), RSU_DEV_HT(AZUREWAVE, RTL8192SU_3), RSU_DEV_HT(AZUREWAVE, RTL8192SU_5), RSU_DEV_HT(BELKIN, RTL8192SU_1), RSU_DEV_HT(BELKIN, RTL8192SU_2), RSU_DEV_HT(BELKIN, RTL8192SU_3), RSU_DEV_HT(CONCEPTRONIC2, RTL8192SU_1), RSU_DEV_HT(CONCEPTRONIC2, RTL8192SU_2), RSU_DEV_HT(CONCEPTRONIC2, RTL8192SU_3), RSU_DEV_HT(COREGA, RTL8192SU), RSU_DEV_HT(DLINK2, DWA131A1), RSU_DEV_HT(DLINK2, RTL8192SU_1), RSU_DEV_HT(DLINK2, RTL8192SU_2), RSU_DEV_HT(EDIMAX, RTL8192SU_1), RSU_DEV_HT(EDIMAX, RTL8192SU_2), RSU_DEV_HT(EDIMAX, EW7622UMN), RSU_DEV_HT(GUILLEMOT, HWGUN54), RSU_DEV_HT(GUILLEMOT, HWNUM300), RSU_DEV_HT(HAWKING, RTL8192SU_1), RSU_DEV_HT(HAWKING, RTL8192SU_2), RSU_DEV_HT(PLANEX2, GWUSNANO), RSU_DEV_HT(REALTEK, RTL8171), RSU_DEV_HT(REALTEK, RTL8172), RSU_DEV_HT(REALTEK, RTL8173), RSU_DEV_HT(REALTEK, RTL8174), RSU_DEV_HT(REALTEK, RTL8192SU), RSU_DEV_HT(REALTEK, RTL8712), RSU_DEV_HT(REALTEK, RTL8713), RSU_DEV_HT(SENAO, RTL8192SU_1), RSU_DEV_HT(SENAO, RTL8192SU_2), RSU_DEV_HT(SITECOMEU, WL349V1), RSU_DEV_HT(SITECOMEU, WL353), RSU_DEV_HT(SWEEX2, LW154), RSU_DEV_HT(TRENDNET, TEW646UBH), #undef RSU_DEV_HT #undef RSU_DEV }; static device_probe_t rsu_match; static device_attach_t rsu_attach; static device_detach_t rsu_detach; static usb_callback_t rsu_bulk_tx_callback_be_bk; static usb_callback_t rsu_bulk_tx_callback_vi_vo; static usb_callback_t rsu_bulk_tx_callback_h2c; static usb_callback_t rsu_bulk_rx_callback; static usb_error_t rsu_do_request(struct rsu_softc *, struct usb_device_request *, void *); static struct ieee80211vap * rsu_vap_create(struct ieee80211com *, const char name[], int, enum ieee80211_opmode, int, const uint8_t bssid[], const uint8_t mac[]); static void rsu_vap_delete(struct ieee80211vap *); static void rsu_scan_start(struct ieee80211com *); static void rsu_scan_end(struct ieee80211com *); static void rsu_getradiocaps(struct ieee80211com *, int, int *, struct ieee80211_channel[]); static void rsu_set_channel(struct ieee80211com *); static void rsu_update_mcast(struct ieee80211com *); static int rsu_alloc_rx_list(struct rsu_softc *); static void rsu_free_rx_list(struct rsu_softc *); static int rsu_alloc_tx_list(struct rsu_softc *); static void rsu_free_tx_list(struct rsu_softc *); static void rsu_free_list(struct rsu_softc *, struct rsu_data [], int); static struct rsu_data *_rsu_getbuf(struct rsu_softc *); static struct rsu_data *rsu_getbuf(struct rsu_softc *); static void rsu_freebuf(struct rsu_softc *, struct rsu_data *); static int rsu_write_region_1(struct rsu_softc *, uint16_t, uint8_t *, int); static void rsu_write_1(struct rsu_softc *, uint16_t, uint8_t); static void rsu_write_2(struct rsu_softc *, uint16_t, uint16_t); static void rsu_write_4(struct rsu_softc *, uint16_t, uint32_t); static int rsu_read_region_1(struct rsu_softc *, uint16_t, uint8_t *, int); static uint8_t rsu_read_1(struct rsu_softc *, uint16_t); static uint16_t rsu_read_2(struct rsu_softc *, uint16_t); static uint32_t rsu_read_4(struct rsu_softc *, uint16_t); static int rsu_fw_iocmd(struct rsu_softc *, uint32_t); static uint8_t rsu_efuse_read_1(struct rsu_softc *, uint16_t); static int rsu_read_rom(struct rsu_softc *); static int rsu_fw_cmd(struct rsu_softc *, uint8_t, void *, int); static void rsu_calib_task(void *, int); static void rsu_tx_task(void *, int); static int rsu_newstate(struct ieee80211vap *, enum ieee80211_state, int); #ifdef notyet static void rsu_set_key(struct rsu_softc *, const struct ieee80211_key *); static void rsu_delete_key(struct rsu_softc *, const struct ieee80211_key *); #endif static int rsu_site_survey(struct rsu_softc *, struct ieee80211vap *); static int rsu_join_bss(struct rsu_softc *, struct ieee80211_node *); static int rsu_disconnect(struct rsu_softc *); static int rsu_hwrssi_to_rssi(struct rsu_softc *, int hw_rssi); static void rsu_event_survey(struct rsu_softc *, uint8_t *, int); static void rsu_event_join_bss(struct rsu_softc *, uint8_t *, int); static void rsu_rx_event(struct rsu_softc *, uint8_t, uint8_t *, int); static void rsu_rx_multi_event(struct rsu_softc *, uint8_t *, int); #if 0 static int8_t rsu_get_rssi(struct rsu_softc *, int, void *); #endif static struct mbuf * rsu_rx_frame(struct rsu_softc *, uint8_t *, int); static struct mbuf * rsu_rx_multi_frame(struct rsu_softc *, uint8_t *, int); static struct mbuf * rsu_rxeof(struct usb_xfer *, struct rsu_data *); static void rsu_txeof(struct usb_xfer *, struct rsu_data *); static int rsu_raw_xmit(struct ieee80211_node *, struct mbuf *, const struct ieee80211_bpf_params *); static void rsu_init(struct rsu_softc *); static int rsu_tx_start(struct rsu_softc *, struct ieee80211_node *, struct mbuf *, struct rsu_data *); static int rsu_transmit(struct ieee80211com *, struct mbuf *); static void rsu_start(struct rsu_softc *); static void _rsu_start(struct rsu_softc *); static void rsu_parent(struct ieee80211com *); static void rsu_stop(struct rsu_softc *); static void rsu_ms_delay(struct rsu_softc *, int); static device_method_t rsu_methods[] = { DEVMETHOD(device_probe, rsu_match), DEVMETHOD(device_attach, rsu_attach), DEVMETHOD(device_detach, rsu_detach), DEVMETHOD_END }; static driver_t rsu_driver = { .name = "rsu", .methods = rsu_methods, .size = sizeof(struct rsu_softc) }; static devclass_t rsu_devclass; DRIVER_MODULE(rsu, uhub, rsu_driver, rsu_devclass, NULL, 0); MODULE_DEPEND(rsu, wlan, 1, 1, 1); MODULE_DEPEND(rsu, usb, 1, 1, 1); MODULE_DEPEND(rsu, firmware, 1, 1, 1); MODULE_VERSION(rsu, 1); USB_PNP_HOST_INFO(rsu_devs); static const uint8_t rsu_chan_2ghz[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 }; static uint8_t rsu_wme_ac_xfer_map[4] = { [WME_AC_BE] = RSU_BULK_TX_BE_BK, [WME_AC_BK] = RSU_BULK_TX_BE_BK, [WME_AC_VI] = RSU_BULK_TX_VI_VO, [WME_AC_VO] = RSU_BULK_TX_VI_VO, }; /* XXX hard-coded */ #define RSU_H2C_ENDPOINT 3 static const struct usb_config rsu_config[RSU_N_TRANSFER] = { [RSU_BULK_RX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = RSU_RXBUFSZ, .flags = { .pipe_bof = 1, .short_xfer_ok = 1 }, .callback = rsu_bulk_rx_callback }, [RSU_BULK_TX_BE_BK] = { .type = UE_BULK, .endpoint = 0x06, .direction = UE_DIR_OUT, .bufsize = RSU_TXBUFSZ, .flags = { .ext_buffer = 1, .pipe_bof = 1, .force_short_xfer = 1 }, .callback = rsu_bulk_tx_callback_be_bk, .timeout = RSU_TX_TIMEOUT }, [RSU_BULK_TX_VI_VO] = { .type = UE_BULK, .endpoint = 0x04, .direction = UE_DIR_OUT, .bufsize = RSU_TXBUFSZ, .flags = { .ext_buffer = 1, .pipe_bof = 1, .force_short_xfer = 1 }, .callback = rsu_bulk_tx_callback_vi_vo, .timeout = RSU_TX_TIMEOUT }, [RSU_BULK_TX_H2C] = { .type = UE_BULK, .endpoint = 0x0d, .direction = UE_DIR_OUT, .bufsize = RSU_TXBUFSZ, .flags = { .ext_buffer = 1, .pipe_bof = 1, .short_xfer_ok = 1 }, .callback = rsu_bulk_tx_callback_h2c, .timeout = RSU_TX_TIMEOUT }, }; static int rsu_match(device_t self) { struct usb_attach_arg *uaa = device_get_ivars(self); if (uaa->usb_mode != USB_MODE_HOST || uaa->info.bIfaceIndex != 0 || uaa->info.bConfigIndex != 0) return (ENXIO); return (usbd_lookup_id_by_uaa(rsu_devs, sizeof(rsu_devs), uaa)); } static int rsu_send_mgmt(struct ieee80211_node *ni, int type, int arg) { return (ENOTSUP); } static void rsu_update_chw(struct ieee80211com *ic) { } /* * notification from net80211 that it'd like to do A-MPDU on the given TID. * * Note: this actually hangs traffic at the present moment, so don't use it. * The firmware debug does indiciate it's sending and establishing a TX AMPDU * session, but then no traffic flows. */ static int rsu_ampdu_enable(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap) { #if 0 struct rsu_softc *sc = ni->ni_ic->ic_softc; struct r92s_add_ba_req req; /* Don't enable if it's requested or running */ if (IEEE80211_AMPDU_REQUESTED(tap)) return (0); if (IEEE80211_AMPDU_RUNNING(tap)) return (0); /* We've decided to send addba; so send it */ req.tid = htole32(tap->txa_tid); /* Attempt net80211 state */ if (ieee80211_ampdu_tx_request_ext(ni, tap->txa_tid) != 1) return (0); /* Send the firmware command */ RSU_DPRINTF(sc, RSU_DEBUG_AMPDU, "%s: establishing AMPDU TX for TID %d\n", __func__, tap->txa_tid); RSU_LOCK(sc); if (rsu_fw_cmd(sc, R92S_CMD_ADDBA_REQ, &req, sizeof(req)) != 1) { RSU_UNLOCK(sc); /* Mark failure */ (void) ieee80211_ampdu_tx_request_active_ext(ni, tap->txa_tid, 0); return (0); } RSU_UNLOCK(sc); /* Mark success; we don't get any further notifications */ (void) ieee80211_ampdu_tx_request_active_ext(ni, tap->txa_tid, 1); #endif /* Return 0, we're driving this ourselves */ return (0); } static int rsu_wme_update(struct ieee80211com *ic) { /* Firmware handles this; not our problem */ return (0); } static int rsu_attach(device_t self) { struct usb_attach_arg *uaa = device_get_ivars(self); struct rsu_softc *sc = device_get_softc(self); struct ieee80211com *ic = &sc->sc_ic; int error; uint8_t iface_index; struct usb_interface *iface; const char *rft; device_set_usb_desc(self); sc->sc_udev = uaa->device; sc->sc_dev = self; if (rsu_enable_11n) sc->sc_ht = !! (USB_GET_DRIVER_INFO(uaa) & RSU_HT_SUPPORTED); /* Get number of endpoints */ iface = usbd_get_iface(sc->sc_udev, 0); sc->sc_nendpoints = iface->idesc->bNumEndpoints; /* Endpoints are hard-coded for now, so enforce 4-endpoint only */ if (sc->sc_nendpoints != 4) { device_printf(sc->sc_dev, "the driver currently only supports 4-endpoint devices\n"); return (ENXIO); } mtx_init(&sc->sc_mtx, device_get_nameunit(self), MTX_NETWORK_LOCK, MTX_DEF); TIMEOUT_TASK_INIT(taskqueue_thread, &sc->calib_task, 0, rsu_calib_task, sc); TASK_INIT(&sc->tx_task, 0, rsu_tx_task, sc); mbufq_init(&sc->sc_snd, ifqmaxlen); /* Allocate Tx/Rx buffers. */ error = rsu_alloc_rx_list(sc); if (error != 0) { device_printf(sc->sc_dev, "could not allocate Rx buffers\n"); goto fail_usb; } error = rsu_alloc_tx_list(sc); if (error != 0) { device_printf(sc->sc_dev, "could not allocate Tx buffers\n"); rsu_free_rx_list(sc); goto fail_usb; } iface_index = 0; error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer, rsu_config, RSU_N_TRANSFER, sc, &sc->sc_mtx); if (error) { device_printf(sc->sc_dev, "could not allocate USB transfers, err=%s\n", usbd_errstr(error)); goto fail_usb; } RSU_LOCK(sc); /* Read chip revision. */ sc->cut = MS(rsu_read_4(sc, R92S_PMC_FSM), R92S_PMC_FSM_CUT); if (sc->cut != 3) sc->cut = (sc->cut >> 1) + 1; error = rsu_read_rom(sc); RSU_UNLOCK(sc); if (error != 0) { device_printf(self, "could not read ROM\n"); goto fail_rom; } /* Figure out TX/RX streams */ switch (sc->rom[84]) { case 0x0: sc->sc_rftype = RTL8712_RFCONFIG_1T1R; sc->sc_nrxstream = 1; sc->sc_ntxstream = 1; rft = "1T1R"; break; case 0x1: sc->sc_rftype = RTL8712_RFCONFIG_1T2R; sc->sc_nrxstream = 2; sc->sc_ntxstream = 1; rft = "1T2R"; break; case 0x2: sc->sc_rftype = RTL8712_RFCONFIG_2T2R; sc->sc_nrxstream = 2; sc->sc_ntxstream = 2; rft = "2T2R"; break; default: device_printf(sc->sc_dev, "%s: unknown board type (rfconfig=0x%02x)\n", __func__, sc->rom[84]); goto fail_rom; } IEEE80211_ADDR_COPY(ic->ic_macaddr, &sc->rom[0x12]); device_printf(self, "MAC/BB RTL8712 cut %d %s\n", sc->cut, rft); ic->ic_softc = sc; ic->ic_name = device_get_nameunit(self); 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 */ #if 0 IEEE80211_C_BGSCAN | /* Background scan. */ #endif IEEE80211_C_SHPREAMBLE | /* Short preamble supported. */ IEEE80211_C_WME | /* WME/QoS */ IEEE80211_C_SHSLOT | /* Short slot time supported. */ IEEE80211_C_WPA; /* WPA/RSN. */ /* Check if HT support is present. */ if (sc->sc_ht) { device_printf(sc->sc_dev, "%s: enabling 11n\n", __func__); /* Enable basic HT */ ic->ic_htcaps = IEEE80211_HTC_HT | #if 0 IEEE80211_HTC_AMPDU | #endif IEEE80211_HTC_AMSDU | IEEE80211_HTCAP_MAXAMSDU_3839 | IEEE80211_HTCAP_SMPS_OFF; ic->ic_htcaps |= IEEE80211_HTCAP_CHWIDTH40; /* set number of spatial streams */ ic->ic_txstream = sc->sc_ntxstream; ic->ic_rxstream = sc->sc_nrxstream; } rsu_getradiocaps(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans, ic->ic_channels); ieee80211_ifattach(ic); ic->ic_raw_xmit = rsu_raw_xmit; ic->ic_scan_start = rsu_scan_start; ic->ic_scan_end = rsu_scan_end; ic->ic_getradiocaps = rsu_getradiocaps; ic->ic_set_channel = rsu_set_channel; ic->ic_vap_create = rsu_vap_create; ic->ic_vap_delete = rsu_vap_delete; ic->ic_update_mcast = rsu_update_mcast; ic->ic_parent = rsu_parent; ic->ic_transmit = rsu_transmit; ic->ic_send_mgmt = rsu_send_mgmt; ic->ic_update_chw = rsu_update_chw; ic->ic_ampdu_enable = rsu_ampdu_enable; ic->ic_wme.wme_update = rsu_wme_update; ieee80211_radiotap_attach(ic, &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap), RSU_TX_RADIOTAP_PRESENT, &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap), RSU_RX_RADIOTAP_PRESENT); if (bootverbose) ieee80211_announce(ic); return (0); fail_rom: usbd_transfer_unsetup(sc->sc_xfer, RSU_N_TRANSFER); fail_usb: mtx_destroy(&sc->sc_mtx); return (ENXIO); } static int rsu_detach(device_t self) { struct rsu_softc *sc = device_get_softc(self); struct ieee80211com *ic = &sc->sc_ic; RSU_LOCK(sc); rsu_stop(sc); RSU_UNLOCK(sc); usbd_transfer_unsetup(sc->sc_xfer, RSU_N_TRANSFER); /* * Free buffers /before/ we detach from net80211, else node * references to destroyed vaps will lead to a panic. */ /* Free Tx/Rx buffers. */ RSU_LOCK(sc); rsu_free_tx_list(sc); rsu_free_rx_list(sc); RSU_UNLOCK(sc); /* Frames are freed; detach from net80211 */ ieee80211_ifdetach(ic); taskqueue_drain_timeout(taskqueue_thread, &sc->calib_task); taskqueue_drain(taskqueue_thread, &sc->tx_task); mtx_destroy(&sc->sc_mtx); return (0); } static usb_error_t rsu_do_request(struct rsu_softc *sc, struct usb_device_request *req, void *data) { usb_error_t err; int ntries = 10; RSU_ASSERT_LOCKED(sc); while (ntries--) { err = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, req, data, 0, NULL, 250 /* ms */); if (err == 0 || err == USB_ERR_NOT_CONFIGURED) break; DPRINTFN(1, "Control request failed, %s (retrying)\n", usbd_errstr(err)); rsu_ms_delay(sc, 10); } return (err); } static struct ieee80211vap * rsu_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 rsu_vap *uvp; struct ieee80211vap *vap; if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ return (NULL); uvp = malloc(sizeof(struct rsu_vap), M_80211_VAP, M_WAITOK | M_ZERO); vap = &uvp->vap; if (ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid) != 0) { /* out of memory */ free(uvp, M_80211_VAP); return (NULL); } /* override state transition machine */ uvp->newstate = vap->iv_newstate; vap->iv_newstate = rsu_newstate; /* Limits from the r92su driver */ vap->iv_ampdu_density = IEEE80211_HTCAP_MPDUDENSITY_16; vap->iv_ampdu_rxmax = IEEE80211_HTCAP_MAXRXAMPDU_32K; /* complete setup */ ieee80211_vap_attach(vap, ieee80211_media_change, ieee80211_media_status, mac); ic->ic_opmode = opmode; return (vap); } static void rsu_vap_delete(struct ieee80211vap *vap) { struct rsu_vap *uvp = RSU_VAP(vap); ieee80211_vap_detach(vap); free(uvp, M_80211_VAP); } static void rsu_scan_start(struct ieee80211com *ic) { struct rsu_softc *sc = ic->ic_softc; int error; /* Scanning is done by the firmware. */ RSU_LOCK(sc); /* XXX TODO: force awake if in in network-sleep? */ error = rsu_site_survey(sc, TAILQ_FIRST(&ic->ic_vaps)); RSU_UNLOCK(sc); if (error != 0) device_printf(sc->sc_dev, "could not send site survey command\n"); } static void rsu_scan_end(struct ieee80211com *ic) { /* Nothing to do here. */ } static void rsu_getradiocaps(struct ieee80211com *ic, int maxchans, int *nchans, struct ieee80211_channel chans[]) { struct rsu_softc *sc = ic->ic_softc; uint8_t bands[IEEE80211_MODE_BYTES]; /* Set supported .11b and .11g rates. */ memset(bands, 0, sizeof(bands)); setbit(bands, IEEE80211_MODE_11B); setbit(bands, IEEE80211_MODE_11G); if (sc->sc_ht) setbit(bands, IEEE80211_MODE_11NG); ieee80211_add_channel_list_2ghz(chans, maxchans, nchans, rsu_chan_2ghz, nitems(rsu_chan_2ghz), bands, 0); } static void rsu_set_channel(struct ieee80211com *ic __unused) { /* We are unable to switch channels, yet. */ } static void rsu_update_mcast(struct ieee80211com *ic) { /* XXX do nothing? */ } static int rsu_alloc_list(struct rsu_softc *sc, struct rsu_data data[], int ndata, int maxsz) { int i, error; for (i = 0; i < ndata; i++) { struct rsu_data *dp = &data[i]; dp->sc = sc; dp->m = NULL; dp->buf = malloc(maxsz, M_USBDEV, M_NOWAIT); if (dp->buf == NULL) { device_printf(sc->sc_dev, "could not allocate buffer\n"); error = ENOMEM; goto fail; } dp->ni = NULL; } return (0); fail: rsu_free_list(sc, data, ndata); return (error); } static int rsu_alloc_rx_list(struct rsu_softc *sc) { int error, i; error = rsu_alloc_list(sc, sc->sc_rx, RSU_RX_LIST_COUNT, RSU_RXBUFSZ); if (error != 0) return (error); STAILQ_INIT(&sc->sc_rx_active); STAILQ_INIT(&sc->sc_rx_inactive); for (i = 0; i < RSU_RX_LIST_COUNT; i++) STAILQ_INSERT_HEAD(&sc->sc_rx_inactive, &sc->sc_rx[i], next); return (0); } static int rsu_alloc_tx_list(struct rsu_softc *sc) { int error, i; error = rsu_alloc_list(sc, sc->sc_tx, RSU_TX_LIST_COUNT, RSU_TXBUFSZ); if (error != 0) return (error); STAILQ_INIT(&sc->sc_tx_inactive); for (i = 0; i != RSU_N_TRANSFER; i++) { STAILQ_INIT(&sc->sc_tx_active[i]); STAILQ_INIT(&sc->sc_tx_pending[i]); } for (i = 0; i < RSU_TX_LIST_COUNT; i++) { STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, &sc->sc_tx[i], next); } return (0); } static void rsu_free_tx_list(struct rsu_softc *sc) { int i; /* prevent further allocations from TX list(s) */ STAILQ_INIT(&sc->sc_tx_inactive); for (i = 0; i != RSU_N_TRANSFER; i++) { STAILQ_INIT(&sc->sc_tx_active[i]); STAILQ_INIT(&sc->sc_tx_pending[i]); } rsu_free_list(sc, sc->sc_tx, RSU_TX_LIST_COUNT); } static void rsu_free_rx_list(struct rsu_softc *sc) { /* prevent further allocations from RX list(s) */ STAILQ_INIT(&sc->sc_rx_inactive); STAILQ_INIT(&sc->sc_rx_active); rsu_free_list(sc, sc->sc_rx, RSU_RX_LIST_COUNT); } static void rsu_free_list(struct rsu_softc *sc, struct rsu_data data[], int ndata) { int i; for (i = 0; i < ndata; i++) { struct rsu_data *dp = &data[i]; if (dp->buf != NULL) { free(dp->buf, M_USBDEV); dp->buf = NULL; } if (dp->ni != NULL) { ieee80211_free_node(dp->ni); dp->ni = NULL; } } } static struct rsu_data * _rsu_getbuf(struct rsu_softc *sc) { struct rsu_data *bf; bf = STAILQ_FIRST(&sc->sc_tx_inactive); if (bf != NULL) STAILQ_REMOVE_HEAD(&sc->sc_tx_inactive, next); else bf = NULL; return (bf); } static struct rsu_data * rsu_getbuf(struct rsu_softc *sc) { struct rsu_data *bf; RSU_ASSERT_LOCKED(sc); bf = _rsu_getbuf(sc); if (bf == NULL) { RSU_DPRINTF(sc, RSU_DEBUG_TX, "%s: no buffers\n", __func__); } return (bf); } static void rsu_freebuf(struct rsu_softc *sc, struct rsu_data *bf) { RSU_ASSERT_LOCKED(sc); STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, bf, next); } static int rsu_write_region_1(struct rsu_softc *sc, uint16_t addr, uint8_t *buf, int len) { usb_device_request_t req; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = R92S_REQ_REGS; USETW(req.wValue, addr); USETW(req.wIndex, 0); USETW(req.wLength, len); return (rsu_do_request(sc, &req, buf)); } static void rsu_write_1(struct rsu_softc *sc, uint16_t addr, uint8_t val) { rsu_write_region_1(sc, addr, &val, 1); } static void rsu_write_2(struct rsu_softc *sc, uint16_t addr, uint16_t val) { val = htole16(val); rsu_write_region_1(sc, addr, (uint8_t *)&val, 2); } static void rsu_write_4(struct rsu_softc *sc, uint16_t addr, uint32_t val) { val = htole32(val); rsu_write_region_1(sc, addr, (uint8_t *)&val, 4); } static int rsu_read_region_1(struct rsu_softc *sc, uint16_t addr, uint8_t *buf, int len) { usb_device_request_t req; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = R92S_REQ_REGS; USETW(req.wValue, addr); USETW(req.wIndex, 0); USETW(req.wLength, len); return (rsu_do_request(sc, &req, buf)); } static uint8_t rsu_read_1(struct rsu_softc *sc, uint16_t addr) { uint8_t val; if (rsu_read_region_1(sc, addr, &val, 1) != 0) return (0xff); return (val); } static uint16_t rsu_read_2(struct rsu_softc *sc, uint16_t addr) { uint16_t val; if (rsu_read_region_1(sc, addr, (uint8_t *)&val, 2) != 0) return (0xffff); return (le16toh(val)); } static uint32_t rsu_read_4(struct rsu_softc *sc, uint16_t addr) { uint32_t val; if (rsu_read_region_1(sc, addr, (uint8_t *)&val, 4) != 0) return (0xffffffff); return (le32toh(val)); } static int rsu_fw_iocmd(struct rsu_softc *sc, uint32_t iocmd) { int ntries; rsu_write_4(sc, R92S_IOCMD_CTRL, iocmd); rsu_ms_delay(sc, 1); for (ntries = 0; ntries < 50; ntries++) { if (rsu_read_4(sc, R92S_IOCMD_CTRL) == 0) return (0); rsu_ms_delay(sc, 1); } return (ETIMEDOUT); } static uint8_t rsu_efuse_read_1(struct rsu_softc *sc, uint16_t addr) { uint32_t reg; int ntries; reg = rsu_read_4(sc, R92S_EFUSE_CTRL); reg = RW(reg, R92S_EFUSE_CTRL_ADDR, addr); reg &= ~R92S_EFUSE_CTRL_VALID; rsu_write_4(sc, R92S_EFUSE_CTRL, reg); /* Wait for read operation to complete. */ for (ntries = 0; ntries < 100; ntries++) { reg = rsu_read_4(sc, R92S_EFUSE_CTRL); if (reg & R92S_EFUSE_CTRL_VALID) return (MS(reg, R92S_EFUSE_CTRL_DATA)); rsu_ms_delay(sc, 1); } device_printf(sc->sc_dev, "could not read efuse byte at address 0x%x\n", addr); return (0xff); } static int rsu_read_rom(struct rsu_softc *sc) { uint8_t *rom = sc->rom; uint16_t addr = 0; uint32_t reg; uint8_t off, msk; int i; /* Make sure that ROM type is eFuse and that autoload succeeded. */ reg = rsu_read_1(sc, R92S_EE_9346CR); if ((reg & (R92S_9356SEL | R92S_EEPROM_EN)) != R92S_EEPROM_EN) return (EIO); /* Turn on 2.5V to prevent eFuse leakage. */ reg = rsu_read_1(sc, R92S_EFUSE_TEST + 3); rsu_write_1(sc, R92S_EFUSE_TEST + 3, reg | 0x80); rsu_ms_delay(sc, 1); rsu_write_1(sc, R92S_EFUSE_TEST + 3, reg & ~0x80); /* Read full ROM image. */ memset(&sc->rom, 0xff, sizeof(sc->rom)); while (addr < 512) { reg = rsu_efuse_read_1(sc, addr); if (reg == 0xff) break; addr++; off = reg >> 4; msk = reg & 0xf; for (i = 0; i < 4; i++) { if (msk & (1 << i)) continue; rom[off * 8 + i * 2 + 0] = rsu_efuse_read_1(sc, addr); addr++; rom[off * 8 + i * 2 + 1] = rsu_efuse_read_1(sc, addr); addr++; } } #ifdef USB_DEBUG if (rsu_debug >= 5) { /* Dump ROM content. */ printf("\n"); for (i = 0; i < sizeof(sc->rom); i++) printf("%02x:", rom[i]); printf("\n"); } #endif return (0); } static int rsu_fw_cmd(struct rsu_softc *sc, uint8_t code, void *buf, int len) { const uint8_t which = RSU_H2C_ENDPOINT; struct rsu_data *data; struct r92s_tx_desc *txd; struct r92s_fw_cmd_hdr *cmd; int cmdsz; int xferlen; RSU_ASSERT_LOCKED(sc); data = rsu_getbuf(sc); if (data == NULL) return (ENOMEM); /* Blank the entire payload, just to be safe */ memset(data->buf, '\0', RSU_TXBUFSZ); /* Round-up command length to a multiple of 8 bytes. */ /* XXX TODO: is this required? */ cmdsz = (len + 7) & ~7; xferlen = sizeof(*txd) + sizeof(*cmd) + cmdsz; KASSERT(xferlen <= RSU_TXBUFSZ, ("%s: invalid length", __func__)); memset(data->buf, 0, xferlen); /* Setup Tx descriptor. */ txd = (struct r92s_tx_desc *)data->buf; txd->txdw0 = htole32( SM(R92S_TXDW0_OFFSET, sizeof(*txd)) | SM(R92S_TXDW0_PKTLEN, sizeof(*cmd) + cmdsz) | R92S_TXDW0_OWN | R92S_TXDW0_FSG | R92S_TXDW0_LSG); txd->txdw1 = htole32(SM(R92S_TXDW1_QSEL, R92S_TXDW1_QSEL_H2C)); /* Setup command header. */ cmd = (struct r92s_fw_cmd_hdr *)&txd[1]; cmd->len = htole16(cmdsz); cmd->code = code; cmd->seq = sc->cmd_seq; sc->cmd_seq = (sc->cmd_seq + 1) & 0x7f; /* Copy command payload. */ memcpy(&cmd[1], buf, len); RSU_DPRINTF(sc, RSU_DEBUG_TX | RSU_DEBUG_FWCMD, "%s: Tx cmd code=0x%x len=0x%x\n", __func__, code, cmdsz); data->buflen = xferlen; STAILQ_INSERT_TAIL(&sc->sc_tx_pending[which], data, next); usbd_transfer_start(sc->sc_xfer[which]); return (0); } /* ARGSUSED */ static void rsu_calib_task(void *arg, int pending __unused) { struct rsu_softc *sc = arg; #ifdef notyet uint32_t reg; #endif RSU_DPRINTF(sc, RSU_DEBUG_CALIB, "%s: running calibration task\n", __func__); RSU_LOCK(sc); #ifdef notyet /* Read WPS PBC status. */ rsu_write_1(sc, R92S_MAC_PINMUX_CTRL, R92S_GPIOMUX_EN | SM(R92S_GPIOSEL_GPIO, R92S_GPIOSEL_GPIO_JTAG)); rsu_write_1(sc, R92S_GPIO_IO_SEL, rsu_read_1(sc, R92S_GPIO_IO_SEL) & ~R92S_GPIO_WPS); reg = rsu_read_1(sc, R92S_GPIO_CTRL); if (reg != 0xff && (reg & R92S_GPIO_WPS)) DPRINTF(("WPS PBC is pushed\n")); #endif /* Read current signal level. */ if (rsu_fw_iocmd(sc, 0xf4000001) == 0) { sc->sc_currssi = rsu_read_4(sc, R92S_IOCMD_DATA); RSU_DPRINTF(sc, RSU_DEBUG_CALIB, "%s: RSSI=%d (%d)\n", __func__, sc->sc_currssi, rsu_hwrssi_to_rssi(sc, sc->sc_currssi)); } if (sc->sc_calibrating) taskqueue_enqueue_timeout(taskqueue_thread, &sc->calib_task, hz); RSU_UNLOCK(sc); } static void rsu_tx_task(void *arg, int pending __unused) { struct rsu_softc *sc = arg; RSU_LOCK(sc); _rsu_start(sc); RSU_UNLOCK(sc); } #define RSU_PWR_UNKNOWN 0x0 #define RSU_PWR_ACTIVE 0x1 #define RSU_PWR_OFF 0x2 #define RSU_PWR_SLEEP 0x3 /* * Set the current power state. * * The rtlwifi code doesn't do this so aggressively; it * waits for an idle period after association with * no traffic before doing this. * * For now - it's on in all states except RUN, and * in RUN it'll transition to allow sleep. */ struct r92s_pwr_cmd { uint8_t mode; uint8_t smart_ps; uint8_t bcn_pass_time; }; static int rsu_set_fw_power_state(struct rsu_softc *sc, int state) { struct r92s_set_pwr_mode cmd; //struct r92s_pwr_cmd cmd; int error; RSU_ASSERT_LOCKED(sc); /* only change state if required */ if (sc->sc_curpwrstate == state) return (0); memset(&cmd, 0, sizeof(cmd)); switch (state) { case RSU_PWR_ACTIVE: /* Force the hardware awake */ rsu_write_1(sc, R92S_USB_HRPWM, R92S_USB_HRPWM_PS_ST_ACTIVE | R92S_USB_HRPWM_PS_ALL_ON); cmd.mode = R92S_PS_MODE_ACTIVE; break; case RSU_PWR_SLEEP: cmd.mode = R92S_PS_MODE_DTIM; /* XXX configurable? */ cmd.smart_ps = 1; /* XXX 2 if doing p2p */ cmd.bcn_pass_time = 5; /* in 100mS usb.c, linux/rtlwifi */ break; case RSU_PWR_OFF: cmd.mode = R92S_PS_MODE_RADIOOFF; break; default: device_printf(sc->sc_dev, "%s: unknown ps mode (%d)\n", __func__, state); return (ENXIO); } RSU_DPRINTF(sc, RSU_DEBUG_RESET, "%s: setting ps mode to %d (mode %d)\n", __func__, state, cmd.mode); error = rsu_fw_cmd(sc, R92S_CMD_SET_PWR_MODE, &cmd, sizeof(cmd)); if (error == 0) sc->sc_curpwrstate = state; return (error); } static int rsu_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct rsu_vap *uvp = RSU_VAP(vap); struct ieee80211com *ic = vap->iv_ic; struct rsu_softc *sc = ic->ic_softc; struct ieee80211_node *ni; struct ieee80211_rateset *rs; enum ieee80211_state ostate; int error, startcal = 0; ostate = vap->iv_state; RSU_DPRINTF(sc, RSU_DEBUG_STATE, "%s: %s -> %s\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate]); IEEE80211_UNLOCK(ic); if (ostate == IEEE80211_S_RUN) { RSU_LOCK(sc); /* Stop calibration. */ sc->sc_calibrating = 0; RSU_UNLOCK(sc); taskqueue_drain_timeout(taskqueue_thread, &sc->calib_task); taskqueue_drain(taskqueue_thread, &sc->tx_task); /* Disassociate from our current BSS. */ RSU_LOCK(sc); rsu_disconnect(sc); } else RSU_LOCK(sc); switch (nstate) { case IEEE80211_S_INIT: (void) rsu_set_fw_power_state(sc, RSU_PWR_ACTIVE); break; case IEEE80211_S_AUTH: ni = ieee80211_ref_node(vap->iv_bss); (void) rsu_set_fw_power_state(sc, RSU_PWR_ACTIVE); error = rsu_join_bss(sc, ni); ieee80211_free_node(ni); if (error != 0) { device_printf(sc->sc_dev, "could not send join command\n"); } break; case IEEE80211_S_RUN: ni = ieee80211_ref_node(vap->iv_bss); rs = &ni->ni_rates; /* Indicate highest supported rate. */ ni->ni_txrate = rs->rs_rates[rs->rs_nrates - 1]; (void) rsu_set_fw_power_state(sc, RSU_PWR_SLEEP); ieee80211_free_node(ni); startcal = 1; break; default: break; } if (startcal != 0) { sc->sc_calibrating = 1; /* Start periodic calibration. */ taskqueue_enqueue_timeout(taskqueue_thread, &sc->calib_task, hz); } RSU_UNLOCK(sc); IEEE80211_LOCK(ic); return (uvp->newstate(vap, nstate, arg)); } #ifdef notyet static void rsu_set_key(struct rsu_softc *sc, const struct ieee80211_key *k) { struct r92s_fw_cmd_set_key key; memset(&key, 0, sizeof(key)); /* Map net80211 cipher to HW crypto algorithm. */ switch (k->wk_cipher->ic_cipher) { case IEEE80211_CIPHER_WEP: if (k->wk_keylen < 8) key.algo = R92S_KEY_ALGO_WEP40; else key.algo = R92S_KEY_ALGO_WEP104; break; case IEEE80211_CIPHER_TKIP: key.algo = R92S_KEY_ALGO_TKIP; break; case IEEE80211_CIPHER_AES_CCM: key.algo = R92S_KEY_ALGO_AES; break; default: return; } key.id = k->wk_keyix; key.grpkey = (k->wk_flags & IEEE80211_KEY_GROUP) != 0; memcpy(key.key, k->wk_key, MIN(k->wk_keylen, sizeof(key.key))); (void)rsu_fw_cmd(sc, R92S_CMD_SET_KEY, &key, sizeof(key)); } static void rsu_delete_key(struct rsu_softc *sc, const struct ieee80211_key *k) { struct r92s_fw_cmd_set_key key; memset(&key, 0, sizeof(key)); key.id = k->wk_keyix; (void)rsu_fw_cmd(sc, R92S_CMD_SET_KEY, &key, sizeof(key)); } #endif static int rsu_site_survey(struct rsu_softc *sc, struct ieee80211vap *vap) { struct r92s_fw_cmd_sitesurvey cmd; struct ieee80211com *ic = &sc->sc_ic; int r; RSU_ASSERT_LOCKED(sc); memset(&cmd, 0, sizeof(cmd)); if ((ic->ic_flags & IEEE80211_F_ASCAN) || sc->sc_scan_pass == 1) cmd.active = htole32(1); cmd.limit = htole32(48); if (sc->sc_scan_pass == 1 && vap->iv_des_nssid > 0) { /* Do a directed scan for second pass. */ cmd.ssidlen = htole32(vap->iv_des_ssid[0].len); memcpy(cmd.ssid, vap->iv_des_ssid[0].ssid, vap->iv_des_ssid[0].len); } DPRINTF("sending site survey command, pass=%d\n", sc->sc_scan_pass); r = rsu_fw_cmd(sc, R92S_CMD_SITE_SURVEY, &cmd, sizeof(cmd)); if (r == 0) { sc->sc_scanning = 1; } return (r); } static int rsu_join_bss(struct rsu_softc *sc, struct ieee80211_node *ni) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = ni->ni_vap; struct ndis_wlan_bssid_ex *bss; struct ndis_802_11_fixed_ies *fixed; struct r92s_fw_cmd_auth auth; uint8_t buf[sizeof(*bss) + 128] __aligned(4); uint8_t *frm; uint8_t opmode; int error; int cnt; char *msg = "rsujoin"; RSU_ASSERT_LOCKED(sc); /* * Until net80211 scanning doesn't automatically finish * before we tell it to, let's just wait until any pending * scan is done. * * XXX TODO: yes, this releases and re-acquires the lock. * We should re-verify the state whenever we re-attempt this! */ cnt = 0; while (sc->sc_scanning && cnt < 10) { device_printf(sc->sc_dev, "%s: still scanning! (attempt %d)\n", __func__, cnt); msleep(msg, &sc->sc_mtx, 0, msg, hz / 2); cnt++; } /* Let the FW decide the opmode based on the capinfo field. */ opmode = NDIS802_11AUTOUNKNOWN; RSU_DPRINTF(sc, RSU_DEBUG_RESET, "%s: setting operating mode to %d\n", __func__, opmode); error = rsu_fw_cmd(sc, R92S_CMD_SET_OPMODE, &opmode, sizeof(opmode)); if (error != 0) return (error); memset(&auth, 0, sizeof(auth)); if (vap->iv_flags & IEEE80211_F_WPA) { auth.mode = R92S_AUTHMODE_WPA; auth.dot1x = (ni->ni_authmode == IEEE80211_AUTH_8021X); } else auth.mode = R92S_AUTHMODE_OPEN; RSU_DPRINTF(sc, RSU_DEBUG_RESET, "%s: setting auth mode to %d\n", __func__, auth.mode); error = rsu_fw_cmd(sc, R92S_CMD_SET_AUTH, &auth, sizeof(auth)); if (error != 0) return (error); memset(buf, 0, sizeof(buf)); bss = (struct ndis_wlan_bssid_ex *)buf; IEEE80211_ADDR_COPY(bss->macaddr, ni->ni_bssid); bss->ssid.ssidlen = htole32(ni->ni_esslen); memcpy(bss->ssid.ssid, ni->ni_essid, ni->ni_esslen); if (vap->iv_flags & (IEEE80211_F_PRIVACY | IEEE80211_F_WPA)) bss->privacy = htole32(1); bss->rssi = htole32(ni->ni_avgrssi); if (ic->ic_curmode == IEEE80211_MODE_11B) bss->networktype = htole32(NDIS802_11DS); else bss->networktype = htole32(NDIS802_11OFDM24); bss->config.len = htole32(sizeof(bss->config)); bss->config.bintval = htole32(ni->ni_intval); bss->config.dsconfig = htole32(ieee80211_chan2ieee(ic, ni->ni_chan)); bss->inframode = htole32(NDIS802_11INFRASTRUCTURE); /* XXX verify how this is supposed to look! */ memcpy(bss->supprates, ni->ni_rates.rs_rates, ni->ni_rates.rs_nrates); /* Write the fixed fields of the beacon frame. */ fixed = (struct ndis_802_11_fixed_ies *)&bss[1]; memcpy(&fixed->tstamp, ni->ni_tstamp.data, 8); fixed->bintval = htole16(ni->ni_intval); fixed->capabilities = htole16(ni->ni_capinfo); /* Write IEs to be included in the association request. */ frm = (uint8_t *)&fixed[1]; frm = ieee80211_add_rsn(frm, vap); frm = ieee80211_add_wpa(frm, vap); frm = ieee80211_add_qos(frm, ni); if ((ic->ic_flags & IEEE80211_F_WME) && (ni->ni_ies.wme_ie != NULL)) frm = ieee80211_add_wme_info(frm, &ic->ic_wme); if (ni->ni_flags & IEEE80211_NODE_HT) { frm = ieee80211_add_htcap(frm, ni); frm = ieee80211_add_htinfo(frm, ni); } bss->ieslen = htole32(frm - (uint8_t *)fixed); bss->len = htole32(((frm - buf) + 3) & ~3); RSU_DPRINTF(sc, RSU_DEBUG_RESET | RSU_DEBUG_FWCMD, "%s: sending join bss command to %s chan %d\n", __func__, ether_sprintf(bss->macaddr), le32toh(bss->config.dsconfig)); return (rsu_fw_cmd(sc, R92S_CMD_JOIN_BSS, buf, sizeof(buf))); } static int rsu_disconnect(struct rsu_softc *sc) { uint32_t zero = 0; /* :-) */ /* Disassociate from our current BSS. */ RSU_DPRINTF(sc, RSU_DEBUG_STATE | RSU_DEBUG_FWCMD, "%s: sending disconnect command\n", __func__); return (rsu_fw_cmd(sc, R92S_CMD_DISCONNECT, &zero, sizeof(zero))); } /* * Map the hardware provided RSSI value to a signal level. * For the most part it's just something we divide by and cap * so it doesn't overflow the representation by net80211. */ static int rsu_hwrssi_to_rssi(struct rsu_softc *sc, int hw_rssi) { int v; if (hw_rssi == 0) return (0); v = hw_rssi >> 4; if (v > 80) v = 80; return (v); } static void rsu_event_survey(struct rsu_softc *sc, uint8_t *buf, int len) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_frame *wh; struct ndis_wlan_bssid_ex *bss; struct ieee80211_rx_stats rxs; struct mbuf *m; int pktlen; if (__predict_false(len < sizeof(*bss))) return; bss = (struct ndis_wlan_bssid_ex *)buf; if (__predict_false(len < sizeof(*bss) + le32toh(bss->ieslen))) return; RSU_DPRINTF(sc, RSU_DEBUG_SCAN, "%s: found BSS %s: len=%d chan=%d inframode=%d " "networktype=%d privacy=%d, RSSI=%d\n", __func__, ether_sprintf(bss->macaddr), le32toh(bss->len), le32toh(bss->config.dsconfig), le32toh(bss->inframode), le32toh(bss->networktype), le32toh(bss->privacy), le32toh(bss->rssi)); /* Build a fake beacon frame to let net80211 do all the parsing. */ /* XXX TODO: just call the new scan API methods! */ pktlen = sizeof(*wh) + le32toh(bss->ieslen); if (__predict_false(pktlen > MCLBYTES)) return; m = m_get2(pktlen, M_NOWAIT, MT_DATA, M_PKTHDR); if (__predict_false(m == NULL)) return; wh = mtod(m, struct ieee80211_frame *); wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_BEACON; wh->i_fc[1] = IEEE80211_FC1_DIR_NODS; USETW(wh->i_dur, 0); IEEE80211_ADDR_COPY(wh->i_addr1, ieee80211broadcastaddr); IEEE80211_ADDR_COPY(wh->i_addr2, bss->macaddr); IEEE80211_ADDR_COPY(wh->i_addr3, bss->macaddr); *(uint16_t *)wh->i_seq = 0; memcpy(&wh[1], (uint8_t *)&bss[1], le32toh(bss->ieslen)); /* Finalize mbuf. */ m->m_pkthdr.len = m->m_len = pktlen; /* Set channel flags for input path */ 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 = le32toh(bss->config.dsconfig); rxs.c_freq = ieee80211_ieee2mhz(rxs.c_ieee, IEEE80211_CHAN_2GHZ); /* This is a number from 0..100; so let's just divide it down a bit */ rxs.c_rssi = le32toh(bss->rssi) / 2; rxs.c_nf = -96; + if (ieee80211_add_rx_params(m, &rxs) == 0) + return; /* XXX avoid a LOR */ RSU_UNLOCK(sc); - ieee80211_input_mimo_all(ic, m, &rxs); + ieee80211_input_mimo_all(ic, m); RSU_LOCK(sc); } static void rsu_event_join_bss(struct rsu_softc *sc, uint8_t *buf, int len) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); struct ieee80211_node *ni = vap->iv_bss; struct r92s_event_join_bss *rsp; uint32_t tmp; int res; if (__predict_false(len < sizeof(*rsp))) return; rsp = (struct r92s_event_join_bss *)buf; res = (int)le32toh(rsp->join_res); RSU_DPRINTF(sc, RSU_DEBUG_STATE | RSU_DEBUG_FWCMD, "%s: Rx join BSS event len=%d res=%d\n", __func__, len, res); /* * XXX Don't do this; there's likely a better way to tell * the caller we failed. */ if (res <= 0) { RSU_UNLOCK(sc); ieee80211_new_state(vap, IEEE80211_S_SCAN, -1); RSU_LOCK(sc); return; } tmp = le32toh(rsp->associd); if (tmp >= vap->iv_max_aid) { DPRINTF("Assoc ID overflow\n"); tmp = 1; } RSU_DPRINTF(sc, RSU_DEBUG_STATE | RSU_DEBUG_FWCMD, "%s: associated with %s associd=%d\n", __func__, ether_sprintf(rsp->bss.macaddr), tmp); /* XXX is this required? What's the top two bits for again? */ ni->ni_associd = tmp | 0xc000; RSU_UNLOCK(sc); ieee80211_new_state(vap, IEEE80211_S_RUN, IEEE80211_FC0_SUBTYPE_ASSOC_RESP); RSU_LOCK(sc); } static void rsu_event_addba_req_report(struct rsu_softc *sc, uint8_t *buf, int len) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); struct r92s_add_ba_event *ba = (void *) buf; struct ieee80211_node *ni; if (len < sizeof(*ba)) { device_printf(sc->sc_dev, "%s: short read (%d)\n", __func__, len); return; } if (vap == NULL) return; RSU_DPRINTF(sc, RSU_DEBUG_AMPDU, "%s: mac=%s, tid=%d, ssn=%d\n", __func__, ether_sprintf(ba->mac_addr), (int) ba->tid, (int) le16toh(ba->ssn)); /* XXX do node lookup; this is STA specific */ ni = ieee80211_ref_node(vap->iv_bss); ieee80211_ampdu_rx_start_ext(ni, ba->tid, le16toh(ba->ssn) >> 4, 32); ieee80211_free_node(ni); } static void rsu_rx_event(struct rsu_softc *sc, uint8_t code, uint8_t *buf, int len) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); RSU_DPRINTF(sc, RSU_DEBUG_RX | RSU_DEBUG_FWCMD, "%s: Rx event code=%d len=%d\n", __func__, code, len); switch (code) { case R92S_EVT_SURVEY: rsu_event_survey(sc, buf, len); break; case R92S_EVT_SURVEY_DONE: RSU_DPRINTF(sc, RSU_DEBUG_SCAN, "%s: site survey pass %d done, found %d BSS\n", __func__, sc->sc_scan_pass, le32toh(*(uint32_t *)buf)); sc->sc_scanning = 0; if (vap->iv_state != IEEE80211_S_SCAN) break; /* Ignore if not scanning. */ /* * XXX TODO: This needs to be done without a transition to * the SCAN state again. Grr. */ if (sc->sc_scan_pass == 0 && vap->iv_des_nssid != 0) { /* Schedule a directed scan for hidden APs. */ /* XXX bad! */ sc->sc_scan_pass = 1; RSU_UNLOCK(sc); ieee80211_new_state(vap, IEEE80211_S_SCAN, -1); RSU_LOCK(sc); break; } sc->sc_scan_pass = 0; break; case R92S_EVT_JOIN_BSS: if (vap->iv_state == IEEE80211_S_AUTH) rsu_event_join_bss(sc, buf, len); break; case R92S_EVT_DEL_STA: RSU_DPRINTF(sc, RSU_DEBUG_FWCMD | RSU_DEBUG_STATE, "%s: disassociated from %s\n", __func__, ether_sprintf(buf)); if (vap->iv_state == IEEE80211_S_RUN && IEEE80211_ADDR_EQ(vap->iv_bss->ni_bssid, buf)) { RSU_UNLOCK(sc); ieee80211_new_state(vap, IEEE80211_S_SCAN, -1); RSU_LOCK(sc); } break; case R92S_EVT_WPS_PBC: RSU_DPRINTF(sc, RSU_DEBUG_RX | RSU_DEBUG_FWCMD, "%s: WPS PBC pushed.\n", __func__); break; case R92S_EVT_FWDBG: buf[60] = '\0'; RSU_DPRINTF(sc, RSU_DEBUG_FWDBG, "FWDBG: %s\n", (char *)buf); break; case R92S_EVT_ADDBA_REQ_REPORT: rsu_event_addba_req_report(sc, buf, len); break; default: device_printf(sc->sc_dev, "%s: unhandled code (%d)\n", __func__, code); break; } } static void rsu_rx_multi_event(struct rsu_softc *sc, uint8_t *buf, int len) { struct r92s_fw_cmd_hdr *cmd; int cmdsz; RSU_DPRINTF(sc, RSU_DEBUG_RX, "%s: Rx events len=%d\n", __func__, len); /* Skip Rx status. */ buf += sizeof(struct r92s_rx_stat); len -= sizeof(struct r92s_rx_stat); /* Process all events. */ for (;;) { /* Check that command header fits. */ if (__predict_false(len < sizeof(*cmd))) break; cmd = (struct r92s_fw_cmd_hdr *)buf; /* Check that command payload fits. */ cmdsz = le16toh(cmd->len); if (__predict_false(len < sizeof(*cmd) + cmdsz)) break; /* Process firmware event. */ rsu_rx_event(sc, cmd->code, (uint8_t *)&cmd[1], cmdsz); if (!(cmd->seq & R92S_FW_CMD_MORE)) break; buf += sizeof(*cmd) + cmdsz; len -= sizeof(*cmd) + cmdsz; } } #if 0 static int8_t rsu_get_rssi(struct rsu_softc *sc, int rate, void *physt) { static const int8_t cckoff[] = { 14, -2, -20, -40 }; struct r92s_rx_phystat *phy; struct r92s_rx_cck *cck; uint8_t rpt; int8_t rssi; if (rate <= 3) { cck = (struct r92s_rx_cck *)physt; rpt = (cck->agc_rpt >> 6) & 0x3; rssi = cck->agc_rpt & 0x3e; rssi = cckoff[rpt] - rssi; } else { /* OFDM/HT. */ phy = (struct r92s_rx_phystat *)physt; rssi = ((le32toh(phy->phydw1) >> 1) & 0x7f) - 106; } return (rssi); } #endif static struct mbuf * rsu_rx_frame(struct rsu_softc *sc, uint8_t *buf, int pktlen) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_frame *wh; struct r92s_rx_stat *stat; uint32_t rxdw0, rxdw3; struct mbuf *m; uint8_t rate; int infosz; stat = (struct r92s_rx_stat *)buf; rxdw0 = le32toh(stat->rxdw0); rxdw3 = le32toh(stat->rxdw3); if (__predict_false(rxdw0 & R92S_RXDW0_CRCERR)) { counter_u64_add(ic->ic_ierrors, 1); return NULL; } if (__predict_false(pktlen < sizeof(*wh) || pktlen > MCLBYTES)) { counter_u64_add(ic->ic_ierrors, 1); return NULL; } rate = MS(rxdw3, R92S_RXDW3_RATE); infosz = MS(rxdw0, R92S_RXDW0_INFOSZ) * 8; #if 0 /* Get RSSI from PHY status descriptor if present. */ if (infosz != 0) *rssi = rsu_get_rssi(sc, rate, &stat[1]); else *rssi = 0; #endif RSU_DPRINTF(sc, RSU_DEBUG_RX, "%s: Rx frame len=%d rate=%d infosz=%d\n", __func__, pktlen, rate, infosz); m = m_get2(pktlen, M_NOWAIT, MT_DATA, M_PKTHDR); if (__predict_false(m == NULL)) { counter_u64_add(ic->ic_ierrors, 1); return NULL; } /* Hardware does Rx TCP checksum offload. */ if (rxdw3 & R92S_RXDW3_TCPCHKVALID) { if (__predict_true(rxdw3 & R92S_RXDW3_TCPCHKRPT)) m->m_pkthdr.csum_flags |= CSUM_DATA_VALID; } wh = (struct ieee80211_frame *)((uint8_t *)&stat[1] + infosz); memcpy(mtod(m, uint8_t *), wh, pktlen); m->m_pkthdr.len = m->m_len = pktlen; if (ieee80211_radiotap_active(ic)) { struct rsu_rx_radiotap_header *tap = &sc->sc_rxtap; /* Map HW rate index to 802.11 rate. */ tap->wr_flags = 2; if (!(rxdw3 & R92S_RXDW3_HTC)) { switch (rate) { /* CCK. */ case 0: tap->wr_rate = 2; break; case 1: tap->wr_rate = 4; break; case 2: tap->wr_rate = 11; break; case 3: tap->wr_rate = 22; break; /* OFDM. */ case 4: tap->wr_rate = 12; break; case 5: tap->wr_rate = 18; break; case 6: tap->wr_rate = 24; break; case 7: tap->wr_rate = 36; break; case 8: tap->wr_rate = 48; break; case 9: tap->wr_rate = 72; break; case 10: tap->wr_rate = 96; break; case 11: tap->wr_rate = 108; break; } } else if (rate >= 12) { /* MCS0~15. */ /* Bit 7 set means HT MCS instead of rate. */ tap->wr_rate = 0x80 | (rate - 12); } #if 0 tap->wr_dbm_antsignal = *rssi; #endif /* XXX not nice */ tap->wr_dbm_antsignal = rsu_hwrssi_to_rssi(sc, sc->sc_currssi); tap->wr_chan_freq = htole16(ic->ic_curchan->ic_freq); tap->wr_chan_flags = htole16(ic->ic_curchan->ic_flags); } return (m); } static struct mbuf * rsu_rx_multi_frame(struct rsu_softc *sc, uint8_t *buf, int len) { struct r92s_rx_stat *stat; uint32_t rxdw0; int totlen, pktlen, infosz, npkts; struct mbuf *m, *m0 = NULL, *prevm = NULL; /* Get the number of encapsulated frames. */ stat = (struct r92s_rx_stat *)buf; npkts = MS(le32toh(stat->rxdw2), R92S_RXDW2_PKTCNT); RSU_DPRINTF(sc, RSU_DEBUG_RX, "%s: Rx %d frames in one chunk\n", __func__, npkts); /* Process all of them. */ while (npkts-- > 0) { if (__predict_false(len < sizeof(*stat))) break; stat = (struct r92s_rx_stat *)buf; rxdw0 = le32toh(stat->rxdw0); pktlen = MS(rxdw0, R92S_RXDW0_PKTLEN); if (__predict_false(pktlen == 0)) break; infosz = MS(rxdw0, R92S_RXDW0_INFOSZ) * 8; /* Make sure everything fits in xfer. */ totlen = sizeof(*stat) + infosz + pktlen; if (__predict_false(totlen > len)) break; /* Process 802.11 frame. */ m = rsu_rx_frame(sc, buf, pktlen); if (m0 == NULL) m0 = m; if (prevm == NULL) prevm = m; else { prevm->m_next = m; prevm = m; } /* Next chunk is 128-byte aligned. */ totlen = (totlen + 127) & ~127; buf += totlen; len -= totlen; } return (m0); } static struct mbuf * rsu_rxeof(struct usb_xfer *xfer, struct rsu_data *data) { struct rsu_softc *sc = data->sc; struct ieee80211com *ic = &sc->sc_ic; struct r92s_rx_stat *stat; int len; usbd_xfer_status(xfer, &len, NULL, NULL, NULL); if (__predict_false(len < sizeof(*stat))) { DPRINTF("xfer too short %d\n", len); counter_u64_add(ic->ic_ierrors, 1); return (NULL); } /* Determine if it is a firmware C2H event or an 802.11 frame. */ stat = (struct r92s_rx_stat *)data->buf; if ((le32toh(stat->rxdw1) & 0x1ff) == 0x1ff) { rsu_rx_multi_event(sc, data->buf, len); /* No packets to process. */ return (NULL); } else return (rsu_rx_multi_frame(sc, data->buf, len)); } static void rsu_bulk_rx_callback(struct usb_xfer *xfer, usb_error_t error) { struct rsu_softc *sc = usbd_xfer_softc(xfer); struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_frame *wh; struct ieee80211_node *ni; struct mbuf *m = NULL, *next; struct rsu_data *data; RSU_ASSERT_LOCKED(sc); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: data = STAILQ_FIRST(&sc->sc_rx_active); if (data == NULL) goto tr_setup; STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next); m = rsu_rxeof(xfer, data); STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: /* * XXX TODO: if we have an mbuf list, but then * we hit data == NULL, what now? */ data = STAILQ_FIRST(&sc->sc_rx_inactive); if (data == NULL) { KASSERT(m == NULL, ("mbuf isn't NULL")); return; } STAILQ_REMOVE_HEAD(&sc->sc_rx_inactive, next); STAILQ_INSERT_TAIL(&sc->sc_rx_active, data, next); usbd_xfer_set_frame_data(xfer, 0, data->buf, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); /* * To avoid LOR we should unlock our private mutex here to call * ieee80211_input() because here is at the end of a USB * callback and safe to unlock. */ RSU_UNLOCK(sc); while (m != NULL) { int rssi; /* Cheat and get the last calibrated RSSI */ rssi = rsu_hwrssi_to_rssi(sc, sc->sc_currssi); next = m->m_next; m->m_next = NULL; wh = mtod(m, struct ieee80211_frame *); ni = ieee80211_find_rxnode(ic, (struct ieee80211_frame_min *)wh); if (ni != NULL) { if (ni->ni_flags & IEEE80211_NODE_HT) m->m_flags |= M_AMPDU; (void)ieee80211_input(ni, m, rssi, -96); ieee80211_free_node(ni); } else (void)ieee80211_input_all(ic, m, rssi, -96); m = next; } RSU_LOCK(sc); break; default: /* needs it to the inactive queue due to a error. */ data = STAILQ_FIRST(&sc->sc_rx_active); if (data != NULL) { STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next); STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next); } if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); counter_u64_add(ic->ic_ierrors, 1); goto tr_setup; } break; } } static void rsu_txeof(struct usb_xfer *xfer, struct rsu_data *data) { #ifdef USB_DEBUG struct rsu_softc *sc = usbd_xfer_softc(xfer); #endif RSU_DPRINTF(sc, RSU_DEBUG_TXDONE, "%s: called; data=%p\n", __func__, data); if (data->m) { /* XXX status? */ ieee80211_tx_complete(data->ni, data->m, 0); data->m = NULL; data->ni = NULL; } } static void rsu_bulk_tx_callback_sub(struct usb_xfer *xfer, usb_error_t error, uint8_t which) { struct rsu_softc *sc = usbd_xfer_softc(xfer); struct ieee80211com *ic = &sc->sc_ic; struct rsu_data *data; RSU_ASSERT_LOCKED(sc); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: data = STAILQ_FIRST(&sc->sc_tx_active[which]); if (data == NULL) goto tr_setup; RSU_DPRINTF(sc, RSU_DEBUG_TXDONE, "%s: transfer done %p\n", __func__, data); STAILQ_REMOVE_HEAD(&sc->sc_tx_active[which], next); rsu_txeof(xfer, data); rsu_freebuf(sc, data); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: data = STAILQ_FIRST(&sc->sc_tx_pending[which]); if (data == NULL) { RSU_DPRINTF(sc, RSU_DEBUG_TXDONE, "%s: empty pending queue sc %p\n", __func__, sc); return; } STAILQ_REMOVE_HEAD(&sc->sc_tx_pending[which], next); STAILQ_INSERT_TAIL(&sc->sc_tx_active[which], data, next); usbd_xfer_set_frame_data(xfer, 0, data->buf, data->buflen); RSU_DPRINTF(sc, RSU_DEBUG_TXDONE, "%s: submitting transfer %p\n", __func__, data); usbd_transfer_submit(xfer); break; default: data = STAILQ_FIRST(&sc->sc_tx_active[which]); if (data != NULL) { STAILQ_REMOVE_HEAD(&sc->sc_tx_active[which], next); rsu_txeof(xfer, data); rsu_freebuf(sc, data); } counter_u64_add(ic->ic_oerrors, 1); if (error != USB_ERR_CANCELLED) { usbd_xfer_set_stall(xfer); goto tr_setup; } break; } /* * XXX TODO: if the queue is low, flush out FF TX frames. * Remember to unlock the driver for now; net80211 doesn't * defer it for us. */ } static void rsu_bulk_tx_callback_be_bk(struct usb_xfer *xfer, usb_error_t error) { struct rsu_softc *sc = usbd_xfer_softc(xfer); rsu_bulk_tx_callback_sub(xfer, error, RSU_BULK_TX_BE_BK); /* This kicks the TX taskqueue */ rsu_start(sc); } static void rsu_bulk_tx_callback_vi_vo(struct usb_xfer *xfer, usb_error_t error) { struct rsu_softc *sc = usbd_xfer_softc(xfer); rsu_bulk_tx_callback_sub(xfer, error, RSU_BULK_TX_VI_VO); /* This kicks the TX taskqueue */ rsu_start(sc); } static void rsu_bulk_tx_callback_h2c(struct usb_xfer *xfer, usb_error_t error) { struct rsu_softc *sc = usbd_xfer_softc(xfer); rsu_bulk_tx_callback_sub(xfer, error, RSU_BULK_TX_H2C); /* This kicks the TX taskqueue */ rsu_start(sc); } /* * Transmit the given frame. * * This doesn't free the node or mbuf upon failure. */ static int rsu_tx_start(struct rsu_softc *sc, struct ieee80211_node *ni, struct mbuf *m0, struct rsu_data *data) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_frame *wh; struct ieee80211_key *k = NULL; struct r92s_tx_desc *txd; uint8_t type; int prio = 0; uint8_t which; int hasqos; int xferlen; int qid; RSU_ASSERT_LOCKED(sc); wh = mtod(m0, struct ieee80211_frame *); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; RSU_DPRINTF(sc, RSU_DEBUG_TX, "%s: data=%p, m=%p\n", __func__, data, m0); if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { k = ieee80211_crypto_encap(ni, m0); if (k == NULL) { device_printf(sc->sc_dev, "ieee80211_crypto_encap returns NULL.\n"); /* XXX we don't expect the fragmented frames */ return (ENOBUFS); } wh = mtod(m0, struct ieee80211_frame *); } /* If we have QoS then use it */ /* XXX TODO: mbuf WME/PRI versus TID? */ if (IEEE80211_QOS_HAS_SEQ(wh)) { /* Has QoS */ prio = M_WME_GETAC(m0); which = rsu_wme_ac_xfer_map[prio]; hasqos = 1; } else { /* Non-QoS TID */ /* XXX TODO: tid=0 for non-qos TID? */ which = rsu_wme_ac_xfer_map[WME_AC_BE]; hasqos = 0; prio = 0; } qid = rsu_ac2qid[prio]; #if 0 switch (type) { case IEEE80211_FC0_TYPE_CTL: case IEEE80211_FC0_TYPE_MGT: which = rsu_wme_ac_xfer_map[WME_AC_VO]; break; default: which = rsu_wme_ac_xfer_map[M_WME_GETAC(m0)]; break; } hasqos = 0; #endif RSU_DPRINTF(sc, RSU_DEBUG_TX, "%s: pri=%d, which=%d, hasqos=%d\n", __func__, prio, which, hasqos); /* Fill Tx descriptor. */ txd = (struct r92s_tx_desc *)data->buf; memset(txd, 0, sizeof(*txd)); txd->txdw0 |= htole32( SM(R92S_TXDW0_PKTLEN, m0->m_pkthdr.len) | SM(R92S_TXDW0_OFFSET, sizeof(*txd)) | R92S_TXDW0_OWN | R92S_TXDW0_FSG | R92S_TXDW0_LSG); txd->txdw1 |= htole32( SM(R92S_TXDW1_MACID, R92S_MACID_BSS) | SM(R92S_TXDW1_QSEL, qid)); if (!hasqos) txd->txdw1 |= htole32(R92S_TXDW1_NONQOS); #ifdef notyet if (k != NULL) { switch (k->wk_cipher->ic_cipher) { case IEEE80211_CIPHER_WEP: cipher = R92S_TXDW1_CIPHER_WEP; break; case IEEE80211_CIPHER_TKIP: cipher = R92S_TXDW1_CIPHER_TKIP; break; case IEEE80211_CIPHER_AES_CCM: cipher = R92S_TXDW1_CIPHER_AES; break; default: cipher = R92S_TXDW1_CIPHER_NONE; } txd->txdw1 |= htole32( SM(R92S_TXDW1_CIPHER, cipher) | SM(R92S_TXDW1_KEYIDX, k->k_id)); } #endif /* XXX todo: set AGGEN bit if appropriate? */ txd->txdw2 |= htole32(R92S_TXDW2_BK); if (IEEE80211_IS_MULTICAST(wh->i_addr1)) txd->txdw2 |= htole32(R92S_TXDW2_BMCAST); /* * Firmware will use and increment the sequence number for the * specified priority. */ txd->txdw3 |= htole32(SM(R92S_TXDW3_SEQ, prio)); if (ieee80211_radiotap_active_vap(vap)) { struct rsu_tx_radiotap_header *tap = &sc->sc_txtap; tap->wt_flags = 0; tap->wt_chan_freq = htole16(ic->ic_curchan->ic_freq); tap->wt_chan_flags = htole16(ic->ic_curchan->ic_flags); ieee80211_radiotap_tx(vap, m0); } xferlen = sizeof(*txd) + m0->m_pkthdr.len; m_copydata(m0, 0, m0->m_pkthdr.len, (caddr_t)&txd[1]); data->buflen = xferlen; data->ni = ni; data->m = m0; STAILQ_INSERT_TAIL(&sc->sc_tx_pending[which], data, next); /* start transfer, if any */ usbd_transfer_start(sc->sc_xfer[which]); return (0); } static int rsu_transmit(struct ieee80211com *ic, struct mbuf *m) { struct rsu_softc *sc = ic->ic_softc; int error; RSU_LOCK(sc); if (!sc->sc_running) { RSU_UNLOCK(sc); return (ENXIO); } /* * XXX TODO: ensure that we treat 'm' as a list of frames * to transmit! */ error = mbufq_enqueue(&sc->sc_snd, m); if (error) { RSU_DPRINTF(sc, RSU_DEBUG_TX, "%s: mbufq_enable: failed (%d)\n", __func__, error); RSU_UNLOCK(sc); return (error); } RSU_UNLOCK(sc); /* This kicks the TX taskqueue */ rsu_start(sc); return (0); } static void rsu_drain_mbufq(struct rsu_softc *sc) { struct mbuf *m; struct ieee80211_node *ni; RSU_ASSERT_LOCKED(sc); while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) { ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; m->m_pkthdr.rcvif = NULL; ieee80211_free_node(ni); m_freem(m); } } static void _rsu_start(struct rsu_softc *sc) { struct ieee80211_node *ni; struct rsu_data *bf; struct mbuf *m; RSU_ASSERT_LOCKED(sc); while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) { bf = rsu_getbuf(sc); if (bf == NULL) { RSU_DPRINTF(sc, RSU_DEBUG_TX, "%s: failed to get buffer\n", __func__); mbufq_prepend(&sc->sc_snd, m); break; } ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; m->m_pkthdr.rcvif = NULL; if (rsu_tx_start(sc, ni, m, bf) != 0) { RSU_DPRINTF(sc, RSU_DEBUG_TX, "%s: failed to transmit\n", __func__); if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1); rsu_freebuf(sc, bf); ieee80211_free_node(ni); m_freem(m); break; } } } static void rsu_start(struct rsu_softc *sc) { taskqueue_enqueue(taskqueue_thread, &sc->tx_task); } static void rsu_parent(struct ieee80211com *ic) { struct rsu_softc *sc = ic->ic_softc; int startall = 0; RSU_LOCK(sc); if (ic->ic_nrunning > 0) { if (!sc->sc_running) { rsu_init(sc); startall = 1; } } else if (sc->sc_running) rsu_stop(sc); RSU_UNLOCK(sc); if (startall) ieee80211_start_all(ic); } /* * Power on sequence for A-cut adapters. */ static void rsu_power_on_acut(struct rsu_softc *sc) { uint32_t reg; rsu_write_1(sc, R92S_SPS0_CTRL + 1, 0x53); rsu_write_1(sc, R92S_SPS0_CTRL + 0, 0x57); /* Enable AFE macro block's bandgap and Mbias. */ rsu_write_1(sc, R92S_AFE_MISC, rsu_read_1(sc, R92S_AFE_MISC) | R92S_AFE_MISC_BGEN | R92S_AFE_MISC_MBEN); /* Enable LDOA15 block. */ rsu_write_1(sc, R92S_LDOA15_CTRL, rsu_read_1(sc, R92S_LDOA15_CTRL) | R92S_LDA15_EN); rsu_write_1(sc, R92S_SPS1_CTRL, rsu_read_1(sc, R92S_SPS1_CTRL) | R92S_SPS1_LDEN); rsu_ms_delay(sc, 2000); /* Enable switch regulator block. */ rsu_write_1(sc, R92S_SPS1_CTRL, rsu_read_1(sc, R92S_SPS1_CTRL) | R92S_SPS1_SWEN); rsu_write_4(sc, R92S_SPS1_CTRL, 0x00a7b267); rsu_write_1(sc, R92S_SYS_ISO_CTRL + 1, rsu_read_1(sc, R92S_SYS_ISO_CTRL + 1) | 0x08); rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x20); rsu_write_1(sc, R92S_SYS_ISO_CTRL + 1, rsu_read_1(sc, R92S_SYS_ISO_CTRL + 1) & ~0x90); /* Enable AFE clock. */ rsu_write_1(sc, R92S_AFE_XTAL_CTRL + 1, rsu_read_1(sc, R92S_AFE_XTAL_CTRL + 1) & ~0x04); /* Enable AFE PLL macro block. */ rsu_write_1(sc, R92S_AFE_PLL_CTRL, rsu_read_1(sc, R92S_AFE_PLL_CTRL) | 0x11); /* Attach AFE PLL to MACTOP/BB. */ rsu_write_1(sc, R92S_SYS_ISO_CTRL, rsu_read_1(sc, R92S_SYS_ISO_CTRL) & ~0x11); /* Switch to 40MHz clock instead of 80MHz. */ rsu_write_2(sc, R92S_SYS_CLKR, rsu_read_2(sc, R92S_SYS_CLKR) & ~R92S_SYS_CLKSEL); /* Enable MAC clock. */ rsu_write_2(sc, R92S_SYS_CLKR, rsu_read_2(sc, R92S_SYS_CLKR) | R92S_MAC_CLK_EN | R92S_SYS_CLK_EN); rsu_write_1(sc, R92S_PMC_FSM, 0x02); /* Enable digital core and IOREG R/W. */ rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x08); rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x80); /* Switch the control path to firmware. */ reg = rsu_read_2(sc, R92S_SYS_CLKR); reg = (reg & ~R92S_SWHW_SEL) | R92S_FWHW_SEL; rsu_write_2(sc, R92S_SYS_CLKR, reg); rsu_write_2(sc, R92S_CR, 0x37fc); /* Fix USB RX FIFO issue. */ rsu_write_1(sc, 0xfe5c, rsu_read_1(sc, 0xfe5c) | 0x80); rsu_write_1(sc, 0x00ab, rsu_read_1(sc, 0x00ab) | 0xc0); rsu_write_1(sc, R92S_SYS_CLKR, rsu_read_1(sc, R92S_SYS_CLKR) & ~R92S_SYS_CPU_CLKSEL); } /* * Power on sequence for B-cut and C-cut adapters. */ static void rsu_power_on_bcut(struct rsu_softc *sc) { uint32_t reg; int ntries; /* Prevent eFuse leakage. */ rsu_write_1(sc, 0x37, 0xb0); rsu_ms_delay(sc, 10); rsu_write_1(sc, 0x37, 0x30); /* Switch the control path to hardware. */ reg = rsu_read_2(sc, R92S_SYS_CLKR); if (reg & R92S_FWHW_SEL) { rsu_write_2(sc, R92S_SYS_CLKR, reg & ~(R92S_SWHW_SEL | R92S_FWHW_SEL)); } rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) & ~0x8c); rsu_ms_delay(sc, 1); rsu_write_1(sc, R92S_SPS0_CTRL + 1, 0x53); rsu_write_1(sc, R92S_SPS0_CTRL + 0, 0x57); reg = rsu_read_1(sc, R92S_AFE_MISC); rsu_write_1(sc, R92S_AFE_MISC, reg | R92S_AFE_MISC_BGEN); rsu_write_1(sc, R92S_AFE_MISC, reg | R92S_AFE_MISC_BGEN | R92S_AFE_MISC_MBEN | R92S_AFE_MISC_I32_EN); /* Enable PLL. */ rsu_write_1(sc, R92S_LDOA15_CTRL, rsu_read_1(sc, R92S_LDOA15_CTRL) | R92S_LDA15_EN); rsu_write_1(sc, R92S_LDOV12D_CTRL, rsu_read_1(sc, R92S_LDOV12D_CTRL) | R92S_LDV12_EN); rsu_write_1(sc, R92S_SYS_ISO_CTRL + 1, rsu_read_1(sc, R92S_SYS_ISO_CTRL + 1) | 0x08); rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x20); /* Support 64KB IMEM. */ rsu_write_1(sc, R92S_SYS_ISO_CTRL + 1, rsu_read_1(sc, R92S_SYS_ISO_CTRL + 1) & ~0x97); /* Enable AFE clock. */ rsu_write_1(sc, R92S_AFE_XTAL_CTRL + 1, rsu_read_1(sc, R92S_AFE_XTAL_CTRL + 1) & ~0x04); /* Enable AFE PLL macro block. */ reg = rsu_read_1(sc, R92S_AFE_PLL_CTRL); rsu_write_1(sc, R92S_AFE_PLL_CTRL, reg | 0x11); rsu_ms_delay(sc, 1); rsu_write_1(sc, R92S_AFE_PLL_CTRL, reg | 0x51); rsu_ms_delay(sc, 1); rsu_write_1(sc, R92S_AFE_PLL_CTRL, reg | 0x11); rsu_ms_delay(sc, 1); /* Attach AFE PLL to MACTOP/BB. */ rsu_write_1(sc, R92S_SYS_ISO_CTRL, rsu_read_1(sc, R92S_SYS_ISO_CTRL) & ~0x11); /* Switch to 40MHz clock. */ rsu_write_1(sc, R92S_SYS_CLKR, 0x00); /* Disable CPU clock and 80MHz SSC. */ rsu_write_1(sc, R92S_SYS_CLKR, rsu_read_1(sc, R92S_SYS_CLKR) | 0xa0); /* Enable MAC clock. */ rsu_write_2(sc, R92S_SYS_CLKR, rsu_read_2(sc, R92S_SYS_CLKR) | R92S_MAC_CLK_EN | R92S_SYS_CLK_EN); rsu_write_1(sc, R92S_PMC_FSM, 0x02); /* Enable digital core and IOREG R/W. */ rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x08); rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x80); /* Switch the control path to firmware. */ reg = rsu_read_2(sc, R92S_SYS_CLKR); reg = (reg & ~R92S_SWHW_SEL) | R92S_FWHW_SEL; rsu_write_2(sc, R92S_SYS_CLKR, reg); rsu_write_2(sc, R92S_CR, 0x37fc); /* Fix USB RX FIFO issue. */ rsu_write_1(sc, 0xfe5c, rsu_read_1(sc, 0xfe5c) | 0x80); rsu_write_1(sc, R92S_SYS_CLKR, rsu_read_1(sc, R92S_SYS_CLKR) & ~R92S_SYS_CPU_CLKSEL); rsu_write_1(sc, 0xfe1c, 0x80); /* Make sure TxDMA is ready to download firmware. */ for (ntries = 0; ntries < 20; ntries++) { reg = rsu_read_1(sc, R92S_TCR); if ((reg & (R92S_TCR_IMEM_CHK_RPT | R92S_TCR_EMEM_CHK_RPT)) == (R92S_TCR_IMEM_CHK_RPT | R92S_TCR_EMEM_CHK_RPT)) break; rsu_ms_delay(sc, 1); } if (ntries == 20) { RSU_DPRINTF(sc, RSU_DEBUG_RESET | RSU_DEBUG_TX, "%s: TxDMA is not ready\n", __func__); /* Reset TxDMA. */ reg = rsu_read_1(sc, R92S_CR); rsu_write_1(sc, R92S_CR, reg & ~R92S_CR_TXDMA_EN); rsu_ms_delay(sc, 1); rsu_write_1(sc, R92S_CR, reg | R92S_CR_TXDMA_EN); } } static void rsu_power_off(struct rsu_softc *sc) { /* Turn RF off. */ rsu_write_1(sc, R92S_RF_CTRL, 0x00); rsu_ms_delay(sc, 5); /* Turn MAC off. */ /* Switch control path. */ rsu_write_1(sc, R92S_SYS_CLKR + 1, 0x38); /* Reset MACTOP. */ rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, 0x70); rsu_write_1(sc, R92S_PMC_FSM, 0x06); rsu_write_1(sc, R92S_SYS_ISO_CTRL + 0, 0xf9); rsu_write_1(sc, R92S_SYS_ISO_CTRL + 1, 0xe8); /* Disable AFE PLL. */ rsu_write_1(sc, R92S_AFE_PLL_CTRL, 0x00); /* Disable A15V. */ rsu_write_1(sc, R92S_LDOA15_CTRL, 0x54); /* Disable eFuse 1.2V. */ rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, 0x50); rsu_write_1(sc, R92S_LDOV12D_CTRL, 0x24); /* Enable AFE macro block's bandgap and Mbias. */ rsu_write_1(sc, R92S_AFE_MISC, 0x30); /* Disable 1.6V LDO. */ rsu_write_1(sc, R92S_SPS0_CTRL + 0, 0x56); rsu_write_1(sc, R92S_SPS0_CTRL + 1, 0x43); /* Firmware - tell it to switch things off */ (void) rsu_set_fw_power_state(sc, RSU_PWR_OFF); } static int rsu_fw_loadsection(struct rsu_softc *sc, const uint8_t *buf, int len) { const uint8_t which = rsu_wme_ac_xfer_map[WME_AC_VO]; struct rsu_data *data; struct r92s_tx_desc *txd; int mlen; while (len > 0) { data = rsu_getbuf(sc); if (data == NULL) return (ENOMEM); txd = (struct r92s_tx_desc *)data->buf; memset(txd, 0, sizeof(*txd)); if (len <= RSU_TXBUFSZ - sizeof(*txd)) { /* Last chunk. */ txd->txdw0 |= htole32(R92S_TXDW0_LINIP); mlen = len; } else mlen = RSU_TXBUFSZ - sizeof(*txd); txd->txdw0 |= htole32(SM(R92S_TXDW0_PKTLEN, mlen)); memcpy(&txd[1], buf, mlen); data->buflen = sizeof(*txd) + mlen; RSU_DPRINTF(sc, RSU_DEBUG_TX | RSU_DEBUG_FW | RSU_DEBUG_RESET, "%s: starting transfer %p\n", __func__, data); STAILQ_INSERT_TAIL(&sc->sc_tx_pending[which], data, next); buf += mlen; len -= mlen; } usbd_transfer_start(sc->sc_xfer[which]); return (0); } static int rsu_load_firmware(struct rsu_softc *sc) { const struct r92s_fw_hdr *hdr; struct r92s_fw_priv *dmem; struct ieee80211com *ic = &sc->sc_ic; const uint8_t *imem, *emem; int imemsz, ememsz; const struct firmware *fw; size_t size; uint32_t reg; int ntries, error; if (rsu_read_1(sc, R92S_TCR) & R92S_TCR_FWRDY) { RSU_DPRINTF(sc, RSU_DEBUG_ANY, "%s: Firmware already loaded\n", __func__); return (0); } RSU_UNLOCK(sc); /* Read firmware image from the filesystem. */ if ((fw = firmware_get("rsu-rtl8712fw")) == NULL) { device_printf(sc->sc_dev, "%s: failed load firmware of file rsu-rtl8712fw\n", __func__); RSU_LOCK(sc); return (ENXIO); } RSU_LOCK(sc); size = fw->datasize; if (size < sizeof(*hdr)) { device_printf(sc->sc_dev, "firmware too short\n"); error = EINVAL; goto fail; } hdr = (const struct r92s_fw_hdr *)fw->data; if (hdr->signature != htole16(0x8712) && hdr->signature != htole16(0x8192)) { device_printf(sc->sc_dev, "invalid firmware signature 0x%x\n", le16toh(hdr->signature)); error = EINVAL; goto fail; } DPRINTF("FW V%d %02x-%02x %02x:%02x\n", le16toh(hdr->version), hdr->month, hdr->day, hdr->hour, hdr->minute); /* Make sure that driver and firmware are in sync. */ if (hdr->privsz != htole32(sizeof(*dmem))) { device_printf(sc->sc_dev, "unsupported firmware image\n"); error = EINVAL; goto fail; } /* Get FW sections sizes. */ imemsz = le32toh(hdr->imemsz); ememsz = le32toh(hdr->sramsz); /* Check that all FW sections fit in image. */ if (size < sizeof(*hdr) + imemsz + ememsz) { device_printf(sc->sc_dev, "firmware too short\n"); error = EINVAL; goto fail; } imem = (const uint8_t *)&hdr[1]; emem = imem + imemsz; /* Load IMEM section. */ error = rsu_fw_loadsection(sc, imem, imemsz); if (error != 0) { device_printf(sc->sc_dev, "could not load firmware section %s\n", "IMEM"); goto fail; } /* Wait for load to complete. */ for (ntries = 0; ntries != 50; ntries++) { rsu_ms_delay(sc, 10); reg = rsu_read_1(sc, R92S_TCR); if (reg & R92S_TCR_IMEM_CODE_DONE) break; } if (ntries == 50) { device_printf(sc->sc_dev, "timeout waiting for IMEM transfer\n"); error = ETIMEDOUT; goto fail; } /* Load EMEM section. */ error = rsu_fw_loadsection(sc, emem, ememsz); if (error != 0) { device_printf(sc->sc_dev, "could not load firmware section %s\n", "EMEM"); goto fail; } /* Wait for load to complete. */ for (ntries = 0; ntries != 50; ntries++) { rsu_ms_delay(sc, 10); reg = rsu_read_2(sc, R92S_TCR); if (reg & R92S_TCR_EMEM_CODE_DONE) break; } if (ntries == 50) { device_printf(sc->sc_dev, "timeout waiting for EMEM transfer\n"); error = ETIMEDOUT; goto fail; } /* Enable CPU. */ rsu_write_1(sc, R92S_SYS_CLKR, rsu_read_1(sc, R92S_SYS_CLKR) | R92S_SYS_CPU_CLKSEL); if (!(rsu_read_1(sc, R92S_SYS_CLKR) & R92S_SYS_CPU_CLKSEL)) { device_printf(sc->sc_dev, "could not enable system clock\n"); error = EIO; goto fail; } rsu_write_2(sc, R92S_SYS_FUNC_EN, rsu_read_2(sc, R92S_SYS_FUNC_EN) | R92S_FEN_CPUEN); if (!(rsu_read_2(sc, R92S_SYS_FUNC_EN) & R92S_FEN_CPUEN)) { device_printf(sc->sc_dev, "could not enable microcontroller\n"); error = EIO; goto fail; } /* Wait for CPU to initialize. */ for (ntries = 0; ntries < 100; ntries++) { if (rsu_read_1(sc, R92S_TCR) & R92S_TCR_IMEM_RDY) break; rsu_ms_delay(sc, 1); } if (ntries == 100) { device_printf(sc->sc_dev, "timeout waiting for microcontroller\n"); error = ETIMEDOUT; goto fail; } /* Update DMEM section before loading. */ dmem = __DECONST(struct r92s_fw_priv *, &hdr->priv); memset(dmem, 0, sizeof(*dmem)); dmem->hci_sel = R92S_HCI_SEL_USB | R92S_HCI_SEL_8172; dmem->nendpoints = sc->sc_nendpoints; dmem->chip_version = sc->cut; dmem->rf_config = sc->sc_rftype; dmem->vcs_type = R92S_VCS_TYPE_AUTO; dmem->vcs_mode = R92S_VCS_MODE_RTS_CTS; dmem->turbo_mode = 0; dmem->bw40_en = !! (ic->ic_htcaps & IEEE80211_HTCAP_CHWIDTH40); dmem->amsdu2ampdu_en = !! (sc->sc_ht); dmem->ampdu_en = !! (sc->sc_ht); dmem->agg_offload = !! (sc->sc_ht); dmem->qos_en = 1; dmem->ps_offload = 1; dmem->lowpower_mode = 1; /* XXX TODO: configurable? */ /* Load DMEM section. */ error = rsu_fw_loadsection(sc, (uint8_t *)dmem, sizeof(*dmem)); if (error != 0) { device_printf(sc->sc_dev, "could not load firmware section %s\n", "DMEM"); goto fail; } /* Wait for load to complete. */ for (ntries = 0; ntries < 100; ntries++) { if (rsu_read_1(sc, R92S_TCR) & R92S_TCR_DMEM_CODE_DONE) break; rsu_ms_delay(sc, 1); } if (ntries == 100) { device_printf(sc->sc_dev, "timeout waiting for %s transfer\n", "DMEM"); error = ETIMEDOUT; goto fail; } /* Wait for firmware readiness. */ for (ntries = 0; ntries < 60; ntries++) { if (!(rsu_read_1(sc, R92S_TCR) & R92S_TCR_FWRDY)) break; rsu_ms_delay(sc, 1); } if (ntries == 60) { device_printf(sc->sc_dev, "timeout waiting for firmware readiness\n"); error = ETIMEDOUT; goto fail; } fail: firmware_put(fw, FIRMWARE_UNLOAD); return (error); } static int rsu_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = ni->ni_ic; struct rsu_softc *sc = ic->ic_softc; struct rsu_data *bf; /* prevent management frames from being sent if we're not ready */ if (!sc->sc_running) { m_freem(m); return (ENETDOWN); } RSU_LOCK(sc); bf = rsu_getbuf(sc); if (bf == NULL) { m_freem(m); RSU_UNLOCK(sc); return (ENOBUFS); } if (rsu_tx_start(sc, ni, m, bf) != 0) { m_freem(m); rsu_freebuf(sc, bf); RSU_UNLOCK(sc); return (EIO); } RSU_UNLOCK(sc); return (0); } static void rsu_init(struct rsu_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); uint8_t macaddr[IEEE80211_ADDR_LEN]; int error; int i; RSU_ASSERT_LOCKED(sc); /* Ensure the mbuf queue is drained */ rsu_drain_mbufq(sc); /* Init host async commands ring. */ sc->cmdq.cur = sc->cmdq.next = sc->cmdq.queued = 0; /* Reset power management state. */ rsu_write_1(sc, R92S_USB_HRPWM, 0); /* Power on adapter. */ if (sc->cut == 1) rsu_power_on_acut(sc); else rsu_power_on_bcut(sc); /* Load firmware. */ error = rsu_load_firmware(sc); if (error != 0) goto fail; /* Enable Rx TCP checksum offload. */ rsu_write_4(sc, R92S_RCR, rsu_read_4(sc, R92S_RCR) | 0x04000000); /* Append PHY status. */ rsu_write_4(sc, R92S_RCR, rsu_read_4(sc, R92S_RCR) | 0x02000000); rsu_write_4(sc, R92S_CR, rsu_read_4(sc, R92S_CR) & ~0xff000000); /* Use 128 bytes pages. */ rsu_write_1(sc, 0x00b5, rsu_read_1(sc, 0x00b5) | 0x01); /* Enable USB Rx aggregation. */ rsu_write_1(sc, 0x00bd, rsu_read_1(sc, 0x00bd) | 0x80); /* Set USB Rx aggregation threshold. */ rsu_write_1(sc, 0x00d9, 0x01); /* Set USB Rx aggregation timeout (1.7ms/4). */ rsu_write_1(sc, 0xfe5b, 0x04); /* Fix USB Rx FIFO issue. */ rsu_write_1(sc, 0xfe5c, rsu_read_1(sc, 0xfe5c) | 0x80); /* Set MAC address. */ IEEE80211_ADDR_COPY(macaddr, vap ? vap->iv_myaddr : ic->ic_macaddr); rsu_write_region_1(sc, R92S_MACID, macaddr, IEEE80211_ADDR_LEN); /* It really takes 1.5 seconds for the firmware to boot: */ rsu_ms_delay(sc, 2000); RSU_DPRINTF(sc, RSU_DEBUG_RESET, "%s: setting MAC address to %s\n", __func__, ether_sprintf(macaddr)); error = rsu_fw_cmd(sc, R92S_CMD_SET_MAC_ADDRESS, macaddr, IEEE80211_ADDR_LEN); if (error != 0) { device_printf(sc->sc_dev, "could not set MAC address\n"); goto fail; } /* Set PS mode fully active */ error = rsu_set_fw_power_state(sc, RSU_PWR_ACTIVE); if (error != 0) { device_printf(sc->sc_dev, "could not set PS mode\n"); goto fail; } sc->sc_scan_pass = 0; usbd_transfer_start(sc->sc_xfer[RSU_BULK_RX]); /* We're ready to go. */ sc->sc_running = 1; sc->sc_scanning = 0; return; fail: /* Need to stop all failed transfers, if any */ for (i = 0; i != RSU_N_TRANSFER; i++) usbd_transfer_stop(sc->sc_xfer[i]); } static void rsu_stop(struct rsu_softc *sc) { int i; RSU_ASSERT_LOCKED(sc); sc->sc_running = 0; sc->sc_calibrating = 0; taskqueue_cancel_timeout(taskqueue_thread, &sc->calib_task, NULL); taskqueue_cancel(taskqueue_thread, &sc->tx_task, NULL); /* Power off adapter. */ rsu_power_off(sc); for (i = 0; i < RSU_N_TRANSFER; i++) usbd_transfer_stop(sc->sc_xfer[i]); /* Ensure the mbuf queue is drained */ rsu_drain_mbufq(sc); } /* * Note: usb_pause_mtx() actually releases the mutex before calling pause(), * which breaks any kind of driver serialisation. */ static void rsu_ms_delay(struct rsu_softc *sc, int ms) { //usb_pause_mtx(&sc->sc_mtx, hz / 1000); DELAY(ms * 1000); } Index: head/sys/net80211/ieee80211_input.c =================================================================== --- head/sys/net80211/ieee80211_input.c (revision 307157) +++ head/sys/net80211/ieee80211_input.c (revision 307158) @@ -1,981 +1,972 @@ /*- * Copyright (c) 2001 Atsushi Onoe * Copyright (c) 2002-2009 Sam Leffler, Errno Consulting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_MESH #include #endif #include #ifdef INET #include #include #endif static void ieee80211_process_mimo(struct ieee80211_node *ni, struct ieee80211_rx_stats *rx) { int i; /* Verify the required MIMO bits are set */ if ((rx->r_flags & (IEEE80211_R_C_CHAIN | IEEE80211_R_C_NF | IEEE80211_R_C_RSSI)) != (IEEE80211_R_C_CHAIN | IEEE80211_R_C_NF | IEEE80211_R_C_RSSI)) return; /* XXX This assumes the MIMO radios have both ctl and ext chains */ for (i = 0; i < MIN(rx->c_chain, IEEE80211_MAX_CHAINS); i++) { IEEE80211_RSSI_LPF(ni->ni_mimo_rssi_ctl[i], rx->c_rssi_ctl[i]); IEEE80211_RSSI_LPF(ni->ni_mimo_rssi_ext[i], rx->c_rssi_ext[i]); } /* XXX This also assumes the MIMO radios have both ctl and ext chains */ for(i = 0; i < MIN(rx->c_chain, IEEE80211_MAX_CHAINS); i++) { ni->ni_mimo_noise_ctl[i] = rx->c_nf_ctl[i]; ni->ni_mimo_noise_ext[i] = rx->c_nf_ext[i]; } ni->ni_mimo_chains = rx->c_chain; } int -ieee80211_input_mimo(struct ieee80211_node *ni, struct mbuf *m, - struct ieee80211_rx_stats *rx) +ieee80211_input_mimo(struct ieee80211_node *ni, struct mbuf *m) { struct ieee80211_rx_stats rxs; - if (rx) { - memcpy(&rxs, rx, sizeof(*rx)); - } else { - /* try to read from mbuf */ - bzero(&rxs, sizeof(rxs)); - ieee80211_get_rx_params(m, &rxs); - } + /* try to read stats from mbuf */ + bzero(&rxs, sizeof(rxs)); + if (ieee80211_get_rx_params(m, &rxs) != 0) + return (-1); /* XXX should assert IEEE80211_R_NF and IEEE80211_R_RSSI are set */ ieee80211_process_mimo(ni, &rxs); //return ieee80211_input(ni, m, rx->rssi, rx->nf); return ni->ni_vap->iv_input(ni, m, &rxs, rxs.c_rssi, rxs.c_nf); } int ieee80211_input_all(struct ieee80211com *ic, struct mbuf *m, int rssi, int nf) { struct ieee80211_rx_stats rx; rx.r_flags = IEEE80211_R_NF | IEEE80211_R_RSSI; rx.c_nf = nf; rx.c_rssi = rssi; - return ieee80211_input_mimo_all(ic, m, &rx); + + if (!ieee80211_add_rx_params(m, &rx)) + return (-1); + + return ieee80211_input_mimo_all(ic, m); } int -ieee80211_input_mimo_all(struct ieee80211com *ic, struct mbuf *m, - struct ieee80211_rx_stats *rx) +ieee80211_input_mimo_all(struct ieee80211com *ic, struct mbuf *m) { - struct ieee80211_rx_stats rxs; struct ieee80211vap *vap; int type = -1; m->m_flags |= M_BCAST; /* NB: mark for bpf tap'ing */ - if (rx) { - memcpy(&rxs, rx, sizeof(*rx)); - } else { - /* try to read from mbuf */ - bzero(&rxs, sizeof(rxs)); - ieee80211_get_rx_params(m, &rxs); - } - /* XXX locking */ TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { struct ieee80211_node *ni; struct mbuf *mcopy; /* NB: could check for IFF_UP but this is cheaper */ if (vap->iv_state == IEEE80211_S_INIT) continue; /* * WDS vap's only receive directed traffic from the * station at the ``far end''. That traffic should * be passed through the AP vap the station is associated * to--so don't spam them with mcast frames. */ if (vap->iv_opmode == IEEE80211_M_WDS) continue; if (TAILQ_NEXT(vap, iv_next) != NULL) { /* * Packet contents are changed by ieee80211_decap * so do a deep copy of the packet. + * NB: tags are copied too. */ mcopy = m_dup(m, M_NOWAIT); if (mcopy == NULL) { /* XXX stat+msg */ continue; } } else { mcopy = m; m = NULL; } ni = ieee80211_ref_node(vap->iv_bss); - type = ieee80211_input_mimo(ni, mcopy, &rxs); + type = ieee80211_input_mimo(ni, mcopy); ieee80211_free_node(ni); } if (m != NULL) /* no vaps, reclaim mbuf */ m_freem(m); return type; } /* * This function reassembles fragments. * * XXX should handle 3 concurrent reassemblies per-spec. */ struct mbuf * ieee80211_defrag(struct ieee80211_node *ni, struct mbuf *m, int hdrspace) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_frame *wh = mtod(m, struct ieee80211_frame *); struct ieee80211_frame *lwh; uint16_t rxseq; uint8_t fragno; uint8_t more_frag = wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG; struct mbuf *mfrag; KASSERT(!IEEE80211_IS_MULTICAST(wh->i_addr1), ("multicast fragm?")); rxseq = le16toh(*(uint16_t *)wh->i_seq); fragno = rxseq & IEEE80211_SEQ_FRAG_MASK; /* Quick way out, if there's nothing to defragment */ if (!more_frag && fragno == 0 && ni->ni_rxfrag[0] == NULL) return m; /* * Remove frag to insure it doesn't get reaped by timer. */ if (ni->ni_table == NULL) { /* * Should never happen. If the node is orphaned (not in * the table) then input packets should not reach here. * Otherwise, a concurrent request that yanks the table * should be blocked by other interlocking and/or by first * shutting the driver down. Regardless, be defensive * here and just bail */ /* XXX need msg+stat */ m_freem(m); return NULL; } IEEE80211_NODE_LOCK(ni->ni_table); mfrag = ni->ni_rxfrag[0]; ni->ni_rxfrag[0] = NULL; IEEE80211_NODE_UNLOCK(ni->ni_table); /* * Validate new fragment is in order and * related to the previous ones. */ if (mfrag != NULL) { uint16_t last_rxseq; lwh = mtod(mfrag, struct ieee80211_frame *); last_rxseq = le16toh(*(uint16_t *)lwh->i_seq); /* NB: check seq # and frag together */ if (rxseq == last_rxseq+1 && IEEE80211_ADDR_EQ(wh->i_addr1, lwh->i_addr1) && IEEE80211_ADDR_EQ(wh->i_addr2, lwh->i_addr2)) { /* XXX clear MORE_FRAG bit? */ /* track last seqnum and fragno */ *(uint16_t *) lwh->i_seq = *(uint16_t *) wh->i_seq; m_adj(m, hdrspace); /* strip header */ m_catpkt(mfrag, m); /* concatenate */ } else { /* * Unrelated fragment or no space for it, * clear current fragments. */ m_freem(mfrag); mfrag = NULL; } } if (mfrag == NULL) { if (fragno != 0) { /* !first fragment, discard */ vap->iv_stats.is_rx_defrag++; IEEE80211_NODE_STAT(ni, rx_defrag); m_freem(m); return NULL; } mfrag = m; } if (more_frag) { /* more to come, save */ ni->ni_rxfragstamp = ticks; ni->ni_rxfrag[0] = mfrag; mfrag = NULL; } return mfrag; } void ieee80211_deliver_data(struct ieee80211vap *vap, struct ieee80211_node *ni, struct mbuf *m) { struct ether_header *eh = mtod(m, struct ether_header *); struct ifnet *ifp = vap->iv_ifp; /* clear driver/net80211 flags before passing up */ m->m_flags &= ~(M_MCAST | M_BCAST); m_clrprotoflags(m); /* NB: see hostap_deliver_data, this path doesn't handle hostap */ KASSERT(vap->iv_opmode != IEEE80211_M_HOSTAP, ("gack, hostap")); /* * Do accounting. */ if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); IEEE80211_NODE_STAT(ni, rx_data); IEEE80211_NODE_STAT_ADD(ni, rx_bytes, m->m_pkthdr.len); if (ETHER_IS_MULTICAST(eh->ether_dhost)) { if (ETHER_IS_BROADCAST(eh->ether_dhost)) m->m_flags |= M_BCAST; else m->m_flags |= M_MCAST; IEEE80211_NODE_STAT(ni, rx_mcast); } else IEEE80211_NODE_STAT(ni, rx_ucast); m->m_pkthdr.rcvif = ifp; if (ni->ni_vlan != 0) { /* attach vlan tag */ m->m_pkthdr.ether_vtag = ni->ni_vlan; m->m_flags |= M_VLANTAG; } ifp->if_input(ifp, m); } struct mbuf * ieee80211_decap(struct ieee80211vap *vap, struct mbuf *m, int hdrlen) { struct ieee80211_qosframe_addr4 wh; struct ether_header *eh; struct llc *llc; KASSERT(hdrlen <= sizeof(wh), ("hdrlen %d > max %zd", hdrlen, sizeof(wh))); if (m->m_len < hdrlen + sizeof(*llc) && (m = m_pullup(m, hdrlen + sizeof(*llc))) == NULL) { vap->iv_stats.is_rx_tooshort++; /* XXX msg */ return NULL; } memcpy(&wh, mtod(m, caddr_t), hdrlen); llc = (struct llc *)(mtod(m, caddr_t) + hdrlen); if (llc->llc_dsap == LLC_SNAP_LSAP && llc->llc_ssap == LLC_SNAP_LSAP && llc->llc_control == LLC_UI && llc->llc_snap.org_code[0] == 0 && llc->llc_snap.org_code[1] == 0 && llc->llc_snap.org_code[2] == 0 && /* NB: preserve AppleTalk frames that have a native SNAP hdr */ !(llc->llc_snap.ether_type == htons(ETHERTYPE_AARP) || llc->llc_snap.ether_type == htons(ETHERTYPE_IPX))) { m_adj(m, hdrlen + sizeof(struct llc) - sizeof(*eh)); llc = NULL; } else { m_adj(m, hdrlen - sizeof(*eh)); } eh = mtod(m, struct ether_header *); switch (wh.i_fc[1] & IEEE80211_FC1_DIR_MASK) { case IEEE80211_FC1_DIR_NODS: IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr1); IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr2); break; case IEEE80211_FC1_DIR_TODS: IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr3); IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr2); break; case IEEE80211_FC1_DIR_FROMDS: IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr1); IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr3); break; case IEEE80211_FC1_DIR_DSTODS: IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr3); IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr4); break; } #ifndef __NO_STRICT_ALIGNMENT if (!ALIGNED_POINTER(mtod(m, caddr_t) + sizeof(*eh), uint32_t)) { m = ieee80211_realign(vap, m, sizeof(*eh)); if (m == NULL) return NULL; } #endif /* !__NO_STRICT_ALIGNMENT */ if (llc != NULL) { eh = mtod(m, struct ether_header *); eh->ether_type = htons(m->m_pkthdr.len - sizeof(*eh)); } return m; } /* * Decap a frame encapsulated in a fast-frame/A-MSDU. */ struct mbuf * ieee80211_decap1(struct mbuf *m, int *framelen) { #define FF_LLC_SIZE (sizeof(struct ether_header) + sizeof(struct llc)) struct ether_header *eh; struct llc *llc; /* * The frame has an 802.3 header followed by an 802.2 * LLC header. The encapsulated frame length is in the * first header type field; save that and overwrite it * with the true type field found in the second. Then * copy the 802.3 header up to where it belongs and * adjust the mbuf contents to remove the void. */ if (m->m_len < FF_LLC_SIZE && (m = m_pullup(m, FF_LLC_SIZE)) == NULL) return NULL; eh = mtod(m, struct ether_header *); /* 802.3 header is first */ llc = (struct llc *)&eh[1]; /* 802.2 header follows */ *framelen = ntohs(eh->ether_type) /* encap'd frame size */ + sizeof(struct ether_header) - sizeof(struct llc); eh->ether_type = llc->llc_un.type_snap.ether_type; ovbcopy(eh, mtod(m, uint8_t *) + sizeof(struct llc), sizeof(struct ether_header)); m_adj(m, sizeof(struct llc)); return m; #undef FF_LLC_SIZE } /* * Install received rate set information in the node's state block. */ int ieee80211_setup_rates(struct ieee80211_node *ni, const uint8_t *rates, const uint8_t *xrates, int flags) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_rateset *rs = &ni->ni_rates; memset(rs, 0, sizeof(*rs)); rs->rs_nrates = rates[1]; memcpy(rs->rs_rates, rates + 2, rs->rs_nrates); if (xrates != NULL) { uint8_t nxrates; /* * Tack on 11g extended supported rate element. */ nxrates = xrates[1]; if (rs->rs_nrates + nxrates > IEEE80211_RATE_MAXSIZE) { nxrates = IEEE80211_RATE_MAXSIZE - rs->rs_nrates; IEEE80211_NOTE(vap, IEEE80211_MSG_XRATE, ni, "extended rate set too large; only using " "%u of %u rates", nxrates, xrates[1]); vap->iv_stats.is_rx_rstoobig++; } memcpy(rs->rs_rates + rs->rs_nrates, xrates+2, nxrates); rs->rs_nrates += nxrates; } return ieee80211_fix_rate(ni, rs, flags); } /* * Send a management frame error response to the specified * station. If ni is associated with the station then use * it; otherwise allocate a temporary node suitable for * transmitting the frame and then free the reference so * it will go away as soon as the frame has been transmitted. */ void ieee80211_send_error(struct ieee80211_node *ni, const uint8_t mac[IEEE80211_ADDR_LEN], int subtype, int arg) { struct ieee80211vap *vap = ni->ni_vap; int istmp; if (ni == vap->iv_bss) { if (vap->iv_state != IEEE80211_S_RUN) { /* * XXX hack until we get rid of this routine. * We can be called prior to the vap reaching * run state under certain conditions in which * case iv_bss->ni_chan will not be setup. * Check for this explicitly and and just ignore * the request. */ return; } ni = ieee80211_tmp_node(vap, mac); if (ni == NULL) { /* XXX msg */ return; } istmp = 1; } else istmp = 0; IEEE80211_SEND_MGMT(ni, subtype, arg); if (istmp) ieee80211_free_node(ni); } int ieee80211_alloc_challenge(struct ieee80211_node *ni) { if (ni->ni_challenge == NULL) ni->ni_challenge = (uint32_t *) IEEE80211_MALLOC(IEEE80211_CHALLENGE_LEN, M_80211_NODE, IEEE80211_M_NOWAIT); if (ni->ni_challenge == NULL) { IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, ni, "%s", "shared key challenge alloc failed"); /* XXX statistic */ } return (ni->ni_challenge != NULL); } /* * Parse a Beacon or ProbeResponse frame and return the * useful information in an ieee80211_scanparams structure. * Status is set to 0 if no problems were found; otherwise * a bitmask of IEEE80211_BPARSE_* items is returned that * describes the problems detected. */ int ieee80211_parse_beacon(struct ieee80211_node *ni, struct mbuf *m, struct ieee80211_channel *rxchan, struct ieee80211_scanparams *scan) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_frame *wh; uint8_t *frm, *efrm; wh = mtod(m, struct ieee80211_frame *); frm = (uint8_t *)&wh[1]; efrm = mtod(m, uint8_t *) + m->m_len; scan->status = 0; /* * beacon/probe response frame format * [8] time stamp * [2] beacon interval * [2] capability information * [tlv] ssid * [tlv] supported rates * [tlv] country information * [tlv] channel switch announcement (CSA) * [tlv] parameter set (FH/DS) * [tlv] erp information * [tlv] extended supported rates * [tlv] WME * [tlv] WPA or RSN * [tlv] HT capabilities * [tlv] HT information * [tlv] Atheros capabilities * [tlv] Mesh ID * [tlv] Mesh Configuration */ IEEE80211_VERIFY_LENGTH(efrm - frm, 12, return (scan->status = IEEE80211_BPARSE_BADIELEN)); memset(scan, 0, sizeof(*scan)); scan->tstamp = frm; frm += 8; scan->bintval = le16toh(*(uint16_t *)frm); frm += 2; scan->capinfo = le16toh(*(uint16_t *)frm); frm += 2; scan->bchan = ieee80211_chan2ieee(ic, rxchan); scan->chan = scan->bchan; scan->ies = frm; scan->ies_len = efrm - frm; while (efrm - frm > 1) { IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2, return (scan->status = IEEE80211_BPARSE_BADIELEN)); switch (*frm) { case IEEE80211_ELEMID_SSID: scan->ssid = frm; break; case IEEE80211_ELEMID_RATES: scan->rates = frm; break; case IEEE80211_ELEMID_COUNTRY: scan->country = frm; break; case IEEE80211_ELEMID_CSA: scan->csa = frm; break; case IEEE80211_ELEMID_QUIET: scan->quiet = frm; break; case IEEE80211_ELEMID_FHPARMS: if (ic->ic_phytype == IEEE80211_T_FH) { scan->fhdwell = le16dec(&frm[2]); scan->chan = IEEE80211_FH_CHAN(frm[4], frm[5]); scan->fhindex = frm[6]; } break; case IEEE80211_ELEMID_DSPARMS: /* * XXX hack this since depending on phytype * is problematic for multi-mode devices. */ if (ic->ic_phytype != IEEE80211_T_FH) scan->chan = frm[2]; break; case IEEE80211_ELEMID_TIM: /* XXX ATIM? */ scan->tim = frm; scan->timoff = frm - mtod(m, uint8_t *); break; case IEEE80211_ELEMID_IBSSPARMS: case IEEE80211_ELEMID_CFPARMS: case IEEE80211_ELEMID_PWRCNSTR: case IEEE80211_ELEMID_BSSLOAD: case IEEE80211_ELEMID_APCHANREP: /* NB: avoid debugging complaints */ break; case IEEE80211_ELEMID_XRATES: scan->xrates = frm; break; case IEEE80211_ELEMID_ERP: if (frm[1] != 1) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID, wh, "ERP", "bad len %u", frm[1]); vap->iv_stats.is_rx_elem_toobig++; break; } scan->erp = frm[2] | 0x100; break; case IEEE80211_ELEMID_HTCAP: scan->htcap = frm; break; case IEEE80211_ELEMID_RSN: scan->rsn = frm; break; case IEEE80211_ELEMID_HTINFO: scan->htinfo = frm; break; #ifdef IEEE80211_SUPPORT_MESH case IEEE80211_ELEMID_MESHID: scan->meshid = frm; break; case IEEE80211_ELEMID_MESHCONF: scan->meshconf = frm; break; #endif /* Extended capabilities; nothing handles it for now */ case IEEE80211_ELEMID_EXTCAP: break; case IEEE80211_ELEMID_VENDOR: if (iswpaoui(frm)) scan->wpa = frm; else if (iswmeparam(frm) || iswmeinfo(frm)) scan->wme = frm; #ifdef IEEE80211_SUPPORT_SUPERG else if (isatherosoui(frm)) scan->ath = frm; #endif #ifdef IEEE80211_SUPPORT_TDMA else if (istdmaoui(frm)) scan->tdma = frm; #endif else if (vap->iv_flags_ht & IEEE80211_FHT_HTCOMPAT) { /* * Accept pre-draft HT ie's if the * standard ones have not been seen. */ if (ishtcapoui(frm)) { if (scan->htcap == NULL) scan->htcap = frm; } else if (ishtinfooui(frm)) { if (scan->htinfo == NULL) scan->htcap = frm; } } break; default: IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID, wh, "unhandled", "id %u, len %u", *frm, frm[1]); vap->iv_stats.is_rx_elem_unknown++; break; } frm += frm[1] + 2; } IEEE80211_VERIFY_ELEMENT(scan->rates, IEEE80211_RATE_MAXSIZE, scan->status |= IEEE80211_BPARSE_RATES_INVALID); if (scan->rates != NULL && scan->xrates != NULL) { /* * NB: don't process XRATES if RATES is missing. This * avoids a potential null ptr deref and should be ok * as the return code will already note RATES is missing * (so callers shouldn't otherwise process the frame). */ IEEE80211_VERIFY_ELEMENT(scan->xrates, IEEE80211_RATE_MAXSIZE - scan->rates[1], scan->status |= IEEE80211_BPARSE_XRATES_INVALID); } IEEE80211_VERIFY_ELEMENT(scan->ssid, IEEE80211_NWID_LEN, scan->status |= IEEE80211_BPARSE_SSID_INVALID); if (scan->chan != scan->bchan && ic->ic_phytype != IEEE80211_T_FH) { /* * Frame was received on a channel different from the * one indicated in the DS params element id; * silently discard it. * * NB: this can happen due to signal leakage. * But we should take it for FH phy because * the rssi value should be correct even for * different hop pattern in FH. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_INPUT, wh, NULL, "for off-channel %u (bchan=%u)", scan->chan, scan->bchan); vap->iv_stats.is_rx_chanmismatch++; scan->status |= IEEE80211_BPARSE_OFFCHAN; } if (!(IEEE80211_BINTVAL_MIN <= scan->bintval && scan->bintval <= IEEE80211_BINTVAL_MAX)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_INPUT, wh, NULL, "bogus beacon interval (%d TU)", (int) scan->bintval); vap->iv_stats.is_rx_badbintval++; scan->status |= IEEE80211_BPARSE_BINTVAL_INVALID; } if (scan->country != NULL) { /* * Validate we have at least enough data to extract * the country code. Not sure if we should return an * error instead of discarding the IE; consider this * being lenient as we don't depend on the data for * correct operation. */ IEEE80211_VERIFY_LENGTH(scan->country[1], 3 * sizeof(uint8_t), scan->country = NULL); } if (scan->csa != NULL) { /* * Validate Channel Switch Announcement; this must * be the correct length or we toss the frame. */ IEEE80211_VERIFY_LENGTH(scan->csa[1], 3 * sizeof(uint8_t), scan->status |= IEEE80211_BPARSE_CSA_INVALID); } /* * Process HT ie's. This is complicated by our * accepting both the standard ie's and the pre-draft * vendor OUI ie's that some vendors still use/require. */ if (scan->htcap != NULL) { IEEE80211_VERIFY_LENGTH(scan->htcap[1], scan->htcap[0] == IEEE80211_ELEMID_VENDOR ? 4 + sizeof(struct ieee80211_ie_htcap)-2 : sizeof(struct ieee80211_ie_htcap)-2, scan->htcap = NULL); } if (scan->htinfo != NULL) { IEEE80211_VERIFY_LENGTH(scan->htinfo[1], scan->htinfo[0] == IEEE80211_ELEMID_VENDOR ? 4 + sizeof(struct ieee80211_ie_htinfo)-2 : sizeof(struct ieee80211_ie_htinfo)-2, scan->htinfo = NULL); } return scan->status; } /* * Parse an Action frame. Return 0 on success, non-zero on failure. */ int ieee80211_parse_action(struct ieee80211_node *ni, struct mbuf *m) { struct ieee80211vap *vap = ni->ni_vap; const struct ieee80211_action *ia; struct ieee80211_frame *wh; uint8_t *frm, *efrm; /* * action frame format: * [1] category * [1] action * [tlv] parameters */ wh = mtod(m, struct ieee80211_frame *); frm = (u_int8_t *)&wh[1]; efrm = mtod(m, u_int8_t *) + m->m_len; IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_action), return EINVAL); ia = (const struct ieee80211_action *) frm; vap->iv_stats.is_rx_action++; IEEE80211_NODE_STAT(ni, rx_action); /* verify frame payloads but defer processing */ switch (ia->ia_category) { case IEEE80211_ACTION_CAT_BA: switch (ia->ia_action) { case IEEE80211_ACTION_BA_ADDBA_REQUEST: IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_action_ba_addbarequest), return EINVAL); break; case IEEE80211_ACTION_BA_ADDBA_RESPONSE: IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_action_ba_addbaresponse), return EINVAL); break; case IEEE80211_ACTION_BA_DELBA: IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_action_ba_delba), return EINVAL); break; } break; case IEEE80211_ACTION_CAT_HT: switch (ia->ia_action) { case IEEE80211_ACTION_HT_TXCHWIDTH: IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_action_ht_txchwidth), return EINVAL); break; case IEEE80211_ACTION_HT_MIMOPWRSAVE: IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_action_ht_mimopowersave), return EINVAL); break; } break; #ifdef IEEE80211_SUPPORT_MESH case IEEE80211_ACTION_CAT_MESH: switch (ia->ia_action) { case IEEE80211_ACTION_MESH_LMETRIC: /* * XXX: verification is true only if we are using * Airtime link metric (default) */ IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_meshlmetric_ie), return EINVAL); break; case IEEE80211_ACTION_MESH_HWMP: /* verify something */ break; case IEEE80211_ACTION_MESH_GANN: IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_meshgann_ie), return EINVAL); break; case IEEE80211_ACTION_MESH_CC: case IEEE80211_ACTION_MESH_MCCA_SREQ: case IEEE80211_ACTION_MESH_MCCA_SREP: case IEEE80211_ACTION_MESH_MCCA_AREQ: case IEEE80211_ACTION_MESH_MCCA_ADVER: case IEEE80211_ACTION_MESH_MCCA_TRDOWN: case IEEE80211_ACTION_MESH_TBTT_REQ: case IEEE80211_ACTION_MESH_TBTT_RES: /* reject these early on, not implemented */ IEEE80211_DISCARD(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_INPUT, wh, NULL, "not implemented yet, act=0x%02X", ia->ia_action); return EINVAL; } break; case IEEE80211_ACTION_CAT_SELF_PROT: /* If TA or RA group address discard silently */ if (IEEE80211_IS_MULTICAST(wh->i_addr1) || IEEE80211_IS_MULTICAST(wh->i_addr2)) return EINVAL; /* * XXX: Should we verify complete length now or it is * to varying in sizes? */ switch (ia->ia_action) { case IEEE80211_ACTION_MESHPEERING_CONFIRM: case IEEE80211_ACTION_MESHPEERING_CLOSE: /* is not a peering candidate (yet) */ if (ni == vap->iv_bss) return EINVAL; break; } break; #endif } return 0; } #ifdef IEEE80211_DEBUG /* * Debugging support. */ void ieee80211_ssid_mismatch(struct ieee80211vap *vap, const char *tag, uint8_t mac[IEEE80211_ADDR_LEN], uint8_t *ssid) { printf("[%s] discard %s frame, ssid mismatch: ", ether_sprintf(mac), tag); ieee80211_print_essid(ssid + 2, ssid[1]); printf("\n"); } /* * Return the bssid of a frame. */ static const uint8_t * ieee80211_getbssid(const struct ieee80211vap *vap, const struct ieee80211_frame *wh) { if (vap->iv_opmode == IEEE80211_M_STA) return wh->i_addr2; if ((wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) != IEEE80211_FC1_DIR_NODS) return wh->i_addr1; if ((wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) == IEEE80211_FC0_SUBTYPE_PS_POLL) return wh->i_addr1; return wh->i_addr3; } #include void ieee80211_note(const struct ieee80211vap *vap, const char *fmt, ...) { char buf[128]; /* XXX */ va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if_printf(vap->iv_ifp, "%s", buf); /* NB: no \n */ } void ieee80211_note_frame(const struct ieee80211vap *vap, const struct ieee80211_frame *wh, const char *fmt, ...) { char buf[128]; /* XXX */ va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if_printf(vap->iv_ifp, "[%s] %s\n", ether_sprintf(ieee80211_getbssid(vap, wh)), buf); } void ieee80211_note_mac(const struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN], const char *fmt, ...) { char buf[128]; /* XXX */ va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if_printf(vap->iv_ifp, "[%s] %s\n", ether_sprintf(mac), buf); } void ieee80211_discard_frame(const struct ieee80211vap *vap, const struct ieee80211_frame *wh, const char *type, const char *fmt, ...) { va_list ap; if_printf(vap->iv_ifp, "[%s] discard ", ether_sprintf(ieee80211_getbssid(vap, wh))); printf("%s frame, ", type != NULL ? type : ieee80211_mgt_subtype_name(wh->i_fc[0])); va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); printf("\n"); } void ieee80211_discard_ie(const struct ieee80211vap *vap, const struct ieee80211_frame *wh, const char *type, const char *fmt, ...) { va_list ap; if_printf(vap->iv_ifp, "[%s] discard ", ether_sprintf(ieee80211_getbssid(vap, wh))); if (type != NULL) printf("%s information element, ", type); else printf("information element, "); va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); printf("\n"); } void ieee80211_discard_mac(const struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN], const char *type, const char *fmt, ...) { va_list ap; if_printf(vap->iv_ifp, "[%s] discard ", ether_sprintf(mac)); if (type != NULL) printf("%s frame, ", type); else printf("frame, "); va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); printf("\n"); } #endif /* IEEE80211_DEBUG */ Index: head/sys/net80211/ieee80211_proto.h =================================================================== --- head/sys/net80211/ieee80211_proto.h (revision 307157) +++ head/sys/net80211/ieee80211_proto.h (revision 307158) @@ -1,431 +1,429 @@ /*- * Copyright (c) 2001 Atsushi Onoe * Copyright (c) 2002-2009 Sam Leffler, Errno Consulting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. * * $FreeBSD$ */ #ifndef _NET80211_IEEE80211_PROTO_H_ #define _NET80211_IEEE80211_PROTO_H_ /* * 802.11 protocol implementation definitions. */ enum ieee80211_state { IEEE80211_S_INIT = 0, /* default state */ IEEE80211_S_SCAN = 1, /* scanning */ IEEE80211_S_AUTH = 2, /* try to authenticate */ IEEE80211_S_ASSOC = 3, /* try to assoc */ IEEE80211_S_CAC = 4, /* doing channel availability check */ IEEE80211_S_RUN = 5, /* operational (e.g. associated) */ IEEE80211_S_CSA = 6, /* channel switch announce pending */ IEEE80211_S_SLEEP = 7, /* power save */ }; #define IEEE80211_S_MAX (IEEE80211_S_SLEEP+1) #define IEEE80211_SEND_MGMT(_ni,_type,_arg) \ ((*(_ni)->ni_ic->ic_send_mgmt)(_ni, _type, _arg)) extern const char *mgt_subtype_name[]; extern const char *ctl_subtype_name[]; extern const char *ieee80211_phymode_name[IEEE80211_MODE_MAX]; extern const int ieee80211_opcap[IEEE80211_OPMODE_MAX]; static __inline const char * ieee80211_mgt_subtype_name(uint8_t subtype) { return mgt_subtype_name[(subtype & IEEE80211_FC0_SUBTYPE_MASK) >> IEEE80211_FC0_SUBTYPE_SHIFT]; } static __inline const char * ieee80211_ctl_subtype_name(uint8_t subtype) { return ctl_subtype_name[(subtype & IEEE80211_FC0_SUBTYPE_MASK) >> IEEE80211_FC0_SUBTYPE_SHIFT]; } const char *ieee80211_reason_to_string(uint16_t); void ieee80211_proto_attach(struct ieee80211com *); void ieee80211_proto_detach(struct ieee80211com *); void ieee80211_proto_vattach(struct ieee80211vap *); void ieee80211_proto_vdetach(struct ieee80211vap *); void ieee80211_promisc(struct ieee80211vap *, bool); void ieee80211_allmulti(struct ieee80211vap *, bool); void ieee80211_syncflag(struct ieee80211vap *, int flag); void ieee80211_syncflag_ht(struct ieee80211vap *, int flag); void ieee80211_syncflag_ext(struct ieee80211vap *, int flag); #define ieee80211_input(ni, m, rssi, nf) \ ((ni)->ni_vap->iv_input(ni, m, NULL, rssi, nf)) int ieee80211_input_all(struct ieee80211com *, struct mbuf *, int, int); -int ieee80211_input_mimo(struct ieee80211_node *, struct mbuf *, - struct ieee80211_rx_stats *); -int ieee80211_input_mimo_all(struct ieee80211com *, struct mbuf *, - struct ieee80211_rx_stats *); +int ieee80211_input_mimo(struct ieee80211_node *, struct mbuf *); +int ieee80211_input_mimo_all(struct ieee80211com *, struct mbuf *); struct ieee80211_bpf_params; int ieee80211_mgmt_output(struct ieee80211_node *, struct mbuf *, int, struct ieee80211_bpf_params *); int ieee80211_raw_xmit(struct ieee80211_node *, struct mbuf *, const struct ieee80211_bpf_params *); int ieee80211_output(struct ifnet *, struct mbuf *, const struct sockaddr *, struct route *ro); int ieee80211_vap_pkt_send_dest(struct ieee80211vap *, struct mbuf *, struct ieee80211_node *); int ieee80211_raw_output(struct ieee80211vap *, struct ieee80211_node *, struct mbuf *, const struct ieee80211_bpf_params *); void ieee80211_send_setup(struct ieee80211_node *, struct mbuf *, int, int, const uint8_t [IEEE80211_ADDR_LEN], const uint8_t [IEEE80211_ADDR_LEN], const uint8_t [IEEE80211_ADDR_LEN]); int ieee80211_vap_transmit(struct ifnet *ifp, struct mbuf *m); void ieee80211_vap_qflush(struct ifnet *ifp); int ieee80211_send_nulldata(struct ieee80211_node *); int ieee80211_classify(struct ieee80211_node *, struct mbuf *m); struct mbuf *ieee80211_mbuf_adjust(struct ieee80211vap *, int, struct ieee80211_key *, struct mbuf *); struct mbuf *ieee80211_encap(struct ieee80211vap *, struct ieee80211_node *, struct mbuf *); void ieee80211_free_mbuf(struct mbuf *); int ieee80211_send_mgmt(struct ieee80211_node *, int, int); struct ieee80211_appie; int ieee80211_send_probereq(struct ieee80211_node *ni, const uint8_t sa[IEEE80211_ADDR_LEN], const uint8_t da[IEEE80211_ADDR_LEN], const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t *ssid, size_t ssidlen); struct mbuf * ieee80211_ff_encap1(struct ieee80211vap *, struct mbuf *, const struct ether_header *); void ieee80211_tx_complete(struct ieee80211_node *, struct mbuf *, int); /* * The formation of ProbeResponse frames requires guidance to * deal with legacy clients. When the client is identified as * "legacy 11b" ieee80211_send_proberesp is passed this token. */ #define IEEE80211_SEND_LEGACY_11B 0x1 /* legacy 11b client */ #define IEEE80211_SEND_LEGACY_11 0x2 /* other legacy client */ #define IEEE80211_SEND_LEGACY 0x3 /* any legacy client */ struct mbuf *ieee80211_alloc_proberesp(struct ieee80211_node *, int); int ieee80211_send_proberesp(struct ieee80211vap *, const uint8_t da[IEEE80211_ADDR_LEN], int); struct mbuf *ieee80211_alloc_rts(struct ieee80211com *ic, const uint8_t [IEEE80211_ADDR_LEN], const uint8_t [IEEE80211_ADDR_LEN], uint16_t); struct mbuf *ieee80211_alloc_cts(struct ieee80211com *, const uint8_t [IEEE80211_ADDR_LEN], uint16_t); uint8_t *ieee80211_add_rates(uint8_t *, const struct ieee80211_rateset *); uint8_t *ieee80211_add_xrates(uint8_t *, const struct ieee80211_rateset *); uint8_t *ieee80211_add_ssid(uint8_t *, const uint8_t *, u_int); uint8_t *ieee80211_add_wpa(uint8_t *, const struct ieee80211vap *); uint8_t *ieee80211_add_rsn(uint8_t *, const struct ieee80211vap *); uint8_t *ieee80211_add_qos(uint8_t *, const struct ieee80211_node *); uint16_t ieee80211_getcapinfo(struct ieee80211vap *, struct ieee80211_channel *); struct ieee80211_wme_state; uint8_t * ieee80211_add_wme_info(uint8_t *frm, struct ieee80211_wme_state *wme); void ieee80211_reset_erp(struct ieee80211com *); void ieee80211_set_shortslottime(struct ieee80211com *, int onoff); int ieee80211_iserp_rateset(const struct ieee80211_rateset *); void ieee80211_setbasicrates(struct ieee80211_rateset *, enum ieee80211_phymode); void ieee80211_addbasicrates(struct ieee80211_rateset *, enum ieee80211_phymode); /* * Return the size of the 802.11 header for a management or data frame. */ static __inline int ieee80211_hdrsize(const void *data) { const struct ieee80211_frame *wh = data; int size = sizeof(struct ieee80211_frame); /* NB: we don't handle control frames */ KASSERT((wh->i_fc[0]&IEEE80211_FC0_TYPE_MASK) != IEEE80211_FC0_TYPE_CTL, ("%s: control frame", __func__)); if (IEEE80211_IS_DSTODS(wh)) size += IEEE80211_ADDR_LEN; if (IEEE80211_QOS_HAS_SEQ(wh)) size += sizeof(uint16_t); return size; } /* * Like ieee80211_hdrsize, but handles any type of frame. */ static __inline int ieee80211_anyhdrsize(const void *data) { const struct ieee80211_frame *wh = data; if ((wh->i_fc[0]&IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_CTL) { switch (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) { case IEEE80211_FC0_SUBTYPE_CTS: case IEEE80211_FC0_SUBTYPE_ACK: return sizeof(struct ieee80211_frame_ack); case IEEE80211_FC0_SUBTYPE_BAR: return sizeof(struct ieee80211_frame_bar); } return sizeof(struct ieee80211_frame_min); } else return ieee80211_hdrsize(data); } /* * Template for an in-kernel authenticator. Authenticators * register with the protocol code and are typically loaded * as separate modules as needed. One special authenticator * is xauth; it intercepts requests so that protocols like * WPA can be handled in user space. */ struct ieee80211_authenticator { const char *ia_name; /* printable name */ int (*ia_attach)(struct ieee80211vap *); void (*ia_detach)(struct ieee80211vap *); void (*ia_node_join)(struct ieee80211_node *); void (*ia_node_leave)(struct ieee80211_node *); }; void ieee80211_authenticator_register(int type, const struct ieee80211_authenticator *); void ieee80211_authenticator_unregister(int type); const struct ieee80211_authenticator *ieee80211_authenticator_get(int auth); struct ieee80211req; /* * Template for an MAC ACL policy module. Such modules * register with the protocol code and are passed the sender's * address of each received auth frame for validation. */ struct ieee80211_aclator { const char *iac_name; /* printable name */ int (*iac_attach)(struct ieee80211vap *); void (*iac_detach)(struct ieee80211vap *); int (*iac_check)(struct ieee80211vap *, const struct ieee80211_frame *wh); int (*iac_add)(struct ieee80211vap *, const uint8_t mac[IEEE80211_ADDR_LEN]); int (*iac_remove)(struct ieee80211vap *, const uint8_t mac[IEEE80211_ADDR_LEN]); int (*iac_flush)(struct ieee80211vap *); int (*iac_setpolicy)(struct ieee80211vap *, int); int (*iac_getpolicy)(struct ieee80211vap *); int (*iac_setioctl)(struct ieee80211vap *, struct ieee80211req *); int (*iac_getioctl)(struct ieee80211vap *, struct ieee80211req *); }; void ieee80211_aclator_register(const struct ieee80211_aclator *); void ieee80211_aclator_unregister(const struct ieee80211_aclator *); const struct ieee80211_aclator *ieee80211_aclator_get(const char *name); /* flags for ieee80211_fix_rate() */ #define IEEE80211_F_DOSORT 0x00000001 /* sort rate list */ #define IEEE80211_F_DOFRATE 0x00000002 /* use fixed legacy rate */ #define IEEE80211_F_DONEGO 0x00000004 /* calc negotiated rate */ #define IEEE80211_F_DODEL 0x00000008 /* delete ignore rate */ #define IEEE80211_F_DOBRS 0x00000010 /* check basic rate set */ #define IEEE80211_F_JOIN 0x00000020 /* sta joining our bss */ #define IEEE80211_F_DOFMCS 0x00000040 /* use fixed HT rate */ int ieee80211_fix_rate(struct ieee80211_node *, struct ieee80211_rateset *, int); /* * WME/WMM support. */ struct wmeParams { uint8_t wmep_acm; uint8_t wmep_aifsn; uint8_t wmep_logcwmin; /* log2(cwmin) */ uint8_t wmep_logcwmax; /* log2(cwmax) */ uint8_t wmep_txopLimit; uint8_t wmep_noackPolicy; /* 0 (ack), 1 (no ack) */ }; #define IEEE80211_TXOP_TO_US(_txop) ((_txop)<<5) #define IEEE80211_US_TO_TXOP(_us) ((_us)>>5) struct chanAccParams { uint8_t cap_info; /* version of the current set */ struct wmeParams cap_wmeParams[WME_NUM_AC]; }; struct ieee80211_wme_state { u_int wme_flags; #define WME_F_AGGRMODE 0x00000001 /* STATUS: WME aggressive mode */ u_int wme_hipri_traffic; /* VI/VO frames in beacon interval */ u_int wme_hipri_switch_thresh;/* aggressive mode switch thresh */ u_int wme_hipri_switch_hysteresis;/* aggressive mode switch hysteresis */ struct wmeParams wme_params[4]; /* from assoc resp for each AC*/ struct chanAccParams wme_wmeChanParams; /* WME params applied to self */ struct chanAccParams wme_wmeBssChanParams;/* WME params bcast to stations */ struct chanAccParams wme_chanParams; /* params applied to self */ struct chanAccParams wme_bssChanParams; /* params bcast to stations */ int (*wme_update)(struct ieee80211com *); }; void ieee80211_wme_initparams(struct ieee80211vap *); void ieee80211_wme_updateparams(struct ieee80211vap *); void ieee80211_wme_updateparams_locked(struct ieee80211vap *); /* * Return the WME TID from a QoS frame. If no TID * is present return the index for the "non-QoS" entry. */ static __inline uint8_t ieee80211_gettid(const struct ieee80211_frame *wh) { uint8_t tid; if (IEEE80211_QOS_HAS_SEQ(wh)) { if (IEEE80211_IS_DSTODS(wh)) tid = ((const struct ieee80211_qosframe_addr4 *)wh)-> i_qos[0]; else tid = ((const struct ieee80211_qosframe *)wh)->i_qos[0]; tid &= IEEE80211_QOS_TID; } else tid = IEEE80211_NONQOS_TID; return tid; } void ieee80211_waitfor_parent(struct ieee80211com *); void ieee80211_start_locked(struct ieee80211vap *); void ieee80211_init(void *); void ieee80211_start_all(struct ieee80211com *); void ieee80211_stop_locked(struct ieee80211vap *); void ieee80211_stop(struct ieee80211vap *); void ieee80211_stop_all(struct ieee80211com *); void ieee80211_suspend_all(struct ieee80211com *); void ieee80211_resume_all(struct ieee80211com *); void ieee80211_restart_all(struct ieee80211com *); void ieee80211_dturbo_switch(struct ieee80211vap *, int newflags); void ieee80211_swbmiss(void *arg); void ieee80211_beacon_miss(struct ieee80211com *); int ieee80211_new_state(struct ieee80211vap *, enum ieee80211_state, int); int ieee80211_new_state_locked(struct ieee80211vap *, enum ieee80211_state, int); void ieee80211_print_essid(const uint8_t *, int); void ieee80211_dump_pkt(struct ieee80211com *, const uint8_t *, int, int, int); extern const char *ieee80211_opmode_name[]; extern const char *ieee80211_state_name[IEEE80211_S_MAX]; extern const char *ieee80211_wme_acnames[]; /* * Beacon frames constructed by ieee80211_beacon_alloc * have the following structure filled in so drivers * can update the frame later w/ minimal overhead. */ struct ieee80211_beacon_offsets { uint8_t bo_flags[4]; /* update/state flags */ uint16_t *bo_caps; /* capabilities */ uint8_t *bo_cfp; /* start of CFParms element */ uint8_t *bo_tim; /* start of atim/dtim */ uint8_t *bo_wme; /* start of WME parameters */ uint8_t *bo_tdma; /* start of TDMA parameters */ uint8_t *bo_tim_trailer;/* start of fixed-size trailer */ uint16_t bo_tim_len; /* atim/dtim length in bytes */ uint16_t bo_tim_trailer_len;/* tim trailer length in bytes */ uint8_t *bo_erp; /* start of ERP element */ uint8_t *bo_htinfo; /* start of HT info element */ uint8_t *bo_ath; /* start of ATH parameters */ uint8_t *bo_appie; /* start of AppIE element */ uint16_t bo_appie_len; /* AppIE length in bytes */ uint16_t bo_csa_trailer_len; uint8_t *bo_csa; /* start of CSA element */ uint8_t *bo_quiet; /* start of Quiet element */ uint8_t *bo_meshconf; /* start of MESHCONF element */ uint8_t *bo_spare[3]; }; struct mbuf *ieee80211_beacon_alloc(struct ieee80211_node *); /* * Beacon frame updates are signaled through calls to iv_update_beacon * with one of the IEEE80211_BEACON_* tokens defined below. For devices * that construct beacon frames on the host this can trigger a rebuild * or defer the processing. For devices that offload beacon frame * handling this callback can be used to signal a rebuild. The bo_flags * array in the ieee80211_beacon_offsets structure is intended to record * deferred processing requirements; ieee80211_beacon_update uses the * state to optimize work. Since this structure is owned by the driver * and not visible to the 802.11 layer drivers must supply an iv_update_beacon * callback that marks the flag bits and schedules (as necessary) an update. */ enum { IEEE80211_BEACON_CAPS = 0, /* capabilities */ IEEE80211_BEACON_TIM = 1, /* DTIM/ATIM */ IEEE80211_BEACON_WME = 2, IEEE80211_BEACON_ERP = 3, /* Extended Rate Phy */ IEEE80211_BEACON_HTINFO = 4, /* HT Information */ IEEE80211_BEACON_APPIE = 5, /* Application IE's */ IEEE80211_BEACON_CFP = 6, /* CFParms */ IEEE80211_BEACON_CSA = 7, /* Channel Switch Announcement */ IEEE80211_BEACON_TDMA = 9, /* TDMA Info */ IEEE80211_BEACON_ATH = 10, /* ATH parameters */ IEEE80211_BEACON_MESHCONF = 11, /* Mesh Configuration */ }; int ieee80211_beacon_update(struct ieee80211_node *, struct mbuf *, int mcast); void ieee80211_csa_startswitch(struct ieee80211com *, struct ieee80211_channel *, int mode, int count); void ieee80211_csa_completeswitch(struct ieee80211com *); void ieee80211_csa_cancelswitch(struct ieee80211com *); void ieee80211_cac_completeswitch(struct ieee80211vap *); /* * Notification methods called from the 802.11 state machine. * Note that while these are defined here, their implementation * is OS-specific. */ void ieee80211_notify_node_join(struct ieee80211_node *, int newassoc); void ieee80211_notify_node_leave(struct ieee80211_node *); void ieee80211_notify_scan_done(struct ieee80211vap *); void ieee80211_notify_wds_discover(struct ieee80211_node *); void ieee80211_notify_csa(struct ieee80211com *, const struct ieee80211_channel *, int mode, int count); void ieee80211_notify_radar(struct ieee80211com *, const struct ieee80211_channel *); enum ieee80211_notify_cac_event { IEEE80211_NOTIFY_CAC_START = 0, /* CAC timer started */ IEEE80211_NOTIFY_CAC_STOP = 1, /* CAC intentionally stopped */ IEEE80211_NOTIFY_CAC_RADAR = 2, /* CAC stopped due to radar detectio */ IEEE80211_NOTIFY_CAC_EXPIRE = 3, /* CAC expired w/o radar */ }; void ieee80211_notify_cac(struct ieee80211com *, const struct ieee80211_channel *, enum ieee80211_notify_cac_event); void ieee80211_notify_node_deauth(struct ieee80211_node *); void ieee80211_notify_node_auth(struct ieee80211_node *); void ieee80211_notify_country(struct ieee80211vap *, const uint8_t [], const uint8_t cc[2]); void ieee80211_notify_radio(struct ieee80211com *, int); #endif /* _NET80211_IEEE80211_PROTO_H_ */