diff --git a/sys/dev/iwm/if_iwm.c b/sys/dev/iwm/if_iwm.c index b89c895efb50..f994e8e75307 100644 --- a/sys/dev/iwm/if_iwm.c +++ b/sys/dev/iwm/if_iwm.c @@ -1,6684 +1,6689 @@ /* $OpenBSD: if_iwm.c,v 1.167 2017/04/04 00:40:52 claudio 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 "opt_iwm.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 #include #include #include #include #include /* From DragonflyBSD */ #define mtodoff(m, t, off) ((t)((m)->m_data + (off))) 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; }; #define IWM_UCODE_ALIVE_TIMEOUT hz #define IWM_UCODE_CALIB_TIMEOUT (2*hz) struct iwm_alive_data { int valid; uint32_t scd_base_addr; }; 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 *); 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_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_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_trans_pcie_fw_alive(struct iwm_softc *, uint32_t); 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 *, uint32_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 struct iwm_nvm_data * 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_free_nvm_data(struct iwm_nvm_data *); static void iwm_set_hw_address_family_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 struct iwm_nvm_data * iwm_parse_nvm_sections(struct iwm_softc *, struct iwm_nvm_section *); static int iwm_nvm_init(struct iwm_softc *); static int iwm_pcie_load_section(struct iwm_softc *, uint8_t, const struct iwm_fw_desc *); static int iwm_pcie_load_firmware_chunk(struct iwm_softc *, uint32_t, bus_addr_t, uint32_t); static int iwm_pcie_load_cpu_sections_8000(struct iwm_softc *sc, const struct iwm_fw_img *, int, int *); static int iwm_pcie_load_cpu_sections(struct iwm_softc *, const struct iwm_fw_img *, int, int *); static int iwm_pcie_load_given_ucode_8000(struct iwm_softc *, const struct iwm_fw_img *); static int iwm_pcie_load_given_ucode(struct iwm_softc *, const struct iwm_fw_img *); static int iwm_start_fw(struct iwm_softc *, const struct iwm_fw_img *); 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_load_ucode_wait_alive(struct iwm_softc *, enum iwm_ucode_type); static int iwm_run_init_ucode(struct iwm_softc *, int); static int iwm_config_ltr(struct iwm_softc *sc); static int iwm_rx_addbuf(struct iwm_softc *, int, int); static void iwm_rx_rx_phy_cmd(struct iwm_softc *, struct iwm_rx_packet *); static int iwm_get_noise(struct iwm_softc *, const struct iwm_statistics_rx_non_phy *); static void iwm_handle_rx_statistics(struct iwm_softc *, struct iwm_rx_packet *); static bool iwm_rx_mpdu(struct iwm_softc *, struct mbuf *, uint32_t, bool); static int iwm_rx_tx_cmd_single(struct iwm_softc *, struct iwm_rx_packet *, struct iwm_node *); static void iwm_rx_tx_cmd(struct iwm_softc *, struct iwm_rx_packet *); 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_update_quotas(struct iwm_softc *, struct iwm_vap *); static int iwm_auth(struct ieee80211vap *, struct iwm_softc *); static struct ieee80211_node * iwm_node_alloc(struct ieee80211vap *, const uint8_t[IEEE80211_ADDR_LEN]); static uint8_t iwm_rate_from_ucode_rate(uint32_t); static int iwm_rate2ridx(struct iwm_softc *, uint8_t); static void iwm_setrates(struct iwm_softc *, struct iwm_node *, int); static int iwm_newstate(struct ieee80211vap *, enum ieee80211_state, int); static void iwm_endscan_cb(void *, int); static int iwm_send_bt_init_conf(struct iwm_softc *); static boolean_t iwm_is_lar_supported(struct iwm_softc *); static boolean_t iwm_is_wifi_mcc_supported(struct iwm_softc *); static int iwm_send_update_mcc_cmd(struct iwm_softc *, const char *); static void iwm_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_handle_rxb(struct iwm_softc *, struct mbuf *); 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_xmit_queue_drain(struct iwm_softc *); 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); static int iwm_lar_disable = 0; TUNABLE_INT("hw.iwm.lar.disable", &iwm_lar_disable); /* * 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_img *fws; struct iwm_fw_desc *fwone; if (type >= IWM_UCODE_TYPE_MAX) return EINVAL; if (dlen < sizeof(uint32_t)) return EINVAL; fws = &sc->sc_fw.img[type]; if (fws->fw_count >= IWM_UCODE_SECTION_MAX) return EINVAL; fwone = &fws->sec[fws->fw_count]; /* first 32bit are device load offset */ memcpy(&fwone->offset, data, sizeof(uint32_t)); /* rest is data */ fwone->data = data + sizeof(uint32_t); fwone->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 int iwm_set_ucode_api_flags(struct iwm_softc *sc, const uint8_t *data, struct iwm_ucode_capabilities *capa) { const struct iwm_ucode_api *ucode_api = (const void *)data; uint32_t api_index = le32toh(ucode_api->api_index); uint32_t api_flags = le32toh(ucode_api->api_flags); int i; if (api_index >= howmany(IWM_NUM_UCODE_TLV_API, 32)) { device_printf(sc->sc_dev, "api flags index %d larger than supported by driver\n", api_index); /* don't return an error so we can load FW that has more bits */ return 0; } for (i = 0; i < 32; i++) { if (api_flags & (1U << i)) setbit(capa->enabled_api, i + 32 * api_index); } return 0; } static int iwm_set_ucode_capabilities(struct iwm_softc *sc, const uint8_t *data, struct iwm_ucode_capabilities *capa) { const struct iwm_ucode_capa *ucode_capa = (const void *)data; uint32_t api_index = le32toh(ucode_capa->api_index); uint32_t api_flags = le32toh(ucode_capa->api_capa); int i; if (api_index >= howmany(IWM_NUM_UCODE_TLV_CAPA, 32)) { device_printf(sc->sc_dev, "capa flags index %d larger than supported by driver\n", api_index); /* don't return an error so we can load FW that has more bits */ return 0; } for (i = 0; i < 32; i++) { if (api_flags & (1U << i)) setbit(capa->enabled_capa, i + 32 * api_index); } return 0; } static void iwm_fw_info_free(struct iwm_fw_info *fw) { firmware_put(fw->fw_fp, FIRMWARE_UNLOAD); fw->fw_fp = NULL; memset(fw->img, 0, sizeof(fw->img)); } static int iwm_read_firmware(struct iwm_softc *sc) { struct iwm_fw_info *fw = &sc->sc_fw; const struct iwm_tlv_ucode_header *uhdr; const struct iwm_ucode_tlv *tlv; struct iwm_ucode_capabilities *capa = &sc->sc_fw.ucode_capa; enum iwm_ucode_tlv_type tlv_type; const struct firmware *fwp; const uint8_t *data; uint32_t tlv_len; uint32_t usniffer_img; const uint8_t *tlv_data; uint32_t paging_mem_size; int num_of_cpus; int error = 0; size_t len; /* * Load firmware into driver memory. * fw_fp will be set. */ fwp = firmware_get(sc->cfg->fw_name); if (fwp == NULL) { device_printf(sc->sc_dev, "could not read firmware %s (error %d)\n", sc->cfg->fw_name, error); goto out; } fw->fw_fp = fwp; /* (Re-)Initialize default values. */ capa->flags = 0; capa->max_probe_length = IWM_DEFAULT_MAX_PROBE_LENGTH; capa->n_scan_channels = IWM_DEFAULT_SCAN_CHANNELS; memset(capa->enabled_capa, 0, sizeof(capa->enabled_capa)); memset(capa->enabled_api, 0, sizeof(capa->enabled_api)); 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->cfg->fw_name); error = EINVAL; goto out; } snprintf(sc->sc_fwver, sizeof(sc->sc_fwver), "%u.%u (API ver %u)", 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)) { len -= sizeof(*tlv); tlv = (const void *)data; tlv_len = le32toh(tlv->length); tlv_type = le32toh(tlv->type); tlv_data = tlv->data; if (len < tlv_len) { device_printf(sc->sc_dev, "firmware too short: %zu bytes\n", len); error = EINVAL; goto parse_out; } len -= roundup2(tlv_len, 4); data += sizeof(*tlv) + roundup2(tlv_len, 4); 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 (%u) != sizeof(uint32_t)\n", __func__, tlv_len); error = EINVAL; goto parse_out; } capa->max_probe_length = le32_to_cpup((const uint32_t *)tlv_data); /* limit it to something sensible */ if (capa->max_probe_length > 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 (%u) > 0\n", __func__, tlv_len); error = EINVAL; goto parse_out; } capa->flags |= 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 (%u) < sizeof(uint32_t)\n", __func__, tlv_len); error = EINVAL; goto parse_out; } if (tlv_len % sizeof(uint32_t)) { device_printf(sc->sc_dev, "%s: IWM_UCODE_TLV_FLAGS: tlv_len (%u) %% sizeof(uint32_t)\n", __func__, 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 */ capa->flags = le32_to_cpup((const uint32_t *)tlv_data); break; case IWM_UCODE_TLV_CSCHEME: if ((error = iwm_store_cscheme(sc, tlv_data, tlv_len)) != 0) { device_printf(sc->sc_dev, "%s: iwm_store_cscheme(): returned %d\n", __func__, error); goto parse_out; } break; case IWM_UCODE_TLV_NUM_OF_CPU: if (tlv_len != sizeof(uint32_t)) { device_printf(sc->sc_dev, "%s: IWM_UCODE_TLV_NUM_OF_CPU: tlv_len (%u) != sizeof(uint32_t)\n", __func__, tlv_len); error = EINVAL; goto parse_out; } num_of_cpus = le32_to_cpup((const uint32_t *)tlv_data); if (num_of_cpus == 2) { fw->img[IWM_UCODE_REGULAR].is_dual_cpus = TRUE; fw->img[IWM_UCODE_INIT].is_dual_cpus = TRUE; fw->img[IWM_UCODE_WOWLAN].is_dual_cpus = TRUE; } else if ((num_of_cpus > 2) || (num_of_cpus < 1)) { 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_REGULAR, tlv_data, tlv_len)) != 0) { device_printf(sc->sc_dev, "%s: IWM_UCODE_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_INIT, tlv_data, tlv_len)) != 0) { device_printf(sc->sc_dev, "%s: IWM_UCODE_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_WOWLAN, tlv_data, tlv_len)) != 0) { device_printf(sc->sc_dev, "%s: IWM_UCODE_WOWLAN: 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 (%u) < sizeof(iwm_tlv_calib_data) (%zu)\n", __func__, tlv_len, 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 (%u) < sizeof(uint32_t)\n", __func__, tlv_len); goto parse_out; } sc->sc_fw.phy_config = le32_to_cpup((const uint32_t *)tlv_data); sc->sc_fw.valid_tx_ant = (sc->sc_fw.phy_config & IWM_FW_PHY_CFG_TX_CHAIN) >> IWM_FW_PHY_CFG_TX_CHAIN_POS; sc->sc_fw.valid_rx_ant = (sc->sc_fw.phy_config & IWM_FW_PHY_CFG_RX_CHAIN) >> IWM_FW_PHY_CFG_RX_CHAIN_POS; break; case IWM_UCODE_TLV_API_CHANGES_SET: { if (tlv_len != sizeof(struct iwm_ucode_api)) { error = EINVAL; goto parse_out; } if (iwm_set_ucode_api_flags(sc, tlv_data, capa)) { error = EINVAL; goto parse_out; } break; } case IWM_UCODE_TLV_ENABLED_CAPABILITIES: { if (tlv_len != sizeof(struct iwm_ucode_capa)) { error = EINVAL; goto parse_out; } if (iwm_set_ucode_capabilities(sc, tlv_data, capa)) { error = EINVAL; goto parse_out; } break; } case IWM_UCODE_TLV_CMD_VERSIONS: 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_REGULAR_USNIFFER, tlv_data, tlv_len)) != 0) goto parse_out; break; case IWM_UCODE_TLV_PAGING: if (tlv_len != sizeof(uint32_t)) { error = EINVAL; goto parse_out; } paging_mem_size = le32_to_cpup((const uint32_t *)tlv_data); IWM_DPRINTF(sc, IWM_DEBUG_FIRMWARE_TLV, "%s: Paging: paging enabled (size = %u bytes)\n", __func__, paging_mem_size); if (paging_mem_size > IWM_MAX_PAGING_IMAGE_SIZE) { device_printf(sc->sc_dev, "%s: Paging: driver supports up to %u bytes for paging image\n", __func__, IWM_MAX_PAGING_IMAGE_SIZE); error = EINVAL; goto out; } if (paging_mem_size & (IWM_FW_PAGING_SIZE - 1)) { device_printf(sc->sc_dev, "%s: Paging: image isn't multiple %u\n", __func__, IWM_FW_PAGING_SIZE); error = EINVAL; goto out; } sc->sc_fw.img[IWM_UCODE_REGULAR].paging_mem_size = paging_mem_size; usniffer_img = IWM_UCODE_REGULAR_USNIFFER; sc->sc_fw.img[usniffer_img].paging_mem_size = paging_mem_size; break; case IWM_UCODE_TLV_N_SCAN_CHANNELS: if (tlv_len != sizeof(uint32_t)) { error = EINVAL; goto parse_out; } capa->n_scan_channels = le32_to_cpup((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), "%u.%u.%u", le32toh(((const uint32_t *)tlv_data)[0]), le32toh(((const uint32_t *)tlv_data)[1]), le32toh(((const uint32_t *)tlv_data)[2])); break; case IWM_UCODE_TLV_FW_MEM_SEG: break; default: device_printf(sc->sc_dev, "%s: unknown firmware section %d, abort\n", __func__, tlv_type); error = EINVAL; goto parse_out; } } 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); } out: if (error) { if (fw->fw_fp != NULL) iwm_fw_info_free(fw); } return error; } /* * DMA resource routines */ /* 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, IWM_FH_MEM_TB_MAX_LENGTH, 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; if (sc->cfg->mqrx_supported) { count = IWM_RX_MQ_RING_COUNT; descsz = sizeof(uint64_t); } else { count = IWM_RX_LEGACY_RING_COUNT; descsz = sizeof(uint32_t); } /* Allocate RX descriptors (256-byte aligned). */ size = count * descsz; error = iwm_dma_contig_alloc(sc->sc_dmat, &ring->free_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->free_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; if (sc->cfg->mqrx_supported) { size = count * sizeof(uint32_t); error = iwm_dma_contig_alloc(sc->sc_dmat, &ring->used_desc_dma, size, 256); if (error != 0) { device_printf(sc->sc_dev, "could not allocate RX ring DMA memory\n"); goto fail; } } /* 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 < 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_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. */ if (sc->rxq.stat) 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 count, i; iwm_dma_contig_free(&ring->free_desc_dma); iwm_dma_contig_free(&ring->stat_dma); iwm_dma_contig_free(&ring->used_desc_dma); count = sc->cfg->mqrx_supported ? IWM_RX_MQ_RING_COUNT : IWM_RX_LEGACY_RING_COUNT; for (i = 0; i < 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_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_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; if (ring->qid == IWM_CMD_QUEUE && sc->cmd_hold_nic_awake) iwm_pcie_clear_cmd_in_flight(sc); } 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->phy_ctxt = NULL; iv->is_uploaded = 0; } sc->sc_firmware_state = 0; sc->sc_flags &= ~IWM_FLAG_TE_ACTIVE; /* 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 */ if (iwm_nic_lock(sc)) { iwm_write_prph(sc, IWM_SCD_TXFACT, 0); /* 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_pcie_rx_stop(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]); if (sc->cfg->device_family == IWM_DEVICE_FAMILY_7000) { /* Power-down device's busmaster DMA clocks */ if (iwm_nic_lock(sc)) { iwm_write_prph(sc, IWM_APMG_CLK_DIS_REG, IWM_APMG_CLK_VAL_DMA_CLK_RQT); iwm_nic_unlock(sc); } 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); /* stop and reset the on-board processor */ IWM_SETBITS(sc, IWM_CSR_RESET, IWM_CSR_RESET_REG_FLAG_SW_RESET); DELAY(5000); /* * Upon stop, the APM issues an interrupt if HW RF kill is set. */ iwm_disable_interrupts(sc); /* * Even if we stop the HW, we still want the RF kill * interrupt */ iwm_enable_rfkill_int(sc); iwm_check_rfkill(sc); iwm_prepare_card_hw(sc); } /* iwlwifi: mvm/ops.c */ static void iwm_nic_config(struct iwm_softc *sc) { uint8_t radio_cfg_type, radio_cfg_step, radio_cfg_dash; uint32_t reg_val = 0; uint32_t phy_config = iwm_get_phy_config(sc); radio_cfg_type = (phy_config & IWM_FW_PHY_CFG_RADIO_TYPE) >> IWM_FW_PHY_CFG_RADIO_TYPE_POS; radio_cfg_step = (phy_config & IWM_FW_PHY_CFG_RADIO_STEP) >> IWM_FW_PHY_CFG_RADIO_STEP_POS; radio_cfg_dash = (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, IWM_CSR_HW_IF_CONFIG_REG_MSK_MAC_DASH | IWM_CSR_HW_IF_CONFIG_REG_MSK_MAC_STEP | IWM_CSR_HW_IF_CONFIG_REG_MSK_PHY_STEP | IWM_CSR_HW_IF_CONFIG_REG_MSK_PHY_DASH | IWM_CSR_HW_IF_CONFIG_REG_MSK_PHY_TYPE | IWM_CSR_HW_IF_CONFIG_REG_BIT_RADIO_SI | IWM_CSR_HW_IF_CONFIG_REG_BIT_MAC_SI | 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->cfg->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_mq_init(struct iwm_softc *sc) { int enabled; if (!iwm_nic_lock(sc)) return EBUSY; /* Stop RX DMA. */ iwm_write_prph(sc, IWM_RFH_RXF_DMA_CFG, 0); /* Disable RX used and free queue operation. */ iwm_write_prph(sc, IWM_RFH_RXF_RXQ_ACTIVE, 0); iwm_write_prph64(sc, IWM_RFH_Q0_FRBDCB_BA_LSB, sc->rxq.free_desc_dma.paddr); iwm_write_prph64(sc, IWM_RFH_Q0_URBDCB_BA_LSB, sc->rxq.used_desc_dma.paddr); iwm_write_prph64(sc, IWM_RFH_Q0_URBD_STTS_WPTR_LSB, sc->rxq.stat_dma.paddr); iwm_write_prph(sc, IWM_RFH_Q0_FRBDCB_WIDX, 0); iwm_write_prph(sc, IWM_RFH_Q0_FRBDCB_RIDX, 0); iwm_write_prph(sc, IWM_RFH_Q0_URBDCB_WIDX, 0); /* We configure only queue 0 for now. */ enabled = ((1 << 0) << 16) | (1 << 0); /* Enable RX DMA, 4KB buffer size. */ iwm_write_prph(sc, IWM_RFH_RXF_DMA_CFG, IWM_RFH_DMA_EN_ENABLE_VAL | IWM_RFH_RXF_DMA_RB_SIZE_4K | IWM_RFH_RXF_DMA_MIN_RB_4_8 | IWM_RFH_RXF_DMA_DROP_TOO_LARGE_MASK | IWM_RFH_RXF_DMA_RBDCB_SIZE_512); /* Enable RX DMA snooping. */ iwm_write_prph(sc, IWM_RFH_GEN_CFG, IWM_RFH_GEN_CFG_RFH_DMA_SNOOP | IWM_RFH_GEN_CFG_SERVICE_DMA_SNOOP | (sc->cfg->integrated ? IWM_RFH_GEN_CFG_RB_CHUNK_SIZE_64 : IWM_RFH_GEN_CFG_RB_CHUNK_SIZE_128)); /* Enable the configured queue(s). */ iwm_write_prph(sc, IWM_RFH_RXF_RXQ_ACTIVE, enabled); iwm_nic_unlock(sc); IWM_WRITE_1(sc, IWM_CSR_INT_COALESCING, IWM_HOST_INT_TIMEOUT_DEF); IWM_WRITE(sc, IWM_RFH_Q0_FRBDCB_WIDX_TRG, 8); return (0); } static int iwm_nic_rx_legacy_init(struct iwm_softc *sc) { /* Stop Rx DMA */ iwm_pcie_rx_stop(sc); if (!iwm_nic_lock(sc)) return EBUSY; /* reset and flush pointers */ 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.free_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 DMA * XXX 5000 HW isn't supported by the iwm(4) driver. * IWM_FH_RCSR_CHNL0_RX_IGNORE_RXF_EMPTY is set because of HW bug in * the credit mechanism in 5000 HW RX FIFO * Direct rx interrupts to hosts * Rx buffer size 4 or 8k or 12k * RB timeout 0x10 * 256 RBDs */ IWM_WRITE(sc, IWM_FH_MEM_RCSR_CHNL0_CONFIG_REG, IWM_FH_RCSR_RX_CONFIG_CHNL_EN_ENABLE_VAL | IWM_FH_RCSR_CHNL0_RX_IGNORE_RXF_EMPTY | /* HW bug */ IWM_FH_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_INT_HOST_VAL | IWM_FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_4K | (IWM_RX_RB_TIMEOUT << IWM_FH_RCSR_RX_CONFIG_REG_IRQ_RBTH_POS) | 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->cfg->host_interrupt_operation_mode) IWM_SETBITS(sc, IWM_CSR_INT_COALESCING, IWM_HOST_INT_OPER_MODE); iwm_nic_unlock(sc); IWM_WRITE(sc, IWM_FH_RSCSR_CHNL0_WPTR, 8); return 0; } static int iwm_nic_rx_init(struct iwm_softc *sc) { if (sc->cfg->mqrx_supported) return iwm_nic_rx_mq_init(sc); else return iwm_nic_rx_legacy_init(sc); } 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_set_bits_prph(sc, IWM_SCD_GP_CTRL, IWM_SCD_GP_CTRL_AUTO_ACTIVE_MODE | IWM_SCD_GP_CTRL_ENABLE_31_QUEUES); iwm_nic_unlock(sc); return 0; } static int iwm_nic_init(struct iwm_softc *sc) { int error; iwm_apm_init(sc); if (sc->cfg->device_family == IWM_DEVICE_FAMILY_7000) iwm_set_pwr(sc); iwm_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; } int iwm_enable_txq(struct iwm_softc *sc, int sta_id, int qid, int fifo) { int qmsk; qmsk = 1 << qid; 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_CMD_QUEUE) { /* Disable the scheduler. */ iwm_write_prph(sc, IWM_SCD_EN_CTRL, 0); /* Stop the TX queue prior to 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_nic_unlock(sc); /* Disable aggregations for this queue. */ iwm_clear_bits_prph(sc, IWM_SCD_AGGR_SEL, qmsk); if (!iwm_nic_lock(sc)) { device_printf(sc->sc_dev, "%s: cannot enable txq %d\n", __func__, qid); return EBUSY; } iwm_write_prph(sc, IWM_SCD_QUEUE_RDPTR(qid), 0); iwm_nic_unlock(sc); iwm_write_mem32(sc, sc->scd_base_addr + IWM_SCD_CONTEXT_QUEUE_OFFSET(qid), 0); /* Set scheduler window size and frame limit. */ iwm_write_mem32(sc, sc->scd_base_addr + 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)); if (!iwm_nic_lock(sc)) { device_printf(sc->sc_dev, "%s: cannot enable txq %d\n", __func__, qid); return EBUSY; } 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); /* Enable the scheduler for this queue. */ iwm_write_prph(sc, IWM_SCD_EN_CTRL, qmsk); } 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_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_nic_unlock(sc); IWM_DPRINTF(sc, IWM_DEBUG_XMIT, "%s: enabled txq %d FIFO %d\n", __func__, qid, fifo); return 0; } static int iwm_trans_pcie_fw_alive(struct iwm_softc *sc, uint32_t scd_base_addr) { int error, chnl; int clear_dwords = (IWM_SCD_TRANS_TBL_MEM_UPPER_BOUND - IWM_SCD_CONTEXT_MEM_LOWER_BOUND) / sizeof(uint32_t); if (!iwm_nic_lock(sc)) return EBUSY; iwm_ict_reset(sc); sc->scd_base_addr = iwm_read_prph(sc, IWM_SCD_SRAM_BASE_ADDR); if (scd_base_addr != 0 && scd_base_addr != sc->scd_base_addr) { device_printf(sc->sc_dev, "%s: sched addr mismatch: alive: 0x%x prph: 0x%x\n", __func__, sc->scd_base_addr, scd_base_addr); } iwm_nic_unlock(sc); /* reset context data, TX status and translation data */ error = iwm_write_mem(sc, sc->scd_base_addr + IWM_SCD_CONTEXT_MEM_LOWER_BOUND, NULL, clear_dwords); if (error) return EBUSY; if (!iwm_nic_lock(sc)) return EBUSY; /* 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_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); iwm_nic_unlock(sc); /* Enable L1-Active */ if (sc->cfg->device_family < IWM_DEVICE_FAMILY_8000) { iwm_clear_bits_prph(sc, IWM_APMG_PCIDEV_STT_REG, IWM_APMG_PCIDEV_STT_VAL_L1_ACT_DIS); } return error; } /* * NVM read access and content parsing. We do not support * external NVM or writing NVM. * iwlwifi/mvm/nvm.c */ /* Default NVM size to read */ #define IWM_NVM_DEFAULT_CHUNK_SIZE (2*1024) #define IWM_NVM_WRITE_OPCODE 1 #define IWM_NVM_READ_OPCODE 0 /* load nvm chunk response */ enum { IWM_READ_NVM_CHUNK_SUCCEED = 0, IWM_READ_NVM_CHUNK_NOT_VALID_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) { 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_WANT_SKB | IWM_CMD_SEND_IN_RFKILL, .data = { &nvm_access_cmd, }, }; int ret, bytes_read, offset_read; uint8_t *resp_data; cmd.len[0] = sizeof(struct iwm_nvm_access_cmd); ret = iwm_send_cmd(sc, &cmd); if (ret) { device_printf(sc->sc_dev, "Could not send NVM_ACCESS command (error=%d)\n", ret); return ret; } pkt = cmd.resp_pkt; /* 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) { if ((offset != 0) && (ret == IWM_READ_NVM_CHUNK_NOT_VALID_ADDRESS)) { /* * meaning of NOT_VALID_ADDRESS: * driver try to read chunk from address that is * multiple of 2K and got an error since addr is empty. * meaning of (offset != 0): driver already * read valid data from another chunk so this case * is not an error. */ IWM_DPRINTF(sc, IWM_DEBUG_EEPROM | IWM_DEBUG_RESET, "NVM access command failed on offset 0x%x since that section size is multiple 2K\n", offset); *len = 0; ret = 0; } else { IWM_DPRINTF(sc, IWM_DEBUG_EEPROM | IWM_DEBUG_RESET, "NVM access command failed with status %d\n", ret); ret = EIO; } 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, %d bytes received)\n", length, bytes_read); ret = EINVAL; goto exit; } /* Write data to NVM */ 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, uint32_t size_read) { uint16_t seglen, length, offset = 0; int ret; /* Set nvm section read length */ length = IWM_NVM_DEFAULT_CHUNK_SIZE; seglen = length; /* Read the NVM until exhausted (reading less than requested) */ while (seglen == length) { /* Check no memory assumptions fail and cause an overflow */ if ((size_read + offset + length) > sc->cfg->eeprom_size) { device_printf(sc->sc_dev, "EEPROM size is too small for NVM\n"); return ENOBUFS; } ret = iwm_nvm_read_chunk(sc, section, offset, length, data, &seglen); if (ret) { IWM_DPRINTF(sc, IWM_DEBUG_EEPROM | IWM_DEBUG_RESET, "Cannot read NVM from section %d offset %d, length %d\n", section, offset, length); return ret; } offset += seglen; } IWM_DPRINTF(sc, IWM_DEBUG_EEPROM | IWM_DEBUG_RESET, "NVM section %d read completed\n", section); *len = offset; return 0; } /* * 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) /** * 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 iwm-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->nvm_data->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->cfg->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->nvm_data; 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->cfg->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_family_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->cfg->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->cfg->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->cfg->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->cfg->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->cfg->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_set_hw_address(struct iwm_softc *sc, struct iwm_nvm_data *data, const uint16_t *nvm_hw, const uint16_t *mac_override) { #ifdef notyet /* for FAMILY 9000 */ if (cfg->mac_addr_from_csr) { iwm_set_hw_address_from_csr(sc, data); } else #endif if (sc->cfg->device_family < IWM_DEVICE_FAMILY_8000) { const uint8_t *hw_addr = (const uint8_t *)(nvm_hw + IWM_HW_ADDR); /* The byte order is little endian 16 bit, meaning 214365 */ 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_family_8000(sc, data, mac_override, nvm_hw); } if (!iwm_is_valid_ether_addr(data->hw_addr)) { device_printf(sc->sc_dev, "no valid mac address was found\n"); return EINVAL; } return 0; } static struct iwm_nvm_data * 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; uint32_t sku, radio_cfg; uint16_t lar_config; if (sc->cfg->device_family < IWM_DEVICE_FAMILY_8000) { data = malloc(sizeof(*data) + IWM_NUM_CHANNELS * sizeof(uint16_t), M_DEVBUF, M_NOWAIT | M_ZERO); } else { data = malloc(sizeof(*data) + IWM_NUM_CHANNELS_8000 * sizeof(uint16_t), M_DEVBUF, M_NOWAIT | M_ZERO); } if (!data) return NULL; 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); if (sc->cfg->device_family >= IWM_DEVICE_FAMILY_8000) { /* TODO: use IWL_NVM_EXT */ uint16_t lar_offset = data->nvm_version < 0xE39 ? IWM_NVM_LAR_OFFSET_8000_OLD : IWM_NVM_LAR_OFFSET_8000; lar_config = le16_to_cpup(regulatory + lar_offset); data->lar_enabled = !!(lar_config & IWM_NVM_LAR_ENABLED_8000); } /* If no valid mac address was found - bail out */ if (iwm_set_hw_address(sc, data, nvm_hw, mac_override)) { free(data, M_DEVBUF); return NULL; } if (sc->cfg->device_family == IWM_DEVICE_FAMILY_7000) { memcpy(data->nvm_ch_flags, sc->cfg->nvm_type == IWM_NVM_SDP ? ®ulatory[0] : &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 data; } static void iwm_free_nvm_data(struct iwm_nvm_data *data) { if (data != NULL) free(data, M_DEVBUF); } static struct iwm_nvm_data * 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->cfg->device_family == IWM_DEVICE_FAMILY_7000) { if (!sections[IWM_NVM_SECTION_TYPE_SW].data || !sections[sc->cfg->nvm_hw_section_num].data) { device_printf(sc->sc_dev, "Can't parse empty OTP/NVM sections\n"); return NULL; } } else if (sc->cfg->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 NULL; } /* MAC_OVERRIDE or at least HW section must exist */ if (!sections[sc->cfg->nvm_hw_section_num].data && !sections[IWM_NVM_SECTION_TYPE_MAC_OVERRIDE].data) { device_printf(sc->sc_dev, "Can't parse mac_address, empty sections\n"); return NULL; } /* 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 NULL; } } else { panic("unknown device family %d\n", sc->cfg->device_family); } hw = (const uint16_t *) sections[sc->cfg->nvm_hw_section_num].data; sw = (const uint16_t *)sections[IWM_NVM_SECTION_TYPE_SW].data; calib = (const uint16_t *) sections[IWM_NVM_SECTION_TYPE_CALIBRATION].data; regulatory = sc->cfg->nvm_type == IWM_NVM_SDP ? (const uint16_t *)sections[IWM_NVM_SECTION_TYPE_REGULATORY_SDP].data : (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_MAX_NUM_SECTIONS]; int i, ret, section; uint32_t size_read = 0; uint8_t *nvm_buffer, *temp; uint16_t len; memset(nvm_sections, 0, sizeof(nvm_sections)); if (sc->cfg->nvm_hw_section_num >= IWM_NVM_MAX_NUM_SECTIONS) return EINVAL; /* load NVM values from nic */ /* Read From FW NVM */ IWM_DPRINTF(sc, IWM_DEBUG_EEPROM, "Read from NVM\n"); nvm_buffer = malloc(sc->cfg->eeprom_size, M_DEVBUF, M_NOWAIT | M_ZERO); if (!nvm_buffer) return ENOMEM; for (section = 0; section < IWM_NVM_MAX_NUM_SECTIONS; section++) { /* we override the constness for initial read */ ret = iwm_nvm_read_section(sc, section, nvm_buffer, &len, size_read); if (ret) continue; size_read += len; temp = malloc(len, M_DEVBUF, M_NOWAIT); if (!temp) { ret = ENOMEM; break; } memcpy(temp, nvm_buffer, len); nvm_sections[section].data = temp; nvm_sections[section].length = len; } if (!size_read) device_printf(sc->sc_dev, "OTP is blank\n"); free(nvm_buffer, M_DEVBUF); sc->nvm_data = iwm_parse_nvm_sections(sc, nvm_sections); if (!sc->nvm_data) return EINVAL; IWM_DPRINTF(sc, IWM_DEBUG_EEPROM | IWM_DEBUG_RESET, "nvm version = %x\n", sc->nvm_data->nvm_version); for (i = 0; i < IWM_NVM_MAX_NUM_SECTIONS; i++) { if (nvm_sections[i].data != NULL) free(nvm_sections[i].data, M_DEVBUF); } return 0; } static int iwm_pcie_load_section(struct iwm_softc *sc, uint8_t section_num, const struct iwm_fw_desc *section) { struct iwm_dma_info *dma = &sc->fw_dma; uint8_t *v_addr; bus_addr_t p_addr; uint32_t offset, chunk_sz = MIN(IWM_FH_MEM_TB_MAX_LENGTH, section->len); int ret = 0; IWM_DPRINTF(sc, IWM_DEBUG_RESET, "%s: [%d] uCode section being loaded...\n", __func__, section_num); v_addr = dma->vaddr; p_addr = dma->paddr; for (offset = 0; offset < section->len; offset += chunk_sz) { uint32_t copy_size, dst_addr; int extended_addr = FALSE; copy_size = MIN(chunk_sz, section->len - offset); dst_addr = section->offset + offset; if (dst_addr >= IWM_FW_MEM_EXTENDED_START && dst_addr <= IWM_FW_MEM_EXTENDED_END) extended_addr = TRUE; if (extended_addr) iwm_set_bits_prph(sc, IWM_LMPM_CHICK, IWM_LMPM_CHICK_EXTENDED_ADDR_SPACE); memcpy(v_addr, (const uint8_t *)section->data + offset, copy_size); bus_dmamap_sync(dma->tag, dma->map, BUS_DMASYNC_PREWRITE); ret = iwm_pcie_load_firmware_chunk(sc, dst_addr, p_addr, copy_size); if (extended_addr) iwm_clear_bits_prph(sc, IWM_LMPM_CHICK, IWM_LMPM_CHICK_EXTENDED_ADDR_SPACE); if (ret) { device_printf(sc->sc_dev, "%s: Could not load the [%d] uCode section\n", __func__, section_num); break; } } return ret; } /* * ucode */ static int iwm_pcie_load_firmware_chunk(struct iwm_softc *sc, uint32_t dst_addr, bus_addr_t phy_addr, uint32_t byte_cnt) { 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), phy_addr & 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(phy_addr) << 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 up to 5s for this segment to load */ msleep(&sc->sc_fw, &sc->sc_mtx, 0, "iwmfw", hz * 5); 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); return ETIMEDOUT; } return 0; } static int iwm_pcie_load_cpu_sections_8000(struct iwm_softc *sc, const struct iwm_fw_img *image, int cpu, int *first_ucode_section) { int shift_param; int i, ret = 0, sec_num = 0x1; uint32_t val, last_read_idx = 0; 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_SECTION_MAX; i++) { last_read_idx = i; /* * CPU1_CPU2_SEPARATOR_SECTION delimiter - separate between * CPU1 to CPU2. * PAGING_SEPARATOR_SECTION delimiter - separate between * CPU2 non paged to CPU2 paging sec. */ if (!image->sec[i].data || image->sec[i].offset == IWM_CPU1_CPU2_SEPARATOR_SECTION || image->sec[i].offset == IWM_PAGING_SEPARATOR_SECTION) { IWM_DPRINTF(sc, IWM_DEBUG_RESET, "Break since Data not valid or Empty section, sec = %d\n", i); break; } ret = iwm_pcie_load_section(sc, i, &image->sec[i]); if (ret) return ret; /* 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); } } *first_ucode_section = last_read_idx; iwm_enable_interrupts(sc); 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; } static int iwm_pcie_load_cpu_sections(struct iwm_softc *sc, const struct iwm_fw_img *image, int cpu, int *first_ucode_section) { int shift_param; int i, ret = 0; uint32_t last_read_idx = 0; 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_SECTION_MAX; i++) { last_read_idx = i; /* * CPU1_CPU2_SEPARATOR_SECTION delimiter - separate between * CPU1 to CPU2. * PAGING_SEPARATOR_SECTION delimiter - separate between * CPU2 non paged to CPU2 paging sec. */ if (!image->sec[i].data || image->sec[i].offset == IWM_CPU1_CPU2_SEPARATOR_SECTION || image->sec[i].offset == IWM_PAGING_SEPARATOR_SECTION) { IWM_DPRINTF(sc, IWM_DEBUG_RESET, "Break since Data not valid or Empty section, sec = %d\n", i); break; } ret = iwm_pcie_load_section(sc, i, &image->sec[i]); if (ret) return ret; } *first_ucode_section = last_read_idx; return 0; } static int iwm_pcie_load_given_ucode(struct iwm_softc *sc, const struct iwm_fw_img *image) { int ret = 0; int first_ucode_section; IWM_DPRINTF(sc, IWM_DEBUG_RESET, "working with %s CPU\n", image->is_dual_cpus ? "Dual" : "Single"); /* load to FW the binary non secured sections of CPU1 */ ret = iwm_pcie_load_cpu_sections(sc, image, 1, &first_ucode_section); if (ret) return ret; if (image->is_dual_cpus) { /* set CPU2 header address */ if (iwm_nic_lock(sc)) { iwm_write_prph(sc, IWM_LMPM_SECURE_UCODE_LOAD_CPU2_HDR_ADDR, IWM_LMPM_SECURE_CPU2_HDR_MEM_SPACE); iwm_nic_unlock(sc); } /* load to FW the binary sections of CPU2 */ ret = iwm_pcie_load_cpu_sections(sc, image, 2, &first_ucode_section); if (ret) return ret; } iwm_enable_interrupts(sc); /* release CPU reset */ IWM_WRITE(sc, IWM_CSR_RESET, 0); return 0; } int iwm_pcie_load_given_ucode_8000(struct iwm_softc *sc, const struct iwm_fw_img *image) { int ret = 0; int first_ucode_section; IWM_DPRINTF(sc, IWM_DEBUG_RESET, "working with %s CPU\n", image->is_dual_cpus ? "Dual" : "Single"); /* configure the ucode to be ready to get the secured image */ /* release CPU reset */ if (iwm_nic_lock(sc)) { iwm_write_prph(sc, IWM_RELEASE_CPU_RESET, IWM_RELEASE_CPU_RESET_BIT); iwm_nic_unlock(sc); } /* load to FW the binary Secured sections of CPU1 */ ret = iwm_pcie_load_cpu_sections_8000(sc, image, 1, &first_ucode_section); if (ret) return ret; /* load to FW the binary sections of CPU2 */ return iwm_pcie_load_cpu_sections_8000(sc, image, 2, &first_ucode_section); } /* XXX Get rid of this definition */ static inline void iwm_enable_fw_load_int(struct iwm_softc *sc) { IWM_DPRINTF(sc, IWM_DEBUG_INTR, "Enabling FW load interrupt\n"); sc->sc_intmask = IWM_CSR_INT_BIT_FH_TX; IWM_WRITE(sc, IWM_CSR_INT_MASK, sc->sc_intmask); } /* XXX Add proper rfkill support code */ static int iwm_start_fw(struct iwm_softc *sc, const struct iwm_fw_img *fw) { int ret; /* This may fail if AMT took ownership of the device */ if (iwm_prepare_card_hw(sc)) { device_printf(sc->sc_dev, "%s: Exit HW not ready\n", __func__); ret = EIO; goto out; } IWM_WRITE(sc, IWM_CSR_INT, 0xFFFFFFFF); iwm_disable_interrupts(sc); /* 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, 0xFFFFFFFF); ret = iwm_nic_init(sc); if (ret) { device_printf(sc->sc_dev, "%s: Unable to init nic\n", __func__); goto out; } /* * Now, we load the firmware and don't want to be interrupted, even * by the RF-Kill interrupt (hence mask all the interrupt besides the * FH_TX interrupt which is needed to load the firmware). If the * RF-Kill switch is toggled, we will find out after having loaded * the firmware and return the proper value to the caller. */ iwm_enable_fw_load_int(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 */ if (sc->cfg->device_family >= IWM_DEVICE_FAMILY_8000) ret = iwm_pcie_load_given_ucode_8000(sc, fw); else ret = iwm_pcie_load_given_ucode(sc, fw); /* XXX re-check RF-Kill state */ out: return ret; } 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_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->cur_ucode; /* Set parameters */ phy_cfg_cmd.phy_cfg = htole32(iwm_get_phy_config(sc)); 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_send_cmd_pdu(sc, IWM_PHY_CONFIGURATION_CMD, IWM_CMD_SYNC, sizeof(phy_cfg_cmd), &phy_cfg_cmd); } static int iwm_alive_fn(struct iwm_softc *sc, struct iwm_rx_packet *pkt, void *data) { struct iwm_alive_data *alive_data = data; struct iwm_alive_resp_v3 *palive3; struct iwm_alive_resp *palive; struct iwm_umac_alive *umac; struct iwm_lmac_alive *lmac1; struct iwm_lmac_alive *lmac2 = NULL; uint16_t status; if (iwm_rx_packet_payload_len(pkt) == sizeof(*palive)) { palive = (void *)pkt->data; umac = &palive->umac_data; lmac1 = &palive->lmac_data[0]; lmac2 = &palive->lmac_data[1]; status = le16toh(palive->status); } else { palive3 = (void *)pkt->data; umac = &palive3->umac_data; lmac1 = &palive3->lmac_data; status = le16toh(palive3->status); } sc->error_event_table[0] = le32toh(lmac1->error_event_table_ptr); if (lmac2) sc->error_event_table[1] = le32toh(lmac2->error_event_table_ptr); sc->log_event_table = le32toh(lmac1->log_event_table_ptr); sc->umac_error_event_table = le32toh(umac->error_info_addr); alive_data->scd_base_addr = le32toh(lmac1->scd_base_ptr); alive_data->valid = status == IWM_ALIVE_STATUS_OK; if (sc->umac_error_event_table) sc->support_umac_log = TRUE; IWM_DPRINTF(sc, IWM_DEBUG_FW, "Alive ucode status 0x%04x revision 0x%01X 0x%01X\n", status, lmac1->ver_type, lmac1->ver_subtype); if (lmac2) IWM_DPRINTF(sc, IWM_DEBUG_FW, "Alive ucode CDB\n"); IWM_DPRINTF(sc, IWM_DEBUG_FW, "UMAC version: Major - 0x%x, Minor - 0x%x\n", le32toh(umac->umac_major), le32toh(umac->umac_minor)); return TRUE; } static int iwm_wait_phy_db_entry(struct iwm_softc *sc, struct iwm_rx_packet *pkt, void *data) { struct iwm_phy_db *phy_db = data; if (pkt->hdr.code != IWM_CALIB_RES_NOTIF_PHY_DB) { if(pkt->hdr.code != IWM_INIT_COMPLETE_NOTIF) { device_printf(sc->sc_dev, "%s: Unexpected cmd: %d\n", __func__, pkt->hdr.code); } return TRUE; } if (iwm_phy_db_set_section(phy_db, pkt)) { device_printf(sc->sc_dev, "%s: iwm_phy_db_set_section failed\n", __func__); } return FALSE; } static int iwm_load_ucode_wait_alive(struct iwm_softc *sc, enum iwm_ucode_type ucode_type) { struct iwm_notification_wait alive_wait; struct iwm_alive_data alive_data; const struct iwm_fw_img *fw; enum iwm_ucode_type old_type = sc->cur_ucode; int error; static const uint16_t alive_cmd[] = { IWM_ALIVE }; fw = &sc->sc_fw.img[ucode_type]; sc->cur_ucode = ucode_type; sc->ucode_loaded = FALSE; memset(&alive_data, 0, sizeof(alive_data)); iwm_init_notification_wait(sc->sc_notif_wait, &alive_wait, alive_cmd, nitems(alive_cmd), iwm_alive_fn, &alive_data); error = iwm_start_fw(sc, fw); if (error) { device_printf(sc->sc_dev, "iwm_start_fw: failed %d\n", error); sc->cur_ucode = old_type; iwm_remove_notification(sc->sc_notif_wait, &alive_wait); return error; } /* * Some things may run in the background now, but we * just wait for the ALIVE notification here. */ IWM_UNLOCK(sc); error = iwm_wait_notification(sc->sc_notif_wait, &alive_wait, IWM_UCODE_ALIVE_TIMEOUT); IWM_LOCK(sc); if (error) { if (sc->cfg->device_family >= IWM_DEVICE_FAMILY_8000) { uint32_t a = 0x5a5a5a5a, b = 0x5a5a5a5a; if (iwm_nic_lock(sc)) { a = iwm_read_prph(sc, IWM_SB_CPU_1_STATUS); b = iwm_read_prph(sc, IWM_SB_CPU_2_STATUS); iwm_nic_unlock(sc); } device_printf(sc->sc_dev, "SecBoot CPU1 Status: 0x%x, CPU2 Status: 0x%x\n", a, b); } sc->cur_ucode = old_type; return error; } if (!alive_data.valid) { device_printf(sc->sc_dev, "%s: Loaded ucode is not valid\n", __func__); sc->cur_ucode = old_type; return EIO; } iwm_trans_pcie_fw_alive(sc, alive_data.scd_base_addr); /* * configure and operate fw paging mechanism. * driver configures the paging flow only once, CPU2 paging image * included in the IWM_UCODE_INIT image. */ if (fw->paging_mem_size) { error = iwm_save_fw_paging(sc, fw); if (error) { device_printf(sc->sc_dev, "%s: failed to save the FW paging image\n", __func__); return error; } error = iwm_send_paging_cmd(sc, fw); if (error) { device_printf(sc->sc_dev, "%s: failed to send the paging cmd\n", __func__); iwm_free_fw_paging(sc); return error; } } if (!error) sc->ucode_loaded = TRUE; return error; } /* * mvm misc bits */ /* * follows iwlwifi/fw.c */ static int iwm_run_init_ucode(struct iwm_softc *sc, int justnvm) { struct iwm_notification_wait calib_wait; static const uint16_t init_complete[] = { IWM_INIT_COMPLETE_NOTIF, IWM_CALIB_RES_NOTIF_PHY_DB }; int ret; /* 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; } iwm_init_notification_wait(sc->sc_notif_wait, &calib_wait, init_complete, nitems(init_complete), iwm_wait_phy_db_entry, sc->sc_phy_db); /* Will also start the device */ ret = iwm_load_ucode_wait_alive(sc, IWM_UCODE_INIT); if (ret) { device_printf(sc->sc_dev, "Failed to start INIT ucode: %d\n", ret); goto error; } if (sc->cfg->device_family < IWM_DEVICE_FAMILY_8000) { ret = iwm_send_bt_init_conf(sc); if (ret) { device_printf(sc->sc_dev, "failed to send bt coex configuration: %d\n", ret); goto error; } } if (justnvm) { /* Read nvm */ ret = iwm_nvm_init(sc); if (ret) { device_printf(sc->sc_dev, "failed to read nvm\n"); goto error; } IEEE80211_ADDR_COPY(sc->sc_ic.ic_macaddr, sc->nvm_data->hw_addr); goto error; } /* Send TX valid antennas before triggering calibrations */ ret = iwm_send_tx_ant_cfg(sc, iwm_get_valid_tx_ant(sc)); if (ret) { device_printf(sc->sc_dev, "failed to send antennas before calibration: %d\n", ret); goto error; } /* * Send phy configurations command to init uCode * to start the 16.0 uCode init image internal calibrations. */ ret = iwm_send_phy_cfg_cmd(sc); if (ret) { device_printf(sc->sc_dev, "%s: Failed to run INIT calibrations: %d\n", __func__, ret); goto error; } /* * Nothing to do but wait for the init complete notification * from the firmware. */ IWM_UNLOCK(sc); ret = iwm_wait_notification(sc->sc_notif_wait, &calib_wait, IWM_UCODE_CALIB_TIMEOUT); IWM_LOCK(sc); goto out; error: iwm_remove_notification(sc->sc_notif_wait, &calib_wait); out: return ret; } static int iwm_config_ltr(struct iwm_softc *sc) { struct iwm_ltr_config_cmd cmd = { .flags = htole32(IWM_LTR_CFG_FLAG_FEATURE_ENABLE), }; if (!sc->sc_ltr_enabled) return 0; return iwm_send_cmd_pdu(sc, IWM_LTR_CONFIG, 0, sizeof(cmd), &cmd); } /* * 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; 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); m_freem(m); return error; } 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")); if (sc->cfg->mqrx_supported) ((uint64_t *)ring->desc)[idx] = htole64(seg.ds_addr); else ((uint32_t *)ring->desc)[idx] = htole32(seg.ds_addr >> 8); bus_dmamap_sync(ring->free_desc_dma.tag, ring->free_desc_dma.map, BUS_DMASYNC_PREWRITE); return 0; } static void iwm_rx_rx_phy_cmd(struct iwm_softc *sc, struct iwm_rx_packet *pkt) { struct iwm_rx_phy_info *phy_info = (void *)pkt->data; IWM_DPRINTF(sc, IWM_DEBUG_RECV, "received PHY stats\n"); 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_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 } static void iwm_handle_rx_statistics(struct iwm_softc *sc, struct iwm_rx_packet *pkt) { struct iwm_notif_statistics_v10 *stats = (void *)&pkt->data; memcpy(&sc->sc_stats, stats, sizeof(sc->sc_stats)); sc->sc_noise = iwm_get_noise(sc, &stats->rx.general); } /* iwlwifi: mvm/rx.c */ /* * iwm_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_rx_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 int iwm_rxmq_get_signal_strength(struct iwm_softc *sc, struct iwm_rx_mpdu_desc *desc) { int energy_a, energy_b; energy_a = desc->v1.energy_a; energy_b = desc->v1.energy_b; energy_a = energy_a ? -energy_a : -256; energy_b = energy_b ? -energy_b : -256; return MAX(energy_a, energy_b); } /* * iwm_rx_rx_mpdu - IWM_REPLY_RX_MPDU_CMD handler * * Handles the actual data of the Rx packet from the fw */ static bool iwm_rx_rx_mpdu(struct iwm_softc *sc, struct mbuf *m, uint32_t offset, bool stolen) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); struct ieee80211_frame *wh; struct ieee80211_rx_stats rxs; struct iwm_rx_phy_info *phy_info; struct iwm_rx_mpdu_res_start *rx_res; struct iwm_rx_packet *pkt = mtodoff(m, struct iwm_rx_packet *, offset); uint32_t len; uint32_t rx_pkt_status; int rssi; 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)); if (__predict_false(phy_info->cfg_phy_cnt > 20)) { device_printf(sc->sc_dev, "dsp size out of range [0,20]: %d\n", phy_info->cfg_phy_cnt); return false; } if (!(rx_pkt_status & IWM_RX_MPDU_RES_STATUS_CRC_OK) || !(rx_pkt_status & IWM_RX_MPDU_RES_STATUS_OVERRUN_OK)) { IWM_DPRINTF(sc, IWM_DEBUG_RECV, "Bad CRC or FIFO: 0x%08X.\n", rx_pkt_status); return false; } rssi = iwm_rx_get_signal_strength(sc, phy_info); /* Map it to relative value */ rssi = rssi - sc->sc_noise; /* replenish ring for the buffer we're going to feed to the sharks */ if (!stolen && iwm_rx_addbuf(sc, IWM_RBUF_SIZE, sc->rxq.cur) != 0) { device_printf(sc->sc_dev, "%s: unable to add more buffers\n", __func__); return false; } m->m_data = pkt->data + sizeof(*rx_res); m->m_pkthdr.len = m->m_len = len; IWM_DPRINTF(sc, IWM_DEBUG_RECV, "%s: rssi=%d, noise=%d\n", __func__, rssi, sc->sc_noise); 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_BAND; 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); + rxs.c_band = IEEE80211_CHAN_2GHZ; } else { rxs.c_freq = ieee80211_ieee2mhz(rxs.c_ieee, IEEE80211_CHAN_5GHZ); + rxs.c_band = 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) return false; 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; } } return true; } static bool iwm_rx_mpdu_mq(struct iwm_softc *sc, struct mbuf *m, uint32_t offset, bool stolen) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); struct ieee80211_frame *wh; struct ieee80211_rx_stats rxs; struct iwm_rx_mpdu_desc *desc; struct iwm_rx_packet *pkt; int rssi; uint32_t hdrlen, len, rate_n_flags; uint16_t phy_info; uint8_t channel; pkt = mtodo(m, offset); desc = (void *)pkt->data; if (!(desc->status & htole16(IWM_RX_MPDU_RES_STATUS_CRC_OK)) || !(desc->status & htole16(IWM_RX_MPDU_RES_STATUS_OVERRUN_OK))) { IWM_DPRINTF(sc, IWM_DEBUG_RECV, "Bad CRC or FIFO: 0x%08X.\n", desc->status); return false; } channel = desc->v1.channel; len = le16toh(desc->mpdu_len); phy_info = le16toh(desc->phy_info); rate_n_flags = desc->v1.rate_n_flags; wh = mtodo(m, sizeof(*desc)); m->m_data = pkt->data + sizeof(*desc); m->m_pkthdr.len = m->m_len = len; m->m_len = len; /* Account for padding following the frame header. */ if ((desc->mac_flags2 & IWM_RX_MPDU_MFLG2_PAD)) { hdrlen = ieee80211_anyhdrsize(wh); memmove(mtodo(m, 2), mtodo(m, 0), hdrlen); m->m_data = mtodo(m, 2); wh = mtod(m, struct ieee80211_frame *); } /* Map it to relative value */ rssi = iwm_rxmq_get_signal_strength(sc, desc); rssi = rssi - sc->sc_noise; /* replenish ring for the buffer we're going to feed to the sharks */ if (!stolen && iwm_rx_addbuf(sc, IWM_RBUF_SIZE, sc->rxq.cur) != 0) { device_printf(sc->sc_dev, "%s: unable to add more buffers\n", __func__); return false; } IWM_DPRINTF(sc, IWM_DEBUG_RECV, "%s: rssi=%d, noise=%d\n", __func__, rssi, sc->sc_noise); /* * 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_BAND; rxs.r_flags |= IEEE80211_R_NF | IEEE80211_R_RSSI; rxs.c_ieee = channel; rxs.c_freq = ieee80211_ieee2mhz(rxs.c_ieee, channel <= 14 ? IEEE80211_CHAN_2GHZ : IEEE80211_CHAN_5GHZ); + rxs.c_band = channel <= 14 ? IEEE80211_CHAN_2GHZ : 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) return false; if (ieee80211_radiotap_active_vap(vap)) { struct iwm_rx_radiotap_header *tap = &sc->sc_rxtap; tap->wr_flags = 0; if ((phy_info & IWM_RX_MPDU_PHY_SHORT_PREAMBLE) != 0) 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 = desc->v1.gp2_on_air_rise; switch ((rate_n_flags & 0xff)) { /* 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; } } return true; } static bool iwm_rx_mpdu(struct iwm_softc *sc, struct mbuf *m, uint32_t offset, bool stolen) { struct epoch_tracker et; struct ieee80211com *ic; struct ieee80211_frame *wh; struct ieee80211_node *ni; bool ret; ic = &sc->sc_ic; ret = sc->cfg->mqrx_supported ? iwm_rx_mpdu_mq(sc, m, offset, stolen) : iwm_rx_rx_mpdu(sc, m, offset, stolen); if (!ret) { counter_u64_add(ic->ic_ierrors, 1); return (ret); } wh = mtod(m, struct ieee80211_frame *); ni = ieee80211_find_rxnode(ic, (struct ieee80211_frame_min *)wh); IWM_UNLOCK(sc); NET_EPOCH_ENTER(et); if (ni != NULL) { IWM_DPRINTF(sc, IWM_DEBUG_RECV, "input m %p\n", m); 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); } NET_EPOCH_EXIT(et); IWM_LOCK(sc); return true; } static int iwm_rx_tx_cmd_single(struct iwm_softc *sc, struct iwm_rx_packet *pkt, struct iwm_node *in) { struct iwm_tx_resp *tx_resp = (void *)pkt->data; struct ieee80211_ratectl_tx_status *txs = &sc->sc_txs; struct ieee80211_node *ni = &in->in_ni; struct ieee80211vap *vap = ni->ni_vap; int status = le16toh(tx_resp->status.status) & IWM_TX_STATUS_MSK; int new_rate, cur_rate = vap->iv_bss->ni_txrate; boolean_t rate_matched; uint8_t tx_resp_rate; 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)); tx_resp_rate = iwm_rate_from_ucode_rate(le32toh(tx_resp->initial_rate)); /* For rate control, ignore frames sent at different initial rate */ rate_matched = (tx_resp_rate != 0 && tx_resp_rate == cur_rate); if (tx_resp_rate != 0 && cur_rate != 0 && !rate_matched) { IWM_DPRINTF(sc, IWM_DEBUG_TXRATE, "tx_resp_rate doesn't match ni_txrate (tx_resp_rate=%u " "ni_txrate=%d)\n", tx_resp_rate, cur_rate); } 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; } if (rate_matched) { ieee80211_ratectl_tx_complete(ni, txs); int rix = ieee80211_ratectl_rate(vap->iv_bss, NULL, 0); new_rate = vap->iv_bss->ni_txrate; if (new_rate != 0 && new_rate != cur_rate) { struct iwm_node *in = IWM_NODE(vap->iv_bss); iwm_setrates(sc, in, rix); iwm_send_lq_cmd(sc, &in->in_lq, FALSE); } } return (txs->status != IEEE80211_RATECTL_TX_SUCCESS); } static void iwm_rx_tx_cmd(struct iwm_softc *sc, struct iwm_rx_packet *pkt) { struct iwm_cmd_header *cmd_hdr; struct iwm_tx_ring *ring; struct iwm_tx_data *txd; struct iwm_node *in; struct mbuf *m; int idx, qid, qmsk, status; cmd_hdr = &pkt->hdr; idx = cmd_hdr->idx; qid = cmd_hdr->qid; ring = &sc->txq[qid]; txd = &ring->data[idx]; in = txd->in; m = txd->m; KASSERT(txd->done == 0, ("txd not done")); KASSERT(txd->in != NULL, ("txd without node")); KASSERT(txd->m != NULL, ("txd without mbuf")); sc->sc_tx_timer = 0; status = iwm_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); qmsk = 1 << qid; if (--ring->queued < IWM_TX_RING_LOMARK && (sc->qfullmsk & qmsk) != 0) { sc->qfullmsk &= ~qmsk; if (sc->qfullmsk == 0) 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_CMD_QUEUE]; struct iwm_tx_data *data; if (pkt->hdr.qid != IWM_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 (((pkt->hdr.idx + ring->queued) % IWM_TX_RING_COUNT) != ring->cur) { device_printf(sc->sc_dev, "%s: Some HCMDs skipped?: idx=%d queued=%d cur=%d\n", __func__, pkt->hdr.idx, ring->queued, ring->cur); /* XXX call iwm_force_nmi() */ } KASSERT(ring->queued > 0, ("ring->queued is empty?")); ring->queued--; if (ring->queued == 0) iwm_pcie_clear_cmd_in_flight(sc); } #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 */ 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 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 || type == IEEE80211_FC0_TYPE_CTL || (m->m_flags & M_EAPOL) != 0) { 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 { /* for data frames, use RS table */ IWM_DPRINTF(sc, IWM_DEBUG_TXRATE, "%s: DATA\n", __func__); ridx = iwm_rate2ridx(sc, ni->ni_txrate); if (ridx == -1) ridx = 0; /* This is the index into the programmed table */ tx->initial_rate_index = 0; tx->tx_flags |= htole32(IWM_TX_CMD_FLG_STA_RATE); } 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? */ if (sc->cfg->device_family == IWM_DEVICE_FAMILY_9000) rate_flags = IWM_RATE_MCS_ANT_B_MSK; else rate_flags = IWM_RATE_MCS_ANT_A_MSK; 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]; 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); } flags = 0; totlen = m->m_pkthdr.len; 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; } 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; tx->offload_assist |= htole16(1 << IWM_TX_CMD_OFFLD_PAD); pad = 4 - (hdrlen & 3); } else { tx->offload_assist = 0; pad = 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. */ memset(desc, 0, sizeof(*desc)); 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); } if (sc->sc_tx_timer == 0) callout_reset(&sc->sc_watchdog_to, hz, iwm_watchdog, sc); 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_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_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/quota.c */ static int iwm_update_quotas(struct iwm_softc *sc, struct iwm_vap *ivp) { 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 (ivp) { id = ivp->phy_ctxt->id; KASSERT(id < IWM_MAX_BINDINGS, ("invalid id")); colors[id] = ivp->phy_ctxt->color; if (1) n_ifs[id] = 1; } /* * The FW's scheduling session consists of * IWM_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_MAX_QUOTA / num_active_macs; quota_rem = IWM_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_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); IWM_DPRINTF(sc, IWM_DEBUG_STATE, "%s: Current node bssid: %s\n", __func__, ether_sprintf(ni->ni_bssid)); in->in_assoc = 0; iv->iv_auth = 1; /* * Firmware bug - it'll crash if the beacon interval is less * than 16. We can't avoid connecting at all, so refuse the * station state change, this will cause net80211 to abandon * attempts to connect to this AP, and eventually wpa_s will * blacklist the AP... */ if (ni->ni_intval < 16) { device_printf(sc->sc_dev, "AP %s beacon interval is %d, refusing due to firmware bug!\n", ether_sprintf(ni->ni_bssid), ni->ni_intval); error = EINVAL; goto out; } 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_mac_ctxt_changed(sc, vap)) != 0) { device_printf(sc->sc_dev, "%s: failed to update MAC\n", __func__); goto out; } } else { if ((error = iwm_mac_ctxt_add(sc, vap)) != 0) { device_printf(sc->sc_dev, "%s: failed to add MAC\n", __func__); goto out; } } sc->sc_firmware_state = 1; if ((error = iwm_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; } iv->phy_ctxt = &sc->sc_phyctxt[0]; if ((error = iwm_binding_add_vif(sc, iv)) != 0) { device_printf(sc->sc_dev, "%s: binding update cmd\n", __func__); goto out; } sc->sc_firmware_state = 2; /* * Authentication becomes unreliable when powersaving is left enabled * here. Powersaving will be activated again when association has * finished or is aborted. */ iv->ps_disabled = TRUE; error = iwm_power_update_mac(sc); iv->ps_disabled = FALSE; if (error != 0) { device_printf(sc->sc_dev, "%s: failed to update power management\n", __func__); goto out; } if ((error = iwm_add_sta(sc, in)) != 0) { device_printf(sc->sc_dev, "%s: failed to add sta\n", __func__); goto out; } sc->sc_firmware_state = 3; /* * 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_TE_SESSION_PROTECTION_MAX_TIME_MS; iwm_protect_session(sc, iv, duration, 500 /* XXX magic number */, TRUE); error = 0; out: if (error != 0) iv->iv_auth = 0; ieee80211_free_node(ni); return (error); } 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 uint8_t iwm_rate_from_ucode_rate(uint32_t rate_n_flags) { uint8_t plcp = rate_n_flags & 0xff; int i; for (i = 0; i <= IWM_RIDX_MAX; i++) { if (iwm_rates[i].plcp == plcp) return iwm_rates[i].rate; } return 0; } uint8_t iwm_ridx2rate(struct ieee80211_rateset *rs, int ridx) { int i; uint8_t rval; for (i = 0; i < rs->rs_nrates; i++) { rval = (rs->rs_rates[i] & IEEE80211_RATE_VAL); if (rval == iwm_rates[ridx].rate) return rs->rs_rates[i]; } return 0; } static int iwm_rate2ridx(struct iwm_softc *sc, uint8_t rate) { int i; for (i = 0; i <= IWM_RIDX_MAX; i++) { if (iwm_rates[i].rate == rate) return i; } device_printf(sc->sc_dev, "%s: WARNING: device rate for %u not found!\n", __func__, rate); return -1; } static void iwm_setrates(struct iwm_softc *sc, struct iwm_node *in, int rix) { struct ieee80211_node *ni = &in->in_ni; struct iwm_lq_cmd *lq = &in->in_lq; struct ieee80211_rateset *rs = &ni->ni_rates; int nrates = rs->rs_nrates; int i, ridx, tab = 0; // int txant = 0; KASSERT(rix >= 0 && rix < nrates, ("invalid rix")); 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; } nrates = imin(rix + 1, nrates); IWM_DPRINTF(sc, IWM_DEBUG_TXRATE, "%s: nrates=%d\n", __func__, nrates); /* 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). */ for (i = 0; i < nrates; i++) { int rate = rs->rs_rates[rix - i] & IEEE80211_RATE_VAL; int nextant; /* Map 802.11 rate to HW rate index. */ ridx = iwm_rate2ridx(sc, rate); if (ridx == -1) continue; #if 0 if (txant == 0) txant = iwm_get_valid_tx_ant(sc); nextant = 1<<(ffs(txant)-1); txant &= ~nextant; #else nextant = iwm_get_valid_tx_ant(sc); #endif 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 void iwm_bring_down_firmware(struct iwm_softc *sc, struct ieee80211vap *vap) { struct iwm_vap *ivp = IWM_VAP(vap); int error; /* Avoid Tx watchdog triggering, when transfers get dropped here. */ sc->sc_tx_timer = 0; ivp->iv_auth = 0; if (sc->sc_firmware_state == 3) { iwm_xmit_queue_drain(sc); // iwm_flush_tx_path(sc, 0xf, IWM_CMD_SYNC); error = iwm_rm_sta(sc, vap, TRUE); if (error) { device_printf(sc->sc_dev, "%s: Failed to remove station: %d\n", __func__, error); } } if (sc->sc_firmware_state == 3) { error = iwm_mac_ctxt_changed(sc, vap); if (error) { device_printf(sc->sc_dev, "%s: Failed to change mac context: %d\n", __func__, error); } } if (sc->sc_firmware_state == 3) { error = iwm_sf_update(sc, vap, FALSE); if (error) { device_printf(sc->sc_dev, "%s: Failed to update smart FIFO: %d\n", __func__, error); } } if (sc->sc_firmware_state == 3) { error = iwm_rm_sta_id(sc, vap); if (error) { device_printf(sc->sc_dev, "%s: Failed to remove station id: %d\n", __func__, error); } } if (sc->sc_firmware_state == 3) { error = iwm_update_quotas(sc, NULL); if (error) { device_printf(sc->sc_dev, "%s: Failed to update PHY quota: %d\n", __func__, error); } } if (sc->sc_firmware_state == 3) { /* XXX Might need to specify bssid correctly. */ error = iwm_mac_ctxt_changed(sc, vap); if (error) { device_printf(sc->sc_dev, "%s: Failed to change mac context: %d\n", __func__, error); } } if (sc->sc_firmware_state == 3) { sc->sc_firmware_state = 2; } if (sc->sc_firmware_state > 1) { error = iwm_binding_remove_vif(sc, ivp); if (error) { device_printf(sc->sc_dev, "%s: Failed to remove channel ctx: %d\n", __func__, error); } } if (sc->sc_firmware_state > 1) { sc->sc_firmware_state = 1; } ivp->phy_ctxt = NULL; if (sc->sc_firmware_state > 0) { error = iwm_mac_ctxt_changed(sc, vap); if (error) { device_printf(sc->sc_dev, "%s: Failed to change mac context: %d\n", __func__, error); } } if (sc->sc_firmware_state > 0) { error = iwm_power_update_mac(sc); if (error != 0) { device_printf(sc->sc_dev, "%s: failed to update power management\n", __func__); } } sc->sc_firmware_state = 0; } 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 arg=0x%x\n", ieee80211_state_name[vap->iv_state], ieee80211_state_name[nstate], arg); IEEE80211_UNLOCK(ic); IWM_LOCK(sc); if ((sc->sc_flags & IWM_FLAG_SCAN_RUNNING) && (nstate == IEEE80211_S_AUTH || nstate == IEEE80211_S_ASSOC || nstate == IEEE80211_S_RUN)) { /* Stop blinking for a scan, when authenticating. */ iwm_led_blink_stop(sc); } if (vap->iv_state == IEEE80211_S_RUN && nstate != IEEE80211_S_RUN) { iwm_led_disable(sc); /* disable beacon filtering if we're hopping out of RUN */ iwm_disable_beacon_filter(sc); if (((in = IWM_NODE(vap->iv_bss)) != NULL)) in->in_assoc = 0; } if ((vap->iv_state == IEEE80211_S_AUTH || vap->iv_state == IEEE80211_S_ASSOC || vap->iv_state == IEEE80211_S_RUN) && (nstate == IEEE80211_S_INIT || nstate == IEEE80211_S_SCAN || nstate == IEEE80211_S_AUTH)) { iwm_stop_session_protection(sc, ivp); } if ((vap->iv_state == IEEE80211_S_RUN || vap->iv_state == IEEE80211_S_ASSOC) && nstate == IEEE80211_S_INIT) { /* * In this case, iv_newstate() wants to send an 80211 frame on * the network that we are leaving. So we need to call it, * before tearing down all the firmware state. */ IWM_UNLOCK(sc); IEEE80211_LOCK(ic); ivp->iv_newstate(vap, nstate, arg); IEEE80211_UNLOCK(ic); IWM_LOCK(sc); iwm_bring_down_firmware(sc, vap); IWM_UNLOCK(sc); IEEE80211_LOCK(ic); return 0; } switch (nstate) { case IEEE80211_S_INIT: case IEEE80211_S_SCAN: break; case IEEE80211_S_AUTH: iwm_bring_down_firmware(sc, vap); if ((error = iwm_auth(vap, sc)) != 0) { device_printf(sc->sc_dev, "%s: could not move to auth state: %d\n", __func__, error); iwm_bring_down_firmware(sc, vap); IWM_UNLOCK(sc); IEEE80211_LOCK(ic); return 1; } break; case IEEE80211_S_ASSOC: /* * EBS may be disabled due to previous failures reported by FW. * Reset EBS status here assuming environment has been changed. */ sc->last_ebs_successful = TRUE; break; case IEEE80211_S_RUN: in = IWM_NODE(vap->iv_bss); /* Update the association state, now we have it all */ /* (eg associd comes in at this point */ error = iwm_update_sta(sc, in); if (error != 0) { device_printf(sc->sc_dev, "%s: failed to update STA\n", __func__); IWM_UNLOCK(sc); IEEE80211_LOCK(ic); return error; } in->in_assoc = 1; error = iwm_mac_ctxt_changed(sc, vap); if (error != 0) { device_printf(sc->sc_dev, "%s: failed to update MAC: %d\n", __func__, error); } iwm_sf_update(sc, vap, FALSE); iwm_enable_beacon_filter(sc, ivp); iwm_power_update_mac(sc); iwm_update_quotas(sc, ivp); int rix = ieee80211_ratectl_rate(&in->in_ni, NULL, 0); iwm_setrates(sc, in, rix); if ((error = iwm_send_lq_cmd(sc, &in->in_lq, TRUE)) != 0) { device_printf(sc->sc_dev, "%s: IWM_LQ_CMD failed: %d\n", __func__, error); } iwm_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)); } 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_send_cmd_pdu(sc, IWM_BT_CONFIG, 0, sizeof(bt_cmd), &bt_cmd); } static boolean_t iwm_is_lar_supported(struct iwm_softc *sc) { boolean_t nvm_lar = sc->nvm_data->lar_enabled; boolean_t tlv_lar = iwm_fw_has_capa(sc, IWM_UCODE_TLV_CAPA_LAR_SUPPORT); if (iwm_lar_disable) return FALSE; /* * Enable LAR only if it is supported by the FW (TLV) && * enabled in the NVM */ if (sc->cfg->device_family >= IWM_DEVICE_FAMILY_8000) return nvm_lar && tlv_lar; else return tlv_lar; } static boolean_t iwm_is_wifi_mcc_supported(struct iwm_softc *sc) { return iwm_fw_has_api(sc, IWM_UCODE_TLV_API_WIFI_MCC_UPDATE) || iwm_fw_has_capa(sc, IWM_UCODE_TLV_CAPA_LAR_MULTI_MCC); } 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 = iwm_fw_has_capa(sc, IWM_UCODE_TLV_CAPA_LAR_SUPPORT_V2); if (!iwm_is_lar_supported(sc)) { IWM_DPRINTF(sc, IWM_DEBUG_LAR, "%s: no LAR support\n", __func__); return 0; } memset(&mcc_cmd, 0, sizeof(mcc_cmd)); mcc_cmd.mcc = htole16(alpha2[0] << 8 | alpha2[1]); if (iwm_is_wifi_mcc_supported(sc)) 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_LAR, "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_LAR, "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_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; sc->sf_state = IWM_SF_UNINIT; if ((error = iwm_start_hw(sc)) != 0) { printf("iwm_start_hw: failed %d\n", error); return error; } if ((error = iwm_run_init_ucode(sc, 0)) != 0) { printf("iwm_run_init_ucode: failed %d\n", error); return error; } /* * should stop and start HW since that INIT * image just loaded */ iwm_stop_device(sc); sc->sc_ps_disabled = FALSE; 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_load_ucode_wait_alive(sc, IWM_UCODE_REGULAR); if (error) { device_printf(sc->sc_dev, "could not load firmware\n"); goto error; } error = iwm_sf_update(sc, NULL, FALSE); if (error) device_printf(sc->sc_dev, "Failed to initialize Smart Fifo\n"); if ((error = iwm_send_bt_init_conf(sc)) != 0) { device_printf(sc->sc_dev, "bt init conf failed\n"); goto error; } error = iwm_send_tx_ant_cfg(sc, iwm_get_valid_tx_ant(sc)); if (error != 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->sc_phy_db)) != 0) 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_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_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->cfg->device_family == IWM_DEVICE_FAMILY_7000) iwm_tt_tx_backoff(sc, 0); if (iwm_config_ltr(sc) != 0) device_printf(sc->sc_dev, "PCIe LTR configuration failed\n"); error = iwm_power_update_device(sc); if (error) goto error; if ((error = iwm_send_update_mcc_cmd(sc, "ZZ")) != 0) goto error; if (iwm_fw_has_capa(sc, IWM_UCODE_TLV_CAPA_UMAC_SCAN)) { if ((error = iwm_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_ac_to_tx_fifo[ac]); if (error) goto error; } if ((error = iwm_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_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; } 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; } if (sc->sc_tx_timer == 0) { callout_reset(&sc->sc_watchdog_to, hz, iwm_watchdog, sc); } 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); sc->sc_flags &= ~IWM_FLAG_SCAN_RUNNING; } static void iwm_watchdog(void *arg) { struct iwm_softc *sc = arg; struct ieee80211com *ic = &sc->sc_ic; if (sc->sc_attached == 0) return; 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; int rfkill = 0; IWM_LOCK(sc); if (ic->ic_nrunning > 0) { if (!(sc->sc_flags & IWM_FLAG_HW_INITED)) { iwm_init(sc); rfkill = iwm_check_rfkill(sc); if (!rfkill) startall = 1; } } else if (sc->sc_flags & IWM_FLAG_HW_INITED) iwm_stop(sc); IWM_UNLOCK(sc); if (startall) ieee80211_start_all(ic); else if (rfkill) taskqueue_enqueue(sc->sc_tq, &sc->sc_rftoggle_task); } static void iwm_rftoggle_task(void *arg, int npending __unused) { struct iwm_softc *sc = arg; struct ieee80211com *ic = &sc->sc_ic; int rfkill; IWM_LOCK(sc); rfkill = iwm_check_rfkill(sc); IWM_UNLOCK(sc); if (rfkill) { device_printf(sc->sc_dev, "%s: rfkill switch, disabling interface\n", __func__); ieee80211_suspend_all(ic); ieee80211_notify_radio(ic, 0); } else { device_printf(sc->sc_dev, "%s: rfkill cleared, re-enabling interface\n", __func__); ieee80211_resume_all(ic); ieee80211_notify_radio(ic, 1); } } /* * 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->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->error_event_table[0]; 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->umac_error_event_table) iwm_nic_umac_error(sc); } #endif static void iwm_handle_rxb(struct iwm_softc *sc, struct mbuf *m) { struct ieee80211com *ic = &sc->sc_ic; struct iwm_cmd_response *cresp; struct mbuf *m1; uint32_t offset = 0; uint32_t maxoff = IWM_RBUF_SIZE; uint32_t nextoff; boolean_t stolen = FALSE; #define HAVEROOM(a) \ ((a) + sizeof(uint32_t) + sizeof(struct iwm_cmd_header) < maxoff) while (HAVEROOM(offset)) { struct iwm_rx_packet *pkt = mtodoff(m, struct iwm_rx_packet *, offset); int qid, idx, code, len; qid = pkt->hdr.qid; idx = pkt->hdr.idx; code = IWM_WIDE_ID(pkt->hdr.flags, pkt->hdr.code); /* * randomly get these from the firmware, no idea why. * they at least seem harmless, so just ignore them for now */ if ((pkt->hdr.code == 0 && (qid & ~0x80) == 0 && idx == 0) || pkt->len_n_flags == htole32(IWM_FH_RSCSR_FRAME_INVALID)) { break; } IWM_DPRINTF(sc, IWM_DEBUG_INTR, "rx packet qid=%d idx=%d type=%x\n", qid & ~0x80, pkt->hdr.idx, code); len = iwm_rx_packet_len(pkt); len += sizeof(uint32_t); /* account for status word */ nextoff = offset + roundup2(len, IWM_FH_RSCSR_FRAME_ALIGN); iwm_notification_wait_notify(sc->sc_notif_wait, code, pkt); switch (code) { case IWM_REPLY_RX_PHY_CMD: iwm_rx_rx_phy_cmd(sc, pkt); break; case IWM_REPLY_RX_MPDU_CMD: { /* * If this is the last frame in the RX buffer, we * can directly feed the mbuf to the sharks here. */ struct iwm_rx_packet *nextpkt = mtodoff(m, struct iwm_rx_packet *, nextoff); if (!HAVEROOM(nextoff) || (nextpkt->hdr.code == 0 && (nextpkt->hdr.qid & ~0x80) == 0 && nextpkt->hdr.idx == 0) || (nextpkt->len_n_flags == htole32(IWM_FH_RSCSR_FRAME_INVALID))) { if (iwm_rx_mpdu(sc, m, offset, stolen)) { stolen = FALSE; /* Make sure we abort the loop */ nextoff = maxoff; } break; } /* * Use m_copym instead of m_split, because that * makes it easier to keep a valid rx buffer in * the ring, when iwm_rx_mpdu() fails. * * We need to start m_copym() at offset 0, to get the * M_PKTHDR flag preserved. */ m1 = m_copym(m, 0, M_COPYALL, M_NOWAIT); if (m1) { if (iwm_rx_mpdu(sc, m1, offset, stolen)) stolen = TRUE; else m_freem(m1); } break; } case IWM_TX_CMD: iwm_rx_tx_cmd(sc, pkt); 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); resp = (void *)pkt->data; 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_ALIVE: break; case IWM_CALIB_RES_NOTIF_PHY_DB: break; case IWM_STATISTICS_NOTIFICATION: iwm_handle_rx_statistics(sc, pkt); break; case IWM_NVM_ACCESS_CMD: case IWM_MCC_UPDATE_CMD: if (sc->sc_wantresp == (((qid & ~0x80) << 16) | idx)) { memcpy(sc->sc_cmd_resp, pkt, sizeof(sc->sc_cmd_resp)); } break; case IWM_MCC_CHUB_UPDATE_CMD: { struct iwm_mcc_chub_notif *notif; notif = (void *)pkt->data; 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_LAR, "fw source %d sent CC '%s'\n", notif->source_id, sc->sc_fw_mcc); break; } case IWM_DTS_MEASUREMENT_NOTIFICATION: case IWM_WIDE_ID(IWM_PHY_OPS_GROUP, IWM_DTS_MEASUREMENT_NOTIF_WIDE): { struct iwm_dts_measurement_notif_v1 *notif; if (iwm_rx_packet_payload_len(pkt) < sizeof(*notif)) { device_printf(sc->sc_dev, "Invalid DTS_MEASUREMENT_NOTIFICATION\n"); break; } notif = (void *)pkt->data; IWM_DPRINTF(sc, IWM_DEBUG_TEMP, "IWM_DTS_MEASUREMENT_NOTIFICATION - %d\n", notif->temp); 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_LTR_CONFIG: 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_WIDE_ID(IWM_ALWAYS_LONG_GROUP, IWM_SCAN_ABORT_UMAC): case IWM_SCAN_OFFLOAD_REQUEST_CMD: case IWM_SCAN_OFFLOAD_ABORT_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_WIDE_ID(IWM_ALWAYS_LONG_GROUP, IWM_FW_PAGING_BLOCK_CMD): case IWM_BT_CONFIG: case IWM_REPLY_THERMAL_MNG_BACKOFF: cresp = (void *)pkt->data; if (sc->sc_wantresp == (((qid & ~0x80) << 16) | idx)) { memcpy(sc->sc_cmd_resp, pkt, sizeof(*pkt)+sizeof(*cresp)); } break; /* ignore */ case IWM_PHY_DB_CMD: break; case IWM_INIT_COMPLETE_NOTIF: break; case IWM_SCAN_OFFLOAD_COMPLETE: iwm_rx_lmac_scan_complete_notif(sc, pkt); if (sc->sc_flags & IWM_FLAG_SCAN_RUNNING) { sc->sc_flags &= ~IWM_FLAG_SCAN_RUNNING; ieee80211_runtask(ic, &sc->sc_es_task); } break; case IWM_SCAN_ITERATION_COMPLETE: { struct iwm_lmac_scan_complete_notif *notif; notif = (void *)pkt->data; break; } case IWM_SCAN_COMPLETE_UMAC: iwm_rx_umac_scan_complete_notif(sc, pkt); if (sc->sc_flags & IWM_FLAG_SCAN_RUNNING) { sc->sc_flags &= ~IWM_FLAG_SCAN_RUNNING; ieee80211_runtask(ic, &sc->sc_es_task); } break; case IWM_SCAN_ITERATION_COMPLETE_UMAC: { struct iwm_umac_scan_iter_complete_notif *notif; notif = (void *)pkt->data; IWM_DPRINTF(sc, IWM_DEBUG_SCAN, "UMAC scan iteration " "complete, status=0x%x, %d channels scanned\n", notif->status, notif->scanned_channels); break; } case IWM_REPLY_ERROR: { struct iwm_error_resp *resp; resp = (void *)pkt->data; 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: iwm_rx_time_event_notif(sc, pkt); break; /* * Firmware versions 21 and 22 generate some DEBUG_LOG_MSG * messages. Just ignore them for now. */ case IWM_DEBUG_LOG_MSG: break; case IWM_MCAST_FILTER_CMD: break; case IWM_SCD_QUEUE_CFG: { struct iwm_scd_txq_cfg_rsp *rsp; rsp = (void *)pkt->data; 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, "code %x, frame %d/%d %x unhandled\n", code, qid & ~0x80, 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 (!(qid & (1 << 7))) iwm_cmd_done(sc, pkt); offset = nextoff; } if (stolen) m_freem(m); #undef HAVEROOM } /* * 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) { int count; uint32_t wreg; uint16_t hw; bus_dmamap_sync(sc->rxq.stat_dma.tag, sc->rxq.stat_dma.map, BUS_DMASYNC_POSTREAD); if (sc->cfg->mqrx_supported) { count = IWM_RX_MQ_RING_COUNT; wreg = IWM_RFH_Q0_FRBDCB_WIDX_TRG; } else { count = IWM_RX_LEGACY_RING_COUNT; wreg = IWM_FH_RSCSR_CHNL0_WPTR; } 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 = &ring->data[ring->cur]; bus_dmamap_sync(ring->data_dmat, data->map, BUS_DMASYNC_POSTREAD); IWM_DPRINTF(sc, IWM_DEBUG_INTR, "%s: hw = %d cur = %d\n", __func__, hw, ring->cur); iwm_handle_rxb(sc, data->m); ring->cur = (ring->cur + 1) % count; } /* * Tell the firmware that it can reuse the ring entries that * we have just processed. * Seems like the hardware gets upset unless we align * the write by 8?? */ hw = (hw == 0) ? count - 1 : hw - 1; IWM_WRITE(sc, wreg, rounddown2(hw, 8)); } 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); /* Safely ignore these bits for debug checks below */ 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_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); /* Reset our firmware state tracking. */ sc->sc_firmware_state = 0; /* 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); 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; taskqueue_enqueue(sc->sc_tq, &sc->sc_rftoggle_task); } /* * 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_3168_1 0x24fb #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 #define PCI_PRODUCT_INTEL_WL_8265_1 0x24fd #define PCI_PRODUCT_INTEL_WL_9560_1 0x9df0 #define PCI_PRODUCT_INTEL_WL_9560_2 0xa370 #define PCI_PRODUCT_INTEL_WL_9560_3 0x31dc #define PCI_PRODUCT_INTEL_WL_9260_1 0x2526 static const struct iwm_devices { uint16_t device; const struct iwm_cfg *cfg; } iwm_devices[] = { { PCI_PRODUCT_INTEL_WL_3160_1, &iwm3160_cfg }, { PCI_PRODUCT_INTEL_WL_3160_2, &iwm3160_cfg }, { PCI_PRODUCT_INTEL_WL_3165_1, &iwm3165_cfg }, { PCI_PRODUCT_INTEL_WL_3165_2, &iwm3165_cfg }, { PCI_PRODUCT_INTEL_WL_3168_1, &iwm3168_cfg }, { PCI_PRODUCT_INTEL_WL_7260_1, &iwm7260_cfg }, { PCI_PRODUCT_INTEL_WL_7260_2, &iwm7260_cfg }, { PCI_PRODUCT_INTEL_WL_7265_1, &iwm7265_cfg }, { PCI_PRODUCT_INTEL_WL_7265_2, &iwm7265_cfg }, { PCI_PRODUCT_INTEL_WL_8260_1, &iwm8260_cfg }, { PCI_PRODUCT_INTEL_WL_8260_2, &iwm8260_cfg }, { PCI_PRODUCT_INTEL_WL_8265_1, &iwm8265_cfg }, { PCI_PRODUCT_INTEL_WL_9560_1, &iwm9560_cfg }, { PCI_PRODUCT_INTEL_WL_9560_2, &iwm9560_cfg }, { PCI_PRODUCT_INTEL_WL_9560_3, &iwm9560_cfg }, { PCI_PRODUCT_INTEL_WL_9260_1, &iwm9260_cfg }, }; 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].cfg->name); return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static int iwm_dev_check(device_t dev) { struct iwm_softc *sc; uint16_t devid; int i; sc = device_get_softc(dev); devid = pci_get_device(dev); for (i = 0; i < nitems(iwm_devices); i++) { if (iwm_devices[i].device == devid) { sc->cfg = iwm_devices[i].cfg; return (0); } } device_printf(dev, "unknown adapter type\n"); return ENXIO; } /* PCI registers */ #define PCI_CFG_RETRY_TIMEOUT 0x041 static int iwm_pci_attach(device_t dev) { struct iwm_softc *sc; int count, error, rid; uint16_t reg; sc = device_get_softc(dev); /* We disable the RETRY_TIMEOUT register (0x41) to keep * PCI Tx retries from interfering with C3 CPU state */ pci_write_config(dev, PCI_CFG_RETRY_TIMEOUT, 0x00, 1); /* 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; sc->sc_attached = 1; 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); TASK_INIT(&sc->sc_rftoggle_task, 0, iwm_rftoggle_task, sc); sc->sc_tq = taskqueue_create("iwm_taskq", M_WAITOK, taskqueue_thread_enqueue, &sc->sc_tq); error = taskqueue_start_threads(&sc->sc_tq, 1, 0, "iwm_taskq"); if (error != 0) { device_printf(dev, "can't start taskq thread, error %d\n", error); goto fail; } error = iwm_dev_check(dev); if (error != 0) goto fail; sc->sc_notif_wait = iwm_notification_wait_init(sc); if (sc->sc_notif_wait == NULL) { device_printf(dev, "failed to init notification wait struct\n"); goto fail; } sc->sf_state = IWM_SF_UNINIT; /* Init phy db */ sc->sc_phy_db = iwm_phy_db_init(sc); if (!sc->sc_phy_db) { device_printf(dev, "Cannot init phy_db\n"); goto fail; } /* Set EBS as successful as long as not stated otherwise by the FW. */ sc->last_ebs_successful = TRUE; /* PCI attach */ error = iwm_pci_attach(dev); if (error != 0) goto fail; sc->sc_wantresp = -1; sc->sc_hw_rev = IWM_READ(sc, IWM_CSR_HW_REV); /* * 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->cfg->device_family >= IWM_DEVICE_FAMILY_8000) { int ret; uint32_t hw_step; 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; } /* * 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; } } /* special-case 7265D, it has the same PCI IDs. */ if (sc->cfg == &iwm7265_cfg && (sc->sc_hw_rev & IWM_CSR_HW_REV_TYPE_MSK) == IWM_CSR_HW_REV_TYPE_7265D) { sc->cfg = &iwm7265d_cfg; } /* 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_PMGT | IEEE80211_C_SHSLOT | /* short slot time supported */ IEEE80211_C_SHPREAMBLE /* short preamble supported */ // IEEE80211_C_BGSCAN /* capable of bg scanning */ ; /* Advertise full-offload scanning */ ic->ic_flags_ext = IEEE80211_FEXT_SCAN_OFFLOAD; 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; #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 error = iwm_read_firmware(sc); if (error) { goto fail; } else if (sc->sc_fw.fw_fp == NULL) { /* * XXX Add a solution for properly deferring firmware load * during bootup. */ goto fail; } else { 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; } } 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_wme_update(struct ieee80211com *ic) { #define IWM_EXP2(x) ((1 << (x)) - 1) /* CWmin = 2^ECWmin - 1 */ struct iwm_softc *sc = ic->ic_softc; struct chanAccParams chp; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); struct iwm_vap *ivp = IWM_VAP(vap); struct iwm_node *in; struct wmeParams tmp[WME_NUM_AC]; int aci, error; if (vap == NULL) return (0); ieee80211_wme_ic_getparams(ic, &chp); IEEE80211_LOCK(ic); for (aci = 0; aci < WME_NUM_AC; aci++) tmp[aci] = chp.cap_wmeParams[aci]; IEEE80211_UNLOCK(ic); IWM_LOCK(sc); for (aci = 0; aci < WME_NUM_AC; aci++) { const struct wmeParams *ac = &tmp[aci]; ivp->queue_params[aci].aifsn = ac->wmep_aifsn; ivp->queue_params[aci].cw_min = IWM_EXP2(ac->wmep_logcwmin); ivp->queue_params[aci].cw_max = IWM_EXP2(ac->wmep_logcwmax); ivp->queue_params[aci].edca_txop = IEEE80211_TXOP_TO_US(ac->wmep_txopLimit); } ivp->have_wme = TRUE; if (ivp->is_uploaded && vap->iv_bss != NULL) { in = IWM_NODE(vap->iv_bss); if (in->in_assoc) { if ((error = iwm_mac_ctxt_changed(sc, vap)) != 0) { device_printf(sc->sc_dev, "%s: failed to update MAC\n", __func__); } } } IWM_UNLOCK(sc); return (0); #undef IWM_EXP2 } 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_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->nvm_data->hw_addr)); /* not all hardware can do 5GHz band */ if (!sc->nvm_data->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_wme_update; 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; ivp->id = IWM_DEFAULT_MACID; ivp->color = IWM_DEFAULT_COLOR; ivp->have_wme = FALSE; ivp->ps_disabled = FALSE; 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 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_xmit_queue_drain(struct iwm_softc *sc) { struct mbuf *m; struct ieee80211_node *ni; while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) { ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; ieee80211_free_node(ni); m_freem(m); } } 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 (sc->sc_flags & IWM_FLAG_SCAN_RUNNING) { /* This should not be possible */ device_printf(sc->sc_dev, "%s: Previous scan not completed yet\n", __func__); } if (iwm_fw_has_capa(sc, IWM_UCODE_TLV_CAPA_UMAC_SCAN)) error = iwm_umac_scan(sc); else error = iwm_lmac_scan(sc); if (error != 0) { device_printf(sc->sc_dev, "could not initiate scan\n"); IWM_UNLOCK(sc); ieee80211_cancel_scan(vap); } else { sc->sc_flags |= IWM_FLAG_SCAN_RUNNING; 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_led_enable(sc); if (sc->sc_flags & IWM_FLAG_SCAN_RUNNING) { /* * Removing IWM_FLAG_SCAN_RUNNING now, is fine because * both iwm_scan_end and iwm_scan_start run in the ic->ic_tq * taskqueue. */ sc->sc_flags &= ~IWM_FLAG_SCAN_RUNNING; iwm_scan_stop_wait(sc); } IWM_UNLOCK(sc); /* * Make sure we don't race, if sc_es_task is still enqueued here. * This is to make sure that it won't call ieee80211_scan_done * when we have already started the next scan. */ taskqueue_cancel(ic->ic_tq, &sc->sc_es_task, NULL); } 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) { } 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; /* * We disable the RETRY_TIMEOUT register (0x41) to keep * PCI Tx retries from interfering with C3 CPU state. */ pci_write_config(dev, PCI_CFG_RETRY_TIMEOUT, 0x00, 1); if (!sc->sc_attached) return 0; 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); if (!sc->sc_attached) return (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; if (!sc->sc_attached) return 0; sc->sc_attached = 0; if (do_net80211) { ieee80211_draintask(&sc->sc_ic, &sc->sc_es_task); } iwm_stop_device(sc); taskqueue_drain_all(sc->sc_tq); taskqueue_free(sc->sc_tq); if (do_net80211) { IWM_LOCK(sc); iwm_xmit_queue_drain(sc); IWM_UNLOCK(sc); ieee80211_ifdetach(&sc->sc_ic); } callout_drain(&sc->sc_led_blink_to); callout_drain(&sc->sc_watchdog_to); iwm_phy_db_free(sc->sc_phy_db); sc->sc_phy_db = NULL; iwm_free_nvm_data(sc->nvm_data); /* 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); iwm_free_fw_paging(sc); /* Finished with the hardware - detach things */ iwm_pci_detach(dev); if (sc->sc_notif_wait != NULL) { iwm_notification_wait_free(sc->sc_notif_wait); sc->sc_notif_wait = NULL; } 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_PNP_INFO("U16:device;P:#;T:vendor=0x8086", pci, iwm_pci_driver, iwm_devices, nitems(iwm_devices)); MODULE_DEPEND(iwm, firmware, 1, 1, 1); MODULE_DEPEND(iwm, pci, 1, 1, 1); MODULE_DEPEND(iwm, wlan, 1, 1, 1); diff --git a/sys/dev/rtwn/rtl8188e/r88e_rx.c b/sys/dev/rtwn/rtl8188e/r88e_rx.c index dad1bc56446a..16510839bd44 100644 --- a/sys/dev/rtwn/rtl8188e/r88e_rx.c +++ b/sys/dev/rtwn/rtl8188e/r88e_rx.c @@ -1,247 +1,249 @@ /* $OpenBSD: if_urtwn.c,v 1.16 2011/02/10 17:26:40 jakemsr Exp $ */ /*- * Copyright (c) 2010 Damien Bergamini * Copyright (c) 2014 Kevin Lo * Copyright (c) 2015-2016 Andriy Voskoboinyk * * 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 int r88e_classify_intr(struct rtwn_softc *sc, void *buf, int len) { struct r92c_rx_stat *stat = buf; int report_sel = MS(le32toh(stat->rxdw3), R88E_RXDW3_RPT); switch (report_sel) { case R88E_RXDW3_RPT_RX: return (RTWN_RX_DATA); case R88E_RXDW3_RPT_TX1: /* per-packet Tx report */ case R88E_RXDW3_RPT_TX2: /* periodical Tx report */ return (RTWN_RX_TX_REPORT); case R88E_RXDW3_RPT_HIS: return (RTWN_RX_OTHER); default: /* shut up the compiler */ return (RTWN_RX_DATA); } } void r88e_ratectl_tx_complete(struct rtwn_softc *sc, uint8_t *buf, int len) { #if __FreeBSD_version >= 1200012 struct ieee80211_ratectl_tx_status txs; #endif struct r88e_tx_rpt_ccx *rpt; struct ieee80211_node *ni; uint8_t macid; int ntries; /* Skip Rx descriptor. */ buf += sizeof(struct r92c_rx_stat); len -= sizeof(struct r92c_rx_stat); rpt = (struct r88e_tx_rpt_ccx *)buf; if (len != sizeof(*rpt)) { RTWN_DPRINTF(sc, RTWN_DEBUG_INTR, "%s: wrong report size (%d, must be %zu)\n", __func__, len, sizeof(*rpt)); return; } RTWN_DPRINTF(sc, RTWN_DEBUG_INTR, "%s: ccx report dump: 0: %02X, 1: %02X, 2: %02X, queue time: " "low %02X, high %02X, final ridx: %02X, 6: %02X, 7: %02X\n", __func__, rpt->rptb0, rpt->rptb1, rpt->rptb2, rpt->queue_time_low, rpt->queue_time_high, rpt->final_rate, rpt->rptb6, rpt->rptb7); macid = MS(rpt->rptb1, R88E_RPTB1_MACID); if (macid > sc->macid_limit) { device_printf(sc->sc_dev, "macid %u is too big; increase MACID_MAX limit\n", macid); return; } ntries = MS(rpt->rptb2, R88E_RPTB2_RETRY_CNT); ni = sc->node_list[macid]; if (ni != NULL) { RTWN_DPRINTF(sc, RTWN_DEBUG_INTR, "%s: frame for macid %u was" "%s sent (%d retries)\n", __func__, macid, (rpt->rptb1 & R88E_RPTB1_PKT_OK) ? "" : " not", ntries); #if __FreeBSD_version >= 1200012 txs.flags = IEEE80211_RATECTL_STATUS_LONG_RETRY | IEEE80211_RATECTL_STATUS_FINAL_RATE; txs.long_retries = ntries; if (rpt->final_rate > RTWN_RIDX_OFDM54) { /* MCS */ txs.final_rate = rpt->final_rate - RTWN_RIDX_HT_MCS_SHIFT; txs.final_rate |= IEEE80211_RATE_MCS; } else txs.final_rate = ridx2rate[rpt->final_rate]; if (rpt->rptb1 & R88E_RPTB1_PKT_OK) txs.status = IEEE80211_RATECTL_TX_SUCCESS; else if (rpt->rptb2 & R88E_RPTB2_RETRY_OVER) txs.status = IEEE80211_RATECTL_TX_FAIL_LONG; else if (rpt->rptb2 & R88E_RPTB2_LIFE_EXPIRE) txs.status = IEEE80211_RATECTL_TX_FAIL_EXPIRED; else txs.status = IEEE80211_RATECTL_TX_FAIL_UNSPECIFIED; ieee80211_ratectl_tx_complete(ni, &txs); #else struct ieee80211vap *vap = ni->ni_vap; if (rpt->rptb1 & R88E_RPTB1_PKT_OK) { ieee80211_ratectl_tx_complete(vap, ni, IEEE80211_RATECTL_TX_SUCCESS, &ntries, NULL); } else { ieee80211_ratectl_tx_complete(vap, ni, IEEE80211_RATECTL_TX_FAILURE, &ntries, NULL); } #endif } else { RTWN_DPRINTF(sc, RTWN_DEBUG_INTR, "%s: macid %u, ni is NULL\n", __func__, macid); } } void r88e_handle_c2h_report(struct rtwn_softc *sc, uint8_t *buf, int len) { /* Skip Rx descriptor. */ buf += sizeof(struct r92c_rx_stat); len -= sizeof(struct r92c_rx_stat); if (len != R88E_INTR_MSG_LEN) { RTWN_DPRINTF(sc, RTWN_DEBUG_INTR, "%s: wrong interrupt message size (%d, must be %d)\n", __func__, len, R88E_INTR_MSG_LEN); return; } /* XXX TODO */ } int8_t r88e_get_rssi_cck(struct rtwn_softc *sc, void *physt) { struct r88e_rx_phystat *phy = (struct r88e_rx_phystat *)physt; int8_t lna_idx, vga_idx, rssi; lna_idx = (phy->agc_rpt & 0xe0) >> 5; vga_idx = (phy->agc_rpt & 0x1f); rssi = 6 - 2 * vga_idx; switch (lna_idx) { case 7: if (vga_idx > 27) rssi = -100 + 6; else rssi += -100 + 2 * 27; break; case 6: rssi += -48 + 2 * 2; break; case 5: rssi += -42 + 2 * 7; break; case 4: rssi += -36 + 2 * 7; break; case 3: rssi += -24 + 2 * 7; break; case 2: rssi += -6 + 2 * 5; if (sc->sc_flags & RTWN_FLAG_CCK_HIPWR) rssi -= 6; break; case 1: rssi += 8; break; case 0: rssi += 14; break; } return (rssi); } int8_t r88e_get_rssi_ofdm(struct rtwn_softc *sc, void *physt) { struct r88e_rx_phystat *phy = (struct r88e_rx_phystat *)physt; int rssi; /* Get average RSSI. */ rssi = ((phy->sig_qual >> 1) & 0x7f) - 110; return (rssi); } void r88e_get_rx_stats(struct rtwn_softc *sc, struct ieee80211_rx_stats *rxs, const void *desc, const void *physt_ptr) { const struct r88e_rx_phystat *physt = physt_ptr; r92c_get_rx_stats(sc, rxs, desc, physt_ptr); if (!sc->sc_ht40) { /* XXX center channel */ rxs->r_flags |= IEEE80211_R_IEEE | IEEE80211_R_FREQ; + rxs->r_flags |= IEEE80211_R_BAND; rxs->c_ieee = physt->chan; rxs->c_freq = ieee80211_ieee2mhz(rxs->c_ieee, IEEE80211_CHAN_2GHZ); + rxs->c_band = IEEE80211_CHAN_2GHZ; } } diff --git a/sys/dev/rtwn/rtl8812a/r12a_rx.c b/sys/dev/rtwn/rtl8812a/r12a_rx.c index d8dcb3de12c5..98e0cabdff7c 100644 --- a/sys/dev/rtwn/rtl8812a/r12a_rx.c +++ b/sys/dev/rtwn/rtl8812a/r12a_rx.c @@ -1,328 +1,330 @@ /*- * Copyright (c) 2016 Andriy Voskoboinyk * 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 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 AUTHOR 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. */ #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 #ifndef RTWN_WITHOUT_UCODE void r12a_ratectl_tx_complete(struct rtwn_softc *sc, uint8_t *buf, int len) { #if __FreeBSD_version >= 1200012 struct ieee80211_ratectl_tx_status txs; #endif struct r12a_c2h_tx_rpt *rpt; struct ieee80211_node *ni; int ntries; /* Skip Rx descriptor / report id / sequence fields. */ buf += sizeof(struct r92c_rx_stat) + 2; len -= sizeof(struct r92c_rx_stat) + 2; rpt = (struct r12a_c2h_tx_rpt *)buf; if (len != sizeof(*rpt)) { device_printf(sc->sc_dev, "%s: wrong report size (%d, must be %zu)\n", __func__, len, sizeof(*rpt)); return; } RTWN_DPRINTF(sc, RTWN_DEBUG_INTR, "%s: ccx report dump: 0: %02X, id: %02X, 2: %02X, queue time: " "low %02X, high %02X, final ridx: %02X, rsvd: %04X\n", __func__, rpt->txrptb0, rpt->macid, rpt->txrptb2, rpt->queue_time_low, rpt->queue_time_high, rpt->final_rate, rpt->reserved); if (rpt->macid > sc->macid_limit) { device_printf(sc->sc_dev, "macid %u is too big; increase MACID_MAX limit\n", rpt->macid); return; } ntries = MS(rpt->txrptb2, R12A_TXRPTB2_RETRY_CNT); ni = sc->node_list[rpt->macid]; if (ni != NULL) { RTWN_DPRINTF(sc, RTWN_DEBUG_INTR, "%s: frame for macid %u was" "%s sent (%d retries)\n", __func__, rpt->macid, (rpt->txrptb0 & (R12A_TXRPTB0_RETRY_OVER | R12A_TXRPTB0_LIFE_EXPIRE)) ? " not" : "", ntries); #if __FreeBSD_version >= 1200012 txs.flags = IEEE80211_RATECTL_STATUS_LONG_RETRY | IEEE80211_RATECTL_STATUS_FINAL_RATE; txs.long_retries = ntries; if (rpt->final_rate > RTWN_RIDX_OFDM54) { /* MCS */ txs.final_rate = rpt->final_rate - RTWN_RIDX_HT_MCS_SHIFT; txs.final_rate |= IEEE80211_RATE_MCS; } else txs.final_rate = ridx2rate[rpt->final_rate]; if (rpt->txrptb0 & R12A_TXRPTB0_RETRY_OVER) txs.status = IEEE80211_RATECTL_TX_FAIL_LONG; else if (rpt->txrptb0 & R12A_TXRPTB0_LIFE_EXPIRE) txs.status = IEEE80211_RATECTL_TX_FAIL_EXPIRED; else txs.status = IEEE80211_RATECTL_TX_SUCCESS; ieee80211_ratectl_tx_complete(ni, &txs); #else struct ieee80211vap *vap = ni->ni_vap; if (rpt->txrptb0 & R12A_TXRPTB0_RETRY_OVER) { ieee80211_ratectl_tx_complete(vap, ni, IEEE80211_RATECTL_TX_FAILURE, &ntries, NULL); } else { ieee80211_ratectl_tx_complete(vap, ni, IEEE80211_RATECTL_TX_SUCCESS, &ntries, NULL); } #endif } else { RTWN_DPRINTF(sc, RTWN_DEBUG_INTR, "%s: macid %u, ni is NULL\n", __func__, rpt->macid); } } void r12a_handle_c2h_report(struct rtwn_softc *sc, uint8_t *buf, int len) { struct r12a_softc *rs = sc->sc_priv; /* Skip Rx descriptor. */ buf += sizeof(struct r92c_rx_stat); len -= sizeof(struct r92c_rx_stat); if (len < 2) { device_printf(sc->sc_dev, "C2H report too short (len %d)\n", len); return; } len -= 2; switch (buf[0]) { /* command id */ case R12A_C2H_TX_REPORT: /* NOTREACHED */ KASSERT(0, ("use handle_tx_report() instead of %s\n", __func__)); break; case R12A_C2H_IQK_FINISHED: RTWN_DPRINTF(sc, RTWN_DEBUG_CALIB, "FW IQ calibration finished\n"); rs->rs_flags &= ~R12A_IQK_RUNNING; break; default: device_printf(sc->sc_dev, "%s: C2H report %u was not handled\n", __func__, buf[0]); } } #else void r12a_ratectl_tx_complete(struct rtwn_softc *sc, uint8_t *buf, int len) { /* Should not happen. */ device_printf(sc->sc_dev, "%s: called\n", __func__); } void r12a_handle_c2h_report(struct rtwn_softc *sc, uint8_t *buf, int len) { /* Should not happen. */ device_printf(sc->sc_dev, "%s: called\n", __func__); } #endif int r12a_check_frame_checksum(struct rtwn_softc *sc, struct mbuf *m) { struct r12a_softc *rs = sc->sc_priv; struct r92c_rx_stat *stat; uint32_t rxdw1; stat = mtod(m, struct r92c_rx_stat *); rxdw1 = le32toh(stat->rxdw1); if (rxdw1 & R12A_RXDW1_CKSUM) { RTWN_DPRINTF(sc, RTWN_DEBUG_RECV, "%s: %s/%s checksum is %s\n", __func__, (rxdw1 & R12A_RXDW1_UDP) ? "UDP" : "TCP", (rxdw1 & R12A_RXDW1_IPV6) ? "IPv6" : "IP", (rxdw1 & R12A_RXDW1_CKSUM_ERR) ? "invalid" : "valid"); if (rxdw1 & R12A_RXDW1_CKSUM_ERR) return (-1); if ((rxdw1 & R12A_RXDW1_IPV6) ? (rs->rs_flags & R12A_RXCKSUM6_EN) : (rs->rs_flags & R12A_RXCKSUM_EN)) { m->m_pkthdr.csum_flags = CSUM_IP_CHECKED | CSUM_IP_VALID | CSUM_DATA_VALID | CSUM_PSEUDO_HDR; m->m_pkthdr.csum_data = 0xffff; } } return (0); } uint8_t r12a_rx_radiotap_flags(const void *buf) { const struct r92c_rx_stat *stat = buf; uint8_t flags, rate; if (!(stat->rxdw4 & htole32(R12A_RXDW4_SPLCP))) return (0); rate = MS(le32toh(stat->rxdw3), R12A_RXDW3_RATE); if (RTWN_RATE_IS_CCK(rate)) flags = IEEE80211_RADIOTAP_F_SHORTPRE; else flags = IEEE80211_RADIOTAP_F_SHORTGI; return (flags); } void r12a_get_rx_stats(struct rtwn_softc *sc, struct ieee80211_rx_stats *rxs, const void *desc, const void *physt_ptr) { const struct r92c_rx_stat *stat = desc; const struct r12a_rx_phystat *physt = physt_ptr; uint32_t rxdw0, rxdw1, rxdw3, rxdw4; uint8_t rate; rxdw0 = le32toh(stat->rxdw0); rxdw1 = le32toh(stat->rxdw1); rxdw3 = le32toh(stat->rxdw3); rxdw4 = le32toh(stat->rxdw4); rate = MS(rxdw3, R12A_RXDW3_RATE); /* TODO: STBC */ if (rxdw4 & R12A_RXDW4_LDPC) rxs->c_pktflags |= IEEE80211_RX_F_LDPC; if (rxdw1 & R12A_RXDW1_AMPDU) { if (rxdw0 & R92C_RXDW0_PHYST) rxs->c_pktflags |= IEEE80211_RX_F_AMPDU; else rxs->c_pktflags |= IEEE80211_RX_F_AMPDU_MORE; } if ((rxdw4 & R12A_RXDW4_SPLCP) && rate >= RTWN_RIDX_HT_MCS(0)) rxs->c_pktflags |= IEEE80211_RX_F_SHORTGI; switch (MS(rxdw4, R12A_RXDW4_BW)) { case R12A_RXDW4_BW20: rxs->c_width = IEEE80211_RX_FW_20MHZ; break; case R12A_RXDW4_BW40: rxs->c_width = IEEE80211_RX_FW_40MHZ; break; case R12A_RXDW4_BW80: rxs->c_width = IEEE80211_RX_FW_80MHZ; break; default: break; } if (RTWN_RATE_IS_CCK(rate)) rxs->c_phytype = IEEE80211_RX_FP_11B; else { int is5ghz; /* XXX magic */ /* XXX check with RTL8812AU */ is5ghz = (physt->cfosho[2] != 0x01); if (rate < RTWN_RIDX_HT_MCS(0)) { if (is5ghz) rxs->c_phytype = IEEE80211_RX_FP_11A; else rxs->c_phytype = IEEE80211_RX_FP_11G; } else { if (is5ghz) rxs->c_phytype = IEEE80211_RX_FP_11NA; else rxs->c_phytype = IEEE80211_RX_FP_11NG; } } /* Map HW rate index to 802.11 rate. */ if (rate < RTWN_RIDX_HT_MCS(0)) { rxs->c_rate = ridx2rate[rate]; if (RTWN_RATE_IS_CCK(rate)) rxs->c_pktflags |= IEEE80211_RX_F_CCK; else rxs->c_pktflags |= IEEE80211_RX_F_OFDM; } else { /* MCS0~15. */ /* TODO: VHT rates */ rxs->c_rate = IEEE80211_RATE_MCS | (rate - RTWN_RIDX_HT_MCS_SHIFT); rxs->c_pktflags |= IEEE80211_RX_F_HT; } /* * XXX always zero for RTL8821AU * (vendor driver does not check this field) */ #if 0 rxs->r_flags |= IEEE80211_R_IEEE | IEEE80211_R_FREQ; + rxs->r_flags |= IEEE80211_R_BAND; rxs->c_ieee = MS(le16toh(physt->phyw1), R12A_PHYW1_CHAN); rxs->c_freq = ieee80211_ieee2mhz(rxs->c_ieee, (rxs->c_ieee < 36) ? IEEE80211_CHAN_2GHZ : IEEE80211_CHAN_5GHZ); + rxs->c_band = (rxs->c_ieee < 36) ? IEEE80211_CHAN_2GHZ : IEEE80211_CHAN_5GHZ; #endif } diff --git a/sys/dev/usb/wlan/if_rsu.c b/sys/dev/usb/wlan/if_rsu.c index 4dd1c624b9d0..747e3532cde4 100644 --- a/sys/dev/usb/wlan/if_rsu.c +++ b/sys/dev/usb/wlan/if_rsu.c @@ -1,3766 +1,3768 @@ /* $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 tx a-mpdu * o hostap / ibss / mesh * 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 "usbdevs.h" #include /* XXX */ #include #define RSU_RATE_IS_CCK RTWN_RATE_IS_CCK #ifdef USB_DEBUG static int rsu_debug = 0; SYSCTL_NODE(_hw_usb, OID_AUTO, rsu, CTLFLAG_RW | CTLFLAG_MPSAFE, 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 #define RSU_DEBUG_KEY 0x00000800 #define RSU_DEBUG_USB 0x00001000 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(SITECOMEU, WLA1000), 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_scan_curchan(struct ieee80211_scan_state *, unsigned long); static void rsu_scan_mindwell(struct ieee80211_scan_state *); static void rsu_update_promisc(struct ieee80211com *); static uint8_t rsu_get_multi_pos(const uint8_t[]); static void rsu_set_multi(struct rsu_softc *); 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 void rsu_set_led(struct rsu_softc *, int); static int rsu_monitor_newstate(struct ieee80211vap *, enum ieee80211_state, int); static int rsu_newstate(struct ieee80211vap *, enum ieee80211_state, int); static int rsu_key_alloc(struct ieee80211vap *, struct ieee80211_key *, ieee80211_keyix *, ieee80211_keyix *); static int rsu_process_key(struct ieee80211vap *, const struct ieee80211_key *, int); static int rsu_key_set(struct ieee80211vap *, const struct ieee80211_key *); static int rsu_key_delete(struct ieee80211vap *, const struct ieee80211_key *); static int rsu_cam_read(struct rsu_softc *, uint8_t, uint32_t *); static void rsu_cam_write(struct rsu_softc *, uint8_t, uint32_t); static int rsu_key_check(struct rsu_softc *, ieee80211_keyix, int); static uint8_t rsu_crypto_mode(struct rsu_softc *, u_int, int); static int rsu_set_key_group(struct rsu_softc *, const struct ieee80211_key *); static int rsu_set_key_pair(struct rsu_softc *, const struct ieee80211_key *); static int rsu_reinit_static_keys(struct rsu_softc *); static int rsu_delete_key(struct rsu_softc *sc, ieee80211_keyix); static void rsu_delete_key_pair_cb(void *, int); static int rsu_site_survey(struct rsu_softc *, struct ieee80211_scan_ssid *); 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); static int8_t rsu_get_rssi(struct rsu_softc *, int, void *); static struct mbuf * rsu_rx_copy_to_mbuf(struct rsu_softc *, struct r92s_rx_stat *, int); static uint32_t rsu_get_tsf_low(struct rsu_softc *); static uint32_t rsu_get_tsf_high(struct rsu_softc *); static struct ieee80211_node * rsu_rx_frame(struct rsu_softc *, struct mbuf *); 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_rxfilter_init(struct rsu_softc *); static void rsu_rxfilter_set(struct rsu_softc *, uint32_t, uint32_t); static void rsu_rxfilter_refresh(struct rsu_softc *); static int 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 int rsu_ioctl_net(struct ieee80211com *, u_long, void *); 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 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; sc->sc_rx_checksum_enable = 1; 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); RSU_DELKEY_BMAP_LOCK_INIT(sc); TIMEOUT_TASK_INIT(taskqueue_thread, &sc->calib_task, 0, rsu_calib_task, sc); TASK_INIT(&sc->del_key_task, 0, rsu_delete_key_pair_cb, 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; case 0x3: /* "green" NIC */ sc->sc_rftype = RTL8712_RFCONFIG_1T2R; sc->sc_nrxstream = 2; sc->sc_ntxstream = 1; rft = "1T2R ('green')"; 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 */ IEEE80211_C_MONITOR | /* monitor mode supported */ #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. */ ic->ic_cryptocaps = IEEE80211_CRYPTO_WEP | IEEE80211_CRYPTO_TKIP | IEEE80211_CRYPTO_AES_CCM; /* 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; } ic->ic_flags_ext |= IEEE80211_FEXT_SCAN_OFFLOAD; 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_scan_curchan = rsu_scan_curchan; ic->ic_scan_mindwell = rsu_scan_mindwell; ic->ic_vap_create = rsu_vap_create; ic->ic_vap_delete = rsu_vap_delete; ic->ic_update_promisc = rsu_update_promisc; ic->ic_update_mcast = rsu_update_mcast; ic->ic_ioctl = rsu_ioctl_net; 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_stop(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->del_key_task); taskqueue_drain(taskqueue_thread, &sc->tx_task); RSU_DELKEY_BMAP_LOCK_DESTROY(sc); 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; RSU_DPRINTF(sc, RSU_DEBUG_USB, "Control request failed, %s (retries left: %d)\n", usbd_errstr(err), ntries); 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_softc *sc = ic->ic_softc; struct rsu_vap *uvp; struct ieee80211vap *vap; struct ifnet *ifp; 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); } ifp = vap->iv_ifp; ifp->if_capabilities = IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6; RSU_LOCK(sc); if (sc->sc_rx_checksum_enable) ifp->if_capenable |= IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6; RSU_UNLOCK(sc); /* override state transition machine */ uvp->newstate = vap->iv_newstate; if (opmode == IEEE80211_M_MONITOR) vap->iv_newstate = rsu_monitor_newstate; else vap->iv_newstate = rsu_newstate; vap->iv_key_alloc = rsu_key_alloc; vap->iv_key_set = rsu_key_set; vap->iv_key_delete = rsu_key_delete; /* 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; struct ieee80211_scan_state *ss = ic->ic_scan; struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); int error; /* Scanning is done by the firmware. */ RSU_LOCK(sc); sc->sc_active_scan = !!(ss->ss_flags & IEEE80211_SCAN_ACTIVE); /* XXX TODO: force awake if in network-sleep? */ error = rsu_site_survey(sc, ss->ss_nssid > 0 ? &ss->ss_ssid[0] : NULL); RSU_UNLOCK(sc); if (error != 0) { device_printf(sc->sc_dev, "could not send site survey command\n"); ieee80211_cancel_scan(vap); } } 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_channels_default_2ghz(chans, maxchans, nchans, bands, (ic->ic_htcaps & IEEE80211_HTCAP_CHWIDTH40) ? NET80211_CBW_FLAG_HT40 : 0); } static void rsu_set_channel(struct ieee80211com *ic) { struct rsu_softc *sc = ic->ic_softc; /* * Only need to set the channel in Monitor mode. AP scanning and auth * are already taken care of by their respective firmware commands. */ if (ic->ic_opmode == IEEE80211_M_MONITOR) { struct r92s_set_channel cmd; int error; cmd.channel = IEEE80211_CHAN2IEEE(ic->ic_curchan); RSU_LOCK(sc); error = rsu_fw_cmd(sc, R92S_CMD_SET_CHANNEL, &cmd, sizeof(cmd)); if (error != 0) { device_printf(sc->sc_dev, "%s: error %d setting channel\n", __func__, error); } RSU_UNLOCK(sc); } } static void rsu_scan_curchan(struct ieee80211_scan_state *ss, unsigned long maxdwell) { /* Scan is done in rsu_scan_start(). */ } /** * Called by the net80211 framework to indicate * the minimum dwell time has been met, terminate the scan. * We don't actually terminate the scan as the firmware will notify * us when it's finished and we have no way to interrupt it. */ static void rsu_scan_mindwell(struct ieee80211_scan_state *ss) { /* NB: don't try to abort scan; wait for firmware to finish */ } static void rsu_update_promisc(struct ieee80211com *ic) { struct rsu_softc *sc = ic->ic_softc; RSU_LOCK(sc); if (sc->sc_running) rsu_rxfilter_refresh(sc); RSU_UNLOCK(sc); } /* * The same as rtwn_get_multi_pos() / rtwn_set_multi(). */ static uint8_t rsu_get_multi_pos(const uint8_t maddr[]) { uint64_t mask = 0x00004d101df481b4; uint8_t pos = 0x27; /* initial value */ int i, j; for (i = 0; i < IEEE80211_ADDR_LEN; i++) for (j = (i == 0) ? 1 : 0; j < 8; j++) if ((maddr[i] >> j) & 1) pos ^= (mask >> (i * 8 + j - 1)); pos &= 0x3f; return (pos); } static u_int rsu_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt) { uint32_t *mfilt = arg; uint8_t pos; pos = rsu_get_multi_pos(LLADDR(sdl)); mfilt[pos / 32] |= (1 << (pos % 32)); return (1); } static void rsu_set_multi(struct rsu_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; uint32_t mfilt[2]; RSU_ASSERT_LOCKED(sc); /* general structure was copied from ath(4). */ if (ic->ic_allmulti == 0) { struct ieee80211vap *vap; /* * Merge multicast addresses to form the hardware filter. */ mfilt[0] = mfilt[1] = 0; TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) if_foreach_llmaddr(vap->iv_ifp, rsu_hash_maddr, &mfilt); } else mfilt[0] = mfilt[1] = ~0; rsu_write_4(sc, R92S_MAR + 0, mfilt[0]); rsu_write_4(sc, R92S_MAR + 4, mfilt[1]); RSU_DPRINTF(sc, RSU_DEBUG_STATE, "%s: MC filter %08x:%08x\n", __func__, mfilt[0], mfilt[1]); } static void rsu_update_mcast(struct ieee80211com *ic) { struct rsu_softc *sc = ic->ic_softc; RSU_LOCK(sc); if (sc->sc_running) rsu_set_multi(sc); RSU_UNLOCK(sc); } 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 & RSU_DEBUG_RESET) { /* 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)) RSU_DPRINTF(sc, RSU_DEBUG_CALIB, "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 void rsu_set_led(struct rsu_softc *sc, int on) { rsu_write_1(sc, R92S_LEDCFG, (rsu_read_1(sc, R92S_LEDCFG) & 0xf0) | (!on << 3)); } static int rsu_monitor_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct ieee80211com *ic = vap->iv_ic; struct rsu_softc *sc = ic->ic_softc; struct rsu_vap *uvp = RSU_VAP(vap); if (vap->iv_state != nstate) { IEEE80211_UNLOCK(ic); RSU_LOCK(sc); switch (nstate) { case IEEE80211_S_INIT: sc->sc_vap_is_running = 0; rsu_set_led(sc, 0); break; case IEEE80211_S_RUN: sc->sc_vap_is_running = 1; rsu_set_led(sc, 1); break; default: /* NOTREACHED */ break; } rsu_rxfilter_refresh(sc); RSU_UNLOCK(sc); IEEE80211_LOCK(ic); } return (uvp->newstate(vap, nstate, arg)); } 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; /* Pause Tx for AC queues. */ rsu_write_1(sc, R92S_TXPAUSE, R92S_TXPAUSE_AC); usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(10)); RSU_UNLOCK(sc); taskqueue_drain_timeout(taskqueue_thread, &sc->calib_task); taskqueue_drain(taskqueue_thread, &sc->tx_task); RSU_LOCK(sc); /* Disassociate from our current BSS. */ rsu_disconnect(sc); usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(10)); /* Refresh Rx filter (may be modified by firmware). */ sc->sc_vap_is_running = 0; rsu_rxfilter_refresh(sc); /* Reinstall static keys. */ if (sc->sc_running) rsu_reinit_static_keys(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: /* Flush all AC queues. */ rsu_write_1(sc, R92S_TXPAUSE, 0); 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)); } static int rsu_key_alloc(struct ieee80211vap *vap, struct ieee80211_key *k, ieee80211_keyix *keyix, ieee80211_keyix *rxkeyix) { struct rsu_softc *sc = vap->iv_ic->ic_softc; int is_checked = 0; if (&vap->iv_nw_keys[0] <= k && k < &vap->iv_nw_keys[IEEE80211_WEP_NKID]) { *keyix = ieee80211_crypto_get_key_wepidx(vap, k); } else { if (vap->iv_opmode != IEEE80211_M_STA) { *keyix = 0; /* TODO: obtain keyix from node id */ is_checked = 1; k->wk_flags |= IEEE80211_KEY_SWCRYPT; } else *keyix = R92S_MACID_BSS; } if (!is_checked) { RSU_LOCK(sc); if (isset(sc->keys_bmap, *keyix)) { device_printf(sc->sc_dev, "%s: key slot %d is already used!\n", __func__, *keyix); RSU_UNLOCK(sc); return (0); } setbit(sc->keys_bmap, *keyix); RSU_UNLOCK(sc); } *rxkeyix = *keyix; return (1); } static int rsu_process_key(struct ieee80211vap *vap, const struct ieee80211_key *k, int set) { struct rsu_softc *sc = vap->iv_ic->ic_softc; int ret; if (k->wk_flags & IEEE80211_KEY_SWCRYPT) { /* Not for us. */ return (1); } /* Handle group keys. */ if (&vap->iv_nw_keys[0] <= k && k < &vap->iv_nw_keys[IEEE80211_WEP_NKID]) { KASSERT(k->wk_keyix < nitems(sc->group_keys), ("keyix %u > %zu\n", k->wk_keyix, nitems(sc->group_keys))); RSU_LOCK(sc); sc->group_keys[k->wk_keyix] = (set ? k : NULL); if (!sc->sc_running) { /* Static keys will be set during device startup. */ RSU_UNLOCK(sc); return (1); } if (set) ret = rsu_set_key_group(sc, k); else ret = rsu_delete_key(sc, k->wk_keyix); RSU_UNLOCK(sc); return (!ret); } if (set) { /* wait for pending key removal */ taskqueue_drain(taskqueue_thread, &sc->del_key_task); RSU_LOCK(sc); ret = rsu_set_key_pair(sc, k); RSU_UNLOCK(sc); } else { RSU_DELKEY_BMAP_LOCK(sc); setbit(sc->free_keys_bmap, k->wk_keyix); RSU_DELKEY_BMAP_UNLOCK(sc); /* workaround ieee80211_node_delucastkey() locking */ taskqueue_enqueue(taskqueue_thread, &sc->del_key_task); ret = 0; /* fake success */ } return (!ret); } static int rsu_key_set(struct ieee80211vap *vap, const struct ieee80211_key *k) { return (rsu_process_key(vap, k, 1)); } static int rsu_key_delete(struct ieee80211vap *vap, const struct ieee80211_key *k) { return (rsu_process_key(vap, k, 0)); } static int rsu_cam_read(struct rsu_softc *sc, uint8_t addr, uint32_t *val) { int ntries; rsu_write_4(sc, R92S_CAMCMD, R92S_CAMCMD_POLLING | SM(R92S_CAMCMD_ADDR, addr)); for (ntries = 0; ntries < 10; ntries++) { if (!(rsu_read_4(sc, R92S_CAMCMD) & R92S_CAMCMD_POLLING)) break; usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(1)); } if (ntries == 10) { device_printf(sc->sc_dev, "%s: cannot read CAM entry at address %02X\n", __func__, addr); return (ETIMEDOUT); } *val = rsu_read_4(sc, R92S_CAMREAD); return (0); } static void rsu_cam_write(struct rsu_softc *sc, uint8_t addr, uint32_t data) { rsu_write_4(sc, R92S_CAMWRITE, data); rsu_write_4(sc, R92S_CAMCMD, R92S_CAMCMD_POLLING | R92S_CAMCMD_WRITE | SM(R92S_CAMCMD_ADDR, addr)); } static int rsu_key_check(struct rsu_softc *sc, ieee80211_keyix keyix, int is_valid) { uint32_t val; int error, ntries; for (ntries = 0; ntries < 20; ntries++) { usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(1)); error = rsu_cam_read(sc, R92S_CAM_CTL0(keyix), &val); if (error != 0) { device_printf(sc->sc_dev, "%s: cannot check key status!\n", __func__); return (error); } if (((val & R92S_CAM_VALID) == 0) ^ is_valid) break; } if (ntries == 20) { device_printf(sc->sc_dev, "%s: key %d is %s marked as valid, rejecting request\n", __func__, keyix, is_valid ? "not" : "still"); return (EIO); } return (0); } /* * Map net80211 cipher to RTL8712 security mode. */ static uint8_t rsu_crypto_mode(struct rsu_softc *sc, u_int cipher, int keylen) { switch (cipher) { case IEEE80211_CIPHER_WEP: return keylen < 8 ? R92S_KEY_ALGO_WEP40 : R92S_KEY_ALGO_WEP104; case IEEE80211_CIPHER_TKIP: return R92S_KEY_ALGO_TKIP; case IEEE80211_CIPHER_AES_CCM: return R92S_KEY_ALGO_AES; default: device_printf(sc->sc_dev, "unknown cipher %d\n", cipher); return R92S_KEY_ALGO_INVALID; } } static int rsu_set_key_group(struct rsu_softc *sc, const struct ieee80211_key *k) { struct r92s_fw_cmd_set_key key; uint8_t algo; int error; RSU_ASSERT_LOCKED(sc); /* Map net80211 cipher to HW crypto algorithm. */ algo = rsu_crypto_mode(sc, k->wk_cipher->ic_cipher, k->wk_keylen); if (algo == R92S_KEY_ALGO_INVALID) return (EINVAL); memset(&key, 0, sizeof(key)); key.algo = algo; key.cam_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))); RSU_DPRINTF(sc, RSU_DEBUG_KEY | RSU_DEBUG_FWCMD, "%s: keyix %u, group %u, algo %u/%u, flags %04X, len %u, " "macaddr %s\n", __func__, key.cam_id, key.grpkey, k->wk_cipher->ic_cipher, key.algo, k->wk_flags, k->wk_keylen, ether_sprintf(k->wk_macaddr)); error = rsu_fw_cmd(sc, R92S_CMD_SET_KEY, &key, sizeof(key)); if (error != 0) { device_printf(sc->sc_dev, "%s: cannot send firmware command, error %d\n", __func__, error); return (error); } return (rsu_key_check(sc, k->wk_keyix, 1)); } static int rsu_set_key_pair(struct rsu_softc *sc, const struct ieee80211_key *k) { struct r92s_fw_cmd_set_key_mac key; uint8_t algo; int error; RSU_ASSERT_LOCKED(sc); if (!sc->sc_running) return (ESHUTDOWN); /* Map net80211 cipher to HW crypto algorithm. */ algo = rsu_crypto_mode(sc, k->wk_cipher->ic_cipher, k->wk_keylen); if (algo == R92S_KEY_ALGO_INVALID) return (EINVAL); memset(&key, 0, sizeof(key)); key.algo = algo; memcpy(key.macaddr, k->wk_macaddr, sizeof(key.macaddr)); memcpy(key.key, k->wk_key, MIN(k->wk_keylen, sizeof(key.key))); RSU_DPRINTF(sc, RSU_DEBUG_KEY | RSU_DEBUG_FWCMD, "%s: keyix %u, algo %u/%u, flags %04X, len %u, macaddr %s\n", __func__, k->wk_keyix, k->wk_cipher->ic_cipher, key.algo, k->wk_flags, k->wk_keylen, ether_sprintf(key.macaddr)); error = rsu_fw_cmd(sc, R92S_CMD_SET_STA_KEY, &key, sizeof(key)); if (error != 0) { device_printf(sc->sc_dev, "%s: cannot send firmware command, error %d\n", __func__, error); return (error); } return (rsu_key_check(sc, k->wk_keyix, 1)); } static int rsu_reinit_static_keys(struct rsu_softc *sc) { int i, error; for (i = 0; i < nitems(sc->group_keys); i++) { if (sc->group_keys[i] != NULL) { error = rsu_set_key_group(sc, sc->group_keys[i]); if (error != 0) { device_printf(sc->sc_dev, "%s: failed to set static key %d, " "error %d\n", __func__, i, error); return (error); } } } return (0); } static int rsu_delete_key(struct rsu_softc *sc, ieee80211_keyix keyix) { struct r92s_fw_cmd_set_key key; uint32_t val; int error; RSU_ASSERT_LOCKED(sc); if (!sc->sc_running) return (0); /* check if it was automatically removed by firmware */ error = rsu_cam_read(sc, R92S_CAM_CTL0(keyix), &val); if (error == 0 && (val & R92S_CAM_VALID) == 0) { RSU_DPRINTF(sc, RSU_DEBUG_KEY, "%s: key %u does not exist\n", __func__, keyix); clrbit(sc->keys_bmap, keyix); return (0); } memset(&key, 0, sizeof(key)); key.cam_id = keyix; RSU_DPRINTF(sc, RSU_DEBUG_KEY | RSU_DEBUG_FWCMD, "%s: removing key %u\n", __func__, key.cam_id); error = rsu_fw_cmd(sc, R92S_CMD_SET_KEY, &key, sizeof(key)); if (error != 0) { device_printf(sc->sc_dev, "%s: cannot send firmware command, error %d\n", __func__, error); goto finish; } usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(5)); /* * Clear 'valid' bit manually (cannot be done via firmware command). * Used for key check + when firmware command cannot be sent. */ finish: rsu_cam_write(sc, R92S_CAM_CTL0(keyix), 0); clrbit(sc->keys_bmap, keyix); return (rsu_key_check(sc, keyix, 0)); } static void rsu_delete_key_pair_cb(void *arg, int pending __unused) { struct rsu_softc *sc = arg; int i; RSU_DELKEY_BMAP_LOCK(sc); for (i = IEEE80211_WEP_NKID; i < R92S_CAM_ENTRY_LIMIT; i++) { if (isset(sc->free_keys_bmap, i)) { RSU_DELKEY_BMAP_UNLOCK(sc); RSU_LOCK(sc); RSU_DPRINTF(sc, RSU_DEBUG_KEY, "%s: calling rsu_delete_key() with keyix = %d\n", __func__, i); (void) rsu_delete_key(sc, i); RSU_UNLOCK(sc); RSU_DELKEY_BMAP_LOCK(sc); clrbit(sc->free_keys_bmap, i); /* bmap can be changed */ i = IEEE80211_WEP_NKID - 1; continue; } } RSU_DELKEY_BMAP_UNLOCK(sc); } static int rsu_site_survey(struct rsu_softc *sc, struct ieee80211_scan_ssid *ssid) { struct r92s_fw_cmd_sitesurvey cmd; RSU_ASSERT_LOCKED(sc); memset(&cmd, 0, sizeof(cmd)); /* TODO: passive channels? */ if (sc->sc_active_scan) cmd.active = htole32(1); cmd.limit = htole32(48); if (ssid != NULL) { sc->sc_extra_scan = 1; cmd.ssidlen = htole32(ssid->len); memcpy(cmd.ssid, ssid->ssid, ssid->len); } #ifdef USB_DEBUG if (rsu_debug & (RSU_DEBUG_SCAN | RSU_DEBUG_FWCMD)) { device_printf(sc->sc_dev, "sending site survey command, active %d", le32toh(cmd.active)); if (ssid != NULL) { printf(", ssid: "); ieee80211_print_essid(cmd.ssid, le32toh(cmd.ssidlen)); } printf("\n"); } #endif return (rsu_fw_cmd(sc, R92S_CMD_SITE_SURVEY, &cmd, sizeof(cmd))); } 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; RSU_ASSERT_LOCKED(sc); /* 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, ni); 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); } CTASSERT(MCLBYTES > sizeof(struct ieee80211_frame)); 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; uint32_t ieslen; uint32_t pktlen; if (__predict_false(len < sizeof(*bss))) return; bss = (struct ndis_wlan_bssid_ex *)buf; ieslen = le32toh(bss->ieslen); /* range check length of information element */ if (__predict_false(ieslen > (uint32_t)(len - sizeof(*bss)))) 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), ieslen, 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! */ if (__predict_false(ieslen > (size_t)(MCLBYTES - sizeof(*wh)))) return; pktlen = sizeof(*wh) + ieslen; 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], 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_BAND; 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); + rxs.c_band = 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); 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) { RSU_DPRINTF(sc, RSU_DEBUG_ANY, "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; /* Refresh Rx filter (was changed by firmware). */ sc->sc_vap_is_running = 1; rsu_rxfilter_refresh(sc); 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: %s scan done, found %d BSS\n", __func__, sc->sc_extra_scan ? "direct" : "broadcast", le32toh(*(uint32_t *)buf)); if (sc->sc_extra_scan == 1) { /* Send broadcast probe request. */ sc->sc_extra_scan = 0; if (vap != NULL && rsu_site_survey(sc, NULL) != 0) { RSU_UNLOCK(sc); ieee80211_cancel_scan(vap); RSU_LOCK(sc); } break; } if (vap != NULL) { RSU_UNLOCK(sc); ieee80211_scan_done(vap); RSU_LOCK(sc); } 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; } } 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); } static struct mbuf * rsu_rx_copy_to_mbuf(struct rsu_softc *sc, struct r92s_rx_stat *stat, int totlen) { struct ieee80211com *ic = &sc->sc_ic; struct mbuf *m; uint32_t rxdw0; int pktlen; rxdw0 = le32toh(stat->rxdw0); if (__predict_false(rxdw0 & (R92S_RXDW0_CRCERR | R92S_RXDW0_ICVERR))) { RSU_DPRINTF(sc, RSU_DEBUG_RX, "%s: RX flags error (%s)\n", __func__, rxdw0 & R92S_RXDW0_CRCERR ? "CRC" : "ICV"); goto fail; } pktlen = MS(rxdw0, R92S_RXDW0_PKTLEN); if (__predict_false(pktlen < sizeof (struct ieee80211_frame_ack))) { RSU_DPRINTF(sc, RSU_DEBUG_RX, "%s: frame is too short: %d\n", __func__, pktlen); goto fail; } m = m_get2(totlen, M_NOWAIT, MT_DATA, M_PKTHDR); if (__predict_false(m == NULL)) { device_printf(sc->sc_dev, "%s: could not allocate RX mbuf, totlen %d\n", __func__, totlen); goto fail; } /* Finalize mbuf. */ memcpy(mtod(m, uint8_t *), (uint8_t *)stat, totlen); m->m_pkthdr.len = m->m_len = totlen; return (m); fail: counter_u64_add(ic->ic_ierrors, 1); return (NULL); } static uint32_t rsu_get_tsf_low(struct rsu_softc *sc) { return (rsu_read_4(sc, R92S_TSFTR)); } static uint32_t rsu_get_tsf_high(struct rsu_softc *sc) { return (rsu_read_4(sc, R92S_TSFTR + 4)); } static struct ieee80211_node * rsu_rx_frame(struct rsu_softc *sc, struct mbuf *m) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_frame_min *wh; struct ieee80211_rx_stats rxs; struct r92s_rx_stat *stat; uint32_t rxdw0, rxdw3; uint8_t cipher, rate; int infosz; int rssi; stat = mtod(m, struct r92s_rx_stat *); rxdw0 = le32toh(stat->rxdw0); rxdw3 = le32toh(stat->rxdw3); rate = MS(rxdw3, R92S_RXDW3_RATE); cipher = MS(rxdw0, R92S_RXDW0_CIPHER); infosz = MS(rxdw0, R92S_RXDW0_INFOSZ) * 8; /* Get RSSI from PHY status descriptor if present. */ if (infosz != 0 && (rxdw0 & R92S_RXDW0_PHYST)) rssi = rsu_get_rssi(sc, rate, &stat[1]); else { /* Cheat and get the last calibrated RSSI */ rssi = rsu_hwrssi_to_rssi(sc, sc->sc_currssi); } /* Hardware does Rx TCP checksum offload. */ /* * This flag can be set for some other * (e.g., EAPOL) frame types, so don't rely on it. */ if (rxdw3 & R92S_RXDW3_TCPCHKVALID) { RSU_DPRINTF(sc, RSU_DEBUG_RX, "%s: TCP/IP checksums: %schecked / %schecked\n", __func__, (rxdw3 & R92S_RXDW3_TCPCHKRPT) ? "" : "not ", (rxdw3 & R92S_RXDW3_IPCHKRPT) ? "" : "not "); /* * 'IP header checksum valid' bit will not be set if * the frame was not checked / has incorrect checksum / * does not have checksum (IPv6). * * NB: if DF bit is not set then frame will not be checked. */ if (rxdw3 & R92S_RXDW3_IPCHKRPT) { m->m_pkthdr.csum_flags = CSUM_IP_CHECKED; m->m_pkthdr.csum_flags |= CSUM_IP_VALID; } /* * This is independent of the above check. */ if (rxdw3 & R92S_RXDW3_TCPCHKRPT) { m->m_pkthdr.csum_flags |= CSUM_DATA_VALID; m->m_pkthdr.csum_flags |= CSUM_PSEUDO_HDR; m->m_pkthdr.csum_data = 0xffff; } } /* RX flags */ /* Set channel flags for input path */ bzero(&rxs, sizeof(rxs)); /* normal RSSI */ rxs.r_flags |= IEEE80211_R_NF | IEEE80211_R_RSSI; rxs.c_rssi = rssi; rxs.c_nf = -96; /* Rate */ if (rate < 12) { rxs.c_rate = ridx2rate[rate]; if (RSU_RATE_IS_CCK(rate)) rxs.c_pktflags |= IEEE80211_RX_F_CCK; else rxs.c_pktflags |= IEEE80211_RX_F_OFDM; } else { rxs.c_rate = IEEE80211_RATE_MCS | (rate - 12); rxs.c_pktflags |= IEEE80211_RX_F_HT; } 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 = 0; /* TODO */ tap->wr_tsft = rsu_get_tsf_high(sc); if (le32toh(stat->tsf_low) > rsu_get_tsf_low(sc)) tap->wr_tsft--; tap->wr_tsft = (uint64_t)htole32(tap->wr_tsft) << 32; tap->wr_tsft += stat->tsf_low; tap->wr_rate = rxs.c_rate; tap->wr_dbm_antsignal = rssi; }; (void) ieee80211_add_rx_params(m, &rxs); /* Drop descriptor. */ m_adj(m, sizeof(*stat) + infosz); wh = mtod(m, struct ieee80211_frame_min *); if ((wh->i_fc[1] & IEEE80211_FC1_PROTECTED) && cipher != R92S_KEY_ALGO_NONE) { m->m_flags |= M_WEP; } RSU_DPRINTF(sc, RSU_DEBUG_RX, "%s: Rx frame len %d, rate %d, infosz %d\n", __func__, m->m_len, rate, infosz); if (m->m_len >= sizeof(*wh)) return (ieee80211_find_rxnode(ic, wh)); return (NULL); } 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; /* * don't pass packets to the ieee80211 framework if the driver isn't * RUNNING. */ if (!sc->sc_running) return (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_copy_to_mbuf(sc, stat, totlen); 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))) { RSU_DPRINTF(sc, RSU_DEBUG_RX, "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 epoch_tracker et; struct rsu_softc *sc = usbd_xfer_softc(xfer); struct ieee80211com *ic = &sc->sc_ic; 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: 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. */ NET_EPOCH_ENTER(et); while (m != NULL) { next = m->m_next; m->m_next = NULL; ni = rsu_rx_frame(sc, m); RSU_UNLOCK(sc); if (ni != NULL) { if (ni->ni_flags & IEEE80211_NODE_HT) m->m_flags |= M_AMPDU; (void)ieee80211_input_mimo(ni, m); ieee80211_free_node(ni); } else (void)ieee80211_input_mimo_all(ic, m); RSU_LOCK(sc); m = next; } NET_EPOCH_EXIT(et); 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) { const struct ieee80211_txparam *tp = ni->ni_txparms; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_frame *wh; struct ieee80211_key *k = NULL; struct r92s_tx_desc *txd; uint8_t rate, ridx, type, cipher, qos; int prio = 0; uint8_t which; int hasqos; int ismcast; int xferlen; int qid; RSU_ASSERT_LOCKED(sc); wh = mtod(m0, struct ieee80211_frame *); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; ismcast = IEEE80211_IS_MULTICAST(wh->i_addr1); RSU_DPRINTF(sc, RSU_DEBUG_TX, "%s: data=%p, m=%p\n", __func__, data, m0); /* Choose a TX rate index. */ if (type == IEEE80211_FC0_TYPE_MGT || type == IEEE80211_FC0_TYPE_CTL || (m0->m_flags & M_EAPOL) != 0) rate = tp->mgmtrate; else if (ismcast) rate = tp->mcastrate; else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) rate = tp->ucastrate; else rate = 0; if (rate != 0) ridx = rate2ridx(rate); 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; qos = ((const struct ieee80211_qosframe *)wh)->i_qos[0]; } 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; qos = 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); if (k != NULL && !(k->wk_flags & IEEE80211_KEY_SWENCRYPT)) { 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->wk_keyix)); } /* XXX todo: set AGGEN bit if appropriate? */ txd->txdw2 |= htole32(R92S_TXDW2_BK); if (ismcast) txd->txdw2 |= htole32(R92S_TXDW2_BMCAST); if (!ismcast && (!qos || (qos & IEEE80211_QOS_ACKPOLICY) != IEEE80211_QOS_ACKPOLICY_NOACK)) { txd->txdw2 |= htole32(R92S_TXDW2_RTY_LMT_ENA); txd->txdw2 |= htole32(SM(R92S_TXDW2_RTY_LMT, tp->maxretry)); } /* Force mgmt / mcast / ucast rate if needed. */ if (rate != 0) { /* Data rate fallback limit (max). */ txd->txdw5 |= htole32(SM(R92S_TXDW5_DATARATE_FB_LMT, 0x1f)); txd->txdw5 |= htole32(SM(R92S_TXDW5_DATARATE, ridx)); txd->txdw4 |= htole32(R92S_TXDW4_DRVRATE); } /* * 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; ieee80211_radiotap_tx(vap, m0); } xferlen = sizeof(*txd) + m0->m_pkthdr.len; KASSERT(xferlen <= RSU_TXBUFSZ, ("%s: invalid length", __func__)); 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 int rsu_ioctl_net(struct ieee80211com *ic, u_long cmd, void *data) { struct rsu_softc *sc = ic->ic_softc; struct ifreq *ifr = (struct ifreq *)data; int error; error = 0; switch (cmd) { case SIOCSIFCAP: { struct ieee80211vap *vap; int rxmask; rxmask = ifr->ifr_reqcap & (IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6); RSU_LOCK(sc); /* Both RXCSUM bits must be set (or unset). */ if (sc->sc_rx_checksum_enable && rxmask != (IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6)) { rxmask = 0; sc->sc_rx_checksum_enable = 0; rsu_rxfilter_set(sc, R92S_RCR_TCP_OFFLD_EN, 0); } else if (!sc->sc_rx_checksum_enable && rxmask != 0) { rxmask = IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6; sc->sc_rx_checksum_enable = 1; rsu_rxfilter_set(sc, 0, R92S_RCR_TCP_OFFLD_EN); } else { /* Nothing to do. */ RSU_UNLOCK(sc); break; } RSU_UNLOCK(sc); IEEE80211_LOCK(ic); /* XXX */ TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { struct ifnet *ifp = vap->iv_ifp; ifp->if_capenable &= ~(IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6); ifp->if_capenable |= rxmask; } IEEE80211_UNLOCK(ic); break; } default: error = ENOTTY; /* for net80211 */ break; } return (error); } static void rsu_parent(struct ieee80211com *ic) { struct rsu_softc *sc = ic->ic_softc; if (ic->ic_nrunning > 0) { if (rsu_init(sc) == 0) ieee80211_start_all(ic); else { struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); if (vap != NULL) ieee80211_stop(vap); } } else rsu_stop(sc); } /* * 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); } CTASSERT(sizeof(size_t) >= sizeof(uint32_t)); 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; uint32_t 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; } RSU_DPRINTF(sc, RSU_DEBUG_FW, "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 (imemsz > (size_t)(size - sizeof(*hdr)) || ememsz > (size_t)(size - sizeof(*hdr) - imemsz)) { 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. */ 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_rxfilter_init(struct rsu_softc *sc) { uint32_t reg; RSU_ASSERT_LOCKED(sc); /* Setup multicast filter. */ rsu_set_multi(sc); /* Adjust Rx filter. */ reg = rsu_read_4(sc, R92S_RCR); reg &= ~R92S_RCR_AICV; reg |= R92S_RCR_APP_PHYSTS; if (sc->sc_rx_checksum_enable) reg |= R92S_RCR_TCP_OFFLD_EN; rsu_write_4(sc, R92S_RCR, reg); /* Update dynamic Rx filter parts. */ rsu_rxfilter_refresh(sc); } static void rsu_rxfilter_set(struct rsu_softc *sc, uint32_t clear, uint32_t set) { /* NB: firmware can touch this register too. */ rsu_write_4(sc, R92S_RCR, (rsu_read_4(sc, R92S_RCR) & ~clear) | set); } static void rsu_rxfilter_refresh(struct rsu_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; uint32_t mask_all, mask_min; RSU_ASSERT_LOCKED(sc); /* NB: RCR_AMF / RXFLTMAP_MGT are used by firmware. */ mask_all = R92S_RCR_ACF | R92S_RCR_AAP; mask_min = R92S_RCR_APM; if (sc->sc_vap_is_running) mask_min |= R92S_RCR_CBSSID; else mask_all |= R92S_RCR_ADF; if (ic->ic_opmode == IEEE80211_M_MONITOR) { uint16_t rxfltmap; if (sc->sc_vap_is_running) rxfltmap = 0; else rxfltmap = R92S_RXFLTMAP_MGT_DEF; rsu_write_2(sc, R92S_RXFLTMAP_MGT, rxfltmap); } if (ic->ic_promisc == 0 && ic->ic_opmode != IEEE80211_M_MONITOR) rsu_rxfilter_set(sc, mask_all, mask_min); else rsu_rxfilter_set(sc, mask_min, mask_all); } static int 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_LOCK(sc); if (sc->sc_running) { RSU_UNLOCK(sc); return (0); } /* Ensure the mbuf queue is drained */ rsu_drain_mbufq(sc); /* 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; 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: */ usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(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; } /* Initialize Rx filter. */ rsu_rxfilter_init(sc); /* 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; } /* Install static keys (if any). */ error = rsu_reinit_static_keys(sc); if (error != 0) goto fail; sc->sc_extra_scan = 0; usbd_transfer_start(sc->sc_xfer[RSU_BULK_RX]); /* We're ready to go. */ sc->sc_running = 1; RSU_UNLOCK(sc); return (0); fail: /* Need to stop all failed transfers, if any */ for (i = 0; i != RSU_N_TRANSFER; i++) usbd_transfer_stop(sc->sc_xfer[i]); RSU_UNLOCK(sc); return (error); } static void rsu_stop(struct rsu_softc *sc) { int i; RSU_LOCK(sc); if (!sc->sc_running) { RSU_UNLOCK(sc); return; } sc->sc_running = 0; sc->sc_vap_is_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); /* * CAM is not accessible after shutdown; * all entries are marked (by firmware?) as invalid. */ memset(sc->free_keys_bmap, 0, sizeof(sc->free_keys_bmap)); memset(sc->keys_bmap, 0, sizeof(sc->keys_bmap)); 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); RSU_UNLOCK(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); } diff --git a/sys/net80211/_ieee80211.h b/sys/net80211/_ieee80211.h index dc6773afac09..754e4647e82f 100644 --- a/sys/net80211/_ieee80211.h +++ b/sys/net80211/_ieee80211.h @@ -1,632 +1,635 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2001 Atsushi Onoe * Copyright (c) 2002-2008 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_H_ #define _NET80211__IEEE80211_H_ /* * 802.11 implementation definitions. * * NB: this file is used by applications. */ /* * PHY type; mostly used to identify FH phys. */ enum ieee80211_phytype { IEEE80211_T_DS, /* direct sequence spread spectrum */ IEEE80211_T_FH, /* frequency hopping */ IEEE80211_T_OFDM, /* frequency division multiplexing */ IEEE80211_T_TURBO, /* high rate OFDM, aka turbo mode */ IEEE80211_T_HT, /* high throughput */ IEEE80211_T_OFDM_HALF, /* 1/2 rate OFDM */ IEEE80211_T_OFDM_QUARTER, /* 1/4 rate OFDM */ IEEE80211_T_VHT, /* VHT PHY */ }; #define IEEE80211_T_CCK IEEE80211_T_DS /* more common nomenclature */ /* * PHY mode; this is not really a mode as multi-mode devices * have multiple PHY's. Mode is mostly used as a shorthand * for constraining which channels to consider in setting up * operation. Modes used to be used more extensively when * channels were identified as IEEE channel numbers. */ enum ieee80211_phymode { IEEE80211_MODE_AUTO = 0, /* autoselect */ IEEE80211_MODE_11A = 1, /* 5GHz, OFDM */ IEEE80211_MODE_11B = 2, /* 2GHz, CCK */ IEEE80211_MODE_11G = 3, /* 2GHz, OFDM */ IEEE80211_MODE_FH = 4, /* 2GHz, GFSK */ IEEE80211_MODE_TURBO_A = 5, /* 5GHz, OFDM, 2x clock */ IEEE80211_MODE_TURBO_G = 6, /* 2GHz, OFDM, 2x clock */ IEEE80211_MODE_STURBO_A = 7, /* 5GHz, OFDM, 2x clock, static */ IEEE80211_MODE_11NA = 8, /* 5GHz, w/ HT */ IEEE80211_MODE_11NG = 9, /* 2GHz, w/ HT */ IEEE80211_MODE_HALF = 10, /* OFDM, 1/2x clock */ IEEE80211_MODE_QUARTER = 11, /* OFDM, 1/4x clock */ IEEE80211_MODE_VHT_2GHZ = 12, /* 2GHz, VHT */ IEEE80211_MODE_VHT_5GHZ = 13, /* 5GHz, VHT */ }; #define IEEE80211_MODE_MAX (IEEE80211_MODE_VHT_5GHZ+1) #define IEEE80211_MODE_BYTES howmany(IEEE80211_MODE_MAX, NBBY) /* * Operating mode. Devices do not necessarily support * all modes; they indicate which are supported in their * capabilities. */ enum ieee80211_opmode { IEEE80211_M_IBSS = 0, /* IBSS (adhoc) station */ IEEE80211_M_STA = 1, /* infrastructure station */ IEEE80211_M_WDS = 2, /* WDS link */ IEEE80211_M_AHDEMO = 3, /* Old lucent compatible adhoc demo */ IEEE80211_M_HOSTAP = 4, /* Software Access Point */ IEEE80211_M_MONITOR = 5, /* Monitor mode */ IEEE80211_M_MBSS = 6, /* MBSS (Mesh Point) link */ }; #define IEEE80211_OPMODE_MAX (IEEE80211_M_MBSS+1) /* * 802.11g/802.11n protection mode. */ enum ieee80211_protmode { IEEE80211_PROT_NONE = 0, /* no protection */ IEEE80211_PROT_CTSONLY = 1, /* CTS to self */ IEEE80211_PROT_RTSCTS = 2, /* RTS-CTS */ }; /* * Authentication mode. The open and shared key authentication * modes are implemented within the 802.11 layer. 802.1x and * WPA/802.11i are implemented in user mode by setting the * 802.11 layer into IEEE80211_AUTH_8021X and deferring * authentication to user space programs. */ enum ieee80211_authmode { IEEE80211_AUTH_NONE = 0, IEEE80211_AUTH_OPEN = 1, /* open */ IEEE80211_AUTH_SHARED = 2, /* shared-key */ IEEE80211_AUTH_8021X = 3, /* 802.1x */ IEEE80211_AUTH_AUTO = 4, /* auto-select/accept */ /* NB: these are used only for ioctls */ IEEE80211_AUTH_WPA = 5, /* WPA/RSN w/ 802.1x/PSK */ }; /* * Roaming mode is effectively who controls the operation * of the 802.11 state machine when operating as a station. * State transitions are controlled either by the driver * (typically when management frames are processed by the * hardware/firmware), the host (auto/normal operation of * the 802.11 layer), or explicitly through ioctl requests * when applications like wpa_supplicant want control. */ enum ieee80211_roamingmode { IEEE80211_ROAMING_DEVICE= 0, /* driver/hardware control */ IEEE80211_ROAMING_AUTO = 1, /* 802.11 layer control */ IEEE80211_ROAMING_MANUAL= 2, /* application control */ }; /* * Channels are specified by frequency and attributes. */ struct ieee80211_channel { uint32_t ic_flags; /* see below */ uint16_t ic_freq; /* primary centre frequency in MHz */ uint8_t ic_ieee; /* IEEE channel number */ int8_t ic_maxregpower; /* maximum regulatory tx power in dBm */ int8_t ic_maxpower; /* maximum tx power in .5 dBm */ int8_t ic_minpower; /* minimum tx power in .5 dBm */ uint8_t ic_state; /* dynamic state */ uint8_t ic_extieee; /* HT40 extension channel number */ int8_t ic_maxantgain; /* maximum antenna gain in .5 dBm */ uint8_t ic_pad; uint16_t ic_devdata; /* opaque device/driver data */ uint8_t ic_vht_ch_freq1; /* VHT primary freq1 IEEE value */ uint8_t ic_vht_ch_freq2; /* VHT secondary 80MHz freq2 IEEE value */ uint16_t ic_freq2; /* VHT secondary 80MHz freq2 MHz */ }; /* * Note: for VHT operation we will need significantly more than * IEEE80211_CHAN_MAX channels because of the combinations of * VHT20, VHT40, VHT80, VHT160, and VHT80+80. */ #define IEEE80211_CHAN_MAX 1024 #define IEEE80211_CHAN_BYTES howmany(IEEE80211_CHAN_MAX, NBBY) #define IEEE80211_CHAN_ANY 0xffff /* token for ``any channel'' */ #define IEEE80211_CHAN_ANYC \ ((struct ieee80211_channel *) IEEE80211_CHAN_ANY) /* channel attributes */ #define IEEE80211_CHAN_PRIV0 0x00000001 /* driver private bit 0 */ #define IEEE80211_CHAN_PRIV1 0x00000002 /* driver private bit 1 */ #define IEEE80211_CHAN_PRIV2 0x00000004 /* driver private bit 2 */ #define IEEE80211_CHAN_PRIV3 0x00000008 /* driver private bit 3 */ #define IEEE80211_CHAN_TURBO 0x00000010 /* Turbo channel */ #define IEEE80211_CHAN_CCK 0x00000020 /* CCK channel */ #define IEEE80211_CHAN_OFDM 0x00000040 /* OFDM channel */ #define IEEE80211_CHAN_2GHZ 0x00000080 /* 2 GHz spectrum channel. */ #define IEEE80211_CHAN_5GHZ 0x00000100 /* 5 GHz spectrum channel */ #define IEEE80211_CHAN_PASSIVE 0x00000200 /* Only passive scan allowed */ #define IEEE80211_CHAN_DYN 0x00000400 /* Dynamic CCK-OFDM channel */ #define IEEE80211_CHAN_GFSK 0x00000800 /* GFSK channel (FHSS PHY) */ #define IEEE80211_CHAN_GSM 0x00001000 /* 900 MHz spectrum channel */ #define IEEE80211_CHAN_STURBO 0x00002000 /* 11a static turbo channel only */ #define IEEE80211_CHAN_HALF 0x00004000 /* Half rate channel */ #define IEEE80211_CHAN_QUARTER 0x00008000 /* Quarter rate channel */ #define IEEE80211_CHAN_HT20 0x00010000 /* HT 20 channel */ #define IEEE80211_CHAN_HT40U 0x00020000 /* HT 40 channel w/ ext above */ #define IEEE80211_CHAN_HT40D 0x00040000 /* HT 40 channel w/ ext below */ #define IEEE80211_CHAN_DFS 0x00080000 /* DFS required */ #define IEEE80211_CHAN_4MSXMIT 0x00100000 /* 4ms limit on frame length */ #define IEEE80211_CHAN_NOADHOC 0x00200000 /* adhoc mode not allowed */ #define IEEE80211_CHAN_NOHOSTAP 0x00400000 /* hostap mode not allowed */ #define IEEE80211_CHAN_11D 0x00800000 /* 802.11d required */ #define IEEE80211_CHAN_VHT20 0x01000000 /* VHT20 channel */ #define IEEE80211_CHAN_VHT40U 0x02000000 /* VHT40 channel, ext above */ #define IEEE80211_CHAN_VHT40D 0x04000000 /* VHT40 channel, ext below */ #define IEEE80211_CHAN_VHT80 0x08000000 /* VHT80 channel */ #define IEEE80211_CHAN_VHT160 0x10000000 /* VHT160 channel */ #define IEEE80211_CHAN_VHT80P80 0x20000000 /* VHT80+80 channel */ /* XXX note: 0x80000000 is used in src/sbin/ifconfig/ifieee80211.c :( */ #define IEEE80211_CHAN_HT40 (IEEE80211_CHAN_HT40U | IEEE80211_CHAN_HT40D) #define IEEE80211_CHAN_HT (IEEE80211_CHAN_HT20 | IEEE80211_CHAN_HT40) #define IEEE80211_CHAN_VHT40 (IEEE80211_CHAN_VHT40U | IEEE80211_CHAN_VHT40D) #define IEEE80211_CHAN_VHT (IEEE80211_CHAN_VHT20 | IEEE80211_CHAN_VHT40 \ | IEEE80211_CHAN_VHT80 | IEEE80211_CHAN_VHT160 \ | IEEE80211_CHAN_VHT80P80) #define IEEE80211_CHAN_BITS \ "\20\1PRIV0\2PRIV2\3PRIV3\4PRIV4\5TURBO\6CCK\7OFDM\0102GHZ\0115GHZ" \ "\12PASSIVE\13DYN\14GFSK\15GSM\16STURBO\17HALF\20QUARTER\21HT20" \ "\22HT40U\23HT40D\24DFS\0254MSXMIT\26NOADHOC\27NOHOSTAP\03011D" \ "\031VHT20\032VHT40U\033VHT40D\034VHT80\035VHT160\036VHT80P80" /* * Useful combinations of channel characteristics. */ #define IEEE80211_CHAN_FHSS \ (IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_GFSK) #define IEEE80211_CHAN_A \ (IEEE80211_CHAN_5GHZ | IEEE80211_CHAN_OFDM) #define IEEE80211_CHAN_B \ (IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_CCK) #define IEEE80211_CHAN_PUREG \ (IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_OFDM) #define IEEE80211_CHAN_G \ (IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_DYN) #define IEEE80211_CHAN_108A \ (IEEE80211_CHAN_A | IEEE80211_CHAN_TURBO) #define IEEE80211_CHAN_108G \ (IEEE80211_CHAN_PUREG | IEEE80211_CHAN_TURBO) #define IEEE80211_CHAN_ST \ (IEEE80211_CHAN_108A | IEEE80211_CHAN_STURBO) #define IEEE80211_CHAN_ALL \ (IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_5GHZ | IEEE80211_CHAN_GFSK | \ IEEE80211_CHAN_CCK | IEEE80211_CHAN_OFDM | IEEE80211_CHAN_DYN | \ IEEE80211_CHAN_HALF | IEEE80211_CHAN_QUARTER | \ IEEE80211_CHAN_HT | IEEE80211_CHAN_VHT) #define IEEE80211_CHAN_ALLTURBO \ (IEEE80211_CHAN_ALL | IEEE80211_CHAN_TURBO | IEEE80211_CHAN_STURBO) #define IEEE80211_IS_CHAN_FHSS(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_FHSS) == IEEE80211_CHAN_FHSS) #define IEEE80211_IS_CHAN_A(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_A) == IEEE80211_CHAN_A) #define IEEE80211_IS_CHAN_B(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_B) == IEEE80211_CHAN_B) #define IEEE80211_IS_CHAN_PUREG(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_PUREG) == IEEE80211_CHAN_PUREG) #define IEEE80211_IS_CHAN_G(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_G) == IEEE80211_CHAN_G) #define IEEE80211_IS_CHAN_ANYG(_c) \ (IEEE80211_IS_CHAN_PUREG(_c) || IEEE80211_IS_CHAN_G(_c)) #define IEEE80211_IS_CHAN_ST(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_ST) == IEEE80211_CHAN_ST) #define IEEE80211_IS_CHAN_108A(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_108A) == IEEE80211_CHAN_108A) #define IEEE80211_IS_CHAN_108G(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_108G) == IEEE80211_CHAN_108G) #define IEEE80211_IS_CHAN_2GHZ(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_2GHZ) != 0) #define IEEE80211_IS_CHAN_5GHZ(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_5GHZ) != 0) #define IEEE80211_IS_CHAN_PASSIVE(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_PASSIVE) != 0) #define IEEE80211_IS_CHAN_OFDM(_c) \ (((_c)->ic_flags & (IEEE80211_CHAN_OFDM | IEEE80211_CHAN_DYN)) != 0) #define IEEE80211_IS_CHAN_CCK(_c) \ (((_c)->ic_flags & (IEEE80211_CHAN_CCK | IEEE80211_CHAN_DYN)) != 0) #define IEEE80211_IS_CHAN_DYN(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_DYN) == IEEE80211_CHAN_DYN) #define IEEE80211_IS_CHAN_GFSK(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_GFSK) != 0) #define IEEE80211_IS_CHAN_TURBO(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_TURBO) != 0) #define IEEE80211_IS_CHAN_STURBO(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_STURBO) != 0) #define IEEE80211_IS_CHAN_DTURBO(_c) \ (((_c)->ic_flags & \ (IEEE80211_CHAN_TURBO | IEEE80211_CHAN_STURBO)) == IEEE80211_CHAN_TURBO) #define IEEE80211_IS_CHAN_HALF(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_HALF) != 0) #define IEEE80211_IS_CHAN_QUARTER(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_QUARTER) != 0) #define IEEE80211_IS_CHAN_FULL(_c) \ (((_c)->ic_flags & (IEEE80211_CHAN_QUARTER | IEEE80211_CHAN_HALF)) == 0) #define IEEE80211_IS_CHAN_GSM(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_GSM) != 0) #define IEEE80211_IS_CHAN_HT(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_HT) != 0) #define IEEE80211_IS_CHAN_HT20(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_HT20) != 0) #define IEEE80211_IS_CHAN_HT40(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_HT40) != 0) #define IEEE80211_IS_CHAN_HT40U(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_HT40U) != 0) #define IEEE80211_IS_CHAN_HT40D(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_HT40D) != 0) #define IEEE80211_IS_CHAN_HTA(_c) \ (IEEE80211_IS_CHAN_5GHZ(_c) && \ ((_c)->ic_flags & IEEE80211_CHAN_HT) != 0) #define IEEE80211_IS_CHAN_HTG(_c) \ (IEEE80211_IS_CHAN_2GHZ(_c) && \ ((_c)->ic_flags & IEEE80211_CHAN_HT) != 0) #define IEEE80211_IS_CHAN_DFS(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_DFS) != 0) #define IEEE80211_IS_CHAN_NOADHOC(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_NOADHOC) != 0) #define IEEE80211_IS_CHAN_NOHOSTAP(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_NOHOSTAP) != 0) #define IEEE80211_IS_CHAN_11D(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_11D) != 0) #define IEEE80211_IS_CHAN_VHT(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_VHT) != 0) #define IEEE80211_IS_CHAN_VHT_2GHZ(_c) \ (IEEE80211_IS_CHAN_2GHZ(_c) && \ ((_c)->ic_flags & IEEE80211_CHAN_VHT) != 0) #define IEEE80211_IS_CHAN_VHT_5GHZ(_c) \ (IEEE80211_IS_CHAN_5GHZ(_c) && \ ((_c)->ic_flags & IEEE80211_CHAN_VHT) != 0) #define IEEE80211_IS_CHAN_VHT20(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_VHT20) != 0) #define IEEE80211_IS_CHAN_VHT40(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_VHT40) != 0) #define IEEE80211_IS_CHAN_VHT40U(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_VHT40U) != 0) #define IEEE80211_IS_CHAN_VHT40D(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_VHT40D) != 0) #define IEEE80211_IS_CHAN_VHTA(_c) \ (IEEE80211_IS_CHAN_5GHZ(_c) && \ ((_c)->ic_flags & IEEE80211_CHAN_VHT) != 0) #define IEEE80211_IS_CHAN_VHTG(_c) \ (IEEE80211_IS_CHAN_2GHZ(_c) && \ ((_c)->ic_flags & IEEE80211_CHAN_VHT) != 0) #define IEEE80211_IS_CHAN_VHT80(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_VHT80) != 0) #define IEEE80211_IS_CHAN_VHT160(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_VHT160) != 0) #define IEEE80211_IS_CHAN_VHT80P80(_c) \ (((_c)->ic_flags & IEEE80211_CHAN_VHT80P80) != 0) #define IEEE80211_CHAN2IEEE(_c) (_c)->ic_ieee /* dynamic state */ #define IEEE80211_CHANSTATE_RADAR 0x01 /* radar detected */ #define IEEE80211_CHANSTATE_CACDONE 0x02 /* CAC completed */ #define IEEE80211_CHANSTATE_CWINT 0x04 /* interference detected */ #define IEEE80211_CHANSTATE_NORADAR 0x10 /* post notify on radar clear */ #define IEEE80211_IS_CHAN_RADAR(_c) \ (((_c)->ic_state & IEEE80211_CHANSTATE_RADAR) != 0) #define IEEE80211_IS_CHAN_CACDONE(_c) \ (((_c)->ic_state & IEEE80211_CHANSTATE_CACDONE) != 0) #define IEEE80211_IS_CHAN_CWINT(_c) \ (((_c)->ic_state & IEEE80211_CHANSTATE_CWINT) != 0) /* ni_chan encoding for FH phy */ #define IEEE80211_FH_CHANMOD 80 #define IEEE80211_FH_CHAN(set,pat) (((set)-1)*IEEE80211_FH_CHANMOD+(pat)) #define IEEE80211_FH_CHANSET(chan) ((chan)/IEEE80211_FH_CHANMOD+1) #define IEEE80211_FH_CHANPAT(chan) ((chan)%IEEE80211_FH_CHANMOD) #define IEEE80211_TID_SIZE (WME_NUM_TID+1) /* WME TID's +1 for non-QoS */ #define IEEE80211_NONQOS_TID WME_NUM_TID /* index for non-QoS sta */ /* * The 802.11 spec says at most 2007 stations may be * associated at once. For most AP's this is way more * than is feasible so we use a default of 128. This * number may be overridden by the driver and/or by * user configuration but may not be less than IEEE80211_AID_MIN. */ #define IEEE80211_AID_DEF 128 #define IEEE80211_AID_MIN 16 /* * 802.11 rate set. */ #define IEEE80211_RATE_SIZE 8 /* 802.11 standard */ #define IEEE80211_RATE_MAXSIZE 15 /* max rates we'll handle */ struct ieee80211_rateset { uint8_t rs_nrates; uint8_t rs_rates[IEEE80211_RATE_MAXSIZE]; }; /* * 802.11n variant of ieee80211_rateset. Instead of * legacy rates the entries are MCS rates. We define * the structure such that it can be used interchangeably * with an ieee80211_rateset (modulo structure size). */ #define IEEE80211_HTRATE_MAXSIZE 77 struct ieee80211_htrateset { uint8_t rs_nrates; uint8_t rs_rates[IEEE80211_HTRATE_MAXSIZE]; }; #define IEEE80211_RATE_MCS 0x80 /* * Per-mode transmit parameters/controls visible to user space. * These can be used to set fixed transmit rate for all operating * modes or on a per-client basis according to the capabilities * of the client (e.g. an 11b client associated to an 11g ap). * * MCS are distinguished from legacy rates by or'ing in 0x80. */ struct ieee80211_txparam { uint8_t ucastrate; /* ucast data rate (legacy/MCS|0x80) */ uint8_t mgmtrate; /* mgmt frame rate (legacy/MCS|0x80) */ uint8_t mcastrate; /* multicast rate (legacy/MCS|0x80) */ uint8_t maxretry; /* max unicast data retry count */ }; /* * Per-mode roaming state visible to user space. There are two * thresholds that control whether roaming is considered; when * either is exceeded the 802.11 layer will check the scan cache * for another AP. If the cache is stale then a scan may be * triggered. */ struct ieee80211_roamparam { int8_t rssi; /* rssi thresh (.5 dBm) */ uint8_t rate; /* tx rate thresh (.5 Mb/s or MCS) */ uint16_t pad; /* reserve */ }; /* * Regulatory Information. */ struct ieee80211_regdomain { uint16_t regdomain; /* SKU */ uint16_t country; /* ISO country code */ uint8_t location; /* I (indoor), O (outdoor), other */ uint8_t ecm; /* Extended Channel Mode */ char isocc[2]; /* country code string */ short pad[2]; }; /* * MIMO antenna/radio state. */ #define IEEE80211_MAX_CHAINS 4 /* * This is the number of sub-channels for a channel. * 0 - pri20 * 1 - sec20 (HT40, VHT40) * 2 - sec40 (VHT80) * 3 - sec80 (VHT80+80, VHT160) */ #define IEEE80211_MAX_CHAIN_PRISEC 4 #define IEEE80211_MAX_EVM_DWORDS 16 /* 16 pilots, 4 chains */ #define IEEE80211_MAX_EVM_PILOTS 16 /* 468 subcarriers, 16 pilots */ struct ieee80211_mimo_chan_info { int8_t rssi[IEEE80211_MAX_CHAIN_PRISEC]; int8_t noise[IEEE80211_MAX_CHAIN_PRISEC]; }; struct ieee80211_mimo_info { struct ieee80211_mimo_chan_info ch[IEEE80211_MAX_CHAINS]; uint32_t evm[IEEE80211_MAX_EVM_DWORDS]; }; /* * ic_caps/iv_caps: device driver capabilities */ /* 0x2e available */ #define IEEE80211_C_STA 0x00000001 /* CAPABILITY: STA available */ #define IEEE80211_C_8023ENCAP 0x00000002 /* CAPABILITY: 802.3 encap */ #define IEEE80211_C_FF 0x00000040 /* CAPABILITY: ATH FF avail */ #define IEEE80211_C_TURBOP 0x00000080 /* CAPABILITY: ATH Turbo avail*/ #define IEEE80211_C_IBSS 0x00000100 /* CAPABILITY: IBSS available */ #define IEEE80211_C_PMGT 0x00000200 /* CAPABILITY: Power mgmt */ #define IEEE80211_C_HOSTAP 0x00000400 /* CAPABILITY: HOSTAP avail */ #define IEEE80211_C_AHDEMO 0x00000800 /* CAPABILITY: Old Adhoc Demo */ #define IEEE80211_C_SWRETRY 0x00001000 /* CAPABILITY: sw tx retry */ #define IEEE80211_C_TXPMGT 0x00002000 /* CAPABILITY: tx power mgmt */ #define IEEE80211_C_SHSLOT 0x00004000 /* CAPABILITY: short slottime */ #define IEEE80211_C_SHPREAMBLE 0x00008000 /* CAPABILITY: short preamble */ #define IEEE80211_C_MONITOR 0x00010000 /* CAPABILITY: monitor mode */ #define IEEE80211_C_DFS 0x00020000 /* CAPABILITY: DFS/radar avail*/ #define IEEE80211_C_MBSS 0x00040000 /* CAPABILITY: MBSS available */ #define IEEE80211_C_SWSLEEP 0x00080000 /* CAPABILITY: do sleep here */ #define IEEE80211_C_SWAMSDUTX 0x00100000 /* CAPABILITY: software A-MSDU TX */ #define IEEE80211_C_UAPSD 0x00200000 /* CAPABILITY: U-APSD */ /* 0x7c0000 available */ #define IEEE80211_C_WPA1 0x00800000 /* CAPABILITY: WPA1 avail */ #define IEEE80211_C_WPA2 0x01000000 /* CAPABILITY: WPA2 avail */ #define IEEE80211_C_WPA 0x01800000 /* CAPABILITY: WPA1+WPA2 avail*/ #define IEEE80211_C_BURST 0x02000000 /* CAPABILITY: frame bursting */ #define IEEE80211_C_WME 0x04000000 /* CAPABILITY: WME avail */ #define IEEE80211_C_WDS 0x08000000 /* CAPABILITY: 4-addr support */ /* 0x10000000 reserved */ #define IEEE80211_C_BGSCAN 0x20000000 /* CAPABILITY: bg scanning */ #define IEEE80211_C_TXFRAG 0x40000000 /* CAPABILITY: tx fragments */ #define IEEE80211_C_TDMA 0x80000000 /* CAPABILITY: TDMA avail */ /* XXX protection/barker? */ #define IEEE80211_C_OPMODE \ (IEEE80211_C_STA | IEEE80211_C_IBSS | IEEE80211_C_HOSTAP | \ IEEE80211_C_AHDEMO | IEEE80211_C_MONITOR | IEEE80211_C_WDS | \ IEEE80211_C_TDMA | IEEE80211_C_MBSS) #define IEEE80211_C_BITS \ "\20\1STA\002803ENCAP\7FF\10TURBOP\11IBSS\12PMGT" \ "\13HOSTAP\14AHDEMO\15SWRETRY\16TXPMGT\17SHSLOT\20SHPREAMBLE" \ "\21MONITOR\22DFS\23MBSS\30WPA1\31WPA2\32BURST\33WME\34WDS\36BGSCAN" \ "\37TXFRAG\40TDMA" /* * ic_htcaps/iv_htcaps: HT-specific device/driver capabilities * * NB: the low 16-bits are the 802.11 definitions, the upper * 16-bits are used to define s/w/driver capabilities. */ #define IEEE80211_HTC_AMPDU 0x00010000 /* CAPABILITY: A-MPDU tx */ #define IEEE80211_HTC_AMSDU 0x00020000 /* CAPABILITY: A-MSDU tx */ /* NB: HT40 is implied by IEEE80211_HTCAP_CHWIDTH40 */ #define IEEE80211_HTC_HT 0x00040000 /* CAPABILITY: HT operation */ #define IEEE80211_HTC_SMPS 0x00080000 /* CAPABILITY: MIMO power save*/ #define IEEE80211_HTC_RIFS 0x00100000 /* CAPABILITY: RIFS support */ #define IEEE80211_HTC_RXUNEQUAL 0x00200000 /* CAPABILITY: RX unequal MCS */ #define IEEE80211_HTC_RXMCS32 0x00400000 /* CAPABILITY: MCS32 support */ #define IEEE80211_HTC_TXUNEQUAL 0x00800000 /* CAPABILITY: TX unequal MCS */ #define IEEE80211_HTC_TXMCS32 0x01000000 /* CAPABILITY: MCS32 support */ #define IEEE80211_HTC_TXLDPC 0x02000000 /* CAPABILITY: TX using LDPC */ #define IEEE80211_HTC_RX_AMSDU_AMPDU 0x04000000 /* CAPABILITY: RX A-MSDU in A-MPDU */ #define IEEE80211_HTC_TX_AMSDU_AMPDU 0x08000000 /* CAPABILITY: TX A-MSDU in A-MPDU */ #define IEEE80211_C_HTCAP_BITS \ "\20\1LDPC\2CHWIDTH40\5GREENFIELD\6SHORTGI20\7SHORTGI40\10TXSTBC" \ "\21AMPDU\22AMSDU\23HT\24SMPS\25RIFS\32TXLDPC\33RXAMSDUAMPDU" \ "\34TXAMSDUAMPDU" /* * RX status notification - which fields are valid. */ #define IEEE80211_R_NF 0x00000001 /* global NF value valid */ #define IEEE80211_R_RSSI 0x00000002 /* global RSSI value valid */ #define IEEE80211_R_C_CHAIN 0x00000004 /* RX chain count valid */ #define IEEE80211_R_C_NF 0x00000008 /* per-chain NF value valid */ #define IEEE80211_R_C_RSSI 0x00000010 /* per-chain RSSI value valid */ #define IEEE80211_R_C_EVM 0x00000020 /* per-chain EVM valid */ #define IEEE80211_R_C_HT40 0x00000040 /* RX'ed packet is 40mhz, pilots 4,5 valid */ #define IEEE80211_R_FREQ 0x00000080 /* Freq value populated, MHz */ #define IEEE80211_R_IEEE 0x00000100 /* IEEE value populated */ #define IEEE80211_R_BAND 0x00000200 /* Frequency band populated */ #define IEEE80211_R_TSF32 0x00004000 /* 32 bit TSF */ #define IEEE80211_R_TSF64 0x00008000 /* 64 bit TSF */ #define IEEE80211_R_TSF_START 0x00010000 /* TSF is sampled at start of frame */ #define IEEE80211_R_TSF_END 0x00020000 /* TSF is sampled at end of frame */ /* * RX status notification - describe the packet. */ #define IEEE80211_RX_F_STBC 0x00000001 #define IEEE80211_RX_F_LDPC 0x00000002 #define IEEE80211_RX_F_AMSDU 0x00000004 /* This is the start of an decap AMSDU list */ #define IEEE80211_RX_F_AMSDU_MORE 0x00000008 /* This is another decap AMSDU frame in the batch */ #define IEEE80211_RX_F_AMPDU 0x00000010 /* This is the start of an decap AMPDU list */ #define IEEE80211_RX_F_AMPDU_MORE 0x00000020 /* This is another decap AMPDU frame in the batch */ #define IEEE80211_RX_F_FAIL_FCSCRC 0x00000040 /* Failed CRC/FCS */ #define IEEE80211_RX_F_FAIL_MIC 0x00000080 /* Failed MIC check */ #define IEEE80211_RX_F_DECRYPTED 0x00000100 /* Hardware decrypted */ #define IEEE80211_RX_F_IV_STRIP 0x00000200 /* Decrypted; IV stripped */ #define IEEE80211_RX_F_MMIC_STRIP 0x00000400 /* Decrypted; MMIC stripped */ #define IEEE80211_RX_F_SHORTGI 0x00000800 /* This is a short-GI frame */ #define IEEE80211_RX_F_CCK 0x00001000 #define IEEE80211_RX_F_OFDM 0x00002000 #define IEEE80211_RX_F_HT 0x00004000 #define IEEE80211_RX_F_VHT 0x00008000 /* Channel width */ #define IEEE80211_RX_FW_20MHZ 1 #define IEEE80211_RX_FW_40MHZ 2 #define IEEE80211_RX_FW_80MHZ 3 #define IEEE80211_RX_FW_160MHZ 4 /* PHY type */ #define IEEE80211_RX_FP_11B 1 #define IEEE80211_RX_FP_11G 2 #define IEEE80211_RX_FP_11A 3 #define IEEE80211_RX_FP_11NA 4 #define IEEE80211_RX_FP_11NG 5 struct ieee80211_rx_stats { uint32_t r_flags; /* IEEE80211_R_* flags */ uint32_t c_pktflags; /* IEEE80211_RX_F_* flags */ uint64_t c_rx_tsf; /* 32 or 64 bit TSF */ /* All DWORD aligned */ int16_t c_nf_ctl[IEEE80211_MAX_CHAINS]; /* per-chain NF */ int16_t c_nf_ext[IEEE80211_MAX_CHAINS]; /* per-chain NF */ int16_t c_rssi_ctl[IEEE80211_MAX_CHAINS]; /* per-chain RSSI */ int16_t c_rssi_ext[IEEE80211_MAX_CHAINS]; /* per-chain RSSI */ /* 32 bits */ uint8_t c_nf; /* global NF */ uint8_t c_rssi; /* global RSSI */ uint8_t c_chain; /* number of RX chains involved */ uint8_t c_rate; /* legacy; 11n rate code; VHT MCS */ /* 32 bits */ uint16_t c_freq; /* Frequency, MHz */ uint8_t c_ieee; /* Channel */ uint8_t c_width; /* channel width, FW flags above */ + /* 32 bits */ + uint32_t c_band; /* Band; XXX we do not have a real band. */ + /* Force alignment to DWORD */ union { uint8_t evm[IEEE80211_MAX_CHAINS][IEEE80211_MAX_EVM_PILOTS]; /* per-chain, per-pilot EVM values */ uint32_t __aln[8]; } evm; /* 32 bits */ uint8_t c_phytype; /* PHY type, FW flags above */ uint8_t c_vhtnss; /* VHT - number of spatial streams */ uint8_t c_pad2[2]; }; struct ieee80211_rx_params { struct ieee80211_rx_stats params; }; #endif /* _NET80211__IEEE80211_H_ */ diff --git a/sys/net80211/ieee80211.c b/sys/net80211/ieee80211.c index 2e98e67edc47..35cf7abdbf3b 100644 --- a/sys/net80211/ieee80211.c +++ b/sys/net80211/ieee80211.c @@ -1,2634 +1,2645 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * 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$"); /* * IEEE 802.11 generic handler */ #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_SUPERG #include #endif #include #include #include const char *ieee80211_phymode_name[IEEE80211_MODE_MAX] = { [IEEE80211_MODE_AUTO] = "auto", [IEEE80211_MODE_11A] = "11a", [IEEE80211_MODE_11B] = "11b", [IEEE80211_MODE_11G] = "11g", [IEEE80211_MODE_FH] = "FH", [IEEE80211_MODE_TURBO_A] = "turboA", [IEEE80211_MODE_TURBO_G] = "turboG", [IEEE80211_MODE_STURBO_A] = "sturboA", [IEEE80211_MODE_HALF] = "half", [IEEE80211_MODE_QUARTER] = "quarter", [IEEE80211_MODE_11NA] = "11na", [IEEE80211_MODE_11NG] = "11ng", [IEEE80211_MODE_VHT_2GHZ] = "11acg", [IEEE80211_MODE_VHT_5GHZ] = "11ac", }; /* map ieee80211_opmode to the corresponding capability bit */ const int ieee80211_opcap[IEEE80211_OPMODE_MAX] = { [IEEE80211_M_IBSS] = IEEE80211_C_IBSS, [IEEE80211_M_WDS] = IEEE80211_C_WDS, [IEEE80211_M_STA] = IEEE80211_C_STA, [IEEE80211_M_AHDEMO] = IEEE80211_C_AHDEMO, [IEEE80211_M_HOSTAP] = IEEE80211_C_HOSTAP, [IEEE80211_M_MONITOR] = IEEE80211_C_MONITOR, #ifdef IEEE80211_SUPPORT_MESH [IEEE80211_M_MBSS] = IEEE80211_C_MBSS, #endif }; const uint8_t ieee80211broadcastaddr[IEEE80211_ADDR_LEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; static void ieee80211_syncflag_locked(struct ieee80211com *ic, int flag); static void ieee80211_syncflag_ht_locked(struct ieee80211com *ic, int flag); static void ieee80211_syncflag_ext_locked(struct ieee80211com *ic, int flag); static void ieee80211_syncflag_vht_locked(struct ieee80211com *ic, int flag); static int ieee80211_media_setup(struct ieee80211com *ic, struct ifmedia *media, int caps, int addsta, ifm_change_cb_t media_change, ifm_stat_cb_t media_stat); static int media_status(enum ieee80211_opmode, const struct ieee80211_channel *); static uint64_t ieee80211_get_counter(struct ifnet *, ift_counter); MALLOC_DEFINE(M_80211_VAP, "80211vap", "802.11 vap state"); /* * Default supported rates for 802.11 operation (in IEEE .5Mb units). */ #define B(r) ((r) | IEEE80211_RATE_BASIC) static const struct ieee80211_rateset ieee80211_rateset_11a = { 8, { B(12), 18, B(24), 36, B(48), 72, 96, 108 } }; static const struct ieee80211_rateset ieee80211_rateset_half = { 8, { B(6), 9, B(12), 18, B(24), 36, 48, 54 } }; static const struct ieee80211_rateset ieee80211_rateset_quarter = { 8, { B(3), 4, B(6), 9, B(12), 18, 24, 27 } }; static const struct ieee80211_rateset ieee80211_rateset_11b = { 4, { B(2), B(4), B(11), B(22) } }; /* NB: OFDM rates are handled specially based on mode */ static const struct ieee80211_rateset ieee80211_rateset_11g = { 12, { B(2), B(4), B(11), B(22), 12, 18, 24, 36, 48, 72, 96, 108 } }; #undef B static int set_vht_extchan(struct ieee80211_channel *c); /* * Fill in 802.11 available channel set, mark * all available channels as active, and pick * a default channel if not already specified. */ void ieee80211_chan_init(struct ieee80211com *ic) { #define DEFAULTRATES(m, def) do { \ if (ic->ic_sup_rates[m].rs_nrates == 0) \ ic->ic_sup_rates[m] = def; \ } while (0) struct ieee80211_channel *c; int i; KASSERT(0 < ic->ic_nchans && ic->ic_nchans <= IEEE80211_CHAN_MAX, ("invalid number of channels specified: %u", ic->ic_nchans)); memset(ic->ic_chan_avail, 0, sizeof(ic->ic_chan_avail)); memset(ic->ic_modecaps, 0, sizeof(ic->ic_modecaps)); setbit(ic->ic_modecaps, IEEE80211_MODE_AUTO); for (i = 0; i < ic->ic_nchans; i++) { c = &ic->ic_channels[i]; KASSERT(c->ic_flags != 0, ("channel with no flags")); /* * Help drivers that work only with frequencies by filling * in IEEE channel #'s if not already calculated. Note this * mimics similar work done in ieee80211_setregdomain when * changing regulatory state. */ if (c->ic_ieee == 0) c->ic_ieee = ieee80211_mhz2ieee(c->ic_freq,c->ic_flags); /* * Setup the HT40/VHT40 upper/lower bits. * The VHT80/... math is done elsewhere. */ if (IEEE80211_IS_CHAN_HT40(c) && c->ic_extieee == 0) c->ic_extieee = ieee80211_mhz2ieee(c->ic_freq + (IEEE80211_IS_CHAN_HT40U(c) ? 20 : -20), c->ic_flags); /* Update VHT math */ /* * XXX VHT again, note that this assumes VHT80/... channels * are legit already. */ set_vht_extchan(c); /* default max tx power to max regulatory */ if (c->ic_maxpower == 0) c->ic_maxpower = 2*c->ic_maxregpower; setbit(ic->ic_chan_avail, c->ic_ieee); /* * Identify mode capabilities. */ if (IEEE80211_IS_CHAN_A(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_11A); if (IEEE80211_IS_CHAN_B(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_11B); if (IEEE80211_IS_CHAN_ANYG(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_11G); if (IEEE80211_IS_CHAN_FHSS(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_FH); if (IEEE80211_IS_CHAN_108A(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_TURBO_A); if (IEEE80211_IS_CHAN_108G(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_TURBO_G); if (IEEE80211_IS_CHAN_ST(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_STURBO_A); if (IEEE80211_IS_CHAN_HALF(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_HALF); if (IEEE80211_IS_CHAN_QUARTER(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_QUARTER); if (IEEE80211_IS_CHAN_HTA(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_11NA); if (IEEE80211_IS_CHAN_HTG(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_11NG); if (IEEE80211_IS_CHAN_VHTA(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_VHT_5GHZ); if (IEEE80211_IS_CHAN_VHTG(c)) setbit(ic->ic_modecaps, IEEE80211_MODE_VHT_2GHZ); } /* initialize candidate channels to all available */ memcpy(ic->ic_chan_active, ic->ic_chan_avail, sizeof(ic->ic_chan_avail)); /* sort channel table to allow lookup optimizations */ ieee80211_sort_channels(ic->ic_channels, ic->ic_nchans); /* invalidate any previous state */ ic->ic_bsschan = IEEE80211_CHAN_ANYC; ic->ic_prevchan = NULL; ic->ic_csa_newchan = NULL; /* arbitrarily pick the first channel */ ic->ic_curchan = &ic->ic_channels[0]; ic->ic_rt = ieee80211_get_ratetable(ic->ic_curchan); /* fillin well-known rate sets if driver has not specified */ DEFAULTRATES(IEEE80211_MODE_11B, ieee80211_rateset_11b); DEFAULTRATES(IEEE80211_MODE_11G, ieee80211_rateset_11g); DEFAULTRATES(IEEE80211_MODE_11A, ieee80211_rateset_11a); DEFAULTRATES(IEEE80211_MODE_TURBO_A, ieee80211_rateset_11a); DEFAULTRATES(IEEE80211_MODE_TURBO_G, ieee80211_rateset_11g); DEFAULTRATES(IEEE80211_MODE_STURBO_A, ieee80211_rateset_11a); DEFAULTRATES(IEEE80211_MODE_HALF, ieee80211_rateset_half); DEFAULTRATES(IEEE80211_MODE_QUARTER, ieee80211_rateset_quarter); DEFAULTRATES(IEEE80211_MODE_11NA, ieee80211_rateset_11a); DEFAULTRATES(IEEE80211_MODE_11NG, ieee80211_rateset_11g); DEFAULTRATES(IEEE80211_MODE_VHT_2GHZ, ieee80211_rateset_11g); DEFAULTRATES(IEEE80211_MODE_VHT_5GHZ, ieee80211_rateset_11a); /* * Setup required information to fill the mcsset field, if driver did * not. Assume a 2T2R setup for historic reasons. */ if (ic->ic_rxstream == 0) ic->ic_rxstream = 2; if (ic->ic_txstream == 0) ic->ic_txstream = 2; ieee80211_init_suphtrates(ic); /* * Set auto mode to reset active channel state and any desired channel. */ (void) ieee80211_setmode(ic, IEEE80211_MODE_AUTO); #undef DEFAULTRATES } static void null_update_mcast(struct ieee80211com *ic) { ic_printf(ic, "need multicast update callback\n"); } static void null_update_promisc(struct ieee80211com *ic) { ic_printf(ic, "need promiscuous mode update callback\n"); } static void null_update_chw(struct ieee80211com *ic) { ic_printf(ic, "%s: need callback\n", __func__); } int ic_printf(struct ieee80211com *ic, const char * fmt, ...) { va_list ap; int retval; retval = printf("%s: ", ic->ic_name); va_start(ap, fmt); retval += vprintf(fmt, ap); va_end(ap); return (retval); } static LIST_HEAD(, ieee80211com) ic_head = LIST_HEAD_INITIALIZER(ic_head); static struct mtx ic_list_mtx; MTX_SYSINIT(ic_list, &ic_list_mtx, "ieee80211com list", MTX_DEF); static int sysctl_ieee80211coms(SYSCTL_HANDLER_ARGS) { struct ieee80211com *ic; struct sbuf sb; char *sp; int error; error = sysctl_wire_old_buffer(req, 0); if (error) return (error); sbuf_new_for_sysctl(&sb, NULL, 8, req); sbuf_clear_flags(&sb, SBUF_INCLUDENUL); sp = ""; mtx_lock(&ic_list_mtx); LIST_FOREACH(ic, &ic_head, ic_next) { sbuf_printf(&sb, "%s%s", sp, ic->ic_name); sp = " "; } mtx_unlock(&ic_list_mtx); error = sbuf_finish(&sb); sbuf_delete(&sb); return (error); } SYSCTL_PROC(_net_wlan, OID_AUTO, devices, CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, 0, sysctl_ieee80211coms, "A", "names of available 802.11 devices"); /* * Attach/setup the common net80211 state. Called by * the driver on attach to prior to creating any vap's. */ void ieee80211_ifattach(struct ieee80211com *ic) { IEEE80211_LOCK_INIT(ic, ic->ic_name); IEEE80211_TX_LOCK_INIT(ic, ic->ic_name); TAILQ_INIT(&ic->ic_vaps); /* Create a taskqueue for all state changes */ ic->ic_tq = taskqueue_create("ic_taskq", M_WAITOK | M_ZERO, taskqueue_thread_enqueue, &ic->ic_tq); taskqueue_start_threads(&ic->ic_tq, 1, PI_NET, "%s net80211 taskq", ic->ic_name); ic->ic_ierrors = counter_u64_alloc(M_WAITOK); ic->ic_oerrors = counter_u64_alloc(M_WAITOK); /* * Fill in 802.11 available channel set, mark all * available channels as active, and pick a default * channel if not already specified. */ ieee80211_chan_init(ic); ic->ic_update_mcast = null_update_mcast; ic->ic_update_promisc = null_update_promisc; ic->ic_update_chw = null_update_chw; ic->ic_hash_key = arc4random(); ic->ic_bintval = IEEE80211_BINTVAL_DEFAULT; ic->ic_lintval = ic->ic_bintval; ic->ic_txpowlimit = IEEE80211_TXPOWER_MAX; ieee80211_crypto_attach(ic); ieee80211_node_attach(ic); ieee80211_power_attach(ic); ieee80211_proto_attach(ic); #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_superg_attach(ic); #endif ieee80211_ht_attach(ic); ieee80211_vht_attach(ic); ieee80211_scan_attach(ic); ieee80211_regdomain_attach(ic); ieee80211_dfs_attach(ic); ieee80211_sysctl_attach(ic); mtx_lock(&ic_list_mtx); LIST_INSERT_HEAD(&ic_head, ic, ic_next); mtx_unlock(&ic_list_mtx); } /* * Detach net80211 state on device detach. Tear down * all vap's and reclaim all common state prior to the * device state going away. Note we may call back into * driver; it must be prepared for this. */ void ieee80211_ifdetach(struct ieee80211com *ic) { struct ieee80211vap *vap; /* * We use this as an indicator that ifattach never had a chance to be * called, e.g. early driver attach failed and ifdetach was called * during subsequent detach. Never fear, for we have nothing to do * here. */ if (ic->ic_tq == NULL) return; mtx_lock(&ic_list_mtx); LIST_REMOVE(ic, ic_next); mtx_unlock(&ic_list_mtx); taskqueue_drain(taskqueue_thread, &ic->ic_restart_task); /* * The VAP is responsible for setting and clearing * the VIMAGE context. */ while ((vap = TAILQ_FIRST(&ic->ic_vaps)) != NULL) { ieee80211_com_vdetach(vap); ieee80211_vap_destroy(vap); } ieee80211_waitfor_parent(ic); ieee80211_sysctl_detach(ic); ieee80211_dfs_detach(ic); ieee80211_regdomain_detach(ic); ieee80211_scan_detach(ic); #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_superg_detach(ic); #endif ieee80211_vht_detach(ic); ieee80211_ht_detach(ic); /* NB: must be called before ieee80211_node_detach */ ieee80211_proto_detach(ic); ieee80211_crypto_detach(ic); ieee80211_power_detach(ic); ieee80211_node_detach(ic); counter_u64_free(ic->ic_ierrors); counter_u64_free(ic->ic_oerrors); taskqueue_free(ic->ic_tq); IEEE80211_TX_LOCK_DESTROY(ic); IEEE80211_LOCK_DESTROY(ic); } struct ieee80211com * ieee80211_find_com(const char *name) { struct ieee80211com *ic; mtx_lock(&ic_list_mtx); LIST_FOREACH(ic, &ic_head, ic_next) if (strcmp(ic->ic_name, name) == 0) break; mtx_unlock(&ic_list_mtx); return (ic); } void ieee80211_iterate_coms(ieee80211_com_iter_func *f, void *arg) { struct ieee80211com *ic; mtx_lock(&ic_list_mtx); LIST_FOREACH(ic, &ic_head, ic_next) (*f)(arg, ic); mtx_unlock(&ic_list_mtx); } /* * Default reset method for use with the ioctl support. This * method is invoked after any state change in the 802.11 * layer that should be propagated to the hardware but not * require re-initialization of the 802.11 state machine (e.g * rescanning for an ap). We always return ENETRESET which * should cause the driver to re-initialize the device. Drivers * can override this method to implement more optimized support. */ static int default_reset(struct ieee80211vap *vap, u_long cmd) { return ENETRESET; } /* * Default for updating the VAP default TX key index. * * Drivers that support TX offload as well as hardware encryption offload * may need to be informed of key index changes separate from the key * update. */ static void default_update_deftxkey(struct ieee80211vap *vap, ieee80211_keyix kid) { /* XXX assert validity */ /* XXX assert we're in a key update block */ vap->iv_def_txkey = kid; } /* * Add underlying device errors to vap errors. */ static uint64_t ieee80211_get_counter(struct ifnet *ifp, ift_counter cnt) { struct ieee80211vap *vap = ifp->if_softc; struct ieee80211com *ic = vap->iv_ic; uint64_t rv; rv = if_get_counter_default(ifp, cnt); switch (cnt) { case IFCOUNTER_OERRORS: rv += counter_u64_fetch(ic->ic_oerrors); break; case IFCOUNTER_IERRORS: rv += counter_u64_fetch(ic->ic_ierrors); break; default: break; } return (rv); } /* * Prepare a vap for use. Drivers use this call to * setup net80211 state in new vap's prior attaching * them with ieee80211_vap_attach (below). */ int ieee80211_vap_setup(struct ieee80211com *ic, struct ieee80211vap *vap, const char name[IFNAMSIZ], int unit, enum ieee80211_opmode opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN]) { struct ifnet *ifp; ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { ic_printf(ic, "%s: unable to allocate ifnet\n", __func__); return ENOMEM; } if_initname(ifp, name, unit); ifp->if_softc = vap; /* back pointer */ ifp->if_flags = IFF_SIMPLEX | IFF_BROADCAST | IFF_MULTICAST; ifp->if_transmit = ieee80211_vap_transmit; ifp->if_qflush = ieee80211_vap_qflush; ifp->if_ioctl = ieee80211_ioctl; ifp->if_init = ieee80211_init; ifp->if_get_counter = ieee80211_get_counter; vap->iv_ifp = ifp; vap->iv_ic = ic; vap->iv_flags = ic->ic_flags; /* propagate common flags */ vap->iv_flags_ext = ic->ic_flags_ext; vap->iv_flags_ven = ic->ic_flags_ven; vap->iv_caps = ic->ic_caps &~ IEEE80211_C_OPMODE; /* 11n capabilities - XXX methodize */ vap->iv_htcaps = ic->ic_htcaps; vap->iv_htextcaps = ic->ic_htextcaps; /* 11ac capabilities - XXX methodize */ vap->iv_vhtcaps = ic->ic_vhtcaps; vap->iv_vhtextcaps = ic->ic_vhtextcaps; vap->iv_opmode = opmode; vap->iv_caps |= ieee80211_opcap[opmode]; IEEE80211_ADDR_COPY(vap->iv_myaddr, ic->ic_macaddr); switch (opmode) { case IEEE80211_M_WDS: /* * WDS links must specify the bssid of the far end. * For legacy operation this is a static relationship. * For non-legacy operation the station must associate * and be authorized to pass traffic. Plumbing the * vap to the proper node happens when the vap * transitions to RUN state. */ IEEE80211_ADDR_COPY(vap->iv_des_bssid, bssid); vap->iv_flags |= IEEE80211_F_DESBSSID; if (flags & IEEE80211_CLONE_WDSLEGACY) vap->iv_flags_ext |= IEEE80211_FEXT_WDSLEGACY; break; #ifdef IEEE80211_SUPPORT_TDMA case IEEE80211_M_AHDEMO: if (flags & IEEE80211_CLONE_TDMA) { /* NB: checked before clone operation allowed */ KASSERT(ic->ic_caps & IEEE80211_C_TDMA, ("not TDMA capable, ic_caps 0x%x", ic->ic_caps)); /* * Propagate TDMA capability to mark vap; this * cannot be removed and is used to distinguish * regular ahdemo operation from ahdemo+tdma. */ vap->iv_caps |= IEEE80211_C_TDMA; } break; #endif default: break; } /* auto-enable s/w beacon miss support */ if (flags & IEEE80211_CLONE_NOBEACONS) vap->iv_flags_ext |= IEEE80211_FEXT_SWBMISS; /* auto-generated or user supplied MAC address */ if (flags & (IEEE80211_CLONE_BSSID|IEEE80211_CLONE_MACADDR)) vap->iv_flags_ext |= IEEE80211_FEXT_UNIQMAC; /* * Enable various functionality by default if we're * capable; the driver can override us if it knows better. */ if (vap->iv_caps & IEEE80211_C_WME) vap->iv_flags |= IEEE80211_F_WME; if (vap->iv_caps & IEEE80211_C_BURST) vap->iv_flags |= IEEE80211_F_BURST; /* NB: bg scanning only makes sense for station mode right now */ if (vap->iv_opmode == IEEE80211_M_STA && (vap->iv_caps & IEEE80211_C_BGSCAN)) vap->iv_flags |= IEEE80211_F_BGSCAN; vap->iv_flags |= IEEE80211_F_DOTH; /* XXX no cap, just ena */ /* NB: DFS support only makes sense for ap mode right now */ if (vap->iv_opmode == IEEE80211_M_HOSTAP && (vap->iv_caps & IEEE80211_C_DFS)) vap->iv_flags_ext |= IEEE80211_FEXT_DFS; /* NB: only flip on U-APSD for hostap/sta for now */ if ((vap->iv_opmode == IEEE80211_M_STA) || (vap->iv_opmode == IEEE80211_M_HOSTAP)) { if (vap->iv_caps & IEEE80211_C_UAPSD) vap->iv_flags_ext |= IEEE80211_FEXT_UAPSD; } vap->iv_des_chan = IEEE80211_CHAN_ANYC; /* any channel is ok */ vap->iv_bmissthreshold = IEEE80211_HWBMISS_DEFAULT; vap->iv_dtim_period = IEEE80211_DTIM_DEFAULT; /* * Install a default reset method for the ioctl support; * the driver can override this. */ vap->iv_reset = default_reset; /* * Install a default crypto key update method, the driver * can override this. */ vap->iv_update_deftxkey = default_update_deftxkey; ieee80211_sysctl_vattach(vap); ieee80211_crypto_vattach(vap); ieee80211_node_vattach(vap); ieee80211_power_vattach(vap); ieee80211_proto_vattach(vap); #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_superg_vattach(vap); #endif ieee80211_ht_vattach(vap); ieee80211_vht_vattach(vap); ieee80211_scan_vattach(vap); ieee80211_regdomain_vattach(vap); ieee80211_radiotap_vattach(vap); ieee80211_vap_reset_erp(vap); ieee80211_ratectl_set(vap, IEEE80211_RATECTL_NONE); return 0; } /* * Activate a vap. State should have been prepared with a * call to ieee80211_vap_setup and by the driver. On return * from this call the vap is ready for use. */ int ieee80211_vap_attach(struct ieee80211vap *vap, ifm_change_cb_t media_change, ifm_stat_cb_t media_stat, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct ifnet *ifp = vap->iv_ifp; struct ieee80211com *ic = vap->iv_ic; struct ifmediareq imr; int maxrate; IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s parent %s flags 0x%x flags_ext 0x%x\n", __func__, ieee80211_opmode_name[vap->iv_opmode], ic->ic_name, vap->iv_flags, vap->iv_flags_ext); /* * Do late attach work that cannot happen until after * the driver has had a chance to override defaults. */ ieee80211_node_latevattach(vap); ieee80211_power_latevattach(vap); maxrate = ieee80211_media_setup(ic, &vap->iv_media, vap->iv_caps, vap->iv_opmode == IEEE80211_M_STA, media_change, media_stat); ieee80211_media_status(ifp, &imr); /* NB: strip explicit mode; we're actually in autoselect */ ifmedia_set(&vap->iv_media, imr.ifm_active &~ (IFM_MMASK | IFM_IEEE80211_TURBO)); if (maxrate) ifp->if_baudrate = IF_Mbps(maxrate); ether_ifattach(ifp, macaddr); IEEE80211_ADDR_COPY(vap->iv_myaddr, IF_LLADDR(ifp)); /* hook output method setup by ether_ifattach */ vap->iv_output = ifp->if_output; ifp->if_output = ieee80211_output; /* NB: if_mtu set by ether_ifattach to ETHERMTU */ IEEE80211_LOCK(ic); TAILQ_INSERT_TAIL(&ic->ic_vaps, vap, iv_next); ieee80211_syncflag_locked(ic, IEEE80211_F_WME); #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_syncflag_locked(ic, IEEE80211_F_TURBOP); #endif ieee80211_syncflag_locked(ic, IEEE80211_F_PCF); ieee80211_syncflag_locked(ic, IEEE80211_F_BURST); ieee80211_syncflag_ht_locked(ic, IEEE80211_FHT_HT); ieee80211_syncflag_ht_locked(ic, IEEE80211_FHT_USEHT40); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_VHT); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_USEVHT40); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_USEVHT80); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_USEVHT160); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_USEVHT80P80); IEEE80211_UNLOCK(ic); return 1; } /* * Tear down vap state and reclaim the ifnet. * The driver is assumed to have prepared for * this; e.g. by turning off interrupts for the * underlying device. */ void ieee80211_vap_detach(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ifnet *ifp = vap->iv_ifp; CURVNET_SET(ifp->if_vnet); IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s parent %s\n", __func__, ieee80211_opmode_name[vap->iv_opmode], ic->ic_name); /* NB: bpfdetach is called by ether_ifdetach and claims all taps */ ether_ifdetach(ifp); ieee80211_stop(vap); /* * Flush any deferred vap tasks. */ ieee80211_draintask(ic, &vap->iv_nstate_task); ieee80211_draintask(ic, &vap->iv_swbmiss_task); ieee80211_draintask(ic, &vap->iv_wme_task); ieee80211_draintask(ic, &ic->ic_parent_task); /* XXX band-aid until ifnet handles this for us */ taskqueue_drain(taskqueue_swi, &ifp->if_linktask); IEEE80211_LOCK(ic); KASSERT(vap->iv_state == IEEE80211_S_INIT , ("vap still running")); TAILQ_REMOVE(&ic->ic_vaps, vap, iv_next); ieee80211_syncflag_locked(ic, IEEE80211_F_WME); #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_syncflag_locked(ic, IEEE80211_F_TURBOP); #endif ieee80211_syncflag_locked(ic, IEEE80211_F_PCF); ieee80211_syncflag_locked(ic, IEEE80211_F_BURST); ieee80211_syncflag_ht_locked(ic, IEEE80211_FHT_HT); ieee80211_syncflag_ht_locked(ic, IEEE80211_FHT_USEHT40); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_VHT); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_USEVHT40); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_USEVHT80); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_USEVHT160); ieee80211_syncflag_vht_locked(ic, IEEE80211_FVHT_USEVHT80P80); /* NB: this handles the bpfdetach done below */ ieee80211_syncflag_ext_locked(ic, IEEE80211_FEXT_BPF); if (vap->iv_ifflags & IFF_PROMISC) ieee80211_promisc(vap, false); if (vap->iv_ifflags & IFF_ALLMULTI) ieee80211_allmulti(vap, false); IEEE80211_UNLOCK(ic); ifmedia_removeall(&vap->iv_media); ieee80211_radiotap_vdetach(vap); ieee80211_regdomain_vdetach(vap); ieee80211_scan_vdetach(vap); #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_superg_vdetach(vap); #endif ieee80211_vht_vdetach(vap); ieee80211_ht_vdetach(vap); /* NB: must be before ieee80211_node_vdetach */ ieee80211_proto_vdetach(vap); ieee80211_crypto_vdetach(vap); ieee80211_power_vdetach(vap); ieee80211_node_vdetach(vap); ieee80211_sysctl_vdetach(vap); if_free(ifp); CURVNET_RESTORE(); } /* * Count number of vaps in promisc, and issue promisc on * parent respectively. */ void ieee80211_promisc(struct ieee80211vap *vap, bool on) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK_ASSERT(ic); if (on) { if (++ic->ic_promisc == 1) ieee80211_runtask(ic, &ic->ic_promisc_task); } else { KASSERT(ic->ic_promisc > 0, ("%s: ic %p not promisc", __func__, ic)); if (--ic->ic_promisc == 0) ieee80211_runtask(ic, &ic->ic_promisc_task); } } /* * Count number of vaps in allmulti, and issue allmulti on * parent respectively. */ void ieee80211_allmulti(struct ieee80211vap *vap, bool on) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK_ASSERT(ic); if (on) { if (++ic->ic_allmulti == 1) ieee80211_runtask(ic, &ic->ic_mcast_task); } else { KASSERT(ic->ic_allmulti > 0, ("%s: ic %p not allmulti", __func__, ic)); if (--ic->ic_allmulti == 0) ieee80211_runtask(ic, &ic->ic_mcast_task); } } /* * Synchronize flag bit state in the com structure * according to the state of all vap's. This is used, * for example, to handle state changes via ioctls. */ static void ieee80211_syncflag_locked(struct ieee80211com *ic, int flag) { struct ieee80211vap *vap; int bit; IEEE80211_LOCK_ASSERT(ic); bit = 0; TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) if (vap->iv_flags & flag) { bit = 1; break; } if (bit) ic->ic_flags |= flag; else ic->ic_flags &= ~flag; } void ieee80211_syncflag(struct ieee80211vap *vap, int flag) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK(ic); if (flag < 0) { flag = -flag; vap->iv_flags &= ~flag; } else vap->iv_flags |= flag; ieee80211_syncflag_locked(ic, flag); IEEE80211_UNLOCK(ic); } /* * Synchronize flags_ht bit state in the com structure * according to the state of all vap's. This is used, * for example, to handle state changes via ioctls. */ static void ieee80211_syncflag_ht_locked(struct ieee80211com *ic, int flag) { struct ieee80211vap *vap; int bit; IEEE80211_LOCK_ASSERT(ic); bit = 0; TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) if (vap->iv_flags_ht & flag) { bit = 1; break; } if (bit) ic->ic_flags_ht |= flag; else ic->ic_flags_ht &= ~flag; } void ieee80211_syncflag_ht(struct ieee80211vap *vap, int flag) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK(ic); if (flag < 0) { flag = -flag; vap->iv_flags_ht &= ~flag; } else vap->iv_flags_ht |= flag; ieee80211_syncflag_ht_locked(ic, flag); IEEE80211_UNLOCK(ic); } /* * Synchronize flags_vht bit state in the com structure * according to the state of all vap's. This is used, * for example, to handle state changes via ioctls. */ static void ieee80211_syncflag_vht_locked(struct ieee80211com *ic, int flag) { struct ieee80211vap *vap; int bit; IEEE80211_LOCK_ASSERT(ic); bit = 0; TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) if (vap->iv_flags_vht & flag) { bit = 1; break; } if (bit) ic->ic_flags_vht |= flag; else ic->ic_flags_vht &= ~flag; } void ieee80211_syncflag_vht(struct ieee80211vap *vap, int flag) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK(ic); if (flag < 0) { flag = -flag; vap->iv_flags_vht &= ~flag; } else vap->iv_flags_vht |= flag; ieee80211_syncflag_vht_locked(ic, flag); IEEE80211_UNLOCK(ic); } /* * Synchronize flags_ext bit state in the com structure * according to the state of all vap's. This is used, * for example, to handle state changes via ioctls. */ static void ieee80211_syncflag_ext_locked(struct ieee80211com *ic, int flag) { struct ieee80211vap *vap; int bit; IEEE80211_LOCK_ASSERT(ic); bit = 0; TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) if (vap->iv_flags_ext & flag) { bit = 1; break; } if (bit) ic->ic_flags_ext |= flag; else ic->ic_flags_ext &= ~flag; } void ieee80211_syncflag_ext(struct ieee80211vap *vap, int flag) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK(ic); if (flag < 0) { flag = -flag; vap->iv_flags_ext &= ~flag; } else vap->iv_flags_ext |= flag; ieee80211_syncflag_ext_locked(ic, flag); IEEE80211_UNLOCK(ic); } static __inline int mapgsm(u_int freq, u_int flags) { freq *= 10; if (flags & IEEE80211_CHAN_QUARTER) freq += 5; else if (flags & IEEE80211_CHAN_HALF) freq += 10; else freq += 20; /* NB: there is no 907/20 wide but leave room */ return (freq - 906*10) / 5; } static __inline int mappsb(u_int freq, u_int flags) { return 37 + ((freq * 10) + ((freq % 5) == 2 ? 5 : 0) - 49400) / 5; } /* * Convert MHz frequency to IEEE channel number. */ int ieee80211_mhz2ieee(u_int freq, u_int flags) { #define IS_FREQ_IN_PSB(_freq) ((_freq) > 4940 && (_freq) < 4990) if (flags & IEEE80211_CHAN_GSM) return mapgsm(freq, flags); if (flags & IEEE80211_CHAN_2GHZ) { /* 2GHz band */ if (freq == 2484) return 14; if (freq < 2484) return ((int) freq - 2407) / 5; else return 15 + ((freq - 2512) / 20); } else if (flags & IEEE80211_CHAN_5GHZ) { /* 5Ghz band */ if (freq <= 5000) { /* XXX check regdomain? */ if (IS_FREQ_IN_PSB(freq)) return mappsb(freq, flags); return (freq - 4000) / 5; } else return (freq - 5000) / 5; } else { /* either, guess */ if (freq == 2484) return 14; if (freq < 2484) { if (907 <= freq && freq <= 922) return mapgsm(freq, flags); return ((int) freq - 2407) / 5; } if (freq < 5000) { if (IS_FREQ_IN_PSB(freq)) return mappsb(freq, flags); else if (freq > 4900) return (freq - 4000) / 5; else return 15 + ((freq - 2512) / 20); } return (freq - 5000) / 5; } #undef IS_FREQ_IN_PSB } /* * Convert channel to IEEE channel number. */ int ieee80211_chan2ieee(struct ieee80211com *ic, const struct ieee80211_channel *c) { if (c == NULL) { ic_printf(ic, "invalid channel (NULL)\n"); return 0; /* XXX */ } return (c == IEEE80211_CHAN_ANYC ? IEEE80211_CHAN_ANY : c->ic_ieee); } /* * Convert IEEE channel number to MHz frequency. */ u_int ieee80211_ieee2mhz(u_int chan, u_int flags) { if (flags & IEEE80211_CHAN_GSM) return 907 + 5 * (chan / 10); if (flags & IEEE80211_CHAN_2GHZ) { /* 2GHz band */ if (chan == 14) return 2484; if (chan < 14) return 2407 + chan*5; else return 2512 + ((chan-15)*20); } else if (flags & IEEE80211_CHAN_5GHZ) {/* 5Ghz band */ if (flags & (IEEE80211_CHAN_HALF|IEEE80211_CHAN_QUARTER)) { chan -= 37; return 4940 + chan*5 + (chan % 5 ? 2 : 0); } return 5000 + (chan*5); } else { /* either, guess */ /* XXX can't distinguish PSB+GSM channels */ if (chan == 14) return 2484; if (chan < 14) /* 0-13 */ return 2407 + chan*5; if (chan < 27) /* 15-26 */ return 2512 + ((chan-15)*20); return 5000 + (chan*5); } } static __inline void set_extchan(struct ieee80211_channel *c) { /* * IEEE Std 802.11-2012, page 1738, subclause 20.3.15.4: * "the secondary channel number shall be 'N + [1,-1] * 4' */ if (c->ic_flags & IEEE80211_CHAN_HT40U) c->ic_extieee = c->ic_ieee + 4; else if (c->ic_flags & IEEE80211_CHAN_HT40D) c->ic_extieee = c->ic_ieee - 4; else c->ic_extieee = 0; } /* * Populate the freq1/freq2 fields as appropriate for VHT channels. * * This for now uses a hard-coded list of 80MHz wide channels. * * For HT20/HT40, freq1 just is the centre frequency of the 40MHz * wide channel we've already decided upon. * * For VHT80 and VHT160, there are only a small number of fixed * 80/160MHz wide channels, so we just use those. * * This is all likely very very wrong - both the regulatory code * and this code needs to ensure that all four channels are * available and valid before the VHT80 (and eight for VHT160) channel * is created. */ struct vht_chan_range { uint16_t freq_start; uint16_t freq_end; }; struct vht_chan_range vht80_chan_ranges[] = { { 5170, 5250 }, { 5250, 5330 }, { 5490, 5570 }, { 5570, 5650 }, { 5650, 5730 }, { 5735, 5815 }, { 0, 0 } }; struct vht_chan_range vht160_chan_ranges[] = { { 5170, 5330 }, { 5490, 5650 }, { 0, 0 } }; static int set_vht_extchan(struct ieee80211_channel *c) { int i; if (! IEEE80211_IS_CHAN_VHT(c)) return (0); if (IEEE80211_IS_CHAN_VHT80P80(c)) { printf("%s: TODO VHT80+80 channel (ieee=%d, flags=0x%08x)\n", __func__, c->ic_ieee, c->ic_flags); } if (IEEE80211_IS_CHAN_VHT160(c)) { for (i = 0; vht160_chan_ranges[i].freq_start != 0; i++) { if (c->ic_freq >= vht160_chan_ranges[i].freq_start && c->ic_freq < vht160_chan_ranges[i].freq_end) { int midpoint; midpoint = vht160_chan_ranges[i].freq_start + 80; c->ic_vht_ch_freq1 = ieee80211_mhz2ieee(midpoint, c->ic_flags); c->ic_vht_ch_freq2 = 0; #if 0 printf("%s: %d, freq=%d, midpoint=%d, freq1=%d, freq2=%d\n", __func__, c->ic_ieee, c->ic_freq, midpoint, c->ic_vht_ch_freq1, c->ic_vht_ch_freq2); #endif return (1); } } return (0); } if (IEEE80211_IS_CHAN_VHT80(c)) { for (i = 0; vht80_chan_ranges[i].freq_start != 0; i++) { if (c->ic_freq >= vht80_chan_ranges[i].freq_start && c->ic_freq < vht80_chan_ranges[i].freq_end) { int midpoint; midpoint = vht80_chan_ranges[i].freq_start + 40; c->ic_vht_ch_freq1 = ieee80211_mhz2ieee(midpoint, c->ic_flags); c->ic_vht_ch_freq2 = 0; #if 0 printf("%s: %d, freq=%d, midpoint=%d, freq1=%d, freq2=%d\n", __func__, c->ic_ieee, c->ic_freq, midpoint, c->ic_vht_ch_freq1, c->ic_vht_ch_freq2); #endif return (1); } } return (0); } if (IEEE80211_IS_CHAN_VHT40(c)) { if (IEEE80211_IS_CHAN_HT40U(c)) c->ic_vht_ch_freq1 = c->ic_ieee + 2; else if (IEEE80211_IS_CHAN_HT40D(c)) c->ic_vht_ch_freq1 = c->ic_ieee - 2; else return (0); return (1); } if (IEEE80211_IS_CHAN_VHT20(c)) { c->ic_vht_ch_freq1 = c->ic_ieee; return (1); } printf("%s: unknown VHT channel type (ieee=%d, flags=0x%08x)\n", __func__, c->ic_ieee, c->ic_flags); return (0); } /* * Return whether the current channel could possibly be a part of * a VHT80/VHT160 channel. * * This doesn't check that the whole range is in the allowed list * according to regulatory. */ static bool is_vht160_valid_freq(uint16_t freq) { int i; for (i = 0; vht160_chan_ranges[i].freq_start != 0; i++) { if (freq >= vht160_chan_ranges[i].freq_start && freq < vht160_chan_ranges[i].freq_end) return (true); } return (false); } static int is_vht80_valid_freq(uint16_t freq) { int i; for (i = 0; vht80_chan_ranges[i].freq_start != 0; i++) { if (freq >= vht80_chan_ranges[i].freq_start && freq < vht80_chan_ranges[i].freq_end) return (1); } return (0); } static int addchan(struct ieee80211_channel chans[], int maxchans, int *nchans, uint8_t ieee, uint16_t freq, int8_t maxregpower, uint32_t flags) { struct ieee80211_channel *c; if (*nchans >= maxchans) return (ENOBUFS); #if 0 printf("%s: %d: ieee=%d, freq=%d, flags=0x%08x\n", __func__, *nchans, ieee, freq, flags); #endif c = &chans[(*nchans)++]; c->ic_ieee = ieee; c->ic_freq = freq != 0 ? freq : ieee80211_ieee2mhz(ieee, flags); c->ic_maxregpower = maxregpower; c->ic_maxpower = 2 * maxregpower; c->ic_flags = flags; c->ic_vht_ch_freq1 = 0; c->ic_vht_ch_freq2 = 0; set_extchan(c); set_vht_extchan(c); return (0); } static int copychan_prev(struct ieee80211_channel chans[], int maxchans, int *nchans, uint32_t flags) { struct ieee80211_channel *c; KASSERT(*nchans > 0, ("channel list is empty\n")); if (*nchans >= maxchans) return (ENOBUFS); #if 0 printf("%s: %d: flags=0x%08x\n", __func__, *nchans, flags); #endif c = &chans[(*nchans)++]; c[0] = c[-1]; c->ic_flags = flags; c->ic_vht_ch_freq1 = 0; c->ic_vht_ch_freq2 = 0; set_extchan(c); set_vht_extchan(c); return (0); } /* * XXX VHT-2GHz */ static void getflags_2ghz(const uint8_t bands[], uint32_t flags[], int cbw_flags) { int nmodes; nmodes = 0; if (isset(bands, IEEE80211_MODE_11B)) flags[nmodes++] = IEEE80211_CHAN_B; if (isset(bands, IEEE80211_MODE_11G)) flags[nmodes++] = IEEE80211_CHAN_G; if (isset(bands, IEEE80211_MODE_11NG)) flags[nmodes++] = IEEE80211_CHAN_G | IEEE80211_CHAN_HT20; if (cbw_flags & NET80211_CBW_FLAG_HT40) { flags[nmodes++] = IEEE80211_CHAN_G | IEEE80211_CHAN_HT40U; flags[nmodes++] = IEEE80211_CHAN_G | IEEE80211_CHAN_HT40D; } flags[nmodes] = 0; } static void getflags_5ghz(const uint8_t bands[], uint32_t flags[], int cbw_flags) { int nmodes; /* * The addchan_list() function seems to expect the flags array to * be in channel width order, so the VHT bits are interspersed * as appropriate to maintain said order. * * It also assumes HT40U is before HT40D. */ nmodes = 0; /* 20MHz */ if (isset(bands, IEEE80211_MODE_11A)) flags[nmodes++] = IEEE80211_CHAN_A; if (isset(bands, IEEE80211_MODE_11NA)) flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT20; if (isset(bands, IEEE80211_MODE_VHT_5GHZ)) { flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT20 | IEEE80211_CHAN_VHT20; } /* 40MHz */ if (cbw_flags & NET80211_CBW_FLAG_HT40) flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U; if ((cbw_flags & NET80211_CBW_FLAG_HT40) && isset(bands, IEEE80211_MODE_VHT_5GHZ)) flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U | IEEE80211_CHAN_VHT40U; if (cbw_flags & NET80211_CBW_FLAG_HT40) flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D; if ((cbw_flags & NET80211_CBW_FLAG_HT40) && isset(bands, IEEE80211_MODE_VHT_5GHZ)) flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D | IEEE80211_CHAN_VHT40D; /* 80MHz */ if ((cbw_flags & NET80211_CBW_FLAG_VHT80) && isset(bands, IEEE80211_MODE_VHT_5GHZ)) { flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U | IEEE80211_CHAN_VHT80; flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D | IEEE80211_CHAN_VHT80; } /* VHT160 */ if ((cbw_flags & NET80211_CBW_FLAG_VHT160) && isset(bands, IEEE80211_MODE_VHT_5GHZ)) { flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U | IEEE80211_CHAN_VHT160; flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D | IEEE80211_CHAN_VHT160; } /* VHT80+80 */ if ((cbw_flags & NET80211_CBW_FLAG_VHT80P80) && isset(bands, IEEE80211_MODE_VHT_5GHZ)) { flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U | IEEE80211_CHAN_VHT80P80; flags[nmodes++] = IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D | IEEE80211_CHAN_VHT80P80; } flags[nmodes] = 0; } static void getflags(const uint8_t bands[], uint32_t flags[], int cbw_flags) { flags[0] = 0; if (isset(bands, IEEE80211_MODE_11A) || isset(bands, IEEE80211_MODE_11NA) || isset(bands, IEEE80211_MODE_VHT_5GHZ)) { if (isset(bands, IEEE80211_MODE_11B) || isset(bands, IEEE80211_MODE_11G) || isset(bands, IEEE80211_MODE_11NG) || isset(bands, IEEE80211_MODE_VHT_2GHZ)) return; getflags_5ghz(bands, flags, cbw_flags); } else getflags_2ghz(bands, flags, cbw_flags); } /* * Add one 20 MHz channel into specified channel list. * You MUST NOT mix bands when calling this. It will not add 5ghz * channels if you have any B/G/N band bit set. * The _cbw() variant does also support HT40/VHT80/160/80+80. */ int ieee80211_add_channel_cbw(struct ieee80211_channel chans[], int maxchans, int *nchans, uint8_t ieee, uint16_t freq, int8_t maxregpower, uint32_t chan_flags, const uint8_t bands[], int cbw_flags) { uint32_t flags[IEEE80211_MODE_MAX]; int i, error; getflags(bands, flags, cbw_flags); KASSERT(flags[0] != 0, ("%s: no correct mode provided\n", __func__)); error = addchan(chans, maxchans, nchans, ieee, freq, maxregpower, flags[0] | chan_flags); for (i = 1; flags[i] != 0 && error == 0; i++) { error = copychan_prev(chans, maxchans, nchans, flags[i] | chan_flags); } return (error); } int ieee80211_add_channel(struct ieee80211_channel chans[], int maxchans, int *nchans, uint8_t ieee, uint16_t freq, int8_t maxregpower, uint32_t chan_flags, const uint8_t bands[]) { return (ieee80211_add_channel_cbw(chans, maxchans, nchans, ieee, freq, maxregpower, chan_flags, bands, 0)); } static struct ieee80211_channel * findchannel(struct ieee80211_channel chans[], int nchans, uint16_t freq, uint32_t flags) { struct ieee80211_channel *c; int i; flags &= IEEE80211_CHAN_ALLTURBO; /* brute force search */ for (i = 0; i < nchans; i++) { c = &chans[i]; if (c->ic_freq == freq && (c->ic_flags & IEEE80211_CHAN_ALLTURBO) == flags) return c; } return NULL; } /* * Add 40 MHz channel pair into specified channel list. */ /* XXX VHT */ int ieee80211_add_channel_ht40(struct ieee80211_channel chans[], int maxchans, int *nchans, uint8_t ieee, int8_t maxregpower, uint32_t flags) { struct ieee80211_channel *cent, *extc; uint16_t freq; int error; freq = ieee80211_ieee2mhz(ieee, flags); /* * Each entry defines an HT40 channel pair; find the * center channel, then the extension channel above. */ flags |= IEEE80211_CHAN_HT20; cent = findchannel(chans, *nchans, freq, flags); if (cent == NULL) return (EINVAL); extc = findchannel(chans, *nchans, freq + 20, flags); if (extc == NULL) return (ENOENT); flags &= ~IEEE80211_CHAN_HT; error = addchan(chans, maxchans, nchans, cent->ic_ieee, cent->ic_freq, maxregpower, flags | IEEE80211_CHAN_HT40U); if (error != 0) return (error); error = addchan(chans, maxchans, nchans, extc->ic_ieee, extc->ic_freq, maxregpower, flags | IEEE80211_CHAN_HT40D); return (error); } /* * Fetch the center frequency for the primary channel. */ uint32_t ieee80211_get_channel_center_freq(const struct ieee80211_channel *c) { return (c->ic_freq); } /* * Fetch the center frequency for the primary BAND channel. * * For 5, 10, 20MHz channels it'll be the normally configured channel * frequency. * * For 40MHz, 80MHz, 160Mhz channels it'll the the centre of the * wide channel, not the centre of the primary channel (that's ic_freq). * * For 80+80MHz channels this will be the centre of the primary * 80MHz channel; the secondary 80MHz channel will be center_freq2(). */ uint32_t ieee80211_get_channel_center_freq1(const struct ieee80211_channel *c) { /* * VHT - use the pre-calculated centre frequency * of the given channel. */ if (IEEE80211_IS_CHAN_VHT(c)) return (ieee80211_ieee2mhz(c->ic_vht_ch_freq1, c->ic_flags)); if (IEEE80211_IS_CHAN_HT40U(c)) { return (c->ic_freq + 10); } if (IEEE80211_IS_CHAN_HT40D(c)) { return (c->ic_freq - 10); } return (c->ic_freq); } /* * For now, no 80+80 support; it will likely always return 0. */ uint32_t ieee80211_get_channel_center_freq2(const struct ieee80211_channel *c) { if (IEEE80211_IS_CHAN_VHT(c) && (c->ic_vht_ch_freq2 != 0)) return (ieee80211_ieee2mhz(c->ic_vht_ch_freq2, c->ic_flags)); return (0); } /* * Adds channels into specified channel list (ieee[] array must be sorted). * Channels are already sorted. */ static int add_chanlist(struct ieee80211_channel chans[], int maxchans, int *nchans, const uint8_t ieee[], int nieee, uint32_t flags[]) { uint16_t freq; int i, j, error; int is_vht; for (i = 0; i < nieee; i++) { freq = ieee80211_ieee2mhz(ieee[i], flags[0]); for (j = 0; flags[j] != 0; j++) { /* * Notes: * + HT40 and VHT40 channels occur together, so * we need to be careful that we actually allow that. * + VHT80, VHT160 will coexist with HT40/VHT40, so * make sure it's not skipped because of the overlap * check used for (V)HT40. */ is_vht = !! (flags[j] & IEEE80211_CHAN_VHT); /* XXX TODO FIXME VHT80P80. */ /* Test for VHT160 analogue to the VHT80 below. */ if (is_vht && flags[j] & IEEE80211_CHAN_VHT160) if (! is_vht160_valid_freq(freq)) continue; /* * Test for VHT80. * XXX This is all very broken right now. * What we /should/ do is: * * + check that the frequency is in the list of * allowed VHT80 ranges; and * + the other 3 channels in the list are actually * also available. */ if (is_vht && flags[j] & IEEE80211_CHAN_VHT80) if (! is_vht80_valid_freq(freq)) continue; /* * Test for (V)HT40. * * This is also a fall through from VHT80; as we only * allow a VHT80 channel if the VHT40 combination is * also valid. If the VHT40 form is not valid then * we certainly can't do VHT80.. */ if (flags[j] & IEEE80211_CHAN_HT40D) /* * Can't have a "lower" channel if we are the * first channel. * * Can't have a "lower" channel if it's below/ * within 20MHz of the first channel. * * Can't have a "lower" channel if the channel * below it is not 20MHz away. */ if (i == 0 || ieee[i] < ieee[0] + 4 || freq - 20 != ieee80211_ieee2mhz(ieee[i] - 4, flags[j])) continue; if (flags[j] & IEEE80211_CHAN_HT40U) /* * Can't have an "upper" channel if we are * the last channel. * * Can't have an "upper" channel be above the * last channel in the list. * * Can't have an "upper" channel if the next * channel according to the math isn't 20MHz * away. (Likely for channel 13/14.) */ if (i == nieee - 1 || ieee[i] + 4 > ieee[nieee - 1] || freq + 20 != ieee80211_ieee2mhz(ieee[i] + 4, flags[j])) continue; if (j == 0) { error = addchan(chans, maxchans, nchans, ieee[i], freq, 0, flags[j]); } else { error = copychan_prev(chans, maxchans, nchans, flags[j]); } if (error != 0) return (error); } } return (0); } int ieee80211_add_channel_list_2ghz(struct ieee80211_channel chans[], int maxchans, int *nchans, const uint8_t ieee[], int nieee, const uint8_t bands[], int cbw_flags) { uint32_t flags[IEEE80211_MODE_MAX]; /* XXX no VHT for now */ getflags_2ghz(bands, flags, cbw_flags); KASSERT(flags[0] != 0, ("%s: no correct mode provided\n", __func__)); return (add_chanlist(chans, maxchans, nchans, ieee, nieee, flags)); } int ieee80211_add_channels_default_2ghz(struct ieee80211_channel chans[], int maxchans, int *nchans, const uint8_t bands[], int cbw_flags) { const uint8_t default_chan_list[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 }; return (ieee80211_add_channel_list_2ghz(chans, maxchans, nchans, default_chan_list, nitems(default_chan_list), bands, cbw_flags)); } int ieee80211_add_channel_list_5ghz(struct ieee80211_channel chans[], int maxchans, int *nchans, const uint8_t ieee[], int nieee, const uint8_t bands[], int cbw_flags) { /* * XXX-BZ with HT and VHT there is no 1:1 mapping anymore. Review all * uses of IEEE80211_MODE_MAX and add a new #define name for array size. */ uint32_t flags[2 * IEEE80211_MODE_MAX]; getflags_5ghz(bands, flags, cbw_flags); KASSERT(flags[0] != 0, ("%s: no correct mode provided\n", __func__)); return (add_chanlist(chans, maxchans, nchans, ieee, nieee, flags)); } /* * Locate a channel given a frequency+flags. We cache * the previous lookup to optimize switching between two * channels--as happens with dynamic turbo. */ struct ieee80211_channel * ieee80211_find_channel(struct ieee80211com *ic, int freq, int flags) { struct ieee80211_channel *c; flags &= IEEE80211_CHAN_ALLTURBO; c = ic->ic_prevchan; if (c != NULL && c->ic_freq == freq && (c->ic_flags & IEEE80211_CHAN_ALLTURBO) == flags) return c; /* brute force search */ return (findchannel(ic->ic_channels, ic->ic_nchans, freq, flags)); } /* * Locate a channel given a channel number+flags. We cache * the previous lookup to optimize switching between two * channels--as happens with dynamic turbo. */ struct ieee80211_channel * ieee80211_find_channel_byieee(struct ieee80211com *ic, int ieee, int flags) { struct ieee80211_channel *c; int i; flags &= IEEE80211_CHAN_ALLTURBO; c = ic->ic_prevchan; if (c != NULL && c->ic_ieee == ieee && (c->ic_flags & IEEE80211_CHAN_ALLTURBO) == flags) return c; /* brute force search */ for (i = 0; i < ic->ic_nchans; i++) { c = &ic->ic_channels[i]; if (c->ic_ieee == ieee && (c->ic_flags & IEEE80211_CHAN_ALLTURBO) == flags) return c; } return NULL; } /* * Lookup a channel suitable for the given rx status. * * This is used to find a channel for a frame (eg beacon, probe * response) based purely on the received PHY information. * * For now it tries to do it based on R_FREQ / R_IEEE. * This is enough for 11bg and 11a (and thus 11ng/11na) * but it will not be enough for GSM, PSB channels and the * like. It also doesn't know about legacy-turbog and * legacy-turbo modes, which some offload NICs actually * support in weird ways. * * Takes the ic and rxstatus; returns the channel or NULL * if not found. * * XXX TODO: Add support for that when the need arises. */ struct ieee80211_channel * ieee80211_lookup_channel_rxstatus(struct ieee80211vap *vap, const struct ieee80211_rx_stats *rxs) { struct ieee80211com *ic = vap->iv_ic; uint32_t flags; struct ieee80211_channel *c; if (rxs == NULL) return (NULL); /* * Strictly speaking we only use freq for now, * however later on we may wish to just store * the ieee for verification. */ if ((rxs->r_flags & IEEE80211_R_FREQ) == 0) return (NULL); if ((rxs->r_flags & IEEE80211_R_IEEE) == 0) return (NULL); + if ((rxs->r_flags & IEEE80211_R_BAND) == 0) + return (NULL); /* * If the rx status contains a valid ieee/freq, then * ensure we populate the correct channel information * in rxchan before passing it up to the scan infrastructure. * Offload NICs will pass up beacons from all channels * during background scans. */ /* Determine a band */ - /* XXX should be done by the driver? */ - if (rxs->c_freq < 3000) { + switch (rxs->c_band) { + case IEEE80211_CHAN_2GHZ: flags = IEEE80211_CHAN_G; - } else { + break; + case IEEE80211_CHAN_5GHZ: flags = IEEE80211_CHAN_A; + break; + default: + if (rxs->c_freq < 3000) { + flags = IEEE80211_CHAN_G; + } else { + flags = IEEE80211_CHAN_A; + } + break; } /* Channel lookup */ c = ieee80211_find_channel(ic, rxs->c_freq, flags); IEEE80211_DPRINTF(vap, IEEE80211_MSG_INPUT, "%s: freq=%d, ieee=%d, flags=0x%08x; c=%p\n", __func__, (int) rxs->c_freq, (int) rxs->c_ieee, flags, c); return (c); } static void addmedia(struct ifmedia *media, int caps, int addsta, int mode, int mword) { #define ADD(_ic, _s, _o) \ ifmedia_add(media, \ IFM_MAKEWORD(IFM_IEEE80211, (_s), (_o), 0), 0, NULL) static const u_int mopts[IEEE80211_MODE_MAX] = { [IEEE80211_MODE_AUTO] = IFM_AUTO, [IEEE80211_MODE_11A] = IFM_IEEE80211_11A, [IEEE80211_MODE_11B] = IFM_IEEE80211_11B, [IEEE80211_MODE_11G] = IFM_IEEE80211_11G, [IEEE80211_MODE_FH] = IFM_IEEE80211_FH, [IEEE80211_MODE_TURBO_A] = IFM_IEEE80211_11A|IFM_IEEE80211_TURBO, [IEEE80211_MODE_TURBO_G] = IFM_IEEE80211_11G|IFM_IEEE80211_TURBO, [IEEE80211_MODE_STURBO_A] = IFM_IEEE80211_11A|IFM_IEEE80211_TURBO, [IEEE80211_MODE_HALF] = IFM_IEEE80211_11A, /* XXX */ [IEEE80211_MODE_QUARTER] = IFM_IEEE80211_11A, /* XXX */ [IEEE80211_MODE_11NA] = IFM_IEEE80211_11NA, [IEEE80211_MODE_11NG] = IFM_IEEE80211_11NG, [IEEE80211_MODE_VHT_2GHZ] = IFM_IEEE80211_VHT2G, [IEEE80211_MODE_VHT_5GHZ] = IFM_IEEE80211_VHT5G, }; u_int mopt; mopt = mopts[mode]; if (addsta) ADD(ic, mword, mopt); /* STA mode has no cap */ if (caps & IEEE80211_C_IBSS) ADD(media, mword, mopt | IFM_IEEE80211_ADHOC); if (caps & IEEE80211_C_HOSTAP) ADD(media, mword, mopt | IFM_IEEE80211_HOSTAP); if (caps & IEEE80211_C_AHDEMO) ADD(media, mword, mopt | IFM_IEEE80211_ADHOC | IFM_FLAG0); if (caps & IEEE80211_C_MONITOR) ADD(media, mword, mopt | IFM_IEEE80211_MONITOR); if (caps & IEEE80211_C_WDS) ADD(media, mword, mopt | IFM_IEEE80211_WDS); if (caps & IEEE80211_C_MBSS) ADD(media, mword, mopt | IFM_IEEE80211_MBSS); #undef ADD } /* * Setup the media data structures according to the channel and * rate tables. */ static int ieee80211_media_setup(struct ieee80211com *ic, struct ifmedia *media, int caps, int addsta, ifm_change_cb_t media_change, ifm_stat_cb_t media_stat) { int i, j, rate, maxrate, mword, r; enum ieee80211_phymode mode; const struct ieee80211_rateset *rs; struct ieee80211_rateset allrates; /* * Fill in media characteristics. */ ifmedia_init(media, 0, media_change, media_stat); maxrate = 0; /* * Add media for legacy operating modes. */ memset(&allrates, 0, sizeof(allrates)); for (mode = IEEE80211_MODE_AUTO; mode < IEEE80211_MODE_11NA; mode++) { if (isclr(ic->ic_modecaps, mode)) continue; addmedia(media, caps, addsta, mode, IFM_AUTO); if (mode == IEEE80211_MODE_AUTO) continue; rs = &ic->ic_sup_rates[mode]; for (i = 0; i < rs->rs_nrates; i++) { rate = rs->rs_rates[i]; mword = ieee80211_rate2media(ic, rate, mode); if (mword == 0) continue; addmedia(media, caps, addsta, mode, mword); /* * Add legacy rate to the collection of all rates. */ r = rate & IEEE80211_RATE_VAL; for (j = 0; j < allrates.rs_nrates; j++) if (allrates.rs_rates[j] == r) break; if (j == allrates.rs_nrates) { /* unique, add to the set */ allrates.rs_rates[j] = r; allrates.rs_nrates++; } rate = (rate & IEEE80211_RATE_VAL) / 2; if (rate > maxrate) maxrate = rate; } } for (i = 0; i < allrates.rs_nrates; i++) { mword = ieee80211_rate2media(ic, allrates.rs_rates[i], IEEE80211_MODE_AUTO); if (mword == 0) continue; /* NB: remove media options from mword */ addmedia(media, caps, addsta, IEEE80211_MODE_AUTO, IFM_SUBTYPE(mword)); } /* * Add HT/11n media. Note that we do not have enough * bits in the media subtype to express the MCS so we * use a "placeholder" media subtype and any fixed MCS * must be specified with a different mechanism. */ for (; mode <= IEEE80211_MODE_11NG; mode++) { if (isclr(ic->ic_modecaps, mode)) continue; addmedia(media, caps, addsta, mode, IFM_AUTO); addmedia(media, caps, addsta, mode, IFM_IEEE80211_MCS); } if (isset(ic->ic_modecaps, IEEE80211_MODE_11NA) || isset(ic->ic_modecaps, IEEE80211_MODE_11NG)) { addmedia(media, caps, addsta, IEEE80211_MODE_AUTO, IFM_IEEE80211_MCS); i = ic->ic_txstream * 8 - 1; if ((ic->ic_htcaps & IEEE80211_HTCAP_CHWIDTH40) && (ic->ic_htcaps & IEEE80211_HTCAP_SHORTGI40)) rate = ieee80211_htrates[i].ht40_rate_400ns; else if ((ic->ic_htcaps & IEEE80211_HTCAP_CHWIDTH40)) rate = ieee80211_htrates[i].ht40_rate_800ns; else if ((ic->ic_htcaps & IEEE80211_HTCAP_SHORTGI20)) rate = ieee80211_htrates[i].ht20_rate_400ns; else rate = ieee80211_htrates[i].ht20_rate_800ns; if (rate > maxrate) maxrate = rate; } /* * Add VHT media. * XXX-BZ skip "VHT_2GHZ" for now. */ for (mode = IEEE80211_MODE_VHT_5GHZ; mode <= IEEE80211_MODE_VHT_5GHZ; mode++) { if (isclr(ic->ic_modecaps, mode)) continue; addmedia(media, caps, addsta, mode, IFM_AUTO); addmedia(media, caps, addsta, mode, IFM_IEEE80211_VHT); } if (isset(ic->ic_modecaps, IEEE80211_MODE_VHT_5GHZ)) { addmedia(media, caps, addsta, IEEE80211_MODE_AUTO, IFM_IEEE80211_VHT); /* XXX TODO: VHT maxrate */ } return maxrate; } /* XXX inline or eliminate? */ const struct ieee80211_rateset * ieee80211_get_suprates(struct ieee80211com *ic, const struct ieee80211_channel *c) { /* XXX does this work for 11ng basic rates? */ return &ic->ic_sup_rates[ieee80211_chan2mode(c)]; } /* XXX inline or eliminate? */ const struct ieee80211_htrateset * ieee80211_get_suphtrates(struct ieee80211com *ic, const struct ieee80211_channel *c) { return &ic->ic_sup_htrates; } void ieee80211_announce(struct ieee80211com *ic) { int i, rate, mword; enum ieee80211_phymode mode; const struct ieee80211_rateset *rs; /* NB: skip AUTO since it has no rates */ for (mode = IEEE80211_MODE_AUTO+1; mode < IEEE80211_MODE_11NA; mode++) { if (isclr(ic->ic_modecaps, mode)) continue; ic_printf(ic, "%s rates: ", ieee80211_phymode_name[mode]); rs = &ic->ic_sup_rates[mode]; for (i = 0; i < rs->rs_nrates; i++) { mword = ieee80211_rate2media(ic, rs->rs_rates[i], mode); if (mword == 0) continue; rate = ieee80211_media2rate(mword); printf("%s%d%sMbps", (i != 0 ? " " : ""), rate / 2, ((rate & 0x1) != 0 ? ".5" : "")); } printf("\n"); } ieee80211_ht_announce(ic); ieee80211_vht_announce(ic); } void ieee80211_announce_channels(struct ieee80211com *ic) { const struct ieee80211_channel *c; char type; int i, cw; printf("Chan Freq CW RegPwr MinPwr MaxPwr\n"); for (i = 0; i < ic->ic_nchans; i++) { c = &ic->ic_channels[i]; if (IEEE80211_IS_CHAN_ST(c)) type = 'S'; else if (IEEE80211_IS_CHAN_108A(c)) type = 'T'; else if (IEEE80211_IS_CHAN_108G(c)) type = 'G'; else if (IEEE80211_IS_CHAN_HT(c)) type = 'n'; else if (IEEE80211_IS_CHAN_A(c)) type = 'a'; else if (IEEE80211_IS_CHAN_ANYG(c)) type = 'g'; else if (IEEE80211_IS_CHAN_B(c)) type = 'b'; else type = 'f'; if (IEEE80211_IS_CHAN_HT40(c) || IEEE80211_IS_CHAN_TURBO(c)) cw = 40; else if (IEEE80211_IS_CHAN_HALF(c)) cw = 10; else if (IEEE80211_IS_CHAN_QUARTER(c)) cw = 5; else cw = 20; printf("%4d %4d%c %2d%c %6d %4d.%d %4d.%d\n" , c->ic_ieee, c->ic_freq, type , cw , IEEE80211_IS_CHAN_HT40U(c) ? '+' : IEEE80211_IS_CHAN_HT40D(c) ? '-' : ' ' , c->ic_maxregpower , c->ic_minpower / 2, c->ic_minpower & 1 ? 5 : 0 , c->ic_maxpower / 2, c->ic_maxpower & 1 ? 5 : 0 ); } } static int media2mode(const struct ifmedia_entry *ime, uint32_t flags, uint16_t *mode) { switch (IFM_MODE(ime->ifm_media)) { case IFM_IEEE80211_11A: *mode = IEEE80211_MODE_11A; break; case IFM_IEEE80211_11B: *mode = IEEE80211_MODE_11B; break; case IFM_IEEE80211_11G: *mode = IEEE80211_MODE_11G; break; case IFM_IEEE80211_FH: *mode = IEEE80211_MODE_FH; break; case IFM_IEEE80211_11NA: *mode = IEEE80211_MODE_11NA; break; case IFM_IEEE80211_11NG: *mode = IEEE80211_MODE_11NG; break; case IFM_IEEE80211_VHT2G: *mode = IEEE80211_MODE_VHT_2GHZ; break; case IFM_IEEE80211_VHT5G: *mode = IEEE80211_MODE_VHT_5GHZ; break; case IFM_AUTO: *mode = IEEE80211_MODE_AUTO; break; default: return 0; } /* * Turbo mode is an ``option''. * XXX does not apply to AUTO */ if (ime->ifm_media & IFM_IEEE80211_TURBO) { if (*mode == IEEE80211_MODE_11A) { if (flags & IEEE80211_F_TURBOP) *mode = IEEE80211_MODE_TURBO_A; else *mode = IEEE80211_MODE_STURBO_A; } else if (*mode == IEEE80211_MODE_11G) *mode = IEEE80211_MODE_TURBO_G; else return 0; } /* XXX HT40 +/- */ return 1; } /* * Handle a media change request on the vap interface. */ int ieee80211_media_change(struct ifnet *ifp) { struct ieee80211vap *vap = ifp->if_softc; struct ifmedia_entry *ime = vap->iv_media.ifm_cur; uint16_t newmode; if (!media2mode(ime, vap->iv_flags, &newmode)) return EINVAL; if (vap->iv_des_mode != newmode) { vap->iv_des_mode = newmode; /* XXX kick state machine if up+running */ } return 0; } /* * Common code to calculate the media status word * from the operating mode and channel state. */ static int media_status(enum ieee80211_opmode opmode, const struct ieee80211_channel *chan) { int status; status = IFM_IEEE80211; switch (opmode) { case IEEE80211_M_STA: break; case IEEE80211_M_IBSS: status |= IFM_IEEE80211_ADHOC; break; case IEEE80211_M_HOSTAP: status |= IFM_IEEE80211_HOSTAP; break; case IEEE80211_M_MONITOR: status |= IFM_IEEE80211_MONITOR; break; case IEEE80211_M_AHDEMO: status |= IFM_IEEE80211_ADHOC | IFM_FLAG0; break; case IEEE80211_M_WDS: status |= IFM_IEEE80211_WDS; break; case IEEE80211_M_MBSS: status |= IFM_IEEE80211_MBSS; break; } if (IEEE80211_IS_CHAN_HTA(chan)) { status |= IFM_IEEE80211_11NA; } else if (IEEE80211_IS_CHAN_HTG(chan)) { status |= IFM_IEEE80211_11NG; } else if (IEEE80211_IS_CHAN_A(chan)) { status |= IFM_IEEE80211_11A; } else if (IEEE80211_IS_CHAN_B(chan)) { status |= IFM_IEEE80211_11B; } else if (IEEE80211_IS_CHAN_ANYG(chan)) { status |= IFM_IEEE80211_11G; } else if (IEEE80211_IS_CHAN_FHSS(chan)) { status |= IFM_IEEE80211_FH; } /* XXX else complain? */ if (IEEE80211_IS_CHAN_TURBO(chan)) status |= IFM_IEEE80211_TURBO; #if 0 if (IEEE80211_IS_CHAN_HT20(chan)) status |= IFM_IEEE80211_HT20; if (IEEE80211_IS_CHAN_HT40(chan)) status |= IFM_IEEE80211_HT40; #endif return status; } void ieee80211_media_status(struct ifnet *ifp, struct ifmediareq *imr) { struct ieee80211vap *vap = ifp->if_softc; struct ieee80211com *ic = vap->iv_ic; enum ieee80211_phymode mode; imr->ifm_status = IFM_AVALID; /* * NB: use the current channel's mode to lock down a xmit * rate only when running; otherwise we may have a mismatch * in which case the rate will not be convertible. */ if (vap->iv_state == IEEE80211_S_RUN || vap->iv_state == IEEE80211_S_SLEEP) { imr->ifm_status |= IFM_ACTIVE; mode = ieee80211_chan2mode(ic->ic_curchan); } else mode = IEEE80211_MODE_AUTO; imr->ifm_active = media_status(vap->iv_opmode, ic->ic_curchan); /* * Calculate a current rate if possible. */ if (vap->iv_txparms[mode].ucastrate != IEEE80211_FIXED_RATE_NONE) { /* * A fixed rate is set, report that. */ imr->ifm_active |= ieee80211_rate2media(ic, vap->iv_txparms[mode].ucastrate, mode); } else if (vap->iv_opmode == IEEE80211_M_STA) { /* * In station mode report the current transmit rate. */ imr->ifm_active |= ieee80211_rate2media(ic, vap->iv_bss->ni_txrate, mode); } else imr->ifm_active |= IFM_AUTO; if (imr->ifm_status & IFM_ACTIVE) imr->ifm_current = imr->ifm_active; } /* * Set the current phy mode and recalculate the active channel * set based on the available channels for this mode. Also * select a new default/current channel if the current one is * inappropriate for this mode. */ int ieee80211_setmode(struct ieee80211com *ic, enum ieee80211_phymode mode) { /* * Adjust basic rates in 11b/11g supported rate set. * Note that if operating on a hal/quarter rate channel * this is a noop as those rates sets are different * and used instead. */ if (mode == IEEE80211_MODE_11G || mode == IEEE80211_MODE_11B) ieee80211_setbasicrates(&ic->ic_sup_rates[mode], mode); ic->ic_curmode = mode; ieee80211_reset_erp(ic); /* reset global ERP state */ return 0; } /* * Return the phy mode for with the specified channel. */ enum ieee80211_phymode ieee80211_chan2mode(const struct ieee80211_channel *chan) { if (IEEE80211_IS_CHAN_VHT_2GHZ(chan)) return IEEE80211_MODE_VHT_2GHZ; else if (IEEE80211_IS_CHAN_VHT_5GHZ(chan)) return IEEE80211_MODE_VHT_5GHZ; else if (IEEE80211_IS_CHAN_HTA(chan)) return IEEE80211_MODE_11NA; else if (IEEE80211_IS_CHAN_HTG(chan)) return IEEE80211_MODE_11NG; else if (IEEE80211_IS_CHAN_108G(chan)) return IEEE80211_MODE_TURBO_G; else if (IEEE80211_IS_CHAN_ST(chan)) return IEEE80211_MODE_STURBO_A; else if (IEEE80211_IS_CHAN_TURBO(chan)) return IEEE80211_MODE_TURBO_A; else if (IEEE80211_IS_CHAN_HALF(chan)) return IEEE80211_MODE_HALF; else if (IEEE80211_IS_CHAN_QUARTER(chan)) return IEEE80211_MODE_QUARTER; else if (IEEE80211_IS_CHAN_A(chan)) return IEEE80211_MODE_11A; else if (IEEE80211_IS_CHAN_ANYG(chan)) return IEEE80211_MODE_11G; else if (IEEE80211_IS_CHAN_B(chan)) return IEEE80211_MODE_11B; else if (IEEE80211_IS_CHAN_FHSS(chan)) return IEEE80211_MODE_FH; /* NB: should not get here */ printf("%s: cannot map channel to mode; freq %u flags 0x%x\n", __func__, chan->ic_freq, chan->ic_flags); return IEEE80211_MODE_11B; } struct ratemedia { u_int match; /* rate + mode */ u_int media; /* if_media rate */ }; static int findmedia(const struct ratemedia rates[], int n, u_int match) { int i; for (i = 0; i < n; i++) if (rates[i].match == match) return rates[i].media; return IFM_AUTO; } /* * Convert IEEE80211 rate value to ifmedia subtype. * Rate is either a legacy rate in units of 0.5Mbps * or an MCS index. */ int ieee80211_rate2media(struct ieee80211com *ic, int rate, enum ieee80211_phymode mode) { static const struct ratemedia rates[] = { { 2 | IFM_IEEE80211_FH, IFM_IEEE80211_FH1 }, { 4 | IFM_IEEE80211_FH, IFM_IEEE80211_FH2 }, { 2 | IFM_IEEE80211_11B, IFM_IEEE80211_DS1 }, { 4 | IFM_IEEE80211_11B, IFM_IEEE80211_DS2 }, { 11 | IFM_IEEE80211_11B, IFM_IEEE80211_DS5 }, { 22 | IFM_IEEE80211_11B, IFM_IEEE80211_DS11 }, { 44 | IFM_IEEE80211_11B, IFM_IEEE80211_DS22 }, { 12 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM6 }, { 18 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM9 }, { 24 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM12 }, { 36 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM18 }, { 48 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM24 }, { 72 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM36 }, { 96 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM48 }, { 108 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM54 }, { 2 | IFM_IEEE80211_11G, IFM_IEEE80211_DS1 }, { 4 | IFM_IEEE80211_11G, IFM_IEEE80211_DS2 }, { 11 | IFM_IEEE80211_11G, IFM_IEEE80211_DS5 }, { 22 | IFM_IEEE80211_11G, IFM_IEEE80211_DS11 }, { 12 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM6 }, { 18 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM9 }, { 24 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM12 }, { 36 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM18 }, { 48 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM24 }, { 72 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM36 }, { 96 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM48 }, { 108 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM54 }, { 6 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM3 }, { 9 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM4 }, { 54 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM27 }, /* NB: OFDM72 doesn't really exist so we don't handle it */ }; static const struct ratemedia htrates[] = { { 0, IFM_IEEE80211_MCS }, { 1, IFM_IEEE80211_MCS }, { 2, IFM_IEEE80211_MCS }, { 3, IFM_IEEE80211_MCS }, { 4, IFM_IEEE80211_MCS }, { 5, IFM_IEEE80211_MCS }, { 6, IFM_IEEE80211_MCS }, { 7, IFM_IEEE80211_MCS }, { 8, IFM_IEEE80211_MCS }, { 9, IFM_IEEE80211_MCS }, { 10, IFM_IEEE80211_MCS }, { 11, IFM_IEEE80211_MCS }, { 12, IFM_IEEE80211_MCS }, { 13, IFM_IEEE80211_MCS }, { 14, IFM_IEEE80211_MCS }, { 15, IFM_IEEE80211_MCS }, { 16, IFM_IEEE80211_MCS }, { 17, IFM_IEEE80211_MCS }, { 18, IFM_IEEE80211_MCS }, { 19, IFM_IEEE80211_MCS }, { 20, IFM_IEEE80211_MCS }, { 21, IFM_IEEE80211_MCS }, { 22, IFM_IEEE80211_MCS }, { 23, IFM_IEEE80211_MCS }, { 24, IFM_IEEE80211_MCS }, { 25, IFM_IEEE80211_MCS }, { 26, IFM_IEEE80211_MCS }, { 27, IFM_IEEE80211_MCS }, { 28, IFM_IEEE80211_MCS }, { 29, IFM_IEEE80211_MCS }, { 30, IFM_IEEE80211_MCS }, { 31, IFM_IEEE80211_MCS }, { 32, IFM_IEEE80211_MCS }, { 33, IFM_IEEE80211_MCS }, { 34, IFM_IEEE80211_MCS }, { 35, IFM_IEEE80211_MCS }, { 36, IFM_IEEE80211_MCS }, { 37, IFM_IEEE80211_MCS }, { 38, IFM_IEEE80211_MCS }, { 39, IFM_IEEE80211_MCS }, { 40, IFM_IEEE80211_MCS }, { 41, IFM_IEEE80211_MCS }, { 42, IFM_IEEE80211_MCS }, { 43, IFM_IEEE80211_MCS }, { 44, IFM_IEEE80211_MCS }, { 45, IFM_IEEE80211_MCS }, { 46, IFM_IEEE80211_MCS }, { 47, IFM_IEEE80211_MCS }, { 48, IFM_IEEE80211_MCS }, { 49, IFM_IEEE80211_MCS }, { 50, IFM_IEEE80211_MCS }, { 51, IFM_IEEE80211_MCS }, { 52, IFM_IEEE80211_MCS }, { 53, IFM_IEEE80211_MCS }, { 54, IFM_IEEE80211_MCS }, { 55, IFM_IEEE80211_MCS }, { 56, IFM_IEEE80211_MCS }, { 57, IFM_IEEE80211_MCS }, { 58, IFM_IEEE80211_MCS }, { 59, IFM_IEEE80211_MCS }, { 60, IFM_IEEE80211_MCS }, { 61, IFM_IEEE80211_MCS }, { 62, IFM_IEEE80211_MCS }, { 63, IFM_IEEE80211_MCS }, { 64, IFM_IEEE80211_MCS }, { 65, IFM_IEEE80211_MCS }, { 66, IFM_IEEE80211_MCS }, { 67, IFM_IEEE80211_MCS }, { 68, IFM_IEEE80211_MCS }, { 69, IFM_IEEE80211_MCS }, { 70, IFM_IEEE80211_MCS }, { 71, IFM_IEEE80211_MCS }, { 72, IFM_IEEE80211_MCS }, { 73, IFM_IEEE80211_MCS }, { 74, IFM_IEEE80211_MCS }, { 75, IFM_IEEE80211_MCS }, { 76, IFM_IEEE80211_MCS }, }; static const struct ratemedia vhtrates[] = { { 0, IFM_IEEE80211_VHT }, { 1, IFM_IEEE80211_VHT }, { 2, IFM_IEEE80211_VHT }, { 3, IFM_IEEE80211_VHT }, { 4, IFM_IEEE80211_VHT }, { 5, IFM_IEEE80211_VHT }, { 6, IFM_IEEE80211_VHT }, { 7, IFM_IEEE80211_VHT }, { 8, IFM_IEEE80211_VHT }, /* Optional. */ { 9, IFM_IEEE80211_VHT }, /* Optional. */ #if 0 /* Some QCA and BRCM seem to support this; offspec. */ { 10, IFM_IEEE80211_VHT }, { 11, IFM_IEEE80211_VHT }, #endif }; int m; /* * Check 11ac/11n rates first for match as an MCS. */ if (mode == IEEE80211_MODE_VHT_5GHZ) { if (rate & IFM_IEEE80211_VHT) { rate &= ~IFM_IEEE80211_VHT; m = findmedia(vhtrates, nitems(vhtrates), rate); if (m != IFM_AUTO) return (m | IFM_IEEE80211_VHT); } } else if (mode == IEEE80211_MODE_11NA) { if (rate & IEEE80211_RATE_MCS) { rate &= ~IEEE80211_RATE_MCS; m = findmedia(htrates, nitems(htrates), rate); if (m != IFM_AUTO) return m | IFM_IEEE80211_11NA; } } else if (mode == IEEE80211_MODE_11NG) { /* NB: 12 is ambiguous, it will be treated as an MCS */ if (rate & IEEE80211_RATE_MCS) { rate &= ~IEEE80211_RATE_MCS; m = findmedia(htrates, nitems(htrates), rate); if (m != IFM_AUTO) return m | IFM_IEEE80211_11NG; } } rate &= IEEE80211_RATE_VAL; switch (mode) { case IEEE80211_MODE_11A: case IEEE80211_MODE_HALF: /* XXX good 'nuf */ case IEEE80211_MODE_QUARTER: case IEEE80211_MODE_11NA: case IEEE80211_MODE_TURBO_A: case IEEE80211_MODE_STURBO_A: return findmedia(rates, nitems(rates), rate | IFM_IEEE80211_11A); case IEEE80211_MODE_11B: return findmedia(rates, nitems(rates), rate | IFM_IEEE80211_11B); case IEEE80211_MODE_FH: return findmedia(rates, nitems(rates), rate | IFM_IEEE80211_FH); case IEEE80211_MODE_AUTO: /* NB: ic may be NULL for some drivers */ if (ic != NULL && ic->ic_phytype == IEEE80211_T_FH) return findmedia(rates, nitems(rates), rate | IFM_IEEE80211_FH); /* NB: hack, 11g matches both 11b+11a rates */ /* fall thru... */ case IEEE80211_MODE_11G: case IEEE80211_MODE_11NG: case IEEE80211_MODE_TURBO_G: return findmedia(rates, nitems(rates), rate | IFM_IEEE80211_11G); case IEEE80211_MODE_VHT_2GHZ: case IEEE80211_MODE_VHT_5GHZ: /* XXX TODO: need to figure out mapping for VHT rates */ return IFM_AUTO; } return IFM_AUTO; } int ieee80211_media2rate(int mword) { static const int ieeerates[] = { -1, /* IFM_AUTO */ 0, /* IFM_MANUAL */ 0, /* IFM_NONE */ 2, /* IFM_IEEE80211_FH1 */ 4, /* IFM_IEEE80211_FH2 */ 2, /* IFM_IEEE80211_DS1 */ 4, /* IFM_IEEE80211_DS2 */ 11, /* IFM_IEEE80211_DS5 */ 22, /* IFM_IEEE80211_DS11 */ 44, /* IFM_IEEE80211_DS22 */ 12, /* IFM_IEEE80211_OFDM6 */ 18, /* IFM_IEEE80211_OFDM9 */ 24, /* IFM_IEEE80211_OFDM12 */ 36, /* IFM_IEEE80211_OFDM18 */ 48, /* IFM_IEEE80211_OFDM24 */ 72, /* IFM_IEEE80211_OFDM36 */ 96, /* IFM_IEEE80211_OFDM48 */ 108, /* IFM_IEEE80211_OFDM54 */ 144, /* IFM_IEEE80211_OFDM72 */ 0, /* IFM_IEEE80211_DS354k */ 0, /* IFM_IEEE80211_DS512k */ 6, /* IFM_IEEE80211_OFDM3 */ 9, /* IFM_IEEE80211_OFDM4 */ 54, /* IFM_IEEE80211_OFDM27 */ -1, /* IFM_IEEE80211_MCS */ -1, /* IFM_IEEE80211_VHT */ }; return IFM_SUBTYPE(mword) < nitems(ieeerates) ? ieeerates[IFM_SUBTYPE(mword)] : 0; } /* * The following hash function is adapted from "Hash Functions" by Bob Jenkins * ("Algorithm Alley", Dr. Dobbs Journal, September 1997). */ #define mix(a, b, c) \ do { \ a -= b; a -= c; a ^= (c >> 13); \ b -= c; b -= a; b ^= (a << 8); \ c -= a; c -= b; c ^= (b >> 13); \ a -= b; a -= c; a ^= (c >> 12); \ b -= c; b -= a; b ^= (a << 16); \ c -= a; c -= b; c ^= (b >> 5); \ a -= b; a -= c; a ^= (c >> 3); \ b -= c; b -= a; b ^= (a << 10); \ c -= a; c -= b; c ^= (b >> 15); \ } while (/*CONSTCOND*/0) uint32_t ieee80211_mac_hash(const struct ieee80211com *ic, const uint8_t addr[IEEE80211_ADDR_LEN]) { uint32_t a = 0x9e3779b9, b = 0x9e3779b9, c = ic->ic_hash_key; b += addr[5] << 8; b += addr[4]; a += addr[3] << 24; a += addr[2] << 16; a += addr[1] << 8; a += addr[0]; mix(a, b, c); return c; } #undef mix char ieee80211_channel_type_char(const struct ieee80211_channel *c) { if (IEEE80211_IS_CHAN_ST(c)) return 'S'; if (IEEE80211_IS_CHAN_108A(c)) return 'T'; if (IEEE80211_IS_CHAN_108G(c)) return 'G'; if (IEEE80211_IS_CHAN_VHT(c)) return 'v'; if (IEEE80211_IS_CHAN_HT(c)) return 'n'; if (IEEE80211_IS_CHAN_A(c)) return 'a'; if (IEEE80211_IS_CHAN_ANYG(c)) return 'g'; if (IEEE80211_IS_CHAN_B(c)) return 'b'; return 'f'; }