blob: 78c3f56336923298248bd5176955f7a73a16f08f [file] [log] [blame]
Johannes Berg9bb7e0f2018-09-10 13:29:12 +02001/* SPDX-License-Identifier: GPL-2.0 */
2/*
3 * Copyright (C) 2018 Intel Corporation
4 */
5#ifndef __PMSR_H
6#define __PMSR_H
7#include <net/cfg80211.h>
8#include "core.h"
9#include "nl80211.h"
10#include "rdev-ops.h"
11
12static int pmsr_parse_ftm(struct cfg80211_registered_device *rdev,
13 struct nlattr *ftmreq,
14 struct cfg80211_pmsr_request_peer *out,
15 struct genl_info *info)
16{
17 const struct cfg80211_pmsr_capabilities *capa = rdev->wiphy.pmsr_capa;
18 struct nlattr *tb[NL80211_PMSR_FTM_REQ_ATTR_MAX + 1];
19 u32 preamble = NL80211_PREAMBLE_DMG; /* only optional in DMG */
20
21 /* validate existing data */
22 if (!(rdev->wiphy.pmsr_capa->ftm.bandwidths & BIT(out->chandef.width))) {
23 NL_SET_ERR_MSG(info->extack, "FTM: unsupported bandwidth");
24 return -EINVAL;
25 }
26
27 /* no validation needed - was already done via nested policy */
28 nla_parse_nested(tb, NL80211_PMSR_FTM_REQ_ATTR_MAX, ftmreq, NULL, NULL);
29
30 if (tb[NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE])
31 preamble = nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE]);
32
33 /* set up values - struct is 0-initialized */
34 out->ftm.requested = true;
35
36 switch (out->chandef.chan->band) {
37 case NL80211_BAND_60GHZ:
38 /* optional */
39 break;
40 default:
41 if (!tb[NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE]) {
42 NL_SET_ERR_MSG(info->extack,
43 "FTM: must specify preamble");
44 return -EINVAL;
45 }
46 }
47
48 if (!(capa->ftm.preambles & BIT(preamble))) {
49 NL_SET_ERR_MSG_ATTR(info->extack,
50 tb[NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE],
51 "FTM: invalid preamble");
52 return -EINVAL;
53 }
54
55 out->ftm.preamble = preamble;
56
57 out->ftm.burst_period = 0;
58 if (tb[NL80211_PMSR_FTM_REQ_ATTR_BURST_PERIOD])
59 out->ftm.burst_period =
60 nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_BURST_PERIOD]);
61
62 out->ftm.asap = !!tb[NL80211_PMSR_FTM_REQ_ATTR_ASAP];
63 if (out->ftm.asap && !capa->ftm.asap) {
64 NL_SET_ERR_MSG_ATTR(info->extack,
65 tb[NL80211_PMSR_FTM_REQ_ATTR_ASAP],
66 "FTM: ASAP mode not supported");
67 return -EINVAL;
68 }
69
70 if (!out->ftm.asap && !capa->ftm.non_asap) {
71 NL_SET_ERR_MSG(info->extack,
72 "FTM: non-ASAP mode not supported");
73 return -EINVAL;
74 }
75
76 out->ftm.num_bursts_exp = 0;
77 if (tb[NL80211_PMSR_FTM_REQ_ATTR_NUM_BURSTS_EXP])
78 out->ftm.num_bursts_exp =
79 nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_NUM_BURSTS_EXP]);
80
81 if (capa->ftm.max_bursts_exponent >= 0 &&
82 out->ftm.num_bursts_exp > capa->ftm.max_bursts_exponent) {
83 NL_SET_ERR_MSG_ATTR(info->extack,
84 tb[NL80211_PMSR_FTM_REQ_ATTR_NUM_BURSTS_EXP],
85 "FTM: max NUM_BURSTS_EXP must be set lower than the device limit");
86 return -EINVAL;
87 }
88
89 out->ftm.burst_duration = 15;
90 if (tb[NL80211_PMSR_FTM_REQ_ATTR_BURST_DURATION])
91 out->ftm.burst_duration =
92 nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_BURST_DURATION]);
93
94 out->ftm.ftms_per_burst = 0;
95 if (tb[NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST])
96 out->ftm.ftms_per_burst =
97 nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST]);
98
99 if (capa->ftm.max_ftms_per_burst &&
100 (out->ftm.ftms_per_burst > capa->ftm.max_ftms_per_burst ||
101 out->ftm.ftms_per_burst == 0)) {
102 NL_SET_ERR_MSG_ATTR(info->extack,
103 tb[NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST],
104 "FTM: FTMs per burst must be set lower than the device limit but non-zero");
105 return -EINVAL;
106 }
107
108 out->ftm.ftmr_retries = 3;
109 if (tb[NL80211_PMSR_FTM_REQ_ATTR_NUM_FTMR_RETRIES])
110 out->ftm.ftmr_retries =
111 nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_NUM_FTMR_RETRIES]);
112
113 out->ftm.request_lci = !!tb[NL80211_PMSR_FTM_REQ_ATTR_REQUEST_LCI];
114 if (out->ftm.request_lci && !capa->ftm.request_lci) {
115 NL_SET_ERR_MSG_ATTR(info->extack,
116 tb[NL80211_PMSR_FTM_REQ_ATTR_REQUEST_LCI],
117 "FTM: LCI request not supported");
118 }
119
120 out->ftm.request_civicloc =
121 !!tb[NL80211_PMSR_FTM_REQ_ATTR_REQUEST_CIVICLOC];
122 if (out->ftm.request_civicloc && !capa->ftm.request_civicloc) {
123 NL_SET_ERR_MSG_ATTR(info->extack,
124 tb[NL80211_PMSR_FTM_REQ_ATTR_REQUEST_CIVICLOC],
125 "FTM: civic location request not supported");
126 }
127
128 return 0;
129}
130
131static int pmsr_parse_peer(struct cfg80211_registered_device *rdev,
132 struct nlattr *peer,
133 struct cfg80211_pmsr_request_peer *out,
134 struct genl_info *info)
135{
136 struct nlattr *tb[NL80211_PMSR_PEER_ATTR_MAX + 1];
137 struct nlattr *req[NL80211_PMSR_REQ_ATTR_MAX + 1];
138 struct nlattr *treq;
139 int err, rem;
140
141 /* no validation needed - was already done via nested policy */
142 nla_parse_nested(tb, NL80211_PMSR_PEER_ATTR_MAX, peer, NULL, NULL);
143
144 if (!tb[NL80211_PMSR_PEER_ATTR_ADDR] ||
145 !tb[NL80211_PMSR_PEER_ATTR_CHAN] ||
146 !tb[NL80211_PMSR_PEER_ATTR_REQ]) {
147 NL_SET_ERR_MSG_ATTR(info->extack, peer,
148 "insufficient peer data");
149 return -EINVAL;
150 }
151
152 memcpy(out->addr, nla_data(tb[NL80211_PMSR_PEER_ATTR_ADDR]), ETH_ALEN);
153
154 /* reuse info->attrs */
155 memset(info->attrs, 0, sizeof(*info->attrs) * (NL80211_ATTR_MAX + 1));
156 /* need to validate here, we don't want to have validation recursion */
157 err = nla_parse_nested(info->attrs, NL80211_ATTR_MAX,
158 tb[NL80211_PMSR_PEER_ATTR_CHAN],
159 nl80211_policy, info->extack);
160 if (err)
161 return err;
162
163 err = nl80211_parse_chandef(rdev, info, &out->chandef);
164 if (err)
165 return err;
166
167 /* no validation needed - was already done via nested policy */
168 nla_parse_nested(req, NL80211_PMSR_REQ_ATTR_MAX,
169 tb[NL80211_PMSR_PEER_ATTR_REQ],
170 NULL, NULL);
171
172 if (!req[NL80211_PMSR_REQ_ATTR_DATA]) {
173 NL_SET_ERR_MSG_ATTR(info->extack,
174 tb[NL80211_PMSR_PEER_ATTR_REQ],
175 "missing request type/data");
176 return -EINVAL;
177 }
178
179 if (req[NL80211_PMSR_REQ_ATTR_GET_AP_TSF])
180 out->report_ap_tsf = true;
181
182 if (out->report_ap_tsf && !rdev->wiphy.pmsr_capa->report_ap_tsf) {
183 NL_SET_ERR_MSG_ATTR(info->extack,
184 req[NL80211_PMSR_REQ_ATTR_GET_AP_TSF],
185 "reporting AP TSF is not supported");
186 return -EINVAL;
187 }
188
189 nla_for_each_nested(treq, req[NL80211_PMSR_REQ_ATTR_DATA], rem) {
190 switch (nla_type(treq)) {
191 case NL80211_PMSR_TYPE_FTM:
192 err = pmsr_parse_ftm(rdev, treq, out, info);
193 break;
194 default:
195 NL_SET_ERR_MSG_ATTR(info->extack, treq,
196 "unsupported measurement type");
197 err = -EINVAL;
198 }
199 }
200
201 if (err)
202 return err;
203
204 return 0;
205}
206
207int nl80211_pmsr_start(struct sk_buff *skb, struct genl_info *info)
208{
209 struct nlattr *reqattr = info->attrs[NL80211_ATTR_PEER_MEASUREMENTS];
210 struct cfg80211_registered_device *rdev = info->user_ptr[0];
211 struct wireless_dev *wdev = info->user_ptr[1];
212 struct cfg80211_pmsr_request *req;
213 struct nlattr *peers, *peer;
214 int count, rem, err, idx;
215
216 if (!rdev->wiphy.pmsr_capa)
217 return -EOPNOTSUPP;
218
219 if (!reqattr)
220 return -EINVAL;
221
222 peers = nla_find(nla_data(reqattr), nla_len(reqattr),
223 NL80211_PMSR_ATTR_PEERS);
224 if (!peers)
225 return -EINVAL;
226
227 count = 0;
228 nla_for_each_nested(peer, peers, rem) {
229 count++;
230
231 if (count > rdev->wiphy.pmsr_capa->max_peers) {
232 NL_SET_ERR_MSG_ATTR(info->extack, peer,
233 "Too many peers used");
234 return -EINVAL;
235 }
236 }
237
238 req = kzalloc(struct_size(req, peers, count), GFP_KERNEL);
239 if (!req)
240 return -ENOMEM;
241
242 if (info->attrs[NL80211_ATTR_TIMEOUT])
243 req->timeout = nla_get_u32(info->attrs[NL80211_ATTR_TIMEOUT]);
244
245 if (info->attrs[NL80211_ATTR_MAC]) {
246 if (!rdev->wiphy.pmsr_capa->randomize_mac_addr) {
247 NL_SET_ERR_MSG_ATTR(info->extack,
248 info->attrs[NL80211_ATTR_MAC],
249 "device cannot randomize MAC address");
250 err = -EINVAL;
251 goto out_err;
252 }
253
254 err = nl80211_parse_random_mac(info->attrs, req->mac_addr,
255 req->mac_addr_mask);
256 if (err)
257 goto out_err;
258 } else {
Johannes Berg0acd9922019-02-06 07:59:41 +0200259 memcpy(req->mac_addr, wdev_address(wdev), ETH_ALEN);
Johannes Berg9bb7e0f2018-09-10 13:29:12 +0200260 memset(req->mac_addr_mask, 0xff, ETH_ALEN);
261 }
262
263 idx = 0;
264 nla_for_each_nested(peer, peers, rem) {
265 /* NB: this reuses info->attrs, but we no longer need it */
266 err = pmsr_parse_peer(rdev, peer, &req->peers[idx], info);
267 if (err)
268 goto out_err;
269 idx++;
270 }
271
272 req->n_peers = count;
273 req->cookie = cfg80211_assign_cookie(rdev);
274
275 err = rdev_start_pmsr(rdev, wdev, req);
276 if (err)
277 goto out_err;
278
279 list_add_tail(&req->list, &wdev->pmsr_list);
280
281 nl_set_extack_cookie_u64(info->extack, req->cookie);
282 return 0;
283out_err:
284 kfree(req);
285 return err;
286}
287
288void cfg80211_pmsr_complete(struct wireless_dev *wdev,
289 struct cfg80211_pmsr_request *req,
290 gfp_t gfp)
291{
292 struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
293 struct sk_buff *msg;
294 void *hdr;
295
296 trace_cfg80211_pmsr_complete(wdev->wiphy, wdev, req->cookie);
297
298 msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp);
299 if (!msg)
300 goto free_request;
301
302 hdr = nl80211hdr_put(msg, 0, 0, 0,
303 NL80211_CMD_PEER_MEASUREMENT_COMPLETE);
304 if (!hdr)
305 goto free_msg;
306
307 if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
308 nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev),
309 NL80211_ATTR_PAD))
310 goto free_msg;
311
312 if (nla_put_u64_64bit(msg, NL80211_ATTR_COOKIE, req->cookie,
313 NL80211_ATTR_PAD))
314 goto free_msg;
315
316 genlmsg_end(msg, hdr);
317 genlmsg_unicast(wiphy_net(wdev->wiphy), msg, req->nl_portid);
318 goto free_request;
319free_msg:
320 nlmsg_free(msg);
321free_request:
322 spin_lock_bh(&wdev->pmsr_lock);
323 list_del(&req->list);
324 spin_unlock_bh(&wdev->pmsr_lock);
325 kfree(req);
326}
327EXPORT_SYMBOL_GPL(cfg80211_pmsr_complete);
328
329static int nl80211_pmsr_send_ftm_res(struct sk_buff *msg,
330 struct cfg80211_pmsr_result *res)
331{
332 if (res->status == NL80211_PMSR_STATUS_FAILURE) {
333 if (nla_put_u32(msg, NL80211_PMSR_FTM_RESP_ATTR_FAIL_REASON,
334 res->ftm.failure_reason))
335 goto error;
336
337 if (res->ftm.failure_reason ==
338 NL80211_PMSR_FTM_FAILURE_PEER_BUSY &&
339 res->ftm.busy_retry_time &&
340 nla_put_u32(msg, NL80211_PMSR_FTM_RESP_ATTR_BUSY_RETRY_TIME,
341 res->ftm.busy_retry_time))
342 goto error;
343
344 return 0;
345 }
346
347#define PUT(tp, attr, val) \
348 do { \
349 if (nla_put_##tp(msg, \
350 NL80211_PMSR_FTM_RESP_ATTR_##attr, \
351 res->ftm.val)) \
352 goto error; \
353 } while (0)
354
355#define PUTOPT(tp, attr, val) \
356 do { \
357 if (res->ftm.val##_valid) \
358 PUT(tp, attr, val); \
359 } while (0)
360
361#define PUT_U64(attr, val) \
362 do { \
363 if (nla_put_u64_64bit(msg, \
364 NL80211_PMSR_FTM_RESP_ATTR_##attr,\
365 res->ftm.val, \
366 NL80211_PMSR_FTM_RESP_ATTR_PAD)) \
367 goto error; \
368 } while (0)
369
370#define PUTOPT_U64(attr, val) \
371 do { \
372 if (res->ftm.val##_valid) \
373 PUT_U64(attr, val); \
374 } while (0)
375
376 if (res->ftm.burst_index >= 0)
377 PUT(u32, BURST_INDEX, burst_index);
378 PUTOPT(u32, NUM_FTMR_ATTEMPTS, num_ftmr_attempts);
379 PUTOPT(u32, NUM_FTMR_SUCCESSES, num_ftmr_successes);
380 PUT(u8, NUM_BURSTS_EXP, num_bursts_exp);
381 PUT(u8, BURST_DURATION, burst_duration);
382 PUT(u8, FTMS_PER_BURST, ftms_per_burst);
383 PUTOPT(s32, RSSI_AVG, rssi_avg);
384 PUTOPT(s32, RSSI_SPREAD, rssi_spread);
385 if (res->ftm.tx_rate_valid &&
386 !nl80211_put_sta_rate(msg, &res->ftm.tx_rate,
387 NL80211_PMSR_FTM_RESP_ATTR_TX_RATE))
388 goto error;
389 if (res->ftm.rx_rate_valid &&
390 !nl80211_put_sta_rate(msg, &res->ftm.rx_rate,
391 NL80211_PMSR_FTM_RESP_ATTR_RX_RATE))
392 goto error;
393 PUTOPT_U64(RTT_AVG, rtt_avg);
394 PUTOPT_U64(RTT_VARIANCE, rtt_variance);
395 PUTOPT_U64(RTT_SPREAD, rtt_spread);
396 PUTOPT_U64(DIST_AVG, dist_avg);
397 PUTOPT_U64(DIST_VARIANCE, dist_variance);
398 PUTOPT_U64(DIST_SPREAD, dist_spread);
399 if (res->ftm.lci && res->ftm.lci_len &&
400 nla_put(msg, NL80211_PMSR_FTM_RESP_ATTR_LCI,
401 res->ftm.lci_len, res->ftm.lci))
402 goto error;
403 if (res->ftm.civicloc && res->ftm.civicloc_len &&
404 nla_put(msg, NL80211_PMSR_FTM_RESP_ATTR_CIVICLOC,
405 res->ftm.civicloc_len, res->ftm.civicloc))
406 goto error;
407#undef PUT
408#undef PUTOPT
409#undef PUT_U64
410#undef PUTOPT_U64
411
412 return 0;
413error:
414 return -ENOSPC;
415}
416
417static int nl80211_pmsr_send_result(struct sk_buff *msg,
418 struct cfg80211_pmsr_result *res)
419{
420 struct nlattr *pmsr, *peers, *peer, *resp, *data, *typedata;
421
422 pmsr = nla_nest_start(msg, NL80211_ATTR_PEER_MEASUREMENTS);
423 if (!pmsr)
424 goto error;
425
426 peers = nla_nest_start(msg, NL80211_PMSR_ATTR_PEERS);
427 if (!peers)
428 goto error;
429
430 peer = nla_nest_start(msg, 1);
431 if (!peer)
432 goto error;
433
434 if (nla_put(msg, NL80211_PMSR_PEER_ATTR_ADDR, ETH_ALEN, res->addr))
435 goto error;
436
437 resp = nla_nest_start(msg, NL80211_PMSR_PEER_ATTR_RESP);
438 if (!resp)
439 goto error;
440
441 if (nla_put_u32(msg, NL80211_PMSR_RESP_ATTR_STATUS, res->status) ||
442 nla_put_u64_64bit(msg, NL80211_PMSR_RESP_ATTR_HOST_TIME,
443 res->host_time, NL80211_PMSR_RESP_ATTR_PAD))
444 goto error;
445
446 if (res->ap_tsf_valid &&
447 nla_put_u64_64bit(msg, NL80211_PMSR_RESP_ATTR_AP_TSF,
448 res->host_time, NL80211_PMSR_RESP_ATTR_PAD))
449 goto error;
450
451 if (res->final && nla_put_flag(msg, NL80211_PMSR_RESP_ATTR_FINAL))
452 goto error;
453
454 data = nla_nest_start(msg, NL80211_PMSR_RESP_ATTR_DATA);
455 if (!data)
456 goto error;
457
458 typedata = nla_nest_start(msg, res->type);
459 if (!typedata)
460 goto error;
461
462 switch (res->type) {
463 case NL80211_PMSR_TYPE_FTM:
464 if (nl80211_pmsr_send_ftm_res(msg, res))
465 goto error;
466 break;
467 default:
468 WARN_ON(1);
469 }
470
471 nla_nest_end(msg, typedata);
472 nla_nest_end(msg, data);
473 nla_nest_end(msg, resp);
474 nla_nest_end(msg, peer);
475 nla_nest_end(msg, peers);
476 nla_nest_end(msg, pmsr);
477
478 return 0;
479error:
480 return -ENOSPC;
481}
482
483void cfg80211_pmsr_report(struct wireless_dev *wdev,
484 struct cfg80211_pmsr_request *req,
485 struct cfg80211_pmsr_result *result,
486 gfp_t gfp)
487{
488 struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
489 struct sk_buff *msg;
490 void *hdr;
491 int err;
492
493 trace_cfg80211_pmsr_report(wdev->wiphy, wdev, req->cookie,
494 result->addr);
495
496 /*
497 * Currently, only variable items are LCI and civic location,
498 * both of which are reasonably short so we don't need to
499 * worry about them here for the allocation.
500 */
501 msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp);
502 if (!msg)
503 return;
504
505 hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_PEER_MEASUREMENT_RESULT);
506 if (!hdr)
507 goto free;
508
509 if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
510 nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev),
511 NL80211_ATTR_PAD))
512 goto free;
513
514 if (nla_put_u64_64bit(msg, NL80211_ATTR_COOKIE, req->cookie,
515 NL80211_ATTR_PAD))
516 goto free;
517
518 err = nl80211_pmsr_send_result(msg, result);
519 if (err) {
520 pr_err_ratelimited("peer measurement result: message didn't fit!");
521 goto free;
522 }
523
524 genlmsg_end(msg, hdr);
525 genlmsg_unicast(wiphy_net(wdev->wiphy), msg, req->nl_portid);
526 return;
527free:
528 nlmsg_free(msg);
529}
530EXPORT_SYMBOL_GPL(cfg80211_pmsr_report);
531
Johannes Berg733504242019-02-06 08:03:10 +0200532static void cfg80211_pmsr_process_abort(struct wireless_dev *wdev)
Johannes Berg9bb7e0f2018-09-10 13:29:12 +0200533{
Johannes Berg9bb7e0f2018-09-10 13:29:12 +0200534 struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
535 struct cfg80211_pmsr_request *req, *tmp;
536 LIST_HEAD(free_list);
537
Johannes Berg733504242019-02-06 08:03:10 +0200538 lockdep_assert_held(&wdev->mtx);
539
Johannes Berg9bb7e0f2018-09-10 13:29:12 +0200540 spin_lock_bh(&wdev->pmsr_lock);
541 list_for_each_entry_safe(req, tmp, &wdev->pmsr_list, list) {
542 if (req->nl_portid)
543 continue;
544 list_move_tail(&req->list, &free_list);
545 }
546 spin_unlock_bh(&wdev->pmsr_lock);
547
548 list_for_each_entry_safe(req, tmp, &free_list, list) {
Johannes Berg9bb7e0f2018-09-10 13:29:12 +0200549 rdev_abort_pmsr(rdev, wdev, req);
Johannes Berg9bb7e0f2018-09-10 13:29:12 +0200550
551 kfree(req);
552 }
553}
554
Johannes Berg733504242019-02-06 08:03:10 +0200555void cfg80211_pmsr_free_wk(struct work_struct *work)
556{
557 struct wireless_dev *wdev = container_of(work, struct wireless_dev,
558 pmsr_free_wk);
559
560 wdev_lock(wdev);
561 cfg80211_pmsr_process_abort(wdev);
562 wdev_unlock(wdev);
563}
564
Johannes Berg9bb7e0f2018-09-10 13:29:12 +0200565void cfg80211_pmsr_wdev_down(struct wireless_dev *wdev)
566{
567 struct cfg80211_pmsr_request *req;
568 bool found = false;
569
570 spin_lock_bh(&wdev->pmsr_lock);
571 list_for_each_entry(req, &wdev->pmsr_list, list) {
572 found = true;
573 req->nl_portid = 0;
574 }
575 spin_unlock_bh(&wdev->pmsr_lock);
576
577 if (found)
Johannes Berg733504242019-02-06 08:03:10 +0200578 cfg80211_pmsr_process_abort(wdev);
579
Johannes Berg9bb7e0f2018-09-10 13:29:12 +0200580 WARN_ON(!list_empty(&wdev->pmsr_list));
581}
582
583void cfg80211_release_pmsr(struct wireless_dev *wdev, u32 portid)
584{
585 struct cfg80211_pmsr_request *req;
586
587 spin_lock_bh(&wdev->pmsr_lock);
588 list_for_each_entry(req, &wdev->pmsr_list, list) {
589 if (req->nl_portid == portid) {
590 req->nl_portid = 0;
591 schedule_work(&wdev->pmsr_free_wk);
592 }
593 }
594 spin_unlock_bh(&wdev->pmsr_lock);
595}
596
597#endif /* __PMSR_H */