diff --git a/sys/compat/linuxkpi/common/include/linux/ieee80211.h b/sys/compat/linuxkpi/common/include/linux/ieee80211.h index 865d85c6f7b7..58ebbcfe0097 100644 --- a/sys/compat/linuxkpi/common/include/linux/ieee80211.h +++ b/sys/compat/linuxkpi/common/include/linux/ieee80211.h @@ -1,1226 +1,1218 @@ /*- * Copyright (c) 2020-2024 The FreeBSD Foundation * * This software was developed by Björn Zeeb under sponsorship from * the FreeBSD Foundation. * * 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. */ #ifndef _LINUXKPI_LINUX_IEEE80211_H #define _LINUXKPI_LINUX_IEEE80211_H #include #include #include #include #include #include /* linux_80211.c */ extern int linuxkpi_debug_80211; #ifndef D80211_TODO #define D80211_TODO 0x1 #endif #define TODO(fmt, ...) if (linuxkpi_debug_80211 & D80211_TODO) \ printf("%s:%d: XXX LKPI80211 TODO " fmt "\n", __func__, __LINE__, ##__VA_ARGS__) /* 9.4.2.55 Management MIC element (CMAC-256, GMAC-128, and GMAC-256). */ struct ieee80211_mmie_16 { uint8_t element_id; uint8_t length; uint16_t key_id; uint8_t ipn[6]; uint8_t mic[16]; }; #define IEEE80211_CCMP_HDR_LEN 8 /* 802.11i .. net80211 comment */ #define IEEE80211_CCMP_PN_LEN 6 #define IEEE80211_CCMP_MIC_LEN 8 /* || 16 */ #define IEEE80211_CCMP_256_HDR_LEN 8 #define IEEE80211_CCMP_256_MIC_LEN 16 #define IEEE80211_GCMP_HDR_LEN 8 #define IEEE80211_GCMP_MIC_LEN 16 #define IEEE80211_GCMP_PN_LEN 6 #define IEEE80211_GMAC_PN_LEN 6 #define IEEE80211_MAX_PN_LEN 16 #define IEEE80211_INVAL_HW_QUEUE ((uint8_t)-1) #define IEEE80211_MAX_AMPDU_BUF_HT IEEE80211_AGGR_BAWMAX #define IEEE80211_MAX_AMPDU_BUF_HE 256 #define IEEE80211_MAX_AMPDU_BUF_EHT 1024 #define IEEE80211_MAX_FRAME_LEN 2352 #define IEEE80211_MAX_DATA_LEN (2300 + IEEE80211_CRC_LEN) #define IEEE80211_MAX_MPDU_LEN_HT_BA 4095 /* 9.3.2.1 Format of Data frames; non-VHT non-DMG STA */ #define IEEE80211_MAX_MPDU_LEN_HT_3839 3839 #define IEEE80211_MAX_MPDU_LEN_HT_7935 7935 #define IEEE80211_MAX_MPDU_LEN_VHT_3895 3895 #define IEEE80211_MAX_MPDU_LEN_VHT_7991 7991 #define IEEE80211_MAX_MPDU_LEN_VHT_11454 11454 #define IEEE80211_MAX_RTS_THRESHOLD 2346 /* net80211::IEEE80211_RTS_MAX */ #define IEEE80211_MIN_ACTION_SIZE 23 /* ? */ /* Wi-Fi Peer-to-Peer (P2P) Technical Specification */ #define IEEE80211_P2P_OPPPS_CTWINDOW_MASK 0x7f #define IEEE80211_P2P_OPPPS_ENABLE_BIT BIT(7) /* 802.11-2016, 9.2.4.5.1, Table 9-6 QoS Control Field */ #define IEEE80211_QOS_CTL_TAG1D_MASK 0x0007 #define IEEE80211_QOS_CTL_TID_MASK IEEE80211_QOS_TID #define IEEE80211_QOS_CTL_EOSP 0x0010 #define IEEE80211_QOS_CTL_A_MSDU_PRESENT 0x0080 #define IEEE80211_QOS_CTL_ACK_POLICY_MASK 0x0060 #define IEEE80211_QOS_CTL_ACK_POLICY_NOACK 0x0020 #define IEEE80211_QOS_CTL_MESH_CONTROL_PRESENT 0x0100 #define IEEE80211_RATE_SHORT_PREAMBLE BIT(0) enum ieee80211_rate_control_changed_flags { IEEE80211_RC_BW_CHANGED = BIT(0), IEEE80211_RC_NSS_CHANGED = BIT(1), IEEE80211_RC_SUPP_RATES_CHANGED = BIT(2), IEEE80211_RC_SMPS_CHANGED = BIT(3), }; #define IEEE80211_SCTL_FRAG IEEE80211_SEQ_FRAG_MASK #define IEEE80211_SCTL_SEQ IEEE80211_SEQ_SEQ_MASK #define IEEE80211_TKIP_ICV_LEN 4 #define IEEE80211_TKIP_IV_LEN 8 /* WEP + KID + EXT */ #define IEEE80211_VHT_EXT_NSS_BW_CAPABLE (1 << 13) /* assigned to tx_highest */ #define IEEE80211_VHT_MAX_AMPDU_1024K 7 /* 9.4.2.56.3 A-MPDU Parameters field, Table 9-163 */ #define IEEE80211_WEP_IV_LEN 3 /* net80211: IEEE80211_WEP_IVLEN */ #define IEEE80211_WEP_ICV_LEN 4 #define WLAN_AUTH_OPEN __LINE__ /* TODO FIXME brcmfmac */ #define WLAN_CAPABILITY_IBSS __LINE__ /* TODO FIXME no longer used? */ #define WLAN_CAPABILITY_SHORT_PREAMBLE __LINE__ /* TODO FIXME brcmfmac */ #define WLAN_CAPABILITY_SHORT_SLOT_TIME __LINE__ /* TODO FIXME brcmfmac */ enum wlan_ht_cap_sm_ps { WLAN_HT_CAP_SM_PS_STATIC = 0, WLAN_HT_CAP_SM_PS_DYNAMIC, WLAN_HT_CAP_SM_PS_INVALID, WLAN_HT_CAP_SM_PS_DISABLED, }; #define WLAN_MAX_KEY_LEN 32 #define WLAN_PMKID_LEN 16 #define WLAN_PMK_LEN_SUITE_B_192 48 enum ieee80211_key_len { WLAN_KEY_LEN_WEP40 = 5, WLAN_KEY_LEN_WEP104 = 13, WLAN_KEY_LEN_TKIP = 32, WLAN_KEY_LEN_CCMP = 16, WLAN_KEY_LEN_GCMP = 16, WLAN_KEY_LEN_AES_CMAC = 16, WLAN_KEY_LEN_GCMP_256 = 32, WLAN_KEY_LEN_BIP_CMAC_256 = 32, WLAN_KEY_LEN_BIP_GMAC_128 = 16, WLAN_KEY_LEN_BIP_GMAC_256 = 32, }; /* 802.11-2020, 9.4.2.55.3, Table 9-185 Subfields of the A-MPDU Parameters field */ enum ieee80211_min_mpdu_start_spacing { IEEE80211_HT_MPDU_DENSITY_NONE = 0, #if 0 IEEE80211_HT_MPDU_DENSITY_XXX = 1, /* 1/4 us */ #endif IEEE80211_HT_MPDU_DENSITY_0_5 = 2, /* 1/2 us */ IEEE80211_HT_MPDU_DENSITY_1 = 3, /* 1 us */ IEEE80211_HT_MPDU_DENSITY_2 = 4, /* 2 us */ IEEE80211_HT_MPDU_DENSITY_4 = 5, /* 4us */ IEEE80211_HT_MPDU_DENSITY_8 = 6, /* 8us */ IEEE80211_HT_MPDU_DENSITY_16 = 7, /* 16us */ }; /* 9.4.2.57, Table 9-168, HT Operation element fields and subfields */ #define IEEE80211_HT_STBC_PARAM_DUAL_CTS_PROT 0x0080 /* B24.. */ #define IEEE80211_FCTL_FTYPE IEEE80211_FC0_TYPE_MASK #define IEEE80211_FCTL_STYPE IEEE80211_FC0_SUBTYPE_MASK #define IEEE80211_FCTL_ORDER (IEEE80211_FC1_ORDER << 8) #define IEEE80211_FCTL_PROTECTED (IEEE80211_FC1_PROTECTED << 8) #define IEEE80211_FCTL_FROMDS (IEEE80211_FC1_DIR_FROMDS << 8) #define IEEE80211_FCTL_TODS (IEEE80211_FC1_DIR_TODS << 8) #define IEEE80211_FCTL_MOREFRAGS (IEEE80211_FC1_MORE_FRAG << 8) #define IEEE80211_FCTL_PM (IEEE80211_FC1_PWR_MGT << 8) #define IEEE80211_FTYPE_MGMT IEEE80211_FC0_TYPE_MGT #define IEEE80211_FTYPE_CTL IEEE80211_FC0_TYPE_CTL #define IEEE80211_FTYPE_DATA IEEE80211_FC0_TYPE_DATA #define IEEE80211_STYPE_ASSOC_REQ IEEE80211_FC0_SUBTYPE_ASSOC_REQ #define IEEE80211_STYPE_REASSOC_REQ IEEE80211_FC0_SUBTYPE_REASSOC_REQ #define IEEE80211_STYPE_PROBE_REQ IEEE80211_FC0_SUBTYPE_PROBE_REQ #define IEEE80211_STYPE_DISASSOC IEEE80211_FC0_SUBTYPE_DISASSOC #define IEEE80211_STYPE_AUTH IEEE80211_FC0_SUBTYPE_AUTH #define IEEE80211_STYPE_DEAUTH IEEE80211_FC0_SUBTYPE_DEAUTH #define IEEE80211_STYPE_CTS IEEE80211_FC0_SUBTYPE_CTS #define IEEE80211_STYPE_RTS IEEE80211_FC0_SUBTYPE_RTS #define IEEE80211_STYPE_ACTION IEEE80211_FC0_SUBTYPE_ACTION #define IEEE80211_STYPE_DATA IEEE80211_FC0_SUBTYPE_DATA #define IEEE80211_STYPE_QOS_DATA IEEE80211_FC0_SUBTYPE_QOS_DATA #define IEEE80211_STYPE_QOS_NULLFUNC IEEE80211_FC0_SUBTYPE_QOS_NULL #define IEEE80211_STYPE_QOS_CFACK 0xd0 /* XXX-BZ reserved? */ #define IEEE80211_NUM_ACS 4 /* net8021::WME_NUM_AC */ #define IEEE80211_MAX_SSID_LEN 32 /* 9.4.2.2 SSID element, net80211: IEEE80211_NWID_LEN */ /* Figure 9-27, BAR Control field */ #define IEEE80211_BAR_CTRL_TID_INFO_MASK 0xf000 #define IEEE80211_BAR_CTRL_TID_INFO_SHIFT 12 #define IEEE80211_PPE_THRES_INFO_PPET_SIZE 1 /* TODO FIXME ax? */ #define IEEE80211_PPE_THRES_NSS_MASK 2 /* TODO FIXME ax? */ #define IEEE80211_PPE_THRES_RU_INDEX_BITMASK_POS 3 /* TODO FIXME ax? */ #define IEEE80211_PPE_THRES_RU_INDEX_BITMASK_MASK 8 /* TODO FIXME ax? */ #define IEEE80211_HE_PPE_THRES_INFO_HEADER_SIZE 16 /* TODO FIXME ax? */ /* 802.11-2012, Table 8-130-HT Operation element fields and subfields, HT Protection */ #define IEEE80211_HT_OP_MODE_PROTECTION IEEE80211_HTINFO_OPMODE /* Mask. */ #define IEEE80211_HT_OP_MODE_PROTECTION_NONE IEEE80211_HTINFO_OPMODE_PURE /* No protection */ #define IEEE80211_HT_OP_MODE_PROTECTION_NONMEMBER IEEE80211_HTINFO_OPMODE_PROTOPT /* Nonmember protection */ #define IEEE80211_HT_OP_MODE_PROTECTION_20MHZ IEEE80211_HTINFO_OPMODE_HT20PR /* 20 MHz protection */ #define IEEE80211_HT_OP_MODE_PROTECTION_NONHT_MIXED IEEE80211_HTINFO_OPMODE_MIXED /* Non-HT mixed */ /* 9.6.13.1, Table 9-342 TDLS Action field values. */ enum ieee80211_tdls_action_code { WLAN_TDLS_SETUP_REQUEST = 0, WLAN_TDLS_SETUP_RESPONSE = 1, WLAN_TDLS_SETUP_CONFIRM = 2, WLAN_TDLS_TEARDOWN = 3, WLAN_TDLS_PEER_TRAFFIC_INDICATION = 4, WLAN_TDLS_CHANNEL_SWITCH_REQUEST = 5, WLAN_TDLS_CHANNEL_SWITCH_RESPONSE = 6, WLAN_TDLS_PEER_PSM_REQUEST = 7, WLAN_TDLS_PEER_PSM_RESPONSE = 8, WLAN_TDLS_PEER_TRAFFIC_RESPONSE = 9, WLAN_TDLS_DISCOVERY_REQUEST = 10, /* 11-255 reserved */ }; /* 802.11-2020 9.4.2.26, Table 9-153. Extended Capabilities field. */ /* This is split up into octets CAPA1 = octet 1, ... */ #define WLAN_EXT_CAPA1_EXT_CHANNEL_SWITCHING BIT(2 % 8) #define WLAN_EXT_CAPA3_MULTI_BSSID_SUPPORT BIT(22 % 8) #define WLAN_EXT_CAPA3_TIMING_MEASUREMENT_SUPPORT BIT(23 % 8) #define WLAN_EXT_CAPA8_OPMODE_NOTIF BIT(62 % 8) #define WLAN_EXT_CAPA8_MAX_MSDU_IN_AMSDU_LSB BIT(63 % 8) #define WLAN_EXT_CAPA9_MAX_MSDU_IN_AMSDU_MSB BIT(64 % 8) #define WLAN_EXT_CAPA10_TWT_REQUESTER_SUPPORT BIT(77 % 8) #define WLAN_EXT_CAPA10_TWT_RESPONDER_SUPPORT BIT(78 % 8) #define WLAN_EXT_CAPA10_OBSS_NARROW_BW_RU_TOLERANCE_SUPPORT BIT(79 % 8) #define WLAN_EXT_CAPA11_EMA_SUPPORT 0x00 /* XXX TODO FIXME */ /* iwlwifi/mvm/utils:: for (ac = IEEE80211_AC_VO; ac <= IEEE80211_AC_VI; ac++) */ /* Would be so much easier if we'd define constants to the same. */ enum ieee80211_ac_numbers { IEEE80211_AC_VO = 0, /* net80211::WME_AC_VO */ IEEE80211_AC_VI = 1, /* net80211::WME_AC_VI */ IEEE80211_AC_BE = 2, /* net80211::WME_AC_BE */ IEEE80211_AC_BK = 3, /* net80211::WME_AC_BK */ }; #define IEEE80211_MAX_QUEUES 16 /* Assume IEEE80211_NUM_TIDS for the moment. */ #define IEEE80211_WMM_IE_STA_QOSINFO_AC_VO 1 #define IEEE80211_WMM_IE_STA_QOSINFO_AC_VI 2 #define IEEE80211_WMM_IE_STA_QOSINFO_AC_BK 4 #define IEEE80211_WMM_IE_STA_QOSINFO_AC_BE 8 #define IEEE80211_WMM_IE_STA_QOSINFO_SP_ALL 0xf /* Define the LinuxKPI names directly to the net80211 ones. */ #define IEEE80211_HT_CAP_LDPC_CODING IEEE80211_HTCAP_LDPC #define IEEE80211_HT_CAP_SUP_WIDTH_20_40 IEEE80211_HTCAP_CHWIDTH40 #define IEEE80211_HT_CAP_SM_PS IEEE80211_HTCAP_SMPS #define IEEE80211_HT_CAP_SM_PS_SHIFT 2 #define IEEE80211_HT_CAP_GRN_FLD IEEE80211_HTCAP_GREENFIELD #define IEEE80211_HT_CAP_SGI_20 IEEE80211_HTCAP_SHORTGI20 #define IEEE80211_HT_CAP_SGI_40 IEEE80211_HTCAP_SHORTGI40 #define IEEE80211_HT_CAP_TX_STBC IEEE80211_HTCAP_TXSTBC #define IEEE80211_HT_CAP_RX_STBC IEEE80211_HTCAP_RXSTBC #define IEEE80211_HT_CAP_RX_STBC_SHIFT IEEE80211_HTCAP_RXSTBC_S #define IEEE80211_HT_CAP_MAX_AMSDU IEEE80211_HTCAP_MAXAMSDU #define IEEE80211_HT_CAP_DSSSCCK40 IEEE80211_HTCAP_DSSSCCK40 #define IEEE80211_HT_CAP_LSIG_TXOP_PROT IEEE80211_HTCAP_LSIGTXOPPROT #define IEEE80211_HT_MCS_TX_DEFINED 0x0001 #define IEEE80211_HT_MCS_TX_RX_DIFF 0x0002 #define IEEE80211_HT_MCS_TX_MAX_STREAMS_SHIFT 2 #define IEEE80211_HT_MCS_TX_MAX_STREAMS_MASK 0x0c #define IEEE80211_HT_MCS_RX_HIGHEST_MASK 0x3ff #define IEEE80211_HT_MCS_MASK_LEN 10 #define IEEE80211_MLD_MAX_NUM_LINKS 15 #define IEEE80211_MLD_CAP_OP_TID_TO_LINK_MAP_NEG_SUPP 0x0060 #define IEEE80211_MLD_CAP_OP_TID_TO_LINK_MAP_NEG_SUPP_SAME 1 struct ieee80211_mcs_info { uint8_t rx_mask[IEEE80211_HT_MCS_MASK_LEN]; uint16_t rx_highest; uint8_t tx_params; uint8_t __reserved[3]; }; /* 802.11-2020, 9.4.2.55.1 HT Capabilities element structure */ struct ieee80211_ht_cap { uint16_t cap_info; uint8_t ampdu_params_info; struct ieee80211_mcs_info mcs; uint16_t extended_ht_cap_info; uint32_t tx_BF_cap_info; uint8_t antenna_selection_info; }; #define IEEE80211_HT_MAX_AMPDU_FACTOR 13 #define IEEE80211_HE_HT_MAX_AMPDU_FACTOR 16 #define IEEE80211_HE_VHT_MAX_AMPDU_FACTOR 20 #define IEEE80211_HE_6GHZ_MAX_AMPDU_FACTOR 13 enum ieee80211_ht_max_ampdu_len { IEEE80211_HT_MAX_AMPDU_64K }; enum ieee80211_ampdu_mlme_action { IEEE80211_AMPDU_RX_START, IEEE80211_AMPDU_RX_STOP, IEEE80211_AMPDU_TX_OPERATIONAL, IEEE80211_AMPDU_TX_START, IEEE80211_AMPDU_TX_STOP_CONT, IEEE80211_AMPDU_TX_STOP_FLUSH, IEEE80211_AMPDU_TX_STOP_FLUSH_CONT }; #define IEEE80211_AMPDU_TX_START_IMMEDIATE 1 #define IEEE80211_AMPDU_TX_START_DELAY_ADDBA 2 enum ieee80211_chanctx_switch_mode { CHANCTX_SWMODE_REASSIGN_VIF, CHANCTX_SWMODE_SWAP_CONTEXTS, }; enum ieee80211_chanctx_change_flags { IEEE80211_CHANCTX_CHANGE_MIN_WIDTH = BIT(0), IEEE80211_CHANCTX_CHANGE_RADAR = BIT(1), IEEE80211_CHANCTX_CHANGE_RX_CHAINS = BIT(2), IEEE80211_CHANCTX_CHANGE_WIDTH = BIT(3), IEEE80211_CHANCTX_CHANGE_CHANNEL = BIT(4), IEEE80211_CHANCTX_CHANGE_PUNCTURING = BIT(5), }; enum ieee80211_frame_release_type { IEEE80211_FRAME_RELEASE_PSPOLL = 1, IEEE80211_FRAME_RELEASE_UAPSD = 2, }; enum ieee80211_p2p_attr_ids { IEEE80211_P2P_ATTR_DEVICE_ID, IEEE80211_P2P_ATTR_DEVICE_INFO, IEEE80211_P2P_ATTR_GROUP_ID, IEEE80211_P2P_ATTR_LISTEN_CHANNEL, IEEE80211_P2P_ATTR_ABSENCE_NOTICE, }; enum ieee80211_reconfig_type { IEEE80211_RECONFIG_TYPE_RESTART, IEEE80211_RECONFIG_TYPE_SUSPEND, }; enum ieee80211_roc_type { IEEE80211_ROC_TYPE_MGMT_TX, IEEE80211_ROC_TYPE_NORMAL, }; enum ieee80211_smps_mode { IEEE80211_SMPS_OFF, IEEE80211_SMPS_STATIC, IEEE80211_SMPS_DYNAMIC, IEEE80211_SMPS_AUTOMATIC, IEEE80211_SMPS_NUM_MODES, }; /* net80211::IEEE80211_S_* different but represents the state machine. */ /* Note: order here is important! */ enum ieee80211_sta_state { IEEE80211_STA_NOTEXIST = 0, IEEE80211_STA_NONE = 1, IEEE80211_STA_AUTH = 2, IEEE80211_STA_ASSOC = 3, IEEE80211_STA_AUTHORIZED = 4, /* 802.1x */ }; -enum ieee80211_sta_rx_bw { - IEEE80211_STA_RX_BW_20, - IEEE80211_STA_RX_BW_40, - IEEE80211_STA_RX_BW_80, - IEEE80211_STA_RX_BW_160, - IEEE80211_STA_RX_BW_320, -}; - enum ieee80211_tx_info_flags { /* XXX TODO .. right shift numbers - not sure where that came from? */ IEEE80211_TX_CTL_AMPDU = BIT(0), IEEE80211_TX_CTL_ASSIGN_SEQ = BIT(1), IEEE80211_TX_CTL_NO_ACK = BIT(2), IEEE80211_TX_CTL_SEND_AFTER_DTIM = BIT(3), IEEE80211_TX_CTL_TX_OFFCHAN = BIT(4), IEEE80211_TX_CTL_REQ_TX_STATUS = BIT(5), IEEE80211_TX_STATUS_EOSP = BIT(6), IEEE80211_TX_STAT_ACK = BIT(7), IEEE80211_TX_STAT_AMPDU = BIT(8), IEEE80211_TX_STAT_AMPDU_NO_BACK = BIT(9), IEEE80211_TX_STAT_TX_FILTERED = BIT(10), IEEE80211_TX_STAT_NOACK_TRANSMITTED = BIT(11), IEEE80211_TX_CTL_FIRST_FRAGMENT = BIT(12), IEEE80211_TX_INTFL_DONT_ENCRYPT = BIT(13), IEEE80211_TX_CTL_NO_CCK_RATE = BIT(14), IEEE80211_TX_CTL_INJECTED = BIT(15), IEEE80211_TX_CTL_HW_80211_ENCAP = BIT(16), IEEE80211_TX_CTL_USE_MINRATE = BIT(17), IEEE80211_TX_CTL_RATE_CTRL_PROBE = BIT(18), IEEE80211_TX_CTL_LDPC = BIT(19), IEEE80211_TX_CTL_STBC = BIT(20), }; enum ieee80211_tx_status_flags { IEEE80211_TX_STATUS_ACK_SIGNAL_VALID = BIT(0), }; enum ieee80211_tx_control_flags { /* XXX TODO .. right shift numbers */ IEEE80211_TX_CTRL_PORT_CTRL_PROTO = BIT(0), IEEE80211_TX_CTRL_PS_RESPONSE = BIT(1), IEEE80211_TX_CTRL_RATE_INJECT = BIT(2), IEEE80211_TX_CTRL_MLO_LINK = 0xF0000000, /* This is IEEE80211_LINK_UNSPECIFIED on the high bits. */ }; enum ieee80211_tx_rate_flags { /* XXX TODO .. right shift numbers */ IEEE80211_TX_RC_40_MHZ_WIDTH = BIT(0), IEEE80211_TX_RC_80_MHZ_WIDTH = BIT(1), IEEE80211_TX_RC_160_MHZ_WIDTH = BIT(2), IEEE80211_TX_RC_GREEN_FIELD = BIT(3), IEEE80211_TX_RC_MCS = BIT(4), IEEE80211_TX_RC_SHORT_GI = BIT(5), IEEE80211_TX_RC_VHT_MCS = BIT(6), IEEE80211_TX_RC_USE_SHORT_PREAMBLE = BIT(7), }; #define IEEE80211_RNR_TBTT_PARAMS_PSD_RESERVED -128 #define IEEE80211_HT_CTL_LEN 4 struct ieee80211_hdr { /* net80211::ieee80211_frame_addr4 */ __le16 frame_control; __le16 duration_id; uint8_t addr1[ETH_ALEN]; uint8_t addr2[ETH_ALEN]; uint8_t addr3[ETH_ALEN]; __le16 seq_ctrl; uint8_t addr4[ETH_ALEN]; }; struct ieee80211_hdr_3addr { /* net80211::ieee80211_frame */ __le16 frame_control; __le16 duration_id; uint8_t addr1[ETH_ALEN]; uint8_t addr2[ETH_ALEN]; uint8_t addr3[ETH_ALEN]; __le16 seq_ctrl; }; struct ieee80211_qos_hdr { /* net80211:ieee80211_qosframe */ __le16 frame_control; __le16 duration_id; uint8_t addr1[ETH_ALEN]; uint8_t addr2[ETH_ALEN]; uint8_t addr3[ETH_ALEN]; __le16 seq_ctrl; __le16 qos_ctrl; }; struct ieee80211_vendor_ie { }; /* 802.11-2020, Table 9-359-Block Ack Action field values */ enum ieee80211_back { WLAN_ACTION_ADDBA_REQ = 0, }; enum ieee80211_sa_query { WLAN_ACTION_SA_QUERY_RESPONSE = 1, }; /* 802.11-2020, Table 9-51-Category values */ enum ieee80211_category { WLAN_CATEGORY_BACK = 3, WLAN_CATEGORY_SA_QUERY = 8, /* net80211::IEEE80211_ACTION_CAT_SA_QUERY */ }; /* 80211-2020 9.3.3.2 Format of Management frames */ struct ieee80211_mgmt { __le16 frame_control; __le16 duration_id; uint8_t da[ETH_ALEN]; uint8_t sa[ETH_ALEN]; uint8_t bssid[ETH_ALEN]; __le16 seq_ctrl; union { /* 9.3.3.3 Beacon frame format */ struct { uint64_t timestamp; uint16_t beacon_int; uint16_t capab_info; uint8_t variable[0]; } beacon; /* 9.3.3.10 Probe Request frame format */ struct { uint8_t variable[0]; } probe_req; /* 9.3.3.11 Probe Response frame format */ struct { uint64_t timestamp; uint16_t beacon_int; uint16_t capab_info; uint8_t variable[0]; } probe_resp; /* 9.3.3.14 Action frame format */ struct { /* 9.4.1.11 Action field */ uint8_t category; /* 9.6.8 Public Action details */ union { /* 9.6.2.5 TPC Report frame format */ struct { uint8_t spec_mgmt; uint8_t dialog_token; /* uint32_t tpc_rep_elem:: */ uint8_t tpc_elem_id; uint8_t tpc_elem_length; uint8_t tpc_elem_tx_power; uint8_t tpc_elem_link_margin; } tpc_report; /* 9.6.8.33 Fine Timing Measurement frame format */ struct { uint8_t dialog_token; uint8_t follow_up; uint8_t tod[6]; uint8_t toa[6]; uint16_t tod_error; uint16_t toa_error; uint8_t variable[0]; } ftm; /* 802.11-2016, 9.6.5.2 ADDBA Request frame format */ struct { uint8_t action_code; uint8_t dialog_token; uint16_t capab; uint16_t timeout; uint16_t start_seq_num; /* Optional follows... */ uint8_t variable[0]; } addba_req; /* XXX */ struct { uint8_t dialog_token; } wnm_timing_msr; } u; } action; DECLARE_FLEX_ARRAY(uint8_t, body); } u; }; struct ieee80211_cts { /* net80211::ieee80211_frame_cts */ __le16 frame_control; __le16 duration; uint8_t ra[ETH_ALEN]; } __packed; struct ieee80211_rts { /* net80211::ieee80211_frame_rts */ __le16 frame_control; __le16 duration; uint8_t ra[ETH_ALEN]; uint8_t ta[ETH_ALEN]; } __packed; #define MHZ_TO_KHZ(_f) ((_f) * 1000) #define DBI_TO_MBI(_g) ((_g) * 100) #define MBI_TO_DBI(_x) ((_x) / 100) #define DBM_TO_MBM(_g) ((_g) * 100) #define MBM_TO_DBM(_x) ((_x) / 100) #define IEEE80211_SEQ_TO_SN(_seqn) (((_seqn) & IEEE80211_SEQ_SEQ_MASK) >> \ IEEE80211_SEQ_SEQ_SHIFT) #define IEEE80211_SN_TO_SEQ(_sn) (((_sn) << IEEE80211_SEQ_SEQ_SHIFT) & \ IEEE80211_SEQ_SEQ_MASK) /* Time unit (TU) to .. See net80211: IEEE80211_DUR_TU */ #define TU_TO_JIFFIES(_tu) (usecs_to_jiffies(_tu) * 1024) #define TU_TO_EXP_TIME(_tu) (jiffies + TU_TO_JIFFIES(_tu)) /* 9.4.2.21.1, Table 9-82. */ #define IEEE80211_SPCT_MSR_RPRT_TYPE_LCI 8 #define IEEE80211_SPCT_MSR_RPRT_TYPE_CIVIC 11 /* 9.4.2.1, Table 9-77. Element IDs. */ enum ieee80211_eid { WLAN_EID_SSID = 0, WLAN_EID_SUPP_RATES = 1, WLAN_EID_DS_PARAMS = 3, WLAN_EID_TIM = 5, WLAN_EID_COUNTRY = 7, /* IEEE80211_ELEMID_COUNTRY */ WLAN_EID_REQUEST = 10, WLAN_EID_QBSS_LOAD = 11, /* IEEE80211_ELEMID_BSSLOAD */ WLAN_EID_CHANNEL_SWITCH = 37, WLAN_EID_MEASURE_REPORT = 39, WLAN_EID_HT_CAPABILITY = 45, /* IEEE80211_ELEMID_HTCAP */ WLAN_EID_RSN = 48, /* IEEE80211_ELEMID_RSN */ WLAN_EID_EXT_SUPP_RATES = 50, WLAN_EID_EXT_NON_INHERITANCE = 56, WLAN_EID_EXT_CHANSWITCH_ANN = 60, WLAN_EID_MULTIPLE_BSSID = 71, /* IEEE80211_ELEMID_MULTIBSSID */ WLAN_EID_MULTI_BSSID_IDX = 85, WLAN_EID_EXT_CAPABILITY = 127, WLAN_EID_VHT_CAPABILITY = 191, /* IEEE80211_ELEMID_VHT_CAP */ WLAN_EID_S1G_TWT = 216, WLAN_EID_VENDOR_SPECIFIC = 221, /* IEEE80211_ELEMID_VENDOR */ }; enum ieee80211_eid_ext { WLAN_EID_EXT_HE_CAPABILITY = 35, }; #define for_each_element(_elem, _data, _len) \ for (_elem = (const struct element *)(_data); \ (((const uint8_t *)(_data) + (_len) - (const uint8_t *)_elem) >= sizeof(*_elem)) && \ (((const uint8_t *)(_data) + (_len) - (const uint8_t *)_elem) >= (sizeof(*_elem) + _elem->datalen)); \ _elem = (const struct element *)(_elem->data + _elem->datalen)) #define for_each_element_id(_elem, _eid, _data, _len) \ for_each_element(_elem, _data, _len) \ if (_elem->id == (_eid)) /* 9.4.1.7, Table 9-45. Reason codes. */ enum ieee80211_reason_code { /* reserved = 0, */ WLAN_REASON_UNSPECIFIED = 1, WLAN_REASON_DEAUTH_LEAVING = 3, /* LEAVING_NETWORK_DEAUTH */ WLAN_REASON_TDLS_TEARDOWN_UNREACHABLE = 25, WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED = 26, }; /* 9.4.1.9, Table 9-46. Status codes. */ enum ieee80211_status_code { WLAN_STATUS_SUCCESS = 0, WLAN_STATUS_AUTH_TIMEOUT = 16, /* REJECTED_SEQUENCE_TIMEOUT */ }; /* 9.3.1.22 Trigger frame format; 80211ax-2021 */ struct ieee80211_trigger { __le16 frame_control; __le16 duration_id; uint8_t ra[ETH_ALEN]; uint8_t ta[ETH_ALEN]; __le64 common_info; /* 8+ really */ uint8_t variable[]; }; /* Table 9-29c-Trigger Type subfield encoding */ enum { IEEE80211_TRIGGER_TYPE_BASIC = 0x0, IEEE80211_TRIGGER_TYPE_MU_BAR = 0x2, #if 0 /* Not seen yet. */ BFRP = 0x1, MU-RTS = 0x3, BSRP = 0x4, GCR MU-BAR = 0x5, BQRP = 0x6, NFRP = 0x7, /* 0x8..0xf reserved */ #endif IEEE80211_TRIGGER_TYPE_MASK = 0xf }; #define IEEE80211_TRIGGER_ULBW_MASK 0xc0000 #define IEEE80211_TRIGGER_ULBW_20MHZ 0x0 #define IEEE80211_TRIGGER_ULBW_40MHZ 0x1 #define IEEE80211_TRIGGER_ULBW_80MHZ 0x2 #define IEEE80211_TRIGGER_ULBW_160_80P80MHZ 0x3 /* 802.11-2020, Figure 9-687-Control field format; 802.11ax-2021 */ #define IEEE80211_TWT_CONTROL_NEG_TYPE_BROADCAST BIT(3) #define IEEE80211_TWT_CONTROL_RX_DISABLED BIT(4) #define IEEE80211_TWT_CONTROL_WAKE_DUR_UNIT BIT(5) /* 802.11-2020, Figure 9-688-Request Type field format; 802.11ax-2021 */ #define IEEE80211_TWT_REQTYPE_SETUP_CMD (BIT(1) | BIT(2) | BIT(3)) #define IEEE80211_TWT_REQTYPE_TRIGGER BIT(4) #define IEEE80211_TWT_REQTYPE_IMPLICIT BIT(5) #define IEEE80211_TWT_REQTYPE_FLOWTYPE BIT(6) #define IEEE80211_TWT_REQTYPE_FLOWID (BIT(7) | BIT(8) | BIT(9)) #define IEEE80211_TWT_REQTYPE_WAKE_INT_EXP (BIT(10) | BIT(11) | BIT(12) | BIT(13) | BIT(14)) #define IEEE80211_TWT_REQTYPE_PROTECTION BIT(15) struct ieee80211_twt_params { int mantissa, min_twt_dur, twt; uint16_t req_type; }; struct ieee80211_twt_setup { int control; struct ieee80211_twt_params *params; }; /* 802.11-2020, Table 9-297-TWT Setup Command field values */ enum ieee80211_twt_setup_cmd { TWT_SETUP_CMD_REQUEST = 0, TWT_SETUP_CMD_SUGGEST = 1, /* DEMAND = 2, */ /* GROUPING = 3, */ TWT_SETUP_CMD_ACCEPT = 4, /* ALTERNATE = 5 */ TWT_SETUP_CMD_DICTATE = 6, TWT_SETUP_CMD_REJECT = 7, }; struct ieee80211_bssid_index { int bssid_index; }; enum ieee80211_ap_reg_power { IEEE80211_REG_UNSET_AP, IEEE80211_REG_LPI_AP, IEEE80211_REG_SP_AP, IEEE80211_REG_VLP_AP, }; /* * 802.11ax-2021, Table 9-277-Meaning of Maximum Transmit Power Count subfield * if Maximum Transmit Power Interpretation subfield is 1 or 3 */ #define IEEE80211_MAX_NUM_PWR_LEVEL 8 /* * 802.11ax-2021, Table 9-275a-Maximum Transmit Power Interpretation subfield * encoding (4) * Table E-12-Regulatory Info subfield encoding in the * United States (2) */ #define IEEE80211_TPE_MAX_IE_NUM 8 /* 802.11ax-2021, 9.4.2.161 Transmit Power Envelope element */ struct ieee80211_tx_pwr_env { uint8_t tx_power_info; uint8_t tx_power[IEEE80211_MAX_NUM_PWR_LEVEL]; }; /* 802.11ax-2021, Figure 9-617-Transmit Power Information field format */ /* These are field masks (3bit/3bit/2bit). */ #define IEEE80211_TX_PWR_ENV_INFO_COUNT 0x07 #define IEEE80211_TX_PWR_ENV_INFO_INTERPRET 0x38 #define IEEE80211_TX_PWR_ENV_INFO_CATEGORY 0xc0 /* * 802.11ax-2021, Table 9-275a-Maximum Transmit Power Interpretation subfield * encoding */ enum ieee80211_tx_pwr_interpretation_subfield_enc { IEEE80211_TPE_LOCAL_EIRP, IEEE80211_TPE_LOCAL_EIRP_PSD, IEEE80211_TPE_REG_CLIENT_EIRP, IEEE80211_TPE_REG_CLIENT_EIRP_PSD, }; enum ieee80211_tx_pwr_category_6ghz { IEEE80211_TPE_CAT_6GHZ_DEFAULT, }; /* 802.11-2020, 9.4.2.27 BSS Load element */ struct ieee80211_bss_load_elem { uint16_t sta_count; uint8_t channel_util; uint16_t avail_adm_capa; }; /* net80211: IEEE80211_IS_CTL() */ static __inline bool ieee80211_is_ctl(__le16 fc) { __le16 v; fc &= htole16(IEEE80211_FC0_TYPE_MASK); v = htole16(IEEE80211_FC0_TYPE_CTL); return (fc == v); } /* net80211: IEEE80211_IS_DATA() */ static __inline bool ieee80211_is_data(__le16 fc) { __le16 v; fc &= htole16(IEEE80211_FC0_TYPE_MASK); v = htole16(IEEE80211_FC0_TYPE_DATA); return (fc == v); } /* net80211: IEEE80211_IS_QOSDATA() */ static __inline bool ieee80211_is_data_qos(__le16 fc) { __le16 v; fc &= htole16(IEEE80211_FC0_SUBTYPE_QOS_DATA | IEEE80211_FC0_TYPE_MASK); v = htole16(IEEE80211_FC0_SUBTYPE_QOS_DATA | IEEE80211_FC0_TYPE_DATA); return (fc == v); } /* net80211: IEEE80211_IS_MGMT() */ static __inline bool ieee80211_is_mgmt(__le16 fc) { __le16 v; fc &= htole16(IEEE80211_FC0_TYPE_MASK); v = htole16(IEEE80211_FC0_TYPE_MGT); return (fc == v); } /* Derived from net80211::ieee80211_anyhdrsize. */ static __inline unsigned int ieee80211_hdrlen(__le16 fc) { unsigned int size; if (ieee80211_is_ctl(fc)) { switch (fc & htole16(IEEE80211_FC0_SUBTYPE_MASK)) { case htole16(IEEE80211_FC0_SUBTYPE_CTS): case htole16(IEEE80211_FC0_SUBTYPE_ACK): return sizeof(struct ieee80211_frame_ack); case htole16(IEEE80211_FC0_SUBTYPE_BAR): return sizeof(struct ieee80211_frame_bar); } return (sizeof(struct ieee80211_frame_min)); } size = sizeof(struct ieee80211_frame); if (ieee80211_is_data(fc)) { if ((fc & htole16(IEEE80211_FC1_DIR_MASK << 8)) == htole16(IEEE80211_FC1_DIR_DSTODS << 8)) size += IEEE80211_ADDR_LEN; if ((fc & htole16(IEEE80211_FC0_SUBTYPE_QOS_DATA | IEEE80211_FC0_TYPE_MASK)) == htole16(IEEE80211_FC0_SUBTYPE_QOS_DATA | IEEE80211_FC0_TYPE_DATA)) size += sizeof(uint16_t); } if (ieee80211_is_mgmt(fc)) { #ifdef __notyet__ printf("XXX-BZ %s: TODO? fc %#04x size %u\n", __func__, fc, size); #endif ; } return (size); } static inline bool ieee80211_is_trigger(__le16 fc) { __le16 v; fc &= htole16(IEEE80211_FC0_SUBTYPE_MASK | IEEE80211_FC0_TYPE_MASK); v = htole16(IEEE80211_FC0_SUBTYPE_TRIGGER | IEEE80211_FC0_TYPE_CTL); return (fc == v); } static __inline bool ieee80211_is_action(__le16 fc) { __le16 v; fc &= htole16(IEEE80211_FC0_SUBTYPE_MASK | IEEE80211_FC0_TYPE_MASK); v = htole16(IEEE80211_FC0_SUBTYPE_ACTION | IEEE80211_FC0_TYPE_MGT); return (fc == v); } static __inline bool ieee80211_is_probe_resp(__le16 fc) { __le16 v; fc &= htole16(IEEE80211_FC0_SUBTYPE_MASK | IEEE80211_FC0_TYPE_MASK); v = htole16(IEEE80211_FC0_SUBTYPE_PROBE_RESP | IEEE80211_FC0_TYPE_MGT); return (fc == v); } static __inline bool ieee80211_is_auth(__le16 fc) { __le16 v; fc &= htole16(IEEE80211_FC0_SUBTYPE_MASK | IEEE80211_FC0_TYPE_MASK); v = htole16(IEEE80211_FC0_SUBTYPE_AUTH | IEEE80211_FC0_TYPE_MGT); return (fc == v); } static __inline bool ieee80211_is_assoc_req(__le16 fc) { __le16 v; fc &= htole16(IEEE80211_FC0_SUBTYPE_MASK | IEEE80211_FC0_TYPE_MASK); v = htole16(IEEE80211_FC0_SUBTYPE_ASSOC_REQ | IEEE80211_FC0_TYPE_MGT); return (fc == v); } static __inline bool ieee80211_is_assoc_resp(__le16 fc) { __le16 v; fc &= htole16(IEEE80211_FC0_SUBTYPE_MASK | IEEE80211_FC0_TYPE_MASK); v = htole16(IEEE80211_FC0_SUBTYPE_ASSOC_RESP | IEEE80211_FC0_TYPE_MGT); return (fc == v); } static __inline bool ieee80211_is_reassoc_req(__le16 fc) { __le16 v; fc &= htole16(IEEE80211_FC0_SUBTYPE_MASK | IEEE80211_FC0_TYPE_MASK); v = htole16(IEEE80211_FC0_SUBTYPE_REASSOC_REQ | IEEE80211_FC0_TYPE_MGT); return (fc == v); } static __inline bool ieee80211_is_reassoc_resp(__le16 fc) { __le16 v; fc &= htole16(IEEE80211_FC0_SUBTYPE_MASK | IEEE80211_FC0_TYPE_MASK); v = htole16(IEEE80211_FC0_SUBTYPE_REASSOC_RESP | IEEE80211_FC0_TYPE_MGT); return (fc == v); } static __inline bool ieee80211_is_disassoc(__le16 fc) { __le16 v; fc &= htole16(IEEE80211_FC0_SUBTYPE_MASK | IEEE80211_FC0_TYPE_MASK); v = htole16(IEEE80211_FC0_SUBTYPE_DISASSOC | IEEE80211_FC0_TYPE_MGT); return (fc == v); } static __inline bool ieee80211_is_data_present(__le16 fc) { __le16 v; /* If it is a data frame and NODATA is not present. */ fc &= htole16(IEEE80211_FC0_TYPE_MASK | IEEE80211_FC0_SUBTYPE_NODATA); v = htole16(IEEE80211_FC0_TYPE_DATA); return (fc == v); } static __inline bool ieee80211_is_deauth(__le16 fc) { __le16 v; fc &= htole16(IEEE80211_FC0_SUBTYPE_MASK | IEEE80211_FC0_TYPE_MASK); v = htole16(IEEE80211_FC0_SUBTYPE_DEAUTH | IEEE80211_FC0_TYPE_MGT); return (fc == v); } static __inline bool ieee80211_is_beacon(__le16 fc) { __le16 v; /* * For as much as I get it this comes in LE and unlike FreeBSD * where we get the entire frame header and u8[], here we get the * 9.2.4.1 Frame Control field only. Mask and compare. */ fc &= htole16(IEEE80211_FC0_SUBTYPE_MASK | IEEE80211_FC0_TYPE_MASK); v = htole16(IEEE80211_FC0_SUBTYPE_BEACON | IEEE80211_FC0_TYPE_MGT); return (fc == v); } static __inline bool ieee80211_is_probe_req(__le16 fc) { __le16 v; fc &= htole16(IEEE80211_FC0_SUBTYPE_MASK | IEEE80211_FC0_TYPE_MASK); v = htole16(IEEE80211_FC0_SUBTYPE_PROBE_REQ | IEEE80211_FC0_TYPE_MGT); return (fc == v); } static __inline bool ieee80211_has_protected(__le16 fc) { return (fc & htole16(IEEE80211_FC1_PROTECTED << 8)); } static __inline bool ieee80211_is_back_req(__le16 fc) { __le16 v; fc &= htole16(IEEE80211_FC0_SUBTYPE_MASK | IEEE80211_FC0_TYPE_MASK); v = htole16(IEEE80211_FC0_SUBTYPE_BAR | IEEE80211_FC0_TYPE_CTL); return (fc == v); } static __inline bool ieee80211_is_bufferable_mmpdu(struct sk_buff *skb) { struct ieee80211_mgmt *mgmt; __le16 fc; mgmt = (struct ieee80211_mgmt *)skb->data; fc = mgmt->frame_control; /* 11.2.2 Bufferable MMPDUs, 80211-2020. */ /* XXX we do not care about IBSS yet. */ if (!ieee80211_is_mgmt(fc)) return (false); if (ieee80211_is_action(fc)) /* XXX FTM? */ return (true); /* XXX false? */ if (ieee80211_is_disassoc(fc)) return (true); if (ieee80211_is_deauth(fc)) return (true); TODO(); return (false); } static __inline bool ieee80211_is_nullfunc(__le16 fc) { __le16 v; fc &= htole16(IEEE80211_FC0_SUBTYPE_MASK | IEEE80211_FC0_TYPE_MASK); v = htole16(IEEE80211_FC0_SUBTYPE_NODATA | IEEE80211_FC0_TYPE_DATA); return (fc == v); } static __inline bool ieee80211_is_qos_nullfunc(__le16 fc) { __le16 v; fc &= htole16(IEEE80211_FC0_SUBTYPE_MASK | IEEE80211_FC0_TYPE_MASK); v = htole16(IEEE80211_FC0_SUBTYPE_QOS_NULL | IEEE80211_FC0_TYPE_DATA); return (fc == v); } static __inline bool ieee80211_is_any_nullfunc(__le16 fc) { return (ieee80211_is_nullfunc(fc) || ieee80211_is_qos_nullfunc(fc)); } static inline bool ieee80211_is_pspoll(__le16 fc) { __le16 v; fc &= htole16(IEEE80211_FC0_SUBTYPE_MASK | IEEE80211_FC0_TYPE_MASK); v = htole16(IEEE80211_FC0_SUBTYPE_PS_POLL | IEEE80211_FC0_TYPE_CTL); return (fc == v); } static __inline bool ieee80211_has_a4(__le16 fc) { __le16 v; fc &= htole16((IEEE80211_FC1_DIR_TODS | IEEE80211_FC1_DIR_FROMDS) << 8); v = htole16((IEEE80211_FC1_DIR_TODS | IEEE80211_FC1_DIR_FROMDS) << 8); return (fc == v); } static __inline bool ieee80211_has_order(__le16 fc) { return (fc & htole16(IEEE80211_FC1_ORDER << 8)); } static __inline bool ieee80211_has_retry(__le16 fc) { return (fc & htole16(IEEE80211_FC1_RETRY << 8)); } static __inline bool ieee80211_has_fromds(__le16 fc) { return (fc & htole16(IEEE80211_FC1_DIR_FROMDS << 8)); } static __inline bool ieee80211_has_tods(__le16 fc) { return (fc & htole16(IEEE80211_FC1_DIR_TODS << 8)); } static __inline uint8_t * ieee80211_get_SA(struct ieee80211_hdr *hdr) { if (ieee80211_has_a4(hdr->frame_control)) return (hdr->addr4); if (ieee80211_has_fromds(hdr->frame_control)) return (hdr->addr3); return (hdr->addr2); } static __inline uint8_t * ieee80211_get_DA(struct ieee80211_hdr *hdr) { if (ieee80211_has_tods(hdr->frame_control)) return (hdr->addr3); return (hdr->addr1); } static __inline bool ieee80211_is_frag(struct ieee80211_hdr *hdr) { TODO(); return (false); } static __inline bool ieee80211_is_first_frag(__le16 fc) { TODO(); return (false); } static __inline bool ieee80211_is_robust_mgmt_frame(struct sk_buff *skb) { TODO(); return (false); } static __inline bool ieee80211_is_ftm(struct sk_buff *skb) { TODO(); return (false); } static __inline bool ieee80211_is_timing_measurement(struct sk_buff *skb) { TODO(); return (false); } static __inline bool ieee80211_has_pm(__le16 fc) { TODO(); return (false); } static __inline bool ieee80211_has_morefrags(__le16 fc) { fc &= htole16(IEEE80211_FC1_MORE_FRAG << 8); return (fc != 0); } static __inline u8 * ieee80211_get_qos_ctl(struct ieee80211_hdr *hdr) { if (ieee80211_has_a4(hdr->frame_control)) return (u8 *)hdr + 30; else return (u8 *)hdr + 24; } #endif /* _LINUXKPI_LINUX_IEEE80211_H */ diff --git a/sys/dev/ath/ath_rate/sample/sample.c b/sys/dev/ath/ath_rate/sample/sample.c index 8e70699f708d..58eea8f64993 100644 --- a/sys/dev/ath/ath_rate/sample/sample.c +++ b/sys/dev/ath/ath_rate/sample/sample.c @@ -1,1662 +1,1662 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 2005 John Bicket * 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, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * 3. Neither the names of the above-listed copyright holders nor the names * of any contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * Alternatively, this software may be distributed under the terms of the * GNU General Public License ("GPL") version 2 as published by the Free * Software Foundation. * * NO WARRANTY * 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 NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. * */ #include /* * John Bicket's SampleRate control algorithm. */ #include "opt_ath.h" #include "opt_inet.h" #include "opt_wlan.h" #include "opt_ah.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* XXX for ether_sprintf */ #include #include #ifdef INET #include #include #endif #include #include #include #include /* * This file is an implementation of the SampleRate algorithm * in "Bit-rate Selection in Wireless Networks" * (http://www.pdos.lcs.mit.edu/papers/jbicket-ms.ps) * * SampleRate chooses the bit-rate it predicts will provide the most * throughput based on estimates of the expected per-packet * transmission time for each bit-rate. SampleRate periodically sends * packets at bit-rates other than the current one to estimate when * another bit-rate will provide better performance. SampleRate * switches to another bit-rate when its estimated per-packet * transmission time becomes smaller than the current bit-rate's. * SampleRate reduces the number of bit-rates it must sample by * eliminating those that could not perform better than the one * currently being used. SampleRate also stops probing at a bit-rate * if it experiences several successive losses. * * The difference between the algorithm in the thesis and the one in this * file is that the one in this file uses a ewma instead of a window. * * Also, this implementation tracks the average transmission time for * a few different packet sizes independently for each link. */ /* XXX TODO: move this into ath_hal/net80211 so it can be shared */ #define MCS_HT20 0 #define MCS_HT20_SGI 1 #define MCS_HT40 2 #define MCS_HT40_SGI 3 /* * This is currently a copy/paste from the 11n tx code. * * It's used to determine the maximum frame length allowed for the * given rate. For now this ignores SGI/LGI and will assume long-GI. * This only matters for lower rates that can't fill a full 64k A-MPDU. * * (But it's also important because right now rate control doesn't set * flags like SGI/LGI, STBC, LDPC, TX power, etc.) * * When selecting a set of rates the rate control code will iterate * over the HT20/HT40 max frame length and tell the caller the maximum * length (@ LGI.) It will also choose a bucket that's the minimum * of this value and the provided aggregate length. That way the * rate selection will closely match what the eventual formed aggregate * will be rather than "not at all". */ static int ath_rate_sample_max_4ms_framelen[4][32] = { [MCS_HT20] = { 3212, 6432, 9648, 12864, 19300, 25736, 28952, 32172, 6424, 12852, 19280, 25708, 38568, 51424, 57852, 64280, 9628, 19260, 28896, 38528, 57792, 65532, 65532, 65532, 12828, 25656, 38488, 51320, 65532, 65532, 65532, 65532, }, [MCS_HT20_SGI] = { 3572, 7144, 10720, 14296, 21444, 28596, 32172, 35744, 7140, 14284, 21428, 28568, 42856, 57144, 64288, 65532, 10700, 21408, 32112, 42816, 64228, 65532, 65532, 65532, 14256, 28516, 42780, 57040, 65532, 65532, 65532, 65532, }, [MCS_HT40] = { 6680, 13360, 20044, 26724, 40092, 53456, 60140, 65532, 13348, 26700, 40052, 53400, 65532, 65532, 65532, 65532, 20004, 40008, 60016, 65532, 65532, 65532, 65532, 65532, 26644, 53292, 65532, 65532, 65532, 65532, 65532, 65532, }, [MCS_HT40_SGI] = { 7420, 14844, 22272, 29696, 44544, 59396, 65532, 65532, 14832, 29668, 44504, 59340, 65532, 65532, 65532, 65532, 22232, 44464, 65532, 65532, 65532, 65532, 65532, 65532, 29616, 59232, 65532, 65532, 65532, 65532, 65532, 65532, } }; /* * Given the (potentially MRR) transmit schedule, calculate the maximum * allowed packet size for forming aggregates based on the lowest * MCS rate in the transmit schedule. * * Returns -1 if it's a legacy rate or no MRR. * * XXX TODO: this needs to be limited by the RTS/CTS AR5416 8KB bug limit! * (by checking rts/cts flags and applying sc_rts_aggr_limit) * * XXX TODO: apply per-node max-ampdu size and driver ampdu size limits too. */ static int ath_rate_sample_find_min_pktlength(struct ath_softc *sc, struct ath_node *an, uint8_t rix0, int is_aggr) { #define MCS_IDX(ix) (rt->info[ix].dot11Rate) const HAL_RATE_TABLE *rt = sc->sc_currates; struct sample_node *sn = ATH_NODE_SAMPLE(an); const struct txschedule *sched = &sn->sched[rix0]; int max_pkt_length = 65530; // ATH_AGGR_MAXSIZE // Note: this may not be true in all cases; need to check? - int is_ht40 = (an->an_node.ni_chw == 40); + int is_ht40 = (an->an_node.ni_chw == IEEE80211_STA_RX_BW_40); // Note: not great, but good enough.. int idx = is_ht40 ? MCS_HT40 : MCS_HT20; if (rt->info[rix0].phy != IEEE80211_T_HT) { return -1; } if (! sc->sc_mrretry) { return -1; } KASSERT(rix0 == sched->r0, ("rix0 (%x) != sched->r0 (%x)!\n", rix0, sched->r0)); /* * Update based on sched->r{0,1,2,3} if sched->t{0,1,2,3} * is not zero. * * Note: assuming all four PHYs are HT! * * XXX TODO: right now I hardcode here and in getxtxrates() that * rates 2 and 3 in the tx schedule are ignored. This is important * for forming larger aggregates because right now (a) the tx schedule * per rate is fixed, and (b) reliable packet transmission at those * higher rates kinda needs a lower MCS rate in there somewhere. * However, this means we can only form shorter aggregates. * If we've negotiated aggregation then we can actually just * rely on software retransmit rather than having things fall * back to like MCS0/1 in hardware, and rate control will hopefully * do the right thing. * * Once the whole rate schedule is passed into ath_rate_findrate(), * the ath_rc_series is populated ,the fixed tx schedule stuff * is removed AND getxtxrates() is removed then we can remove this * check as it can just NOT populate t2/t3. It also means * probing can actually use rix0 for probeing and rix1 for the * current best rate.. */ if (sched->t0 != 0) { max_pkt_length = MIN(max_pkt_length, ath_rate_sample_max_4ms_framelen[idx][MCS_IDX(sched->r0)]); } if (sched->t1 != 0) { max_pkt_length = MIN(max_pkt_length, ath_rate_sample_max_4ms_framelen[idx][MCS_IDX(sched->r1)]); } if (sched->t2 != 0 && (! is_aggr)) { max_pkt_length = MIN(max_pkt_length, ath_rate_sample_max_4ms_framelen[idx][MCS_IDX(sched->r2)]); } if (sched->t3 != 0 && (! is_aggr)) { max_pkt_length = MIN(max_pkt_length, ath_rate_sample_max_4ms_framelen[idx][MCS_IDX(sched->r3)]); } return max_pkt_length; #undef MCS } static void ath_rate_ctl_reset(struct ath_softc *, struct ieee80211_node *); static __inline int size_to_bin(int size) { #if NUM_PACKET_SIZE_BINS > 1 if (size <= packet_size_bins[0]) return 0; #endif #if NUM_PACKET_SIZE_BINS > 2 if (size <= packet_size_bins[1]) return 1; #endif #if NUM_PACKET_SIZE_BINS > 3 if (size <= packet_size_bins[2]) return 2; #endif #if NUM_PACKET_SIZE_BINS > 4 if (size <= packet_size_bins[3]) return 3; #endif #if NUM_PACKET_SIZE_BINS > 5 if (size <= packet_size_bins[4]) return 4; #endif #if NUM_PACKET_SIZE_BINS > 6 if (size <= packet_size_bins[5]) return 5; #endif #if NUM_PACKET_SIZE_BINS > 7 if (size <= packet_size_bins[6]) return 6; #endif #if NUM_PACKET_SIZE_BINS > 8 #error "add support for more packet sizes" #endif return NUM_PACKET_SIZE_BINS-1; } void ath_rate_node_init(struct ath_softc *sc, struct ath_node *an) { /* NB: assumed to be zero'd by caller */ } void ath_rate_node_cleanup(struct ath_softc *sc, struct ath_node *an) { } static int dot11rate(const HAL_RATE_TABLE *rt, int rix) { if (rix < 0) return -1; return rt->info[rix].phy == IEEE80211_T_HT ? rt->info[rix].dot11Rate : (rt->info[rix].dot11Rate & IEEE80211_RATE_VAL) / 2; } static const char * dot11rate_label(const HAL_RATE_TABLE *rt, int rix) { if (rix < 0) return ""; return rt->info[rix].phy == IEEE80211_T_HT ? "MCS" : "Mb "; } /* * Return the rix with the lowest average_tx_time, * or -1 if all the average_tx_times are 0. */ static __inline int pick_best_rate(struct ath_node *an, const HAL_RATE_TABLE *rt, int size_bin, int require_acked_before) { struct sample_node *sn = ATH_NODE_SAMPLE(an); int best_rate_rix, best_rate_tt, best_rate_pct; uint64_t mask; int rix, tt, pct; best_rate_rix = 0; best_rate_tt = 0; best_rate_pct = 0; for (mask = sn->ratemask, rix = 0; mask != 0; mask >>= 1, rix++) { if ((mask & 1) == 0) /* not a supported rate */ continue; /* Don't pick a non-HT rate for a HT node */ if ((an->an_node.ni_flags & IEEE80211_NODE_HT) && (rt->info[rix].phy != IEEE80211_T_HT)) { continue; } tt = sn->stats[size_bin][rix].average_tx_time; if (tt <= 0 || (require_acked_before && !sn->stats[size_bin][rix].packets_acked)) continue; /* Calculate percentage if possible */ if (sn->stats[size_bin][rix].total_packets > 0) { pct = sn->stats[size_bin][rix].ewma_pct; } else { pct = -1; /* No percent yet to compare against! */ } /* don't use a bit-rate that has been failing */ if (sn->stats[size_bin][rix].successive_failures > 3) continue; /* * For HT, Don't use a bit rate that is more * lossy than the best. Give a bit of leeway. * * Don't consider best rates that we haven't seen * packets for yet; let sampling start inflence that. */ if (an->an_node.ni_flags & IEEE80211_NODE_HT) { if (pct == -1) continue; #if 0 IEEE80211_NOTE(an->an_node.ni_vap, IEEE80211_MSG_RATECTL, &an->an_node, "%s: size %d comparing best rate 0x%x pkts/ewma/tt (%ju/%d/%d) " "to 0x%x pkts/ewma/tt (%ju/%d/%d)", __func__, bin_to_size(size_bin), rt->info[best_rate_rix].dot11Rate, sn->stats[size_bin][best_rate_rix].total_packets, best_rate_pct, best_rate_tt, rt->info[rix].dot11Rate, sn->stats[size_bin][rix].total_packets, pct, tt); #endif if (best_rate_pct > (pct + 50)) continue; } /* * For non-MCS rates, use the current average txtime for * comparison. */ if (! (an->an_node.ni_flags & IEEE80211_NODE_HT)) { if (best_rate_tt == 0 || tt <= best_rate_tt) { best_rate_tt = tt; best_rate_rix = rix; best_rate_pct = pct; } } /* * Since 2 and 3 stream rates have slightly higher TX times, * allow a little bit of leeway. This should later * be abstracted out and properly handled. */ if (an->an_node.ni_flags & IEEE80211_NODE_HT) { if (best_rate_tt == 0 || ((tt * 10) <= (best_rate_tt * 10))) { best_rate_tt = tt; best_rate_rix = rix; best_rate_pct = pct; } } } return (best_rate_tt ? best_rate_rix : -1); } /* * Pick a good "random" bit-rate to sample other than the current one. */ static __inline int pick_sample_rate(struct sample_softc *ssc , struct ath_node *an, const HAL_RATE_TABLE *rt, int size_bin) { #define DOT11RATE(ix) (rt->info[ix].dot11Rate & IEEE80211_RATE_VAL) #define MCS(ix) (rt->info[ix].dot11Rate | IEEE80211_RATE_MCS) struct sample_node *sn = ATH_NODE_SAMPLE(an); int current_rix, rix; unsigned current_tt; uint64_t mask; current_rix = sn->current_rix[size_bin]; if (current_rix < 0) { /* no successes yet, send at the lowest bit-rate */ /* XXX TODO should return MCS0 if HT */ return 0; } current_tt = sn->stats[size_bin][current_rix].average_tx_time; rix = sn->last_sample_rix[size_bin]+1; /* next sample rate */ mask = sn->ratemask &~ ((uint64_t) 1<= rt->rateCount) rix = 0; continue; } /* * The following code stops trying to sample * non-MCS rates when speaking to an MCS node. * However, at least for CCK rates in 2.4GHz mode, * the non-MCS rates MAY actually provide better * PER at the very far edge of reception. * * However! Until ath_rate_form_aggr() grows * some logic to not form aggregates if the * selected rate is non-MCS, this won't work. * * So don't disable this code until you've taught * ath_rate_form_aggr() to drop out if any of * the selected rates are non-MCS. */ #if 1 /* if the node is HT and the rate isn't HT, don't bother sample */ if ((an->an_node.ni_flags & IEEE80211_NODE_HT) && (rt->info[rix].phy != IEEE80211_T_HT)) { mask &= ~((uint64_t) 1<stats[size_bin][rix].perfect_tx_time > current_tt) { mask &= ~((uint64_t) 1<stats[size_bin][rix].successive_failures > ssc->max_successive_failures && ticks - sn->stats[size_bin][rix].last_tx < ssc->stale_failure_timeout) { mask &= ~((uint64_t) 1<an_node.ni_flags & IEEE80211_NODE_HT) { uint8_t current_mcs, rix_mcs; current_mcs = MCS(current_rix) & 0x7; rix_mcs = MCS(rix) & 0x7; if (rix_mcs < (current_mcs - 2) || rix_mcs > (current_mcs + 2)) { mask &= ~((uint64_t) 1< 11M for non-HT rates */ if (! (an->an_node.ni_flags & IEEE80211_NODE_HT)) { if (DOT11RATE(rix) > 2*11 && rix > current_rix + 2) { mask &= ~((uint64_t) 1<last_sample_rix[size_bin] = rix; return rix; } return current_rix; #undef DOT11RATE #undef MCS } static int ath_rate_get_static_rix(struct ath_softc *sc, const struct ieee80211_node *ni) { #define RATE(_ix) (ni->ni_rates.rs_rates[(_ix)] & IEEE80211_RATE_VAL) #define DOT11RATE(_ix) (rt->info[(_ix)].dot11Rate & IEEE80211_RATE_VAL) #define MCS(_ix) (ni->ni_htrates.rs_rates[_ix] | IEEE80211_RATE_MCS) const struct ieee80211_txparam *tp = ni->ni_txparms; int srate; /* Check MCS rates */ for (srate = ni->ni_htrates.rs_nrates - 1; srate >= 0; srate--) { if (MCS(srate) == tp->ucastrate) return sc->sc_rixmap[tp->ucastrate]; } /* Check legacy rates */ for (srate = ni->ni_rates.rs_nrates - 1; srate >= 0; srate--) { if (RATE(srate) == tp->ucastrate) return sc->sc_rixmap[tp->ucastrate]; } return -1; #undef RATE #undef DOT11RATE #undef MCS } static void ath_rate_update_static_rix(struct ath_softc *sc, struct ieee80211_node *ni) { struct ath_node *an = ATH_NODE(ni); const struct ieee80211_txparam *tp = ni->ni_txparms; struct sample_node *sn = ATH_NODE_SAMPLE(an); if (tp != NULL && tp->ucastrate != IEEE80211_FIXED_RATE_NONE) { /* * A fixed rate is to be used; ucastrate is the IEEE code * for this rate (sans basic bit). Check this against the * negotiated rate set for the node. Note the fixed rate * may not be available for various reasons so we only * setup the static rate index if the lookup is successful. */ sn->static_rix = ath_rate_get_static_rix(sc, ni); } else { sn->static_rix = -1; } } /* * Pick a non-HT rate to begin using. */ static int ath_rate_pick_seed_rate_legacy(struct ath_softc *sc, struct ath_node *an, int frameLen) { #define DOT11RATE(ix) (rt->info[ix].dot11Rate & IEEE80211_RATE_VAL) #define MCS(ix) (rt->info[ix].dot11Rate | IEEE80211_RATE_MCS) #define RATE(ix) (DOT11RATE(ix) / 2) int rix = -1; const HAL_RATE_TABLE *rt = sc->sc_currates; struct sample_node *sn = ATH_NODE_SAMPLE(an); const int size_bin = size_to_bin(frameLen); /* no packet has been sent successfully yet */ for (rix = rt->rateCount-1; rix > 0; rix--) { if ((sn->ratemask & ((uint64_t) 1<info[rix].phy == IEEE80211_T_HT) continue; /* * Pick the highest rate <= 36 Mbps * that hasn't failed. */ if (DOT11RATE(rix) <= 72 && sn->stats[size_bin][rix].successive_failures == 0) { break; } } return rix; #undef RATE #undef MCS #undef DOT11RATE } /* * Pick a HT rate to begin using. * * Don't use any non-HT rates; only consider HT rates. */ static int ath_rate_pick_seed_rate_ht(struct ath_softc *sc, struct ath_node *an, int frameLen) { #define DOT11RATE(ix) (rt->info[ix].dot11Rate & IEEE80211_RATE_VAL) #define MCS(ix) (rt->info[ix].dot11Rate | IEEE80211_RATE_MCS) #define RATE(ix) (DOT11RATE(ix) / 2) int rix = -1, ht_rix = -1; const HAL_RATE_TABLE *rt = sc->sc_currates; struct sample_node *sn = ATH_NODE_SAMPLE(an); const int size_bin = size_to_bin(frameLen); /* no packet has been sent successfully yet */ for (rix = rt->rateCount-1; rix > 0; rix--) { /* Skip rates we can't use */ if ((sn->ratemask & ((uint64_t) 1<info[rix].phy == IEEE80211_T_HT) ht_rix = rix; /* Skip non-HT rates */ if (rt->info[rix].phy != IEEE80211_T_HT) continue; /* * Pick a medium-speed rate at 1 spatial stream * which has not seen any failures. * Higher rates may fail; we'll try them later. */ if (((MCS(rix)& 0x7f) <= 4) && sn->stats[size_bin][rix].successive_failures == 0) { break; } } /* * If all the MCS rates have successive failures, rix should be * > 0; otherwise use the lowest MCS rix (hopefully MCS 0.) */ return MAX(rix, ht_rix); #undef RATE #undef MCS #undef DOT11RATE } void ath_rate_findrate(struct ath_softc *sc, struct ath_node *an, int shortPreamble, size_t frameLen, int tid, int is_aggr, u_int8_t *rix0, int *try0, u_int8_t *txrate, int *maxdur, int *maxpktlen) { #define DOT11RATE(ix) (rt->info[ix].dot11Rate & IEEE80211_RATE_VAL) #define MCS(ix) (rt->info[ix].dot11Rate | IEEE80211_RATE_MCS) #define RATE(ix) (DOT11RATE(ix) / 2) struct sample_node *sn = ATH_NODE_SAMPLE(an); struct sample_softc *ssc = ATH_SOFTC_SAMPLE(sc); struct ieee80211com *ic = &sc->sc_ic; const HAL_RATE_TABLE *rt = sc->sc_currates; int size_bin = size_to_bin(frameLen); int rix, mrr, best_rix, change_rates; unsigned average_tx_time; int max_pkt_len; ath_rate_update_static_rix(sc, &an->an_node); /* For now don't take TID, is_aggr into account */ /* Also for now don't calculate a max duration; that'll come later */ *maxdur = -1; /* * For now just set it to the frame length; we'll optimise it later. */ *maxpktlen = frameLen; if (sn->currates != sc->sc_currates) { device_printf(sc->sc_dev, "%s: currates != sc_currates!\n", __func__); rix = 0; *try0 = ATH_TXMAXTRY; goto done; } if (sn->static_rix != -1) { rix = sn->static_rix; *try0 = ATH_TXMAXTRY; /* * Ensure we limit max packet length here too! */ max_pkt_len = ath_rate_sample_find_min_pktlength(sc, an, sn->static_rix, is_aggr); if (max_pkt_len > 0) { *maxpktlen = frameLen = MIN(frameLen, max_pkt_len); size_bin = size_to_bin(frameLen); } goto done; } mrr = sc->sc_mrretry; /* XXX check HT protmode too */ /* XXX turn into a cap; 11n MACs support MRR+RTSCTS */ if (mrr && (ic->ic_flags & IEEE80211_F_USEPROT && !sc->sc_mrrprot)) mrr = 0; best_rix = pick_best_rate(an, rt, size_bin, !mrr); /* * At this point we've chosen the best rix, so now we * need to potentially update our maximum packet length * and size_bin if we're doing 11n rates. */ max_pkt_len = ath_rate_sample_find_min_pktlength(sc, an, best_rix, is_aggr); if (max_pkt_len > 0) { #if 0 device_printf(sc->sc_dev, "Limiting maxpktlen from %d to %d bytes\n", (int) frameLen, max_pkt_len); #endif *maxpktlen = frameLen = MIN(frameLen, max_pkt_len); size_bin = size_to_bin(frameLen); } if (best_rix >= 0) { average_tx_time = sn->stats[size_bin][best_rix].average_tx_time; } else { average_tx_time = 0; } /* * Limit the time measuring the performance of other tx * rates to sample_rate% of the total transmission time. */ if (sn->sample_tt[size_bin] < average_tx_time * (sn->packets_since_sample[size_bin]*ssc->sample_rate/100)) { rix = pick_sample_rate(ssc, an, rt, size_bin); IEEE80211_NOTE(an->an_node.ni_vap, IEEE80211_MSG_RATECTL, &an->an_node, "att %d sample_tt %d size %u " "sample rate %d %s current rate %d %s", average_tx_time, sn->sample_tt[size_bin], bin_to_size(size_bin), dot11rate(rt, rix), dot11rate_label(rt, rix), dot11rate(rt, sn->current_rix[size_bin]), dot11rate_label(rt, sn->current_rix[size_bin])); if (rix != sn->current_rix[size_bin]) { sn->current_sample_rix[size_bin] = rix; } else { sn->current_sample_rix[size_bin] = -1; } sn->packets_since_sample[size_bin] = 0; } else { change_rates = 0; if (!sn->packets_sent[size_bin] || best_rix == -1) { /* no packet has been sent successfully yet */ change_rates = 1; if (an->an_node.ni_flags & IEEE80211_NODE_HT) best_rix = ath_rate_pick_seed_rate_ht(sc, an, frameLen); else best_rix = ath_rate_pick_seed_rate_legacy(sc, an, frameLen); } else if (sn->packets_sent[size_bin] < 20) { /* let the bit-rate switch quickly during the first few packets */ IEEE80211_NOTE(an->an_node.ni_vap, IEEE80211_MSG_RATECTL, &an->an_node, "%s: switching quickly..", __func__); change_rates = 1; } else if (ticks - ssc->min_switch > sn->ticks_since_switch[size_bin]) { /* min_switch seconds have gone by */ IEEE80211_NOTE(an->an_node.ni_vap, IEEE80211_MSG_RATECTL, &an->an_node, "%s: min_switch %d > ticks_since_switch %d..", __func__, ticks - ssc->min_switch, sn->ticks_since_switch[size_bin]); change_rates = 1; } else if ((! (an->an_node.ni_flags & IEEE80211_NODE_HT)) && (2*average_tx_time < sn->stats[size_bin][sn->current_rix[size_bin]].average_tx_time)) { /* the current bit-rate is twice as slow as the best one */ IEEE80211_NOTE(an->an_node.ni_vap, IEEE80211_MSG_RATECTL, &an->an_node, "%s: 2x att (= %d) < cur_rix att %d", __func__, 2 * average_tx_time, sn->stats[size_bin][sn->current_rix[size_bin]].average_tx_time); change_rates = 1; } else if ((an->an_node.ni_flags & IEEE80211_NODE_HT)) { int cur_rix = sn->current_rix[size_bin]; int cur_att = sn->stats[size_bin][cur_rix].average_tx_time; /* * If the node is HT, it if the rate isn't the * same and the average tx time is within 10% * of the current rate. It can fail a little. * * This is likely not optimal! */ #if 0 printf("cur rix/att %x/%d, best rix/att %x/%d\n", MCS(cur_rix), cur_att, MCS(best_rix), average_tx_time); #endif if ((best_rix != cur_rix) && (average_tx_time * 9) <= (cur_att * 10)) { IEEE80211_NOTE(an->an_node.ni_vap, IEEE80211_MSG_RATECTL, &an->an_node, "%s: HT: size %d best_rix 0x%x > " " cur_rix 0x%x, average_tx_time %d," " cur_att %d", __func__, bin_to_size(size_bin), MCS(best_rix), MCS(cur_rix), average_tx_time, cur_att); change_rates = 1; } } sn->packets_since_sample[size_bin]++; if (change_rates) { if (best_rix != sn->current_rix[size_bin]) { IEEE80211_NOTE(an->an_node.ni_vap, IEEE80211_MSG_RATECTL, &an->an_node, "%s: size %d switch rate %d %s (%d/%d) EWMA %d -> %d %s (%d/%d) EWMA %d after %d packets mrr %d", __func__, bin_to_size(size_bin), dot11rate(rt, sn->current_rix[size_bin]), dot11rate_label(rt, sn->current_rix[size_bin]), sn->stats[size_bin][sn->current_rix[size_bin]].average_tx_time, sn->stats[size_bin][sn->current_rix[size_bin]].perfect_tx_time, sn->stats[size_bin][sn->current_rix[size_bin]].ewma_pct, dot11rate(rt, best_rix), dot11rate_label(rt, best_rix), sn->stats[size_bin][best_rix].average_tx_time, sn->stats[size_bin][best_rix].perfect_tx_time, sn->stats[size_bin][best_rix].ewma_pct, sn->packets_since_switch[size_bin], mrr); } sn->packets_since_switch[size_bin] = 0; sn->current_rix[size_bin] = best_rix; sn->ticks_since_switch[size_bin] = ticks; /* * Set the visible txrate for this node. */ an->an_node.ni_txrate = (rt->info[best_rix].phy == IEEE80211_T_HT) ? MCS(best_rix) : DOT11RATE(best_rix); } rix = sn->current_rix[size_bin]; sn->packets_since_switch[size_bin]++; } *try0 = mrr ? sn->sched[rix].t0 : ATH_TXMAXTRY; done: /* * This bug totally sucks and should be fixed. * * For now though, let's not panic, so we can start to figure * out how to better reproduce it. */ if (rix < 0 || rix >= rt->rateCount) { printf("%s: ERROR: rix %d out of bounds (rateCount=%d)\n", __func__, rix, rt->rateCount); rix = 0; /* XXX just default for now */ } KASSERT(rix >= 0 && rix < rt->rateCount, ("rix is %d", rix)); *rix0 = rix; *txrate = rt->info[rix].rateCode | (shortPreamble ? rt->info[rix].shortPreamble : 0); sn->packets_sent[size_bin]++; #undef DOT11RATE #undef MCS #undef RATE } /* * Get the TX rates. Don't fiddle with short preamble flags for them; * the caller can do that. */ void ath_rate_getxtxrates(struct ath_softc *sc, struct ath_node *an, uint8_t rix0, int is_aggr, struct ath_rc_series *rc) { struct sample_node *sn = ATH_NODE_SAMPLE(an); const struct txschedule *sched = &sn->sched[rix0]; KASSERT(rix0 == sched->r0, ("rix0 (%x) != sched->r0 (%x)!\n", rix0, sched->r0)); rc[0].flags = rc[1].flags = rc[2].flags = rc[3].flags = 0; rc[0].rix = sched->r0; rc[1].rix = sched->r1; rc[2].rix = sched->r2; rc[3].rix = sched->r3; rc[0].tries = sched->t0; rc[1].tries = sched->t1; if (is_aggr) { rc[2].tries = rc[3].tries = 0; } else { rc[2].tries = sched->t2; rc[3].tries = sched->t3; } } void ath_rate_setupxtxdesc(struct ath_softc *sc, struct ath_node *an, struct ath_desc *ds, int shortPreamble, u_int8_t rix) { struct sample_node *sn = ATH_NODE_SAMPLE(an); const struct txschedule *sched = &sn->sched[rix]; const HAL_RATE_TABLE *rt = sc->sc_currates; uint8_t rix1, s1code, rix2, s2code, rix3, s3code; /* XXX precalculate short preamble tables */ rix1 = sched->r1; s1code = rt->info[rix1].rateCode | (shortPreamble ? rt->info[rix1].shortPreamble : 0); rix2 = sched->r2; s2code = rt->info[rix2].rateCode | (shortPreamble ? rt->info[rix2].shortPreamble : 0); rix3 = sched->r3; s3code = rt->info[rix3].rateCode | (shortPreamble ? rt->info[rix3].shortPreamble : 0); ath_hal_setupxtxdesc(sc->sc_ah, ds, s1code, sched->t1, /* series 1 */ s2code, sched->t2, /* series 2 */ s3code, sched->t3); /* series 3 */ } /* * Update the current statistics. * * Note that status is for the FINAL transmit status, not this * particular attempt. So, check if tries > tries0 and if so * assume this status failed. * * This is important because some failures are due to both * short AND long retries; if the final issue was a short * retry failure then we still want to account for the * bad long retry attempts. */ static void update_stats(struct ath_softc *sc, struct ath_node *an, int frame_size, int rix0, int tries0, int short_tries, int tries, int status, int nframes, int nbad) { struct sample_node *sn = ATH_NODE_SAMPLE(an); struct sample_softc *ssc = ATH_SOFTC_SAMPLE(sc); #ifdef IEEE80211_DEBUG const HAL_RATE_TABLE *rt = sc->sc_currates; #endif const int size_bin = size_to_bin(frame_size); const int size = bin_to_size(size_bin); int tt; - int is_ht40 = (an->an_node.ni_chw == 40); + int is_ht40 = (an->an_node.ni_chw == IEEE80211_STA_RX_BW_40); int pct; if (!IS_RATE_DEFINED(sn, rix0)) return; /* * Treat long retries as us exceeding retries, even * if the eventual attempt at some other MRR schedule * succeeded. */ if (tries > tries0) { status = HAL_TXERR_XRETRY; } /* * If status is FAIL then we treat all frames as bad. * This better accurately tracks EWMA and average TX time * because even if the eventual transmission succeeded, * transmission at this rate did not. */ if (status != 0) nbad = nframes; /* * Ignore short tries count as contributing to failure. * Right now there's no way to know if it's part of any * given rate attempt, and outside of the RTS/CTS management * rate, it doesn't /really/ help. */ tt = calc_usecs_unicast_packet(sc, size, rix0, 0 /* short_tries */, MIN(tries0, tries) - 1, is_ht40); if (sn->stats[size_bin][rix0].total_packets < ssc->smoothing_minpackets) { /* just average the first few packets */ int avg_tx = sn->stats[size_bin][rix0].average_tx_time; int packets = sn->stats[size_bin][rix0].total_packets; sn->stats[size_bin][rix0].average_tx_time = (tt+(avg_tx*packets))/(packets+nframes); } else { /* use a ewma */ sn->stats[size_bin][rix0].average_tx_time = ((sn->stats[size_bin][rix0].average_tx_time * ssc->smoothing_rate) + (tt * (100 - ssc->smoothing_rate))) / 100; } if (nframes == nbad) { sn->stats[size_bin][rix0].successive_failures += nbad; } else { sn->stats[size_bin][rix0].packets_acked += (nframes - nbad); sn->stats[size_bin][rix0].successive_failures = 0; } sn->stats[size_bin][rix0].tries += tries; sn->stats[size_bin][rix0].last_tx = ticks; sn->stats[size_bin][rix0].total_packets += nframes; /* update EWMA for this rix */ /* Calculate percentage based on current rate */ if (nframes == 0) nframes = nbad = 1; pct = ((nframes - nbad) * 1000) / nframes; if (sn->stats[size_bin][rix0].total_packets < ssc->smoothing_minpackets) { /* just average the first few packets */ int a_pct = (sn->stats[size_bin][rix0].packets_acked * 1000) / (sn->stats[size_bin][rix0].total_packets); sn->stats[size_bin][rix0].ewma_pct = a_pct; } else { /* use a ewma */ sn->stats[size_bin][rix0].ewma_pct = ((sn->stats[size_bin][rix0].ewma_pct * ssc->smoothing_rate) + (pct * (100 - ssc->smoothing_rate))) / 100; } /* * Only update the sample time for the initial sample rix. * We've updated the statistics on each of the other retries * fine, but we should only update the sample_tt with what * was actually sampled. * * However, to aide in debugging, log all the failures for * each of the buckets */ IEEE80211_NOTE(an->an_node.ni_vap, IEEE80211_MSG_RATECTL, &an->an_node, "%s: size %d %s %s rate %d %s tries (%d/%d) tt %d " "avg_tt (%d/%d) nfrm %d nbad %d", __func__, size, status ? "FAIL" : "OK", rix0 == sn->current_sample_rix[size_bin] ? "sample" : "mrr", dot11rate(rt, rix0), dot11rate_label(rt, rix0), short_tries, tries, tt, sn->stats[size_bin][rix0].average_tx_time, sn->stats[size_bin][rix0].perfect_tx_time, nframes, nbad); if (rix0 == sn->current_sample_rix[size_bin]) { sn->sample_tt[size_bin] = tt; sn->current_sample_rix[size_bin] = -1; } } static void badrate(struct ath_softc *sc, int series, int hwrate, int tries, int status) { device_printf(sc->sc_dev, "bad series%d hwrate 0x%x, tries %u ts_status 0x%x\n", series, hwrate, tries, status); } void ath_rate_tx_complete(struct ath_softc *sc, struct ath_node *an, const struct ath_rc_series *rc, const struct ath_tx_status *ts, int frame_size, int rc_framesize, int nframes, int nbad) { struct ieee80211com *ic = &sc->sc_ic; struct sample_node *sn = ATH_NODE_SAMPLE(an); int final_rix, short_tries, long_tries; const HAL_RATE_TABLE *rt = sc->sc_currates; int status = ts->ts_status; int mrr; final_rix = rt->rateCodeToIndex[ts->ts_rate]; short_tries = ts->ts_shortretry; long_tries = ts->ts_longretry + 1; if (nframes == 0) { device_printf(sc->sc_dev, "%s: nframes=0?\n", __func__); return; } if (frame_size == 0) /* NB: should not happen */ frame_size = 1500; if (rc_framesize == 0) /* NB: should not happen */ rc_framesize = 1500; /* * There are still some places where what rate control set as * a limit but the hardware decided, for some reason, to transmit * at a smaller size that fell into a different bucket. * * The eternal question here is - which size_bin should it go in? * The one that was requested, or the one that was transmitted? * * Here's the problem - if we use the one that was transmitted, * we may continue to hit corner cases where we make a rate * selection using a higher bin but only update the smaller bin; * thus never really "adapting". * * If however we update the larger bin, we're not accurately * representing the channel state at that frame/aggregate size. * However if we keep hitting the larger request but completing * a smaller size, we at least updates based on what the * request was /for/. * * I'm going to err on the side of caution and choose the * latter. */ if (size_to_bin(frame_size) != size_to_bin(rc_framesize)) { #if 0 device_printf(sc->sc_dev, "%s: completed but frame size buckets mismatch " "(completed %d tx'ed %d)\n", __func__, frame_size, rc_framesize); #endif frame_size = rc_framesize; } if (sn->ratemask == 0) { IEEE80211_NOTE(an->an_node.ni_vap, IEEE80211_MSG_RATECTL, &an->an_node, "%s: size %d %s rate/try %d/%d no rates yet", __func__, bin_to_size(size_to_bin(frame_size)), status ? "FAIL" : "OK", short_tries, long_tries); return; } mrr = sc->sc_mrretry; /* XXX check HT protmode too */ if (mrr && (ic->ic_flags & IEEE80211_F_USEPROT && !sc->sc_mrrprot)) mrr = 0; if (!mrr || ts->ts_finaltsi == 0) { if (!IS_RATE_DEFINED(sn, final_rix)) { device_printf(sc->sc_dev, "%s: ts_rate=%d ts_finaltsi=%d, final_rix=%d\n", __func__, ts->ts_rate, ts->ts_finaltsi, final_rix); badrate(sc, 0, ts->ts_rate, long_tries, status); return; } /* * Only one rate was used; optimize work. */ IEEE80211_NOTE(an->an_node.ni_vap, IEEE80211_MSG_RATECTL, &an->an_node, "%s: size %d (%d bytes) %s rate/short/long %d %s/%d/%d nframes/nbad [%d/%d]", __func__, bin_to_size(size_to_bin(frame_size)), frame_size, status ? "FAIL" : "OK", dot11rate(rt, final_rix), dot11rate_label(rt, final_rix), short_tries, long_tries, nframes, nbad); update_stats(sc, an, frame_size, final_rix, long_tries, short_tries, long_tries, status, nframes, nbad); } else { int finalTSIdx = ts->ts_finaltsi; int i; /* * Process intermediate rates that failed. */ IEEE80211_NOTE(an->an_node.ni_vap, IEEE80211_MSG_RATECTL, &an->an_node, "%s: size %d (%d bytes) finaltsidx %d short %d long %d %s rate/try [%d %s/%d %d %s/%d %d %s/%d %d %s/%d] nframes/nbad [%d/%d]", __func__, bin_to_size(size_to_bin(frame_size)), frame_size, finalTSIdx, short_tries, long_tries, status ? "FAIL" : "OK", dot11rate(rt, rc[0].rix), dot11rate_label(rt, rc[0].rix), rc[0].tries, dot11rate(rt, rc[1].rix), dot11rate_label(rt, rc[1].rix), rc[1].tries, dot11rate(rt, rc[2].rix), dot11rate_label(rt, rc[2].rix), rc[2].tries, dot11rate(rt, rc[3].rix), dot11rate_label(rt, rc[3].rix), rc[3].tries, nframes, nbad); for (i = 0; i < 4; i++) { if (rc[i].tries && !IS_RATE_DEFINED(sn, rc[i].rix)) badrate(sc, 0, rc[i].ratecode, rc[i].tries, status); } /* * This used to not penalise other tries because loss * can be bursty, but it's then not accurately keeping * the avg TX time and EWMA updated. */ if (rc[0].tries) { update_stats(sc, an, frame_size, rc[0].rix, rc[0].tries, short_tries, long_tries, status, nframes, nbad); long_tries -= rc[0].tries; } if (rc[1].tries && finalTSIdx > 0) { update_stats(sc, an, frame_size, rc[1].rix, rc[1].tries, short_tries, long_tries, status, nframes, nbad); long_tries -= rc[1].tries; } if (rc[2].tries && finalTSIdx > 1) { update_stats(sc, an, frame_size, rc[2].rix, rc[2].tries, short_tries, long_tries, status, nframes, nbad); long_tries -= rc[2].tries; } if (rc[3].tries && finalTSIdx > 2) { update_stats(sc, an, frame_size, rc[3].rix, rc[3].tries, short_tries, long_tries, status, nframes, nbad); } } } void ath_rate_newassoc(struct ath_softc *sc, struct ath_node *an, int isnew) { if (isnew) ath_rate_ctl_reset(sc, &an->an_node); } void ath_rate_update_rx_rssi(struct ath_softc *sc, struct ath_node *an, int rssi) { } static const struct txschedule *mrr_schedules[IEEE80211_MODE_MAX+2] = { NULL, /* IEEE80211_MODE_AUTO */ series_11a, /* IEEE80211_MODE_11A */ series_11g, /* IEEE80211_MODE_11B */ series_11g, /* IEEE80211_MODE_11G */ NULL, /* IEEE80211_MODE_FH */ series_11a, /* IEEE80211_MODE_TURBO_A */ series_11g, /* IEEE80211_MODE_TURBO_G */ series_11a, /* IEEE80211_MODE_STURBO_A */ series_11na, /* IEEE80211_MODE_11NA */ series_11ng, /* IEEE80211_MODE_11NG */ series_half, /* IEEE80211_MODE_HALF */ series_quarter, /* IEEE80211_MODE_QUARTER */ }; /* * Initialize the tables for a node. */ static void ath_rate_ctl_reset(struct ath_softc *sc, struct ieee80211_node *ni) { #define RATE(_ix) (ni->ni_rates.rs_rates[(_ix)] & IEEE80211_RATE_VAL) #define DOT11RATE(_ix) (rt->info[(_ix)].dot11Rate & IEEE80211_RATE_VAL) #define MCS(_ix) (ni->ni_htrates.rs_rates[_ix] | IEEE80211_RATE_MCS) struct ath_node *an = ATH_NODE(ni); struct sample_node *sn = ATH_NODE_SAMPLE(an); const HAL_RATE_TABLE *rt = sc->sc_currates; int x, y, rix; KASSERT(rt != NULL, ("no rate table, mode %u", sc->sc_curmode)); KASSERT(sc->sc_curmode < IEEE80211_MODE_MAX+2, ("curmode %u", sc->sc_curmode)); sn->sched = mrr_schedules[sc->sc_curmode]; KASSERT(sn->sched != NULL, ("no mrr schedule for mode %u", sc->sc_curmode)); sn->static_rix = -1; ath_rate_update_static_rix(sc, ni); sn->currates = sc->sc_currates; /* * Construct a bitmask of usable rates. This has all * negotiated rates minus those marked by the hal as * to be ignored for doing rate control. */ sn->ratemask = 0; /* MCS rates */ if (ni->ni_flags & IEEE80211_NODE_HT) { for (x = 0; x < ni->ni_htrates.rs_nrates; x++) { rix = sc->sc_rixmap[MCS(x)]; if (rix == 0xff) continue; /* skip rates marked broken by hal */ if (!rt->info[rix].valid) continue; KASSERT(rix < SAMPLE_MAXRATES, ("mcs %u has rix %d", MCS(x), rix)); sn->ratemask |= (uint64_t) 1<ni_rates.rs_nrates; x++) { rix = sc->sc_rixmap[RATE(x)]; if (rix == 0xff) continue; /* skip rates marked broken by hal */ if (!rt->info[rix].valid) continue; KASSERT(rix < SAMPLE_MAXRATES, ("rate %u has rix %d", RATE(x), rix)); sn->ratemask |= (uint64_t) 1<ni_vap, IEEE80211_MSG_RATECTL)) { uint64_t mask; ieee80211_note(ni->ni_vap, "[%6D] %s: size 1600 rate/tt", ni->ni_macaddr, ":", __func__); for (mask = sn->ratemask, rix = 0; mask != 0; mask >>= 1, rix++) { if ((mask & 1) == 0) continue; printf(" %d %s/%d", dot11rate(rt, rix), dot11rate_label(rt, rix), calc_usecs_unicast_packet(sc, 1600, rix, 0,0, - (ni->ni_chw == 40))); + (ni->ni_chw == IEEE80211_STA_RX_BW_40))); } printf("\n"); } #endif for (y = 0; y < NUM_PACKET_SIZE_BINS; y++) { int size = bin_to_size(y); uint64_t mask; sn->packets_sent[y] = 0; sn->current_sample_rix[y] = -1; sn->last_sample_rix[y] = 0; /* XXX start with first valid rate */ sn->current_rix[y] = ffs(sn->ratemask)-1; /* * Initialize the statistics buckets; these are * indexed by the rate code index. */ for (rix = 0, mask = sn->ratemask; mask != 0; rix++, mask >>= 1) { if ((mask & 1) == 0) /* not a valid rate */ continue; sn->stats[y][rix].successive_failures = 0; sn->stats[y][rix].tries = 0; sn->stats[y][rix].total_packets = 0; sn->stats[y][rix].packets_acked = 0; sn->stats[y][rix].last_tx = 0; sn->stats[y][rix].ewma_pct = 0; sn->stats[y][rix].perfect_tx_time = calc_usecs_unicast_packet(sc, size, rix, 0, 0, - (ni->ni_chw == 40)); + (ni->ni_chw == IEEE80211_STA_RX_BW_40)); sn->stats[y][rix].average_tx_time = sn->stats[y][rix].perfect_tx_time; } } #if 0 /* XXX 0, num_rates-1 are wrong */ IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_RATECTL, ni, "%s: %d rates %d%sMbps (%dus)- %d%sMbps (%dus)", __func__, sn->num_rates, DOT11RATE(0)/2, DOT11RATE(0) % 1 ? ".5" : "", sn->stats[1][0].perfect_tx_time, DOT11RATE(sn->num_rates-1)/2, DOT11RATE(sn->num_rates-1) % 1 ? ".5" : "", sn->stats[1][sn->num_rates-1].perfect_tx_time ); #endif /* set the visible bit-rate */ if (sn->static_rix != -1) ni->ni_txrate = DOT11RATE(sn->static_rix); else ni->ni_txrate = RATE(0); #undef RATE #undef DOT11RATE } /* * Fetch the statistics for the given node. * * The ieee80211 node must be referenced and unlocked, however the ath_node * must be locked. * * The main difference here is that we convert the rate indexes * to 802.11 rates, or the userland output won't make much sense * as it has no access to the rix table. */ int ath_rate_fetch_node_stats(struct ath_softc *sc, struct ath_node *an, struct ath_rateioctl *rs) { struct sample_node *sn = ATH_NODE_SAMPLE(an); const HAL_RATE_TABLE *rt = sc->sc_currates; struct ath_rateioctl_tlv av; struct ath_rateioctl_rt *tv; int error, y; int o = 0; ATH_NODE_LOCK_ASSERT(an); error = 0; /* * Ensure there's enough space for the statistics. */ if (rs->len < sizeof(struct ath_rateioctl_tlv) + sizeof(struct ath_rateioctl_rt) + sizeof(struct ath_rateioctl_tlv) + sizeof(struct sample_node)) { device_printf(sc->sc_dev, "%s: len=%d, too short\n", __func__, rs->len); return (EINVAL); } /* * Take a temporary copy of the sample node state so we can * modify it before we copy it. */ tv = malloc(sizeof(struct ath_rateioctl_rt), M_TEMP, M_NOWAIT | M_ZERO); if (tv == NULL) { return (ENOMEM); } /* * Populate the rate table mapping TLV. */ tv->nentries = rt->rateCount; for (y = 0; y < rt->rateCount; y++) { tv->ratecode[y] = rt->info[y].dot11Rate & IEEE80211_RATE_VAL; if (rt->info[y].phy == IEEE80211_T_HT) tv->ratecode[y] |= IEEE80211_RATE_MCS; } o = 0; /* * First TLV - rate code mapping */ av.tlv_id = ATH_RATE_TLV_RATETABLE; av.tlv_len = sizeof(struct ath_rateioctl_rt); error = copyout(&av, rs->buf + o, sizeof(struct ath_rateioctl_tlv)); if (error != 0) goto out; o += sizeof(struct ath_rateioctl_tlv); error = copyout(tv, rs->buf + o, sizeof(struct ath_rateioctl_rt)); if (error != 0) goto out; o += sizeof(struct ath_rateioctl_rt); /* * Second TLV - sample node statistics */ av.tlv_id = ATH_RATE_TLV_SAMPLENODE; av.tlv_len = sizeof(struct sample_node); error = copyout(&av, rs->buf + o, sizeof(struct ath_rateioctl_tlv)); if (error != 0) goto out; o += sizeof(struct ath_rateioctl_tlv); /* * Copy the statistics over to the provided buffer. */ error = copyout(sn, rs->buf + o, sizeof(struct sample_node)); if (error != 0) goto out; o += sizeof(struct sample_node); out: free(tv, M_TEMP); return (error); } static void sample_stats(void *arg, struct ieee80211_node *ni) { struct ath_softc *sc = arg; const HAL_RATE_TABLE *rt = sc->sc_currates; struct sample_node *sn = ATH_NODE_SAMPLE(ATH_NODE(ni)); uint64_t mask; int rix, y; printf("\n[%s] refcnt %d static_rix (%d %s) ratemask 0x%jx\n", ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni), dot11rate(rt, sn->static_rix), dot11rate_label(rt, sn->static_rix), (uintmax_t)sn->ratemask); for (y = 0; y < NUM_PACKET_SIZE_BINS; y++) { printf("[%4u] cur rix %d (%d %s) since switch: packets %d ticks %u\n", bin_to_size(y), sn->current_rix[y], dot11rate(rt, sn->current_rix[y]), dot11rate_label(rt, sn->current_rix[y]), sn->packets_since_switch[y], sn->ticks_since_switch[y]); printf("[%4u] last sample (%d %s) cur sample (%d %s) packets sent %d\n", bin_to_size(y), dot11rate(rt, sn->last_sample_rix[y]), dot11rate_label(rt, sn->last_sample_rix[y]), dot11rate(rt, sn->current_sample_rix[y]), dot11rate_label(rt, sn->current_sample_rix[y]), sn->packets_sent[y]); printf("[%4u] packets since sample %d sample tt %u\n", bin_to_size(y), sn->packets_since_sample[y], sn->sample_tt[y]); } for (mask = sn->ratemask, rix = 0; mask != 0; mask >>= 1, rix++) { if ((mask & 1) == 0) continue; for (y = 0; y < NUM_PACKET_SIZE_BINS; y++) { if (sn->stats[y][rix].total_packets == 0) continue; printf("[%2u %s:%4u] %8ju:%-8ju (%3d%%) (EWMA %3d.%1d%%) T %8ju F %4d avg %5u last %u\n", dot11rate(rt, rix), dot11rate_label(rt, rix), bin_to_size(y), (uintmax_t) sn->stats[y][rix].total_packets, (uintmax_t) sn->stats[y][rix].packets_acked, (int) ((sn->stats[y][rix].packets_acked * 100ULL) / sn->stats[y][rix].total_packets), sn->stats[y][rix].ewma_pct / 10, sn->stats[y][rix].ewma_pct % 10, (uintmax_t) sn->stats[y][rix].tries, sn->stats[y][rix].successive_failures, sn->stats[y][rix].average_tx_time, ticks - sn->stats[y][rix].last_tx); } } } static int ath_rate_sysctl_stats(SYSCTL_HANDLER_ARGS) { struct ath_softc *sc = arg1; struct ieee80211com *ic = &sc->sc_ic; int error, v; v = 0; error = sysctl_handle_int(oidp, &v, 0, req); if (error || !req->newptr) return error; ieee80211_iterate_nodes(&ic->ic_sta, sample_stats, sc); return 0; } static int ath_rate_sysctl_smoothing_rate(SYSCTL_HANDLER_ARGS) { struct sample_softc *ssc = arg1; int rate, error; rate = ssc->smoothing_rate; error = sysctl_handle_int(oidp, &rate, 0, req); if (error || !req->newptr) return error; if (!(0 <= rate && rate < 100)) return EINVAL; ssc->smoothing_rate = rate; ssc->smoothing_minpackets = 100 / (100 - rate); return 0; } static int ath_rate_sysctl_sample_rate(SYSCTL_HANDLER_ARGS) { struct sample_softc *ssc = arg1; int rate, error; rate = ssc->sample_rate; error = sysctl_handle_int(oidp, &rate, 0, req); if (error || !req->newptr) return error; if (!(2 <= rate && rate <= 100)) return EINVAL; ssc->sample_rate = rate; return 0; } static void ath_rate_sysctlattach(struct ath_softc *sc, struct sample_softc *ssc) { struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(sc->sc_dev); struct sysctl_oid *tree = device_get_sysctl_tree(sc->sc_dev); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "smoothing_rate", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, ssc, 0, ath_rate_sysctl_smoothing_rate, "I", "sample: smoothing rate for avg tx time (%%)"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "sample_rate", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, ssc, 0, ath_rate_sysctl_sample_rate, "I", "sample: percent air time devoted to sampling new rates (%%)"); /* XXX max_successive_failures, stale_failure_timeout, min_switch */ SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "sample_stats", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, ath_rate_sysctl_stats, "I", "sample: print statistics"); } struct ath_ratectrl * ath_rate_attach(struct ath_softc *sc) { struct sample_softc *ssc; ssc = malloc(sizeof(struct sample_softc), M_DEVBUF, M_NOWAIT|M_ZERO); if (ssc == NULL) return NULL; ssc->arc.arc_space = sizeof(struct sample_node); ssc->smoothing_rate = 75; /* ewma percentage ([0..99]) */ ssc->smoothing_minpackets = 100 / (100 - ssc->smoothing_rate); ssc->sample_rate = 10; /* %time to try diff tx rates */ ssc->max_successive_failures = 3; /* threshold for rate sampling*/ ssc->stale_failure_timeout = 10 * hz; /* 10 seconds */ ssc->min_switch = hz; /* 1 second */ ath_rate_sysctlattach(sc, ssc); return &ssc->arc; } void ath_rate_detach(struct ath_ratectrl *arc) { struct sample_softc *ssc = (struct sample_softc *) arc; free(ssc, M_DEVBUF); } diff --git a/sys/dev/ath/if_ath_tx_ht.c b/sys/dev/ath/if_ath_tx_ht.c index 9558b7c9e696..ac61c7b4cf54 100644 --- a/sys/dev/ath/if_ath_tx_ht.c +++ b/sys/dev/ath/if_ath_tx_ht.c @@ -1,1016 +1,1016 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011 Adrian Chadd, Xenion Pty Ltd. * 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, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * * NO WARRANTY * 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 NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. */ #include #include "opt_inet.h" #include "opt_ath.h" #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 #ifdef IEEE80211_SUPPORT_SUPERG #include #endif #ifdef IEEE80211_SUPPORT_TDMA #include #endif #include #ifdef INET #include #include #endif #include #include /* XXX for softled */ #include #ifdef ATH_TX99_DIAG #include #endif #include /* XXX for some support functions */ #include #include #include /* * XXX net80211? */ #define IEEE80211_AMPDU_SUBFRAME_DEFAULT 32 #define ATH_AGGR_DELIM_SZ 4 /* delimiter size */ #define ATH_AGGR_MINPLEN 256 /* in bytes, minimum packet length */ /* number of delimiters for encryption padding */ #define ATH_AGGR_ENCRYPTDELIM 10 /* * returns delimiter padding required given the packet length */ #define ATH_AGGR_GET_NDELIM(_len) \ (((((_len) + ATH_AGGR_DELIM_SZ) < ATH_AGGR_MINPLEN) ? \ (ATH_AGGR_MINPLEN - (_len) - ATH_AGGR_DELIM_SZ) : 0) >> 2) #define PADBYTES(_len) ((4 - ((_len) % 4)) % 4) int ath_max_4ms_framelen[4][32] = { [MCS_HT20] = { 3212, 6432, 9648, 12864, 19300, 25736, 28952, 32172, 6424, 12852, 19280, 25708, 38568, 51424, 57852, 64280, 9628, 19260, 28896, 38528, 57792, 65532, 65532, 65532, 12828, 25656, 38488, 51320, 65532, 65532, 65532, 65532, }, [MCS_HT20_SGI] = { 3572, 7144, 10720, 14296, 21444, 28596, 32172, 35744, 7140, 14284, 21428, 28568, 42856, 57144, 64288, 65532, 10700, 21408, 32112, 42816, 64228, 65532, 65532, 65532, 14256, 28516, 42780, 57040, 65532, 65532, 65532, 65532, }, [MCS_HT40] = { 6680, 13360, 20044, 26724, 40092, 53456, 60140, 65532, 13348, 26700, 40052, 53400, 65532, 65532, 65532, 65532, 20004, 40008, 60016, 65532, 65532, 65532, 65532, 65532, 26644, 53292, 65532, 65532, 65532, 65532, 65532, 65532, }, [MCS_HT40_SGI] = { 7420, 14844, 22272, 29696, 44544, 59396, 65532, 65532, 14832, 29668, 44504, 59340, 65532, 65532, 65532, 65532, 22232, 44464, 65532, 65532, 65532, 65532, 65532, 65532, 29616, 59232, 65532, 65532, 65532, 65532, 65532, 65532, } }; /* * XXX should be in net80211 */ static int ieee80211_mpdudensity_map[] = { 0, /* IEEE80211_HTCAP_MPDUDENSITY_NA */ 25, /* IEEE80211_HTCAP_MPDUDENSITY_025 */ 50, /* IEEE80211_HTCAP_MPDUDENSITY_05 */ 100, /* IEEE80211_HTCAP_MPDUDENSITY_1 */ 200, /* IEEE80211_HTCAP_MPDUDENSITY_2 */ 400, /* IEEE80211_HTCAP_MPDUDENSITY_4 */ 800, /* IEEE80211_HTCAP_MPDUDENSITY_8 */ 1600, /* IEEE80211_HTCAP_MPDUDENSITY_16 */ }; /* * XXX should be in the HAL/net80211 ? */ #define BITS_PER_BYTE 8 #define OFDM_PLCP_BITS 22 #define HT_RC_2_MCS(_rc) ((_rc) & 0x7f) #define HT_RC_2_STREAMS(_rc) ((((_rc) & 0x78) >> 3) + 1) #define L_STF 8 #define L_LTF 8 #define L_SIG 4 #define HT_SIG 8 #define HT_STF 4 #define HT_LTF(_ns) (4 * (_ns)) #define SYMBOL_TIME(_ns) ((_ns) << 2) // ns * 4 us #define SYMBOL_TIME_HALFGI(_ns) (((_ns) * 18 + 4) / 5) // ns * 3.6 us #define NUM_SYMBOLS_PER_USEC(_usec) (_usec >> 2) #define NUM_SYMBOLS_PER_USEC_HALFGI(_usec) (((_usec*5)-4)/18) #define IS_HT_RATE(_rate) ((_rate) & 0x80) const uint32_t bits_per_symbol[][2] = { /* 20MHz 40MHz */ { 26, 54 }, // 0: BPSK { 52, 108 }, // 1: QPSK 1/2 { 78, 162 }, // 2: QPSK 3/4 { 104, 216 }, // 3: 16-QAM 1/2 { 156, 324 }, // 4: 16-QAM 3/4 { 208, 432 }, // 5: 64-QAM 2/3 { 234, 486 }, // 6: 64-QAM 3/4 { 260, 540 }, // 7: 64-QAM 5/6 { 52, 108 }, // 8: BPSK { 104, 216 }, // 9: QPSK 1/2 { 156, 324 }, // 10: QPSK 3/4 { 208, 432 }, // 11: 16-QAM 1/2 { 312, 648 }, // 12: 16-QAM 3/4 { 416, 864 }, // 13: 64-QAM 2/3 { 468, 972 }, // 14: 64-QAM 3/4 { 520, 1080 }, // 15: 64-QAM 5/6 { 78, 162 }, // 16: BPSK { 156, 324 }, // 17: QPSK 1/2 { 234, 486 }, // 18: QPSK 3/4 { 312, 648 }, // 19: 16-QAM 1/2 { 468, 972 }, // 20: 16-QAM 3/4 { 624, 1296 }, // 21: 64-QAM 2/3 { 702, 1458 }, // 22: 64-QAM 3/4 { 780, 1620 }, // 23: 64-QAM 5/6 { 104, 216 }, // 24: BPSK { 208, 432 }, // 25: QPSK 1/2 { 312, 648 }, // 26: QPSK 3/4 { 416, 864 }, // 27: 16-QAM 1/2 { 624, 1296 }, // 28: 16-QAM 3/4 { 832, 1728 }, // 29: 64-QAM 2/3 { 936, 1944 }, // 30: 64-QAM 3/4 { 1040, 2160 }, // 31: 64-QAM 5/6 }; /* * Fill in the rate array information based on the current * node configuration and the choices made by the rate * selection code and ath_buf setup code. * * Later on, this may end up also being made by the * rate control code, but for now it can live here. * * This needs to be called just before the packet is * queued to the software queue or hardware queue, * so all of the needed fields in bf_state are setup. */ void ath_tx_rate_fill_rcflags(struct ath_softc *sc, struct ath_buf *bf) { struct ieee80211_node *ni = bf->bf_node; struct ieee80211com *ic = ni->ni_ic; const HAL_RATE_TABLE *rt = sc->sc_currates; struct ath_rc_series *rc = bf->bf_state.bfs_rc; uint8_t rate; int i; int do_ldpc; int do_stbc; /* * We only do LDPC if the rate is 11n, both we and the * receiver support LDPC and it's enabled. * * It's a global flag, not a per-try flag, so we clear * it if any of the rate entries aren't 11n. */ do_ldpc = 0; if ((ni->ni_vap->iv_flags_ht & IEEE80211_FHT_LDPC_TX) && (ni->ni_htcap & IEEE80211_HTCAP_LDPC)) do_ldpc = 1; /* * The 11n duration calculation doesn't know about LDPC, * so don't enable it for positioning. */ if (bf->bf_flags & ATH_BUF_TOA_PROBE) do_ldpc = 0; do_stbc = 0; for (i = 0; i < ATH_RC_NUM; i++) { rc[i].flags = 0; if (rc[i].tries == 0) continue; rate = rt->info[rc[i].rix].rateCode; /* * Only enable short preamble for legacy rates */ if ((! IS_HT_RATE(rate)) && bf->bf_state.bfs_shpream) rate |= rt->info[rc[i].rix].shortPreamble; /* * Save this, used by the TX and completion code */ rc[i].ratecode = rate; if (bf->bf_state.bfs_txflags & (HAL_TXDESC_RTSENA | HAL_TXDESC_CTSENA)) rc[i].flags |= ATH_RC_RTSCTS_FLAG; /* * If we can't do LDPC, don't. */ if (! IS_HT_RATE(rate)) do_ldpc = 0; /* Only enable shortgi, 2040, dual-stream if HT is set */ if (IS_HT_RATE(rate)) { rc[i].flags |= ATH_RC_HT_FLAG; - if (ni->ni_chw == 40) + if (ni->ni_chw == IEEE80211_STA_RX_BW_40) rc[i].flags |= ATH_RC_CW40_FLAG; /* * NOTE: Don't do short-gi for positioning frames. * * For now, the ath_hal and net80211 HT duration * calculation rounds up the 11n data txtime * to the nearest multiple of 3.6 microseconds * and doesn't return the fractional part, so * we are always "out" by some amount. */ - if (ni->ni_chw == 40 && + if (ni->ni_chw == IEEE80211_STA_RX_BW_40 && ieee80211_ht_check_tx_shortgi_40(ni) && (bf->bf_flags & ATH_BUF_TOA_PROBE) == 0) { rc[i].flags |= ATH_RC_SGI_FLAG; } - if (ni->ni_chw == 20 && + if (ni->ni_chw == IEEE80211_STA_RX_BW_40 && ieee80211_ht_check_tx_shortgi_20(ni) && (bf->bf_flags & ATH_BUF_TOA_PROBE) == 0) { rc[i].flags |= ATH_RC_SGI_FLAG; } /* * If we have STBC TX enabled and the receiver * can receive (at least) 1 stream STBC, AND it's * MCS 0-7, AND we have at least two chains enabled, * and we're not doing positioning, enable STBC. */ if (ic->ic_htcaps & IEEE80211_HTCAP_TXSTBC && (ni->ni_vap->iv_flags_ht & IEEE80211_FHT_STBC_TX) && (ni->ni_htcap & IEEE80211_HTCAP_RXSTBC) && (sc->sc_cur_txchainmask > 1) && (HT_RC_2_STREAMS(rate) == 1) && (bf->bf_flags & ATH_BUF_TOA_PROBE) == 0) { rc[i].flags |= ATH_RC_STBC_FLAG; do_stbc = 1; } /* * Dual / Triple stream rate? */ if (HT_RC_2_STREAMS(rate) == 2) rc[i].flags |= ATH_RC_DS_FLAG; else if (HT_RC_2_STREAMS(rate) == 3) rc[i].flags |= ATH_RC_TS_FLAG; } /* * Calculate the maximum TX power cap for the current * node. */ rc[i].tx_power_cap = ieee80211_get_node_txpower(ni); /* * Calculate the maximum 4ms frame length based * on the MCS rate, SGI and channel width flags. */ if ((rc[i].flags & ATH_RC_HT_FLAG) && (HT_RC_2_MCS(rate) < 32)) { int j; if (rc[i].flags & ATH_RC_CW40_FLAG) { if (rc[i].flags & ATH_RC_SGI_FLAG) j = MCS_HT40_SGI; else j = MCS_HT40; } else { if (rc[i].flags & ATH_RC_SGI_FLAG) j = MCS_HT20_SGI; else j = MCS_HT20; } rc[i].max4msframelen = ath_max_4ms_framelen[j][HT_RC_2_MCS(rate)]; } else rc[i].max4msframelen = 0; DPRINTF(sc, ATH_DEBUG_SW_TX_AGGR, "%s: i=%d, rate=0x%x, flags=0x%x, max4ms=%d\n", __func__, i, rate, rc[i].flags, rc[i].max4msframelen); } /* * LDPC is a global flag, so ... */ if (do_ldpc) { bf->bf_state.bfs_txflags |= HAL_TXDESC_LDPC; sc->sc_stats.ast_tx_ldpc++; } if (do_stbc) { sc->sc_stats.ast_tx_stbc++; } } /* * Return the number of delimiters to be added to * meet the minimum required mpdudensity. * * Caller should make sure that the rate is HT. * * TODO: is this delimiter calculation supposed to be the * total frame length, the hdr length, the data length (including * delimiters, padding, CRC, etc) or ? * * TODO: this should ensure that the rate control information * HAS been setup for the first rate. * * TODO: ensure this is only called for MCS rates. * * TODO: enforce MCS < 31 */ static int ath_compute_num_delims(struct ath_softc *sc, struct ath_buf *first_bf, uint16_t pktlen, int is_first) { const HAL_RATE_TABLE *rt = sc->sc_currates; struct ieee80211_node *ni = first_bf->bf_node; int ndelim, mindelim = 0; int mpdudensity; /* in 1/100'th of a microsecond */ int peer_mpdudensity; /* net80211 value */ uint8_t rc, rix, flags; int width, half_gi; uint32_t nsymbits, nsymbols; uint16_t minlen; /* * Get the advertised density from the node. */ peer_mpdudensity = ieee80211_ht_get_node_ampdu_density(ni); /* * Convert the A-MPDU density net80211 value to a 1/100 microsecond * value for subsequent calculations. */ if (peer_mpdudensity > IEEE80211_HTCAP_MPDUDENSITY_16) mpdudensity = 1600; /* maximum density */ else mpdudensity = ieee80211_mpdudensity_map[peer_mpdudensity]; /* Select standard number of delimiters based on frame length */ ndelim = ATH_AGGR_GET_NDELIM(pktlen); /* * If encryption is enabled, add extra delimiters to let the * crypto hardware catch up. This could be tuned per-MAC and * per-rate, but for now we'll simply assume encryption is * always enabled. * * Also note that the Atheros reference driver inserts two * delimiters by default for pre-AR9380 peers. This will * include "that" required delimiter. */ ndelim += ATH_AGGR_ENCRYPTDELIM; /* * For AR9380, there's a minimum number of delimiters * required when doing RTS. * * XXX TODO: this is only needed if (a) RTS/CTS is enabled for * this exchange, and (b) (done) this is the first sub-frame * in the aggregate. */ if (sc->sc_use_ent && (sc->sc_ent_cfg & AH_ENT_RTSCTS_DELIM_WAR) && ndelim < AH_FIRST_DESC_NDELIMS && is_first) ndelim = AH_FIRST_DESC_NDELIMS; /* * If sc_delim_min_pad is non-zero, enforce it as the minimum * pad delimiter count. */ if (sc->sc_delim_min_pad != 0) ndelim = MAX(ndelim, sc->sc_delim_min_pad); DPRINTF(sc, ATH_DEBUG_SW_TX_AGGR, "%s: pktlen=%d, ndelim=%d, mpdudensity=%d\n", __func__, pktlen, ndelim, mpdudensity); /* * If the MPDU density is 0, we can return here. * Otherwise, we need to convert the desired mpdudensity * into a byte length, based on the rate in the subframe. */ if (mpdudensity == 0) return ndelim; /* * Convert desired mpdu density from microeconds to bytes based * on highest rate in rate series (i.e. first rate) to determine * required minimum length for subframe. Take into account * whether high rate is 20 or 40Mhz and half or full GI. */ rix = first_bf->bf_state.bfs_rc[0].rix; rc = rt->info[rix].rateCode; flags = first_bf->bf_state.bfs_rc[0].flags; width = !! (flags & ATH_RC_CW40_FLAG); half_gi = !! (flags & ATH_RC_SGI_FLAG); /* * mpdudensity is in 1/100th of a usec, so divide by 100 */ if (half_gi) nsymbols = NUM_SYMBOLS_PER_USEC_HALFGI(mpdudensity); else nsymbols = NUM_SYMBOLS_PER_USEC(mpdudensity); nsymbols /= 100; if (nsymbols == 0) nsymbols = 1; nsymbits = bits_per_symbol[HT_RC_2_MCS(rc)][width]; minlen = (nsymbols * nsymbits) / BITS_PER_BYTE; /* * Min length is the minimum frame length for the * required MPDU density. */ if (pktlen < minlen) { mindelim = (minlen - pktlen) / ATH_AGGR_DELIM_SZ; ndelim = MAX(mindelim, ndelim); } DPRINTF(sc, ATH_DEBUG_SW_TX_AGGR, "%s: pktlen=%d, minlen=%d, rix=%x, rc=%x, width=%d, hgi=%d, ndelim=%d\n", __func__, pktlen, minlen, rix, rc, width, half_gi, ndelim); return ndelim; } /* * XXX TODO: put into net80211 */ static int ath_rx_ampdu_to_byte(char a) { switch (a) { case IEEE80211_HTCAP_MAXRXAMPDU_16K: return 16384; break; case IEEE80211_HTCAP_MAXRXAMPDU_32K: return 32768; break; case IEEE80211_HTCAP_MAXRXAMPDU_64K: return 65536; break; case IEEE80211_HTCAP_MAXRXAMPDU_8K: default: return 8192; break; } } /* * Fetch the aggregation limit. * * It's the lowest of the four rate series 4ms frame length. * * Also take into account the hardware specific limits (8KiB on AR5416) * and per-peer limits in non-STA mode. */ static int ath_get_aggr_limit(struct ath_softc *sc, struct ieee80211_node *ni, struct ath_buf *bf) { int amin = ATH_AGGR_MAXSIZE; int i; /* Extract out the maximum configured driver A-MPDU limit */ if (sc->sc_aggr_limit > 0 && sc->sc_aggr_limit < ATH_AGGR_MAXSIZE) amin = sc->sc_aggr_limit; /* Check the vap and node configured transmit limit */ amin = MIN(amin, ath_rx_ampdu_to_byte(ieee80211_ht_get_node_ampdu_limit(ni))); for (i = 0; i < ATH_RC_NUM; i++) { if (bf->bf_state.bfs_rc[i].tries == 0) continue; amin = MIN(amin, bf->bf_state.bfs_rc[i].max4msframelen); } DPRINTF(sc, ATH_DEBUG_SW_TX_AGGR, "%s: aggr_limit=%d, iv_ampdu_limit=%d, " "peer maxrxampdu=%d, max frame len=%d\n", __func__, sc->sc_aggr_limit, ni->ni_vap->iv_ampdu_limit, _IEEE80211_MASKSHIFT(ni->ni_htparam, IEEE80211_HTCAP_MAXRXAMPDU), amin); return amin; } /* * Setup a 11n rate series structure * * This should be called for both legacy and MCS rates. * * This uses the rate series stuf from ath_tx_rate_fill_rcflags(). * * It, along with ath_buf_set_rate, must be called -after- a burst * or aggregate is setup. */ static void ath_rateseries_setup(struct ath_softc *sc, struct ieee80211_node *ni, struct ath_buf *bf, HAL_11N_RATE_SERIES *series) { struct ieee80211com *ic = ni->ni_ic; struct ath_hal *ah = sc->sc_ah; HAL_BOOL shortPreamble = AH_FALSE; const HAL_RATE_TABLE *rt = sc->sc_currates; int i; int pktlen; struct ath_rc_series *rc = bf->bf_state.bfs_rc; if ((ic->ic_flags & IEEE80211_F_SHPREAMBLE) && (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE)) shortPreamble = AH_TRUE; /* * If this is the first frame in an aggregate series, * use the aggregate length. */ if (bf->bf_state.bfs_aggr) pktlen = bf->bf_state.bfs_al; else pktlen = bf->bf_state.bfs_pktlen; /* * XXX TODO: modify this routine to use the bfs_rc[x].flags * XXX fields. */ memset(series, 0, sizeof(HAL_11N_RATE_SERIES) * 4); for (i = 0; i < ATH_RC_NUM; i++) { /* Only set flags for actual TX attempts */ if (rc[i].tries == 0) continue; series[i].Tries = rc[i].tries; /* * XXX TODO: When the NIC is capable of three stream TX, * transmit 1/2 stream rates on two streams. * * This reduces the power consumption of the NIC and * keeps it within the PCIe slot power limits. */ series[i].ChSel = sc->sc_cur_txchainmask; /* * Setup rate and TX power cap for this series. */ series[i].Rate = rt->info[rc[i].rix].rateCode; series[i].RateIndex = rc[i].rix; series[i].tx_power_cap = rc[i].tx_power_cap; /* * Enable RTS/CTS as appropriate. */ if (rc[i].flags & ATH_RC_RTSCTS_FLAG) series[i].RateFlags |= HAL_RATESERIES_RTS_CTS; /* * 11n rate? Update 11n flags. */ if (rc[i].flags & ATH_RC_HT_FLAG) { if (rc[i].flags & ATH_RC_CW40_FLAG) series[i].RateFlags |= HAL_RATESERIES_2040; if (rc[i].flags & ATH_RC_SGI_FLAG) series[i].RateFlags |= HAL_RATESERIES_HALFGI; if (rc[i].flags & ATH_RC_STBC_FLAG) series[i].RateFlags |= HAL_RATESERIES_STBC; } /* * TODO: If we're all doing 11n rates then we can set LDPC. * If we've been asked to /do/ LDPC but we are handed a * legacy rate, then we should complain. Loudly. */ /* * PktDuration doesn't include slot, ACK, RTS, etc timing - * it's just the packet duration */ if (rc[i].flags & ATH_RC_HT_FLAG) { series[i].PktDuration = ath_computedur_ht(pktlen , series[i].Rate , HT_RC_2_STREAMS(series[i].Rate) , series[i].RateFlags & HAL_RATESERIES_2040 , series[i].RateFlags & HAL_RATESERIES_HALFGI); } else { if (shortPreamble) series[i].Rate |= rt->info[rc[i].rix].shortPreamble; /* XXX TODO: don't include SIFS */ series[i].PktDuration = ath_hal_computetxtime(ah, rt, pktlen, rc[i].rix, shortPreamble, AH_TRUE); } } } #ifdef ATH_DEBUG static void ath_rateseries_print(struct ath_softc *sc, HAL_11N_RATE_SERIES *series) { int i; for (i = 0; i < ATH_RC_NUM; i++) { device_printf(sc->sc_dev ,"series %d: rate %x; tries %d; " "pktDuration %d; chSel %d; txpowcap %d, rateFlags %x\n", i, series[i].Rate, series[i].Tries, series[i].PktDuration, series[i].ChSel, series[i].tx_power_cap, series[i].RateFlags); } } #endif /* * Setup the 11n rate scenario and burst duration for the given TX descriptor * list. * * This isn't useful for sending beacon frames, which has different needs * wrt what's passed into the rate scenario function. */ void ath_buf_set_rate(struct ath_softc *sc, struct ieee80211_node *ni, struct ath_buf *bf) { HAL_11N_RATE_SERIES series[4]; struct ath_desc *ds = bf->bf_desc; struct ath_hal *ah = sc->sc_ah; int is_pspoll = (bf->bf_state.bfs_atype == HAL_PKT_TYPE_PSPOLL); int ctsrate = bf->bf_state.bfs_ctsrate; int flags = bf->bf_state.bfs_txflags; /* Setup rate scenario */ memset(&series, 0, sizeof(series)); ath_rateseries_setup(sc, ni, bf, series); #ifdef ATH_DEBUG if (sc->sc_debug & ATH_DEBUG_XMIT) ath_rateseries_print(sc, series); #endif /* Set rate scenario */ /* * Note: Don't allow hardware to override the duration on * ps-poll packets. */ ath_hal_set11nratescenario(ah, ds, !is_pspoll, /* whether to override the duration or not */ ctsrate, /* rts/cts rate */ series, /* 11n rate series */ 4, /* number of series */ flags); /* Set burst duration */ /* * This is only required when doing 11n burst, not aggregation * ie, if there's a second frame in a RIFS or A-MPDU burst * w/ >1 A-MPDU frame bursting back to back. * Normal A-MPDU doesn't do bursting -between- aggregates. * * .. and it's highly likely this won't ever be implemented */ //ath_hal_set11nburstduration(ah, ds, 8192); } /* * Form an aggregate packet list. * * This function enforces the aggregate restrictions/requirements. * * These are: * * + The aggregate size maximum (64k for AR9160 and later, 8K for * AR5416 when doing RTS frame protection.) * + Maximum number of sub-frames for an aggregate * + The aggregate delimiter size, giving MACs time to do whatever is * needed before each frame * + Enforce the BAW limit * * Each descriptor queued should have the DMA setup. * The rate series, descriptor setup, linking, etc is all done * externally. This routine simply chains them together. * ath_tx_setds_11n() will take care of configuring the per- * descriptor setup, and ath_buf_set_rate() will configure the * rate control. * * The TID lock is required for the entirety of this function. * * If some code in another thread adds to the head of this * list, very strange behaviour will occur. Since retransmission is the * only reason this will occur, and this routine is designed to be called * from within the scheduler task, it won't ever clash with the completion * task. * * So if you want to call this from an upper layer context (eg, to direct- * dispatch aggregate frames to the hardware), please keep this in mind. */ ATH_AGGR_STATUS ath_tx_form_aggr(struct ath_softc *sc, struct ath_node *an, struct ath_tid *tid, ath_bufhead *bf_q) { //struct ieee80211_node *ni = &an->an_node; struct ath_buf *bf, *bf_first = NULL, *bf_prev = NULL; int nframes = 0; uint16_t aggr_limit = 0, al = 0, bpad = 0, al_delta, h_baw; struct ieee80211_tx_ampdu *tap; int status = ATH_AGGR_DONE; int prev_frames = 0; /* XXX for AR5416 burst, not done here */ int prev_al = 0; /* XXX also for AR5416 burst */ ATH_TX_LOCK_ASSERT(sc); tap = ath_tx_get_tx_tid(an, tid->tid); if (tap == NULL) { status = ATH_AGGR_ERROR; goto finish; } /* * Limit the maximum number of frames in this A-MPDU * to half of the window size. This is done to prevent * sending a LOT of frames that may fail in one batch * when operating in higher MCS rates. If there are more * frames available to send then up to two A-MPDUs will * be queued per hardware queue, so we'll "just" get * a second A-MPDU. */ h_baw = tap->txa_wnd / 2; for (;;) { bf = ATH_TID_FIRST(tid); if (bf == NULL) { status = ATH_AGGR_DONE; break; } if (bf_first == NULL) { bf_first = bf; /* * It's the first frame; * set the aggregation limit based on the * rate control decision that has been made. */ aggr_limit = ath_get_aggr_limit(sc, &an->an_node, bf_first); if (bf_first->bf_state.bfs_rc_maxpktlen > 0) { aggr_limit = MIN(aggr_limit, bf_first->bf_state.bfs_rc_maxpktlen); } } /* Set this early just so things don't get confused */ bf->bf_next = NULL; /* * If the frame doesn't have a sequence number that we're * tracking in the BAW (eg NULL QOS data frame), we can't * aggregate it. Stop the aggregation process; the sender * can then TX what's in the list thus far and then * TX the frame individually. */ if (! bf->bf_state.bfs_dobaw) { status = ATH_AGGR_NONAGGR; break; } /* * If any of the rates are non-HT, this packet * can't be aggregated. * XXX TODO: add a bf_state flag which gets marked * if any active rate is non-HT. */ /* * do not exceed aggregation limit */ al_delta = ATH_AGGR_DELIM_SZ + bf->bf_state.bfs_pktlen; if (nframes && (aggr_limit < (al + bpad + al_delta + prev_al))) { status = ATH_AGGR_LIMITED; break; } /* * If RTS/CTS is set on the first frame, enforce * the RTS aggregate limit. */ if (bf_first->bf_state.bfs_txflags & (HAL_TXDESC_CTSENA | HAL_TXDESC_RTSENA)) { if (nframes && (sc->sc_rts_aggr_limit < (al + bpad + al_delta + prev_al))) { status = ATH_AGGR_8K_LIMITED; break; } } /* * Do not exceed subframe limit. */ if ((nframes + prev_frames) >= MIN((h_baw), IEEE80211_AMPDU_SUBFRAME_DEFAULT)) { status = ATH_AGGR_LIMITED; break; } /* * If the current frame has an RTS/CTS configuration * that differs from the first frame, override the * subsequent frame with this config. */ if (bf != bf_first) { bf->bf_state.bfs_txflags &= ~ (HAL_TXDESC_RTSENA | HAL_TXDESC_CTSENA); bf->bf_state.bfs_txflags |= bf_first->bf_state.bfs_txflags & (HAL_TXDESC_RTSENA | HAL_TXDESC_CTSENA); } /* * If the packet has a sequence number, do not * step outside of the block-ack window. */ if (! BAW_WITHIN(tap->txa_start, tap->txa_wnd, SEQNO(bf->bf_state.bfs_seqno))) { status = ATH_AGGR_BAW_CLOSED; break; } /* * this packet is part of an aggregate. */ ATH_TID_REMOVE(tid, bf, bf_list); /* The TID lock is required for the BAW update */ ath_tx_addto_baw(sc, an, tid, bf); bf->bf_state.bfs_addedbaw = 1; /* * XXX enforce ACK for aggregate frames (this needs to be * XXX handled more gracefully? */ if (bf->bf_state.bfs_txflags & HAL_TXDESC_NOACK) { device_printf(sc->sc_dev, "%s: HAL_TXDESC_NOACK set for an aggregate frame?\n", __func__); bf->bf_state.bfs_txflags &= (~HAL_TXDESC_NOACK); } /* * Add the now owned buffer (which isn't * on the software TXQ any longer) to our * aggregate frame list. */ TAILQ_INSERT_TAIL(bf_q, bf, bf_list); nframes ++; /* Completion handler */ bf->bf_comp = ath_tx_aggr_comp; /* * add padding for previous frame to aggregation length */ al += bpad + al_delta; /* * Calculate delimiters needed for the current frame */ bf->bf_state.bfs_ndelim = ath_compute_num_delims(sc, bf_first, bf->bf_state.bfs_pktlen, (bf_first == bf)); /* * Calculate the padding needed from this set of delimiters, * used when calculating if the next frame will fit in * the aggregate. */ bpad = PADBYTES(al_delta) + (bf->bf_state.bfs_ndelim << 2); /* * Chain the buffers together */ if (bf_prev) bf_prev->bf_next = bf; bf_prev = bf; /* * If we're leaking frames, just return at this point; * we've queued a single frame and we don't want to add * any more. */ if (tid->an->an_leak_count) { status = ATH_AGGR_LEAK_CLOSED; break; } #if 0 /* * terminate aggregation on a small packet boundary */ if (bf->bf_state.bfs_pktlen < ATH_AGGR_MINPLEN) { status = ATH_AGGR_SHORTPKT; break; } #endif } finish: /* * Just in case the list was empty when we tried to * dequeue a packet .. */ if (bf_first) { DPRINTF(sc, ATH_DEBUG_SW_TX_AGGR, "%s: al=%d bytes; requested %d bytes\n", __func__, al, bf_first->bf_state.bfs_rc_maxpktlen); bf_first->bf_state.bfs_al = al; bf_first->bf_state.bfs_nframes = nframes; } return status; } diff --git a/sys/dev/mwl/if_mwl.c b/sys/dev/mwl/if_mwl.c index f396ef7256f4..d0d13e576d31 100644 --- a/sys/dev/mwl/if_mwl.c +++ b/sys/dev/mwl/if_mwl.c @@ -1,4818 +1,4818 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2007-2009 Sam Leffler, Errno Consulting * Copyright (c) 2007-2008 Marvell Semiconductor, Inc. * 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, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * * NO WARRANTY * 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 NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. */ #include /* * Driver for the Marvell 88W8363 Wireless LAN controller. */ #include "opt_inet.h" #include "opt_mwl.h" #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 #ifdef INET #include #include #endif /* INET */ #include #include static struct ieee80211vap *mwl_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 mwl_vap_delete(struct ieee80211vap *); static int mwl_setupdma(struct mwl_softc *); static int mwl_hal_reset(struct mwl_softc *sc); static int mwl_init(struct mwl_softc *); static void mwl_parent(struct ieee80211com *); static int mwl_reset(struct ieee80211vap *, u_long); static void mwl_stop(struct mwl_softc *); static void mwl_start(struct mwl_softc *); static int mwl_transmit(struct ieee80211com *, struct mbuf *); static int mwl_raw_xmit(struct ieee80211_node *, struct mbuf *, const struct ieee80211_bpf_params *); static int mwl_media_change(if_t); static void mwl_watchdog(void *); static int mwl_ioctl(struct ieee80211com *, u_long, void *); static void mwl_radar_proc(void *, int); static void mwl_chanswitch_proc(void *, int); static void mwl_bawatchdog_proc(void *, int); static int mwl_key_alloc(struct ieee80211vap *, struct ieee80211_key *, ieee80211_keyix *, ieee80211_keyix *); static int mwl_key_delete(struct ieee80211vap *, const struct ieee80211_key *); static int mwl_key_set(struct ieee80211vap *, const struct ieee80211_key *); static int _mwl_key_set(struct ieee80211vap *, const struct ieee80211_key *, const uint8_t mac[IEEE80211_ADDR_LEN]); static int mwl_mode_init(struct mwl_softc *); static void mwl_update_mcast(struct ieee80211com *); static void mwl_update_promisc(struct ieee80211com *); static void mwl_updateslot(struct ieee80211com *); static int mwl_beacon_setup(struct ieee80211vap *); static void mwl_beacon_update(struct ieee80211vap *, int); #ifdef MWL_HOST_PS_SUPPORT static void mwl_update_ps(struct ieee80211vap *, int); static int mwl_set_tim(struct ieee80211_node *, int); #endif static int mwl_dma_setup(struct mwl_softc *); static void mwl_dma_cleanup(struct mwl_softc *); static struct ieee80211_node *mwl_node_alloc(struct ieee80211vap *, const uint8_t [IEEE80211_ADDR_LEN]); static void mwl_node_cleanup(struct ieee80211_node *); static void mwl_node_drain(struct ieee80211_node *); static void mwl_node_getsignal(const struct ieee80211_node *, int8_t *, int8_t *); static void mwl_node_getmimoinfo(const struct ieee80211_node *, struct ieee80211_mimo_info *); static int mwl_rxbuf_init(struct mwl_softc *, struct mwl_rxbuf *); static void mwl_rx_proc(void *, int); static void mwl_txq_init(struct mwl_softc *sc, struct mwl_txq *, int); static int mwl_tx_setup(struct mwl_softc *, int, int); static int mwl_wme_update(struct ieee80211com *); static void mwl_tx_cleanupq(struct mwl_softc *, struct mwl_txq *); static void mwl_tx_cleanup(struct mwl_softc *); static uint16_t mwl_calcformat(uint8_t rate, const struct ieee80211_node *); static int mwl_tx_start(struct mwl_softc *, struct ieee80211_node *, struct mwl_txbuf *, struct mbuf *); static void mwl_tx_proc(void *, int); static int mwl_chan_set(struct mwl_softc *, struct ieee80211_channel *); static void mwl_draintxq(struct mwl_softc *); static void mwl_cleartxq(struct mwl_softc *, struct ieee80211vap *); static int mwl_recv_action(struct ieee80211_node *, const struct ieee80211_frame *, const uint8_t *, const uint8_t *); static int mwl_addba_request(struct ieee80211_node *, struct ieee80211_tx_ampdu *, int dialogtoken, int baparamset, int batimeout); static int mwl_addba_response(struct ieee80211_node *, struct ieee80211_tx_ampdu *, int status, int baparamset, int batimeout); static void mwl_addba_stop(struct ieee80211_node *, struct ieee80211_tx_ampdu *); static int mwl_startrecv(struct mwl_softc *); static MWL_HAL_APMODE mwl_getapmode(const struct ieee80211vap *, struct ieee80211_channel *); static int mwl_setapmode(struct ieee80211vap *, struct ieee80211_channel*); static void mwl_scan_start(struct ieee80211com *); static void mwl_scan_end(struct ieee80211com *); static void mwl_set_channel(struct ieee80211com *); static int mwl_peerstadb(struct ieee80211_node *, int aid, int staid, MWL_HAL_PEERINFO *pi); static int mwl_localstadb(struct ieee80211vap *); static int mwl_newstate(struct ieee80211vap *, enum ieee80211_state, int); static int allocstaid(struct mwl_softc *sc, int aid); static void delstaid(struct mwl_softc *sc, int staid); static void mwl_newassoc(struct ieee80211_node *, int); static void mwl_agestations(void *); static int mwl_setregdomain(struct ieee80211com *, struct ieee80211_regdomain *, int, struct ieee80211_channel []); static void mwl_getradiocaps(struct ieee80211com *, int, int *, struct ieee80211_channel []); static int mwl_getchannels(struct mwl_softc *); static void mwl_sysctlattach(struct mwl_softc *); static void mwl_announce(struct mwl_softc *); SYSCTL_NODE(_hw, OID_AUTO, mwl, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "Marvell driver parameters"); static int mwl_rxdesc = MWL_RXDESC; /* # rx desc's to allocate */ SYSCTL_INT(_hw_mwl, OID_AUTO, rxdesc, CTLFLAG_RW, &mwl_rxdesc, 0, "rx descriptors allocated"); static int mwl_rxbuf = MWL_RXBUF; /* # rx buffers to allocate */ SYSCTL_INT(_hw_mwl, OID_AUTO, rxbuf, CTLFLAG_RWTUN, &mwl_rxbuf, 0, "rx buffers allocated"); static int mwl_txbuf = MWL_TXBUF; /* # tx buffers to allocate */ SYSCTL_INT(_hw_mwl, OID_AUTO, txbuf, CTLFLAG_RWTUN, &mwl_txbuf, 0, "tx buffers allocated"); static int mwl_txcoalesce = 8; /* # tx packets to q before poking f/w*/ SYSCTL_INT(_hw_mwl, OID_AUTO, txcoalesce, CTLFLAG_RWTUN, &mwl_txcoalesce, 0, "tx buffers to send at once"); static int mwl_rxquota = MWL_RXBUF; /* # max buffers to process */ SYSCTL_INT(_hw_mwl, OID_AUTO, rxquota, CTLFLAG_RWTUN, &mwl_rxquota, 0, "max rx buffers to process per interrupt"); static int mwl_rxdmalow = 3; /* # min buffers for wakeup */ SYSCTL_INT(_hw_mwl, OID_AUTO, rxdmalow, CTLFLAG_RWTUN, &mwl_rxdmalow, 0, "min free rx buffers before restarting traffic"); #ifdef MWL_DEBUG static int mwl_debug = 0; SYSCTL_INT(_hw_mwl, OID_AUTO, debug, CTLFLAG_RWTUN, &mwl_debug, 0, "control debugging printfs"); enum { MWL_DEBUG_XMIT = 0x00000001, /* basic xmit operation */ MWL_DEBUG_XMIT_DESC = 0x00000002, /* xmit descriptors */ MWL_DEBUG_RECV = 0x00000004, /* basic recv operation */ MWL_DEBUG_RECV_DESC = 0x00000008, /* recv descriptors */ MWL_DEBUG_RESET = 0x00000010, /* reset processing */ MWL_DEBUG_BEACON = 0x00000020, /* beacon handling */ MWL_DEBUG_INTR = 0x00000040, /* ISR */ MWL_DEBUG_TX_PROC = 0x00000080, /* tx ISR proc */ MWL_DEBUG_RX_PROC = 0x00000100, /* rx ISR proc */ MWL_DEBUG_KEYCACHE = 0x00000200, /* key cache management */ MWL_DEBUG_STATE = 0x00000400, /* 802.11 state transitions */ MWL_DEBUG_NODE = 0x00000800, /* node management */ MWL_DEBUG_RECV_ALL = 0x00001000, /* trace all frames (beacons) */ MWL_DEBUG_TSO = 0x00002000, /* TSO processing */ MWL_DEBUG_AMPDU = 0x00004000, /* BA stream handling */ MWL_DEBUG_ANY = 0xffffffff }; #define IFF_DUMPPKTS_RECV(sc, wh) \ ((sc->sc_debug & MWL_DEBUG_RECV) && \ ((sc->sc_debug & MWL_DEBUG_RECV_ALL) || !IEEE80211_IS_MGMT_BEACON(wh))) #define IFF_DUMPPKTS_XMIT(sc) \ (sc->sc_debug & MWL_DEBUG_XMIT) #define DPRINTF(sc, m, fmt, ...) do { \ if (sc->sc_debug & (m)) \ printf(fmt, __VA_ARGS__); \ } while (0) #define KEYPRINTF(sc, hk, mac) do { \ if (sc->sc_debug & MWL_DEBUG_KEYCACHE) \ mwl_keyprint(sc, __func__, hk, mac); \ } while (0) static void mwl_printrxbuf(const struct mwl_rxbuf *bf, u_int ix); static void mwl_printtxbuf(const struct mwl_txbuf *bf, u_int qnum, u_int ix); #else #define IFF_DUMPPKTS_RECV(sc, wh) 0 #define IFF_DUMPPKTS_XMIT(sc) 0 #define DPRINTF(sc, m, fmt, ...) do { (void )sc; } while (0) #define KEYPRINTF(sc, k, mac) do { (void )sc; } while (0) #endif static MALLOC_DEFINE(M_MWLDEV, "mwldev", "mwl driver dma buffers"); /* * Each packet has fixed front matter: a 2-byte length * of the payload, followed by a 4-address 802.11 header * (regardless of the actual header and always w/o any * QoS header). The payload then follows. */ struct mwltxrec { uint16_t fwlen; struct ieee80211_frame_addr4 wh; } __packed; /* * Read/Write shorthands for accesses to BAR 0. Note * that all BAR 1 operations are done in the "hal" and * there should be no reference to them here. */ #ifdef MWL_DEBUG static __inline uint32_t RD4(struct mwl_softc *sc, bus_size_t off) { return bus_space_read_4(sc->sc_io0t, sc->sc_io0h, off); } #endif static __inline void WR4(struct mwl_softc *sc, bus_size_t off, uint32_t val) { bus_space_write_4(sc->sc_io0t, sc->sc_io0h, off, val); } int mwl_attach(uint16_t devid, struct mwl_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct mwl_hal *mh; int error = 0; DPRINTF(sc, MWL_DEBUG_ANY, "%s: devid 0x%x\n", __func__, devid); /* * Setup the RX free list lock early, so it can be consistently * removed. */ MWL_RXFREE_INIT(sc); mh = mwl_hal_attach(sc->sc_dev, devid, sc->sc_io1h, sc->sc_io1t, sc->sc_dmat); if (mh == NULL) { device_printf(sc->sc_dev, "unable to attach HAL\n"); error = EIO; goto bad; } sc->sc_mh = mh; /* * Load firmware so we can get setup. We arbitrarily * pick station firmware; we'll re-load firmware as * needed so setting up the wrong mode isn't a big deal. */ if (mwl_hal_fwload(mh, NULL) != 0) { device_printf(sc->sc_dev, "unable to setup builtin firmware\n"); error = EIO; goto bad1; } if (mwl_hal_gethwspecs(mh, &sc->sc_hwspecs) != 0) { device_printf(sc->sc_dev, "unable to fetch h/w specs\n"); error = EIO; goto bad1; } error = mwl_getchannels(sc); if (error != 0) goto bad1; sc->sc_txantenna = 0; /* h/w default */ sc->sc_rxantenna = 0; /* h/w default */ sc->sc_invalid = 0; /* ready to go, enable int handling */ sc->sc_ageinterval = MWL_AGEINTERVAL; /* * Allocate tx+rx descriptors and populate the lists. * We immediately push the information to the firmware * as otherwise it gets upset. */ error = mwl_dma_setup(sc); if (error != 0) { device_printf(sc->sc_dev, "failed to setup descriptors: %d\n", error); goto bad1; } error = mwl_setupdma(sc); /* push to firmware */ if (error != 0) /* NB: mwl_setupdma prints msg */ goto bad1; callout_init(&sc->sc_timer, 1); callout_init_mtx(&sc->sc_watchdog, &sc->sc_mtx, 0); mbufq_init(&sc->sc_snd, ifqmaxlen); sc->sc_tq = taskqueue_create("mwl_taskq", M_NOWAIT, taskqueue_thread_enqueue, &sc->sc_tq); taskqueue_start_threads(&sc->sc_tq, 1, PI_NET, "%s taskq", device_get_nameunit(sc->sc_dev)); NET_TASK_INIT(&sc->sc_rxtask, 0, mwl_rx_proc, sc); TASK_INIT(&sc->sc_radartask, 0, mwl_radar_proc, sc); TASK_INIT(&sc->sc_chanswitchtask, 0, mwl_chanswitch_proc, sc); TASK_INIT(&sc->sc_bawatchdogtask, 0, mwl_bawatchdog_proc, sc); /* NB: insure BK queue is the lowest priority h/w queue */ if (!mwl_tx_setup(sc, WME_AC_BK, MWL_WME_AC_BK)) { device_printf(sc->sc_dev, "unable to setup xmit queue for %s traffic!\n", ieee80211_wme_acnames[WME_AC_BK]); error = EIO; goto bad2; } if (!mwl_tx_setup(sc, WME_AC_BE, MWL_WME_AC_BE) || !mwl_tx_setup(sc, WME_AC_VI, MWL_WME_AC_VI) || !mwl_tx_setup(sc, WME_AC_VO, MWL_WME_AC_VO)) { /* * Not enough hardware tx queues to properly do WME; * just punt and assign them all to the same h/w queue. * We could do a better job of this if, for example, * we allocate queues when we switch from station to * AP mode. */ if (sc->sc_ac2q[WME_AC_VI] != NULL) mwl_tx_cleanupq(sc, sc->sc_ac2q[WME_AC_VI]); if (sc->sc_ac2q[WME_AC_BE] != NULL) mwl_tx_cleanupq(sc, sc->sc_ac2q[WME_AC_BE]); sc->sc_ac2q[WME_AC_BE] = sc->sc_ac2q[WME_AC_BK]; sc->sc_ac2q[WME_AC_VI] = sc->sc_ac2q[WME_AC_BK]; sc->sc_ac2q[WME_AC_VO] = sc->sc_ac2q[WME_AC_BK]; } TASK_INIT(&sc->sc_txtask, 0, mwl_tx_proc, sc); ic->ic_softc = sc; ic->ic_name = device_get_nameunit(sc->sc_dev); /* XXX not right but it's not used anywhere important */ ic->ic_phytype = IEEE80211_T_OFDM; ic->ic_opmode = IEEE80211_M_STA; ic->ic_caps = IEEE80211_C_STA /* station mode supported */ | IEEE80211_C_HOSTAP /* hostap mode */ | IEEE80211_C_MONITOR /* monitor mode */ #if 0 | IEEE80211_C_IBSS /* ibss, nee adhoc, mode */ | IEEE80211_C_AHDEMO /* adhoc demo mode */ #endif | IEEE80211_C_MBSS /* mesh point link mode */ | IEEE80211_C_WDS /* WDS supported */ | IEEE80211_C_SHPREAMBLE /* short preamble supported */ | IEEE80211_C_SHSLOT /* short slot time supported */ | IEEE80211_C_WME /* WME/WMM supported */ | IEEE80211_C_BURST /* xmit bursting supported */ | IEEE80211_C_WPA /* capable of WPA1+WPA2 */ | IEEE80211_C_BGSCAN /* capable of bg scanning */ | IEEE80211_C_TXFRAG /* handle tx frags */ | IEEE80211_C_TXPMGT /* capable of txpow mgt */ | IEEE80211_C_DFS /* DFS supported */ ; ic->ic_htcaps = IEEE80211_HTCAP_SMPS_ENA /* SM PS mode enabled */ | IEEE80211_HTCAP_CHWIDTH40 /* 40MHz channel width */ | IEEE80211_HTCAP_SHORTGI20 /* short GI in 20MHz */ | IEEE80211_HTCAP_SHORTGI40 /* short GI in 40MHz */ | IEEE80211_HTCAP_RXSTBC_2STREAM/* 1-2 spatial streams */ #if MWL_AGGR_SIZE == 7935 | IEEE80211_HTCAP_MAXAMSDU_7935 /* max A-MSDU length */ #else | IEEE80211_HTCAP_MAXAMSDU_3839 /* max A-MSDU length */ #endif #if 0 | IEEE80211_HTCAP_PSMP /* PSMP supported */ | IEEE80211_HTCAP_40INTOLERANT /* 40MHz intolerant */ #endif /* s/w capabilities */ | IEEE80211_HTC_HT /* HT operation */ | IEEE80211_HTC_AMPDU /* tx A-MPDU */ | IEEE80211_HTC_AMSDU /* tx A-MSDU */ | IEEE80211_HTC_SMPS /* SMPS available */ ; /* * Mark h/w crypto support. * XXX no way to query h/w support. */ ic->ic_cryptocaps |= IEEE80211_CRYPTO_WEP | IEEE80211_CRYPTO_AES_CCM | IEEE80211_CRYPTO_TKIP | IEEE80211_CRYPTO_TKIPMIC ; /* * Transmit requires space in the packet for a special * format transmit record and optional padding between * this record and the payload. Ask the net80211 layer * to arrange this when encapsulating packets so we can * add it efficiently. */ ic->ic_headroom = sizeof(struct mwltxrec) - sizeof(struct ieee80211_frame); IEEE80211_ADDR_COPY(ic->ic_macaddr, sc->sc_hwspecs.macAddr); /* call MI attach routine. */ ieee80211_ifattach(ic); ic->ic_setregdomain = mwl_setregdomain; ic->ic_getradiocaps = mwl_getradiocaps; /* override default methods */ ic->ic_raw_xmit = mwl_raw_xmit; ic->ic_newassoc = mwl_newassoc; ic->ic_updateslot = mwl_updateslot; ic->ic_update_mcast = mwl_update_mcast; ic->ic_update_promisc = mwl_update_promisc; ic->ic_wme.wme_update = mwl_wme_update; ic->ic_transmit = mwl_transmit; ic->ic_ioctl = mwl_ioctl; ic->ic_parent = mwl_parent; ic->ic_node_alloc = mwl_node_alloc; sc->sc_node_cleanup = ic->ic_node_cleanup; ic->ic_node_cleanup = mwl_node_cleanup; sc->sc_node_drain = ic->ic_node_drain; ic->ic_node_drain = mwl_node_drain; ic->ic_node_getsignal = mwl_node_getsignal; ic->ic_node_getmimoinfo = mwl_node_getmimoinfo; ic->ic_scan_start = mwl_scan_start; ic->ic_scan_end = mwl_scan_end; ic->ic_set_channel = mwl_set_channel; sc->sc_recv_action = ic->ic_recv_action; ic->ic_recv_action = mwl_recv_action; sc->sc_addba_request = ic->ic_addba_request; ic->ic_addba_request = mwl_addba_request; sc->sc_addba_response = ic->ic_addba_response; ic->ic_addba_response = mwl_addba_response; sc->sc_addba_stop = ic->ic_addba_stop; ic->ic_addba_stop = mwl_addba_stop; ic->ic_vap_create = mwl_vap_create; ic->ic_vap_delete = mwl_vap_delete; ieee80211_radiotap_attach(ic, &sc->sc_tx_th.wt_ihdr, sizeof(sc->sc_tx_th), MWL_TX_RADIOTAP_PRESENT, &sc->sc_rx_th.wr_ihdr, sizeof(sc->sc_rx_th), MWL_RX_RADIOTAP_PRESENT); /* * Setup dynamic sysctl's now that country code and * regdomain are available from the hal. */ mwl_sysctlattach(sc); if (bootverbose) ieee80211_announce(ic); mwl_announce(sc); return 0; bad2: mwl_dma_cleanup(sc); bad1: mwl_hal_detach(mh); bad: MWL_RXFREE_DESTROY(sc); sc->sc_invalid = 1; return error; } int mwl_detach(struct mwl_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; MWL_LOCK(sc); mwl_stop(sc); MWL_UNLOCK(sc); /* * NB: the order of these is important: * o call the 802.11 layer before detaching the hal to * insure callbacks into the driver to delete global * key cache entries can be handled * o reclaim the tx queue data structures after calling * the 802.11 layer as we'll get called back to reclaim * node state and potentially want to use them * o to cleanup the tx queues the hal is called, so detach * it last * Other than that, it's straightforward... */ ieee80211_ifdetach(ic); callout_drain(&sc->sc_watchdog); mwl_dma_cleanup(sc); MWL_RXFREE_DESTROY(sc); mwl_tx_cleanup(sc); mwl_hal_detach(sc->sc_mh); mbufq_drain(&sc->sc_snd); return 0; } /* * MAC address handling for multiple BSS on the same radio. * The first vap uses the MAC address from the EEPROM. For * subsequent vap's we set the U/L bit (bit 1) in the MAC * address and use the next six bits as an index. */ static void assign_address(struct mwl_softc *sc, uint8_t mac[IEEE80211_ADDR_LEN], int clone) { int i; if (clone && mwl_hal_ismbsscapable(sc->sc_mh)) { /* NB: we only do this if h/w supports multiple bssid */ for (i = 0; i < 32; i++) if ((sc->sc_bssidmask & (1<sc_bssidmask |= 1<sc_nbssid0++; } static void reclaim_address(struct mwl_softc *sc, const uint8_t mac[IEEE80211_ADDR_LEN]) { int i = mac[0] >> 2; if (i != 0 || --sc->sc_nbssid0 == 0) sc->sc_bssidmask &= ~(1<ic_softc; struct mwl_hal *mh = sc->sc_mh; struct ieee80211vap *vap, *apvap; struct mwl_hal_vap *hvap; struct mwl_vap *mvp; uint8_t mac[IEEE80211_ADDR_LEN]; IEEE80211_ADDR_COPY(mac, mac0); switch (opmode) { case IEEE80211_M_HOSTAP: case IEEE80211_M_MBSS: if ((flags & IEEE80211_CLONE_MACADDR) == 0) assign_address(sc, mac, flags & IEEE80211_CLONE_BSSID); hvap = mwl_hal_newvap(mh, MWL_HAL_AP, mac); if (hvap == NULL) { if ((flags & IEEE80211_CLONE_MACADDR) == 0) reclaim_address(sc, mac); return NULL; } break; case IEEE80211_M_STA: if ((flags & IEEE80211_CLONE_MACADDR) == 0) assign_address(sc, mac, flags & IEEE80211_CLONE_BSSID); hvap = mwl_hal_newvap(mh, MWL_HAL_STA, mac); if (hvap == NULL) { if ((flags & IEEE80211_CLONE_MACADDR) == 0) reclaim_address(sc, mac); return NULL; } /* no h/w beacon miss support; always use s/w */ flags |= IEEE80211_CLONE_NOBEACONS; break; case IEEE80211_M_WDS: hvap = NULL; /* NB: we use associated AP vap */ if (sc->sc_napvaps == 0) return NULL; /* no existing AP vap */ break; case IEEE80211_M_MONITOR: hvap = NULL; break; case IEEE80211_M_IBSS: case IEEE80211_M_AHDEMO: default: return NULL; } mvp = malloc(sizeof(struct mwl_vap), M_80211_VAP, M_WAITOK | M_ZERO); mvp->mv_hvap = hvap; if (opmode == IEEE80211_M_WDS) { /* * WDS vaps must have an associated AP vap; find one. * XXX not right. */ TAILQ_FOREACH(apvap, &ic->ic_vaps, iv_next) if (apvap->iv_opmode == IEEE80211_M_HOSTAP) { mvp->mv_ap_hvap = MWL_VAP(apvap)->mv_hvap; break; } KASSERT(mvp->mv_ap_hvap != NULL, ("no ap vap")); } vap = &mvp->mv_vap; ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid); /* override with driver methods */ mvp->mv_newstate = vap->iv_newstate; vap->iv_newstate = mwl_newstate; vap->iv_max_keyix = 0; /* XXX */ vap->iv_key_alloc = mwl_key_alloc; vap->iv_key_delete = mwl_key_delete; vap->iv_key_set = mwl_key_set; #ifdef MWL_HOST_PS_SUPPORT if (opmode == IEEE80211_M_HOSTAP || opmode == IEEE80211_M_MBSS) { vap->iv_update_ps = mwl_update_ps; mvp->mv_set_tim = vap->iv_set_tim; vap->iv_set_tim = mwl_set_tim; } #endif vap->iv_reset = mwl_reset; vap->iv_update_beacon = mwl_beacon_update; /* override max aid so sta's cannot assoc when we're out of sta id's */ vap->iv_max_aid = MWL_MAXSTAID; /* override default A-MPDU rx parameters */ vap->iv_ampdu_rxmax = IEEE80211_HTCAP_MAXRXAMPDU_64K; vap->iv_ampdu_density = IEEE80211_HTCAP_MPDUDENSITY_4; /* complete setup */ ieee80211_vap_attach(vap, mwl_media_change, ieee80211_media_status, mac); switch (vap->iv_opmode) { case IEEE80211_M_HOSTAP: case IEEE80211_M_MBSS: case IEEE80211_M_STA: /* * Setup sta db entry for local address. */ mwl_localstadb(vap); if (vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_MBSS) sc->sc_napvaps++; else sc->sc_nstavaps++; break; case IEEE80211_M_WDS: sc->sc_nwdsvaps++; break; default: break; } /* * Setup overall operating mode. */ if (sc->sc_napvaps) ic->ic_opmode = IEEE80211_M_HOSTAP; else if (sc->sc_nstavaps) ic->ic_opmode = IEEE80211_M_STA; else ic->ic_opmode = opmode; return vap; } static void mwl_vap_delete(struct ieee80211vap *vap) { struct mwl_vap *mvp = MWL_VAP(vap); struct mwl_softc *sc = vap->iv_ic->ic_softc; struct mwl_hal *mh = sc->sc_mh; struct mwl_hal_vap *hvap = mvp->mv_hvap; enum ieee80211_opmode opmode = vap->iv_opmode; /* XXX disallow ap vap delete if WDS still present */ if (sc->sc_running) { /* quiesce h/w while we remove the vap */ mwl_hal_intrset(mh, 0); /* disable interrupts */ } ieee80211_vap_detach(vap); switch (opmode) { case IEEE80211_M_HOSTAP: case IEEE80211_M_MBSS: case IEEE80211_M_STA: KASSERT(hvap != NULL, ("no hal vap handle")); (void) mwl_hal_delstation(hvap, vap->iv_myaddr); mwl_hal_delvap(hvap); if (opmode == IEEE80211_M_HOSTAP || opmode == IEEE80211_M_MBSS) sc->sc_napvaps--; else sc->sc_nstavaps--; /* XXX don't do it for IEEE80211_CLONE_MACADDR */ reclaim_address(sc, vap->iv_myaddr); break; case IEEE80211_M_WDS: sc->sc_nwdsvaps--; break; default: break; } mwl_cleartxq(sc, vap); free(mvp, M_80211_VAP); if (sc->sc_running) mwl_hal_intrset(mh, sc->sc_imask); } void mwl_suspend(struct mwl_softc *sc) { MWL_LOCK(sc); mwl_stop(sc); MWL_UNLOCK(sc); } void mwl_resume(struct mwl_softc *sc) { int error = EDOOFUS; MWL_LOCK(sc); if (sc->sc_ic.ic_nrunning > 0) error = mwl_init(sc); MWL_UNLOCK(sc); if (error == 0) ieee80211_start_all(&sc->sc_ic); /* start all vap's */ } void mwl_shutdown(void *arg) { struct mwl_softc *sc = arg; MWL_LOCK(sc); mwl_stop(sc); MWL_UNLOCK(sc); } /* * Interrupt handler. Most of the actual processing is deferred. */ void mwl_intr(void *arg) { struct mwl_softc *sc = arg; struct mwl_hal *mh = sc->sc_mh; uint32_t status; if (sc->sc_invalid) { /* * The hardware is not ready/present, don't touch anything. * Note this can happen early on if the IRQ is shared. */ DPRINTF(sc, MWL_DEBUG_ANY, "%s: invalid; ignored\n", __func__); return; } /* * Figure out the reason(s) for the interrupt. */ mwl_hal_getisr(mh, &status); /* NB: clears ISR too */ if (status == 0) /* must be a shared irq */ return; DPRINTF(sc, MWL_DEBUG_INTR, "%s: status 0x%x imask 0x%x\n", __func__, status, sc->sc_imask); if (status & MACREG_A2HRIC_BIT_RX_RDY) taskqueue_enqueue(sc->sc_tq, &sc->sc_rxtask); if (status & MACREG_A2HRIC_BIT_TX_DONE) taskqueue_enqueue(sc->sc_tq, &sc->sc_txtask); if (status & MACREG_A2HRIC_BIT_BA_WATCHDOG) taskqueue_enqueue(sc->sc_tq, &sc->sc_bawatchdogtask); if (status & MACREG_A2HRIC_BIT_OPC_DONE) mwl_hal_cmddone(mh); if (status & MACREG_A2HRIC_BIT_MAC_EVENT) { ; } if (status & MACREG_A2HRIC_BIT_ICV_ERROR) { /* TKIP ICV error */ sc->sc_stats.mst_rx_badtkipicv++; } if (status & MACREG_A2HRIC_BIT_QUEUE_EMPTY) { /* 11n aggregation queue is empty, re-fill */ ; } if (status & MACREG_A2HRIC_BIT_QUEUE_FULL) { ; } if (status & MACREG_A2HRIC_BIT_RADAR_DETECT) { /* radar detected, process event */ taskqueue_enqueue(sc->sc_tq, &sc->sc_radartask); } if (status & MACREG_A2HRIC_BIT_CHAN_SWITCH) { /* DFS channel switch */ taskqueue_enqueue(sc->sc_tq, &sc->sc_chanswitchtask); } } static void mwl_radar_proc(void *arg, int pending) { struct mwl_softc *sc = arg; struct ieee80211com *ic = &sc->sc_ic; DPRINTF(sc, MWL_DEBUG_ANY, "%s: radar detected, pending %u\n", __func__, pending); sc->sc_stats.mst_radardetect++; /* XXX stop h/w BA streams? */ IEEE80211_LOCK(ic); ieee80211_dfs_notify_radar(ic, ic->ic_curchan); IEEE80211_UNLOCK(ic); } static void mwl_chanswitch_proc(void *arg, int pending) { struct mwl_softc *sc = arg; struct ieee80211com *ic = &sc->sc_ic; DPRINTF(sc, MWL_DEBUG_ANY, "%s: channel switch notice, pending %u\n", __func__, pending); IEEE80211_LOCK(ic); sc->sc_csapending = 0; ieee80211_csa_completeswitch(ic); IEEE80211_UNLOCK(ic); } static void mwl_bawatchdog(const MWL_HAL_BASTREAM *sp) { struct ieee80211_node *ni = sp->data[0]; /* send DELBA and drop the stream */ ieee80211_ampdu_stop(ni, sp->data[1], IEEE80211_REASON_UNSPECIFIED); } static void mwl_bawatchdog_proc(void *arg, int pending) { struct mwl_softc *sc = arg; struct mwl_hal *mh = sc->sc_mh; const MWL_HAL_BASTREAM *sp; uint8_t bitmap, n; sc->sc_stats.mst_bawatchdog++; if (mwl_hal_getwatchdogbitmap(mh, &bitmap) != 0) { DPRINTF(sc, MWL_DEBUG_AMPDU, "%s: could not get bitmap\n", __func__); sc->sc_stats.mst_bawatchdog_failed++; return; } DPRINTF(sc, MWL_DEBUG_AMPDU, "%s: bitmap 0x%x\n", __func__, bitmap); if (bitmap == 0xff) { n = 0; /* disable all ba streams */ for (bitmap = 0; bitmap < 8; bitmap++) { sp = mwl_hal_bastream_lookup(mh, bitmap); if (sp != NULL) { mwl_bawatchdog(sp); n++; } } if (n == 0) { DPRINTF(sc, MWL_DEBUG_AMPDU, "%s: no BA streams found\n", __func__); sc->sc_stats.mst_bawatchdog_empty++; } } else if (bitmap != 0xaa) { /* disable a single ba stream */ sp = mwl_hal_bastream_lookup(mh, bitmap); if (sp != NULL) { mwl_bawatchdog(sp); } else { DPRINTF(sc, MWL_DEBUG_AMPDU, "%s: no BA stream %d\n", __func__, bitmap); sc->sc_stats.mst_bawatchdog_notfound++; } } } /* * Convert net80211 channel to a HAL channel. */ static void mwl_mapchan(MWL_HAL_CHANNEL *hc, const struct ieee80211_channel *chan) { hc->channel = chan->ic_ieee; *(uint32_t *)&hc->channelFlags = 0; if (IEEE80211_IS_CHAN_2GHZ(chan)) hc->channelFlags.FreqBand = MWL_FREQ_BAND_2DOT4GHZ; else if (IEEE80211_IS_CHAN_5GHZ(chan)) hc->channelFlags.FreqBand = MWL_FREQ_BAND_5GHZ; if (IEEE80211_IS_CHAN_HT40(chan)) { hc->channelFlags.ChnlWidth = MWL_CH_40_MHz_WIDTH; if (IEEE80211_IS_CHAN_HT40U(chan)) hc->channelFlags.ExtChnlOffset = MWL_EXT_CH_ABOVE_CTRL_CH; else hc->channelFlags.ExtChnlOffset = MWL_EXT_CH_BELOW_CTRL_CH; } else hc->channelFlags.ChnlWidth = MWL_CH_20_MHz_WIDTH; /* XXX 10MHz channels */ } /* * Inform firmware of our tx/rx dma setup. The BAR 0 * writes below are for compatibility with older firmware. * For current firmware we send this information with a * cmd block via mwl_hal_sethwdma. */ static int mwl_setupdma(struct mwl_softc *sc) { int error, i; sc->sc_hwdma.rxDescRead = sc->sc_rxdma.dd_desc_paddr; WR4(sc, sc->sc_hwspecs.rxDescRead, sc->sc_hwdma.rxDescRead); WR4(sc, sc->sc_hwspecs.rxDescWrite, sc->sc_hwdma.rxDescRead); for (i = 0; i < MWL_NUM_TX_QUEUES-MWL_NUM_ACK_QUEUES; i++) { struct mwl_txq *txq = &sc->sc_txq[i]; sc->sc_hwdma.wcbBase[i] = txq->dma.dd_desc_paddr; WR4(sc, sc->sc_hwspecs.wcbBase[i], sc->sc_hwdma.wcbBase[i]); } sc->sc_hwdma.maxNumTxWcb = mwl_txbuf; sc->sc_hwdma.maxNumWCB = MWL_NUM_TX_QUEUES-MWL_NUM_ACK_QUEUES; error = mwl_hal_sethwdma(sc->sc_mh, &sc->sc_hwdma); if (error != 0) { device_printf(sc->sc_dev, "unable to setup tx/rx dma; hal status %u\n", error); /* XXX */ } return error; } /* * Inform firmware of tx rate parameters. * Called after a channel change. */ static int mwl_setcurchanrates(struct mwl_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; const struct ieee80211_rateset *rs; MWL_HAL_TXRATE rates; memset(&rates, 0, sizeof(rates)); rs = ieee80211_get_suprates(ic, ic->ic_curchan); /* rate used to send management frames */ rates.MgtRate = rs->rs_rates[0] & IEEE80211_RATE_VAL; /* rate used to send multicast frames */ rates.McastRate = rates.MgtRate; return mwl_hal_settxrate_auto(sc->sc_mh, &rates); } /* * Inform firmware of tx rate parameters. Called whenever * user-settable params change and after a channel change. */ static int mwl_setrates(struct ieee80211vap *vap) { struct mwl_vap *mvp = MWL_VAP(vap); struct ieee80211_node *ni = vap->iv_bss; const struct ieee80211_txparam *tp = ni->ni_txparms; MWL_HAL_TXRATE rates; KASSERT(vap->iv_state == IEEE80211_S_RUN, ("state %d", vap->iv_state)); /* * Update the h/w rate map. * NB: 0x80 for MCS is passed through unchanged */ memset(&rates, 0, sizeof(rates)); /* rate used to send management frames */ rates.MgtRate = tp->mgmtrate; /* rate used to send multicast frames */ rates.McastRate = tp->mcastrate; /* while here calculate EAPOL fixed rate cookie */ mvp->mv_eapolformat = htole16(mwl_calcformat(rates.MgtRate, ni)); return mwl_hal_settxrate(mvp->mv_hvap, tp->ucastrate != IEEE80211_FIXED_RATE_NONE ? RATE_FIXED : RATE_AUTO, &rates); } /* * Setup a fixed xmit rate cookie for EAPOL frames. */ static void mwl_seteapolformat(struct ieee80211vap *vap) { struct mwl_vap *mvp = MWL_VAP(vap); struct ieee80211_node *ni = vap->iv_bss; enum ieee80211_phymode mode; uint8_t rate; KASSERT(vap->iv_state == IEEE80211_S_RUN, ("state %d", vap->iv_state)); mode = ieee80211_chan2mode(ni->ni_chan); /* * Use legacy rates when operating a mixed HT+non-HT bss. * NB: this may violate POLA for sta and wds vap's. */ if (mode == IEEE80211_MODE_11NA && (vap->iv_flags_ht & IEEE80211_FHT_PUREN) == 0) rate = vap->iv_txparms[IEEE80211_MODE_11A].mgmtrate; else if (mode == IEEE80211_MODE_11NG && (vap->iv_flags_ht & IEEE80211_FHT_PUREN) == 0) rate = vap->iv_txparms[IEEE80211_MODE_11G].mgmtrate; else rate = vap->iv_txparms[mode].mgmtrate; mvp->mv_eapolformat = htole16(mwl_calcformat(rate, ni)); } /* * Map SKU+country code to region code for radar bin'ing. */ static int mwl_map2regioncode(const struct ieee80211_regdomain *rd) { switch (rd->regdomain) { case SKU_FCC: case SKU_FCC3: return DOMAIN_CODE_FCC; case SKU_CA: return DOMAIN_CODE_IC; case SKU_ETSI: case SKU_ETSI2: case SKU_ETSI3: if (rd->country == CTRY_SPAIN) return DOMAIN_CODE_SPAIN; if (rd->country == CTRY_FRANCE || rd->country == CTRY_FRANCE2) return DOMAIN_CODE_FRANCE; /* XXX force 1.3.1 radar type */ return DOMAIN_CODE_ETSI_131; case SKU_JAPAN: return DOMAIN_CODE_MKK; case SKU_ROW: return DOMAIN_CODE_DGT; /* Taiwan */ case SKU_APAC: case SKU_APAC2: case SKU_APAC3: return DOMAIN_CODE_AUS; /* Australia */ } /* XXX KOREA? */ return DOMAIN_CODE_FCC; /* XXX? */ } static int mwl_hal_reset(struct mwl_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct mwl_hal *mh = sc->sc_mh; mwl_hal_setantenna(mh, WL_ANTENNATYPE_RX, sc->sc_rxantenna); mwl_hal_setantenna(mh, WL_ANTENNATYPE_TX, sc->sc_txantenna); mwl_hal_setradio(mh, 1, WL_AUTO_PREAMBLE); mwl_hal_setwmm(sc->sc_mh, (ic->ic_flags & IEEE80211_F_WME) != 0); mwl_chan_set(sc, ic->ic_curchan); /* NB: RF/RA performance tuned for indoor mode */ mwl_hal_setrateadaptmode(mh, 0); mwl_hal_setoptimizationlevel(mh, (ic->ic_flags & IEEE80211_F_BURST) != 0); mwl_hal_setregioncode(mh, mwl_map2regioncode(&ic->ic_regdomain)); mwl_hal_setaggampduratemode(mh, 1, 80); /* XXX */ mwl_hal_setcfend(mh, 0); /* XXX */ return 1; } static int mwl_init(struct mwl_softc *sc) { struct mwl_hal *mh = sc->sc_mh; int error = 0; MWL_LOCK_ASSERT(sc); /* * Stop anything previously setup. This is safe * whether this is the first time through or not. */ mwl_stop(sc); /* * Push vap-independent state to the firmware. */ if (!mwl_hal_reset(sc)) { device_printf(sc->sc_dev, "unable to reset hardware\n"); return EIO; } /* * Setup recv (once); transmit is already good to go. */ error = mwl_startrecv(sc); if (error != 0) { device_printf(sc->sc_dev, "unable to start recv logic\n"); return error; } /* * Enable interrupts. */ sc->sc_imask = MACREG_A2HRIC_BIT_RX_RDY | MACREG_A2HRIC_BIT_TX_DONE | MACREG_A2HRIC_BIT_OPC_DONE #if 0 | MACREG_A2HRIC_BIT_MAC_EVENT #endif | MACREG_A2HRIC_BIT_ICV_ERROR | MACREG_A2HRIC_BIT_RADAR_DETECT | MACREG_A2HRIC_BIT_CHAN_SWITCH #if 0 | MACREG_A2HRIC_BIT_QUEUE_EMPTY #endif | MACREG_A2HRIC_BIT_BA_WATCHDOG | MACREQ_A2HRIC_BIT_TX_ACK ; sc->sc_running = 1; mwl_hal_intrset(mh, sc->sc_imask); callout_reset(&sc->sc_watchdog, hz, mwl_watchdog, sc); return 0; } static void mwl_stop(struct mwl_softc *sc) { MWL_LOCK_ASSERT(sc); if (sc->sc_running) { /* * Shutdown the hardware and driver. */ sc->sc_running = 0; callout_stop(&sc->sc_watchdog); sc->sc_tx_timer = 0; mwl_draintxq(sc); } } static int mwl_reset_vap(struct ieee80211vap *vap, int state) { struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap; struct ieee80211com *ic = vap->iv_ic; if (state == IEEE80211_S_RUN) mwl_setrates(vap); /* XXX off by 1? */ mwl_hal_setrtsthreshold(hvap, vap->iv_rtsthreshold); /* XXX auto? 20/40 split? */ mwl_hal_sethtgi(hvap, (vap->iv_flags_ht & (IEEE80211_FHT_SHORTGI20|IEEE80211_FHT_SHORTGI40)) ? 1 : 0); mwl_hal_setnprot(hvap, ic->ic_htprotmode == IEEE80211_PROT_NONE ? HTPROTECT_NONE : HTPROTECT_AUTO); /* XXX txpower cap */ /* re-setup beacons */ if (state == IEEE80211_S_RUN && (vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_MBSS || vap->iv_opmode == IEEE80211_M_IBSS)) { mwl_setapmode(vap, vap->iv_bss->ni_chan); mwl_hal_setnprotmode(hvap, _IEEE80211_MASKSHIFT( ic->ic_curhtprotmode, IEEE80211_HTINFO_OPMODE)); return mwl_beacon_setup(vap); } return 0; } /* * Reset the hardware w/o losing operational state. * Used to reset or reload hardware state for a vap. */ static int mwl_reset(struct ieee80211vap *vap, u_long cmd) { struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap; int error = 0; if (hvap != NULL) { /* WDS, MONITOR, etc. */ struct ieee80211com *ic = vap->iv_ic; struct mwl_softc *sc = ic->ic_softc; struct mwl_hal *mh = sc->sc_mh; /* XXX handle DWDS sta vap change */ /* XXX do we need to disable interrupts? */ mwl_hal_intrset(mh, 0); /* disable interrupts */ error = mwl_reset_vap(vap, vap->iv_state); mwl_hal_intrset(mh, sc->sc_imask); } return error; } /* * Allocate a tx buffer for sending a frame. The * packet is assumed to have the WME AC stored so * we can use it to select the appropriate h/w queue. */ static struct mwl_txbuf * mwl_gettxbuf(struct mwl_softc *sc, struct mwl_txq *txq) { struct mwl_txbuf *bf; /* * Grab a TX buffer and associated resources. */ MWL_TXQ_LOCK(txq); bf = STAILQ_FIRST(&txq->free); if (bf != NULL) { STAILQ_REMOVE_HEAD(&txq->free, bf_list); txq->nfree--; } MWL_TXQ_UNLOCK(txq); if (bf == NULL) DPRINTF(sc, MWL_DEBUG_XMIT, "%s: out of xmit buffers on q %d\n", __func__, txq->qnum); return bf; } /* * Return a tx buffer to the queue it came from. Note there * are two cases because we must preserve the order of buffers * as it reflects the fixed order of descriptors in memory * (the firmware pre-fetches descriptors so we cannot reorder). */ static void mwl_puttxbuf_head(struct mwl_txq *txq, struct mwl_txbuf *bf) { bf->bf_m = NULL; bf->bf_node = NULL; MWL_TXQ_LOCK(txq); STAILQ_INSERT_HEAD(&txq->free, bf, bf_list); txq->nfree++; MWL_TXQ_UNLOCK(txq); } static void mwl_puttxbuf_tail(struct mwl_txq *txq, struct mwl_txbuf *bf) { bf->bf_m = NULL; bf->bf_node = NULL; MWL_TXQ_LOCK(txq); STAILQ_INSERT_TAIL(&txq->free, bf, bf_list); txq->nfree++; MWL_TXQ_UNLOCK(txq); } static int mwl_transmit(struct ieee80211com *ic, struct mbuf *m) { struct mwl_softc *sc = ic->ic_softc; int error; MWL_LOCK(sc); if (!sc->sc_running) { MWL_UNLOCK(sc); return (ENXIO); } error = mbufq_enqueue(&sc->sc_snd, m); if (error) { MWL_UNLOCK(sc); return (error); } mwl_start(sc); MWL_UNLOCK(sc); return (0); } static void mwl_start(struct mwl_softc *sc) { struct ieee80211_node *ni; struct mwl_txbuf *bf; struct mbuf *m; struct mwl_txq *txq = NULL; /* XXX silence gcc */ int nqueued; MWL_LOCK_ASSERT(sc); if (!sc->sc_running || sc->sc_invalid) return; nqueued = 0; while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) { /* * Grab the node for the destination. */ ni = (struct ieee80211_node *) m->m_pkthdr.rcvif; KASSERT(ni != NULL, ("no node")); m->m_pkthdr.rcvif = NULL; /* committed, clear ref */ /* * Grab a TX buffer and associated resources. * We honor the classification by the 802.11 layer. */ txq = sc->sc_ac2q[M_WME_GETAC(m)]; bf = mwl_gettxbuf(sc, txq); if (bf == NULL) { m_freem(m); ieee80211_free_node(ni); #ifdef MWL_TX_NODROP sc->sc_stats.mst_tx_qstop++; break; #else DPRINTF(sc, MWL_DEBUG_XMIT, "%s: tail drop on q %d\n", __func__, txq->qnum); sc->sc_stats.mst_tx_qdrop++; continue; #endif /* MWL_TX_NODROP */ } /* * Pass the frame to the h/w for transmission. */ if (mwl_tx_start(sc, ni, bf, m)) { if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1); mwl_puttxbuf_head(txq, bf); ieee80211_free_node(ni); continue; } nqueued++; if (nqueued >= mwl_txcoalesce) { /* * Poke the firmware to process queued frames; * see below about (lack of) locking. */ nqueued = 0; mwl_hal_txstart(sc->sc_mh, 0/*XXX*/); } } if (nqueued) { /* * NB: We don't need to lock against tx done because * this just prods the firmware to check the transmit * descriptors. The firmware will also start fetching * descriptors by itself if it notices new ones are * present when it goes to deliver a tx done interrupt * to the host. So if we race with tx done processing * it's ok. Delivering the kick here rather than in * mwl_tx_start is an optimization to avoid poking the * firmware for each packet. * * NB: the queue id isn't used so 0 is ok. */ mwl_hal_txstart(sc->sc_mh, 0/*XXX*/); } } static int mwl_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = ni->ni_ic; struct mwl_softc *sc = ic->ic_softc; struct mwl_txbuf *bf; struct mwl_txq *txq; if (!sc->sc_running || sc->sc_invalid) { m_freem(m); return ENETDOWN; } /* * Grab a TX buffer and associated resources. * Note that we depend on the classification * by the 802.11 layer to get to the right h/w * queue. Management frames must ALWAYS go on * queue 1 but we cannot just force that here * because we may receive non-mgt frames. */ txq = sc->sc_ac2q[M_WME_GETAC(m)]; bf = mwl_gettxbuf(sc, txq); if (bf == NULL) { sc->sc_stats.mst_tx_qstop++; m_freem(m); return ENOBUFS; } /* * Pass the frame to the h/w for transmission. */ if (mwl_tx_start(sc, ni, bf, m)) { mwl_puttxbuf_head(txq, bf); return EIO; /* XXX */ } /* * NB: We don't need to lock against tx done because * this just prods the firmware to check the transmit * descriptors. The firmware will also start fetching * descriptors by itself if it notices new ones are * present when it goes to deliver a tx done interrupt * to the host. So if we race with tx done processing * it's ok. Delivering the kick here rather than in * mwl_tx_start is an optimization to avoid poking the * firmware for each packet. * * NB: the queue id isn't used so 0 is ok. */ mwl_hal_txstart(sc->sc_mh, 0/*XXX*/); return 0; } static int mwl_media_change(if_t ifp) { struct ieee80211vap *vap; int error; /* NB: only the fixed rate can change and that doesn't need a reset */ error = ieee80211_media_change(ifp); if (error != 0) return (error); vap = if_getsoftc(ifp); mwl_setrates(vap); return (0); } #ifdef MWL_DEBUG static void mwl_keyprint(struct mwl_softc *sc, const char *tag, const MWL_HAL_KEYVAL *hk, const uint8_t mac[IEEE80211_ADDR_LEN]) { static const char *ciphers[] = { "WEP", "TKIP", "AES-CCM", }; int i, n; printf("%s: [%u] %-7s", tag, hk->keyIndex, ciphers[hk->keyTypeId]); for (i = 0, n = hk->keyLen; i < n; i++) printf(" %02x", hk->key.aes[i]); printf(" mac %s", ether_sprintf(mac)); if (hk->keyTypeId == KEY_TYPE_ID_TKIP) { printf(" %s", "rxmic"); for (i = 0; i < sizeof(hk->key.tkip.rxMic); i++) printf(" %02x", hk->key.tkip.rxMic[i]); printf(" txmic"); for (i = 0; i < sizeof(hk->key.tkip.txMic); i++) printf(" %02x", hk->key.tkip.txMic[i]); } printf(" flags 0x%x\n", hk->keyFlags); } #endif /* * Allocate a key cache slot for a unicast key. The * firmware handles key allocation and every station is * guaranteed key space so we are always successful. */ static int mwl_key_alloc(struct ieee80211vap *vap, struct ieee80211_key *k, ieee80211_keyix *keyix, ieee80211_keyix *rxkeyix) { struct mwl_softc *sc = vap->iv_ic->ic_softc; if (k->wk_keyix != IEEE80211_KEYIX_NONE || (k->wk_flags & IEEE80211_KEY_GROUP)) { if (!ieee80211_is_key_global(vap, k)) { /* should not happen */ DPRINTF(sc, MWL_DEBUG_KEYCACHE, "%s: bogus group key\n", __func__); return 0; } /* give the caller what they requested */ *keyix = *rxkeyix = ieee80211_crypto_get_key_wepidx(vap, k); } else { /* * Firmware handles key allocation. */ *keyix = *rxkeyix = 0; } return 1; } /* * Delete a key entry allocated by mwl_key_alloc. */ static int mwl_key_delete(struct ieee80211vap *vap, const struct ieee80211_key *k) { struct mwl_softc *sc = vap->iv_ic->ic_softc; struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap; MWL_HAL_KEYVAL hk; const uint8_t bcastaddr[IEEE80211_ADDR_LEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; if (hvap == NULL) { if (vap->iv_opmode != IEEE80211_M_WDS) { /* XXX monitor mode? */ DPRINTF(sc, MWL_DEBUG_KEYCACHE, "%s: no hvap for opmode %d\n", __func__, vap->iv_opmode); return 0; } hvap = MWL_VAP(vap)->mv_ap_hvap; } DPRINTF(sc, MWL_DEBUG_KEYCACHE, "%s: delete key %u\n", __func__, k->wk_keyix); memset(&hk, 0, sizeof(hk)); hk.keyIndex = k->wk_keyix; switch (k->wk_cipher->ic_cipher) { case IEEE80211_CIPHER_WEP: hk.keyTypeId = KEY_TYPE_ID_WEP; break; case IEEE80211_CIPHER_TKIP: hk.keyTypeId = KEY_TYPE_ID_TKIP; break; case IEEE80211_CIPHER_AES_CCM: hk.keyTypeId = KEY_TYPE_ID_AES; break; default: /* XXX should not happen */ DPRINTF(sc, MWL_DEBUG_KEYCACHE, "%s: unknown cipher %d\n", __func__, k->wk_cipher->ic_cipher); return 0; } return (mwl_hal_keyreset(hvap, &hk, bcastaddr) == 0); /*XXX*/ } static __inline int addgroupflags(MWL_HAL_KEYVAL *hk, const struct ieee80211_key *k) { if (k->wk_flags & IEEE80211_KEY_GROUP) { if (k->wk_flags & IEEE80211_KEY_XMIT) hk->keyFlags |= KEY_FLAG_TXGROUPKEY; if (k->wk_flags & IEEE80211_KEY_RECV) hk->keyFlags |= KEY_FLAG_RXGROUPKEY; return 1; } else return 0; } /* * Set the key cache contents for the specified key. Key cache * slot(s) must already have been allocated by mwl_key_alloc. */ static int mwl_key_set(struct ieee80211vap *vap, const struct ieee80211_key *k) { return (_mwl_key_set(vap, k, k->wk_macaddr)); } static int _mwl_key_set(struct ieee80211vap *vap, const struct ieee80211_key *k, const uint8_t mac[IEEE80211_ADDR_LEN]) { #define GRPXMIT (IEEE80211_KEY_XMIT | IEEE80211_KEY_GROUP) /* NB: static wep keys are marked GROUP+tx/rx; GTK will be tx or rx */ #define IEEE80211_IS_STATICKEY(k) \ (((k)->wk_flags & (GRPXMIT|IEEE80211_KEY_RECV)) == \ (GRPXMIT|IEEE80211_KEY_RECV)) struct mwl_softc *sc = vap->iv_ic->ic_softc; struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap; const struct ieee80211_cipher *cip = k->wk_cipher; const uint8_t *macaddr; MWL_HAL_KEYVAL hk; KASSERT((k->wk_flags & IEEE80211_KEY_SWCRYPT) == 0, ("s/w crypto set?")); if (hvap == NULL) { if (vap->iv_opmode != IEEE80211_M_WDS) { /* XXX monitor mode? */ DPRINTF(sc, MWL_DEBUG_KEYCACHE, "%s: no hvap for opmode %d\n", __func__, vap->iv_opmode); return 0; } hvap = MWL_VAP(vap)->mv_ap_hvap; } memset(&hk, 0, sizeof(hk)); hk.keyIndex = k->wk_keyix; switch (cip->ic_cipher) { case IEEE80211_CIPHER_WEP: hk.keyTypeId = KEY_TYPE_ID_WEP; hk.keyLen = k->wk_keylen; if (k->wk_keyix == vap->iv_def_txkey) hk.keyFlags = KEY_FLAG_WEP_TXKEY; if (!IEEE80211_IS_STATICKEY(k)) { /* NB: WEP is never used for the PTK */ (void) addgroupflags(&hk, k); } break; case IEEE80211_CIPHER_TKIP: hk.keyTypeId = KEY_TYPE_ID_TKIP; hk.key.tkip.tsc.high = (uint32_t)(k->wk_keytsc >> 16); hk.key.tkip.tsc.low = (uint16_t)k->wk_keytsc; hk.keyFlags = KEY_FLAG_TSC_VALID | KEY_FLAG_MICKEY_VALID; hk.keyLen = k->wk_keylen + IEEE80211_MICBUF_SIZE; if (!addgroupflags(&hk, k)) hk.keyFlags |= KEY_FLAG_PAIRWISE; break; case IEEE80211_CIPHER_AES_CCM: hk.keyTypeId = KEY_TYPE_ID_AES; hk.keyLen = k->wk_keylen; if (!addgroupflags(&hk, k)) hk.keyFlags |= KEY_FLAG_PAIRWISE; break; default: /* XXX should not happen */ DPRINTF(sc, MWL_DEBUG_KEYCACHE, "%s: unknown cipher %d\n", __func__, k->wk_cipher->ic_cipher); return 0; } /* * NB: tkip mic keys get copied here too; the layout * just happens to match that in ieee80211_key. */ memcpy(hk.key.aes, k->wk_key, hk.keyLen); /* * Locate address of sta db entry for writing key; * the convention unfortunately is somewhat different * than how net80211, hostapd, and wpa_supplicant think. */ if (vap->iv_opmode == IEEE80211_M_STA) { /* * NB: keys plumbed before the sta reaches AUTH state * will be discarded or written to the wrong sta db * entry because iv_bss is meaningless. This is ok * (right now) because we handle deferred plumbing of * WEP keys when the sta reaches AUTH state. */ macaddr = vap->iv_bss->ni_bssid; if ((k->wk_flags & IEEE80211_KEY_GROUP) == 0) { /* XXX plumb to local sta db too for static key wep */ mwl_hal_keyset(hvap, &hk, vap->iv_myaddr); } } else if (vap->iv_opmode == IEEE80211_M_WDS && vap->iv_state != IEEE80211_S_RUN) { /* * Prior to RUN state a WDS vap will not it's BSS node * setup so we will plumb the key to the wrong mac * address (it'll be our local address). Workaround * this for the moment by grabbing the correct address. */ macaddr = vap->iv_des_bssid; } else if ((k->wk_flags & GRPXMIT) == GRPXMIT) macaddr = vap->iv_myaddr; else macaddr = mac; KEYPRINTF(sc, &hk, macaddr); return (mwl_hal_keyset(hvap, &hk, macaddr) == 0); #undef IEEE80211_IS_STATICKEY #undef GRPXMIT } /* * Set the multicast filter contents into the hardware. * XXX f/w has no support; just defer to the os. */ static void mwl_setmcastfilter(struct mwl_softc *sc) { #if 0 struct ether_multi *enm; struct ether_multistep estep; uint8_t macs[IEEE80211_ADDR_LEN*MWL_HAL_MCAST_MAX];/* XXX stack use */ uint8_t *mp; int nmc; mp = macs; nmc = 0; ETHER_FIRST_MULTI(estep, &sc->sc_ec, enm); while (enm != NULL) { /* XXX Punt on ranges. */ if (nmc == MWL_HAL_MCAST_MAX || !IEEE80211_ADDR_EQ(enm->enm_addrlo, enm->enm_addrhi)) { if_setflagsbit(ifp, IFF_ALLMULTI, 0); return; } IEEE80211_ADDR_COPY(mp, enm->enm_addrlo); mp += IEEE80211_ADDR_LEN, nmc++; ETHER_NEXT_MULTI(estep, enm); } if_setflagsbit(ifp, 0, IFF_ALLMULTI); mwl_hal_setmcast(sc->sc_mh, nmc, macs); #endif } static int mwl_mode_init(struct mwl_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; struct mwl_hal *mh = sc->sc_mh; mwl_hal_setpromisc(mh, ic->ic_promisc > 0); mwl_setmcastfilter(sc); return 0; } /* * Callback from the 802.11 layer after a multicast state change. */ static void mwl_update_mcast(struct ieee80211com *ic) { struct mwl_softc *sc = ic->ic_softc; mwl_setmcastfilter(sc); } /* * Callback from the 802.11 layer after a promiscuous mode change. * Note this interface does not check the operating mode as this * is an internal callback and we are expected to honor the current * state (e.g. this is used for setting the interface in promiscuous * mode when operating in hostap mode to do ACS). */ static void mwl_update_promisc(struct ieee80211com *ic) { struct mwl_softc *sc = ic->ic_softc; mwl_hal_setpromisc(sc->sc_mh, ic->ic_promisc > 0); } /* * Callback from the 802.11 layer to update the slot time * based on the current setting. We use it to notify the * firmware of ERP changes and the f/w takes care of things * like slot time and preamble. */ static void mwl_updateslot(struct ieee80211com *ic) { struct mwl_softc *sc = ic->ic_softc; struct mwl_hal *mh = sc->sc_mh; int prot; /* NB: can be called early; suppress needless cmds */ if (!sc->sc_running) return; /* * Calculate the ERP flags. The firwmare will use * this to carry out the appropriate measures. */ prot = 0; if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan)) { if ((ic->ic_flags & IEEE80211_F_SHSLOT) == 0) prot |= IEEE80211_ERP_NON_ERP_PRESENT; if (ic->ic_flags & IEEE80211_F_USEPROT) prot |= IEEE80211_ERP_USE_PROTECTION; if (ic->ic_flags & IEEE80211_F_USEBARKER) prot |= IEEE80211_ERP_LONG_PREAMBLE; } DPRINTF(sc, MWL_DEBUG_RESET, "%s: chan %u MHz/flags 0x%x %s slot, (prot 0x%x ic_flags 0x%x)\n", __func__, ic->ic_curchan->ic_freq, ic->ic_curchan->ic_flags, ic->ic_flags & IEEE80211_F_SHSLOT ? "short" : "long", prot, ic->ic_flags); mwl_hal_setgprot(mh, prot); } /* * Setup the beacon frame. */ static int mwl_beacon_setup(struct ieee80211vap *vap) { struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap; struct ieee80211_node *ni = vap->iv_bss; struct mbuf *m; m = ieee80211_beacon_alloc(ni); if (m == NULL) return ENOBUFS; mwl_hal_setbeacon(hvap, mtod(m, const void *), m->m_len); m_free(m); return 0; } /* * Update the beacon frame in response to a change. */ static void mwl_beacon_update(struct ieee80211vap *vap, int item) { struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap; struct ieee80211com *ic = vap->iv_ic; KASSERT(hvap != NULL, ("no beacon")); switch (item) { case IEEE80211_BEACON_ERP: mwl_updateslot(ic); break; case IEEE80211_BEACON_HTINFO: mwl_hal_setnprotmode(hvap, _IEEE80211_MASKSHIFT( ic->ic_curhtprotmode, IEEE80211_HTINFO_OPMODE)); break; case IEEE80211_BEACON_CAPS: case IEEE80211_BEACON_WME: case IEEE80211_BEACON_APPIE: case IEEE80211_BEACON_CSA: break; case IEEE80211_BEACON_TIM: /* NB: firmware always forms TIM */ return; } /* XXX retain beacon frame and update */ mwl_beacon_setup(vap); } static void mwl_load_cb(void *arg, bus_dma_segment_t *segs, int nsegs, int error) { bus_addr_t *paddr = (bus_addr_t*) arg; KASSERT(error == 0, ("error %u on bus_dma callback", error)); *paddr = segs->ds_addr; } #ifdef MWL_HOST_PS_SUPPORT /* * Handle power save station occupancy changes. */ static void mwl_update_ps(struct ieee80211vap *vap, int nsta) { struct mwl_vap *mvp = MWL_VAP(vap); if (nsta == 0 || mvp->mv_last_ps_sta == 0) mwl_hal_setpowersave_bss(mvp->mv_hvap, nsta); mvp->mv_last_ps_sta = nsta; } /* * Handle associated station power save state changes. */ static int mwl_set_tim(struct ieee80211_node *ni, int set) { struct ieee80211vap *vap = ni->ni_vap; struct mwl_vap *mvp = MWL_VAP(vap); if (mvp->mv_set_tim(ni, set)) { /* NB: state change */ mwl_hal_setpowersave_sta(mvp->mv_hvap, IEEE80211_AID(ni->ni_associd), set); return 1; } else return 0; } #endif /* MWL_HOST_PS_SUPPORT */ static int mwl_desc_setup(struct mwl_softc *sc, const char *name, struct mwl_descdma *dd, int nbuf, size_t bufsize, int ndesc, size_t descsize) { uint8_t *ds; int error; DPRINTF(sc, MWL_DEBUG_RESET, "%s: %s DMA: %u bufs (%ju) %u desc/buf (%ju)\n", __func__, name, nbuf, (uintmax_t) bufsize, ndesc, (uintmax_t) descsize); dd->dd_name = name; dd->dd_desc_len = nbuf * ndesc * descsize; /* * Setup DMA descriptor area. */ error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), /* parent */ PAGE_SIZE, 0, /* alignment, bounds */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ dd->dd_desc_len, /* maxsize */ 1, /* nsegments */ dd->dd_desc_len, /* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ NULL, /* lockfunc */ NULL, /* lockarg */ &dd->dd_dmat); if (error != 0) { device_printf(sc->sc_dev, "cannot allocate %s DMA tag\n", dd->dd_name); return error; } /* allocate descriptors */ error = bus_dmamem_alloc(dd->dd_dmat, (void**) &dd->dd_desc, BUS_DMA_NOWAIT | BUS_DMA_COHERENT, &dd->dd_dmamap); if (error != 0) { device_printf(sc->sc_dev, "unable to alloc memory for %u %s descriptors, " "error %u\n", nbuf * ndesc, dd->dd_name, error); goto fail1; } error = bus_dmamap_load(dd->dd_dmat, dd->dd_dmamap, dd->dd_desc, dd->dd_desc_len, mwl_load_cb, &dd->dd_desc_paddr, BUS_DMA_NOWAIT); if (error != 0) { device_printf(sc->sc_dev, "unable to map %s descriptors, error %u\n", dd->dd_name, error); goto fail2; } ds = dd->dd_desc; memset(ds, 0, dd->dd_desc_len); DPRINTF(sc, MWL_DEBUG_RESET, "%s: %s DMA map: %p (%lu) -> 0x%jx (%lu)\n", __func__, dd->dd_name, ds, (u_long) dd->dd_desc_len, (uintmax_t) dd->dd_desc_paddr, /*XXX*/ (u_long) dd->dd_desc_len); return 0; fail2: bus_dmamem_free(dd->dd_dmat, dd->dd_desc, dd->dd_dmamap); fail1: bus_dma_tag_destroy(dd->dd_dmat); memset(dd, 0, sizeof(*dd)); return error; #undef DS2PHYS } static void mwl_desc_cleanup(struct mwl_softc *sc, struct mwl_descdma *dd) { bus_dmamap_unload(dd->dd_dmat, dd->dd_dmamap); bus_dmamem_free(dd->dd_dmat, dd->dd_desc, dd->dd_dmamap); bus_dma_tag_destroy(dd->dd_dmat); memset(dd, 0, sizeof(*dd)); } /* * Construct a tx q's free list. The order of entries on * the list must reflect the physical layout of tx descriptors * because the firmware pre-fetches descriptors. * * XXX might be better to use indices into the buffer array. */ static void mwl_txq_reset(struct mwl_softc *sc, struct mwl_txq *txq) { struct mwl_txbuf *bf; int i; bf = txq->dma.dd_bufptr; STAILQ_INIT(&txq->free); for (i = 0; i < mwl_txbuf; i++, bf++) STAILQ_INSERT_TAIL(&txq->free, bf, bf_list); txq->nfree = i; } #define DS2PHYS(_dd, _ds) \ ((_dd)->dd_desc_paddr + ((caddr_t)(_ds) - (caddr_t)(_dd)->dd_desc)) static int mwl_txdma_setup(struct mwl_softc *sc, struct mwl_txq *txq) { int error, bsize, i; struct mwl_txbuf *bf; struct mwl_txdesc *ds; error = mwl_desc_setup(sc, "tx", &txq->dma, mwl_txbuf, sizeof(struct mwl_txbuf), MWL_TXDESC, sizeof(struct mwl_txdesc)); if (error != 0) return error; /* allocate and setup tx buffers */ bsize = mwl_txbuf * sizeof(struct mwl_txbuf); bf = malloc(bsize, M_MWLDEV, M_NOWAIT | M_ZERO); if (bf == NULL) { device_printf(sc->sc_dev, "malloc of %u tx buffers failed\n", mwl_txbuf); return ENOMEM; } txq->dma.dd_bufptr = bf; ds = txq->dma.dd_desc; for (i = 0; i < mwl_txbuf; i++, bf++, ds += MWL_TXDESC) { bf->bf_desc = ds; bf->bf_daddr = DS2PHYS(&txq->dma, ds); error = bus_dmamap_create(sc->sc_dmat, BUS_DMA_NOWAIT, &bf->bf_dmamap); if (error != 0) { device_printf(sc->sc_dev, "unable to create dmamap for tx " "buffer %u, error %u\n", i, error); return error; } } mwl_txq_reset(sc, txq); return 0; } static void mwl_txdma_cleanup(struct mwl_softc *sc, struct mwl_txq *txq) { struct mwl_txbuf *bf; int i; bf = txq->dma.dd_bufptr; for (i = 0; i < mwl_txbuf; i++, bf++) { KASSERT(bf->bf_m == NULL, ("mbuf on free list")); KASSERT(bf->bf_node == NULL, ("node on free list")); if (bf->bf_dmamap != NULL) bus_dmamap_destroy(sc->sc_dmat, bf->bf_dmamap); } STAILQ_INIT(&txq->free); txq->nfree = 0; if (txq->dma.dd_bufptr != NULL) { free(txq->dma.dd_bufptr, M_MWLDEV); txq->dma.dd_bufptr = NULL; } if (txq->dma.dd_desc_len != 0) mwl_desc_cleanup(sc, &txq->dma); } static int mwl_rxdma_setup(struct mwl_softc *sc) { int error, jumbosize, bsize, i; struct mwl_rxbuf *bf; struct mwl_jumbo *rbuf; struct mwl_rxdesc *ds; caddr_t data; error = mwl_desc_setup(sc, "rx", &sc->sc_rxdma, mwl_rxdesc, sizeof(struct mwl_rxbuf), 1, sizeof(struct mwl_rxdesc)); if (error != 0) return error; /* * Receive is done to a private pool of jumbo buffers. * This allows us to attach to mbuf's and avoid re-mapping * memory on each rx we post. We allocate a large chunk * of memory and manage it in the driver. The mbuf free * callback method is used to reclaim frames after sending * them up the stack. By default we allocate 2x the number of * rx descriptors configured so we have some slop to hold * us while frames are processed. */ if (mwl_rxbuf < 2*mwl_rxdesc) { device_printf(sc->sc_dev, "too few rx dma buffers (%d); increasing to %d\n", mwl_rxbuf, 2*mwl_rxdesc); mwl_rxbuf = 2*mwl_rxdesc; } jumbosize = roundup(MWL_AGGR_SIZE, PAGE_SIZE); sc->sc_rxmemsize = mwl_rxbuf*jumbosize; error = bus_dma_tag_create(sc->sc_dmat, /* parent */ PAGE_SIZE, 0, /* alignment, bounds */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ sc->sc_rxmemsize, /* maxsize */ 1, /* nsegments */ sc->sc_rxmemsize, /* maxsegsize */ BUS_DMA_ALLOCNOW, /* flags */ NULL, /* lockfunc */ NULL, /* lockarg */ &sc->sc_rxdmat); if (error != 0) { device_printf(sc->sc_dev, "could not create rx DMA tag\n"); return error; } error = bus_dmamem_alloc(sc->sc_rxdmat, (void**) &sc->sc_rxmem, BUS_DMA_NOWAIT | BUS_DMA_COHERENT, &sc->sc_rxmap); if (error != 0) { device_printf(sc->sc_dev, "could not alloc %ju bytes of rx DMA memory\n", (uintmax_t) sc->sc_rxmemsize); return error; } error = bus_dmamap_load(sc->sc_rxdmat, sc->sc_rxmap, sc->sc_rxmem, sc->sc_rxmemsize, mwl_load_cb, &sc->sc_rxmem_paddr, BUS_DMA_NOWAIT); if (error != 0) { device_printf(sc->sc_dev, "could not load rx DMA map\n"); return error; } /* * Allocate rx buffers and set them up. */ bsize = mwl_rxdesc * sizeof(struct mwl_rxbuf); bf = malloc(bsize, M_MWLDEV, M_NOWAIT | M_ZERO); if (bf == NULL) { device_printf(sc->sc_dev, "malloc of %u rx buffers failed\n", bsize); return error; } sc->sc_rxdma.dd_bufptr = bf; STAILQ_INIT(&sc->sc_rxbuf); ds = sc->sc_rxdma.dd_desc; for (i = 0; i < mwl_rxdesc; i++, bf++, ds++) { bf->bf_desc = ds; bf->bf_daddr = DS2PHYS(&sc->sc_rxdma, ds); /* pre-assign dma buffer */ bf->bf_data = ((uint8_t *)sc->sc_rxmem) + (i*jumbosize); /* NB: tail is intentional to preserve descriptor order */ STAILQ_INSERT_TAIL(&sc->sc_rxbuf, bf, bf_list); } /* * Place remainder of dma memory buffers on the free list. */ SLIST_INIT(&sc->sc_rxfree); for (; i < mwl_rxbuf; i++) { data = ((uint8_t *)sc->sc_rxmem) + (i*jumbosize); rbuf = MWL_JUMBO_DATA2BUF(data); SLIST_INSERT_HEAD(&sc->sc_rxfree, rbuf, next); sc->sc_nrxfree++; } return 0; } #undef DS2PHYS static void mwl_rxdma_cleanup(struct mwl_softc *sc) { if (sc->sc_rxmem_paddr != 0) { bus_dmamap_unload(sc->sc_rxdmat, sc->sc_rxmap); sc->sc_rxmem_paddr = 0; } if (sc->sc_rxmem != NULL) { bus_dmamem_free(sc->sc_rxdmat, sc->sc_rxmem, sc->sc_rxmap); sc->sc_rxmem = NULL; } if (sc->sc_rxdma.dd_bufptr != NULL) { free(sc->sc_rxdma.dd_bufptr, M_MWLDEV); sc->sc_rxdma.dd_bufptr = NULL; } if (sc->sc_rxdma.dd_desc_len != 0) mwl_desc_cleanup(sc, &sc->sc_rxdma); } static int mwl_dma_setup(struct mwl_softc *sc) { int error, i; error = mwl_rxdma_setup(sc); if (error != 0) { mwl_rxdma_cleanup(sc); return error; } for (i = 0; i < MWL_NUM_TX_QUEUES; i++) { error = mwl_txdma_setup(sc, &sc->sc_txq[i]); if (error != 0) { mwl_dma_cleanup(sc); return error; } } return 0; } static void mwl_dma_cleanup(struct mwl_softc *sc) { int i; for (i = 0; i < MWL_NUM_TX_QUEUES; i++) mwl_txdma_cleanup(sc, &sc->sc_txq[i]); mwl_rxdma_cleanup(sc); } static struct ieee80211_node * mwl_node_alloc(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN]) { struct ieee80211com *ic = vap->iv_ic; struct mwl_softc *sc = ic->ic_softc; const size_t space = sizeof(struct mwl_node); struct mwl_node *mn; mn = malloc(space, M_80211_NODE, M_NOWAIT|M_ZERO); if (mn == NULL) { /* XXX stat+msg */ return NULL; } DPRINTF(sc, MWL_DEBUG_NODE, "%s: mn %p\n", __func__, mn); return &mn->mn_node; } static void mwl_node_cleanup(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; struct mwl_softc *sc = ic->ic_softc; struct mwl_node *mn = MWL_NODE(ni); DPRINTF(sc, MWL_DEBUG_NODE, "%s: ni %p ic %p staid %d\n", __func__, ni, ni->ni_ic, mn->mn_staid); if (mn->mn_staid != 0) { struct ieee80211vap *vap = ni->ni_vap; if (mn->mn_hvap != NULL) { if (vap->iv_opmode == IEEE80211_M_STA) mwl_hal_delstation(mn->mn_hvap, vap->iv_myaddr); else mwl_hal_delstation(mn->mn_hvap, ni->ni_macaddr); } /* * NB: legacy WDS peer sta db entry is installed using * the associate ap's hvap; use it again to delete it. * XXX can vap be NULL? */ else if (vap->iv_opmode == IEEE80211_M_WDS && MWL_VAP(vap)->mv_ap_hvap != NULL) mwl_hal_delstation(MWL_VAP(vap)->mv_ap_hvap, ni->ni_macaddr); delstaid(sc, mn->mn_staid); mn->mn_staid = 0; } sc->sc_node_cleanup(ni); } /* * Reclaim rx dma buffers from packets sitting on the ampdu * reorder queue for a station. We replace buffers with a * system cluster (if available). */ static void mwl_ampdu_rxdma_reclaim(struct ieee80211_rx_ampdu *rap) { #if 0 int i, n, off; struct mbuf *m; void *cl; n = rap->rxa_qframes; for (i = 0; i < rap->rxa_wnd && n > 0; i++) { m = rap->rxa_m[i]; if (m == NULL) continue; n--; /* our dma buffers have a well-known free routine */ if ((m->m_flags & M_EXT) == 0 || m->m_ext.ext_free != mwl_ext_free) continue; /* * Try to allocate a cluster and move the data. */ off = m->m_data - m->m_ext.ext_buf; if (off + m->m_pkthdr.len > MCLBYTES) { /* XXX no AMSDU for now */ continue; } cl = pool_cache_get_paddr(&mclpool_cache, 0, &m->m_ext.ext_paddr); if (cl != NULL) { /* * Copy the existing data to the cluster, remove * the rx dma buffer, and attach the cluster in * its place. Note we preserve the offset to the * data so frames being bridged can still prepend * their headers without adding another mbuf. */ memcpy((caddr_t) cl + off, m->m_data, m->m_pkthdr.len); MEXTREMOVE(m); MEXTADD(m, cl, MCLBYTES, 0, NULL, &mclpool_cache); /* setup mbuf like _MCLGET does */ m->m_flags |= M_CLUSTER | M_EXT_RW; _MOWNERREF(m, M_EXT | M_CLUSTER); /* NB: m_data is clobbered by MEXTADDR, adjust */ m->m_data += off; } } #endif } /* * Callback to reclaim resources. We first let the * net80211 layer do it's thing, then if we are still * blocked by a lack of rx dma buffers we walk the ampdu * reorder q's to reclaim buffers by copying to a system * cluster. */ static void mwl_node_drain(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; struct mwl_softc *sc = ic->ic_softc; struct mwl_node *mn = MWL_NODE(ni); DPRINTF(sc, MWL_DEBUG_NODE, "%s: ni %p vap %p staid %d\n", __func__, ni, ni->ni_vap, mn->mn_staid); /* NB: call up first to age out ampdu q's */ sc->sc_node_drain(ni); /* XXX better to not check low water mark? */ if (sc->sc_rxblocked && mn->mn_staid != 0 && (ni->ni_flags & IEEE80211_NODE_HT)) { uint8_t tid; /* * Walk the reorder q and reclaim rx dma buffers by copying * the packet contents into clusters. */ for (tid = 0; tid < WME_NUM_TID; tid++) { struct ieee80211_rx_ampdu *rap; rap = &ni->ni_rx_ampdu[tid]; if ((rap->rxa_flags & IEEE80211_AGGR_XCHGPEND) == 0) continue; if (rap->rxa_qframes) mwl_ampdu_rxdma_reclaim(rap); } } } static void mwl_node_getsignal(const struct ieee80211_node *ni, int8_t *rssi, int8_t *noise) { *rssi = ni->ni_ic->ic_node_getrssi(ni); #ifdef MWL_ANT_INFO_SUPPORT #if 0 /* XXX need to smooth data */ *noise = -MWL_NODE_CONST(ni)->mn_ai.nf; #else *noise = -95; /* XXX */ #endif #else *noise = -95; /* XXX */ #endif } /* * Convert Hardware per-antenna rssi info to common format: * Let a1, a2, a3 represent the amplitudes per chain * Let amax represent max[a1, a2, a3] * Rssi1_dBm = RSSI_dBm + 20*log10(a1/amax) * Rssi1_dBm = RSSI_dBm + 20*log10(a1) - 20*log10(amax) * We store a table that is 4*20*log10(idx) - the extra 4 is to store or * maintain some extra precision. * * Values are stored in .5 db format capped at 127. */ static void mwl_node_getmimoinfo(const struct ieee80211_node *ni, struct ieee80211_mimo_info *mi) { #define CVT(_dst, _src) do { \ (_dst) = rssi + ((logdbtbl[_src] - logdbtbl[rssi_max]) >> 2); \ (_dst) = (_dst) > 64 ? 127 : ((_dst) << 1); \ } while (0) static const int8_t logdbtbl[32] = { 0, 0, 24, 38, 48, 56, 62, 68, 72, 76, 80, 83, 86, 89, 92, 94, 96, 98, 100, 102, 104, 106, 107, 109, 110, 112, 113, 115, 116, 117, 118, 119 }; const struct mwl_node *mn = MWL_NODE_CONST(ni); uint8_t rssi = mn->mn_ai.rsvd1/2; /* XXX */ uint32_t rssi_max; rssi_max = mn->mn_ai.rssi_a; if (mn->mn_ai.rssi_b > rssi_max) rssi_max = mn->mn_ai.rssi_b; if (mn->mn_ai.rssi_c > rssi_max) rssi_max = mn->mn_ai.rssi_c; CVT(mi->ch[0].rssi[0], mn->mn_ai.rssi_a); CVT(mi->ch[1].rssi[0], mn->mn_ai.rssi_b); CVT(mi->ch[2].rssi[0], mn->mn_ai.rssi_c); mi->ch[0].noise[0] = mn->mn_ai.nf_a; mi->ch[1].noise[0] = mn->mn_ai.nf_b; mi->ch[2].noise[0] = mn->mn_ai.nf_c; #undef CVT } static __inline void * mwl_getrxdma(struct mwl_softc *sc) { struct mwl_jumbo *buf; void *data; /* * Allocate from jumbo pool. */ MWL_RXFREE_LOCK(sc); buf = SLIST_FIRST(&sc->sc_rxfree); if (buf == NULL) { DPRINTF(sc, MWL_DEBUG_ANY, "%s: out of rx dma buffers\n", __func__); sc->sc_stats.mst_rx_nodmabuf++; data = NULL; } else { SLIST_REMOVE_HEAD(&sc->sc_rxfree, next); sc->sc_nrxfree--; data = MWL_JUMBO_BUF2DATA(buf); } MWL_RXFREE_UNLOCK(sc); return data; } static __inline void mwl_putrxdma(struct mwl_softc *sc, void *data) { struct mwl_jumbo *buf; /* XXX bounds check data */ MWL_RXFREE_LOCK(sc); buf = MWL_JUMBO_DATA2BUF(data); SLIST_INSERT_HEAD(&sc->sc_rxfree, buf, next); sc->sc_nrxfree++; MWL_RXFREE_UNLOCK(sc); } static int mwl_rxbuf_init(struct mwl_softc *sc, struct mwl_rxbuf *bf) { struct mwl_rxdesc *ds; ds = bf->bf_desc; if (bf->bf_data == NULL) { bf->bf_data = mwl_getrxdma(sc); if (bf->bf_data == NULL) { /* mark descriptor to be skipped */ ds->RxControl = EAGLE_RXD_CTRL_OS_OWN; /* NB: don't need PREREAD */ MWL_RXDESC_SYNC(sc, ds, BUS_DMASYNC_PREWRITE); sc->sc_stats.mst_rxbuf_failed++; return ENOMEM; } } /* * NB: DMA buffer contents is known to be unmodified * so there's no need to flush the data cache. */ /* * Setup descriptor. */ ds->QosCtrl = 0; ds->RSSI = 0; ds->Status = EAGLE_RXD_STATUS_IDLE; ds->Channel = 0; ds->PktLen = htole16(MWL_AGGR_SIZE); ds->SQ2 = 0; ds->pPhysBuffData = htole32(MWL_JUMBO_DMA_ADDR(sc, bf->bf_data)); /* NB: don't touch pPhysNext, set once */ ds->RxControl = EAGLE_RXD_CTRL_DRIVER_OWN; MWL_RXDESC_SYNC(sc, ds, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); return 0; } static void mwl_ext_free(struct mbuf *m) { struct mwl_softc *sc = m->m_ext.ext_arg1; /* XXX bounds check data */ mwl_putrxdma(sc, m->m_ext.ext_buf); /* * If we were previously blocked by a lack of rx dma buffers * check if we now have enough to restart rx interrupt handling. */ if (sc->sc_rxblocked && sc->sc_nrxfree > mwl_rxdmalow) { sc->sc_rxblocked = 0; mwl_hal_intrset(sc->sc_mh, sc->sc_imask); } } struct mwl_frame_bar { u_int8_t i_fc[2]; u_int8_t i_dur[2]; u_int8_t i_ra[IEEE80211_ADDR_LEN]; u_int8_t i_ta[IEEE80211_ADDR_LEN]; /* ctl, seq, FCS */ } __packed; /* * Like ieee80211_anyhdrsize, but handles BAR frames * specially so the logic below to piece the 802.11 * header together works. */ static __inline int mwl_anyhdrsize(const void *data) { const struct ieee80211_frame *wh = data; if (IEEE80211_IS_CTL(wh)) { switch (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) { case IEEE80211_FC0_SUBTYPE_CTS: case IEEE80211_FC0_SUBTYPE_ACK: return sizeof(struct ieee80211_frame_ack); case IEEE80211_FC0_SUBTYPE_BAR: return sizeof(struct mwl_frame_bar); } return sizeof(struct ieee80211_frame_min); } else return ieee80211_hdrsize(data); } static void mwl_handlemicerror(struct ieee80211com *ic, const uint8_t *data) { const struct ieee80211_frame *wh; struct ieee80211_node *ni; wh = (const struct ieee80211_frame *)(data + sizeof(uint16_t)); ni = ieee80211_find_rxnode(ic, (const struct ieee80211_frame_min *) wh); if (ni != NULL) { ieee80211_notify_michael_failure(ni->ni_vap, wh, 0); ieee80211_free_node(ni); } } /* * Convert hardware signal strength to rssi. The value * provided by the device has the noise floor added in; * we need to compensate for this but we don't have that * so we use a fixed value. * * The offset of 8 is good for both 2.4 and 5GHz. The LNA * offset is already set as part of the initial gain. This * will give at least +/- 3dB for 2.4GHz and +/- 5dB for 5GHz. */ static __inline int cvtrssi(uint8_t ssi) { int rssi = (int) ssi + 8; /* XXX hack guess until we have a real noise floor */ rssi = 2*(87 - rssi); /* NB: .5 dBm units */ return (rssi < 0 ? 0 : rssi > 127 ? 127 : rssi); } static void mwl_rx_proc(void *arg, int npending) { struct mwl_softc *sc = arg; struct ieee80211com *ic = &sc->sc_ic; struct mwl_rxbuf *bf; struct mwl_rxdesc *ds; struct mbuf *m; struct ieee80211_qosframe *wh; struct ieee80211_node *ni; struct mwl_node *mn; int off, len, hdrlen, pktlen, rssi, ntodo; uint8_t *data, status; void *newdata; int16_t nf; DPRINTF(sc, MWL_DEBUG_RX_PROC, "%s: pending %u rdptr 0x%x wrptr 0x%x\n", __func__, npending, RD4(sc, sc->sc_hwspecs.rxDescRead), RD4(sc, sc->sc_hwspecs.rxDescWrite)); nf = -96; /* XXX */ bf = sc->sc_rxnext; for (ntodo = mwl_rxquota; ntodo > 0; ntodo--) { if (bf == NULL) bf = STAILQ_FIRST(&sc->sc_rxbuf); ds = bf->bf_desc; data = bf->bf_data; if (data == NULL) { /* * If data allocation failed previously there * will be no buffer; try again to re-populate it. * Note the firmware will not advance to the next * descriptor with a dma buffer so we must mimic * this or we'll get out of sync. */ DPRINTF(sc, MWL_DEBUG_ANY, "%s: rx buf w/o dma memory\n", __func__); (void) mwl_rxbuf_init(sc, bf); sc->sc_stats.mst_rx_dmabufmissing++; break; } MWL_RXDESC_SYNC(sc, ds, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); if (ds->RxControl != EAGLE_RXD_CTRL_DMA_OWN) break; #ifdef MWL_DEBUG if (sc->sc_debug & MWL_DEBUG_RECV_DESC) mwl_printrxbuf(bf, 0); #endif status = ds->Status; if (status & EAGLE_RXD_STATUS_DECRYPT_ERR_MASK) { counter_u64_add(ic->ic_ierrors, 1); sc->sc_stats.mst_rx_crypto++; /* * NB: Check EAGLE_RXD_STATUS_GENERAL_DECRYPT_ERR * for backwards compatibility. */ if (status != EAGLE_RXD_STATUS_GENERAL_DECRYPT_ERR && (status & EAGLE_RXD_STATUS_TKIP_MIC_DECRYPT_ERR)) { /* * MIC error, notify upper layers. */ bus_dmamap_sync(sc->sc_rxdmat, sc->sc_rxmap, BUS_DMASYNC_POSTREAD); mwl_handlemicerror(ic, data); sc->sc_stats.mst_rx_tkipmic++; } /* XXX too painful to tap packets */ goto rx_next; } /* * Sync the data buffer. */ len = le16toh(ds->PktLen); bus_dmamap_sync(sc->sc_rxdmat, sc->sc_rxmap, BUS_DMASYNC_POSTREAD); /* * The 802.11 header is provided all or in part at the front; * use it to calculate the true size of the header that we'll * construct below. We use this to figure out where to copy * payload prior to constructing the header. */ hdrlen = mwl_anyhdrsize(data + sizeof(uint16_t)); off = sizeof(uint16_t) + sizeof(struct ieee80211_frame_addr4); /* calculate rssi early so we can re-use for each aggregate */ rssi = cvtrssi(ds->RSSI); pktlen = hdrlen + (len - off); /* * NB: we know our frame is at least as large as * IEEE80211_MIN_LEN because there is a 4-address * frame at the front. Hence there's no need to * vet the packet length. If the frame in fact * is too small it should be discarded at the * net80211 layer. */ /* * Attach dma buffer to an mbuf. We tried * doing this based on the packet size (i.e. * copying small packets) but it turns out to * be a net loss. The tradeoff might be system * dependent (cache architecture is important). */ MGETHDR(m, M_NOWAIT, MT_DATA); if (m == NULL) { DPRINTF(sc, MWL_DEBUG_ANY, "%s: no rx mbuf\n", __func__); sc->sc_stats.mst_rx_nombuf++; goto rx_next; } /* * Acquire the replacement dma buffer before * processing the frame. If we're out of dma * buffers we disable rx interrupts and wait * for the free pool to reach mlw_rxdmalow buffers * before starting to do work again. If the firmware * runs out of descriptors then it will toss frames * which is better than our doing it as that can * starve our processing. It is also important that * we always process rx'd frames in case they are * A-MPDU as otherwise the host's view of the BA * window may get out of sync with the firmware. */ newdata = mwl_getrxdma(sc); if (newdata == NULL) { /* NB: stat+msg in mwl_getrxdma */ m_free(m); /* disable RX interrupt and mark state */ mwl_hal_intrset(sc->sc_mh, sc->sc_imask &~ MACREG_A2HRIC_BIT_RX_RDY); sc->sc_rxblocked = 1; ieee80211_drain(ic); /* XXX check rxblocked and immediately start again? */ goto rx_stop; } bf->bf_data = newdata; /* * Attach the dma buffer to the mbuf; * mwl_rxbuf_init will re-setup the rx * descriptor using the replacement dma * buffer we just installed above. */ m_extadd(m, data, MWL_AGGR_SIZE, mwl_ext_free, sc, NULL, 0, EXT_NET_DRV); m->m_data += off - hdrlen; m->m_pkthdr.len = m->m_len = pktlen; /* NB: dma buffer assumed read-only */ /* * Piece 802.11 header together. */ wh = mtod(m, struct ieee80211_qosframe *); /* NB: don't need to do this sometimes but ... */ /* XXX special case so we can memcpy after m_devget? */ ovbcopy(data + sizeof(uint16_t), wh, hdrlen); if (IEEE80211_QOS_HAS_SEQ(wh)) *(uint16_t *)ieee80211_getqos(wh) = ds->QosCtrl; /* * The f/w strips WEP header but doesn't clear * the WEP bit; mark the packet with M_WEP so * net80211 will treat the data as decrypted. * While here also clear the PWR_MGT bit since * power save is handled by the firmware and * passing this up will potentially cause the * upper layer to put a station in power save * (except when configured with MWL_HOST_PS_SUPPORT). */ if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) m->m_flags |= M_WEP; #ifdef MWL_HOST_PS_SUPPORT wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED; #else wh->i_fc[1] &= ~(IEEE80211_FC1_PROTECTED | IEEE80211_FC1_PWR_MGT); #endif if (ieee80211_radiotap_active(ic)) { struct mwl_rx_radiotap_header *tap = &sc->sc_rx_th; tap->wr_flags = 0; tap->wr_rate = ds->Rate; tap->wr_antsignal = rssi + nf; tap->wr_antnoise = nf; } if (IFF_DUMPPKTS_RECV(sc, wh)) { ieee80211_dump_pkt(ic, mtod(m, caddr_t), len, ds->Rate, rssi); } /* dispatch */ ni = ieee80211_find_rxnode(ic, (const struct ieee80211_frame_min *) wh); if (ni != NULL) { mn = MWL_NODE(ni); #ifdef MWL_ANT_INFO_SUPPORT mn->mn_ai.rssi_a = ds->ai.rssi_a; mn->mn_ai.rssi_b = ds->ai.rssi_b; mn->mn_ai.rssi_c = ds->ai.rssi_c; mn->mn_ai.rsvd1 = rssi; #endif /* tag AMPDU aggregates for reorder processing */ if (ni->ni_flags & IEEE80211_NODE_HT) m->m_flags |= M_AMPDU; (void) ieee80211_input(ni, m, rssi, nf); ieee80211_free_node(ni); } else (void) ieee80211_input_all(ic, m, rssi, nf); rx_next: /* NB: ignore ENOMEM so we process more descriptors */ (void) mwl_rxbuf_init(sc, bf); bf = STAILQ_NEXT(bf, bf_list); } rx_stop: sc->sc_rxnext = bf; if (mbufq_first(&sc->sc_snd) != NULL) { /* NB: kick fw; the tx thread may have been preempted */ mwl_hal_txstart(sc->sc_mh, 0); mwl_start(sc); } } static void mwl_txq_init(struct mwl_softc *sc, struct mwl_txq *txq, int qnum) { struct mwl_txbuf *bf, *bn; struct mwl_txdesc *ds; MWL_TXQ_LOCK_INIT(sc, txq); txq->qnum = qnum; txq->txpri = 0; /* XXX */ #if 0 /* NB: q setup by mwl_txdma_setup XXX */ STAILQ_INIT(&txq->free); #endif STAILQ_FOREACH(bf, &txq->free, bf_list) { bf->bf_txq = txq; ds = bf->bf_desc; bn = STAILQ_NEXT(bf, bf_list); if (bn == NULL) bn = STAILQ_FIRST(&txq->free); ds->pPhysNext = htole32(bn->bf_daddr); } STAILQ_INIT(&txq->active); } /* * Setup a hardware data transmit queue for the specified * access control. We record the mapping from ac's * to h/w queues for use by mwl_tx_start. */ static int mwl_tx_setup(struct mwl_softc *sc, int ac, int mvtype) { struct mwl_txq *txq; if (ac >= nitems(sc->sc_ac2q)) { device_printf(sc->sc_dev, "AC %u out of range, max %zu!\n", ac, nitems(sc->sc_ac2q)); return 0; } if (mvtype >= MWL_NUM_TX_QUEUES) { device_printf(sc->sc_dev, "mvtype %u out of range, max %u!\n", mvtype, MWL_NUM_TX_QUEUES); return 0; } txq = &sc->sc_txq[mvtype]; mwl_txq_init(sc, txq, mvtype); sc->sc_ac2q[ac] = txq; return 1; } /* * Update WME parameters for a transmit queue. */ static int mwl_txq_update(struct mwl_softc *sc, int ac) { #define MWL_EXPONENT_TO_VALUE(v) ((1<sc_ic; struct chanAccParams chp; struct mwl_txq *txq = sc->sc_ac2q[ac]; struct wmeParams *wmep; struct mwl_hal *mh = sc->sc_mh; int aifs, cwmin, cwmax, txoplim; ieee80211_wme_ic_getparams(ic, &chp); wmep = &chp.cap_wmeParams[ac]; aifs = wmep->wmep_aifsn; /* XXX in sta mode need to pass log values for cwmin/max */ cwmin = MWL_EXPONENT_TO_VALUE(wmep->wmep_logcwmin); cwmax = MWL_EXPONENT_TO_VALUE(wmep->wmep_logcwmax); txoplim = wmep->wmep_txopLimit; /* NB: units of 32us */ if (mwl_hal_setedcaparams(mh, txq->qnum, cwmin, cwmax, aifs, txoplim)) { device_printf(sc->sc_dev, "unable to update hardware queue " "parameters for %s traffic!\n", ieee80211_wme_acnames[ac]); return 0; } return 1; #undef MWL_EXPONENT_TO_VALUE } /* * Callback from the 802.11 layer to update WME parameters. */ static int mwl_wme_update(struct ieee80211com *ic) { struct mwl_softc *sc = ic->ic_softc; return !mwl_txq_update(sc, WME_AC_BE) || !mwl_txq_update(sc, WME_AC_BK) || !mwl_txq_update(sc, WME_AC_VI) || !mwl_txq_update(sc, WME_AC_VO) ? EIO : 0; } /* * Reclaim resources for a setup queue. */ static void mwl_tx_cleanupq(struct mwl_softc *sc, struct mwl_txq *txq) { /* XXX hal work? */ MWL_TXQ_LOCK_DESTROY(txq); } /* * Reclaim all tx queue resources. */ static void mwl_tx_cleanup(struct mwl_softc *sc) { int i; for (i = 0; i < MWL_NUM_TX_QUEUES; i++) mwl_tx_cleanupq(sc, &sc->sc_txq[i]); } static int mwl_tx_dmasetup(struct mwl_softc *sc, struct mwl_txbuf *bf, struct mbuf *m0) { struct mbuf *m; int error; /* * Load the DMA map so any coalescing is done. This * also calculates the number of descriptors we need. */ error = bus_dmamap_load_mbuf_sg(sc->sc_dmat, bf->bf_dmamap, m0, bf->bf_segs, &bf->bf_nseg, BUS_DMA_NOWAIT); if (error == EFBIG) { /* XXX packet requires too many descriptors */ bf->bf_nseg = MWL_TXDESC+1; } else if (error != 0) { sc->sc_stats.mst_tx_busdma++; m_freem(m0); return error; } /* * Discard null packets and check for packets that * require too many TX descriptors. We try to convert * the latter to a cluster. */ if (error == EFBIG) { /* too many desc's, linearize */ sc->sc_stats.mst_tx_linear++; #if MWL_TXDESC > 1 m = m_collapse(m0, M_NOWAIT, MWL_TXDESC); #else m = m_defrag(m0, M_NOWAIT); #endif if (m == NULL) { m_freem(m0); sc->sc_stats.mst_tx_nombuf++; return ENOMEM; } m0 = m; error = bus_dmamap_load_mbuf_sg(sc->sc_dmat, bf->bf_dmamap, m0, bf->bf_segs, &bf->bf_nseg, BUS_DMA_NOWAIT); if (error != 0) { sc->sc_stats.mst_tx_busdma++; m_freem(m0); return error; } KASSERT(bf->bf_nseg <= MWL_TXDESC, ("too many segments after defrag; nseg %u", bf->bf_nseg)); } else if (bf->bf_nseg == 0) { /* null packet, discard */ sc->sc_stats.mst_tx_nodata++; m_freem(m0); return EIO; } DPRINTF(sc, MWL_DEBUG_XMIT, "%s: m %p len %u\n", __func__, m0, m0->m_pkthdr.len); bus_dmamap_sync(sc->sc_dmat, bf->bf_dmamap, BUS_DMASYNC_PREWRITE); bf->bf_m = m0; return 0; } static __inline int mwl_cvtlegacyrate(int rate) { switch (rate) { case 2: return 0; case 4: return 1; case 11: return 2; case 22: return 3; case 44: return 4; case 12: return 5; case 18: return 6; case 24: return 7; case 36: return 8; case 48: return 9; case 72: return 10; case 96: return 11; case 108:return 12; } return 0; } /* * Calculate fixed tx rate information per client state; * this value is suitable for writing to the Format field * of a tx descriptor. */ static uint16_t mwl_calcformat(uint8_t rate, const struct ieee80211_node *ni) { uint16_t fmt; fmt = _IEEE80211_SHIFTMASK(3, EAGLE_TXD_ANTENNA) | (IEEE80211_IS_CHAN_HT40D(ni->ni_chan) ? EAGLE_TXD_EXTCHAN_LO : EAGLE_TXD_EXTCHAN_HI); if (rate & IEEE80211_RATE_MCS) { /* HT MCS */ fmt |= EAGLE_TXD_FORMAT_HT /* NB: 0x80 implicitly stripped from ucastrate */ | _IEEE80211_SHIFTMASK(rate, EAGLE_TXD_RATE); /* XXX short/long GI may be wrong; re-check */ if (IEEE80211_IS_CHAN_HT40(ni->ni_chan)) { fmt |= EAGLE_TXD_CHW_40 | (ni->ni_htcap & IEEE80211_HTCAP_SHORTGI40 ? EAGLE_TXD_GI_SHORT : EAGLE_TXD_GI_LONG); } else { fmt |= EAGLE_TXD_CHW_20 | (ni->ni_htcap & IEEE80211_HTCAP_SHORTGI20 ? EAGLE_TXD_GI_SHORT : EAGLE_TXD_GI_LONG); } } else { /* legacy rate */ fmt |= EAGLE_TXD_FORMAT_LEGACY | _IEEE80211_SHIFTMASK(mwl_cvtlegacyrate(rate), EAGLE_TXD_RATE) | EAGLE_TXD_CHW_20 /* XXX iv_flags & IEEE80211_F_SHPREAMBLE? */ | (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE ? EAGLE_TXD_PREAMBLE_SHORT : EAGLE_TXD_PREAMBLE_LONG); } return fmt; } static int mwl_tx_start(struct mwl_softc *sc, struct ieee80211_node *ni, struct mwl_txbuf *bf, struct mbuf *m0) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211vap *vap = ni->ni_vap; int error, iswep, ismcast; int hdrlen, pktlen; struct mwl_txdesc *ds; struct mwl_txq *txq; struct ieee80211_frame *wh; struct mwltxrec *tr; struct mwl_node *mn; uint16_t qos; #if MWL_TXDESC > 1 int i; #endif wh = mtod(m0, struct ieee80211_frame *); iswep = wh->i_fc[1] & IEEE80211_FC1_PROTECTED; ismcast = IEEE80211_IS_MULTICAST(wh->i_addr1); hdrlen = ieee80211_anyhdrsize(wh); pktlen = m0->m_pkthdr.len; if (IEEE80211_QOS_HAS_SEQ(wh)) { qos = *(uint16_t *)ieee80211_getqos(wh); } else qos = 0; if (iswep) { const struct ieee80211_cipher *cip; struct ieee80211_key *k; /* * Construct the 802.11 header+trailer for an encrypted * frame. The only reason this can fail is because of an * unknown or unsupported cipher/key type. * * NB: we do this even though the firmware will ignore * what we've done for WEP and TKIP as we need the * ExtIV filled in for CCMP and this also adjusts * the headers which simplifies our work below. */ k = ieee80211_crypto_encap(ni, m0); if (k == NULL) { /* * This can happen when the key is yanked after the * frame was queued. Just discard the frame; the * 802.11 layer counts failures and provides * debugging/diagnostics. */ m_freem(m0); return EIO; } /* * Adjust the packet length for the crypto additions * done during encap and any other bits that the f/w * will add later on. */ cip = k->wk_cipher; pktlen += cip->ic_header + cip->ic_miclen + cip->ic_trailer; /* packet header may have moved, reset our local pointer */ wh = mtod(m0, struct ieee80211_frame *); } if (ieee80211_radiotap_active_vap(vap)) { sc->sc_tx_th.wt_flags = 0; /* XXX */ if (iswep) sc->sc_tx_th.wt_flags |= IEEE80211_RADIOTAP_F_WEP; #if 0 sc->sc_tx_th.wt_rate = ds->DataRate; #endif sc->sc_tx_th.wt_txpower = ni->ni_txpower; sc->sc_tx_th.wt_antenna = sc->sc_txantenna; ieee80211_radiotap_tx(vap, m0); } /* * Copy up/down the 802.11 header; the firmware requires * we present a 2-byte payload length followed by a * 4-address header (w/o QoS), followed (optionally) by * any WEP/ExtIV header (but only filled in for CCMP). * We are assured the mbuf has sufficient headroom to * prepend in-place by the setup of ic_headroom in * mwl_attach. */ if (hdrlen < sizeof(struct mwltxrec)) { const int space = sizeof(struct mwltxrec) - hdrlen; if (M_LEADINGSPACE(m0) < space) { /* NB: should never happen */ device_printf(sc->sc_dev, "not enough headroom, need %d found %zd, " "m_flags 0x%x m_len %d\n", space, M_LEADINGSPACE(m0), m0->m_flags, m0->m_len); ieee80211_dump_pkt(ic, mtod(m0, const uint8_t *), m0->m_len, 0, -1); m_freem(m0); sc->sc_stats.mst_tx_noheadroom++; return EIO; } M_PREPEND(m0, space, M_NOWAIT); } tr = mtod(m0, struct mwltxrec *); if (wh != (struct ieee80211_frame *) &tr->wh) ovbcopy(wh, &tr->wh, hdrlen); /* * Note: the "firmware length" is actually the length * of the fully formed "802.11 payload". That is, it's * everything except for the 802.11 header. In particular * this includes all crypto material including the MIC! */ tr->fwlen = htole16(pktlen - hdrlen); /* * Load the DMA map so any coalescing is done. This * also calculates the number of descriptors we need. */ error = mwl_tx_dmasetup(sc, bf, m0); if (error != 0) { /* NB: stat collected in mwl_tx_dmasetup */ DPRINTF(sc, MWL_DEBUG_XMIT, "%s: unable to setup dma\n", __func__); return error; } bf->bf_node = ni; /* NB: held reference */ m0 = bf->bf_m; /* NB: may have changed */ tr = mtod(m0, struct mwltxrec *); wh = (struct ieee80211_frame *)&tr->wh; /* * Formulate tx descriptor. */ ds = bf->bf_desc; txq = bf->bf_txq; ds->QosCtrl = qos; /* NB: already little-endian */ #if MWL_TXDESC == 1 /* * NB: multiframes should be zero because the descriptors * are initialized to zero. This should handle the case * where the driver is built with MWL_TXDESC=1 but we are * using firmware with multi-segment support. */ ds->PktPtr = htole32(bf->bf_segs[0].ds_addr); ds->PktLen = htole16(bf->bf_segs[0].ds_len); #else ds->multiframes = htole32(bf->bf_nseg); ds->PktLen = htole16(m0->m_pkthdr.len); for (i = 0; i < bf->bf_nseg; i++) { ds->PktPtrArray[i] = htole32(bf->bf_segs[i].ds_addr); ds->PktLenArray[i] = htole16(bf->bf_segs[i].ds_len); } #endif /* NB: pPhysNext, DataRate, and SapPktInfo setup once, don't touch */ ds->Format = 0; ds->pad = 0; ds->ack_wcb_addr = 0; mn = MWL_NODE(ni); /* * Select transmit rate. */ switch (wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) { case IEEE80211_FC0_TYPE_MGT: sc->sc_stats.mst_tx_mgmt++; /* fall thru... */ case IEEE80211_FC0_TYPE_CTL: /* NB: assign to BE q to avoid bursting */ ds->TxPriority = MWL_WME_AC_BE; break; case IEEE80211_FC0_TYPE_DATA: if (!ismcast) { const struct ieee80211_txparam *tp = ni->ni_txparms; /* * EAPOL frames get forced to a fixed rate and w/o * aggregation; otherwise check for any fixed rate * for the client (may depend on association state). */ if (m0->m_flags & M_EAPOL) { const struct mwl_vap *mvp = MWL_VAP_CONST(vap); ds->Format = mvp->mv_eapolformat; ds->pad = htole16( EAGLE_TXD_FIXED_RATE | EAGLE_TXD_DONT_AGGR); } else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) { /* XXX pre-calculate per node */ ds->Format = htole16( mwl_calcformat(tp->ucastrate, ni)); ds->pad = htole16(EAGLE_TXD_FIXED_RATE); } /* NB: EAPOL frames will never have qos set */ if (qos == 0) ds->TxPriority = txq->qnum; #if MWL_MAXBA > 3 else if (mwl_bastream_match(&mn->mn_ba[3], qos)) ds->TxPriority = mn->mn_ba[3].txq; #endif #if MWL_MAXBA > 2 else if (mwl_bastream_match(&mn->mn_ba[2], qos)) ds->TxPriority = mn->mn_ba[2].txq; #endif #if MWL_MAXBA > 1 else if (mwl_bastream_match(&mn->mn_ba[1], qos)) ds->TxPriority = mn->mn_ba[1].txq; #endif #if MWL_MAXBA > 0 else if (mwl_bastream_match(&mn->mn_ba[0], qos)) ds->TxPriority = mn->mn_ba[0].txq; #endif else ds->TxPriority = txq->qnum; } else ds->TxPriority = txq->qnum; break; default: device_printf(sc->sc_dev, "bogus frame type 0x%x (%s)\n", wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK, __func__); sc->sc_stats.mst_tx_badframetype++; m_freem(m0); return EIO; } if (IFF_DUMPPKTS_XMIT(sc)) ieee80211_dump_pkt(ic, mtod(m0, const uint8_t *)+sizeof(uint16_t), m0->m_len - sizeof(uint16_t), ds->DataRate, -1); MWL_TXQ_LOCK(txq); ds->Status = htole32(EAGLE_TXD_STATUS_FW_OWNED); STAILQ_INSERT_TAIL(&txq->active, bf, bf_list); MWL_TXDESC_SYNC(txq, ds, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); sc->sc_tx_timer = 5; MWL_TXQ_UNLOCK(txq); return 0; } static __inline int mwl_cvtlegacyrix(int rix) { static const int ieeerates[] = { 2, 4, 11, 22, 44, 12, 18, 24, 36, 48, 72, 96, 108 }; return (rix < nitems(ieeerates) ? ieeerates[rix] : 0); } /* * Process completed xmit descriptors from the specified queue. */ static int mwl_tx_processq(struct mwl_softc *sc, struct mwl_txq *txq) { #define EAGLE_TXD_STATUS_MCAST \ (EAGLE_TXD_STATUS_MULTICAST_TX | EAGLE_TXD_STATUS_BROADCAST_TX) struct ieee80211com *ic = &sc->sc_ic; struct mwl_txbuf *bf; struct mwl_txdesc *ds; struct ieee80211_node *ni; int nreaped; uint32_t status; DPRINTF(sc, MWL_DEBUG_TX_PROC, "%s: tx queue %u\n", __func__, txq->qnum); for (nreaped = 0;; nreaped++) { MWL_TXQ_LOCK(txq); bf = STAILQ_FIRST(&txq->active); if (bf == NULL) { MWL_TXQ_UNLOCK(txq); break; } ds = bf->bf_desc; MWL_TXDESC_SYNC(txq, ds, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); if (ds->Status & htole32(EAGLE_TXD_STATUS_FW_OWNED)) { MWL_TXQ_UNLOCK(txq); break; } STAILQ_REMOVE_HEAD(&txq->active, bf_list); MWL_TXQ_UNLOCK(txq); #ifdef MWL_DEBUG if (sc->sc_debug & MWL_DEBUG_XMIT_DESC) mwl_printtxbuf(bf, txq->qnum, nreaped); #endif ni = bf->bf_node; if (ni != NULL) { status = le32toh(ds->Status); if (status & EAGLE_TXD_STATUS_OK) { uint16_t Format = le16toh(ds->Format); uint8_t txant = _IEEE80211_MASKSHIFT(Format, EAGLE_TXD_ANTENNA); sc->sc_stats.mst_ant_tx[txant]++; if (status & EAGLE_TXD_STATUS_OK_RETRY) sc->sc_stats.mst_tx_retries++; if (status & EAGLE_TXD_STATUS_OK_MORE_RETRY) sc->sc_stats.mst_tx_mretries++; if (txq->qnum >= MWL_WME_AC_VO) ic->ic_wme.wme_hipri_traffic++; ni->ni_txrate = _IEEE80211_MASKSHIFT(Format, EAGLE_TXD_RATE); if ((Format & EAGLE_TXD_FORMAT_HT) == 0) { ni->ni_txrate = mwl_cvtlegacyrix( ni->ni_txrate); } else ni->ni_txrate |= IEEE80211_RATE_MCS; sc->sc_stats.mst_tx_rate = ni->ni_txrate; } else { if (status & EAGLE_TXD_STATUS_FAILED_LINK_ERROR) sc->sc_stats.mst_tx_linkerror++; if (status & EAGLE_TXD_STATUS_FAILED_XRETRY) sc->sc_stats.mst_tx_xretries++; if (status & EAGLE_TXD_STATUS_FAILED_AGING) sc->sc_stats.mst_tx_aging++; if (bf->bf_m->m_flags & M_FF) sc->sc_stats.mst_ff_txerr++; } if (bf->bf_m->m_flags & M_TXCB) /* XXX strip fw len in case header inspected */ m_adj(bf->bf_m, sizeof(uint16_t)); ieee80211_tx_complete(ni, bf->bf_m, (status & EAGLE_TXD_STATUS_OK) == 0); } else m_freem(bf->bf_m); ds->Status = htole32(EAGLE_TXD_STATUS_IDLE); bus_dmamap_sync(sc->sc_dmat, bf->bf_dmamap, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->sc_dmat, bf->bf_dmamap); mwl_puttxbuf_tail(txq, bf); } return nreaped; #undef EAGLE_TXD_STATUS_MCAST } /* * Deferred processing of transmit interrupt; special-cased * for four hardware queues, 0-3. */ static void mwl_tx_proc(void *arg, int npending) { struct mwl_softc *sc = arg; int nreaped; /* * Process each active queue. */ nreaped = 0; if (!STAILQ_EMPTY(&sc->sc_txq[0].active)) nreaped += mwl_tx_processq(sc, &sc->sc_txq[0]); if (!STAILQ_EMPTY(&sc->sc_txq[1].active)) nreaped += mwl_tx_processq(sc, &sc->sc_txq[1]); if (!STAILQ_EMPTY(&sc->sc_txq[2].active)) nreaped += mwl_tx_processq(sc, &sc->sc_txq[2]); if (!STAILQ_EMPTY(&sc->sc_txq[3].active)) nreaped += mwl_tx_processq(sc, &sc->sc_txq[3]); if (nreaped != 0) { sc->sc_tx_timer = 0; if (mbufq_first(&sc->sc_snd) != NULL) { /* NB: kick fw; the tx thread may have been preempted */ mwl_hal_txstart(sc->sc_mh, 0); mwl_start(sc); } } } static void mwl_tx_draintxq(struct mwl_softc *sc, struct mwl_txq *txq) { struct ieee80211_node *ni; struct mwl_txbuf *bf; u_int ix __unused; /* * NB: this assumes output has been stopped and * we do not need to block mwl_tx_tasklet */ for (ix = 0;; ix++) { MWL_TXQ_LOCK(txq); bf = STAILQ_FIRST(&txq->active); if (bf == NULL) { MWL_TXQ_UNLOCK(txq); break; } STAILQ_REMOVE_HEAD(&txq->active, bf_list); MWL_TXQ_UNLOCK(txq); #ifdef MWL_DEBUG if (sc->sc_debug & MWL_DEBUG_RESET) { struct ieee80211com *ic = &sc->sc_ic; const struct mwltxrec *tr = mtod(bf->bf_m, const struct mwltxrec *); mwl_printtxbuf(bf, txq->qnum, ix); ieee80211_dump_pkt(ic, (const uint8_t *)&tr->wh, bf->bf_m->m_len - sizeof(tr->fwlen), 0, -1); } #endif /* MWL_DEBUG */ bus_dmamap_unload(sc->sc_dmat, bf->bf_dmamap); ni = bf->bf_node; if (ni != NULL) { /* * Reclaim node reference. */ ieee80211_free_node(ni); } m_freem(bf->bf_m); mwl_puttxbuf_tail(txq, bf); } } /* * Drain the transmit queues and reclaim resources. */ static void mwl_draintxq(struct mwl_softc *sc) { int i; for (i = 0; i < MWL_NUM_TX_QUEUES; i++) mwl_tx_draintxq(sc, &sc->sc_txq[i]); sc->sc_tx_timer = 0; } #ifdef MWL_DIAGAPI /* * Reset the transmit queues to a pristine state after a fw download. */ static void mwl_resettxq(struct mwl_softc *sc) { int i; for (i = 0; i < MWL_NUM_TX_QUEUES; i++) mwl_txq_reset(sc, &sc->sc_txq[i]); } #endif /* MWL_DIAGAPI */ /* * Clear the transmit queues of any frames submitted for the * specified vap. This is done when the vap is deleted so we * don't potentially reference the vap after it is gone. * Note we cannot remove the frames; we only reclaim the node * reference. */ static void mwl_cleartxq(struct mwl_softc *sc, struct ieee80211vap *vap) { struct mwl_txq *txq; struct mwl_txbuf *bf; int i; for (i = 0; i < MWL_NUM_TX_QUEUES; i++) { txq = &sc->sc_txq[i]; MWL_TXQ_LOCK(txq); STAILQ_FOREACH(bf, &txq->active, bf_list) { struct ieee80211_node *ni = bf->bf_node; if (ni != NULL && ni->ni_vap == vap) { bf->bf_node = NULL; ieee80211_free_node(ni); } } MWL_TXQ_UNLOCK(txq); } } static int mwl_recv_action(struct ieee80211_node *ni, const struct ieee80211_frame *wh, const uint8_t *frm, const uint8_t *efrm) { struct mwl_softc *sc = ni->ni_ic->ic_softc; const struct ieee80211_action *ia; ia = (const struct ieee80211_action *) frm; if (ia->ia_category == IEEE80211_ACTION_CAT_HT && ia->ia_action == IEEE80211_ACTION_HT_MIMOPWRSAVE) { const struct ieee80211_action_ht_mimopowersave *mps = (const struct ieee80211_action_ht_mimopowersave *) ia; mwl_hal_setmimops(sc->sc_mh, ni->ni_macaddr, mps->am_control & IEEE80211_A_HT_MIMOPWRSAVE_ENA, _IEEE80211_MASKSHIFT(mps->am_control, IEEE80211_A_HT_MIMOPWRSAVE_MODE)); return 0; } else return sc->sc_recv_action(ni, wh, frm, efrm); } static int mwl_addba_request(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap, int dialogtoken, int baparamset, int batimeout) { struct mwl_softc *sc = ni->ni_ic->ic_softc; struct ieee80211vap *vap = ni->ni_vap; struct mwl_node *mn = MWL_NODE(ni); struct mwl_bastate *bas; bas = tap->txa_private; if (bas == NULL) { const MWL_HAL_BASTREAM *sp; /* * Check for a free BA stream slot. */ #if MWL_MAXBA > 3 if (mn->mn_ba[3].bastream == NULL) bas = &mn->mn_ba[3]; else #endif #if MWL_MAXBA > 2 if (mn->mn_ba[2].bastream == NULL) bas = &mn->mn_ba[2]; else #endif #if MWL_MAXBA > 1 if (mn->mn_ba[1].bastream == NULL) bas = &mn->mn_ba[1]; else #endif #if MWL_MAXBA > 0 if (mn->mn_ba[0].bastream == NULL) bas = &mn->mn_ba[0]; else #endif { /* sta already has max BA streams */ /* XXX assign BA stream to highest priority tid */ DPRINTF(sc, MWL_DEBUG_AMPDU, "%s: already has max bastreams\n", __func__); sc->sc_stats.mst_ampdu_reject++; return 0; } /* NB: no held reference to ni */ sp = mwl_hal_bastream_alloc(MWL_VAP(vap)->mv_hvap, (baparamset & IEEE80211_BAPS_POLICY_IMMEDIATE) != 0, ni->ni_macaddr, tap->txa_tid, ni->ni_htparam, ni, tap); if (sp == NULL) { /* * No available stream, return 0 so no * a-mpdu aggregation will be done. */ DPRINTF(sc, MWL_DEBUG_AMPDU, "%s: no bastream available\n", __func__); sc->sc_stats.mst_ampdu_nostream++; return 0; } DPRINTF(sc, MWL_DEBUG_AMPDU, "%s: alloc bastream %p\n", __func__, sp); /* NB: qos is left zero so we won't match in mwl_tx_start */ bas->bastream = sp; tap->txa_private = bas; } /* fetch current seq# from the firmware; if available */ if (mwl_hal_bastream_get_seqno(sc->sc_mh, bas->bastream, vap->iv_opmode == IEEE80211_M_STA ? vap->iv_myaddr : ni->ni_macaddr, &tap->txa_start) != 0) tap->txa_start = 0; return sc->sc_addba_request(ni, tap, dialogtoken, baparamset, batimeout); } static int mwl_addba_response(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap, int code, int baparamset, int batimeout) { struct mwl_softc *sc = ni->ni_ic->ic_softc; struct mwl_bastate *bas; bas = tap->txa_private; if (bas == NULL) { /* XXX should not happen */ DPRINTF(sc, MWL_DEBUG_AMPDU, "%s: no BA stream allocated, TID %d\n", __func__, tap->txa_tid); sc->sc_stats.mst_addba_nostream++; return 0; } if (code == IEEE80211_STATUS_SUCCESS) { struct ieee80211vap *vap = ni->ni_vap; int bufsiz, error; /* * Tell the firmware to setup the BA stream; * we know resources are available because we * pre-allocated one before forming the request. */ bufsiz = _IEEE80211_MASKSHIFT(baparamset, IEEE80211_BAPS_BUFSIZ); if (bufsiz == 0) bufsiz = IEEE80211_AGGR_BAWMAX; error = mwl_hal_bastream_create(MWL_VAP(vap)->mv_hvap, bas->bastream, bufsiz, bufsiz, tap->txa_start); if (error != 0) { /* * Setup failed, return immediately so no a-mpdu * aggregation will be done. */ mwl_hal_bastream_destroy(sc->sc_mh, bas->bastream); mwl_bastream_free(bas); tap->txa_private = NULL; DPRINTF(sc, MWL_DEBUG_AMPDU, "%s: create failed, error %d, bufsiz %d TID %d " "htparam 0x%x\n", __func__, error, bufsiz, tap->txa_tid, ni->ni_htparam); sc->sc_stats.mst_bacreate_failed++; return 0; } /* NB: cache txq to avoid ptr indirect */ mwl_bastream_setup(bas, tap->txa_tid, bas->bastream->txq); DPRINTF(sc, MWL_DEBUG_AMPDU, "%s: bastream %p assigned to txq %d TID %d bufsiz %d " "htparam 0x%x\n", __func__, bas->bastream, bas->txq, tap->txa_tid, bufsiz, ni->ni_htparam); } else { /* * Other side NAK'd us; return the resources. */ DPRINTF(sc, MWL_DEBUG_AMPDU, "%s: request failed with code %d, destroy bastream %p\n", __func__, code, bas->bastream); mwl_hal_bastream_destroy(sc->sc_mh, bas->bastream); mwl_bastream_free(bas); tap->txa_private = NULL; } /* NB: firmware sends BAR so we don't need to */ return sc->sc_addba_response(ni, tap, code, baparamset, batimeout); } static void mwl_addba_stop(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap) { struct mwl_softc *sc = ni->ni_ic->ic_softc; struct mwl_bastate *bas; bas = tap->txa_private; if (bas != NULL) { DPRINTF(sc, MWL_DEBUG_AMPDU, "%s: destroy bastream %p\n", __func__, bas->bastream); mwl_hal_bastream_destroy(sc->sc_mh, bas->bastream); mwl_bastream_free(bas); tap->txa_private = NULL; } sc->sc_addba_stop(ni, tap); } /* * Setup the rx data structures. This should only be * done once or we may get out of sync with the firmware. */ static int mwl_startrecv(struct mwl_softc *sc) { if (!sc->sc_recvsetup) { struct mwl_rxbuf *bf, *prev; struct mwl_rxdesc *ds; prev = NULL; STAILQ_FOREACH(bf, &sc->sc_rxbuf, bf_list) { int error = mwl_rxbuf_init(sc, bf); if (error != 0) { DPRINTF(sc, MWL_DEBUG_RECV, "%s: mwl_rxbuf_init failed %d\n", __func__, error); return error; } if (prev != NULL) { ds = prev->bf_desc; ds->pPhysNext = htole32(bf->bf_daddr); } prev = bf; } if (prev != NULL) { ds = prev->bf_desc; ds->pPhysNext = htole32(STAILQ_FIRST(&sc->sc_rxbuf)->bf_daddr); } sc->sc_recvsetup = 1; } mwl_mode_init(sc); /* set filters, etc. */ return 0; } static MWL_HAL_APMODE mwl_getapmode(const struct ieee80211vap *vap, struct ieee80211_channel *chan) { MWL_HAL_APMODE mode; if (IEEE80211_IS_CHAN_HT(chan)) { if (vap->iv_flags_ht & IEEE80211_FHT_PUREN) mode = AP_MODE_N_ONLY; else if (IEEE80211_IS_CHAN_5GHZ(chan)) mode = AP_MODE_AandN; else if (vap->iv_flags & IEEE80211_F_PUREG) mode = AP_MODE_GandN; else mode = AP_MODE_BandGandN; } else if (IEEE80211_IS_CHAN_ANYG(chan)) { if (vap->iv_flags & IEEE80211_F_PUREG) mode = AP_MODE_G_ONLY; else mode = AP_MODE_MIXED; } else if (IEEE80211_IS_CHAN_B(chan)) mode = AP_MODE_B_ONLY; else if (IEEE80211_IS_CHAN_A(chan)) mode = AP_MODE_A_ONLY; else mode = AP_MODE_MIXED; /* XXX should not happen? */ return mode; } static int mwl_setapmode(struct ieee80211vap *vap, struct ieee80211_channel *chan) { struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap; return mwl_hal_setapmode(hvap, mwl_getapmode(vap, chan)); } /* * Set/change channels. */ static int mwl_chan_set(struct mwl_softc *sc, struct ieee80211_channel *chan) { struct mwl_hal *mh = sc->sc_mh; struct ieee80211com *ic = &sc->sc_ic; MWL_HAL_CHANNEL hchan; int maxtxpow; DPRINTF(sc, MWL_DEBUG_RESET, "%s: chan %u MHz/flags 0x%x\n", __func__, chan->ic_freq, chan->ic_flags); /* * Convert to a HAL channel description with * the flags constrained to reflect the current * operating mode. */ mwl_mapchan(&hchan, chan); mwl_hal_intrset(mh, 0); /* disable interrupts */ #if 0 mwl_draintxq(sc); /* clear pending tx frames */ #endif mwl_hal_setchannel(mh, &hchan); /* * Tx power is cap'd by the regulatory setting and * possibly a user-set limit. We pass the min of * these to the hal to apply them to the cal data * for this channel. * XXX min bound? */ maxtxpow = 2*chan->ic_maxregpower; if (maxtxpow > ic->ic_txpowlimit) maxtxpow = ic->ic_txpowlimit; mwl_hal_settxpower(mh, &hchan, maxtxpow / 2); /* NB: potentially change mcast/mgt rates */ mwl_setcurchanrates(sc); /* * Update internal state. */ sc->sc_tx_th.wt_chan_freq = htole16(chan->ic_freq); sc->sc_rx_th.wr_chan_freq = htole16(chan->ic_freq); if (IEEE80211_IS_CHAN_A(chan)) { sc->sc_tx_th.wt_chan_flags = htole16(IEEE80211_CHAN_A); sc->sc_rx_th.wr_chan_flags = htole16(IEEE80211_CHAN_A); } else if (IEEE80211_IS_CHAN_ANYG(chan)) { sc->sc_tx_th.wt_chan_flags = htole16(IEEE80211_CHAN_G); sc->sc_rx_th.wr_chan_flags = htole16(IEEE80211_CHAN_G); } else { sc->sc_tx_th.wt_chan_flags = htole16(IEEE80211_CHAN_B); sc->sc_rx_th.wr_chan_flags = htole16(IEEE80211_CHAN_B); } sc->sc_curchan = hchan; mwl_hal_intrset(mh, sc->sc_imask); return 0; } static void mwl_scan_start(struct ieee80211com *ic) { struct mwl_softc *sc = ic->ic_softc; DPRINTF(sc, MWL_DEBUG_STATE, "%s\n", __func__); } static void mwl_scan_end(struct ieee80211com *ic) { struct mwl_softc *sc = ic->ic_softc; DPRINTF(sc, MWL_DEBUG_STATE, "%s\n", __func__); } static void mwl_set_channel(struct ieee80211com *ic) { struct mwl_softc *sc = ic->ic_softc; (void) mwl_chan_set(sc, ic->ic_curchan); } /* * Handle a channel switch request. We inform the firmware * and mark the global state to suppress various actions. * NB: we issue only one request to the fw; we may be called * multiple times if there are multiple vap's. */ static void mwl_startcsa(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct mwl_softc *sc = ic->ic_softc; MWL_HAL_CHANNEL hchan; if (sc->sc_csapending) return; mwl_mapchan(&hchan, ic->ic_csa_newchan); /* 1 =>'s quiet channel */ mwl_hal_setchannelswitchie(sc->sc_mh, &hchan, 1, ic->ic_csa_count); sc->sc_csapending = 1; } /* * Plumb any static WEP key for the station. This is * necessary as we must propagate the key from the * global key table of the vap to each sta db entry. */ static void mwl_setanywepkey(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN]) { if ((vap->iv_flags & (IEEE80211_F_PRIVACY|IEEE80211_F_WPA)) == IEEE80211_F_PRIVACY && vap->iv_def_txkey != IEEE80211_KEYIX_NONE && vap->iv_nw_keys[vap->iv_def_txkey].wk_keyix != IEEE80211_KEYIX_NONE) (void) _mwl_key_set(vap, &vap->iv_nw_keys[vap->iv_def_txkey], mac); } static int mwl_peerstadb(struct ieee80211_node *ni, int aid, int staid, MWL_HAL_PEERINFO *pi) { #define WME(ie) ((const struct ieee80211_wme_info *) ie) struct ieee80211vap *vap = ni->ni_vap; struct mwl_hal_vap *hvap; int error; if (vap->iv_opmode == IEEE80211_M_WDS) { /* * WDS vap's do not have a f/w vap; instead they piggyback * on an AP vap and we must install the sta db entry and * crypto state using that AP's handle (the WDS vap has none). */ hvap = MWL_VAP(vap)->mv_ap_hvap; } else hvap = MWL_VAP(vap)->mv_hvap; error = mwl_hal_newstation(hvap, ni->ni_macaddr, aid, staid, pi, ni->ni_flags & (IEEE80211_NODE_QOS | IEEE80211_NODE_HT), ni->ni_ies.wme_ie != NULL ? WME(ni->ni_ies.wme_ie)->wme_info : 0); if (error == 0) { /* * Setup security for this station. For sta mode this is * needed even though do the same thing on transition to * AUTH state because the call to mwl_hal_newstation * clobbers the crypto state we setup. */ mwl_setanywepkey(vap, ni->ni_macaddr); } return error; #undef WME } static void mwl_setglobalkeys(struct ieee80211vap *vap) { struct ieee80211_key *wk; wk = &vap->iv_nw_keys[0]; for (; wk < &vap->iv_nw_keys[IEEE80211_WEP_NKID]; wk++) if (wk->wk_keyix != IEEE80211_KEYIX_NONE) (void) _mwl_key_set(vap, wk, vap->iv_myaddr); } /* * Convert a legacy rate set to a firmware bitmask. */ static uint32_t get_rate_bitmap(const struct ieee80211_rateset *rs) { uint32_t rates; int i; rates = 0; for (i = 0; i < rs->rs_nrates; i++) switch (rs->rs_rates[i] & IEEE80211_RATE_VAL) { case 2: rates |= 0x001; break; case 4: rates |= 0x002; break; case 11: rates |= 0x004; break; case 22: rates |= 0x008; break; case 44: rates |= 0x010; break; case 12: rates |= 0x020; break; case 18: rates |= 0x040; break; case 24: rates |= 0x080; break; case 36: rates |= 0x100; break; case 48: rates |= 0x200; break; case 72: rates |= 0x400; break; case 96: rates |= 0x800; break; case 108: rates |= 0x1000; break; } return rates; } /* * Construct an HT firmware bitmask from an HT rate set. */ static uint32_t get_htrate_bitmap(const struct ieee80211_htrateset *rs) { uint32_t rates; int i; rates = 0; for (i = 0; i < rs->rs_nrates; i++) { if (rs->rs_rates[i] < 16) rates |= 1<rs_rates[i]; } return rates; } /* * Craft station database entry for station. * NB: use host byte order here, the hal handles byte swapping. */ static MWL_HAL_PEERINFO * mkpeerinfo(MWL_HAL_PEERINFO *pi, const struct ieee80211_node *ni) { const struct ieee80211vap *vap = ni->ni_vap; memset(pi, 0, sizeof(*pi)); pi->LegacyRateBitMap = get_rate_bitmap(&ni->ni_rates); pi->CapInfo = ni->ni_capinfo; if (ni->ni_flags & IEEE80211_NODE_HT) { /* HT capabilities, etc */ pi->HTCapabilitiesInfo = ni->ni_htcap; /* XXX pi.HTCapabilitiesInfo */ pi->MacHTParamInfo = ni->ni_htparam; pi->HTRateBitMap = get_htrate_bitmap(&ni->ni_htrates); pi->AddHtInfo.ControlChan = ni->ni_htctlchan; pi->AddHtInfo.AddChan = ni->ni_ht2ndchan; pi->AddHtInfo.OpMode = ni->ni_htopmode; pi->AddHtInfo.stbc = ni->ni_htstbc; /* constrain according to local configuration */ if ((vap->iv_flags_ht & IEEE80211_FHT_SHORTGI40) == 0) pi->HTCapabilitiesInfo &= ~IEEE80211_HTCAP_SHORTGI40; if ((vap->iv_flags_ht & IEEE80211_FHT_SHORTGI20) == 0) pi->HTCapabilitiesInfo &= ~IEEE80211_HTCAP_SHORTGI20; - if (ni->ni_chw != 40) + if (ni->ni_chw != IEEE80211_STA_RX_BW_40) pi->HTCapabilitiesInfo &= ~IEEE80211_HTCAP_CHWIDTH40; } return pi; } /* * Re-create the local sta db entry for a vap to ensure * up to date WME state is pushed to the firmware. Because * this resets crypto state this must be followed by a * reload of any keys in the global key table. */ static int mwl_localstadb(struct ieee80211vap *vap) { #define WME(ie) ((const struct ieee80211_wme_info *) ie) struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap; struct ieee80211_node *bss; MWL_HAL_PEERINFO pi; int error; switch (vap->iv_opmode) { case IEEE80211_M_STA: bss = vap->iv_bss; error = mwl_hal_newstation(hvap, vap->iv_myaddr, 0, 0, vap->iv_state == IEEE80211_S_RUN ? mkpeerinfo(&pi, bss) : NULL, (bss->ni_flags & (IEEE80211_NODE_QOS | IEEE80211_NODE_HT)), bss->ni_ies.wme_ie != NULL ? WME(bss->ni_ies.wme_ie)->wme_info : 0); if (error == 0) mwl_setglobalkeys(vap); break; case IEEE80211_M_HOSTAP: case IEEE80211_M_MBSS: error = mwl_hal_newstation(hvap, vap->iv_myaddr, 0, 0, NULL, vap->iv_flags & IEEE80211_F_WME, 0); if (error == 0) mwl_setglobalkeys(vap); break; default: error = 0; break; } return error; #undef WME } static int mwl_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct mwl_vap *mvp = MWL_VAP(vap); struct mwl_hal_vap *hvap = mvp->mv_hvap; struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni = NULL; struct mwl_softc *sc = ic->ic_softc; struct mwl_hal *mh = sc->sc_mh; enum ieee80211_state ostate = vap->iv_state; int error; DPRINTF(sc, MWL_DEBUG_STATE, "%s: %s: %s -> %s\n", if_name(vap->iv_ifp), __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate]); callout_stop(&sc->sc_timer); /* * Clear current radar detection state. */ if (ostate == IEEE80211_S_CAC) { /* stop quiet mode radar detection */ mwl_hal_setradardetection(mh, DR_CHK_CHANNEL_AVAILABLE_STOP); } else if (sc->sc_radarena) { /* stop in-service radar detection */ mwl_hal_setradardetection(mh, DR_DFS_DISABLE); sc->sc_radarena = 0; } /* * Carry out per-state actions before doing net80211 work. */ if (nstate == IEEE80211_S_INIT) { /* NB: only ap+sta vap's have a fw entity */ if (hvap != NULL) mwl_hal_stop(hvap); } else if (nstate == IEEE80211_S_SCAN) { mwl_hal_start(hvap); /* NB: this disables beacon frames */ mwl_hal_setinframode(hvap); } else if (nstate == IEEE80211_S_AUTH) { /* * Must create a sta db entry in case a WEP key needs to * be plumbed. This entry will be overwritten if we * associate; otherwise it will be reclaimed on node free. */ ni = vap->iv_bss; MWL_NODE(ni)->mn_hvap = hvap; (void) mwl_peerstadb(ni, 0, 0, NULL); } else if (nstate == IEEE80211_S_CSA) { /* XXX move to below? */ if (vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_MBSS) mwl_startcsa(vap); } else if (nstate == IEEE80211_S_CAC) { /* XXX move to below? */ /* stop ap xmit and enable quiet mode radar detection */ mwl_hal_setradardetection(mh, DR_CHK_CHANNEL_AVAILABLE_START); } /* * Invoke the parent method to do net80211 work. */ error = mvp->mv_newstate(vap, nstate, arg); /* * Carry out work that must be done after net80211 runs; * this work requires up to date state (e.g. iv_bss). */ if (error == 0 && nstate == IEEE80211_S_RUN) { /* NB: collect bss node again, it may have changed */ ni = vap->iv_bss; DPRINTF(sc, MWL_DEBUG_STATE, "%s: %s(RUN): iv_flags 0x%08x bintvl %d bssid %s " "capinfo 0x%04x chan %d\n", if_name(vap->iv_ifp), __func__, vap->iv_flags, ni->ni_intval, ether_sprintf(ni->ni_bssid), ni->ni_capinfo, ieee80211_chan2ieee(ic, ic->ic_curchan)); /* * Recreate local sta db entry to update WME/HT state. */ mwl_localstadb(vap); switch (vap->iv_opmode) { case IEEE80211_M_HOSTAP: case IEEE80211_M_MBSS: if (ostate == IEEE80211_S_CAC) { /* enable in-service radar detection */ mwl_hal_setradardetection(mh, DR_IN_SERVICE_MONITOR_START); sc->sc_radarena = 1; } /* * Allocate and setup the beacon frame * (and related state). */ error = mwl_reset_vap(vap, IEEE80211_S_RUN); if (error != 0) { DPRINTF(sc, MWL_DEBUG_STATE, "%s: beacon setup failed, error %d\n", __func__, error); goto bad; } /* NB: must be after setting up beacon */ mwl_hal_start(hvap); break; case IEEE80211_M_STA: DPRINTF(sc, MWL_DEBUG_STATE, "%s: %s: aid 0x%x\n", if_name(vap->iv_ifp), __func__, ni->ni_associd); /* * Set state now that we're associated. */ mwl_hal_setassocid(hvap, ni->ni_bssid, ni->ni_associd); mwl_setrates(vap); mwl_hal_setrtsthreshold(hvap, vap->iv_rtsthreshold); if ((vap->iv_flags & IEEE80211_F_DWDS) && sc->sc_ndwdsvaps++ == 0) mwl_hal_setdwds(mh, 1); break; case IEEE80211_M_WDS: DPRINTF(sc, MWL_DEBUG_STATE, "%s: %s: bssid %s\n", if_name(vap->iv_ifp), __func__, ether_sprintf(ni->ni_bssid)); mwl_seteapolformat(vap); break; default: break; } /* * Set CS mode according to operating channel; * this mostly an optimization for 5GHz. * * NB: must follow mwl_hal_start which resets csmode */ if (IEEE80211_IS_CHAN_5GHZ(ic->ic_bsschan)) mwl_hal_setcsmode(mh, CSMODE_AGGRESSIVE); else mwl_hal_setcsmode(mh, CSMODE_AUTO_ENA); /* * Start timer to prod firmware. */ if (sc->sc_ageinterval != 0) callout_reset(&sc->sc_timer, sc->sc_ageinterval*hz, mwl_agestations, sc); } else if (nstate == IEEE80211_S_SLEEP) { /* XXX set chip in power save */ } else if ((vap->iv_flags & IEEE80211_F_DWDS) && --sc->sc_ndwdsvaps == 0) mwl_hal_setdwds(mh, 0); bad: return error; } /* * Manage station id's; these are separate from AID's * as AID's may have values out of the range of possible * station id's acceptable to the firmware. */ static int allocstaid(struct mwl_softc *sc, int aid) { int staid; if (!(0 < aid && aid < MWL_MAXSTAID) || isset(sc->sc_staid, aid)) { /* NB: don't use 0 */ for (staid = 1; staid < MWL_MAXSTAID; staid++) if (isclr(sc->sc_staid, staid)) break; } else staid = aid; setbit(sc->sc_staid, staid); return staid; } static void delstaid(struct mwl_softc *sc, int staid) { clrbit(sc->sc_staid, staid); } /* * Setup driver-specific state for a newly associated node. * Note that we're called also on a re-associate, the isnew * param tells us if this is the first time or not. */ static void mwl_newassoc(struct ieee80211_node *ni, int isnew) { struct ieee80211vap *vap = ni->ni_vap; struct mwl_softc *sc = vap->iv_ic->ic_softc; struct mwl_node *mn = MWL_NODE(ni); MWL_HAL_PEERINFO pi; uint16_t aid; int error; aid = IEEE80211_AID(ni->ni_associd); if (isnew) { mn->mn_staid = allocstaid(sc, aid); mn->mn_hvap = MWL_VAP(vap)->mv_hvap; } else { mn = MWL_NODE(ni); /* XXX reset BA stream? */ } DPRINTF(sc, MWL_DEBUG_NODE, "%s: mac %s isnew %d aid %d staid %d\n", __func__, ether_sprintf(ni->ni_macaddr), isnew, aid, mn->mn_staid); error = mwl_peerstadb(ni, aid, mn->mn_staid, mkpeerinfo(&pi, ni)); if (error != 0) { DPRINTF(sc, MWL_DEBUG_NODE, "%s: error %d creating sta db entry\n", __func__, error); /* XXX how to deal with error? */ } } /* * Periodically poke the firmware to age out station state * (power save queues, pending tx aggregates). */ static void mwl_agestations(void *arg) { struct mwl_softc *sc = arg; mwl_hal_setkeepalive(sc->sc_mh); if (sc->sc_ageinterval != 0) /* NB: catch dynamic changes */ callout_schedule(&sc->sc_timer, sc->sc_ageinterval*hz); } static const struct mwl_hal_channel * findhalchannel(const MWL_HAL_CHANNELINFO *ci, int ieee) { int i; for (i = 0; i < ci->nchannels; i++) { const struct mwl_hal_channel *hc = &ci->channels[i]; if (hc->ieee == ieee) return hc; } return NULL; } static int mwl_setregdomain(struct ieee80211com *ic, struct ieee80211_regdomain *rd, int nchan, struct ieee80211_channel chans[]) { struct mwl_softc *sc = ic->ic_softc; struct mwl_hal *mh = sc->sc_mh; const MWL_HAL_CHANNELINFO *ci; int i; for (i = 0; i < nchan; i++) { struct ieee80211_channel *c = &chans[i]; const struct mwl_hal_channel *hc; if (IEEE80211_IS_CHAN_2GHZ(c)) { mwl_hal_getchannelinfo(mh, MWL_FREQ_BAND_2DOT4GHZ, IEEE80211_IS_CHAN_HT40(c) ? MWL_CH_40_MHz_WIDTH : MWL_CH_20_MHz_WIDTH, &ci); } else if (IEEE80211_IS_CHAN_5GHZ(c)) { mwl_hal_getchannelinfo(mh, MWL_FREQ_BAND_5GHZ, IEEE80211_IS_CHAN_HT40(c) ? MWL_CH_40_MHz_WIDTH : MWL_CH_20_MHz_WIDTH, &ci); } else { device_printf(sc->sc_dev, "%s: channel %u freq %u/0x%x not 2.4/5GHz\n", __func__, c->ic_ieee, c->ic_freq, c->ic_flags); return EINVAL; } /* * Verify channel has cal data and cap tx power. */ hc = findhalchannel(ci, c->ic_ieee); if (hc != NULL) { if (c->ic_maxpower > 2*hc->maxTxPow) c->ic_maxpower = 2*hc->maxTxPow; goto next; } if (IEEE80211_IS_CHAN_HT40(c)) { /* * Look for the extension channel since the * hal table only has the primary channel. */ hc = findhalchannel(ci, c->ic_extieee); if (hc != NULL) { if (c->ic_maxpower > 2*hc->maxTxPow) c->ic_maxpower = 2*hc->maxTxPow; goto next; } } device_printf(sc->sc_dev, "%s: no cal data for channel %u ext %u freq %u/0x%x\n", __func__, c->ic_ieee, c->ic_extieee, c->ic_freq, c->ic_flags); return EINVAL; next: ; } return 0; } #define IEEE80211_CHAN_HTG (IEEE80211_CHAN_HT|IEEE80211_CHAN_G) #define IEEE80211_CHAN_HTA (IEEE80211_CHAN_HT|IEEE80211_CHAN_A) static void addht40channels(struct ieee80211_channel chans[], int maxchans, int *nchans, const MWL_HAL_CHANNELINFO *ci, int flags) { int i, error; for (i = 0; i < ci->nchannels; i++) { const struct mwl_hal_channel *hc = &ci->channels[i]; error = ieee80211_add_channel_ht40(chans, maxchans, nchans, hc->ieee, hc->maxTxPow, flags); if (error != 0 && error != ENOENT) break; } } static void addchannels(struct ieee80211_channel chans[], int maxchans, int *nchans, const MWL_HAL_CHANNELINFO *ci, const uint8_t bands[]) { int i, error; error = 0; for (i = 0; i < ci->nchannels && error == 0; i++) { const struct mwl_hal_channel *hc = &ci->channels[i]; error = ieee80211_add_channel(chans, maxchans, nchans, hc->ieee, hc->freq, hc->maxTxPow, 0, bands); } } static void getchannels(struct mwl_softc *sc, int maxchans, int *nchans, struct ieee80211_channel chans[]) { const MWL_HAL_CHANNELINFO *ci; uint8_t bands[IEEE80211_MODE_BYTES]; /* * Use the channel info from the hal to craft the * channel list. Note that we pass back an unsorted * list; the caller is required to sort it for us * (if desired). */ *nchans = 0; if (mwl_hal_getchannelinfo(sc->sc_mh, MWL_FREQ_BAND_2DOT4GHZ, MWL_CH_20_MHz_WIDTH, &ci) == 0) { memset(bands, 0, sizeof(bands)); setbit(bands, IEEE80211_MODE_11B); setbit(bands, IEEE80211_MODE_11G); setbit(bands, IEEE80211_MODE_11NG); addchannels(chans, maxchans, nchans, ci, bands); } if (mwl_hal_getchannelinfo(sc->sc_mh, MWL_FREQ_BAND_5GHZ, MWL_CH_20_MHz_WIDTH, &ci) == 0) { memset(bands, 0, sizeof(bands)); setbit(bands, IEEE80211_MODE_11A); setbit(bands, IEEE80211_MODE_11NA); addchannels(chans, maxchans, nchans, ci, bands); } if (mwl_hal_getchannelinfo(sc->sc_mh, MWL_FREQ_BAND_2DOT4GHZ, MWL_CH_40_MHz_WIDTH, &ci) == 0) addht40channels(chans, maxchans, nchans, ci, IEEE80211_CHAN_HTG); if (mwl_hal_getchannelinfo(sc->sc_mh, MWL_FREQ_BAND_5GHZ, MWL_CH_40_MHz_WIDTH, &ci) == 0) addht40channels(chans, maxchans, nchans, ci, IEEE80211_CHAN_HTA); } static void mwl_getradiocaps(struct ieee80211com *ic, int maxchans, int *nchans, struct ieee80211_channel chans[]) { struct mwl_softc *sc = ic->ic_softc; getchannels(sc, maxchans, nchans, chans); } static int mwl_getchannels(struct mwl_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; /* * Use the channel info from the hal to craft the * channel list for net80211. Note that we pass up * an unsorted list; net80211 will sort it for us. */ memset(ic->ic_channels, 0, sizeof(ic->ic_channels)); ic->ic_nchans = 0; getchannels(sc, IEEE80211_CHAN_MAX, &ic->ic_nchans, ic->ic_channels); ic->ic_regdomain.regdomain = SKU_DEBUG; ic->ic_regdomain.country = CTRY_DEFAULT; ic->ic_regdomain.location = 'I'; ic->ic_regdomain.isocc[0] = ' '; /* XXX? */ ic->ic_regdomain.isocc[1] = ' '; return (ic->ic_nchans == 0 ? EIO : 0); } #undef IEEE80211_CHAN_HTA #undef IEEE80211_CHAN_HTG #ifdef MWL_DEBUG static void mwl_printrxbuf(const struct mwl_rxbuf *bf, u_int ix) { const struct mwl_rxdesc *ds = bf->bf_desc; uint32_t status = le32toh(ds->Status); printf("R[%2u] (DS.V:%p DS.P:0x%jx) NEXT:%08x DATA:%08x RC:%02x%s\n" " STAT:%02x LEN:%04x RSSI:%02x CHAN:%02x RATE:%02x QOS:%04x HT:%04x\n", ix, ds, (uintmax_t)bf->bf_daddr, le32toh(ds->pPhysNext), le32toh(ds->pPhysBuffData), ds->RxControl, ds->RxControl != EAGLE_RXD_CTRL_DRIVER_OWN ? "" : (status & EAGLE_RXD_STATUS_OK) ? " *" : " !", ds->Status, le16toh(ds->PktLen), ds->RSSI, ds->Channel, ds->Rate, le16toh(ds->QosCtrl), le16toh(ds->HtSig2)); } static void mwl_printtxbuf(const struct mwl_txbuf *bf, u_int qnum, u_int ix) { const struct mwl_txdesc *ds = bf->bf_desc; uint32_t status = le32toh(ds->Status); printf("Q%u[%3u]", qnum, ix); printf(" (DS.V:%p DS.P:0x%jx)\n", ds, (uintmax_t)bf->bf_daddr); printf(" NEXT:%08x DATA:%08x LEN:%04x STAT:%08x%s\n", le32toh(ds->pPhysNext), le32toh(ds->PktPtr), le16toh(ds->PktLen), status, status & EAGLE_TXD_STATUS_USED ? "" : (status & 3) != 0 ? " *" : " !"); printf(" RATE:%02x PRI:%x QOS:%04x SAP:%08x FORMAT:%04x\n", ds->DataRate, ds->TxPriority, le16toh(ds->QosCtrl), le32toh(ds->SapPktInfo), le16toh(ds->Format)); #if MWL_TXDESC > 1 printf(" MULTIFRAMES:%u LEN:%04x %04x %04x %04x %04x %04x\n" , le32toh(ds->multiframes) , le16toh(ds->PktLenArray[0]), le16toh(ds->PktLenArray[1]) , le16toh(ds->PktLenArray[2]), le16toh(ds->PktLenArray[3]) , le16toh(ds->PktLenArray[4]), le16toh(ds->PktLenArray[5]) ); printf(" DATA:%08x %08x %08x %08x %08x %08x\n" , le32toh(ds->PktPtrArray[0]), le32toh(ds->PktPtrArray[1]) , le32toh(ds->PktPtrArray[2]), le32toh(ds->PktPtrArray[3]) , le32toh(ds->PktPtrArray[4]), le32toh(ds->PktPtrArray[5]) ); #endif #if 0 { const uint8_t *cp = (const uint8_t *) ds; int i; for (i = 0; i < sizeof(struct mwl_txdesc); i++) { printf("%02x ", cp[i]); if (((i+1) % 16) == 0) printf("\n"); } printf("\n"); } #endif } #endif /* MWL_DEBUG */ #if 0 static void mwl_txq_dump(struct mwl_txq *txq) { struct mwl_txbuf *bf; int i = 0; MWL_TXQ_LOCK(txq); STAILQ_FOREACH(bf, &txq->active, bf_list) { struct mwl_txdesc *ds = bf->bf_desc; MWL_TXDESC_SYNC(txq, ds, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); #ifdef MWL_DEBUG mwl_printtxbuf(bf, txq->qnum, i); #endif i++; } MWL_TXQ_UNLOCK(txq); } #endif static void mwl_watchdog(void *arg) { struct mwl_softc *sc = arg; callout_reset(&sc->sc_watchdog, hz, mwl_watchdog, sc); if (sc->sc_tx_timer == 0 || --sc->sc_tx_timer > 0) return; if (sc->sc_running && !sc->sc_invalid) { if (mwl_hal_setkeepalive(sc->sc_mh)) device_printf(sc->sc_dev, "transmit timeout (firmware hung?)\n"); else device_printf(sc->sc_dev, "transmit timeout\n"); #if 0 mwl_reset(sc); mwl_txq_dump(&sc->sc_txq[0]);/*XXX*/ #endif counter_u64_add(sc->sc_ic.ic_oerrors, 1); sc->sc_stats.mst_watchdog++; } } #ifdef MWL_DIAGAPI /* * Diagnostic interface to the HAL. This is used by various * tools to do things like retrieve register contents for * debugging. The mechanism is intentionally opaque so that * it can change frequently w/o concern for compatibility. */ static int mwl_ioctl_diag(struct mwl_softc *sc, struct mwl_diag *md) { struct mwl_hal *mh = sc->sc_mh; u_int id = md->md_id & MWL_DIAG_ID; void *indata = NULL; void *outdata = NULL; u_int32_t insize = md->md_in_size; u_int32_t outsize = md->md_out_size; int error = 0; if (md->md_id & MWL_DIAG_IN) { /* * Copy in data. */ indata = malloc(insize, M_TEMP, M_NOWAIT); if (indata == NULL) { error = ENOMEM; goto bad; } error = copyin(md->md_in_data, indata, insize); if (error) goto bad; } if (md->md_id & MWL_DIAG_DYN) { /* * Allocate a buffer for the results (otherwise the HAL * returns a pointer to a buffer where we can read the * results). Note that we depend on the HAL leaving this * pointer for us to use below in reclaiming the buffer; * may want to be more defensive. */ outdata = malloc(outsize, M_TEMP, M_NOWAIT); if (outdata == NULL) { error = ENOMEM; goto bad; } } if (mwl_hal_getdiagstate(mh, id, indata, insize, &outdata, &outsize)) { if (outsize < md->md_out_size) md->md_out_size = outsize; if (outdata != NULL) error = copyout(outdata, md->md_out_data, md->md_out_size); } else { error = EINVAL; } bad: if ((md->md_id & MWL_DIAG_IN) && indata != NULL) free(indata, M_TEMP); if ((md->md_id & MWL_DIAG_DYN) && outdata != NULL) free(outdata, M_TEMP); return error; } static int mwl_ioctl_reset(struct mwl_softc *sc, struct mwl_diag *md) { struct mwl_hal *mh = sc->sc_mh; int error; MWL_LOCK_ASSERT(sc); if (md->md_id == 0 && mwl_hal_fwload(mh, NULL) != 0) { device_printf(sc->sc_dev, "unable to load firmware\n"); return EIO; } if (mwl_hal_gethwspecs(mh, &sc->sc_hwspecs) != 0) { device_printf(sc->sc_dev, "unable to fetch h/w specs\n"); return EIO; } error = mwl_setupdma(sc); if (error != 0) { /* NB: mwl_setupdma prints a msg */ return error; } /* * Reset tx/rx data structures; after reload we must * re-start the driver's notion of the next xmit/recv. */ mwl_draintxq(sc); /* clear pending frames */ mwl_resettxq(sc); /* rebuild tx q lists */ sc->sc_rxnext = NULL; /* force rx to start at the list head */ return 0; } #endif /* MWL_DIAGAPI */ static void mwl_parent(struct ieee80211com *ic) { struct mwl_softc *sc = ic->ic_softc; int startall = 0; MWL_LOCK(sc); if (ic->ic_nrunning > 0) { if (sc->sc_running) { /* * To avoid rescanning another access point, * do not call mwl_init() here. Instead, * only reflect promisc mode settings. */ mwl_mode_init(sc); } else { /* * Beware of being called during attach/detach * to reset promiscuous mode. In that case we * will still be marked UP but not RUNNING. * However trying to re-init the interface * is the wrong thing to do as we've already * torn down much of our state. There's * probably a better way to deal with this. */ if (!sc->sc_invalid) { mwl_init(sc); /* XXX lose error */ startall = 1; } } } else mwl_stop(sc); MWL_UNLOCK(sc); if (startall) ieee80211_start_all(ic); } static int mwl_ioctl(struct ieee80211com *ic, u_long cmd, void *data) { struct mwl_softc *sc = ic->ic_softc; struct ifreq *ifr = data; int error = 0; switch (cmd) { case SIOCGMVSTATS: mwl_hal_gethwstats(sc->sc_mh, &sc->sc_stats.hw_stats); #if 0 /* NB: embed these numbers to get a consistent view */ sc->sc_stats.mst_tx_packets = if_get_counter(ifp, IFCOUNTER_OPACKETS); sc->sc_stats.mst_rx_packets = if_get_counter(ifp, IFCOUNTER_IPACKETS); #endif /* * NB: Drop the softc lock in case of a page fault; * we'll accept any potential inconsisentcy in the * statistics. The alternative is to copy the data * to a local structure. */ return (copyout(&sc->sc_stats, ifr_data_get_ptr(ifr), sizeof (sc->sc_stats))); #ifdef MWL_DIAGAPI case SIOCGMVDIAG: /* XXX check privs */ return mwl_ioctl_diag(sc, (struct mwl_diag *) ifr); case SIOCGMVRESET: /* XXX check privs */ MWL_LOCK(sc); error = mwl_ioctl_reset(sc,(struct mwl_diag *) ifr); MWL_UNLOCK(sc); break; #endif /* MWL_DIAGAPI */ default: error = ENOTTY; break; } return (error); } #ifdef MWL_DEBUG static int mwl_sysctl_debug(SYSCTL_HANDLER_ARGS) { struct mwl_softc *sc = arg1; int debug, error; debug = sc->sc_debug | (mwl_hal_getdebug(sc->sc_mh) << 24); error = sysctl_handle_int(oidp, &debug, 0, req); if (error || !req->newptr) return error; mwl_hal_setdebug(sc->sc_mh, debug >> 24); sc->sc_debug = debug & 0x00ffffff; return 0; } #endif /* MWL_DEBUG */ static void mwl_sysctlattach(struct mwl_softc *sc) { #ifdef MWL_DEBUG struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(sc->sc_dev); struct sysctl_oid *tree = device_get_sysctl_tree(sc->sc_dev); sc->sc_debug = mwl_debug; SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "debug", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0, mwl_sysctl_debug, "I", "control debugging printfs"); #endif } /* * Announce various information on device/driver attach. */ static void mwl_announce(struct mwl_softc *sc) { device_printf(sc->sc_dev, "Rev A%d hardware, v%d.%d.%d.%d firmware (regioncode %d)\n", sc->sc_hwspecs.hwVersion, (sc->sc_hwspecs.fwReleaseNumber>>24) & 0xff, (sc->sc_hwspecs.fwReleaseNumber>>16) & 0xff, (sc->sc_hwspecs.fwReleaseNumber>>8) & 0xff, (sc->sc_hwspecs.fwReleaseNumber>>0) & 0xff, sc->sc_hwspecs.regionCode); sc->sc_fwrelease = sc->sc_hwspecs.fwReleaseNumber; if (bootverbose) { int i; for (i = 0; i <= WME_AC_VO; i++) { struct mwl_txq *txq = sc->sc_ac2q[i]; device_printf(sc->sc_dev, "Use hw queue %u for %s traffic\n", txq->qnum, ieee80211_wme_acnames[i]); } } if (bootverbose || mwl_rxdesc != MWL_RXDESC) device_printf(sc->sc_dev, "using %u rx descriptors\n", mwl_rxdesc); if (bootverbose || mwl_rxbuf != MWL_RXBUF) device_printf(sc->sc_dev, "using %u rx buffers\n", mwl_rxbuf); if (bootverbose || mwl_txbuf != MWL_TXBUF) device_printf(sc->sc_dev, "using %u tx buffers\n", mwl_txbuf); if (bootverbose && mwl_hal_ismbsscapable(sc->sc_mh)) device_printf(sc->sc_dev, "multi-bss support\n"); #ifdef MWL_TX_NODROP if (bootverbose) device_printf(sc->sc_dev, "no tx drop\n"); #endif } diff --git a/sys/net80211/ieee80211_ddb.c b/sys/net80211/ieee80211_ddb.c index eca893fa6810..0050038457c7 100644 --- a/sys/net80211/ieee80211_ddb.c +++ b/sys/net80211/ieee80211_ddb.c @@ -1,1051 +1,1052 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2007-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 #include "opt_ddb.h" #include "opt_wlan.h" #ifdef DDB /* * IEEE 802.11 DDB support */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_TDMA #include #endif #ifdef IEEE80211_SUPPORT_MESH #include #endif #include #include #define DB_PRINTSYM(prefix, name, addr) do { \ db_printf("%s%-25s : ", prefix, name); \ db_printsym((db_addr_t) addr, DB_STGY_ANY); \ db_printf("\n"); \ } while (0) static void _db_show_sta(const struct ieee80211_node *); static void _db_show_vap(const struct ieee80211vap *, int, int); static void _db_show_com(const struct ieee80211com *, int showvaps, int showsta, int showmesh, int showprocs, int); static void _db_show_all_vaps(void *, struct ieee80211com *); static void _db_show_node_table(const char *tag, const struct ieee80211_node_table *); static void _db_show_channel(const char *tag, const struct ieee80211_channel *); static void _db_show_ssid(const char *tag, int ix, int len, const uint8_t *); static void _db_show_appie(const char *tag, const struct ieee80211_appie *); static void _db_show_key(const char *tag, int ix, const struct ieee80211_key *); static void _db_show_roamparams(const char *tag, const void *arg, const struct ieee80211_roamparam *rp); static void _db_show_txparams(const char *tag, const void *arg, const struct ieee80211_txparam *tp); static void _db_show_ageq(const char *tag, const struct ieee80211_ageq *q); static void _db_show_stats(const struct ieee80211_stats *); #ifdef IEEE80211_SUPPORT_MESH static void _db_show_mesh(const struct ieee80211_mesh_state *); #endif DB_SHOW_COMMAND(sta, db_show_sta) { if (!have_addr) { db_printf("usage: show sta \n"); return; } _db_show_sta((const struct ieee80211_node *) addr); } DB_SHOW_COMMAND(statab, db_show_statab) { if (!have_addr) { db_printf("usage: show statab \n"); return; } _db_show_node_table("", (const struct ieee80211_node_table *) addr); } DB_SHOW_COMMAND(vap, db_show_vap) { int i, showmesh = 0, showprocs = 0; if (!have_addr) { db_printf("usage: show vap \n"); return; } for (i = 0; modif[i] != '\0'; i++) switch (modif[i]) { case 'a': showprocs = 1; showmesh = 1; break; case 'm': showmesh = 1; break; case 'p': showprocs = 1; break; } _db_show_vap((const struct ieee80211vap *) addr, showmesh, showprocs); } DB_SHOW_COMMAND(com, db_show_com) { const struct ieee80211com *ic; int i, showprocs = 0, showvaps = 0, showsta = 0, showmesh = 0, showscan = 0; if (!have_addr) { db_printf("usage: show com \n"); return; } for (i = 0; modif[i] != '\0'; i++) switch (modif[i]) { case 'a': showsta = showmesh = showvaps = showprocs = showscan = 1; break; case 'S': showscan = 1; break; case 's': showsta = 1; break; case 'm': showmesh = 1; break; case 'v': showvaps = 1; break; case 'p': showprocs = 1; break; } ic = (const struct ieee80211com *) addr; _db_show_com(ic, showvaps, showsta, showmesh, showprocs, showscan); } DB_SHOW_ALL_COMMAND(vaps, db_show_all_vaps) { int i, showall = 0; for (i = 0; modif[i] != '\0'; i++) switch (modif[i]) { case 'a': showall = 1; break; } ieee80211_iterate_coms(_db_show_all_vaps, &showall); } #ifdef IEEE80211_SUPPORT_MESH DB_SHOW_ALL_COMMAND(mesh, db_show_mesh) { const struct ieee80211_mesh_state *ms; if (!have_addr) { db_printf("usage: show mesh \n"); return; } ms = (const struct ieee80211_mesh_state *) addr; _db_show_mesh(ms); } #endif /* IEEE80211_SUPPORT_MESH */ static void _db_show_txampdu(const char *sep, int ix, const struct ieee80211_tx_ampdu *tap) { db_printf("%stxampdu[%d]: %p flags %b %s\n", sep, ix, tap, tap->txa_flags, IEEE80211_AGGR_BITS, ieee80211_wme_acnames[TID_TO_WME_AC(tap->txa_tid)]); db_printf("%s token %u lastsample %d pkts %d avgpps %d qbytes %d qframes %d\n", sep, tap->txa_token, tap->txa_lastsample, tap->txa_pkts, tap->txa_avgpps, tap->txa_qbytes, tap->txa_qframes); db_printf("%s start %u seqpending %u wnd %u attempts %d nextrequest %d\n", sep, tap->txa_start, tap->txa_seqpending, tap->txa_wnd, tap->txa_attempts, tap->txa_nextrequest); /* XXX timer */ } static void _db_show_rxampdu(const char *sep, int ix, const struct ieee80211_rx_ampdu *rap) { struct mbuf *m; int i; db_printf("%srxampdu[%d]: %p flags 0x%x tid %u\n", sep, ix, rap, rap->rxa_flags, ix /*XXX */); db_printf("%s qbytes %d qframes %d seqstart %u start %u wnd %u\n", sep, rap->rxa_qbytes, rap->rxa_qframes, rap->rxa_seqstart, rap->rxa_start, rap->rxa_wnd); db_printf("%s age %d nframes %d\n", sep, rap->rxa_age, rap->rxa_nframes); for (i = 0; i < IEEE80211_AGGR_BAWMAX; i++) if (!mbufq_empty(&rap->rxa_mq[i])) { db_printf("%s m[%2u:%4u] ", sep, i, IEEE80211_SEQ_ADD(rap->rxa_start, i)); STAILQ_FOREACH(m, &rap->rxa_mq[i].mq_head, m_stailqpkt) { db_printf(" %p", m); } db_printf("\n"); } } static void _db_show_sta(const struct ieee80211_node *ni) { int i; db_printf("STA: %p: mac %s refcnt %d\n", ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)); db_printf("\tvap %p wdsvap %p ic %p table %p\n", ni->ni_vap, ni->ni_wdsvap, ni->ni_ic, ni->ni_table); db_printf("\tflags=%b\n", ni->ni_flags, IEEE80211_NODE_BITS); db_printf("\tauthmode %u ath_flags 0x%x ath_defkeyix %u\n", ni->ni_authmode, ni->ni_ath_flags, ni->ni_ath_defkeyix); db_printf("\tassocid 0x%x txpower %u vlan %u\n", ni->ni_associd, ni->ni_txpower, ni->ni_vlan); db_printf("\tjointime %d (%lu secs) challenge %p\n", ni->ni_jointime, (unsigned long)(time_uptime - ni->ni_jointime), ni->ni_challenge); db_printf("\ties: data %p len %d\n", ni->ni_ies.data, ni->ni_ies.len); db_printf("\t[wpa_ie %p rsn_ie %p wme_ie %p ath_ie %p\n", ni->ni_ies.wpa_ie, ni->ni_ies.rsn_ie, ni->ni_ies.wme_ie, ni->ni_ies.ath_ie); db_printf("\t htcap_ie %p htinfo_ie %p]\n", ni->ni_ies.htcap_ie, ni->ni_ies.htinfo_ie); db_printf("\t vhtcap_ie %p vhtopmode_ie %p vhtpwrenv_ie %p]\n", ni->ni_ies.vhtcap_ie, ni->ni_ies.vhtopmode_ie, ni->ni_ies.vhtpwrenv_ie); if (ni->ni_flags & IEEE80211_NODE_QOS) { for (i = 0; i < WME_NUM_TID; i++) { if (ni->ni_txseqs[i] || ni->ni_rxseqs[i]) db_printf("\t[%u] txseq %u rxseq %u fragno %u\n", i, ni->ni_txseqs[i], ni->ni_rxseqs[i] >> IEEE80211_SEQ_SEQ_SHIFT, ni->ni_rxseqs[i] & IEEE80211_SEQ_FRAG_MASK); } } db_printf("\ttxseq %u rxseq %u fragno %u rxfragstamp %u\n", ni->ni_txseqs[IEEE80211_NONQOS_TID], ni->ni_rxseqs[IEEE80211_NONQOS_TID] >> IEEE80211_SEQ_SEQ_SHIFT, ni->ni_rxseqs[IEEE80211_NONQOS_TID] & IEEE80211_SEQ_FRAG_MASK, ni->ni_rxfragstamp); db_printf("\trxfrag[0] %p rxfrag[1] %p rxfrag[2] %p\n", ni->ni_rxfrag[0], ni->ni_rxfrag[1], ni->ni_rxfrag[2]); _db_show_key("\tucastkey", 0, &ni->ni_ucastkey); db_printf("\tavgrssi 0x%x (rssi %d) noise %d\n", ni->ni_avgrssi, IEEE80211_RSSI_GET(ni->ni_avgrssi), ni->ni_noise); db_printf("\tintval %u capinfo %b\n", ni->ni_intval, ni->ni_capinfo, IEEE80211_CAPINFO_BITS); db_printf("\tbssid %s", ether_sprintf(ni->ni_bssid)); _db_show_ssid(" essid ", 0, ni->ni_esslen, ni->ni_essid); db_printf("\n"); _db_show_channel("\tchannel", ni->ni_chan); db_printf("\n"); db_printf("\terp %b dtim_period %u dtim_count %u\n", ni->ni_erp, IEEE80211_ERP_BITS, ni->ni_dtim_period, ni->ni_dtim_count); db_printf("\thtcap %b htparam 0x%x htctlchan %u ht2ndchan %u\n", ni->ni_htcap, IEEE80211_HTCAP_BITS, ni->ni_htparam, ni->ni_htctlchan, ni->ni_ht2ndchan); - db_printf("\thtopmode 0x%x htstbc 0x%x chw %u\n", - ni->ni_htopmode, ni->ni_htstbc, ni->ni_chw); + db_printf("\thtopmode 0x%x htstbc 0x%x chw %b\n", + ni->ni_htopmode, ni->ni_htstbc, + ni->ni_chw, IEEE80211_NI_CHW_BITS); /* XXX ampdu state */ for (i = 0; i < WME_NUM_TID; i++) if (ni->ni_tx_ampdu[i].txa_flags & IEEE80211_AGGR_SETUP) _db_show_txampdu("\t", i, &ni->ni_tx_ampdu[i]); for (i = 0; i < WME_NUM_TID; i++) if (ni->ni_rx_ampdu[i].rxa_flags) _db_show_rxampdu("\t", i, &ni->ni_rx_ampdu[i]); db_printf("\tinact %u inact_reload %u txrate %u\n", ni->ni_inact, ni->ni_inact_reload, ni->ni_txrate); #ifdef IEEE80211_SUPPORT_MESH _db_show_ssid("\tmeshid ", 0, ni->ni_meshidlen, ni->ni_meshid); db_printf(" mlstate %b mllid 0x%x mlpid 0x%x mlrcnt %u mltval %u\n", ni->ni_mlstate, IEEE80211_MESH_MLSTATE_BITS, ni->ni_mllid, ni->ni_mlpid, ni->ni_mlrcnt, ni->ni_mltval); #endif /* VHT state */ db_printf("\tvhtcap %b vht_basicmcs %#06x vht_pad2 %#06x\n", ni->ni_vhtcap, IEEE80211_VHTCAP_BITS, ni->ni_vht_basicmcs, ni->ni_vht_pad2); db_printf("\tvht_mcsinfo: { rx_mcs_map %#06x rx_highest %#06x " "tx_mcs_map %#06x tx_highest %#06x }\n", ni->ni_vht_mcsinfo.rx_mcs_map, ni->ni_vht_mcsinfo.rx_highest, ni->ni_vht_mcsinfo.tx_mcs_map, ni->ni_vht_mcsinfo.tx_highest); db_printf("\tvht_chan1/chan2 %u/%u vht_chanwidth %#04x\n", ni->ni_vht_chan1, ni->ni_vht_chan2, ni->ni_vht_chanwidth); db_printf("\tvht_pad1 %#04x vht_spare { %#x %#x %#x %#x %#x %#x %#x %#x }\n", ni->ni_vht_pad1, ni->ni_vht_spare[0], ni->ni_vht_spare[1], ni->ni_vht_spare[2], ni->ni_vht_spare[3], ni->ni_vht_spare[4], ni->ni_vht_spare[5], ni->ni_vht_spare[6], ni->ni_vht_spare[7]); db_printf("\tni_tx_superg[] = {"); for (i = 0; i < WME_NUM_TID; i++) db_printf(" %p%s", ni->ni_tx_superg[i], (i == 0) ? "" : ","); db_printf(" }\n"); db_printf("\tni_rctls = %p", ni->ni_rctls); db_printf("\tni_drv_data = %p", ni->ni_drv_data); db_printf("\n"); db_printf("\tni_spare[3] = { %#jx %#jx %#jx }", ni->ni_spare[0], ni->ni_spare[1], ni->ni_spare[2]); db_printf("\n"); #ifdef __notyet__ struct ieee80211_psq ni_psq; /* power save queue */ struct ieee80211_nodestats ni_stats; /* per-node statistics */ /* quiet time IE state for the given node */ uint32_t ni_quiet_ie_set; /* Quiet time IE was seen */ struct ieee80211_quiet_ie ni_quiet_ie; /* last seen quiet IE */ /* U-APSD */ uint8_t ni_uapsd; /* U-APSD per-node flags matching WMM STA QoS Info field */ #endif } #ifdef IEEE80211_SUPPORT_TDMA static void _db_show_tdma(const char *sep, const struct ieee80211_tdma_state *ts, int showprocs) { db_printf("%stdma %p:\n", sep, ts); db_printf("%s version %u slot %u bintval %u peer %p\n", sep, ts->tdma_version, ts->tdma_slot, ts->tdma_bintval, ts->tdma_peer); db_printf("%s slotlen %u slotcnt %u", sep, ts->tdma_slotlen, ts->tdma_slotcnt); db_printf(" inuse 0x%x active 0x%x count %d\n", ts->tdma_inuse[0], ts->tdma_active[0], ts->tdma_count); if (showprocs) { DB_PRINTSYM(sep, " tdma_newstate", ts->tdma_newstate); DB_PRINTSYM(sep, " tdma_recv_mgmt", ts->tdma_recv_mgmt); DB_PRINTSYM(sep, " tdma_opdetach", ts->tdma_opdetach); } } #endif /* IEEE80211_SUPPORT_TDMA */ static void _db_show_scan(const struct ieee80211_scan_state *ss, int showprocs) { int i; const struct ieee80211_scanner *ss_ops; db_printf("SCAN %p:", ss); db_printf(" vap %p ic %p", ss->ss_vap, ss->ss_ic); db_printf("\n"); db_printf("\tss_ops %p (%s) ss_priv %p", ss->ss_ops, ss->ss_ops->scan_name, ss->ss_priv); db_printf("\n"); if (showprocs) { ss_ops = ss->ss_ops; DB_PRINTSYM("\t", "scan_attach", ss_ops->scan_attach); DB_PRINTSYM("\t", "scan_detach", ss_ops->scan_detach); DB_PRINTSYM("\t", "scan_start", ss_ops->scan_start); DB_PRINTSYM("\t", "scan_restart", ss_ops->scan_restart); DB_PRINTSYM("\t", "scan_cancel", ss_ops->scan_cancel); DB_PRINTSYM("\t", "scan_end", ss_ops->scan_end); DB_PRINTSYM("\t", "scan_flush", ss_ops->scan_flush); DB_PRINTSYM("\t", "scan_pickchan", ss_ops->scan_pickchan); DB_PRINTSYM("\t", "scan_add", ss_ops->scan_add); DB_PRINTSYM("\t", "scan_age", ss_ops->scan_age); DB_PRINTSYM("\t", "scan_assoc_fail", ss_ops->scan_assoc_fail); DB_PRINTSYM("\t", "scan_assoc_success", ss_ops->scan_assoc_success); DB_PRINTSYM("\t", "scan_iterate", ss_ops->scan_iterate); DB_PRINTSYM("\t", "scan_spare0", ss_ops->scan_spare0); DB_PRINTSYM("\t", "scan_spare1", ss_ops->scan_spare1); DB_PRINTSYM("\t", "scan_spare2", ss_ops->scan_spare2); DB_PRINTSYM("\t", "scan_spare3", ss_ops->scan_spare3); } db_printf("\tss_flags %b", ss->ss_flags, IEEE80211_SS_FLAGS_BITS); db_printf("\n"); db_printf("\tss_nssid %u", ss->ss_nssid); for (i = 0; i < ss->ss_nssid && i < IEEE80211_SCAN_MAX_SSID; i++) _db_show_ssid(" ss_nssid[%d]", i, ss->ss_ssid[i].len, ss->ss_ssid[i].ssid); db_printf("\n"); db_printf("\tss_chans:\n"); for (i = 0; i < ss->ss_last && i < IEEE80211_SCAN_MAX; i++) { db_printf("\t%-3d", i); _db_show_channel(" ", ss->ss_chans[i]); db_printf("\n"); } db_printf("\tss_next %u ss_last %u ss_mindwell %lu ss_maxdwell %lu", ss->ss_next, ss->ss_last, ss->ss_mindwell, ss->ss_maxdwell); db_printf("\n"); } static void _db_show_rate(const struct ieee80211_ratectl *rate, const void *rs, const int showprocs) { db_printf("\tiv_rate %p", rate); db_printf(" iv_rs %p", rs); db_printf("\n"); if (showprocs) { db_printf("\t ir_name %s", rate->ir_name); db_printf("\n"); DB_PRINTSYM("\t ", "ir_attach", rate->ir_attach); DB_PRINTSYM("\t ", "ir_detach", rate->ir_detach); DB_PRINTSYM("\t ", "ir_init", rate->ir_init); DB_PRINTSYM("\t ", "ir_deinit", rate->ir_deinit); DB_PRINTSYM("\t ", "ir_node_init", rate->ir_node_init); DB_PRINTSYM("\t ", "ir_node_deinit", rate->ir_node_deinit); DB_PRINTSYM("\t ", "ir_rate", rate->ir_rate); DB_PRINTSYM("\t ", "ir_tx_complete", rate->ir_tx_complete); DB_PRINTSYM("\t ", "ir_tx_update", rate->ir_tx_update); DB_PRINTSYM("\t ", "ir_setinterval", rate->ir_setinterval); DB_PRINTSYM("\t ", "ir_node_stats", rate->ir_node_stats); } } static void _db_show_vap(const struct ieee80211vap *vap, int showmesh, int showprocs) { const struct ieee80211com *ic = vap->iv_ic; int i; db_printf("VAP %p:", vap); db_printf(" bss %p", vap->iv_bss); db_printf(" myaddr %s", ether_sprintf(vap->iv_myaddr)); db_printf("\n"); db_printf("\topmode %s", ieee80211_opmode_name[vap->iv_opmode]); #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) db_printf("(%p)", vap->iv_mesh); #endif db_printf(" state %#x %s", vap->iv_state, ieee80211_state_name[vap->iv_state]); db_printf(" ifp %p(%s)", vap->iv_ifp, if_name(vap->iv_ifp)); db_printf("\n"); db_printf("\tic %p", vap->iv_ic); db_printf(" media %p", &vap->iv_media); db_printf(" bpf_if %p", vap->iv_rawbpf); db_printf(" mgtsend %p", &vap->iv_mgtsend); #if 0 struct sysctllog *iv_sysctl; /* dynamic sysctl context */ #endif db_printf("\n"); db_printf("\tiv_nstate %#x %s iv_nstate_b %d iv_nstate_n %d\n", vap->iv_nstate, ieee80211_state_name[vap->iv_nstate], /* historic */ vap->iv_nstate_b, vap->iv_nstate_n); for (i = 0; i < NET80211_IV_NSTATE_NUM; i++) { db_printf("\t [%d] iv_nstates %#x %s _task %p _args %d\n", i, vap->iv_nstates[i], ieee80211_state_name[vap->iv_nstates[i]], &vap->iv_nstate_task[i], vap->iv_nstate_args[i]); } db_printf("\tdebug=%b\n", vap->iv_debug, IEEE80211_MSG_BITS); db_printf("\tflags=%b\n", vap->iv_flags, IEEE80211_F_BITS); db_printf("\tflags_ext=%b\n", vap->iv_flags_ext, IEEE80211_FEXT_BITS); db_printf("\tflags_ht=%b\n", vap->iv_flags_ht, IEEE80211_FHT_BITS); db_printf("\tflags_ven=%b\n", vap->iv_flags_ven, IEEE80211_FVEN_BITS); db_printf("\tcaps=%b\n", vap->iv_caps, IEEE80211_C_BITS); db_printf("\thtcaps=%b\n", vap->iv_htcaps, IEEE80211_C_HTCAP_BITS); db_printf("\tvhtcap=%b\n", vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_BITS); _db_show_stats(&vap->iv_stats); db_printf("\tinact_init %d", vap->iv_inact_init); db_printf(" inact_auth %d", vap->iv_inact_auth); db_printf(" inact_run %d", vap->iv_inact_run); db_printf(" inact_probe %d", vap->iv_inact_probe); db_printf("\n"); db_printf("\tdes_nssid %d", vap->iv_des_nssid); if (vap->iv_des_nssid) _db_show_ssid(" des_ssid[%u] ", 0, vap->iv_des_ssid[0].len, vap->iv_des_ssid[0].ssid); db_printf(" des_bssid %s", ether_sprintf(vap->iv_des_bssid)); db_printf("\n"); db_printf("\tdes_mode %d", vap->iv_des_mode); _db_show_channel(" des_chan", vap->iv_des_chan); db_printf("\n"); #if 0 int iv_nicknamelen; /* XXX junk */ uint8_t iv_nickname[IEEE80211_NWID_LEN]; #endif db_printf("\tbgscanidle %u", vap->iv_bgscanidle); db_printf(" bgscanintvl %u", vap->iv_bgscanintvl); db_printf(" scanvalid %u", vap->iv_scanvalid); db_printf("\n"); db_printf("\tscanreq_duration %u", vap->iv_scanreq_duration); db_printf(" scanreq_mindwell %u", vap->iv_scanreq_mindwell); db_printf(" scanreq_maxdwell %u", vap->iv_scanreq_maxdwell); db_printf("\n"); db_printf("\tscanreq_flags 0x%x", vap->iv_scanreq_flags); db_printf(" scanreq_nssid %d", vap->iv_scanreq_nssid); for (i = 0; i < vap->iv_scanreq_nssid; i++) _db_show_ssid(" scanreq_ssid[%u]", i, vap->iv_scanreq_ssid[i].len, vap->iv_scanreq_ssid[i].ssid); db_printf(" roaming %d", vap->iv_roaming); db_printf("\n"); for (i = IEEE80211_MODE_11A; i < IEEE80211_MODE_MAX; i++) if (isset(ic->ic_modecaps, i)) { _db_show_roamparams("\troamparms[%s]", ieee80211_phymode_name[i], &vap->iv_roamparms[i]); db_printf("\n"); } db_printf("\tbmissthreshold %u", vap->iv_bmissthreshold); db_printf(" bmiss_max %u", vap->iv_bmiss_count); db_printf(" bmiss_max %d", vap->iv_bmiss_max); db_printf("\n"); db_printf("\tswbmiss_count %u", vap->iv_swbmiss_count); db_printf(" swbmiss_period %u", vap->iv_swbmiss_period); db_printf(" swbmiss %p", &vap->iv_swbmiss); db_printf("\n"); db_printf("\tampdu_rxmax %d", vap->iv_ampdu_rxmax); db_printf(" ampdu_density %d", vap->iv_ampdu_density); db_printf(" ampdu_limit %d", vap->iv_ampdu_limit); db_printf(" amsdu_limit %d", vap->iv_amsdu_limit); db_printf("\n"); db_printf("\tmax_aid %u", vap->iv_max_aid); db_printf(" aid_bitmap %p", vap->iv_aid_bitmap); db_printf("\n"); db_printf("\tsta_assoc %u", vap->iv_sta_assoc); db_printf(" ps_sta %u", vap->iv_ps_sta); db_printf(" ps_pending %u", vap->iv_ps_pending); db_printf(" tim_len %u", vap->iv_tim_len); db_printf(" tim_bitmap %p", vap->iv_tim_bitmap); db_printf("\n"); db_printf("\tdtim_period %u", vap->iv_dtim_period); db_printf(" dtim_count %u", vap->iv_dtim_count); db_printf(" set_tim %p", vap->iv_set_tim); db_printf(" csa_count %d", vap->iv_csa_count); db_printf("\n"); db_printf("\trtsthreshold %u", vap->iv_rtsthreshold); db_printf(" fragthreshold %u", vap->iv_fragthreshold); db_printf(" inact_timer %d", vap->iv_inact_timer); db_printf("\n"); for (i = IEEE80211_MODE_11A; i < IEEE80211_MODE_MAX; i++) if (isset(ic->ic_modecaps, i)) { _db_show_txparams("\ttxparms[%s]", ieee80211_phymode_name[i], &vap->iv_txparms[i]); db_printf("\n"); } /* application-specified IE's to attach to mgt frames */ _db_show_appie("\tappie_beacon", vap->iv_appie_beacon); _db_show_appie("\tappie_probereq", vap->iv_appie_probereq); _db_show_appie("\tappie_proberesp", vap->iv_appie_proberesp); _db_show_appie("\tappie_assocreq", vap->iv_appie_assocreq); _db_show_appie("\tappie_asscoresp", vap->iv_appie_assocresp); _db_show_appie("\tappie_wpa", vap->iv_appie_wpa); if (vap->iv_wpa_ie != NULL || vap->iv_rsn_ie != NULL) { if (vap->iv_wpa_ie != NULL) db_printf("\twpa_ie %p", vap->iv_wpa_ie); if (vap->iv_rsn_ie != NULL) db_printf("\trsn_ie %p", vap->iv_rsn_ie); db_printf("\n"); } db_printf("\tmax_keyix %u", vap->iv_max_keyix); db_printf(" def_txkey %d", vap->iv_def_txkey); db_printf("\n"); for (i = 0; i < IEEE80211_WEP_NKID; i++) _db_show_key("\tnw_keys[%u]", i, &vap->iv_nw_keys[i]); db_printf("\tauth %p(%s)", vap->iv_auth, vap->iv_auth->ia_name); db_printf(" ec %p", vap->iv_ec); db_printf(" acl %p", vap->iv_acl); db_printf(" as %p", vap->iv_as); db_printf("\n"); #ifdef IEEE80211_SUPPORT_MESH if (showmesh && vap->iv_mesh != NULL) _db_show_mesh(vap->iv_mesh); #endif #ifdef IEEE80211_SUPPORT_TDMA if (vap->iv_tdma != NULL) _db_show_tdma("\t", vap->iv_tdma, showprocs); #endif /* IEEE80211_SUPPORT_TDMA */ db_printf("\tsta_assoc %u", vap->iv_sta_assoc); db_printf(" ht_sta_assoc %u", vap->iv_ht_sta_assoc); db_printf(" ht40_sta_assoc %u", vap->iv_ht40_sta_assoc); db_printf("\n"); db_printf("\tnonerpsta %u", vap->iv_nonerpsta); db_printf(" longslotsta %u", vap->iv_longslotsta); db_printf(" lastnonerp %d", vap->iv_lastnonerp); db_printf(" lastnonht %d", vap->iv_lastnonht); db_printf("\n"); if (vap->iv_rate != NULL) _db_show_rate(vap->iv_rate, vap->iv_rs, showprocs); if (showprocs) { DB_PRINTSYM("\t", "iv_key_alloc", vap->iv_key_alloc); DB_PRINTSYM("\t", "iv_key_delete", vap->iv_key_delete); DB_PRINTSYM("\t", "iv_key_set", vap->iv_key_set); DB_PRINTSYM("\t", "iv_key_update_begin", vap->iv_key_update_begin); DB_PRINTSYM("\t", "iv_key_update_end", vap->iv_key_update_end); DB_PRINTSYM("\t", "iv_opdetach", vap->iv_opdetach); DB_PRINTSYM("\t", "iv_input", vap->iv_input); DB_PRINTSYM("\t", "iv_recv_mgmt", vap->iv_recv_mgmt); DB_PRINTSYM("\t", "iv_deliver_data", vap->iv_deliver_data); DB_PRINTSYM("\t", "iv_bmiss", vap->iv_bmiss); DB_PRINTSYM("\t", "iv_reset", vap->iv_reset); DB_PRINTSYM("\t", "iv_update_beacon", vap->iv_update_beacon); DB_PRINTSYM("\t", "iv_newstate", vap->iv_newstate); DB_PRINTSYM("\t", "iv_output", vap->iv_output); } } static void _db_show_com(const struct ieee80211com *ic, int showvaps, int showsta, int showmesh, int showprocs, int showscan) { struct ieee80211vap *vap; db_printf("COM: %p:", ic); TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) db_printf(" %s(%p)", if_name(vap->iv_ifp), vap); db_printf("\n"); db_printf("\tsoftc %p", ic->ic_softc); db_printf("\tname %s", ic->ic_name); db_printf(" comlock %p", &ic->ic_comlock); db_printf(" txlock %p", &ic->ic_txlock); db_printf(" fflock %p", &ic->ic_fflock); db_printf("\n"); db_printf("\theadroom %d", ic->ic_headroom); db_printf(" phytype %d", ic->ic_phytype); db_printf(" opmode %s", ieee80211_opmode_name[ic->ic_opmode]); db_printf("\n"); db_printf("\tinact %p", &ic->ic_inact); db_printf("\n"); db_printf("\tflags=%b\n", ic->ic_flags, IEEE80211_F_BITS); db_printf("\tflags_ext=%b\n", ic->ic_flags_ext, IEEE80211_FEXT_BITS); db_printf("\tflags_ht=%b\n", ic->ic_flags_ht, IEEE80211_FHT_BITS); db_printf("\tflags_ven=%b\n", ic->ic_flags_ven, IEEE80211_FVEN_BITS); db_printf("\tcaps=%b\n", ic->ic_caps, IEEE80211_C_BITS); db_printf("\tcryptocaps=%b\n", ic->ic_cryptocaps, IEEE80211_CRYPTO_BITS); db_printf("\thtcaps=%b\n", ic->ic_htcaps, IEEE80211_HTCAP_BITS); db_printf("\tvhtcaps=%b\n", ic->ic_vht_cap.vht_cap_info, IEEE80211_VHTCAP_BITS); #if 0 uint8_t ic_modecaps[2]; /* set of mode capabilities */ #endif db_printf("\tcurmode %u", ic->ic_curmode); db_printf(" promisc %u", ic->ic_promisc); db_printf(" allmulti %u", ic->ic_allmulti); db_printf(" nrunning %u", ic->ic_nrunning); db_printf("\n"); db_printf("\tbintval %u", ic->ic_bintval); db_printf(" lintval %u", ic->ic_lintval); db_printf(" holdover %u", ic->ic_holdover); db_printf(" txpowlimit %u", ic->ic_txpowlimit); db_printf("\n"); #if 0 struct ieee80211_rateset ic_sup_rates[IEEE80211_MODE_MAX]; #endif /* * Channel state: * * ic_channels is the set of available channels for the device; * it is setup by the driver * ic_nchans is the number of valid entries in ic_channels * ic_chan_avail is a bit vector of these channels used to check * whether a channel is available w/o searching the channel table. * ic_chan_active is a (potentially) constrained subset of * ic_chan_avail that reflects any mode setting or user-specified * limit on the set of channels to use/scan * ic_curchan is the current channel the device is set to; it may * be different from ic_bsschan when we are off-channel scanning * or otherwise doing background work * ic_bsschan is the channel selected for operation; it may * be undefined (IEEE80211_CHAN_ANYC) * ic_prevchan is a cached ``previous channel'' used to optimize * lookups when switching back+forth between two channels * (e.g. for dynamic turbo) */ db_printf("\tnchans %d", ic->ic_nchans); #if 0 struct ieee80211_channel ic_channels[IEEE80211_CHAN_MAX]; uint8_t ic_chan_avail[IEEE80211_CHAN_BYTES]; uint8_t ic_chan_active[IEEE80211_CHAN_BYTES]; uint8_t ic_chan_scan[IEEE80211_CHAN_BYTES]; #endif db_printf("\n"); _db_show_channel("\tcurchan", ic->ic_curchan); db_printf("\n"); _db_show_channel("\tbsschan", ic->ic_bsschan); db_printf("\n"); _db_show_channel("\tprevchan", ic->ic_prevchan); db_printf("\n"); db_printf("\tregdomain %p", &ic->ic_regdomain); db_printf("\n"); _db_show_channel("\tcsa_newchan", ic->ic_csa_newchan); db_printf(" csa_count %d", ic->ic_csa_count); db_printf( "dfs %p", &ic->ic_dfs); db_printf("\n"); db_printf("\tscan %p", ic->ic_scan); db_printf(" lastdata %d", ic->ic_lastdata); db_printf(" lastscan %d", ic->ic_lastscan); db_printf("\n"); db_printf("\tmax_keyix %d", ic->ic_max_keyix); db_printf(" hash_key 0x%x", ic->ic_hash_key); db_printf(" wme %p", &ic->ic_wme); if (!showsta) db_printf(" sta %p", &ic->ic_sta); db_printf("\n"); db_printf("\tstageq@%p:\n", &ic->ic_stageq); _db_show_ageq("\t", &ic->ic_stageq); if (showsta) _db_show_node_table("\t", &ic->ic_sta); db_printf("\tprotmode %d", ic->ic_protmode); db_printf("\tcurhtprotmode 0x%x", ic->ic_curhtprotmode); db_printf(" htprotmode %d", ic->ic_htprotmode); db_printf("\n"); db_printf("\tsuperg %p\n", ic->ic_superg); db_printf("\tmontaps %d th %p txchan %p rh %p rxchan %p\n", ic->ic_montaps, ic->ic_th, ic->ic_txchan, ic->ic_rh, ic->ic_rxchan); if (showprocs) { DB_PRINTSYM("\t", "ic_vap_create", ic->ic_vap_create); DB_PRINTSYM("\t", "ic_vap_delete", ic->ic_vap_delete); #if 0 /* operating mode attachment */ ieee80211vap_attach ic_vattach[IEEE80211_OPMODE_MAX]; #endif DB_PRINTSYM("\t", "ic_newassoc", ic->ic_newassoc); DB_PRINTSYM("\t", "ic_getradiocaps", ic->ic_getradiocaps); DB_PRINTSYM("\t", "ic_setregdomain", ic->ic_setregdomain); DB_PRINTSYM("\t", "ic_send_mgmt", ic->ic_send_mgmt); DB_PRINTSYM("\t", "ic_raw_xmit", ic->ic_raw_xmit); DB_PRINTSYM("\t", "ic_updateslot", ic->ic_updateslot); DB_PRINTSYM("\t", "ic_update_mcast", ic->ic_update_mcast); DB_PRINTSYM("\t", "ic_update_promisc", ic->ic_update_promisc); DB_PRINTSYM("\t", "ic_node_alloc", ic->ic_node_alloc); DB_PRINTSYM("\t", "ic_node_free", ic->ic_node_free); DB_PRINTSYM("\t", "ic_node_cleanup", ic->ic_node_cleanup); DB_PRINTSYM("\t", "ic_node_getrssi", ic->ic_node_getrssi); DB_PRINTSYM("\t", "ic_node_getsignal", ic->ic_node_getsignal); DB_PRINTSYM("\t", "ic_node_getmimoinfo", ic->ic_node_getmimoinfo); DB_PRINTSYM("\t", "ic_scan_start", ic->ic_scan_start); DB_PRINTSYM("\t", "ic_scan_end", ic->ic_scan_end); DB_PRINTSYM("\t", "ic_set_channel", ic->ic_set_channel); DB_PRINTSYM("\t", "ic_scan_curchan", ic->ic_scan_curchan); DB_PRINTSYM("\t", "ic_scan_mindwell", ic->ic_scan_mindwell); DB_PRINTSYM("\t", "ic_recv_action", ic->ic_recv_action); DB_PRINTSYM("\t", "ic_send_action", ic->ic_send_action); DB_PRINTSYM("\t", "ic_addba_request", ic->ic_addba_request); DB_PRINTSYM("\t", "ic_addba_response", ic->ic_addba_response); DB_PRINTSYM("\t", "ic_addba_stop", ic->ic_addba_stop); } if (showscan) { db_printf("\n"); _db_show_scan(ic->ic_scan, showprocs); } if (showvaps && !TAILQ_EMPTY(&ic->ic_vaps)) { db_printf("\n"); TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) _db_show_vap(vap, showmesh, showprocs); } if (showsta && !TAILQ_EMPTY(&ic->ic_sta.nt_node)) { const struct ieee80211_node_table *nt = &ic->ic_sta; const struct ieee80211_node *ni; TAILQ_FOREACH(ni, &nt->nt_node, ni_list) { db_printf("\n"); _db_show_sta(ni); } } } static void _db_show_all_vaps(void *arg, struct ieee80211com *ic) { int showall = *(int *)arg; if (!showall) { const struct ieee80211vap *vap; db_printf("%s: com %p vaps:", ic->ic_name, ic); TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) db_printf(" %s(%p)", if_name(vap->iv_ifp), vap); db_printf("\n"); } else _db_show_com(ic, 1, 1, 1, 1, 1); } static void _db_show_node_table(const char *tag, const struct ieee80211_node_table *nt) { int i; db_printf("%s%s@%p:\n", tag, nt->nt_name, nt); db_printf("%s nodelock %p", tag, &nt->nt_nodelock); db_printf(" inact_init %d", nt->nt_inact_init); db_printf("%s keyixmax %d keyixmap %p\n", tag, nt->nt_keyixmax, nt->nt_keyixmap); for (i = 0; i < nt->nt_keyixmax; i++) { const struct ieee80211_node *ni = nt->nt_keyixmap[i]; if (ni != NULL) db_printf("%s [%3u] %p %s\n", tag, i, ni, ether_sprintf(ni->ni_macaddr)); } } static void _db_show_channel(const char *tag, const struct ieee80211_channel *c) { db_printf("%s ", tag); if (c == NULL) db_printf(""); else if (c == IEEE80211_CHAN_ANYC) db_printf(""); else db_printf("[%u (%u) flags=%b maxreg %d maxpow %d minpow %d state 0x%x extieee %u]", c->ic_freq, c->ic_ieee, c->ic_flags, IEEE80211_CHAN_BITS, c->ic_maxregpower, c->ic_maxpower, c->ic_minpower, c->ic_state, c->ic_extieee); } static void _db_show_ssid(const char *tag, int ix, int len, const uint8_t *ssid) { const uint8_t *p; int i; db_printf(tag, ix); if (len > IEEE80211_NWID_LEN) len = IEEE80211_NWID_LEN; /* determine printable or not */ for (i = 0, p = ssid; i < len; i++, p++) { if (*p < ' ' || *p > 0x7e) break; } if (i == len) { db_printf("\""); for (i = 0, p = ssid; i < len; i++, p++) db_printf("%c", *p); db_printf("\""); } else { db_printf("0x"); for (i = 0, p = ssid; i < len; i++, p++) db_printf("%02x", *p); } } static void _db_show_appie(const char *tag, const struct ieee80211_appie *ie) { const uint8_t *p; int i; if (ie == NULL) return; db_printf("%s [0x", tag); for (i = 0, p = ie->ie_data; i < ie->ie_len; i++, p++) db_printf("%02x", *p); db_printf("]\n"); } static void _db_show_key(const char *tag, int ix, const struct ieee80211_key *wk) { static const uint8_t zerodata[IEEE80211_KEYBUF_SIZE]; const struct ieee80211_cipher *cip = wk->wk_cipher; int keylen = wk->wk_keylen; db_printf(tag, ix); switch (cip->ic_cipher) { case IEEE80211_CIPHER_WEP: /* compatibility */ db_printf(" wepkey %u:%s", wk->wk_keyix, keylen <= 5 ? "40-bit" : keylen <= 13 ? "104-bit" : "128-bit"); break; case IEEE80211_CIPHER_TKIP: if (keylen > 128/8) keylen -= 128/8; /* ignore MIC for now */ db_printf(" TKIP %u:%u-bit", wk->wk_keyix, 8*keylen); break; case IEEE80211_CIPHER_AES_OCB: db_printf(" AES-OCB %u:%u-bit", wk->wk_keyix, 8*keylen); break; case IEEE80211_CIPHER_AES_CCM: db_printf(" AES-CCM %u:%u-bit", wk->wk_keyix, 8*keylen); break; case IEEE80211_CIPHER_CKIP: db_printf(" CKIP %u:%u-bit", wk->wk_keyix, 8*keylen); break; case IEEE80211_CIPHER_NONE: db_printf(" NULL %u:%u-bit", wk->wk_keyix, 8*keylen); break; default: db_printf(" UNKNOWN (0x%x) %u:%u-bit", cip->ic_cipher, wk->wk_keyix, 8*keylen); break; } if (wk->wk_rxkeyix != wk->wk_keyix) db_printf(" rxkeyix %u", wk->wk_rxkeyix); if (memcmp(wk->wk_key, zerodata, keylen) != 0) { int i; db_printf(" <"); for (i = 0; i < keylen; i++) db_printf("%02x", wk->wk_key[i]); db_printf(">"); if (cip->ic_cipher != IEEE80211_CIPHER_WEP && wk->wk_keyrsc[IEEE80211_NONQOS_TID] != 0) db_printf(" rsc %ju", (uintmax_t)wk->wk_keyrsc[IEEE80211_NONQOS_TID]); if (cip->ic_cipher != IEEE80211_CIPHER_WEP && wk->wk_keytsc != 0) db_printf(" tsc %ju", (uintmax_t)wk->wk_keytsc); db_printf(" flags=%b", wk->wk_flags, IEEE80211_KEY_BITS); } db_printf("\n"); } static void printrate(const char *tag, int v) { if (v == IEEE80211_FIXED_RATE_NONE) db_printf(" %s ", tag); else if (v == 11) db_printf(" %s 5.5", tag); else if (v & IEEE80211_RATE_MCS) db_printf(" %s MCS%d", tag, v &~ IEEE80211_RATE_MCS); else db_printf(" %s %d", tag, v/2); } static void _db_show_roamparams(const char *tag, const void *arg, const struct ieee80211_roamparam *rp) { db_printf(tag, arg); if (rp->rssi & 1) db_printf(" rssi %u.5", rp->rssi/2); else db_printf(" rssi %u", rp->rssi/2); printrate("rate", rp->rate); } static void _db_show_txparams(const char *tag, const void *arg, const struct ieee80211_txparam *tp) { db_printf(tag, arg); printrate("ucastrate", tp->ucastrate); printrate("mcastrate", tp->mcastrate); printrate("mgmtrate", tp->mgmtrate); db_printf(" maxretry %d", tp->maxretry); } static void _db_show_ageq(const char *tag, const struct ieee80211_ageq *q) { const struct mbuf *m; db_printf("%s lock %p len %d maxlen %d drops %d head %p tail %p\n", tag, &q->aq_lock, q->aq_len, q->aq_maxlen, q->aq_drops, q->aq_head, q->aq_tail); for (m = q->aq_head; m != NULL; m = m->m_nextpkt) db_printf("%s %p (len %d, %b)\n", tag, m, m->m_len, /* XXX could be either TX or RX but is mostly TX */ m->m_flags, IEEE80211_MBUF_TX_FLAG_BITS); } static void _db_show_stats(const struct ieee80211_stats *is) { } #ifdef IEEE80211_SUPPORT_MESH static void _db_show_mesh(const struct ieee80211_mesh_state *ms) { struct ieee80211_mesh_route *rt; int i; _db_show_ssid(" meshid ", 0, ms->ms_idlen, ms->ms_id); db_printf("nextseq %u ttl %u flags 0x%x\n", ms->ms_seq, ms->ms_ttl, ms->ms_flags); db_printf("routing table:\n"); i = 0; TAILQ_FOREACH(rt, &ms->ms_routes, rt_next) { db_printf("entry %d:\tdest: %6D nexthop: %6D metric: %u", i, rt->rt_dest, ":", rt->rt_nexthop, ":", rt->rt_metric); db_printf("\tlifetime: %u lastseq: %u priv: %p\n", ieee80211_mesh_rt_update(rt, 0), rt->rt_lastmseq, rt->rt_priv); i++; } } #endif /* IEEE80211_SUPPORT_MESH */ #endif /* DDB */ diff --git a/sys/net80211/ieee80211_ht.c b/sys/net80211/ieee80211_ht.c index 1f97b7b9927a..293b5be20d32 100644 --- a/sys/net80211/ieee80211_ht.c +++ b/sys/net80211/ieee80211_ht.c @@ -1,3746 +1,3748 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2007-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. */ /* * IEEE 802.11n protocol support. */ #include "opt_inet.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include const struct ieee80211_mcs_rates ieee80211_htrates[IEEE80211_HTRATE_MAXSIZE] = { { 13, 14, 27, 30 }, /* MCS 0 */ { 26, 29, 54, 60 }, /* MCS 1 */ { 39, 43, 81, 90 }, /* MCS 2 */ { 52, 58, 108, 120 }, /* MCS 3 */ { 78, 87, 162, 180 }, /* MCS 4 */ { 104, 116, 216, 240 }, /* MCS 5 */ { 117, 130, 243, 270 }, /* MCS 6 */ { 130, 144, 270, 300 }, /* MCS 7 */ { 26, 29, 54, 60 }, /* MCS 8 */ { 52, 58, 108, 120 }, /* MCS 9 */ { 78, 87, 162, 180 }, /* MCS 10 */ { 104, 116, 216, 240 }, /* MCS 11 */ { 156, 173, 324, 360 }, /* MCS 12 */ { 208, 231, 432, 480 }, /* MCS 13 */ { 234, 260, 486, 540 }, /* MCS 14 */ { 260, 289, 540, 600 }, /* MCS 15 */ { 39, 43, 81, 90 }, /* MCS 16 */ { 78, 87, 162, 180 }, /* MCS 17 */ { 117, 130, 243, 270 }, /* MCS 18 */ { 156, 173, 324, 360 }, /* MCS 19 */ { 234, 260, 486, 540 }, /* MCS 20 */ { 312, 347, 648, 720 }, /* MCS 21 */ { 351, 390, 729, 810 }, /* MCS 22 */ { 390, 433, 810, 900 }, /* MCS 23 */ { 52, 58, 108, 120 }, /* MCS 24 */ { 104, 116, 216, 240 }, /* MCS 25 */ { 156, 173, 324, 360 }, /* MCS 26 */ { 208, 231, 432, 480 }, /* MCS 27 */ { 312, 347, 648, 720 }, /* MCS 28 */ { 416, 462, 864, 960 }, /* MCS 29 */ { 468, 520, 972, 1080 }, /* MCS 30 */ { 520, 578, 1080, 1200 }, /* MCS 31 */ { 0, 0, 12, 13 }, /* MCS 32 */ { 78, 87, 162, 180 }, /* MCS 33 */ { 104, 116, 216, 240 }, /* MCS 34 */ { 130, 144, 270, 300 }, /* MCS 35 */ { 117, 130, 243, 270 }, /* MCS 36 */ { 156, 173, 324, 360 }, /* MCS 37 */ { 195, 217, 405, 450 }, /* MCS 38 */ { 104, 116, 216, 240 }, /* MCS 39 */ { 130, 144, 270, 300 }, /* MCS 40 */ { 130, 144, 270, 300 }, /* MCS 41 */ { 156, 173, 324, 360 }, /* MCS 42 */ { 182, 202, 378, 420 }, /* MCS 43 */ { 182, 202, 378, 420 }, /* MCS 44 */ { 208, 231, 432, 480 }, /* MCS 45 */ { 156, 173, 324, 360 }, /* MCS 46 */ { 195, 217, 405, 450 }, /* MCS 47 */ { 195, 217, 405, 450 }, /* MCS 48 */ { 234, 260, 486, 540 }, /* MCS 49 */ { 273, 303, 567, 630 }, /* MCS 50 */ { 273, 303, 567, 630 }, /* MCS 51 */ { 312, 347, 648, 720 }, /* MCS 52 */ { 130, 144, 270, 300 }, /* MCS 53 */ { 156, 173, 324, 360 }, /* MCS 54 */ { 182, 202, 378, 420 }, /* MCS 55 */ { 156, 173, 324, 360 }, /* MCS 56 */ { 182, 202, 378, 420 }, /* MCS 57 */ { 208, 231, 432, 480 }, /* MCS 58 */ { 234, 260, 486, 540 }, /* MCS 59 */ { 208, 231, 432, 480 }, /* MCS 60 */ { 234, 260, 486, 540 }, /* MCS 61 */ { 260, 289, 540, 600 }, /* MCS 62 */ { 260, 289, 540, 600 }, /* MCS 63 */ { 286, 318, 594, 660 }, /* MCS 64 */ { 195, 217, 405, 450 }, /* MCS 65 */ { 234, 260, 486, 540 }, /* MCS 66 */ { 273, 303, 567, 630 }, /* MCS 67 */ { 234, 260, 486, 540 }, /* MCS 68 */ { 273, 303, 567, 630 }, /* MCS 69 */ { 312, 347, 648, 720 }, /* MCS 70 */ { 351, 390, 729, 810 }, /* MCS 71 */ { 312, 347, 648, 720 }, /* MCS 72 */ { 351, 390, 729, 810 }, /* MCS 73 */ { 390, 433, 810, 900 }, /* MCS 74 */ { 390, 433, 810, 900 }, /* MCS 75 */ { 429, 477, 891, 990 }, /* MCS 76 */ }; static int ieee80211_ampdu_age = -1; /* threshold for ampdu reorder q (ms) */ SYSCTL_PROC(_net_wlan, OID_AUTO, ampdu_age, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, &ieee80211_ampdu_age, 0, ieee80211_sysctl_msecs_ticks, "I", "AMPDU max reorder age (ms)"); static int ieee80211_recv_bar_ena = 1; SYSCTL_INT(_net_wlan, OID_AUTO, recv_bar, CTLFLAG_RW, &ieee80211_recv_bar_ena, 0, "BAR frame processing (ena/dis)"); static int ieee80211_addba_timeout = -1;/* timeout for ADDBA response */ SYSCTL_PROC(_net_wlan, OID_AUTO, addba_timeout, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, &ieee80211_addba_timeout, 0, ieee80211_sysctl_msecs_ticks, "I", "ADDBA request timeout (ms)"); static int ieee80211_addba_backoff = -1;/* backoff after max ADDBA requests */ SYSCTL_PROC(_net_wlan, OID_AUTO, addba_backoff, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, &ieee80211_addba_backoff, 0, ieee80211_sysctl_msecs_ticks, "I", "ADDBA request backoff (ms)"); static int ieee80211_addba_maxtries = 3;/* max ADDBA requests before backoff */ SYSCTL_INT(_net_wlan, OID_AUTO, addba_maxtries, CTLFLAG_RW, &ieee80211_addba_maxtries, 0, "max ADDBA requests sent before backoff"); static int ieee80211_bar_timeout = -1; /* timeout waiting for BAR response */ static int ieee80211_bar_maxtries = 50;/* max BAR requests before DELBA */ static ieee80211_recv_action_func ht_recv_action_ba_addba_request; static ieee80211_recv_action_func ht_recv_action_ba_addba_response; static ieee80211_recv_action_func ht_recv_action_ba_delba; static ieee80211_recv_action_func ht_recv_action_ht_mimopwrsave; static ieee80211_recv_action_func ht_recv_action_ht_txchwidth; static ieee80211_send_action_func ht_send_action_ba_addba; static ieee80211_send_action_func ht_send_action_ba_delba; static ieee80211_send_action_func ht_send_action_ht_txchwidth; static void ieee80211_ht_init(void) { /* * Setup HT parameters that depends on the clock frequency. */ ieee80211_ampdu_age = msecs_to_ticks(500); ieee80211_addba_timeout = msecs_to_ticks(250); ieee80211_addba_backoff = msecs_to_ticks(10*1000); ieee80211_bar_timeout = msecs_to_ticks(250); /* * Register action frame handlers. */ ieee80211_recv_action_register(IEEE80211_ACTION_CAT_BA, IEEE80211_ACTION_BA_ADDBA_REQUEST, ht_recv_action_ba_addba_request); ieee80211_recv_action_register(IEEE80211_ACTION_CAT_BA, IEEE80211_ACTION_BA_ADDBA_RESPONSE, ht_recv_action_ba_addba_response); ieee80211_recv_action_register(IEEE80211_ACTION_CAT_BA, IEEE80211_ACTION_BA_DELBA, ht_recv_action_ba_delba); ieee80211_recv_action_register(IEEE80211_ACTION_CAT_HT, IEEE80211_ACTION_HT_MIMOPWRSAVE, ht_recv_action_ht_mimopwrsave); ieee80211_recv_action_register(IEEE80211_ACTION_CAT_HT, IEEE80211_ACTION_HT_TXCHWIDTH, ht_recv_action_ht_txchwidth); ieee80211_send_action_register(IEEE80211_ACTION_CAT_BA, IEEE80211_ACTION_BA_ADDBA_REQUEST, ht_send_action_ba_addba); ieee80211_send_action_register(IEEE80211_ACTION_CAT_BA, IEEE80211_ACTION_BA_ADDBA_RESPONSE, ht_send_action_ba_addba); ieee80211_send_action_register(IEEE80211_ACTION_CAT_BA, IEEE80211_ACTION_BA_DELBA, ht_send_action_ba_delba); ieee80211_send_action_register(IEEE80211_ACTION_CAT_HT, IEEE80211_ACTION_HT_TXCHWIDTH, ht_send_action_ht_txchwidth); } SYSINIT(wlan_ht, SI_SUB_DRIVERS, SI_ORDER_FIRST, ieee80211_ht_init, NULL); static int ieee80211_ampdu_enable(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap); static int ieee80211_addba_request(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap, int dialogtoken, int baparamset, int batimeout); static int ieee80211_addba_response(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap, int code, int baparamset, int batimeout); static void ieee80211_addba_stop(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap); static void null_addba_response_timeout(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap); static void ieee80211_bar_response(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap, int status); static void ampdu_tx_stop(struct ieee80211_tx_ampdu *tap); static void bar_stop_timer(struct ieee80211_tx_ampdu *tap); static int ampdu_rx_start(struct ieee80211_node *, struct ieee80211_rx_ampdu *, int baparamset, int batimeout, int baseqctl); static void ampdu_rx_stop(struct ieee80211_node *, struct ieee80211_rx_ampdu *); void ieee80211_ht_attach(struct ieee80211com *ic) { /* setup default aggregation policy */ ic->ic_recv_action = ieee80211_recv_action; ic->ic_send_action = ieee80211_send_action; ic->ic_ampdu_enable = ieee80211_ampdu_enable; ic->ic_addba_request = ieee80211_addba_request; ic->ic_addba_response = ieee80211_addba_response; ic->ic_addba_response_timeout = null_addba_response_timeout; ic->ic_addba_stop = ieee80211_addba_stop; ic->ic_bar_response = ieee80211_bar_response; ic->ic_ampdu_rx_start = ampdu_rx_start; ic->ic_ampdu_rx_stop = ampdu_rx_stop; ic->ic_htprotmode = IEEE80211_PROT_RTSCTS; ic->ic_curhtprotmode = IEEE80211_HTINFO_OPMODE_PURE; } void ieee80211_ht_detach(struct ieee80211com *ic) { } void ieee80211_ht_vattach(struct ieee80211vap *vap) { /* driver can override defaults */ vap->iv_ampdu_rxmax = IEEE80211_HTCAP_MAXRXAMPDU_8K; vap->iv_ampdu_density = IEEE80211_HTCAP_MPDUDENSITY_NA; vap->iv_ampdu_limit = vap->iv_ampdu_rxmax; vap->iv_amsdu_limit = vap->iv_htcaps & IEEE80211_HTCAP_MAXAMSDU; /* tx aggregation traffic thresholds */ vap->iv_ampdu_mintraffic[WME_AC_BK] = 128; vap->iv_ampdu_mintraffic[WME_AC_BE] = 64; vap->iv_ampdu_mintraffic[WME_AC_VO] = 32; vap->iv_ampdu_mintraffic[WME_AC_VI] = 32; vap->iv_htprotmode = IEEE80211_PROT_RTSCTS; vap->iv_curhtprotmode = IEEE80211_HTINFO_OPMODE_PURE; if (vap->iv_htcaps & IEEE80211_HTC_HT) { /* * Device is HT capable; enable all HT-related * facilities by default. * XXX these choices may be too aggressive. */ vap->iv_flags_ht |= IEEE80211_FHT_HT | IEEE80211_FHT_HTCOMPAT ; if (vap->iv_htcaps & IEEE80211_HTCAP_SHORTGI20) vap->iv_flags_ht |= IEEE80211_FHT_SHORTGI20; /* XXX infer from channel list? */ if (vap->iv_htcaps & IEEE80211_HTCAP_CHWIDTH40) { vap->iv_flags_ht |= IEEE80211_FHT_USEHT40; if (vap->iv_htcaps & IEEE80211_HTCAP_SHORTGI40) vap->iv_flags_ht |= IEEE80211_FHT_SHORTGI40; } /* enable RIFS if capable */ if (vap->iv_htcaps & IEEE80211_HTC_RIFS) vap->iv_flags_ht |= IEEE80211_FHT_RIFS; /* NB: A-MPDU and A-MSDU rx are mandated, these are tx only */ vap->iv_flags_ht |= IEEE80211_FHT_AMPDU_RX; if (vap->iv_htcaps & IEEE80211_HTC_AMPDU) vap->iv_flags_ht |= IEEE80211_FHT_AMPDU_TX; vap->iv_flags_ht |= IEEE80211_FHT_AMSDU_RX; if (vap->iv_htcaps & IEEE80211_HTC_AMSDU) vap->iv_flags_ht |= IEEE80211_FHT_AMSDU_TX; if (vap->iv_htcaps & IEEE80211_HTCAP_TXSTBC) vap->iv_flags_ht |= IEEE80211_FHT_STBC_TX; if (vap->iv_htcaps & IEEE80211_HTCAP_RXSTBC) vap->iv_flags_ht |= IEEE80211_FHT_STBC_RX; if (vap->iv_htcaps & IEEE80211_HTCAP_LDPC) vap->iv_flags_ht |= IEEE80211_FHT_LDPC_RX; if (vap->iv_htcaps & IEEE80211_HTC_TXLDPC) vap->iv_flags_ht |= IEEE80211_FHT_LDPC_TX; } /* NB: disable default legacy WDS, too many issues right now */ if (vap->iv_flags_ext & IEEE80211_FEXT_WDSLEGACY) vap->iv_flags_ht &= ~IEEE80211_FHT_HT; } void ieee80211_ht_vdetach(struct ieee80211vap *vap) { } static int ht_getrate(struct ieee80211com *ic, int index, enum ieee80211_phymode mode, int ratetype) { int mword, rate; mword = ieee80211_rate2media(ic, index | IEEE80211_RATE_MCS, mode); if (IFM_SUBTYPE(mword) != IFM_IEEE80211_MCS) return (0); switch (ratetype) { case 0: rate = ieee80211_htrates[index].ht20_rate_800ns; break; case 1: rate = ieee80211_htrates[index].ht20_rate_400ns; break; case 2: rate = ieee80211_htrates[index].ht40_rate_800ns; break; default: rate = ieee80211_htrates[index].ht40_rate_400ns; break; } return (rate); } static struct printranges { int minmcs; int maxmcs; int txstream; int ratetype; int htcapflags; } ranges[] = { { 0, 7, 1, 0, 0 }, { 8, 15, 2, 0, 0 }, { 16, 23, 3, 0, 0 }, { 24, 31, 4, 0, 0 }, { 32, 0, 1, 2, IEEE80211_HTC_TXMCS32 }, { 33, 38, 2, 0, IEEE80211_HTC_TXUNEQUAL }, { 39, 52, 3, 0, IEEE80211_HTC_TXUNEQUAL }, { 53, 76, 4, 0, IEEE80211_HTC_TXUNEQUAL }, { 0, 0, 0, 0, 0 }, }; static void ht_rateprint(struct ieee80211com *ic, enum ieee80211_phymode mode, int ratetype) { int minrate, maxrate; struct printranges *range; for (range = ranges; range->txstream != 0; range++) { if (ic->ic_txstream < range->txstream) continue; if (range->htcapflags && (ic->ic_htcaps & range->htcapflags) == 0) continue; if (ratetype < range->ratetype) continue; minrate = ht_getrate(ic, range->minmcs, mode, ratetype); maxrate = ht_getrate(ic, range->maxmcs, mode, ratetype); if (range->maxmcs) { ic_printf(ic, "MCS %d-%d: %d%sMbps - %d%sMbps\n", range->minmcs, range->maxmcs, minrate/2, ((minrate & 0x1) != 0 ? ".5" : ""), maxrate/2, ((maxrate & 0x1) != 0 ? ".5" : "")); } else { ic_printf(ic, "MCS %d: %d%sMbps\n", range->minmcs, minrate/2, ((minrate & 0x1) != 0 ? ".5" : "")); } } } static void ht_announce(struct ieee80211com *ic, enum ieee80211_phymode mode) { const char *modestr = ieee80211_phymode_name[mode]; ic_printf(ic, "%s MCS 20MHz\n", modestr); ht_rateprint(ic, mode, 0); if (ic->ic_htcaps & IEEE80211_HTCAP_SHORTGI20) { ic_printf(ic, "%s MCS 20MHz SGI\n", modestr); ht_rateprint(ic, mode, 1); } if (ic->ic_htcaps & IEEE80211_HTCAP_CHWIDTH40) { ic_printf(ic, "%s MCS 40MHz:\n", modestr); ht_rateprint(ic, mode, 2); } if ((ic->ic_htcaps & IEEE80211_HTCAP_CHWIDTH40) && (ic->ic_htcaps & IEEE80211_HTCAP_SHORTGI40)) { ic_printf(ic, "%s MCS 40MHz SGI:\n", modestr); ht_rateprint(ic, mode, 3); } } void ieee80211_ht_announce(struct ieee80211com *ic) { if (isset(ic->ic_modecaps, IEEE80211_MODE_11NA) || isset(ic->ic_modecaps, IEEE80211_MODE_11NG)) ic_printf(ic, "%dT%dR\n", ic->ic_txstream, ic->ic_rxstream); if (isset(ic->ic_modecaps, IEEE80211_MODE_11NA)) ht_announce(ic, IEEE80211_MODE_11NA); if (isset(ic->ic_modecaps, IEEE80211_MODE_11NG)) ht_announce(ic, IEEE80211_MODE_11NG); } void ieee80211_init_suphtrates(struct ieee80211com *ic) { #define ADDRATE(x) do { \ htrateset->rs_rates[htrateset->rs_nrates] = x; \ htrateset->rs_nrates++; \ } while (0) struct ieee80211_htrateset *htrateset = &ic->ic_sup_htrates; int i; memset(htrateset, 0, sizeof(struct ieee80211_htrateset)); for (i = 0; i < ic->ic_txstream * 8; i++) ADDRATE(i); if ((ic->ic_htcaps & IEEE80211_HTCAP_CHWIDTH40) && (ic->ic_htcaps & IEEE80211_HTC_TXMCS32)) ADDRATE(32); if (ic->ic_htcaps & IEEE80211_HTC_TXUNEQUAL) { if (ic->ic_txstream >= 2) { for (i = 33; i <= 38; i++) ADDRATE(i); } if (ic->ic_txstream >= 3) { for (i = 39; i <= 52; i++) ADDRATE(i); } if (ic->ic_txstream == 4) { for (i = 53; i <= 76; i++) ADDRATE(i); } } #undef ADDRATE } /* * Receive processing. */ /* * Decap the encapsulated A-MSDU frames and dispatch all but * the last for delivery. The last frame is returned for * delivery via the normal path. */ struct mbuf * ieee80211_decap_amsdu(struct ieee80211_node *ni, struct mbuf *m) { struct ieee80211vap *vap = ni->ni_vap; int framelen; struct mbuf *n; /* discard 802.3 header inserted by ieee80211_decap */ m_adj(m, sizeof(struct ether_header)); vap->iv_stats.is_amsdu_decap++; for (;;) { /* * Decap the first frame, bust it apart from the * remainder and deliver. We leave the last frame * delivery to the caller (for consistency with other * code paths, could also do it here). */ m = ieee80211_decap1(m, &framelen); if (m == NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, "a-msdu", "%s", "decap failed"); vap->iv_stats.is_amsdu_tooshort++; return NULL; } if (m->m_pkthdr.len == framelen) break; n = m_split(m, framelen, IEEE80211_M_NOWAIT); if (n == NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, "a-msdu", "%s", "unable to split encapsulated frames"); vap->iv_stats.is_amsdu_split++; m_freem(m); /* NB: must reclaim */ return NULL; } vap->iv_deliver_data(vap, ni, m); /* * Remove frame contents; each intermediate frame * is required to be aligned to a 4-byte boundary. */ m = n; m_adj(m, roundup2(framelen, 4) - framelen); /* padding */ } return m; /* last delivered by caller */ } static void ampdu_rx_purge_slot(struct ieee80211_rx_ampdu *rap, int i) { struct mbuf *m; /* Walk the queue, removing frames as appropriate */ for (;;) { m = mbufq_dequeue(&rap->rxa_mq[i]); if (m == NULL) break; rap->rxa_qbytes -= m->m_pkthdr.len; rap->rxa_qframes--; m_freem(m); } } /* * Add the given frame to the current RX reorder slot. * * For future offloaded A-MSDU handling where multiple frames with * the same sequence number show up here, this routine will append * those frames as long as they're appropriately tagged. */ static int ampdu_rx_add_slot(struct ieee80211_rx_ampdu *rap, int off, int tid, ieee80211_seq rxseq, struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_rx_stats *rxs) { const struct ieee80211_rx_stats *rxs_final = NULL; struct ieee80211vap *vap = ni->ni_vap; int toss_dup; #define PROCESS 0 /* caller should process frame */ #define CONSUMED 1 /* frame consumed, caller does nothing */ /* * Figure out if this is a duplicate frame for the given slot. * * We're assuming that the driver will hand us all the frames * for a given AMSDU decap pass and if we get /a/ frame * for an AMSDU decap then we'll get all of them. * * The tricksy bit is that we don't know when the /end/ of * the decap pass is, because we aren't tracking state here * per-slot to know that we've finished receiving the frame list. * * The driver sets RX_F_AMSDU and RX_F_AMSDU_MORE to tell us * what's going on; so ideally we'd just check the frame at the * end of the reassembly slot to see if its F_AMSDU w/ no F_AMSDU_MORE - * that means we've received the whole AMSDU decap pass. */ /* * Get the rxs of the final mbuf in the slot, if one exists. */ if (!mbufq_empty(&rap->rxa_mq[off])) { rxs_final = ieee80211_get_rx_params_ptr(mbufq_last(&rap->rxa_mq[off])); } /* Default to tossing the duplicate frame */ toss_dup = 1; /* * Check to see if the final frame has F_AMSDU and F_AMSDU set, AND * this frame has F_AMSDU set (MORE or otherwise.) That's a sign * that more can come. */ if ((rxs != NULL) && (rxs_final != NULL) && ieee80211_check_rxseq_amsdu(rxs) && ieee80211_check_rxseq_amsdu(rxs_final)) { if (! ieee80211_check_rxseq_amsdu_more(rxs_final)) { /* * amsdu_more() returning 0 means "it's not the * final frame" so we can append more * frames here. */ toss_dup = 0; } } /* * If the list is empty OR we have determined we can put more * driver decap'ed AMSDU frames in here, then insert. */ if (mbufq_empty(&rap->rxa_mq[off]) || (toss_dup == 0)) { if (mbufq_enqueue(&rap->rxa_mq[off], m) != 0) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_11N, ni->ni_macaddr, "a-mpdu queue fail", "seqno %u tid %u BA win <%u:%u> off=%d, qlen=%d, maxqlen=%d", rxseq, tid, rap->rxa_start, IEEE80211_SEQ_ADD(rap->rxa_start, rap->rxa_wnd-1), off, mbufq_len(&rap->rxa_mq[off]), rap->rxa_mq[off].mq_maxlen); /* XXX error count */ m_freem(m); return CONSUMED; } rap->rxa_qframes++; rap->rxa_qbytes += m->m_pkthdr.len; vap->iv_stats.is_ampdu_rx_reorder++; /* * Statistics for AMSDU decap. */ if (rxs != NULL && ieee80211_check_rxseq_amsdu(rxs)) { if (ieee80211_check_rxseq_amsdu_more(rxs)) { /* more=1, AMSDU, end of batch */ IEEE80211_NODE_STAT(ni, rx_amsdu_more_end); } else { IEEE80211_NODE_STAT(ni, rx_amsdu_more); } } } else { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_11N, ni->ni_macaddr, "a-mpdu duplicate", "seqno %u tid %u BA win <%u:%u>", rxseq, tid, rap->rxa_start, IEEE80211_SEQ_ADD(rap->rxa_start, rap->rxa_wnd-1)); if (rxs != NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_11N, ni->ni_macaddr, "a-mpdu duplicate", "seqno %d tid %u pktflags 0x%08x\n", rxseq, tid, rxs->c_pktflags); } if (rxs_final != NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_11N, ni->ni_macaddr, "a-mpdu duplicate", "final: pktflags 0x%08x\n", rxs_final->c_pktflags); } vap->iv_stats.is_rx_dup++; IEEE80211_NODE_STAT(ni, rx_dup); m_freem(m); } return CONSUMED; #undef CONSUMED #undef PROCESS } /* * Purge all frames in the A-MPDU re-order queue. */ static void ampdu_rx_purge(struct ieee80211_rx_ampdu *rap) { int i; for (i = 0; i < rap->rxa_wnd; i++) { ampdu_rx_purge_slot(rap, i); if (rap->rxa_qframes == 0) break; } KASSERT(rap->rxa_qbytes == 0 && rap->rxa_qframes == 0, ("lost %u data, %u frames on ampdu rx q", rap->rxa_qbytes, rap->rxa_qframes)); } static void ieee80211_ampdu_rx_init_rap(struct ieee80211_node *ni, struct ieee80211_rx_ampdu *rap) { int i; /* XXX TODO: ensure the queues are empty */ memset(rap, 0, sizeof(*rap)); for (i = 0; i < IEEE80211_AGGR_BAWMAX; i++) mbufq_init(&rap->rxa_mq[i], 256); } /* * Start A-MPDU rx/re-order processing for the specified TID. */ static int ampdu_rx_start(struct ieee80211_node *ni, struct ieee80211_rx_ampdu *rap, int baparamset, int batimeout, int baseqctl) { struct ieee80211vap *vap = ni->ni_vap; int bufsiz = _IEEE80211_MASKSHIFT(baparamset, IEEE80211_BAPS_BUFSIZ); if (rap->rxa_flags & IEEE80211_AGGR_RUNNING) { /* * AMPDU previously setup and not terminated with a DELBA, * flush the reorder q's in case anything remains. */ ampdu_rx_purge(rap); } ieee80211_ampdu_rx_init_rap(ni, rap); rap->rxa_wnd = (bufsiz == 0) ? IEEE80211_AGGR_BAWMAX : min(bufsiz, IEEE80211_AGGR_BAWMAX); rap->rxa_start = _IEEE80211_MASKSHIFT(baseqctl, IEEE80211_BASEQ_START); rap->rxa_flags |= IEEE80211_AGGR_RUNNING | IEEE80211_AGGR_XCHGPEND; /* XXX this should be a configuration flag */ if ((vap->iv_htcaps & IEEE80211_HTC_RX_AMSDU_AMPDU) && (_IEEE80211_MASKSHIFT(baparamset, IEEE80211_BAPS_AMSDU))) rap->rxa_flags |= IEEE80211_AGGR_AMSDU; else rap->rxa_flags &= ~IEEE80211_AGGR_AMSDU; return 0; } /* * Public function; manually setup the RX ampdu state. */ int ieee80211_ampdu_rx_start_ext(struct ieee80211_node *ni, int tid, int seq, int baw) { struct ieee80211_rx_ampdu *rap; /* XXX TODO: sanity check tid, seq, baw */ rap = &ni->ni_rx_ampdu[tid]; if (rap->rxa_flags & IEEE80211_AGGR_RUNNING) { /* * AMPDU previously setup and not terminated with a DELBA, * flush the reorder q's in case anything remains. */ ampdu_rx_purge(rap); } ieee80211_ampdu_rx_init_rap(ni, rap); rap->rxa_wnd = (baw== 0) ? IEEE80211_AGGR_BAWMAX : min(baw, IEEE80211_AGGR_BAWMAX); if (seq == -1) { /* Wait for the first RX frame, use that as BAW */ rap->rxa_start = 0; rap->rxa_flags |= IEEE80211_AGGR_WAITRX; } else { rap->rxa_start = seq; } rap->rxa_flags |= IEEE80211_AGGR_RUNNING | IEEE80211_AGGR_XCHGPEND; /* XXX TODO: no amsdu flag */ IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: tid=%d, start=%d, wnd=%d, flags=0x%08x", __func__, tid, seq, rap->rxa_wnd, rap->rxa_flags); return 0; } /* * Public function; manually stop the RX AMPDU state. */ void ieee80211_ampdu_rx_stop_ext(struct ieee80211_node *ni, int tid) { struct ieee80211_rx_ampdu *rap; /* XXX TODO: sanity check tid, seq, baw */ rap = &ni->ni_rx_ampdu[tid]; ampdu_rx_stop(ni, rap); } /* * Stop A-MPDU rx processing for the specified TID. */ static void ampdu_rx_stop(struct ieee80211_node *ni, struct ieee80211_rx_ampdu *rap) { ampdu_rx_purge(rap); rap->rxa_flags &= ~(IEEE80211_AGGR_RUNNING | IEEE80211_AGGR_XCHGPEND | IEEE80211_AGGR_WAITRX); } /* * Dispatch a frame from the A-MPDU reorder queue. The * frame is fed back into ieee80211_input marked with an * M_AMPDU_MPDU flag so it doesn't come back to us (it also * permits ieee80211_input to optimize re-processing). */ static __inline void ampdu_dispatch(struct ieee80211_node *ni, struct mbuf *m) { m->m_flags |= M_AMPDU_MPDU; /* bypass normal processing */ /* NB: rssi and noise are ignored w/ M_AMPDU_MPDU set */ (void) ieee80211_input(ni, m, 0, 0); } static int ampdu_dispatch_slot(struct ieee80211_rx_ampdu *rap, struct ieee80211_node *ni, int i) { struct mbuf *m; int n = 0; for (;;) { m = mbufq_dequeue(&rap->rxa_mq[i]); if (m == NULL) break; n++; rap->rxa_qbytes -= m->m_pkthdr.len; rap->rxa_qframes--; ampdu_dispatch(ni, m); } return (n); } static void ampdu_rx_moveup(struct ieee80211_rx_ampdu *rap, struct ieee80211_node *ni, int i, int winstart) { struct ieee80211vap *vap = ni->ni_vap; /* * If frames remain, copy the mbuf pointers down so * they correspond to the offsets in the new window. */ if (rap->rxa_qframes != 0) { int n = rap->rxa_qframes, j; for (j = i+1; j < rap->rxa_wnd; j++) { /* * Concat the list contents over, which will * blank the source list for us. */ if (mbufq_len(&rap->rxa_mq[j]) != 0) { n = n - mbufq_len(&rap->rxa_mq[j]); mbufq_concat(&rap->rxa_mq[j-i], &rap->rxa_mq[j]); KASSERT(n >= 0, ("%s: n < 0 (%d)", __func__, n)); if (n == 0) break; } } KASSERT(n == 0, ("%s: lost %d frames, qframes %d off %d " "BA win <%d:%d> winstart %d", __func__, n, rap->rxa_qframes, i, rap->rxa_start, IEEE80211_SEQ_ADD(rap->rxa_start, rap->rxa_wnd-1), winstart)); vap->iv_stats.is_ampdu_rx_copy += rap->rxa_qframes; } } /* * Dispatch as many frames as possible from the re-order queue. * Frames will always be "at the front"; we process all frames * up to the first empty slot in the window. On completion we * cleanup state if there are still pending frames in the current * BA window. We assume the frame at slot 0 is already handled * by the caller; we always start at slot 1. */ static void ampdu_rx_dispatch(struct ieee80211_rx_ampdu *rap, struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; int i, r, r2; /* flush run of frames */ r2 = 0; for (i = 1; i < rap->rxa_wnd; i++) { r = ampdu_dispatch_slot(rap, ni, i); if (r == 0) break; r2 += r; } /* move up frames */ ampdu_rx_moveup(rap, ni, i, -1); /* * Adjust the start of the BA window to * reflect the frames just dispatched. */ rap->rxa_start = IEEE80211_SEQ_ADD(rap->rxa_start, i); vap->iv_stats.is_ampdu_rx_oor += r2; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: moved slot up %d slots to start at %d (%d frames)", __func__, i, rap->rxa_start, r2); } /* * Dispatch all frames in the A-MPDU re-order queue. */ static void ampdu_rx_flush(struct ieee80211_node *ni, struct ieee80211_rx_ampdu *rap) { int i, r; for (i = 0; i < rap->rxa_wnd; i++) { r = ampdu_dispatch_slot(rap, ni, i); if (r == 0) continue; ni->ni_vap->iv_stats.is_ampdu_rx_oor += r; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: moved slot up %d slots to start at %d (%d frames)", __func__, 1, rap->rxa_start, r); if (rap->rxa_qframes == 0) break; } } /* * Dispatch all frames in the A-MPDU re-order queue * preceding the specified sequence number. This logic * handles window moves due to a received MSDU or BAR. */ static void ampdu_rx_flush_upto(struct ieee80211_node *ni, struct ieee80211_rx_ampdu *rap, ieee80211_seq winstart) { struct ieee80211vap *vap = ni->ni_vap; ieee80211_seq seqno; int i, r; /* * Flush any complete MSDU's with a sequence number lower * than winstart. Gaps may exist. Note that we may actually * dispatch frames past winstart if a run continues; this is * an optimization that avoids having to do a separate pass * to dispatch frames after moving the BA window start. */ seqno = rap->rxa_start; for (i = 0; i < rap->rxa_wnd; i++) { if ((r = mbufq_len(&rap->rxa_mq[i])) != 0) { (void) ampdu_dispatch_slot(rap, ni, i); } else { if (!IEEE80211_SEQ_BA_BEFORE(seqno, winstart)) break; } vap->iv_stats.is_ampdu_rx_oor += r; seqno = IEEE80211_SEQ_INC(seqno); IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: moved slot up %d slots to start at %d (%d frames)", __func__, 1, seqno, r); } /* * If frames remain, copy the mbuf pointers down so * they correspond to the offsets in the new window. */ ampdu_rx_moveup(rap, ni, i, winstart); /* * Move the start of the BA window; we use the * sequence number of the last MSDU that was * passed up the stack+1 or winstart if stopped on * a gap in the reorder buffer. */ rap->rxa_start = seqno; } /* * Process a received QoS data frame for an HT station. Handle * A-MPDU reordering: if this frame is received out of order * and falls within the BA window hold onto it. Otherwise if * this frame completes a run, flush any pending frames. We * return 1 if the frame is consumed. A 0 is returned if * the frame should be processed normally by the caller. * * A-MSDU: handle hardware decap'ed A-MSDU frames that are * pretending to be MPDU's. They're dispatched directly if * able; or attempted to put into the receive reordering slot. */ int ieee80211_ampdu_reorder(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_rx_stats *rxs) { #define PROCESS 0 /* caller should process frame */ #define CONSUMED 1 /* frame consumed, caller does nothing */ struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_qosframe *wh; struct ieee80211_rx_ampdu *rap; ieee80211_seq rxseq; uint8_t tid; int off; int amsdu = ieee80211_check_rxseq_amsdu(rxs); int amsdu_end = ieee80211_check_rxseq_amsdu_more(rxs); KASSERT((m->m_flags & (M_AMPDU | M_AMPDU_MPDU)) == M_AMPDU, ("!a-mpdu or already re-ordered, flags 0x%x", m->m_flags)); KASSERT(ni->ni_flags & IEEE80211_NODE_HT, ("not an HT sta")); /* NB: m_len known to be sufficient */ wh = mtod(m, struct ieee80211_qosframe *); if (!IEEE80211_IS_QOSDATA(wh)) { /* * Not QoS data, shouldn't get here but just * return it to the caller for processing. */ return PROCESS; } /* * 802.11-2012 9.3.2.10 - Duplicate detection and recovery. * * Multicast QoS data frames are checked against a different * counter, not the per-TID counter. */ if (IEEE80211_IS_MULTICAST(wh->i_addr1)) return PROCESS; tid = ieee80211_getqos(wh)[0]; tid &= IEEE80211_QOS_TID; rap = &ni->ni_rx_ampdu[tid]; if ((rap->rxa_flags & IEEE80211_AGGR_XCHGPEND) == 0) { /* * No ADDBA request yet, don't touch. */ return PROCESS; } rxseq = le16toh(*(uint16_t *)wh->i_seq); if ((rxseq & IEEE80211_SEQ_FRAG_MASK) != 0) { /* * Fragments are not allowed; toss. */ IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_11N, ni->ni_macaddr, "A-MPDU", "fragment, rxseq 0x%x tid %u%s", rxseq, tid, wh->i_fc[1] & IEEE80211_FC1_RETRY ? " (retransmit)" : ""); vap->iv_stats.is_ampdu_rx_drop++; IEEE80211_NODE_STAT(ni, rx_drop); m_freem(m); return CONSUMED; } rxseq >>= IEEE80211_SEQ_SEQ_SHIFT; rap->rxa_nframes++; /* * Handle waiting for the first frame to define the BAW. * Some firmware doesn't provide the RX of the starting point * of the BAW and we have to cope. */ if (rap->rxa_flags & IEEE80211_AGGR_WAITRX) { rap->rxa_flags &= ~IEEE80211_AGGR_WAITRX; rap->rxa_start = rxseq; } again: if (rxseq == rap->rxa_start) { /* * First frame in window. */ if (rap->rxa_qframes != 0) { /* * Dispatch as many packets as we can. */ KASSERT(mbufq_empty(&rap->rxa_mq[0]), ("unexpected dup")); ampdu_dispatch(ni, m); ampdu_rx_dispatch(rap, ni); return CONSUMED; } else { /* * In order; advance window if needed and notify * caller to dispatch directly. */ if (amsdu) { if (amsdu_end) { rap->rxa_start = IEEE80211_SEQ_INC(rxseq); IEEE80211_NODE_STAT(ni, rx_amsdu_more_end); } else { IEEE80211_NODE_STAT(ni, rx_amsdu_more); } } else { rap->rxa_start = IEEE80211_SEQ_INC(rxseq); } return PROCESS; } } /* * Frame is out of order; store if in the BA window. */ /* calculate offset in BA window */ off = IEEE80211_SEQ_SUB(rxseq, rap->rxa_start); if (off < rap->rxa_wnd) { /* * Common case (hopefully): in the BA window. * Sec 9.10.7.6.2 a) (p.137) */ /* * Check for frames sitting too long in the reorder queue. * This should only ever happen if frames are not delivered * without the sender otherwise notifying us (e.g. with a * BAR to move the window). Typically this happens because * of vendor bugs that cause the sequence number to jump. * When this happens we get a gap in the reorder queue that * leaves frame sitting on the queue until they get pushed * out due to window moves. When the vendor does not send * BAR this move only happens due to explicit packet sends * * NB: we only track the time of the oldest frame in the * reorder q; this means that if we flush we might push * frames that still "new"; if this happens then subsequent * frames will result in BA window moves which cost something * but is still better than a big throughput dip. */ if (rap->rxa_qframes != 0) { /* XXX honor batimeout? */ if (ticks - rap->rxa_age > ieee80211_ampdu_age) { /* * Too long since we received the first * frame; flush the reorder buffer. */ if (rap->rxa_qframes != 0) { vap->iv_stats.is_ampdu_rx_age += rap->rxa_qframes; ampdu_rx_flush(ni, rap); } /* * Advance the window if needed and notify * the caller to dispatch directly. */ if (amsdu) { if (amsdu_end) { rap->rxa_start = IEEE80211_SEQ_INC(rxseq); IEEE80211_NODE_STAT(ni, rx_amsdu_more_end); } else { IEEE80211_NODE_STAT(ni, rx_amsdu_more); } } else { rap->rxa_start = IEEE80211_SEQ_INC(rxseq); } return PROCESS; } } else { /* * First frame, start aging timer. */ rap->rxa_age = ticks; } /* save packet - this consumes, no matter what */ ampdu_rx_add_slot(rap, off, tid, rxseq, ni, m, rxs); return CONSUMED; } if (off < IEEE80211_SEQ_BA_RANGE) { /* * Outside the BA window, but within range; * flush the reorder q and move the window. * Sec 9.10.7.6.2 b) (p.138) */ IEEE80211_NOTE(vap, IEEE80211_MSG_11N, ni, "move BA win <%u:%u> (%u frames) rxseq %u tid %u", rap->rxa_start, IEEE80211_SEQ_ADD(rap->rxa_start, rap->rxa_wnd-1), rap->rxa_qframes, rxseq, tid); vap->iv_stats.is_ampdu_rx_move++; /* * The spec says to flush frames up to but not including: * WinStart_B = rxseq - rap->rxa_wnd + 1 * Then insert the frame or notify the caller to process * it immediately. We can safely do this by just starting * over again because we know the frame will now be within * the BA window. */ /* NB: rxa_wnd known to be >0 */ ampdu_rx_flush_upto(ni, rap, IEEE80211_SEQ_SUB(rxseq, rap->rxa_wnd-1)); goto again; } else { /* * Outside the BA window and out of range; toss. * Sec 9.10.7.6.2 c) (p.138) */ IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_11N, ni->ni_macaddr, "MPDU", "BA win <%u:%u> (%u frames) rxseq %u tid %u%s", rap->rxa_start, IEEE80211_SEQ_ADD(rap->rxa_start, rap->rxa_wnd-1), rap->rxa_qframes, rxseq, tid, wh->i_fc[1] & IEEE80211_FC1_RETRY ? " (retransmit)" : ""); vap->iv_stats.is_ampdu_rx_drop++; IEEE80211_NODE_STAT(ni, rx_drop); m_freem(m); return CONSUMED; } #undef CONSUMED #undef PROCESS } /* * Process a BAR ctl frame. Dispatch all frames up to * the sequence number of the frame. If this frame is * out of range it's discarded. */ void ieee80211_recv_bar(struct ieee80211_node *ni, struct mbuf *m0) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_frame_bar *wh; struct ieee80211_rx_ampdu *rap; ieee80211_seq rxseq; int tid, off; if (!ieee80211_recv_bar_ena) { #if 0 IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_11N, ni->ni_macaddr, "BAR", "%s", "processing disabled"); #endif vap->iv_stats.is_ampdu_bar_bad++; return; } wh = mtod(m0, struct ieee80211_frame_bar *); /* XXX check basic BAR */ tid = _IEEE80211_MASKSHIFT(le16toh(wh->i_ctl), IEEE80211_BAR_TID); rap = &ni->ni_rx_ampdu[tid]; if ((rap->rxa_flags & IEEE80211_AGGR_XCHGPEND) == 0) { /* * No ADDBA request yet, don't touch. */ IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_11N, ni->ni_macaddr, "BAR", "no BA stream, tid %u", tid); vap->iv_stats.is_ampdu_bar_bad++; return; } vap->iv_stats.is_ampdu_bar_rx++; rxseq = le16toh(wh->i_seq) >> IEEE80211_SEQ_SEQ_SHIFT; if (rxseq == rap->rxa_start) return; /* calculate offset in BA window */ off = IEEE80211_SEQ_SUB(rxseq, rap->rxa_start); if (off < IEEE80211_SEQ_BA_RANGE) { /* * Flush the reorder q up to rxseq and move the window. * Sec 9.10.7.6.3 a) (p.138) */ IEEE80211_NOTE(vap, IEEE80211_MSG_11N, ni, "BAR moves BA win <%u:%u> (%u frames) rxseq %u tid %u", rap->rxa_start, IEEE80211_SEQ_ADD(rap->rxa_start, rap->rxa_wnd-1), rap->rxa_qframes, rxseq, tid); vap->iv_stats.is_ampdu_bar_move++; ampdu_rx_flush_upto(ni, rap, rxseq); if (off >= rap->rxa_wnd) { /* * BAR specifies a window start to the right of BA * window; we must move it explicitly since * ampdu_rx_flush_upto will not. */ rap->rxa_start = rxseq; } } else { /* * Out of range; toss. * Sec 9.10.7.6.3 b) (p.138) */ IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_11N, ni->ni_macaddr, "BAR", "BA win <%u:%u> (%u frames) rxseq %u tid %u%s", rap->rxa_start, IEEE80211_SEQ_ADD(rap->rxa_start, rap->rxa_wnd-1), rap->rxa_qframes, rxseq, tid, wh->i_fc[1] & IEEE80211_FC1_RETRY ? " (retransmit)" : ""); vap->iv_stats.is_ampdu_bar_oow++; IEEE80211_NODE_STAT(ni, rx_drop); } } /* * Setup HT-specific state in a node. Called only * when HT use is negotiated so we don't do extra * work for temporary and/or legacy sta's. */ void ieee80211_ht_node_init(struct ieee80211_node *ni) { struct ieee80211_tx_ampdu *tap; int tid; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: called (%p)", __func__, ni); if (ni->ni_flags & IEEE80211_NODE_HT) { /* * Clean AMPDU state on re-associate. This handles the case * where a station leaves w/o notifying us and then returns * before node is reaped for inactivity. */ IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: calling cleanup (%p)", __func__, ni); ieee80211_ht_node_cleanup(ni); } for (tid = 0; tid < WME_NUM_TID; tid++) { tap = &ni->ni_tx_ampdu[tid]; tap->txa_tid = tid; tap->txa_ni = ni; ieee80211_txampdu_init_pps(tap); /* NB: further initialization deferred */ ieee80211_ampdu_rx_init_rap(ni, &ni->ni_rx_ampdu[tid]); } ni->ni_flags |= IEEE80211_NODE_HT | IEEE80211_NODE_AMPDU | IEEE80211_NODE_AMSDU; } /* * Cleanup HT-specific state in a node. Called only * when HT use has been marked. */ void ieee80211_ht_node_cleanup(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; int i; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: called (%p)", __func__, ni); KASSERT(ni->ni_flags & IEEE80211_NODE_HT, ("not an HT node")); /* XXX optimize this */ for (i = 0; i < WME_NUM_TID; i++) { struct ieee80211_tx_ampdu *tap = &ni->ni_tx_ampdu[i]; if (tap->txa_flags & IEEE80211_AGGR_SETUP) ampdu_tx_stop(tap); } for (i = 0; i < WME_NUM_TID; i++) ic->ic_ampdu_rx_stop(ni, &ni->ni_rx_ampdu[i]); ni->ni_htcap = 0; ni->ni_flags &= ~IEEE80211_NODE_HT_ALL; } /* * Age out HT resources for a station. */ void ieee80211_ht_node_age(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; uint8_t tid; KASSERT(ni->ni_flags & IEEE80211_NODE_HT, ("not an HT sta")); for (tid = 0; tid < WME_NUM_TID; tid++) { struct ieee80211_rx_ampdu *rap; rap = &ni->ni_rx_ampdu[tid]; if ((rap->rxa_flags & IEEE80211_AGGR_XCHGPEND) == 0) continue; if (rap->rxa_qframes == 0) continue; /* * Check for frames sitting too long in the reorder queue. * See above for more details on what's happening here. */ /* XXX honor batimeout? */ if (ticks - rap->rxa_age > ieee80211_ampdu_age) { /* * Too long since we received the first * frame; flush the reorder buffer. */ vap->iv_stats.is_ampdu_rx_age += rap->rxa_qframes; ampdu_rx_flush(ni, rap); } } } static struct ieee80211_channel * findhtchan(struct ieee80211com *ic, struct ieee80211_channel *c, int htflags) { return ieee80211_find_channel(ic, c->ic_freq, (c->ic_flags &~ IEEE80211_CHAN_HT) | htflags); } /* * Adjust a channel to be HT/non-HT according to the vap's configuration. */ struct ieee80211_channel * ieee80211_ht_adjust_channel(struct ieee80211com *ic, struct ieee80211_channel *chan, int flags) { struct ieee80211_channel *c; if (flags & IEEE80211_FHT_HT) { /* promote to HT if possible */ if (flags & IEEE80211_FHT_USEHT40) { if (!IEEE80211_IS_CHAN_HT40(chan)) { /* NB: arbitrarily pick ht40+ over ht40- */ c = findhtchan(ic, chan, IEEE80211_CHAN_HT40U); if (c == NULL) c = findhtchan(ic, chan, IEEE80211_CHAN_HT40D); if (c == NULL) c = findhtchan(ic, chan, IEEE80211_CHAN_HT20); if (c != NULL) chan = c; } } else if (!IEEE80211_IS_CHAN_HT20(chan)) { c = findhtchan(ic, chan, IEEE80211_CHAN_HT20); if (c != NULL) chan = c; } } else if (IEEE80211_IS_CHAN_HT(chan)) { /* demote to legacy, HT use is disabled */ c = ieee80211_find_channel(ic, chan->ic_freq, chan->ic_flags &~ IEEE80211_CHAN_HT); if (c != NULL) chan = c; } return chan; } /* * Setup HT-specific state for a legacy WDS peer. */ void ieee80211_ht_wds_init(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_tx_ampdu *tap; int tid; KASSERT(vap->iv_flags_ht & IEEE80211_FHT_HT, ("no HT requested")); /* XXX check scan cache in case peer has an ap and we have info */ /* * If setup with a legacy channel; locate an HT channel. * Otherwise if the inherited channel (from a companion * AP) is suitable use it so we use the same location * for the extension channel). */ ni->ni_chan = ieee80211_ht_adjust_channel(ni->ni_ic, ni->ni_chan, ieee80211_htchanflags(ni->ni_chan)); ni->ni_htcap = 0; if (vap->iv_flags_ht & IEEE80211_FHT_SHORTGI20) ni->ni_htcap |= IEEE80211_HTCAP_SHORTGI20; if (IEEE80211_IS_CHAN_HT40(ni->ni_chan)) { ni->ni_htcap |= IEEE80211_HTCAP_CHWIDTH40; - ni->ni_chw = 40; + ni->ni_chw = IEEE80211_STA_RX_BW_40; if (IEEE80211_IS_CHAN_HT40U(ni->ni_chan)) ni->ni_ht2ndchan = IEEE80211_HTINFO_2NDCHAN_ABOVE; else if (IEEE80211_IS_CHAN_HT40D(ni->ni_chan)) ni->ni_ht2ndchan = IEEE80211_HTINFO_2NDCHAN_BELOW; if (vap->iv_flags_ht & IEEE80211_FHT_SHORTGI40) ni->ni_htcap |= IEEE80211_HTCAP_SHORTGI40; } else { - ni->ni_chw = 20; + ni->ni_chw = IEEE80211_STA_RX_BW_20; ni->ni_ht2ndchan = IEEE80211_HTINFO_2NDCHAN_NONE; } ni->ni_htctlchan = ni->ni_chan->ic_ieee; if (vap->iv_flags_ht & IEEE80211_FHT_RIFS) ni->ni_flags |= IEEE80211_NODE_RIFS; /* XXX does it make sense to enable SMPS? */ ni->ni_htopmode = 0; /* XXX need protection state */ ni->ni_htstbc = 0; /* XXX need info */ for (tid = 0; tid < WME_NUM_TID; tid++) { tap = &ni->ni_tx_ampdu[tid]; tap->txa_tid = tid; ieee80211_txampdu_init_pps(tap); } /* NB: AMPDU tx/rx governed by IEEE80211_FHT_AMPDU_{TX,RX} */ ni->ni_flags |= IEEE80211_NODE_HT | IEEE80211_NODE_AMPDU | IEEE80211_NODE_AMSDU; } /* * Notify a VAP of a change in the HTINFO ie if it's a hostap VAP. * * This is to be called from the deferred HT protection update * task once the flags are updated. */ void ieee80211_htinfo_notify(struct ieee80211vap *vap) { IEEE80211_LOCK_ASSERT(vap->iv_ic); if (vap->iv_opmode != IEEE80211_M_HOSTAP) return; if (vap->iv_state != IEEE80211_S_RUN || !IEEE80211_IS_CHAN_HT(vap->iv_bss->ni_chan)) return; IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_11N, vap->iv_bss, "HT bss occupancy change: %d sta, %d ht, " "%d ht40%s, HT protmode now 0x%x" , vap->iv_sta_assoc , vap->iv_ht_sta_assoc , vap->iv_ht40_sta_assoc , (vap->iv_flags_ht & IEEE80211_FHT_NONHT_PR) ? ", non-HT sta present" : "" , vap->iv_curhtprotmode); ieee80211_beacon_notify(vap, IEEE80211_BEACON_HTINFO); } /* * Calculate HT protection mode from current * state and handle updates. */ static void htinfo_update(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; uint8_t protmode; if (vap->iv_sta_assoc != vap->iv_ht_sta_assoc) { protmode = IEEE80211_HTINFO_OPMODE_MIXED | IEEE80211_HTINFO_NONHT_PRESENT; } else if (vap->iv_flags_ht & IEEE80211_FHT_NONHT_PR) { protmode = IEEE80211_HTINFO_OPMODE_PROTOPT | IEEE80211_HTINFO_NONHT_PRESENT; } else if (ic->ic_bsschan != IEEE80211_CHAN_ANYC && IEEE80211_IS_CHAN_HT40(ic->ic_bsschan) && vap->iv_sta_assoc != vap->iv_ht40_sta_assoc) { protmode = IEEE80211_HTINFO_OPMODE_HT20PR; } else { protmode = IEEE80211_HTINFO_OPMODE_PURE; } if (protmode != vap->iv_curhtprotmode) { vap->iv_curhtprotmode = protmode; /* Update VAP with new protection mode */ ieee80211_vap_update_ht_protmode(vap); } } /* * Handle an HT station joining a BSS. */ void ieee80211_ht_node_join(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; IEEE80211_LOCK_ASSERT(vap->iv_ic); if (ni->ni_flags & IEEE80211_NODE_HT) { vap->iv_ht_sta_assoc++; - if (ni->ni_chw == 40) + if (ni->ni_chw == IEEE80211_STA_RX_BW_40) vap->iv_ht40_sta_assoc++; } htinfo_update(vap); } /* * Handle an HT station leaving a BSS. */ void ieee80211_ht_node_leave(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; IEEE80211_LOCK_ASSERT(vap->iv_ic); if (ni->ni_flags & IEEE80211_NODE_HT) { vap->iv_ht_sta_assoc--; - if (ni->ni_chw == 40) + if (ni->ni_chw == IEEE80211_STA_RX_BW_40) vap->iv_ht40_sta_assoc--; } htinfo_update(vap); } /* * Public version of htinfo_update; used for processing * beacon frames from overlapping bss. * * Caller can specify either IEEE80211_HTINFO_OPMODE_MIXED * (on receipt of a beacon that advertises MIXED) or * IEEE80211_HTINFO_OPMODE_PROTOPT (on receipt of a beacon * from an overlapping legacy bss). We treat MIXED with * a higher precedence than PROTOPT (i.e. we will not change * change PROTOPT -> MIXED; only MIXED -> PROTOPT). This * corresponds to how we handle things in htinfo_update. * */ void ieee80211_htprot_update(struct ieee80211vap *vap, int protmode) { struct ieee80211com *ic = vap->iv_ic; #define OPMODE(x) _IEEE80211_SHIFTMASK(x, IEEE80211_HTINFO_OPMODE) IEEE80211_LOCK(ic); /* track non-HT station presence */ KASSERT(protmode & IEEE80211_HTINFO_NONHT_PRESENT, ("protmode 0x%x", protmode)); vap->iv_flags_ht |= IEEE80211_FHT_NONHT_PR; vap->iv_lastnonht = ticks; if (protmode != vap->iv_curhtprotmode && (OPMODE(vap->iv_curhtprotmode) != IEEE80211_HTINFO_OPMODE_MIXED || OPMODE(protmode) == IEEE80211_HTINFO_OPMODE_PROTOPT)) { vap->iv_curhtprotmode = protmode; /* Update VAP with new protection mode */ ieee80211_vap_update_ht_protmode(vap); } IEEE80211_UNLOCK(ic); #undef OPMODE } /* * Time out presence of an overlapping bss with non-HT * stations. When operating in hostap mode we listen for * beacons from other stations and if we identify a non-HT * station is present we update the opmode field of the * HTINFO ie. To identify when all non-HT stations are * gone we time out this condition. */ void ieee80211_ht_timeout(struct ieee80211vap *vap) { IEEE80211_LOCK_ASSERT(vap->iv_ic); if ((vap->iv_flags_ht & IEEE80211_FHT_NONHT_PR) && ieee80211_time_after(ticks, vap->iv_lastnonht + IEEE80211_NONHT_PRESENT_AGE)) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_11N, "%s", "time out non-HT STA present on channel"); vap->iv_flags_ht &= ~IEEE80211_FHT_NONHT_PR; htinfo_update(vap); } } /* * Process an 802.11n HT capabilities ie. */ void ieee80211_parse_htcap(struct ieee80211_node *ni, const uint8_t *ie) { if (ie[0] == IEEE80211_ELEMID_VENDOR) { /* * Station used Vendor OUI ie to associate; * mark the node so when we respond we'll use * the Vendor OUI's and not the standard ie's. */ ni->ni_flags |= IEEE80211_NODE_HTCOMPAT; ie += 4; } else ni->ni_flags &= ~IEEE80211_NODE_HTCOMPAT; ni->ni_htcap = le16dec(ie + __offsetof(struct ieee80211_ie_htcap, hc_cap)); ni->ni_htparam = ie[__offsetof(struct ieee80211_ie_htcap, hc_param)]; } static void htinfo_parse(struct ieee80211_node *ni, const struct ieee80211_ie_htinfo *htinfo) { uint16_t w; ni->ni_htctlchan = htinfo->hi_ctrlchannel; ni->ni_ht2ndchan = _IEEE80211_SHIFTMASK(htinfo->hi_byte1, IEEE80211_HTINFO_2NDCHAN); w = le16dec(&htinfo->hi_byte2); ni->ni_htopmode = _IEEE80211_SHIFTMASK(w, IEEE80211_HTINFO_OPMODE); w = le16dec(&htinfo->hi_byte45); ni->ni_htstbc = _IEEE80211_SHIFTMASK(w, IEEE80211_HTINFO_BASIC_STBCMCS); } /* * Parse an 802.11n HT info ie and save useful information * to the node state. Note this does not effect any state * changes such as for channel width change. */ void ieee80211_parse_htinfo(struct ieee80211_node *ni, const uint8_t *ie) { if (ie[0] == IEEE80211_ELEMID_VENDOR) ie += 4; htinfo_parse(ni, (const struct ieee80211_ie_htinfo *) ie); } /* * Handle 11n/11ac channel switch. * * Use the received HT/VHT ie's to identify the right channel to use. * If we cannot locate it in the channel table then fallback to * legacy operation. * * Note that we use this information to identify the node's * channel only; the caller is responsible for insuring any * required channel change is done (e.g. in sta mode when * parsing the contents of a beacon frame). */ static int htinfo_update_chw(struct ieee80211_node *ni, int htflags, int vhtflags) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211_channel *c; int chanflags; int ret = 0; /* * First step - do HT/VHT only channel lookup based on operating mode * flags. This involves masking out the VHT flags as well. * Otherwise we end up doing the full channel walk each time * we trigger this, which is expensive. */ chanflags = (ni->ni_chan->ic_flags &~ (IEEE80211_CHAN_HT | IEEE80211_CHAN_VHT)) | htflags | vhtflags; if (chanflags == ni->ni_chan->ic_flags) goto done; /* * If HT /or/ VHT flags have changed then check both. * We need to start by picking a HT channel anyway. */ c = NULL; chanflags = (ni->ni_chan->ic_flags &~ (IEEE80211_CHAN_HT | IEEE80211_CHAN_VHT)) | htflags; /* XXX not right for ht40- */ c = ieee80211_find_channel(ic, ni->ni_chan->ic_freq, chanflags); if (c == NULL && (htflags & IEEE80211_CHAN_HT40)) { /* * No HT40 channel entry in our table; fall back * to HT20 operation. This should not happen. */ c = findhtchan(ic, ni->ni_chan, IEEE80211_CHAN_HT20); #if 0 IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_11N, ni, "no HT40 channel (freq %u), falling back to HT20", ni->ni_chan->ic_freq); #endif /* XXX stat */ } /* Nothing found - leave it alone; move onto VHT */ if (c == NULL) c = ni->ni_chan; /* * If it's non-HT, then bail out now. */ if (! IEEE80211_IS_CHAN_HT(c)) { IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_11N, ni, "not HT; skipping VHT check (%u/0x%x)", c->ic_freq, c->ic_flags); goto done; } /* * Next step - look at the current VHT flags and determine * if we need to upgrade. Mask out the VHT and HT flags since * the vhtflags field will already have the correct HT * flags to use. */ if (IEEE80211_CONF_VHT(ic) && ni->ni_vhtcap != 0 && vhtflags != 0) { chanflags = (c->ic_flags &~ (IEEE80211_CHAN_HT | IEEE80211_CHAN_VHT)) | vhtflags; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_11N, ni, "%s: VHT; chanwidth=0x%02x; vhtflags=0x%08x", __func__, ni->ni_vht_chanwidth, vhtflags); IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_11N, ni, "%s: VHT; trying lookup for %d/0x%08x", __func__, c->ic_freq, chanflags); c = ieee80211_find_channel(ic, c->ic_freq, chanflags); } /* Finally, if it's changed */ if (c != NULL && c != ni->ni_chan) { IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_11N, ni, "switch station to %s%d channel %u/0x%x", IEEE80211_IS_CHAN_VHT(c) ? "VHT" : "HT", IEEE80211_IS_CHAN_VHT80(c) ? 80 : (IEEE80211_IS_CHAN_HT40(c) ? 40 : 20), c->ic_freq, c->ic_flags); ni->ni_chan = c; ret = 1; } /* NB: caller responsible for forcing any channel change */ done: /* update node's (11n) tx channel width */ - ni->ni_chw = IEEE80211_IS_CHAN_HT40(ni->ni_chan)? 40 : 20; + ni->ni_chw = IEEE80211_IS_CHAN_HT40(ni->ni_chan) ? + IEEE80211_STA_RX_BW_40 : IEEE80211_STA_RX_BW_20; return (ret); } /* * Update 11n MIMO PS state according to received htcap. */ static __inline int htcap_update_mimo_ps(struct ieee80211_node *ni) { uint16_t oflags = ni->ni_flags; switch (ni->ni_htcap & IEEE80211_HTCAP_SMPS) { case IEEE80211_HTCAP_SMPS_DYNAMIC: ni->ni_flags |= IEEE80211_NODE_MIMO_PS; ni->ni_flags |= IEEE80211_NODE_MIMO_RTS; break; case IEEE80211_HTCAP_SMPS_ENA: ni->ni_flags |= IEEE80211_NODE_MIMO_PS; ni->ni_flags &= ~IEEE80211_NODE_MIMO_RTS; break; case IEEE80211_HTCAP_SMPS_OFF: default: /* disable on rx of reserved value */ ni->ni_flags &= ~IEEE80211_NODE_MIMO_PS; ni->ni_flags &= ~IEEE80211_NODE_MIMO_RTS; break; } return (oflags ^ ni->ni_flags); } /* * Update short GI state according to received htcap * and local settings. */ static __inline void htcap_update_shortgi(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; ni->ni_flags &= ~(IEEE80211_NODE_SGI20|IEEE80211_NODE_SGI40); if ((ni->ni_htcap & IEEE80211_HTCAP_SHORTGI20) && (vap->iv_flags_ht & IEEE80211_FHT_SHORTGI20)) ni->ni_flags |= IEEE80211_NODE_SGI20; if ((ni->ni_htcap & IEEE80211_HTCAP_SHORTGI40) && (vap->iv_flags_ht & IEEE80211_FHT_SHORTGI40)) ni->ni_flags |= IEEE80211_NODE_SGI40; } /* * Update LDPC state according to received htcap * and local settings. */ static __inline void htcap_update_ldpc(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; if ((ni->ni_htcap & IEEE80211_HTCAP_LDPC) && (vap->iv_flags_ht & IEEE80211_FHT_LDPC_TX)) ni->ni_flags |= IEEE80211_NODE_LDPC; } /* * Parse and update HT-related state extracted from * the HT cap and info ie's. * * This is called from the STA management path and * the ieee80211_node_join() path. It will take into * account the IEs discovered during scanning and * adjust things accordingly. */ void ieee80211_ht_updateparams(struct ieee80211_node *ni, const uint8_t *htcapie, const uint8_t *htinfoie) { struct ieee80211vap *vap = ni->ni_vap; const struct ieee80211_ie_htinfo *htinfo; ieee80211_parse_htcap(ni, htcapie); if (vap->iv_htcaps & IEEE80211_HTC_SMPS) htcap_update_mimo_ps(ni); htcap_update_shortgi(ni); htcap_update_ldpc(ni); if (htinfoie[0] == IEEE80211_ELEMID_VENDOR) htinfoie += 4; htinfo = (const struct ieee80211_ie_htinfo *) htinfoie; htinfo_parse(ni, htinfo); /* * Defer the node channel change; we need to now * update VHT parameters before we do it. */ if ((htinfo->hi_byte1 & IEEE80211_HTINFO_RIFSMODE_PERM) && (vap->iv_flags_ht & IEEE80211_FHT_RIFS)) ni->ni_flags |= IEEE80211_NODE_RIFS; else ni->ni_flags &= ~IEEE80211_NODE_RIFS; } static uint32_t ieee80211_vht_get_vhtflags(struct ieee80211_node *ni, uint32_t htflags) { struct ieee80211vap *vap = ni->ni_vap; uint32_t vhtflags = 0; vhtflags = 0; if (ni->ni_flags & IEEE80211_NODE_VHT && vap->iv_vht_flags & IEEE80211_FVHT_VHT) { if ((ni->ni_vht_chanwidth == IEEE80211_VHT_CHANWIDTH_160MHZ) && /* XXX 2 means "160MHz and 80+80MHz", 1 means "160MHz" */ (_IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_MASK) >= 1) && (vap->iv_vht_flags & IEEE80211_FVHT_USEVHT160)) { vhtflags = IEEE80211_CHAN_VHT160; /* Mirror the HT40 flags */ if (htflags == IEEE80211_CHAN_HT40U) { vhtflags |= IEEE80211_CHAN_HT40U; } else if (htflags == IEEE80211_CHAN_HT40D) { vhtflags |= IEEE80211_CHAN_HT40D; } } else if ((ni->ni_vht_chanwidth == IEEE80211_VHT_CHANWIDTH_80P80MHZ) && /* XXX 2 means "160MHz and 80+80MHz" */ (_IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_MASK) == 2) && (vap->iv_vht_flags & IEEE80211_FVHT_USEVHT80P80)) { vhtflags = IEEE80211_CHAN_VHT80P80; /* Mirror the HT40 flags */ if (htflags == IEEE80211_CHAN_HT40U) { vhtflags |= IEEE80211_CHAN_HT40U; } else if (htflags == IEEE80211_CHAN_HT40D) { vhtflags |= IEEE80211_CHAN_HT40D; } } else if ((ni->ni_vht_chanwidth == IEEE80211_VHT_CHANWIDTH_80MHZ) && (vap->iv_vht_flags & IEEE80211_FVHT_USEVHT80)) { vhtflags = IEEE80211_CHAN_VHT80; /* Mirror the HT40 flags */ if (htflags == IEEE80211_CHAN_HT40U) { vhtflags |= IEEE80211_CHAN_HT40U; } else if (htflags == IEEE80211_CHAN_HT40D) { vhtflags |= IEEE80211_CHAN_HT40D; } } else if (ni->ni_vht_chanwidth == IEEE80211_VHT_CHANWIDTH_USE_HT) { /* Mirror the HT40 flags */ /* * XXX TODO: if ht40 is disabled, but vht40 isn't * disabled then this logic will get very, very sad. * It's quite possible the only sane thing to do is * to not have vht40 as an option, and just obey * 'ht40' as that flag. */ if ((htflags == IEEE80211_CHAN_HT40U) && (vap->iv_vht_flags & IEEE80211_FVHT_USEVHT40)) { vhtflags = IEEE80211_CHAN_VHT40U | IEEE80211_CHAN_HT40U; } else if (htflags == IEEE80211_CHAN_HT40D && (vap->iv_vht_flags & IEEE80211_FVHT_USEVHT40)) { vhtflags = IEEE80211_CHAN_VHT40D | IEEE80211_CHAN_HT40D; } else if (htflags == IEEE80211_CHAN_HT20) { vhtflags = IEEE80211_CHAN_VHT20 | IEEE80211_CHAN_HT20; } } else { vhtflags = IEEE80211_CHAN_VHT20; } } return (vhtflags); } /* * Final part of updating the HT parameters. * * This is called from the STA management path and * the ieee80211_node_join() path. It will take into * account the IEs discovered during scanning and * adjust things accordingly. * * This is done after a call to ieee80211_ht_updateparams() * because it (and the upcoming VHT version of updateparams) * needs to ensure everything is parsed before htinfo_update_chw() * is called - which will change the channel config for the * node for us. */ int ieee80211_ht_updateparams_final(struct ieee80211_node *ni, const uint8_t *htcapie, const uint8_t *htinfoie) { struct ieee80211vap *vap = ni->ni_vap; const struct ieee80211_ie_htinfo *htinfo; int htflags, vhtflags; int ret = 0; htinfo = (const struct ieee80211_ie_htinfo *) htinfoie; htflags = (vap->iv_flags_ht & IEEE80211_FHT_HT) ? IEEE80211_CHAN_HT20 : 0; /* NB: honor operating mode constraint */ if ((htinfo->hi_byte1 & IEEE80211_HTINFO_TXWIDTH_2040) && (vap->iv_flags_ht & IEEE80211_FHT_USEHT40)) { if (ni->ni_ht2ndchan == IEEE80211_HTINFO_2NDCHAN_ABOVE) htflags = IEEE80211_CHAN_HT40U; else if (ni->ni_ht2ndchan == IEEE80211_HTINFO_2NDCHAN_BELOW) htflags = IEEE80211_CHAN_HT40D; } /* * VHT flags - do much the same; check whether VHT is available * and if so, what our ideal channel use would be based on our * capabilities and the (pre-parsed) VHT info IE. */ vhtflags = ieee80211_vht_get_vhtflags(ni, htflags); if (htinfo_update_chw(ni, htflags, vhtflags)) ret = 1; return (ret); } /* * Parse and update HT-related state extracted from the HT cap ie * for a station joining an HT BSS. * * This is called from the hostap path for each station. */ void ieee80211_ht_updatehtcap(struct ieee80211_node *ni, const uint8_t *htcapie) { struct ieee80211vap *vap = ni->ni_vap; ieee80211_parse_htcap(ni, htcapie); if (vap->iv_htcaps & IEEE80211_HTC_SMPS) htcap_update_mimo_ps(ni); htcap_update_shortgi(ni); htcap_update_ldpc(ni); } /* * Called once HT and VHT capabilities are parsed in hostap mode - * this will adjust the channel configuration of the given node * based on the configuration and capabilities. */ void ieee80211_ht_updatehtcap_final(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; int htflags; int vhtflags; /* NB: honor operating mode constraint */ /* XXX 40 MHz intolerant */ htflags = (vap->iv_flags_ht & IEEE80211_FHT_HT) ? IEEE80211_CHAN_HT20 : 0; if ((ni->ni_htcap & IEEE80211_HTCAP_CHWIDTH40) && (vap->iv_flags_ht & IEEE80211_FHT_USEHT40)) { if (IEEE80211_IS_CHAN_HT40U(vap->iv_bss->ni_chan)) htflags = IEEE80211_CHAN_HT40U; else if (IEEE80211_IS_CHAN_HT40D(vap->iv_bss->ni_chan)) htflags = IEEE80211_CHAN_HT40D; } /* * VHT flags - do much the same; check whether VHT is available * and if so, what our ideal channel use would be based on our * capabilities and the (pre-parsed) VHT info IE. */ vhtflags = ieee80211_vht_get_vhtflags(ni, htflags); (void) htinfo_update_chw(ni, htflags, vhtflags); } /* * Install received HT rate set by parsing the HT cap ie. */ int ieee80211_setup_htrates(struct ieee80211_node *ni, const uint8_t *ie, int flags) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; const struct ieee80211_ie_htcap *htcap; struct ieee80211_htrateset *rs; int i, maxequalmcs, maxunequalmcs; maxequalmcs = ic->ic_txstream * 8 - 1; maxunequalmcs = 0; if (ic->ic_htcaps & IEEE80211_HTC_TXUNEQUAL) { if (ic->ic_txstream >= 2) maxunequalmcs = 38; if (ic->ic_txstream >= 3) maxunequalmcs = 52; if (ic->ic_txstream >= 4) maxunequalmcs = 76; } rs = &ni->ni_htrates; memset(rs, 0, sizeof(*rs)); if (ie != NULL) { if (ie[0] == IEEE80211_ELEMID_VENDOR) ie += 4; htcap = (const struct ieee80211_ie_htcap *) ie; for (i = 0; i < IEEE80211_HTRATE_MAXSIZE; i++) { if (isclr(htcap->hc_mcsset, i)) continue; if (rs->rs_nrates == IEEE80211_HTRATE_MAXSIZE) { IEEE80211_NOTE(vap, IEEE80211_MSG_XRATE | IEEE80211_MSG_11N, ni, "WARNING, HT rate set too large; only " "using %u rates", IEEE80211_HTRATE_MAXSIZE); vap->iv_stats.is_rx_rstoobig++; break; } if (i <= 31 && i > maxequalmcs) continue; if (i == 32 && (ic->ic_htcaps & IEEE80211_HTC_TXMCS32) == 0) continue; if (i > 32 && i > maxunequalmcs) continue; rs->rs_rates[rs->rs_nrates++] = i; } } return ieee80211_fix_rate(ni, (struct ieee80211_rateset *) rs, flags); } /* * Mark rates in a node's HT rate set as basic according * to the information in the supplied HT info ie. */ void ieee80211_setup_basic_htrates(struct ieee80211_node *ni, const uint8_t *ie) { const struct ieee80211_ie_htinfo *htinfo; struct ieee80211_htrateset *rs; int i, j; if (ie[0] == IEEE80211_ELEMID_VENDOR) ie += 4; htinfo = (const struct ieee80211_ie_htinfo *) ie; rs = &ni->ni_htrates; if (rs->rs_nrates == 0) { IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_XRATE | IEEE80211_MSG_11N, ni, "%s", "WARNING, empty HT rate set"); return; } for (i = 0; i < IEEE80211_HTRATE_MAXSIZE; i++) { if (isclr(htinfo->hi_basicmcsset, i)) continue; for (j = 0; j < rs->rs_nrates; j++) if ((rs->rs_rates[j] & IEEE80211_RATE_VAL) == i) rs->rs_rates[j] |= IEEE80211_RATE_BASIC; } } static void ampdu_tx_setup(struct ieee80211_tx_ampdu *tap) { callout_init(&tap->txa_timer, 1); tap->txa_flags |= IEEE80211_AGGR_SETUP; tap->txa_lastsample = ticks; } static void ampdu_tx_stop(struct ieee80211_tx_ampdu *tap) { struct ieee80211_node *ni = tap->txa_ni; struct ieee80211com *ic = ni->ni_ic; IEEE80211_NOTE(tap->txa_ni->ni_vap, IEEE80211_MSG_11N, tap->txa_ni, "%s: called", __func__); KASSERT(tap->txa_flags & IEEE80211_AGGR_SETUP, ("txa_flags 0x%x tid %d ac %d", tap->txa_flags, tap->txa_tid, TID_TO_WME_AC(tap->txa_tid))); /* * Stop BA stream if setup so driver has a chance * to reclaim any resources it might have allocated. */ ic->ic_addba_stop(ni, tap); /* * Stop any pending BAR transmit. */ bar_stop_timer(tap); /* * Reset packet estimate. */ ieee80211_txampdu_init_pps(tap); /* NB: clearing NAK means we may re-send ADDBA */ tap->txa_flags &= ~(IEEE80211_AGGR_SETUP | IEEE80211_AGGR_NAK); } /* * ADDBA response timeout. * * If software aggregation and per-TID queue management was done here, * that queue would be unpaused after the ADDBA timeout occurs. */ static void addba_timeout(void *arg) { struct ieee80211_tx_ampdu *tap = arg; struct ieee80211_node *ni = tap->txa_ni; struct ieee80211com *ic = ni->ni_ic; /* XXX ? */ tap->txa_flags &= ~IEEE80211_AGGR_XCHGPEND; tap->txa_attempts++; ic->ic_addba_response_timeout(ni, tap); } static void addba_start_timeout(struct ieee80211_tx_ampdu *tap) { /* XXX use CALLOUT_PENDING instead? */ callout_reset(&tap->txa_timer, ieee80211_addba_timeout, addba_timeout, tap); tap->txa_flags |= IEEE80211_AGGR_XCHGPEND; tap->txa_nextrequest = ticks + ieee80211_addba_timeout; } static void addba_stop_timeout(struct ieee80211_tx_ampdu *tap) { /* XXX use CALLOUT_PENDING instead? */ if (tap->txa_flags & IEEE80211_AGGR_XCHGPEND) { callout_stop(&tap->txa_timer); tap->txa_flags &= ~IEEE80211_AGGR_XCHGPEND; } } static void null_addba_response_timeout(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap) { } /* * Default method for requesting A-MPDU tx aggregation. * We setup the specified state block and start a timer * to wait for an ADDBA response frame. */ static int ieee80211_addba_request(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap, int dialogtoken, int baparamset, int batimeout) { int bufsiz; /* XXX locking */ tap->txa_token = dialogtoken; tap->txa_flags |= IEEE80211_AGGR_IMMEDIATE; bufsiz = _IEEE80211_MASKSHIFT(baparamset, IEEE80211_BAPS_BUFSIZ); tap->txa_wnd = (bufsiz == 0) ? IEEE80211_AGGR_BAWMAX : min(bufsiz, IEEE80211_AGGR_BAWMAX); addba_start_timeout(tap); return 1; } /* * Called by drivers that wish to request an ADDBA session be * setup. This brings it up and starts the request timer. */ int ieee80211_ampdu_tx_request_ext(struct ieee80211_node *ni, int tid) { struct ieee80211_tx_ampdu *tap; if (tid < 0 || tid > 15) return (0); tap = &ni->ni_tx_ampdu[tid]; /* XXX locking */ if ((tap->txa_flags & IEEE80211_AGGR_SETUP) == 0) { /* do deferred setup of state */ ampdu_tx_setup(tap); } /* XXX hack for not doing proper locking */ tap->txa_flags &= ~IEEE80211_AGGR_NAK; addba_start_timeout(tap); return (1); } /* * Called by drivers that have marked a session as active. */ int ieee80211_ampdu_tx_request_active_ext(struct ieee80211_node *ni, int tid, int status) { struct ieee80211_tx_ampdu *tap; if (tid < 0 || tid > 15) return (0); tap = &ni->ni_tx_ampdu[tid]; /* XXX locking */ addba_stop_timeout(tap); if (status == 1) { tap->txa_flags |= IEEE80211_AGGR_RUNNING; tap->txa_attempts = 0; } else { /* mark tid so we don't try again */ tap->txa_flags |= IEEE80211_AGGR_NAK; } return (1); } /* * Default method for processing an A-MPDU tx aggregation * response. We shutdown any pending timer and update the * state block according to the reply. */ static int ieee80211_addba_response(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap, int status, int baparamset, int batimeout) { struct ieee80211vap *vap = ni->ni_vap; int bufsiz; /* XXX locking */ addba_stop_timeout(tap); if (status == IEEE80211_STATUS_SUCCESS) { bufsiz = _IEEE80211_MASKSHIFT(baparamset, IEEE80211_BAPS_BUFSIZ); /* XXX override our request? */ tap->txa_wnd = (bufsiz == 0) ? IEEE80211_AGGR_BAWMAX : min(bufsiz, IEEE80211_AGGR_BAWMAX); #ifdef __notyet__ tid = _IEEE80211_MASKSHIFT(baparamset, IEEE80211_BAPS_TID); #endif tap->txa_flags |= IEEE80211_AGGR_RUNNING; tap->txa_attempts = 0; /* TODO: this should be a vap flag */ if ((vap->iv_htcaps & IEEE80211_HTC_TX_AMSDU_AMPDU) && (ni->ni_flags & IEEE80211_NODE_AMSDU_TX) && (_IEEE80211_MASKSHIFT(baparamset, IEEE80211_BAPS_AMSDU))) tap->txa_flags |= IEEE80211_AGGR_AMSDU; else tap->txa_flags &= ~IEEE80211_AGGR_AMSDU; } else { /* mark tid so we don't try again */ tap->txa_flags |= IEEE80211_AGGR_NAK; } return 1; } /* * Default method for stopping A-MPDU tx aggregation. * Any timer is cleared and we drain any pending frames. */ static void ieee80211_addba_stop(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap) { /* XXX locking */ addba_stop_timeout(tap); if (tap->txa_flags & IEEE80211_AGGR_RUNNING) { /* XXX clear aggregation queue */ tap->txa_flags &= ~(IEEE80211_AGGR_RUNNING | IEEE80211_AGGR_AMSDU); } tap->txa_attempts = 0; } /* * Process a received action frame using the default aggregation * policy. We intercept ADDBA-related frames and use them to * update our aggregation state. All other frames are passed up * for processing by ieee80211_recv_action. */ static int ht_recv_action_ba_addba_request(struct ieee80211_node *ni, const struct ieee80211_frame *wh, const uint8_t *frm, const uint8_t *efrm) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_rx_ampdu *rap; uint8_t dialogtoken; uint16_t baparamset, batimeout, baseqctl; uint16_t args[5]; int tid; dialogtoken = frm[2]; baparamset = le16dec(frm+3); batimeout = le16dec(frm+5); baseqctl = le16dec(frm+7); tid = _IEEE80211_MASKSHIFT(baparamset, IEEE80211_BAPS_TID); IEEE80211_NOTE(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, "recv ADDBA request: dialogtoken %u baparamset 0x%x " "(tid %d bufsiz %d) batimeout %d baseqctl %d:%d amsdu %d", dialogtoken, baparamset, tid, _IEEE80211_MASKSHIFT(baparamset, IEEE80211_BAPS_BUFSIZ), batimeout, _IEEE80211_MASKSHIFT(baseqctl, IEEE80211_BASEQ_START), _IEEE80211_MASKSHIFT(baseqctl, IEEE80211_BASEQ_FRAG), _IEEE80211_MASKSHIFT(baparamset, IEEE80211_BAPS_AMSDU)); rap = &ni->ni_rx_ampdu[tid]; /* Send ADDBA response */ args[0] = dialogtoken; /* * NB: We ack only if the sta associated with HT and * the ap is configured to do AMPDU rx (the latter * violates the 11n spec and is mostly for testing). */ if ((ni->ni_flags & IEEE80211_NODE_AMPDU_RX) && (vap->iv_flags_ht & IEEE80211_FHT_AMPDU_RX)) { /* XXX TODO: handle ampdu_rx_start failure */ ic->ic_ampdu_rx_start(ni, rap, baparamset, batimeout, baseqctl); args[1] = IEEE80211_STATUS_SUCCESS; } else { IEEE80211_NOTE(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, "reject ADDBA request: %s", ni->ni_flags & IEEE80211_NODE_AMPDU_RX ? "administratively disabled" : "not negotiated for station"); vap->iv_stats.is_addba_reject++; args[1] = IEEE80211_STATUS_UNSPECIFIED; } /* XXX honor rap flags? */ args[2] = IEEE80211_BAPS_POLICY_IMMEDIATE | _IEEE80211_SHIFTMASK(tid, IEEE80211_BAPS_TID) | _IEEE80211_SHIFTMASK(rap->rxa_wnd, IEEE80211_BAPS_BUFSIZ) ; /* * TODO: we're out of iv_flags_ht fields; once * this is extended we should make this configurable. */ if ((baparamset & IEEE80211_BAPS_AMSDU) && (ni->ni_flags & IEEE80211_NODE_AMSDU_RX) && (vap->iv_htcaps & IEEE80211_HTC_RX_AMSDU_AMPDU)) args[2] |= IEEE80211_BAPS_AMSDU; args[3] = 0; args[4] = 0; ic->ic_send_action(ni, IEEE80211_ACTION_CAT_BA, IEEE80211_ACTION_BA_ADDBA_RESPONSE, args); return 0; } static int ht_recv_action_ba_addba_response(struct ieee80211_node *ni, const struct ieee80211_frame *wh, const uint8_t *frm, const uint8_t *efrm) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_tx_ampdu *tap; uint8_t dialogtoken, policy; uint16_t baparamset, batimeout, code; int tid; #ifdef IEEE80211_DEBUG int amsdu, bufsiz; #endif dialogtoken = frm[2]; code = le16dec(frm+3); baparamset = le16dec(frm+5); tid = _IEEE80211_MASKSHIFT(baparamset, IEEE80211_BAPS_TID); #ifdef IEEE80211_DEBUG bufsiz = _IEEE80211_MASKSHIFT(baparamset, IEEE80211_BAPS_BUFSIZ); amsdu = !! _IEEE80211_MASKSHIFT(baparamset, IEEE80211_BAPS_AMSDU); #endif policy = _IEEE80211_MASKSHIFT(baparamset, IEEE80211_BAPS_POLICY); batimeout = le16dec(frm+7); tap = &ni->ni_tx_ampdu[tid]; if ((tap->txa_flags & IEEE80211_AGGR_XCHGPEND) == 0) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni->ni_macaddr, "ADDBA response", "no pending ADDBA, tid %d dialogtoken %u " "code %d", tid, dialogtoken, code); vap->iv_stats.is_addba_norequest++; return 0; } if (dialogtoken != tap->txa_token) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni->ni_macaddr, "ADDBA response", "dialogtoken mismatch: waiting for %d, " "received %d, tid %d code %d", tap->txa_token, dialogtoken, tid, code); vap->iv_stats.is_addba_badtoken++; return 0; } /* NB: assumes IEEE80211_AGGR_IMMEDIATE is 1 */ if (policy != (tap->txa_flags & IEEE80211_AGGR_IMMEDIATE)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni->ni_macaddr, "ADDBA response", "policy mismatch: expecting %s, " "received %s, tid %d code %d", tap->txa_flags & IEEE80211_AGGR_IMMEDIATE, policy, tid, code); vap->iv_stats.is_addba_badpolicy++; return 0; } #if 0 /* XXX we take MIN in ieee80211_addba_response */ if (bufsiz > IEEE80211_AGGR_BAWMAX) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni->ni_macaddr, "ADDBA response", "BA window too large: max %d, " "received %d, tid %d code %d", bufsiz, IEEE80211_AGGR_BAWMAX, tid, code); vap->iv_stats.is_addba_badbawinsize++; return 0; } #endif IEEE80211_NOTE(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, "recv ADDBA response: dialogtoken %u code %d " "baparamset 0x%x (tid %d bufsiz %d amsdu %d) batimeout %d", dialogtoken, code, baparamset, tid, bufsiz, amsdu, batimeout); ic->ic_addba_response(ni, tap, code, baparamset, batimeout); return 0; } static int ht_recv_action_ba_delba(struct ieee80211_node *ni, const struct ieee80211_frame *wh, const uint8_t *frm, const uint8_t *efrm) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211_rx_ampdu *rap; struct ieee80211_tx_ampdu *tap; uint16_t baparamset; #ifdef IEEE80211_DEBUG uint16_t code; #endif int tid; baparamset = le16dec(frm+2); #ifdef IEEE80211_DEBUG code = le16dec(frm+4); #endif tid = _IEEE80211_MASKSHIFT(baparamset, IEEE80211_DELBAPS_TID); IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, "recv DELBA: baparamset 0x%x (tid %d initiator %d) " "code %d", baparamset, tid, _IEEE80211_MASKSHIFT(baparamset, IEEE80211_DELBAPS_INIT), code); if ((baparamset & IEEE80211_DELBAPS_INIT) == 0) { tap = &ni->ni_tx_ampdu[tid]; ic->ic_addba_stop(ni, tap); } else { rap = &ni->ni_rx_ampdu[tid]; ic->ic_ampdu_rx_stop(ni, rap); } return 0; } static int ht_recv_action_ht_txchwidth(struct ieee80211_node *ni, const struct ieee80211_frame *wh, const uint8_t *frm, const uint8_t *efrm) { int chw; - chw = (frm[2] == IEEE80211_A_HT_TXCHWIDTH_2040) ? 40 : 20; + chw = (frm[2] == IEEE80211_A_HT_TXCHWIDTH_2040) ? + IEEE80211_STA_RX_BW_40 : IEEE80211_STA_RX_BW_20; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, - "%s: HT txchwidth, width %d%s", - __func__, chw, ni->ni_chw != chw ? "*" : ""); + "%s: HT txchwidth, width %b%s", + __func__, chw, IEEE80211_NI_CHW_BITS, ni->ni_chw != chw ? "*" : ""); if (chw != ni->ni_chw) { /* XXX does this need to change the ht40 station count? */ ni->ni_chw = chw; /* XXX notify on change */ } return 0; } static int ht_recv_action_ht_mimopwrsave(struct ieee80211_node *ni, const struct ieee80211_frame *wh, const uint8_t *frm, const uint8_t *efrm) { const struct ieee80211_action_ht_mimopowersave *mps = (const struct ieee80211_action_ht_mimopowersave *) frm; /* XXX check iv_htcaps */ if (mps->am_control & IEEE80211_A_HT_MIMOPWRSAVE_ENA) ni->ni_flags |= IEEE80211_NODE_MIMO_PS; else ni->ni_flags &= ~IEEE80211_NODE_MIMO_PS; if (mps->am_control & IEEE80211_A_HT_MIMOPWRSAVE_MODE) ni->ni_flags |= IEEE80211_NODE_MIMO_RTS; else ni->ni_flags &= ~IEEE80211_NODE_MIMO_RTS; /* XXX notify on change */ IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, "%s: HT MIMO PS (%s%s)", __func__, (ni->ni_flags & IEEE80211_NODE_MIMO_PS) ? "on" : "off", (ni->ni_flags & IEEE80211_NODE_MIMO_RTS) ? "+rts" : "" ); return 0; } /* * Transmit processing. */ /* * Check if A-MPDU should be requested/enabled for a stream. * We require a traffic rate above a per-AC threshold and we * also handle backoff from previous failed attempts. * * Drivers may override this method to bring in information * such as link state conditions in making the decision. */ static int ieee80211_ampdu_enable(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap) { struct ieee80211vap *vap = ni->ni_vap; if (tap->txa_avgpps < vap->iv_ampdu_mintraffic[TID_TO_WME_AC(tap->txa_tid)]) return 0; /* XXX check rssi? */ if (tap->txa_attempts >= ieee80211_addba_maxtries && ieee80211_time_after(ticks, tap->txa_nextrequest)) { /* * Don't retry too often; txa_nextrequest is set * to the minimum interval we'll retry after * ieee80211_addba_maxtries failed attempts are made. */ return 0; } IEEE80211_NOTE(vap, IEEE80211_MSG_11N, ni, "enable AMPDU on tid %d (%s), avgpps %d pkts %d attempt %d", tap->txa_tid, ieee80211_wme_acnames[TID_TO_WME_AC(tap->txa_tid)], tap->txa_avgpps, tap->txa_pkts, tap->txa_attempts); return 1; } /* * Request A-MPDU tx aggregation. Setup local state and * issue an ADDBA request. BA use will only happen after * the other end replies with ADDBA response. */ int ieee80211_ampdu_request(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap) { struct ieee80211com *ic = ni->ni_ic; uint16_t args[5]; int tid, dialogtoken; static int tokens = 0; /* XXX */ /* XXX locking */ if ((tap->txa_flags & IEEE80211_AGGR_SETUP) == 0) { /* do deferred setup of state */ ampdu_tx_setup(tap); } /* XXX hack for not doing proper locking */ tap->txa_flags &= ~IEEE80211_AGGR_NAK; dialogtoken = (tokens+1) % 63; /* XXX */ tid = tap->txa_tid; /* * XXX TODO: This is racy with any other parallel TX going on. :( */ tap->txa_start = ni->ni_txseqs[tid]; args[0] = dialogtoken; args[1] = 0; /* NB: status code not used */ args[2] = IEEE80211_BAPS_POLICY_IMMEDIATE | _IEEE80211_SHIFTMASK(tid, IEEE80211_BAPS_TID) | _IEEE80211_SHIFTMASK(IEEE80211_AGGR_BAWMAX, IEEE80211_BAPS_BUFSIZ) ; /* XXX TODO: this should be a flag, not iv_htcaps */ if ((ni->ni_flags & IEEE80211_NODE_AMSDU_TX) && (ni->ni_vap->iv_htcaps & IEEE80211_HTC_TX_AMSDU_AMPDU)) args[2] |= IEEE80211_BAPS_AMSDU; args[3] = 0; /* batimeout */ /* NB: do first so there's no race against reply */ if (!ic->ic_addba_request(ni, tap, dialogtoken, args[2], args[3])) { /* unable to setup state, don't make request */ IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: could not setup BA stream for TID %d AC %d", __func__, tap->txa_tid, TID_TO_WME_AC(tap->txa_tid)); /* defer next try so we don't slam the driver with requests */ tap->txa_attempts = ieee80211_addba_maxtries; /* NB: check in case driver wants to override */ if (tap->txa_nextrequest <= ticks) tap->txa_nextrequest = ticks + ieee80211_addba_backoff; return 0; } tokens = dialogtoken; /* allocate token */ /* NB: after calling ic_addba_request so driver can set txa_start */ args[4] = _IEEE80211_SHIFTMASK(tap->txa_start, IEEE80211_BASEQ_START) | _IEEE80211_SHIFTMASK(0, IEEE80211_BASEQ_FRAG) ; return ic->ic_send_action(ni, IEEE80211_ACTION_CAT_BA, IEEE80211_ACTION_BA_ADDBA_REQUEST, args); } /* * Terminate an AMPDU tx stream. State is reclaimed * and the peer notified with a DelBA Action frame. */ void ieee80211_ampdu_stop(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap, int reason) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; uint16_t args[4]; /* XXX locking */ tap->txa_flags &= ~IEEE80211_AGGR_BARPEND; if (IEEE80211_AMPDU_RUNNING(tap)) { IEEE80211_NOTE(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, "%s: stop BA stream for TID %d (reason: %d (%s))", __func__, tap->txa_tid, reason, ieee80211_reason_to_string(reason)); vap->iv_stats.is_ampdu_stop++; ic->ic_addba_stop(ni, tap); args[0] = tap->txa_tid; args[1] = IEEE80211_DELBAPS_INIT; args[2] = reason; /* XXX reason code */ ic->ic_send_action(ni, IEEE80211_ACTION_CAT_BA, IEEE80211_ACTION_BA_DELBA, args); } else { IEEE80211_NOTE(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, "%s: BA stream for TID %d not running " "(reason: %d (%s))", __func__, tap->txa_tid, reason, ieee80211_reason_to_string(reason)); vap->iv_stats.is_ampdu_stop_failed++; } } /* XXX */ static void bar_start_timer(struct ieee80211_tx_ampdu *tap); static void bar_timeout(void *arg) { struct ieee80211_tx_ampdu *tap = arg; struct ieee80211_node *ni = tap->txa_ni; KASSERT((tap->txa_flags & IEEE80211_AGGR_XCHGPEND) == 0, ("bar/addba collision, flags 0x%x", tap->txa_flags)); IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: tid %u flags 0x%x attempts %d", __func__, tap->txa_tid, tap->txa_flags, tap->txa_attempts); /* guard against race with bar_tx_complete */ if ((tap->txa_flags & IEEE80211_AGGR_BARPEND) == 0) return; /* XXX ? */ if (tap->txa_attempts >= ieee80211_bar_maxtries) { struct ieee80211com *ic = ni->ni_ic; ni->ni_vap->iv_stats.is_ampdu_bar_tx_fail++; /* * If (at least) the last BAR TX timeout was due to * an ieee80211_send_bar() failures, then we need * to make sure we notify the driver that a BAR * TX did occur and fail. This gives the driver * a chance to undo any queue pause that may * have occurred. */ ic->ic_bar_response(ni, tap, 1); ieee80211_ampdu_stop(ni, tap, IEEE80211_REASON_TIMEOUT); } else { ni->ni_vap->iv_stats.is_ampdu_bar_tx_retry++; if (ieee80211_send_bar(ni, tap, tap->txa_seqpending) != 0) { IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: failed to TX, starting timer\n", __func__); /* * If ieee80211_send_bar() fails here, the * timer may have stopped and/or the pending * flag may be clear. Because of this, * fake the BARPEND and reset the timer. * A retransmission attempt will then occur * during the next timeout. */ /* XXX locking */ tap->txa_flags |= IEEE80211_AGGR_BARPEND; bar_start_timer(tap); } } } static void bar_start_timer(struct ieee80211_tx_ampdu *tap) { IEEE80211_NOTE(tap->txa_ni->ni_vap, IEEE80211_MSG_11N, tap->txa_ni, "%s: called", __func__); callout_reset(&tap->txa_timer, ieee80211_bar_timeout, bar_timeout, tap); } static void bar_stop_timer(struct ieee80211_tx_ampdu *tap) { IEEE80211_NOTE(tap->txa_ni->ni_vap, IEEE80211_MSG_11N, tap->txa_ni, "%s: called", __func__); callout_stop(&tap->txa_timer); } static void bar_tx_complete(struct ieee80211_node *ni, void *arg, int status) { struct ieee80211_tx_ampdu *tap = arg; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: tid %u flags 0x%x pending %d status %d", __func__, tap->txa_tid, tap->txa_flags, callout_pending(&tap->txa_timer), status); ni->ni_vap->iv_stats.is_ampdu_bar_tx++; /* XXX locking */ if ((tap->txa_flags & IEEE80211_AGGR_BARPEND) && callout_pending(&tap->txa_timer)) { struct ieee80211com *ic = ni->ni_ic; if (status == 0) /* ACK'd */ bar_stop_timer(tap); ic->ic_bar_response(ni, tap, status); /* NB: just let timer expire so we pace requests */ } } static void ieee80211_bar_response(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap, int status) { IEEE80211_NOTE(tap->txa_ni->ni_vap, IEEE80211_MSG_11N, tap->txa_ni, "%s: called", __func__); if (status == 0) { /* got ACK */ IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "BAR moves BA win <%u:%u> (%u frames) txseq %u tid %u", tap->txa_start, IEEE80211_SEQ_ADD(tap->txa_start, tap->txa_wnd-1), tap->txa_qframes, tap->txa_seqpending, tap->txa_tid); /* NB: timer already stopped in bar_tx_complete */ tap->txa_start = tap->txa_seqpending; tap->txa_flags &= ~IEEE80211_AGGR_BARPEND; } } /* * Transmit a BAR frame to the specified node. The * BAR contents are drawn from the supplied aggregation * state associated with the node. * * NB: we only handle immediate ACK w/ compressed bitmap. */ int ieee80211_send_bar(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap, ieee80211_seq seq) { #define senderr(_x, _v) do { vap->iv_stats._v++; ret = _x; goto bad; } while (0) struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_frame_bar *bar; struct mbuf *m; uint16_t barctl, barseqctl; uint8_t *frm; int tid, ret; IEEE80211_NOTE(tap->txa_ni->ni_vap, IEEE80211_MSG_11N, tap->txa_ni, "%s: called", __func__); if ((tap->txa_flags & IEEE80211_AGGR_RUNNING) == 0) { /* no ADDBA response, should not happen */ /* XXX stat+msg */ return EINVAL; } /* XXX locking */ bar_stop_timer(tap); ieee80211_ref_node(ni); m = ieee80211_getmgtframe(&frm, ic->ic_headroom, sizeof(*bar)); if (m == NULL) senderr(ENOMEM, is_tx_nobuf); if (!ieee80211_add_callback(m, bar_tx_complete, tap)) { m_freem(m); senderr(ENOMEM, is_tx_nobuf); /* XXX */ /* NOTREACHED */ } bar = mtod(m, struct ieee80211_frame_bar *); bar->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_CTL | IEEE80211_FC0_SUBTYPE_BAR; bar->i_fc[1] = 0; IEEE80211_ADDR_COPY(bar->i_ra, ni->ni_macaddr); IEEE80211_ADDR_COPY(bar->i_ta, vap->iv_myaddr); tid = tap->txa_tid; barctl = (tap->txa_flags & IEEE80211_AGGR_IMMEDIATE ? 0 : IEEE80211_BAR_NOACK) | IEEE80211_BAR_COMP | _IEEE80211_SHIFTMASK(tid, IEEE80211_BAR_TID) ; barseqctl = _IEEE80211_SHIFTMASK(seq, IEEE80211_BAR_SEQ_START); /* NB: known to have proper alignment */ bar->i_ctl = htole16(barctl); bar->i_seq = htole16(barseqctl); m->m_pkthdr.len = m->m_len = sizeof(struct ieee80211_frame_bar); M_WME_SETAC(m, WME_AC_VO); IEEE80211_NODE_STAT(ni, tx_mgmt); /* XXX tx_ctl? */ /* XXX locking */ /* init/bump attempts counter */ if ((tap->txa_flags & IEEE80211_AGGR_BARPEND) == 0) tap->txa_attempts = 1; else tap->txa_attempts++; tap->txa_seqpending = seq; tap->txa_flags |= IEEE80211_AGGR_BARPEND; IEEE80211_NOTE(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_11N, ni, "send BAR: tid %u ctl 0x%x start %u (attempt %d)", tid, barctl, seq, tap->txa_attempts); /* * ic_raw_xmit will free the node reference * regardless of queue/TX success or failure. */ IEEE80211_TX_LOCK(ic); ret = ieee80211_raw_output(vap, ni, m, NULL); IEEE80211_TX_UNLOCK(ic); if (ret != 0) { IEEE80211_NOTE(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_11N, ni, "send BAR: failed: (ret = %d)\n", ret); /* xmit failed, clear state flag */ tap->txa_flags &= ~IEEE80211_AGGR_BARPEND; vap->iv_stats.is_ampdu_bar_tx_fail++; return ret; } /* XXX hack against tx complete happening before timer is started */ if (tap->txa_flags & IEEE80211_AGGR_BARPEND) bar_start_timer(tap); return 0; bad: IEEE80211_NOTE(tap->txa_ni->ni_vap, IEEE80211_MSG_11N, tap->txa_ni, "%s: bad! ret=%d", __func__, ret); vap->iv_stats.is_ampdu_bar_tx_fail++; ieee80211_free_node(ni); return ret; #undef senderr } static int ht_action_output(struct ieee80211_node *ni, struct mbuf *m) { struct ieee80211_bpf_params params; memset(¶ms, 0, sizeof(params)); params.ibp_pri = WME_AC_VO; params.ibp_rate0 = ni->ni_txparms->mgmtrate; /* NB: we know all frames are unicast */ params.ibp_try0 = ni->ni_txparms->maxretry; params.ibp_power = ni->ni_txpower; return ieee80211_mgmt_output(ni, m, IEEE80211_FC0_SUBTYPE_ACTION, ¶ms); } #define ADDSHORT(frm, v) do { \ frm[0] = (v) & 0xff; \ frm[1] = (v) >> 8; \ frm += 2; \ } while (0) /* * Send an action management frame. The arguments are stuff * into a frame without inspection; the caller is assumed to * prepare them carefully (e.g. based on the aggregation state). */ static int ht_send_action_ba_addba(struct ieee80211_node *ni, int category, int action, void *arg0) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; uint16_t *args = arg0; struct mbuf *m; uint8_t *frm; IEEE80211_NOTE(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, "send ADDBA %s: dialogtoken %d status %d " "baparamset 0x%x (tid %d amsdu %d) batimeout 0x%x baseqctl 0x%x", (action == IEEE80211_ACTION_BA_ADDBA_REQUEST) ? "request" : "response", args[0], args[1], args[2], _IEEE80211_MASKSHIFT(args[2], IEEE80211_BAPS_TID), _IEEE80211_MASKSHIFT(args[2], IEEE80211_BAPS_AMSDU), args[3], args[4]); IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", __func__, __LINE__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); ieee80211_ref_node(ni); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(uint16_t) /* action+category */ /* XXX may action payload */ + sizeof(struct ieee80211_action_ba_addbaresponse) ); if (m != NULL) { *frm++ = category; *frm++ = action; *frm++ = args[0]; /* dialog token */ if (action == IEEE80211_ACTION_BA_ADDBA_RESPONSE) ADDSHORT(frm, args[1]); /* status code */ ADDSHORT(frm, args[2]); /* baparamset */ ADDSHORT(frm, args[3]); /* batimeout */ if (action == IEEE80211_ACTION_BA_ADDBA_REQUEST) ADDSHORT(frm, args[4]); /* baseqctl */ m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); return ht_action_output(ni, m); } else { vap->iv_stats.is_tx_nobuf++; ieee80211_free_node(ni); return ENOMEM; } } static int ht_send_action_ba_delba(struct ieee80211_node *ni, int category, int action, void *arg0) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; uint16_t *args = arg0; struct mbuf *m; uint16_t baparamset; uint8_t *frm; baparamset = _IEEE80211_SHIFTMASK(args[0], IEEE80211_DELBAPS_TID) | args[1] ; IEEE80211_NOTE(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, "send DELBA action: tid %d, initiator %d reason %d (%s)", args[0], args[1], args[2], ieee80211_reason_to_string(args[2])); IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", __func__, __LINE__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); ieee80211_ref_node(ni); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(uint16_t) /* action+category */ /* XXX may action payload */ + sizeof(struct ieee80211_action_ba_addbaresponse) ); if (m != NULL) { *frm++ = category; *frm++ = action; ADDSHORT(frm, baparamset); ADDSHORT(frm, args[2]); /* reason code */ m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); return ht_action_output(ni, m); } else { vap->iv_stats.is_tx_nobuf++; ieee80211_free_node(ni); return ENOMEM; } } static int ht_send_action_ht_txchwidth(struct ieee80211_node *ni, int category, int action, void *arg0) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct mbuf *m; uint8_t *frm; IEEE80211_NOTE(vap, IEEE80211_MSG_ACTION | IEEE80211_MSG_11N, ni, "send HT txchwidth: width %d", IEEE80211_IS_CHAN_HT40(ni->ni_chan) ? 40 : 20); IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", __func__, __LINE__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); ieee80211_ref_node(ni); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(uint16_t) /* action+category */ /* XXX may action payload */ + sizeof(struct ieee80211_action_ba_addbaresponse) ); if (m != NULL) { *frm++ = category; *frm++ = action; *frm++ = IEEE80211_IS_CHAN_HT40(ni->ni_chan) ? IEEE80211_A_HT_TXCHWIDTH_2040 : IEEE80211_A_HT_TXCHWIDTH_20; m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); return ht_action_output(ni, m); } else { vap->iv_stats.is_tx_nobuf++; ieee80211_free_node(ni); return ENOMEM; } } #undef ADDSHORT /* * Construct the MCS bit mask for inclusion in an HT capabilities * information element. */ static void ieee80211_set_mcsset(struct ieee80211com *ic, uint8_t *frm) { int i; uint8_t txparams; KASSERT((ic->ic_rxstream > 0 && ic->ic_rxstream <= 4), ("ic_rxstream %d out of range", ic->ic_rxstream)); KASSERT((ic->ic_txstream > 0 && ic->ic_txstream <= 4), ("ic_txstream %d out of range", ic->ic_txstream)); for (i = 0; i < ic->ic_rxstream * 8; i++) setbit(frm, i); if ((ic->ic_htcaps & IEEE80211_HTCAP_CHWIDTH40) && (ic->ic_htcaps & IEEE80211_HTC_RXMCS32)) setbit(frm, 32); if (ic->ic_htcaps & IEEE80211_HTC_RXUNEQUAL) { if (ic->ic_rxstream >= 2) { for (i = 33; i <= 38; i++) setbit(frm, i); } if (ic->ic_rxstream >= 3) { for (i = 39; i <= 52; i++) setbit(frm, i); } if (ic->ic_rxstream >= 4) { for (i = 53; i <= 76; i++) setbit(frm, i); } } txparams = 0x1; /* TX MCS set defined */ if (ic->ic_rxstream != ic->ic_txstream) { txparams |= 0x2; /* TX RX MCS not equal */ txparams |= (ic->ic_txstream - 1) << 2; /* num TX streams */ if (ic->ic_htcaps & IEEE80211_HTC_TXUNEQUAL) txparams |= 0x16; /* TX unequal modulation sup */ } frm[12] = txparams; } /* * Add body of an HTCAP information element. */ static uint8_t * ieee80211_add_htcap_body(uint8_t *frm, struct ieee80211_node *ni) { #define ADDSHORT(frm, v) do { \ frm[0] = (v) & 0xff; \ frm[1] = (v) >> 8; \ frm += 2; \ } while (0) struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; uint16_t caps, extcaps; int rxmax, density; /* HT capabilities */ caps = vap->iv_htcaps & 0xffff; /* * Note channel width depends on whether we are operating as * a sta or not. When operating as a sta we are generating * a request based on our desired configuration. Otherwise * we are operational and the channel attributes identify * how we've been setup (which might be different if a fixed * channel is specified). */ if (vap->iv_opmode == IEEE80211_M_STA) { /* override 20/40 use based on config */ if (vap->iv_flags_ht & IEEE80211_FHT_USEHT40) caps |= IEEE80211_HTCAP_CHWIDTH40; else caps &= ~IEEE80211_HTCAP_CHWIDTH40; /* Start by using the advertised settings */ rxmax = _IEEE80211_MASKSHIFT(ni->ni_htparam, IEEE80211_HTCAP_MAXRXAMPDU); density = _IEEE80211_MASKSHIFT(ni->ni_htparam, IEEE80211_HTCAP_MPDUDENSITY); IEEE80211_DPRINTF(vap, IEEE80211_MSG_11N, "%s: advertised rxmax=%d, density=%d, vap rxmax=%d, density=%d\n", __func__, rxmax, density, vap->iv_ampdu_rxmax, vap->iv_ampdu_density); /* Cap at VAP rxmax */ if (rxmax > vap->iv_ampdu_rxmax) rxmax = vap->iv_ampdu_rxmax; /* * If the VAP ampdu density value greater, use that. * * (Larger density value == larger minimum gap between A-MPDU * subframes.) */ if (vap->iv_ampdu_density > density) density = vap->iv_ampdu_density; /* * NB: Hardware might support HT40 on some but not all * channels. We can't determine this earlier because only * after association the channel is upgraded to HT based * on the negotiated capabilities. */ if (ni->ni_chan != IEEE80211_CHAN_ANYC && findhtchan(ic, ni->ni_chan, IEEE80211_CHAN_HT40U) == NULL && findhtchan(ic, ni->ni_chan, IEEE80211_CHAN_HT40D) == NULL) caps &= ~IEEE80211_HTCAP_CHWIDTH40; } else { /* override 20/40 use based on current channel */ if (IEEE80211_IS_CHAN_HT40(ni->ni_chan)) caps |= IEEE80211_HTCAP_CHWIDTH40; else caps &= ~IEEE80211_HTCAP_CHWIDTH40; /* XXX TODO should it start by using advertised settings? */ rxmax = vap->iv_ampdu_rxmax; density = vap->iv_ampdu_density; } /* adjust short GI based on channel and config */ if ((vap->iv_flags_ht & IEEE80211_FHT_SHORTGI20) == 0) caps &= ~IEEE80211_HTCAP_SHORTGI20; if ((vap->iv_flags_ht & IEEE80211_FHT_SHORTGI40) == 0 || (caps & IEEE80211_HTCAP_CHWIDTH40) == 0) caps &= ~IEEE80211_HTCAP_SHORTGI40; /* adjust STBC based on receive capabilities */ if ((vap->iv_flags_ht & IEEE80211_FHT_STBC_RX) == 0) caps &= ~IEEE80211_HTCAP_RXSTBC; /* adjust LDPC based on receive capabilites */ if ((vap->iv_flags_ht & IEEE80211_FHT_LDPC_RX) == 0) caps &= ~IEEE80211_HTCAP_LDPC; ADDSHORT(frm, caps); /* HT parameters */ *frm = _IEEE80211_SHIFTMASK(rxmax, IEEE80211_HTCAP_MAXRXAMPDU) | _IEEE80211_SHIFTMASK(density, IEEE80211_HTCAP_MPDUDENSITY) ; frm++; /* pre-zero remainder of ie */ memset(frm, 0, sizeof(struct ieee80211_ie_htcap) - __offsetof(struct ieee80211_ie_htcap, hc_mcsset)); /* supported MCS set */ /* * XXX: For sta mode the rate set should be restricted based * on the AP's capabilities, but ni_htrates isn't setup when * we're called to form an AssocReq frame so for now we're * restricted to the device capabilities. */ ieee80211_set_mcsset(ni->ni_ic, frm); frm += __offsetof(struct ieee80211_ie_htcap, hc_extcap) - __offsetof(struct ieee80211_ie_htcap, hc_mcsset); /* HT extended capabilities */ extcaps = vap->iv_htextcaps & 0xffff; ADDSHORT(frm, extcaps); frm += sizeof(struct ieee80211_ie_htcap) - __offsetof(struct ieee80211_ie_htcap, hc_txbf); return frm; #undef ADDSHORT } /* * Add 802.11n HT capabilities information element */ uint8_t * ieee80211_add_htcap(uint8_t *frm, struct ieee80211_node *ni) { frm[0] = IEEE80211_ELEMID_HTCAP; frm[1] = sizeof(struct ieee80211_ie_htcap) - 2; return ieee80211_add_htcap_body(frm + 2, ni); } /* * Non-associated probe request - add HT capabilities based on * the current channel configuration. */ static uint8_t * ieee80211_add_htcap_body_ch(uint8_t *frm, struct ieee80211vap *vap, struct ieee80211_channel *c) { #define ADDSHORT(frm, v) do { \ frm[0] = (v) & 0xff; \ frm[1] = (v) >> 8; \ frm += 2; \ } while (0) struct ieee80211com *ic = vap->iv_ic; uint16_t caps, extcaps; int rxmax, density; /* HT capabilities */ caps = vap->iv_htcaps & 0xffff; /* * We don't use this in STA mode; only in IBSS mode. * So in IBSS mode we base our HTCAP flags on the * given channel. */ /* override 20/40 use based on current channel */ if (IEEE80211_IS_CHAN_HT40(c)) caps |= IEEE80211_HTCAP_CHWIDTH40; else caps &= ~IEEE80211_HTCAP_CHWIDTH40; /* Use the currently configured values */ rxmax = vap->iv_ampdu_rxmax; density = vap->iv_ampdu_density; /* adjust short GI based on channel and config */ if ((vap->iv_flags_ht & IEEE80211_FHT_SHORTGI20) == 0) caps &= ~IEEE80211_HTCAP_SHORTGI20; if ((vap->iv_flags_ht & IEEE80211_FHT_SHORTGI40) == 0 || (caps & IEEE80211_HTCAP_CHWIDTH40) == 0) caps &= ~IEEE80211_HTCAP_SHORTGI40; ADDSHORT(frm, caps); /* HT parameters */ *frm = _IEEE80211_SHIFTMASK(rxmax, IEEE80211_HTCAP_MAXRXAMPDU) | _IEEE80211_SHIFTMASK(density, IEEE80211_HTCAP_MPDUDENSITY) ; frm++; /* pre-zero remainder of ie */ memset(frm, 0, sizeof(struct ieee80211_ie_htcap) - __offsetof(struct ieee80211_ie_htcap, hc_mcsset)); /* supported MCS set */ /* * XXX: For sta mode the rate set should be restricted based * on the AP's capabilities, but ni_htrates isn't setup when * we're called to form an AssocReq frame so for now we're * restricted to the device capabilities. */ ieee80211_set_mcsset(ic, frm); frm += __offsetof(struct ieee80211_ie_htcap, hc_extcap) - __offsetof(struct ieee80211_ie_htcap, hc_mcsset); /* HT extended capabilities */ extcaps = vap->iv_htextcaps & 0xffff; ADDSHORT(frm, extcaps); frm += sizeof(struct ieee80211_ie_htcap) - __offsetof(struct ieee80211_ie_htcap, hc_txbf); return frm; #undef ADDSHORT } /* * Add 802.11n HT capabilities information element */ uint8_t * ieee80211_add_htcap_ch(uint8_t *frm, struct ieee80211vap *vap, struct ieee80211_channel *c) { frm[0] = IEEE80211_ELEMID_HTCAP; frm[1] = sizeof(struct ieee80211_ie_htcap) - 2; return ieee80211_add_htcap_body_ch(frm + 2, vap, c); } /* * Add Broadcom OUI wrapped standard HTCAP ie; this is * used for compatibility w/ pre-draft implementations. */ uint8_t * ieee80211_add_htcap_vendor(uint8_t *frm, struct ieee80211_node *ni) { frm[0] = IEEE80211_ELEMID_VENDOR; frm[1] = 4 + sizeof(struct ieee80211_ie_htcap) - 2; frm[2] = (BCM_OUI >> 0) & 0xff; frm[3] = (BCM_OUI >> 8) & 0xff; frm[4] = (BCM_OUI >> 16) & 0xff; frm[5] = BCM_OUI_HTCAP; return ieee80211_add_htcap_body(frm + 6, ni); } /* * Construct the MCS bit mask of basic rates * for inclusion in an HT information element. */ static void ieee80211_set_basic_htrates(uint8_t *frm, const struct ieee80211_htrateset *rs) { int i; for (i = 0; i < rs->rs_nrates; i++) { int r = rs->rs_rates[i] & IEEE80211_RATE_VAL; if ((rs->rs_rates[i] & IEEE80211_RATE_BASIC) && r < IEEE80211_HTRATE_MAXSIZE) { /* NB: this assumes a particular implementation */ setbit(frm, r); } } } /* * Update the HTINFO ie for a beacon frame. */ void ieee80211_ht_update_beacon(struct ieee80211vap *vap, struct ieee80211_beacon_offsets *bo) { #define PROTMODE (IEEE80211_HTINFO_OPMODE|IEEE80211_HTINFO_NONHT_PRESENT) struct ieee80211_node *ni; const struct ieee80211_channel *bsschan; struct ieee80211com *ic = vap->iv_ic; struct ieee80211_ie_htinfo *ht = (struct ieee80211_ie_htinfo *) bo->bo_htinfo; ni = ieee80211_ref_node(vap->iv_bss); bsschan = ni->ni_chan; /* XXX only update on channel change */ ht->hi_ctrlchannel = ieee80211_chan2ieee(ic, bsschan); if (vap->iv_flags_ht & IEEE80211_FHT_RIFS) ht->hi_byte1 = IEEE80211_HTINFO_RIFSMODE_PERM; else ht->hi_byte1 = IEEE80211_HTINFO_RIFSMODE_PROH; if (IEEE80211_IS_CHAN_HT40U(bsschan)) ht->hi_byte1 |= IEEE80211_HTINFO_2NDCHAN_ABOVE; else if (IEEE80211_IS_CHAN_HT40D(bsschan)) ht->hi_byte1 |= IEEE80211_HTINFO_2NDCHAN_BELOW; else ht->hi_byte1 |= IEEE80211_HTINFO_2NDCHAN_NONE; if (IEEE80211_IS_CHAN_HT40(bsschan)) ht->hi_byte1 |= IEEE80211_HTINFO_TXWIDTH_2040; /* protection mode */ /* * XXX TODO: this uses the global flag, not the per-VAP flag. * Eventually (once the protection modes are done per-channel * rather than per-VAP) we can flip this over to be per-VAP but * using the channel protection mode. */ ht->hi_byte2 = (ht->hi_byte2 &~ PROTMODE) | ic->ic_curhtprotmode; ieee80211_free_node(ni); /* XXX propagate to vendor ie's */ #undef PROTMODE } /* * Add body of an HTINFO information element. * * NB: We don't use struct ieee80211_ie_htinfo because we can * be called to fillin both a standard ie and a compat ie that * has a vendor OUI at the front. */ static uint8_t * ieee80211_add_htinfo_body(uint8_t *frm, struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; /* pre-zero remainder of ie */ memset(frm, 0, sizeof(struct ieee80211_ie_htinfo) - 2); /* primary/control channel center */ *frm++ = ieee80211_chan2ieee(ic, ni->ni_chan); if (vap->iv_flags_ht & IEEE80211_FHT_RIFS) frm[0] = IEEE80211_HTINFO_RIFSMODE_PERM; else frm[0] = IEEE80211_HTINFO_RIFSMODE_PROH; if (IEEE80211_IS_CHAN_HT40U(ni->ni_chan)) frm[0] |= IEEE80211_HTINFO_2NDCHAN_ABOVE; else if (IEEE80211_IS_CHAN_HT40D(ni->ni_chan)) frm[0] |= IEEE80211_HTINFO_2NDCHAN_BELOW; else frm[0] |= IEEE80211_HTINFO_2NDCHAN_NONE; if (IEEE80211_IS_CHAN_HT40(ni->ni_chan)) frm[0] |= IEEE80211_HTINFO_TXWIDTH_2040; /* * Add current protection mode. Unlike for beacons, * this will respect the per-VAP flags. */ frm[1] = vap->iv_curhtprotmode; frm += 5; /* basic MCS set */ ieee80211_set_basic_htrates(frm, &ni->ni_htrates); frm += sizeof(struct ieee80211_ie_htinfo) - __offsetof(struct ieee80211_ie_htinfo, hi_basicmcsset); return frm; } /* * Add 802.11n HT information element. */ uint8_t * ieee80211_add_htinfo(uint8_t *frm, struct ieee80211_node *ni) { frm[0] = IEEE80211_ELEMID_HTINFO; frm[1] = sizeof(struct ieee80211_ie_htinfo) - 2; return ieee80211_add_htinfo_body(frm + 2, ni); } /* * Add Broadcom OUI wrapped standard HTINFO ie; this is * used for compatibility w/ pre-draft implementations. */ uint8_t * ieee80211_add_htinfo_vendor(uint8_t *frm, struct ieee80211_node *ni) { frm[0] = IEEE80211_ELEMID_VENDOR; frm[1] = 4 + sizeof(struct ieee80211_ie_htinfo) - 2; frm[2] = (BCM_OUI >> 0) & 0xff; frm[3] = (BCM_OUI >> 8) & 0xff; frm[4] = (BCM_OUI >> 16) & 0xff; frm[5] = BCM_OUI_HTINFO; return ieee80211_add_htinfo_body(frm + 6, ni); } /* * Get the HT density for the given 802.11n node. * * Take into account the density advertised from the peer. * Larger values are longer A-MPDU density spacing values, and * we want to obey them per station if we get them. */ int ieee80211_ht_get_node_ampdu_density(const struct ieee80211_node *ni) { struct ieee80211vap *vap; int peer_mpdudensity; vap = ni->ni_vap; peer_mpdudensity = _IEEE80211_MASKSHIFT(ni->ni_htparam, IEEE80211_HTCAP_MPDUDENSITY); if (vap->iv_ampdu_density > peer_mpdudensity) peer_mpdudensity = vap->iv_ampdu_density; return (peer_mpdudensity); } /* * Get the transmit A-MPDU limit for the given 802.11n node. * * Take into account the limit advertised from the peer. * Smaller values indicate smaller maximum A-MPDU sizes, and * should be used when forming an A-MPDU to the given peer. */ int ieee80211_ht_get_node_ampdu_limit(const struct ieee80211_node *ni) { struct ieee80211vap *vap; int peer_mpdulimit; vap = ni->ni_vap; peer_mpdulimit = _IEEE80211_MASKSHIFT(ni->ni_htparam, IEEE80211_HTCAP_MAXRXAMPDU); return (MIN(vap->iv_ampdu_limit, peer_mpdulimit)); } /* * Return true if short-GI is available when transmitting to * the given node at 20MHz. * * Ensure it's configured and available in the VAP / driver as * well as the node. */ bool ieee80211_ht_check_tx_shortgi_20(const struct ieee80211_node *ni) { const struct ieee80211vap *vap; const struct ieee80211com *ic; if (! ieee80211_ht_check_tx_ht(ni)) return (false); vap = ni->ni_vap; ic = ni->ni_ic; return ((ic->ic_htcaps & IEEE80211_HTCAP_SHORTGI20) && (ni->ni_htcap & IEEE80211_HTCAP_SHORTGI20) && (vap->iv_flags_ht & IEEE80211_FHT_SHORTGI20)); } /* * Return true if short-GI is available when transmitting to * the given node at 40MHz. * * Ensure it's configured and available in the VAP / driver as * well as the node and BSS. */ bool ieee80211_ht_check_tx_shortgi_40(const struct ieee80211_node *ni) { const struct ieee80211vap *vap; const struct ieee80211com *ic; if (! ieee80211_ht_check_tx_ht40(ni)) return (false); vap = ni->ni_vap; ic = ni->ni_ic; return ((ic->ic_htcaps & IEEE80211_HTCAP_SHORTGI40) && (ni->ni_htcap & IEEE80211_HTCAP_SHORTGI40) && (vap->iv_flags_ht & IEEE80211_FHT_SHORTGI40)); } /* * Return true if HT rates can be used for the given node. * * There are some situations seen in the wild, wild past where * HT APs would announce HT but no HT rates. */ bool ieee80211_ht_check_tx_ht(const struct ieee80211_node *ni) { const struct ieee80211vap *vap; const struct ieee80211_channel *bss_chan; if (ni == NULL || ni->ni_chan == IEEE80211_CHAN_ANYC || ni->ni_vap == NULL || ni->ni_vap->iv_bss == NULL) return (false); vap = ni->ni_vap; bss_chan = vap->iv_bss->ni_chan; if (bss_chan == IEEE80211_CHAN_ANYC) return (false); if (IEEE80211_IS_CHAN_HT(ni->ni_chan) && ni->ni_htrates.rs_nrates == 0) return (false); return (IEEE80211_IS_CHAN_HT(ni->ni_chan)); } /* * Return true if HT40 rates can be transmitted to the given node. * * This verifies that the BSS is HT40 capable and the current * node channel width is 40MHz. */ bool ieee80211_ht_check_tx_ht40(const struct ieee80211_node *ni) { struct ieee80211vap *vap; struct ieee80211_channel *bss_chan; if (! ieee80211_ht_check_tx_ht(ni)) return (false); vap = ni->ni_vap; bss_chan = vap->iv_bss->ni_chan; return (IEEE80211_IS_CHAN_HT40(bss_chan) && IEEE80211_IS_CHAN_HT40(ni->ni_chan) && - (ni->ni_chw == 40)); + (ni->ni_chw == IEEE80211_STA_RX_BW_40)); } diff --git a/sys/net80211/ieee80211_node.c b/sys/net80211/ieee80211_node.c index 8f8dc5f378b8..d2a4558970f9 100644 --- a/sys/net80211/ieee80211_node.c +++ b/sys/net80211/ieee80211_node.c @@ -1,3138 +1,3139 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * 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 #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_SUPERG #include #endif #ifdef IEEE80211_SUPPORT_TDMA #include #endif #include #include #include #include #include #ifdef IEEE80211_DEBUG_REFCNT #define __debrefcnt_used #else #define __debrefcnt_used __unused #endif /* * IEEE80211_NODE_HASHSIZE must be a power of 2. */ CTASSERT((IEEE80211_NODE_HASHSIZE & (IEEE80211_NODE_HASHSIZE-1)) == 0); /* * Association id's are managed with a bit vector. */ #define IEEE80211_AID_SET(_vap, b) \ ((_vap)->iv_aid_bitmap[IEEE80211_AID(b) / 32] |= \ (1 << (IEEE80211_AID(b) % 32))) #define IEEE80211_AID_CLR(_vap, b) \ ((_vap)->iv_aid_bitmap[IEEE80211_AID(b) / 32] &= \ ~(1 << (IEEE80211_AID(b) % 32))) #define IEEE80211_AID_ISSET(_vap, b) \ ((_vap)->iv_aid_bitmap[IEEE80211_AID(b) / 32] & (1 << (IEEE80211_AID(b) % 32))) static int ieee80211_sta_join1(struct ieee80211_node *); static struct ieee80211_node *ieee80211_alloc_node( struct ieee80211_node_table *, struct ieee80211vap *, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *, int); static struct ieee80211_node *node_alloc(struct ieee80211vap *, const uint8_t [IEEE80211_ADDR_LEN]); static int node_init(struct ieee80211_node *); static void node_cleanup(struct ieee80211_node *); static void node_free(struct ieee80211_node *); static void node_age(struct ieee80211_node *); static int8_t node_getrssi(const struct ieee80211_node *); static void node_getsignal(const struct ieee80211_node *, int8_t *, int8_t *); static void node_getmimoinfo(const struct ieee80211_node *, struct ieee80211_mimo_info *); static void __ieee80211_free_node(struct ieee80211_node *); static void node_reclaim(struct ieee80211_node_table *nt, struct ieee80211_node *ni); static void ieee80211_node_table_init(struct ieee80211com *ic, struct ieee80211_node_table *nt, const char *name, int inact, int keymaxix); static void ieee80211_node_table_reset(struct ieee80211_node_table *, struct ieee80211vap *); static void ieee80211_node_table_cleanup(struct ieee80211_node_table *nt); static void ieee80211_vap_erp_timeout(struct ieee80211vap *); MALLOC_DEFINE(M_80211_NODE, "80211node", "802.11 node state"); MALLOC_DEFINE(M_80211_NODE_IE, "80211nodeie", "802.11 node ie"); void ieee80211_node_attach(struct ieee80211com *ic) { /* XXX really want maxlen enforced per-sta */ ieee80211_ageq_init(&ic->ic_stageq, ic->ic_max_keyix * 8, "802.11 staging q"); ieee80211_node_table_init(ic, &ic->ic_sta, "station", IEEE80211_INACT_INIT, ic->ic_max_keyix); callout_init(&ic->ic_inact, 1); callout_reset(&ic->ic_inact, IEEE80211_INACT_WAIT*hz, ieee80211_node_timeout, ic); ic->ic_node_alloc = node_alloc; ic->ic_node_init = node_init; ic->ic_node_free = node_free; ic->ic_node_cleanup = node_cleanup; ic->ic_node_age = node_age; ic->ic_node_drain = node_age; /* NB: same as age */ ic->ic_node_getrssi = node_getrssi; ic->ic_node_getsignal = node_getsignal; ic->ic_node_getmimoinfo = node_getmimoinfo; /* * Set flags to be propagated to all vap's; * these define default behaviour/configuration. */ ic->ic_flags_ext |= IEEE80211_FEXT_INACT; /* inactivity processing */ } void ieee80211_node_detach(struct ieee80211com *ic) { callout_drain(&ic->ic_inact); ieee80211_node_table_cleanup(&ic->ic_sta); ieee80211_ageq_drain(&ic->ic_stageq); ieee80211_ageq_cleanup(&ic->ic_stageq); } void ieee80211_node_vattach(struct ieee80211vap *vap) { /* NB: driver can override */ vap->iv_max_aid = IEEE80211_AID_DEF; /* default station inactivity timer settings */ vap->iv_inact_init = IEEE80211_INACT_INIT; vap->iv_inact_auth = IEEE80211_INACT_AUTH; vap->iv_inact_run = IEEE80211_INACT_RUN; vap->iv_inact_probe = IEEE80211_INACT_PROBE; IEEE80211_DPRINTF(vap, IEEE80211_MSG_INACT, "%s: init %u auth %u run %u probe %u\n", __func__, vap->iv_inact_init, vap->iv_inact_auth, vap->iv_inact_run, vap->iv_inact_probe); } void ieee80211_node_latevattach(struct ieee80211vap *vap) { /* XXX should ieee80211_vap_attach(), our only caller hold the lock? */ IEEE80211_UNLOCK_ASSERT(vap->iv_ic); if (vap->iv_opmode == IEEE80211_M_HOSTAP) { /* XXX should we allow max aid to be zero? */ if (vap->iv_max_aid < IEEE80211_AID_MIN) { vap->iv_max_aid = IEEE80211_AID_MIN; if_printf(vap->iv_ifp, "WARNING: max aid too small, changed to %d\n", vap->iv_max_aid); } vap->iv_aid_bitmap = (uint32_t *) IEEE80211_MALLOC( howmany(vap->iv_max_aid, 32) * sizeof(uint32_t), M_80211_NODE, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (vap->iv_aid_bitmap == NULL) { /* XXX no way to recover */ printf("%s: no memory for AID bitmap, max aid %d!\n", __func__, vap->iv_max_aid); vap->iv_max_aid = 0; } } IEEE80211_LOCK(vap->iv_ic); ieee80211_reset_bss(vap); IEEE80211_UNLOCK(vap->iv_ic); vap->iv_auth = ieee80211_authenticator_get(vap->iv_bss->ni_authmode); } void ieee80211_node_vdetach(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; /* XXX should ieee80211_vap_detach(), our only caller hold the lock? */ IEEE80211_UNLOCK_ASSERT(vap->iv_ic); ieee80211_node_table_reset(&ic->ic_sta, vap); IEEE80211_LOCK(ic); if (vap->iv_bss != NULL) { ieee80211_free_node(vap->iv_bss); vap->iv_update_bss(vap, NULL); } IEEE80211_UNLOCK(ic); if (vap->iv_aid_bitmap != NULL) { IEEE80211_FREE(vap->iv_aid_bitmap, M_80211_NODE); vap->iv_aid_bitmap = NULL; } } /* * Port authorize/unauthorize interfaces for use by an authenticator. */ void ieee80211_node_authorize(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; ni->ni_flags |= IEEE80211_NODE_AUTH; ni->ni_inact_reload = vap->iv_inact_run; ni->ni_inact = ni->ni_inact_reload; IEEE80211_NOTE(vap, IEEE80211_MSG_INACT, ni, "%s: inact_reload %u", __func__, ni->ni_inact_reload); } void ieee80211_node_unauthorize(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; ni->ni_flags &= ~IEEE80211_NODE_AUTH; ni->ni_inact_reload = vap->iv_inact_auth; if (ni->ni_inact > ni->ni_inact_reload) ni->ni_inact = ni->ni_inact_reload; IEEE80211_NOTE(vap, IEEE80211_MSG_INACT, ni, "%s: inact_reload %u inact %u", __func__, ni->ni_inact_reload, ni->ni_inact); } /* * Fix tx parameters for a node according to ``association state''. */ void ieee80211_node_setuptxparms(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; enum ieee80211_phymode mode; if (ni->ni_flags & IEEE80211_NODE_VHT) { if (IEEE80211_IS_CHAN_5GHZ(ni->ni_chan)) mode = IEEE80211_MODE_VHT_5GHZ; else mode = IEEE80211_MODE_VHT_2GHZ; } else if (ni->ni_flags & IEEE80211_NODE_HT) { if (IEEE80211_IS_CHAN_5GHZ(ni->ni_chan)) mode = IEEE80211_MODE_11NA; else mode = IEEE80211_MODE_11NG; } else { /* legacy rate handling */ if (IEEE80211_IS_CHAN_ST(ni->ni_chan)) mode = IEEE80211_MODE_STURBO_A; else if (IEEE80211_IS_CHAN_HALF(ni->ni_chan)) mode = IEEE80211_MODE_HALF; else if (IEEE80211_IS_CHAN_QUARTER(ni->ni_chan)) mode = IEEE80211_MODE_QUARTER; /* NB: 108A should be handled as 11a */ else if (IEEE80211_IS_CHAN_A(ni->ni_chan)) mode = IEEE80211_MODE_11A; else if (IEEE80211_IS_CHAN_108G(ni->ni_chan) || (ni->ni_flags & IEEE80211_NODE_ERP)) mode = IEEE80211_MODE_11G; else mode = IEEE80211_MODE_11B; } ni->ni_txparms = &vap->iv_txparms[mode]; } /* * Set/change the channel. The rate set is also updated as * to insure a consistent view by drivers. * XXX should be private but hostap needs it to deal with CSA */ void ieee80211_node_set_chan(struct ieee80211_node *ni, struct ieee80211_channel *chan) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; enum ieee80211_phymode mode; KASSERT(chan != IEEE80211_CHAN_ANYC, ("no channel")); ni->ni_chan = chan; mode = ieee80211_chan2mode(chan); if (IEEE80211_IS_CHAN_HT(chan)) { /* * We must install the legacy rate est in ni_rates and the * HT rate set in ni_htrates. */ ni->ni_htrates = *ieee80211_get_suphtrates(ic, chan); /* * Setup bss tx parameters based on operating mode. We * use legacy rates when operating in a mixed HT+non-HT bss * and non-ERP rates in 11g for mixed ERP+non-ERP bss. */ if (mode == IEEE80211_MODE_11NA && (vap->iv_flags_ht & IEEE80211_FHT_PUREN) == 0) mode = IEEE80211_MODE_11A; else if (mode == IEEE80211_MODE_11NG && (vap->iv_flags_ht & IEEE80211_FHT_PUREN) == 0) mode = IEEE80211_MODE_11G; if (mode == IEEE80211_MODE_11G && (vap->iv_flags & IEEE80211_F_PUREG) == 0) mode = IEEE80211_MODE_11B; } ni->ni_txparms = &vap->iv_txparms[mode]; ni->ni_rates = *ieee80211_get_suprates(ic, chan); } static __inline void copy_bss(struct ieee80211_node *nbss, const struct ieee80211_node *obss) { /* propagate useful state */ nbss->ni_authmode = obss->ni_authmode; nbss->ni_txpower = obss->ni_txpower; nbss->ni_vlan = obss->ni_vlan; /* XXX statistics? */ /* XXX legacy WDS bssid? */ } void ieee80211_create_ibss(struct ieee80211vap* vap, struct ieee80211_channel *chan) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: creating %s on channel %u%c flags 0x%08x\n", __func__, ieee80211_opmode_name[vap->iv_opmode], ieee80211_chan2ieee(ic, chan), ieee80211_channel_type_char(chan), chan->ic_flags); ni = ieee80211_alloc_node(&ic->ic_sta, vap, vap->iv_myaddr, __func__, __LINE__); if (ni == NULL) { /* XXX recovery? */ return; } IEEE80211_ADDR_COPY(ni->ni_bssid, vap->iv_myaddr); ni->ni_esslen = vap->iv_des_ssid[0].len; memcpy(ni->ni_essid, vap->iv_des_ssid[0].ssid, ni->ni_esslen); if (vap->iv_bss != NULL) copy_bss(ni, vap->iv_bss); ni->ni_intval = ic->ic_bintval; if (vap->iv_flags & IEEE80211_F_PRIVACY) ni->ni_capinfo |= IEEE80211_CAPINFO_PRIVACY; if (ic->ic_phytype == IEEE80211_T_FH) { ni->ni_fhdwell = 200; /* XXX */ ni->ni_fhindex = 1; } if (vap->iv_opmode == IEEE80211_M_IBSS) { ni->ni_capinfo |= IEEE80211_CAPINFO_IBSS; /* XXX */ if (vap->iv_flags & IEEE80211_F_DESBSSID) IEEE80211_ADDR_COPY(ni->ni_bssid, vap->iv_des_bssid); else { net80211_get_random_bytes(ni->ni_bssid, IEEE80211_ADDR_LEN); /* clear group bit, add local bit */ ni->ni_bssid[0] = (ni->ni_bssid[0] &~ 0x01) | 0x02; } } else if (vap->iv_opmode == IEEE80211_M_AHDEMO) { if (vap->iv_flags & IEEE80211_F_DESBSSID) IEEE80211_ADDR_COPY(ni->ni_bssid, vap->iv_des_bssid); else #ifdef IEEE80211_SUPPORT_TDMA if ((vap->iv_caps & IEEE80211_C_TDMA) == 0) #endif memset(ni->ni_bssid, 0, IEEE80211_ADDR_LEN); #ifdef IEEE80211_SUPPORT_MESH } else if (vap->iv_opmode == IEEE80211_M_MBSS) { ni->ni_meshidlen = vap->iv_mesh->ms_idlen; memcpy(ni->ni_meshid, vap->iv_mesh->ms_id, ni->ni_meshidlen); #endif } /* * Fix the channel and related attributes. */ /* clear DFS CAC state on previous channel */ if (ic->ic_bsschan != IEEE80211_CHAN_ANYC && ic->ic_bsschan->ic_freq != chan->ic_freq && IEEE80211_IS_CHAN_CACDONE(ic->ic_bsschan)) ieee80211_dfs_cac_clear(ic, ic->ic_bsschan); ic->ic_bsschan = chan; ieee80211_node_set_chan(ni, chan); ic->ic_curmode = ieee80211_chan2mode(chan); /* * Do mode-specific setup. */ if (IEEE80211_IS_CHAN_FULL(chan)) { if (IEEE80211_IS_CHAN_ANYG(chan)) { /* * Use a mixed 11b/11g basic rate set. */ ieee80211_setbasicrates(&ni->ni_rates, IEEE80211_MODE_11G); if (vap->iv_flags & IEEE80211_F_PUREG) { /* * Also mark OFDM rates basic so 11b * stations do not join (WiFi compliance). */ ieee80211_addbasicrates(&ni->ni_rates, IEEE80211_MODE_11A); } } else if (IEEE80211_IS_CHAN_B(chan)) { /* * Force pure 11b rate set. */ ieee80211_setbasicrates(&ni->ni_rates, IEEE80211_MODE_11B); } } /* XXX TODO: other bits and pieces - eg fast-frames? */ /* If we're an 11n channel then initialise the 11n bits */ if (IEEE80211_IS_CHAN_VHT(ni->ni_chan)) { /* XXX what else? */ ieee80211_ht_node_init(ni); ieee80211_vht_node_init(ni); } else if (IEEE80211_IS_CHAN_HT(ni->ni_chan)) { /* XXX what else? */ ieee80211_ht_node_init(ni); } (void) ieee80211_sta_join1(ieee80211_ref_node(ni)); } /* * Reset bss state on transition to the INIT state. * Clear any stations from the table (they have been * deauth'd) and reset the bss node (clears key, rate * etc. state). */ void ieee80211_reset_bss(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni, *obss; IEEE80211_LOCK_ASSERT(ic); ieee80211_node_table_reset(&ic->ic_sta, vap); /* XXX multi-bss: wrong */ ieee80211_vap_reset_erp(vap); ni = ieee80211_alloc_node(&ic->ic_sta, vap, vap->iv_myaddr, __func__, __LINE__); KASSERT(ni != NULL, ("unable to setup initial BSS node")); obss = vap->iv_update_bss(vap, ieee80211_ref_node(ni)); if (obss != NULL) { copy_bss(ni, obss); ni->ni_intval = ic->ic_bintval; ieee80211_free_node(obss); } else IEEE80211_ADDR_COPY(ni->ni_bssid, vap->iv_myaddr); } static int match_ssid(const struct ieee80211_node *ni, int nssid, const struct ieee80211_scan_ssid ssids[]) { int i; for (i = 0; i < nssid; i++) { if (ni->ni_esslen == ssids[i].len && memcmp(ni->ni_essid, ssids[i].ssid, ni->ni_esslen) == 0) return 1; } return 0; } /* * Test a node for suitability/compatibility. */ static int check_bss(struct ieee80211vap *vap, struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; uint8_t rate; if (isclr(ic->ic_chan_active, ieee80211_chan2ieee(ic, ni->ni_chan))) return 0; if (vap->iv_opmode == IEEE80211_M_IBSS) { if ((ni->ni_capinfo & IEEE80211_CAPINFO_IBSS) == 0) return 0; } else { if ((ni->ni_capinfo & IEEE80211_CAPINFO_ESS) == 0) return 0; } if (vap->iv_flags & IEEE80211_F_PRIVACY) { if ((ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) == 0) return 0; } else { /* XXX does this mean privacy is supported or required? */ if (ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) return 0; } rate = ieee80211_fix_rate(ni, &ni->ni_rates, IEEE80211_F_JOIN | IEEE80211_F_DONEGO | IEEE80211_F_DOFRATE); if (rate & IEEE80211_RATE_BASIC) return 0; if (vap->iv_des_nssid != 0 && !match_ssid(ni, vap->iv_des_nssid, vap->iv_des_ssid)) return 0; if ((vap->iv_flags & IEEE80211_F_DESBSSID) && !IEEE80211_ADDR_EQ(vap->iv_des_bssid, ni->ni_bssid)) return 0; return 1; } #ifdef IEEE80211_DEBUG /* * Display node suitability/compatibility. */ static void check_bss_debug(struct ieee80211vap *vap, struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; uint8_t rate; int fail; fail = 0; if (isclr(ic->ic_chan_active, ieee80211_chan2ieee(ic, ni->ni_chan))) fail |= 0x01; if (vap->iv_opmode == IEEE80211_M_IBSS) { if ((ni->ni_capinfo & IEEE80211_CAPINFO_IBSS) == 0) fail |= 0x02; } else { if ((ni->ni_capinfo & IEEE80211_CAPINFO_ESS) == 0) fail |= 0x02; } if (vap->iv_flags & IEEE80211_F_PRIVACY) { if ((ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) == 0) fail |= 0x04; } else { /* XXX does this mean privacy is supported or required? */ if (ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) fail |= 0x04; } rate = ieee80211_fix_rate(ni, &ni->ni_rates, IEEE80211_F_JOIN | IEEE80211_F_DONEGO | IEEE80211_F_DOFRATE); if (rate & IEEE80211_RATE_BASIC) fail |= 0x08; if (vap->iv_des_nssid != 0 && !match_ssid(ni, vap->iv_des_nssid, vap->iv_des_ssid)) fail |= 0x10; if ((vap->iv_flags & IEEE80211_F_DESBSSID) && !IEEE80211_ADDR_EQ(vap->iv_des_bssid, ni->ni_bssid)) fail |= 0x20; printf(" %c %s", fail ? '-' : '+', ether_sprintf(ni->ni_macaddr)); printf(" %s%c", ether_sprintf(ni->ni_bssid), fail & 0x20 ? '!' : ' '); printf(" %3d%c", ieee80211_chan2ieee(ic, ni->ni_chan), fail & 0x01 ? '!' : ' '); printf(" %2dM%c", (rate & IEEE80211_RATE_VAL) / 2, fail & 0x08 ? '!' : ' '); printf(" %4s%c", (ni->ni_capinfo & IEEE80211_CAPINFO_ESS) ? "ess" : (ni->ni_capinfo & IEEE80211_CAPINFO_IBSS) ? "ibss" : "????", fail & 0x02 ? '!' : ' '); printf(" %3s%c ", (ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) ? "wep" : "no", fail & 0x04 ? '!' : ' '); ieee80211_print_essid(ni->ni_essid, ni->ni_esslen); printf("%s\n", fail & 0x10 ? "!" : ""); } #endif /* IEEE80211_DEBUG */ int ieee80211_ibss_merge_check(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; if (ni == vap->iv_bss || IEEE80211_ADDR_EQ(ni->ni_bssid, vap->iv_bss->ni_bssid)) { /* unchanged, nothing to do */ return 0; } if (!check_bss(vap, ni)) { /* capabilities mismatch */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, "%s: merge failed, capabilities mismatch\n", __func__); #ifdef IEEE80211_DEBUG if (ieee80211_msg_assoc(vap)) check_bss_debug(vap, ni); #endif vap->iv_stats.is_ibss_capmismatch++; return 0; } return 1; } /* * Check if the given node should populate the node table. * * We need to be in "see all beacons for all ssids" mode in order * to do IBSS merges, however this means we will populate nodes for * /all/ IBSS SSIDs, versus just the one we care about. * * So this check ensures the node can actually belong to our IBSS * configuration. For now it simply checks the SSID. */ int ieee80211_ibss_node_check_new(struct ieee80211_node *ni, const struct ieee80211_scanparams *scan) { struct ieee80211vap *vap = ni->ni_vap; int i; /* * If we have no SSID and no scan SSID, return OK. */ if (vap->iv_des_nssid == 0 && scan->ssid == NULL) goto ok; /* * If we have one of (SSID, scan SSID) then return error. */ if (!! (vap->iv_des_nssid == 0) != !! (scan->ssid == NULL)) goto mismatch; /* * Double-check - we need scan SSID. */ if (scan->ssid == NULL) goto mismatch; /* * Check if the scan SSID matches the SSID list for the VAP. */ for (i = 0; i < vap->iv_des_nssid; i++) { /* Sanity length check */ if (vap->iv_des_ssid[i].len != scan->ssid[1]) continue; /* Note: SSID in the scan entry is the IE format */ if (memcmp(vap->iv_des_ssid[i].ssid, scan->ssid + 2, vap->iv_des_ssid[i].len) == 0) goto ok; } mismatch: return (0); ok: return (1); } /* * Handle 802.11 ad hoc network merge. The * convention, set by the Wireless Ethernet Compatibility Alliance * (WECA), is that an 802.11 station will change its BSSID to match * the "oldest" 802.11 ad hoc network, on the same channel, that * has the station's desired SSID. The "oldest" 802.11 network * sends beacons with the greatest TSF timestamp. * * The caller is assumed to validate TSF's before attempting a merge. * * Return !0 if the BSSID changed, 0 otherwise. */ int ieee80211_ibss_merge(struct ieee80211_node *ni) { #ifdef IEEE80211_DEBUG struct ieee80211vap *vap = ni->ni_vap; #endif if (! ieee80211_ibss_merge_check(ni)) return 0; IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, "%s: new bssid %s: %s preamble, %s slot time%s\n", __func__, ether_sprintf(ni->ni_bssid), vap->iv_flags&IEEE80211_F_SHPREAMBLE ? "short" : "long", vap->iv_flags&IEEE80211_F_SHSLOT ? "short" : "long", vap->iv_flags&IEEE80211_F_USEPROT ? ", protection" : "" ); return ieee80211_sta_join1(ieee80211_ref_node(ni)); } /* * Calculate HT channel promotion flags for all vaps. * This assumes ni_chan have been setup for each vap. */ static int gethtadjustflags(struct ieee80211com *ic) { struct ieee80211vap *vap; int flags; flags = 0; /* XXX locking */ TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { if (vap->iv_state < IEEE80211_S_RUN) continue; switch (vap->iv_opmode) { case IEEE80211_M_WDS: case IEEE80211_M_STA: case IEEE80211_M_AHDEMO: case IEEE80211_M_HOSTAP: case IEEE80211_M_IBSS: case IEEE80211_M_MBSS: flags |= ieee80211_htchanflags(vap->iv_bss->ni_chan); break; default: break; } } return flags; } /* * Calculate VHT channel promotion flags for all vaps. * This assumes ni_chan have been setup for each vap. */ static int getvhtadjustflags(struct ieee80211com *ic) { struct ieee80211vap *vap; int flags; flags = 0; /* XXX locking */ TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { if (vap->iv_state < IEEE80211_S_RUN) continue; switch (vap->iv_opmode) { case IEEE80211_M_WDS: case IEEE80211_M_STA: case IEEE80211_M_AHDEMO: case IEEE80211_M_HOSTAP: case IEEE80211_M_IBSS: case IEEE80211_M_MBSS: flags |= ieee80211_vhtchanflags(vap->iv_bss->ni_chan); break; default: break; } } return flags; } /* * Check if the current channel needs to change based on whether * any vap's are using HT20/HT40. This is used to sync the state * of ic_curchan after a channel width change on a running vap. * * Same applies for VHT. */ void ieee80211_sync_curchan(struct ieee80211com *ic) { struct ieee80211_channel *c; c = ieee80211_ht_adjust_channel(ic, ic->ic_curchan, gethtadjustflags(ic)); c = ieee80211_vht_adjust_channel(ic, c, getvhtadjustflags(ic)); if (c != ic->ic_curchan) { ic->ic_curchan = c; ic->ic_curmode = ieee80211_chan2mode(ic->ic_curchan); ic->ic_rt = ieee80211_get_ratetable(ic->ic_curchan); IEEE80211_UNLOCK(ic); ic->ic_set_channel(ic); ieee80211_radiotap_chan_change(ic); IEEE80211_LOCK(ic); } } /* * Setup the current channel. The request channel may be * promoted if other vap's are operating with HT20/HT40. */ void ieee80211_setupcurchan(struct ieee80211com *ic, struct ieee80211_channel *c) { if (ic->ic_htcaps & IEEE80211_HTC_HT) { int flags = gethtadjustflags(ic); /* * Check for channel promotion required to support the * set of running vap's. This assumes we are called * after ni_chan is setup for each vap. */ /* XXX VHT? */ /* NB: this assumes IEEE80211_FHT_USEHT40 > IEEE80211_FHT_HT */ if (flags > ieee80211_htchanflags(c)) c = ieee80211_ht_adjust_channel(ic, c, flags); } /* * VHT promotion - this will at least promote to VHT20/40 * based on what HT has done; it may further promote the * channel to VHT80 or above. */ if (ic->ic_vht_cap.vht_cap_info != 0) { int flags = getvhtadjustflags(ic); if (flags > ieee80211_vhtchanflags(c)) c = ieee80211_vht_adjust_channel(ic, c, flags); } ic->ic_bsschan = ic->ic_curchan = c; ic->ic_curmode = ieee80211_chan2mode(ic->ic_curchan); ic->ic_rt = ieee80211_get_ratetable(ic->ic_curchan); } /* * Change the current channel. The channel change is guaranteed to have * happened before the next state change. */ void ieee80211_setcurchan(struct ieee80211com *ic, struct ieee80211_channel *c) { ieee80211_setupcurchan(ic, c); ieee80211_runtask(ic, &ic->ic_chan_task); } void ieee80211_update_chw(struct ieee80211com *ic) { ieee80211_setupcurchan(ic, ic->ic_curchan); ieee80211_runtask(ic, &ic->ic_chw_task); } /* * Join the specified IBSS/BSS network. The node is assumed to * be passed in with a held reference. */ static int ieee80211_sta_join1(struct ieee80211_node *selbs) { struct ieee80211vap *vap = selbs->ni_vap; struct ieee80211com *ic = selbs->ni_ic; struct ieee80211_node *obss; int canreassoc; /* * Committed to selbs, setup state. */ IEEE80211_LOCK(ic); /* XXX may recurse here, check callers. */ obss = vap->iv_update_bss(vap, selbs); /* NB: caller assumed to bump refcnt */ IEEE80211_UNLOCK(ic); /* * Check if old+new node have the same address in which * case we can reassociate when operating in sta mode. */ /* XXX We'll not be in RUN anymore as iv_state got updated already? */ canreassoc = (obss != NULL && vap->iv_state == IEEE80211_S_RUN && IEEE80211_ADDR_EQ(obss->ni_macaddr, selbs->ni_macaddr)); if (obss != NULL) { struct ieee80211_node_table *nt = obss->ni_table; copy_bss(selbs, obss); if (nt != NULL) { ieee80211_node_decref(obss); /* iv_bss reference */ IEEE80211_NODE_LOCK(nt); node_reclaim(nt, obss); /* station table reference */ IEEE80211_NODE_UNLOCK(nt); } else { ieee80211_free_node(obss); /* iv_bss reference */ } obss = NULL; /* NB: guard against later use */ } /* * Delete unusable rates; we've already checked * that the negotiated rate set is acceptable. */ ieee80211_fix_rate(vap->iv_bss, &vap->iv_bss->ni_rates, IEEE80211_F_DODEL | IEEE80211_F_JOIN); ieee80211_setcurchan(ic, selbs->ni_chan); /* * Set the erp state (mostly the slot time) to deal with * the auto-select case; this should be redundant if the * mode is locked. */ ieee80211_vap_reset_erp(vap); ieee80211_wme_initparams(vap); if (vap->iv_opmode == IEEE80211_M_STA) { if (canreassoc) { /* Reassociate */ ieee80211_new_state(vap, IEEE80211_S_ASSOC, 1); } else { /* * Act as if we received a DEAUTH frame in case we * are invoked from the RUN state. This will cause * us to try to re-authenticate if we are operating * as a station. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_AUTH, "%s %p<%s> %s -> AUTH, FC0_SUBTYPE_DEAUTH\n", __func__, selbs, ether_sprintf(selbs->ni_macaddr), ieee80211_state_name[vap->iv_state]); ieee80211_new_state(vap, IEEE80211_S_AUTH, IEEE80211_FC0_SUBTYPE_DEAUTH); } } else ieee80211_new_state(vap, IEEE80211_S_RUN, -1); return 1; } int ieee80211_sta_join(struct ieee80211vap *vap, struct ieee80211_channel *chan, const struct ieee80211_scan_entry *se) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; int do_ht = 0; ni = ieee80211_alloc_node(&ic->ic_sta, vap, se->se_macaddr, __func__, __LINE__); if (ni == NULL) { /* XXX msg */ return 0; } /* * Expand scan state into node's format. * XXX may not need all this stuff */ IEEE80211_ADDR_COPY(ni->ni_bssid, se->se_bssid); ni->ni_esslen = se->se_ssid[1]; memcpy(ni->ni_essid, se->se_ssid+2, ni->ni_esslen); ni->ni_tstamp.tsf = se->se_tstamp.tsf; ni->ni_intval = se->se_intval; ni->ni_capinfo = se->se_capinfo; ni->ni_chan = chan; ni->ni_timoff = se->se_timoff; ni->ni_fhdwell = se->se_fhdwell; ni->ni_fhindex = se->se_fhindex; ni->ni_erp = se->se_erp; IEEE80211_RSSI_LPF(ni->ni_avgrssi, se->se_rssi); ni->ni_noise = se->se_noise; if (vap->iv_opmode == IEEE80211_M_STA) { /* NB: only infrastructure mode requires an associd */ ni->ni_flags |= IEEE80211_NODE_ASSOCID; } if (ieee80211_ies_init(&ni->ni_ies, se->se_ies.data, se->se_ies.len)) { ieee80211_ies_expand(&ni->ni_ies); #ifdef IEEE80211_SUPPORT_SUPERG if (ni->ni_ies.ath_ie != NULL) ieee80211_parse_ath(ni, ni->ni_ies.ath_ie); #endif if (ni->ni_ies.htcap_ie != NULL) ieee80211_parse_htcap(ni, ni->ni_ies.htcap_ie); if (ni->ni_ies.htinfo_ie != NULL) ieee80211_parse_htinfo(ni, ni->ni_ies.htinfo_ie); #ifdef IEEE80211_SUPPORT_MESH if (ni->ni_ies.meshid_ie != NULL) ieee80211_parse_meshid(ni, ni->ni_ies.meshid_ie); #endif #ifdef IEEE80211_SUPPORT_TDMA if (ni->ni_ies.tdma_ie != NULL) ieee80211_parse_tdma(ni, ni->ni_ies.tdma_ie); #endif if (ni->ni_ies.vhtcap_ie != NULL) ieee80211_parse_vhtcap(ni, ni->ni_ies.vhtcap_ie); if (ni->ni_ies.vhtopmode_ie != NULL) ieee80211_parse_vhtopmode(ni, ni->ni_ies.vhtopmode_ie); /* XXX parse BSSLOAD IE */ /* XXX parse TXPWRENV IE */ /* XXX parse APCHANREP IE */ } vap->iv_dtim_period = se->se_dtimperiod; vap->iv_dtim_count = 0; /* NB: must be after ni_chan is setup */ ieee80211_setup_rates(ni, se->se_rates, se->se_xrates, IEEE80211_F_DOSORT); if (ieee80211_iserp_rateset(&ni->ni_rates)) ni->ni_flags |= IEEE80211_NODE_ERP; /* * Setup HT state for this node if it's available, otherwise * non-STA modes won't pick this state up. * * For IBSS and related modes that don't go through an * association request/response, the only appropriate place * to setup the HT state is here. */ if (ni->ni_ies.htinfo_ie != NULL && ni->ni_ies.htcap_ie != NULL && vap->iv_flags_ht & IEEE80211_FHT_HT) { ieee80211_ht_node_init(ni); ieee80211_ht_updateparams(ni, ni->ni_ies.htcap_ie, ni->ni_ies.htinfo_ie); do_ht = 1; } /* * Setup VHT state for this node if it's available. * Same as the above. * * For now, don't allow 2GHz VHT operation. */ if (ni->ni_ies.vhtopmode_ie != NULL && ni->ni_ies.vhtcap_ie != NULL && vap->iv_vht_flags & IEEE80211_FVHT_VHT) { if (IEEE80211_IS_CHAN_2GHZ(ni->ni_chan)) { printf("%s: BSS %6D: 2GHz channel, VHT info; ignoring\n", __func__, ni->ni_macaddr, ":"); } else { ieee80211_vht_node_init(ni); ieee80211_vht_updateparams(ni, ni->ni_ies.vhtcap_ie, ni->ni_ies.vhtopmode_ie); ieee80211_setup_vht_rates(ni, ni->ni_ies.vhtcap_ie, ni->ni_ies.vhtopmode_ie); do_ht = 1; } } /* Finally do the node channel change */ if (do_ht) { ieee80211_ht_updateparams_final(ni, ni->ni_ies.htcap_ie, ni->ni_ies.htinfo_ie); ieee80211_setup_htrates(ni, ni->ni_ies.htcap_ie, IEEE80211_F_JOIN | IEEE80211_F_DOBRS); ieee80211_setup_basic_htrates(ni, ni->ni_ies.htinfo_ie); } /* XXX else check for ath FF? */ /* XXX QoS? Difficult given that WME config is specific to a master */ ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); return ieee80211_sta_join1(ieee80211_ref_node(ni)); } /* * Leave the specified IBSS/BSS network. The node is assumed to * be passed in with a held reference. */ void ieee80211_sta_leave(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; ic->ic_node_cleanup(ni); ieee80211_notify_node_leave(ni); } /* * Send a deauthenticate frame and drop the station. */ void ieee80211_node_deauth(struct ieee80211_node *ni, int reason) { /* NB: bump the refcnt to be sure temporary nodes are not reclaimed */ ieee80211_ref_node(ni); if (ni->ni_associd != 0) IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, reason); ieee80211_node_leave(ni); ieee80211_free_node(ni); } static struct ieee80211_node * node_alloc(struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct ieee80211_node *ni; ni = (struct ieee80211_node *) IEEE80211_MALLOC(sizeof(struct ieee80211_node), M_80211_NODE, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); return ni; } static int node_init(struct ieee80211_node *ni) { return 0; } /* * Initialize an ie blob with the specified data. If previous * data exists re-use the data block. As a side effect we clear * all references to specific ie's; the caller is required to * recalculate them. */ int ieee80211_ies_init(struct ieee80211_ies *ies, const uint8_t *data, int len) { /* NB: assumes data+len are the last fields */ memset(ies, 0, offsetof(struct ieee80211_ies, data)); if (ies->data != NULL && ies->len != len) { /* data size changed */ IEEE80211_FREE(ies->data, M_80211_NODE_IE); ies->data = NULL; } if (ies->data == NULL) { ies->data = (uint8_t *) IEEE80211_MALLOC(len, M_80211_NODE_IE, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (ies->data == NULL) { ies->len = 0; /* NB: pointers have already been zero'd above */ return 0; } } memcpy(ies->data, data, len); ies->len = len; return 1; } /* * Reclaim storage for an ie blob. */ void ieee80211_ies_cleanup(struct ieee80211_ies *ies) { if (ies->data != NULL) IEEE80211_FREE(ies->data, M_80211_NODE_IE); } /* * Expand an ie blob data contents and to fillin individual * ie pointers. The data blob is assumed to be well-formed; * we don't do any validity checking of ie lengths. */ void ieee80211_ies_expand(struct ieee80211_ies *ies) { uint8_t *ie; int ielen; ie = ies->data; ielen = ies->len; while (ielen > 1) { /* Make sure the given IE length fits into the total length. */ if ((2 + ie[1]) > ielen) { printf("%s: malformed IEs! ies %p { data %p len %d }: " "ie %u len 2+%u > total len left %d\n", __func__, ies, ies->data, ies->len, ie[0], ie[1], ielen); return; } switch (ie[0]) { case IEEE80211_ELEMID_VENDOR: if (iswpaoui(ie)) ies->wpa_ie = ie; else if (iswmeoui(ie)) ies->wme_ie = ie; #ifdef IEEE80211_SUPPORT_SUPERG else if (isatherosoui(ie)) ies->ath_ie = ie; #endif #ifdef IEEE80211_SUPPORT_TDMA else if (istdmaoui(ie)) ies->tdma_ie = ie; #endif break; case IEEE80211_ELEMID_RSN: ies->rsn_ie = ie; break; case IEEE80211_ELEMID_HTCAP: ies->htcap_ie = ie; break; case IEEE80211_ELEMID_HTINFO: ies->htinfo_ie = ie; break; #ifdef IEEE80211_SUPPORT_MESH case IEEE80211_ELEMID_MESHID: ies->meshid_ie = ie; break; #endif case IEEE80211_ELEMID_VHT_CAP: ies->vhtcap_ie = ie; break; case IEEE80211_ELEMID_VHT_OPMODE: ies->vhtopmode_ie = ie; break; case IEEE80211_ELEMID_VHT_PWR_ENV: ies->vhtpwrenv_ie = ie; break; case IEEE80211_ELEMID_BSSLOAD: ies->bssload_ie = ie; break; case IEEE80211_ELEMID_APCHANREP: ies->apchanrep_ie = ie; break; } ielen -= 2 + ie[1]; ie += 2 + ie[1]; } } /* * Reclaim any resources in a node and reset any critical * state. Typically nodes are free'd immediately after, * but in some cases the storage may be reused so we need * to insure consistent state (should probably fix that). */ static void node_cleanup(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; int i; /* NB: preserve ni_table */ if (ni->ni_flags & IEEE80211_NODE_PWR_MGT) { if (vap->iv_opmode != IEEE80211_M_STA) vap->iv_ps_sta--; ni->ni_flags &= ~IEEE80211_NODE_PWR_MGT; IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, "power save mode off, %u sta's in ps mode", vap->iv_ps_sta); } /* * Cleanup any VHT and HT-related state. */ if (ni->ni_flags & IEEE80211_NODE_VHT) ieee80211_vht_node_cleanup(ni); if (ni->ni_flags & IEEE80211_NODE_HT) ieee80211_ht_node_cleanup(ni); #ifdef IEEE80211_SUPPORT_SUPERG /* Always do FF node cleanup; for A-MSDU */ ieee80211_ff_node_cleanup(ni); #endif #ifdef IEEE80211_SUPPORT_MESH /* * Cleanup any mesh-related state. */ if (vap->iv_opmode == IEEE80211_M_MBSS) ieee80211_mesh_node_cleanup(ni); #endif /* * Clear any staging queue entries. */ ieee80211_ageq_drain_node(&ic->ic_stageq, ni); /* * Clear AREF flag that marks the authorization refcnt bump * has happened. This is probably not needed as the node * should always be removed from the table so not found but * do it just in case. * Likewise clear the ASSOCID flag as these flags are intended * to be managed in tandem. */ ni->ni_flags &= ~(IEEE80211_NODE_AREF | IEEE80211_NODE_ASSOCID); /* * Drain power save queue and, if needed, clear TIM. */ if (ieee80211_node_psq_drain(ni) != 0 && vap->iv_set_tim != NULL) vap->iv_set_tim(ni, 0); ni->ni_associd = 0; if (ni->ni_challenge != NULL) { IEEE80211_FREE(ni->ni_challenge, M_80211_NODE); ni->ni_challenge = NULL; } /* * Preserve SSID, WPA, and WME ie's so the bss node is * reusable during a re-auth/re-assoc state transition. * If we remove these data they will not be recreated * because they come from a probe-response or beacon frame * which cannot be expected prior to the association-response. * This should not be an issue when operating in other modes * as stations leaving always go through a full state transition * which will rebuild this state. * * XXX does this leave us open to inheriting old state? */ for (i = 0; i < nitems(ni->ni_rxfrag); i++) if (ni->ni_rxfrag[i] != NULL) { m_freem(ni->ni_rxfrag[i]); ni->ni_rxfrag[i] = NULL; } /* * Must be careful here to remove any key map entry w/o a LOR. */ ieee80211_node_delucastkey(ni); } static void node_free(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; ieee80211_ratectl_node_deinit(ni); ic->ic_node_cleanup(ni); ieee80211_ies_cleanup(&ni->ni_ies); ieee80211_psq_cleanup(&ni->ni_psq); IEEE80211_FREE(ni, M_80211_NODE); } static void node_age(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; /* * Age frames on the power save queue. */ if (ieee80211_node_psq_age(ni) != 0 && ni->ni_psq.psq_len == 0 && vap->iv_set_tim != NULL) vap->iv_set_tim(ni, 0); /* * Age out HT resources (e.g. frames on the * A-MPDU reorder queues). */ if (ni->ni_associd != 0 && (ni->ni_flags & IEEE80211_NODE_HT)) ieee80211_ht_node_age(ni); } static int8_t node_getrssi(const struct ieee80211_node *ni) { uint32_t avgrssi = ni->ni_avgrssi; int32_t rssi; if (avgrssi == IEEE80211_RSSI_DUMMY_MARKER) return 0; rssi = IEEE80211_RSSI_GET(avgrssi); return rssi < 0 ? 0 : rssi > 127 ? 127 : rssi; } static void node_getsignal(const struct ieee80211_node *ni, int8_t *rssi, int8_t *noise) { *rssi = node_getrssi(ni); *noise = ni->ni_noise; } static void node_getmimoinfo(const struct ieee80211_node *ni, struct ieee80211_mimo_info *info) { int i; uint32_t avgrssi; int32_t rssi; bzero(info, sizeof(*info)); for (i = 0; i < MIN(IEEE80211_MAX_CHAINS, ni->ni_mimo_chains); i++) { /* Note: for now, just pri20 channel info */ avgrssi = ni->ni_mimo_rssi_ctl[i]; if (avgrssi == IEEE80211_RSSI_DUMMY_MARKER) { info->ch[i].rssi[0] = 0; } else { rssi = IEEE80211_RSSI_GET(avgrssi); info->ch[i].rssi[0] = rssi < 0 ? 0 : rssi > 127 ? 127 : rssi; } info->ch[i].noise[0] = ni->ni_mimo_noise_ctl[i]; } /* XXX ext radios? */ /* XXX EVM? */ } static void ieee80211_add_node_nt(struct ieee80211_node_table *nt, struct ieee80211_node *ni) { struct ieee80211com *ic = nt->nt_ic; int hash; IEEE80211_NODE_LOCK_ASSERT(nt); hash = IEEE80211_NODE_HASH(ic, ni->ni_macaddr); (void) ic; /* XXX IEEE80211_NODE_HASH */ TAILQ_INSERT_TAIL(&nt->nt_node, ni, ni_list); LIST_INSERT_HEAD(&nt->nt_hash[hash], ni, ni_hash); nt->nt_count++; ni->ni_table = nt; } static void ieee80211_del_node_nt(struct ieee80211_node_table *nt, struct ieee80211_node *ni) { IEEE80211_NODE_LOCK_ASSERT(nt); TAILQ_REMOVE(&nt->nt_node, ni, ni_list); LIST_REMOVE(ni, ni_hash); nt->nt_count--; KASSERT(nt->nt_count >= 0, ("nt_count is negative (%d)!\n", nt->nt_count)); ni->ni_table = NULL; } static struct ieee80211_node * ieee80211_alloc_node(struct ieee80211_node_table *nt, struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func __debrefcnt_used, int line __debrefcnt_used) { struct ieee80211com *ic = nt->nt_ic; struct ieee80211_node *ni; ni = ic->ic_node_alloc(vap, macaddr); if (ni == NULL) { vap->iv_stats.is_rx_nodealloc++; return NULL; } IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "%s %p<%s> in %s table\n", __func__, ni, ether_sprintf(macaddr), nt->nt_name); IEEE80211_ADDR_COPY(ni->ni_macaddr, macaddr); ieee80211_node_initref(ni); /* mark referenced */ #ifdef IEEE80211_DEBUG_REFCNT IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "%s (%s:%u) %p<%s> refcnt %d\n", __func__, func, line, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)); #endif ni->ni_chan = IEEE80211_CHAN_ANYC; ni->ni_authmode = IEEE80211_AUTH_OPEN; ni->ni_txpower = ic->ic_txpowlimit; /* max power */ ni->ni_txparms = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; ieee80211_crypto_resetkey(vap, &ni->ni_ucastkey, IEEE80211_KEYIX_NONE); ni->ni_avgrssi = IEEE80211_RSSI_DUMMY_MARKER; ni->ni_inact_reload = nt->nt_inact_init; ni->ni_inact = ni->ni_inact_reload; ni->ni_ath_defkeyix = 0x7fff; ieee80211_psq_init(&ni->ni_psq, "unknown"); #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) ieee80211_mesh_node_init(vap, ni); #endif IEEE80211_NODE_LOCK(nt); ieee80211_add_node_nt(nt, ni); ni->ni_vap = vap; ni->ni_ic = ic; IEEE80211_NODE_UNLOCK(nt); /* handle failure; free node state */ if (ic->ic_node_init(ni) != 0) { vap->iv_stats.is_rx_nodealloc++; ieee80211_psq_cleanup(&ni->ni_psq); ieee80211_ratectl_node_deinit(ni); __ieee80211_free_node(ni); return NULL; } IEEE80211_NOTE(vap, IEEE80211_MSG_INACT, ni, "%s: inact_reload %u", __func__, ni->ni_inact_reload); return ni; } /* * Craft a temporary node suitable for sending a management frame * to the specified station. We craft only as much state as we * need to do the work since the node will be immediately reclaimed * once the send completes. */ struct ieee80211_node * ieee80211_tmp_node(struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; ni = ic->ic_node_alloc(vap, macaddr); if (ni != NULL) { struct ieee80211_node *bss = vap->iv_bss; IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "%s %p<%s>\n", __func__, ni, ether_sprintf(macaddr)); ni->ni_table = NULL; /* NB: pedantic */ ni->ni_ic = ic; /* NB: needed to set channel */ ni->ni_vap = vap; IEEE80211_ADDR_COPY(ni->ni_macaddr, macaddr); IEEE80211_ADDR_COPY(ni->ni_bssid, bss->ni_bssid); ieee80211_node_initref(ni); /* mark referenced */ #ifdef IEEE80211_DEBUG_REFCNT /* Only one caller so we skip func/line passing into the func. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "%s (%s:%u) %p<%s> refcnt %d\n", __func__, "", -1, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)); #endif /* NB: required by ieee80211_fix_rate */ ieee80211_node_set_chan(ni, bss->ni_chan); ieee80211_crypto_resetkey(vap, &ni->ni_ucastkey, IEEE80211_KEYIX_NONE); ni->ni_txpower = bss->ni_txpower; /* XXX optimize away */ ieee80211_psq_init(&ni->ni_psq, "unknown"); ieee80211_ratectl_node_init(ni); /* handle failure; free node state */ if (ic->ic_node_init(ni) != 0) { vap->iv_stats.is_rx_nodealloc++; ieee80211_psq_cleanup(&ni->ni_psq); ieee80211_ratectl_node_deinit(ni); __ieee80211_free_node(ni); return NULL; } } else { /* XXX msg */ vap->iv_stats.is_rx_nodealloc++; } return ni; } struct ieee80211_node * ieee80211_dup_bss(struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; ni = ieee80211_alloc_node(&ic->ic_sta, vap, macaddr, __func__, __LINE__); if (ni != NULL) { struct ieee80211_node *bss = vap->iv_bss; /* * Inherit from iv_bss. */ copy_bss(ni, bss); IEEE80211_ADDR_COPY(ni->ni_bssid, bss->ni_bssid); ieee80211_node_set_chan(ni, bss->ni_chan); } return ni; } /* * Create a bss node for a legacy WDS vap. The far end does * not associate so we just create create a new node and * simulate an association. The caller is responsible for * installing the node as the bss node and handling any further * setup work like authorizing the port. */ struct ieee80211_node * ieee80211_node_create_wds(struct ieee80211vap *vap, const uint8_t bssid[IEEE80211_ADDR_LEN], struct ieee80211_channel *chan) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; /* XXX check if node already in sta table? */ ni = ieee80211_alloc_node(&ic->ic_sta, vap, bssid, __func__, __LINE__); if (ni != NULL) { ni->ni_wdsvap = vap; IEEE80211_ADDR_COPY(ni->ni_bssid, bssid); /* * Inherit any manually configured settings. */ copy_bss(ni, vap->iv_bss); ieee80211_node_set_chan(ni, chan); /* NB: propagate ssid so available to WPA supplicant */ ni->ni_esslen = vap->iv_des_ssid[0].len; memcpy(ni->ni_essid, vap->iv_des_ssid[0].ssid, ni->ni_esslen); /* NB: no associd for peer */ /* * There are no management frames to use to * discover neighbor capabilities, so blindly * propagate the local configuration. */ if (vap->iv_flags & IEEE80211_F_WME) ni->ni_flags |= IEEE80211_NODE_QOS; #ifdef IEEE80211_SUPPORT_SUPERG if (vap->iv_flags & IEEE80211_F_FF) ni->ni_flags |= IEEE80211_NODE_FF; #endif /* XXX VHT */ if ((ic->ic_htcaps & IEEE80211_HTC_HT) && (vap->iv_flags_ht & IEEE80211_FHT_HT)) { /* * Device is HT-capable and HT is enabled for * the vap; setup HT operation. On return * ni_chan will be adjusted to an HT channel. */ ieee80211_ht_wds_init(ni); if (vap->iv_vht_flags & IEEE80211_FVHT_VHT) { printf("%s: TODO: vht_wds_init\n", __func__); } } else { struct ieee80211_channel *c = ni->ni_chan; /* * Force a legacy channel to be used. */ c = ieee80211_find_channel(ic, c->ic_freq, c->ic_flags &~ IEEE80211_CHAN_HT); KASSERT(c != NULL, ("no legacy channel, %u/%x", ni->ni_chan->ic_freq, ni->ni_chan->ic_flags)); ni->ni_chan = c; } } return ni; } struct ieee80211_node * _ieee80211_find_node_locked(struct ieee80211_node_table *nt, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func __debrefcnt_used, int line __debrefcnt_used) { struct ieee80211_node *ni; int hash; IEEE80211_NODE_LOCK_ASSERT(nt); hash = IEEE80211_NODE_HASH(nt->nt_ic, macaddr); LIST_FOREACH(ni, &nt->nt_hash[hash], ni_hash) { if (IEEE80211_ADDR_EQ(ni->ni_macaddr, macaddr)) { ieee80211_ref_node(ni); /* mark referenced */ #ifdef IEEE80211_DEBUG_REFCNT IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s (%s:%u) %p<%s> refcnt %d\n", __func__, func, line, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)); #endif return ni; } } return NULL; } struct ieee80211_node * _ieee80211_find_node(struct ieee80211_node_table *nt, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func __debrefcnt_used, int line __debrefcnt_used) { struct ieee80211_node *ni; IEEE80211_NODE_LOCK(nt); ni = _ieee80211_find_node_locked(nt, macaddr, func, line); IEEE80211_NODE_UNLOCK(nt); return ni; } struct ieee80211_node * _ieee80211_find_vap_node_locked(struct ieee80211_node_table *nt, const struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func __debrefcnt_used, int line __debrefcnt_used) { struct ieee80211_node *ni; int hash; IEEE80211_NODE_LOCK_ASSERT(nt); hash = IEEE80211_NODE_HASH(nt->nt_ic, macaddr); LIST_FOREACH(ni, &nt->nt_hash[hash], ni_hash) { if (ni->ni_vap == vap && IEEE80211_ADDR_EQ(ni->ni_macaddr, macaddr)) { ieee80211_ref_node(ni); /* mark referenced */ #ifdef IEEE80211_DEBUG_REFCNT IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s (%s:%u) %p<%s> refcnt %d\n", __func__, func, line, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)); #endif return ni; } } return NULL; } struct ieee80211_node * _ieee80211_find_vap_node(struct ieee80211_node_table *nt, const struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func __debrefcnt_used, int line __debrefcnt_used) { struct ieee80211_node *ni; IEEE80211_NODE_LOCK(nt); ni = _ieee80211_find_vap_node_locked(nt, vap, macaddr, func, line); IEEE80211_NODE_UNLOCK(nt); return ni; } /* * Fake up a node; this handles node discovery in adhoc mode. * Note that for the driver's benefit we treat this like * an association so the driver has an opportunity to setup * it's private state. */ struct ieee80211_node * ieee80211_fakeup_adhoc_node(struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct ieee80211_node *ni; IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE | IEEE80211_MSG_ASSOC, "%s: mac<%s>\n", __func__, ether_sprintf(macaddr)); ni = ieee80211_dup_bss(vap, macaddr); if (ni != NULL) { struct ieee80211com *ic = vap->iv_ic; /* XXX no rate negotiation; just dup */ ni->ni_rates = vap->iv_bss->ni_rates; if (ieee80211_iserp_rateset(&ni->ni_rates)) ni->ni_flags |= IEEE80211_NODE_ERP; if (vap->iv_opmode == IEEE80211_M_AHDEMO) { /* * In adhoc demo mode there are no management * frames to use to discover neighbor capabilities, * so blindly propagate the local configuration * so we can do interesting things (e.g. use * WME to disable ACK's). */ /* * XXX TODO: 11n? */ if (vap->iv_flags & IEEE80211_F_WME) ni->ni_flags |= IEEE80211_NODE_QOS; #ifdef IEEE80211_SUPPORT_SUPERG if (vap->iv_flags & IEEE80211_F_FF) ni->ni_flags |= IEEE80211_NODE_FF; #endif } ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); /* * XXX TODO: 11n? At least 20MHz, at least A-MPDU RX, * not A-MPDU TX; not 11n rates, etc. We'll cycle * that after we hear that we can indeed do 11n * (either by a beacon frame or by a probe response.) */ /* * This is the first time we see the node. */ if (ic->ic_newassoc != NULL) ic->ic_newassoc(ni, 1); /* * Kick off a probe request to the given node; * we will then use the probe response to update * 11n/etc configuration state. * * XXX TODO: this isn't guaranteed, and until we get * a probe response, we won't be able to actually * do anything 802.11n related to the node. * So if this does indeed work, maybe we should hold * off on sending responses until we get the probe * response, or just default to some sensible subset * of 802.11n behaviour (eg always allow aggregation * negotiation TO us, but not FROM us, etc) so we * aren't entirely busted. */ if (vap->iv_opmode == IEEE80211_M_IBSS) { ieee80211_send_probereq(ni, /* node */ vap->iv_myaddr, /* SA */ ni->ni_macaddr, /* DA */ vap->iv_bss->ni_bssid, /* BSSID */ vap->iv_bss->ni_essid, vap->iv_bss->ni_esslen); /* SSID */ } /* XXX not right for 802.1x/WPA */ ieee80211_node_authorize(ni); } return ni; } void ieee80211_init_neighbor(struct ieee80211_node *ni, const struct ieee80211_frame *wh, const struct ieee80211_scanparams *sp) { int do_ht_setup = 0, do_vht_setup = 0; ni->ni_esslen = sp->ssid[1]; memcpy(ni->ni_essid, sp->ssid + 2, sp->ssid[1]); IEEE80211_ADDR_COPY(ni->ni_bssid, wh->i_addr3); memcpy(ni->ni_tstamp.data, sp->tstamp, sizeof(ni->ni_tstamp)); ni->ni_intval = sp->bintval; ni->ni_capinfo = sp->capinfo; ni->ni_chan = ni->ni_ic->ic_curchan; ni->ni_fhdwell = sp->fhdwell; ni->ni_fhindex = sp->fhindex; ni->ni_erp = sp->erp; ni->ni_timoff = sp->timoff; #ifdef IEEE80211_SUPPORT_MESH if (ni->ni_vap->iv_opmode == IEEE80211_M_MBSS) ieee80211_mesh_init_neighbor(ni, wh, sp); #endif if (ieee80211_ies_init(&ni->ni_ies, sp->ies, sp->ies_len)) { ieee80211_ies_expand(&ni->ni_ies); if (ni->ni_ies.wme_ie != NULL) ni->ni_flags |= IEEE80211_NODE_QOS; else ni->ni_flags &= ~IEEE80211_NODE_QOS; #ifdef IEEE80211_SUPPORT_SUPERG if (ni->ni_ies.ath_ie != NULL) ieee80211_parse_ath(ni, ni->ni_ies.ath_ie); #endif if (ni->ni_ies.htcap_ie != NULL) ieee80211_parse_htcap(ni, ni->ni_ies.htcap_ie); if (ni->ni_ies.htinfo_ie != NULL) ieee80211_parse_htinfo(ni, ni->ni_ies.htinfo_ie); if (ni->ni_ies.vhtcap_ie != NULL) ieee80211_parse_vhtcap(ni, ni->ni_ies.vhtcap_ie); if (ni->ni_ies.vhtopmode_ie != NULL) ieee80211_parse_vhtopmode(ni, ni->ni_ies.vhtopmode_ie); if ((ni->ni_ies.htcap_ie != NULL) && (ni->ni_ies.htinfo_ie != NULL) && (ni->ni_vap->iv_flags_ht & IEEE80211_FHT_HT)) { do_ht_setup = 1; } if ((ni->ni_ies.vhtcap_ie != NULL) && (ni->ni_ies.vhtopmode_ie != NULL) && (ni->ni_vap->iv_vht_flags & IEEE80211_FVHT_VHT)) { do_vht_setup = 1; } } /* NB: must be after ni_chan is setup */ ieee80211_setup_rates(ni, sp->rates, sp->xrates, IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE | IEEE80211_F_DONEGO | IEEE80211_F_DODEL); /* * If the neighbor is HT compatible, flip that on. */ if (do_ht_setup) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_ASSOC, "%s: doing HT setup\n", __func__); ieee80211_ht_node_init(ni); ieee80211_ht_updateparams(ni, ni->ni_ies.htcap_ie, ni->ni_ies.htinfo_ie); if (do_vht_setup) { if (IEEE80211_IS_CHAN_2GHZ(ni->ni_chan)) { printf("%s: BSS %6D: 2GHz channel, VHT info; ignoring\n", __func__, ni->ni_macaddr, ":"); } else { ieee80211_vht_node_init(ni); ieee80211_vht_updateparams(ni, ni->ni_ies.vhtcap_ie, ni->ni_ies.vhtopmode_ie); ieee80211_setup_vht_rates(ni, ni->ni_ies.vhtcap_ie, ni->ni_ies.vhtopmode_ie); } } /* * Finally do the channel upgrade/change based * on the HT/VHT configuration. */ ieee80211_ht_updateparams_final(ni, ni->ni_ies.htcap_ie, ni->ni_ies.htinfo_ie); ieee80211_setup_htrates(ni, ni->ni_ies.htcap_ie, IEEE80211_F_JOIN | IEEE80211_F_DOBRS); ieee80211_setup_basic_htrates(ni, ni->ni_ies.htinfo_ie); ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); /* Reassociate; we're now 11n/11ac */ /* * XXX TODO: this is the wrong thing to do - * we're calling it with isnew=1 so the ath(4) * driver reinitialises the rate tables. * This "mostly" works for ath(4), but it won't * be right for firmware devices which allocate * node states. * * So, do we just create a new node and delete * the old one? Or? */ if (ni->ni_ic->ic_newassoc) ni->ni_ic->ic_newassoc(ni, 1); } } /* * Do node discovery in adhoc mode on receipt of a beacon * or probe response frame. Note that for the driver's * benefit we treat this like an association so the * driver has an opportunity to setup it's private state. */ struct ieee80211_node * ieee80211_add_neighbor(struct ieee80211vap *vap, const struct ieee80211_frame *wh, const struct ieee80211_scanparams *sp) { struct ieee80211_node *ni; IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, "%s: mac<%s>\n", __func__, ether_sprintf(wh->i_addr2)); ni = ieee80211_dup_bss(vap, wh->i_addr2);/* XXX alloc_node? */ if (ni != NULL) { struct ieee80211com *ic = vap->iv_ic; ieee80211_init_neighbor(ni, wh, sp); if (ieee80211_iserp_rateset(&ni->ni_rates)) ni->ni_flags |= IEEE80211_NODE_ERP; ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); if (ic->ic_newassoc != NULL) ic->ic_newassoc(ni, 1); /* XXX not right for 802.1x/WPA */ ieee80211_node_authorize(ni); } return ni; } #define IS_PROBEREQ(wh) \ ((wh->i_fc[0] & (IEEE80211_FC0_TYPE_MASK|IEEE80211_FC0_SUBTYPE_MASK)) \ == (IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_PROBE_REQ)) #define IS_BCAST_PROBEREQ(wh) \ (IS_PROBEREQ(wh) && IEEE80211_IS_MULTICAST( \ ((const struct ieee80211_frame *)(wh))->i_addr3)) static __inline struct ieee80211_node * _find_rxnode(struct ieee80211_node_table *nt, const struct ieee80211_frame_min *wh, const char *func __debrefcnt_used, int line __debrefcnt_used) { if (IS_BCAST_PROBEREQ(wh)) return NULL; /* spam bcast probe req to all vap's */ return _ieee80211_find_node_locked(nt, wh->i_addr2, func, line); } /* * Locate the node for sender, track state, and then pass the * (referenced) node up to the 802.11 layer for its use. Note * we can return NULL if the sender is not in the table. */ struct ieee80211_node * _ieee80211_find_rxnode(struct ieee80211com *ic, const struct ieee80211_frame_min *wh, const char *func __debrefcnt_used, int line __debrefcnt_used) { struct ieee80211_node_table *nt; struct ieee80211_node *ni; nt = &ic->ic_sta; IEEE80211_NODE_LOCK(nt); ni = _find_rxnode(nt, wh, func, line); IEEE80211_NODE_UNLOCK(nt); return ni; } /* * Like ieee80211_find_rxnode but use the supplied h/w * key index as a hint to locate the node in the key * mapping table. If an entry is present at the key * index we return it; otherwise do a normal lookup and * update the mapping table if the station has a unicast * key assigned to it. */ struct ieee80211_node * _ieee80211_find_rxnode_withkey(struct ieee80211com *ic, const struct ieee80211_frame_min *wh, ieee80211_keyix keyix, const char *func __debrefcnt_used, int line __debrefcnt_used) { struct ieee80211_node_table *nt; struct ieee80211_node *ni; nt = &ic->ic_sta; IEEE80211_NODE_LOCK(nt); if (nt->nt_keyixmap != NULL && keyix < nt->nt_keyixmax) ni = nt->nt_keyixmap[keyix]; else ni = NULL; if (ni == NULL) { ni = _find_rxnode(nt, wh, func, line); if (ni != NULL && nt->nt_keyixmap != NULL) { /* * If the station has a unicast key cache slot * assigned update the key->node mapping table. */ keyix = ni->ni_ucastkey.wk_rxkeyix; /* XXX can keyixmap[keyix] != NULL? */ if (keyix < nt->nt_keyixmax && nt->nt_keyixmap[keyix] == NULL) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s: add key map entry %p<%s> refcnt %d\n", __func__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); nt->nt_keyixmap[keyix] = ieee80211_ref_node(ni); } } } else { if (IS_BCAST_PROBEREQ(wh)) ni = NULL; /* spam bcast probe req to all vap's */ else ieee80211_ref_node(ni); } IEEE80211_NODE_UNLOCK(nt); return ni; } #undef IS_BCAST_PROBEREQ #undef IS_PROBEREQ /* * Return a reference to the appropriate node for sending * a data frame. This handles node discovery in adhoc networks. */ struct ieee80211_node * _ieee80211_find_txnode(struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func __debrefcnt_used, int line __debrefcnt_used) { struct ieee80211_node_table *nt = &vap->iv_ic->ic_sta; struct ieee80211_node *ni; /* * The destination address should be in the node table * unless this is a multicast/broadcast frame. We can * also optimize station mode operation, all frames go * to the bss node. */ /* XXX can't hold lock across dup_bss 'cuz of recursive locking */ IEEE80211_NODE_LOCK(nt); if (vap->iv_opmode == IEEE80211_M_STA || vap->iv_opmode == IEEE80211_M_WDS || IEEE80211_IS_MULTICAST(macaddr)) ni = ieee80211_ref_node(vap->iv_bss); else ni = _ieee80211_find_node_locked(nt, macaddr, func, line); IEEE80211_NODE_UNLOCK(nt); if (ni == NULL) { if (vap->iv_opmode == IEEE80211_M_IBSS || vap->iv_opmode == IEEE80211_M_AHDEMO) { /* * In adhoc mode cons up a node for the destination. * Note that we need an additional reference for the * caller to be consistent with * ieee80211_find_node_locked. */ /* * XXX TODO: this doesn't fake up 11n state; we need * to find another way to get it upgraded. */ ni = ieee80211_fakeup_adhoc_node(vap, macaddr); if (ni != NULL) (void) ieee80211_ref_node(ni); } else { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_OUTPUT, macaddr, "no node, discard frame (%s)", __func__); vap->iv_stats.is_tx_nonode++; } } return ni; } struct ieee80211_node * _ieee80211_ref_node(struct ieee80211_node *ni, const char *func __debrefcnt_used, int line __debrefcnt_used) { #ifdef IEEE80211_DEBUG_REFCNT IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s (%s:%u) %p<%s> refcnt %d\n", __func__, func, line, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); #endif ieee80211_node_incref(ni); return (ni); } static void __ieee80211_free_node(struct ieee80211_node *ni) { struct ieee80211_node_table *nt = ni->ni_table; /* * NB: careful about referencing the vap as it may be * gone if the last reference was held by a driver. * We know the com will always be present so it's safe * to use ni_ic below to reclaim resources. */ #if 0 IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "%s %p<%s> in %s table\n", __func__, ni, ether_sprintf(ni->ni_macaddr), nt != NULL ? nt->nt_name : ""); #endif if (ni->ni_associd != 0) { struct ieee80211vap *vap = ni->ni_vap; if (vap->iv_aid_bitmap != NULL) IEEE80211_AID_CLR(vap, ni->ni_associd); } if (nt != NULL) ieee80211_del_node_nt(nt, ni); ni->ni_ic->ic_node_free(ni); } /* * Clear any entry in the unicast key mapping table. */ static int node_clear_keyixmap(struct ieee80211_node_table *nt, struct ieee80211_node *ni) { ieee80211_keyix keyix; keyix = ni->ni_ucastkey.wk_rxkeyix; if (nt->nt_keyixmap != NULL && keyix < nt->nt_keyixmax && nt->nt_keyixmap[keyix] == ni) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s: %p<%s> clear key map entry %u\n", __func__, ni, ether_sprintf(ni->ni_macaddr), keyix); nt->nt_keyixmap[keyix] = NULL; ieee80211_node_decref(ni); return 1; } return 0; } void _ieee80211_free_node(struct ieee80211_node *ni, const char *func __debrefcnt_used, int line __debrefcnt_used) { struct ieee80211_node_table *nt = ni->ni_table; #ifdef IEEE80211_DEBUG_REFCNT IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s (%s:%u) %p<%s> refcnt %d\n", __func__, func, line, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)-1); #endif if (nt != NULL) { IEEE80211_NODE_LOCK(nt); if (ieee80211_node_dectestref(ni)) { /* * Last reference, reclaim state. */ __ieee80211_free_node(ni); } else if (ieee80211_node_refcnt(ni) == 1) if (node_clear_keyixmap(nt, ni)) __ieee80211_free_node(ni); IEEE80211_NODE_UNLOCK(nt); } else { if (ieee80211_node_dectestref(ni)) __ieee80211_free_node(ni); } } /* * Reclaim a unicast key and clear any key cache state. */ int ieee80211_node_delucastkey(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211_node_table *nt = &ic->ic_sta; struct ieee80211_node *nikey; ieee80211_keyix keyix; int isowned, status; /* * NB: We must beware of LOR here; deleting the key * can cause the crypto layer to block traffic updates * which can generate a LOR against the node table lock; * grab it here and stash the key index for our use below. * * Must also beware of recursion on the node table lock. * When called from node_cleanup we may already have * the node table lock held. Unfortunately there's no * way to separate out this path so we must do this * conditionally. */ isowned = IEEE80211_NODE_IS_LOCKED(nt); if (!isowned) IEEE80211_NODE_LOCK(nt); nikey = NULL; status = 1; /* NB: success */ if (ni->ni_ucastkey.wk_keyix != IEEE80211_KEYIX_NONE) { keyix = ni->ni_ucastkey.wk_rxkeyix; status = ieee80211_crypto_delkey(ni->ni_vap, &ni->ni_ucastkey); if (nt->nt_keyixmap != NULL && keyix < nt->nt_keyixmax) { nikey = nt->nt_keyixmap[keyix]; nt->nt_keyixmap[keyix] = NULL; } } if (!isowned) IEEE80211_NODE_UNLOCK(nt); if (nikey != NULL) { KASSERT(nikey == ni, ("key map out of sync, ni %p nikey %p", ni, nikey)); IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s: delete key map entry %p<%s> refcnt %d\n", __func__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)-1); ieee80211_free_node(ni); } return status; } /* * Reclaim a node. If this is the last reference count then * do the normal free work. Otherwise remove it from the node * table and mark it gone by clearing the back-reference. */ static void node_reclaim(struct ieee80211_node_table *nt, struct ieee80211_node *ni) { IEEE80211_NODE_LOCK_ASSERT(nt); IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_NODE, "%s: remove %p<%s> from %s table, refcnt %d\n", __func__, ni, ether_sprintf(ni->ni_macaddr), nt->nt_name, ieee80211_node_refcnt(ni)-1); /* * Clear any entry in the unicast key mapping table. * We need to do it here so rx lookups don't find it * in the mapping table even if it's not in the hash * table. We cannot depend on the mapping table entry * being cleared because the node may not be free'd. */ (void)node_clear_keyixmap(nt, ni); if (!ieee80211_node_dectestref(ni)) { /* * Other references are present, just remove the * node from the table so it cannot be found. When * the references are dropped storage will be * reclaimed. */ ieee80211_del_node_nt(nt, ni); } else __ieee80211_free_node(ni); } /* * Node table support. */ static void ieee80211_node_table_init(struct ieee80211com *ic, struct ieee80211_node_table *nt, const char *name, int inact, int keyixmax) { nt->nt_ic = ic; IEEE80211_NODE_LOCK_INIT(nt, ic->ic_name); TAILQ_INIT(&nt->nt_node); nt->nt_count = 0; nt->nt_name = name; nt->nt_inact_init = inact; nt->nt_keyixmax = keyixmax; if (nt->nt_keyixmax > 0) { nt->nt_keyixmap = (struct ieee80211_node **) IEEE80211_MALLOC( keyixmax * sizeof(struct ieee80211_node *), M_80211_NODE, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (nt->nt_keyixmap == NULL) ic_printf(ic, "Cannot allocate key index map with %u entries\n", keyixmax); } else nt->nt_keyixmap = NULL; } static void ieee80211_node_table_reset(struct ieee80211_node_table *nt, struct ieee80211vap *match) { struct ieee80211_node *ni, *next; IEEE80211_NODE_LOCK(nt); TAILQ_FOREACH_SAFE(ni, &nt->nt_node, ni_list, next) { if (match != NULL && ni->ni_vap != match) continue; /* XXX can this happen? if so need's work */ if (ni->ni_associd != 0) { struct ieee80211vap *vap = ni->ni_vap; if (vap->iv_auth->ia_node_leave != NULL) vap->iv_auth->ia_node_leave(ni); if (vap->iv_aid_bitmap != NULL) IEEE80211_AID_CLR(vap, ni->ni_associd); } ni->ni_wdsvap = NULL; /* clear reference */ node_reclaim(nt, ni); } if (match != NULL && match->iv_opmode == IEEE80211_M_WDS) { /* * Make a separate pass to clear references to this vap * held by DWDS entries. They will not be matched above * because ni_vap will point to the ap vap but we still * need to clear ni_wdsvap when the WDS vap is destroyed * and/or reset. */ TAILQ_FOREACH_SAFE(ni, &nt->nt_node, ni_list, next) if (ni->ni_wdsvap == match) ni->ni_wdsvap = NULL; } IEEE80211_NODE_UNLOCK(nt); } static void ieee80211_node_table_cleanup(struct ieee80211_node_table *nt) { ieee80211_node_table_reset(nt, NULL); if (nt->nt_keyixmap != NULL) { #ifdef DIAGNOSTIC /* XXX verify all entries are NULL */ int i; for (i = 0; i < nt->nt_keyixmax; i++) if (nt->nt_keyixmap[i] != NULL) printf("%s: %s[%u] still active\n", __func__, nt->nt_name, i); #endif IEEE80211_FREE(nt->nt_keyixmap, M_80211_NODE); nt->nt_keyixmap = NULL; } IEEE80211_NODE_LOCK_DESTROY(nt); } static void timeout_stations(void *arg __unused, struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; /* * Only process stations when in RUN state. This * insures, for example, that we don't timeout an * inactive station during CAC. Note that CSA state * is actually handled in ieee80211_node_timeout as * it applies to more than timeout processing. */ if (vap->iv_state != IEEE80211_S_RUN) return; /* * Ignore entries for which have yet to receive an * authentication frame. These are transient and * will be reclaimed when the last reference to them * goes away (when frame xmits complete). */ if ((vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_STA) && (ni->ni_flags & IEEE80211_NODE_AREF) == 0) return; /* * Free fragment if not needed anymore * (last fragment older than 1s). * XXX doesn't belong here, move to node_age */ if (ni->ni_rxfrag[0] != NULL && ticks > ni->ni_rxfragstamp + hz) { m_freem(ni->ni_rxfrag[0]); ni->ni_rxfrag[0] = NULL; } if (ni->ni_inact > 0) { ni->ni_inact--; IEEE80211_NOTE(vap, IEEE80211_MSG_INACT, ni, "%s: inact %u inact_reload %u nrates %u", __func__, ni->ni_inact, ni->ni_inact_reload, ni->ni_rates.rs_nrates); } /* * Special case ourself; we may be idle for extended periods * of time and regardless reclaiming our state is wrong. * XXX run ic_node_age */ /* XXX before inact decrement? */ if (ni == vap->iv_bss) return; if (ni->ni_associd != 0 || (vap->iv_opmode == IEEE80211_M_IBSS || vap->iv_opmode == IEEE80211_M_AHDEMO)) { /* * Age/drain resources held by the station. */ ic->ic_node_age(ni); /* * Probe the station before time it out. We * send a null data frame which may not be * universally supported by drivers (need it * for ps-poll support so it should be...). * * XXX don't probe the station unless we've * received a frame from them (and have * some idea of the rates they are capable * of); this will get fixed more properly * soon with better handling of the rate set. */ if ((vap->iv_flags_ext & IEEE80211_FEXT_INACT) && (0 < ni->ni_inact && ni->ni_inact <= vap->iv_inact_probe) && ni->ni_rates.rs_nrates != 0) { IEEE80211_NOTE(vap, IEEE80211_MSG_INACT | IEEE80211_MSG_NODE, ni, "%s", "probe station due to inactivity"); /* * Grab a reference so the node cannot * be reclaimed before we send the frame. * ieee80211_send_nulldata understands * we've done this and reclaims the * ref for us as needed. */ /* XXX fix this (not required anymore). */ ieee80211_ref_node(ni); /* XXX useless */ ieee80211_send_nulldata(ni); /* XXX stat? */ return; } } if ((vap->iv_flags_ext & IEEE80211_FEXT_INACT) && ni->ni_inact <= 0) { IEEE80211_NOTE(vap, IEEE80211_MSG_INACT | IEEE80211_MSG_NODE, ni, "station timed out due to inactivity " "(refcnt %u)", ieee80211_node_refcnt(ni)); /* * Send a deauthenticate frame and drop the station. * This is somewhat complicated due to reference counts * and locking. At this point a station will typically * have a reference count of 2. ieee80211_node_leave * will do a "free" of the node which will drop the * reference count. But in the meantime a reference * wil be held by the deauth frame. The actual reclaim * of the node will happen either after the tx is * completed or by ieee80211_node_leave. */ if (ni->ni_associd != 0) { IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_REASON_AUTH_EXPIRE); } ieee80211_node_leave(ni); vap->iv_stats.is_node_timeout++; } } /* * Timeout inactive stations and do related housekeeping. */ static void ieee80211_timeout_stations(struct ieee80211com *ic) { struct ieee80211_node_table *nt = &ic->ic_sta; ieee80211_iterate_nodes(nt, timeout_stations, NULL); } /* * Aggressively reclaim resources. This should be used * only in a critical situation to reclaim mbuf resources. */ void ieee80211_drain(struct ieee80211com *ic) { struct ieee80211_node_table *nt = &ic->ic_sta; struct ieee80211vap *vap; struct ieee80211_node *ni; IEEE80211_NODE_LOCK(nt); TAILQ_FOREACH(ni, &nt->nt_node, ni_list) { /* * Ignore entries for which have yet to receive an * authentication frame. These are transient and * will be reclaimed when the last reference to them * goes away (when frame xmits complete). */ vap = ni->ni_vap; /* * Only process stations when in RUN state. This * insures, for example, that we don't timeout an * inactive station during CAC. Note that CSA state * is actually handled in ieee80211_node_timeout as * it applies to more than timeout processing. */ if (vap->iv_state != IEEE80211_S_RUN) continue; /* XXX can vap be NULL? */ if ((vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_STA) && (ni->ni_flags & IEEE80211_NODE_AREF) == 0) continue; /* * Free fragments. * XXX doesn't belong here, move to node_drain */ if (ni->ni_rxfrag[0] != NULL) { m_freem(ni->ni_rxfrag[0]); ni->ni_rxfrag[0] = NULL; } /* * Drain resources held by the station. */ ic->ic_node_drain(ni); } IEEE80211_NODE_UNLOCK(nt); } /* * Per-ieee80211vap inactivity timer callback. */ static void ieee80211_vap_timeout(struct ieee80211vap *vap) { IEEE80211_LOCK_ASSERT(vap->iv_ic); ieee80211_vap_erp_timeout(vap); ieee80211_ht_timeout(vap); ieee80211_vht_timeout(vap); } /* * Per-ieee80211com inactivity timer callback. */ void ieee80211_node_timeout(void *arg) { struct ieee80211com *ic = arg; struct ieee80211vap *vap; /* * Defer timeout processing if a channel switch is pending. * We typically need to be mute so not doing things that * might generate frames is good to handle in one place. * Suppressing the station timeout processing may extend the * lifetime of inactive stations (by not decrementing their * idle counters) but this should be ok unless the CSA is * active for an unusually long time. */ if ((ic->ic_flags & IEEE80211_F_CSAPENDING) == 0) { ieee80211_scan_timeout(ic); ieee80211_timeout_stations(ic); ieee80211_ageq_age(&ic->ic_stageq, IEEE80211_INACT_WAIT); IEEE80211_LOCK(ic); TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) ieee80211_vap_timeout(vap); IEEE80211_UNLOCK(ic); } callout_reset(&ic->ic_inact, IEEE80211_INACT_WAIT*hz, ieee80211_node_timeout, ic); } /* * The same as ieee80211_iterate_nodes(), but for one vap only. */ int ieee80211_iterate_nodes_vap(struct ieee80211_node_table *nt, struct ieee80211vap *vap, ieee80211_iter_func *f, void *arg) { struct ieee80211_node **ni_arr; struct ieee80211_node *ni; size_t size; int count, i; /* * Iterate over the node table and save an array of ref'ed nodes. * * This is separated out from calling the actual node function so that * no LORs will occur. */ IEEE80211_NODE_LOCK(nt); count = nt->nt_count; size = count * sizeof(struct ieee80211_node *); ni_arr = (struct ieee80211_node **) IEEE80211_MALLOC(size, M_80211_NODE, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); if (ni_arr == NULL) { IEEE80211_NODE_UNLOCK(nt); return (ENOMEM); } i = 0; TAILQ_FOREACH(ni, &nt->nt_node, ni_list) { if (vap != NULL && ni->ni_vap != vap) continue; KASSERT(i < count, ("node array overflow (vap %p, i %d, count %d)\n", vap, i, count)); ni_arr[i] = ieee80211_ref_node(ni); i++; } IEEE80211_NODE_UNLOCK(nt); for (i = 0; i < count; i++) { if (ni_arr[i] == NULL) /* end of the list */ break; (*f)(arg, ni_arr[i]); /* ieee80211_free_node() locks by itself */ ieee80211_free_node(ni_arr[i]); } IEEE80211_FREE(ni_arr, M_80211_NODE); return (0); } /* * Just a wrapper, so we don't have to change every ieee80211_iterate_nodes() * reference in the source. */ void ieee80211_iterate_nodes(struct ieee80211_node_table *nt, ieee80211_iter_func *f, void *arg) { /* XXX no way to pass error to the caller. */ (void) ieee80211_iterate_nodes_vap(nt, NULL, f, arg); } void ieee80211_dump_node(struct ieee80211_node_table *nt __unused, struct ieee80211_node *ni) { printf("%p: mac %s refcnt %d\n", ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)); printf("\tauthmode %u flags 0x%x\n", ni->ni_authmode, ni->ni_flags); printf("\tassocid 0x%x txpower %u vlan %u\n", ni->ni_associd, ni->ni_txpower, ni->ni_vlan); printf("\ttxseq %u rxseq %u fragno %u rxfragstamp %u\n", ni->ni_txseqs[IEEE80211_NONQOS_TID], ni->ni_rxseqs[IEEE80211_NONQOS_TID] >> IEEE80211_SEQ_SEQ_SHIFT, ni->ni_rxseqs[IEEE80211_NONQOS_TID] & IEEE80211_SEQ_FRAG_MASK, ni->ni_rxfragstamp); printf("\trssi %d noise %d intval %u capinfo 0x%x\n", node_getrssi(ni), ni->ni_noise, ni->ni_intval, ni->ni_capinfo); printf("\tbssid %s essid \"%.*s\" channel %u:0x%x\n", ether_sprintf(ni->ni_bssid), ni->ni_esslen, ni->ni_essid, (ni->ni_chan != IEEE80211_CHAN_ANYC) ? ni->ni_chan->ic_freq : 0, (ni->ni_chan != IEEE80211_CHAN_ANYC) ? ni->ni_chan->ic_flags : 0); printf("\tinact %u inact_reload %u txrate %u\n", ni->ni_inact, ni->ni_inact_reload, ni->ni_txrate); printf("\thtcap %x htparam %x htctlchan %u ht2ndchan %u\n", ni->ni_htcap, ni->ni_htparam, ni->ni_htctlchan, ni->ni_ht2ndchan); - printf("\thtopmode %x htstbc %x htchw %u\n", - ni->ni_htopmode, ni->ni_htstbc, ni->ni_chw); + printf("\thtopmode %x htstbc %x htchw %b\n", + ni->ni_htopmode, ni->ni_htstbc, + ni->ni_chw, IEEE80211_NI_CHW_BITS); printf("\tvhtcap %x freq1 %d freq2 %d vhtbasicmcs %x\n", ni->ni_vhtcap, (int) ni->ni_vht_chan1, (int) ni->ni_vht_chan2, (int) ni->ni_vht_basicmcs); /* XXX VHT state */ } void ieee80211_dump_nodes(struct ieee80211_node_table *nt) { ieee80211_iterate_nodes(nt, (ieee80211_iter_func *) ieee80211_dump_node, nt); } /* * Iterate over the VAPs and update their ERP beacon IEs. * * Note this must be called from the deferred ERP update task paths. */ void ieee80211_notify_erp_locked(struct ieee80211com *ic) { struct ieee80211vap *vap; IEEE80211_LOCK_ASSERT(ic); TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) if (vap->iv_opmode == IEEE80211_M_HOSTAP) ieee80211_beacon_notify(vap, IEEE80211_BEACON_ERP); } /* * Handle a station joining an 11g network. */ static void ieee80211_node_join_11g(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; IEEE80211_LOCK_ASSERT(ic); /* * Station isn't capable of short slot time. Bump * the count of long slot time stations and disable * use of short slot time. Note that the actual switch * over to long slot time use may not occur until the * next beacon transmission (per sec. 7.3.1.4 of 11g). */ if ((ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME) == 0) { vap->iv_longslotsta++; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC, ni, "station needs long slot time, count %d", vap->iv_longslotsta); /* * XXX TODO: this may need all VAPs checked! */ if (!IEEE80211_IS_CHAN_108G(ic->ic_bsschan)) { /* * Don't force slot time when switched to turbo * mode as non-ERP stations won't be present; this * need only be done when on the normal G channel. */ ieee80211_vap_set_shortslottime(vap, 0); } } /* * If the new station is not an ERP station * then bump the counter and enable protection * if configured. */ if (!ieee80211_iserp_rateset(&ni->ni_rates)) { vap->iv_nonerpsta++; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC, ni, "station is !ERP, %d non-ERP stations associated", vap->iv_nonerpsta); /* * If station does not support short preamble * then we must enable use of Barker preamble. */ if ((ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE) == 0) { IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC, ni, "%s", "station needs long preamble"); vap->iv_flags |= IEEE80211_F_USEBARKER; vap->iv_flags &= ~IEEE80211_F_SHPREAMBLE; ieee80211_vap_update_preamble(vap); } /* * If protection is configured and this is the first * indication we should use protection, enable it. */ if (vap->iv_protmode != IEEE80211_PROT_NONE && vap->iv_nonerpsta == 1 && (vap->iv_flags_ext & IEEE80211_FEXT_NONERP_PR) == 0) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_ASSOC, "%s: enable use of protection\n", __func__); vap->iv_flags |= IEEE80211_F_USEPROT; ieee80211_vap_update_erp_protmode(vap); } } else ni->ni_flags |= IEEE80211_NODE_ERP; } void ieee80211_node_join(struct ieee80211_node *ni, int resp) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; int newassoc; if (ni->ni_associd == 0) { uint16_t aid; KASSERT(vap->iv_aid_bitmap != NULL, ("no aid bitmap")); /* * It would be good to search the bitmap * more efficiently, but this will do for now. */ for (aid = 1; aid < vap->iv_max_aid; aid++) { if (!IEEE80211_AID_ISSET(vap, aid)) break; } if (aid >= vap->iv_max_aid) { IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_TOOMANY); ieee80211_node_leave(ni); return; } ni->ni_associd = aid | 0xc000; ni->ni_jointime = time_uptime; IEEE80211_LOCK(ic); IEEE80211_AID_SET(vap, ni->ni_associd); vap->iv_sta_assoc++; if (IEEE80211_IS_CHAN_HT(ic->ic_bsschan)) ieee80211_ht_node_join(ni); if (IEEE80211_IS_CHAN_VHT(ic->ic_bsschan)) ieee80211_vht_node_join(ni); if (IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan) && IEEE80211_IS_CHAN_FULL(ic->ic_bsschan)) ieee80211_node_join_11g(ni); IEEE80211_UNLOCK(ic); newassoc = 1; } else newassoc = 0; /* * XXX VHT - should log VHT channel width, etc */ IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_DEBUG, ni, "station associated at aid %d: %s preamble, %s slot time%s%s%s%s%s%s%s%s%s", IEEE80211_NODE_AID(ni), vap->iv_flags & IEEE80211_F_SHPREAMBLE ? "short" : "long", vap->iv_flags & IEEE80211_F_SHSLOT ? "short" : "long", vap->iv_flags & IEEE80211_F_USEPROT ? ", protection" : "", ni->ni_flags & IEEE80211_NODE_QOS ? ", QoS" : "", /* XXX update for VHT string */ ni->ni_flags & IEEE80211_NODE_HT ? - (ni->ni_chw == 40 ? ", HT40" : ", HT20") : "", + (ni->ni_chw == IEEE80211_STA_RX_BW_40 ? ", HT40" : ", HT20") : "", ni->ni_flags & IEEE80211_NODE_AMPDU ? " (+AMPDU)" : "", ni->ni_flags & IEEE80211_NODE_AMSDU ? " (+AMSDU)" : "", ni->ni_flags & IEEE80211_NODE_MIMO_RTS ? " (+SMPS-DYN)" : ni->ni_flags & IEEE80211_NODE_MIMO_PS ? " (+SMPS)" : "", ni->ni_flags & IEEE80211_NODE_RIFS ? " (+RIFS)" : "", IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_FF) ? ", fast-frames" : "", IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_TURBOP) ? ", turbo" : "" ); ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); /* give driver a chance to setup state like ni_txrate */ if (ic->ic_newassoc != NULL) ic->ic_newassoc(ni, newassoc); IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_SUCCESS); /* tell the authenticator about new station */ if (vap->iv_auth->ia_node_join != NULL) vap->iv_auth->ia_node_join(ni); ieee80211_notify_node_join(ni, resp == IEEE80211_FC0_SUBTYPE_ASSOC_RESP); } static void disable_protection(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; KASSERT(vap->iv_nonerpsta == 0 && (vap->iv_flags_ext & IEEE80211_FEXT_NONERP_PR) == 0, ("%d non ERP stations, flags 0x%x", vap->iv_nonerpsta, vap->iv_flags_ext)); vap->iv_flags &= ~IEEE80211_F_USEPROT; /* XXX verify mode? */ if (ic->ic_caps & IEEE80211_C_SHPREAMBLE) { vap->iv_flags |= IEEE80211_F_SHPREAMBLE; vap->iv_flags &= ~IEEE80211_F_USEBARKER; } ieee80211_vap_update_erp_protmode(vap); ieee80211_vap_update_preamble(vap); } /* * Handle a station leaving an 11g network. */ static void ieee80211_node_leave_11g(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; IEEE80211_LOCK_ASSERT(ic); KASSERT(IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan), ("not in 11g, bss %u:0x%x", ic->ic_bsschan->ic_freq, ic->ic_bsschan->ic_flags)); /* * If a long slot station do the slot time bookkeeping. */ if ((ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME) == 0) { KASSERT(vap->iv_longslotsta > 0, ("bogus long slot station count %d", vap->iv_longslotsta)); vap->iv_longslotsta--; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC, ni, "long slot time station leaves, count now %d", vap->iv_longslotsta); /* * XXX TODO: this may need all VAPs checked! */ if (vap->iv_longslotsta == 0) { /* * Re-enable use of short slot time if supported * and not operating in IBSS mode (per spec). */ if ((ic->ic_caps & IEEE80211_C_SHSLOT) && ic->ic_opmode != IEEE80211_M_IBSS) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_ASSOC, "%s: re-enable use of short slot time\n", __func__); ieee80211_vap_set_shortslottime(vap, 1); } } } /* * If a non-ERP station do the protection-related bookkeeping. */ if ((ni->ni_flags & IEEE80211_NODE_ERP) == 0) { KASSERT(vap->iv_nonerpsta > 0, ("bogus non-ERP station count %d", vap->iv_nonerpsta)); vap->iv_nonerpsta--; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_ASSOC, ni, "non-ERP station leaves, count now %d%s", vap->iv_nonerpsta, (vap->iv_flags_ext & IEEE80211_FEXT_NONERP_PR) ? " (non-ERP sta present)" : ""); if (vap->iv_nonerpsta == 0 && (vap->iv_flags_ext & IEEE80211_FEXT_NONERP_PR) == 0) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_ASSOC, "%s: disable use of protection\n", __func__); disable_protection(vap); } } } /* * Time out presence of an overlapping bss with non-ERP * stations. When operating in hostap mode we listen for * beacons from other stations and if we identify a non-ERP * station is present we enable protection. To identify * when all non-ERP stations are gone we time out this * condition. */ static void ieee80211_vap_erp_timeout(struct ieee80211vap *vap) { IEEE80211_LOCK_ASSERT(vap->iv_ic); if ((vap->iv_flags_ext & IEEE80211_FEXT_NONERP_PR) && ieee80211_time_after(ticks, vap->iv_lastnonerp + IEEE80211_NONERP_PRESENT_AGE)) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_ASSOC, "%s", "age out non-ERP sta present on channel"); vap->iv_flags_ext &= ~IEEE80211_FEXT_NONERP_PR; if (vap->iv_nonerpsta == 0) disable_protection(vap); } } /* * Handle bookkeeping for station deauthentication/disassociation * when operating as an ap. */ void ieee80211_node_leave(struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_node_table *nt = ni->ni_table; IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_DEBUG, ni, "station with aid %d leaves", IEEE80211_NODE_AID(ni)); KASSERT(vap->iv_opmode != IEEE80211_M_STA, ("unexpected operating mode %u", vap->iv_opmode)); /* * If node wasn't previously associated all * we need to do is reclaim the reference. */ /* XXX ibss mode bypasses 11g and notification */ if (ni->ni_associd == 0) goto done; /* * Tell the authenticator the station is leaving. * Note that we must do this before yanking the * association id as the authenticator uses the * associd to locate it's state block. */ if (vap->iv_auth->ia_node_leave != NULL) vap->iv_auth->ia_node_leave(ni); IEEE80211_LOCK(ic); IEEE80211_AID_CLR(vap, ni->ni_associd); vap->iv_sta_assoc--; if (IEEE80211_IS_CHAN_VHT(ic->ic_bsschan)) ieee80211_vht_node_leave(ni); if (IEEE80211_IS_CHAN_HT(ic->ic_bsschan)) ieee80211_ht_node_leave(ni); if (IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan) && IEEE80211_IS_CHAN_FULL(ic->ic_bsschan)) ieee80211_node_leave_11g(ni); IEEE80211_UNLOCK(ic); /* * Cleanup station state. In particular clear various * state that might otherwise be reused if the node * is reused before the reference count goes to zero * (and memory is reclaimed). */ ieee80211_sta_leave(ni); done: /* * Remove the node from any table it's recorded in and * drop the caller's reference. Removal from the table * is important to insure the node is not reprocessed * for inactivity. */ if (nt != NULL) { IEEE80211_NODE_LOCK(nt); node_reclaim(nt, ni); IEEE80211_NODE_UNLOCK(nt); } else ieee80211_free_node(ni); } struct rssiinfo { int rssi_samples; uint32_t rssi_total; }; static void get_hostap_rssi(void *arg, struct ieee80211_node *ni) { struct rssiinfo *info = arg; struct ieee80211vap *vap = ni->ni_vap; int8_t rssi; /* only associated stations */ if (ni->ni_associd == 0) return; rssi = vap->iv_ic->ic_node_getrssi(ni); if (rssi != 0) { info->rssi_samples++; info->rssi_total += rssi; } } static void get_adhoc_rssi(void *arg, struct ieee80211_node *ni) { struct rssiinfo *info = arg; struct ieee80211vap *vap = ni->ni_vap; int8_t rssi; /* only neighbors */ /* XXX check bssid */ if ((ni->ni_capinfo & IEEE80211_CAPINFO_IBSS) == 0) return; rssi = vap->iv_ic->ic_node_getrssi(ni); if (rssi != 0) { info->rssi_samples++; info->rssi_total += rssi; } } #ifdef IEEE80211_SUPPORT_MESH static void get_mesh_rssi(void *arg, struct ieee80211_node *ni) { struct rssiinfo *info = arg; struct ieee80211vap *vap = ni->ni_vap; int8_t rssi; /* only neighbors that peered successfully */ if (ni->ni_mlstate != IEEE80211_NODE_MESH_ESTABLISHED) return; rssi = vap->iv_ic->ic_node_getrssi(ni); if (rssi != 0) { info->rssi_samples++; info->rssi_total += rssi; } } #endif /* IEEE80211_SUPPORT_MESH */ int8_t ieee80211_getrssi(struct ieee80211vap *vap) { #define NZ(x) ((x) == 0 ? 1 : (x)) struct ieee80211com *ic = vap->iv_ic; struct rssiinfo info; info.rssi_total = 0; info.rssi_samples = 0; switch (vap->iv_opmode) { case IEEE80211_M_IBSS: /* average of all ibss neighbors */ case IEEE80211_M_AHDEMO: /* average of all neighbors */ ieee80211_iterate_nodes_vap(&ic->ic_sta, vap, get_adhoc_rssi, &info); break; case IEEE80211_M_HOSTAP: /* average of all associated stations */ ieee80211_iterate_nodes_vap(&ic->ic_sta, vap, get_hostap_rssi, &info); break; #ifdef IEEE80211_SUPPORT_MESH case IEEE80211_M_MBSS: /* average of all mesh neighbors */ ieee80211_iterate_nodes_vap(&ic->ic_sta, vap, get_mesh_rssi, &info); break; #endif case IEEE80211_M_MONITOR: /* XXX */ case IEEE80211_M_STA: /* use stats from associated ap */ default: if (vap->iv_bss != NULL) info.rssi_total = ic->ic_node_getrssi(vap->iv_bss); info.rssi_samples = 1; break; } return info.rssi_total / NZ(info.rssi_samples); #undef NZ } void ieee80211_getsignal(struct ieee80211vap *vap, int8_t *rssi, int8_t *noise) { if (vap->iv_bss == NULL) /* NB: shouldn't happen */ return; vap->iv_ic->ic_node_getsignal(vap->iv_bss, rssi, noise); /* for non-station mode return avg'd rssi accounting */ if (vap->iv_opmode != IEEE80211_M_STA) *rssi = ieee80211_getrssi(vap); } diff --git a/sys/net80211/ieee80211_node.h b/sys/net80211/ieee80211_node.h index 954e8e03563f..1f36ceb368b9 100644 --- a/sys/net80211/ieee80211_node.h +++ b/sys/net80211/ieee80211_node.h @@ -1,470 +1,489 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * 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. */ #ifndef _NET80211_IEEE80211_NODE_H_ #define _NET80211_IEEE80211_NODE_H_ #include /* for ieee80211_nodestats */ #include /* for aggregation state */ /* * Each ieee80211com instance has a single timer that fires every * IEEE80211_INACT_WAIT seconds to handle "inactivity processing". * This is used to do node inactivity processing when operating * as an AP, adhoc or mesh mode. For inactivity processing each node * has a timeout set in its ni_inact field that is decremented * on each timeout and the node is reclaimed when the counter goes * to zero. We use different inactivity timeout values depending * on whether the node is associated and authorized (either by * 802.1x or open/shared key authentication) or associated but yet * to be authorized. The latter timeout is shorter to more aggressively * reclaim nodes that leave part way through the 802.1x exchange. */ #define IEEE80211_INACT_WAIT 15 /* inactivity interval (secs) */ #define IEEE80211_INACT_INIT (30/IEEE80211_INACT_WAIT) /* initial */ #define IEEE80211_INACT_AUTH (180/IEEE80211_INACT_WAIT) /* associated but not authorized */ #define IEEE80211_INACT_RUN (300/IEEE80211_INACT_WAIT) /* authorized */ #define IEEE80211_INACT_PROBE (30/IEEE80211_INACT_WAIT) /* probe */ #define IEEE80211_INACT_SCAN (300/IEEE80211_INACT_WAIT) /* scanned */ #define IEEE80211_TRANS_WAIT 2 /* mgt frame tx timer (secs) */ /* threshold for aging overlapping non-ERP bss */ #define IEEE80211_NONERP_PRESENT_AGE msecs_to_ticks(60*1000) #define IEEE80211_NODE_HASHSIZE 32 /* NB: hash size must be pow2 */ /* simple hash is enough for variation of macaddr */ #define IEEE80211_NODE_HASH(ic, addr) \ (((const uint8_t *)(addr))[IEEE80211_ADDR_LEN - 1] % \ IEEE80211_NODE_HASHSIZE) struct ieee80211_node_table; struct ieee80211com; struct ieee80211vap; struct ieee80211_scanparams; /* * Information element (IE) ``blob''. We use this structure * to capture management frame payloads that need to be * retained. Information elements within the payload that * we need to consult have references recorded. */ struct ieee80211_ies { /* the following are either NULL or point within data */ uint8_t *wpa_ie; /* captured WPA ie */ uint8_t *rsn_ie; /* captured RSN ie */ uint8_t *wme_ie; /* captured WME ie */ uint8_t *ath_ie; /* captured Atheros ie */ uint8_t *htcap_ie; /* captured HTCAP ie */ uint8_t *htinfo_ie; /* captured HTINFO ie */ uint8_t *tdma_ie; /* captured TDMA ie */ uint8_t *meshid_ie; /* captured MESH ID ie */ uint8_t *vhtcap_ie; /* captured VHTCAP ie */ uint8_t *vhtopmode_ie; /* captured VHTOPMODE ie */ uint8_t *vhtpwrenv_ie; /* captured VHTPWRENV ie */ uint8_t *apchanrep_ie; /* captured APCHANREP ie */ uint8_t *bssload_ie; /* captured BSSLOAD ie */ uint8_t *spare[4]; /* NB: these must be the last members of this structure */ uint8_t *data; /* frame data > 802.11 header */ int len; /* data size in bytes */ }; /* * 802.11s (Mesh) Peer Link FSM state. */ enum ieee80211_mesh_mlstate { IEEE80211_NODE_MESH_IDLE = 0, IEEE80211_NODE_MESH_OPENSNT = 1, /* open frame sent */ IEEE80211_NODE_MESH_OPENRCV = 2, /* open frame received */ IEEE80211_NODE_MESH_CONFIRMRCV = 3, /* confirm frame received */ IEEE80211_NODE_MESH_ESTABLISHED = 4, /* link established */ IEEE80211_NODE_MESH_HOLDING = 5, /* link closing */ }; #define IEEE80211_MESH_MLSTATE_BITS \ "\20\1IDLE\2OPENSNT\2OPENRCV\3CONFIRMRCV\4ESTABLISHED\5HOLDING" +/* + * This structure is shared with LinuxKPI 802.11 code describing up-to + * which channel width the station can receive. + * Rather than using hardcoded MHz values for the channel width use an enum with + * flags. This allows us to keep the uint8_t slot for ni_chw in + * struct ieee80211_node and means we do not have to sync to the value for + * LinuxKPI. + */ +enum ieee80211_sta_rx_bw { + IEEE80211_STA_RX_BW_20 = 0x01, + IEEE80211_STA_RX_BW_40 = 0x02, + IEEE80211_STA_RX_BW_80 = 0x04, + IEEE80211_STA_RX_BW_160 = 0x08, + IEEE80211_STA_RX_BW_320 = 0x10, +} __packed; + +#define IEEE80211_NI_CHW_BITS \ + "\20\1BW_20\2BW_40\3BW_80\4BW_160\5BW_320" + /* * Node specific information. Note that drivers are expected * to derive from this structure to add device-specific per-node * state. This is done by overriding the ic_node_* methods in * the ieee80211com structure. */ struct ieee80211_node { struct ieee80211vap *ni_vap; /* associated vap */ struct ieee80211com *ni_ic; /* copy from vap to save deref*/ struct ieee80211_node_table *ni_table; /* NB: may be NULL */ TAILQ_ENTRY(ieee80211_node) ni_list; /* list of all nodes */ LIST_ENTRY(ieee80211_node) ni_hash; /* hash collision list */ u_int ni_refcnt; /* count of held references */ u_int ni_flags; #define IEEE80211_NODE_AUTH 0x000001 /* authorized for data */ #define IEEE80211_NODE_QOS 0x000002 /* QoS enabled */ #define IEEE80211_NODE_ERP 0x000004 /* ERP enabled */ /* NB: this must have the same value as IEEE80211_FC1_PWR_MGT */ #define IEEE80211_NODE_PWR_MGT 0x000010 /* power save mode enabled */ #define IEEE80211_NODE_AREF 0x000020 /* authentication ref held */ #define IEEE80211_NODE_HT 0x000040 /* HT enabled */ #define IEEE80211_NODE_HTCOMPAT 0x000080 /* HT setup w/ vendor OUI's */ #define IEEE80211_NODE_WPS 0x000100 /* WPS association */ #define IEEE80211_NODE_TSN 0x000200 /* TSN association */ #define IEEE80211_NODE_AMPDU_RX 0x000400 /* AMPDU rx enabled */ #define IEEE80211_NODE_AMPDU_TX 0x000800 /* AMPDU tx enabled */ #define IEEE80211_NODE_MIMO_PS 0x001000 /* MIMO power save enabled */ #define IEEE80211_NODE_MIMO_RTS 0x002000 /* send RTS in MIMO PS */ #define IEEE80211_NODE_RIFS 0x004000 /* RIFS enabled */ #define IEEE80211_NODE_SGI20 0x008000 /* Short GI in HT20 enabled */ #define IEEE80211_NODE_SGI40 0x010000 /* Short GI in HT40 enabled */ #define IEEE80211_NODE_ASSOCID 0x020000 /* xmit requires associd */ #define IEEE80211_NODE_AMSDU_RX 0x040000 /* AMSDU rx enabled */ #define IEEE80211_NODE_AMSDU_TX 0x080000 /* AMSDU tx enabled */ #define IEEE80211_NODE_VHT 0x100000 /* VHT enabled */ #define IEEE80211_NODE_LDPC 0x200000 /* LDPC enabled */ #define IEEE80211_NODE_UAPSD 0x400000 /* U-APSD power save enabled */ uint16_t ni_associd; /* association ID */ uint16_t ni_vlan; /* vlan tag */ uint16_t ni_txpower; /* current transmit power */ uint8_t ni_authmode; /* authentication algorithm */ uint8_t ni_ath_flags; /* Atheros feature flags */ /* NB: These must have the same values as IEEE80211_ATHC_* */ #define IEEE80211_NODE_TURBOP 0x0001 /* Turbo prime enable */ #define IEEE80211_NODE_COMP 0x0002 /* Compresssion enable */ #define IEEE80211_NODE_FF 0x0004 /* Fast Frame capable */ #define IEEE80211_NODE_XR 0x0008 /* Atheros WME enable */ #define IEEE80211_NODE_AR 0x0010 /* AR capable */ #define IEEE80211_NODE_BOOST 0x0080 /* Dynamic Turbo boosted */ uint16_t ni_ath_defkeyix;/* Atheros def key index */ const struct ieee80211_txparam *ni_txparms; uint32_t ni_jointime; /* time of join (secs) */ uint32_t *ni_challenge; /* shared-key challenge */ struct ieee80211_ies ni_ies; /* captured ie's */ /* tx seq per-tid */ ieee80211_seq ni_txseqs[IEEE80211_TID_SIZE]; /* rx seq previous per-tid*/ ieee80211_seq ni_rxseqs[IEEE80211_TID_SIZE]; uint32_t ni_rxfragstamp; /* time stamp of last rx frag */ struct mbuf *ni_rxfrag[3]; /* rx frag reassembly */ struct ieee80211_key ni_ucastkey; /* unicast key */ /* hardware */ uint32_t ni_avgrssi; /* recv ssi state */ int8_t ni_noise; /* noise floor */ /* mimo statistics */ uint32_t ni_mimo_rssi_ctl[IEEE80211_MAX_CHAINS]; uint32_t ni_mimo_rssi_ext[IEEE80211_MAX_CHAINS]; uint8_t ni_mimo_noise_ctl[IEEE80211_MAX_CHAINS]; uint8_t ni_mimo_noise_ext[IEEE80211_MAX_CHAINS]; uint8_t ni_mimo_chains; /* header */ uint8_t ni_macaddr[IEEE80211_ADDR_LEN]; uint8_t ni_bssid[IEEE80211_ADDR_LEN]; /* beacon, probe response */ union { uint8_t data[8]; u_int64_t tsf; } ni_tstamp; /* from last rcv'd beacon */ uint16_t ni_intval; /* beacon interval */ uint16_t ni_capinfo; /* capabilities */ uint8_t ni_esslen; uint8_t ni_essid[IEEE80211_NWID_LEN]; struct ieee80211_rateset ni_rates; /* negotiated rate set */ struct ieee80211_channel *ni_chan; uint16_t ni_fhdwell; /* FH only */ uint8_t ni_fhindex; /* FH only */ uint16_t ni_erp; /* ERP from beacon/probe resp */ uint16_t ni_timoff; /* byte offset to TIM ie */ uint8_t ni_dtim_period; /* DTIM period */ uint8_t ni_dtim_count; /* DTIM count for last bcn */ /* 11s state */ uint8_t ni_meshidlen; uint8_t ni_meshid[IEEE80211_MESHID_LEN]; enum ieee80211_mesh_mlstate ni_mlstate; /* peering management state */ uint16_t ni_mllid; /* link local ID */ uint16_t ni_mlpid; /* link peer ID */ struct callout ni_mltimer; /* link mesh timer */ uint8_t ni_mlrcnt; /* link mesh retry counter */ uint8_t ni_mltval; /* link mesh timer value */ struct callout ni_mlhtimer; /* link mesh backoff timer */ uint8_t ni_mlhcnt; /* link mesh holding counter */ /* 11n state */ uint16_t ni_htcap; /* HT capabilities */ uint8_t ni_htparam; /* HT params */ uint8_t ni_htctlchan; /* HT control channel */ uint8_t ni_ht2ndchan; /* HT 2nd channel */ uint8_t ni_htopmode; /* HT operating mode */ uint8_t ni_htstbc; /* HT */ - uint8_t ni_chw; /* negotiated channel width */ + enum ieee80211_sta_rx_bw ni_chw; /* negotiated channel width */ struct ieee80211_htrateset ni_htrates; /* negotiated ht rate set */ struct ieee80211_tx_ampdu ni_tx_ampdu[WME_NUM_TID]; struct ieee80211_rx_ampdu ni_rx_ampdu[WME_NUM_TID]; /* VHT state */ uint32_t ni_vhtcap; uint16_t ni_vht_basicmcs; uint16_t ni_vht_pad2; struct ieee80211_vht_mcs_info ni_vht_mcsinfo; uint8_t ni_vht_chan1; /* 20/40/80/160 - VHT chan1 */ uint8_t ni_vht_chan2; /* 80+80 - VHT chan2 */ uint8_t ni_vht_chanwidth; /* IEEE80211_VHT_CHANWIDTH_ */ uint8_t ni_vht_pad1; uint32_t ni_vht_spare[8]; /* fast-frames state */ struct mbuf * ni_tx_superg[WME_NUM_TID]; /* others */ short ni_inact; /* inactivity mark count */ short ni_inact_reload;/* inactivity reload value */ int ni_txrate; /* legacy rate/MCS */ struct ieee80211_psq ni_psq; /* power save queue */ struct ieee80211_nodestats ni_stats; /* per-node statistics */ struct ieee80211vap *ni_wdsvap; /* associated WDS vap */ void *ni_rctls; /* private ratectl state */ /* quiet time IE state for the given node */ uint32_t ni_quiet_ie_set; /* Quiet time IE was seen */ struct ieee80211_quiet_ie ni_quiet_ie; /* last seen quiet IE */ /* U-APSD */ uint8_t ni_uapsd; /* U-APSD per-node flags matching WMM STA QoS Info field */ void *ni_drv_data; /* driver specific */ uint64_t ni_spare[3]; }; MALLOC_DECLARE(M_80211_NODE); MALLOC_DECLARE(M_80211_NODE_IE); #define IEEE80211_NODE_ATH (IEEE80211_NODE_FF | IEEE80211_NODE_TURBOP) #define IEEE80211_NODE_AMPDU \ (IEEE80211_NODE_AMPDU_RX | IEEE80211_NODE_AMPDU_TX) #define IEEE80211_NODE_AMSDU \ (IEEE80211_NODE_AMSDU_RX | IEEE80211_NODE_AMSDU_TX) #define IEEE80211_NODE_HT_ALL \ (IEEE80211_NODE_HT | IEEE80211_NODE_HTCOMPAT | \ IEEE80211_NODE_AMPDU | IEEE80211_NODE_AMSDU | \ IEEE80211_NODE_MIMO_PS | IEEE80211_NODE_MIMO_RTS | \ IEEE80211_NODE_RIFS | IEEE80211_NODE_SGI20 | IEEE80211_NODE_SGI40) #define IEEE80211_NODE_BITS \ "\20\1AUTH\2QOS\3ERP\5PWR_MGT\6AREF\7HT\10HTCOMPAT\11WPS\12TSN" \ "\13AMPDU_RX\14AMPDU_TX\15MIMO_PS\16MIMO_RTS\17RIFS\20SGI20\21SGI40" \ "\22ASSOCID" #define IEEE80211_NODE_AID(ni) IEEE80211_AID(ni->ni_associd) #define IEEE80211_NODE_STAT(ni,stat) (ni->ni_stats.ns_##stat++) #define IEEE80211_NODE_STAT_ADD(ni,stat,v) (ni->ni_stats.ns_##stat += v) #define IEEE80211_NODE_STAT_SET(ni,stat,v) (ni->ni_stats.ns_##stat = v) /* * Filtered rssi calculation support. The receive rssi is maintained * as an average over the last 10 frames received using a low pass filter * (all frames for now, possibly need to be more selective). Calculations * are designed such that a good compiler can optimize them. The avg * rssi state should be initialized to IEEE80211_RSSI_DUMMY_MARKER and * each sample incorporated with IEEE80211_RSSI_LPF. Use IEEE80211_RSSI_GET * to extract the current value. * * Note that we assume rssi data are in the range [-127..127] and we * discard values <-20. This is consistent with assumptions throughout * net80211 that signal strength data are in .5 dBm units relative to * the current noise floor (linear, not log). */ #define IEEE80211_RSSI_LPF_LEN 10 #define IEEE80211_RSSI_DUMMY_MARKER 127 /* NB: pow2 to optimize out * and / */ #define IEEE80211_RSSI_EP_MULTIPLIER (1<<7) #define IEEE80211_RSSI_IN(x) ((x) * IEEE80211_RSSI_EP_MULTIPLIER) #define _IEEE80211_RSSI_LPF(x, y, len) \ (((x) != IEEE80211_RSSI_DUMMY_MARKER) ? (((x) * ((len) - 1) + (y)) / (len)) : (y)) #define IEEE80211_RSSI_LPF(x, y) do { \ if ((y) >= -20) { \ x = _IEEE80211_RSSI_LPF((x), IEEE80211_RSSI_IN((y)), \ IEEE80211_RSSI_LPF_LEN); \ } \ } while (0) #define IEEE80211_RSSI_EP_RND(x, mul) \ ((((x) % (mul)) >= ((mul)/2)) ? ((x) + ((mul) - 1)) / (mul) : (x)/(mul)) #define IEEE80211_RSSI_GET(x) \ IEEE80211_RSSI_EP_RND(x, IEEE80211_RSSI_EP_MULTIPLIER) void ieee80211_node_attach(struct ieee80211com *); void ieee80211_node_lateattach(struct ieee80211com *); void ieee80211_node_detach(struct ieee80211com *); void ieee80211_node_vattach(struct ieee80211vap *); void ieee80211_node_latevattach(struct ieee80211vap *); void ieee80211_node_vdetach(struct ieee80211vap *); static __inline int ieee80211_node_is_authorized(const struct ieee80211_node *ni) { return (ni->ni_flags & IEEE80211_NODE_AUTH); } void ieee80211_node_authorize(struct ieee80211_node *); void ieee80211_node_unauthorize(struct ieee80211_node *); void ieee80211_node_setuptxparms(struct ieee80211_node *); void ieee80211_node_set_chan(struct ieee80211_node *, struct ieee80211_channel *); void ieee80211_create_ibss(struct ieee80211vap*, struct ieee80211_channel *); void ieee80211_reset_bss(struct ieee80211vap *); void ieee80211_sync_curchan(struct ieee80211com *); void ieee80211_setupcurchan(struct ieee80211com *, struct ieee80211_channel *); void ieee80211_setcurchan(struct ieee80211com *, struct ieee80211_channel *); void ieee80211_update_chw(struct ieee80211com *); int ieee80211_ibss_merge_check(struct ieee80211_node *); int ieee80211_ibss_node_check_new(struct ieee80211_node *ni, const struct ieee80211_scanparams *); int ieee80211_ibss_merge(struct ieee80211_node *); struct ieee80211_scan_entry; int ieee80211_sta_join(struct ieee80211vap *, struct ieee80211_channel *, const struct ieee80211_scan_entry *); void ieee80211_sta_leave(struct ieee80211_node *); void ieee80211_node_deauth(struct ieee80211_node *, int); int ieee80211_ies_init(struct ieee80211_ies *, const uint8_t *, int); void ieee80211_ies_cleanup(struct ieee80211_ies *); void ieee80211_ies_expand(struct ieee80211_ies *); #define ieee80211_ies_setie(_ies, _ie, _off) do { \ (_ies)._ie = (_ies).data + (_off); \ } while (0) /* * Table of ieee80211_node instances. Each ieee80211com * has one that holds association stations (when operating * as an ap) or neighbors (in ibss mode). * * XXX embed this in ieee80211com instead of indirect? */ struct ieee80211_node_table { struct ieee80211com *nt_ic; /* back reference */ ieee80211_node_lock_t nt_nodelock; /* on node table */ TAILQ_HEAD(, ieee80211_node) nt_node; /* information of all nodes */ LIST_HEAD(, ieee80211_node) nt_hash[IEEE80211_NODE_HASHSIZE]; int nt_count; /* number of nodes */ struct ieee80211_node **nt_keyixmap; /* key ix -> node map */ int nt_keyixmax; /* keyixmap size */ const char *nt_name; /* table name for debug msgs */ int nt_inact_init; /* initial node inact setting */ }; struct ieee80211_node *ieee80211_tmp_node(struct ieee80211vap *, const uint8_t macaddr[IEEE80211_ADDR_LEN]); struct ieee80211_node *ieee80211_dup_bss(struct ieee80211vap *, const uint8_t macaddr[IEEE80211_ADDR_LEN]); struct ieee80211_node *ieee80211_node_create_wds(struct ieee80211vap *, const uint8_t bssid[IEEE80211_ADDR_LEN], struct ieee80211_channel *); /* These functions are taking __func__, __LINE__ for IEEE80211_DEBUG_REFCNT */ struct ieee80211_node *_ieee80211_ref_node(struct ieee80211_node *, const char *func, int line); void _ieee80211_free_node(struct ieee80211_node *, const char *func, int line); struct ieee80211_node *_ieee80211_find_node_locked( struct ieee80211_node_table *, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func, int line); struct ieee80211_node *_ieee80211_find_node(struct ieee80211_node_table *, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func, int line); struct ieee80211_node *_ieee80211_find_vap_node_locked( struct ieee80211_node_table *, const struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func, int line); struct ieee80211_node *_ieee80211_find_vap_node( struct ieee80211_node_table *, const struct ieee80211vap *vap, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func, int line); struct ieee80211_node *_ieee80211_find_rxnode(struct ieee80211com *, const struct ieee80211_frame_min *, const char *func, int line); struct ieee80211_node *_ieee80211_find_rxnode_withkey( struct ieee80211com *, const struct ieee80211_frame_min *, uint16_t keyix, const char *func, int line); struct ieee80211_node *_ieee80211_find_txnode(struct ieee80211vap *, const uint8_t macaddr[IEEE80211_ADDR_LEN], const char *func, int line); #define ieee80211_ref_node(ni) \ _ieee80211_ref_node(ni, __func__, __LINE__) #define ieee80211_free_node(ni) \ _ieee80211_free_node(ni, __func__, __LINE__) #define ieee80211_find_node_locked(nt, mac) \ _ieee80211_find_node_locked(nt, mac, __func__, __LINE__) #define ieee80211_find_node(nt, mac) \ _ieee80211_find_node(nt, mac, __func__, __LINE__) #define ieee80211_find_vap_node_locked(nt, vap, mac) \ _ieee80211_find_vap_node_locked(nt, vap, mac, __func__, __LINE__) #define ieee80211_find_vap_node(nt, vap, mac) \ _ieee80211_find_vap_node(nt, vap, mac, __func__, __LINE__) #define ieee80211_find_rxnode(ic, wh) \ _ieee80211_find_rxnode(ic, wh, __func__, __LINE__) #define ieee80211_find_rxnode_withkey(ic, wh, keyix) \ _ieee80211_find_rxnode_withkey(ic, wh, keyix, __func__, __LINE__) #define ieee80211_find_txnode(vap, mac) \ _ieee80211_find_txnode(vap, mac, __func__, __LINE__) int ieee80211_node_delucastkey(struct ieee80211_node *); void ieee80211_node_timeout(void *arg); typedef void ieee80211_iter_func(void *, struct ieee80211_node *); int ieee80211_iterate_nodes_vap(struct ieee80211_node_table *, struct ieee80211vap *, ieee80211_iter_func *, void *); void ieee80211_iterate_nodes(struct ieee80211_node_table *, ieee80211_iter_func *, void *); void ieee80211_notify_erp_locked(struct ieee80211com *); void ieee80211_dump_node(struct ieee80211_node_table *, struct ieee80211_node *); void ieee80211_dump_nodes(struct ieee80211_node_table *); struct ieee80211_node *ieee80211_fakeup_adhoc_node(struct ieee80211vap *, const uint8_t macaddr[IEEE80211_ADDR_LEN]); struct ieee80211_scanparams; void ieee80211_init_neighbor(struct ieee80211_node *, const struct ieee80211_frame *, const struct ieee80211_scanparams *); struct ieee80211_node *ieee80211_add_neighbor(struct ieee80211vap *, const struct ieee80211_frame *, const struct ieee80211_scanparams *); void ieee80211_node_join(struct ieee80211_node *,int); void ieee80211_node_leave(struct ieee80211_node *); int8_t ieee80211_getrssi(struct ieee80211vap *); void ieee80211_getsignal(struct ieee80211vap *, int8_t *, int8_t *); #endif /* _NET80211_IEEE80211_NODE_H_ */ diff --git a/sys/net80211/ieee80211_sta.c b/sys/net80211/ieee80211_sta.c index 97ed52295d6d..ca06546abdb9 100644 --- a/sys/net80211/ieee80211_sta.c +++ b/sys/net80211/ieee80211_sta.c @@ -1,2065 +1,2065 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2007-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. */ /* * IEEE 802.11 Station mode support. */ #include "opt_inet.h" #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 #ifdef IEEE80211_SUPPORT_SUPERG #include #endif #include #include #include #define IEEE80211_RATE2MBS(r) (((r) & IEEE80211_RATE_VAL) / 2) static void sta_vattach(struct ieee80211vap *); static void sta_beacon_miss(struct ieee80211vap *); static int sta_newstate(struct ieee80211vap *, enum ieee80211_state, int); static int sta_input(struct ieee80211_node *, struct mbuf *, const struct ieee80211_rx_stats *, int, int); static void sta_recv_mgmt(struct ieee80211_node *, struct mbuf *, int subtype, const struct ieee80211_rx_stats *, int rssi, int nf); static void sta_recv_ctl(struct ieee80211_node *, struct mbuf *, int subtype); void ieee80211_sta_attach(struct ieee80211com *ic) { ic->ic_vattach[IEEE80211_M_STA] = sta_vattach; } void ieee80211_sta_detach(struct ieee80211com *ic) { } static void sta_vdetach(struct ieee80211vap *vap) { } static void sta_vattach(struct ieee80211vap *vap) { vap->iv_newstate = sta_newstate; vap->iv_input = sta_input; vap->iv_recv_mgmt = sta_recv_mgmt; vap->iv_recv_ctl = sta_recv_ctl; vap->iv_opdetach = sta_vdetach; vap->iv_bmiss = sta_beacon_miss; } /* * Handle a beacon miss event. The common code filters out * spurious events that can happen when scanning and/or before * reaching RUN state. */ static void sta_beacon_miss(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; IEEE80211_LOCK_ASSERT(ic); KASSERT((ic->ic_flags & IEEE80211_F_SCAN) == 0, ("scanning")); KASSERT(vap->iv_state >= IEEE80211_S_RUN, ("wrong state %s", ieee80211_state_name[vap->iv_state])); IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE | IEEE80211_MSG_DEBUG, "beacon miss, mode %s state %s\n", ieee80211_opmode_name[vap->iv_opmode], ieee80211_state_name[vap->iv_state]); if (vap->iv_state == IEEE80211_S_CSA) { /* * A Channel Switch is pending; assume we missed the * beacon that would've completed the process and just * force the switch. If we made a mistake we'll not * find the AP on the new channel and fall back to a * normal scan. */ ieee80211_csa_completeswitch(ic); return; } if (++vap->iv_bmiss_count < vap->iv_bmiss_max) { /* * Send a directed probe req before falling back to a * scan; if we receive a response ic_bmiss_count will * be reset. Some cards mistakenly report beacon miss * so this avoids the expensive scan if the ap is * still there. */ ieee80211_send_probereq(vap->iv_bss, vap->iv_myaddr, vap->iv_bss->ni_bssid, vap->iv_bss->ni_bssid, vap->iv_bss->ni_essid, vap->iv_bss->ni_esslen); return; } callout_stop(&vap->iv_swbmiss); vap->iv_bmiss_count = 0; vap->iv_stats.is_beacon_miss++; if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) { #ifdef IEEE80211_SUPPORT_SUPERG /* * If we receive a beacon miss interrupt when using * dynamic turbo, attempt to switch modes before * reassociating. */ if (IEEE80211_ATH_CAP(vap, vap->iv_bss, IEEE80211_NODE_TURBOP)) ieee80211_dturbo_switch(vap, ic->ic_bsschan->ic_flags ^ IEEE80211_CHAN_TURBO); #endif /* * Try to reassociate before scanning for a new ap. */ ieee80211_new_state(vap, IEEE80211_S_ASSOC, 1); } else { /* * Somebody else is controlling state changes (e.g. * a user-mode app) don't do anything that would * confuse them; just drop into scan mode so they'll * notified of the state change and given control. */ ieee80211_new_state(vap, IEEE80211_S_SCAN, 0); } } /* * Handle deauth with reason. We retry only for * the cases where we might succeed. Otherwise * we downgrade the ap and scan. */ static void sta_authretry(struct ieee80211vap *vap, struct ieee80211_node *ni, int reason) { switch (reason) { case IEEE80211_STATUS_SUCCESS: /* NB: MLME assoc */ case IEEE80211_STATUS_TIMEOUT: case IEEE80211_REASON_ASSOC_EXPIRE: case IEEE80211_REASON_NOT_AUTHED: case IEEE80211_REASON_NOT_ASSOCED: case IEEE80211_REASON_ASSOC_LEAVE: case IEEE80211_REASON_ASSOC_NOT_AUTHED: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, 1); break; default: ieee80211_scan_assoc_fail(vap, vap->iv_bss->ni_macaddr, reason); if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) ieee80211_check_scan_current(vap); break; } } static void sta_swbmiss_start(struct ieee80211vap *vap) { if (vap->iv_flags_ext & IEEE80211_FEXT_SWBMISS) { /* * Start s/w beacon miss timer for devices w/o * hardware support. We fudge a bit here since * we're doing this in software. */ vap->iv_swbmiss_period = IEEE80211_TU_TO_TICKS( 2 * vap->iv_bmissthreshold * vap->iv_bss->ni_intval); vap->iv_swbmiss_count = 0; callout_reset(&vap->iv_swbmiss, vap->iv_swbmiss_period, ieee80211_swbmiss, vap); } } /* * IEEE80211_M_STA vap state machine handler. * This routine handles the main states in the 802.11 protocol. */ static int sta_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; enum ieee80211_state ostate; IEEE80211_LOCK_ASSERT(ic); ostate = vap->iv_state; IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s -> %s (%d)\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate], arg); vap->iv_state = nstate; /* state transition */ callout_stop(&vap->iv_mgtsend); /* XXX callout_drain */ if (ostate != IEEE80211_S_SCAN) ieee80211_cancel_scan(vap); /* background scan */ ni = vap->iv_bss; /* NB: no reference held */ if (vap->iv_flags_ext & IEEE80211_FEXT_SWBMISS) callout_stop(&vap->iv_swbmiss); switch (nstate) { case IEEE80211_S_INIT: switch (ostate) { case IEEE80211_S_SLEEP: /* XXX wakeup */ /* XXX driver hook to wakeup the hardware? */ case IEEE80211_S_RUN: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DISASSOC, IEEE80211_REASON_ASSOC_LEAVE); ieee80211_sta_leave(ni); break; case IEEE80211_S_ASSOC: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_REASON_AUTH_LEAVE); break; case IEEE80211_S_SCAN: ieee80211_cancel_scan(vap); break; default: break; } if (ostate != IEEE80211_S_INIT) { /* NB: optimize INIT -> INIT case */ ieee80211_reset_bss(vap); } if (vap->iv_auth->ia_detach != NULL) vap->iv_auth->ia_detach(vap); break; case IEEE80211_S_SCAN: switch (ostate) { case IEEE80211_S_INIT: /* * Initiate a scan. We can come here as a result * of an IEEE80211_IOC_SCAN_REQ too in which case * the vap will be marked with IEEE80211_FEXT_SCANREQ * and the scan request parameters will be present * in iv_scanreq. Otherwise we do the default. */ if (vap->iv_flags_ext & IEEE80211_FEXT_SCANREQ) { ieee80211_check_scan(vap, vap->iv_scanreq_flags, vap->iv_scanreq_duration, vap->iv_scanreq_mindwell, vap->iv_scanreq_maxdwell, vap->iv_scanreq_nssid, vap->iv_scanreq_ssid); vap->iv_flags_ext &= ~IEEE80211_FEXT_SCANREQ; } else ieee80211_check_scan_current(vap); break; case IEEE80211_S_SCAN: case IEEE80211_S_AUTH: case IEEE80211_S_ASSOC: /* * These can happen either because of a timeout * on an assoc/auth response or because of a * change in state that requires a reset. For * the former we're called with a non-zero arg * that is the cause for the failure; pass this * to the scan code so it can update state. * Otherwise trigger a new scan unless we're in * manual roaming mode in which case an application * must issue an explicit scan request. */ if (arg != 0) ieee80211_scan_assoc_fail(vap, vap->iv_bss->ni_macaddr, arg); if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) ieee80211_check_scan_current(vap); break; case IEEE80211_S_SLEEP: /* beacon miss */ /* * XXX if in sleep we need to wakeup the hardware. */ /* FALLTHROUGH */ case IEEE80211_S_RUN: /* beacon miss */ /* * Beacon miss. Notify user space and if not * under control of a user application (roaming * manual) kick off a scan to re-connect. */ ieee80211_sta_leave(ni); if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) ieee80211_check_scan_current(vap); break; default: goto invalid; } break; case IEEE80211_S_AUTH: switch (ostate) { case IEEE80211_S_INIT: case IEEE80211_S_SCAN: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, 1); break; case IEEE80211_S_AUTH: case IEEE80211_S_ASSOC: switch (arg & 0xff) { case IEEE80211_FC0_SUBTYPE_AUTH: /* ??? */ IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, 2); break; case IEEE80211_FC0_SUBTYPE_DEAUTH: sta_authretry(vap, ni, arg>>8); break; } break; case IEEE80211_S_SLEEP: case IEEE80211_S_RUN: switch (arg & 0xff) { case IEEE80211_FC0_SUBTYPE_AUTH: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, 2); vap->iv_state = IEEE80211_S_RUN; /* stay RUN */ break; case IEEE80211_FC0_SUBTYPE_DEAUTH: ieee80211_sta_leave(ni); if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) { /* try to reauth */ IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, 1); } break; } break; default: goto invalid; } break; case IEEE80211_S_ASSOC: switch (ostate) { case IEEE80211_S_AUTH: case IEEE80211_S_ASSOC: IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_ASSOC_REQ, 0); break; case IEEE80211_S_SLEEP: /* cannot happen */ case IEEE80211_S_RUN: ieee80211_sta_leave(ni); if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) { IEEE80211_SEND_MGMT(ni, arg ? IEEE80211_FC0_SUBTYPE_REASSOC_REQ : IEEE80211_FC0_SUBTYPE_ASSOC_REQ, 0); } break; default: goto invalid; } break; case IEEE80211_S_RUN: if (vap->iv_flags & IEEE80211_F_WPA) { /* XXX validate prerequisites */ } switch (ostate) { case IEEE80211_S_RUN: case IEEE80211_S_CSA: break; case IEEE80211_S_AUTH: /* when join is done in fw */ case IEEE80211_S_ASSOC: #ifdef IEEE80211_DEBUG if (ieee80211_msg_debug(vap)) { ieee80211_note(vap, "%s with %s ssid ", (vap->iv_opmode == IEEE80211_M_STA ? "associated" : "synchronized"), ether_sprintf(ni->ni_bssid)); ieee80211_print_essid(vap->iv_bss->ni_essid, ni->ni_esslen); /* XXX MCS/HT */ printf(" channel %d start %uMb\n", ieee80211_chan2ieee(ic, ic->ic_curchan), IEEE80211_RATE2MBS(ni->ni_txrate)); } #endif ieee80211_scan_assoc_success(vap, ni->ni_macaddr); ieee80211_notify_node_join(ni, arg == IEEE80211_FC0_SUBTYPE_ASSOC_RESP); break; case IEEE80211_S_SLEEP: /* Wake up from sleep */ vap->iv_sta_ps(vap, 0); break; default: goto invalid; } ieee80211_sync_curchan(ic); if (ostate != IEEE80211_S_RUN) sta_swbmiss_start(vap); /* * When 802.1x is not in use mark the port authorized * at this point so traffic can flow. */ if (ni->ni_authmode != IEEE80211_AUTH_8021X) ieee80211_node_authorize(ni); /* * Fake association when joining an existing bss. * * Don't do this if we're doing SLEEP->RUN. */ if (ic->ic_newassoc != NULL && ostate != IEEE80211_S_SLEEP) ic->ic_newassoc(vap->iv_bss, (ostate != IEEE80211_S_RUN)); break; case IEEE80211_S_CSA: if (ostate != IEEE80211_S_RUN) goto invalid; break; case IEEE80211_S_SLEEP: sta_swbmiss_start(vap); vap->iv_sta_ps(vap, 1); break; default: invalid: IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: unexpected state transition %s -> %s\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate]); break; } return 0; } /* * Return non-zero if the frame is an echo of a multicast * frame sent by ourself. The dir is known to be DSTODS. */ static __inline int isdstods_mcastecho(struct ieee80211vap *vap, const struct ieee80211_frame *wh) { #define QWH4(wh) ((const struct ieee80211_qosframe_addr4 *)wh) #define WH4(wh) ((const struct ieee80211_frame_addr4 *)wh) const uint8_t *sa; KASSERT(vap->iv_opmode == IEEE80211_M_STA, ("wrong mode")); if (!IEEE80211_IS_MULTICAST(wh->i_addr3)) return 0; sa = IEEE80211_QOS_HAS_SEQ(wh) ? QWH4(wh)->i_addr4 : WH4(wh)->i_addr4; return IEEE80211_ADDR_EQ(sa, vap->iv_myaddr); #undef WH4 #undef QWH4 } /* * Return non-zero if the frame is an echo of a multicast * frame sent by ourself. The dir is known to be FROMDS. */ static __inline int isfromds_mcastecho(struct ieee80211vap *vap, const struct ieee80211_frame *wh) { KASSERT(vap->iv_opmode == IEEE80211_M_STA, ("wrong mode")); if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) return 0; return IEEE80211_ADDR_EQ(wh->i_addr3, vap->iv_myaddr); } /* * Decide if a received management frame should be * printed when debugging is enabled. This filters some * of the less interesting frames that come frequently * (e.g. beacons). */ static __inline int doprint(struct ieee80211vap *vap, int subtype) { switch (subtype) { case IEEE80211_FC0_SUBTYPE_BEACON: return (vap->iv_ic->ic_flags & IEEE80211_F_SCAN); case IEEE80211_FC0_SUBTYPE_PROBE_REQ: return 0; } return 1; } /* * Process a received frame. The node associated with the sender * should be supplied. If nothing was found in the node table then * the caller is assumed to supply a reference to iv_bss instead. * The RSSI and a timestamp are also supplied. The RSSI data is used * during AP scanning to select a AP to associate with; it can have * any units so long as values have consistent units and higher values * mean ``better signal''. The receive timestamp is currently not used * by the 802.11 layer. */ static int sta_input(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_rx_stats *rxs, int rssi, int nf) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ifnet *ifp = vap->iv_ifp; struct ieee80211_frame *wh; struct ieee80211_key *key; struct ether_header *eh; int hdrspace, need_tap = 1; /* mbuf need to be tapped. */ uint8_t dir, type, subtype, qos; uint8_t *bssid; int is_hw_decrypted = 0; int has_decrypted = 0; KASSERT(ni != NULL, ("%s: null node, mbuf %p", __func__, m)); /* Early init in case of early error case. */ type = -1; /* * Bit of a cheat here, we use a pointer for a 3-address * frame format but don't reference fields past outside * ieee80211_frame_min (or other shorter frames) w/o first * validating the data is present. */ wh = mtod(m, struct ieee80211_frame *); if (m->m_pkthdr.len < 2 || m->m_pkthdr.len < ieee80211_anyhdrsize(wh)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "too short (1): len %u", m->m_pkthdr.len); vap->iv_stats.is_rx_tooshort++; goto err; } if (!IEEE80211_IS_FC0_CHECK_VER(wh, IEEE80211_FC0_VERSION_0)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "wrong version, fc %02x:%02x", wh->i_fc[0], wh->i_fc[1]); vap->iv_stats.is_rx_badversion++; goto err; } /* * Some devices do hardware decryption all the way through * to pretending the frame wasn't encrypted in the first place. * So, tag it appropriately so it isn't discarded inappropriately. */ if ((rxs != NULL) && (rxs->c_pktflags & IEEE80211_RX_F_DECRYPTED)) is_hw_decrypted = 1; if (m->m_flags & M_AMPDU_MPDU) { /* * Fastpath for A-MPDU reorder q resubmission. Frames * w/ M_AMPDU_MPDU marked have already passed through * here but were received out of order and been held on * the reorder queue. When resubmitted they are marked * with the M_AMPDU_MPDU flag and we can bypass most of * the normal processing. */ type = IEEE80211_FC0_TYPE_DATA; dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK; subtype = IEEE80211_FC0_SUBTYPE_QOS_DATA; hdrspace = ieee80211_hdrspace(ic, wh); /* XXX optimize? */ goto resubmit_ampdu; } ni->ni_inact = ni->ni_inact_reload; dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK; type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; /* * Control frames are not folowing the header scheme of data and mgmt * frames so we do not apply extra checks here. * We probably should do checks on RA (+TA) where available for those * too, but for now do not drop them. */ if (type != IEEE80211_FC0_TYPE_CTL && (ic->ic_flags & IEEE80211_F_SCAN) == 0) { bssid = wh->i_addr2; if (!IEEE80211_ADDR_EQ(bssid, ni->ni_bssid)) { /* not interested in */ IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, bssid, NULL, "%s", "not to bss"); vap->iv_stats.is_rx_wrongbss++; goto out; } /* * Some devices may be in a promiscuous mode * where they receive frames for multiple station * addresses. * * If we receive a data frame that isn't * destined to our VAP MAC, drop it. * * XXX TODO: This is only enforced when not scanning; * XXX it assumes a software-driven scan will put the NIC * XXX into a "no data frames" mode before setting this * XXX flag. Otherwise it may be possible that we'll still * XXX process data frames whilst scanning. */ if ((! IEEE80211_IS_MULTICAST(wh->i_addr1)) && (! IEEE80211_ADDR_EQ(wh->i_addr1, IF_LLADDR(ifp)))) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, bssid, NULL, "not to cur sta: lladdr=%6D, addr1=%6D", IF_LLADDR(ifp), ":", wh->i_addr1, ":"); vap->iv_stats.is_rx_wrongbss++; goto out; } IEEE80211_RSSI_LPF(ni->ni_avgrssi, rssi); ni->ni_noise = nf; if ( IEEE80211_HAS_SEQ(type, subtype) && !IEEE80211_IS_MULTICAST(wh->i_addr1)) { uint8_t tid = ieee80211_gettid(wh); if (IEEE80211_QOS_HAS_SEQ(wh) && TID_TO_WME_AC(tid) >= WME_AC_VI) ic->ic_wme.wme_hipri_traffic++; if (! ieee80211_check_rxseq(ni, wh, bssid, rxs)) goto out; } } switch (type) { case IEEE80211_FC0_TYPE_DATA: hdrspace = ieee80211_hdrspace(ic, wh); if (m->m_len < hdrspace && (m = m_pullup(m, hdrspace)) == NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "data too short: expecting %u", hdrspace); vap->iv_stats.is_rx_tooshort++; goto out; /* XXX */ } /* * Handle A-MPDU re-ordering. If the frame is to be * processed directly then ieee80211_ampdu_reorder * will return 0; otherwise it has consumed the mbuf * and we should do nothing more with it. */ if ((m->m_flags & M_AMPDU) && (dir == IEEE80211_FC1_DIR_FROMDS || dir == IEEE80211_FC1_DIR_DSTODS) && ieee80211_ampdu_reorder(ni, m, rxs) != 0) { m = NULL; goto out; } resubmit_ampdu: if (dir == IEEE80211_FC1_DIR_FROMDS) { if ((ifp->if_flags & IFF_SIMPLEX) && isfromds_mcastecho(vap, wh)) { /* * In IEEE802.11 network, multicast * packets sent from "me" are broadcast * from the AP; silently discard for * SIMPLEX interface. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "data", "%s", "multicast echo"); vap->iv_stats.is_rx_mcastecho++; goto out; } if ((vap->iv_flags & IEEE80211_F_DWDS) && IEEE80211_IS_MULTICAST(wh->i_addr1)) { /* * DWDS sta's must drop 3-address mcast frames * as they will be sent separately as a 4-addr * frame. Accepting the 3-addr frame will * confuse the bridge into thinking the sending * sta is located at the end of WDS link. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "3-address data", "%s", "DWDS enabled"); vap->iv_stats.is_rx_mcastecho++; goto out; } } else if (dir == IEEE80211_FC1_DIR_DSTODS) { if ((vap->iv_flags & IEEE80211_F_DWDS) == 0) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "4-address data", "%s", "DWDS not enabled"); vap->iv_stats.is_rx_wrongdir++; goto out; } if ((ifp->if_flags & IFF_SIMPLEX) && isdstods_mcastecho(vap, wh)) { /* * In IEEE802.11 network, multicast * packets sent from "me" are broadcast * from the AP; silently discard for * SIMPLEX interface. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "4-address data", "%s", "multicast echo"); vap->iv_stats.is_rx_mcastecho++; goto out; } } else { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "data", "incorrect dir 0x%x", dir); vap->iv_stats.is_rx_wrongdir++; goto out; } /* * Handle privacy requirements for hardware decryption * devices. * * For those devices, a handful of things happen. * * + If IV has been stripped, then we can't run * ieee80211_crypto_decap() - none of the key * + If MIC has been stripped, we can't validate * MIC here. * + If MIC fails, then we need to communicate a * MIC failure up to the stack - but we don't know * which key was used. */ /* * Handle privacy requirements. Note that we * must not be preempted from here until after * we (potentially) call ieee80211_crypto_demic; * otherwise we may violate assumptions in the * crypto cipher modules used to do delayed update * of replay sequence numbers. */ if (is_hw_decrypted || IEEE80211_IS_PROTECTED(wh)) { if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) { /* * Discard encrypted frames when privacy is off. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "WEP", "%s", "PRIVACY off"); vap->iv_stats.is_rx_noprivacy++; IEEE80211_NODE_STAT(ni, rx_noprivacy); goto out; } if (ieee80211_crypto_decap(ni, m, hdrspace, &key) == 0) { /* NB: stats+msgs handled in crypto_decap */ IEEE80211_NODE_STAT(ni, rx_wepfail); goto out; } wh = mtod(m, struct ieee80211_frame *); wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED; has_decrypted = 1; } else { /* XXX M_WEP and IEEE80211_F_PRIVACY */ key = NULL; } /* * Save QoS bits for use below--before we strip the header. */ if (subtype == IEEE80211_FC0_SUBTYPE_QOS_DATA) qos = ieee80211_getqos(wh)[0]; else qos = 0; /* * Next up, any fragmentation. */ if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { m = ieee80211_defrag(ni, m, hdrspace, has_decrypted); if (m == NULL) { /* Fragment dropped or frame not complete yet */ goto out; } } wh = NULL; /* no longer valid, catch any uses */ /* * Next strip any MSDU crypto bits. * * Note: we can't do MIC stripping/verification if the * upper layer has stripped it. We have to check MIC * ourselves. So, key may be NULL, but we have to check * the RX status. */ if (!ieee80211_crypto_demic(vap, key, m, 0)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, ni->ni_macaddr, "data", "%s", "demic error"); vap->iv_stats.is_rx_demicfail++; IEEE80211_NODE_STAT(ni, rx_demicfail); goto out; } /* copy to listener after decrypt */ if (ieee80211_radiotap_active_vap(vap)) ieee80211_radiotap_rx(vap, m); need_tap = 0; /* * Finally, strip the 802.11 header. */ m = ieee80211_decap(vap, m, hdrspace, qos); if (m == NULL) { /* XXX mask bit to check for both */ /* don't count Null data frames as errors */ if (subtype == IEEE80211_FC0_SUBTYPE_NODATA || subtype == IEEE80211_FC0_SUBTYPE_QOS_NULL) goto out; IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, ni->ni_macaddr, "data", "%s", "decap error"); vap->iv_stats.is_rx_decap++; IEEE80211_NODE_STAT(ni, rx_decap); goto err; } if (!(qos & IEEE80211_QOS_AMSDU)) eh = mtod(m, struct ether_header *); else eh = NULL; if (!ieee80211_node_is_authorized(ni)) { /* * Deny any non-PAE frames received prior to * authorization. For open/shared-key * authentication the port is mark authorized * after authentication completes. For 802.1x * the port is not marked authorized by the * authenticator until the handshake has completed. */ if (eh == NULL || eh->ether_type != htons(ETHERTYPE_PAE)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, ni->ni_macaddr, "data", "unauthorized or " "unknown port: ether type 0x%x len %u", eh == NULL ? -1 : eh->ether_type, m->m_pkthdr.len); vap->iv_stats.is_rx_unauth++; IEEE80211_NODE_STAT(ni, rx_unauth); goto err; } } else { /* * When denying unencrypted frames, discard * any non-PAE frames received without encryption. */ if ((vap->iv_flags & IEEE80211_F_DROPUNENC) && ((has_decrypted == 0) && (m->m_flags & M_WEP) == 0) && (is_hw_decrypted == 0) && (eh == NULL || eh->ether_type != htons(ETHERTYPE_PAE))) { /* * Drop unencrypted frames. */ vap->iv_stats.is_rx_unencrypted++; IEEE80211_NODE_STAT(ni, rx_unencrypted); goto out; } } /* XXX require HT? */ if (qos & IEEE80211_QOS_AMSDU) { m = ieee80211_decap_amsdu(ni, m); if (m == NULL) return IEEE80211_FC0_TYPE_DATA; } else { #ifdef IEEE80211_SUPPORT_SUPERG m = ieee80211_decap_fastframe(vap, ni, m); if (m == NULL) return IEEE80211_FC0_TYPE_DATA; #endif } ieee80211_deliver_data(vap, ni, m); return IEEE80211_FC0_TYPE_DATA; case IEEE80211_FC0_TYPE_MGT: vap->iv_stats.is_rx_mgmt++; IEEE80211_NODE_STAT(ni, rx_mgmt); if (dir != IEEE80211_FC1_DIR_NODS) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "data", "incorrect dir 0x%x", dir); vap->iv_stats.is_rx_wrongdir++; goto err; } if (m->m_pkthdr.len < sizeof(struct ieee80211_frame)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, "mgt", "too short: len %u", m->m_pkthdr.len); vap->iv_stats.is_rx_tooshort++; goto out; } #ifdef IEEE80211_DEBUG if ((ieee80211_msg_debug(vap) && doprint(vap, subtype)) || ieee80211_msg_dumppkts(vap)) { if_printf(ifp, "received %s from %s rssi %d\n", ieee80211_mgt_subtype_name(subtype), ether_sprintf(wh->i_addr2), rssi); } #endif /* * Note: See above for hardware offload privacy requirements. * It also applies here. */ /* * Again, having encrypted flag set check would be good, but * then we have to also handle crypto_decap() like above. */ if (IEEE80211_IS_PROTECTED(wh)) { if (subtype != IEEE80211_FC0_SUBTYPE_AUTH) { /* * Only shared key auth frames with a challenge * should be encrypted, discard all others. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, ieee80211_mgt_subtype_name(subtype), "%s", "WEP set but not permitted"); vap->iv_stats.is_rx_mgtdiscard++; /* XXX */ goto out; } if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) { /* * Discard encrypted frames when privacy is off. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "mgt", "%s", "WEP set but PRIVACY off"); vap->iv_stats.is_rx_noprivacy++; goto out; } hdrspace = ieee80211_hdrspace(ic, wh); /* * Again, if IV/MIC was stripped, then this whole * setup will fail. That's going to need some poking. */ if (ieee80211_crypto_decap(ni, m, hdrspace, &key) == 0) { /* NB: stats+msgs handled in crypto_decap */ goto out; } has_decrypted = 1; wh = mtod(m, struct ieee80211_frame *); wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED; } vap->iv_recv_mgmt(ni, m, subtype, rxs, rssi, nf); goto out; case IEEE80211_FC0_TYPE_CTL: vap->iv_stats.is_rx_ctl++; IEEE80211_NODE_STAT(ni, rx_ctrl); vap->iv_recv_ctl(ni, m, subtype); goto out; default: IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, NULL, "bad frame type 0x%x", type); /* should not come here */ break; } err: if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); out: if (m != NULL) { if (need_tap && ieee80211_radiotap_active_vap(vap)) ieee80211_radiotap_rx(vap, m); m_freem(m); } return type; } static void sta_auth_open(struct ieee80211_node *ni, struct ieee80211_frame *wh, int rssi, int nf, uint16_t seq, uint16_t status) { struct ieee80211vap *vap = ni->ni_vap; if (ni->ni_authmode == IEEE80211_AUTH_SHARED) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "open auth", "bad sta auth mode %u", ni->ni_authmode); vap->iv_stats.is_rx_bad_auth++; /* XXX */ return; } if (vap->iv_state != IEEE80211_S_AUTH || seq != IEEE80211_AUTH_OPEN_RESPONSE) { vap->iv_stats.is_rx_bad_auth++; return; } if (status != 0) { IEEE80211_NOTE(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, ni, "open auth failed (reason %d)", status); vap->iv_stats.is_rx_auth_fail++; vap->iv_stats.is_rx_authfail_code = status; ieee80211_new_state(vap, IEEE80211_S_SCAN, IEEE80211_SCAN_FAIL_STATUS); } else ieee80211_new_state(vap, IEEE80211_S_ASSOC, 0); } static void sta_auth_shared(struct ieee80211_node *ni, struct ieee80211_frame *wh, uint8_t *frm, uint8_t *efrm, int rssi, int nf, uint16_t seq, uint16_t status) { struct ieee80211vap *vap = ni->ni_vap; uint8_t *challenge; /* * NB: this can happen as we allow pre-shared key * authentication to be enabled w/o wep being turned * on so that configuration of these can be done * in any order. It may be better to enforce the * ordering in which case this check would just be * for sanity/consistency. */ if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "%s", " PRIVACY is disabled"); goto bad; } /* * Pre-shared key authentication is evil; accept * it only if explicitly configured (it is supported * mainly for compatibility with clients like OS X). */ if (ni->ni_authmode != IEEE80211_AUTH_AUTO && ni->ni_authmode != IEEE80211_AUTH_SHARED) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "bad sta auth mode %u", ni->ni_authmode); vap->iv_stats.is_rx_bad_auth++; /* XXX maybe a unique error? */ goto bad; } challenge = NULL; if (frm + 1 < efrm) { if ((frm[1] + 2) > (efrm - frm)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "ie %d/%d too long", frm[0], (frm[1] + 2) - (efrm - frm)); vap->iv_stats.is_rx_bad_auth++; goto bad; } if (*frm == IEEE80211_ELEMID_CHALLENGE) challenge = frm; frm += frm[1] + 2; } switch (seq) { case IEEE80211_AUTH_SHARED_CHALLENGE: case IEEE80211_AUTH_SHARED_RESPONSE: if (challenge == NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "%s", "no challenge"); vap->iv_stats.is_rx_bad_auth++; goto bad; } if (challenge[1] != IEEE80211_CHALLENGE_LEN) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "bad challenge len %d", challenge[1]); vap->iv_stats.is_rx_bad_auth++; goto bad; } default: break; } if (vap->iv_state != IEEE80211_S_AUTH) return; switch (seq) { case IEEE80211_AUTH_SHARED_PASS: if (ni->ni_challenge != NULL) { IEEE80211_FREE(ni->ni_challenge, M_80211_NODE); ni->ni_challenge = NULL; } if (status != 0) { IEEE80211_NOTE_FRAME(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, wh, "shared key auth failed (reason %d)", status); vap->iv_stats.is_rx_auth_fail++; vap->iv_stats.is_rx_authfail_code = status; return; } ieee80211_new_state(vap, IEEE80211_S_ASSOC, 0); break; case IEEE80211_AUTH_SHARED_CHALLENGE: if (!ieee80211_alloc_challenge(ni)) return; /* XXX could optimize by passing recvd challenge */ memcpy(ni->ni_challenge, &challenge[2], challenge[1]); IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, seq + 1); break; default: IEEE80211_DISCARD(vap, IEEE80211_MSG_AUTH, wh, "shared key auth", "bad seq %d", seq); vap->iv_stats.is_rx_bad_auth++; return; } return; bad: /* * Kick the state machine. This short-circuits * using the mgt frame timeout to trigger the * state transition. */ if (vap->iv_state == IEEE80211_S_AUTH) ieee80211_new_state(vap, IEEE80211_S_SCAN, IEEE80211_SCAN_FAIL_STATUS); } /* * Parse the WME IE for QoS and U-APSD information. * * Returns -1 if the IE isn't found, 1 if it's found. */ int ieee80211_parse_wmeie(uint8_t *frm, const struct ieee80211_frame *wh, struct ieee80211_node *ni) { u_int len = frm[1]; ni->ni_uapsd = 0; if (len < sizeof(struct ieee80211_wme_param)-2) { IEEE80211_DISCARD_IE(ni->ni_vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WME, wh, "WME", "too short, len %u", len); return -1; } ni->ni_uapsd = frm[WME_CAPINFO_IE_OFFSET]; IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_POWER | IEEE80211_MSG_ASSOC, ni, "U-APSD settings from STA: 0x%02x", ni->ni_uapsd); return 1; } int ieee80211_parse_wmeparams(struct ieee80211vap *vap, uint8_t *frm, const struct ieee80211_frame *wh, uint8_t *qosinfo) { struct ieee80211_wme_state *wme = &vap->iv_ic->ic_wme; u_int len = frm[1], qosinfo_count; int i; *qosinfo = 0; if (len < sizeof(struct ieee80211_wme_param)-2) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WME, wh, "WME", "too short, len %u", len); return -1; } *qosinfo = frm[__offsetof(struct ieee80211_wme_param, param_qosInfo)]; qosinfo_count = *qosinfo & WME_QOSINFO_COUNT; /* XXX do proper check for wraparound */ if (qosinfo_count == wme->wme_wmeChanParams.cap_info) return 0; frm += __offsetof(struct ieee80211_wme_param, params_acParams); for (i = 0; i < WME_NUM_AC; i++) { struct wmeParams *wmep = &wme->wme_wmeChanParams.cap_wmeParams[i]; /* NB: ACI not used */ wmep->wmep_acm = _IEEE80211_MASKSHIFT(frm[0], WME_PARAM_ACM); wmep->wmep_aifsn = _IEEE80211_MASKSHIFT(frm[0], WME_PARAM_AIFSN); wmep->wmep_logcwmin = _IEEE80211_MASKSHIFT(frm[1], WME_PARAM_LOGCWMIN); wmep->wmep_logcwmax = _IEEE80211_MASKSHIFT(frm[1], WME_PARAM_LOGCWMAX); wmep->wmep_txopLimit = le16dec(frm+2); IEEE80211_DPRINTF(vap, IEEE80211_MSG_WME, "%s: WME: %d: acm=%d aifsn=%d logcwmin=%d logcwmax=%d txopLimit=%d\n", __func__, i, wmep->wmep_acm, wmep->wmep_aifsn, wmep->wmep_logcwmin, wmep->wmep_logcwmax, wmep->wmep_txopLimit); frm += 4; } wme->wme_wmeChanParams.cap_info = qosinfo_count; return 1; } /* * Process 11h Channel Switch Announcement (CSA) ie. If this * is the first CSA then initiate the switch. Otherwise we * track state and trigger completion and/or cancel of the switch. * XXX should be public for IBSS use */ static void ieee80211_parse_csaparams(struct ieee80211vap *vap, uint8_t *frm, const struct ieee80211_frame *wh) { struct ieee80211com *ic = vap->iv_ic; const struct ieee80211_csa_ie *csa = (const struct ieee80211_csa_ie *) frm; KASSERT(vap->iv_state >= IEEE80211_S_RUN, ("state %s", ieee80211_state_name[vap->iv_state])); if (csa->csa_mode > 1) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH, wh, "CSA", "invalid mode %u", csa->csa_mode); return; } IEEE80211_LOCK(ic); if ((ic->ic_flags & IEEE80211_F_CSAPENDING) == 0) { /* * Convert the channel number to a channel reference. We * try first to preserve turbo attribute of the current * channel then fallback. Note this will not work if the * CSA specifies a channel that requires a band switch (e.g. * 11a => 11g). This is intentional as 11h is defined only * for 5GHz/11a and because the switch does not involve a * reassociation, protocol state (capabilities, negotated * rates, etc) may/will be wrong. */ struct ieee80211_channel *c = ieee80211_find_channel_byieee(ic, csa->csa_newchan, (ic->ic_bsschan->ic_flags & IEEE80211_CHAN_ALLTURBO)); if (c == NULL) { c = ieee80211_find_channel_byieee(ic, csa->csa_newchan, (ic->ic_bsschan->ic_flags & IEEE80211_CHAN_ALL)); if (c == NULL) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH, wh, "CSA", "invalid channel %u", csa->csa_newchan); goto done; } } #if IEEE80211_CSA_COUNT_MIN > 0 if (csa->csa_count < IEEE80211_CSA_COUNT_MIN) { /* * Require at least IEEE80211_CSA_COUNT_MIN count to * reduce the risk of being redirected by a fabricated * CSA. If a valid CSA is dropped we'll still get a * beacon miss when the AP leaves the channel so we'll * eventually follow to the new channel. * * NOTE: this violates the 11h spec that states that * count may be any value and if 0 then a switch * should happen asap. */ IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH, wh, "CSA", "count %u too small, must be >= %u", csa->csa_count, IEEE80211_CSA_COUNT_MIN); goto done; } #endif ieee80211_csa_startswitch(ic, c, csa->csa_mode, csa->csa_count); } else { /* * Validate this ie against the initial CSA. We require * mode and channel not change and the count must be * monotonically decreasing. This may be pointless and * canceling the switch as a result may be too paranoid but * in the worst case if we drop out of CSA because of this * and the AP does move then we'll just end up taking a * beacon miss and scan to find the AP. * * XXX may want <= on count as we also process ProbeResp * frames and those may come in w/ the same count as the * previous beacon; but doing so leaves us open to a stuck * count until we add a dead-man timer */ if (!(csa->csa_count < ic->ic_csa_count && csa->csa_mode == ic->ic_csa_mode && csa->csa_newchan == ieee80211_chan2ieee(ic, ic->ic_csa_newchan))) { IEEE80211_NOTE_FRAME(vap, IEEE80211_MSG_DOTH, wh, "CSA ie mismatch, initial ie <%d,%d,%d>, " "this ie <%d,%d,%d>", ic->ic_csa_mode, ic->ic_csa_newchan, ic->ic_csa_count, csa->csa_mode, csa->csa_newchan, csa->csa_count); ieee80211_csa_cancelswitch(ic); } else { if (csa->csa_count <= 1) ieee80211_csa_completeswitch(ic); else ic->ic_csa_count = csa->csa_count; } } done: IEEE80211_UNLOCK(ic); } /* * Return non-zero if a background scan may be continued: * o bg scan is active * o no channel switch is pending * o there has not been any traffic recently * o no full-offload scan support (no need for explicitly continuing scan then) * * Note we do not check if there is an administrative enable; * this is only done to start the scan. We assume that any * change in state will be accompanied by a request to cancel * active scans which will otherwise cause this test to fail. */ static __inline int contbgscan(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; return ((ic->ic_flags_ext & IEEE80211_FEXT_BGSCAN) && (ic->ic_flags & IEEE80211_F_CSAPENDING) == 0 && !(vap->iv_flags_ext & IEEE80211_FEXT_SCAN_OFFLOAD) && vap->iv_state == IEEE80211_S_RUN && /* XXX? */ ieee80211_time_after(ticks, ic->ic_lastdata + vap->iv_bgscanidle)); } /* * Return non-zero if a backgrond scan may be started: * o bg scanning is administratively enabled * o no channel switch is pending * o we are not boosted on a dynamic turbo channel * o there has not been a scan recently * o there has not been any traffic recently (don't check if full-offload scan) */ static __inline int startbgscan(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; return ((vap->iv_flags & IEEE80211_F_BGSCAN) && (ic->ic_flags & IEEE80211_F_CSAPENDING) == 0 && #ifdef IEEE80211_SUPPORT_SUPERG !IEEE80211_IS_CHAN_DTURBO(ic->ic_curchan) && #endif ieee80211_time_after(ticks, ic->ic_lastscan + vap->iv_bgscanintvl) && ((vap->iv_flags_ext & IEEE80211_FEXT_SCAN_OFFLOAD) || ieee80211_time_after(ticks, ic->ic_lastdata + vap->iv_bgscanidle))); } #ifdef notyet /* * Compare two quiet IEs and return if they are equivalent. * * The tbttcount isnt checked - that's not part of the configuration. */ static int compare_quiet_ie(const struct ieee80211_quiet_ie *q1, const struct ieee80211_quiet_ie *q2) { if (q1->period != q2->period) return (0); if (le16dec(&q1->duration) != le16dec(&q2->duration)) return (0); if (le16dec(&q1->offset) != le16dec(&q2->offset)) return (0); return (1); } #endif static void sta_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m0, int subtype, const struct ieee80211_rx_stats *rxs, int rssi, int nf) { #define ISREASSOC(_st) ((_st) == IEEE80211_FC0_SUBTYPE_REASSOC_RESP) struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_channel *rxchan = ic->ic_curchan; struct ieee80211_frame *wh; int ht_state_change = 0, do_ht = 0; uint8_t *frm, *efrm; uint8_t *rates, *xrates, *wme, *htcap, *htinfo; uint8_t *vhtcap, *vhtopmode; uint8_t rate; uint8_t qosinfo; wh = mtod(m0, struct ieee80211_frame *); frm = (uint8_t *)&wh[1]; efrm = mtod(m0, uint8_t *) + m0->m_len; switch (subtype) { case IEEE80211_FC0_SUBTYPE_PROBE_RESP: case IEEE80211_FC0_SUBTYPE_BEACON: { struct ieee80211_scanparams scan; struct ieee80211_channel *c; /* * We process beacon/probe response frames: * o when scanning, or * o station mode when associated (to collect state * updates such as 802.11g slot time) * Frames otherwise received are discarded. */ if (!((ic->ic_flags & IEEE80211_F_SCAN) || ni->ni_associd)) { vap->iv_stats.is_rx_mgtdiscard++; return; } /* Override RX channel as appropriate */ if (rxs != NULL) { c = ieee80211_lookup_channel_rxstatus(vap, rxs); if (c != NULL) rxchan = c; } /* XXX probe response in sta mode when !scanning? */ if (ieee80211_parse_beacon(ni, m0, rxchan, &scan) != 0) { if (! (ic->ic_flags & IEEE80211_F_SCAN)) vap->iv_stats.is_beacon_bad++; return; } /* * Count frame now that we know it's to be processed. */ if (subtype == IEEE80211_FC0_SUBTYPE_BEACON) { vap->iv_stats.is_rx_beacon++; /* XXX remove */ IEEE80211_NODE_STAT(ni, rx_beacons); } else IEEE80211_NODE_STAT(ni, rx_proberesp); /* * When operating in station mode, check for state updates. * Be careful to ignore beacons received while doing a * background scan. We consider only 11g/WMM stuff right now. */ if (ni->ni_associd != 0 && ((ic->ic_flags & IEEE80211_F_SCAN) == 0 || IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_bssid))) { /* record tsf of last beacon */ memcpy(ni->ni_tstamp.data, scan.tstamp, sizeof(ni->ni_tstamp)); /* count beacon frame for s/w bmiss handling */ vap->iv_swbmiss_count++; vap->iv_bmiss_count = 0; if (ni->ni_erp != scan.erp) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC, wh->i_addr2, "erp change: was 0x%x, now 0x%x", ni->ni_erp, scan.erp); if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan) && (ni->ni_erp & IEEE80211_ERP_USE_PROTECTION)) vap->iv_flags |= IEEE80211_F_USEPROT; else vap->iv_flags &= ~IEEE80211_F_USEPROT; ni->ni_erp = scan.erp; /* XXX statistic */ /* driver notification */ ieee80211_vap_update_erp_protmode(vap); } if ((ni->ni_capinfo ^ scan.capinfo) & IEEE80211_CAPINFO_SHORT_SLOTTIME) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC, wh->i_addr2, "capabilities change: was 0x%x, now 0x%x", ni->ni_capinfo, scan.capinfo); /* * NB: we assume short preamble doesn't * change dynamically */ ieee80211_vap_set_shortslottime(vap, IEEE80211_IS_CHAN_A(ic->ic_bsschan) || (scan.capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME)); ni->ni_capinfo = (ni->ni_capinfo &~ IEEE80211_CAPINFO_SHORT_SLOTTIME) | (scan.capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME); /* XXX statistic */ } if (scan.wme != NULL && (ni->ni_flags & IEEE80211_NODE_QOS)) { int _retval; if ((_retval = ieee80211_parse_wmeparams(vap, scan.wme, wh, &qosinfo)) >= 0) { if (qosinfo & WME_CAPINFO_UAPSD_EN) ni->ni_flags |= IEEE80211_NODE_UAPSD; if (_retval > 0) ieee80211_wme_updateparams(vap); } } else ni->ni_flags &= ~IEEE80211_NODE_UAPSD; #ifdef IEEE80211_SUPPORT_SUPERG if (scan.ath != NULL) ieee80211_parse_athparams(ni, scan.ath, wh); #endif if (scan.htcap != NULL && scan.htinfo != NULL && (vap->iv_flags_ht & IEEE80211_FHT_HT)) { /* XXX state changes? */ ieee80211_ht_updateparams(ni, scan.htcap, scan.htinfo); do_ht = 1; } if (scan.vhtcap != NULL && scan.vhtopmode != NULL && (vap->iv_vht_flags & IEEE80211_FVHT_VHT)) { /* XXX state changes? */ ieee80211_vht_updateparams(ni, scan.vhtcap, scan.vhtopmode); do_ht = 1; } if (do_ht) { if (ieee80211_ht_updateparams_final(ni, scan.htcap, scan.htinfo)) ht_state_change = 1; } /* * If we have a quiet time IE then report it up to * the driver. * * Otherwise, inform the driver that the quiet time * IE has disappeared - only do that once rather than * spamming it each time. */ if (scan.quiet) { ic->ic_set_quiet(ni, scan.quiet); ni->ni_quiet_ie_set = 1; memcpy(&ni->ni_quiet_ie, scan.quiet, sizeof(struct ieee80211_quiet_ie)); } else { if (ni->ni_quiet_ie_set == 1) ic->ic_set_quiet(ni, NULL); ni->ni_quiet_ie_set = 0; bzero(&ni->ni_quiet_ie, sizeof(struct ieee80211_quiet_ie)); } if (scan.tim != NULL) { struct ieee80211_tim_ie *tim = (struct ieee80211_tim_ie *) scan.tim; /* * XXX Check/debug this code; see if it's about * the right time to force the VAP awake if we * receive a frame destined for us? */ int aid = IEEE80211_AID(ni->ni_associd); int ix = aid / NBBY; int min = tim->tim_bitctl &~ 1; int max = tim->tim_len + min - 4; int tim_ucast = 0; #ifdef __notyet__ int tim_mcast = 0; #endif /* * Only do this for unicast traffic in the TIM * The multicast traffic notification for * the scan notification stuff should occur * differently. */ if (min <= ix && ix <= max && isset(tim->tim_bitmap - min, aid)) { tim_ucast = 1; } #ifdef __notyet__ /* * Do a separate notification * for the multicast bit being set. */ if (tim->tim_bitctl & 1) { tim_mcast = 1; } #endif /* * If the TIM indicates there's traffic for * us then get us out of STA mode powersave. */ if (tim_ucast == 1) { /* * Wake us out of SLEEP state if we're * in it; and if we're doing bgscan * then wake us out of STA powersave. */ ieee80211_sta_tim_notify(vap, 1); /* * This is preventing us from * continuing a bgscan; because it * tricks the contbgscan() * routine to think there's always * traffic for us. * * I think we need both an RX and * TX ic_lastdata field. */ ic->ic_lastdata = ticks; } ni->ni_dtim_count = tim->tim_count; ni->ni_dtim_period = tim->tim_period; } if (scan.csa != NULL && (vap->iv_flags & IEEE80211_F_DOTH)) ieee80211_parse_csaparams(vap, scan.csa, wh); else if (ic->ic_flags & IEEE80211_F_CSAPENDING) { /* * No CSA ie or 11h disabled, but a channel * switch is pending; drop out so we aren't * stuck in CSA state. If the AP really is * moving we'll get a beacon miss and scan. */ IEEE80211_LOCK(ic); ieee80211_csa_cancelswitch(ic); IEEE80211_UNLOCK(ic); } /* * If scanning, pass the info to the scan module. * Otherwise, check if it's the right time to do * a background scan. Background scanning must * be enabled and we must not be operating in the * turbo phase of dynamic turbo mode. Then, * it's been a while since the last background * scan and if no data frames have come through * recently, kick off a scan. Note that this * is the mechanism by which a background scan * is started _and_ continued each time we * return on-channel to receive a beacon from * our ap. */ if (ic->ic_flags & IEEE80211_F_SCAN) { ieee80211_add_scan(vap, rxchan, &scan, wh, subtype, rssi, nf); } else if (contbgscan(vap)) { ieee80211_bg_scan(vap, 0); } else if (startbgscan(vap)) { vap->iv_stats.is_scan_bg++; #if 0 /* wakeup if we are sleeing */ ieee80211_set_pwrsave(vap, 0); #endif ieee80211_bg_scan(vap, 0); } /* * Put the station to sleep if we haven't seen * traffic in a while. */ IEEE80211_LOCK(ic); ieee80211_sta_ps_timer_check(vap); IEEE80211_UNLOCK(ic); /* * If we've had a channel width change (eg HT20<->HT40) * then schedule a delayed driver notification. */ if (ht_state_change) ieee80211_update_chw(ic); return; } /* * If scanning, just pass information to the scan module. */ if (ic->ic_flags & IEEE80211_F_SCAN) { if (ic->ic_flags_ext & IEEE80211_FEXT_PROBECHAN) { /* * Actively scanning a channel marked passive; * send a probe request now that we know there * is 802.11 traffic present. * * XXX check if the beacon we recv'd gives * us what we need and suppress the probe req */ ieee80211_probe_curchan(vap, true); ic->ic_flags_ext &= ~IEEE80211_FEXT_PROBECHAN; } ieee80211_add_scan(vap, rxchan, &scan, wh, subtype, rssi, nf); return; } break; } case IEEE80211_FC0_SUBTYPE_AUTH: { uint16_t algo, seq, status; /* * auth frame format * [2] algorithm * [2] sequence * [2] status * [tlv*] challenge */ IEEE80211_VERIFY_LENGTH(efrm - frm, 6, return); algo = le16toh(*(uint16_t *)frm); seq = le16toh(*(uint16_t *)(frm + 2)); status = le16toh(*(uint16_t *)(frm + 4)); IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_AUTH, wh->i_addr2, "recv auth frame with algorithm %d seq %d", algo, seq); if (vap->iv_flags & IEEE80211_F_COUNTERM) { IEEE80211_DISCARD(vap, IEEE80211_MSG_AUTH | IEEE80211_MSG_CRYPTO, wh, "auth", "%s", "TKIP countermeasures enabled"); vap->iv_stats.is_rx_auth_countermeasures++; if (vap->iv_opmode == IEEE80211_M_HOSTAP) { ieee80211_send_error(ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_AUTH, IEEE80211_REASON_MIC_FAILURE); } return; } if (algo == IEEE80211_AUTH_ALG_SHARED) sta_auth_shared(ni, wh, frm + 6, efrm, rssi, nf, seq, status); else if (algo == IEEE80211_AUTH_ALG_OPEN) sta_auth_open(ni, wh, rssi, nf, seq, status); else { IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, "auth", "unsupported alg %d", algo); vap->iv_stats.is_rx_auth_unsupported++; return; } break; } case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: case IEEE80211_FC0_SUBTYPE_REASSOC_RESP: { uint16_t capinfo, associd; uint16_t status; if (vap->iv_state != IEEE80211_S_ASSOC) { vap->iv_stats.is_rx_mgtdiscard++; return; } /* * asresp frame format * [2] capability information * [2] status * [2] association ID * [tlv] supported rates * [tlv] extended supported rates * [tlv] WME * [tlv] HT capabilities * [tlv] HT info */ IEEE80211_VERIFY_LENGTH(efrm - frm, 6, return); ni = vap->iv_bss; capinfo = le16toh(*(uint16_t *)frm); frm += 2; status = le16toh(*(uint16_t *)frm); frm += 2; if (status != 0) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC, wh->i_addr2, "%sassoc failed (reason %d)", ISREASSOC(subtype) ? "re" : "", status); vap->iv_stats.is_rx_auth_fail++; /* XXX */ return; } associd = le16toh(*(uint16_t *)frm); frm += 2; rates = xrates = wme = htcap = htinfo = NULL; vhtcap = vhtopmode = NULL; while (efrm - frm > 1) { IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2, return); switch (*frm) { case IEEE80211_ELEMID_RATES: rates = frm; break; case IEEE80211_ELEMID_XRATES: xrates = frm; break; case IEEE80211_ELEMID_HTCAP: htcap = frm; break; case IEEE80211_ELEMID_HTINFO: htinfo = frm; break; case IEEE80211_ELEMID_VENDOR: if (iswmeoui(frm)) wme = frm; else if (vap->iv_flags_ht & IEEE80211_FHT_HTCOMPAT) { /* * Accept pre-draft HT ie's if the * standard ones have not been seen. */ if (ishtcapoui(frm)) { if (htcap == NULL) htcap = frm; } else if (ishtinfooui(frm)) { if (htinfo == NULL) htinfo = frm; } } /* XXX Atheros OUI support */ break; case IEEE80211_ELEMID_VHT_CAP: vhtcap = frm; break; case IEEE80211_ELEMID_VHT_OPMODE: vhtopmode = frm; break; } frm += frm[1] + 2; } IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE, return); if (xrates != NULL) IEEE80211_VERIFY_ELEMENT(xrates, IEEE80211_RATE_MAXSIZE - rates[1], return); rate = ieee80211_setup_rates(ni, rates, xrates, IEEE80211_F_JOIN | IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE | IEEE80211_F_DONEGO | IEEE80211_F_DODEL); if (rate & IEEE80211_RATE_BASIC) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC, wh->i_addr2, "%sassoc failed (rate set mismatch)", ISREASSOC(subtype) ? "re" : ""); vap->iv_stats.is_rx_assoc_norate++; ieee80211_new_state(vap, IEEE80211_S_SCAN, IEEE80211_SCAN_FAIL_STATUS); return; } ni->ni_capinfo = capinfo; ni->ni_associd = associd; if (ni->ni_jointime == 0) ni->ni_jointime = time_uptime; if (wme != NULL && ieee80211_parse_wmeparams(vap, wme, wh, &qosinfo) >= 0) { ni->ni_flags |= IEEE80211_NODE_QOS; ieee80211_wme_updateparams(vap); } else ni->ni_flags &= ~IEEE80211_NODE_QOS; /* * Setup HT state according to the negotiation. * * NB: shouldn't need to check if HT use is enabled but some * ap's send back HT ie's even when we don't indicate we * are HT capable in our AssocReq. */ if (htcap != NULL && htinfo != NULL && (vap->iv_flags_ht & IEEE80211_FHT_HT)) { ieee80211_ht_node_init(ni); ieee80211_ht_updateparams(ni, htcap, htinfo); if ((vhtcap != NULL) && (vhtopmode != NULL) & (vap->iv_vht_flags & IEEE80211_FVHT_VHT)) { /* * Log if we get a VHT assoc/reassoc response. * We aren't ready for 2GHz VHT support. */ if (IEEE80211_IS_CHAN_2GHZ(ni->ni_chan)) { printf("%s: peer %6D: VHT on 2GHz, ignoring\n", __func__, ni->ni_macaddr, ":"); } else { ieee80211_vht_node_init(ni); ieee80211_vht_updateparams(ni, vhtcap, vhtopmode); ieee80211_setup_vht_rates(ni, vhtcap, vhtopmode); } } ieee80211_ht_updateparams_final(ni, htcap, htinfo); ieee80211_setup_htrates(ni, htcap, IEEE80211_F_JOIN | IEEE80211_F_DOBRS); ieee80211_setup_basic_htrates(ni, htinfo); ieee80211_node_setuptxparms(ni); ieee80211_ratectl_node_init(ni); } /* * Always initialise FF/superg state; we can use this * for doing A-MSDU encapsulation as well. */ #ifdef IEEE80211_SUPPORT_SUPERG ieee80211_ff_node_init(ni); #endif /* * Configure state now that we are associated. * * XXX may need different/additional driver callbacks? */ if (IEEE80211_IS_CHAN_A(ic->ic_curchan) || (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE)) { vap->iv_flags |= IEEE80211_F_SHPREAMBLE; vap->iv_flags &= ~IEEE80211_F_USEBARKER; } else { vap->iv_flags &= ~IEEE80211_F_SHPREAMBLE; vap->iv_flags |= IEEE80211_F_USEBARKER; } ieee80211_vap_set_shortslottime(vap, IEEE80211_IS_CHAN_A(ic->ic_curchan) || (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME)); ieee80211_vap_update_preamble(vap); /* * Honor ERP protection. * * NB: ni_erp should zero for non-11g operation. */ if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan) && (ni->ni_erp & IEEE80211_ERP_USE_PROTECTION)) vap->iv_flags |= IEEE80211_F_USEPROT; else vap->iv_flags &= ~IEEE80211_F_USEPROT; ieee80211_vap_update_erp_protmode(vap); IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_DEBUG, wh->i_addr2, "%sassoc success at aid %d: %s preamble, %s slot time%s%s%s%s%s%s%s%s%s", ISREASSOC(subtype) ? "re" : "", IEEE80211_NODE_AID(ni), vap->iv_flags&IEEE80211_F_SHPREAMBLE ? "short" : "long", vap->iv_flags&IEEE80211_F_SHSLOT ? "short" : "long", vap->iv_flags&IEEE80211_F_USEPROT ? ", protection" : "", ni->ni_flags & IEEE80211_NODE_QOS ? ", QoS" : "", ni->ni_flags & IEEE80211_NODE_HT ? - (ni->ni_chw == 40 ? ", HT40" : ", HT20") : "", + (ni->ni_chw == IEEE80211_STA_RX_BW_40 ? ", HT40" : ", HT20") : "", ni->ni_flags & IEEE80211_NODE_AMPDU ? " (+AMPDU)" : "", ni->ni_flags & IEEE80211_NODE_AMSDU ? " (+AMSDU)" : "", ni->ni_flags & IEEE80211_NODE_MIMO_RTS ? " (+SMPS-DYN)" : ni->ni_flags & IEEE80211_NODE_MIMO_PS ? " (+SMPS)" : "", ni->ni_flags & IEEE80211_NODE_RIFS ? " (+RIFS)" : "", IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_FF) ? ", fast-frames" : "", IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_TURBOP) ? ", turbo" : "" ); ieee80211_new_state(vap, IEEE80211_S_RUN, subtype); break; } case IEEE80211_FC0_SUBTYPE_DEAUTH: { uint16_t reason; if (vap->iv_state == IEEE80211_S_SCAN) { vap->iv_stats.is_rx_mgtdiscard++; return; } if (!IEEE80211_ADDR_EQ(wh->i_addr1, vap->iv_myaddr)) { /* NB: can happen when in promiscuous mode */ vap->iv_stats.is_rx_mgtdiscard++; break; } /* * deauth frame format * [2] reason */ IEEE80211_VERIFY_LENGTH(efrm - frm, 2, return); reason = le16toh(*(uint16_t *)frm); vap->iv_stats.is_rx_deauth++; vap->iv_stats.is_rx_deauth_code = reason; IEEE80211_NODE_STAT(ni, rx_deauth); IEEE80211_NOTE(vap, IEEE80211_MSG_AUTH, ni, "recv deauthenticate (reason: %d (%s))", reason, ieee80211_reason_to_string(reason)); ieee80211_new_state(vap, IEEE80211_S_AUTH, (reason << 8) | IEEE80211_FC0_SUBTYPE_DEAUTH); break; } case IEEE80211_FC0_SUBTYPE_DISASSOC: { uint16_t reason; if (vap->iv_state != IEEE80211_S_RUN && vap->iv_state != IEEE80211_S_ASSOC && vap->iv_state != IEEE80211_S_AUTH) { vap->iv_stats.is_rx_mgtdiscard++; return; } if (!IEEE80211_ADDR_EQ(wh->i_addr1, vap->iv_myaddr)) { /* NB: can happen when in promiscuous mode */ vap->iv_stats.is_rx_mgtdiscard++; break; } /* * disassoc frame format * [2] reason */ IEEE80211_VERIFY_LENGTH(efrm - frm, 2, return); reason = le16toh(*(uint16_t *)frm); vap->iv_stats.is_rx_disassoc++; vap->iv_stats.is_rx_disassoc_code = reason; IEEE80211_NODE_STAT(ni, rx_disassoc); IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni, "recv disassociate (reason: %d (%s))", reason, ieee80211_reason_to_string(reason)); ieee80211_new_state(vap, IEEE80211_S_ASSOC, 0); break; } case IEEE80211_FC0_SUBTYPE_ACTION: case IEEE80211_FC0_SUBTYPE_ACTION_NOACK: if (!IEEE80211_ADDR_EQ(vap->iv_myaddr, wh->i_addr1) && !IEEE80211_IS_MULTICAST(wh->i_addr1)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "not for us"); vap->iv_stats.is_rx_mgtdiscard++; } else if (vap->iv_state != IEEE80211_S_RUN) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "wrong state %s", ieee80211_state_name[vap->iv_state]); vap->iv_stats.is_rx_mgtdiscard++; } else { if (ieee80211_parse_action(ni, m0) == 0) (void)ic->ic_recv_action(ni, wh, frm, efrm); } break; case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: case IEEE80211_FC0_SUBTYPE_PROBE_REQ: case IEEE80211_FC0_SUBTYPE_TIMING_ADV: case IEEE80211_FC0_SUBTYPE_ATIM: IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "not handled"); vap->iv_stats.is_rx_mgtdiscard++; break; default: IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, "mgt", "subtype 0x%x not handled", subtype); vap->iv_stats.is_rx_badsubtype++; break; } #undef ISREASSOC } static void sta_recv_ctl(struct ieee80211_node *ni, struct mbuf *m, int subtype) { switch (subtype) { case IEEE80211_FC0_SUBTYPE_BAR: ieee80211_recv_bar(ni, m); break; } }