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 		&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 
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_CCK_MSK || format == RATE_MCS_LEGACY_OFDM_MSK) {
195 		int rate = u32_get_bits(rate_n_flags, RATE_LEGACY_RATE_MSK);
196 
197 		/* add the offset needed to get to the legacy ofdm indices */
198 		if (format == RATE_MCS_LEGACY_OFDM_MSK)
199 			rate += IWL_FIRST_OFDM_RATE;
200 
201 		switch (rate) {
202 		case IWL_RATE_1M_INDEX:
203 			rinfo->legacy = 10;
204 			break;
205 		case IWL_RATE_2M_INDEX:
206 			rinfo->legacy = 20;
207 			break;
208 		case IWL_RATE_5M_INDEX:
209 			rinfo->legacy = 55;
210 			break;
211 		case IWL_RATE_11M_INDEX:
212 			rinfo->legacy = 110;
213 			break;
214 		case IWL_RATE_6M_INDEX:
215 			rinfo->legacy = 60;
216 			break;
217 		case IWL_RATE_9M_INDEX:
218 			rinfo->legacy = 90;
219 			break;
220 		case IWL_RATE_12M_INDEX:
221 			rinfo->legacy = 120;
222 			break;
223 		case IWL_RATE_18M_INDEX:
224 			rinfo->legacy = 180;
225 			break;
226 		case IWL_RATE_24M_INDEX:
227 			rinfo->legacy = 240;
228 			break;
229 		case IWL_RATE_36M_INDEX:
230 			rinfo->legacy = 360;
231 			break;
232 		case IWL_RATE_48M_INDEX:
233 			rinfo->legacy = 480;
234 			break;
235 		case IWL_RATE_54M_INDEX:
236 			rinfo->legacy = 540;
237 		}
238 		return;
239 	}
240 
241 	rinfo->nss = u32_get_bits(rate_n_flags, RATE_MCS_NSS_MSK) + 1;
242 
243 	if (format == RATE_MCS_HT_MSK)
244 		rinfo->mcs = RATE_HT_MCS_INDEX(rate_n_flags);
245 	else
246 		rinfo->mcs = u32_get_bits(rate_n_flags, RATE_MCS_CODE_MSK);
247 
248 	if (rate_n_flags & RATE_MCS_SGI_MSK)
249 		rinfo->flags |= RATE_INFO_FLAGS_SHORT_GI;
250 
251 	switch (format) {
252 	case RATE_MCS_EHT_MSK:
253 		rinfo->flags |= RATE_INFO_FLAGS_EHT_MCS;
254 		break;
255 	case RATE_MCS_HE_MSK:
256 		gi_ltf = u32_get_bits(rate_n_flags, RATE_MCS_HE_GI_LTF_MSK);
257 
258 		rinfo->flags |= RATE_INFO_FLAGS_HE_MCS;
259 
260 		if (rate_n_flags & RATE_MCS_HE_106T_MSK) {
261 			rinfo->bw = RATE_INFO_BW_HE_RU;
262 			rinfo->he_ru_alloc = NL80211_RATE_INFO_HE_RU_ALLOC_106;
263 		}
264 
265 		switch (rate_n_flags & RATE_MCS_HE_TYPE_MSK) {
266 		case RATE_MCS_HE_TYPE_SU:
267 		case RATE_MCS_HE_TYPE_EXT_SU:
268 			if (gi_ltf == 0 || gi_ltf == 1)
269 				rinfo->he_gi = NL80211_RATE_INFO_HE_GI_0_8;
270 			else if (gi_ltf == 2)
271 				rinfo->he_gi = NL80211_RATE_INFO_HE_GI_1_6;
272 			else if (gi_ltf == 3)
273 				rinfo->he_gi = NL80211_RATE_INFO_HE_GI_3_2;
274 			else
275 				rinfo->he_gi = NL80211_RATE_INFO_HE_GI_0_8;
276 			break;
277 		case RATE_MCS_HE_TYPE_MU:
278 			if (gi_ltf == 0 || gi_ltf == 1)
279 				rinfo->he_gi = NL80211_RATE_INFO_HE_GI_0_8;
280 			else if (gi_ltf == 2)
281 				rinfo->he_gi = NL80211_RATE_INFO_HE_GI_1_6;
282 			else
283 				rinfo->he_gi = NL80211_RATE_INFO_HE_GI_3_2;
284 			break;
285 		case RATE_MCS_HE_TYPE_TRIG:
286 			if (gi_ltf == 0 || gi_ltf == 1)
287 				rinfo->he_gi = NL80211_RATE_INFO_HE_GI_1_6;
288 			else
289 				rinfo->he_gi = NL80211_RATE_INFO_HE_GI_3_2;
290 			break;
291 		}
292 
293 		if (rate_n_flags & RATE_HE_DUAL_CARRIER_MODE_MSK)
294 			rinfo->he_dcm = 1;
295 		break;
296 	case RATE_MCS_HT_MSK:
297 		rinfo->flags |= RATE_INFO_FLAGS_MCS;
298 		break;
299 	case RATE_MCS_VHT_MSK:
300 		rinfo->flags |= RATE_INFO_FLAGS_VHT_MCS;
301 		break;
302 	}
303 }
304 
iwl_mld_mac80211_sta_statistics(struct ieee80211_hw * hw,struct ieee80211_vif * vif,struct ieee80211_sta * sta,struct station_info * sinfo)305 void iwl_mld_mac80211_sta_statistics(struct ieee80211_hw *hw,
306 				     struct ieee80211_vif *vif,
307 				     struct ieee80211_sta *sta,
308 				     struct station_info *sinfo)
309 {
310 	struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
311 
312 	/* This API is not EMLSR ready, so we cannot provide complete
313 	 * information if EMLSR is active
314 	 */
315 	if (hweight16(vif->active_links) > 1)
316 		return;
317 
318 	if (iwl_mld_fw_stats_to_mac80211(mld_sta->mld, mld_sta, sinfo))
319 		return;
320 
321 	iwl_mld_sta_stats_fill_txrate(mld_sta, sinfo);
322 
323 	/* TODO: NL80211_STA_INFO_BEACON_RX */
324 
325 	/* TODO: NL80211_STA_INFO_BEACON_SIGNAL_AVG */
326 }
327 
328 #define IWL_MLD_TRAFFIC_LOAD_MEDIUM_THRESH	10 /* percentage */
329 #define IWL_MLD_TRAFFIC_LOAD_HIGH_THRESH	50 /* percentage */
330 #define IWL_MLD_TRAFFIC_LOAD_MIN_WINDOW_USEC	(500 * 1000)
331 
iwl_mld_stats_load_percentage(u32 last_ts_usec,u32 curr_ts_usec,u32 total_airtime_usec)332 static u8 iwl_mld_stats_load_percentage(u32 last_ts_usec, u32 curr_ts_usec,
333 					u32 total_airtime_usec)
334 {
335 	u32 elapsed_usec = curr_ts_usec - last_ts_usec;
336 
337 	if (elapsed_usec < IWL_MLD_TRAFFIC_LOAD_MIN_WINDOW_USEC)
338 		return 0;
339 
340 	return (100 * total_airtime_usec / elapsed_usec);
341 }
342 
iwl_mld_stats_recalc_traffic_load(struct iwl_mld * mld,u32 total_airtime_usec,u32 curr_ts_usec)343 static void iwl_mld_stats_recalc_traffic_load(struct iwl_mld *mld,
344 					      u32 total_airtime_usec,
345 					      u32 curr_ts_usec)
346 {
347 	u32 last_ts_usec = mld->scan.traffic_load.last_stats_ts_usec;
348 	u8 load_prec;
349 
350 	/* Skip the calculation as this is the first notification received */
351 	if (!last_ts_usec)
352 		goto out;
353 
354 	load_prec = iwl_mld_stats_load_percentage(last_ts_usec, curr_ts_usec,
355 						  total_airtime_usec);
356 
357 	if (load_prec > IWL_MLD_TRAFFIC_LOAD_HIGH_THRESH)
358 		mld->scan.traffic_load.status = IWL_MLD_TRAFFIC_HIGH;
359 	else if (load_prec > IWL_MLD_TRAFFIC_LOAD_MEDIUM_THRESH)
360 		mld->scan.traffic_load.status = IWL_MLD_TRAFFIC_MEDIUM;
361 	else
362 		mld->scan.traffic_load.status = IWL_MLD_TRAFFIC_LOW;
363 
364 out:
365 	mld->scan.traffic_load.last_stats_ts_usec = curr_ts_usec;
366 }
367 
iwl_mld_update_link_sig(struct ieee80211_vif * vif,int sig,struct ieee80211_bss_conf * bss_conf)368 static void iwl_mld_update_link_sig(struct ieee80211_vif *vif, int sig,
369 				    struct ieee80211_bss_conf *bss_conf)
370 {
371 	struct iwl_mld *mld = iwl_mld_vif_from_mac80211(vif)->mld;
372 	int exit_emlsr_thresh;
373 
374 	if (sig == 0) {
375 		IWL_DEBUG_RX(mld, "RSSI is 0 - skip signal based decision\n");
376 		return;
377 	}
378 
379 	/* TODO: task=statistics handle CQM notifications */
380 
381 	if (sig < IWL_MLD_LOW_RSSI_MLO_SCAN_THRESH)
382 		iwl_mld_int_mlo_scan(mld, vif);
383 
384 	if (!iwl_mld_emlsr_active(vif))
385 		return;
386 
387 	/* We are in EMLSR, check if we need to exit */
388 	exit_emlsr_thresh =
389 		iwl_mld_get_emlsr_rssi_thresh(mld, &bss_conf->chanreq.oper,
390 					      true);
391 
392 	if (sig < exit_emlsr_thresh)
393 		iwl_mld_exit_emlsr(mld, vif, IWL_MLD_EMLSR_EXIT_LOW_RSSI,
394 				   iwl_mld_get_other_link(vif,
395 							  bss_conf->link_id));
396 }
397 
398 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)399 iwl_mld_process_per_link_stats(struct iwl_mld *mld,
400 			       const struct iwl_stats_ntfy_per_link *per_link,
401 			       u32 curr_ts_usec)
402 {
403 	u32 total_airtime_usec = 0;
404 
405 	for (u32 fw_id = 0;
406 	     fw_id < ARRAY_SIZE(mld->fw_id_to_bss_conf);
407 	     fw_id++) {
408 		const struct iwl_stats_ntfy_per_link *link_stats;
409 		struct ieee80211_bss_conf *bss_conf;
410 		int sig;
411 
412 		bss_conf = wiphy_dereference(mld->wiphy,
413 					     mld->fw_id_to_bss_conf[fw_id]);
414 		if (!bss_conf || bss_conf->vif->type != NL80211_IFTYPE_STATION)
415 			continue;
416 
417 		link_stats = &per_link[fw_id];
418 
419 		total_airtime_usec += le32_to_cpu(link_stats->air_time);
420 
421 		sig = -le32_to_cpu(link_stats->beacon_filter_average_energy);
422 		iwl_mld_update_link_sig(bss_conf->vif, sig, bss_conf);
423 
424 		/* TODO: parse more fields here (task=statistics)*/
425 	}
426 
427 	iwl_mld_stats_recalc_traffic_load(mld, total_airtime_usec,
428 					  curr_ts_usec);
429 }
430 
431 static void
iwl_mld_process_per_sta_stats(struct iwl_mld * mld,const struct iwl_stats_ntfy_per_sta * per_sta)432 iwl_mld_process_per_sta_stats(struct iwl_mld *mld,
433 			      const struct iwl_stats_ntfy_per_sta *per_sta)
434 {
435 	for (int i = 0; i < mld->fw->ucode_capa.num_stations; i++) {
436 		struct ieee80211_link_sta *link_sta =
437 			wiphy_dereference(mld->wiphy,
438 					  mld->fw_id_to_link_sta[i]);
439 		struct iwl_mld_link_sta *mld_link_sta;
440 		s8 avg_energy =
441 			-(s8)le32_to_cpu(per_sta[i].average_energy);
442 
443 		if (IS_ERR_OR_NULL(link_sta) || !avg_energy)
444 			continue;
445 
446 		mld_link_sta = iwl_mld_link_sta_from_mac80211(link_sta);
447 		if (WARN_ON(!mld_link_sta))
448 			continue;
449 
450 		mld_link_sta->signal_avg = avg_energy;
451 	}
452 }
453 
iwl_mld_fill_chanctx_stats(struct ieee80211_hw * hw,struct ieee80211_chanctx_conf * ctx,void * data)454 static void iwl_mld_fill_chanctx_stats(struct ieee80211_hw *hw,
455 				       struct ieee80211_chanctx_conf *ctx,
456 				       void *data)
457 {
458 	struct iwl_mld_phy *phy = iwl_mld_phy_from_mac80211(ctx);
459 	const struct iwl_stats_ntfy_per_phy *per_phy = data;
460 	u32 new_load, old_load;
461 
462 	if (WARN_ON(phy->fw_id >= IWL_STATS_MAX_PHY_OPERATIONAL))
463 		return;
464 
465 	phy->channel_load_by_us =
466 		le32_to_cpu(per_phy[phy->fw_id].channel_load_by_us);
467 
468 	old_load = phy->avg_channel_load_not_by_us;
469 	new_load = le32_to_cpu(per_phy[phy->fw_id].channel_load_not_by_us);
470 	if (IWL_FW_CHECK(phy->mld, new_load > 100, "Invalid channel load %u\n",
471 			 new_load))
472 		return;
473 
474 	/* give a weight of 0.5 for the old value */
475 	phy->avg_channel_load_not_by_us = (new_load >> 1) + (old_load >> 1);
476 
477 	iwl_mld_emlsr_check_chan_load(hw, phy, old_load);
478 }
479 
480 static void
iwl_mld_process_per_phy_stats(struct iwl_mld * mld,const struct iwl_stats_ntfy_per_phy * per_phy)481 iwl_mld_process_per_phy_stats(struct iwl_mld *mld,
482 			      const struct iwl_stats_ntfy_per_phy *per_phy)
483 {
484 	ieee80211_iter_chan_contexts_mtx(mld->hw,
485 					 iwl_mld_fill_chanctx_stats,
486 					 (void *)(uintptr_t)per_phy);
487 
488 }
489 
iwl_mld_handle_stats_oper_notif(struct iwl_mld * mld,struct iwl_rx_packet * pkt)490 void iwl_mld_handle_stats_oper_notif(struct iwl_mld *mld,
491 				     struct iwl_rx_packet *pkt)
492 {
493 	const struct iwl_system_statistics_notif_oper *stats =
494 		(void *)&pkt->data;
495 	u32 curr_ts_usec = le32_to_cpu(stats->time_stamp);
496 
497 	BUILD_BUG_ON(ARRAY_SIZE(stats->per_sta) != IWL_STATION_COUNT_MAX);
498 	BUILD_BUG_ON(ARRAY_SIZE(stats->per_link) <
499 		     ARRAY_SIZE(mld->fw_id_to_bss_conf));
500 
501 	iwl_mld_process_per_link_stats(mld, stats->per_link, curr_ts_usec);
502 	iwl_mld_process_per_sta_stats(mld, stats->per_sta);
503 	iwl_mld_process_per_phy_stats(mld, stats->per_phy);
504 
505 	iwl_mld_check_omi_bw_reduction(mld);
506 }
507 
iwl_mld_handle_stats_oper_part1_notif(struct iwl_mld * mld,struct iwl_rx_packet * pkt)508 void iwl_mld_handle_stats_oper_part1_notif(struct iwl_mld *mld,
509 					   struct iwl_rx_packet *pkt)
510 {
511 	/* TODO */
512 }
513 
514