19bb7e0f2SJohannes Berg /* SPDX-License-Identifier: GPL-2.0 */ 29bb7e0f2SJohannes Berg /* 3*73807523SAvraham Stern * Copyright (C) 2018 - 2021 Intel Corporation 49bb7e0f2SJohannes Berg */ 59bb7e0f2SJohannes Berg #ifndef __PMSR_H 69bb7e0f2SJohannes Berg #define __PMSR_H 79bb7e0f2SJohannes Berg #include <net/cfg80211.h> 89bb7e0f2SJohannes Berg #include "core.h" 99bb7e0f2SJohannes Berg #include "nl80211.h" 109bb7e0f2SJohannes Berg #include "rdev-ops.h" 119bb7e0f2SJohannes Berg 129bb7e0f2SJohannes Berg static int pmsr_parse_ftm(struct cfg80211_registered_device *rdev, 139bb7e0f2SJohannes Berg struct nlattr *ftmreq, 149bb7e0f2SJohannes Berg struct cfg80211_pmsr_request_peer *out, 159bb7e0f2SJohannes Berg struct genl_info *info) 169bb7e0f2SJohannes Berg { 179bb7e0f2SJohannes Berg const struct cfg80211_pmsr_capabilities *capa = rdev->wiphy.pmsr_capa; 189bb7e0f2SJohannes Berg struct nlattr *tb[NL80211_PMSR_FTM_REQ_ATTR_MAX + 1]; 199bb7e0f2SJohannes Berg u32 preamble = NL80211_PREAMBLE_DMG; /* only optional in DMG */ 209bb7e0f2SJohannes Berg 219bb7e0f2SJohannes Berg /* validate existing data */ 229bb7e0f2SJohannes Berg if (!(rdev->wiphy.pmsr_capa->ftm.bandwidths & BIT(out->chandef.width))) { 239bb7e0f2SJohannes Berg NL_SET_ERR_MSG(info->extack, "FTM: unsupported bandwidth"); 249bb7e0f2SJohannes Berg return -EINVAL; 259bb7e0f2SJohannes Berg } 269bb7e0f2SJohannes Berg 279bb7e0f2SJohannes Berg /* no validation needed - was already done via nested policy */ 288cb08174SJohannes Berg nla_parse_nested_deprecated(tb, NL80211_PMSR_FTM_REQ_ATTR_MAX, ftmreq, 298cb08174SJohannes Berg NULL, NULL); 309bb7e0f2SJohannes Berg 319bb7e0f2SJohannes Berg if (tb[NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE]) 329bb7e0f2SJohannes Berg preamble = nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE]); 339bb7e0f2SJohannes Berg 349bb7e0f2SJohannes Berg /* set up values - struct is 0-initialized */ 359bb7e0f2SJohannes Berg out->ftm.requested = true; 369bb7e0f2SJohannes Berg 379bb7e0f2SJohannes Berg switch (out->chandef.chan->band) { 389bb7e0f2SJohannes Berg case NL80211_BAND_60GHZ: 399bb7e0f2SJohannes Berg /* optional */ 409bb7e0f2SJohannes Berg break; 419bb7e0f2SJohannes Berg default: 429bb7e0f2SJohannes Berg if (!tb[NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE]) { 439bb7e0f2SJohannes Berg NL_SET_ERR_MSG(info->extack, 449bb7e0f2SJohannes Berg "FTM: must specify preamble"); 459bb7e0f2SJohannes Berg return -EINVAL; 469bb7e0f2SJohannes Berg } 479bb7e0f2SJohannes Berg } 489bb7e0f2SJohannes Berg 499bb7e0f2SJohannes Berg if (!(capa->ftm.preambles & BIT(preamble))) { 509bb7e0f2SJohannes Berg NL_SET_ERR_MSG_ATTR(info->extack, 519bb7e0f2SJohannes Berg tb[NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE], 529bb7e0f2SJohannes Berg "FTM: invalid preamble"); 539bb7e0f2SJohannes Berg return -EINVAL; 549bb7e0f2SJohannes Berg } 559bb7e0f2SJohannes Berg 569bb7e0f2SJohannes Berg out->ftm.preamble = preamble; 579bb7e0f2SJohannes Berg 589bb7e0f2SJohannes Berg out->ftm.burst_period = 0; 599bb7e0f2SJohannes Berg if (tb[NL80211_PMSR_FTM_REQ_ATTR_BURST_PERIOD]) 609bb7e0f2SJohannes Berg out->ftm.burst_period = 619bb7e0f2SJohannes Berg nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_BURST_PERIOD]); 629bb7e0f2SJohannes Berg 639bb7e0f2SJohannes Berg out->ftm.asap = !!tb[NL80211_PMSR_FTM_REQ_ATTR_ASAP]; 649bb7e0f2SJohannes Berg if (out->ftm.asap && !capa->ftm.asap) { 659bb7e0f2SJohannes Berg NL_SET_ERR_MSG_ATTR(info->extack, 669bb7e0f2SJohannes Berg tb[NL80211_PMSR_FTM_REQ_ATTR_ASAP], 679bb7e0f2SJohannes Berg "FTM: ASAP mode not supported"); 689bb7e0f2SJohannes Berg return -EINVAL; 699bb7e0f2SJohannes Berg } 709bb7e0f2SJohannes Berg 719bb7e0f2SJohannes Berg if (!out->ftm.asap && !capa->ftm.non_asap) { 729bb7e0f2SJohannes Berg NL_SET_ERR_MSG(info->extack, 739bb7e0f2SJohannes Berg "FTM: non-ASAP mode not supported"); 749bb7e0f2SJohannes Berg return -EINVAL; 759bb7e0f2SJohannes Berg } 769bb7e0f2SJohannes Berg 779bb7e0f2SJohannes Berg out->ftm.num_bursts_exp = 0; 789bb7e0f2SJohannes Berg if (tb[NL80211_PMSR_FTM_REQ_ATTR_NUM_BURSTS_EXP]) 799bb7e0f2SJohannes Berg out->ftm.num_bursts_exp = 809bb7e0f2SJohannes Berg nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_NUM_BURSTS_EXP]); 819bb7e0f2SJohannes Berg 829bb7e0f2SJohannes Berg if (capa->ftm.max_bursts_exponent >= 0 && 839bb7e0f2SJohannes Berg out->ftm.num_bursts_exp > capa->ftm.max_bursts_exponent) { 849bb7e0f2SJohannes Berg NL_SET_ERR_MSG_ATTR(info->extack, 859bb7e0f2SJohannes Berg tb[NL80211_PMSR_FTM_REQ_ATTR_NUM_BURSTS_EXP], 869bb7e0f2SJohannes Berg "FTM: max NUM_BURSTS_EXP must be set lower than the device limit"); 879bb7e0f2SJohannes Berg return -EINVAL; 889bb7e0f2SJohannes Berg } 899bb7e0f2SJohannes Berg 909bb7e0f2SJohannes Berg out->ftm.burst_duration = 15; 919bb7e0f2SJohannes Berg if (tb[NL80211_PMSR_FTM_REQ_ATTR_BURST_DURATION]) 929bb7e0f2SJohannes Berg out->ftm.burst_duration = 939bb7e0f2SJohannes Berg nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_BURST_DURATION]); 949bb7e0f2SJohannes Berg 959bb7e0f2SJohannes Berg out->ftm.ftms_per_burst = 0; 969bb7e0f2SJohannes Berg if (tb[NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST]) 979bb7e0f2SJohannes Berg out->ftm.ftms_per_burst = 989bb7e0f2SJohannes Berg nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST]); 999bb7e0f2SJohannes Berg 1009bb7e0f2SJohannes Berg if (capa->ftm.max_ftms_per_burst && 1019bb7e0f2SJohannes Berg (out->ftm.ftms_per_burst > capa->ftm.max_ftms_per_burst || 1029bb7e0f2SJohannes Berg out->ftm.ftms_per_burst == 0)) { 1039bb7e0f2SJohannes Berg NL_SET_ERR_MSG_ATTR(info->extack, 1049bb7e0f2SJohannes Berg tb[NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST], 1059bb7e0f2SJohannes Berg "FTM: FTMs per burst must be set lower than the device limit but non-zero"); 1069bb7e0f2SJohannes Berg return -EINVAL; 1079bb7e0f2SJohannes Berg } 1089bb7e0f2SJohannes Berg 1099bb7e0f2SJohannes Berg out->ftm.ftmr_retries = 3; 1109bb7e0f2SJohannes Berg if (tb[NL80211_PMSR_FTM_REQ_ATTR_NUM_FTMR_RETRIES]) 1119bb7e0f2SJohannes Berg out->ftm.ftmr_retries = 1129bb7e0f2SJohannes Berg nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_NUM_FTMR_RETRIES]); 1139bb7e0f2SJohannes Berg 1149bb7e0f2SJohannes Berg out->ftm.request_lci = !!tb[NL80211_PMSR_FTM_REQ_ATTR_REQUEST_LCI]; 1159bb7e0f2SJohannes Berg if (out->ftm.request_lci && !capa->ftm.request_lci) { 1169bb7e0f2SJohannes Berg NL_SET_ERR_MSG_ATTR(info->extack, 1179bb7e0f2SJohannes Berg tb[NL80211_PMSR_FTM_REQ_ATTR_REQUEST_LCI], 1189bb7e0f2SJohannes Berg "FTM: LCI request not supported"); 1199bb7e0f2SJohannes Berg } 1209bb7e0f2SJohannes Berg 1219bb7e0f2SJohannes Berg out->ftm.request_civicloc = 1229bb7e0f2SJohannes Berg !!tb[NL80211_PMSR_FTM_REQ_ATTR_REQUEST_CIVICLOC]; 1239bb7e0f2SJohannes Berg if (out->ftm.request_civicloc && !capa->ftm.request_civicloc) { 1249bb7e0f2SJohannes Berg NL_SET_ERR_MSG_ATTR(info->extack, 1259bb7e0f2SJohannes Berg tb[NL80211_PMSR_FTM_REQ_ATTR_REQUEST_CIVICLOC], 1269bb7e0f2SJohannes Berg "FTM: civic location request not supported"); 1279bb7e0f2SJohannes Berg } 1289bb7e0f2SJohannes Berg 129efb5520dSAvraham Stern out->ftm.trigger_based = 130efb5520dSAvraham Stern !!tb[NL80211_PMSR_FTM_REQ_ATTR_TRIGGER_BASED]; 131efb5520dSAvraham Stern if (out->ftm.trigger_based && !capa->ftm.trigger_based) { 132efb5520dSAvraham Stern NL_SET_ERR_MSG_ATTR(info->extack, 133efb5520dSAvraham Stern tb[NL80211_PMSR_FTM_REQ_ATTR_TRIGGER_BASED], 134efb5520dSAvraham Stern "FTM: trigger based ranging is not supported"); 135efb5520dSAvraham Stern return -EINVAL; 136efb5520dSAvraham Stern } 137efb5520dSAvraham Stern 138efb5520dSAvraham Stern out->ftm.non_trigger_based = 139efb5520dSAvraham Stern !!tb[NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED]; 140efb5520dSAvraham Stern if (out->ftm.non_trigger_based && !capa->ftm.non_trigger_based) { 141efb5520dSAvraham Stern NL_SET_ERR_MSG_ATTR(info->extack, 142efb5520dSAvraham Stern tb[NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED], 143efb5520dSAvraham Stern "FTM: trigger based ranging is not supported"); 144efb5520dSAvraham Stern return -EINVAL; 145efb5520dSAvraham Stern } 146efb5520dSAvraham Stern 147efb5520dSAvraham Stern if (out->ftm.trigger_based && out->ftm.non_trigger_based) { 148efb5520dSAvraham Stern NL_SET_ERR_MSG(info->extack, 149efb5520dSAvraham Stern "FTM: can't set both trigger based and non trigger based"); 150efb5520dSAvraham Stern return -EINVAL; 151efb5520dSAvraham Stern } 152efb5520dSAvraham Stern 153efb5520dSAvraham Stern if ((out->ftm.trigger_based || out->ftm.non_trigger_based) && 154efb5520dSAvraham Stern out->ftm.preamble != NL80211_PREAMBLE_HE) { 155efb5520dSAvraham Stern NL_SET_ERR_MSG_ATTR(info->extack, 156efb5520dSAvraham Stern tb[NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE], 157efb5520dSAvraham Stern "FTM: non EDCA based ranging must use HE preamble"); 158efb5520dSAvraham Stern return -EINVAL; 159efb5520dSAvraham Stern } 160efb5520dSAvraham Stern 161*73807523SAvraham Stern out->ftm.lmr_feedback = 162*73807523SAvraham Stern !!tb[NL80211_PMSR_FTM_REQ_ATTR_LMR_FEEDBACK]; 163*73807523SAvraham Stern if (!out->ftm.trigger_based && !out->ftm.non_trigger_based && 164*73807523SAvraham Stern out->ftm.lmr_feedback) { 165*73807523SAvraham Stern NL_SET_ERR_MSG_ATTR(info->extack, 166*73807523SAvraham Stern tb[NL80211_PMSR_FTM_REQ_ATTR_LMR_FEEDBACK], 167*73807523SAvraham Stern "FTM: LMR feedback set for EDCA based ranging"); 168*73807523SAvraham Stern return -EINVAL; 169*73807523SAvraham Stern } 170*73807523SAvraham Stern 1719bb7e0f2SJohannes Berg return 0; 1729bb7e0f2SJohannes Berg } 1739bb7e0f2SJohannes Berg 1749bb7e0f2SJohannes Berg static int pmsr_parse_peer(struct cfg80211_registered_device *rdev, 1759bb7e0f2SJohannes Berg struct nlattr *peer, 1769bb7e0f2SJohannes Berg struct cfg80211_pmsr_request_peer *out, 1779bb7e0f2SJohannes Berg struct genl_info *info) 1789bb7e0f2SJohannes Berg { 1799bb7e0f2SJohannes Berg struct nlattr *tb[NL80211_PMSR_PEER_ATTR_MAX + 1]; 1809bb7e0f2SJohannes Berg struct nlattr *req[NL80211_PMSR_REQ_ATTR_MAX + 1]; 1819bb7e0f2SJohannes Berg struct nlattr *treq; 1829bb7e0f2SJohannes Berg int err, rem; 1839bb7e0f2SJohannes Berg 1849bb7e0f2SJohannes Berg /* no validation needed - was already done via nested policy */ 1858cb08174SJohannes Berg nla_parse_nested_deprecated(tb, NL80211_PMSR_PEER_ATTR_MAX, peer, 1868cb08174SJohannes Berg NULL, NULL); 1879bb7e0f2SJohannes Berg 1889bb7e0f2SJohannes Berg if (!tb[NL80211_PMSR_PEER_ATTR_ADDR] || 1899bb7e0f2SJohannes Berg !tb[NL80211_PMSR_PEER_ATTR_CHAN] || 1909bb7e0f2SJohannes Berg !tb[NL80211_PMSR_PEER_ATTR_REQ]) { 1919bb7e0f2SJohannes Berg NL_SET_ERR_MSG_ATTR(info->extack, peer, 1929bb7e0f2SJohannes Berg "insufficient peer data"); 1939bb7e0f2SJohannes Berg return -EINVAL; 1949bb7e0f2SJohannes Berg } 1959bb7e0f2SJohannes Berg 1969bb7e0f2SJohannes Berg memcpy(out->addr, nla_data(tb[NL80211_PMSR_PEER_ATTR_ADDR]), ETH_ALEN); 1979bb7e0f2SJohannes Berg 1989bb7e0f2SJohannes Berg /* reuse info->attrs */ 1999bb7e0f2SJohannes Berg memset(info->attrs, 0, sizeof(*info->attrs) * (NL80211_ATTR_MAX + 1)); 2008cb08174SJohannes Berg err = nla_parse_nested_deprecated(info->attrs, NL80211_ATTR_MAX, 2019bb7e0f2SJohannes Berg tb[NL80211_PMSR_PEER_ATTR_CHAN], 202d15da2a2SJohannes Berg NULL, info->extack); 2039bb7e0f2SJohannes Berg if (err) 2049bb7e0f2SJohannes Berg return err; 2059bb7e0f2SJohannes Berg 2069bb7e0f2SJohannes Berg err = nl80211_parse_chandef(rdev, info, &out->chandef); 2079bb7e0f2SJohannes Berg if (err) 2089bb7e0f2SJohannes Berg return err; 2099bb7e0f2SJohannes Berg 2109bb7e0f2SJohannes Berg /* no validation needed - was already done via nested policy */ 2118cb08174SJohannes Berg nla_parse_nested_deprecated(req, NL80211_PMSR_REQ_ATTR_MAX, 2128cb08174SJohannes Berg tb[NL80211_PMSR_PEER_ATTR_REQ], NULL, 2138cb08174SJohannes Berg NULL); 2149bb7e0f2SJohannes Berg 2159bb7e0f2SJohannes Berg if (!req[NL80211_PMSR_REQ_ATTR_DATA]) { 2169bb7e0f2SJohannes Berg NL_SET_ERR_MSG_ATTR(info->extack, 2179bb7e0f2SJohannes Berg tb[NL80211_PMSR_PEER_ATTR_REQ], 2189bb7e0f2SJohannes Berg "missing request type/data"); 2199bb7e0f2SJohannes Berg return -EINVAL; 2209bb7e0f2SJohannes Berg } 2219bb7e0f2SJohannes Berg 2229bb7e0f2SJohannes Berg if (req[NL80211_PMSR_REQ_ATTR_GET_AP_TSF]) 2239bb7e0f2SJohannes Berg out->report_ap_tsf = true; 2249bb7e0f2SJohannes Berg 2259bb7e0f2SJohannes Berg if (out->report_ap_tsf && !rdev->wiphy.pmsr_capa->report_ap_tsf) { 2269bb7e0f2SJohannes Berg NL_SET_ERR_MSG_ATTR(info->extack, 2279bb7e0f2SJohannes Berg req[NL80211_PMSR_REQ_ATTR_GET_AP_TSF], 2289bb7e0f2SJohannes Berg "reporting AP TSF is not supported"); 2299bb7e0f2SJohannes Berg return -EINVAL; 2309bb7e0f2SJohannes Berg } 2319bb7e0f2SJohannes Berg 2329bb7e0f2SJohannes Berg nla_for_each_nested(treq, req[NL80211_PMSR_REQ_ATTR_DATA], rem) { 2339bb7e0f2SJohannes Berg switch (nla_type(treq)) { 2349bb7e0f2SJohannes Berg case NL80211_PMSR_TYPE_FTM: 2359bb7e0f2SJohannes Berg err = pmsr_parse_ftm(rdev, treq, out, info); 2369bb7e0f2SJohannes Berg break; 2379bb7e0f2SJohannes Berg default: 2389bb7e0f2SJohannes Berg NL_SET_ERR_MSG_ATTR(info->extack, treq, 2399bb7e0f2SJohannes Berg "unsupported measurement type"); 2409bb7e0f2SJohannes Berg err = -EINVAL; 2419bb7e0f2SJohannes Berg } 2429bb7e0f2SJohannes Berg } 2439bb7e0f2SJohannes Berg 2449bb7e0f2SJohannes Berg if (err) 2459bb7e0f2SJohannes Berg return err; 2469bb7e0f2SJohannes Berg 2479bb7e0f2SJohannes Berg return 0; 2489bb7e0f2SJohannes Berg } 2499bb7e0f2SJohannes Berg 2509bb7e0f2SJohannes Berg int nl80211_pmsr_start(struct sk_buff *skb, struct genl_info *info) 2519bb7e0f2SJohannes Berg { 2529bb7e0f2SJohannes Berg struct nlattr *reqattr = info->attrs[NL80211_ATTR_PEER_MEASUREMENTS]; 2539bb7e0f2SJohannes Berg struct cfg80211_registered_device *rdev = info->user_ptr[0]; 2549bb7e0f2SJohannes Berg struct wireless_dev *wdev = info->user_ptr[1]; 2559bb7e0f2SJohannes Berg struct cfg80211_pmsr_request *req; 2569bb7e0f2SJohannes Berg struct nlattr *peers, *peer; 2579bb7e0f2SJohannes Berg int count, rem, err, idx; 2589bb7e0f2SJohannes Berg 2599bb7e0f2SJohannes Berg if (!rdev->wiphy.pmsr_capa) 2609bb7e0f2SJohannes Berg return -EOPNOTSUPP; 2619bb7e0f2SJohannes Berg 2629bb7e0f2SJohannes Berg if (!reqattr) 2639bb7e0f2SJohannes Berg return -EINVAL; 2649bb7e0f2SJohannes Berg 2659bb7e0f2SJohannes Berg peers = nla_find(nla_data(reqattr), nla_len(reqattr), 2669bb7e0f2SJohannes Berg NL80211_PMSR_ATTR_PEERS); 2679bb7e0f2SJohannes Berg if (!peers) 2689bb7e0f2SJohannes Berg return -EINVAL; 2699bb7e0f2SJohannes Berg 2709bb7e0f2SJohannes Berg count = 0; 2719bb7e0f2SJohannes Berg nla_for_each_nested(peer, peers, rem) { 2729bb7e0f2SJohannes Berg count++; 2739bb7e0f2SJohannes Berg 2749bb7e0f2SJohannes Berg if (count > rdev->wiphy.pmsr_capa->max_peers) { 2759bb7e0f2SJohannes Berg NL_SET_ERR_MSG_ATTR(info->extack, peer, 2769bb7e0f2SJohannes Berg "Too many peers used"); 2779bb7e0f2SJohannes Berg return -EINVAL; 2789bb7e0f2SJohannes Berg } 2799bb7e0f2SJohannes Berg } 2809bb7e0f2SJohannes Berg 2819bb7e0f2SJohannes Berg req = kzalloc(struct_size(req, peers, count), GFP_KERNEL); 2829bb7e0f2SJohannes Berg if (!req) 2839bb7e0f2SJohannes Berg return -ENOMEM; 2849bb7e0f2SJohannes Berg 2859bb7e0f2SJohannes Berg if (info->attrs[NL80211_ATTR_TIMEOUT]) 2869bb7e0f2SJohannes Berg req->timeout = nla_get_u32(info->attrs[NL80211_ATTR_TIMEOUT]); 2879bb7e0f2SJohannes Berg 2889bb7e0f2SJohannes Berg if (info->attrs[NL80211_ATTR_MAC]) { 2899bb7e0f2SJohannes Berg if (!rdev->wiphy.pmsr_capa->randomize_mac_addr) { 2909bb7e0f2SJohannes Berg NL_SET_ERR_MSG_ATTR(info->extack, 2919bb7e0f2SJohannes Berg info->attrs[NL80211_ATTR_MAC], 2929bb7e0f2SJohannes Berg "device cannot randomize MAC address"); 2939bb7e0f2SJohannes Berg err = -EINVAL; 2949bb7e0f2SJohannes Berg goto out_err; 2959bb7e0f2SJohannes Berg } 2969bb7e0f2SJohannes Berg 2979bb7e0f2SJohannes Berg err = nl80211_parse_random_mac(info->attrs, req->mac_addr, 2989bb7e0f2SJohannes Berg req->mac_addr_mask); 2999bb7e0f2SJohannes Berg if (err) 3009bb7e0f2SJohannes Berg goto out_err; 3019bb7e0f2SJohannes Berg } else { 3020acd9928SJohannes Berg memcpy(req->mac_addr, wdev_address(wdev), ETH_ALEN); 30376763741SMao Wenan eth_broadcast_addr(req->mac_addr_mask); 3049bb7e0f2SJohannes Berg } 3059bb7e0f2SJohannes Berg 3069bb7e0f2SJohannes Berg idx = 0; 3079bb7e0f2SJohannes Berg nla_for_each_nested(peer, peers, rem) { 3089bb7e0f2SJohannes Berg /* NB: this reuses info->attrs, but we no longer need it */ 3099bb7e0f2SJohannes Berg err = pmsr_parse_peer(rdev, peer, &req->peers[idx], info); 3109bb7e0f2SJohannes Berg if (err) 3119bb7e0f2SJohannes Berg goto out_err; 3129bb7e0f2SJohannes Berg idx++; 3139bb7e0f2SJohannes Berg } 3149bb7e0f2SJohannes Berg 3159bb7e0f2SJohannes Berg req->n_peers = count; 3169bb7e0f2SJohannes Berg req->cookie = cfg80211_assign_cookie(rdev); 317ff1bab1bSJohannes Berg req->nl_portid = info->snd_portid; 3189bb7e0f2SJohannes Berg 3199bb7e0f2SJohannes Berg err = rdev_start_pmsr(rdev, wdev, req); 3209bb7e0f2SJohannes Berg if (err) 3219bb7e0f2SJohannes Berg goto out_err; 3229bb7e0f2SJohannes Berg 3239bb7e0f2SJohannes Berg list_add_tail(&req->list, &wdev->pmsr_list); 3249bb7e0f2SJohannes Berg 3259bb7e0f2SJohannes Berg nl_set_extack_cookie_u64(info->extack, req->cookie); 3269bb7e0f2SJohannes Berg return 0; 3279bb7e0f2SJohannes Berg out_err: 3289bb7e0f2SJohannes Berg kfree(req); 3299bb7e0f2SJohannes Berg return err; 3309bb7e0f2SJohannes Berg } 3319bb7e0f2SJohannes Berg 3329bb7e0f2SJohannes Berg void cfg80211_pmsr_complete(struct wireless_dev *wdev, 3339bb7e0f2SJohannes Berg struct cfg80211_pmsr_request *req, 3349bb7e0f2SJohannes Berg gfp_t gfp) 3359bb7e0f2SJohannes Berg { 3369bb7e0f2SJohannes Berg struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy); 3379bb7e0f2SJohannes Berg struct sk_buff *msg; 3389bb7e0f2SJohannes Berg void *hdr; 3399bb7e0f2SJohannes Berg 3409bb7e0f2SJohannes Berg trace_cfg80211_pmsr_complete(wdev->wiphy, wdev, req->cookie); 3419bb7e0f2SJohannes Berg 3429bb7e0f2SJohannes Berg msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp); 3439bb7e0f2SJohannes Berg if (!msg) 3449bb7e0f2SJohannes Berg goto free_request; 3459bb7e0f2SJohannes Berg 3469bb7e0f2SJohannes Berg hdr = nl80211hdr_put(msg, 0, 0, 0, 3479bb7e0f2SJohannes Berg NL80211_CMD_PEER_MEASUREMENT_COMPLETE); 3489bb7e0f2SJohannes Berg if (!hdr) 3499bb7e0f2SJohannes Berg goto free_msg; 3509bb7e0f2SJohannes Berg 3519bb7e0f2SJohannes Berg if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) || 3529bb7e0f2SJohannes Berg nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev), 3539bb7e0f2SJohannes Berg NL80211_ATTR_PAD)) 3549bb7e0f2SJohannes Berg goto free_msg; 3559bb7e0f2SJohannes Berg 3569bb7e0f2SJohannes Berg if (nla_put_u64_64bit(msg, NL80211_ATTR_COOKIE, req->cookie, 3579bb7e0f2SJohannes Berg NL80211_ATTR_PAD)) 3589bb7e0f2SJohannes Berg goto free_msg; 3599bb7e0f2SJohannes Berg 3609bb7e0f2SJohannes Berg genlmsg_end(msg, hdr); 3619bb7e0f2SJohannes Berg genlmsg_unicast(wiphy_net(wdev->wiphy), msg, req->nl_portid); 3629bb7e0f2SJohannes Berg goto free_request; 3639bb7e0f2SJohannes Berg free_msg: 3649bb7e0f2SJohannes Berg nlmsg_free(msg); 3659bb7e0f2SJohannes Berg free_request: 3669bb7e0f2SJohannes Berg spin_lock_bh(&wdev->pmsr_lock); 3679bb7e0f2SJohannes Berg list_del(&req->list); 3689bb7e0f2SJohannes Berg spin_unlock_bh(&wdev->pmsr_lock); 3699bb7e0f2SJohannes Berg kfree(req); 3709bb7e0f2SJohannes Berg } 3719bb7e0f2SJohannes Berg EXPORT_SYMBOL_GPL(cfg80211_pmsr_complete); 3729bb7e0f2SJohannes Berg 3739bb7e0f2SJohannes Berg static int nl80211_pmsr_send_ftm_res(struct sk_buff *msg, 3749bb7e0f2SJohannes Berg struct cfg80211_pmsr_result *res) 3759bb7e0f2SJohannes Berg { 3769bb7e0f2SJohannes Berg if (res->status == NL80211_PMSR_STATUS_FAILURE) { 3779bb7e0f2SJohannes Berg if (nla_put_u32(msg, NL80211_PMSR_FTM_RESP_ATTR_FAIL_REASON, 3789bb7e0f2SJohannes Berg res->ftm.failure_reason)) 3799bb7e0f2SJohannes Berg goto error; 3809bb7e0f2SJohannes Berg 3819bb7e0f2SJohannes Berg if (res->ftm.failure_reason == 3829bb7e0f2SJohannes Berg NL80211_PMSR_FTM_FAILURE_PEER_BUSY && 3839bb7e0f2SJohannes Berg res->ftm.busy_retry_time && 3849bb7e0f2SJohannes Berg nla_put_u32(msg, NL80211_PMSR_FTM_RESP_ATTR_BUSY_RETRY_TIME, 3859bb7e0f2SJohannes Berg res->ftm.busy_retry_time)) 3869bb7e0f2SJohannes Berg goto error; 3879bb7e0f2SJohannes Berg 3889bb7e0f2SJohannes Berg return 0; 3899bb7e0f2SJohannes Berg } 3909bb7e0f2SJohannes Berg 3919bb7e0f2SJohannes Berg #define PUT(tp, attr, val) \ 3929bb7e0f2SJohannes Berg do { \ 3939bb7e0f2SJohannes Berg if (nla_put_##tp(msg, \ 3949bb7e0f2SJohannes Berg NL80211_PMSR_FTM_RESP_ATTR_##attr, \ 3959bb7e0f2SJohannes Berg res->ftm.val)) \ 3969bb7e0f2SJohannes Berg goto error; \ 3979bb7e0f2SJohannes Berg } while (0) 3989bb7e0f2SJohannes Berg 3999bb7e0f2SJohannes Berg #define PUTOPT(tp, attr, val) \ 4009bb7e0f2SJohannes Berg do { \ 4019bb7e0f2SJohannes Berg if (res->ftm.val##_valid) \ 4029bb7e0f2SJohannes Berg PUT(tp, attr, val); \ 4039bb7e0f2SJohannes Berg } while (0) 4049bb7e0f2SJohannes Berg 4059bb7e0f2SJohannes Berg #define PUT_U64(attr, val) \ 4069bb7e0f2SJohannes Berg do { \ 4079bb7e0f2SJohannes Berg if (nla_put_u64_64bit(msg, \ 4089bb7e0f2SJohannes Berg NL80211_PMSR_FTM_RESP_ATTR_##attr,\ 4099bb7e0f2SJohannes Berg res->ftm.val, \ 4109bb7e0f2SJohannes Berg NL80211_PMSR_FTM_RESP_ATTR_PAD)) \ 4119bb7e0f2SJohannes Berg goto error; \ 4129bb7e0f2SJohannes Berg } while (0) 4139bb7e0f2SJohannes Berg 4149bb7e0f2SJohannes Berg #define PUTOPT_U64(attr, val) \ 4159bb7e0f2SJohannes Berg do { \ 4169bb7e0f2SJohannes Berg if (res->ftm.val##_valid) \ 4179bb7e0f2SJohannes Berg PUT_U64(attr, val); \ 4189bb7e0f2SJohannes Berg } while (0) 4199bb7e0f2SJohannes Berg 4209bb7e0f2SJohannes Berg if (res->ftm.burst_index >= 0) 4219bb7e0f2SJohannes Berg PUT(u32, BURST_INDEX, burst_index); 4229bb7e0f2SJohannes Berg PUTOPT(u32, NUM_FTMR_ATTEMPTS, num_ftmr_attempts); 4239bb7e0f2SJohannes Berg PUTOPT(u32, NUM_FTMR_SUCCESSES, num_ftmr_successes); 4249bb7e0f2SJohannes Berg PUT(u8, NUM_BURSTS_EXP, num_bursts_exp); 4259bb7e0f2SJohannes Berg PUT(u8, BURST_DURATION, burst_duration); 4269bb7e0f2SJohannes Berg PUT(u8, FTMS_PER_BURST, ftms_per_burst); 4279bb7e0f2SJohannes Berg PUTOPT(s32, RSSI_AVG, rssi_avg); 4289bb7e0f2SJohannes Berg PUTOPT(s32, RSSI_SPREAD, rssi_spread); 4299bb7e0f2SJohannes Berg if (res->ftm.tx_rate_valid && 4309bb7e0f2SJohannes Berg !nl80211_put_sta_rate(msg, &res->ftm.tx_rate, 4319bb7e0f2SJohannes Berg NL80211_PMSR_FTM_RESP_ATTR_TX_RATE)) 4329bb7e0f2SJohannes Berg goto error; 4339bb7e0f2SJohannes Berg if (res->ftm.rx_rate_valid && 4349bb7e0f2SJohannes Berg !nl80211_put_sta_rate(msg, &res->ftm.rx_rate, 4359bb7e0f2SJohannes Berg NL80211_PMSR_FTM_RESP_ATTR_RX_RATE)) 4369bb7e0f2SJohannes Berg goto error; 4379bb7e0f2SJohannes Berg PUTOPT_U64(RTT_AVG, rtt_avg); 4389bb7e0f2SJohannes Berg PUTOPT_U64(RTT_VARIANCE, rtt_variance); 4399bb7e0f2SJohannes Berg PUTOPT_U64(RTT_SPREAD, rtt_spread); 4409bb7e0f2SJohannes Berg PUTOPT_U64(DIST_AVG, dist_avg); 4419bb7e0f2SJohannes Berg PUTOPT_U64(DIST_VARIANCE, dist_variance); 4429bb7e0f2SJohannes Berg PUTOPT_U64(DIST_SPREAD, dist_spread); 4439bb7e0f2SJohannes Berg if (res->ftm.lci && res->ftm.lci_len && 4449bb7e0f2SJohannes Berg nla_put(msg, NL80211_PMSR_FTM_RESP_ATTR_LCI, 4459bb7e0f2SJohannes Berg res->ftm.lci_len, res->ftm.lci)) 4469bb7e0f2SJohannes Berg goto error; 4479bb7e0f2SJohannes Berg if (res->ftm.civicloc && res->ftm.civicloc_len && 4489bb7e0f2SJohannes Berg nla_put(msg, NL80211_PMSR_FTM_RESP_ATTR_CIVICLOC, 4499bb7e0f2SJohannes Berg res->ftm.civicloc_len, res->ftm.civicloc)) 4509bb7e0f2SJohannes Berg goto error; 4519bb7e0f2SJohannes Berg #undef PUT 4529bb7e0f2SJohannes Berg #undef PUTOPT 4539bb7e0f2SJohannes Berg #undef PUT_U64 4549bb7e0f2SJohannes Berg #undef PUTOPT_U64 4559bb7e0f2SJohannes Berg 4569bb7e0f2SJohannes Berg return 0; 4579bb7e0f2SJohannes Berg error: 4589bb7e0f2SJohannes Berg return -ENOSPC; 4599bb7e0f2SJohannes Berg } 4609bb7e0f2SJohannes Berg 4619bb7e0f2SJohannes Berg static int nl80211_pmsr_send_result(struct sk_buff *msg, 4629bb7e0f2SJohannes Berg struct cfg80211_pmsr_result *res) 4639bb7e0f2SJohannes Berg { 4649bb7e0f2SJohannes Berg struct nlattr *pmsr, *peers, *peer, *resp, *data, *typedata; 4659bb7e0f2SJohannes Berg 466ae0be8deSMichal Kubecek pmsr = nla_nest_start_noflag(msg, NL80211_ATTR_PEER_MEASUREMENTS); 4679bb7e0f2SJohannes Berg if (!pmsr) 4689bb7e0f2SJohannes Berg goto error; 4699bb7e0f2SJohannes Berg 470ae0be8deSMichal Kubecek peers = nla_nest_start_noflag(msg, NL80211_PMSR_ATTR_PEERS); 4719bb7e0f2SJohannes Berg if (!peers) 4729bb7e0f2SJohannes Berg goto error; 4739bb7e0f2SJohannes Berg 474ae0be8deSMichal Kubecek peer = nla_nest_start_noflag(msg, 1); 4759bb7e0f2SJohannes Berg if (!peer) 4769bb7e0f2SJohannes Berg goto error; 4779bb7e0f2SJohannes Berg 4789bb7e0f2SJohannes Berg if (nla_put(msg, NL80211_PMSR_PEER_ATTR_ADDR, ETH_ALEN, res->addr)) 4799bb7e0f2SJohannes Berg goto error; 4809bb7e0f2SJohannes Berg 481ae0be8deSMichal Kubecek resp = nla_nest_start_noflag(msg, NL80211_PMSR_PEER_ATTR_RESP); 4829bb7e0f2SJohannes Berg if (!resp) 4839bb7e0f2SJohannes Berg goto error; 4849bb7e0f2SJohannes Berg 4859bb7e0f2SJohannes Berg if (nla_put_u32(msg, NL80211_PMSR_RESP_ATTR_STATUS, res->status) || 4869bb7e0f2SJohannes Berg nla_put_u64_64bit(msg, NL80211_PMSR_RESP_ATTR_HOST_TIME, 4879bb7e0f2SJohannes Berg res->host_time, NL80211_PMSR_RESP_ATTR_PAD)) 4889bb7e0f2SJohannes Berg goto error; 4899bb7e0f2SJohannes Berg 4909bb7e0f2SJohannes Berg if (res->ap_tsf_valid && 4919bb7e0f2SJohannes Berg nla_put_u64_64bit(msg, NL80211_PMSR_RESP_ATTR_AP_TSF, 492b6584202SAvraham Stern res->ap_tsf, NL80211_PMSR_RESP_ATTR_PAD)) 4939bb7e0f2SJohannes Berg goto error; 4949bb7e0f2SJohannes Berg 4959bb7e0f2SJohannes Berg if (res->final && nla_put_flag(msg, NL80211_PMSR_RESP_ATTR_FINAL)) 4969bb7e0f2SJohannes Berg goto error; 4979bb7e0f2SJohannes Berg 498ae0be8deSMichal Kubecek data = nla_nest_start_noflag(msg, NL80211_PMSR_RESP_ATTR_DATA); 4999bb7e0f2SJohannes Berg if (!data) 5009bb7e0f2SJohannes Berg goto error; 5019bb7e0f2SJohannes Berg 502ae0be8deSMichal Kubecek typedata = nla_nest_start_noflag(msg, res->type); 5039bb7e0f2SJohannes Berg if (!typedata) 5049bb7e0f2SJohannes Berg goto error; 5059bb7e0f2SJohannes Berg 5069bb7e0f2SJohannes Berg switch (res->type) { 5079bb7e0f2SJohannes Berg case NL80211_PMSR_TYPE_FTM: 5089bb7e0f2SJohannes Berg if (nl80211_pmsr_send_ftm_res(msg, res)) 5099bb7e0f2SJohannes Berg goto error; 5109bb7e0f2SJohannes Berg break; 5119bb7e0f2SJohannes Berg default: 5129bb7e0f2SJohannes Berg WARN_ON(1); 5139bb7e0f2SJohannes Berg } 5149bb7e0f2SJohannes Berg 5159bb7e0f2SJohannes Berg nla_nest_end(msg, typedata); 5169bb7e0f2SJohannes Berg nla_nest_end(msg, data); 5179bb7e0f2SJohannes Berg nla_nest_end(msg, resp); 5189bb7e0f2SJohannes Berg nla_nest_end(msg, peer); 5199bb7e0f2SJohannes Berg nla_nest_end(msg, peers); 5209bb7e0f2SJohannes Berg nla_nest_end(msg, pmsr); 5219bb7e0f2SJohannes Berg 5229bb7e0f2SJohannes Berg return 0; 5239bb7e0f2SJohannes Berg error: 5249bb7e0f2SJohannes Berg return -ENOSPC; 5259bb7e0f2SJohannes Berg } 5269bb7e0f2SJohannes Berg 5279bb7e0f2SJohannes Berg void cfg80211_pmsr_report(struct wireless_dev *wdev, 5289bb7e0f2SJohannes Berg struct cfg80211_pmsr_request *req, 5299bb7e0f2SJohannes Berg struct cfg80211_pmsr_result *result, 5309bb7e0f2SJohannes Berg gfp_t gfp) 5319bb7e0f2SJohannes Berg { 5329bb7e0f2SJohannes Berg struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy); 5339bb7e0f2SJohannes Berg struct sk_buff *msg; 5349bb7e0f2SJohannes Berg void *hdr; 5359bb7e0f2SJohannes Berg int err; 5369bb7e0f2SJohannes Berg 5379bb7e0f2SJohannes Berg trace_cfg80211_pmsr_report(wdev->wiphy, wdev, req->cookie, 5389bb7e0f2SJohannes Berg result->addr); 5399bb7e0f2SJohannes Berg 5409bb7e0f2SJohannes Berg /* 5419bb7e0f2SJohannes Berg * Currently, only variable items are LCI and civic location, 5429bb7e0f2SJohannes Berg * both of which are reasonably short so we don't need to 5439bb7e0f2SJohannes Berg * worry about them here for the allocation. 5449bb7e0f2SJohannes Berg */ 5459bb7e0f2SJohannes Berg msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp); 5469bb7e0f2SJohannes Berg if (!msg) 5479bb7e0f2SJohannes Berg return; 5489bb7e0f2SJohannes Berg 5499bb7e0f2SJohannes Berg hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_PEER_MEASUREMENT_RESULT); 5509bb7e0f2SJohannes Berg if (!hdr) 5519bb7e0f2SJohannes Berg goto free; 5529bb7e0f2SJohannes Berg 5539bb7e0f2SJohannes Berg if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) || 5549bb7e0f2SJohannes Berg nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev), 5559bb7e0f2SJohannes Berg NL80211_ATTR_PAD)) 5569bb7e0f2SJohannes Berg goto free; 5579bb7e0f2SJohannes Berg 5589bb7e0f2SJohannes Berg if (nla_put_u64_64bit(msg, NL80211_ATTR_COOKIE, req->cookie, 5599bb7e0f2SJohannes Berg NL80211_ATTR_PAD)) 5609bb7e0f2SJohannes Berg goto free; 5619bb7e0f2SJohannes Berg 5629bb7e0f2SJohannes Berg err = nl80211_pmsr_send_result(msg, result); 5639bb7e0f2SJohannes Berg if (err) { 5649bb7e0f2SJohannes Berg pr_err_ratelimited("peer measurement result: message didn't fit!"); 5659bb7e0f2SJohannes Berg goto free; 5669bb7e0f2SJohannes Berg } 5679bb7e0f2SJohannes Berg 5689bb7e0f2SJohannes Berg genlmsg_end(msg, hdr); 5699bb7e0f2SJohannes Berg genlmsg_unicast(wiphy_net(wdev->wiphy), msg, req->nl_portid); 5709bb7e0f2SJohannes Berg return; 5719bb7e0f2SJohannes Berg free: 5729bb7e0f2SJohannes Berg nlmsg_free(msg); 5739bb7e0f2SJohannes Berg } 5749bb7e0f2SJohannes Berg EXPORT_SYMBOL_GPL(cfg80211_pmsr_report); 5759bb7e0f2SJohannes Berg 57673350424SJohannes Berg static void cfg80211_pmsr_process_abort(struct wireless_dev *wdev) 5779bb7e0f2SJohannes Berg { 5789bb7e0f2SJohannes Berg struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy); 5799bb7e0f2SJohannes Berg struct cfg80211_pmsr_request *req, *tmp; 5809bb7e0f2SJohannes Berg LIST_HEAD(free_list); 5819bb7e0f2SJohannes Berg 58273350424SJohannes Berg lockdep_assert_held(&wdev->mtx); 58373350424SJohannes Berg 5849bb7e0f2SJohannes Berg spin_lock_bh(&wdev->pmsr_lock); 5859bb7e0f2SJohannes Berg list_for_each_entry_safe(req, tmp, &wdev->pmsr_list, list) { 5869bb7e0f2SJohannes Berg if (req->nl_portid) 5879bb7e0f2SJohannes Berg continue; 5889bb7e0f2SJohannes Berg list_move_tail(&req->list, &free_list); 5899bb7e0f2SJohannes Berg } 5909bb7e0f2SJohannes Berg spin_unlock_bh(&wdev->pmsr_lock); 5919bb7e0f2SJohannes Berg 5929bb7e0f2SJohannes Berg list_for_each_entry_safe(req, tmp, &free_list, list) { 5939bb7e0f2SJohannes Berg rdev_abort_pmsr(rdev, wdev, req); 5949bb7e0f2SJohannes Berg 5959bb7e0f2SJohannes Berg kfree(req); 5969bb7e0f2SJohannes Berg } 5979bb7e0f2SJohannes Berg } 5989bb7e0f2SJohannes Berg 59973350424SJohannes Berg void cfg80211_pmsr_free_wk(struct work_struct *work) 60073350424SJohannes Berg { 60173350424SJohannes Berg struct wireless_dev *wdev = container_of(work, struct wireless_dev, 60273350424SJohannes Berg pmsr_free_wk); 60373350424SJohannes Berg 60473350424SJohannes Berg wdev_lock(wdev); 60573350424SJohannes Berg cfg80211_pmsr_process_abort(wdev); 60673350424SJohannes Berg wdev_unlock(wdev); 60773350424SJohannes Berg } 60873350424SJohannes Berg 6099bb7e0f2SJohannes Berg void cfg80211_pmsr_wdev_down(struct wireless_dev *wdev) 6109bb7e0f2SJohannes Berg { 6119bb7e0f2SJohannes Berg struct cfg80211_pmsr_request *req; 6129bb7e0f2SJohannes Berg bool found = false; 6139bb7e0f2SJohannes Berg 6149bb7e0f2SJohannes Berg spin_lock_bh(&wdev->pmsr_lock); 6159bb7e0f2SJohannes Berg list_for_each_entry(req, &wdev->pmsr_list, list) { 6169bb7e0f2SJohannes Berg found = true; 6179bb7e0f2SJohannes Berg req->nl_portid = 0; 6189bb7e0f2SJohannes Berg } 6199bb7e0f2SJohannes Berg spin_unlock_bh(&wdev->pmsr_lock); 6209bb7e0f2SJohannes Berg 6219bb7e0f2SJohannes Berg if (found) 62273350424SJohannes Berg cfg80211_pmsr_process_abort(wdev); 62373350424SJohannes Berg 6249bb7e0f2SJohannes Berg WARN_ON(!list_empty(&wdev->pmsr_list)); 6259bb7e0f2SJohannes Berg } 6269bb7e0f2SJohannes Berg 6279bb7e0f2SJohannes Berg void cfg80211_release_pmsr(struct wireless_dev *wdev, u32 portid) 6289bb7e0f2SJohannes Berg { 6299bb7e0f2SJohannes Berg struct cfg80211_pmsr_request *req; 6309bb7e0f2SJohannes Berg 6319bb7e0f2SJohannes Berg spin_lock_bh(&wdev->pmsr_lock); 6329bb7e0f2SJohannes Berg list_for_each_entry(req, &wdev->pmsr_list, list) { 6339bb7e0f2SJohannes Berg if (req->nl_portid == portid) { 6349bb7e0f2SJohannes Berg req->nl_portid = 0; 6359bb7e0f2SJohannes Berg schedule_work(&wdev->pmsr_free_wk); 6369bb7e0f2SJohannes Berg } 6379bb7e0f2SJohannes Berg } 6389bb7e0f2SJohannes Berg spin_unlock_bh(&wdev->pmsr_lock); 6399bb7e0f2SJohannes Berg } 6409bb7e0f2SJohannes Berg 6419bb7e0f2SJohannes Berg #endif /* __PMSR_H */ 642