1 /*
2  * Copyright (c) 2012-2021 The Linux Foundation. All rights reserved.
3  * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
4  *
5  * Permission to use, copy, modify, and/or distribute this software for
6  * any purpose with or without fee is hereby granted, provided that the
7  * above copyright notice and this permission notice appear in all
8  * copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
11  * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
12  * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
13  * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
14  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
15  * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
16  * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17  * PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 /**
21  * DOC: wlan_hdd_bss_transition.c
22  *
23  * WLAN bss transition functions
24  *
25  */
26 
27 #include "osif_sync.h"
28 #include <wlan_hdd_includes.h>
29 #include <linux/netdevice.h>
30 #include <linux/skbuff.h>
31 #include <linux/etherdevice.h>
32 #include <linux/if_ether.h>
33 #include <wlan_hdd_bss_transition.h>
34 
35 /**
36  * wlan_hdd_is_bt_in_progress() - check if bt activity is in progress
37  * @hdd_ctx : HDD context
38  *
39  * Return: true if BT activity is in progress else false
40  */
wlan_hdd_is_bt_in_progress(struct hdd_context * hdd_ctx)41 static bool wlan_hdd_is_bt_in_progress(struct hdd_context *hdd_ctx)
42 {
43 	if (hdd_ctx->bt_a2dp_active || hdd_ctx->bt_vo_active)
44 		return true;
45 
46 	return false;
47 }
48 
49 /**
50  * wlan_hdd_fill_btm_resp() - Fill bss candidate response buffer
51  * @reply_skb : pointer to reply_skb
52  * @info : bss candidate information
53  * @index : attribute type index for nla_next_start()
54  *
55  * Return : 0 on success and errno on failure
56  */
wlan_hdd_fill_btm_resp(struct sk_buff * reply_skb,struct bss_candidate_info * info,int index)57 static int wlan_hdd_fill_btm_resp(struct sk_buff *reply_skb,
58 				  struct bss_candidate_info *info,
59 				  int index)
60 {
61 	struct nlattr *attr;
62 
63 	attr = nla_nest_start(reply_skb, index);
64 	if (!attr) {
65 		hdd_err("nla_nest_start failed");
66 		wlan_cfg80211_vendor_free_skb(reply_skb);
67 		return -EINVAL;
68 	}
69 
70 	if (nla_put(reply_skb,
71 		  QCA_WLAN_VENDOR_ATTR_BTM_CANDIDATE_INFO_BSSID,
72 		  ETH_ALEN, info->bssid.bytes) ||
73 	    nla_put_u32(reply_skb,
74 		 QCA_WLAN_VENDOR_ATTR_BTM_CANDIDATE_INFO_STATUS,
75 		 info->status)) {
76 		hdd_err("nla_put failed");
77 		wlan_cfg80211_vendor_free_skb(reply_skb);
78 		return -EINVAL;
79 	}
80 
81 	nla_nest_end(reply_skb, attr);
82 
83 	return 0;
84 }
85 
86 const struct nla_policy
87 btm_params_policy[QCA_WLAN_VENDOR_ATTR_MAX + 1] = {
88 	[QCA_WLAN_VENDOR_ATTR_BTM_MBO_TRANSITION_REASON] = {
89 						.type = NLA_U8},
90 	[QCA_WLAN_VENDOR_ATTR_BTM_CANDIDATE_INFO] =
91 			VENDOR_NLA_POLICY_NESTED(btm_cand_list_policy),
92 };
93 
94 const struct nla_policy
95 btm_cand_list_policy[QCA_WLAN_VENDOR_ATTR_BTM_CANDIDATE_INFO_MAX + 1]
96 	= {[QCA_WLAN_VENDOR_ATTR_BTM_CANDIDATE_INFO_BSSID] = {
97 					.len = QDF_MAC_ADDR_SIZE},
98 	   [QCA_WLAN_VENDOR_ATTR_BTM_CANDIDATE_INFO_STATUS] = {
99 						.type = NLA_U32},
100 };
101 
102 /**
103  * __wlan_hdd_cfg80211_fetch_bss_transition_status() - fetch bss transition
104  * status
105  * @wiphy : WIPHY structure pointer
106  * @wdev : Wireless device structure pointer
107  * @data : Pointer to the data received
108  * @data_len : Length of the data received
109  *
110  * This function is used to fetch transition status for candidate bss. The
111  * transition status is either accept or reason for reject.
112  *
113  * Return : 0 on success and errno on failure
114  */
115 static int
__wlan_hdd_cfg80211_fetch_bss_transition_status(struct wiphy * wiphy,struct wireless_dev * wdev,const void * data,int data_len)116 __wlan_hdd_cfg80211_fetch_bss_transition_status(struct wiphy *wiphy,
117 						struct wireless_dev *wdev,
118 						const void *data, int data_len)
119 {
120 	struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_MAX + 1];
121 	struct nlattr *tb_msg[QCA_WLAN_VENDOR_ATTR_BTM_CANDIDATE_INFO_MAX + 1];
122 	uint8_t transition_reason;
123 	struct nlattr *attr;
124 	struct sk_buff *skb;
125 	int rem, j;
126 	int ret;
127 	bool is_bt_in_progress;
128 	struct bss_candidate_info candidate_info[MAX_CANDIDATE_INFO];
129 	uint16_t nof_candidates, i = 0;
130 	struct hdd_context *hdd_ctx = wiphy_priv(wiphy);
131 	struct net_device *dev = wdev->netdev;
132 	struct hdd_adapter *adapter = WLAN_HDD_GET_PRIV_PTR(dev);
133 	struct hdd_station_ctx *hdd_sta_ctx;
134 	mac_handle_t mac_handle;
135 	QDF_STATUS status;
136 
137 	hdd_enter();
138 
139 	if (hdd_get_conparam() == QDF_GLOBAL_FTM_MODE) {
140 		hdd_err("Command not allowed in FTM mode");
141 		return -EINVAL;
142 	}
143 
144 	ret = wlan_hdd_validate_context(hdd_ctx);
145 	if (ret)
146 		return ret;
147 
148 	hdd_sta_ctx = WLAN_HDD_GET_STATION_CTX_PTR(adapter->deflink);
149 	if (adapter->device_mode != QDF_STA_MODE ||
150 	    !hdd_cm_is_vdev_associated(adapter->deflink)) {
151 		hdd_err("Command is either not invoked for STA mode (device mode: %d) or STA is not associated (Connection state: %d)",
152 			adapter->device_mode, hdd_sta_ctx->conn_info.conn_state);
153 		return -EINVAL;
154 	}
155 
156 	ret = wlan_cfg80211_nla_parse(tb, QCA_WLAN_VENDOR_ATTR_MAX, data,
157 				      data_len, btm_params_policy);
158 	if (ret) {
159 		hdd_err("Attribute parse failed");
160 		return -EINVAL;
161 	}
162 
163 	if (!tb[QCA_WLAN_VENDOR_ATTR_BTM_MBO_TRANSITION_REASON] ||
164 	    !tb[QCA_WLAN_VENDOR_ATTR_BTM_CANDIDATE_INFO]) {
165 		hdd_err("Missing attributes");
166 		return -EINVAL;
167 	}
168 
169 	transition_reason = nla_get_u8(
170 			    tb[QCA_WLAN_VENDOR_ATTR_BTM_MBO_TRANSITION_REASON]);
171 
172 	nla_for_each_nested(attr,
173 			    tb[QCA_WLAN_VENDOR_ATTR_BTM_CANDIDATE_INFO],
174 			    rem) {
175 		ret = wlan_cfg80211_nla_parse_nested(tb_msg,
176 				    QCA_WLAN_VENDOR_ATTR_BTM_CANDIDATE_INFO_MAX,
177 				    attr, btm_cand_list_policy);
178 		if (ret) {
179 			hdd_err("Attribute parse failed");
180 			return -EINVAL;
181 		}
182 
183 		if (!tb_msg[QCA_WLAN_VENDOR_ATTR_BTM_CANDIDATE_INFO_BSSID]) {
184 			hdd_err("Missing BSSID attribute");
185 			return -EINVAL;
186 		}
187 
188 		qdf_mem_copy((void *)candidate_info[i].bssid.bytes,
189 			     nla_data(tb_msg[
190 			     QCA_WLAN_VENDOR_ATTR_BTM_CANDIDATE_INFO_BSSID]),
191 			     QDF_MAC_ADDR_SIZE);
192 		i++;
193 		if (i == MAX_CANDIDATE_INFO)
194 			break;
195 	}
196 
197 	/*
198 	 * Determine status for each candidate and fill in the status field.
199 	 * Also arrange the candidates in the order of preference.
200 	 */
201 	nof_candidates = i;
202 
203 	is_bt_in_progress = wlan_hdd_is_bt_in_progress(hdd_ctx);
204 
205 	mac_handle = hdd_ctx->mac_handle;
206 	status = sme_get_bss_transition_status(mac_handle, transition_reason,
207 					       &hdd_sta_ctx->conn_info.bssid,
208 					       candidate_info,
209 					       nof_candidates,
210 					       is_bt_in_progress);
211 	if (QDF_IS_STATUS_ERROR(status))
212 		return -EINVAL;
213 
214 	/* Prepare the reply and send it to userspace */
215 	skb = wlan_cfg80211_vendor_cmd_alloc_reply_skb(wiphy,
216 						       (QDF_MAC_ADDR_SIZE +
217 							sizeof(uint32_t)) *
218 						       nof_candidates +
219 						       NLMSG_HDRLEN);
220 	if (!skb) {
221 		hdd_err("reply buffer alloc failed");
222 		return -ENOMEM;
223 	}
224 
225 	attr = nla_nest_start(skb,
226 			      QCA_WLAN_VENDOR_ATTR_BTM_CANDIDATE_INFO);
227 	if (!attr) {
228 		hdd_err("nla_nest_start failed");
229 		wlan_cfg80211_vendor_free_skb(skb);
230 		return -EINVAL;
231 	}
232 
233 	/*
234 	 * Order candidates as - accepted candidate list followed by rejected
235 	 * candidate list
236 	 */
237 	for (i = 0, j = 0; i < nof_candidates; i++) {
238 		/* copy accepted candidate list */
239 		if (candidate_info[i].status == QCA_STATUS_ACCEPT) {
240 			if (wlan_hdd_fill_btm_resp(skb,
241 						   &candidate_info[i], j))
242 				return -EINVAL;
243 			j++;
244 		}
245 	}
246 	for (i = 0; i < nof_candidates; i++) {
247 		/* copy rejected candidate list */
248 		if (candidate_info[i].status != QCA_STATUS_ACCEPT) {
249 			if (wlan_hdd_fill_btm_resp(skb,
250 						   &candidate_info[i], j))
251 				return -EINVAL;
252 			j++;
253 		}
254 	}
255 	nla_nest_end(skb, attr);
256 
257 	hdd_exit();
258 
259 	return wlan_cfg80211_vendor_cmd_reply(skb);
260 }
261 
wlan_hdd_cfg80211_fetch_bss_transition_status(struct wiphy * wiphy,struct wireless_dev * wdev,const void * data,int data_len)262 int wlan_hdd_cfg80211_fetch_bss_transition_status(struct wiphy *wiphy,
263 						  struct wireless_dev *wdev,
264 						  const void *data,
265 						  int data_len)
266 {
267 	struct osif_vdev_sync *vdev_sync;
268 	int errno;
269 
270 	errno = osif_vdev_sync_op_start(wdev->netdev, &vdev_sync);
271 	if (errno)
272 		return errno;
273 
274 	errno = __wlan_hdd_cfg80211_fetch_bss_transition_status(wiphy, wdev,
275 								data, data_len);
276 
277 	osif_vdev_sync_op_stop(vdev_sync);
278 
279 	return errno;
280 }
281 
282