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