xref: /linux/drivers/net/wireless/mediatek/mt76/testmode.c (revision 5d581c33230065b07887a40dab8d29c63a8c6e7e)
1f0efa862SFelix Fietkau // SPDX-License-Identifier: ISC
2f0efa862SFelix Fietkau /* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name> */
360c45a78SShayne Chen 
460c45a78SShayne Chen #include <linux/random.h>
5f0efa862SFelix Fietkau #include "mt76.h"
6f0efa862SFelix Fietkau 
7bce84458SLorenzo Bianconi const struct nla_policy mt76_tm_policy[NUM_MT76_TM_ATTRS] = {
8f0efa862SFelix Fietkau 	[MT76_TM_ATTR_RESET] = { .type = NLA_FLAG },
9f0efa862SFelix Fietkau 	[MT76_TM_ATTR_STATE] = { .type = NLA_U8 },
10f0efa862SFelix Fietkau 	[MT76_TM_ATTR_TX_COUNT] = { .type = NLA_U32 },
1174f12d51SLin Ma 	[MT76_TM_ATTR_TX_LENGTH] = { .type = NLA_U32 },
12f0efa862SFelix Fietkau 	[MT76_TM_ATTR_TX_RATE_MODE] = { .type = NLA_U8 },
13f0efa862SFelix Fietkau 	[MT76_TM_ATTR_TX_RATE_NSS] = { .type = NLA_U8 },
14f0efa862SFelix Fietkau 	[MT76_TM_ATTR_TX_RATE_IDX] = { .type = NLA_U8 },
15f0efa862SFelix Fietkau 	[MT76_TM_ATTR_TX_RATE_SGI] = { .type = NLA_U8 },
16f0efa862SFelix Fietkau 	[MT76_TM_ATTR_TX_RATE_LDPC] = { .type = NLA_U8 },
177f54c742SShayne Chen 	[MT76_TM_ATTR_TX_RATE_STBC] = { .type = NLA_U8 },
181a38c2f5SShayne Chen 	[MT76_TM_ATTR_TX_LTF] = { .type = NLA_U8 },
19f0efa862SFelix Fietkau 	[MT76_TM_ATTR_TX_ANTENNA] = { .type = NLA_U8 },
20fdc9c18eSShayne Chen 	[MT76_TM_ATTR_TX_SPE_IDX] = { .type = NLA_U8 },
21f0efa862SFelix Fietkau 	[MT76_TM_ATTR_TX_POWER_CONTROL] = { .type = NLA_U8 },
22f0efa862SFelix Fietkau 	[MT76_TM_ATTR_TX_POWER] = { .type = NLA_NESTED },
23b8cbdb97SShayne Chen 	[MT76_TM_ATTR_TX_DUTY_CYCLE] = { .type = NLA_U8 },
24b8cbdb97SShayne Chen 	[MT76_TM_ATTR_TX_IPG] = { .type = NLA_U32 },
25b8cbdb97SShayne Chen 	[MT76_TM_ATTR_TX_TIME] = { .type = NLA_U32 },
26f0efa862SFelix Fietkau 	[MT76_TM_ATTR_FREQ_OFFSET] = { .type = NLA_U32 },
27bce84458SLorenzo Bianconi 	[MT76_TM_ATTR_DRV_DATA] = { .type = NLA_NESTED },
28f0efa862SFelix Fietkau };
29bce84458SLorenzo Bianconi EXPORT_SYMBOL_GPL(mt76_tm_policy);
30f0efa862SFelix Fietkau 
31c918c74dSShayne Chen void mt76_testmode_tx_pending(struct mt76_phy *phy)
32f0efa862SFelix Fietkau {
33c918c74dSShayne Chen 	struct mt76_testmode_data *td = &phy->test;
34c918c74dSShayne Chen 	struct mt76_dev *dev = phy->dev;
35f0efa862SFelix Fietkau 	struct mt76_wcid *wcid = &dev->global_wcid;
36f0efa862SFelix Fietkau 	struct sk_buff *skb = td->tx_skb;
37f0efa862SFelix Fietkau 	struct mt76_queue *q;
38ba459094SShayne Chen 	u16 tx_queued_limit;
39f0efa862SFelix Fietkau 	int qid;
40f0efa862SFelix Fietkau 
41f0efa862SFelix Fietkau 	if (!skb || !td->tx_pending)
42f0efa862SFelix Fietkau 		return;
43f0efa862SFelix Fietkau 
44f0efa862SFelix Fietkau 	qid = skb_get_queue_mapping(skb);
4591990519SLorenzo Bianconi 	q = phy->q_tx[qid];
46f0efa862SFelix Fietkau 
47ba459094SShayne Chen 	tx_queued_limit = td->tx_queued_limit ? td->tx_queued_limit : 1000;
48ba459094SShayne Chen 
49f0efa862SFelix Fietkau 	spin_lock_bh(&q->lock);
50f0efa862SFelix Fietkau 
51ba459094SShayne Chen 	while (td->tx_pending > 0 &&
52ba459094SShayne Chen 	       td->tx_queued - td->tx_done < tx_queued_limit &&
539729ff4cSFelix Fietkau 	       q->queued < q->ndesc / 2) {
54f0efa862SFelix Fietkau 		int ret;
55f0efa862SFelix Fietkau 
56*5d581c33SFelix Fietkau 		ret = dev->queue_ops->tx_queue_skb(phy, q, qid, skb_get(skb),
57d08295f5SFelix Fietkau 						   wcid, NULL);
58f0efa862SFelix Fietkau 		if (ret < 0)
59f0efa862SFelix Fietkau 			break;
60f0efa862SFelix Fietkau 
61f0efa862SFelix Fietkau 		td->tx_pending--;
62f0efa862SFelix Fietkau 		td->tx_queued++;
63f0efa862SFelix Fietkau 	}
64f0efa862SFelix Fietkau 
65f0efa862SFelix Fietkau 	dev->queue_ops->kick(dev, q);
66f0efa862SFelix Fietkau 
67f0efa862SFelix Fietkau 	spin_unlock_bh(&q->lock);
68f0efa862SFelix Fietkau }
69f0efa862SFelix Fietkau 
702601dda8SShayne Chen static u32
712601dda8SShayne Chen mt76_testmode_max_mpdu_len(struct mt76_phy *phy, u8 tx_rate_mode)
722601dda8SShayne Chen {
732601dda8SShayne Chen 	switch (tx_rate_mode) {
742601dda8SShayne Chen 	case MT76_TM_TX_MODE_HT:
752601dda8SShayne Chen 		return IEEE80211_MAX_MPDU_LEN_HT_7935;
762601dda8SShayne Chen 	case MT76_TM_TX_MODE_VHT:
772601dda8SShayne Chen 	case MT76_TM_TX_MODE_HE_SU:
782601dda8SShayne Chen 	case MT76_TM_TX_MODE_HE_EXT_SU:
792601dda8SShayne Chen 	case MT76_TM_TX_MODE_HE_TB:
802601dda8SShayne Chen 	case MT76_TM_TX_MODE_HE_MU:
812601dda8SShayne Chen 		if (phy->sband_5g.sband.vht_cap.cap &
822601dda8SShayne Chen 		    IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_7991)
832601dda8SShayne Chen 			return IEEE80211_MAX_MPDU_LEN_VHT_7991;
842601dda8SShayne Chen 		return IEEE80211_MAX_MPDU_LEN_VHT_11454;
852601dda8SShayne Chen 	case MT76_TM_TX_MODE_CCK:
862601dda8SShayne Chen 	case MT76_TM_TX_MODE_OFDM:
872601dda8SShayne Chen 	default:
882601dda8SShayne Chen 		return IEEE80211_MAX_FRAME_LEN;
892601dda8SShayne Chen 	}
902601dda8SShayne Chen }
91f0efa862SFelix Fietkau 
922601dda8SShayne Chen static void
932601dda8SShayne Chen mt76_testmode_free_skb(struct mt76_phy *phy)
94f0efa862SFelix Fietkau {
95c918c74dSShayne Chen 	struct mt76_testmode_data *td = &phy->test;
962601dda8SShayne Chen 
97d705ae86SLorenzo Bianconi 	dev_kfree_skb(td->tx_skb);
982601dda8SShayne Chen 	td->tx_skb = NULL;
992601dda8SShayne Chen }
1002601dda8SShayne Chen 
1012601dda8SShayne Chen int mt76_testmode_alloc_skb(struct mt76_phy *phy, u32 len)
1022601dda8SShayne Chen {
1032601dda8SShayne Chen #define MT_TXP_MAX_LEN	4095
104f0efa862SFelix Fietkau 	u16 fc = IEEE80211_FTYPE_DATA | IEEE80211_STYPE_DATA |
105f0efa862SFelix Fietkau 		 IEEE80211_FCTL_FROMDS;
1062601dda8SShayne Chen 	struct mt76_testmode_data *td = &phy->test;
1072601dda8SShayne Chen 	struct sk_buff **frag_tail, *head;
1082601dda8SShayne Chen 	struct ieee80211_tx_info *info;
1092601dda8SShayne Chen 	struct ieee80211_hdr *hdr;
1102601dda8SShayne Chen 	u32 max_len, head_len;
1112601dda8SShayne Chen 	int nfrags, i;
112f0efa862SFelix Fietkau 
1132601dda8SShayne Chen 	max_len = mt76_testmode_max_mpdu_len(phy, td->tx_rate_mode);
1142601dda8SShayne Chen 	if (len > max_len)
1152601dda8SShayne Chen 		len = max_len;
1162601dda8SShayne Chen 	else if (len < sizeof(struct ieee80211_hdr))
1172601dda8SShayne Chen 		len = sizeof(struct ieee80211_hdr);
118f0efa862SFelix Fietkau 
1192601dda8SShayne Chen 	nfrags = len / MT_TXP_MAX_LEN;
1202601dda8SShayne Chen 	head_len = nfrags ? MT_TXP_MAX_LEN : len;
1212601dda8SShayne Chen 
1222601dda8SShayne Chen 	if (len > IEEE80211_MAX_FRAME_LEN)
1232601dda8SShayne Chen 		fc |= IEEE80211_STYPE_QOS_DATA;
1242601dda8SShayne Chen 
1252601dda8SShayne Chen 	head = alloc_skb(head_len, GFP_KERNEL);
1262601dda8SShayne Chen 	if (!head)
127f0efa862SFelix Fietkau 		return -ENOMEM;
128f0efa862SFelix Fietkau 
12960c45a78SShayne Chen 	hdr = __skb_put_zero(head, sizeof(*hdr));
130f0efa862SFelix Fietkau 	hdr->frame_control = cpu_to_le16(fc);
131c40b42c2SShayne Chen 	memcpy(hdr->addr1, td->addr[0], ETH_ALEN);
132c40b42c2SShayne Chen 	memcpy(hdr->addr2, td->addr[1], ETH_ALEN);
133c40b42c2SShayne Chen 	memcpy(hdr->addr3, td->addr[2], ETH_ALEN);
1342601dda8SShayne Chen 	skb_set_queue_mapping(head, IEEE80211_AC_BE);
13560c45a78SShayne Chen 	get_random_bytes(__skb_put(head, head_len - sizeof(*hdr)),
13660c45a78SShayne Chen 			 head_len - sizeof(*hdr));
137f0efa862SFelix Fietkau 
1382601dda8SShayne Chen 	info = IEEE80211_SKB_CB(head);
139f0efa862SFelix Fietkau 	info->flags = IEEE80211_TX_CTL_INJECTED |
140f0efa862SFelix Fietkau 		      IEEE80211_TX_CTL_NO_ACK |
141f0efa862SFelix Fietkau 		      IEEE80211_TX_CTL_NO_PS_BUFFER;
14261fe7357SShayne Chen 
143a062f001SLorenzo Bianconi 	info->hw_queue |= FIELD_PREP(MT_TX_HW_QUEUE_PHY, phy->band_idx);
1442601dda8SShayne Chen 	frag_tail = &skb_shinfo(head)->frag_list;
1452601dda8SShayne Chen 
1462601dda8SShayne Chen 	for (i = 0; i < nfrags; i++) {
1472601dda8SShayne Chen 		struct sk_buff *frag;
1482601dda8SShayne Chen 		u16 frag_len;
1492601dda8SShayne Chen 
1502601dda8SShayne Chen 		if (i == nfrags - 1)
1512601dda8SShayne Chen 			frag_len = len % MT_TXP_MAX_LEN;
1522601dda8SShayne Chen 		else
1532601dda8SShayne Chen 			frag_len = MT_TXP_MAX_LEN;
1542601dda8SShayne Chen 
1552601dda8SShayne Chen 		frag = alloc_skb(frag_len, GFP_KERNEL);
156fe2c3b1fSLorenzo Bianconi 		if (!frag) {
157fe2c3b1fSLorenzo Bianconi 			mt76_testmode_free_skb(phy);
158fe2c3b1fSLorenzo Bianconi 			dev_kfree_skb(head);
1592601dda8SShayne Chen 			return -ENOMEM;
160fe2c3b1fSLorenzo Bianconi 		}
1612601dda8SShayne Chen 
16260c45a78SShayne Chen 		get_random_bytes(__skb_put(frag, frag_len), frag_len);
1632601dda8SShayne Chen 		head->len += frag->len;
1642601dda8SShayne Chen 		head->data_len += frag->len;
1652601dda8SShayne Chen 
1662601dda8SShayne Chen 		*frag_tail = frag;
167223cea6dSLorenzo Bianconi 		frag_tail = &(*frag_tail)->next;
1682601dda8SShayne Chen 	}
1692601dda8SShayne Chen 
1702601dda8SShayne Chen 	mt76_testmode_free_skb(phy);
1712601dda8SShayne Chen 	td->tx_skb = head;
1722601dda8SShayne Chen 
1732601dda8SShayne Chen 	return 0;
1742601dda8SShayne Chen }
1752601dda8SShayne Chen EXPORT_SYMBOL(mt76_testmode_alloc_skb);
1762601dda8SShayne Chen 
1772601dda8SShayne Chen static int
1782601dda8SShayne Chen mt76_testmode_tx_init(struct mt76_phy *phy)
1792601dda8SShayne Chen {
1802601dda8SShayne Chen 	struct mt76_testmode_data *td = &phy->test;
1812601dda8SShayne Chen 	struct ieee80211_tx_info *info;
1822601dda8SShayne Chen 	struct ieee80211_tx_rate *rate;
1832601dda8SShayne Chen 	u8 max_nss = hweight8(phy->antenna_mask);
1842601dda8SShayne Chen 	int ret;
1852601dda8SShayne Chen 
1862601dda8SShayne Chen 	ret = mt76_testmode_alloc_skb(phy, td->tx_mpdu_len);
1872601dda8SShayne Chen 	if (ret)
1882601dda8SShayne Chen 		return ret;
1892601dda8SShayne Chen 
19061fe7357SShayne Chen 	if (td->tx_rate_mode > MT76_TM_TX_MODE_VHT)
19161fe7357SShayne Chen 		goto out;
19261fe7357SShayne Chen 
1932601dda8SShayne Chen 	if (td->tx_antenna_mask)
1942601dda8SShayne Chen 		max_nss = min_t(u8, max_nss, hweight8(td->tx_antenna_mask));
1952601dda8SShayne Chen 
1962601dda8SShayne Chen 	info = IEEE80211_SKB_CB(td->tx_skb);
197f0efa862SFelix Fietkau 	rate = &info->control.rates[0];
198f0efa862SFelix Fietkau 	rate->count = 1;
199f0efa862SFelix Fietkau 	rate->idx = td->tx_rate_idx;
200f0efa862SFelix Fietkau 
201f0efa862SFelix Fietkau 	switch (td->tx_rate_mode) {
202f0efa862SFelix Fietkau 	case MT76_TM_TX_MODE_CCK:
20398df2baeSLorenzo Bianconi 		if (phy->chandef.chan->band != NL80211_BAND_2GHZ)
204f0efa862SFelix Fietkau 			return -EINVAL;
205f0efa862SFelix Fietkau 
206f0efa862SFelix Fietkau 		if (rate->idx > 4)
207f0efa862SFelix Fietkau 			return -EINVAL;
208f0efa862SFelix Fietkau 		break;
209f0efa862SFelix Fietkau 	case MT76_TM_TX_MODE_OFDM:
21098df2baeSLorenzo Bianconi 		if (phy->chandef.chan->band != NL80211_BAND_2GHZ)
211f0efa862SFelix Fietkau 			break;
212f0efa862SFelix Fietkau 
213f0efa862SFelix Fietkau 		if (rate->idx > 8)
214f0efa862SFelix Fietkau 			return -EINVAL;
215f0efa862SFelix Fietkau 
216f0efa862SFelix Fietkau 		rate->idx += 4;
217f0efa862SFelix Fietkau 		break;
218f0efa862SFelix Fietkau 	case MT76_TM_TX_MODE_HT:
219f0efa862SFelix Fietkau 		if (rate->idx > 8 * max_nss &&
220f0efa862SFelix Fietkau 			!(rate->idx == 32 &&
22198df2baeSLorenzo Bianconi 			  phy->chandef.width >= NL80211_CHAN_WIDTH_40))
222f0efa862SFelix Fietkau 			return -EINVAL;
223f0efa862SFelix Fietkau 
224f0efa862SFelix Fietkau 		rate->flags |= IEEE80211_TX_RC_MCS;
225f0efa862SFelix Fietkau 		break;
226f0efa862SFelix Fietkau 	case MT76_TM_TX_MODE_VHT:
227f0efa862SFelix Fietkau 		if (rate->idx > 9)
228f0efa862SFelix Fietkau 			return -EINVAL;
229f0efa862SFelix Fietkau 
230f0efa862SFelix Fietkau 		if (td->tx_rate_nss > max_nss)
231f0efa862SFelix Fietkau 			return -EINVAL;
232f0efa862SFelix Fietkau 
233f0efa862SFelix Fietkau 		ieee80211_rate_set_vht(rate, td->tx_rate_idx, td->tx_rate_nss);
234f0efa862SFelix Fietkau 		rate->flags |= IEEE80211_TX_RC_VHT_MCS;
235f0efa862SFelix Fietkau 		break;
236f0efa862SFelix Fietkau 	default:
237f0efa862SFelix Fietkau 		break;
238f0efa862SFelix Fietkau 	}
239f0efa862SFelix Fietkau 
240f0efa862SFelix Fietkau 	if (td->tx_rate_sgi)
241f0efa862SFelix Fietkau 		rate->flags |= IEEE80211_TX_RC_SHORT_GI;
242f0efa862SFelix Fietkau 
243f0efa862SFelix Fietkau 	if (td->tx_rate_ldpc)
244f0efa862SFelix Fietkau 		info->flags |= IEEE80211_TX_CTL_LDPC;
245f0efa862SFelix Fietkau 
2467f54c742SShayne Chen 	if (td->tx_rate_stbc)
2477f54c742SShayne Chen 		info->flags |= IEEE80211_TX_CTL_STBC;
2487f54c742SShayne Chen 
249f0efa862SFelix Fietkau 	if (td->tx_rate_mode >= MT76_TM_TX_MODE_HT) {
25098df2baeSLorenzo Bianconi 		switch (phy->chandef.width) {
251f0efa862SFelix Fietkau 		case NL80211_CHAN_WIDTH_40:
252f0efa862SFelix Fietkau 			rate->flags |= IEEE80211_TX_RC_40_MHZ_WIDTH;
253f0efa862SFelix Fietkau 			break;
254f0efa862SFelix Fietkau 		case NL80211_CHAN_WIDTH_80:
255f0efa862SFelix Fietkau 			rate->flags |= IEEE80211_TX_RC_80_MHZ_WIDTH;
256f0efa862SFelix Fietkau 			break;
257f0efa862SFelix Fietkau 		case NL80211_CHAN_WIDTH_80P80:
258f0efa862SFelix Fietkau 		case NL80211_CHAN_WIDTH_160:
259f0efa862SFelix Fietkau 			rate->flags |= IEEE80211_TX_RC_160_MHZ_WIDTH;
260f0efa862SFelix Fietkau 			break;
261f0efa862SFelix Fietkau 		default:
262f0efa862SFelix Fietkau 			break;
263f0efa862SFelix Fietkau 		}
264f0efa862SFelix Fietkau 	}
26561fe7357SShayne Chen out:
266f0efa862SFelix Fietkau 	return 0;
267f0efa862SFelix Fietkau }
268f0efa862SFelix Fietkau 
269f0efa862SFelix Fietkau static void
270c918c74dSShayne Chen mt76_testmode_tx_start(struct mt76_phy *phy)
271f0efa862SFelix Fietkau {
272c918c74dSShayne Chen 	struct mt76_testmode_data *td = &phy->test;
273c918c74dSShayne Chen 	struct mt76_dev *dev = phy->dev;
274f0efa862SFelix Fietkau 
275f0efa862SFelix Fietkau 	td->tx_queued = 0;
276f0efa862SFelix Fietkau 	td->tx_done = 0;
277f0efa862SFelix Fietkau 	td->tx_pending = td->tx_count;
278781eef5bSFelix Fietkau 	mt76_worker_schedule(&dev->tx_worker);
279f0efa862SFelix Fietkau }
280f0efa862SFelix Fietkau 
281f0efa862SFelix Fietkau static void
282c918c74dSShayne Chen mt76_testmode_tx_stop(struct mt76_phy *phy)
283f0efa862SFelix Fietkau {
284c918c74dSShayne Chen 	struct mt76_testmode_data *td = &phy->test;
285c918c74dSShayne Chen 	struct mt76_dev *dev = phy->dev;
286f0efa862SFelix Fietkau 
287781eef5bSFelix Fietkau 	mt76_worker_disable(&dev->tx_worker);
288f0efa862SFelix Fietkau 
289f0efa862SFelix Fietkau 	td->tx_pending = 0;
290f0efa862SFelix Fietkau 
291781eef5bSFelix Fietkau 	mt76_worker_enable(&dev->tx_worker);
292f0efa862SFelix Fietkau 
293ba459094SShayne Chen 	wait_event_timeout(dev->tx_wait, td->tx_done == td->tx_queued,
294ba459094SShayne Chen 			   MT76_TM_TIMEOUT * HZ);
295f0efa862SFelix Fietkau 
2962601dda8SShayne Chen 	mt76_testmode_free_skb(phy);
297f0efa862SFelix Fietkau }
298f0efa862SFelix Fietkau 
299f0efa862SFelix Fietkau static inline void
300f0efa862SFelix Fietkau mt76_testmode_param_set(struct mt76_testmode_data *td, u16 idx)
301f0efa862SFelix Fietkau {
302f0efa862SFelix Fietkau 	td->param_set[idx / 32] |= BIT(idx % 32);
303f0efa862SFelix Fietkau }
304f0efa862SFelix Fietkau 
305f0efa862SFelix Fietkau static inline bool
306f0efa862SFelix Fietkau mt76_testmode_param_present(struct mt76_testmode_data *td, u16 idx)
307f0efa862SFelix Fietkau {
308f0efa862SFelix Fietkau 	return td->param_set[idx / 32] & BIT(idx % 32);
309f0efa862SFelix Fietkau }
310f0efa862SFelix Fietkau 
311f0efa862SFelix Fietkau static void
312c918c74dSShayne Chen mt76_testmode_init_defaults(struct mt76_phy *phy)
313f0efa862SFelix Fietkau {
314c918c74dSShayne Chen 	struct mt76_testmode_data *td = &phy->test;
315f0efa862SFelix Fietkau 
3162601dda8SShayne Chen 	if (td->tx_mpdu_len > 0)
317f0efa862SFelix Fietkau 		return;
318f0efa862SFelix Fietkau 
3192601dda8SShayne Chen 	td->tx_mpdu_len = 1024;
320f0efa862SFelix Fietkau 	td->tx_count = 1;
321f0efa862SFelix Fietkau 	td->tx_rate_mode = MT76_TM_TX_MODE_OFDM;
322f0efa862SFelix Fietkau 	td->tx_rate_nss = 1;
323c40b42c2SShayne Chen 
324c40b42c2SShayne Chen 	memcpy(td->addr[0], phy->macaddr, ETH_ALEN);
325c40b42c2SShayne Chen 	memcpy(td->addr[1], phy->macaddr, ETH_ALEN);
326c40b42c2SShayne Chen 	memcpy(td->addr[2], phy->macaddr, ETH_ALEN);
327f0efa862SFelix Fietkau }
328f0efa862SFelix Fietkau 
329f0efa862SFelix Fietkau static int
330c918c74dSShayne Chen __mt76_testmode_set_state(struct mt76_phy *phy, enum mt76_testmode_state state)
331f0efa862SFelix Fietkau {
332c918c74dSShayne Chen 	enum mt76_testmode_state prev_state = phy->test.state;
333c918c74dSShayne Chen 	struct mt76_dev *dev = phy->dev;
334f0efa862SFelix Fietkau 	int err;
335f0efa862SFelix Fietkau 
336f0efa862SFelix Fietkau 	if (prev_state == MT76_TM_STATE_TX_FRAMES)
337c918c74dSShayne Chen 		mt76_testmode_tx_stop(phy);
338f0efa862SFelix Fietkau 
339f0efa862SFelix Fietkau 	if (state == MT76_TM_STATE_TX_FRAMES) {
340c918c74dSShayne Chen 		err = mt76_testmode_tx_init(phy);
341f0efa862SFelix Fietkau 		if (err)
342f0efa862SFelix Fietkau 			return err;
343f0efa862SFelix Fietkau 	}
344f0efa862SFelix Fietkau 
345c918c74dSShayne Chen 	err = dev->test_ops->set_state(phy, state);
346f0efa862SFelix Fietkau 	if (err) {
347f0efa862SFelix Fietkau 		if (state == MT76_TM_STATE_TX_FRAMES)
348c918c74dSShayne Chen 			mt76_testmode_tx_stop(phy);
349f0efa862SFelix Fietkau 
350f0efa862SFelix Fietkau 		return err;
351f0efa862SFelix Fietkau 	}
352f0efa862SFelix Fietkau 
353f0efa862SFelix Fietkau 	if (state == MT76_TM_STATE_TX_FRAMES)
354c918c74dSShayne Chen 		mt76_testmode_tx_start(phy);
355f0efa862SFelix Fietkau 	else if (state == MT76_TM_STATE_RX_FRAMES) {
356c918c74dSShayne Chen 		memset(&phy->test.rx_stats, 0, sizeof(phy->test.rx_stats));
357f0efa862SFelix Fietkau 	}
358f0efa862SFelix Fietkau 
359c918c74dSShayne Chen 	phy->test.state = state;
360f0efa862SFelix Fietkau 
361f0efa862SFelix Fietkau 	return 0;
362f0efa862SFelix Fietkau }
363f0efa862SFelix Fietkau 
364c918c74dSShayne Chen int mt76_testmode_set_state(struct mt76_phy *phy, enum mt76_testmode_state state)
365f0efa862SFelix Fietkau {
366c918c74dSShayne Chen 	struct mt76_testmode_data *td = &phy->test;
367c918c74dSShayne Chen 	struct ieee80211_hw *hw = phy->hw;
368f0efa862SFelix Fietkau 
369f0efa862SFelix Fietkau 	if (state == td->state && state == MT76_TM_STATE_OFF)
370f0efa862SFelix Fietkau 		return 0;
371f0efa862SFelix Fietkau 
372f0efa862SFelix Fietkau 	if (state > MT76_TM_STATE_OFF &&
373c918c74dSShayne Chen 	    (!test_bit(MT76_STATE_RUNNING, &phy->state) ||
374f0efa862SFelix Fietkau 	     !(hw->conf.flags & IEEE80211_CONF_MONITOR)))
375f0efa862SFelix Fietkau 		return -ENOTCONN;
376f0efa862SFelix Fietkau 
377f0efa862SFelix Fietkau 	if (state != MT76_TM_STATE_IDLE &&
378f0efa862SFelix Fietkau 	    td->state != MT76_TM_STATE_IDLE) {
379f0efa862SFelix Fietkau 		int ret;
380f0efa862SFelix Fietkau 
381c918c74dSShayne Chen 		ret = __mt76_testmode_set_state(phy, MT76_TM_STATE_IDLE);
382f0efa862SFelix Fietkau 		if (ret)
383f0efa862SFelix Fietkau 			return ret;
384f0efa862SFelix Fietkau 	}
385f0efa862SFelix Fietkau 
386c918c74dSShayne Chen 	return __mt76_testmode_set_state(phy, state);
387f0efa862SFelix Fietkau 
388f0efa862SFelix Fietkau }
389f0efa862SFelix Fietkau EXPORT_SYMBOL(mt76_testmode_set_state);
390f0efa862SFelix Fietkau 
391f0efa862SFelix Fietkau static int
392f0efa862SFelix Fietkau mt76_tm_get_u8(struct nlattr *attr, u8 *dest, u8 min, u8 max)
393f0efa862SFelix Fietkau {
394f0efa862SFelix Fietkau 	u8 val;
395f0efa862SFelix Fietkau 
396f0efa862SFelix Fietkau 	if (!attr)
397f0efa862SFelix Fietkau 		return 0;
398f0efa862SFelix Fietkau 
399f0efa862SFelix Fietkau 	val = nla_get_u8(attr);
400f0efa862SFelix Fietkau 	if (val < min || val > max)
401f0efa862SFelix Fietkau 		return -EINVAL;
402f0efa862SFelix Fietkau 
403f0efa862SFelix Fietkau 	*dest = val;
404f0efa862SFelix Fietkau 	return 0;
405f0efa862SFelix Fietkau }
406f0efa862SFelix Fietkau 
407f0efa862SFelix Fietkau int mt76_testmode_cmd(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
408f0efa862SFelix Fietkau 		      void *data, int len)
409f0efa862SFelix Fietkau {
410f0efa862SFelix Fietkau 	struct mt76_phy *phy = hw->priv;
411f0efa862SFelix Fietkau 	struct mt76_dev *dev = phy->dev;
412c918c74dSShayne Chen 	struct mt76_testmode_data *td = &phy->test;
413f0efa862SFelix Fietkau 	struct nlattr *tb[NUM_MT76_TM_ATTRS];
414f0efa862SFelix Fietkau 	u32 state;
415f0efa862SFelix Fietkau 	int err;
416f0efa862SFelix Fietkau 	int i;
417f0efa862SFelix Fietkau 
418f0efa862SFelix Fietkau 	if (!dev->test_ops)
419f0efa862SFelix Fietkau 		return -EOPNOTSUPP;
420f0efa862SFelix Fietkau 
421f0efa862SFelix Fietkau 	err = nla_parse_deprecated(tb, MT76_TM_ATTR_MAX, data, len,
422f0efa862SFelix Fietkau 				   mt76_tm_policy, NULL);
423f0efa862SFelix Fietkau 	if (err)
424f0efa862SFelix Fietkau 		return err;
425f0efa862SFelix Fietkau 
426f0efa862SFelix Fietkau 	err = -EINVAL;
427f0efa862SFelix Fietkau 
428f0efa862SFelix Fietkau 	mutex_lock(&dev->mutex);
429f0efa862SFelix Fietkau 
430f0efa862SFelix Fietkau 	if (tb[MT76_TM_ATTR_RESET]) {
431c918c74dSShayne Chen 		mt76_testmode_set_state(phy, MT76_TM_STATE_OFF);
432f0efa862SFelix Fietkau 		memset(td, 0, sizeof(*td));
433f0efa862SFelix Fietkau 	}
434f0efa862SFelix Fietkau 
435c918c74dSShayne Chen 	mt76_testmode_init_defaults(phy);
436f0efa862SFelix Fietkau 
437f0efa862SFelix Fietkau 	if (tb[MT76_TM_ATTR_TX_COUNT])
438f0efa862SFelix Fietkau 		td->tx_count = nla_get_u32(tb[MT76_TM_ATTR_TX_COUNT]);
439f0efa862SFelix Fietkau 
440f0efa862SFelix Fietkau 	if (tb[MT76_TM_ATTR_TX_RATE_IDX])
441f0efa862SFelix Fietkau 		td->tx_rate_idx = nla_get_u8(tb[MT76_TM_ATTR_TX_RATE_IDX]);
442f0efa862SFelix Fietkau 
443f0efa862SFelix Fietkau 	if (mt76_tm_get_u8(tb[MT76_TM_ATTR_TX_RATE_MODE], &td->tx_rate_mode,
444f0efa862SFelix Fietkau 			   0, MT76_TM_TX_MODE_MAX) ||
445f0efa862SFelix Fietkau 	    mt76_tm_get_u8(tb[MT76_TM_ATTR_TX_RATE_NSS], &td->tx_rate_nss,
446f0efa862SFelix Fietkau 			   1, hweight8(phy->antenna_mask)) ||
4471a38c2f5SShayne Chen 	    mt76_tm_get_u8(tb[MT76_TM_ATTR_TX_RATE_SGI], &td->tx_rate_sgi, 0, 2) ||
448f0efa862SFelix Fietkau 	    mt76_tm_get_u8(tb[MT76_TM_ATTR_TX_RATE_LDPC], &td->tx_rate_ldpc, 0, 1) ||
4497f54c742SShayne Chen 	    mt76_tm_get_u8(tb[MT76_TM_ATTR_TX_RATE_STBC], &td->tx_rate_stbc, 0, 1) ||
4501a38c2f5SShayne Chen 	    mt76_tm_get_u8(tb[MT76_TM_ATTR_TX_LTF], &td->tx_ltf, 0, 2) ||
45199ad32a4SBo Jiao 	    mt76_tm_get_u8(tb[MT76_TM_ATTR_TX_ANTENNA],
45299ad32a4SBo Jiao 			   &td->tx_antenna_mask, 0, 0xff) ||
453fdc9c18eSShayne Chen 	    mt76_tm_get_u8(tb[MT76_TM_ATTR_TX_SPE_IDX], &td->tx_spe_idx, 0, 27) ||
454b8cbdb97SShayne Chen 	    mt76_tm_get_u8(tb[MT76_TM_ATTR_TX_DUTY_CYCLE],
455b8cbdb97SShayne Chen 			   &td->tx_duty_cycle, 0, 99) ||
456f0efa862SFelix Fietkau 	    mt76_tm_get_u8(tb[MT76_TM_ATTR_TX_POWER_CONTROL],
457f0efa862SFelix Fietkau 			   &td->tx_power_control, 0, 1))
458f0efa862SFelix Fietkau 		goto out;
459f0efa862SFelix Fietkau 
4602601dda8SShayne Chen 	if (tb[MT76_TM_ATTR_TX_LENGTH]) {
4612601dda8SShayne Chen 		u32 val = nla_get_u32(tb[MT76_TM_ATTR_TX_LENGTH]);
4622601dda8SShayne Chen 
4632601dda8SShayne Chen 		if (val > mt76_testmode_max_mpdu_len(phy, td->tx_rate_mode) ||
4642601dda8SShayne Chen 		    val < sizeof(struct ieee80211_hdr))
4652601dda8SShayne Chen 			goto out;
4662601dda8SShayne Chen 
4672601dda8SShayne Chen 		td->tx_mpdu_len = val;
4682601dda8SShayne Chen 	}
4692601dda8SShayne Chen 
470b8cbdb97SShayne Chen 	if (tb[MT76_TM_ATTR_TX_IPG])
471b8cbdb97SShayne Chen 		td->tx_ipg = nla_get_u32(tb[MT76_TM_ATTR_TX_IPG]);
472b8cbdb97SShayne Chen 
473b8cbdb97SShayne Chen 	if (tb[MT76_TM_ATTR_TX_TIME])
474b8cbdb97SShayne Chen 		td->tx_time = nla_get_u32(tb[MT76_TM_ATTR_TX_TIME]);
475b8cbdb97SShayne Chen 
476f0efa862SFelix Fietkau 	if (tb[MT76_TM_ATTR_FREQ_OFFSET])
477f0efa862SFelix Fietkau 		td->freq_offset = nla_get_u32(tb[MT76_TM_ATTR_FREQ_OFFSET]);
478f0efa862SFelix Fietkau 
479f0efa862SFelix Fietkau 	if (tb[MT76_TM_ATTR_STATE]) {
480f0efa862SFelix Fietkau 		state = nla_get_u32(tb[MT76_TM_ATTR_STATE]);
481f0efa862SFelix Fietkau 		if (state > MT76_TM_STATE_MAX)
482f0efa862SFelix Fietkau 			goto out;
483f0efa862SFelix Fietkau 	} else {
484f0efa862SFelix Fietkau 		state = td->state;
485f0efa862SFelix Fietkau 	}
486f0efa862SFelix Fietkau 
487f0efa862SFelix Fietkau 	if (tb[MT76_TM_ATTR_TX_POWER]) {
488f0efa862SFelix Fietkau 		struct nlattr *cur;
489f0efa862SFelix Fietkau 		int idx = 0;
490f0efa862SFelix Fietkau 		int rem;
491f0efa862SFelix Fietkau 
492f0efa862SFelix Fietkau 		nla_for_each_nested(cur, tb[MT76_TM_ATTR_TX_POWER], rem) {
493f0efa862SFelix Fietkau 			if (nla_len(cur) != 1 ||
494f0efa862SFelix Fietkau 			    idx >= ARRAY_SIZE(td->tx_power))
495f0efa862SFelix Fietkau 				goto out;
496f0efa862SFelix Fietkau 
497f0efa862SFelix Fietkau 			td->tx_power[idx++] = nla_get_u8(cur);
498f0efa862SFelix Fietkau 		}
499f0efa862SFelix Fietkau 	}
500f0efa862SFelix Fietkau 
501c40b42c2SShayne Chen 	if (tb[MT76_TM_ATTR_MAC_ADDRS]) {
502c40b42c2SShayne Chen 		struct nlattr *cur;
503c40b42c2SShayne Chen 		int idx = 0;
504c40b42c2SShayne Chen 		int rem;
505c40b42c2SShayne Chen 
506c40b42c2SShayne Chen 		nla_for_each_nested(cur, tb[MT76_TM_ATTR_MAC_ADDRS], rem) {
507c40b42c2SShayne Chen 			if (nla_len(cur) != ETH_ALEN || idx >= 3)
508c40b42c2SShayne Chen 				goto out;
509c40b42c2SShayne Chen 
510c40b42c2SShayne Chen 			memcpy(td->addr[idx], nla_data(cur), ETH_ALEN);
511c40b42c2SShayne Chen 			idx++;
512c40b42c2SShayne Chen 		}
513c40b42c2SShayne Chen 	}
514c40b42c2SShayne Chen 
515f0efa862SFelix Fietkau 	if (dev->test_ops->set_params) {
516c918c74dSShayne Chen 		err = dev->test_ops->set_params(phy, tb, state);
517f0efa862SFelix Fietkau 		if (err)
518f0efa862SFelix Fietkau 			goto out;
519f0efa862SFelix Fietkau 	}
520f0efa862SFelix Fietkau 
521f0efa862SFelix Fietkau 	for (i = MT76_TM_ATTR_STATE; i < ARRAY_SIZE(tb); i++)
522f0efa862SFelix Fietkau 		if (tb[i])
523f0efa862SFelix Fietkau 			mt76_testmode_param_set(td, i);
524f0efa862SFelix Fietkau 
525f0efa862SFelix Fietkau 	err = 0;
526f0efa862SFelix Fietkau 	if (tb[MT76_TM_ATTR_STATE])
527c918c74dSShayne Chen 		err = mt76_testmode_set_state(phy, state);
528f0efa862SFelix Fietkau 
529f0efa862SFelix Fietkau out:
530f0efa862SFelix Fietkau 	mutex_unlock(&dev->mutex);
531f0efa862SFelix Fietkau 
532f0efa862SFelix Fietkau 	return err;
533f0efa862SFelix Fietkau }
534f0efa862SFelix Fietkau EXPORT_SYMBOL(mt76_testmode_cmd);
535f0efa862SFelix Fietkau 
536f0efa862SFelix Fietkau static int
537c918c74dSShayne Chen mt76_testmode_dump_stats(struct mt76_phy *phy, struct sk_buff *msg)
538f0efa862SFelix Fietkau {
539c918c74dSShayne Chen 	struct mt76_testmode_data *td = &phy->test;
540c918c74dSShayne Chen 	struct mt76_dev *dev = phy->dev;
541f0efa862SFelix Fietkau 	u64 rx_packets = 0;
542f0efa862SFelix Fietkau 	u64 rx_fcs_error = 0;
543f0efa862SFelix Fietkau 	int i;
544f0efa862SFelix Fietkau 
545a0d65f62SShayne Chen 	if (dev->test_ops->dump_stats) {
546a0d65f62SShayne Chen 		int ret;
547a0d65f62SShayne Chen 
548a0d65f62SShayne Chen 		ret = dev->test_ops->dump_stats(phy, msg);
549a0d65f62SShayne Chen 		if (ret)
550a0d65f62SShayne Chen 			return ret;
551a0d65f62SShayne Chen 	}
552a0d65f62SShayne Chen 
553f0efa862SFelix Fietkau 	for (i = 0; i < ARRAY_SIZE(td->rx_stats.packets); i++) {
554f0efa862SFelix Fietkau 		rx_packets += td->rx_stats.packets[i];
555f0efa862SFelix Fietkau 		rx_fcs_error += td->rx_stats.fcs_error[i];
556f0efa862SFelix Fietkau 	}
557f0efa862SFelix Fietkau 
558f0efa862SFelix Fietkau 	if (nla_put_u32(msg, MT76_TM_STATS_ATTR_TX_PENDING, td->tx_pending) ||
559f0efa862SFelix Fietkau 	    nla_put_u32(msg, MT76_TM_STATS_ATTR_TX_QUEUED, td->tx_queued) ||
560f0efa862SFelix Fietkau 	    nla_put_u32(msg, MT76_TM_STATS_ATTR_TX_DONE, td->tx_done) ||
561f0efa862SFelix Fietkau 	    nla_put_u64_64bit(msg, MT76_TM_STATS_ATTR_RX_PACKETS, rx_packets,
562f0efa862SFelix Fietkau 			      MT76_TM_STATS_ATTR_PAD) ||
563f0efa862SFelix Fietkau 	    nla_put_u64_64bit(msg, MT76_TM_STATS_ATTR_RX_FCS_ERROR, rx_fcs_error,
564f0efa862SFelix Fietkau 			      MT76_TM_STATS_ATTR_PAD))
565f0efa862SFelix Fietkau 		return -EMSGSIZE;
566f0efa862SFelix Fietkau 
567f0efa862SFelix Fietkau 	return 0;
568f0efa862SFelix Fietkau }
569f0efa862SFelix Fietkau 
570f0efa862SFelix Fietkau int mt76_testmode_dump(struct ieee80211_hw *hw, struct sk_buff *msg,
571f0efa862SFelix Fietkau 		       struct netlink_callback *cb, void *data, int len)
572f0efa862SFelix Fietkau {
573f0efa862SFelix Fietkau 	struct mt76_phy *phy = hw->priv;
574f0efa862SFelix Fietkau 	struct mt76_dev *dev = phy->dev;
575c918c74dSShayne Chen 	struct mt76_testmode_data *td = &phy->test;
576f0efa862SFelix Fietkau 	struct nlattr *tb[NUM_MT76_TM_ATTRS] = {};
577f0efa862SFelix Fietkau 	int err = 0;
578f0efa862SFelix Fietkau 	void *a;
579f0efa862SFelix Fietkau 	int i;
580f0efa862SFelix Fietkau 
581f0efa862SFelix Fietkau 	if (!dev->test_ops)
582f0efa862SFelix Fietkau 		return -EOPNOTSUPP;
583f0efa862SFelix Fietkau 
584f0efa862SFelix Fietkau 	if (cb->args[2]++ > 0)
585f0efa862SFelix Fietkau 		return -ENOENT;
586f0efa862SFelix Fietkau 
587f0efa862SFelix Fietkau 	if (data) {
588f0efa862SFelix Fietkau 		err = nla_parse_deprecated(tb, MT76_TM_ATTR_MAX, data, len,
589f0efa862SFelix Fietkau 					   mt76_tm_policy, NULL);
590f0efa862SFelix Fietkau 		if (err)
591f0efa862SFelix Fietkau 			return err;
592f0efa862SFelix Fietkau 	}
593f0efa862SFelix Fietkau 
594f0efa862SFelix Fietkau 	mutex_lock(&dev->mutex);
595f0efa862SFelix Fietkau 
596f0efa862SFelix Fietkau 	if (tb[MT76_TM_ATTR_STATS]) {
597ce8463a7SLorenzo Bianconi 		err = -EINVAL;
598ce8463a7SLorenzo Bianconi 
599f0efa862SFelix Fietkau 		a = nla_nest_start(msg, MT76_TM_ATTR_STATS);
600ce8463a7SLorenzo Bianconi 		if (a) {
601c918c74dSShayne Chen 			err = mt76_testmode_dump_stats(phy, msg);
602f0efa862SFelix Fietkau 			nla_nest_end(msg, a);
603ce8463a7SLorenzo Bianconi 		}
604f0efa862SFelix Fietkau 
605f0efa862SFelix Fietkau 		goto out;
606f0efa862SFelix Fietkau 	}
607f0efa862SFelix Fietkau 
608c918c74dSShayne Chen 	mt76_testmode_init_defaults(phy);
609f0efa862SFelix Fietkau 
610f0efa862SFelix Fietkau 	err = -EMSGSIZE;
611f0efa862SFelix Fietkau 	if (nla_put_u32(msg, MT76_TM_ATTR_STATE, td->state))
612f0efa862SFelix Fietkau 		goto out;
613f0efa862SFelix Fietkau 
614e7a6a044SShayne Chen 	if (dev->test_mtd.name &&
615e7a6a044SShayne Chen 	    (nla_put_string(msg, MT76_TM_ATTR_MTD_PART, dev->test_mtd.name) ||
616e7a6a044SShayne Chen 	     nla_put_u32(msg, MT76_TM_ATTR_MTD_OFFSET, dev->test_mtd.offset)))
617f0efa862SFelix Fietkau 		goto out;
618f0efa862SFelix Fietkau 
619f0efa862SFelix Fietkau 	if (nla_put_u32(msg, MT76_TM_ATTR_TX_COUNT, td->tx_count) ||
6202601dda8SShayne Chen 	    nla_put_u32(msg, MT76_TM_ATTR_TX_LENGTH, td->tx_mpdu_len) ||
621f0efa862SFelix Fietkau 	    nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_MODE, td->tx_rate_mode) ||
622f0efa862SFelix Fietkau 	    nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_NSS, td->tx_rate_nss) ||
623f0efa862SFelix Fietkau 	    nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_IDX, td->tx_rate_idx) ||
624f0efa862SFelix Fietkau 	    nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_SGI, td->tx_rate_sgi) ||
625f0efa862SFelix Fietkau 	    nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_LDPC, td->tx_rate_ldpc) ||
6267f54c742SShayne Chen 	    nla_put_u8(msg, MT76_TM_ATTR_TX_RATE_STBC, td->tx_rate_stbc) ||
6271a38c2f5SShayne Chen 	    (mt76_testmode_param_present(td, MT76_TM_ATTR_TX_LTF) &&
6281a38c2f5SShayne Chen 	     nla_put_u8(msg, MT76_TM_ATTR_TX_LTF, td->tx_ltf)) ||
629f0efa862SFelix Fietkau 	    (mt76_testmode_param_present(td, MT76_TM_ATTR_TX_ANTENNA) &&
630f0efa862SFelix Fietkau 	     nla_put_u8(msg, MT76_TM_ATTR_TX_ANTENNA, td->tx_antenna_mask)) ||
631fdc9c18eSShayne Chen 	    (mt76_testmode_param_present(td, MT76_TM_ATTR_TX_SPE_IDX) &&
632fdc9c18eSShayne Chen 	     nla_put_u8(msg, MT76_TM_ATTR_TX_SPE_IDX, td->tx_spe_idx)) ||
633b8cbdb97SShayne Chen 	    (mt76_testmode_param_present(td, MT76_TM_ATTR_TX_DUTY_CYCLE) &&
634b8cbdb97SShayne Chen 	     nla_put_u8(msg, MT76_TM_ATTR_TX_DUTY_CYCLE, td->tx_duty_cycle)) ||
635b8cbdb97SShayne Chen 	    (mt76_testmode_param_present(td, MT76_TM_ATTR_TX_IPG) &&
636b8cbdb97SShayne Chen 	     nla_put_u32(msg, MT76_TM_ATTR_TX_IPG, td->tx_ipg)) ||
637b8cbdb97SShayne Chen 	    (mt76_testmode_param_present(td, MT76_TM_ATTR_TX_TIME) &&
638b8cbdb97SShayne Chen 	     nla_put_u32(msg, MT76_TM_ATTR_TX_TIME, td->tx_time)) ||
639f0efa862SFelix Fietkau 	    (mt76_testmode_param_present(td, MT76_TM_ATTR_TX_POWER_CONTROL) &&
640f0efa862SFelix Fietkau 	     nla_put_u8(msg, MT76_TM_ATTR_TX_POWER_CONTROL, td->tx_power_control)) ||
641f0efa862SFelix Fietkau 	    (mt76_testmode_param_present(td, MT76_TM_ATTR_FREQ_OFFSET) &&
642f0efa862SFelix Fietkau 	     nla_put_u8(msg, MT76_TM_ATTR_FREQ_OFFSET, td->freq_offset)))
643f0efa862SFelix Fietkau 		goto out;
644f0efa862SFelix Fietkau 
645f0efa862SFelix Fietkau 	if (mt76_testmode_param_present(td, MT76_TM_ATTR_TX_POWER)) {
646f0efa862SFelix Fietkau 		a = nla_nest_start(msg, MT76_TM_ATTR_TX_POWER);
647f0efa862SFelix Fietkau 		if (!a)
648f0efa862SFelix Fietkau 			goto out;
649f0efa862SFelix Fietkau 
650f0efa862SFelix Fietkau 		for (i = 0; i < ARRAY_SIZE(td->tx_power); i++)
651f0efa862SFelix Fietkau 			if (nla_put_u8(msg, i, td->tx_power[i]))
652f0efa862SFelix Fietkau 				goto out;
653f0efa862SFelix Fietkau 
654f0efa862SFelix Fietkau 		nla_nest_end(msg, a);
655f0efa862SFelix Fietkau 	}
656f0efa862SFelix Fietkau 
657c40b42c2SShayne Chen 	if (mt76_testmode_param_present(td, MT76_TM_ATTR_MAC_ADDRS)) {
658c40b42c2SShayne Chen 		a = nla_nest_start(msg, MT76_TM_ATTR_MAC_ADDRS);
659c40b42c2SShayne Chen 		if (!a)
660c40b42c2SShayne Chen 			goto out;
661c40b42c2SShayne Chen 
662c40b42c2SShayne Chen 		for (i = 0; i < 3; i++)
663c40b42c2SShayne Chen 			if (nla_put(msg, i, ETH_ALEN, td->addr[i]))
664c40b42c2SShayne Chen 				goto out;
665c40b42c2SShayne Chen 
666c40b42c2SShayne Chen 		nla_nest_end(msg, a);
667c40b42c2SShayne Chen 	}
668c40b42c2SShayne Chen 
669f0efa862SFelix Fietkau 	err = 0;
670f0efa862SFelix Fietkau 
671f0efa862SFelix Fietkau out:
672f0efa862SFelix Fietkau 	mutex_unlock(&dev->mutex);
673f0efa862SFelix Fietkau 
674f0efa862SFelix Fietkau 	return err;
675f0efa862SFelix Fietkau }
676f0efa862SFelix Fietkau EXPORT_SYMBOL(mt76_testmode_dump);
677