File Index
Files and directories
Sign in
FILE

stats.c

File preview with syntax highlighting.

linux-6.19.14/drivers/net/wireless/intel/iwlwifi/mld/stats.c C
  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
 16static 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
 29int 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
 38static void
 39iwl_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		&notif->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
 70unlock:
 71	rcu_read_unlock();
 72}
 73
 74struct iwl_mld_stats_data {
 75	u8 fw_sta_id;
 76	struct station_info *sinfo;
 77	struct iwl_mld *mld;
 78};
 79
 80static 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
101static int
102iwl_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
156int 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
166static 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
306void 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
333static 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
344static 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
365out:
366	mld->scan.traffic_load.last_stats_ts_usec = curr_ts_usec;
367}
368
369static 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 (!iwl_mld_emlsr_active(vif)) {
383		/* We're not in EMLSR and our signal is bad,
384		 * try to switch link maybe. EMLSR will be handled below.
385		 */
386		if (sig < IWL_MLD_LOW_RSSI_MLO_SCAN_THRESH)
387			iwl_mld_int_mlo_scan(mld, vif);
388		return;
389	}
390
391	/* We are in EMLSR, check if we need to exit */
392	exit_emlsr_thresh =
393		iwl_mld_get_emlsr_rssi_thresh(mld, &bss_conf->chanreq.oper,
394					      true);
395
396	if (sig < exit_emlsr_thresh)
397		iwl_mld_exit_emlsr(mld, vif, IWL_MLD_EMLSR_EXIT_LOW_RSSI,
398				   iwl_mld_get_other_link(vif,
399							  bss_conf->link_id));
400}
401
402static void
403iwl_mld_process_per_link_stats(struct iwl_mld *mld,
404			       const struct iwl_stats_ntfy_per_link *per_link,
405			       u32 curr_ts_usec)
406{
407	u32 total_airtime_usec = 0;
408
409	for (u32 fw_id = 0;
410	     fw_id < ARRAY_SIZE(mld->fw_id_to_bss_conf);
411	     fw_id++) {
412		const struct iwl_stats_ntfy_per_link *link_stats;
413		struct ieee80211_bss_conf *bss_conf;
414		int sig;
415
416		bss_conf = wiphy_dereference(mld->wiphy,
417					     mld->fw_id_to_bss_conf[fw_id]);
418		if (!bss_conf || bss_conf->vif->type != NL80211_IFTYPE_STATION)
419			continue;
420
421		link_stats = &per_link[fw_id];
422
423		total_airtime_usec += le32_to_cpu(link_stats->air_time);
424
425		sig = -le32_to_cpu(link_stats->beacon_filter_average_energy);
426		iwl_mld_update_link_sig(bss_conf->vif, sig, bss_conf);
427
428		/* TODO: parse more fields here (task=statistics)*/
429	}
430
431	iwl_mld_stats_recalc_traffic_load(mld, total_airtime_usec,
432					  curr_ts_usec);
433}
434
435static void
436iwl_mld_process_per_sta_stats(struct iwl_mld *mld,
437			      const struct iwl_stats_ntfy_per_sta *per_sta)
438{
439	for (int i = 0; i < mld->fw->ucode_capa.num_stations; i++) {
440		struct ieee80211_link_sta *link_sta =
441			wiphy_dereference(mld->wiphy,
442					  mld->fw_id_to_link_sta[i]);
443		struct iwl_mld_link_sta *mld_link_sta;
444		s8 avg_energy =
445			-(s8)le32_to_cpu(per_sta[i].average_energy);
446
447		if (IS_ERR_OR_NULL(link_sta) || !avg_energy)
448			continue;
449
450		mld_link_sta = iwl_mld_link_sta_from_mac80211(link_sta);
451		if (WARN_ON(!mld_link_sta))
452			continue;
453
454		mld_link_sta->signal_avg = avg_energy;
455	}
456}
457
458static void iwl_mld_fill_chanctx_stats(struct ieee80211_hw *hw,
459				       struct ieee80211_chanctx_conf *ctx,
460				       void *data)
461{
462	struct iwl_mld_phy *phy = iwl_mld_phy_from_mac80211(ctx);
463	const struct iwl_stats_ntfy_per_phy *per_phy = data;
464	u32 new_load, old_load;
465
466	if (WARN_ON(phy->fw_id >= IWL_STATS_MAX_PHY_OPERATIONAL))
467		return;
468
469	phy->channel_load_by_us =
470		le32_to_cpu(per_phy[phy->fw_id].channel_load_by_us);
471
472	old_load = phy->avg_channel_load_not_by_us;
473	new_load = le32_to_cpu(per_phy[phy->fw_id].channel_load_not_by_us);
474
475	if (IWL_FW_CHECK(phy->mld,
476			 new_load != IWL_STATS_UNKNOWN_CHANNEL_LOAD &&
477				new_load > 100,
478			 "Invalid channel load %u\n", new_load))
479		return;
480
481	if (new_load != IWL_STATS_UNKNOWN_CHANNEL_LOAD) {
482		/* update giving a weight of 0.5 for the old value */
483		phy->avg_channel_load_not_by_us = (new_load >> 1) +
484						  (old_load >> 1);
485	}
486
487	iwl_mld_emlsr_check_chan_load(hw, phy, old_load);
488}
489
490static void
491iwl_mld_process_per_phy_stats(struct iwl_mld *mld,
492			      const struct iwl_stats_ntfy_per_phy *per_phy)
493{
494	ieee80211_iter_chan_contexts_mtx(mld->hw,
495					 iwl_mld_fill_chanctx_stats,
496					 (void *)(uintptr_t)per_phy);
497
498}
499
500void iwl_mld_handle_stats_oper_notif(struct iwl_mld *mld,
501				     struct iwl_rx_packet *pkt)
502{
503	const struct iwl_system_statistics_notif_oper *stats =
504		(void *)&pkt->data;
505	u32 curr_ts_usec = le32_to_cpu(stats->time_stamp);
506
507	BUILD_BUG_ON(ARRAY_SIZE(stats->per_sta) != IWL_STATION_COUNT_MAX);
508	BUILD_BUG_ON(ARRAY_SIZE(stats->per_link) <
509		     ARRAY_SIZE(mld->fw_id_to_bss_conf));
510
511	iwl_mld_process_per_link_stats(mld, stats->per_link, curr_ts_usec);
512	iwl_mld_process_per_sta_stats(mld, stats->per_sta);
513	iwl_mld_process_per_phy_stats(mld, stats->per_phy);
514}
515
516void iwl_mld_handle_stats_oper_part1_notif(struct iwl_mld *mld,
517					   struct iwl_rx_packet *pkt)
518{
519	/* TODO */
520}
521
~/ Read-only file preview.