linux-stable/net/wireless/pmsr.c
Avraham Stern dd3e4fc75b nl80211/cfg80211: add BSS color to NDP ranging parameters
In NDP ranging, the initiator need to set the BSS color in the NDP
to the BSS color of the responder. Add the BSS color as a parameter
for NDP ranging.

Signed-off-by: Avraham Stern <avraham.stern@intel.com>
Signed-off-by: Luca Coelho <luciano.coelho@intel.com>
Link: https://lore.kernel.org/r/iwlwifi.20210618133832.f097a6144b59.I27dec8b994df52e691925ea61be4dd4fa6d396c0@changeid
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
2021-06-23 11:29:14 +02:00

665 lines
18 KiB
C

/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2018 - 2021 Intel Corporation
*/
#ifndef __PMSR_H
#define __PMSR_H
#include <net/cfg80211.h>
#include "core.h"
#include "nl80211.h"
#include "rdev-ops.h"
static int pmsr_parse_ftm(struct cfg80211_registered_device *rdev,
struct nlattr *ftmreq,
struct cfg80211_pmsr_request_peer *out,
struct genl_info *info)
{
const struct cfg80211_pmsr_capabilities *capa = rdev->wiphy.pmsr_capa;
struct nlattr *tb[NL80211_PMSR_FTM_REQ_ATTR_MAX + 1];
u32 preamble = NL80211_PREAMBLE_DMG; /* only optional in DMG */
/* validate existing data */
if (!(rdev->wiphy.pmsr_capa->ftm.bandwidths & BIT(out->chandef.width))) {
NL_SET_ERR_MSG(info->extack, "FTM: unsupported bandwidth");
return -EINVAL;
}
/* no validation needed - was already done via nested policy */
nla_parse_nested_deprecated(tb, NL80211_PMSR_FTM_REQ_ATTR_MAX, ftmreq,
NULL, NULL);
if (tb[NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE])
preamble = nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE]);
/* set up values - struct is 0-initialized */
out->ftm.requested = true;
switch (out->chandef.chan->band) {
case NL80211_BAND_60GHZ:
/* optional */
break;
default:
if (!tb[NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE]) {
NL_SET_ERR_MSG(info->extack,
"FTM: must specify preamble");
return -EINVAL;
}
}
if (!(capa->ftm.preambles & BIT(preamble))) {
NL_SET_ERR_MSG_ATTR(info->extack,
tb[NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE],
"FTM: invalid preamble");
return -EINVAL;
}
out->ftm.preamble = preamble;
out->ftm.burst_period = 0;
if (tb[NL80211_PMSR_FTM_REQ_ATTR_BURST_PERIOD])
out->ftm.burst_period =
nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_BURST_PERIOD]);
out->ftm.asap = !!tb[NL80211_PMSR_FTM_REQ_ATTR_ASAP];
if (out->ftm.asap && !capa->ftm.asap) {
NL_SET_ERR_MSG_ATTR(info->extack,
tb[NL80211_PMSR_FTM_REQ_ATTR_ASAP],
"FTM: ASAP mode not supported");
return -EINVAL;
}
if (!out->ftm.asap && !capa->ftm.non_asap) {
NL_SET_ERR_MSG(info->extack,
"FTM: non-ASAP mode not supported");
return -EINVAL;
}
out->ftm.num_bursts_exp = 0;
if (tb[NL80211_PMSR_FTM_REQ_ATTR_NUM_BURSTS_EXP])
out->ftm.num_bursts_exp =
nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_NUM_BURSTS_EXP]);
if (capa->ftm.max_bursts_exponent >= 0 &&
out->ftm.num_bursts_exp > capa->ftm.max_bursts_exponent) {
NL_SET_ERR_MSG_ATTR(info->extack,
tb[NL80211_PMSR_FTM_REQ_ATTR_NUM_BURSTS_EXP],
"FTM: max NUM_BURSTS_EXP must be set lower than the device limit");
return -EINVAL;
}
out->ftm.burst_duration = 15;
if (tb[NL80211_PMSR_FTM_REQ_ATTR_BURST_DURATION])
out->ftm.burst_duration =
nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_BURST_DURATION]);
out->ftm.ftms_per_burst = 0;
if (tb[NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST])
out->ftm.ftms_per_burst =
nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST]);
if (capa->ftm.max_ftms_per_burst &&
(out->ftm.ftms_per_burst > capa->ftm.max_ftms_per_burst ||
out->ftm.ftms_per_burst == 0)) {
NL_SET_ERR_MSG_ATTR(info->extack,
tb[NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST],
"FTM: FTMs per burst must be set lower than the device limit but non-zero");
return -EINVAL;
}
out->ftm.ftmr_retries = 3;
if (tb[NL80211_PMSR_FTM_REQ_ATTR_NUM_FTMR_RETRIES])
out->ftm.ftmr_retries =
nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_NUM_FTMR_RETRIES]);
out->ftm.request_lci = !!tb[NL80211_PMSR_FTM_REQ_ATTR_REQUEST_LCI];
if (out->ftm.request_lci && !capa->ftm.request_lci) {
NL_SET_ERR_MSG_ATTR(info->extack,
tb[NL80211_PMSR_FTM_REQ_ATTR_REQUEST_LCI],
"FTM: LCI request not supported");
}
out->ftm.request_civicloc =
!!tb[NL80211_PMSR_FTM_REQ_ATTR_REQUEST_CIVICLOC];
if (out->ftm.request_civicloc && !capa->ftm.request_civicloc) {
NL_SET_ERR_MSG_ATTR(info->extack,
tb[NL80211_PMSR_FTM_REQ_ATTR_REQUEST_CIVICLOC],
"FTM: civic location request not supported");
}
out->ftm.trigger_based =
!!tb[NL80211_PMSR_FTM_REQ_ATTR_TRIGGER_BASED];
if (out->ftm.trigger_based && !capa->ftm.trigger_based) {
NL_SET_ERR_MSG_ATTR(info->extack,
tb[NL80211_PMSR_FTM_REQ_ATTR_TRIGGER_BASED],
"FTM: trigger based ranging is not supported");
return -EINVAL;
}
out->ftm.non_trigger_based =
!!tb[NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED];
if (out->ftm.non_trigger_based && !capa->ftm.non_trigger_based) {
NL_SET_ERR_MSG_ATTR(info->extack,
tb[NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED],
"FTM: trigger based ranging is not supported");
return -EINVAL;
}
if (out->ftm.trigger_based && out->ftm.non_trigger_based) {
NL_SET_ERR_MSG(info->extack,
"FTM: can't set both trigger based and non trigger based");
return -EINVAL;
}
if ((out->ftm.trigger_based || out->ftm.non_trigger_based) &&
out->ftm.preamble != NL80211_PREAMBLE_HE) {
NL_SET_ERR_MSG_ATTR(info->extack,
tb[NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE],
"FTM: non EDCA based ranging must use HE preamble");
return -EINVAL;
}
out->ftm.lmr_feedback =
!!tb[NL80211_PMSR_FTM_REQ_ATTR_LMR_FEEDBACK];
if (!out->ftm.trigger_based && !out->ftm.non_trigger_based &&
out->ftm.lmr_feedback) {
NL_SET_ERR_MSG_ATTR(info->extack,
tb[NL80211_PMSR_FTM_REQ_ATTR_LMR_FEEDBACK],
"FTM: LMR feedback set for EDCA based ranging");
return -EINVAL;
}
if (tb[NL80211_PMSR_FTM_REQ_ATTR_BSS_COLOR]) {
if (!out->ftm.non_trigger_based && !out->ftm.trigger_based) {
NL_SET_ERR_MSG_ATTR(info->extack,
tb[NL80211_PMSR_FTM_REQ_ATTR_BSS_COLOR],
"FTM: BSS color set for EDCA based ranging");
return -EINVAL;
}
out->ftm.bss_color =
nla_get_u8(tb[NL80211_PMSR_FTM_REQ_ATTR_BSS_COLOR]);
}
return 0;
}
static int pmsr_parse_peer(struct cfg80211_registered_device *rdev,
struct nlattr *peer,
struct cfg80211_pmsr_request_peer *out,
struct genl_info *info)
{
struct nlattr *tb[NL80211_PMSR_PEER_ATTR_MAX + 1];
struct nlattr *req[NL80211_PMSR_REQ_ATTR_MAX + 1];
struct nlattr *treq;
int err, rem;
/* no validation needed - was already done via nested policy */
nla_parse_nested_deprecated(tb, NL80211_PMSR_PEER_ATTR_MAX, peer,
NULL, NULL);
if (!tb[NL80211_PMSR_PEER_ATTR_ADDR] ||
!tb[NL80211_PMSR_PEER_ATTR_CHAN] ||
!tb[NL80211_PMSR_PEER_ATTR_REQ]) {
NL_SET_ERR_MSG_ATTR(info->extack, peer,
"insufficient peer data");
return -EINVAL;
}
memcpy(out->addr, nla_data(tb[NL80211_PMSR_PEER_ATTR_ADDR]), ETH_ALEN);
/* reuse info->attrs */
memset(info->attrs, 0, sizeof(*info->attrs) * (NL80211_ATTR_MAX + 1));
err = nla_parse_nested_deprecated(info->attrs, NL80211_ATTR_MAX,
tb[NL80211_PMSR_PEER_ATTR_CHAN],
NULL, info->extack);
if (err)
return err;
err = nl80211_parse_chandef(rdev, info, &out->chandef);
if (err)
return err;
/* no validation needed - was already done via nested policy */
nla_parse_nested_deprecated(req, NL80211_PMSR_REQ_ATTR_MAX,
tb[NL80211_PMSR_PEER_ATTR_REQ], NULL,
NULL);
if (!req[NL80211_PMSR_REQ_ATTR_DATA]) {
NL_SET_ERR_MSG_ATTR(info->extack,
tb[NL80211_PMSR_PEER_ATTR_REQ],
"missing request type/data");
return -EINVAL;
}
if (req[NL80211_PMSR_REQ_ATTR_GET_AP_TSF])
out->report_ap_tsf = true;
if (out->report_ap_tsf && !rdev->wiphy.pmsr_capa->report_ap_tsf) {
NL_SET_ERR_MSG_ATTR(info->extack,
req[NL80211_PMSR_REQ_ATTR_GET_AP_TSF],
"reporting AP TSF is not supported");
return -EINVAL;
}
nla_for_each_nested(treq, req[NL80211_PMSR_REQ_ATTR_DATA], rem) {
switch (nla_type(treq)) {
case NL80211_PMSR_TYPE_FTM:
err = pmsr_parse_ftm(rdev, treq, out, info);
break;
default:
NL_SET_ERR_MSG_ATTR(info->extack, treq,
"unsupported measurement type");
err = -EINVAL;
}
}
if (err)
return err;
return 0;
}
int nl80211_pmsr_start(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr *reqattr = info->attrs[NL80211_ATTR_PEER_MEASUREMENTS];
struct cfg80211_registered_device *rdev = info->user_ptr[0];
struct wireless_dev *wdev = info->user_ptr[1];
struct cfg80211_pmsr_request *req;
struct nlattr *peers, *peer;
int count, rem, err, idx;
if (!rdev->wiphy.pmsr_capa)
return -EOPNOTSUPP;
if (!reqattr)
return -EINVAL;
peers = nla_find(nla_data(reqattr), nla_len(reqattr),
NL80211_PMSR_ATTR_PEERS);
if (!peers)
return -EINVAL;
count = 0;
nla_for_each_nested(peer, peers, rem) {
count++;
if (count > rdev->wiphy.pmsr_capa->max_peers) {
NL_SET_ERR_MSG_ATTR(info->extack, peer,
"Too many peers used");
return -EINVAL;
}
}
req = kzalloc(struct_size(req, peers, count), GFP_KERNEL);
if (!req)
return -ENOMEM;
if (info->attrs[NL80211_ATTR_TIMEOUT])
req->timeout = nla_get_u32(info->attrs[NL80211_ATTR_TIMEOUT]);
if (info->attrs[NL80211_ATTR_MAC]) {
if (!rdev->wiphy.pmsr_capa->randomize_mac_addr) {
NL_SET_ERR_MSG_ATTR(info->extack,
info->attrs[NL80211_ATTR_MAC],
"device cannot randomize MAC address");
err = -EINVAL;
goto out_err;
}
err = nl80211_parse_random_mac(info->attrs, req->mac_addr,
req->mac_addr_mask);
if (err)
goto out_err;
} else {
memcpy(req->mac_addr, wdev_address(wdev), ETH_ALEN);
eth_broadcast_addr(req->mac_addr_mask);
}
idx = 0;
nla_for_each_nested(peer, peers, rem) {
/* NB: this reuses info->attrs, but we no longer need it */
err = pmsr_parse_peer(rdev, peer, &req->peers[idx], info);
if (err)
goto out_err;
idx++;
}
req->n_peers = count;
req->cookie = cfg80211_assign_cookie(rdev);
req->nl_portid = info->snd_portid;
err = rdev_start_pmsr(rdev, wdev, req);
if (err)
goto out_err;
list_add_tail(&req->list, &wdev->pmsr_list);
nl_set_extack_cookie_u64(info->extack, req->cookie);
return 0;
out_err:
kfree(req);
return err;
}
void cfg80211_pmsr_complete(struct wireless_dev *wdev,
struct cfg80211_pmsr_request *req,
gfp_t gfp)
{
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
struct cfg80211_pmsr_request *tmp, *prev, *to_free = NULL;
struct sk_buff *msg;
void *hdr;
trace_cfg80211_pmsr_complete(wdev->wiphy, wdev, req->cookie);
msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp);
if (!msg)
goto free_request;
hdr = nl80211hdr_put(msg, 0, 0, 0,
NL80211_CMD_PEER_MEASUREMENT_COMPLETE);
if (!hdr)
goto free_msg;
if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev),
NL80211_ATTR_PAD))
goto free_msg;
if (nla_put_u64_64bit(msg, NL80211_ATTR_COOKIE, req->cookie,
NL80211_ATTR_PAD))
goto free_msg;
genlmsg_end(msg, hdr);
genlmsg_unicast(wiphy_net(wdev->wiphy), msg, req->nl_portid);
goto free_request;
free_msg:
nlmsg_free(msg);
free_request:
spin_lock_bh(&wdev->pmsr_lock);
/*
* cfg80211_pmsr_process_abort() may have already moved this request
* to the free list, and will free it later. In this case, don't free
* it here.
*/
list_for_each_entry_safe(tmp, prev, &wdev->pmsr_list, list) {
if (tmp == req) {
list_del(&req->list);
to_free = req;
break;
}
}
spin_unlock_bh(&wdev->pmsr_lock);
kfree(to_free);
}
EXPORT_SYMBOL_GPL(cfg80211_pmsr_complete);
static int nl80211_pmsr_send_ftm_res(struct sk_buff *msg,
struct cfg80211_pmsr_result *res)
{
if (res->status == NL80211_PMSR_STATUS_FAILURE) {
if (nla_put_u32(msg, NL80211_PMSR_FTM_RESP_ATTR_FAIL_REASON,
res->ftm.failure_reason))
goto error;
if (res->ftm.failure_reason ==
NL80211_PMSR_FTM_FAILURE_PEER_BUSY &&
res->ftm.busy_retry_time &&
nla_put_u32(msg, NL80211_PMSR_FTM_RESP_ATTR_BUSY_RETRY_TIME,
res->ftm.busy_retry_time))
goto error;
return 0;
}
#define PUT(tp, attr, val) \
do { \
if (nla_put_##tp(msg, \
NL80211_PMSR_FTM_RESP_ATTR_##attr, \
res->ftm.val)) \
goto error; \
} while (0)
#define PUTOPT(tp, attr, val) \
do { \
if (res->ftm.val##_valid) \
PUT(tp, attr, val); \
} while (0)
#define PUT_U64(attr, val) \
do { \
if (nla_put_u64_64bit(msg, \
NL80211_PMSR_FTM_RESP_ATTR_##attr,\
res->ftm.val, \
NL80211_PMSR_FTM_RESP_ATTR_PAD)) \
goto error; \
} while (0)
#define PUTOPT_U64(attr, val) \
do { \
if (res->ftm.val##_valid) \
PUT_U64(attr, val); \
} while (0)
if (res->ftm.burst_index >= 0)
PUT(u32, BURST_INDEX, burst_index);
PUTOPT(u32, NUM_FTMR_ATTEMPTS, num_ftmr_attempts);
PUTOPT(u32, NUM_FTMR_SUCCESSES, num_ftmr_successes);
PUT(u8, NUM_BURSTS_EXP, num_bursts_exp);
PUT(u8, BURST_DURATION, burst_duration);
PUT(u8, FTMS_PER_BURST, ftms_per_burst);
PUTOPT(s32, RSSI_AVG, rssi_avg);
PUTOPT(s32, RSSI_SPREAD, rssi_spread);
if (res->ftm.tx_rate_valid &&
!nl80211_put_sta_rate(msg, &res->ftm.tx_rate,
NL80211_PMSR_FTM_RESP_ATTR_TX_RATE))
goto error;
if (res->ftm.rx_rate_valid &&
!nl80211_put_sta_rate(msg, &res->ftm.rx_rate,
NL80211_PMSR_FTM_RESP_ATTR_RX_RATE))
goto error;
PUTOPT_U64(RTT_AVG, rtt_avg);
PUTOPT_U64(RTT_VARIANCE, rtt_variance);
PUTOPT_U64(RTT_SPREAD, rtt_spread);
PUTOPT_U64(DIST_AVG, dist_avg);
PUTOPT_U64(DIST_VARIANCE, dist_variance);
PUTOPT_U64(DIST_SPREAD, dist_spread);
if (res->ftm.lci && res->ftm.lci_len &&
nla_put(msg, NL80211_PMSR_FTM_RESP_ATTR_LCI,
res->ftm.lci_len, res->ftm.lci))
goto error;
if (res->ftm.civicloc && res->ftm.civicloc_len &&
nla_put(msg, NL80211_PMSR_FTM_RESP_ATTR_CIVICLOC,
res->ftm.civicloc_len, res->ftm.civicloc))
goto error;
#undef PUT
#undef PUTOPT
#undef PUT_U64
#undef PUTOPT_U64
return 0;
error:
return -ENOSPC;
}
static int nl80211_pmsr_send_result(struct sk_buff *msg,
struct cfg80211_pmsr_result *res)
{
struct nlattr *pmsr, *peers, *peer, *resp, *data, *typedata;
pmsr = nla_nest_start_noflag(msg, NL80211_ATTR_PEER_MEASUREMENTS);
if (!pmsr)
goto error;
peers = nla_nest_start_noflag(msg, NL80211_PMSR_ATTR_PEERS);
if (!peers)
goto error;
peer = nla_nest_start_noflag(msg, 1);
if (!peer)
goto error;
if (nla_put(msg, NL80211_PMSR_PEER_ATTR_ADDR, ETH_ALEN, res->addr))
goto error;
resp = nla_nest_start_noflag(msg, NL80211_PMSR_PEER_ATTR_RESP);
if (!resp)
goto error;
if (nla_put_u32(msg, NL80211_PMSR_RESP_ATTR_STATUS, res->status) ||
nla_put_u64_64bit(msg, NL80211_PMSR_RESP_ATTR_HOST_TIME,
res->host_time, NL80211_PMSR_RESP_ATTR_PAD))
goto error;
if (res->ap_tsf_valid &&
nla_put_u64_64bit(msg, NL80211_PMSR_RESP_ATTR_AP_TSF,
res->ap_tsf, NL80211_PMSR_RESP_ATTR_PAD))
goto error;
if (res->final && nla_put_flag(msg, NL80211_PMSR_RESP_ATTR_FINAL))
goto error;
data = nla_nest_start_noflag(msg, NL80211_PMSR_RESP_ATTR_DATA);
if (!data)
goto error;
typedata = nla_nest_start_noflag(msg, res->type);
if (!typedata)
goto error;
switch (res->type) {
case NL80211_PMSR_TYPE_FTM:
if (nl80211_pmsr_send_ftm_res(msg, res))
goto error;
break;
default:
WARN_ON(1);
}
nla_nest_end(msg, typedata);
nla_nest_end(msg, data);
nla_nest_end(msg, resp);
nla_nest_end(msg, peer);
nla_nest_end(msg, peers);
nla_nest_end(msg, pmsr);
return 0;
error:
return -ENOSPC;
}
void cfg80211_pmsr_report(struct wireless_dev *wdev,
struct cfg80211_pmsr_request *req,
struct cfg80211_pmsr_result *result,
gfp_t gfp)
{
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
struct sk_buff *msg;
void *hdr;
int err;
trace_cfg80211_pmsr_report(wdev->wiphy, wdev, req->cookie,
result->addr);
/*
* Currently, only variable items are LCI and civic location,
* both of which are reasonably short so we don't need to
* worry about them here for the allocation.
*/
msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp);
if (!msg)
return;
hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_PEER_MEASUREMENT_RESULT);
if (!hdr)
goto free;
if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev),
NL80211_ATTR_PAD))
goto free;
if (nla_put_u64_64bit(msg, NL80211_ATTR_COOKIE, req->cookie,
NL80211_ATTR_PAD))
goto free;
err = nl80211_pmsr_send_result(msg, result);
if (err) {
pr_err_ratelimited("peer measurement result: message didn't fit!");
goto free;
}
genlmsg_end(msg, hdr);
genlmsg_unicast(wiphy_net(wdev->wiphy), msg, req->nl_portid);
return;
free:
nlmsg_free(msg);
}
EXPORT_SYMBOL_GPL(cfg80211_pmsr_report);
static void cfg80211_pmsr_process_abort(struct wireless_dev *wdev)
{
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
struct cfg80211_pmsr_request *req, *tmp;
LIST_HEAD(free_list);
lockdep_assert_held(&wdev->mtx);
spin_lock_bh(&wdev->pmsr_lock);
list_for_each_entry_safe(req, tmp, &wdev->pmsr_list, list) {
if (req->nl_portid)
continue;
list_move_tail(&req->list, &free_list);
}
spin_unlock_bh(&wdev->pmsr_lock);
list_for_each_entry_safe(req, tmp, &free_list, list) {
rdev_abort_pmsr(rdev, wdev, req);
kfree(req);
}
}
void cfg80211_pmsr_free_wk(struct work_struct *work)
{
struct wireless_dev *wdev = container_of(work, struct wireless_dev,
pmsr_free_wk);
wdev_lock(wdev);
cfg80211_pmsr_process_abort(wdev);
wdev_unlock(wdev);
}
void cfg80211_pmsr_wdev_down(struct wireless_dev *wdev)
{
struct cfg80211_pmsr_request *req;
bool found = false;
spin_lock_bh(&wdev->pmsr_lock);
list_for_each_entry(req, &wdev->pmsr_list, list) {
found = true;
req->nl_portid = 0;
}
spin_unlock_bh(&wdev->pmsr_lock);
if (found)
cfg80211_pmsr_process_abort(wdev);
WARN_ON(!list_empty(&wdev->pmsr_list));
}
void cfg80211_release_pmsr(struct wireless_dev *wdev, u32 portid)
{
struct cfg80211_pmsr_request *req;
spin_lock_bh(&wdev->pmsr_lock);
list_for_each_entry(req, &wdev->pmsr_list, list) {
if (req->nl_portid == portid) {
req->nl_portid = 0;
schedule_work(&wdev->pmsr_free_wk);
}
}
spin_unlock_bh(&wdev->pmsr_lock);
}
#endif /* __PMSR_H */