xref: /linux/drivers/net/wireless/intel/iwlwifi/mld/stats.c (revision 4f9786035f9e519db41375818e1d0b5f20da2f10)
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 		&notif->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