blob: f24fa9c69a8b4611561599c936d5d9d918e74fdb [file] [log] [blame]
developer175704f2021-06-22 17:33:53 +08001/* Copyright (C) 2021 Mediatek Inc. */
2#define _GNU_SOURCE
3
4#include "mt76-vendor.h"
5
6struct csi_data *csi;
7int csi_idx;
8
9static struct nla_policy csi_ctrl_policy[NUM_MTK_VENDOR_ATTRS_CSI_CTRL] = {
10 [MTK_VENDOR_ATTR_CSI_CTRL_CFG] = { .type = NLA_NESTED },
11 [MTK_VENDOR_ATTR_CSI_CTRL_CFG_MODE] = { .type = NLA_U8 },
12 [MTK_VENDOR_ATTR_CSI_CTRL_CFG_TYPE] = { .type = NLA_U8 },
13 [MTK_VENDOR_ATTR_CSI_CTRL_CFG_VAL1] = { .type = NLA_U8 },
14 [MTK_VENDOR_ATTR_CSI_CTRL_CFG_VAL2] = { .type = NLA_U8 },
15 [MTK_VENDOR_ATTR_CSI_CTRL_MAC_ADDR] = { .type = NLA_NESTED },
16 [MTK_VENDOR_ATTR_CSI_CTRL_INTERVAL] = { .type = NLA_U32 },
developer0fad8922023-11-28 20:15:39 +080017 [MTK_VENDOR_ATTR_CSI_CTRL_STA_INTERVAL] = { .type = NLA_U32 },
developer175704f2021-06-22 17:33:53 +080018 [MTK_VENDOR_ATTR_CSI_CTRL_DUMP_NUM] = { .type = NLA_U16 },
19 [MTK_VENDOR_ATTR_CSI_CTRL_DATA] = { .type = NLA_NESTED },
developerf5c22132024-05-23 20:10:30 +080020 [MTK_VENDOR_ATTR_CSI_CTRL_DUMP_MAC_FILTER] = { .type = NLA_NESTED },
developer175704f2021-06-22 17:33:53 +080021};
22
23static struct nla_policy csi_data_policy[NUM_MTK_VENDOR_ATTRS_CSI_DATA] = {
24 [MTK_VENDOR_ATTR_CSI_DATA_VER] = { .type = NLA_U8 },
25 [MTK_VENDOR_ATTR_CSI_DATA_TS] = { .type = NLA_U32 },
26 [MTK_VENDOR_ATTR_CSI_DATA_RSSI] = { .type = NLA_U8 },
27 [MTK_VENDOR_ATTR_CSI_DATA_SNR] = { .type = NLA_U8 },
28 [MTK_VENDOR_ATTR_CSI_DATA_BW] = { .type = NLA_U8 },
29 [MTK_VENDOR_ATTR_CSI_DATA_CH_IDX] = { .type = NLA_U8 },
30 [MTK_VENDOR_ATTR_CSI_DATA_TA] = { .type = NLA_NESTED },
developer0fad8922023-11-28 20:15:39 +080031 [MTK_VENDOR_ATTR_CSI_DATA_NUM] = { .type = NLA_U32 },
developer175704f2021-06-22 17:33:53 +080032 [MTK_VENDOR_ATTR_CSI_DATA_I] = { .type = NLA_NESTED },
33 [MTK_VENDOR_ATTR_CSI_DATA_Q] = { .type = NLA_NESTED },
34 [MTK_VENDOR_ATTR_CSI_DATA_INFO] = { .type = NLA_U32 },
35 [MTK_VENDOR_ATTR_CSI_DATA_TX_ANT] = { .type = NLA_U8 },
36 [MTK_VENDOR_ATTR_CSI_DATA_RX_ANT] = { .type = NLA_U8 },
37 [MTK_VENDOR_ATTR_CSI_DATA_MODE] = { .type = NLA_U8 },
developer0fad8922023-11-28 20:15:39 +080038 [MTK_VENDOR_ATTR_CSI_DATA_CHAIN_INFO] = { .type = NLA_U32 },
developer175704f2021-06-22 17:33:53 +080039};
40
developerf5c22132024-05-23 20:10:30 +080041static struct nla_policy csi_filter_policy[NUM_MTK_VENDOR_ATTRS_CSI_MAC_FILTER] = {
42 [MTK_VENDOR_ATTR_CSI_MAC_FILTER_INTERVAL] = { .type = NLA_U32 },
43};
44
developer175704f2021-06-22 17:33:53 +080045static int mt76_csi_dump_cb(struct nl_msg *msg, void *arg)
46{
47 struct nlattr *tb[NUM_MTK_VENDOR_ATTRS_CSI_CTRL];
48 struct nlattr *tb_data[NUM_MTK_VENDOR_ATTRS_CSI_DATA];
49 struct nlattr *attr;
50 struct nlattr *cur;
developer0497a7b2021-10-04 10:00:27 +080051 size_t idx;
52 int rem;
developer175704f2021-06-22 17:33:53 +080053 struct csi_data *c = &csi[csi_idx];
54
55 attr = unl_find_attr(&unl, msg, NL80211_ATTR_VENDOR_DATA);
56 if (!attr) {
57 fprintf(stderr, "Testdata attribute not found\n");
58 return NL_SKIP;
59 }
60
61 nla_parse_nested(tb, MTK_VENDOR_ATTR_CSI_CTRL_MAX,
62 attr, csi_ctrl_policy);
63
64 if (!tb[MTK_VENDOR_ATTR_CSI_CTRL_DATA])
65 return NL_SKIP;
66
67 nla_parse_nested(tb_data, MTK_VENDOR_ATTR_CSI_DATA_MAX,
68 tb[MTK_VENDOR_ATTR_CSI_CTRL_DATA], csi_data_policy);
69
70 if (!(tb_data[MTK_VENDOR_ATTR_CSI_DATA_VER] &&
71 tb_data[MTK_VENDOR_ATTR_CSI_DATA_TS] &&
72 tb_data[MTK_VENDOR_ATTR_CSI_DATA_RSSI] &&
73 tb_data[MTK_VENDOR_ATTR_CSI_DATA_SNR] &&
74 tb_data[MTK_VENDOR_ATTR_CSI_DATA_BW] &&
75 tb_data[MTK_VENDOR_ATTR_CSI_DATA_CH_IDX] &&
76 tb_data[MTK_VENDOR_ATTR_CSI_DATA_TA] &&
77 tb_data[MTK_VENDOR_ATTR_CSI_DATA_I] &&
78 tb_data[MTK_VENDOR_ATTR_CSI_DATA_Q] &&
79 tb_data[MTK_VENDOR_ATTR_CSI_DATA_INFO] &&
80 tb_data[MTK_VENDOR_ATTR_CSI_DATA_MODE] &&
developer0fad8922023-11-28 20:15:39 +080081 tb_data[MTK_VENDOR_ATTR_CSI_DATA_CHAIN_INFO])) {
developer175704f2021-06-22 17:33:53 +080082 fprintf(stderr, "Attributes error for CSI data\n");
83 return NL_SKIP;
84 }
85
86 c->rssi = nla_get_u8(tb_data[MTK_VENDOR_ATTR_CSI_DATA_RSSI]);
87 c->snr = nla_get_u8(tb_data[MTK_VENDOR_ATTR_CSI_DATA_SNR]);
88 c->data_bw = nla_get_u8(tb_data[MTK_VENDOR_ATTR_CSI_DATA_BW]);
89 c->pri_ch_idx = nla_get_u8(tb_data[MTK_VENDOR_ATTR_CSI_DATA_CH_IDX]);
90 c->rx_mode = nla_get_u8(tb_data[MTK_VENDOR_ATTR_CSI_DATA_MODE]);
91
92 c->tx_idx = nla_get_u16(tb_data[MTK_VENDOR_ATTR_CSI_DATA_TX_ANT]);
93 c->rx_idx = nla_get_u16(tb_data[MTK_VENDOR_ATTR_CSI_DATA_RX_ANT]);
94
developer0fad8922023-11-28 20:15:39 +080095 c->ext_info = nla_get_u32(tb_data[MTK_VENDOR_ATTR_CSI_DATA_INFO]);
96 c->chain_info = nla_get_u32(tb_data[MTK_VENDOR_ATTR_CSI_DATA_CHAIN_INFO]);
developer175704f2021-06-22 17:33:53 +080097
98 c->ts = nla_get_u32(tb_data[MTK_VENDOR_ATTR_CSI_DATA_TS]);
99
developer0fad8922023-11-28 20:15:39 +0800100 c->data_num = nla_get_u32(tb_data[MTK_VENDOR_ATTR_CSI_DATA_NUM]);
101
developer175704f2021-06-22 17:33:53 +0800102 idx = 0;
103 nla_for_each_nested(cur, tb_data[MTK_VENDOR_ATTR_CSI_DATA_TA], rem) {
developer0497a7b2021-10-04 10:00:27 +0800104 if (idx < ETH_ALEN)
105 c->ta[idx++] = nla_get_u8(cur);
developer175704f2021-06-22 17:33:53 +0800106 }
107
108 idx = 0;
109 nla_for_each_nested(cur, tb_data[MTK_VENDOR_ATTR_CSI_DATA_I], rem) {
developer0fad8922023-11-28 20:15:39 +0800110 if (idx < c->data_num)
developer0497a7b2021-10-04 10:00:27 +0800111 c->data_i[idx++] = nla_get_u16(cur);
developer175704f2021-06-22 17:33:53 +0800112 }
113
114 idx = 0;
115 nla_for_each_nested(cur, tb_data[MTK_VENDOR_ATTR_CSI_DATA_Q], rem) {
developer0fad8922023-11-28 20:15:39 +0800116 if (idx < c->data_num)
developer0497a7b2021-10-04 10:00:27 +0800117 c->data_q[idx++] = nla_get_u16(cur);
developer175704f2021-06-22 17:33:53 +0800118 }
119
120 csi_idx++;
121
122 return NL_SKIP;
123}
124
125static int mt76_csi_to_json(const char *name)
126{
127#define MAX_BUF_SIZE 6000
128 FILE *f;
developer0497a7b2021-10-04 10:00:27 +0800129 int i, ret = -ENOMEM;
developer175704f2021-06-22 17:33:53 +0800130
131 f = fopen(name, "a+");
132 if (!f) {
133 printf("open failure");
134 return 1;
135 }
136
developer0497a7b2021-10-04 10:00:27 +0800137 if (fwrite("[", 1, 1, f) != 1) {
138 perror("fwrite");
139 goto out;
140 }
developer175704f2021-06-22 17:33:53 +0800141
142 for (i = 0; i < csi_idx; i++) {
developer175704f2021-06-22 17:33:53 +0800143 struct csi_data *c = &csi[i];
developer0497a7b2021-10-04 10:00:27 +0800144 char *pos, *buf;
developer175704f2021-06-22 17:33:53 +0800145 int j;
146
developer0497a7b2021-10-04 10:00:27 +0800147 buf = malloc(MAX_BUF_SIZE);
148 if (!buf)
149 goto out;
developer175704f2021-06-22 17:33:53 +0800150
developer0497a7b2021-10-04 10:00:27 +0800151 pos = buf;
developer175704f2021-06-22 17:33:53 +0800152 pos += snprintf(pos, MAX_BUF_SIZE, "%c", '[');
153
developer6f8cffd2021-09-29 19:48:25 +0800154 pos += snprintf(pos, MAX_BUF_SIZE, "%d,", c->ts);
developer175704f2021-06-22 17:33:53 +0800155 pos += snprintf(pos, MAX_BUF_SIZE, "\"%02x%02x%02x%02x%02x%02x\",", c->ta[0], c->ta[1], c->ta[2], c->ta[3], c->ta[4], c->ta[5]);
156
157 pos += snprintf(pos, MAX_BUF_SIZE, "%d,", c->rssi);
158 pos += snprintf(pos, MAX_BUF_SIZE, "%u,", c->snr);
159 pos += snprintf(pos, MAX_BUF_SIZE, "%u,", c->data_bw);
160 pos += snprintf(pos, MAX_BUF_SIZE, "%u,", c->pri_ch_idx);
161 pos += snprintf(pos, MAX_BUF_SIZE, "%u,", c->rx_mode);
162 pos += snprintf(pos, MAX_BUF_SIZE, "%d,", c->tx_idx);
163 pos += snprintf(pos, MAX_BUF_SIZE, "%d,", c->rx_idx);
developer0fad8922023-11-28 20:15:39 +0800164 pos += snprintf(pos, MAX_BUF_SIZE, "%d,", c->chain_info);
165 pos += snprintf(pos, MAX_BUF_SIZE, "%d,", c->ext_info);
developer175704f2021-06-22 17:33:53 +0800166
167 pos += snprintf(pos, MAX_BUF_SIZE, "%c", '[');
developer0fad8922023-11-28 20:15:39 +0800168 for (j = 0; j < c->data_num; j++) {
developer175704f2021-06-22 17:33:53 +0800169 pos += snprintf(pos, MAX_BUF_SIZE, "%d", c->data_i[j]);
developer0fad8922023-11-28 20:15:39 +0800170 if (j != (c->data_num - 1))
developer175704f2021-06-22 17:33:53 +0800171 pos += snprintf(pos, MAX_BUF_SIZE, ",");
172 }
173 pos += snprintf(pos, MAX_BUF_SIZE, "%c,", ']');
174
175 pos += snprintf(pos, MAX_BUF_SIZE, "%c", '[');
developer0fad8922023-11-28 20:15:39 +0800176 for (j = 0; j < c->data_num; j++) {
developer175704f2021-06-22 17:33:53 +0800177 pos += snprintf(pos, MAX_BUF_SIZE, "%d", c->data_q[j]);
developer0fad8922023-11-28 20:15:39 +0800178 if (j != (c->data_num - 1))
developer175704f2021-06-22 17:33:53 +0800179 pos += snprintf(pos, MAX_BUF_SIZE, ",");
180 }
181 pos += snprintf(pos, MAX_BUF_SIZE, "%c", ']');
182
183 pos += snprintf(pos, MAX_BUF_SIZE, "%c", ']');
184 if (i != csi_idx - 1)
185 pos += snprintf(pos, MAX_BUF_SIZE, ",");
186
developer0497a7b2021-10-04 10:00:27 +0800187 if (fwrite(buf, 1, pos - buf, f) != (pos - buf)) {
188 perror("fwrite");
189 free(buf);
190 goto out;
191 }
192
developer175704f2021-06-22 17:33:53 +0800193 free(buf);
194 }
195
developer0497a7b2021-10-04 10:00:27 +0800196 if (fwrite("]", 1, 1, f) != 1) {
197 perror("fwrite");
198 goto out;
199 }
developer6f8cffd2021-09-29 19:48:25 +0800200
developer0497a7b2021-10-04 10:00:27 +0800201 ret = 0;
202out:
203 if (fclose(f))
204 perror("fclose");
205
206 return ret;
developer175704f2021-06-22 17:33:53 +0800207}
208
209int mt76_csi_dump(int idx, int argc, char **argv)
210{
developer6f8cffd2021-09-29 19:48:25 +0800211 int pkt_num, ret = 0, i;
developer175704f2021-06-22 17:33:53 +0800212 struct nl_msg *msg;
213 void *data;
214
215 if (argc < 2)
216 return 1;
developer0497a7b2021-10-04 10:00:27 +0800217
developer175704f2021-06-22 17:33:53 +0800218 pkt_num = strtol(argv[0], NULL, 10);
developer0497a7b2021-10-04 10:00:27 +0800219 if (pkt_num < 0 || pkt_num > 30000)
220 return -EINVAL;
developer175704f2021-06-22 17:33:53 +0800221
222#define CSI_DUMP_PER_NUM 3
223 csi_idx = 0;
224 csi = (struct csi_data *)calloc(pkt_num, sizeof(*csi));
225
226 for (i = 0; i < pkt_num / CSI_DUMP_PER_NUM; i++) {
227 if (unl_genl_init(&unl, "nl80211") < 0) {
228 fprintf(stderr, "Failed to connect to nl80211\n");
229 return 2;
230 }
231
232 msg = unl_genl_msg(&unl, NL80211_CMD_VENDOR, true);
233
234 if (nla_put_u32(msg, NL80211_ATTR_IFINDEX, idx) ||
235 nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, MTK_NL80211_VENDOR_ID) ||
236 nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD, MTK_NL80211_VENDOR_SUBCMD_CSI_CTRL))
237 return false;
238
239 data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA | NLA_F_NESTED);
developer0497a7b2021-10-04 10:00:27 +0800240 if (!data)
241 return -ENOMEM;
developer175704f2021-06-22 17:33:53 +0800242
243 if (nla_put_u16(msg, MTK_VENDOR_ATTR_CSI_CTRL_DUMP_NUM, CSI_DUMP_PER_NUM))
244 return false;
245
246 nla_nest_end(msg, data);
247
248 ret = unl_genl_request(&unl, msg, mt76_csi_dump_cb, NULL);
249 if (ret)
250 fprintf(stderr, "nl80211 call failed: %s\n", strerror(-ret));
251
252 unl_free(&unl);
developer0fad8922023-11-28 20:15:39 +0800253
developer175704f2021-06-22 17:33:53 +0800254 }
255
256 mt76_csi_to_json(argv[1]);
257 free(csi);
258
259 return ret;
260}
261
262static int mt76_csi_set_attr(struct nl_msg *msg, int argc, char **argv)
263{
264 int idx = MTK_VENDOR_ATTR_CSI_CTRL_CFG_MODE;
265 char *val, *s1, *s2, *cur;
266 void *data;
267
268 val = strchr(argv[0], '=');
developer6f8cffd2021-09-29 19:48:25 +0800269 if (!val)
270 return -EINVAL;
271
272 *(val++) = 0;
developer175704f2021-06-22 17:33:53 +0800273
274 if (!strncmp(argv[0], "ctrl", 4)) {
developer175704f2021-06-22 17:33:53 +0800275 data = nla_nest_start(msg, MTK_VENDOR_ATTR_CSI_CTRL_CFG | NLA_F_NESTED);
developer0497a7b2021-10-04 10:00:27 +0800276 if (!data)
277 return -ENOMEM;
developer175704f2021-06-22 17:33:53 +0800278
developer0497a7b2021-10-04 10:00:27 +0800279 s1 = s2 = strdup(val);
280
281 while ((cur = strsep(&s1, ",")) != NULL) {
282 u8 param = strtoul(cur, NULL, 0);
283
284 nla_put_u8(msg, idx++, param);
285 }
developer175704f2021-06-22 17:33:53 +0800286
287 nla_nest_end(msg, data);
288
289 free(s2);
290
developer0fad8922023-11-28 20:15:39 +0800291 if ((argc == 2 || argc == 3) &&
developer175704f2021-06-22 17:33:53 +0800292 !strncmp(argv[1], "mac_addr", strlen("mac_addr"))) {
293 u8 a[ETH_ALEN];
294 int matches, i;
295
296 val = strchr(argv[1], '=');
developer0497a7b2021-10-04 10:00:27 +0800297 if (!val)
298 return -EINVAL;
developer175704f2021-06-22 17:33:53 +0800299
developer0497a7b2021-10-04 10:00:27 +0800300 *(val++) = 0;
developer175704f2021-06-22 17:33:53 +0800301 matches = sscanf(val, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
302 a, a+1, a+2, a+3, a+4, a+5);
303
304 if (matches != ETH_ALEN)
305 return -EINVAL;
306
307 data = nla_nest_start(msg, MTK_VENDOR_ATTR_CSI_CTRL_MAC_ADDR | NLA_F_NESTED);
developer0497a7b2021-10-04 10:00:27 +0800308 if (!data)
309 return -ENOMEM;
310
developer175704f2021-06-22 17:33:53 +0800311 for (i = 0; i < ETH_ALEN; i++)
312 nla_put_u8(msg, i, a[i]);
313
314 nla_nest_end(msg, data);
315 }
developer0fad8922023-11-28 20:15:39 +0800316
317 if (argc == 3 &&
318 !strncmp(argv[2], "sta_interval", strlen("sta_interval"))) {
319 u32 sta_interval = 0;
320
321 val = strchr(argv[2], '=');
322
323 *(val++) = 0;
324
325 if (!val)
326 return -EINVAL;
327
328 sta_interval = strtoul(val, NULL, 0);
329
330 nla_put_u32(msg, MTK_VENDOR_ATTR_CSI_CTRL_STA_INTERVAL, sta_interval);
331
332 }
333
developer175704f2021-06-22 17:33:53 +0800334 } else if (!strncmp(argv[0], "interval", 8)) {
developer0497a7b2021-10-04 10:00:27 +0800335 u32 interval = strtoul(val, NULL, 0);
336
337 nla_put_u32(msg, MTK_VENDOR_ATTR_CSI_CTRL_INTERVAL, interval);
developer175704f2021-06-22 17:33:53 +0800338 }
339
340 return 0;
341}
342
developerf5c22132024-05-23 20:10:30 +0800343static int mt76_csi_set_cb(struct nl_msg *msg, void *arg)
344{
345 struct nlattr *tb[NUM_MTK_VENDOR_ATTRS_CSI_CTRL];
346 struct nlattr *cur, *tb_entry[NUM_MTK_VENDOR_ATTRS_CSI_MAC_FILTER];
347 int rem;
348 struct nlattr *attr;
349
350 attr = unl_find_attr(&unl, msg, NL80211_ATTR_VENDOR_DATA);
351 if (!attr) {
352 fprintf(stderr, "Testdata attribute not found\n");
353 return NL_SKIP;
354 }
355
356 nla_parse_nested(tb, MTK_VENDOR_ATTR_CSI_CTRL_MAX, attr, csi_ctrl_policy);
357
358 if (!tb[MTK_VENDOR_ATTR_CSI_CTRL_DUMP_MAC_FILTER])
359 return NL_SKIP;
360
361 nla_for_each_nested(cur, tb[MTK_VENDOR_ATTR_CSI_CTRL_DUMP_MAC_FILTER], rem) {
362 struct nlattr *tb[MTK_VENDOR_ATTR_CSI_MAC_FILTER_MAX + 1];
363 u8 entry_mac[ETH_ALEN] = {0};
364 u32 entry_interval = 0;
365
366 nla_parse_nested(tb_entry, MTK_VENDOR_ATTR_CSI_MAC_FILTER_MAX,
367 cur, csi_filter_policy);
368
369 if (!tb_entry[MTK_VENDOR_ATTR_CSI_MAC_FILTER_MAC] ||
370 !tb_entry[MTK_VENDOR_ATTR_CSI_MAC_FILTER_INTERVAL])
371 continue;
372
373 memcpy(entry_mac, nla_data(tb_entry[MTK_VENDOR_ATTR_CSI_MAC_FILTER_MAC]), ETH_ALEN);
374 entry_interval = nla_get_u32(tb_entry[MTK_VENDOR_ATTR_CSI_MAC_FILTER_INTERVAL]);
375
376 printf("mac: %02x:%02x:%02x:%02x:%02x:%02x, interval: %d\n",
377 entry_mac[0], entry_mac[1], entry_mac[2],
378 entry_mac[3], entry_mac[4], entry_mac[5],
379 entry_interval);
380 }
381
382 return NL_SKIP;
383}
384
developer175704f2021-06-22 17:33:53 +0800385int mt76_csi_set(int idx, int argc, char **argv)
386{
387 struct nl_msg *msg;
388 void *data;
389 int ret;
390
391 if (argc < 1)
392 return 1;
393
394 if (unl_genl_init(&unl, "nl80211") < 0) {
395 fprintf(stderr, "Failed to connect to nl80211\n");
396 return 2;
397 }
398
399 msg = unl_genl_msg(&unl, NL80211_CMD_VENDOR, false);
400
401 if (nla_put_u32(msg, NL80211_ATTR_IFINDEX, idx) ||
402 nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, MTK_NL80211_VENDOR_ID) ||
403 nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD, MTK_NL80211_VENDOR_SUBCMD_CSI_CTRL))
404 return false;
405
406 data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA | NLA_F_NESTED);
developer0497a7b2021-10-04 10:00:27 +0800407 if (!data)
408 return -ENOMEM;
developer175704f2021-06-22 17:33:53 +0800409
410 mt76_csi_set_attr(msg, argc, argv);
411
412 nla_nest_end(msg, data);
413
developerf5c22132024-05-23 20:10:30 +0800414 ret = unl_genl_request(&unl, msg, mt76_csi_set_cb, NULL);
developer175704f2021-06-22 17:33:53 +0800415 if (ret)
416 fprintf(stderr, "nl80211 call failed: %s\n", strerror(-ret));
417
418 unl_free(&unl);
419
420 return ret;
421}