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