| /* SPDX-License-Identifier: GPL-2.0 */ | |
| /* | |
| * Copyright (C) 2018 - 2021, 2023 - 2026 Intel Corporation | |
| */ | |
| #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 (out->ftm.request_type == NL80211_PMSR_FTM_REQ_TYPE_INFRA && | |
| !(capa->ftm.bandwidths & BIT(out->chandef.width))) { | |
| NL_SET_ERR_MSG(info->extack, "FTM: unsupported bandwidth"); | |
| return -EINVAL; | |
| } | |
| if (out->ftm.request_type == NL80211_PMSR_FTM_REQ_TYPE_PD && | |
| !(capa->ftm.pd_bandwidths & BIT(out->chandef.width))) { | |
| NL_SET_ERR_MSG(info->extack, | |
| "FTM: unsupported bandwidth for PD request"); | |
| 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 (out->ftm.request_type == NL80211_PMSR_FTM_REQ_TYPE_INFRA && | |
| !(capa->ftm.preambles & BIT(preamble))) { | |
| NL_SET_ERR_MSG_ATTR(info->extack, | |
| tb[NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE], | |
| "FTM: invalid preamble"); | |
| return -EINVAL; | |
| } | |
| if (out->ftm.request_type == NL80211_PMSR_FTM_REQ_TYPE_PD && | |
| !(capa->ftm.pd_preambles & BIT(preamble))) { | |
| NL_SET_ERR_MSG_ATTR(info->extack, | |
| tb[NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE], | |
| "FTM: invalid preamble for PD request"); | |
| 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_u16(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_u8(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.ftms_per_burst = 0; | |
| if (tb[NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST]) | |
| out->ftm.ftms_per_burst = | |
| nla_get_u8(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) { | |
| 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"); | |
| return -EINVAL; | |
| } | |
| out->ftm.ftmr_retries = 3; | |
| if (tb[NL80211_PMSR_FTM_REQ_ATTR_NUM_FTMR_RETRIES]) | |
| out->ftm.ftmr_retries = | |
| nla_get_u8(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"); | |
| return -EOPNOTSUPP; | |
| } | |
| 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"); | |
| return -EOPNOTSUPP; | |
| } | |
| 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; | |
| } | |
| if (out->ftm.request_type == NL80211_PMSR_FTM_REQ_TYPE_PD && | |
| out->ftm.trigger_based) { | |
| NL_SET_ERR_MSG_ATTR(info->extack, | |
| ftmreq, | |
| "FTM: TB ranging is not supported for PD request type"); | |
| 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.request_type == NL80211_PMSR_FTM_REQ_TYPE_PD && | |
| out->ftm.non_trigger_based && out->ftm.ftms_per_burst > 4) { | |
| NL_SET_ERR_MSG_ATTR(info->extack, | |
| tb[NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST], | |
| "FTM: FTMs per burst must not exceed 4 for PD NTB ranging"); | |
| return -ERANGE; | |
| } | |
| if (out->ftm.ftms_per_burst > 31 && !out->ftm.non_trigger_based && | |
| !out->ftm.trigger_based) { | |
| 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 31"); | |
| return -ERANGE; | |
| } | |
| 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; | |
| } | |
| if (tb[NL80211_PMSR_FTM_REQ_ATTR_BURST_DURATION]) | |
| out->ftm.burst_duration = | |
| nla_get_u8(tb[NL80211_PMSR_FTM_REQ_ATTR_BURST_DURATION]); | |
| else if (!out->ftm.non_trigger_based && !out->ftm.trigger_based) | |
| out->ftm.burst_duration = 15; | |
| 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]); | |
| } | |
| out->ftm.rsta = !!tb[NL80211_PMSR_FTM_REQ_ATTR_RSTA]; | |
| if (out->ftm.rsta && out->ftm.non_trigger_based && | |
| !capa->ftm.rsta.support_ntb) { | |
| NL_SET_ERR_MSG_ATTR(info->extack, | |
| tb[NL80211_PMSR_FTM_REQ_ATTR_RSTA], | |
| "FTM: NTB RSTA not supported by device"); | |
| return -EOPNOTSUPP; | |
| } | |
| if (out->ftm.rsta && out->ftm.trigger_based && | |
| !capa->ftm.rsta.support_tb) { | |
| NL_SET_ERR_MSG_ATTR(info->extack, | |
| tb[NL80211_PMSR_FTM_REQ_ATTR_RSTA], | |
| "FTM: TB RSTA not supported by device"); | |
| return -EOPNOTSUPP; | |
| } | |
| if (out->ftm.rsta && !out->ftm.non_trigger_based && | |
| !out->ftm.trigger_based && | |
| !capa->ftm.rsta.support_edca) { | |
| NL_SET_ERR_MSG_ATTR(info->extack, | |
| tb[NL80211_PMSR_FTM_REQ_ATTR_RSTA], | |
| "FTM: EDCA RSTA not supported by device"); | |
| return -EOPNOTSUPP; | |
| } | |
| if (out->ftm.rsta && | |
| (out->ftm.non_trigger_based || out->ftm.trigger_based) && | |
| !out->ftm.lmr_feedback) { | |
| NL_SET_ERR_MSG_ATTR(info->extack, | |
| tb[NL80211_PMSR_FTM_REQ_ATTR_RSTA], | |
| "FTM: RSTA set without LMR feedback"); | |
| return -EINVAL; | |
| } | |
| if (out->ftm.non_trigger_based) { | |
| if (out->ftm.request_type == NL80211_PMSR_FTM_REQ_TYPE_PD && | |
| !tb[NL80211_PMSR_FTM_REQ_ATTR_NOMINAL_TIME]) { | |
| NL_SET_ERR_MSG(info->extack, | |
| "FTM: nominal time is required for PD NTB ranging"); | |
| return -EINVAL; | |
| } | |
| if (tb[NL80211_PMSR_FTM_REQ_ATTR_NOMINAL_TIME]) | |
| out->ftm.nominal_time = | |
| nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_NOMINAL_TIME]); | |
| if (tb[NL80211_PMSR_FTM_REQ_ATTR_MIN_TIME_BETWEEN_MEASUREMENTS]) | |
| out->ftm.min_time_between_measurements = | |
| nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_MIN_TIME_BETWEEN_MEASUREMENTS]); | |
| if (tb[NL80211_PMSR_FTM_REQ_ATTR_MAX_TIME_BETWEEN_MEASUREMENTS]) | |
| out->ftm.max_time_between_measurements = | |
| nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_MAX_TIME_BETWEEN_MEASUREMENTS]); | |
| if (tb[NL80211_PMSR_FTM_REQ_ATTR_AW_DURATION]) | |
| out->ftm.availability_window = | |
| nla_get_u8(tb[NL80211_PMSR_FTM_REQ_ATTR_AW_DURATION]); | |
| if (tb[NL80211_PMSR_FTM_REQ_ATTR_NUM_MEASUREMENTS]) | |
| out->ftm.num_measurements = | |
| nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_NUM_MEASUREMENTS]); | |
| } | |
| if (tb[NL80211_PMSR_FTM_REQ_ATTR_INGRESS]) | |
| out->ftm.ingress_distance = | |
| nla_get_u64(tb[NL80211_PMSR_FTM_REQ_ATTR_INGRESS]); | |
| if (tb[NL80211_PMSR_FTM_REQ_ATTR_EGRESS]) | |
| out->ftm.egress_distance = | |
| nla_get_u64(tb[NL80211_PMSR_FTM_REQ_ATTR_EGRESS]); | |
| out->ftm.pd_suppress_range_results = | |
| nla_get_flag(tb[NL80211_PMSR_FTM_REQ_ATTR_PD_SUPPRESS_RESULTS]); | |
| if (out->ftm.request_type != NL80211_PMSR_FTM_REQ_TYPE_PD && | |
| out->ftm.pd_suppress_range_results) { | |
| NL_SET_ERR_MSG_ATTR(info->extack, | |
| tb[NL80211_PMSR_FTM_REQ_ATTR_PD_SUPPRESS_RESULTS], | |
| "FTM: suppress range result flag only valid for PD requests"); | |
| return -EINVAL; | |
| } | |
| 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]; | |
| bool have_measurement_type = false; | |
| 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); | |
| if (tb[NL80211_PMSR_PEER_ATTR_REQ_TYPE]) | |
| out->ftm.request_type = | |
| nla_get_u32(tb[NL80211_PMSR_PEER_ATTR_REQ_TYPE]); | |
| else | |
| out->ftm.request_type = NL80211_PMSR_FTM_REQ_TYPE_INFRA; | |
| if (out->ftm.request_type == NL80211_PMSR_FTM_REQ_TYPE_PD && | |
| !rdev->wiphy.pmsr_capa->ftm.type.pd_support) { | |
| NL_SET_ERR_MSG_ATTR(info->extack, | |
| tb[NL80211_PMSR_PEER_ATTR_REQ_TYPE], | |
| "FTM: PD request type not supported by device"); | |
| return -EINVAL; | |
| } | |
| /* 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->extack, info->attrs, | |
| &out->chandef, false); | |
| 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) { | |
| if (have_measurement_type) { | |
| NL_SET_ERR_MSG_ATTR(info->extack, treq, | |
| "multiple measurement types in request data"); | |
| return -EINVAL; | |
| } | |
| have_measurement_type = true; | |
| 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; | |
| } | |
| if (!have_measurement_type) { | |
| NL_SET_ERR_MSG_ATTR(info->extack, | |
| req[NL80211_PMSR_REQ_ATTR_DATA], | |
| "missing measurement type in request data"); | |
| return -EINVAL; | |
| } | |
| 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]; | |
| int count, rem, err, idx, peer_count; | |
| struct wireless_dev *wdev = info->user_ptr[1]; | |
| const struct cfg80211_pmsr_capabilities *capa; | |
| struct cfg80211_pmsr_request *req; | |
| struct nlattr *peers, *peer; | |
| u64 cookie; | |
| capa = rdev->wiphy.pmsr_capa; | |
| if (!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 > capa->max_peers) { | |
| NL_SET_ERR_MSG_ATTR(info->extack, peer, | |
| "Too many peers used"); | |
| return -EINVAL; | |
| } | |
| } | |
| if (!count) { | |
| NL_SET_ERR_MSG_ATTR(info->extack, peers, "No peers specified"); | |
| return -EINVAL; | |
| } | |
| req = kzalloc_flex(*req, peers, count); | |
| if (!req) | |
| return -ENOMEM; | |
| req->n_peers = count; | |
| if (info->attrs[NL80211_ATTR_TIMEOUT]) | |
| req->timeout = nla_get_u32(info->attrs[NL80211_ATTR_TIMEOUT]); | |
| if (info->attrs[NL80211_ATTR_MAC]) { | |
| if (!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++; | |
| } | |
| /* Validate per-role peer limits if advertised */ | |
| if (capa->ftm.ista.max_peers) { | |
| peer_count = 0; | |
| for (idx = 0; idx < req->n_peers; idx++) { | |
| if (!req->peers[idx].ftm.rsta) { | |
| peer_count++; | |
| if (peer_count > capa->ftm.ista.max_peers) { | |
| NL_SET_ERR_MSG(info->extack, | |
| "Too many ISTA peers for device limit"); | |
| err = -EINVAL; | |
| goto out_err; | |
| } | |
| } | |
| } | |
| } | |
| if (capa->ftm.rsta.max_peers) { | |
| peer_count = 0; | |
| for (idx = 0; idx < req->n_peers; idx++) { | |
| if (req->peers[idx].ftm.rsta) { | |
| peer_count++; | |
| if (peer_count > capa->ftm.rsta.max_peers) { | |
| NL_SET_ERR_MSG(info->extack, | |
| "Too many RSTA peers for device limit"); | |
| err = -EINVAL; | |
| goto out_err; | |
| } | |
| } | |
| } | |
| } | |
| req->cookie = cfg80211_assign_cookie(rdev); | |
| req->nl_portid = info->snd_portid; | |
| cookie = req->cookie; | |
| /* | |
| * Add to the list before the driver call; under races or broken | |
| * drivers, completion may free the request before rdev_start_pmsr() | |
| * returns. Use the saved cookie below. | |
| */ | |
| spin_lock_bh(&wdev->pmsr_lock); | |
| list_add_tail(&req->list, &wdev->pmsr_list); | |
| spin_unlock_bh(&wdev->pmsr_lock); | |
| err = rdev_start_pmsr(rdev, wdev, req); | |
| if (err) { | |
| /* An error return leaves the request owned by this path. */ | |
| spin_lock_bh(&wdev->pmsr_lock); | |
| list_del(&req->list); | |
| spin_unlock_bh(&wdev->pmsr_lock); | |
| goto out_err; | |
| } | |
| nl_set_extack_cookie_u64(info->extack, 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); | |
| PUT(u16, BURST_PERIOD, burst_period); | |
| 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); | |
| PUTOPT(u32, TX_LTF_REPETITION_COUNT, tx_ltf_repetition_count); | |
| PUTOPT(u32, RX_LTF_REPETITION_COUNT, rx_ltf_repetition_count); | |
| PUTOPT(u32, MAX_TIME_BETWEEN_MEASUREMENTS, | |
| max_time_between_measurements); | |
| PUTOPT(u32, MIN_TIME_BETWEEN_MEASUREMENTS, | |
| min_time_between_measurements); | |
| PUTOPT(u8, NUM_TX_SPATIAL_STREAMS, num_tx_spatial_streams); | |
| PUTOPT(u8, NUM_RX_SPATIAL_STREAMS, num_rx_spatial_streams); | |
| PUTOPT(u32, NOMINAL_TIME, nominal_time); | |
| PUTOPT(u8, AVAILABILITY_WINDOW, availability_window); | |
| PUTOPT(u32, CHANNEL_WIDTH, chan_width); | |
| PUTOPT(u32, PREAMBLE, preamble); | |
| if (res->ftm.is_delayed_lmr && | |
| nla_put_flag(msg, NL80211_PMSR_FTM_RESP_ATTR_IS_DELAYED_LMR)) | |
| goto error; | |
| 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_wiphy(wdev->wiphy); | |
| 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 wiphy *wiphy, struct wiphy_work *work) | |
| { | |
| struct wireless_dev *wdev = container_of(work, struct wireless_dev, | |
| pmsr_free_wk); | |
| cfg80211_pmsr_process_abort(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); | |
| wiphy_work_cancel(wdev->wiphy, &wdev->pmsr_free_wk); | |
| 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; | |
| wiphy_work_queue(wdev->wiphy, &wdev->pmsr_free_wk); | |
| } | |
| } | |
| spin_unlock_bh(&wdev->pmsr_lock); | |
| } |