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 "mld.h" 7d1e879ecSMiri Korenblit #include "stats.h" 8d1e879ecSMiri Korenblit #include "sta.h" 9d1e879ecSMiri Korenblit #include "mlo.h" 10d1e879ecSMiri Korenblit #include "hcmd.h" 11d1e879ecSMiri Korenblit #include "iface.h" 12d1e879ecSMiri Korenblit #include "scan.h" 13d1e879ecSMiri Korenblit #include "phy.h" 14d1e879ecSMiri Korenblit #include "fw/api/stats.h" 15d1e879ecSMiri Korenblit 16d1e879ecSMiri Korenblit static int iwl_mld_send_fw_stats_cmd(struct iwl_mld *mld, u32 cfg_mask, 17d1e879ecSMiri Korenblit u32 cfg_time, u32 type_mask) 18d1e879ecSMiri Korenblit { 19d1e879ecSMiri Korenblit u32 cmd_id = WIDE_ID(SYSTEM_GROUP, SYSTEM_STATISTICS_CMD); 20d1e879ecSMiri Korenblit struct iwl_system_statistics_cmd stats_cmd = { 21d1e879ecSMiri Korenblit .cfg_mask = cpu_to_le32(cfg_mask), 22d1e879ecSMiri Korenblit .config_time_sec = cpu_to_le32(cfg_time), 23d1e879ecSMiri Korenblit .type_id_mask = cpu_to_le32(type_mask), 24d1e879ecSMiri Korenblit }; 25d1e879ecSMiri Korenblit 26d1e879ecSMiri Korenblit return iwl_mld_send_cmd_pdu(mld, cmd_id, &stats_cmd); 27d1e879ecSMiri Korenblit } 28d1e879ecSMiri Korenblit 29d1e879ecSMiri Korenblit int iwl_mld_clear_stats_in_fw(struct iwl_mld *mld) 30d1e879ecSMiri Korenblit { 31d1e879ecSMiri Korenblit u32 cfg_mask = IWL_STATS_CFG_FLG_ON_DEMAND_NTFY_MSK; 32d1e879ecSMiri Korenblit u32 type_mask = IWL_STATS_NTFY_TYPE_ID_OPER | 33d1e879ecSMiri Korenblit IWL_STATS_NTFY_TYPE_ID_OPER_PART1; 34d1e879ecSMiri Korenblit 35d1e879ecSMiri Korenblit return iwl_mld_send_fw_stats_cmd(mld, cfg_mask, 0, type_mask); 36d1e879ecSMiri Korenblit } 37d1e879ecSMiri Korenblit 38d1e879ecSMiri Korenblit static void 39d1e879ecSMiri Korenblit iwl_mld_fill_stats_from_oper_notif(struct iwl_mld *mld, 40d1e879ecSMiri Korenblit struct iwl_rx_packet *pkt, 41d1e879ecSMiri Korenblit u8 fw_sta_id, struct station_info *sinfo) 42d1e879ecSMiri Korenblit { 43d1e879ecSMiri Korenblit const struct iwl_system_statistics_notif_oper *notif = 44d1e879ecSMiri Korenblit (void *)&pkt->data; 45d1e879ecSMiri Korenblit const struct iwl_stats_ntfy_per_sta *per_sta = 46d1e879ecSMiri Korenblit ¬if->per_sta[fw_sta_id]; 47d1e879ecSMiri Korenblit struct ieee80211_link_sta *link_sta; 48d1e879ecSMiri Korenblit struct iwl_mld_link_sta *mld_link_sta; 49d1e879ecSMiri Korenblit 50d1e879ecSMiri Korenblit /* 0 isn't a valid value, but FW might send 0. 51d1e879ecSMiri Korenblit * In that case, set the latest non-zero value we stored 52d1e879ecSMiri Korenblit */ 53d1e879ecSMiri Korenblit rcu_read_lock(); 54d1e879ecSMiri Korenblit 55d1e879ecSMiri Korenblit link_sta = rcu_dereference(mld->fw_id_to_link_sta[fw_sta_id]); 56d1e879ecSMiri Korenblit if (IS_ERR_OR_NULL(link_sta)) 57d1e879ecSMiri Korenblit goto unlock; 58d1e879ecSMiri Korenblit 59d1e879ecSMiri Korenblit mld_link_sta = iwl_mld_link_sta_from_mac80211(link_sta); 60d1e879ecSMiri Korenblit if (WARN_ON(!mld_link_sta)) 61d1e879ecSMiri Korenblit goto unlock; 62d1e879ecSMiri Korenblit 63d1e879ecSMiri Korenblit if (per_sta->average_energy) 64d1e879ecSMiri Korenblit mld_link_sta->signal_avg = 65d1e879ecSMiri Korenblit -(s8)le32_to_cpu(per_sta->average_energy); 66d1e879ecSMiri Korenblit 67d1e879ecSMiri Korenblit sinfo->signal_avg = mld_link_sta->signal_avg; 68d1e879ecSMiri Korenblit sinfo->filled |= BIT_ULL(NL80211_STA_INFO_SIGNAL_AVG); 69d1e879ecSMiri Korenblit 70d1e879ecSMiri Korenblit unlock: 71d1e879ecSMiri Korenblit rcu_read_unlock(); 72d1e879ecSMiri Korenblit } 73d1e879ecSMiri Korenblit 74d1e879ecSMiri Korenblit struct iwl_mld_stats_data { 75d1e879ecSMiri Korenblit u8 fw_sta_id; 76d1e879ecSMiri Korenblit struct station_info *sinfo; 77d1e879ecSMiri Korenblit struct iwl_mld *mld; 78d1e879ecSMiri Korenblit }; 79d1e879ecSMiri Korenblit 80d1e879ecSMiri Korenblit static bool iwl_mld_wait_stats_handler(struct iwl_notif_wait_data *notif_data, 81d1e879ecSMiri Korenblit struct iwl_rx_packet *pkt, void *data) 82d1e879ecSMiri Korenblit { 83d1e879ecSMiri Korenblit struct iwl_mld_stats_data *stats_data = data; 84d1e879ecSMiri Korenblit u16 cmd = WIDE_ID(pkt->hdr.group_id, pkt->hdr.cmd); 85d1e879ecSMiri Korenblit 86d1e879ecSMiri Korenblit switch (cmd) { 87d1e879ecSMiri Korenblit case WIDE_ID(STATISTICS_GROUP, STATISTICS_OPER_NOTIF): 88d1e879ecSMiri Korenblit iwl_mld_fill_stats_from_oper_notif(stats_data->mld, pkt, 89d1e879ecSMiri Korenblit stats_data->fw_sta_id, 90d1e879ecSMiri Korenblit stats_data->sinfo); 91d1e879ecSMiri Korenblit break; 92d1e879ecSMiri Korenblit case WIDE_ID(STATISTICS_GROUP, STATISTICS_OPER_PART1_NOTIF): 93d1e879ecSMiri Korenblit break; 94d1e879ecSMiri Korenblit case WIDE_ID(SYSTEM_GROUP, SYSTEM_STATISTICS_END_NOTIF): 95d1e879ecSMiri Korenblit return true; 96d1e879ecSMiri Korenblit } 97d1e879ecSMiri Korenblit 98d1e879ecSMiri Korenblit return false; 99d1e879ecSMiri Korenblit } 100d1e879ecSMiri Korenblit 101d1e879ecSMiri Korenblit static int 102d1e879ecSMiri Korenblit iwl_mld_fw_stats_to_mac80211(struct iwl_mld *mld, struct iwl_mld_sta *mld_sta, 103d1e879ecSMiri Korenblit struct station_info *sinfo) 104d1e879ecSMiri Korenblit { 105d1e879ecSMiri Korenblit u32 cfg_mask = IWL_STATS_CFG_FLG_ON_DEMAND_NTFY_MSK | 106d1e879ecSMiri Korenblit IWL_STATS_CFG_FLG_RESET_MSK; 107d1e879ecSMiri Korenblit u32 type_mask = IWL_STATS_NTFY_TYPE_ID_OPER | 108d1e879ecSMiri Korenblit IWL_STATS_NTFY_TYPE_ID_OPER_PART1; 109d1e879ecSMiri Korenblit static const u16 notifications[] = { 110d1e879ecSMiri Korenblit WIDE_ID(STATISTICS_GROUP, STATISTICS_OPER_NOTIF), 111d1e879ecSMiri Korenblit WIDE_ID(STATISTICS_GROUP, STATISTICS_OPER_PART1_NOTIF), 112d1e879ecSMiri Korenblit WIDE_ID(SYSTEM_GROUP, SYSTEM_STATISTICS_END_NOTIF), 113d1e879ecSMiri Korenblit }; 114d1e879ecSMiri Korenblit struct iwl_mld_stats_data wait_stats_data = { 115d1e879ecSMiri Korenblit /* We don't support drv_sta_statistics in EMLSR */ 116d1e879ecSMiri Korenblit .fw_sta_id = mld_sta->deflink.fw_id, 117d1e879ecSMiri Korenblit .sinfo = sinfo, 118d1e879ecSMiri Korenblit .mld = mld, 119d1e879ecSMiri Korenblit }; 120d1e879ecSMiri Korenblit struct iwl_notification_wait stats_wait; 121d1e879ecSMiri Korenblit int ret; 122d1e879ecSMiri Korenblit 123d1e879ecSMiri Korenblit iwl_init_notification_wait(&mld->notif_wait, &stats_wait, 124d1e879ecSMiri Korenblit notifications, ARRAY_SIZE(notifications), 125d1e879ecSMiri Korenblit iwl_mld_wait_stats_handler, 126d1e879ecSMiri Korenblit &wait_stats_data); 127d1e879ecSMiri Korenblit 128d1e879ecSMiri Korenblit ret = iwl_mld_send_fw_stats_cmd(mld, cfg_mask, 0, type_mask); 129d1e879ecSMiri Korenblit if (ret) { 130d1e879ecSMiri Korenblit iwl_remove_notification(&mld->notif_wait, &stats_wait); 131d1e879ecSMiri Korenblit return ret; 132d1e879ecSMiri Korenblit } 133d1e879ecSMiri Korenblit 134d1e879ecSMiri Korenblit /* Wait 500ms for OPERATIONAL, PART1, and END notifications, 135d1e879ecSMiri Korenblit * which should be sufficient for the firmware to gather data 136d1e879ecSMiri Korenblit * from all LMACs and send notifications to the host. 137d1e879ecSMiri Korenblit */ 138d1e879ecSMiri Korenblit ret = iwl_wait_notification(&mld->notif_wait, &stats_wait, HZ / 2); 139d1e879ecSMiri Korenblit if (ret) 140d1e879ecSMiri Korenblit return ret; 141d1e879ecSMiri Korenblit 142d1e879ecSMiri Korenblit /* When periodic statistics are sent, FW will clear its statistics DB. 143d1e879ecSMiri Korenblit * If the statistics request here happens shortly afterwards, 144d1e879ecSMiri Korenblit * the response will contain data collected over a short time 145d1e879ecSMiri Korenblit * interval. The response we got here shouldn't be processed by 146d1e879ecSMiri Korenblit * the general statistics processing because it's incomplete. 147d1e879ecSMiri Korenblit * So, we delete it from the list so it won't be processed. 148d1e879ecSMiri Korenblit */ 149d1e879ecSMiri Korenblit iwl_mld_delete_handlers(mld, notifications, ARRAY_SIZE(notifications)); 150d1e879ecSMiri Korenblit 151d1e879ecSMiri Korenblit return 0; 152d1e879ecSMiri Korenblit } 153d1e879ecSMiri Korenblit 154d1e879ecSMiri Korenblit #define PERIODIC_STATS_SECONDS 5 155d1e879ecSMiri Korenblit 156d1e879ecSMiri Korenblit int iwl_mld_request_periodic_fw_stats(struct iwl_mld *mld, bool enable) 157d1e879ecSMiri Korenblit { 158d1e879ecSMiri Korenblit u32 cfg_mask = enable ? 0 : IWL_STATS_CFG_FLG_DISABLE_NTFY_MSK; 159d1e879ecSMiri Korenblit u32 type_mask = enable ? (IWL_STATS_NTFY_TYPE_ID_OPER | 160d1e879ecSMiri Korenblit IWL_STATS_NTFY_TYPE_ID_OPER_PART1) : 0; 161d1e879ecSMiri Korenblit u32 cfg_time = enable ? PERIODIC_STATS_SECONDS : 0; 162d1e879ecSMiri Korenblit 163d1e879ecSMiri Korenblit return iwl_mld_send_fw_stats_cmd(mld, cfg_mask, cfg_time, type_mask); 164d1e879ecSMiri Korenblit } 165d1e879ecSMiri Korenblit 166d1e879ecSMiri Korenblit static void iwl_mld_sta_stats_fill_txrate(struct iwl_mld_sta *mld_sta, 167d1e879ecSMiri Korenblit struct station_info *sinfo) 168d1e879ecSMiri Korenblit { 169d1e879ecSMiri Korenblit struct rate_info *rinfo = &sinfo->txrate; 170d1e879ecSMiri Korenblit u32 rate_n_flags = mld_sta->deflink.last_rate_n_flags; 171d1e879ecSMiri Korenblit u32 format = rate_n_flags & RATE_MCS_MOD_TYPE_MSK; 172d1e879ecSMiri Korenblit u32 gi_ltf; 173d1e879ecSMiri Korenblit 174d1e879ecSMiri Korenblit sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE); 175d1e879ecSMiri Korenblit 176d1e879ecSMiri Korenblit switch (rate_n_flags & RATE_MCS_CHAN_WIDTH_MSK) { 177d1e879ecSMiri Korenblit case RATE_MCS_CHAN_WIDTH_20: 178d1e879ecSMiri Korenblit rinfo->bw = RATE_INFO_BW_20; 179d1e879ecSMiri Korenblit break; 180d1e879ecSMiri Korenblit case RATE_MCS_CHAN_WIDTH_40: 181d1e879ecSMiri Korenblit rinfo->bw = RATE_INFO_BW_40; 182d1e879ecSMiri Korenblit break; 183d1e879ecSMiri Korenblit case RATE_MCS_CHAN_WIDTH_80: 184d1e879ecSMiri Korenblit rinfo->bw = RATE_INFO_BW_80; 185d1e879ecSMiri Korenblit break; 186d1e879ecSMiri Korenblit case RATE_MCS_CHAN_WIDTH_160: 187d1e879ecSMiri Korenblit rinfo->bw = RATE_INFO_BW_160; 188d1e879ecSMiri Korenblit break; 189d1e879ecSMiri Korenblit case RATE_MCS_CHAN_WIDTH_320: 190d1e879ecSMiri Korenblit rinfo->bw = RATE_INFO_BW_320; 191d1e879ecSMiri Korenblit break; 192d1e879ecSMiri Korenblit } 193d1e879ecSMiri Korenblit 194d1e879ecSMiri Korenblit if (format == RATE_MCS_CCK_MSK || format == RATE_MCS_LEGACY_OFDM_MSK) { 195d1e879ecSMiri Korenblit int rate = u32_get_bits(rate_n_flags, RATE_LEGACY_RATE_MSK); 196d1e879ecSMiri Korenblit 197d1e879ecSMiri Korenblit /* add the offset needed to get to the legacy ofdm indices */ 198d1e879ecSMiri Korenblit if (format == RATE_MCS_LEGACY_OFDM_MSK) 199d1e879ecSMiri Korenblit rate += IWL_FIRST_OFDM_RATE; 200d1e879ecSMiri Korenblit 201d1e879ecSMiri Korenblit switch (rate) { 202d1e879ecSMiri Korenblit case IWL_RATE_1M_INDEX: 203d1e879ecSMiri Korenblit rinfo->legacy = 10; 204d1e879ecSMiri Korenblit break; 205d1e879ecSMiri Korenblit case IWL_RATE_2M_INDEX: 206d1e879ecSMiri Korenblit rinfo->legacy = 20; 207d1e879ecSMiri Korenblit break; 208d1e879ecSMiri Korenblit case IWL_RATE_5M_INDEX: 209d1e879ecSMiri Korenblit rinfo->legacy = 55; 210d1e879ecSMiri Korenblit break; 211d1e879ecSMiri Korenblit case IWL_RATE_11M_INDEX: 212d1e879ecSMiri Korenblit rinfo->legacy = 110; 213d1e879ecSMiri Korenblit break; 214d1e879ecSMiri Korenblit case IWL_RATE_6M_INDEX: 215d1e879ecSMiri Korenblit rinfo->legacy = 60; 216d1e879ecSMiri Korenblit break; 217d1e879ecSMiri Korenblit case IWL_RATE_9M_INDEX: 218d1e879ecSMiri Korenblit rinfo->legacy = 90; 219d1e879ecSMiri Korenblit break; 220d1e879ecSMiri Korenblit case IWL_RATE_12M_INDEX: 221d1e879ecSMiri Korenblit rinfo->legacy = 120; 222d1e879ecSMiri Korenblit break; 223d1e879ecSMiri Korenblit case IWL_RATE_18M_INDEX: 224d1e879ecSMiri Korenblit rinfo->legacy = 180; 225d1e879ecSMiri Korenblit break; 226d1e879ecSMiri Korenblit case IWL_RATE_24M_INDEX: 227d1e879ecSMiri Korenblit rinfo->legacy = 240; 228d1e879ecSMiri Korenblit break; 229d1e879ecSMiri Korenblit case IWL_RATE_36M_INDEX: 230d1e879ecSMiri Korenblit rinfo->legacy = 360; 231d1e879ecSMiri Korenblit break; 232d1e879ecSMiri Korenblit case IWL_RATE_48M_INDEX: 233d1e879ecSMiri Korenblit rinfo->legacy = 480; 234d1e879ecSMiri Korenblit break; 235d1e879ecSMiri Korenblit case IWL_RATE_54M_INDEX: 236d1e879ecSMiri Korenblit rinfo->legacy = 540; 237d1e879ecSMiri Korenblit } 238d1e879ecSMiri Korenblit return; 239d1e879ecSMiri Korenblit } 240d1e879ecSMiri Korenblit 241d1e879ecSMiri Korenblit rinfo->nss = u32_get_bits(rate_n_flags, RATE_MCS_NSS_MSK) + 1; 242d1e879ecSMiri Korenblit 243d1e879ecSMiri Korenblit if (format == RATE_MCS_HT_MSK) 244d1e879ecSMiri Korenblit rinfo->mcs = RATE_HT_MCS_INDEX(rate_n_flags); 245d1e879ecSMiri Korenblit else 246d1e879ecSMiri Korenblit rinfo->mcs = u32_get_bits(rate_n_flags, RATE_MCS_CODE_MSK); 247d1e879ecSMiri Korenblit 248d1e879ecSMiri Korenblit if (rate_n_flags & RATE_MCS_SGI_MSK) 249d1e879ecSMiri Korenblit rinfo->flags |= RATE_INFO_FLAGS_SHORT_GI; 250d1e879ecSMiri Korenblit 251d1e879ecSMiri Korenblit switch (format) { 252d1e879ecSMiri Korenblit case RATE_MCS_EHT_MSK: 253d1e879ecSMiri Korenblit rinfo->flags |= RATE_INFO_FLAGS_EHT_MCS; 254d1e879ecSMiri Korenblit break; 255d1e879ecSMiri Korenblit case RATE_MCS_HE_MSK: 256d1e879ecSMiri Korenblit gi_ltf = u32_get_bits(rate_n_flags, RATE_MCS_HE_GI_LTF_MSK); 257d1e879ecSMiri Korenblit 258d1e879ecSMiri Korenblit rinfo->flags |= RATE_INFO_FLAGS_HE_MCS; 259d1e879ecSMiri Korenblit 260d1e879ecSMiri Korenblit if (rate_n_flags & RATE_MCS_HE_106T_MSK) { 261d1e879ecSMiri Korenblit rinfo->bw = RATE_INFO_BW_HE_RU; 262d1e879ecSMiri Korenblit rinfo->he_ru_alloc = NL80211_RATE_INFO_HE_RU_ALLOC_106; 263d1e879ecSMiri Korenblit } 264d1e879ecSMiri Korenblit 265d1e879ecSMiri Korenblit switch (rate_n_flags & RATE_MCS_HE_TYPE_MSK) { 266d1e879ecSMiri Korenblit case RATE_MCS_HE_TYPE_SU: 267d1e879ecSMiri Korenblit case RATE_MCS_HE_TYPE_EXT_SU: 268d1e879ecSMiri Korenblit if (gi_ltf == 0 || gi_ltf == 1) 269d1e879ecSMiri Korenblit rinfo->he_gi = NL80211_RATE_INFO_HE_GI_0_8; 270d1e879ecSMiri Korenblit else if (gi_ltf == 2) 271d1e879ecSMiri Korenblit rinfo->he_gi = NL80211_RATE_INFO_HE_GI_1_6; 272d1e879ecSMiri Korenblit else if (gi_ltf == 3) 273d1e879ecSMiri Korenblit rinfo->he_gi = NL80211_RATE_INFO_HE_GI_3_2; 274d1e879ecSMiri Korenblit else 275d1e879ecSMiri Korenblit rinfo->he_gi = NL80211_RATE_INFO_HE_GI_0_8; 276d1e879ecSMiri Korenblit break; 277d1e879ecSMiri Korenblit case RATE_MCS_HE_TYPE_MU: 278d1e879ecSMiri Korenblit if (gi_ltf == 0 || gi_ltf == 1) 279d1e879ecSMiri Korenblit rinfo->he_gi = NL80211_RATE_INFO_HE_GI_0_8; 280d1e879ecSMiri Korenblit else if (gi_ltf == 2) 281d1e879ecSMiri Korenblit rinfo->he_gi = NL80211_RATE_INFO_HE_GI_1_6; 282d1e879ecSMiri Korenblit else 283d1e879ecSMiri Korenblit rinfo->he_gi = NL80211_RATE_INFO_HE_GI_3_2; 284d1e879ecSMiri Korenblit break; 285d1e879ecSMiri Korenblit case RATE_MCS_HE_TYPE_TRIG: 286d1e879ecSMiri Korenblit if (gi_ltf == 0 || gi_ltf == 1) 287d1e879ecSMiri Korenblit rinfo->he_gi = NL80211_RATE_INFO_HE_GI_1_6; 288d1e879ecSMiri Korenblit else 289d1e879ecSMiri Korenblit rinfo->he_gi = NL80211_RATE_INFO_HE_GI_3_2; 290d1e879ecSMiri Korenblit break; 291d1e879ecSMiri Korenblit } 292d1e879ecSMiri Korenblit 293d1e879ecSMiri Korenblit if (rate_n_flags & RATE_HE_DUAL_CARRIER_MODE_MSK) 294d1e879ecSMiri Korenblit rinfo->he_dcm = 1; 295d1e879ecSMiri Korenblit break; 296d1e879ecSMiri Korenblit case RATE_MCS_HT_MSK: 297d1e879ecSMiri Korenblit rinfo->flags |= RATE_INFO_FLAGS_MCS; 298d1e879ecSMiri Korenblit break; 299d1e879ecSMiri Korenblit case RATE_MCS_VHT_MSK: 300d1e879ecSMiri Korenblit rinfo->flags |= RATE_INFO_FLAGS_VHT_MCS; 301d1e879ecSMiri Korenblit break; 302d1e879ecSMiri Korenblit } 303d1e879ecSMiri Korenblit } 304d1e879ecSMiri Korenblit 305d1e879ecSMiri Korenblit void iwl_mld_mac80211_sta_statistics(struct ieee80211_hw *hw, 306d1e879ecSMiri Korenblit struct ieee80211_vif *vif, 307d1e879ecSMiri Korenblit struct ieee80211_sta *sta, 308d1e879ecSMiri Korenblit struct station_info *sinfo) 309d1e879ecSMiri Korenblit { 310d1e879ecSMiri Korenblit struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta); 311d1e879ecSMiri Korenblit 312d1e879ecSMiri Korenblit /* This API is not EMLSR ready, so we cannot provide complete 313d1e879ecSMiri Korenblit * information if EMLSR is active 314d1e879ecSMiri Korenblit */ 315d1e879ecSMiri Korenblit if (hweight16(vif->active_links) > 1) 316d1e879ecSMiri Korenblit return; 317d1e879ecSMiri Korenblit 318d1e879ecSMiri Korenblit if (iwl_mld_fw_stats_to_mac80211(mld_sta->mld, mld_sta, sinfo)) 319d1e879ecSMiri Korenblit return; 320d1e879ecSMiri Korenblit 321d1e879ecSMiri Korenblit iwl_mld_sta_stats_fill_txrate(mld_sta, sinfo); 322d1e879ecSMiri Korenblit 323d1e879ecSMiri Korenblit /* TODO: NL80211_STA_INFO_BEACON_RX */ 324d1e879ecSMiri Korenblit 325d1e879ecSMiri Korenblit /* TODO: NL80211_STA_INFO_BEACON_SIGNAL_AVG */ 326d1e879ecSMiri Korenblit } 327d1e879ecSMiri Korenblit 328d1e879ecSMiri Korenblit #define IWL_MLD_TRAFFIC_LOAD_MEDIUM_THRESH 10 /* percentage */ 329d1e879ecSMiri Korenblit #define IWL_MLD_TRAFFIC_LOAD_HIGH_THRESH 50 /* percentage */ 330d1e879ecSMiri Korenblit #define IWL_MLD_TRAFFIC_LOAD_MIN_WINDOW_USEC (500 * 1000) 331d1e879ecSMiri Korenblit 332d1e879ecSMiri Korenblit static u8 iwl_mld_stats_load_percentage(u32 last_ts_usec, u32 curr_ts_usec, 333d1e879ecSMiri Korenblit u32 total_airtime_usec) 334d1e879ecSMiri Korenblit { 335d1e879ecSMiri Korenblit u32 elapsed_usec = curr_ts_usec - last_ts_usec; 336d1e879ecSMiri Korenblit 337d1e879ecSMiri Korenblit if (elapsed_usec < IWL_MLD_TRAFFIC_LOAD_MIN_WINDOW_USEC) 338d1e879ecSMiri Korenblit return 0; 339d1e879ecSMiri Korenblit 340d1e879ecSMiri Korenblit return (100 * total_airtime_usec / elapsed_usec); 341d1e879ecSMiri Korenblit } 342d1e879ecSMiri Korenblit 343d1e879ecSMiri Korenblit static void iwl_mld_stats_recalc_traffic_load(struct iwl_mld *mld, 344d1e879ecSMiri Korenblit u32 total_airtime_usec, 345d1e879ecSMiri Korenblit u32 curr_ts_usec) 346d1e879ecSMiri Korenblit { 347d1e879ecSMiri Korenblit u32 last_ts_usec = mld->scan.traffic_load.last_stats_ts_usec; 348d1e879ecSMiri Korenblit u8 load_prec; 349d1e879ecSMiri Korenblit 350d1e879ecSMiri Korenblit /* Skip the calculation as this is the first notification received */ 351d1e879ecSMiri Korenblit if (!last_ts_usec) 352d1e879ecSMiri Korenblit goto out; 353d1e879ecSMiri Korenblit 354d1e879ecSMiri Korenblit load_prec = iwl_mld_stats_load_percentage(last_ts_usec, curr_ts_usec, 355d1e879ecSMiri Korenblit total_airtime_usec); 356d1e879ecSMiri Korenblit 357d1e879ecSMiri Korenblit if (load_prec > IWL_MLD_TRAFFIC_LOAD_HIGH_THRESH) 358d1e879ecSMiri Korenblit mld->scan.traffic_load.status = IWL_MLD_TRAFFIC_HIGH; 359d1e879ecSMiri Korenblit else if (load_prec > IWL_MLD_TRAFFIC_LOAD_MEDIUM_THRESH) 360d1e879ecSMiri Korenblit mld->scan.traffic_load.status = IWL_MLD_TRAFFIC_MEDIUM; 361d1e879ecSMiri Korenblit else 362d1e879ecSMiri Korenblit mld->scan.traffic_load.status = IWL_MLD_TRAFFIC_LOW; 363d1e879ecSMiri Korenblit 364d1e879ecSMiri Korenblit out: 365d1e879ecSMiri Korenblit mld->scan.traffic_load.last_stats_ts_usec = curr_ts_usec; 366d1e879ecSMiri Korenblit } 367d1e879ecSMiri Korenblit 368d1e879ecSMiri Korenblit static void iwl_mld_update_link_sig(struct ieee80211_vif *vif, int sig, 369d1e879ecSMiri Korenblit struct ieee80211_bss_conf *bss_conf) 370d1e879ecSMiri Korenblit { 371d1e879ecSMiri Korenblit struct iwl_mld *mld = iwl_mld_vif_from_mac80211(vif)->mld; 372d1e879ecSMiri Korenblit int exit_emlsr_thresh; 373d1e879ecSMiri Korenblit 374d1e879ecSMiri Korenblit if (sig == 0) { 375d1e879ecSMiri Korenblit IWL_DEBUG_RX(mld, "RSSI is 0 - skip signal based decision\n"); 376d1e879ecSMiri Korenblit return; 377d1e879ecSMiri Korenblit } 378d1e879ecSMiri Korenblit 379d1e879ecSMiri Korenblit /* TODO: task=statistics handle CQM notifications */ 380d1e879ecSMiri Korenblit 381d1e879ecSMiri Korenblit if (sig < IWL_MLD_LOW_RSSI_MLO_SCAN_THRESH) 382f31d666fSMiri Korenblit iwl_mld_int_mlo_scan(mld, vif); 383d414ff7aSMiri Korenblit 384d414ff7aSMiri Korenblit if (!iwl_mld_emlsr_active(vif)) 385d1e879ecSMiri Korenblit return; 386d1e879ecSMiri Korenblit 387d1e879ecSMiri Korenblit /* We are in EMLSR, check if we need to exit */ 388d1e879ecSMiri Korenblit exit_emlsr_thresh = 389d1e879ecSMiri Korenblit iwl_mld_get_emlsr_rssi_thresh(mld, &bss_conf->chanreq.oper, 390d1e879ecSMiri Korenblit true); 391d1e879ecSMiri Korenblit 392d1e879ecSMiri Korenblit if (sig < exit_emlsr_thresh) 393d1e879ecSMiri Korenblit iwl_mld_exit_emlsr(mld, vif, IWL_MLD_EMLSR_EXIT_LOW_RSSI, 394d1e879ecSMiri Korenblit iwl_mld_get_other_link(vif, 395d1e879ecSMiri Korenblit bss_conf->link_id)); 396d1e879ecSMiri Korenblit } 397d1e879ecSMiri Korenblit 398d1e879ecSMiri Korenblit static void 399d1e879ecSMiri Korenblit iwl_mld_process_per_link_stats(struct iwl_mld *mld, 400d1e879ecSMiri Korenblit const struct iwl_stats_ntfy_per_link *per_link, 401d1e879ecSMiri Korenblit u32 curr_ts_usec) 402d1e879ecSMiri Korenblit { 403d1e879ecSMiri Korenblit u32 total_airtime_usec = 0; 404d1e879ecSMiri Korenblit 405d1e879ecSMiri Korenblit for (u32 fw_id = 0; 406d1e879ecSMiri Korenblit fw_id < ARRAY_SIZE(mld->fw_id_to_bss_conf); 407d1e879ecSMiri Korenblit fw_id++) { 408d1e879ecSMiri Korenblit const struct iwl_stats_ntfy_per_link *link_stats; 409d1e879ecSMiri Korenblit struct ieee80211_bss_conf *bss_conf; 410d1e879ecSMiri Korenblit int sig; 411d1e879ecSMiri Korenblit 412d1e879ecSMiri Korenblit bss_conf = wiphy_dereference(mld->wiphy, 413d1e879ecSMiri Korenblit mld->fw_id_to_bss_conf[fw_id]); 414d1e879ecSMiri Korenblit if (!bss_conf || bss_conf->vif->type != NL80211_IFTYPE_STATION) 415d1e879ecSMiri Korenblit continue; 416d1e879ecSMiri Korenblit 417d1e879ecSMiri Korenblit link_stats = &per_link[fw_id]; 418d1e879ecSMiri Korenblit 419d1e879ecSMiri Korenblit total_airtime_usec += le32_to_cpu(link_stats->air_time); 420d1e879ecSMiri Korenblit 421d1e879ecSMiri Korenblit sig = -le32_to_cpu(link_stats->beacon_filter_average_energy); 422d1e879ecSMiri Korenblit iwl_mld_update_link_sig(bss_conf->vif, sig, bss_conf); 423d1e879ecSMiri Korenblit 424d1e879ecSMiri Korenblit /* TODO: parse more fields here (task=statistics)*/ 425d1e879ecSMiri Korenblit } 426d1e879ecSMiri Korenblit 427d1e879ecSMiri Korenblit iwl_mld_stats_recalc_traffic_load(mld, total_airtime_usec, 428d1e879ecSMiri Korenblit curr_ts_usec); 429d1e879ecSMiri Korenblit } 430d1e879ecSMiri Korenblit 431d1e879ecSMiri Korenblit static void 432d1e879ecSMiri Korenblit iwl_mld_process_per_sta_stats(struct iwl_mld *mld, 433d1e879ecSMiri Korenblit const struct iwl_stats_ntfy_per_sta *per_sta) 434d1e879ecSMiri Korenblit { 435d1e879ecSMiri Korenblit for (int i = 0; i < mld->fw->ucode_capa.num_stations; i++) { 436d1e879ecSMiri Korenblit struct ieee80211_link_sta *link_sta = 437d1e879ecSMiri Korenblit wiphy_dereference(mld->wiphy, 438d1e879ecSMiri Korenblit mld->fw_id_to_link_sta[i]); 439d1e879ecSMiri Korenblit struct iwl_mld_link_sta *mld_link_sta; 440d1e879ecSMiri Korenblit s8 avg_energy = 441d1e879ecSMiri Korenblit -(s8)le32_to_cpu(per_sta[i].average_energy); 442d1e879ecSMiri Korenblit 443d1e879ecSMiri Korenblit if (IS_ERR_OR_NULL(link_sta) || !avg_energy) 444d1e879ecSMiri Korenblit continue; 445d1e879ecSMiri Korenblit 446d1e879ecSMiri Korenblit mld_link_sta = iwl_mld_link_sta_from_mac80211(link_sta); 447d1e879ecSMiri Korenblit if (WARN_ON(!mld_link_sta)) 448d1e879ecSMiri Korenblit continue; 449d1e879ecSMiri Korenblit 450d1e879ecSMiri Korenblit mld_link_sta->signal_avg = avg_energy; 451d1e879ecSMiri Korenblit } 452d1e879ecSMiri Korenblit } 453d1e879ecSMiri Korenblit 454d1e879ecSMiri Korenblit static void iwl_mld_fill_chanctx_stats(struct ieee80211_hw *hw, 455d1e879ecSMiri Korenblit struct ieee80211_chanctx_conf *ctx, 456d1e879ecSMiri Korenblit void *data) 457d1e879ecSMiri Korenblit { 458d1e879ecSMiri Korenblit struct iwl_mld_phy *phy = iwl_mld_phy_from_mac80211(ctx); 459d1e879ecSMiri Korenblit const struct iwl_stats_ntfy_per_phy *per_phy = data; 460*4d7236f9SMiri Korenblit u32 new_load, old_load; 461d1e879ecSMiri Korenblit 462d1e879ecSMiri Korenblit if (WARN_ON(phy->fw_id >= IWL_STATS_MAX_PHY_OPERATIONAL)) 463d1e879ecSMiri Korenblit return; 464d1e879ecSMiri Korenblit 465d1e879ecSMiri Korenblit phy->channel_load_by_us = 466d1e879ecSMiri Korenblit le32_to_cpu(per_phy[phy->fw_id].channel_load_by_us); 467d1e879ecSMiri Korenblit 468*4d7236f9SMiri Korenblit old_load = phy->avg_channel_load_not_by_us; 469e8670620SMiri Korenblit new_load = le32_to_cpu(per_phy[phy->fw_id].channel_load_not_by_us); 470e8670620SMiri Korenblit if (IWL_FW_CHECK(phy->mld, new_load > 100, "Invalid channel load %u\n", 471e8670620SMiri Korenblit new_load)) 472e8670620SMiri Korenblit return; 473e8670620SMiri Korenblit 474e8670620SMiri Korenblit /* give a weight of 0.5 for the old value */ 475*4d7236f9SMiri Korenblit phy->avg_channel_load_not_by_us = (new_load >> 1) + (old_load >> 1); 476*4d7236f9SMiri Korenblit 477*4d7236f9SMiri Korenblit iwl_mld_emlsr_check_chan_load(hw, phy, old_load); 478d1e879ecSMiri Korenblit } 479d1e879ecSMiri Korenblit 480d1e879ecSMiri Korenblit static void 481d1e879ecSMiri Korenblit iwl_mld_process_per_phy_stats(struct iwl_mld *mld, 482d1e879ecSMiri Korenblit const struct iwl_stats_ntfy_per_phy *per_phy) 483d1e879ecSMiri Korenblit { 484d1e879ecSMiri Korenblit ieee80211_iter_chan_contexts_mtx(mld->hw, 485d1e879ecSMiri Korenblit iwl_mld_fill_chanctx_stats, 486d1e879ecSMiri Korenblit (void *)(uintptr_t)per_phy); 487d1e879ecSMiri Korenblit 488d1e879ecSMiri Korenblit } 489d1e879ecSMiri Korenblit 490d1e879ecSMiri Korenblit void iwl_mld_handle_stats_oper_notif(struct iwl_mld *mld, 491d1e879ecSMiri Korenblit struct iwl_rx_packet *pkt) 492d1e879ecSMiri Korenblit { 493d1e879ecSMiri Korenblit const struct iwl_system_statistics_notif_oper *stats = 494d1e879ecSMiri Korenblit (void *)&pkt->data; 495d1e879ecSMiri Korenblit u32 curr_ts_usec = le32_to_cpu(stats->time_stamp); 496d1e879ecSMiri Korenblit 497d1e879ecSMiri Korenblit BUILD_BUG_ON(ARRAY_SIZE(stats->per_sta) != IWL_STATION_COUNT_MAX); 498d1e879ecSMiri Korenblit BUILD_BUG_ON(ARRAY_SIZE(stats->per_link) < 499d1e879ecSMiri Korenblit ARRAY_SIZE(mld->fw_id_to_bss_conf)); 500d1e879ecSMiri Korenblit 501d1e879ecSMiri Korenblit iwl_mld_process_per_link_stats(mld, stats->per_link, curr_ts_usec); 502d1e879ecSMiri Korenblit iwl_mld_process_per_sta_stats(mld, stats->per_sta); 503d1e879ecSMiri Korenblit iwl_mld_process_per_phy_stats(mld, stats->per_phy); 504d1e879ecSMiri Korenblit 505d1e879ecSMiri Korenblit iwl_mld_check_omi_bw_reduction(mld); 506d1e879ecSMiri Korenblit } 507d1e879ecSMiri Korenblit 508d1e879ecSMiri Korenblit void iwl_mld_handle_stats_oper_part1_notif(struct iwl_mld *mld, 509d1e879ecSMiri Korenblit struct iwl_rx_packet *pkt) 510d1e879ecSMiri Korenblit { 511d1e879ecSMiri Korenblit /* TODO */ 512d1e879ecSMiri Korenblit } 513d1e879ecSMiri Korenblit 514