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 },
11*74f12d51SLin 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
mt76_testmode_tx_pending(struct mt76_phy * phy)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
56d08295f5SFelix Fietkau ret = dev->queue_ops->tx_queue_skb(dev, 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
mt76_testmode_max_mpdu_len(struct mt76_phy * phy,u8 tx_rate_mode)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
mt76_testmode_free_skb(struct mt76_phy * phy)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
mt76_testmode_alloc_skb(struct mt76_phy * phy,u32 len)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
mt76_testmode_tx_init(struct mt76_phy * phy)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
mt76_testmode_tx_start(struct mt76_phy * phy)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
mt76_testmode_tx_stop(struct mt76_phy * phy)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
mt76_testmode_param_set(struct mt76_testmode_data * td,u16 idx)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
mt76_testmode_param_present(struct mt76_testmode_data * td,u16 idx)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
mt76_testmode_init_defaults(struct mt76_phy * phy)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
__mt76_testmode_set_state(struct mt76_phy * phy,enum mt76_testmode_state state)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
mt76_testmode_set_state(struct mt76_phy * phy,enum mt76_testmode_state state)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
mt76_tm_get_u8(struct nlattr * attr,u8 * dest,u8 min,u8 max)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
mt76_testmode_cmd(struct ieee80211_hw * hw,struct ieee80211_vif * vif,void * data,int len)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
mt76_testmode_dump_stats(struct mt76_phy * phy,struct sk_buff * msg)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
mt76_testmode_dump(struct ieee80211_hw * hw,struct sk_buff * msg,struct netlink_callback * cb,void * data,int len)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