1d1e879ecSMiri Korenblit // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 2d1e879ecSMiri Korenblit /* 3d1e879ecSMiri Korenblit * Copyright (C) 2024-2025 Intel Corporation 4d1e879ecSMiri Korenblit */ 5d1e879ecSMiri Korenblit 6d1e879ecSMiri Korenblit #include <linux/ieee80211.h> 7d1e879ecSMiri Korenblit #include <kunit/static_stub.h> 8d1e879ecSMiri Korenblit 9d1e879ecSMiri Korenblit #include "sta.h" 10d1e879ecSMiri Korenblit #include "hcmd.h" 11d1e879ecSMiri Korenblit #include "iface.h" 12d1e879ecSMiri Korenblit #include "mlo.h" 13d1e879ecSMiri Korenblit #include "key.h" 14d1e879ecSMiri Korenblit #include "agg.h" 15d1e879ecSMiri Korenblit #include "tlc.h" 16d1e879ecSMiri Korenblit #include "fw/api/sta.h" 17d1e879ecSMiri Korenblit #include "fw/api/mac.h" 18d1e879ecSMiri Korenblit #include "fw/api/rx.h" 19d1e879ecSMiri Korenblit 20d1e879ecSMiri Korenblit int iwl_mld_fw_sta_id_from_link_sta(struct iwl_mld *mld, 21d1e879ecSMiri Korenblit struct ieee80211_link_sta *link_sta) 22d1e879ecSMiri Korenblit { 23d1e879ecSMiri Korenblit struct iwl_mld_link_sta *mld_link_sta; 24d1e879ecSMiri Korenblit 25d1e879ecSMiri Korenblit /* This function should only be used with the wiphy lock held, 26d1e879ecSMiri Korenblit * In other cases, it is not guaranteed that the link_sta will exist 27d1e879ecSMiri Korenblit * in the driver too, and it is checked here. 28d1e879ecSMiri Korenblit */ 29d1e879ecSMiri Korenblit lockdep_assert_wiphy(mld->wiphy); 30d1e879ecSMiri Korenblit 31d1e879ecSMiri Korenblit /* This is not meant to be called with a NULL pointer */ 32d1e879ecSMiri Korenblit if (WARN_ON(!link_sta)) 33d1e879ecSMiri Korenblit return -ENOENT; 34d1e879ecSMiri Korenblit 35d1e879ecSMiri Korenblit mld_link_sta = iwl_mld_link_sta_from_mac80211(link_sta); 36d1e879ecSMiri Korenblit if (!mld_link_sta) { 37d1e879ecSMiri Korenblit WARN_ON(!iwl_mld_error_before_recovery(mld)); 38d1e879ecSMiri Korenblit return -ENOENT; 39d1e879ecSMiri Korenblit } 40d1e879ecSMiri Korenblit 41d1e879ecSMiri Korenblit return mld_link_sta->fw_id; 42d1e879ecSMiri Korenblit } 43d1e879ecSMiri Korenblit 44d1e879ecSMiri Korenblit static void 45d1e879ecSMiri Korenblit iwl_mld_fill_ampdu_size_and_dens(struct ieee80211_link_sta *link_sta, 46d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link, 47d1e879ecSMiri Korenblit __le32 *tx_ampdu_max_size, 48d1e879ecSMiri Korenblit __le32 *tx_ampdu_spacing) 49d1e879ecSMiri Korenblit { 50d1e879ecSMiri Korenblit u32 agg_size = 0, mpdu_dens = 0; 51d1e879ecSMiri Korenblit 52d1e879ecSMiri Korenblit if (WARN_ON(!link_sta || !link)) 53d1e879ecSMiri Korenblit return; 54d1e879ecSMiri Korenblit 55d1e879ecSMiri Korenblit /* Note that we always use only legacy & highest supported PPDUs, so 56d1e879ecSMiri Korenblit * of Draft P802.11be D.30 Table 10-12a--Fields used for calculating 57d1e879ecSMiri Korenblit * the maximum A-MPDU size of various PPDU types in different bands, 58d1e879ecSMiri Korenblit * we only need to worry about the highest supported PPDU type here. 59d1e879ecSMiri Korenblit */ 60d1e879ecSMiri Korenblit 61d1e879ecSMiri Korenblit if (link_sta->ht_cap.ht_supported) { 62d1e879ecSMiri Korenblit agg_size = link_sta->ht_cap.ampdu_factor; 63d1e879ecSMiri Korenblit mpdu_dens = link_sta->ht_cap.ampdu_density; 64d1e879ecSMiri Korenblit } 65d1e879ecSMiri Korenblit 66d1e879ecSMiri Korenblit if (link->chanreq.oper.chan->band == NL80211_BAND_6GHZ) { 67d1e879ecSMiri Korenblit /* overwrite HT values on 6 GHz */ 68d1e879ecSMiri Korenblit mpdu_dens = 69d1e879ecSMiri Korenblit le16_get_bits(link_sta->he_6ghz_capa.capa, 70d1e879ecSMiri Korenblit IEEE80211_HE_6GHZ_CAP_MIN_MPDU_START); 71d1e879ecSMiri Korenblit agg_size = 72d1e879ecSMiri Korenblit le16_get_bits(link_sta->he_6ghz_capa.capa, 73d1e879ecSMiri Korenblit IEEE80211_HE_6GHZ_CAP_MAX_AMPDU_LEN_EXP); 74d1e879ecSMiri Korenblit } else if (link_sta->vht_cap.vht_supported) { 75d1e879ecSMiri Korenblit /* if VHT supported overwrite HT value */ 76d1e879ecSMiri Korenblit agg_size = 77d1e879ecSMiri Korenblit u32_get_bits(link_sta->vht_cap.cap, 78d1e879ecSMiri Korenblit IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK); 79d1e879ecSMiri Korenblit } 80d1e879ecSMiri Korenblit 81d1e879ecSMiri Korenblit /* D6.0 10.12.2 A-MPDU length limit rules 82d1e879ecSMiri Korenblit * A STA indicates the maximum length of the A-MPDU preEOF padding 83d1e879ecSMiri Korenblit * that it can receive in an HE PPDU in the Maximum A-MPDU Length 84d1e879ecSMiri Korenblit * Exponent field in its HT Capabilities, VHT Capabilities, 85d1e879ecSMiri Korenblit * and HE 6 GHz Band Capabilities elements (if present) and the 86d1e879ecSMiri Korenblit * Maximum AMPDU Length Exponent Extension field in its HE 87d1e879ecSMiri Korenblit * Capabilities element 88d1e879ecSMiri Korenblit */ 89d1e879ecSMiri Korenblit if (link_sta->he_cap.has_he) 90d1e879ecSMiri Korenblit agg_size += 91d1e879ecSMiri Korenblit u8_get_bits(link_sta->he_cap.he_cap_elem.mac_cap_info[3], 92d1e879ecSMiri Korenblit IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_MASK); 93d1e879ecSMiri Korenblit 94d1e879ecSMiri Korenblit if (link_sta->eht_cap.has_eht) 95d1e879ecSMiri Korenblit agg_size += 96d1e879ecSMiri Korenblit u8_get_bits(link_sta->eht_cap.eht_cap_elem.mac_cap_info[1], 97d1e879ecSMiri Korenblit IEEE80211_EHT_MAC_CAP1_MAX_AMPDU_LEN_MASK); 98d1e879ecSMiri Korenblit 99d1e879ecSMiri Korenblit /* Limit to max A-MPDU supported by FW */ 100d1e879ecSMiri Korenblit agg_size = min_t(u32, agg_size, 101d1e879ecSMiri Korenblit STA_FLG_MAX_AGG_SIZE_4M >> STA_FLG_MAX_AGG_SIZE_SHIFT); 102d1e879ecSMiri Korenblit 103d1e879ecSMiri Korenblit *tx_ampdu_max_size = cpu_to_le32(agg_size); 104d1e879ecSMiri Korenblit *tx_ampdu_spacing = cpu_to_le32(mpdu_dens); 105d1e879ecSMiri Korenblit } 106d1e879ecSMiri Korenblit 107d1e879ecSMiri Korenblit static u8 iwl_mld_get_uapsd_acs(struct ieee80211_sta *sta) 108d1e879ecSMiri Korenblit { 109d1e879ecSMiri Korenblit u8 uapsd_acs = 0; 110d1e879ecSMiri Korenblit 111d1e879ecSMiri Korenblit if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_BK) 112d1e879ecSMiri Korenblit uapsd_acs |= BIT(AC_BK); 113d1e879ecSMiri Korenblit if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_BE) 114d1e879ecSMiri Korenblit uapsd_acs |= BIT(AC_BE); 115d1e879ecSMiri Korenblit if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_VI) 116d1e879ecSMiri Korenblit uapsd_acs |= BIT(AC_VI); 117d1e879ecSMiri Korenblit if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_VO) 118d1e879ecSMiri Korenblit uapsd_acs |= BIT(AC_VO); 119d1e879ecSMiri Korenblit 120d1e879ecSMiri Korenblit return uapsd_acs | uapsd_acs << 4; 121d1e879ecSMiri Korenblit } 122d1e879ecSMiri Korenblit 123d1e879ecSMiri Korenblit static u8 iwl_mld_he_get_ppe_val(u8 *ppe, u8 ppe_pos_bit) 124d1e879ecSMiri Korenblit { 125d1e879ecSMiri Korenblit u8 byte_num = ppe_pos_bit / 8; 126d1e879ecSMiri Korenblit u8 bit_num = ppe_pos_bit % 8; 127d1e879ecSMiri Korenblit u8 residue_bits; 128d1e879ecSMiri Korenblit u8 res; 129d1e879ecSMiri Korenblit 130d1e879ecSMiri Korenblit if (bit_num <= 5) 131d1e879ecSMiri Korenblit return (ppe[byte_num] >> bit_num) & 132d1e879ecSMiri Korenblit (BIT(IEEE80211_PPE_THRES_INFO_PPET_SIZE) - 1); 133d1e879ecSMiri Korenblit 134d1e879ecSMiri Korenblit /* If bit_num > 5, we have to combine bits with next byte. 135d1e879ecSMiri Korenblit * Calculate how many bits we need to take from current byte (called 136d1e879ecSMiri Korenblit * here "residue_bits"), and add them to bits from next byte. 137d1e879ecSMiri Korenblit */ 138d1e879ecSMiri Korenblit 139d1e879ecSMiri Korenblit residue_bits = 8 - bit_num; 140d1e879ecSMiri Korenblit 141d1e879ecSMiri Korenblit res = (ppe[byte_num + 1] & 142d1e879ecSMiri Korenblit (BIT(IEEE80211_PPE_THRES_INFO_PPET_SIZE - residue_bits) - 1)) << 143d1e879ecSMiri Korenblit residue_bits; 144d1e879ecSMiri Korenblit res += (ppe[byte_num] >> bit_num) & (BIT(residue_bits) - 1); 145d1e879ecSMiri Korenblit 146d1e879ecSMiri Korenblit return res; 147d1e879ecSMiri Korenblit } 148d1e879ecSMiri Korenblit 149d1e879ecSMiri Korenblit static void iwl_mld_parse_ppe(struct iwl_mld *mld, 150d1e879ecSMiri Korenblit struct iwl_he_pkt_ext_v2 *pkt_ext, u8 nss, 151d1e879ecSMiri Korenblit u8 ru_index_bitmap, u8 *ppe, u8 ppe_pos_bit, 152d1e879ecSMiri Korenblit bool inheritance) 153d1e879ecSMiri Korenblit { 154d1e879ecSMiri Korenblit /* FW currently supports only nss == MAX_HE_SUPP_NSS 155d1e879ecSMiri Korenblit * 156d1e879ecSMiri Korenblit * If nss > MAX: we can ignore values we don't support 157d1e879ecSMiri Korenblit * If nss < MAX: we can set zeros in other streams 158d1e879ecSMiri Korenblit */ 159d1e879ecSMiri Korenblit if (nss > MAX_HE_SUPP_NSS) { 160d1e879ecSMiri Korenblit IWL_DEBUG_INFO(mld, "Got NSS = %d - trimming to %d\n", nss, 161d1e879ecSMiri Korenblit MAX_HE_SUPP_NSS); 162d1e879ecSMiri Korenblit nss = MAX_HE_SUPP_NSS; 163d1e879ecSMiri Korenblit } 164d1e879ecSMiri Korenblit 165d1e879ecSMiri Korenblit for (int i = 0; i < nss; i++) { 166d1e879ecSMiri Korenblit u8 ru_index_tmp = ru_index_bitmap << 1; 167d1e879ecSMiri Korenblit u8 low_th = IWL_HE_PKT_EXT_NONE, high_th = IWL_HE_PKT_EXT_NONE; 168d1e879ecSMiri Korenblit 169d1e879ecSMiri Korenblit for (u8 bw = 0; 170d1e879ecSMiri Korenblit bw < ARRAY_SIZE(pkt_ext->pkt_ext_qam_th[i]); 171d1e879ecSMiri Korenblit bw++) { 172d1e879ecSMiri Korenblit ru_index_tmp >>= 1; 173d1e879ecSMiri Korenblit 174d1e879ecSMiri Korenblit /* According to the 11be spec, if for a specific BW the PPE Thresholds 175d1e879ecSMiri Korenblit * isn't present - it should inherit the thresholds from the last 176d1e879ecSMiri Korenblit * BW for which we had PPE Thresholds. In 11ax though, we don't have 177d1e879ecSMiri Korenblit * this inheritance - continue in this case 178d1e879ecSMiri Korenblit */ 179d1e879ecSMiri Korenblit if (!(ru_index_tmp & 1)) { 180d1e879ecSMiri Korenblit if (inheritance) 181d1e879ecSMiri Korenblit goto set_thresholds; 182d1e879ecSMiri Korenblit else 183d1e879ecSMiri Korenblit continue; 184d1e879ecSMiri Korenblit } 185d1e879ecSMiri Korenblit 186d1e879ecSMiri Korenblit high_th = iwl_mld_he_get_ppe_val(ppe, ppe_pos_bit); 187d1e879ecSMiri Korenblit ppe_pos_bit += IEEE80211_PPE_THRES_INFO_PPET_SIZE; 188d1e879ecSMiri Korenblit low_th = iwl_mld_he_get_ppe_val(ppe, ppe_pos_bit); 189d1e879ecSMiri Korenblit ppe_pos_bit += IEEE80211_PPE_THRES_INFO_PPET_SIZE; 190d1e879ecSMiri Korenblit 191d1e879ecSMiri Korenblit set_thresholds: 192d1e879ecSMiri Korenblit pkt_ext->pkt_ext_qam_th[i][bw][0] = low_th; 193d1e879ecSMiri Korenblit pkt_ext->pkt_ext_qam_th[i][bw][1] = high_th; 194d1e879ecSMiri Korenblit } 195d1e879ecSMiri Korenblit } 196d1e879ecSMiri Korenblit } 197d1e879ecSMiri Korenblit 198d1e879ecSMiri Korenblit static void iwl_mld_set_pkt_ext_from_he_ppe(struct iwl_mld *mld, 199d1e879ecSMiri Korenblit struct ieee80211_link_sta *link_sta, 200d1e879ecSMiri Korenblit struct iwl_he_pkt_ext_v2 *pkt_ext, 201d1e879ecSMiri Korenblit bool inheritance) 202d1e879ecSMiri Korenblit { 203d1e879ecSMiri Korenblit u8 nss = (link_sta->he_cap.ppe_thres[0] & 204d1e879ecSMiri Korenblit IEEE80211_PPE_THRES_NSS_MASK) + 1; 205d1e879ecSMiri Korenblit u8 *ppe = &link_sta->he_cap.ppe_thres[0]; 206d1e879ecSMiri Korenblit u8 ru_index_bitmap = 207d1e879ecSMiri Korenblit u8_get_bits(*ppe, 208d1e879ecSMiri Korenblit IEEE80211_PPE_THRES_RU_INDEX_BITMASK_MASK); 209d1e879ecSMiri Korenblit /* Starting after PPE header */ 210d1e879ecSMiri Korenblit u8 ppe_pos_bit = IEEE80211_HE_PPE_THRES_INFO_HEADER_SIZE; 211d1e879ecSMiri Korenblit 212d1e879ecSMiri Korenblit iwl_mld_parse_ppe(mld, pkt_ext, nss, ru_index_bitmap, ppe, ppe_pos_bit, 213d1e879ecSMiri Korenblit inheritance); 214d1e879ecSMiri Korenblit } 215d1e879ecSMiri Korenblit 216d1e879ecSMiri Korenblit static int 217d1e879ecSMiri Korenblit iwl_mld_set_pkt_ext_from_nominal_padding(struct iwl_he_pkt_ext_v2 *pkt_ext, 218d1e879ecSMiri Korenblit u8 nominal_padding) 219d1e879ecSMiri Korenblit { 220d1e879ecSMiri Korenblit int low_th = -1; 221d1e879ecSMiri Korenblit int high_th = -1; 222d1e879ecSMiri Korenblit 223d1e879ecSMiri Korenblit /* all the macros are the same for EHT and HE */ 224d1e879ecSMiri Korenblit switch (nominal_padding) { 225d1e879ecSMiri Korenblit case IEEE80211_EHT_PHY_CAP5_COMMON_NOMINAL_PKT_PAD_0US: 226d1e879ecSMiri Korenblit low_th = IWL_HE_PKT_EXT_NONE; 227d1e879ecSMiri Korenblit high_th = IWL_HE_PKT_EXT_NONE; 228d1e879ecSMiri Korenblit break; 229d1e879ecSMiri Korenblit case IEEE80211_EHT_PHY_CAP5_COMMON_NOMINAL_PKT_PAD_8US: 230d1e879ecSMiri Korenblit low_th = IWL_HE_PKT_EXT_BPSK; 231d1e879ecSMiri Korenblit high_th = IWL_HE_PKT_EXT_NONE; 232d1e879ecSMiri Korenblit break; 233d1e879ecSMiri Korenblit case IEEE80211_EHT_PHY_CAP5_COMMON_NOMINAL_PKT_PAD_16US: 234d1e879ecSMiri Korenblit case IEEE80211_EHT_PHY_CAP5_COMMON_NOMINAL_PKT_PAD_20US: 235d1e879ecSMiri Korenblit low_th = IWL_HE_PKT_EXT_NONE; 236d1e879ecSMiri Korenblit high_th = IWL_HE_PKT_EXT_BPSK; 237d1e879ecSMiri Korenblit break; 238d1e879ecSMiri Korenblit } 239d1e879ecSMiri Korenblit 240d1e879ecSMiri Korenblit if (low_th < 0 || high_th < 0) 241d1e879ecSMiri Korenblit return -EINVAL; 242d1e879ecSMiri Korenblit 243d1e879ecSMiri Korenblit /* Set the PPE thresholds accordingly */ 244d1e879ecSMiri Korenblit for (int i = 0; i < MAX_HE_SUPP_NSS; i++) { 245d1e879ecSMiri Korenblit for (u8 bw = 0; 246d1e879ecSMiri Korenblit bw < ARRAY_SIZE(pkt_ext->pkt_ext_qam_th[i]); 247d1e879ecSMiri Korenblit bw++) { 248d1e879ecSMiri Korenblit pkt_ext->pkt_ext_qam_th[i][bw][0] = low_th; 249d1e879ecSMiri Korenblit pkt_ext->pkt_ext_qam_th[i][bw][1] = high_th; 250d1e879ecSMiri Korenblit } 251d1e879ecSMiri Korenblit } 252d1e879ecSMiri Korenblit 253d1e879ecSMiri Korenblit return 0; 254d1e879ecSMiri Korenblit } 255d1e879ecSMiri Korenblit 256d1e879ecSMiri Korenblit static void iwl_mld_get_optimal_ppe_info(struct iwl_he_pkt_ext_v2 *pkt_ext, 257d1e879ecSMiri Korenblit u8 nominal_padding) 258d1e879ecSMiri Korenblit { 259d1e879ecSMiri Korenblit for (int i = 0; i < MAX_HE_SUPP_NSS; i++) { 260d1e879ecSMiri Korenblit for (u8 bw = 0; bw < ARRAY_SIZE(pkt_ext->pkt_ext_qam_th[i]); 261d1e879ecSMiri Korenblit bw++) { 262d1e879ecSMiri Korenblit u8 *qam_th = &pkt_ext->pkt_ext_qam_th[i][bw][0]; 263d1e879ecSMiri Korenblit 264d1e879ecSMiri Korenblit if (nominal_padding > 265d1e879ecSMiri Korenblit IEEE80211_EHT_PHY_CAP5_COMMON_NOMINAL_PKT_PAD_8US && 266d1e879ecSMiri Korenblit qam_th[1] == IWL_HE_PKT_EXT_NONE) 267d1e879ecSMiri Korenblit qam_th[1] = IWL_HE_PKT_EXT_4096QAM; 268d1e879ecSMiri Korenblit else if (nominal_padding == 269d1e879ecSMiri Korenblit IEEE80211_EHT_PHY_CAP5_COMMON_NOMINAL_PKT_PAD_8US && 270d1e879ecSMiri Korenblit qam_th[0] == IWL_HE_PKT_EXT_NONE && 271d1e879ecSMiri Korenblit qam_th[1] == IWL_HE_PKT_EXT_NONE) 272d1e879ecSMiri Korenblit qam_th[0] = IWL_HE_PKT_EXT_4096QAM; 273d1e879ecSMiri Korenblit } 274d1e879ecSMiri Korenblit } 275d1e879ecSMiri Korenblit } 276d1e879ecSMiri Korenblit 277d1e879ecSMiri Korenblit static void iwl_mld_fill_pkt_ext(struct iwl_mld *mld, 278d1e879ecSMiri Korenblit struct ieee80211_link_sta *link_sta, 279d1e879ecSMiri Korenblit struct iwl_he_pkt_ext_v2 *pkt_ext) 280d1e879ecSMiri Korenblit { 281d1e879ecSMiri Korenblit if (WARN_ON(!link_sta)) 282d1e879ecSMiri Korenblit return; 283d1e879ecSMiri Korenblit 284d1e879ecSMiri Korenblit /* Initialize the PPE thresholds to "None" (7), as described in Table 285d1e879ecSMiri Korenblit * 9-262ac of 80211.ax/D3.0. 286d1e879ecSMiri Korenblit */ 287d1e879ecSMiri Korenblit memset(pkt_ext, IWL_HE_PKT_EXT_NONE, sizeof(*pkt_ext)); 288d1e879ecSMiri Korenblit 289d1e879ecSMiri Korenblit if (link_sta->eht_cap.has_eht) { 290d1e879ecSMiri Korenblit u8 nominal_padding = 291d1e879ecSMiri Korenblit u8_get_bits(link_sta->eht_cap.eht_cap_elem.phy_cap_info[5], 292d1e879ecSMiri Korenblit IEEE80211_EHT_PHY_CAP5_COMMON_NOMINAL_PKT_PAD_MASK); 293d1e879ecSMiri Korenblit 294d1e879ecSMiri Korenblit /* If PPE Thresholds exists, parse them into a FW-familiar 295d1e879ecSMiri Korenblit * format. 296d1e879ecSMiri Korenblit */ 297d1e879ecSMiri Korenblit if (link_sta->eht_cap.eht_cap_elem.phy_cap_info[5] & 298d1e879ecSMiri Korenblit IEEE80211_EHT_PHY_CAP5_PPE_THRESHOLD_PRESENT) { 299d1e879ecSMiri Korenblit u8 nss = (link_sta->eht_cap.eht_ppe_thres[0] & 300d1e879ecSMiri Korenblit IEEE80211_EHT_PPE_THRES_NSS_MASK) + 1; 301d1e879ecSMiri Korenblit u8 *ppe = &link_sta->eht_cap.eht_ppe_thres[0]; 302d1e879ecSMiri Korenblit u8 ru_index_bitmap = 303d1e879ecSMiri Korenblit u16_get_bits(*ppe, 304d1e879ecSMiri Korenblit IEEE80211_EHT_PPE_THRES_RU_INDEX_BITMASK_MASK); 305d1e879ecSMiri Korenblit /* Starting after PPE header */ 306d1e879ecSMiri Korenblit u8 ppe_pos_bit = IEEE80211_EHT_PPE_THRES_INFO_HEADER_SIZE; 307d1e879ecSMiri Korenblit 308d1e879ecSMiri Korenblit iwl_mld_parse_ppe(mld, pkt_ext, nss, ru_index_bitmap, 309d1e879ecSMiri Korenblit ppe, ppe_pos_bit, true); 310d1e879ecSMiri Korenblit /* EHT PPE Thresholds doesn't exist - set the API according to 311d1e879ecSMiri Korenblit * HE PPE Tresholds 312d1e879ecSMiri Korenblit */ 313d1e879ecSMiri Korenblit } else if (link_sta->he_cap.he_cap_elem.phy_cap_info[6] & 314d1e879ecSMiri Korenblit IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT) { 315d1e879ecSMiri Korenblit /* Even though HE Capabilities IE doesn't contain PPE 316d1e879ecSMiri Korenblit * Thresholds for BW 320Mhz, thresholds for this BW will 317d1e879ecSMiri Korenblit * be filled in with the same values as 160Mhz, due to 318d1e879ecSMiri Korenblit * the inheritance, as required. 319d1e879ecSMiri Korenblit */ 320d1e879ecSMiri Korenblit iwl_mld_set_pkt_ext_from_he_ppe(mld, link_sta, pkt_ext, 321d1e879ecSMiri Korenblit true); 322d1e879ecSMiri Korenblit 323d1e879ecSMiri Korenblit /* According to the requirements, for MCSs 12-13 the 324d1e879ecSMiri Korenblit * maximum value between HE PPE Threshold and Common 325d1e879ecSMiri Korenblit * Nominal Packet Padding needs to be taken 326d1e879ecSMiri Korenblit */ 327d1e879ecSMiri Korenblit iwl_mld_get_optimal_ppe_info(pkt_ext, nominal_padding); 328d1e879ecSMiri Korenblit 329d1e879ecSMiri Korenblit /* if PPE Thresholds doesn't present in both EHT IE and HE IE - 330d1e879ecSMiri Korenblit * take the Thresholds from Common Nominal Packet Padding field 331d1e879ecSMiri Korenblit */ 332d1e879ecSMiri Korenblit } else { 333d1e879ecSMiri Korenblit iwl_mld_set_pkt_ext_from_nominal_padding(pkt_ext, 334d1e879ecSMiri Korenblit nominal_padding); 335d1e879ecSMiri Korenblit } 336d1e879ecSMiri Korenblit } else if (link_sta->he_cap.has_he) { 337d1e879ecSMiri Korenblit /* If PPE Thresholds exist, parse them into a FW-familiar format. */ 338d1e879ecSMiri Korenblit if (link_sta->he_cap.he_cap_elem.phy_cap_info[6] & 339d1e879ecSMiri Korenblit IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT) { 340d1e879ecSMiri Korenblit iwl_mld_set_pkt_ext_from_he_ppe(mld, link_sta, pkt_ext, 341d1e879ecSMiri Korenblit false); 342d1e879ecSMiri Korenblit /* PPE Thresholds doesn't exist - set the API PPE values 343d1e879ecSMiri Korenblit * according to Common Nominal Packet Padding field. 344d1e879ecSMiri Korenblit */ 345d1e879ecSMiri Korenblit } else { 346d1e879ecSMiri Korenblit u8 nominal_padding = 347d1e879ecSMiri Korenblit u8_get_bits(link_sta->he_cap.he_cap_elem.phy_cap_info[9], 348d1e879ecSMiri Korenblit IEEE80211_HE_PHY_CAP9_NOMINAL_PKT_PADDING_MASK); 349d1e879ecSMiri Korenblit if (nominal_padding != IEEE80211_HE_PHY_CAP9_NOMINAL_PKT_PADDING_RESERVED) 350d1e879ecSMiri Korenblit iwl_mld_set_pkt_ext_from_nominal_padding(pkt_ext, 351d1e879ecSMiri Korenblit nominal_padding); 352d1e879ecSMiri Korenblit } 353d1e879ecSMiri Korenblit } 354d1e879ecSMiri Korenblit 355d1e879ecSMiri Korenblit for (int i = 0; i < MAX_HE_SUPP_NSS; i++) { 356d1e879ecSMiri Korenblit for (int bw = 0; 357d1e879ecSMiri Korenblit bw < ARRAY_SIZE(*pkt_ext->pkt_ext_qam_th[i]); 358d1e879ecSMiri Korenblit bw++) { 359d1e879ecSMiri Korenblit u8 *qam_th = 360d1e879ecSMiri Korenblit &pkt_ext->pkt_ext_qam_th[i][bw][0]; 361d1e879ecSMiri Korenblit 362d1e879ecSMiri Korenblit IWL_DEBUG_HT(mld, 363d1e879ecSMiri Korenblit "PPE table: nss[%d] bw[%d] PPET8 = %d, PPET16 = %d\n", 364d1e879ecSMiri Korenblit i, bw, qam_th[0], qam_th[1]); 365d1e879ecSMiri Korenblit } 366d1e879ecSMiri Korenblit } 367d1e879ecSMiri Korenblit } 368d1e879ecSMiri Korenblit 369d1e879ecSMiri Korenblit static u32 iwl_mld_get_htc_flags(struct ieee80211_link_sta *link_sta) 370d1e879ecSMiri Korenblit { 371d1e879ecSMiri Korenblit u8 *mac_cap_info = 372d1e879ecSMiri Korenblit &link_sta->he_cap.he_cap_elem.mac_cap_info[0]; 373d1e879ecSMiri Korenblit u32 htc_flags = 0; 374d1e879ecSMiri Korenblit 375d1e879ecSMiri Korenblit if (mac_cap_info[0] & IEEE80211_HE_MAC_CAP0_HTC_HE) 376d1e879ecSMiri Korenblit htc_flags |= IWL_HE_HTC_SUPPORT; 377d1e879ecSMiri Korenblit if ((mac_cap_info[1] & IEEE80211_HE_MAC_CAP1_LINK_ADAPTATION) || 378d1e879ecSMiri Korenblit (mac_cap_info[2] & IEEE80211_HE_MAC_CAP2_LINK_ADAPTATION)) { 379d1e879ecSMiri Korenblit u8 link_adap = 380d1e879ecSMiri Korenblit ((mac_cap_info[2] & 381d1e879ecSMiri Korenblit IEEE80211_HE_MAC_CAP2_LINK_ADAPTATION) << 1) + 382d1e879ecSMiri Korenblit (mac_cap_info[1] & 383d1e879ecSMiri Korenblit IEEE80211_HE_MAC_CAP1_LINK_ADAPTATION); 384d1e879ecSMiri Korenblit 385d1e879ecSMiri Korenblit if (link_adap == 2) 386d1e879ecSMiri Korenblit htc_flags |= 387d1e879ecSMiri Korenblit IWL_HE_HTC_LINK_ADAP_UNSOLICITED; 388d1e879ecSMiri Korenblit else if (link_adap == 3) 389d1e879ecSMiri Korenblit htc_flags |= IWL_HE_HTC_LINK_ADAP_BOTH; 390d1e879ecSMiri Korenblit } 391d1e879ecSMiri Korenblit if (mac_cap_info[2] & IEEE80211_HE_MAC_CAP2_BSR) 392d1e879ecSMiri Korenblit htc_flags |= IWL_HE_HTC_BSR_SUPP; 393d1e879ecSMiri Korenblit if (mac_cap_info[3] & IEEE80211_HE_MAC_CAP3_OMI_CONTROL) 394d1e879ecSMiri Korenblit htc_flags |= IWL_HE_HTC_OMI_SUPP; 395d1e879ecSMiri Korenblit if (mac_cap_info[4] & IEEE80211_HE_MAC_CAP4_BQR) 396d1e879ecSMiri Korenblit htc_flags |= IWL_HE_HTC_BQR_SUPP; 397d1e879ecSMiri Korenblit 398d1e879ecSMiri Korenblit return htc_flags; 399d1e879ecSMiri Korenblit } 400d1e879ecSMiri Korenblit 401d1e879ecSMiri Korenblit static int iwl_mld_send_sta_cmd(struct iwl_mld *mld, 402d1e879ecSMiri Korenblit const struct iwl_sta_cfg_cmd *cmd) 403d1e879ecSMiri Korenblit { 404d1e879ecSMiri Korenblit int ret = iwl_mld_send_cmd_pdu(mld, 405d1e879ecSMiri Korenblit WIDE_ID(MAC_CONF_GROUP, STA_CONFIG_CMD), 406d1e879ecSMiri Korenblit cmd); 407d1e879ecSMiri Korenblit if (ret) 408d1e879ecSMiri Korenblit IWL_ERR(mld, "STA_CONFIG_CMD send failed, ret=0x%x\n", ret); 409d1e879ecSMiri Korenblit return ret; 410d1e879ecSMiri Korenblit } 411d1e879ecSMiri Korenblit 412d1e879ecSMiri Korenblit static int 413d1e879ecSMiri Korenblit iwl_mld_add_modify_sta_cmd(struct iwl_mld *mld, 414d1e879ecSMiri Korenblit struct ieee80211_link_sta *link_sta) 415d1e879ecSMiri Korenblit { 416d1e879ecSMiri Korenblit struct ieee80211_sta *sta = link_sta->sta; 417d1e879ecSMiri Korenblit struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta); 418d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link; 419d1e879ecSMiri Korenblit struct iwl_mld_link *mld_link; 420d1e879ecSMiri Korenblit struct iwl_sta_cfg_cmd cmd = {}; 421d1e879ecSMiri Korenblit int fw_id = iwl_mld_fw_sta_id_from_link_sta(mld, link_sta); 422d1e879ecSMiri Korenblit 423d1e879ecSMiri Korenblit lockdep_assert_wiphy(mld->wiphy); 424d1e879ecSMiri Korenblit 425d1e879ecSMiri Korenblit link = link_conf_dereference_protected(mld_sta->vif, 426d1e879ecSMiri Korenblit link_sta->link_id); 427d1e879ecSMiri Korenblit 428d1e879ecSMiri Korenblit mld_link = iwl_mld_link_from_mac80211(link); 429d1e879ecSMiri Korenblit 430d1e879ecSMiri Korenblit if (WARN_ON(!link || !mld_link) || fw_id < 0) 431d1e879ecSMiri Korenblit return -EINVAL; 432d1e879ecSMiri Korenblit 433d1e879ecSMiri Korenblit cmd.sta_id = cpu_to_le32(fw_id); 434d1e879ecSMiri Korenblit cmd.station_type = cpu_to_le32(mld_sta->sta_type); 435d1e879ecSMiri Korenblit cmd.link_id = cpu_to_le32(mld_link->fw_id); 436d1e879ecSMiri Korenblit 437d1e879ecSMiri Korenblit memcpy(&cmd.peer_mld_address, sta->addr, ETH_ALEN); 438d1e879ecSMiri Korenblit memcpy(&cmd.peer_link_address, link_sta->addr, ETH_ALEN); 439d1e879ecSMiri Korenblit 440d1e879ecSMiri Korenblit if (mld_sta->sta_state >= IEEE80211_STA_ASSOC) 441d1e879ecSMiri Korenblit cmd.assoc_id = cpu_to_le32(sta->aid); 442d1e879ecSMiri Korenblit 443d1e879ecSMiri Korenblit if (sta->mfp || mld_sta->sta_state < IEEE80211_STA_AUTHORIZED) 444d1e879ecSMiri Korenblit cmd.mfp = cpu_to_le32(1); 445d1e879ecSMiri Korenblit 446d1e879ecSMiri Korenblit switch (link_sta->rx_nss) { 447d1e879ecSMiri Korenblit case 1: 448d1e879ecSMiri Korenblit cmd.mimo = cpu_to_le32(0); 449d1e879ecSMiri Korenblit break; 450d1e879ecSMiri Korenblit case 2 ... 8: 451d1e879ecSMiri Korenblit cmd.mimo = cpu_to_le32(1); 452d1e879ecSMiri Korenblit break; 453d1e879ecSMiri Korenblit } 454d1e879ecSMiri Korenblit 455d1e879ecSMiri Korenblit switch (link_sta->smps_mode) { 456d1e879ecSMiri Korenblit case IEEE80211_SMPS_AUTOMATIC: 457d1e879ecSMiri Korenblit case IEEE80211_SMPS_NUM_MODES: 458d1e879ecSMiri Korenblit WARN_ON(1); 459d1e879ecSMiri Korenblit break; 460d1e879ecSMiri Korenblit case IEEE80211_SMPS_STATIC: 461d1e879ecSMiri Korenblit /* override NSS */ 462d1e879ecSMiri Korenblit cmd.mimo = cpu_to_le32(0); 463d1e879ecSMiri Korenblit break; 464d1e879ecSMiri Korenblit case IEEE80211_SMPS_DYNAMIC: 465d1e879ecSMiri Korenblit cmd.mimo_protection = cpu_to_le32(1); 466d1e879ecSMiri Korenblit break; 467d1e879ecSMiri Korenblit case IEEE80211_SMPS_OFF: 468d1e879ecSMiri Korenblit /* nothing */ 469d1e879ecSMiri Korenblit break; 470d1e879ecSMiri Korenblit } 471d1e879ecSMiri Korenblit 472d1e879ecSMiri Korenblit iwl_mld_fill_ampdu_size_and_dens(link_sta, link, 473d1e879ecSMiri Korenblit &cmd.tx_ampdu_max_size, 474d1e879ecSMiri Korenblit &cmd.tx_ampdu_spacing); 475d1e879ecSMiri Korenblit 476d1e879ecSMiri Korenblit if (sta->wme) { 477d1e879ecSMiri Korenblit cmd.sp_length = 478d1e879ecSMiri Korenblit cpu_to_le32(sta->max_sp ? sta->max_sp * 2 : 128); 479d1e879ecSMiri Korenblit cmd.uapsd_acs = cpu_to_le32(iwl_mld_get_uapsd_acs(sta)); 480d1e879ecSMiri Korenblit } 481d1e879ecSMiri Korenblit 482d1e879ecSMiri Korenblit if (link_sta->he_cap.has_he) { 483d1e879ecSMiri Korenblit cmd.trig_rnd_alloc = 484d1e879ecSMiri Korenblit cpu_to_le32(link->uora_exists ? 1 : 0); 485d1e879ecSMiri Korenblit 486d1e879ecSMiri Korenblit /* PPE Thresholds */ 487d1e879ecSMiri Korenblit iwl_mld_fill_pkt_ext(mld, link_sta, &cmd.pkt_ext); 488d1e879ecSMiri Korenblit 489d1e879ecSMiri Korenblit /* HTC flags */ 490d1e879ecSMiri Korenblit cmd.htc_flags = 491d1e879ecSMiri Korenblit cpu_to_le32(iwl_mld_get_htc_flags(link_sta)); 492d1e879ecSMiri Korenblit 493d1e879ecSMiri Korenblit if (link_sta->he_cap.he_cap_elem.mac_cap_info[2] & 494d1e879ecSMiri Korenblit IEEE80211_HE_MAC_CAP2_ACK_EN) 495d1e879ecSMiri Korenblit cmd.ack_enabled = cpu_to_le32(1); 496d1e879ecSMiri Korenblit } 497d1e879ecSMiri Korenblit 498d1e879ecSMiri Korenblit return iwl_mld_send_sta_cmd(mld, &cmd); 499d1e879ecSMiri Korenblit } 500d1e879ecSMiri Korenblit 501d1e879ecSMiri Korenblit IWL_MLD_ALLOC_FN(link_sta, link_sta) 502d1e879ecSMiri Korenblit 503d1e879ecSMiri Korenblit static int 504d1e879ecSMiri Korenblit iwl_mld_add_link_sta(struct iwl_mld *mld, struct ieee80211_link_sta *link_sta) 505d1e879ecSMiri Korenblit { 506d1e879ecSMiri Korenblit struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta); 507d1e879ecSMiri Korenblit struct iwl_mld_link_sta *mld_link_sta; 508d1e879ecSMiri Korenblit int ret; 509d1e879ecSMiri Korenblit u8 fw_id; 510d1e879ecSMiri Korenblit 511d1e879ecSMiri Korenblit lockdep_assert_wiphy(mld->wiphy); 512d1e879ecSMiri Korenblit 513d1e879ecSMiri Korenblit /* We will fail to add it to the FW anyway */ 514d1e879ecSMiri Korenblit if (iwl_mld_error_before_recovery(mld)) 515d1e879ecSMiri Korenblit return -ENODEV; 516d1e879ecSMiri Korenblit 517d1e879ecSMiri Korenblit mld_link_sta = iwl_mld_link_sta_from_mac80211(link_sta); 518d1e879ecSMiri Korenblit 519d1e879ecSMiri Korenblit /* We need to preserve the fw sta ids during a restart, since the fw 520d1e879ecSMiri Korenblit * will recover SN/PN for them, this is why the mld_link_sta exists. 521d1e879ecSMiri Korenblit */ 522d1e879ecSMiri Korenblit if (mld_link_sta) { 523d1e879ecSMiri Korenblit /* But if we are not restarting, this is not OK */ 524d1e879ecSMiri Korenblit WARN_ON(!mld->fw_status.in_hw_restart); 525d1e879ecSMiri Korenblit 526d1e879ecSMiri Korenblit /* Avoid adding a STA that is already in FW to avoid an assert */ 527d1e879ecSMiri Korenblit if (WARN_ON(mld_link_sta->in_fw)) 528d1e879ecSMiri Korenblit return -EINVAL; 529d1e879ecSMiri Korenblit 530d1e879ecSMiri Korenblit fw_id = mld_link_sta->fw_id; 531d1e879ecSMiri Korenblit goto add_to_fw; 532d1e879ecSMiri Korenblit } 533d1e879ecSMiri Korenblit 534d1e879ecSMiri Korenblit /* Allocate a fw id and map it to the link_sta */ 535d1e879ecSMiri Korenblit ret = iwl_mld_allocate_link_sta_fw_id(mld, &fw_id, link_sta); 536d1e879ecSMiri Korenblit if (ret) 537d1e879ecSMiri Korenblit return ret; 538d1e879ecSMiri Korenblit 539d1e879ecSMiri Korenblit if (link_sta == &link_sta->sta->deflink) { 540d1e879ecSMiri Korenblit mld_link_sta = &mld_sta->deflink; 541d1e879ecSMiri Korenblit } else { 542d1e879ecSMiri Korenblit mld_link_sta = kzalloc(sizeof(*mld_link_sta), GFP_KERNEL); 543d1e879ecSMiri Korenblit if (!mld_link_sta) 544d1e879ecSMiri Korenblit return -ENOMEM; 545d1e879ecSMiri Korenblit } 546d1e879ecSMiri Korenblit 547d1e879ecSMiri Korenblit mld_link_sta->fw_id = fw_id; 548d1e879ecSMiri Korenblit rcu_assign_pointer(mld_sta->link[link_sta->link_id], mld_link_sta); 549d1e879ecSMiri Korenblit 550d1e879ecSMiri Korenblit add_to_fw: 551d1e879ecSMiri Korenblit ret = iwl_mld_add_modify_sta_cmd(mld, link_sta); 552d1e879ecSMiri Korenblit if (ret) { 553d1e879ecSMiri Korenblit RCU_INIT_POINTER(mld->fw_id_to_link_sta[fw_id], NULL); 554d1e879ecSMiri Korenblit RCU_INIT_POINTER(mld_sta->link[link_sta->link_id], NULL); 555d1e879ecSMiri Korenblit if (link_sta != &link_sta->sta->deflink) 556d1e879ecSMiri Korenblit kfree(mld_link_sta); 557d1e879ecSMiri Korenblit return ret; 558d1e879ecSMiri Korenblit } 559d1e879ecSMiri Korenblit mld_link_sta->in_fw = true; 560d1e879ecSMiri Korenblit 561d1e879ecSMiri Korenblit return 0; 562d1e879ecSMiri Korenblit } 563d1e879ecSMiri Korenblit 564d1e879ecSMiri Korenblit static int iwl_mld_rm_sta_from_fw(struct iwl_mld *mld, u8 fw_sta_id) 565d1e879ecSMiri Korenblit { 566d1e879ecSMiri Korenblit struct iwl_remove_sta_cmd cmd = { 567d1e879ecSMiri Korenblit .sta_id = cpu_to_le32(fw_sta_id), 568d1e879ecSMiri Korenblit }; 569d1e879ecSMiri Korenblit int ret; 570d1e879ecSMiri Korenblit 571d1e879ecSMiri Korenblit ret = iwl_mld_send_cmd_pdu(mld, 572d1e879ecSMiri Korenblit WIDE_ID(MAC_CONF_GROUP, STA_REMOVE_CMD), 573d1e879ecSMiri Korenblit &cmd); 574d1e879ecSMiri Korenblit if (ret) 575d1e879ecSMiri Korenblit IWL_ERR(mld, "Failed to remove station. Id=%d\n", fw_sta_id); 576d1e879ecSMiri Korenblit 577d1e879ecSMiri Korenblit return ret; 578d1e879ecSMiri Korenblit } 579d1e879ecSMiri Korenblit 580d1e879ecSMiri Korenblit static void 581d1e879ecSMiri Korenblit iwl_mld_remove_link_sta(struct iwl_mld *mld, 582d1e879ecSMiri Korenblit struct ieee80211_link_sta *link_sta) 583d1e879ecSMiri Korenblit { 584d1e879ecSMiri Korenblit struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta); 585d1e879ecSMiri Korenblit struct iwl_mld_link_sta *mld_link_sta = 586d1e879ecSMiri Korenblit iwl_mld_link_sta_from_mac80211(link_sta); 587d1e879ecSMiri Korenblit 588d1e879ecSMiri Korenblit if (WARN_ON(!mld_link_sta)) 589d1e879ecSMiri Korenblit return; 590d1e879ecSMiri Korenblit 591d1e879ecSMiri Korenblit iwl_mld_rm_sta_from_fw(mld, mld_link_sta->fw_id); 592d1e879ecSMiri Korenblit mld_link_sta->in_fw = false; 593d1e879ecSMiri Korenblit 594d1e879ecSMiri Korenblit /* Now that the STA doesn't exist in FW, we don't expect any new 595d1e879ecSMiri Korenblit * notifications for it. Cancel the ones that are already pending 596d1e879ecSMiri Korenblit */ 597d1e879ecSMiri Korenblit iwl_mld_cancel_notifications_of_object(mld, IWL_MLD_OBJECT_TYPE_STA, 598d1e879ecSMiri Korenblit mld_link_sta->fw_id); 599d1e879ecSMiri Korenblit 600d1e879ecSMiri Korenblit /* This will not be done upon reconfig, so do it also when 601d1e879ecSMiri Korenblit * failed to remove from fw 602d1e879ecSMiri Korenblit */ 603d1e879ecSMiri Korenblit RCU_INIT_POINTER(mld->fw_id_to_link_sta[mld_link_sta->fw_id], NULL); 604d1e879ecSMiri Korenblit RCU_INIT_POINTER(mld_sta->link[link_sta->link_id], NULL); 605d1e879ecSMiri Korenblit if (mld_link_sta != &mld_sta->deflink) 606d1e879ecSMiri Korenblit kfree_rcu(mld_link_sta, rcu_head); 607d1e879ecSMiri Korenblit } 608d1e879ecSMiri Korenblit 609*36b79cb0SIlan Peer static void iwl_mld_set_max_amsdu_len(struct iwl_mld *mld, 610*36b79cb0SIlan Peer struct ieee80211_link_sta *link_sta) 611*36b79cb0SIlan Peer { 612*36b79cb0SIlan Peer const struct ieee80211_sta_ht_cap *ht_cap = &link_sta->ht_cap; 613*36b79cb0SIlan Peer 614*36b79cb0SIlan Peer /* For EHT, HE and VHT we can use the value as it was calculated by 615*36b79cb0SIlan Peer * mac80211. For HT, mac80211 doesn't enforce to 4095, so force it 616*36b79cb0SIlan Peer * here 617*36b79cb0SIlan Peer */ 618*36b79cb0SIlan Peer if (link_sta->eht_cap.has_eht || link_sta->he_cap.has_he || 619*36b79cb0SIlan Peer link_sta->vht_cap.vht_supported || 620*36b79cb0SIlan Peer !ht_cap->ht_supported || 621*36b79cb0SIlan Peer !(ht_cap->cap & IEEE80211_HT_CAP_MAX_AMSDU)) 622*36b79cb0SIlan Peer return; 623*36b79cb0SIlan Peer 624*36b79cb0SIlan Peer link_sta->agg.max_amsdu_len = IEEE80211_MAX_MPDU_LEN_HT_BA; 625*36b79cb0SIlan Peer ieee80211_sta_recalc_aggregates(link_sta->sta); 626*36b79cb0SIlan Peer } 627*36b79cb0SIlan Peer 628d1e879ecSMiri Korenblit int iwl_mld_update_all_link_stations(struct iwl_mld *mld, 629d1e879ecSMiri Korenblit struct ieee80211_sta *sta) 630d1e879ecSMiri Korenblit { 631d1e879ecSMiri Korenblit struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta); 632d1e879ecSMiri Korenblit struct ieee80211_link_sta *link_sta; 633d1e879ecSMiri Korenblit int link_id; 634d1e879ecSMiri Korenblit 635d1e879ecSMiri Korenblit for_each_sta_active_link(mld_sta->vif, sta, link_sta, link_id) { 636d1e879ecSMiri Korenblit int ret = iwl_mld_add_modify_sta_cmd(mld, link_sta); 637d1e879ecSMiri Korenblit 638d1e879ecSMiri Korenblit if (ret) 639d1e879ecSMiri Korenblit return ret; 640*36b79cb0SIlan Peer 641*36b79cb0SIlan Peer if (mld_sta->sta_state == IEEE80211_STA_ASSOC) 642*36b79cb0SIlan Peer iwl_mld_set_max_amsdu_len(mld, link_sta); 643d1e879ecSMiri Korenblit } 644d1e879ecSMiri Korenblit return 0; 645d1e879ecSMiri Korenblit } 646d1e879ecSMiri Korenblit 647d1e879ecSMiri Korenblit static void iwl_mld_destroy_sta(struct ieee80211_sta *sta) 648d1e879ecSMiri Korenblit { 649d1e879ecSMiri Korenblit struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta); 650d1e879ecSMiri Korenblit 651d1e879ecSMiri Korenblit kfree(mld_sta->dup_data); 652d1e879ecSMiri Korenblit kfree(mld_sta->mpdu_counters); 653d1e879ecSMiri Korenblit } 654d1e879ecSMiri Korenblit 655d1e879ecSMiri Korenblit static int 656d1e879ecSMiri Korenblit iwl_mld_alloc_dup_data(struct iwl_mld *mld, struct iwl_mld_sta *mld_sta) 657d1e879ecSMiri Korenblit { 658d1e879ecSMiri Korenblit struct iwl_mld_rxq_dup_data *dup_data; 659d1e879ecSMiri Korenblit 660d1e879ecSMiri Korenblit if (mld->fw_status.in_hw_restart) 661d1e879ecSMiri Korenblit return 0; 662d1e879ecSMiri Korenblit 663d1e879ecSMiri Korenblit dup_data = kcalloc(mld->trans->num_rx_queues, sizeof(*dup_data), 664d1e879ecSMiri Korenblit GFP_KERNEL); 665d1e879ecSMiri Korenblit if (!dup_data) 666d1e879ecSMiri Korenblit return -ENOMEM; 667d1e879ecSMiri Korenblit 668d1e879ecSMiri Korenblit /* Initialize all the last_seq values to 0xffff which can never 669d1e879ecSMiri Korenblit * compare equal to the frame's seq_ctrl in the check in 670d1e879ecSMiri Korenblit * iwl_mld_is_dup() since the lower 4 bits are the fragment 671d1e879ecSMiri Korenblit * number and fragmented packets don't reach that function. 672d1e879ecSMiri Korenblit * 673d1e879ecSMiri Korenblit * This thus allows receiving a packet with seqno 0 and the 674d1e879ecSMiri Korenblit * retry bit set as the very first packet on a new TID. 675d1e879ecSMiri Korenblit */ 676d1e879ecSMiri Korenblit for (int q = 0; q < mld->trans->num_rx_queues; q++) 677d1e879ecSMiri Korenblit memset(dup_data[q].last_seq, 0xff, 678d1e879ecSMiri Korenblit sizeof(dup_data[q].last_seq)); 679d1e879ecSMiri Korenblit mld_sta->dup_data = dup_data; 680d1e879ecSMiri Korenblit 681d1e879ecSMiri Korenblit return 0; 682d1e879ecSMiri Korenblit } 683d1e879ecSMiri Korenblit 684d1e879ecSMiri Korenblit static void iwl_mld_alloc_mpdu_counters(struct iwl_mld *mld, 685d1e879ecSMiri Korenblit struct ieee80211_sta *sta) 686d1e879ecSMiri Korenblit { 687d1e879ecSMiri Korenblit struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta); 688d1e879ecSMiri Korenblit struct ieee80211_vif *vif = mld_sta->vif; 689d1e879ecSMiri Korenblit 690d1e879ecSMiri Korenblit if (mld->fw_status.in_hw_restart) 691d1e879ecSMiri Korenblit return; 692d1e879ecSMiri Korenblit 693d1e879ecSMiri Korenblit /* MPDUs are counted only when EMLSR is possible */ 694d1e879ecSMiri Korenblit if (ieee80211_vif_type_p2p(vif) != NL80211_IFTYPE_STATION || 695d1e879ecSMiri Korenblit sta->tdls || !ieee80211_vif_is_mld(vif)) 696d1e879ecSMiri Korenblit return; 697d1e879ecSMiri Korenblit 698d1e879ecSMiri Korenblit mld_sta->mpdu_counters = kcalloc(mld->trans->num_rx_queues, 699d1e879ecSMiri Korenblit sizeof(*mld_sta->mpdu_counters), 700d1e879ecSMiri Korenblit GFP_KERNEL); 701d1e879ecSMiri Korenblit if (!mld_sta->mpdu_counters) 702d1e879ecSMiri Korenblit return; 703d1e879ecSMiri Korenblit 704d1e879ecSMiri Korenblit for (int q = 0; q < mld->trans->num_rx_queues; q++) 705d1e879ecSMiri Korenblit spin_lock_init(&mld_sta->mpdu_counters[q].lock); 706d1e879ecSMiri Korenblit } 707d1e879ecSMiri Korenblit 708d1e879ecSMiri Korenblit static int 709d1e879ecSMiri Korenblit iwl_mld_init_sta(struct iwl_mld *mld, struct ieee80211_sta *sta, 710d1e879ecSMiri Korenblit struct ieee80211_vif *vif, enum iwl_fw_sta_type type) 711d1e879ecSMiri Korenblit { 712d1e879ecSMiri Korenblit struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta); 713d1e879ecSMiri Korenblit 714d1e879ecSMiri Korenblit mld_sta->vif = vif; 715d1e879ecSMiri Korenblit mld_sta->sta_type = type; 716d1e879ecSMiri Korenblit mld_sta->mld = mld; 717d1e879ecSMiri Korenblit 718d1e879ecSMiri Korenblit if (!mld->fw_status.in_hw_restart) 719d1e879ecSMiri Korenblit for (int i = 0; i < ARRAY_SIZE(sta->txq); i++) 720d1e879ecSMiri Korenblit iwl_mld_init_txq(iwl_mld_txq_from_mac80211(sta->txq[i])); 721d1e879ecSMiri Korenblit 722d1e879ecSMiri Korenblit iwl_mld_alloc_mpdu_counters(mld, sta); 723d1e879ecSMiri Korenblit 724d1e879ecSMiri Korenblit iwl_mld_toggle_tx_ant(mld, &mld_sta->data_tx_ant); 725d1e879ecSMiri Korenblit 726d1e879ecSMiri Korenblit return iwl_mld_alloc_dup_data(mld, mld_sta); 727d1e879ecSMiri Korenblit } 728d1e879ecSMiri Korenblit 729d1e879ecSMiri Korenblit int iwl_mld_add_sta(struct iwl_mld *mld, struct ieee80211_sta *sta, 730d1e879ecSMiri Korenblit struct ieee80211_vif *vif, enum iwl_fw_sta_type type) 731d1e879ecSMiri Korenblit { 732d1e879ecSMiri Korenblit struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta); 733d1e879ecSMiri Korenblit struct ieee80211_link_sta *link_sta; 734d1e879ecSMiri Korenblit int link_id; 735d1e879ecSMiri Korenblit int ret; 736d1e879ecSMiri Korenblit 737d1e879ecSMiri Korenblit ret = iwl_mld_init_sta(mld, sta, vif, type); 738d1e879ecSMiri Korenblit if (ret) 739d1e879ecSMiri Korenblit return ret; 740d1e879ecSMiri Korenblit 741d1e879ecSMiri Korenblit /* We could have add only the deflink link_sta, but it will not work 742d1e879ecSMiri Korenblit * in the restart case if the single link that is active during 743d1e879ecSMiri Korenblit * reconfig is not the deflink one. 744d1e879ecSMiri Korenblit */ 745d1e879ecSMiri Korenblit for_each_sta_active_link(mld_sta->vif, sta, link_sta, link_id) { 746d1e879ecSMiri Korenblit ret = iwl_mld_add_link_sta(mld, link_sta); 747d1e879ecSMiri Korenblit if (ret) 748d1e879ecSMiri Korenblit goto destroy_sta; 749d1e879ecSMiri Korenblit } 750d1e879ecSMiri Korenblit 751d1e879ecSMiri Korenblit return 0; 752d1e879ecSMiri Korenblit 753d1e879ecSMiri Korenblit destroy_sta: 754d1e879ecSMiri Korenblit iwl_mld_destroy_sta(sta); 755d1e879ecSMiri Korenblit 756d1e879ecSMiri Korenblit return ret; 757d1e879ecSMiri Korenblit } 758d1e879ecSMiri Korenblit 759d1e879ecSMiri Korenblit void iwl_mld_flush_sta_txqs(struct iwl_mld *mld, struct ieee80211_sta *sta) 760d1e879ecSMiri Korenblit { 761d1e879ecSMiri Korenblit struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta); 762d1e879ecSMiri Korenblit struct ieee80211_link_sta *link_sta; 763d1e879ecSMiri Korenblit int link_id; 764d1e879ecSMiri Korenblit 765d1e879ecSMiri Korenblit for_each_sta_active_link(mld_sta->vif, sta, link_sta, link_id) { 766d1e879ecSMiri Korenblit int fw_sta_id = iwl_mld_fw_sta_id_from_link_sta(mld, link_sta); 767d1e879ecSMiri Korenblit 768d1e879ecSMiri Korenblit if (fw_sta_id < 0) 769d1e879ecSMiri Korenblit continue; 770d1e879ecSMiri Korenblit 771d1e879ecSMiri Korenblit iwl_mld_flush_link_sta_txqs(mld, fw_sta_id); 772d1e879ecSMiri Korenblit } 773d1e879ecSMiri Korenblit } 774d1e879ecSMiri Korenblit 775d1e879ecSMiri Korenblit void iwl_mld_wait_sta_txqs_empty(struct iwl_mld *mld, struct ieee80211_sta *sta) 776d1e879ecSMiri Korenblit { 777d1e879ecSMiri Korenblit /* Avoid a warning in iwl_trans_wait_txq_empty if are anyway on the way 778d1e879ecSMiri Korenblit * to a restart. 779d1e879ecSMiri Korenblit */ 780d1e879ecSMiri Korenblit if (iwl_mld_error_before_recovery(mld)) 781d1e879ecSMiri Korenblit return; 782d1e879ecSMiri Korenblit 783d1e879ecSMiri Korenblit for (int i = 0; i < ARRAY_SIZE(sta->txq); i++) { 784d1e879ecSMiri Korenblit struct iwl_mld_txq *mld_txq = 785d1e879ecSMiri Korenblit iwl_mld_txq_from_mac80211(sta->txq[i]); 786d1e879ecSMiri Korenblit 787d1e879ecSMiri Korenblit if (!mld_txq->status.allocated) 788d1e879ecSMiri Korenblit continue; 789d1e879ecSMiri Korenblit 790d1e879ecSMiri Korenblit iwl_trans_wait_txq_empty(mld->trans, mld_txq->fw_id); 791d1e879ecSMiri Korenblit } 792d1e879ecSMiri Korenblit } 793d1e879ecSMiri Korenblit 794d1e879ecSMiri Korenblit void iwl_mld_remove_sta(struct iwl_mld *mld, struct ieee80211_sta *sta) 795d1e879ecSMiri Korenblit { 796d1e879ecSMiri Korenblit struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta); 797d1e879ecSMiri Korenblit struct ieee80211_vif *vif = mld_sta->vif; 798d1e879ecSMiri Korenblit struct ieee80211_link_sta *link_sta; 799d1e879ecSMiri Korenblit u8 link_id; 800d1e879ecSMiri Korenblit 801d1e879ecSMiri Korenblit lockdep_assert_wiphy(mld->wiphy); 802d1e879ecSMiri Korenblit 803d1e879ecSMiri Korenblit /* Tell the HW to flush the queues */ 804d1e879ecSMiri Korenblit iwl_mld_flush_sta_txqs(mld, sta); 805d1e879ecSMiri Korenblit 806d1e879ecSMiri Korenblit /* Wait for trans to empty its queues */ 807d1e879ecSMiri Korenblit iwl_mld_wait_sta_txqs_empty(mld, sta); 808d1e879ecSMiri Korenblit 809d1e879ecSMiri Korenblit /* Now we can remove the queues */ 810d1e879ecSMiri Korenblit for (int i = 0; i < ARRAY_SIZE(sta->txq); i++) 811d1e879ecSMiri Korenblit iwl_mld_remove_txq(mld, sta->txq[i]); 812d1e879ecSMiri Korenblit 813d1e879ecSMiri Korenblit for_each_sta_active_link(vif, sta, link_sta, link_id) { 814d1e879ecSMiri Korenblit /* Mac8011 will remove the groupwise keys after the sta is 815d1e879ecSMiri Korenblit * removed, but FW expects all the keys to be removed before 816d1e879ecSMiri Korenblit * the STA is, so remove them all here. 817d1e879ecSMiri Korenblit */ 81867128af0SJohannes Berg if (vif->type == NL80211_IFTYPE_STATION && !sta->tdls) 819d1e879ecSMiri Korenblit iwl_mld_remove_ap_keys(mld, vif, sta, link_id); 820d1e879ecSMiri Korenblit 821d1e879ecSMiri Korenblit /* Remove the link_sta */ 822d1e879ecSMiri Korenblit iwl_mld_remove_link_sta(mld, link_sta); 823d1e879ecSMiri Korenblit } 824d1e879ecSMiri Korenblit 825d1e879ecSMiri Korenblit iwl_mld_destroy_sta(sta); 826d1e879ecSMiri Korenblit } 827d1e879ecSMiri Korenblit 828d1e879ecSMiri Korenblit u32 iwl_mld_fw_sta_id_mask(struct iwl_mld *mld, struct ieee80211_sta *sta) 829d1e879ecSMiri Korenblit { 830d1e879ecSMiri Korenblit struct ieee80211_vif *vif = iwl_mld_sta_from_mac80211(sta)->vif; 831d1e879ecSMiri Korenblit struct ieee80211_link_sta *link_sta; 832d1e879ecSMiri Korenblit unsigned int link_id; 833d1e879ecSMiri Korenblit u32 result = 0; 834d1e879ecSMiri Korenblit 835d1e879ecSMiri Korenblit KUNIT_STATIC_STUB_REDIRECT(iwl_mld_fw_sta_id_mask, mld, sta); 836d1e879ecSMiri Korenblit 837d1e879ecSMiri Korenblit /* This function should only be used with the wiphy lock held, 838d1e879ecSMiri Korenblit * In other cases, it is not guaranteed that the link_sta will exist 839d1e879ecSMiri Korenblit * in the driver too, and it is checked in 840d1e879ecSMiri Korenblit * iwl_mld_fw_sta_id_from_link_sta. 841d1e879ecSMiri Korenblit */ 842d1e879ecSMiri Korenblit lockdep_assert_wiphy(mld->wiphy); 843d1e879ecSMiri Korenblit 844d1e879ecSMiri Korenblit for_each_sta_active_link(vif, sta, link_sta, link_id) { 845d1e879ecSMiri Korenblit int fw_id = iwl_mld_fw_sta_id_from_link_sta(mld, link_sta); 846d1e879ecSMiri Korenblit 847d1e879ecSMiri Korenblit if (!(fw_id < 0)) 848d1e879ecSMiri Korenblit result |= BIT(fw_id); 849d1e879ecSMiri Korenblit } 850d1e879ecSMiri Korenblit 851d1e879ecSMiri Korenblit return result; 852d1e879ecSMiri Korenblit } 853d1e879ecSMiri Korenblit EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_fw_sta_id_mask); 854d1e879ecSMiri Korenblit 855d1e879ecSMiri Korenblit static void iwl_mld_count_mpdu(struct ieee80211_link_sta *link_sta, int queue, 856d1e879ecSMiri Korenblit u32 count, bool tx) 857d1e879ecSMiri Korenblit { 858d1e879ecSMiri Korenblit struct iwl_mld_per_q_mpdu_counter *queue_counter; 859d1e879ecSMiri Korenblit struct iwl_mld_per_link_mpdu_counter *link_counter; 860d1e879ecSMiri Korenblit struct iwl_mld_vif *mld_vif; 861d1e879ecSMiri Korenblit struct iwl_mld_sta *mld_sta; 862d1e879ecSMiri Korenblit struct iwl_mld_link *mld_link; 863d1e879ecSMiri Korenblit struct iwl_mld *mld; 864d1e879ecSMiri Korenblit int total_mpdus = 0; 865d1e879ecSMiri Korenblit 866d1e879ecSMiri Korenblit if (WARN_ON(!link_sta)) 867d1e879ecSMiri Korenblit return; 868d1e879ecSMiri Korenblit 869d1e879ecSMiri Korenblit mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta); 870d1e879ecSMiri Korenblit if (!mld_sta->mpdu_counters) 871d1e879ecSMiri Korenblit return; 872d1e879ecSMiri Korenblit 873d1e879ecSMiri Korenblit mld_vif = iwl_mld_vif_from_mac80211(mld_sta->vif); 874d1e879ecSMiri Korenblit mld_link = iwl_mld_link_dereference_check(mld_vif, link_sta->link_id); 875d1e879ecSMiri Korenblit 876d1e879ecSMiri Korenblit if (WARN_ON_ONCE(!mld_link)) 877d1e879ecSMiri Korenblit return; 878d1e879ecSMiri Korenblit 879d1e879ecSMiri Korenblit queue_counter = &mld_sta->mpdu_counters[queue]; 880d1e879ecSMiri Korenblit 881d1e879ecSMiri Korenblit mld = mld_vif->mld; 882d1e879ecSMiri Korenblit 883d1e879ecSMiri Korenblit /* If it the window is over, first clear the counters. 884d1e879ecSMiri Korenblit * When we are not blocked by TPT, the window is managed by check_tpt_wk 885d1e879ecSMiri Korenblit */ 886d1e879ecSMiri Korenblit if ((mld_vif->emlsr.blocked_reasons & IWL_MLD_EMLSR_BLOCKED_TPT) && 887d1e879ecSMiri Korenblit time_is_before_jiffies(queue_counter->window_start_time + 888d1e879ecSMiri Korenblit IWL_MLD_TPT_COUNT_WINDOW)) { 889d1e879ecSMiri Korenblit memset(queue_counter->per_link, 0, 890d1e879ecSMiri Korenblit sizeof(queue_counter->per_link)); 891d1e879ecSMiri Korenblit queue_counter->window_start_time = jiffies; 892d1e879ecSMiri Korenblit 893d1e879ecSMiri Korenblit IWL_DEBUG_INFO(mld, "MPDU counters are cleared\n"); 894d1e879ecSMiri Korenblit } 895d1e879ecSMiri Korenblit 896d1e879ecSMiri Korenblit link_counter = &queue_counter->per_link[mld_link->fw_id]; 897d1e879ecSMiri Korenblit 898d1e879ecSMiri Korenblit spin_lock_bh(&queue_counter->lock); 899d1e879ecSMiri Korenblit 900d1e879ecSMiri Korenblit /* Update the statistics for this TPT measurement window */ 901d1e879ecSMiri Korenblit if (tx) 902d1e879ecSMiri Korenblit link_counter->tx += count; 903d1e879ecSMiri Korenblit else 904d1e879ecSMiri Korenblit link_counter->rx += count; 905d1e879ecSMiri Korenblit 906d1e879ecSMiri Korenblit /* 907d1e879ecSMiri Korenblit * Next, evaluate whether we should queue an unblock, 908d1e879ecSMiri Korenblit * skip this if we are not blocked due to low throughput. 909d1e879ecSMiri Korenblit */ 910d1e879ecSMiri Korenblit if (!(mld_vif->emlsr.blocked_reasons & IWL_MLD_EMLSR_BLOCKED_TPT)) 911d1e879ecSMiri Korenblit goto unlock; 912d1e879ecSMiri Korenblit 913d1e879ecSMiri Korenblit for (int i = 0; i <= IWL_FW_MAX_LINK_ID; i++) 914d1e879ecSMiri Korenblit total_mpdus += tx ? queue_counter->per_link[i].tx : 915d1e879ecSMiri Korenblit queue_counter->per_link[i].rx; 916d1e879ecSMiri Korenblit 917d1e879ecSMiri Korenblit /* Unblock is already queued if the threshold was reached before */ 918d1e879ecSMiri Korenblit if (total_mpdus - count >= IWL_MLD_ENTER_EMLSR_TPT_THRESH) 919d1e879ecSMiri Korenblit goto unlock; 920d1e879ecSMiri Korenblit 921d1e879ecSMiri Korenblit if (total_mpdus >= IWL_MLD_ENTER_EMLSR_TPT_THRESH) 922d1e879ecSMiri Korenblit wiphy_work_queue(mld->wiphy, &mld_vif->emlsr.unblock_tpt_wk); 923d1e879ecSMiri Korenblit 924d1e879ecSMiri Korenblit unlock: 925d1e879ecSMiri Korenblit spin_unlock_bh(&queue_counter->lock); 926d1e879ecSMiri Korenblit } 927d1e879ecSMiri Korenblit 928d1e879ecSMiri Korenblit /* must be called under rcu_read_lock() */ 929d1e879ecSMiri Korenblit void iwl_mld_count_mpdu_rx(struct ieee80211_link_sta *link_sta, int queue, 930d1e879ecSMiri Korenblit u32 count) 931d1e879ecSMiri Korenblit { 932d1e879ecSMiri Korenblit iwl_mld_count_mpdu(link_sta, queue, count, false); 933d1e879ecSMiri Korenblit } 934d1e879ecSMiri Korenblit 935d1e879ecSMiri Korenblit /* must be called under rcu_read_lock() */ 936d1e879ecSMiri Korenblit void iwl_mld_count_mpdu_tx(struct ieee80211_link_sta *link_sta, u32 count) 937d1e879ecSMiri Korenblit { 938d1e879ecSMiri Korenblit /* use queue 0 for all TX */ 939d1e879ecSMiri Korenblit iwl_mld_count_mpdu(link_sta, 0, count, true); 940d1e879ecSMiri Korenblit } 941d1e879ecSMiri Korenblit 942d1e879ecSMiri Korenblit static int iwl_mld_allocate_internal_txq(struct iwl_mld *mld, 943d1e879ecSMiri Korenblit struct iwl_mld_int_sta *internal_sta, 944d1e879ecSMiri Korenblit u8 tid) 945d1e879ecSMiri Korenblit { 946d1e879ecSMiri Korenblit u32 sta_mask = BIT(internal_sta->sta_id); 947d1e879ecSMiri Korenblit int queue, size; 948d1e879ecSMiri Korenblit 949d1e879ecSMiri Korenblit size = max_t(u32, IWL_MGMT_QUEUE_SIZE, 950d1e879ecSMiri Korenblit mld->trans->cfg->min_txq_size); 951d1e879ecSMiri Korenblit 952d1e879ecSMiri Korenblit queue = iwl_trans_txq_alloc(mld->trans, 0, sta_mask, tid, size, 953d1e879ecSMiri Korenblit IWL_WATCHDOG_DISABLED); 954d1e879ecSMiri Korenblit 955d1e879ecSMiri Korenblit if (queue >= 0) 956d1e879ecSMiri Korenblit IWL_DEBUG_TX_QUEUES(mld, 957d1e879ecSMiri Korenblit "Enabling TXQ #%d for sta mask 0x%x tid %d\n", 958d1e879ecSMiri Korenblit queue, sta_mask, tid); 959d1e879ecSMiri Korenblit return queue; 960d1e879ecSMiri Korenblit } 961d1e879ecSMiri Korenblit 962d1e879ecSMiri Korenblit static int iwl_mld_send_aux_sta_cmd(struct iwl_mld *mld, 963d1e879ecSMiri Korenblit const struct iwl_mld_int_sta *internal_sta) 964d1e879ecSMiri Korenblit { 965d1e879ecSMiri Korenblit struct iwl_aux_sta_cmd cmd = { 966d1e879ecSMiri Korenblit .sta_id = cpu_to_le32(internal_sta->sta_id), 967d1e879ecSMiri Korenblit /* TODO: CDB - properly set the lmac_id */ 968d1e879ecSMiri Korenblit .lmac_id = cpu_to_le32(IWL_LMAC_24G_INDEX), 969d1e879ecSMiri Korenblit }; 970d1e879ecSMiri Korenblit 971d1e879ecSMiri Korenblit return iwl_mld_send_cmd_pdu(mld, WIDE_ID(MAC_CONF_GROUP, AUX_STA_CMD), 972d1e879ecSMiri Korenblit &cmd); 973d1e879ecSMiri Korenblit } 974d1e879ecSMiri Korenblit 975d1e879ecSMiri Korenblit static int 976d1e879ecSMiri Korenblit iwl_mld_add_internal_sta_to_fw(struct iwl_mld *mld, 977d1e879ecSMiri Korenblit const struct iwl_mld_int_sta *internal_sta, 978d1e879ecSMiri Korenblit u8 fw_link_id, 979d1e879ecSMiri Korenblit const u8 *addr) 980d1e879ecSMiri Korenblit { 981d1e879ecSMiri Korenblit struct iwl_sta_cfg_cmd cmd = {}; 982d1e879ecSMiri Korenblit 983d1e879ecSMiri Korenblit if (internal_sta->sta_type == STATION_TYPE_AUX) 984d1e879ecSMiri Korenblit return iwl_mld_send_aux_sta_cmd(mld, internal_sta); 985d1e879ecSMiri Korenblit 986d1e879ecSMiri Korenblit cmd.sta_id = cpu_to_le32((u8)internal_sta->sta_id); 987d1e879ecSMiri Korenblit cmd.link_id = cpu_to_le32(fw_link_id); 988d1e879ecSMiri Korenblit cmd.station_type = cpu_to_le32(internal_sta->sta_type); 989d1e879ecSMiri Korenblit 990d1e879ecSMiri Korenblit /* FW doesn't allow to add a IGTK/BIGTK if the sta isn't marked as MFP. 991d1e879ecSMiri Korenblit * On the other hand, FW will never check this flag during RX since 992d1e879ecSMiri Korenblit * an AP/GO doesn't receive protected broadcast management frames. 993d1e879ecSMiri Korenblit * So, we can set it unconditionally. 994d1e879ecSMiri Korenblit */ 995d1e879ecSMiri Korenblit if (internal_sta->sta_type == STATION_TYPE_BCAST_MGMT) 996d1e879ecSMiri Korenblit cmd.mfp = cpu_to_le32(1); 997d1e879ecSMiri Korenblit 998d1e879ecSMiri Korenblit if (addr) { 999d1e879ecSMiri Korenblit memcpy(cmd.peer_mld_address, addr, ETH_ALEN); 1000d1e879ecSMiri Korenblit memcpy(cmd.peer_link_address, addr, ETH_ALEN); 1001d1e879ecSMiri Korenblit } 1002d1e879ecSMiri Korenblit 1003d1e879ecSMiri Korenblit return iwl_mld_send_sta_cmd(mld, &cmd); 1004d1e879ecSMiri Korenblit } 1005d1e879ecSMiri Korenblit 1006d1e879ecSMiri Korenblit static int iwl_mld_add_internal_sta(struct iwl_mld *mld, 1007d1e879ecSMiri Korenblit struct iwl_mld_int_sta *internal_sta, 1008d1e879ecSMiri Korenblit enum iwl_fw_sta_type sta_type, 1009d1e879ecSMiri Korenblit u8 fw_link_id, const u8 *addr, u8 tid) 1010d1e879ecSMiri Korenblit { 1011d1e879ecSMiri Korenblit int ret, queue_id; 1012d1e879ecSMiri Korenblit 1013d1e879ecSMiri Korenblit ret = iwl_mld_allocate_link_sta_fw_id(mld, 1014d1e879ecSMiri Korenblit &internal_sta->sta_id, 1015d1e879ecSMiri Korenblit ERR_PTR(-EINVAL)); 1016d1e879ecSMiri Korenblit if (ret) 1017d1e879ecSMiri Korenblit return ret; 1018d1e879ecSMiri Korenblit 1019d1e879ecSMiri Korenblit internal_sta->sta_type = sta_type; 1020d1e879ecSMiri Korenblit 1021d1e879ecSMiri Korenblit ret = iwl_mld_add_internal_sta_to_fw(mld, internal_sta, fw_link_id, 1022d1e879ecSMiri Korenblit addr); 1023d1e879ecSMiri Korenblit if (ret) 1024d1e879ecSMiri Korenblit goto err; 1025d1e879ecSMiri Korenblit 1026d1e879ecSMiri Korenblit queue_id = iwl_mld_allocate_internal_txq(mld, internal_sta, tid); 1027d1e879ecSMiri Korenblit if (queue_id < 0) { 1028d1e879ecSMiri Korenblit iwl_mld_rm_sta_from_fw(mld, internal_sta->sta_id); 1029d1e879ecSMiri Korenblit ret = queue_id; 1030d1e879ecSMiri Korenblit goto err; 1031d1e879ecSMiri Korenblit } 1032d1e879ecSMiri Korenblit 1033d1e879ecSMiri Korenblit internal_sta->queue_id = queue_id; 1034d1e879ecSMiri Korenblit 1035d1e879ecSMiri Korenblit return 0; 1036d1e879ecSMiri Korenblit err: 1037d1e879ecSMiri Korenblit iwl_mld_free_internal_sta(mld, internal_sta); 1038d1e879ecSMiri Korenblit return ret; 1039d1e879ecSMiri Korenblit } 1040d1e879ecSMiri Korenblit 1041d1e879ecSMiri Korenblit int iwl_mld_add_bcast_sta(struct iwl_mld *mld, 1042d1e879ecSMiri Korenblit struct ieee80211_vif *vif, 1043d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link) 1044d1e879ecSMiri Korenblit { 1045d1e879ecSMiri Korenblit struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link); 1046d1e879ecSMiri Korenblit const u8 bcast_addr[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; 1047d1e879ecSMiri Korenblit const u8 *addr; 1048d1e879ecSMiri Korenblit 1049d1e879ecSMiri Korenblit if (WARN_ON(!mld_link)) 1050d1e879ecSMiri Korenblit return -EINVAL; 1051d1e879ecSMiri Korenblit 1052d1e879ecSMiri Korenblit if (WARN_ON(vif->type != NL80211_IFTYPE_AP && 1053d1e879ecSMiri Korenblit vif->type != NL80211_IFTYPE_ADHOC)) 1054d1e879ecSMiri Korenblit return -EINVAL; 1055d1e879ecSMiri Korenblit 1056d1e879ecSMiri Korenblit addr = vif->type == NL80211_IFTYPE_ADHOC ? link->bssid : bcast_addr; 1057d1e879ecSMiri Korenblit 1058d1e879ecSMiri Korenblit return iwl_mld_add_internal_sta(mld, &mld_link->bcast_sta, 1059d1e879ecSMiri Korenblit STATION_TYPE_BCAST_MGMT, 1060d1e879ecSMiri Korenblit mld_link->fw_id, addr, 1061d1e879ecSMiri Korenblit IWL_MGMT_TID); 1062d1e879ecSMiri Korenblit } 1063d1e879ecSMiri Korenblit 1064d1e879ecSMiri Korenblit int iwl_mld_add_mcast_sta(struct iwl_mld *mld, 1065d1e879ecSMiri Korenblit struct ieee80211_vif *vif, 1066d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link) 1067d1e879ecSMiri Korenblit { 1068d1e879ecSMiri Korenblit struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link); 1069d1e879ecSMiri Korenblit const u8 mcast_addr[] = {0x03, 0x00, 0x00, 0x00, 0x00, 0x00}; 1070d1e879ecSMiri Korenblit 1071d1e879ecSMiri Korenblit if (WARN_ON(!mld_link)) 1072d1e879ecSMiri Korenblit return -EINVAL; 1073d1e879ecSMiri Korenblit 1074d1e879ecSMiri Korenblit if (WARN_ON(vif->type != NL80211_IFTYPE_AP && 1075d1e879ecSMiri Korenblit vif->type != NL80211_IFTYPE_ADHOC)) 1076d1e879ecSMiri Korenblit return -EINVAL; 1077d1e879ecSMiri Korenblit 1078d1e879ecSMiri Korenblit return iwl_mld_add_internal_sta(mld, &mld_link->mcast_sta, 1079d1e879ecSMiri Korenblit STATION_TYPE_MCAST, 1080d1e879ecSMiri Korenblit mld_link->fw_id, mcast_addr, 0); 1081d1e879ecSMiri Korenblit } 1082d1e879ecSMiri Korenblit 1083d1e879ecSMiri Korenblit int iwl_mld_add_aux_sta(struct iwl_mld *mld, 1084d1e879ecSMiri Korenblit struct iwl_mld_int_sta *internal_sta) 1085d1e879ecSMiri Korenblit { 1086d1e879ecSMiri Korenblit return iwl_mld_add_internal_sta(mld, internal_sta, STATION_TYPE_AUX, 1087d1e879ecSMiri Korenblit 0, NULL, IWL_MAX_TID_COUNT); 1088d1e879ecSMiri Korenblit } 1089d1e879ecSMiri Korenblit 1090d1e879ecSMiri Korenblit static void iwl_mld_remove_internal_sta(struct iwl_mld *mld, 1091d1e879ecSMiri Korenblit struct iwl_mld_int_sta *internal_sta, 1092d1e879ecSMiri Korenblit bool flush, u8 tid) 1093d1e879ecSMiri Korenblit { 1094d1e879ecSMiri Korenblit if (WARN_ON_ONCE(internal_sta->sta_id == IWL_INVALID_STA || 1095d1e879ecSMiri Korenblit internal_sta->queue_id == IWL_MLD_INVALID_QUEUE)) 1096d1e879ecSMiri Korenblit return; 1097d1e879ecSMiri Korenblit 1098d1e879ecSMiri Korenblit if (flush) 1099d1e879ecSMiri Korenblit iwl_mld_flush_link_sta_txqs(mld, internal_sta->sta_id); 1100d1e879ecSMiri Korenblit 1101d1e879ecSMiri Korenblit iwl_mld_free_txq(mld, BIT(internal_sta->sta_id), 1102d1e879ecSMiri Korenblit tid, internal_sta->queue_id); 1103d1e879ecSMiri Korenblit 1104d1e879ecSMiri Korenblit iwl_mld_rm_sta_from_fw(mld, internal_sta->sta_id); 1105d1e879ecSMiri Korenblit 1106d1e879ecSMiri Korenblit iwl_mld_free_internal_sta(mld, internal_sta); 1107d1e879ecSMiri Korenblit } 1108d1e879ecSMiri Korenblit 1109d1e879ecSMiri Korenblit void iwl_mld_remove_bcast_sta(struct iwl_mld *mld, 1110d1e879ecSMiri Korenblit struct ieee80211_vif *vif, 1111d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link) 1112d1e879ecSMiri Korenblit { 1113d1e879ecSMiri Korenblit struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link); 1114d1e879ecSMiri Korenblit 1115d1e879ecSMiri Korenblit if (WARN_ON(!mld_link)) 1116d1e879ecSMiri Korenblit return; 1117d1e879ecSMiri Korenblit 1118d1e879ecSMiri Korenblit if (WARN_ON(vif->type != NL80211_IFTYPE_AP && 1119d1e879ecSMiri Korenblit vif->type != NL80211_IFTYPE_ADHOC)) 1120d1e879ecSMiri Korenblit return; 1121d1e879ecSMiri Korenblit 1122d1e879ecSMiri Korenblit iwl_mld_remove_internal_sta(mld, &mld_link->bcast_sta, true, 1123d1e879ecSMiri Korenblit IWL_MGMT_TID); 1124d1e879ecSMiri Korenblit } 1125d1e879ecSMiri Korenblit 1126d1e879ecSMiri Korenblit void iwl_mld_remove_mcast_sta(struct iwl_mld *mld, 1127d1e879ecSMiri Korenblit struct ieee80211_vif *vif, 1128d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link) 1129d1e879ecSMiri Korenblit { 1130d1e879ecSMiri Korenblit struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link); 1131d1e879ecSMiri Korenblit 1132d1e879ecSMiri Korenblit if (WARN_ON(!mld_link)) 1133d1e879ecSMiri Korenblit return; 1134d1e879ecSMiri Korenblit 1135d1e879ecSMiri Korenblit if (WARN_ON(vif->type != NL80211_IFTYPE_AP && 1136d1e879ecSMiri Korenblit vif->type != NL80211_IFTYPE_ADHOC)) 1137d1e879ecSMiri Korenblit return; 1138d1e879ecSMiri Korenblit 1139d1e879ecSMiri Korenblit iwl_mld_remove_internal_sta(mld, &mld_link->mcast_sta, true, 0); 1140d1e879ecSMiri Korenblit } 1141d1e879ecSMiri Korenblit 1142d1e879ecSMiri Korenblit void iwl_mld_remove_aux_sta(struct iwl_mld *mld, 1143d1e879ecSMiri Korenblit struct ieee80211_vif *vif, 1144d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link) 1145d1e879ecSMiri Korenblit { 1146d1e879ecSMiri Korenblit struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link); 1147d1e879ecSMiri Korenblit 1148d1e879ecSMiri Korenblit if (WARN_ON(!mld_link)) 1149d1e879ecSMiri Korenblit return; 1150d1e879ecSMiri Korenblit 1151d1e879ecSMiri Korenblit /* TODO: Hotspot 2.0 */ 1152d1e879ecSMiri Korenblit if (WARN_ON(vif->type != NL80211_IFTYPE_P2P_DEVICE)) 1153d1e879ecSMiri Korenblit return; 1154d1e879ecSMiri Korenblit 1155d1e879ecSMiri Korenblit iwl_mld_remove_internal_sta(mld, &mld_link->aux_sta, false, 1156d1e879ecSMiri Korenblit IWL_MAX_TID_COUNT); 1157d1e879ecSMiri Korenblit } 1158d1e879ecSMiri Korenblit 1159d1e879ecSMiri Korenblit static int iwl_mld_update_sta_resources(struct iwl_mld *mld, 1160d1e879ecSMiri Korenblit struct ieee80211_vif *vif, 1161d1e879ecSMiri Korenblit struct ieee80211_sta *sta, 1162d1e879ecSMiri Korenblit u32 old_sta_mask, 1163d1e879ecSMiri Korenblit u32 new_sta_mask) 1164d1e879ecSMiri Korenblit { 1165d1e879ecSMiri Korenblit int ret; 1166d1e879ecSMiri Korenblit 1167d1e879ecSMiri Korenblit ret = iwl_mld_update_sta_txqs(mld, sta, old_sta_mask, new_sta_mask); 1168d1e879ecSMiri Korenblit if (ret) 1169d1e879ecSMiri Korenblit return ret; 1170d1e879ecSMiri Korenblit 1171d1e879ecSMiri Korenblit ret = iwl_mld_update_sta_keys(mld, vif, sta, old_sta_mask, new_sta_mask); 1172d1e879ecSMiri Korenblit if (ret) 1173d1e879ecSMiri Korenblit return ret; 1174d1e879ecSMiri Korenblit 1175d1e879ecSMiri Korenblit return iwl_mld_update_sta_baids(mld, old_sta_mask, new_sta_mask); 1176d1e879ecSMiri Korenblit } 1177d1e879ecSMiri Korenblit 1178d1e879ecSMiri Korenblit int iwl_mld_update_link_stas(struct iwl_mld *mld, 1179d1e879ecSMiri Korenblit struct ieee80211_vif *vif, 1180d1e879ecSMiri Korenblit struct ieee80211_sta *sta, 1181d1e879ecSMiri Korenblit u16 old_links, u16 new_links) 1182d1e879ecSMiri Korenblit { 1183d1e879ecSMiri Korenblit struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta); 1184d1e879ecSMiri Korenblit struct iwl_mld_link_sta *mld_link_sta; 1185d1e879ecSMiri Korenblit unsigned long links_to_add = ~old_links & new_links; 1186d1e879ecSMiri Korenblit unsigned long links_to_rem = old_links & ~new_links; 1187d1e879ecSMiri Korenblit unsigned long old_links_long = old_links; 1188d1e879ecSMiri Korenblit unsigned long sta_mask_added = 0; 1189d1e879ecSMiri Korenblit u32 current_sta_mask = 0, sta_mask_to_rem = 0; 1190d1e879ecSMiri Korenblit unsigned int link_id, sta_id; 1191d1e879ecSMiri Korenblit int ret; 1192d1e879ecSMiri Korenblit 1193d1e879ecSMiri Korenblit lockdep_assert_wiphy(mld->wiphy); 1194d1e879ecSMiri Korenblit 1195d1e879ecSMiri Korenblit for_each_set_bit(link_id, &old_links_long, 1196d1e879ecSMiri Korenblit IEEE80211_MLD_MAX_NUM_LINKS) { 1197d1e879ecSMiri Korenblit mld_link_sta = 1198d1e879ecSMiri Korenblit iwl_mld_link_sta_dereference_check(mld_sta, link_id); 1199d1e879ecSMiri Korenblit 1200d1e879ecSMiri Korenblit if (WARN_ON(!mld_link_sta)) 1201d1e879ecSMiri Korenblit return -EINVAL; 1202d1e879ecSMiri Korenblit 1203d1e879ecSMiri Korenblit current_sta_mask |= BIT(mld_link_sta->fw_id); 1204d1e879ecSMiri Korenblit if (links_to_rem & BIT(link_id)) 1205d1e879ecSMiri Korenblit sta_mask_to_rem |= BIT(mld_link_sta->fw_id); 1206d1e879ecSMiri Korenblit } 1207d1e879ecSMiri Korenblit 1208d1e879ecSMiri Korenblit if (sta_mask_to_rem) { 1209d1e879ecSMiri Korenblit ret = iwl_mld_update_sta_resources(mld, vif, sta, 1210d1e879ecSMiri Korenblit current_sta_mask, 1211d1e879ecSMiri Korenblit current_sta_mask & 1212d1e879ecSMiri Korenblit ~sta_mask_to_rem); 1213d1e879ecSMiri Korenblit if (ret) 1214d1e879ecSMiri Korenblit return ret; 1215d1e879ecSMiri Korenblit 1216d1e879ecSMiri Korenblit current_sta_mask &= ~sta_mask_to_rem; 1217d1e879ecSMiri Korenblit } 1218d1e879ecSMiri Korenblit 1219d1e879ecSMiri Korenblit for_each_set_bit(link_id, &links_to_rem, IEEE80211_MLD_MAX_NUM_LINKS) { 1220d1e879ecSMiri Korenblit struct ieee80211_link_sta *link_sta = 1221d1e879ecSMiri Korenblit link_sta_dereference_protected(sta, link_id); 1222d1e879ecSMiri Korenblit 1223d1e879ecSMiri Korenblit if (WARN_ON(!link_sta)) 1224d1e879ecSMiri Korenblit return -EINVAL; 1225d1e879ecSMiri Korenblit 1226d1e879ecSMiri Korenblit iwl_mld_remove_link_sta(mld, link_sta); 1227d1e879ecSMiri Korenblit } 1228d1e879ecSMiri Korenblit 1229d1e879ecSMiri Korenblit for_each_set_bit(link_id, &links_to_add, IEEE80211_MLD_MAX_NUM_LINKS) { 1230d1e879ecSMiri Korenblit struct ieee80211_link_sta *link_sta = 1231d1e879ecSMiri Korenblit link_sta_dereference_protected(sta, link_id); 1232d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link; 1233d1e879ecSMiri Korenblit 1234d1e879ecSMiri Korenblit if (WARN_ON(!link_sta)) 1235d1e879ecSMiri Korenblit return -EINVAL; 1236d1e879ecSMiri Korenblit 1237d1e879ecSMiri Korenblit ret = iwl_mld_add_link_sta(mld, link_sta); 1238d1e879ecSMiri Korenblit if (ret) 1239d1e879ecSMiri Korenblit goto remove_added_link_stas; 1240d1e879ecSMiri Korenblit 1241d1e879ecSMiri Korenblit mld_link_sta = 1242d1e879ecSMiri Korenblit iwl_mld_link_sta_dereference_check(mld_sta, 1243d1e879ecSMiri Korenblit link_id); 1244d1e879ecSMiri Korenblit 1245d1e879ecSMiri Korenblit link = link_conf_dereference_protected(mld_sta->vif, 1246d1e879ecSMiri Korenblit link_sta->link_id); 1247*36b79cb0SIlan Peer 1248*36b79cb0SIlan Peer iwl_mld_set_max_amsdu_len(mld, link_sta); 1249d1e879ecSMiri Korenblit iwl_mld_config_tlc_link(mld, vif, link, link_sta); 1250d1e879ecSMiri Korenblit 1251d1e879ecSMiri Korenblit sta_mask_added |= BIT(mld_link_sta->fw_id); 1252d1e879ecSMiri Korenblit } 1253d1e879ecSMiri Korenblit 1254d1e879ecSMiri Korenblit if (sta_mask_added) { 1255d1e879ecSMiri Korenblit ret = iwl_mld_update_sta_resources(mld, vif, sta, 1256d1e879ecSMiri Korenblit current_sta_mask, 1257d1e879ecSMiri Korenblit current_sta_mask | 1258d1e879ecSMiri Korenblit sta_mask_added); 1259d1e879ecSMiri Korenblit if (ret) 1260d1e879ecSMiri Korenblit goto remove_added_link_stas; 1261d1e879ecSMiri Korenblit } 1262d1e879ecSMiri Korenblit 1263d1e879ecSMiri Korenblit /* We couldn't activate the links before it has a STA. Now we can */ 1264d1e879ecSMiri Korenblit for_each_set_bit(link_id, &links_to_add, IEEE80211_MLD_MAX_NUM_LINKS) { 1265d1e879ecSMiri Korenblit struct ieee80211_bss_conf *link = 1266d1e879ecSMiri Korenblit link_conf_dereference_protected(mld_sta->vif, link_id); 1267d1e879ecSMiri Korenblit 1268d1e879ecSMiri Korenblit if (WARN_ON(!link)) 1269d1e879ecSMiri Korenblit continue; 1270d1e879ecSMiri Korenblit 1271d1e879ecSMiri Korenblit iwl_mld_activate_link(mld, link); 1272d1e879ecSMiri Korenblit } 1273d1e879ecSMiri Korenblit 1274d1e879ecSMiri Korenblit return 0; 1275d1e879ecSMiri Korenblit 1276d1e879ecSMiri Korenblit remove_added_link_stas: 1277d1e879ecSMiri Korenblit for_each_set_bit(sta_id, &sta_mask_added, mld->fw->ucode_capa.num_stations) { 1278d1e879ecSMiri Korenblit struct ieee80211_link_sta *link_sta = 1279d1e879ecSMiri Korenblit wiphy_dereference(mld->wiphy, 1280d1e879ecSMiri Korenblit mld->fw_id_to_link_sta[sta_id]); 1281d1e879ecSMiri Korenblit 1282d1e879ecSMiri Korenblit if (WARN_ON(!link_sta)) 1283d1e879ecSMiri Korenblit continue; 1284d1e879ecSMiri Korenblit 1285d1e879ecSMiri Korenblit iwl_mld_remove_link_sta(mld, link_sta); 1286d1e879ecSMiri Korenblit } 1287d1e879ecSMiri Korenblit 1288d1e879ecSMiri Korenblit return ret; 1289d1e879ecSMiri Korenblit } 1290